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

Scala Days 2019 - Refactor all the things!

Scala Days 2019 - 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 both readable and performant.

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

June 12, 2019
Tweet

More Decks by Daniela Sfregola

Other Decks in Technology

Transcript

  1. Hellooooo • Software Engineer 
 working with Scala • Ex

    Java Developer • Author of 
 "Get Programming with Scala" by Manning
 
 40% off all Manning products with code ctwsdays19
  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. Bonus Round 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") } Thanks Dr M. Odersky!
  20. Bonus Round 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") } Thanks Dr M. Odersky!
  21. Bonus Round def extractParameter(args: Array[String]): String = args match {

    case Array(element) => element case Array() => throw new IllegalArgumentException("No param found") case _ => throw new IllegalArgumentException("Too many params found") } Thanks Dr M. Odersky!
  22. 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
  23. 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
  24. 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 -> Nicolas Rinaudo, tomorrow at 15:45!
  25. Take Away Always make your case classes final* ! If

    you need to extend them, 
 consider converting your case classes to regular classes. *Unless you have a really good reason not to
  26. 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
  27. 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
  28. After def doSomething(): Future[Int] = for { resB <- b()

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

    avoid it, try to do it as late as possible
  30. 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) }
  31. 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) }
  32. 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) }
  33. Before val myMap: Option[Map[String, String]] = ??? val myList: Option[List[String]]

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

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

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

    really need that 
 extra layer in your type?
  37. 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)
  38. 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