खेत

खेत

सीखने का अभ्यास

परिचय

एरर इंटरफेस

Go में एरर हैंडलिंग के लिए एक्सेप्शन का इस्तेमाल नहीं किया जाता। इसके बजाय, एरर उन टाइप की सामान्य वैल्यू होती हैं जो बिल्ट-इन error इंटरफेस को लागू करते हैं। error इंटरफेस बहुत छोटा है। इसमें सिर्फ एक मेथड Error() है, जो एरर का संदेश एक स्ट्रिंग के रूप में लौटाता है।

type error interface {
  Error() string
}

अगर ऐसा फंक्शन बना रहे हैं जिसमें चलते समय कोई एरर हो सकती है और वह एरर कॉल करने वाले तक पहुँचनी चाहिए, तो रिटर्न टाइप में error भी शामिल करना ज़रूरी है। अगर फंक्शन एक से अधिक वैल्यू लौटाता है, तो परंपरा के मुताबिक error हमेशा सबसे बाद में रहता है।

func DoSomething() (int, error) {
  // ...
}

साधारण एरर बनाना और लौटाना

एरर इंटरफेस हमेशा खुद लागू करना ज़रूरी नहीं है। एक साधारण एरर बनाने के लिए आप स्टैंडर्ड लाइब्रेरी पैकेज errors का फंक्शन errors.New() इस्तेमाल कर सकते हैं। इसमें सिर्फ एरर का संदेश स्ट्रिंग के रूप में देना होता है, और errors.New() खुद ही एक ऐसी वैल्यू बना देता है जिसमें आपका संदेश होता है और जो error इंटरफेस को लागू करती है।

अगर फंक्शन एरर लौटाता है, तो बाकी सभी रिटर्न पैरामीटर के लिए शून्य वैल्यू लौटाना अच्छा तरीका माना जाता है:

func DoSomething() (SomeStruct, int, error) {
  // ...
  return SomeStruct{}, 0, errors.New("failed to calculate result")
}
Caution

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

अगर ऐसी साधारण एरर आपको कई जगहों पर चाहिए, तो errors.New को सीधे जहाँ-तहाँ लिखने के बजाय एरर के लिए एक वेरिएबल घोषित कीजिए। परंपरा के मुताबिक वेरिएबल का नाम Err या err से शुरू होना चाहिए (यह इस पर निर्भर करता है कि वह एक्सपोर्टेड है या नहीं)। ऐसे एरर वेरिएबलो को अक्सर सेंटिनल एरर कहा जाता है।

import "errors"

var ErrNotFound = errors.New("resource was not found")

func DoSomething() error {
  // ...
  return ErrNotFound
}

अगर फंक्शन के चलने के दौरान कोई एरर नहीं हुई, तो यह बताने के लिए एरर की जगह nil लौटाइए:

func Foo() (int, error) {
  return 10, nil
}

एरर की जाँच

जब आप कोई ऐसा फंक्शन कॉल करते हैं जो एरर लौटाता है, तो आम तौर पर एरर की वैल्यू err नाम के वेरिएबल में रखी जाती है। फंक्शन के वास्तविक नतीजे का उपयोग करने से पहले आपको जाँच लेना चाहिए कि कोई एरर नहीं थी।

अपने कोड के "हैपी पाथ" को और अंदर तक नेस्ट होने से बचाने के लिए एरर वाले मामलों को पहले निपटा देना चाहिए। एरर की nil से तुलना करने के लिए हम == और != इस्तेमाल कर सकते हैं, और जब err की वैल्यू nil न हो तो हमें पता चल जाता है कि कोई एरर हुई थी।

func processUserFile() error {
	file, err := os.Open("./users.csv")
	if err != nil {
		return err
	}

	// do something with file
}

ज़्यादातर मामलों में एरर ऊपर दिए उदाहरण की तरह फंक्शन स्टैक में ऊपर तक लौटा दी जाती है। एरर को संभालने का एक और तरीका यह हो सकता है कि उसे लॉग में लिख दिया जाए और फिर कोई और काम जारी रखा जाए। अच्छा तरीका यही है कि एरर को या तो लौटाया जाए या लॉग में लिखा जाए, दोनों कभी नहीं।

चूँकि Go में ज़्यादातर फंक्शन अपनी रिटर्न वैल्यू में एरर भी शामिल करते हैं, इसलिए Go कोड में आपको हर जगह if err != nil पैटर्न दिखेगा और आप उसका इस्तेमाल भी करेंगे।

कस्टम एरर टाइप

अगर आप चाहते हैं कि आपकी एरर में सिर्फ एरर संदेश वाली स्ट्रिंग से ज़्यादा जानकारी हो, तो आप एक कस्टम एरर टाइप बना सकते हैं। जैसा पहले बताया गया, Go में हर वह चीज़ एरर का काम कर सकती है जो error इंटरफेस को लागू करती है (यानी जिसमें Error() string मेथड हो)।

कस्टम एरर टाइप बनाने के लिए आम तौर पर स्ट्रक्ट का उपयोग किया जाता है। परंपरा के मुताबिक कस्टम एरर टाइप के नाम के अंत में Error होना चाहिए। साथ ही, Error() string मेथड को पॉइंटर रिसीवर के साथ बनाना सबसे अच्छा होता है; इसके पीछे की वजह जानने के लिए यह Stackoverflow टिप्पणी देखिए। ध्यान रखिए, इसका मतलब है कि आपको अपनी कस्टम एरर का पॉइंटर लौटाना होगा, वरना वह error की तरह नहीं गिनी जाएगी, क्योंकि बिना पॉइंटर वाली वैल्यू Error() string मेथड नहीं देती।

