swift स्विफ्ट में 'थ्रो' सुरक्षित क्यों नहीं है?




try-catch throw (2)

चुनाव एक जानबूझकर डिजाइन निर्णय है।

वे ऐसी स्थिति नहीं चाहते थे जहां आपको ऑब्जेक्ट-सी, सी ++ और सी # में अपवाद को फेंकने की घोषणा करने की आवश्यकता न हो, क्योंकि कॉल करने वाले को या तो सभी कार्यों को अपवाद मान लेना पड़ता है और अपवादों को संभालने के लिए बॉयलरप्लेट को शामिल करना पड़ता है, जो नहीं हो सकता है, या हो सकता है। अपवादों की संभावना को नजरअंदाज करें। जब आप प्रोग्राम को समाप्त करना चाहते हैं, तो इनमें से कोई भी आदर्श नहीं है और दूसरा अपवाद को छोड़ देता है, क्योंकि आप इस बात की गारंटी नहीं दे सकते हैं कि कॉल स्टैक में मौजूद प्रत्येक फ़ंक्शन में स्टैक अनवाउंड होने पर संसाधनों को सही तरीके से डीलोकॉक किया गया है।

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

हमने पार्सिंग विफलता को इंगित करने के लिए अनियंत्रित अपवादों का उपयोग करना चुना। यह मुख्य रूप से किया जाता है क्योंकि आमतौर पर क्लाइंट खराब इनपुट से उबर नहीं पाता है, और इसलिए उन्हें पकड़ने () ब्लॉक में मैला कोड में एक अपवादित अपवाद परिणामों को पकड़ने के लिए मजबूर किया जाता है।

https://github.com/google/gson/blob/master/GsonDesignDocument.md

यह एक बहुत बुरा निर्णय है। "हाय, आपको अपनी त्रुटि से निपटने के लिए भरोसा नहीं किया जा सकता है, इसलिए आपके आवेदन को इसके बजाय दुर्घटनाग्रस्त होना चाहिए"।

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

डिजाइन निर्णय के लिए पूर्ण तर्क https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst

संपादित करें

लगता है कि कुछ लोगों को मेरी कही हुई कुछ बातों से समस्या है। तो यहाँ एक स्पष्टीकरण है।

कारणों की दो व्यापक श्रेणियां हैं कि एक कार्यक्रम एक अपवाद क्यों फेंक सकता है।

  • कार्यक्रम के बाहरी वातावरण में अप्रत्याशित स्थितियां जैसे कि किसी फ़ाइल पर आईओ त्रुटि या विकृत डेटा। ये त्रुटियां हैं जो एप्लिकेशन आमतौर पर उपयोगकर्ता को त्रुटि की रिपोर्ट करके और उन्हें कार्रवाई का एक अलग कोर्स चुनने की अनुमति देकर उदाहरण के लिए संभाल सकता है।
  • प्रोग्रामिंग में त्रुटियां जैसे नल पॉइंटर या ऐर बाउंड एरर। इन्हें ठीक करने का उचित तरीका प्रोग्रामर के लिए कोड परिवर्तन करना है।

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

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

गन्स का रवैया इतना भयावह है क्योंकि वे कह रहे हैं कि आप एक पार्स त्रुटि से उबर नहीं सकते हैं (वास्तव में, इससे भी बदतर, वे आपको बता रहे हैं कि आपके पास पार्स त्रुटि से उबरने के लिए कौशल की कमी है)। यह मुखर करने के लिए एक हास्यास्पद बात है, लोग हर समय अमान्य JSON फ़ाइलों को पार्स करने का प्रयास करते हैं। क्या यह अच्छी बात है कि अगर कोई गलती से XML फाइल चुन लेता है तो मेरा प्रोग्राम क्रैश हो जाता है? नहीं है। इसे समस्या की रिपोर्ट करनी चाहिए और उन्हें एक अलग फाइल का चयन करने के लिए कहना चाहिए।

