परिचय: मानक try/catch पर्याप्त क्यों नहीं है?
\n\nKotlin में विकास, विशेष रूप से सर्वर अनुप्रयोगों या Android के संदर्भ में, अक्सर त्रुटियों के विश्वसनीय प्रबंधन की आवश्यकता का सामना करना पड़ता है। मानक try/catch ब्लॉक एक आधार है, लेकिन व्यवहार में यह तर्क को "बिखेर" देता है और परीक्षण को जटिल बना देता है। उन्नत स्तर पर, हम केवल अपवादों को पकड़ना नहीं चाहते, बल्कि सिस्टम को इस तरह डिज़ाइन करना चाहते हैं कि त्रुटियाँ डेटा प्रकार का हिस्सा हों, न कि नियंत्रण का अप्रत्याशित प्रवाह।
इस लेख में, हम try/catch से आगे बढ़कर कार्यात्मक दृष्टिकोणों का उपयोग करना सीखेंगे: Result, Either (लाइब्रेरीज़ से), और sealed class। आप जानेंगे कि कोड को पूर्वानुमानित, टाइप-सुरक्षित और आसानी से बनाए रखने योग्य कैसे बनाया जाए।
1. क्लासिक try/catch: यह अभी भी कब उपयोगी है?
\n\ntry/catch को पूरी तरह से छोड़ने की आवश्यकता नहीं है। यह लीगेसी कोड, बाहरी लाइब्रेरीज़ (जैसे JSON पार्सिंग) और सिस्टम कॉल्स (इनपुट-आउटपुट) के साथ काम करने के लिए अपरिहार्य है। हालाँकि, कुछ महत्वपूर्ण बारीकियाँ हैं।
सही संरचना और संसाधन
\nJava के विपरीत, Kotlin में चेक किए गए अपवाद नहीं हैं, लेकिन संसाधनों को स्वचालित रूप से बंद करने के लिए एक सुंदर use कंस्ट्रक्शन है:
fun readFile(path: String): String? = try {\n BufferedReader(FileReader(path)).use { it.readText() }\n} catch (e: IOException) {\n println("फ़ाइल पढ़ने में त्रुटि: ${e.message}")\n null\n}\n\nयहाँ use फ़ाइल को बंद करने की गारंटी देता है, भले ही अंदर कोई अपवाद हुआ हो। यह try-with-resources का एक सुरुचिपूर्ण विकल्प है।
समस्या: संदर्भ का नुकसान
\ntry/catch का मुख्य दोष यह है कि यह कॉल करने वाले कोड को यह नहीं बताता कि फ़ंक्शन "फेल" हो सकता है। अपवाद एक छिपा हुआ दुष्प्रभाव है। readFile() का कॉल null लौटा सकता है या IOException फेंक सकता है, लेकिन फ़ंक्शन का सिग्नेचर इसे प्रतिबिंबित नहीं करता। इससे रनटाइम में त्रुटियाँ होती हैं।
समाधान - त्रुटियों के स्पष्ट प्रतिनिधित्व पर स्विच करना है।
\n\n2. Result: सफलता या विफलता के लिए अंतर्निहित मोनाड
\n\nKotlin एक मानक क्लास Result<T> प्रदान करता है। यह एक सरल बीजगणितीय संरचना है: या तो सफलता (मान T), या विफलता (Throwable)। यह त्रुटि प्रबंधन को प्रकार प्रणाली का हिस्सा बनाता है।
मूल उपयोग
\nfun riskyDivision(a: Int, b: Int): Result<Int> = runCatching {\n a / b\n}\n\nfun main() {\n val result = riskyDivision(10, 0)\n result.onSuccess { println("परिणाम: $it") }\n .onFailure { println("त्रुटि: ${it.message}") }\n}\n\nrunCatching try/catch के ऊपर एक रैपर है जो Result लौटाता है। onSuccess और onFailure विधियाँ कॉल श्रृंखला को बाधित किए बिना परिणाम पर प्रतिक्रिया करने की अनुमति देती हैं।
रूपांतरण और श्रृंखलाएँ
\nआप map के अंदर अपवादों की चिंता किए बिना परिणाम को रूपांतरित कर सकते हैं:
\n\nval finalResult = riskyDivision(10, 2)\n .map { it * 2 } // यदि सफलता - गुणा करें\n .recover { error -> // यदि त्रुटि - आरक्षित मान लौटाएँ\n println("त्रुटि के कारण आरक्षित मान का उपयोग: ${error.message}")\n 0\n }\nprintln(finalResult.getOrDefault(-1)) // 10\n\nमहत्वपूर्ण: Result अतिरिक्त प्रसंस्करण के बिना संग्रहों में संग्रहीत करने या थ्रेड्स के बीच स्थानांतरित करने के लिए अभिप्रेत नहीं है। इन उद्देश्यों के लिए, sealed class या Arrow जैसी लाइब्रेरीज़ बेहतर उपयुक्त हैं।
3. Either: त्रुटि प्रकार को अलग करना
\n\nResult क्लास केवल Throwable को कैप्चर करता है। लेकिन अक्सर हम त्रुटियों के प्रकारों को अलग करना चाहते हैं: "नहीं मिला", "अपर्याप्त अधिकार", "सत्यापन त्रुटि"। यहाँ कार्यात्मक लाइब्रेरीज़ (जैसे Arrow या स्वयं) से Either<L, R> मदद करता है।