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.

Note

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")
The source code for this page can be found here.