type MyCustomError struct {
  message string
  details string
}

func (e *MyCustomError) Error() string {
  return fmt.Sprintf("%s, details: %s", e.message, e.details)
}

func someFunction() error {
  // ...
  return &MyCustomError{
    message: "...",
    details: "...",
  }
}

निर्देश

जिस दिन का आपको बहुत समय से इंतज़ार था, वह अंततः आ गया। अब आप आल्प्स में एक सुंदर खेत के गर्वित मालिक हैं।

आपको अब भी सुबह जल्दी उठकर अपनी गायों को चारा खिलाना पसंद नहीं है। चूँकि आप एक बेहतरीन इंजीनियर हैं, इसलिए आप एक फूड डिस्पेंसर बनाते हैं, जिसका नाम FEED-M-ALL है।

अपना प्रोजेक्ट पूरा करने के लिए अब बस एक चीज़ बाकी है: ऐसा कोड जो हिसाब लगाए कि हर गाय को कितना चारा मिलना चाहिए। यह ज़रूरी है कि हर गाय को बराबर मात्रा मिले, इसलिए झगड़ों से बचना होगा। गायें बहुत संवेदनशील होती हैं।

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

type FodderCalculator interface {
	FodderAmount(int) (float64, error)
	FatteningFactor() (float64, error)
}

जैसे-जैसे आप अपने कोड पर काम करेंगे, आप एरर हैंडलिंग को बेहतर बनाएँगे, ताकि जब आप इसका इस्तेमाल रोज़ के खेत के काम में करें, तब यह ज़्यादा मज़बूत रहे और इसे डीबग करना आसान हो।

1. भोजन बराबर बाँटिए

सबसे पहले आप वह कोड लिखने पर ध्यान देते हैं, जो हर गाय के लिए चारे की मात्रा निकालने के लिए ज़रूरी है।

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

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

अगर आपके कॉल किए गए मेथड में से कोई एक एरर लौटाता है, तो आगे का कोड चलना रुक जाना चाहिए और वही एरर DivideFood फंक्शन से (जैसी है वैसी) लौटाई जानी चाहिए।

// For this example, we assume FodderAmount returns 50
// and FatteningFactor returns 1.5.
DivideFood(fodderCalculator, 5)
// => 15 <nil>

// Now assuming FodderAmount returns an error with message "something went wrong".
DivideFood(fodderCalculator, 5)
// => 0 "something went wrong"

2. गायों की संख्या जाँचिए

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

ऐसा फंक्शन ValidateInputAndDivideFood लिखिए, जिसका सिग्नेचर ऊपर दिए DivideFood जैसा ही हो।

  • अगर दी गई गायों की संख्या 0 से अधिक है, तो फंक्शन को DivideFood को कॉल करना चाहिए और उस कॉल का परिणाम लौटाना चाहिए।
  • अगर गायों की संख्या 0 या उससे कम है, तो फंक्शन को "invalid number of cows" संदेश वाली एरर लौटानी चाहिए।
ValidateInputAndDivideFood(fodderCalculator, 5)
// => 15 <nil>

ValidateInputAndDivideFood(fodderCalculator, -2)
// => 0 "invalid number of cows"

3. एरर हैंडलिंग बेहतर कीजिए

गायों की संख्या आगे भेजने से पहले जाँच लेना अच्छा कदम था, लेकिन यह अस्पष्ट एरर संदेश आपको ज़्यादा अच्छा नहीं लगा। आपने सोचा कि इसे बेहतर बनाएँ, और एक कस्टम एरर टाइप बनाया जिसका नाम InvalidCowsError है।

इस कस्टम एरर में गायों की संख्या (int) और एक कस्टम संदेश (string) रखा जाना चाहिए, और Error मेथड को डेटा को इस तरह सीरियलाइज़ करना चाहिए:

{number of cows} cows are invalid: {custom message}

अपनी कस्टम एरर तैयार होने के बाद ऐसा फंक्शन ValidateNumberOfCows बनाइए जो गायों की संख्या पूर्णांक के रूप में लेता है और एक एरर (या nil) लौटाता है।

  • अगर गायों की संख्या 0 से कम है, तो फंक्शन एक InvalidCowsError लौटाता है जिसमें कस्टम संदेश "there are no negative cows" रखा गया हो।
  • अगर गायों की संख्या 0 है, तो फंक्शन एक InvalidCowsError लौटाता है जिसमें कस्टम संदेश "no cows don't need food" रखा गया हो।
  • बाकी मामलों में फंक्शन nil लौटाता है, यह बताने के लिए कि जाँच सफल रही।
err := ValidateNumberOfCows(-5)
err.Error()
// => "-5 cows are invalid: there are no negative cows"

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

GitHub के ज़रिए संपादित करें यह लिंक एक नई विंडो या टैब में खुलता है
Go Exercism

खेत शुरू करने के लिए तैयार हैं?

Exercism पर साइन अप कीजिए और Go को 34 कॉन्सेप्ट166 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।