Advertisement
Advertisement

More Related Content

Advertisement

Recently uploaded(20)

Advertisement

Scala bad practices, scala.io 2019

  1. 2019 Badr Baddou - Loïc Knuchel @GraalSeeker @loicknuchel
  2. @GraalSeeker @loicknuchel The ugly truth
  3. Hello ! Badr Baddou Scala dev @ Zeenea @GraalSeeker Loïc Knuchel Tech lead @ Zeenea @loicknuchel Scala FP DDD Property based testing
  4. Your next data catalog #Data #Documentation #Glossary #Lineage #Gouvernance
  5. @GraalSeeker @loicknuchel Common bad practices ● Do not use null and throw ● Avoid isEmpty / get ● Ban symbols in function names ● Pay attention of primitive types use ● Do not let library conventions leak in your code ● DRY overuse ● Beware of mixing paradigms
  6. @GraalSeeker @loicknuchel Primitive obsession ● Primitive types: Int, String, Boolean… ● They do not carry semantics ○ Int, String vs FileSize, Bandwidth, RelPath, Email… ● They are indistinguishable ○ String <=> String vs UserId <!> CommentId ● You don’t known if they were validated ○ String/String vs Email/CheckedEmail ● You have no helper methods ○ path.split(“/”).dropRight(1).mkString(“/”) vs path.getParent()
  7. @GraalSeeker @loicknuchel Library- splaining Write a wrapper to protect from library conventions ● Anti corruption layer (DDD) ● Do types conversions ○ null <=> Option ○ throw <=> Try, Either ○ Java collections <=> Scala collections ○ primitive types <=> dedicated types => Type Safety o/ ○ Any <=> ADT ● Paradigm shifts ○ mutable <=> immutable ○ currying ○ sync <=> async (dedicated thread pool)
  8. @GraalSeeker @loicknuchel Mixing paradigms class User() { def updatedAt(): Option[Instant] = null def validEmail(): Try[String] = throw new IllegalArgumentException("Email is invalid") }
  9. @GraalSeeker @loicknuchel DRY overuse case class User(id: Option[String], name: String, email: Option[String] = None, friends: Seq[String] = Seq(), admin: Boolean = false) case class UserCreation(name: String, email: String) case class User(id: String, name: String, email: String) case class UserFull(id: String, name: String, email: String, friends: Seq[String], admin: Boolean)
  10. @GraalSeeker @loicknuchel Real code is (often) a mess ● very long ● inconsitencies ● over generalization (DRY)
  11. @GraalSeeker @loicknuchel Conciseness over readability val tuple: (Int, Int) = Map("id" -> ("badr", 13)) .flatMap(e => List(e._2)) .map(_._2 + 1) ./:((0, 0))((a, b) => (a._1 - 1, a._2 + b)) ● Conciseness ≠ Clarity ● No semantics Symbols Tuple Bad naming
  12. @GraalSeeker @loicknuchel Solution val tuple: (Int, Int) = Map("id" -> ("badr", 13)) .flatMap(e => List(e._2)) .map(_._2 + 1) ./:((0, 0))((a, b) => (a._1 - 1, a._2 + b)) case class User(name: String, age: Int) val tuple2: (Int, Int) = Map("id" -> User("badr", 13)) .flatMap { case (_, value) => List(value) } .map(user => user.age + 1) .foldLeft((0, 0))((acc, e) => (acc._1 - 1, acc._2 + e)) “The most common things you want to do should be the most concise, while the less common things should be more verbose” Lihayo No symbols Use class not tuples Named pattern matching Named value
  13. @GraalSeeker @loicknuchel Powerful Feature misuse final case class Arc(name: String) def main() { Arc("zzz").show() } case class RichArc(value: Arc) { def show() = s"${value.name} is a rich Arc" } implicit def richArc(arc: Arc): RichArc = RichArc(arc) // Problem conversion can apply when not needed def createRichArc(): RichArc = { // conversion not needed here Arc("aaa") }
  14. @GraalSeeker @loicknuchel Solution object Syntax { implicit case class EnrichArc(value: Arc) extends AnyVal { def show() = s"${value.name} is a rich Arc" } } def main() { import Syntax._ Arc("zzz").show() }
  15. @GraalSeeker @loicknuchel Apply misuse def main(args: Array[String]): Unit = { val arc = Arc("id", "name") } // returns RichArc !!! object Arc { def apply(id: String, name: String): RichArc = { val (in, out) = nodeRepo.getNodes(id).get // throws :( RichArc(id, name, in, out) } } case class Node(id: String, name: String) case class Arc(id: String, name: String) case class RichArc(id: String, name: String, in: Option[Node], out: Option[Node])
  16. @GraalSeeker @loicknuchel Solution object Arc { def apply(id: String, name: String): RichArc = { val (in, out) = nodeRepo.getNodes(id).get // throws :( RichArc(id, name, in, out) } } case class NodeRepo() { def getNodes(id: String): Try[(Option[Node], Option[Node])] = ??? } object RichArc { def from(id: String, name: String): Try[RichArc] = { nodeRepo.getNodes(id).map { case (in, out) => RichArc(id, name, in, out) } } }
  17. @GraalSeeker @loicknuchel hide and seek implicits object App { def main(args: Array[String]): Unit = { val p: PropertyValue = 1 val q: PropertyValue = true val r: PropertyValue = "toto" } } package object propertyHelpers { implicit def build(v: Any): PropertyValue = PropertyValue(v) } // there should be some implicits here // but no import // where to look??? “Package objects can contain arbitrary definitions. For instance, they are frequently used to hold package-wide implicit conversions.” https://docs.scala-lang.org/tour/package-objects.html
  18. @GraalSeeker @loicknuchel Define operator val name: Prop[String] = Prop("name", StrWrap) val score: Prop[Int] = Prop("score", IntWrap) val props = Props( name -> "Jean", score -> 10)
  19. @GraalSeeker @loicknuchel How it works? val name: Prop[String] = Prop("name", StrWrap) val score: Prop[Int] = Prop("score", IntWrap) val props = Props( name -> "Jean", score -> 10) case class Props(in: Map[Prop.Name, Prop.Value]) object Props { def apply(in: (Prop.Name, Prop.Value)*): Props = new Props(in.toMap) } // look for implicits case class Prop[A](name:Name, wrap:Wrap[A]) { def ->(value: A): (Prop.Name, Prop.Value) = (name, wrap.encode(value)) }
  20. @GraalSeeker @loicknuchel Resources ● https://github.com/loicknuchel/scala-bad-practices ● http://bit.ly/scala-bad-practices ● https://github.com/zeenea/scala-best-practices
Advertisement