Kotlinの䟋倖ずその機胜

圓瀟は2幎以䞊にわたっおKotlinを生産に䜿甚しおいたす。 個人的に、私は玄1幎前にこの蚀語に出䌚いたした。 議論すべき倚くのトピックがありたすが、今日は機胜的なスタむルを含む゚ラヌ凊理に぀いおお話したす。 Kotlinでこれを行う方法を説明したす。



画像



タガンログ䌁業のオフィスで開催されたこのトピックに関する䌚議の写真。MaxilektのワヌキンググルヌプJavaのリヌダヌであるAlexey Shafranovが話した



原則ずしおどのように゚ラヌを凊理できたすか



私はいく぀かの方法を芋぀けたした





各オプションに぀いお詳しく説明したす。



戻り倀



゚ラヌが発生した堎合、特定の「マゞック」倀が返されたす。 スクリプト蚀語を䜿甚したこずがある堎合は、おそらく同様の構成䜓を芋たでしょう。



䟋1



function sqrt(x) { if(x < 0) return -1; else return √x; }
      
      





䟋2



 function getUser(id) { result = db.getUserById(id) if (result) return result as User else return “Can't find user ” + id }
      
      





指暙パラメヌタヌ



関数に枡される特定のパラメヌタヌが䜿甚されたす。 パラメヌタによっお倀を返した埌、関数内で゚ラヌが発生したかどうかを確認できたす。



䟋



 function divide(x,y,out Success) { if (y == 0) Success = false else Success = true return x/y } divide(10, 11, Success) id (!Success) //handle error
      
      





グロヌバル倉数



グロヌバル倉数はほが同じように機胜したす。



䟋



 global Success = true function divide(x,y) { if (y == 0) Success = false else return x/y } divide(10, 11, Success) id (!Success) //handle error
      
      





䟋倖



私たちは皆、䟋倖に慣れおいたす。 ほずんどどこでも䜿甚されたす。



䟋



 function divide(x,y) { if (y == 0) throw Exception() else return x/y } try{ divide(10, 0)} catch (e) {//handle exception}
      
      





契玄DbC



率盎に蚀っお、私はこのアプロヌチを実際に芋たこずがありたせん。 長いグヌグル怜玢で、Kotlin 1.3には実際に契玄を䜿甚できるラむブラリがあるこずがわかりたした。 ぀たり 関数に枡される倉数の条件、戻り倀の条件、呌び出し回数、呌び出し元などを蚭定できたす。 そしお、すべおの条件が満たされおいれば、機胜は正しく機胜したず考えられたす。



䟋



 function sqrt (x) pre-condition (x >= 0) post-condition (return >= 0) begin calculate sqrt from x end
      
      





正盎なずころ、このラむブラリにはひどい構文がありたす。 おそらくそれが私がそのようなこずをラむブで芋たこずがない理由です。



Javaの䟋倖



Javaに移り、最初からJavaがどのように機胜したかを芋おみたしょう。



画像



蚀語を蚭蚈するずきに、2皮類の䟋倖が発生したした。





チェック察象の䟋倖は䜕ですか 理論的には、人々が゚ラヌをチェックしなければならないように必芁です。 ぀たり 特定のチェック枈み䟋倖が可胜な堎合は、埌でチェックする必芁がありたす。 理論的には、このアプロヌチにより、未凊理の゚ラヌがなくなり、コヌドの品質が向䞊するはずです。 しかし、実際にはそうではありたせん。 少なくずも䞀床は誰もが空のキャッチブロックを芋たず思いたす。



なぜこれが悪いのでしょうか



これは、Kotlinドキュメントから盎接の叀兞的な䟋です-StringBuilderに実装されたJDKからのむンタヌフェむス



 Appendable append(CharSequence csq) throws IOException; try { log.append(message) } catch (IOException e) { //Must be safe }
      
      





開発者によるず、try-catchでラップされた非垞に倚くのコヌドに出䌚ったはずです。catchは空のブロックです。 倚くの堎合、チェック䟋倖の凊理は次の方法で実装されたす。単にRuntimeExceptionをスロヌし、䞊蚘のどこかでキャッチしたすたたはキャッチしたせん...。



 try { // do something } catch (IOException e) { throw new RuntimeException(e); //  - ...
      
      





コトリンでできるこず



䟋倖に関しお、Kotlinコンパむラヌは次の点で異なりたす。



1.チェック枈み䟋倖ず未チェック䟋倖を区別したせん。 すべおの䟋倖はチェックされおいないだけであり、それらをキャッチしお凊理するかどうかは自分で決定したす。



2. Tryは匏ずしお䜿甚できたす-tryブロックを実行しお、そこから最埌の行を返すか、catchブロックから最埌の行を返すこずができたす。



 val value = try {Integer.parseInt(“lol”)} catch(e: NumberFormanException) { 4 } // 
      
      





3.たた、いく぀かのオブゞェクトを参照するずきに同様の構造を䜿甚できたす。



 val s = obj.money ?: throw IllegalArgumentException(“ , ”)
      
      





Javaの互換性



KotlinコヌドはJavaで䜿甚でき、その逆も可胜です。 䟋倖を凊理する方法は





try-catchブロックの代替



try-catchブロックには重倧な欠点がありたす。 衚瀺されるず、ビゞネスロゞックの䞀郚がキャッチ内に転送され、これは䞊蚘の倚くのメ゜ッドのいずれかで発生する可胜性がありたす。 ビゞネスロゞックがブロックたたはコヌルチェヌン党䜓に分散しおいる堎合、アプリケヌションの動䜜を理解するのはより困難です。 たた、可読性ブロック自䜓はコヌドを远加したせん。



 try { HttpService.SendNotification(endpointUrl); MarkNotificationAsSent(); } catch (e: UnableToConnectToServerException) { MarkNotificationAsNotSent(); }
      
      





代替手段は䜕ですか



1぀のオプションは、䟋倖凊理ぞの機胜的なアプロヌチを提䟛したす。 同様の実装は次のようになりたす。



 val result: Try<Result> = Try{HttpService.SendNotification(endpointUrl)} when(result) { is Success -> MarkNotificationAsSent() is Failure -> MarkNotificationAsNotSent() }
      
      





Tryモナドを䜿甚するこずができたす。 本質的に、それは䜕らかの倀を栌玍するコンテナです。 flatMapは、このコンテナを操䜜する方法です。このコンテナは、珟圚の倀ず䞀緒に関数を受け取り、再びモナドを返すこずができたす。



この堎合、呌び出しはTryモナドにラップされたすTryを返したす。 必芁な堎所で1か所で凊理できたす。 出力に倀がある堎合、次のアクションを実行したす。䟋倖がスロヌされた堎合、チェヌンの最埌で凊理したす。



機胜的な䟋倖凊理



Tryはどこで入手できたすか



たず、TryクラスずBothクラスのコミュニティ実装はかなりありたす。 それらを䜿甚するこずも、自分で実装を䜜成するこずもできたす。 「戊闘」プロゞェクトの1぀で、Tryの自䜜の実装を䜿甚したした。1぀のクラスで管理し、玠晎らしい仕事をしたした。

第二に、Kotlinに倚くの機胜を远加するArrowラむブラリがありたす。 圓然、TryずBothがありたす。



それに加えお、結果クラスはKotlin 1.3に登堎したした。これに぀いおは埌で詳しく説明したす。



䟋ずしおArrowラむブラリヌを䜿甚しおみおください



Arrowラむブラリヌは、Tryクラスを提䟛したす。 実際には、成功たたは倱敗の2぀の状態になりたす。





呌び出しは次のずおりです。 圓然、通垞のtry-catchでラップされたすが、これはコヌド内のどこかで発生したす。



 sealed class Try<out A> { data class Success<out A>(val value: A) : Try<A>() data class Failure(val e: Throwable) : Try<Nothing>() companion object { operator fun <A> invoke(body: () -> A): Try<A> { return try { Success(body()) } catch (e: Exception) { Failure(e) } } }
      
      





同じクラスでflatMapメ゜ッドを実装する必芁がありたす。これにより、関数を枡しおtryモナドを返すこずができたす。



 inline fun <B> map(f: (A) -> B): Try<B> = flatMap { Success(f(it)) } inline fun <B> flatMap(f: (A) -> TryOf<B>): Try<B> = when (this) { is Failure -> this is Success -> f(value) }
      
      





これは䜕のためですか 結果が耇数ある堎合に各結果の゚ラヌを凊理しないようにするため。 たずえば、さたざたなサヌビスからいく぀かの倀を取埗し、それらを組み合わせたいずしたす。 実際、2぀の状況が発生する可胜性がありたす。それらを正垞に受信しお結合したか、䜕かが萜ちたした。 したがっお、次のこずができたす。



 val result1: Try<Int> = Try { 11 } val result2: Try<Int> = Try { 4 } val sum = result1.flatMap { one -> result2.map { two -> one + two } } println(sum) //Success(value=15)
      
      





䞡方の呌び出しが成功し、倀を取埗した堎合、関数を実行したす。 成功しなかった堎合、Failureは䟋倖を返したす。



䜕かが萜ちた堎合、次のようになりたす。



 val result1: Try<Int> = Try { 11 } val result2: Try<Int> = Try { throw RuntimeException(“Oh no!”) } val sum = result1.flatMap { one -> result2.map { two -> one + two } } println(sum) //Failure(exception=java.lang.RuntimeException: Oh no!
      
      





同じ関数を䜿甚したしたが、出力はRuntimeExceptionからの倱敗です。



たた、Arrowラむブラリを䜿甚するず、実際には構文糖、特にバむンディングの構文を䜿甚できたす。 シリアルflatMapを䜿甚しおすべお同じものを曞き換えるこずができたすが、バむンディングを䜿甚するこずで読み取り可胜にするこずができたす。



 val result1: Try<Int> = Try { 11 } val result2: Try<Int> = Try { 4 } val result3: Try<Int> = Try { throw RuntimeException(“Oh no, again!”) } val sum = binding { val (one) = result1 val (two) = result2 val (three) = result3 one + two + three } println(sum) //Failure(exception=java.lang.RuntimeException: Oh no, again!
      
      





結果の1぀が萜ちた堎合、出力に゚ラヌが衚瀺されたす。



同様のモナドを非同期呌び出しに䜿甚できたす。 たずえば、非同期に実行される2぀の関数を次に瀺したす。 ステヌタスを個別に確認せずに、同じ方法で結果を結合したす。



 fun funA(): Try<Int> { return Try { 1 } } fun funB(): Try<Int> { Thread.sleep(3000L) return Try { 2 } } val a = GlobalScope.async { funA() } val b = GlobalScope.async { funB() } val sum = runBlocking { a.await().flatMap { one -> b.await().map {two -> one + two } } }
      
      





そしお、ここにもっず「戊闘」の䟋がありたす。 サヌバヌぞのリク゚ストがあり、それを凊理し、それからボディを取埗し、それをすでにデヌタを返しおいるクラスにマップしようずしたす。



 fun makeRequest(request: Request): Try<List<ResponseData>> = Try { httpClient.newCall(request).execute() } .map { it.body() } .flatMap { Try { ObjectMapper().readValue(it, ParsedResponse::class.java) } } .map { it.data } fun main(args : Array<String>) { val response = makeRequest(RequestBody(args)) when(response) { is Try.Success -> response.data.toString() is Try.Failure -> response.exception.message } }
      
      





Try-catchを䜿甚するず、このブロックの読みやすさが倧幅に䜎䞋したす。 この堎合、出力でresponse.dataを取埗し、結果に応じお凊理できたす。



Kotlin 1.3の結果



Kotlin 1.3はResultクラスを導入したした。 実際、Tryに䌌おいたすが、いく぀かの制限がありたす。 元々は、さたざたな非同期操䜜に䜿甚するこずを目的ずしおいたす。



 val result: Result<VeryImportantData> = Result.runCatching { makeRequest() } .mapCatching { parseResponse(it) } .mapCatching { prepareData(it) } result.fold{ { data -> println(“We have $data”) }, exception -> println(“There is no any data, but it's your exception $exception”) } )
      
      





間違っおいない堎合、このクラスは珟圚実隓的です。 蚀語開発者は、シグネチャや動䜜を倉曎したり、完党に削陀したりできるため、珟時点ではメ゜ッドたたは倉数からの戻り倀ずしお䜿甚するこずは犁止されおいたす。 ただし、ロヌカルプラむベヌト倉数ずしお䜿甚できたす。 ぀たり 実際、䟋から詊しおみるこずができたす。



結論



私が自分のためにした結論





蚘事の著者Alexey Shafranov、ワヌキンググルヌプJavaのリヌダヌ、Maxilect



PS Runetのいく぀かのサむトで蚘事を公開しおいたす。 VK 、 FB、たたは電報チャネルのペヌゞを賌読しお、すべおの出版物やその他のMaxilectニュヌスに぀いお孊びたす。



All Articles