7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

scala.util.Tryをパターンにわけてうまく実装するぞ

Last updated at Posted at 2016-09-06

主にWebアプリケーションを書くときに、エラーやValidation結果を返すのに、いろいろと悩むことがありますよね。

Option?
Either?
Try?
/?

で最近は、けっこーTry一択で実装してて、なんとなしにパターン的なものが掴めてきた気がするので、自分用も兼ねてまとめてみた。

Try型は低レイヤーのメソッドで生成しよう

最も小さいメソッドでTryを生成し、そのメソッドを呼び出すメソッド内では、for式を使うと良い。

lazy val assertNotEmpty: String => Try[Unit] = (value: String) => Try {
  assert(value.nonEmpty, "value is empty")
}

lazy val findBy: String => Try[String] = (id: String) => Try {
  someTable.findBy(id)
}

def printSomething(id: String): Try[Unit] =
  for {
    u <- assertNotEmpty(id)
    some <- findBy(id)
  } yield
    println(some)

みたいな。

Try.apply で処理を受けてやるのは、小さい粒度?のメソッドでやってやって、そいつを呼ぶメソッドでは、for式で。

こうすると、スッキリとしたコードが書けて良いなって思ってる。

これを守らなかったら、

lazy val assertNotEmpty: String => Unit = (value: String) =>
  assert(value.nonEmpty, "value is empty")

lazy val findBy: String => String = (id: String) =>
  someTable.findBy(id)

def printSomething(id: String): Try[Unit] = Try {
  assertNotEmpty(id)
  val result = findBy(id)
  println(result)
}

こんな感じになって、なんか、valとか入るし、printSomethingの実装のしかたがあんまり決まってこないし、なんかビミョー。

って気がする。

小さい単位でTry型を返すようにしてやると、そいつを呼び出すところの処理がある程度、決まってくるから、なんか書いてて気持ちいい。

仕様が変わったり、処理を追加したりにも柔軟に対応できる。

たとえば、updateしてから、printしてって仕様に変わったとしても、

lazy val update = (id: String) =>  (something: String) => Try {
  someTable.update(id)(something)
}

って関数を追加してやって、for式に一行呼び出しを書くんだろうなってすぐに分かる。

def printSomething(id: String): Try[Unit] =
  for {
    u <- assertNotEmpty(id)
    some <- findBy(id)
    updatedSome <- update(id)(some + "thing") // <= ココに追加したった。
  } yield
    println(updatedSome)

処理の流れも見易くて、Gitとかで差分見ても、一目瞭然。

やっぱこの単位でTryにしていくのがベストな気がします。

Try[Seq[Try[String]]]をほぐす

低レイヤーのメソッドでTryにしていくと、 Try[Seq[Try[String]]] みたいなことになりがち。

lazy val assertNotEmpty: String => Try[Unit] = (value: String) => Try {
  assert(value.nonEmpty, "value is empty")
}

lazy val findAll: () => Try[Seq[String]] = () => Try {
  someTable.findAll
}

lazy val findBy: String => Try[String] = (id: String) => Try {
  someTable.findBy(id)
}

def printSomething(): Try[Seq[Try[String]]] =
  for {
    ids <- findAll()
  } yield for {
    id <- ids
  } yield for {
    u <- assertNotEmpty(id)
    some <- findBy(id)
  } yield
    some

上記のよーな感じ。これをほぐす。

def printSomething(): Try[Seq[String]] =
  for {
    ids <- findAll()
    somes <- Try {
      for {
        id <- ids
      } yield (for {
        u <- assertNotEmpty(id)
        some <- findBy(id)
      } yield some).get
    }
  } yield somes

みたいな。

これで良い感じ。

Option.getは悪だけどTry.getは良い

Option.getをすると、NullPointerになるから、getとか絶対使うなよ!!って言うけど、だからって、Try.getもだぞ!!ってことにならないと考えてます。

Try {
  // something
} match {
  case Success(s) => s
  case Failure(e) => throw e
}

って、

Try {
  // something
}.get

と同じなんだよね。

結構、前者のコードを見る。(まあ、Scala初級者なだけですが…)

けど、そんなん冗長なことせんと、そこは素直にgetでいこーぜって思う。

まとめ

ざっと、眠たい目をこすりながら、思い出したのは、以上。

他に思い出したら追記かなんかします。

以上です。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?