Jakub Kozłowski - Scala Developer, Scalac
1日目からレガシー化
プロジェクトをダメにする方法を紹介します
特定データベースへの悪気はありません
コードレビュー、開発、メンテ
What is it?
Don't do it
...unless you're the one judging
then invite everyone from the company to take part
コードレビューはしなくてもいい
Focus on the least important things
ルール1: 重要性が最低のことに集中する
"Flexible formatting"
「柔軟性のあるフォーマット」
"Technology agnostic" spaghetti
「技術非依存」スパゲッティー
"Optimize early" and prematurely
「早すぎる最適化」
"Optimize for reusability" and never practice it
「再利用のための最適化」
"Simplicity first" boilerplate next
「シンプルさファースト」というボイラープレート擁護
開発におけるコツ
Known fact: teams scale linearly
Grow the team until its velocity satisfies you
...or you're out of moneyチームは線形にスケールする
Roll your own
Case study: External APIs
独自実装する
外部ライブラリは作者のことが好きじゃないかもしれない
互換性が壊れるかもしれない
独自実装ならばリリースを制御できる
Ignore the type system
Case study: MongoDB (de)serialization
型システムを無視する
sealed trait MongoValue
case class Booking(id: BookingId, name: String, time: ZonedDateTime)
//use some typeclass?
def toBooking(mongo: MongoValue): Option[Booking] = ???
MongoDB のシリアライゼーション
Sample AST (assume BSON == JSON for simplicity)
sealed trait MongoValue extends Product with Serializable
case class MongoObject(value: Map[String, MongoValue]) extends MongoValue
case class MongoArray(value: List[MongoValue]) extends MongoValue
case class MongoString(value: String) extends MongoValue
case class MongoNumber(value: Double) extends MongoValue
型クラスを実装してみる
PoC: typeclasses (continued)
trait MongoReader[T]{
def read(from: MongoValue): Option[T]
}
implicit val readLong: MongoReader[Long] = from => from match {
case MongoNumber(num) => Some(num.toLong)
case _ => None
}
//assume instances for field types exist
implicit val readBooking: MongoReader[Booking] = from => for {
id <- from.readAs[BookingId]("id")
name <- from.readAs[String]("name")
time <- from.readAs[ZonedDateTime]("time")
} yield Booking(id, name, time)
def toBooking(mongo: MongoValue): Option[Booking] =
implicitly[MongoReader[Booking]].read(mongo)
Too complicated!
複雑すぎる!
so let's write simple boilerplate instead
型クラスは高度に複雑なパターン
インスタンスは手書きもしくは生成される必要がある
//companion omitted for brevity
class DBObject(internal: Map[String, AnyRef])
//definitely better than implicits
class Booking(obj: DBObject) {
private def idOpt = obj.getAsOpt[String]("id")
def id = idOpt.get //surely safer than nulls
def name = obj.getAs[String]("name")
def name_=(value: String) = obj.set("name", value)
def time = ZonedDateTime.parse(
obj.getAs[String]("timestamp")
)
def time_=(value: ZonedDateTime) =
obj.set("timestamp", value.toString)
}
var booking = new Booking(DBObject.empty)
//lenses for free!
booking.name = "hello world"
booking.time = ZonedDateTime.now()
repository.save(booking)
//pretty sure it won't throw, but if it does, it's fine
//"let it crash" behavior
println(booking.id)
Ignore warnings
type Click = String
def matches(c: Click, str: String) = c == str
Later...
- type Click = String
+ case class Click(value: String) extends AnyVal
<console>:2: warning: comparing values of types
Click and String using `==' will always yield false
def matches(c: Click, str: String) = c == str
^
警告は無視するべき
Compilation finished with 0 errors and 94 warnings.
大丈夫だから
メンテナンスのコツ
Document nothing
Nobody will ever need to modify your code
要求仕様は変わらないので文書は書かない
チーム構成も変わらないし
Release rarely
リリースはたまにでいい
Customize by default
デフォルトで魔改造
カスタムブランチ、カスタム開発スタック
Couple tight
密結合
レイヤーは混ぜる
Refactor in batch
リファクターは後でまとめて
同時に色々リファクタリングする
Never change
変わらなくてもいいよ
"It is not necessary to change. Survival is not mandatory."
- W. Edwards Deming
変化は必須ではない。生存は義務ではないのだから。
/s
ダメになる方法は色々あります
Questions?
Slides: kubukoz.github.io/legacy-day1
Contact me:
ありがとうございました