منذ بعض الوقت ، نشر زميلي
مقالًا عن معالجة الأخطاء في Java / Kotlin. وأصبح من المثير للاهتمام بالنسبة لي ما هي طرق انتقال الخطأ الموجودة في البرمجة بشكل عام. إذا كنت مهتمًا أيضًا ، فحينئذٍ ما يكون البحث نتيجة البحث. على الأرجح ، تم حذف بعض الأساليب الغريبة ، ولكن هنا يوجد أمل واحد فقط للتعليقات ، والتي تكون في بعض الأحيان أكثر إثارة للاهتمام ومفيدة في مقالة هابري. :)
في تاريخ لغات البرمجة ، لم يتم اختراع العديد من الطرق لنقل الخطأ. إذا انفصلنا تمامًا ، فهناك ثلاثة منهم فقط: عودة مباشرة من وظيفة ، ونقل السيطرة ، وتحديد الحالة. كل شيء آخر هو ، إلى حد ما أو آخر ، مزيج من هذه الأساليب. حاولت أدناه جمع ووصف الممثلين الرئيسيين لهذه الأنواع الثلاثة.
إخلاء المسئولية: لإيجاز وتبسيط الإدراك لأي كود تنفيذي معزول يولد خطأً ، سأستخدم كلمة "دالة" ، وأي كيانات غير بدائية (عدد صحيح ، سلسلة ، منطقية ، إلخ ...) - "بنية".
عودة مباشرة
العائد المباشر بسيط. على الرغم من أن هذه هي الطريقة الأكثر استخدامًا ، إلا أن هناك العديد من الخيارات. توحد طريقة المعالجة جميعها - مقارنة قيمة الإرجاع مع القيم المحددة مسبقًا.
- عودة حالة التشغيل. الخيار الأكثر شيوعًا هو TRUE (إذا تم تنفيذه بشكل طبيعي) أو FALSE (إذا كان هناك فشل).
- إرجاع القيمة الصحيحة في حالة النجاح وغير صحيحة في حالة الخطأ.
C / c ++
ترجع الدالة strchr () مؤشرًا إلى التواجد الأول للحرف ch في السلسلة المشار إليها بواسطة str. إذا لم يتم العثور على ch ، يتم إرجاع NULL.
في كثير من الأحيان ، يتم استخدام النهج 1 و 2 بالتزامن مع تحديد الحالة.
- إرجاع رمز الخطأ. إذا كنا لا نريد أن نعرف فقط أن التنفيذ قد انتهى بشكل غير صحيح ، ولكن أيضًا لفهم مكان حدوث الخطأ في الوظيفة. عادة ، إذا تم إكمال الوظيفة دون خطأ ، يتم إرجاع الرمز 0. في حالة وجود خطأ ، يتم استخدام الرمز لتحديد مكان معين في نص الوظيفة حيث حدث خطأ ما. لكن هذه ليست قاعدة حديدية ، انظر ، على سبيل المثال ، على HTTP مع 200.
- إرجاع رمز الخطأ في نطاق غير صالح من القيم. على سبيل المثال ، عادةً ما يجب أن ترجع الدالة عددًا صحيحًا موجبًا ، وفي حالة حدوث خطأ ، يكون الكود الخاص به بعلامة ناقص.
function countElements(param) { if (!isArray(param)) { return -10; } else if(!isInitialized(param)){ return -20 } else { return count(array); } }
- إرجاع أنواع مختلفة للنتائج الإيجابية والسلبية. على سبيل المثال ، اسميا - سلسلة ، ولكن ليس اسميا - رقم أو فئة النجاح وخطأ الفئة.
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" }
يمكنك أيضا أن تتذكر إما من عالم البرمجة الوظيفية. على الرغم من هنا يمكنك القول.
- إرجاع بنية تحتوي على النتيجة نفسها والخطأ.
function doSomething(): array { ... if($somethingWrong === true) { return ["result" => null, "error" => "Alarm!!!"]; } else { return ["result" => $result, "error" => null]; } ... }
- إرجاع قيم متعددة. في البداية كنت أميل إلى عدم فصل هذه الطريقة عن الطريقة السابقة ، لكن في النهاية قررت أن أضعها في فقرة منفصلة. هذا الخيار نادر جدًا ، لأنه يمكن استخدامه حصريًا في اللغات التي تسمح لك بإرجاع عدة قيم من إحدى الوظائف ، وليس هناك الكثير منها. المثال الأكثر وضوحًا ، ولكن ليس المثال الوحيد هو لغة Go .
f, err := Sqrt(-1) if err != nil { fmt.Println(err) }
وضع الدولة
أقدم وأقدم نسخة ، والتي لم تفقد أهميتها حتى يومنا هذا. تتكون في حقيقة أن الوظيفة لا تُرجع أي شيء ، وفي حالة وجود خطأ تكتب قيمتها (في أي شكل) إلى كيان منفصل ، سواء كان سجل معالج أو متغير عمومي أو حقل فئة خاصة. للتعامل مع هذا النوع من الأخطاء ، تحتاج إلى استخراج القيمة بشكل مستقل من المكان الصحيح والتحقق منها.
- وضع الدولة "العالمية". أخذتها في اقتباسات لأننا نتحدث غالبًا عن العالمية في نطاق معين.
- وضع الدولة الخاصة بك. عندما يكون لدينا بعض الهيكل توفير وظيفة. تقوم الدالة بتعيين الحالة لهذا الهيكل ، ويتم بالفعل استخراج الخطأ من البنية إما مباشرة أو باستخدام وظيفة متخصصة أخرى.
$mysqli = new mysqli("localhost", "my_user", "my_password", "world"); $result = $mysqli->query("SET a=1"); if ($mysqli->errno) { printf(" : %d\n", $mysqli->errno); }
- تحديد حالة الكائن المرتجع. أصداء قوية مع الفقرة 6. من القسم السابق. على عكس الفقرة السابقة ، يتم إجراء فحص الحالة على الهيكل المرتجع ، وليس على الوحدة التي توفر الوظيفة. كمثال واضح ، بروتوكول HTTP والمكتبات التي لا تعد ولا تحصى في مجموعة واسعة من اللغات تعمل معها.
Response response = client.newCall("https://www.google.com").execute(); Integer errorCode = response.getCode();
نقل السيطرة
والآن نأتي إلى النموذج الأكثر عصرية. الاستثناءات وعمليات الاسترجاعات ومعالجات الأخطاء العامة - كل هذا. إن ما يوحدهم جميعًا هو أنه في حالة حدوث خطأ ، يتم نقل التحكم إلى معالج محدد مسبقًا ، وليس إلى الرمز الذي يسمى الوظيفة.
- الاستثناءات. الجميع يعرف رمي / محاولة / الصيد. عند رمي استثناء ، تشكل الوظيفة بنية تصف الخطأ ، وفي معظم الأحيان ، تحتوي على بيانات تعريف مفيدة عديدة تسهل تشخيص المشكلة (على سبيل المثال ، مكدس الاستدعاءات). بعد ذلك ، يتم تمرير هذه البنية إلى آلية خاصة "تتدحرج" على طول مكدس الاستدعاء إلى كتلة المحاولة الأولى ، المقترنة بـ catch ، والتي يمكنها معالجة الاستثناءات من هذا النوع. تعتبر هذه الطريقة جيدة حيث يتم تطبيق منطق إلقاء استثناء كامل بواسطة بيئة التنفيذ نفسها. الشيء نفسه سيء ، لأن التكاليف العامة (دعنا فقط دون holivarov :)).
- معالجات الأخطاء العامة. ليست الطريقة الأكثر شيوعًا ، لكنها كذلك. أنا لا أعرف حتى ما أقول هنا. ربما تجدر الإشارة إلى أنه يمكن هنا أيضًا عزو آليات المتصفحات: عندما يراقب الكود الذي يعمل بعيدًا عن التيار الرئيسي الأحداث التي تصل إليه.
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");
- رد. إنهم محبوبون للغاية من قِبل مطوري 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;