Toward a Safer Scala
Detect errors earlier with �static analysis
Gallatin Peak
Thanks,
Thomas!
Gallatin Peak
@leifwickland
tinyurl.com/
pnwslint
Why Static Analysis?
What You Deserve
What You Deserve
What You Deserve
What You Deserve
Static Analysis Options
IDE-based Options
If not in release build process, it doesn’t exist.
If you build your releases with your IDE, I’m very sorry for you.
Criteria
???
Reference uninitialized value
Good: Scala 2.11 warns
Bad: Only warns on change or clean
Ugly: Can still ship it
Seth Tisue says:
I agree with Seth.�Is your project well-managed?
scalac switches
Recommendation: Enable fatal warnings
scalacOptions ++= Seq(
"-Xfatal-warnings"
)
Set(1).equals()
Adapted Arguments
Set(1).equals()
Compiles�Always false
List(1,2,3).toSet()
Adapted Arguments
List(1,2,3).toSet()
Compiles�Always false
Adapted Arguments
Good: Deprecated around 2.10
Bad: Deprecation is just a warning
Adapted Arguments
Ugly: Compiler reports�"re-run with -deprecation for details"
Uglier: Enabling -deprecation and -Xfatal-warnings means you can’t use deprecated dependencies as stopgap
scalac switches
Recommend: Strive to enable -deprecation
Caveat: Hassle when upgrading dependencies.
scalacOptions ++= Seq(
"-deprecation",
"-Xfatal-warnings"
)
logger.error(
"Failed to open %s. Error: %d"� .format(file)�)
Wrong number of args to format()
logger.error(
"Failed to open %s. Error: %d"� .format(file)�)
Runtime Exception
logger.error(
"Failed to open $file." + � "Error: $IoError"�)
Oh, you wanted to interpolate that?
logger.error(
"Failed to open $file." + � "Error: $IoError"�)
Mo’ money. Mo’ problems.
String Interpolation With -Xlint
logger.error(
"Failed to open $file." + � "Error: $IoError"�)
Missing interpolator
scalac switches
Recommendation: Enable -Xlint
Bonus: 2.11.4 allows selective -Xlint enabling
scalacOptions ++= Seq(
"-Xlint”,
"-deprecation",
"-Xfatal-warnings"
)
sealed trait ADT
object A extends ADT
object B extends ADT
final val C = 3
val adt = Seq(A,B,C)
sealed trait ADT
object A extends ADT
object B extends ADT
final val C = 3
val adt = Seq(A,B,C)
Inferred Seq[Any]
final val As = Seq(1, 2)
final val Bs = Seq("3", "4")
val crossProduct = As.flatMap { a =>
Bs.flatMap { b => Seq(a, b) }
}
final val As = Seq(1, 2)
final val Bs = Seq("3", "4")
val crossProduct = As.flatMap { a =>
Bs.flatMap { b => Seq(a, b) }
}
Inferred Seq[Any]
Inferring Any With -Xlint
You’re right, scalac! Thank you!
A type was inferred to be `Any`; this may indicate a programming error.
scalac switches
Recommendation: No seriously enable -Xlint
scalacOptions ++= Seq(
"-Xlint”,
"-deprecation",
"-Xfatal-warnings"
)
scalac switches
Signal/Noise
Suppression
Learning Curve
FindBugs
FindBugs
?
?
Signal/Noise
Suppression
Learning Curve
Scalastyle
Incrementally configured via XML
<scalastyle>
<check level="error" class="NullChecker" enabled="false">
<check level="error" class="FileLengthChecker" enabled="true">
<parameters>
<parameter name="maxFileLength"><![CDATA[1500]]></parameter>
</parameters>
</check>
</scalastyle>
Incrementally configured via XML
<scalastyle>
<check level="error" class="NullChecker" enabled="true">
<check level="error" class="FileLengthChecker" enabled="true">
<parameters>
<parameter name="maxFileLength"><![CDATA[1500]]></parameter>
</parameters>
</check>
</scalastyle>
Incrementally configured via XML
<scalastyle>
<check level="error" class="NullChecker" enabled="true">
<check level="error" class="FileLengthChecker" enabled="true">
<parameters>
<parameter name="maxFileLength"><![CDATA[750]]></parameter>
</parameters>
</check>
</scalastyle>
Suppress via comments
Consistent with Rule 0.1
// scalastyle:off null
TerribleJavaApi.madeMe(null)
// scalastyle:on null
Suppress via comments
Bad.api(null) // scalastyle:ignore null
Executable coding standard
Ban “Better Java”
Encourage Good Practices
Scalastyle Extensibility
Scalastyle
Scalastyle
Signal/Noise
Suppression
Learning Curve
If your coding standard isn’t automatically and uniformly applied, you don’t have a coding standard.
Abide
Rules have mainstream flavor
Abide
?
Signal/Noise
Suppression
Learning Curve
WartRemover
val testUsers = Seq(
("Flintstone, Fred", 42),
("Rubble", "Betty", 35)
)
val testUsers = Seq(
("Flintstone, Fred", 42),
("Rubble", "Betty", 35)
)
Inferred Seq[Product]
val testUsers = Seq(
("Flintstone, Fred", 42),
("Rubble", "Betty", 35)
)
Inferred Seq[Product with Serializable]
Avoid Problematic Inference
Ban “Better Java”
Partial Methods which throw
wartremoverErrors ++= Warts.all
wartremoverErrors ++= Seq(
Wart.Any,
Wart.Nothing,
Wart.Serializable,
Wart.Product
)
wartremoverExcluded ++= Seq(
"com.bad.package",
"com.good.DirtyClass"
)
WartRemover
Signal/Noise
Suppression
Learning Curve
WartRemover
def f(s: String) = {
s.split(':') match {
case Array(k,v) => k -> v
case a => error("bad: " + a)
}
}
def f(s: String) = {
s.split(':') match {
case Array(k,v) => k -> v
case a => error("bad: " + a)
}
}
Array.toString()�bad: [LString;@6f89341e
def g(l: List) = {
if (l.length == 0) 0
else l.reduce(...)
}
def g(l: List) = {
if (l.length == 0) 0
else l.reduce(...)
}
List.length is O(n)
Use isEmpty()
Linter
Implements 50% more rules than Scalastyle
Linter found
Linter Also Found
Linter
Linter
Signal/Noise
Suppression
Learning Curve
seq.find(_ > 42).isDefined
seq.find(_ > 42).isDefined
seq.exists(_ > 42)
seq.exists(_ == "the one")
seq.exists(_ == "the one")
seq.contains("the one")
Scapegoat
WartRemover �++ �Linter
++�Scalastyle
.find(...).isDefined -> .exists(...)
Lonely sealed class
Match instead of partial function
Duplicated import
.length == 0
var could be a val
Unused method parameter
Use .contains(y) instead of� .exists(_ == y)
What I dislike
Scapegoat
Signal/Noise
Suppression
Learning Curve
S/N
Sup
LC
?
?
scalac switches
FindBugs
Scalastyle
Abide
WartRemover
Linter
Scapegoat
?
Greenfield
Protip: Skeleton Project
Existing Project
Start small; tighten screws
TL;DR
scalacOptions ++= Seq(
"-Xlint”,
"-deprecation",
"-Xfatal-warnings"
)
Questions?