कोटलिन अपवाद और उनकी विशेषताएं

हमारी कंपनी दो साल से अधिक समय से उत्पादन में कोटलिन का उपयोग कर रही है। व्यक्तिगत रूप से, मैं लगभग एक साल पहले इस भाषा में आया था। बात करने के लिए कई विषय हैं, लेकिन आज हम एक कार्यात्मक शैली में त्रुटि से निपटने के बारे में बात करेंगे। मैं आपको बताता हूं कि कोटलिन में यह कैसे करना है।

छवि

(इस विषय पर बैठक से फोटो, जो कि टैगानॉग कंपनियों में से एक के कार्यालय में हुई थी। मैक्सिलेक में काम करने वाले समूह (जावा) के नेता एलेक्सी शफ्रानोव ने बात की थी।

आप सिद्धांत रूप में त्रुटियों को कैसे संभाल सकते हैं?


मुझे कई तरीके मिले:

  • आप कुछ वापसी मान का उपयोग इस तथ्य के लिए एक संकेतक के रूप में कर सकते हैं कि कोई त्रुटि है;
  • आप एक ही उद्देश्य के लिए संकेतक पैरामीटर का उपयोग कर सकते हैं,
  • एक वैश्विक चर दर्ज करें
  • अपवादों को संभालें
  • अनुबंध (DbC) जोड़ें।

आइए हम प्रत्येक विकल्पों पर अधिक विस्तार से ध्यान दें।

वापसी मूल्य


यदि कोई त्रुटि होती है, तो एक निश्चित "जादू" मान लौटाया जाता है। यदि आपने कभी स्क्रिप्टिंग भाषाओं का उपयोग किया है, तो आपने समान निर्माणों को देखा होगा।

उदाहरण 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} 

अनुबंध (डीबीसी)


सच कहूं तो मैंने कभी इस दृष्टिकोण को लाइव नहीं देखा। लंबी गुगली करके, मैंने पाया कि कोटलिन 1.3 में एक पुस्तकालय है जो वास्तव में अनुबंधों के उपयोग की अनुमति देता है। यानी आप फ़ंक्शन पर पारित होने वाले चर पर स्थिति सेट कर सकते हैं, वापसी मूल्य पर स्थिति, कॉल की संख्या, जहां से इसे कॉल किया जाता है, आदि। और अगर सभी शर्तों को पूरा किया जाता है, तो यह माना जाता है कि फ़ंक्शन सही ढंग से काम करता है।

एक उदाहरण:

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

ईमानदारी से, इस पुस्तकालय में भयानक वाक्यविन्यास है। शायद इसीलिए मैंने ऐसा कुछ लाइव नहीं देखा।

जावा में अपवाद


चलिए जावा पर चलते हैं और यह सब शुरू से कैसे काम करता है।

छवि

भाषा को डिज़ाइन करते समय, दो प्रकार के अपवाद रखे गए थे:

  • check - चेक किया हुआ;
  • अनियंत्रित - अनियंत्रित।

किन अपवादों की जाँच की जाती है? सैद्धांतिक रूप से, इनकी आवश्यकता होती है ताकि लोगों को त्रुटियों की जांच करनी पड़े। यानी यदि एक निश्चित जाँच अपवाद संभव है, तो इसे बाद में जाँच लेना चाहिए। सैद्धांतिक रूप से, इस दृष्टिकोण में अप्रमाणित त्रुटियों और बेहतर कोड गुणवत्ता के अभाव का कारण होना चाहिए। लेकिन व्यवहार में ऐसा नहीं है। मुझे लगता है कि हर किसी ने अपने जीवन में कम से कम एक बार एक खाली कैच ब्लॉक देखा।

यह बुरा क्यों हो सकता है?

यहाँ कोटलिन प्रलेखन से सीधे एक क्लासिक उदाहरण है - स्ट्रिंगब्यूरी में लागू JDK का एक इंटरफ़ेस:

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

मुझे यकीन है कि आप कोशिश-कैच में लिपटे हुए बहुत सारे कोड से मिल चुके हैं, जहां पकड़ एक खाली ब्लॉक है, क्योंकि ऐसी स्थिति बस डेवलपर के अनुसार नहीं होनी चाहिए थी। कई मामलों में, चेक किए गए अपवादों की हैंडलिंग को निम्नलिखित तरीके से लागू किया जाता है: वे बस एक रनटाइम एक्सप्रेशन फेंकते हैं और इसे ऊपर कहीं पकड़ लेते हैं (या इसे पकड़ नहीं सकते ...)।

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

कोटलिन में क्या संभव है


अपवादों के संदर्भ में, कोटलिन संकलक उस में अलग है:

1. जाँच और अनियंत्रित अपवादों के बीच अंतर नहीं करता है। सभी अपवाद केवल अनियंत्रित हैं, और आप अपने लिए तय करते हैं कि उन्हें पकड़ना है या नहीं।

