SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our User Agreement and Privacy Policy.
SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our Privacy Policy and User Agreement for details.
Successfully reported this slideshow.
Activate your 14 day free trial to unlock unlimited reading.
Discover some of bad practices in Scala and how to avoid them.
This talk is mainly about functionnal programming style but only with very simple concepts.
Discover some of bad practices in Scala and how to avoid them.
This talk is mainly about functionnal programming style but only with very simple concepts.
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])
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))
}