We’ve been constantly improving our Scala Analyzer since its beta version was released 3 months ago. In this update, we’ve added new detection capabilities and improved the overall performance of our Scala Analyzer. Keep reading!
P.S Want to know more about getting started with our Scala Analyzer? All the details are here.
So, what’s new?
We identified and made some crucial improvements to the Analyzer. Result: Analysis runs are now up to 30% faster! We’ll be publishing a detailed blog on this soon.
We’ve also upgraded the detection capabilities and added plenty of new checks. Our Scala Analyzer now has the most number of rules than any other static analysis tool out there!
Here are just a few of the scenarios that we now detect –
SC-W1033 - Potential file handle leak
Calling getLines
or mkString
directly over fromFile
, fromURI
, or fromURL
leaks the underneath opened handle to file.
// Resource leak
val input =
Input.VirtualFile(
pathToFile,
Source.fromFile(pathToFile).mkString
)
// Manually closing the file yourself
val fileSource = Source.fromFile("file.txt")
val fileContents = fileSource.mkString
fileSource.close()
// Using the automatic resource management
Using(Source.fromFile("file.txt")) { reader =>
val fileContents = reader.mkString
// process the `fileContents`
}
// Handling multiple resources
Using.Manager { manage =>
val r1 = manage(new BufferedReader(new FileReader("foo.txt")))
val r2 = manage(new BufferedReader(new FileReader("bar.txt")))
// ...
}
SC-R1009 - Replace find() ==/!= None
with exists()
find()
allows you to check for elements that satisfy the defined condition. However, find() ==/!= None
can be effectively replaced with exists()
.
// Not recommended
if (nums.find(x => x % 5 == 0 && x >= 10) != None) {
// ...
}
// Recommended
if (nums.exists(x => x % 5 == 0 && x >= 10)) {
// ...
}
SC-W1035 - Use Scala’s deprecated
annotation rather than Java’s
Using Java’s annotation may or may not trigger the deprecated warning correctly.
// incorrect - may not necessarily trigger
@Deprecated(...)
def foo(): Unit = {
//
}
// Preferred way
@deprecated(...)
def foo(): Unit = {
//
}
SC-R1000 - Merge detached if conditions
The following if conditions can be merged as – if (x > 0 && y > 0)
if (x > 0) {
if (y > 0) {
//
}
}
SC-R1011 - Consider grouping imports
import scala.collection.immutable.HashSet
import scala.collection.immutable.HashMap
The above imports can be written as –
import scala.collection.immutable.{HashSet, HashMap}
Literal arguments to methods
Passing string literals or boolean parameters to methods may or may not convey the required meaning. Therefore, it is generally suggested that you use named parameters in such scenarios.
// From: https://docs.scala-lang.org/tour/named-arguments.html
// Incorrect way
writeToStream(data, true)
// Preferred way
writeToStream(data, flush=true)
SC-R1006 - Calling head/last
over filter
filter
allows you to filter and select those elements that satisfy your condition. However, calling head
or last
immediately on the results may not be a wise idea. Consider the following example:
val firstElement = nums.filter(...).head
The above code throws an exception if there’s no element that satisfies the said condition. A better approach to this situation is to use headOption
that returns Option[T]
.
val firstElement = nums.filter(...).headOption
// Alternately, the analyzer can also suggest the following rewrite
val firstElement = nums.find(...)
SC-W1051 - Using .deep
to compare Array
s is deprecated
Comparing 2 Array
s using .deep
is deprecated and is not supported beyond Scala version 2.12.
val a = Array(1, 2, 3)
val b = Array(1, 2)
a.deep == b.deep // Deprecated
a.sameElements(b) // Relying on native Scala approach
java.util.Arrays.equals(a, b) // Relying on Java's methods
SC-W1052 - Use .isNaN
to check if a Double
is NaN
Comparison operators such as ==
or !=
do not work when checking against NaN
. The preferred way is to use the .isNaN
method.
val d = Double.NaN
d == Double.NaN // evaluates to false
d.isNaN // evaluates to true
SC-W1054 - Calling .get
on Try()
throws IllegalArgumentException
on Failure
Try()
returns either Success
or Failure
depending on whether the specified operation was a success or not. Therefore, calling .get
over Try()
risks IllegalArgumentException
.
// Not recommended
val returnValue = Try(someMethod()).get
// Recommended
Try(someMethod()) match {
case Success(value) =>
case Failure(fail) =>
}
SC-P1006 - Consider rewriting filter().headOption
as find()
While .find
behaves in the exact same manner as .filter().headOption
, it performs slightly better as it terminates after finding the first element that satisfies your condition while .filter
continues to iterate through the entire collection.
// Not recommended
val firstEven = nums.filter(x => x % 2 == 0).headOption
// Recommended
val firstEven = nums.find(x => x % 2 == 0)
SC-R1025 - Consider using .isEmpty
or .nonEmpty
instead of .size
for List
s
Methods such as .size
have a complexity of O(n) for Lists
. Repeatedly calling such methods can impact the performance of your application. Therefore, it is suggested that you use methods such as .isEmpty
or .nonEmpty
to check if a list is empty or not rather than relying on .size
// Not recommended
def processElements(elements: List[Int]): Unit = {
if (elements.size != 0) {
//
}
}
// Recommended
def processElements(elements: List[Int]): Unit = {
if (elements.nonEmpty) {
//
}
}
Enhancements and fine tuning
We’ve identified a scenario where the SC-W1024 - NonExhaustiveMatch
check was being triggered unnecessarily –
foo match {
case true =>
case false =>
}
Since both the possible cases are handled, the specified check should not have been invoked. A patch has been rolled out to fix this issue.