Inspections
Most debugging starts off with “printf debugging”, where println
and logger.debug
statements are thrown into the code to see the internal state of an object or variable. Inserting statements into the code base for exploratory debugging is by far the most popular means of debugging, and most developers agree that the best invention in debugging was printf debugging.
Given that we already know that a good part of debugging involves inspecting internal state, we can leverage Scala’s macro system to do the work for us.
This code has not been rigorously tested in the field and should be considered experimental. Please file bugs and PRs if you run into problems or edge cases.
Installation
This library is in blindsight-inspection
and does not depend on the rest of Blindsight:
- sbt
libraryDependencies += "com.tersesystems.blindsight" %% "blindsight-inspection" % "1.5.2"
- Maven
<properties> <scala.binary.version>2.13</scala.binary.version> </properties> <dependencies> <dependency> <groupId>com.tersesystems.blindsight</groupId> <artifactId>blindsight-inspection_${scala.binary.version}</artifactId> <version>1.5.2</version> </dependency> </dependencies>
- Gradle
def versions = [ ScalaBinary: "2.13" ] dependencies { implementation "com.tersesystems.blindsight:blindsight-inspection_${versions.ScalaBinary}:1.5.2" }
Usage
You can import the methods using an import, or by incorporating the methods in a trait.
import com.tersesystems.blindsight.inspection.InspectionMacros._
Decorate Ifs
This inspection decorates an if
statement with logging code on every branch:
decorateIfs(dif => logger.debug(s"${dif.code} = ${dif.result}")) {
if (System.currentTimeMillis() % 17 == 0) {
println("branch 1")
} else if (System.getProperty("derp") == null) {
println("branch 2")
} else {
println("else branch")
}
}
Decorate Match
This inspection decorates a match
statement with logging code on every case
statement:
val string = java.time.Instant.now().toString
decorateMatch(dm => logger.debug(s"${dm.code} = ${dm.result}")) {
string match {
case s if s.startsWith("20") =>
println("this example is still valid")
case _ =>
println("oh dear")
}
}
Decorate Vals
This inspection decorates a block so that every val
or var
statement has a logging statement after its definition:
decorateVals(dval => logger.debug(s"${dval.name} = ${dval.value}")) {
val a = 5
val b = 15
a + b
}
Dump Expression
This inspection dumps the code of an expression together with the result:
val dr = dumpExpression(1 + 1)
logger.debug(s"result: ${dr.code} = ${dr.value}")
Dump Public Fields
This inspection dumps the public fields of an object:
class ExampleClass(val someInt: Int) {
protected val protectedInt = 22
}
val exObj = new ExampleClass(42)
val publicFields = dumpPublicFields(exObj)
logger.debug(s"We set exObj.someInt as 42, actual value is $publicFields")