Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Refactor all the things!

Refactor all the things!

Learning the syntax is just the first step towards mastering a new language. Idiomatic expressions and good practices must also be adopted to produce code that is readable and performant. Without guidance on its specific style, you can quickly develop habits that could cause your application to be difficult to reason about and maintain.

In this talk, we'll analyse snippets of code and highlight common Scala anti-patterns that make them difficult to understand. We'll also discuss how to refactor them to improve their readability. At the end of this session, you are going to be able to recognise code smells and refactor your code to make it easier to reason about and maintain, as well as avoid common pitfalls and possible bugs.

Daniela Sfregola

December 14, 2018
Tweet

More Decks by Daniela Sfregola

Other Decks in Programming

Transcript

  1. Hellooooo • Software Engineer 
 working with Scala • Ex

    Java Developer • London Scala User Group • Author of 
 "Get Programming with Scala" by Manning
  2. "Always code as if the guy who ends up maintaining

    your code will be a violent psychopath who knows where you live." by John F. Woods, 1991
  3. by Rory Graves, 2018 "The hardest problem in computer science

    is avoiding accidental complexity - complexity unrelated to the problem at hand"
  4. Before object Database { private def connectToDatabase(config: Config): Connection =

    ??? def connection: Connection = { val config: Config = ??? connectToDatabase(config) } }
  5. Before object Database { private def connectToDatabase(config: Config): Connection =

    ??? def connection: Connection = { val config: Config = ??? connectToDatabase(config) } }
  6. After object Database { private def connectToDatabase(config: Config): Connection =

    ??? val connection: Connection = { val config: Config = ??? connectToDatabase(config) } }
  7. Before class MyApi { def myEntryPoint(data: Data): OtherData = {

    // some stuff here anotherFunction(data) anotherHelpFunction(data) } def anotherFunction(data: Data) = ??? def anotherHelpFunction(data: Data) = ??? }
  8. Before class MyApi { def myEntryPoint(data: Data): OtherData = {

    // some stuff here anotherFunction(data) anotherHelpFunction(data) } def anotherFunction(data: Data) = ??? def anotherHelpFunction(data: Data) = ??? }
  9. After class MyApi { def myEntryPoint(data: Data): OtherData = {

    // some stuff here anotherFunction(data) anotherHelpFunction(data) } private def anotherFunction(data: Data) = ??? private def anotherHelpFunction(data: Data) = ??? }
  10. Take Away Use the most restrictive access modifier that makes

    your code work! Use private and protected whenever possible
  11. Before def myFunction(): Unit = { val data: Int =

    42 // do something here data }
  12. Before def myFunction(): Unit = { val data: Int =

    42 // do something here data }
  13. After def myFunction(): Int = { val data: Int =

    42 // do something here data }
  14. Take Away Enable compiler flags to detect 
 suspicious patterns

    in your code. See Recommended Scalac Flags by Rob Norris scalacOptions += "-Xfatal-warnings" // add to your build.sbt
  15. Before def extractParameter(args: Array[String]): String = if (args.length == 0)

    { throw new IllegalArgumentException("No param found") } else if (args.length >= 1) { throw new IllegalArgumentException("Too many params found") } else args.head
  16. Before def extractParameter(args: Array[String]): String = if (args.length == 0)

    { throw new IllegalArgumentException("No param found") } else if (args.length >= 1) { throw new IllegalArgumentException("Too many params found") } else args.head
  17. After def extractParameter(args: Array[String]): String = args match { case

    Array() => throw new IllegalArgumentException("No param found") case Array(element) => element case _ => throw new IllegalArgumentException("Too many params found") }
  18. Take Away Avoid unsafe functions, such as Collection.head (and Option.get)

    ! Consider using pattern matching and high order functions instead.
  19. Before case class A(x: Int) class B(x: Int, y: Int)

    extends A(x) case class A(x: Int) class B(x: Int, y: Int) extends A(x) Before
  20. Before case class A(x: Int) class B(x: Int, y: Int)

    extends A(x) case class A(x: Int) class B(x: Int, y: Int) extends A(x) Before
  21. Before case class A(x: Int) class B(x: Int, y: Int)

    extends A(x) scala> new B(2, 1) == A(2) res1: Boolean = true WHAT !?! full explanation on stackoverflow
  22. Take Away Always make your case classes final ! If

    you need to extend them, 
 consider converting your case classes to regular classes.
  23. Before def doSomething(): Int = a() + b() + c()

    private def a(): Int = 42 private def b(): Int = { val dataFromDb: Future[Int] = ??? Await.result(dataFromDb, Duration.Inf) } private def c(): Int = 24
  24. Before def doSomething(): Int = a() + b() + c()

    private def a(): Int = 42 private def b(): Int = { val dataFromDb: Future[Int] = ??? Await.result(dataFromDb, Duration.Inf) } private def c(): Int = 24
  25. After def doSomething(): Future[Int] = for { resA <- Future.successful(a())

    resB <- b() resC <- Future.successful(c()) } yield resA + resB + resC private def a(): Int = 42 private def b(): Future[Int] = { val dataFromDb: Future[Int] = ??? dataFromDb } private def c(): Int = 24
  26. Take Away Never ever block Future ! If you cannot

    avoid it, try to do it as late as possible
  27. Before def factorial(number: Int): Int = { def loop(n: Int,

    acc: Int): Int = n match { case 0 => 0 case 1 => acc case x => loop(x - 1, x * acc) } loop(number, acc = 1) }
  28. Before def factorial(number: Int): Int = { def loop(n: Int,

    acc: Int): Int = n match { case 0 => 0 case 1 => acc case x => loop(x - 1, x * acc) } loop(number, acc = 1) }
  29. After def factorial(number: Int): Int = { @tailrec def loop(n:

    Int, acc: Int): Int = n match { case 0 => 0 case 1 => acc case x => loop(x - 1, x * acc) } loop(number, acc = 1) }
  30. Before val myMap: Option[Map[String, String]] = ??? val myList: Option[List[String]]

    = ??? val myOptOpt: Option[Option[String]] = ??? val myBoolean: Option[Boolean] = ???
  31. Before val myMap: Option[Map[String, String]] = ??? val myList: Option[List[String]]

    = ??? val myOptOpt: Option[Option[String]] = ??? val myBoolean: Option[Boolean] = ???
  32. After val myMap: Map[String, String] = ??? val myList: List[String]

    = ??? val myOptOpt: Option[String] = ??? val myBoolean: Boolean = ???
  33. Take Away Try to simplify your types . Do you

    really need that 
 extra layer in your type?
  34. Take Aways •Consider replacing def with val 
 to avoid

    excessive memory allocation •Use private and protected whenever possible •Enable compiler flags to detect 
 suspicious patterns in your code. •Always pick string interpolation 
 over string concatenation •Avoid unsafe functions, 
 such as Collection.head (and Option.get)
  35. Take Aways (2) •Always make your case classes final •Fully

    name Boolean parameters •Never ever block Future •Use the @tailrec annotation •Try to simplify your types
  36. Summary •Refactor your code 
 to improve your coding skills

    •Refactor for the sanity of the people maintaining your code •Refactor to avoid 
 accidental complexity