理解のために使用するというEdmondoの提案には同意しますが、検証ライブラリの使用に関する部分には同意しません(少なくとも、2012年以降にscala標準ライブラリに追加された新機能を考えると、もはやそうではありません)。scala での私の経験から、標準的な lib で適切なステートメントを考え出すのに苦労している開発者は、cats や scalaz のような libs を使用すると、最悪の場合も同じことをすることになります。同じ場所ではないかもしれませんが、理想的には、単に移動するのではなく、問題を解決したいと考えています。
これは、理解のために書き直されたコードであり、それは scala 標準 lib の一部です。
def save() = CORSAction { request =>
// Helper to generate the error
def badRequest(message: String) = Error(status = BAD_REQUEST, message)
//Actual validation
val updateEither = for {
json <- request.body.asJson.toRight(badRequest("Expecting JSON data"))
feature <- json.asOpt[Feature].toRight(badRequest("Invalid feature entity"))
rs <- MaxEntitiyValidator
.checkMaxEntitiesFeature(feature)
.toRight(badRequest("You have already reached the limit"))
} yield toJson(feature.update).toString
// Turn the either into an OK/BadRequest
featureEither match {
case Right(update) => Ok(update)
case Left(error) => BadRequest(toJson(error))
}
}
説明
エラー処理
あなたがどちらについてどれだけ知っているかはわかりませんが、Edmondo によって提示された Validation または scala ライブラリの Try オブジェクトと動作がかなり似ています。これらのオブジェクトの主な違いは、機能とエラーのある動作に関するものですが、それらはすべて同じ方法でマップおよびフラット マップできます。
また、 toRightを使用して、オプションを最後に実行するのではなく、すぐに Each に変換していることもわかります。Java開発者は、物理的に可能な限り例外をスローする反射神経を持っていることがわかりますが、try catchメカニズムが扱いにくいため、ほとんどの場合そうします:成功した場合、tryブロックからデータを取得するには、それらを返す必要がありますまたは、ブロックの外で null に初期化された変数に入れます。しかし、scala の場合はそうではありません: try または either をマップすることができるため、一般に、結果が正しくないと識別されたときにすぐに結果をエラー表現に変換すると、より読みやすいコードが得られます。
理解のために
また、scala を発見した開発者は、理解に困惑することが多いことも知っています。これは、他のほとんどの言語と同じように非常に理解できます. はコレクションの反復にのみ使用されますが、スカラは、関連のない多くの型で使用できるようです. scala では、実際には関数 flatMap を呼び出すより適切な方法です。コンパイラは map または foreach を使用して最適化することを決定する場合がありますが、for を使用すると flatMap の動作が得られると想定することは正しいままです。コレクションで flatMap を呼び出すと、他の言語での for each と同じように動作するため、コレクションを処理するときに標準の for のように scala for を使用できます。ただし、正しいシグネチャを持つ flatMap の実装を提供する他のタイプのオブジェクトでも使用できます。OK/BadRequest も flatMap を実装している場合、
コレクションのように見えないものに対して for を使用するのは簡単ではないため、 for の代わりに flatMap を明示的に使用した場合の関数は次のようになります。
def save() = CORSAction { request =>
def badRequest(message: String) = Error(status = BAD_REQUEST, message)
val updateEither = request.body.asJson.toRight(badRequest("Expecting JSON data"))
.flatMap { json =>
json
.asOpt[Feature]
.toRight(badRequest("Invalid feature entity"))
}
.flatMap { feature =>
MaxEntitiyValidator
.checkMaxEntitiesFeature(feature)
.map(_ => feature)
.toRight(badRequest("You have already reached the limit"))
}
.map { rs =>
toJson(feature.update).toString
}
featureEither match {
case Right(update) => Ok(update)
case Left(error) => BadRequest(toJson(error))
}
}
パラメータのスコープに関しては、関数がネストされている場合はライブで動作し、チェーンされていないことに注意してください。
結論
適切なフレームワークや適切な言語機能を使用していないことよりも、提供されたコードの主な問題は、エラーがどのように処理されるかだと思います。一般に、メソッドの最後にエラー パスが積み重なったと思い込んでエラー パスを記述しないでください。エラーが発生したときにすぐに対処できる場合は、別の場所に移動できます。それどころか、それらを押し戻すほど、不可解なネストを持つコードが増えます。これらは実際には、ある時点で処理することを scala が期待するすべての保留中のエラー ケースの具現化です。