Scala Refactoring for Fun and Profit

31
Scala Refactorin g Tomer Gabel, CodeMotion Tel-Aviv 2015 for Fun and Profit

Transcript of Scala Refactoring for Fun and Profit

Page 1: Scala Refactoring for Fun and Profit

Scala Refactorin

gTomer Gabel, CodeMotion Tel-Aviv 2015

for Fun and Profit

Page 2: Scala Refactoring for Fun and Profit

Agenda• For the next 40

minutes, we’ll:– Look at examples

– Discuss patterns

– … and anti-patterns– Showcase refactoring

techniques

Page 3: Scala Refactoring for Fun and Profit

Our Victim• … is ScalaChess

– Provides a full domain model for chess

– Good test coverage

– Long-lived– High quality code– MIT license– Integrated in lichess.org

* ScalaChess is an open-source project by Thibault Duplessis

Page 4: Scala Refactoring for Fun and Profit

STARTING OFF LIGHT

Page 5: Scala Refactoring for Fun and Profit

Naming Things• New features complicate matters:– Infix notation (a.k.a dot-free syntax)

case class Geo(lat: Double, long: Double) { def distance(other: Geo): Double = // ...}

val (center, location): Geo = // ...if (centre.distance(location) <= radius) Some(loc) else None

Page 6: Scala Refactoring for Fun and Profit

Naming Things• New features complicate matters:– Infix notation (a.k.a dot-free syntax)

case class Geo(lat: Double, long: Double) { def distance(other: Geo): Double = // ...}

val (center, location): Geo = // ...if (centre distance location <= radius) Some(loc) else None What language is

this?

Page 7: Scala Refactoring for Fun and Profit

Naming Things• New features complicate matters:– Infix notation (a.k.a dot-free syntax)

case class Geo(lat: Double, long: Double) { def distanceFrom(other: Geo): Double = //…}

val (center, location): Geo = // ...if (location distanceFrom center <= radius) Some(loc) else None A little clearer now

Page 8: Scala Refactoring for Fun and Profit

Naming Things• Oh, the humanity: Symbolic operators

case class Geo(lat: Double, long: Double) { def <->(other: Geo): Double = // ...}

val Geo(center, location) = // ...if (loc <-> center <= radius) Some(loc) else None

Why would you do that?!

Page 9: Scala Refactoring for Fun and Profit

Naming Things• There are worse offenders. Take Dispatch:

import dispatch.Httpimport Http._

val server = url("http://example.com") val headers = Map("Accept" -> "application/json")

Http(server >>> System.out) // GETHttp(server <:< headers >>> System.out) // GETHttp(server << yourPostData >|) // POST

Page 10: Scala Refactoring for Fun and Profit

Naming Things• There are worse offenders. Take scalaz:

def s[A](a: A) = a.success[List[String]]val add3 = (x: Int) => (y: Int) => (z: Int) => x + y + z

val res = (7) <*> (s(8) <*> (s(9) ∘ add3))assert(res == s(24))

Page 11: Scala Refactoring for Fun and Profit

REAL-WORLD EXAMPLE TIME!

Page 12: Scala Refactoring for Fun and Profit

THE LAY OF THE LAND

Page 13: Scala Refactoring for Fun and Profit

Stringly Typed“Used to describe an implementation that needlessly relies on strings when programmer & refactor friendly options are available.” -- Coding Horror

Page 14: Scala Refactoring for Fun and Profit

Stringly Typed• Examples:– Passing dates as strings– Carrying unparsed data around– Using empty strings instead of Options

case class Person(name: String, created: String)

def resolveConflict(p1: Person, p2: Person): Person = { val c1 = dateParser.parse(p1.created) val c2 = dateParser.parse(p2.created) if (c1 compareTo c2 > 0) p1 else p2}

1. Parser needs to be well-known

2. Error handling all over the place

3. What’s with all the boilerplate?

Page 15: Scala Refactoring for Fun and Profit

Stringly Typed• Examples:– Passing dates as strings– Carrying unparsed data around– Using empty strings instead of Options

case class Person(name: String, location: String)

def nearest(to: Person, all: List[Person]): Person = { val geo: Point = Point.parse(to.location) all.minBy(p => geo.distanceTo(Point.parse(p.location)))}

1. Inefficient (space/time)2. Error handling all over the

place3. What’s with all the

boilerplate?

Page 16: Scala Refactoring for Fun and Profit

Stringly Typed• Examples:– Passing dates as strings– Carrying unparsed data around– Using empty strings instead of Options

case class Person(name: String, location: Point)

def nearest(to: Person, all: List[Person]): Person = all.minBy(p => to.location distanceTo p.location)1. Efficient (only parsed once)

2. Sane error handling3. Zero boilerplate!

Page 17: Scala Refactoring for Fun and Profit

Stringly Typed• Examples:– Passing dates as strings– Carrying unparsed data around– Using empty strings instead of Options

case class Person(firstName: String, lastName: String)

def render(p: Person): String = s""" |<div id='first-name'>${p.firstName}</div> |<div id='last-name'>${p.lastName}</div> """.stripMargin

1. Nothing enforces emptiness check!

2. Scala has a great type for these :-)

Page 18: Scala Refactoring for Fun and Profit

REAL-WORLD EXAMPLE TIME!

Page 19: Scala Refactoring for Fun and Profit

Collective Abuse• Scala has a

massive collection library

• Loads of built-ins too– Case classes– Functions and

partials– Tuples, tuples,

tuples• Fairly easy to

abuse

Page 20: Scala Refactoring for Fun and Profit

Collective Abuse• Common anti-patterns:– Too many inline steps– Tuple overload

val actors: List[(Int, String, Double)] = // ...def bestActor(query: String) = actors.filter(_._2 contains query) .sortBy(-_._3) .map(_._1) .headOption

1. What does this even do?!

2. How does data flow here?

Page 21: Scala Refactoring for Fun and Profit

Collective Abuse• Common anti-patterns:– Too many inline steps– Tuple overload

val actors: List[(Int, String, Double)] = // ...def bestActor(query: String) = { val matching = actors.filter(_._2 contains query) val bestByScore = matching.sortBy(-_._3).headOption bestByScore.map(_._1)}

Name intermediate steps!

Page 22: Scala Refactoring for Fun and Profit

Collective Abuse• Common anti-patterns:– Too many inline steps– Tuple overload

val actors: List[(Int, String, Double)] = // ...def bestActor(query: String) = actors.filter(_._2 contains query) .sortBy(-_._3) .map(_._1) .headOption

What’s with all these underscores?

Page 23: Scala Refactoring for Fun and Profit

Collective Abuse• Common anti-patterns:– Too many inline steps– Tuple overload

case class Actor(id: Int, name: String, score: Double)def bestActor(query: String, actors: List[Actor]) = actors.filter(_.name contains query) .sortBy(-_.score) .map(_.id) .headOption

Scala classes are cheap. Use them.

Page 24: Scala Refactoring for Fun and Profit

REAL-WORLD EXAMPLE TIME!

Page 25: Scala Refactoring for Fun and Profit

Scoping

• Correct scoping is incredibly important!–Separation of concerns–Code navigation and discovery–Reduced autocompletion noise

Page 26: Scala Refactoring for Fun and Profit

Scoping• Scala offers an

impressive toolbox:– Nested scopes– Companion objects– Traits– Visibility modifiers– Type classes

Page 27: Scala Refactoring for Fun and Profit

Scopingclass User(var name: String, var age: Int, initialStatus: UserStatus = New) {

private var _status = initialStatus def status = _status

def delete(): Unit = { _status = Deleted Mailer.notify(Mailer.userDeletion, this) }}

Mutable data ⇒ Boilerplate

SoC broken!

Page 28: Scala Refactoring for Fun and Profit

EXAMPLE TIME!https://github.com/holograph/scala-refactoring

Page 29: Scala Refactoring for Fun and Profit

Scoping

• Lessons learned:–Keep logic and data separate

–Case classes are your friends!–Add “aspects” via type classes

Page 30: Scala Refactoring for Fun and Profit

Scoping

• Rules of the road:–Keep your public API small–Make everything else private–Put helpers in companions–… or via implicit extensions

Page 31: Scala Refactoring for Fun and Profit

Questions?

[email protected]@tomerghttp://il.linkedin.com/in/tomergabel

WE’RE DONE HERE!