Architecture
Scala: Concise, Clean Code for Humans

Let me ask you a simple question: which do you think is a more natural way of thinking?

  1. I am going to go home and take a nap.
  2. My present location is “office,” and my state of wakefulness is “awake.” I am going to change my location to “home” and then change my state of wakefulness to “asleep.”

The answer is probably a unanimous and resounding “the first one!” But when we write code, it is almost always an example of the second one.

Here at Rubrik, while expanding the frontiers of Cloud Data Management, we are also passionate about the psychology of programming and shortening the learning curve for new developers. So, we look for innovative methods to reduce the cognitive load that programmers deal with. That’s where Scala’s magic shines!

In this post, I am going to walk you through how we leverage Scala’s expressiveness to write cleaner, leaner, and more meaningful code. For this example, we’ll write a simple simulation for modeling backup operations and how they consume space.

For starters, let’s simulate, using a toy program, what happens to occupied storage space when we take a snapshot:

object Rubrik {
  private var _storage = 0
  def getStorage() = _storage
  def backup() {
    _storage += 1  // storage is used when we back up
  }
}

No code is done without unit tests, right? Let’s get down to a simple test then! All we want to test is that our toy program actually simulates more storage space being used after a backup operation:

val oldStorage = Rubrik.getStorage()
Rubrik.backup()
val newStorage = Rubrik.getStorage()
newStorage should be > oldStorage

At first glance this test looks just fine. But remember the question at the beginning of this article? Think again! The variables oldStorage and newStorage look conspicuously like the “variables” location and state of wakefulness. They don’t really deserve an existence of their own. They exist merely to express higher-level concepts: going home to take a nap and increase in storage.

Not convinced yet? It gets uglier when we need to allocate multiple of these variables:

val oldStorage = Rubrik.getStorage()
Rubrik.backup()
val newStorage = Rubrik.getStorage()
newStorage should be > oldStorage

val oldStorage1 = Rubrik.getStorage()
Rubrik.archive()
val newStorage1 = Rubrik.getStorage()
newStorage should be < oldStorage

Now, not only do we have variable clutter, but we have numbered variable clutter. Oh yeah, by the way, did you spot the bug in the test above? There’s a copy-paste error. The last line should contain the variables newStorage1 and oldStorage1— not newStorage and oldStorage.

But what if I told you these problems don’t exist with Scala? Here’s the test, written with the testing constructs we developed here at Rubrik, leveraging Scala’s extensibility:

valueOf { Rubrik.getStorage() } must increase after { Rubrik.backup() }
valueOf { Rubrik.getStorage() } must decrease after { Rubrik.archive() }

In addition to providing us the ability to come up with very natural-looking syntactic constructs, Scala delivers a number of benefits that allow for cleaner code. First of all, it runs on the JVM and works with most Java libraries. It also has a rich core collection library of efficiently-implemented immutable data structures.

The object-oriented language is more compact and readable than Java, and it has functional types built in. For instance, declaring a variable as final is just as concise as declaring it to be mutable. This also means fewer lines of code to write and review!

Scala offers a variety of features that developers love, such as a case class with automatic pattern matching support in which you can extract data in a more declarative manner. Check out this example to see the contrast:

// Without pattern matching
if (snapshot.isInstaceOf[VmwareSnapshot]) {
  val vmwareSnapshot = snapshot.asInstanceOf[VmwareSnapshot]
  return vmwareSnapshot.vcenterId
} else if (snapshot.isInstanceOf[HyperVSnapshot]) {
  val hypervSnapshot = snapshot.asInstanceOf[HyperVSnapshot]
  return hypervSnapshot.serverId
}

// With pattern matching
snapshot match {
  case VmwareSnapshot(_, vcenterId, _, _) => vcenterId
  case HyperVSnapshot(_, _, _, serverId) => serverId
}

Also, Scala’s known for supporting mixins so that you can enjoy the perks of multiple inheritance without a ton of rewriting.

Coming back to our example: So, what does this all mean? With Scala, when you want to go home and take a nap, that’s what you say! No clutter of auxiliary variables. No unfortunate copy-paste bugs!

Check out a bare-bones implementation of these constructs below if you’re curious about how we did it:

def valueOf[R](guardStatement: => R): GuardStatement[R] = {
  new GuardStatement(guardStatement)
}

type Checker[T] = (T, T) => Unit

def increase[T <% Ordered[T]]: Checker[T] = (oldVal, newVal) => {
  newVal should be > oldVal
}

class GuardStatement[R](guardStatement: => R) {
  def must(checker: Checker[R]): GuardedBlock[R] = {
    new GuardedBlock[R] {
      override def executeGuardStatement() = guardStatement
      override def guardChecker(before: R, after: R) = {
        checker(before, after)
      }
    }
  }
}

trait GuardedBlock[R] {
  def executeGuardStatement(): R
  def guardChecker(before: R, after: R): Unit
  final def after[G](guardedBlock: => G): G = {
    val oldVal = executeGuardStatement()
    try {
      guardedBlock } finally {
      val newVal = executeGuardStatement()
      guardChecker(before = oldVal, after = newVal)
    }
  }
}

While Scala isn’t a perfect fit for all engineering teams, we at Rubrik leverage it for its concise syntax, baked-in functions, and scalability. I suggest you give it a try!

Want to learn more about Rubrik engineering? Check out our blog on erasure coding.



Close search icon

Contact Sales