और गन्स बात, वैसे, सिर्फ एक उदाहरण है कि त्रुटियों के लिए अनियंत्रित अपवादों का उपयोग करने से आप ठीक हो सकते हैं जो खराब है। अगर मैं XML फ़ाइल का चयन करने वाले किसी व्यक्ति से पुनर्प्राप्त करना चाहता हूं, तो मुझे जावा रनटाइम अपवादों को पकड़ने की आवश्यकता है, लेकिन कौन से हैं? अच्छी तरह से मैं Gson डॉक्स में पता लगाने के लिए देख सकते हैं, यह मानते हुए कि वे सही हैं और अप टू डेट हैं। यदि वे जाँच किए गए अपवादों के साथ गए थे, तो API मुझे बताएगा कि मुझे किन अपवादों की अपेक्षा करनी है और कंपाइलर मुझे बताएंगे कि क्या मैं उन्हें नहीं संभालता।

स्विफ्ट में मेरे लिए सबसे बड़ी गलतफहमी है throws कीवर्ड। निम्नलिखित कोड पर विचार करें:

func myUsefulFunction() throws

हम वास्तव में यह नहीं समझ सकते हैं कि यह किस तरह की त्रुटि है। केवल एक चीज हमें पता है कि यह कुछ त्रुटि फेंक सकती है। यह समझने का एकमात्र तरीका है कि त्रुटि क्या हो सकती है, यह दस्तावेज़ीकरण को देखकर या रनटाइम पर त्रुटि की जांच करने के लिए है।

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

ऐसा क्यों हैं? या मैं कुछ महत्वपूर्ण याद किया और अवधारणा को गलत समझा है?


मैं स्विफ्ट में टाइप की गई त्रुटियों का प्रारंभिक प्रस्तावक था। इस तरह से स्विफ्ट टीम ने मुझे आश्वस्त किया कि मैं गलत था।

मजबूत टाइप की गई त्रुटियां उन तरीकों से नाजुक हैं जो खराब एपीआई विकास को जन्म दे सकती हैं। यदि एपीआई ठीक 3 त्रुटियों में से एक को फेंकने का वादा करता है, तो जब बाद में रिलीज में चौथी त्रुटि की स्थिति उत्पन्न होती है, तो मेरे पास एक विकल्प होता है: मैं इसे किसी तरह मौजूदा 3 में दफन कर देता हूं, या मैं हर कॉलर को अपनी त्रुटि हैंडलिंग कोड को फिर से लिखने के लिए मजबूर करता हूं इसके साथ निपटना। चूँकि यह मूल 3 में नहीं था, यह शायद बहुत सामान्य स्थिति नहीं है, और यह एपीआई पर अपनी त्रुटियों की सूची का विस्तार न करने के लिए मजबूत दबाव डालता है, विशेष रूप से एक बार जब एक रूपरेखा का लंबे समय तक व्यापक उपयोग होता है (विचार करें: फाउंडेशन )।

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