2. कोशिश को एक अभिव्यक्ति के रूप में इस्तेमाल किया जा सकता है - आप कोशिश ब्लॉक को चला सकते हैं और इसमें से अंतिम पंक्ति वापस कर सकते हैं, या पकड़ ब्लॉक से अंतिम पंक्ति वापस कर सकते हैं।

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

3. आप किसी वस्तु का जिक्र करते समय एक समान निर्माण का भी उपयोग कर सकते हैं, जो अशक्त हो सकता है:

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

जावा संगतता


कोटलिन कोड का उपयोग जावा और इसके विपरीत में किया जा सकता है। अपवादों को कैसे संभालें?

  • कोटलिन में जावा से चेक किए गए अपवादों को न तो चेक किया जा सकता है और न ही घोषित किया जा सकता है (चूंकि कोटलिन में कोई अपवाद नहीं हैं)।
  • कोटलिन से संभावित चेक किए गए अपवाद (उदाहरण के लिए, जो मूल रूप से जावा से आए थे) को जावा में जांचने की आवश्यकता नहीं है।
  • यदि यह जांचना आवश्यक है, तो विधि में @Throws एनोटेशन का उपयोग करके अपवाद को सत्यापन योग्य बनाया जा सकता है (यह इंगित करना आवश्यक है कि यह विधि कौन से अपवादों को फेंक सकती है)। उपरोक्त एनोटेशन केवल जावा संगतता के लिए है। लेकिन व्यवहार में, बहुत से लोग यह घोषित करने के लिए उपयोग करते हैं कि इस तरह की विधि, सिद्धांत रूप में, किसी प्रकार के अपवाद को फेंक सकती है।

वैकल्पिक-पकड़ने ब्लॉक का विकल्प


ट्राई-कैच ब्लॉक में एक महत्वपूर्ण खामी है। जब यह प्रकट होता है, तो व्यापार तर्क का हिस्सा पकड़ के अंदर स्थानांतरित किया जाता है, और यह ऊपर दिए गए कई तरीकों में से एक में हो सकता है। जब व्यावसायिक तर्क ब्लॉकों या संपूर्ण कॉल श्रृंखला में फैल जाता है, तो यह समझना अधिक कठिन होता है कि आवेदन कैसे काम करता है। और पठनीयता ब्लॉक स्वयं कोड नहीं जोड़ते हैं।

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

विकल्प क्या हैं?

एक विकल्प हमें अपवाद से निपटने के लिए एक कार्यात्मक दृष्टिकोण प्रदान करता है। एक समान कार्यान्वयन इस तरह दिखता है:

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

हमारे पास ट्राय मोनड का उपयोग करने का अवसर है। संक्षेप में, यह एक कंटेनर है जो कुछ मूल्य संग्रहीत करता है। फ्लैटपाइप इस कंटेनर के साथ काम करने का एक तरीका है, जो वर्तमान मूल्य के साथ मिलकर एक फ़ंक्शन ले सकता है और फिर से, एक मोनाड वापस कर सकता है।

इस स्थिति में, कॉल को ट्राइड मोनड में लपेटा जाता है (हम वापस कोशिश करते हैं)। इसे एक ही स्थान पर संसाधित किया जा सकता है - जहां हमें इसकी आवश्यकता होती है। यदि आउटपुट का कोई मान है, तो हम इसके साथ निम्न क्रिया करते हैं, यदि कोई अपवाद फेंका जाता है, तो हम इसे श्रृंखला के बहुत अंत में संसाधित करते हैं।

कार्यात्मक अपवाद हैंडलिंग


मैं कहाँ से कोशिश कर सकते हैं?

सबसे पहले, कोशिश और या तो वर्गों के काफी कुछ सामुदायिक कार्यान्वयन हैं। आप उन्हें ले सकते हैं या यहां तक ​​कि खुद एक कार्यान्वयन भी लिख सकते हैं। "मुकाबला" परियोजनाओं में से एक में, हमने स्व-निर्मित ट्राई इम्प्लीमेंटेशन का उपयोग किया - हमने एक वर्ग के साथ काम किया और एक उत्कृष्ट काम किया।
दूसरे, एरो लाइब्रेरी है, जो सिद्धांत रूप में कोटलिन में बहुत अधिक कार्यक्षमता जोड़ती है। स्वाभाविक रूप से, कोशिश और या तो हैं।

ठीक है, इसके अलावा, परिणाम वर्ग Kotlin 1.3 में दिखाई दिया, जिसे मैं बाद में और अधिक विस्तार से चर्चा करूंगा।

एक उदाहरण के रूप में एरो लाइब्रेरी का उपयोग करने का प्रयास करें


एरो लाइब्रेरी हमें ट्राई क्लास देती है। वास्तव में, यह दो अवस्थाओं में हो सकता है: सफलता या असफलता:

  • सफल निकासी पर सफलता हमारे मूल्य को बनाए रखेगी,
  • विफलता एक अपवाद को संग्रहीत करती है जो कोड के ब्लॉक के निष्पादन के दौरान हुई थी।

