Golang त्रुटि हैंडलिंग सूचना
त्रुटि प्रकार
त्रुटियों को घोषित करने के लिए कुछ विकल्प हैं। अपने उपयोग मामले के लिए सबसे अच्छा विकल्प चुनने से पहले, निम्नलिखित को ध्यान में रखें:
- क्या कॉलर को त्रुटि को मैच करने की आवश्यकता है? यदि हां, तो हमें
errors.Is
याerrors.As
फ़ंक्शन का समर्थन करना होगा जिसके लिए हमें शीर्ष-स्तरीय त्रुटि चर या कस्टम प्रकार को घोषित करना होगा। - क्या त्रुटि संदेश एक स्थायी स्ट्रिंग है या संदर्भीय जानकारी की आवश्यकता है? स्थायी स्ट्रिंग के लिए, हम
errors.New
का उपयोग कर सकते हैं, लेकिन अंतिम के लिए, हमेंfmt.Errorf
या कस्टम त्रुटि प्रकार का उपयोग करना होगा। - क्या हम नीचे वहा फ़ंक्शन्स द्वारा वापस आए नई त्रुटियों को पारित कर रहे हैं? यदि हां, तो त्रुटि परिदा हेतु देखें।
त्रुटि मैच? | त्रुटि संदेश | मार्गदर्शन |
---|---|---|
नहीं | स्थायी | errors.New |
नहीं | गतिशील | fmt.Errorf |
हां | स्थायी | errors.New के साथ शीर्ष-स्तरीय var |
हां | गतिशील | कस्टम त्रुटि प्रकार |
उदाहरण के लिए, स्थायी स्ट्रिंग्स के साथ त्रुटियों को प्रस्तुत करने के लिए errors.New
का उपयोग करें। अगर कॉलर को इस त्रुटि को मैच और हैंडल करने की आवश्यकता है, तो इसे errors.Is
के साथ मैच करने के लिए निर्यात करें।
कोई त्रुटि मैच नहीं
// पैकेज foo
func Open() error {
return errors.New("could not open")
}
// पैकेज bar
if err := foo.Open(); err != nil {
// त्रुटि को हैंडल नहीं कर सकते।
panic("unknown error")
}
त्रुटि मैच
// पैकेज foo
var ErrCouldNotOpen = errors.New("could not open")
func Open() error {
return ErrCouldNotOpen
}
// पैकेज bar
if err := foo.Open(); err != nil {
if errors.Is(err, foo.ErrCouldNotOpen) {
// त्रुटि को हैंडल करें
} else {
panic("unknown error")
}
}
गतिशील स्ट्रिंग्स वाली त्रुटियों के लिए, यदि कॉलर को इसे मैच करने की आवश्यकता नहीं है, तो fmt.Errorf
का उपयोग करें। यदि कॉलर वास्तव में इसे मैच करने की आवश्यकता है, तो कस्टम त्रुटि
का उपयोग करें।
कोई त्रुटि मैच नहीं
// पैकेज foo
func Open(file string) error {
return fmt.Errorf("file %q not found", file)
}
// पैकेज bar
if err := foo.Open("testfile.txt"); err != nil {
// त्रुटि को हैंडल नहीं कर सकते।
panic("unknown error")
}
त्रुटि मैच
// पैकेज foo
type NotFoundError struct {
File string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("file %q not found", e.File)
}
func Open(file string) error {
return &NotFoundError{File: file}
}
// पैकेज bar
if err := foo.Open("testfile.txt"); err != nil {
var notFound *NotFoundError
if errors.As(err, ¬Found) {
// त्रुटि को हैंडल करें
} else {
panic("unknown error")
}
}
ध्यान दें कि यदि आप पैकेज से त्रुटि चर या प्रकार निर्यात करते हैं, तो वे पैकेज के सार्वजनिक एपीआई का हिस्सा बन जाते हैं।
त्रुटि ढक्कने
किसी अन्य तरीके को बुलाने के दौरान जब त्रुटि होती है, तो सामान्यत: उसे हैंडल करने के तीन तरीके होते हैं:
- मूल त्रुटि को वैसा ही लौटाएं।
-
fmt.Errorf
का इस्तेमाल%w
के साथ करके त्रुटि में संदर्भ जोड़ें और फिर उसे लौटाएं। -
fmt.Errorf
का इस्तेमाल%v
के साथ करके त्रुटि में संदर्भ जोड़ें और फिर उसे लौटाएं।
अगर कोई अतिरिक्त संदर्भ नहीं है, तो मूल त्रुटि को वैसा ही लौटाएं। इससे मूल त्रुटि प्रकार और संदेश को संरक्षित रखा जाएगा। यह विशेष रूप से उपत्रुटि त्रुटि संदेश में पर्याप्त जानकारी होने की स्थिति में उपयुक्त है जहाँ त्रुटि का स्रोत कहाँ से हुई है, जैसे की कहें।
अन्यथा, त्रुटि संदेश में संदर्भ जोड़ें जितनी अधिक संभव हो ताकि "कनेक्शन अस्वीकृत" जैसी अस्पष्ट त्रुटियाँ नहीं होती हैं। इसके बजाय, आपको अधिक उपयोगी त्रुटियाँ मिलेंगी, जैसे "सेवा फू को बुलाना: कनेक्शन अस्वीकृत"।
अपनी त्रुटियों में संदर्भ जोड़ने के लिए fmt.Errorf
का इस्तेमाल करें और %w
या %v
के बीच वर्ब का चयन करें जैसे ही कॉलर को मूल कारण को मिलान और निकालने की अनुमति है।
- यदि कॉलर को निचली त्रुटि तक पहुंच होनी चाहिए, तो
%w
का इस्तेमाल करें। यह बहुत सी अधिक त्रुटियों के लिए अच्छा डिफ़ॉल्ट होता है, लेकिन ध्यान दें कि कॉलर इस व्यवहार पर आधारित होने लग सकता है। इसलिए, जाने पहचाने त्रुटियों के लिए जो उपत्रुटियाँ हैं, उन्हें फ़ंक्शन समझौते का हिस्सा रिकॉर्ड और परीक्षण करें। - यदि कॉलर के द्वारा मूल त्रुटि को छिपाना है, तो
%v
का इस्तेमाल करें। कॉलर को इसे मिलना नहीं होता है, लेकिन आप आवश्यकता पर विशेषत:%w
में स्विच कर सकते हैं।
लौटाई गई त्रुटि में संदर्भ जोड़ते समय, संदर्भ संक्षेपित रखने के लिए "असफल हो गया" जैसी वाक्यांशों का इस्तेमाल न करें। जब त्रुटि स्टैक में परमीट होती है, तो यह परत से परत ढेर हो जाता है:
अनुशंसित नहीं:
s, err := store.New()
if err != nil {
return fmt.Errorf(
"failed to create new store: %w", err)
}
// failed to x: failed to y: failed to create new store: the error
सिफारिश की जाती है:
s, err := store.New()
if err != nil {
return fmt.Errorf(
"new store: %w", err)
}
// x: y: new store: the error
हालांकि, एक बार त्रुटि को किसी अन्य सिस्टम को भेज दिया गया है, तो साफ होना चाहिए कि संदेश एक त्रुटि है (जैसे लॉग में "err" टैग या "असफल" उपसर्ग।)
गलत नामकरण
ग्लोबल चर में भरे गए त्रुटि मान के लिए, प्राथमिकत: टेढ़ा Err
या err
का प्रयोग करें कि क्या वे निर्यात किए गए हैं। कृपया दिशानुसार देखें। निर्यात न होनेवाले श्रेणीबद्ध शीर्ष स्तर के स्थाई निरंतर और चरों के लिए, एक अंडरस्कोर (_) का प्रयोग करें।
var (
// निम्नलिखित दो त्रुटियों को निर्यात करें, ताकि इस पैकेज के उपयोगकर्ता उनको त्रुटियाँ.Is के साथ मेल सकें।
ErrBrokenLink = errors.New("लिंक टूट गया है")
ErrCouldNotOpen = errors.New("खोला नहीं जा सकता")
// यह त्रुटि न निर्यात की गई है क्योंकि हम नहीं चाहते कि यह हमारे सार्वजनिक API का हिस्सा हो। हालांकि हम फिर भी इसे पैकेज के भीतर त्रुटियों के साथ प्रयोग कर सकते हैं।
errNotFound = errors.New("नहीं मिला")
)
कस्टम त्रुटि प्रकारों के लिए, उत्तर प्राप्त करने के लिए सफ़िक्स त्रुटि
का प्रयोग करें।
// लगभग, इस त्रुटि को निर्यात किया गया है ताकि इस पैकेज के उपयोगकर्ता इसे त्रुटियाँ.As के साथ मिला सकें।
type NotFoundError struct {
File string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("फ़ाइल %q नहीं मिला", e.File)
}
// यह त्रुटि न हिस्सा हुई है क्योंकि हम नहीं चाहते कि यह सार्वजनिक API का हिस्सा हो। हालांकि हम फिर भी इसे पैकेज के भीतर त्रुटियों के साथ प्रयोग कर सकते हैं।
type resolveError struct {
Path string
}
func (e *resolveError) Error() string {
return fmt.Sprintf("%q को हल करें", e.Path)
}
त्रुटियों का संबोधन
कॉलर को कैली से एक त्रुटि प्राप्त होने पर, यह त्रुटि समझने के आधार पर विभिन्न तरीकों से निपट सकता है।
यह शामिल है, लेकिन इससे सीमित नहीं है:
- त्रुटि को
errors.Is
याerrors.As
के साथ मेल खाता है अगर कैली ने विशिष्ट त्रुटि परिभाषा पर सहमति दी है, और अलग-अलग शाखाओं में शाखा को हैंडल करता है - त्रुटि को लॉग करना और सजीव असमर्थ अगर त्रुटि पुनर्प्राप्त है, हल करना
- अगर यह एक डोमेन-विशिष्ट असफलता स्थिति को प्रस्तुत करता है, तो एक स्पष्ट-परिभाषित त्रुटि वापस लौटाना
- त्रुटि, समाहित या स्पष्ट, वापस लौटाना
कॉलर त्रुटि को कैसे हैंडल करता है, चाहे वो किसी भी तरीके से करता है, उसे सामान्यतः एक बार ही हैंडल करना चाहिए। उदाहरण के रूप में इसे देखें:
बुरा: त्रुटि को लॉग करके और लौटाने के और बढ़ाना
स्टैक में ऊपर के अन्य कॉलर इस त्रुटि पर समान क्रियाएँ कर सकते हैं। इससे अनुप्रयोग के लॉग में बहुत सारी अफवाहें बनाई जाएंगी जिसमें कुछ भी फायदा नहीं होगा।
u, err := getUser(id)
if err != nil {
// बुरा: विवरण देखें
log.Printf("%q उपयोगकर्ता प्राप्त नहीं कर सका: %v", id, err)
return err
}
अच्छा: त्रुटि को रैप करके और लौटाने के
स्टैक में ऊपर के कॉलर यह त्रुटि हैंडल करेंगे। %w
का उपयोग सुनिश्चित करेगा कि वे त्रुटि को errors.Is
या errors.As
के साथ मेल सकते हैं अगर उपयुक्त हो।
u, err := getUser(id)
if err != nil {
return fmt.Errorf("%q प्राप्त करने में त्रुटि: %w", id, err)
}
अच्छा: त्रुटि को लॉग करना और सजीव असमर्थ बनाना
यदि संचालन अत्यंत आवश्यक नहीं है, हम इससे सजीव असमर्थ दे सकते हैं बिना अनुभव को बाधित किए।
if err := emitMetrics(); err != nil {
// मेट्रिक्स लिखने में असफलता कोई आवेश्यक होती नहीं है
log.Printf("मेट्रिक्स छोड़ दी नहीं जा सकी: %v", err)
}
अच्छा: त्रुटि को मेल करना और सजीव असमर्थ बनाना
यदि कैली ने अपने समझौते में एक विशिष्ट त्रुटि परिभाषित की है और विफलता संशोधनीय है, तो उस त्रुटि मामला मेल खा और सजीव असमर्थ बनाएं। अन्य सभी मामलों के लिए, त्रुटि रैप करें और लौटाएं। स्टैक में ऊपर के त्रुटियां अन्य त्रुटियों को हैंडल करेंगे।
tz, err := getUserTimeZone(id)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
// उपयोगकर्ता मौजूद नहीं है। UTC का उपयोग करें।
tz = time.UTC
} else {
return fmt.Errorf("%q प्राप्त करने में त्रुटि: %w", id, err)
}
}
आधारणा विफलताओं का संबोधन
प्रकार आधारणाएं गलत प्रकार की पहचान में एक ही वापसी मूल्य के साथ विमुक्त हो जाएंगी। इसलिए हमेशा "कॉमा, ठीक है" की उपयोगिता करें।
सिफारिश नहीं:
t := i.(string)
सिफारिश:
t, ok := i.(string)
if !ok {
// त्रुटि को सजीव ढंग से हैंडल करें
पैनिक का इस्तेमाल न करें
प्रोडक्शन योग्यता में कोड चलाते समय पैनिक का इस्तेमाल बचावीहिन परिणामों का मुख्य स्रोत है। यदि कोई त्रुटि होती है, तो फ़ंक्शन को त्रुटि वापस देनी चाहिए और कॉलर को इसे कैसे संभालना है यह फैसला करने देना चाहिए।
सिफारिश नहीं:
func run(args []string) {
if len(args) == 0 {
panic("एक तारीख आवश्यक है")
}
// ...
}
func main() {
run(os.Args[1:])
}
सिफारिश किया गया है:
func run(args []string) error {
if len(args) == 0 {
return errors.New("एक तारीख आवश्यक है")
}
// ...
return nil
}
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
पैनिक/बचाव को त्रुटि संभालने का तरीका नहीं है। यह केवल तभी पैनिक होना चाहिए जब एक पुनरुत्पादनीय घटना होती है (जैसे, शून्य संदर्भ)। एक स्थिति को संरचना के दौरान प्रोग्राम को पैनिक होने के कारकों से निपटने की विशेषता चालित करती है।
var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML"))
परीक्षण कोड में भी, विफलताएँ चिह्नित करने के लिए पैनिक की बजाय t.Fatal
या t.FailNow
का प्रयोग करना अधिक अच्छा है।
सिफारिश नहीं:
// func TestFoo(t *testing.T)
f, err := os.CreateTemp("", "test")
if err != nil {
panic("परीक्षण सेटअप नहीं हो सका")
}
सिफारिश किया गया है:
// func TestFoo(t *testing.T)
f, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal("परीक्षण सेटअप नहीं हो सका")
}