आप अभी भी कह सकते हैं "कम से कम मुझे पता है कि त्रुटि कहाँ से एक खुली दुश्मनी के साथ आती है," लेकिन यह चीजों को बदतर बनाने के लिए जाता है। कहो कि मेरे पास एक लॉगिंग सिस्टम है और यह लिखने की कोशिश करता है और एक IO त्रुटि प्राप्त करता है। इसे क्या लौटना चाहिए? स्विफ्ट में बीजगणितीय डेटा प्रकार नहीं हैं (मैं नहीं कह सकता () -> IOError | LoggingError ), इसलिए मुझे IOError को LoggingError.IO(IOError) में LoggingError.IO(IOError) (जो हर परत को स्पष्ट रूप से IOError से LoggingError.IO(IOError) मजबूर करता है; आप कर सकते हैं 'बहुत बार rethrows )। यहां तक ​​कि अगर यह IOError | MemoryError | LoggingError | UnexpectedError | ... था, क्या आप वास्तव में IOError | MemoryError | LoggingError | UnexpectedError | ... चाहते हैं IOError | MemoryError | LoggingError | UnexpectedError | ... IOError | MemoryError | LoggingError | UnexpectedError | ... IOError | MemoryError | LoggingError | UnexpectedError | ... ? एक बार जब आपके पास कुछ परतें होती हैं, तो मैं कुछ अंतर्निहित "मूल कारण" के लपेटने की परत पर परत के साथ हवा करता हूं जिससे निपटने के लिए दर्द रहित रूप से खोलना पड़ता है।

और आप इससे कैसे निपटेंगे? भारी बहुमत के मामलों में, पकड़ने वाले ब्लॉक क्या दिखते हैं?

} catch {
    logError(error)
    return
}

यह कोको के कार्यक्रमों (यानी "एप्लिकेशन") के लिए अत्यंत असामान्य है, जो त्रुटि के सटीक मूल कारण में गहराई से खोदने और प्रत्येक सटीक मामले के आधार पर विभिन्न संचालन करने के लिए है। एक या दो मामले हो सकते हैं जिनकी वसूली होती है, और बाकी चीजें ऐसी हैं जो आप वैसे भी कुछ भी नहीं कर सकते थे। (यह जावा में एक सामान्य मुद्दा है, अपवादित अपवाद केवल Exception नहीं हैं; ऐसा नहीं है कि कोई भी पहले इस रास्ते से नीचे नहीं गया है। मुझे जावा में चेक किए गए अपवादों के लिए येगोर बुगायेंको के तर्क पसंद हैं जो मूल रूप से अपने पसंदीदा जावा अभ्यास के रूप में तर्क देते हैं। स्विफ्ट समाधान।)

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

दूसरा, अगर यह उपयोग मामला महत्वपूर्ण हो जाता है (और यह हो सकता है), तो उन मामलों के लिए बाद में दृढ़ता से टाइप की गई त्रुटियों को जोड़ना मुश्किल नहीं है, जो सामान्य मामलों को तोड़ना चाहते हैं जो काफी सामान्य त्रुटि से निपटने के लिए चाहते हैं। वे सिर्फ वाक्यविन्यास जोड़ेंगे:

func something() throws MyError { }

और कॉल करने वालों को एक मजबूत प्रकार के रूप में व्यवहार करना होगा।

अंतिम रूप से, बहुत अधिक उपयोग की जाने वाली दृढ़ता से टाइप की गई त्रुटियों के लिए, फाउंडेशन को उन्हें फेंकने की आवश्यकता होगी क्योंकि यह सिस्टम में त्रुटियों का सबसे बड़ा उत्पादक है। (आप वास्तव में फाउंडेशन द्वारा तैयार किए गए सौदे की तुलना में वास्तव में खरोंच से एक NSError कैसे बनाते हैं?) जो कि फाउंडेशन का एक बड़ा ओवरहाल होगा और मौजूदा कोड और ओबजेक के साथ संगत रखने के लिए बहुत कठिन होगा। इसलिए टाइप की गई त्रुटियों को डिफ़ॉल्ट व्यवहार के रूप में विचार करने लायक होने के लिए बहुत ही सामान्य कोको समस्याओं को हल करने में बिल्कुल शानदार होने की आवश्यकता होगी। यह सिर्फ थोड़ा अच्छा नहीं हो सकता है (अकेले ऊपर वर्णित समस्याएं हैं)।

तो इसमें से कोई भी यह नहीं कहना है कि सभी मामलों में त्रुटि से निपटने के लिए अप्रयुक्त त्रुटियां 100% सही समाधान हैं। लेकिन इन तर्कों ने मुझे आश्वस्त किया कि आज स्विफ्ट में जाने का सही तरीका था।





throw