يا لها من رحلة استثنائية

تم نشر مسودات تصميم معالجة الأخطاء الجديدة في Go 2. مؤخرًا ، ومن دواعي السرور الشديد أن اللغة لا تقف في مكان واحد - فهي تتطور وتنمو بشكل أفضل كل عام بسرعة فائقة.


الآن فقط ، في حين أن Go 2 لا تظهر إلا في الأفق ، فمن المؤلم جدًا والمحزن الانتظار. لذلك ، نأخذ الأمور بأيدينا. القليل من توليد الكود ، القليل من العمل مع ast ، ومع حركة طفيفة من الذعر اليدوي ، يتحول الذعر ... إلى استثناءات أنيقة!



وعلى الفور أريد أن أدلي ببيان مهم للغاية وجاد للغاية.
هذا القرار مسلية حصريا وطبيعية تربوية.
أعني 4 متعة فقط. هذا بشكل عام هو إثبات صحة المفهوم في الحقيقة. لقد حذرت :)

فماذا حدث


وكانت النتيجة مولدًا صغيرًا لرموز المكتبة . ومولدات الكود ، كما يعلم الجميع ، تحمل في داخلها الخير والنعمة. في الواقع لا ، ولكن في عالم Go ، فهي تحظى بشعبية كبيرة.


لقد وضعنا مثل هذا مولد الأكواد على go-raw. يقوم بتحليلها لمساعدة وحدة go/ast القياسية ، يقوم ببعض لا تحويلات ماكرة ، يتم كتابة النتيجة بجانب الملف ، مضيفا اللاحقة _jex.go . تريد الملفات الناتجة وقت تشغيل صغير للعمل.


بهذه الطريقة البسيطة ، نضيف استثناءات إلى Go.


نستخدم


نقوم بتوصيل المولد بالملف ، في الرأس (قبل package ) الذي نكتبه


 //+build jex //go:generate jex 

إذا قمت الآن بتشغيل الأمر go generate -tags jex ، فسيتم تنفيذ الأداة المساعدة jex . تأخذ اسم الملف من os.Getenv("GOFILE") ، os.Getenv("GOFILE") وتكتب {file}_jex.go . يحتوي الملف الوليد بالفعل على //+build !jex في الرأس (العلامة مقلوبة) ، لذلك go build ، وفي المقصورة التي بها الأوامر الأخرى ، مثل go test أو go install ، تأخذ في الاعتبار فقط الملفات الجديدة والصحيحة. ليبوتا ...


الآن github.com/anjensan/jex استيراد نقطة.
نعم ، في حين أن الاستيراد من خلال نقطة إلزامي. في المستقبل من المخطط أن تترك نفسها.


 import . "github.com/anjensan/jex" 

رائع ، الآن يمكنك إدراج المكالمات إلى وظائف كعب الروتين TRY ، THROW ، EX في التعليمات البرمجية. لكل هذا ، يظل الرمز صالحًا من الناحية النحوية ، وحتى يتم تجميعه في شكل غير معالج (لا يعمل فقط) ، لذا فإن الإكمال التلقائي متاح ولا تنتفخ الوبر حقًا . سيظهر المحررون أيضًا وثائق لهذه الوظائف ، إذا كان لديهم وظيفة واحدة فقط.


رمي استثناء


 THROW(errors.New("error name")) 

أمسك الاستثناء


 if TRY() { //   } else { fmt.Println(EX()) } 

يتم إنشاء وظيفة مجهولة تحت غطاء المحرك. وفي ذلك defer . ولها وظيفة أخرى. وفيه recover ... حسنًا ، لا يزال هناك القليل من السحر للتعامل مع return defer .


ونعم ، بالمناسبة ، هم مدعومون!


بالإضافة إلى ذلك ، هناك ERR متغير ماكرو خاص. إذا قمت بتعيين خطأ له ، يتم طرح استثناء. من السهل استدعاء الوظائف التي لا تزال تعرض error بالطريقة القديمة


 file, ERR := os.Open(filename) 

بالإضافة إلى ذلك ، هناك زوجان من أكياس المرافق الصغيرة ex must ، ولكن ليس هناك الكثير للحديث عنه.


أمثلة


في ما يلي مثال على رمز Go الاصطلاحي الصحيح


 func CopyFile(src, dst string) error { r, err := os.Open(src) if err != nil { return fmt.Errorf("copy %s %s: %v", src, dst, err) } defer r.Close() w, err := os.Create(dst) if err != nil { return fmt.Errorf("copy %s %s: %v", src, dst, err) } if _, err := io.Copy(w, r); err != nil { w.Close() os.Remove(dst) return fmt.Errorf("copy %s %s: %v", src, dst, err) } if err := w.Close(); err != nil { os.Remove(dst) return fmt.Errorf("copy %s %s: %v", src, dst, err) } } 

هذا الرمز ليس لطيفًا وأنيقًا. بالمناسبة ، هذا ليس رأيي فقط!
لكن jex ستساعدنا على تحسينه.


 func CopyFile_(src, dst string) { defer ex.Logf("copy %s %s", src, dst) r, ERR := os.Open(src) defer r.Close() w, ERR := os.Create(dst) if TRY() { ERR := io.Copy(w, r) ERR := w.Close() } else { w.Close() os.Remove(dst) THROW() } } 

ولكن على سبيل المثال ، البرنامج التالي


 func main() { hex, err := ioutil.ReadAll(os.Stdin) if err != nil { log.Fatal(err) } data, err := parseHexdump(string(hex)) if err != nil { log.Fatal(err) } os.Stdout.Write(data) } 

يمكن إعادة كتابته كـ


 func main() { if TRY() { hex, ERR := ioutil.ReadAll(os.Stdin) data, ERR := parseHexdump(string(hex)) os.Stdout.Write(data) } else { log.Fatal(EX()) } } 

هنا مثال آخر من أجل الشعور بالفكرة المقترحة بشكل أفضل. الكود الأصلي


 func printSum(a, b string) error { x, err := strconv.Atoi(a) if err != nil { return err } y, err := strconv.Atoi(b) if err != nil { return err } fmt.Println("result:", x + y) return nil } 

يمكن إعادة كتابته كـ


 func printSum_(a, b string) { x, ERR := strconv.Atoi(a) y, ERR := strconv.Atoi(b) fmt.Println("result:", x + y) } 

أو حتى ذلك


 func printSum_(a, b string) { fmt.Println("result:", must.Int_(strconv.Atoi(a)) + must.Int_(strconv.Atoi(b))) } 

استثناء


خلاصة القول هي بنية مجمعة بسيطة عبر مثيل error .


 type exception struct { //  ,   err error //  ^W , ,    log []interface{} //      ,    suppress []*exception } 

نقطة مهمة - لا يُنظر إلى نوبات الهلع العادية على أنها استثناءات. لذا ، فإن جميع الأخطاء القياسية مثل runtime.TypeAssertionError ليست استثناء. يتماشى هذا مع أفضل الممارسات المقبولة في Go - إذا كان لدينا ، على سبيل المثال ، عدم الإشارة ، فإننا نسقط العملية برمتها بمرح وبهجة. موثوق ويمكن التنبؤ به. على الرغم من أنني لست متأكدًا ، ربما يجدر مراجعة هذه اللحظة والتقاط مثل هذه الأخطاء. ربما اختياري؟


وإليك مثال على سلسلة الاستثناء


 func one_() { THROW(errors.New("one")) } func two_() { THROW(errors.New("two") } func three() { if TRY() { one_() } else { two_() } } 

هنا نتعامل بهدوء مع الاستثناء one ، كما هو الحال فجأة ... two طرح الاستثناء two . لذلك ، one إرفاق المصدر suppress به في حقل suppress . لن يضيع شيء ، كل شيء سيذهب إلى السجلات. لذلك ، ليست هناك حاجة خاصة لدفع سلسلة الأخطاء بالكامل مباشرة في نص الرسالة باستخدام نمط fmt.Errorf("blabla: %v", err) الشائع جدًا fmt.Errorf("blabla: %v", err) . على الرغم من أنه لا أحد ، بالطبع ، لا يمنع استخدامه هنا ، إذا كنت تريد ذلك حقًا.


عندما نسيت أن يمسك


آه ، نقطة أخرى مهمة للغاية. من أجل زيادة قابلية القراءة ، هناك فحص إضافي: إذا كانت الوظيفة يمكن أن تطلق استثناء ، فيجب أن ينتهي اسمها بـ _ . اسم ملتوي عمداً سيخبر المبرمج "سيدي العزيز ، هنا في برنامجك قد يحدث خطأ ما ، يرجى توخي الحذر والاجتهاد!"


يبدأ التحقق تلقائيًا للملفات المحولة ، بالإضافة إلى أنه يمكن أيضًا بدء تشغيله يدويًا في مشروع باستخدام الأمر jex-check . ربما يكون من المنطقي تشغيله كجزء من عملية البناء إلى جانب الوبر الأخرى.


تم //jex:nocheck التحقق من التعليق //jex:nocheck . هذه ، بالمناسبة ، هي الطريقة الوحيدة لرمي الاستثناءات من وظيفة مجهولة.


بالطبع ، هذه ليست حلا سحريا لجميع المشاكل. المدقق سيفتقد هذا


 func bad_() { THROW(errors.New("ups")) } func worse() { f := bad_ f() } 

من ناحية أخرى ، فإنه ليس أسوأ بكثير من الفحص القياسي err declared and not used ، وهو أمر سهل التحايل عليه.


 func worse() { a, err := foo() if err != nil { return err } b, err := bar() //  ,    ok... go vet, ? } 

بشكل عام ، هذا السؤال فلسفي إلى حد ما ، ما الأفضل القيام به عندما نسيت معالجة الخطأ - تجاهله بهدوء ، أو التخلص من الذعر ... بالمناسبة ، يمكن تحقيق أفضل نتائج الاختبار من خلال تنفيذ دعم الاستثناء في المترجم ، ولكن هذا أبعد بكثير من نطاق هذه المقالة .


قد يقول البعض أنه على الرغم من أن هذا حل رائع ، إلا أنه لم يعد استثناءً ، لأن الاستثناءات الآن تعني تنفيذًا محددًا للغاية. حسنًا ، هناك ، نظرًا لأن آثار المكدس غير مرتبطة بالاستثناءات ، أو أن هناك لنتر منفصل لفحص أسماء الوظائف ، أو أن الوظيفة يمكن أن تنتهي بـ _ ولكن لا ترمي الاستثناءات ، أو لا يوجد دعم مباشر في بناء الجملة ، أو أنه ذعر حقًا ، الذعر ليس استثناء على الإطلاق ، لأن الزنبق ... يمكن أن تكون الجراثيم ساخنة مثل لا قيمة لها ولا معنى لها. لذلك ، سأتركهم خلف لوحة المقالة ، وسأواصل استدعاء الحل الموصوف بشكل غير انتقائي يسمى "الاستثناءات".


حول التكديس


في كثير من الأحيان ، من أجل تبسيط التصحيح ، يلتزم المطورون بتتبع مكدس لعمليات تنفيذ error المخصصة. حتى أن هناك العديد من المكتبات الشعبية لهذا الغرض. ولكن ، لحسن الحظ ، مع وجود استثناءات ، لا يتطلب هذا أي إجراءات إضافية بسبب ميزة واحدة مثيرة للاهتمام في Go - أثناء الذعر ، defer تنفيذ كتل التأجيل في سياق المكدس للرمز الذي ألقى الذعر. لذلك هنا


 func foo_() { THROW(errors.New("ups")) } func bar() { if TRY() { foo_() } else { debug.PrintStack() } } 

ستتم طباعة تتبع مكدس كامل ، وإن كان قليلاً مطولًا (قمت بقص أسماء الملفات)


  runtime/debug.Stack runtime/debug.PrintStack main.bar.func2 github.com/anjensan/jex/runtime.TryCatch.func1 panic main.foo_ main.bar.func1 github.com/anjensan/jex/runtime.TryCatch main.bar main.main 

لا يضر أن تجعل المساعد الخاص بك لتنسيق / طباعة تتبع مكدس ، مع مراعاة الوظائف البديلة وإخفائها لإمكانية قراءتها. أعتقد فكرة جيدة ، كتب في.


أو يمكنك الحصول على المكدس وإرفاقه بالاستثناء باستخدام ex.Log() . ثم ، يُسمح بنقل هذا الاستثناء إلى horoutin آخر - لا تضيع strextraces.


 func foobar_() { e := make(chan error, 1) go func() { defer close(e) if TRY() { checkZero_() } else { EX().Log(debug.Stack()) //   e <- EX().Wrap() //     } }() ex.Must_(<-e) //  ,  ,  } 

لسوء الحظ


إيه ... بالطبع ، شيء من هذا القبيل سيبدو أفضل بكثير


  try { throw io.EOF, "some comment" } catch e { fmt.Printf("exception: %v", e) } 

ولكن للأسف ، فإن بنية Go غير قابلة للتوسيع.
[مدروس] على الرغم من أنه ربما للأفضل ...


على أي حال ، عليك أن تنحرف. كان من الأفكار البديلة تقديم


  TRY; { THROW(io.EOF, "some comment") }; CATCH; { fmt.Printf("exception: %v", EX) } 

لكن مثل هذا الرمز يبدو غبيًا نوعًا ما بعد go fmt . ويقسم المترجم عندما يرى return في كلا الفرعين. لا توجد مثل هذه المشكلة مع if-TRY .


سيكون من الرائع استبدال ماكرو ERR بوظيفة MUST (أفضل من مجرد). من أجل الكتابة


  return MUST(strconv.Atoi(a)) + MUST(strconv.Atoi(b)) 

من حيث المبدأ ، لا يزال هذا ممكنًا ، عند تحليل ast ، يمكنك اشتقاق نوع التعبيرات ، لأن جميع أنواع الأنواع تولد وظيفة مجمعة بسيطة ، مثل تلك المعلنة في الحزمة must ، ثم استبدال MUST باسم وظيفة بديلة مقابلة. هذا ليس تافهًا تمامًا ، ولكنه ممكن تمامًا ... لن يتمكن سوى المحررين / IDE من فهم مثل هذا الرمز. بعد كل شيء ، فإن توقيع وظيفة كعب الروتين MUST لا يكون واضحًا في نظام نوع Go. وبالتالي لا يوجد إكمال تلقائي.


تحت غطاء المحرك


تتم إضافة استيراد جديد إلى جميع الملفات التي تمت معالجتها.


  import _jex "github.com/anjensan/jex/runtime" 

THROW استبدال المكالمة panic(_jex.NewException(...)) مع panic(_jex.NewException(...)) . EX() استبدال EX() أيضًا باسم المتغير المحلي الذي يحتوي على الاستثناء الذي تم اكتشافه.


ولكن if TRY() {..} else {..} أكثر تعقيدًا. أولاً ، تحدث معالجة خاصة لجميع عمليات return defer . ثم تتم معالجتها إذا تم وضع الفروع في وظائف مجهولة. ثم يتم تمرير هذه الوظائف إلى _jex.TryCatch(..) . ها هي


 func test(a int) (int, string) { fmt.Println("before") if TRY() { if a == 0 { THROW(errors.New("a == 0")) } defer fmt.Printf("a = %d\n", a) return a + 1, "ok" } else { fmt.Println("fail") } return 0, "hmm" } 

يتحول إلى شيء مثل هذا (أزلت //line comments):


 func test(a int) (_jex_r0 int, _jex_r1 string) { var _jex_ret bool fmt.Println("before") var _jex_md2502 _jex.MultiDefer defer _jex_md2502.Run() _jex.TryCatch(func() { if a == 0 { panic(_jex.NewException(errors.New("a == 0"))) } { _f, _p0, _p1 := fmt.Printf, "a = %d\n", a _jex_md2502.Defer(func() { _f(_p0, _p1) }) } _jex_ret, _jex_r0, _jex_r1 = true, a+1, "ok" return }, func(_jex_ex _jex.Exception) { defer _jex.Suppress(_jex_ex) fmt.Println("fail") }) if _jex_ret { return } return 0, "hmm" } 

الكثير ، ليست جميلة ، لكنها تعمل. حسنًا ، ليس كل شيء وليس دائمًا. على سبيل المثال ، لا يمكنك إجراء defer-recover المؤجل داخل TRY ، نظرًا لأن استدعاء الوظيفة يتحول إلى لامدا إضافية.


أيضا ، عند عرض شجرة ast ، يشار إلى خيار "حفظ التعليقات". لذا ، من الناحية النظرية ، يجب أن go/printer الطباعة ... ما يفعله بصدق ، والحقيقة هي ملتوية للغاية =) لن أعطي أمثلة ، فقط ملتوية. من حيث المبدأ ، تكون هذه المشكلة قابلة للحل تمامًا إذا قمت بتحديد المواضع بعناية لجميع العقد ast (الآن فارغة) ، ولكن هذا بالتأكيد غير مدرج في قائمة الأشياء الضرورية للنموذج الأولي.


جرب


بدافع الفضول ، كتبت مقياسًا صغيرًا.


لدينا تنفيذ qsort خشبي يتحقق من التكرارات في الحمل. وجدت - خطأ. أحد الإصدارات fmt.Errorf ببساطة من خلال return err ، ويوضح الآخر الخطأ عن طريق استدعاء fmt.Errorf . وآخر يستخدم الاستثناءات. نقوم بفرز شرائح بأحجام مختلفة ، إما بدون تكرار على الإطلاق (لا يوجد خطأ ، يتم فرز الشريحة بالكامل) ، أو مع تكرار واحد (يتم فصل الفرز في منتصف الطريق تقريبًا ، ويمكن رؤيته بالتوقيتات).


النتائج
 ~ > cat /proc/cpuinfo | grep 'model name' | head -1 model name : Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz ~ > go version go version go1.11 linux/amd64 ~ > go test -bench=. github.com/anjensan/jex/demo goos: linux goarch: amd64 pkg: github.com/anjensan/jex/demo BenchmarkNoErrors/_____10/exception-8 10000000 236 ns/op BenchmarkNoErrors/_____10/return_err-8 5000000 255 ns/op BenchmarkNoErrors/_____10/fmt.errorf-8 5000000 287 ns/op BenchmarkNoErrors/____100/exception-8 500000 3119 ns/op BenchmarkNoErrors/____100/return_err-8 500000 3194 ns/op BenchmarkNoErrors/____100/fmt.errorf-8 500000 3533 ns/op BenchmarkNoErrors/___1000/exception-8 30000 42356 ns/op BenchmarkNoErrors/___1000/return_err-8 30000 42204 ns/op BenchmarkNoErrors/___1000/fmt.errorf-8 30000 44465 ns/op BenchmarkNoErrors/__10000/exception-8 3000 525864 ns/op BenchmarkNoErrors/__10000/return_err-8 3000 524781 ns/op BenchmarkNoErrors/__10000/fmt.errorf-8 3000 561256 ns/op BenchmarkNoErrors/_100000/exception-8 200 6309181 ns/op BenchmarkNoErrors/_100000/return_err-8 200 6335135 ns/op BenchmarkNoErrors/_100000/fmt.errorf-8 200 6687197 ns/op BenchmarkNoErrors/1000000/exception-8 20 76274341 ns/op BenchmarkNoErrors/1000000/return_err-8 20 77806506 ns/op BenchmarkNoErrors/1000000/fmt.errorf-8 20 78019041 ns/op BenchmarkOneError/_____10/exception-8 2000000 712 ns/op BenchmarkOneError/_____10/return_err-8 5000000 268 ns/op BenchmarkOneError/_____10/fmt.errorf-8 2000000 799 ns/op BenchmarkOneError/____100/exception-8 500000 2296 ns/op BenchmarkOneError/____100/return_err-8 1000000 1809 ns/op BenchmarkOneError/____100/fmt.errorf-8 500000 3529 ns/op BenchmarkOneError/___1000/exception-8 100000 21168 ns/op BenchmarkOneError/___1000/return_err-8 100000 20747 ns/op BenchmarkOneError/___1000/fmt.errorf-8 50000 24560 ns/op BenchmarkOneError/__10000/exception-8 10000 242077 ns/op BenchmarkOneError/__10000/return_err-8 5000 242376 ns/op BenchmarkOneError/__10000/fmt.errorf-8 5000 251043 ns/op BenchmarkOneError/_100000/exception-8 500 2753692 ns/op BenchmarkOneError/_100000/return_err-8 500 2824116 ns/op BenchmarkOneError/_100000/fmt.errorf-8 500 2845701 ns/op BenchmarkOneError/1000000/exception-8 50 33452819 ns/op BenchmarkOneError/1000000/return_err-8 50 33374000 ns/op BenchmarkOneError/1000000/fmt.errorf-8 50 33705994 ns/op PASS ok github.com/anjensan/jex/demo 64.008s 

إذا لم يتم إلقاء الخطأ (الرمز ثابت وخرسانة مقواة) ، فإن الضمان مع استثناء الاستثناء يمكن مقارنته تقريبًا return err و fmt.Errorf . في بعض الأحيان أسرع قليلاً. ولكن إذا تم طرح الخطأ ، فإن الاستثناءات تأتي في المركز الثاني. ولكن كل هذا يتوقف على نسبة "العمل / الخطأ المفيد" وعمق المكدس. بالنسبة للشرائح الصغيرة ، فإن return err الفجوة ؛ بالنسبة للشرائح المتوسطة والكبيرة ، فإن الاستثناءات تساوي بالفعل إعادة التوجيه اليدوي.


باختصار ، إذا حدثت أخطاء نادرًا جدًا ، فقد تؤدي الاستثناءات إلى تسريع الشفرة قليلاً. إذا كان مثل أي شخص آخر ، فسيكون شيئًا كهذا. ولكن إذا كانت في كثير من الأحيان ... فإن الاستثناءات البطيئة بعيدة كل البعد عن أهم مشكلة ، والتي تستحق القلق بشأنها.


كاختبار ، قمت بترحيل مكتبة غوش حقيقية للاستثناءات.


للأسف الشديد ، لم تنجح إعادة كتابة 1 في 1

بتعبير أدق ، كان من الممكن أن يتحول ، ولكن يجب إزعاج هذا.


لذا ، على سبيل المثال ، يبدو أن وظيفة rpc2XML تُرجع error ... نعم ، فهي لا rpc2XML أبدًا. إذا حاولت إجراء تسلسل لنوع بيانات غير مدعوم - لا يوجد خطأ ، فقط قم بإخراج فارغ. ربما هذا ما كان يقصد؟ .. لا ، الضمير لا يسمح بتركه هكذا. أضيف بواسطة


  default: THROW(fmt.Errorf("unsupported type %T", value)) 

ولكن اتضح أن هذه الوظيفة تستخدم بطريقة خاصة


 func rpcParams2XML(rpc interface{}) (string, error) { var err error buffer := "<params>" for i := 0; i < reflect.ValueOf(rpc).Elem().NumField(); i++ { var xml string buffer += "<param>" xml, err = rpc2XML(reflect.ValueOf(rpc).Elem().Field(i).Interface()) buffer += xml buffer += "</param>" } buffer += "</params>" return buffer, err } 

هنا نراجع قائمة المعلمات ، ونسلسلها جميعًا ، لكننا نرجع خطأً للأخيرة فقط . يتم تجاهل الأخطاء المتبقية. أصبح السلوك الغريب أسهل


 func rpcParams2XML_(rpc interface{}) string { buffer := "<params>" for i := 0; i < reflect.ValueOf(rpc).Elem().NumField(); i++ { buffer += "<param>" buffer += rpc2XML_(reflect.ValueOf(rpc).Elem().Field(i).Interface()) buffer += "</param>" } buffer += "</params>" return buffer } 

إذا لم ينجح حقل واحد على الأقل في إجراء تسلسل - خطأ. حسنًا ، هذا أفضل. ولكن اتضح أن هذه الوظيفة تستخدم أيضًا بطريقة خاصة .


 xmlstr, _ = rpcResponse2XML(response) 

مرة أخرى ، بالنسبة لشفرة المصدر ، هذا ليس مهمًا جدًا ، لأنه يتم تجاهل الأخطاء. لقد بدأت في تخمين سبب حب بعض المبرمجين لمعالجة الأخطاء الصريحة من خلال if err != nil ... ولكن مع بعض الاستثناءات ، لا يزال إعادة التوجيه أو المعالجة أسهل من تجاهلها


 xmlstr = rpcResponse2XML_(response) 

ولم أبدأ بإزالة "سلسلة الأخطاء". هذا هو الرمز الأصلي


 func DecodeClientResponse(r io.Reader, reply interface{}) error { rawxml, err := ioutil.ReadAll(r) if err != nil { return FaultSystemError } return xml2RPC(string(rawxml), reply) } 

هنا هو إعادة الكتابة


 func DecodeClientResponse_(r io.Reader, reply interface{}) { var rawxml []byte if TRY() { rawxml, ERR = ioutil.ReadAll(r) } else { THROW(FaultSystemError) } xml2RPC_(string(rawxml), reply) } 

هنا لن يتم فقدان الخطأ الأصلي (الذي عاد ioutil.ReadAll ) ، سيتم إرفاقه بالاستثناء في حقل ioutil.ReadAll . مرة أخرى ، يمكن القيام بذلك كما في الأصل ، ولكن يجب الخلط بينه بشكل خاص ...


أقوم بإعادة كتابة الاختبارات ، واستبدال if err != nil { log.Error(..) } برمية استثناء بسيطة. هناك نقطة سلبية - تقع الاختبارات على الخطأ الأول ، ولا تستمر في العمل "بشكل جيد ، على الأقل بطريقة أو بأخرى". وفقا للعقل ، سيكون من الضروري تقسيمها إلى اختبارات فرعية ... ما الذي يستحق القيام به بشكل عام على أي حال. ولكن من السهل جدًا الحصول على المكدس الصحيح


 func errorReporter(t testing.TB) func(error) { return func(e error) { t.Log(string(debug.Stack())) t.Fatal(e) } } func TestRPC2XMLConverter_(t *testing.T) { defer ex.Catch(errorReporter(t)) // ... xml := rpcRequest2XML_("Some.Method", req) } 

بشكل عام ، من السهل تجاهل الأخطاء. في القانون الأصلي


 func fault2XML(fault Fault) string { buffer := "<methodResponse><fault>" xml, _ := rpc2XML(fault) buffer += xml buffer += "</fault></methodResponse>" return buffer } 

هنا rpc2XML تجاهل الخطأ من rpc2XML مرة أخرى بهدوء. لقد أصبح مثل هذا


 func fault2XML(fault Fault) string { buffer := "<methodResponse><fault>" if TRY() { buffer += rpc2XML_(fault) } else { fmt.Printf("ERR: %v", EX()) buffer += "<nil/>" } buffer += "</fault></methodResponse>" return buffer } 

وفقًا لمشاعري الشخصية ، من الأسهل إرجاع نتيجة "نصف منتهية" مع وجود أخطاء.
على سبيل المثال ، استجابة نصف مبنية. الاستثناءات أكثر تعقيدًا ، نظرًا لأن الدالة إما تُرجع نتيجة ناجحة أو لا تُرجع شيئًا على الإطلاق. نوع من الذرية. من ناحية أخرى ، من الصعب تجاهل الاستثناءات أو فقدان السبب الجذري في سلسلة الاستثناءات. بعد كل شيء ، لا يزال عليك محاولة القيام بذلك على وجه التحديد. مع الأخطاء ، يحدث هذا بسهولة وبشكل طبيعي.


بدلا من الاستنتاج


عند كتابة هذا المقال ، لم يصب جوفر.


شكرا لك على الصورة من http://migranov.ru


لم أستطع الاختيار بين محاور "البرمجة" و "البرمجة غير الطبيعية".
تمت إضافة خيار صعب للغاية إلى كليهما.

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


All Articles