कॉल इस प्रकार है। स्वाभाविक रूप से, यह एक नियमित कोशिश में लिपटा हुआ है - पकड़, लेकिन यह हमारे कोड के अंदर कहीं होगा।

 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) } } } 

उसी वर्ग को फ्लैटपाइप विधि को लागू करना चाहिए, जो आपको एक फ़ंक्शन पारित करने और हमारे प्रयास को वापस करने की अनुमति देता है:

 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) } 

यह किस लिए है? परिणामों में से प्रत्येक के लिए त्रुटियों को संसाधित नहीं करने के लिए जब हमारे पास उनमें से कई हैं। उदाहरण के लिए, हमने विभिन्न सेवाओं से कई मूल्य प्राप्त किए और उन्हें संयोजित करना चाहते हैं। वास्तव में, हमारे पास दो स्थितियां हो सकती हैं: या तो हमने सफलतापूर्वक प्राप्त किया और उन्हें संयुक्त किया, या कुछ गिर गया। इसलिए, हम निम्नलिखित कर सकते हैं:

 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) 

यदि दोनों कॉल सफल रहे और हमें मान मिले, तो हम फ़ंक्शन को निष्पादित करते हैं। यदि वे सफल नहीं होते हैं, तो विफलता एक अपवाद के साथ वापस आ जाएगी।

यहाँ है कि ऐसा लगता है जैसे कुछ गिर गया:

 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 से विफलता है।

इसके अलावा, एरो लाइब्रेरी आपको उन कंस्ट्रक्शन का उपयोग करने की अनुमति देती है जो विशेष रूप से बाइंडिंग में वास्तव में सिंटैक्टिक चीनी हैं। सभी को एक धारावाहिक फ्लैटपाइप के माध्यम से फिर से लिखा जा सकता है, लेकिन बाध्यकारी आपको इसे पठनीय बनाने की अनुमति देता है।

 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! 

यह देखते हुए कि परिणामों में से एक गिर गया है, हमें आउटपुट पर एक त्रुटि मिलती है।

एक समान मोनाड का उपयोग अतुल्यकालिक कॉल के लिए किया जा सकता है। उदाहरण के लिए, यहां दो कार्य हैं जो अतुल्यकालिक रूप से चलते हैं। हम उनके परिणामों को उसी तरह से जोड़ते हैं, बिना उनकी स्थिति की जाँच किए:

 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 } } 

कोशिश-कैच इस ब्लॉक को बहुत कम पठनीय बनाता है। और इस मामले में, हमें आउटपुट पर response.data मिलता है, जिसे हम परिणाम के आधार पर संसाधित कर सकते हैं।

कोटलिन 1.3 का परिणाम


Kotlin 1.3 ने परिणाम वर्ग प्रस्तुत किया। वास्तव में, यह कोशिश के समान है, लेकिन कई सीमाओं के साथ। यह मूल रूप से विभिन्न अतुल्यकालिक संचालन के लिए उपयोग करने का इरादा है।

 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”) } ) 

यदि गलत नहीं है, तो यह वर्ग वर्तमान में प्रायोगिक है। भाषा डेवलपर्स अपने हस्ताक्षर, व्यवहार को बदल सकते हैं, या इसे पूरी तरह से हटा सकते हैं, इसलिए फिलहाल इसे तरीकों या चर से वापसी मूल्य के रूप में उपयोग करने के लिए मना किया जाता है। हालाँकि, इसका उपयोग स्थानीय (निजी) चर के रूप में किया जा सकता है। यानी वास्तव में, यह उदाहरण से एक कोशिश के रूप में इस्तेमाल किया जा सकता है।

निष्कर्ष


निष्कर्ष जो मैंने खुद के लिए बनाया है:

  • कोटलिन में कार्यात्मक त्रुटि हैंडलिंग सरल और सुविधाजनक है;
  • शास्त्रीय शैली में ट्राइ-कैच के माध्यम से उन्हें संसाधित करने के लिए कोई भी परेशान नहीं करता है (दोनों उस और उस पर जीवन का अधिकार है; दोनों और वह सुविधाजनक है);
  • चेक किए गए अपवादों की अनुपस्थिति का मतलब यह नहीं है कि त्रुटियों को संभाला नहीं जा सकता है;
  • उत्पादन पर बिना किसी अपवाद के दु: खद परिणाम सामने आते हैं।

लेख लेखक: एलेक्सी शफ्रानोव, काम करने वाले समूह (जावा) के नेता, मैक्सिलैक्ट

पुनश्च हमने अपने लेख रनेट की कई साइटों पर प्रकाशित किए हैं। वीके , एफबी या टेलीग्राम-चैनल पर हमारे सभी प्रकाशनों और अन्य मैक्सिलैक्ट समाचारों के बारे में जानने के लिए हमारे पेज की सदस्यता लें।

Source: https://habr.com/ru/post/hi447380/


All Articles