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

CodeFest 2018. Роберт Губин (2ГИС) — Непрерывный рефакторинг и функциональное программирование

CodeFest 2018. Роберт Губин (2ГИС) — Непрерывный рефакторинг и функциональное программирование

Посмотрите выступление Роберта: https://2018.codefest.ru/lecture/1274/

В настоящее время существуют люди и даже целые компании, которые рассматривают код как некий актив или священную корову. То, к чему лучше не прикасаться, пока оно работает. Я считаю такой подход в корне неверным. Код — лишь инструмент в достижении целей компании. Активом является непосредственная функциональность приложения, следовательно скорость доставки и качество этой функциональности — основная цель программирования.

В докладе я расскажу, как перестать бояться и начать рефакторить, как функциональная парадигма может в этом помочь, и почему вам не нужна Scala.

CodeFest

April 05, 2018
Tweet

More Decks by CodeFest

Other Decks in Programming

Transcript

  1. type File[T] = Option[T] def cat(path: String): File[List[String]] = Some(Source.fromFile(path).getLines.toList)

    def grep(pattern: String)(lines: List[String]): File[List[String]] = Some(lines.filter(_.contains(pattern))) def wc(lines: List[String]): File[Int] = Some(lines.size) cat(path) flatMap grep("val") flatMap wc
  2. <?php $trash = [ null, 1, 2 ]; $numbers =

    []; foreach ($trash as $n) { if ($n) { $numbers[] = $n + 1; } } List(None, Some(1), Some(2)) .flatMap{ numberOption => numberOption.map(n => n + 1) }
  3. case class Response(ids: List[Long]) case class User(id: Long, name: String,

    age: Int) def isAdult(u: User): Boolean = u.age >= 18
  4. def someRemoteCall(param: Int): Response = { if (param == 1)

    { Response(List(1l ,2l ,3l)) } else if (param ==2 ) { Response(List(4l ,5l ,6l)) } else { throw new RuntimeException("Everything blown up.") } }
  5. def dbCall(ids: mutable.MutableList[Long]): mutable.MutableList[User] = { if (ids.nonEmpty) { val

    users = List(User(1l, "Billy", 32), User(6l, "Dolly", 11)) .map(user => (user.id, user)).toMap ids.flatMap(users.get) } else { throw new RuntimeException("No ids, no users.") } }
  6. val response1 = someRemoteCall(1) val response2 = someRemoteCall(2) val idList

    = mutable.MutableList[Long]() idList ++= response1.ids idList ++= response2.ids val users = dbCall(idList)
  7. val adults = mutable.MutableList[User]() for { user <- users }

    yield { if (isAdult(user)) { adults += user } }
  8. def dbCall(ids: List[Long]): List[User] = … val response1 = someRemoteCall(1)

    val response2 = someRemoteCall(2) val idList = response1.ids ++ response2.ids val users = dbCall(idList) val adults = users.filter(isAdult)
  9. val responseTry1 = someRemoteCall(1) val responseTry2 = someRemoteCall(2) val idList

    = responseTry1.flatMap{ response1 => responseTry2.map{ response2 => response1.ids ++ response2.ids } }
  10. for { response1 <- someRemoteCall(1) response2 <- someRemoteCall(2) users <-

    dbCall(response1.ids ++ response2.ids) } yield { users.filter(isAdult) }
  11. sealed trait Error { val message: String } case class

    RemoteServiceError(message: String) extends Error case class DatabaseError(message: String) extends Error
  12. def someRemoteCall(param: Int): Either[Error, Response] = { param match {

    case 1 => Right(Response(List(1l ,2l ,3l))) case 2 => Right(Response(List(4l ,5l ,6l))) case _ => Left(RemoteServiceError("Everything blown up.")) } }
  13. def dbCall(ids: List[Long]): Either[Error, List[User]] = { if (ids.nonEmpty) {

    val users = List(User(1l, "Billy", 32), User(6l, "Dolly", 11)) .map(user => (user.id, user)).toMap Right(ids.flatMap(users.get)) } else { Left(DatabaseError("No ids, no users.")) } }
  14. for { response1 <- someRemoteCall(1) response2 <- someRemoteCall(2) users <-

    dbCall(response1.ids ++ response2.ids) } yield { users.filter(isAdult) }
  15. for { errOrResponse1 <- responseF1 errOrResponse2 <- responseF2 } yield

    for { response1 <- errOrResponse1 response2 <- errOrResponse2 } yield { dbCall(response1.ids ++ response2.ids) }
  16. for { response1 <- responseET1 response2 <- responseET2 dbResult <-

    dbCall(response1.ids ++ response2.ids) } yield { dbResult.filter(isAdult) }
  17. def dbCall(ids: NonEmptyList[Long]): Future[List[User]] = { val users = List(User(1l,

    "Billy", 32), User(6l, "Dolly", 11)) .map(user => (user.id, user)).toMap ids.map(usersMap.get).collect { case Some(x) => x } }
  18. for { response1 <- responseET1 response2 <- responseET2 dbResultOpt =

    NonEmptyList.fromList(response1.ids ++ response2.ids).traverse(dbCall) dbResult <- EitherT.fromOptionF(dbResultOpt, DatabaseError("...")) } yield { dbResult.filter(isAdult) }
  19. «Мы понимаем новые вещи в контексте того, что мы уже

    знаем, а большая часть того, что мы уже знаем — конкретная.» Дэниэл Уиллингэм