Skip to content

Instantly share code, notes, and snippets.

@friedbrice
Last active October 31, 2017 15:43
Show Gist options
  • Save friedbrice/76a38a6599ae7a83765457c1c74a4b4b to your computer and use it in GitHub Desktop.
Save friedbrice/76a38a6599ae7a83765457c1c74a4b4b to your computer and use it in GitHub Desktop.
My Attempt at "Refactor for Maintainability" by S. Shubin (https://www.youtube.com/watch?v=b8EB31voC9U)
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.{Path, Paths}
import java.time.{Duration, Instant}
object Lib {
def app(
filename: String,
getTime: Unit => Instant,
readFile: Path => Array[Byte],
emit: String => Unit
): Unit = {
val startTime = getTime(())
val target = new String(readFile(Paths.get(filename)), UTF_8)
emit(s"Hello, $target")
val duration = Duration.between(startTime, getTime(()))
emit(s"${duration.toMillis} milliseconds")
}
}
object Main extends App {
Lib.app(
filename = args.head,
getTime = _ => java.time.Clock.systemUTC.instant,
readFile = java.nio.file.Files.readAllBytes,
emit = println
)
}
object TestLib {
import scala.util.{Try, Success, Failure}
implicit class WithAction[A](val self: A) extends AnyVal {
def withAction(f: A => Unit): A = { f(self); self }
}
implicit class ArgRes[A,B](val self: (A, B)) extends AnyVal {
def arg: A = self._1
def res: B = self._2
}
implicit class AssertMessage(val message: String) extends AnyVal {
def ie(assertion: Boolean): Unit = assert(assertion, message)
}
sealed trait WithHistory[A, B] extends (A => B) { def history: List[(A, B)] }
object WithHistory {
def apply[A, B](f: A => B): WithHistory[A, B] = {
var calls: List[(A, B)] = Nil
new WithHistory[A, B] {
def history: List[(A, B)] = calls.reverse
def apply(a: A): B = f(a) withAction { b => calls = (a, b)::calls }
}
}
}
def rigged[A, B](pairs: (A, List[B])*): A => B = {
var _pairs: Map[A, List[B]] = pairs.toMap
(a: A) => {
val next::rest = _pairs(a)
next withAction { _ => _pairs = _pairs.updated(a, rest) }
}
}
def test(message: String)(actions: => Unit): Unit = Try(actions) match {
case Success(_) => println(s"Passed: $message")
case Failure(err) => println { err.getStackTrace.take(10)
.mkString(s"Failed: $message\n$err\n\tat ", "\n\tat ", "\n\t...")
}
}
}
object Test extends App {
import TestLib._
import scala.util.Random.shuffle
test("application behavior should be correct") {
// given
val filename = "foo"
val start = shuffle(1 to 100).head.toLong
val end = start + shuffle(1 to 100).head.toLong
val clock = WithHistory { rigged( () ->
List(start, end).map(Instant.ofEpochMilli)
)}
val filesys = WithHistory { (path: Path) =>
path.toString match { case "foo" => "bar".getBytes }
}
val printer = WithHistory { (str: String) => () }
// when
Lib.app(filename, clock, filesys, printer)
// then
"app should call getTime twice" ie {
clock.history.length == 2
}
"app should call readFile once" ie {
filesys.history.length == 1
}
"app should call readFile with filename" ie {
filesys.history(0).arg == Paths.get("foo")
}
"app should call emit twice" ie {
printer.history.length == 2
}
"first emit should use the result of readFile" ie {
printer.history(0).arg == "Hello, bar"
}
"second emit should use difference of results of getTime" ie {
printer.history(1).arg == s"${end - start} milliseconds"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment