أين هو الخطأ ، بيلي؟ نحن بحاجة إلى خطأ ...


منذ بعض الوقت ، نشر زميلي مقالًا عن معالجة الأخطاء في Java / Kotlin. وأصبح من المثير للاهتمام بالنسبة لي ما هي طرق انتقال الخطأ الموجودة في البرمجة بشكل عام. إذا كنت مهتمًا أيضًا ، فحينئذٍ ما يكون البحث نتيجة البحث. على الأرجح ، تم حذف بعض الأساليب الغريبة ، ولكن هنا يوجد أمل واحد فقط للتعليقات ، والتي تكون في بعض الأحيان أكثر إثارة للاهتمام ومفيدة في مقالة هابري. :)

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

إخلاء المسئولية: لإيجاز وتبسيط الإدراك لأي كود تنفيذي معزول يولد خطأً ، سأستخدم كلمة "دالة" ، وأي كيانات غير بدائية (عدد صحيح ، سلسلة ، منطقية ، إلخ ...) - "بنية".

عودة مباشرة


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

  1. عودة حالة التشغيل. الخيار الأكثر شيوعًا هو TRUE (إذا تم تنفيذه بشكل طبيعي) أو FALSE (إذا كان هناك فشل).
  2. إرجاع القيمة الصحيحة في حالة النجاح وغير صحيحة في حالة الخطأ.
    C / c ++
    ترجع الدالة strchr () مؤشرًا إلى التواجد الأول للحرف ch في السلسلة المشار إليها بواسطة str. إذا لم يتم العثور على ch ، يتم إرجاع NULL.

    في كثير من الأحيان ، يتم استخدام النهج 1 و 2 بالتزامن مع تحديد الحالة.
  3. إرجاع رمز الخطأ. إذا كنا لا نريد أن نعرف فقط أن التنفيذ قد انتهى بشكل غير صحيح ، ولكن أيضًا لفهم مكان حدوث الخطأ في الوظيفة. عادة ، إذا تم إكمال الوظيفة دون خطأ ، يتم إرجاع الرمز 0. في حالة وجود خطأ ، يتم استخدام الرمز لتحديد مكان معين في نص الوظيفة حيث حدث خطأ ما. لكن هذه ليست قاعدة حديدية ، انظر ، على سبيل المثال ، على HTTP مع 200.
  4. إرجاع رمز الخطأ في نطاق غير صالح من القيم. على سبيل المثال ، عادةً ما يجب أن ترجع الدالة عددًا صحيحًا موجبًا ، وفي حالة حدوث خطأ ، يكون الكود الخاص به بعلامة ناقص.

    function countElements(param) { if (!isArray(param)) { return -10; } else if(!isInitialized(param)){ return -20 } else { return count(array); } } 

  5. إرجاع أنواع مختلفة للنتائج الإيجابية والسلبية. على سبيل المثال ، اسميا - سلسلة ، ولكن ليس اسميا - رقم أو فئة النجاح وخطأ الفئة.

     sealed class UserProfileResult { data class Success(val userProfile: UserProfileDTO) : UserProfileResult() data class Error(val message: String, val cause: Exception? = null) : UserProfileResult() } val avatarUrl = when (val result = client.requestUserProfile(userId)) { is UserProfileResult.Success -> result.userProfile.avatarUrl is UserProfileResult.Error -> "http://domain.com/defaultAvatar.png" } 

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

     function doSomething(): array { ... if($somethingWrong === true) { return ["result" => null, "error" => "Alarm!!!"]; } else { return ["result" => $result, "error" => null]; } ... } 

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

     f, err := Sqrt(-1) if err != nil { fmt.Println(err) } 

وضع الدولة


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

  1. وضع الدولة "العالمية". أخذتها في اقتباسات لأننا نتحدث غالبًا عن العالمية في نطاق معين.

     # ls /unknown/path 2>/dev/null # echo $? 1 

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

     $mysqli = new mysqli("localhost", "my_user", "my_password", "world"); $result = $mysqli->query("SET a=1"); if ($mysqli->errno) { printf(" : %d\n", $mysqli->errno); } 

  3. تحديد حالة الكائن المرتجع. أصداء قوية مع الفقرة 6. من القسم السابق. على عكس الفقرة السابقة ، يتم إجراء فحص الحالة على الهيكل المرتجع ، وليس على الوحدة التي توفر الوظيفة. كمثال واضح ، بروتوكول HTTP والمكتبات التي لا تعد ولا تحصى في مجموعة واسعة من اللغات تعمل معها.

     Response response = client.newCall("https://www.google.com").execute(); Integer errorCode = response.getCode(); 


نقل السيطرة


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

  1. الاستثناءات. الجميع يعرف رمي / محاولة / الصيد. عند رمي استثناء ، تشكل الوظيفة بنية تصف الخطأ ، وفي معظم الأحيان ، تحتوي على بيانات تعريف مفيدة عديدة تسهل تشخيص المشكلة (على سبيل المثال ، مكدس الاستدعاءات). بعد ذلك ، يتم تمرير هذه البنية إلى آلية خاصة "تتدحرج" على طول مكدس الاستدعاء إلى كتلة المحاولة الأولى ، المقترنة بـ catch ، والتي يمكنها معالجة الاستثناءات من هذا النوع. تعتبر هذه الطريقة جيدة حيث يتم تطبيق منطق إلقاء استثناء كامل بواسطة بيئة التنفيذ نفسها. الشيء نفسه سيء ​​، لأن التكاليف العامة (دعنا فقط دون holivarov :)).
  2. معالجات الأخطاء العامة. ليست الطريقة الأكثر شيوعًا ، لكنها كذلك. أنا لا أعرف حتى ما أقول هنا. ربما تجدر الإشارة إلى أنه يمكن هنا أيضًا عزو آليات المتصفحات: عندما يراقب الكود الذي يعمل بعيدًا عن التيار الرئيسي الأحداث التي تصل إليه.

     function myErrorHandler($errno, $errstr, $errfile, $errline) { echo "<b>Custom error:</b> [$errno] $errstr<br>"; echo " Error on line $errline in $errfile<br>"; } set_error_handler("myErrorHandler"); 

  3. رد. إنهم محبوبون للغاية من قِبل مطوري Android و JavaScript ومعتذري البرمجة التفاعلية. الجوهر بسيط: بالإضافة إلى البيانات التي تتم معالجتها ، يتم نقل وظائف المعالج إلى الوظيفة. في حالة وجود خطأ ، ستقوم الوظيفة الرئيسية باستدعاء المعالج المقابل وتمرير الخطأ إليه.

     var observer = Rx.Observer.create( x => console.log(`onNext: ${x}`), e => console.log(`onError: ${e}`), () => console.log('onCompleted')); 


يبدو أنه لم ينس شيئًا.

وحقيقة مضحكة. ربما الطريقة الأكثر أصالة لإرجاع خطأ ، والجمع في نفس الوقت الاستثناءات ، وتحديد الحالة وإرجاع العديد من القيم ، التقيت في Informix SPL (أكتب من الذاكرة):

 CREATE PROCEDURE some_proc(...) RETURNING int, int, int, int; … ON EXCEPTION SET SQLERR, ISAMERR RETURN 0, SQLERR, ISAMERR, USRERR; END EXCEPTION; LET USRERR = 1; -- do Something That May Raise Exception LET USRERR = 2; -- do Something Other That May Raise Exception … RETURN result, 0, 0, 0; END PROCEDURE 

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


All Articles