
لم يكن لدي أي رأي معين فيما يتعلق بمعالجة الأخطاء. إذا بدأت العمل باستخدام الكود الموجود ، واصلت أداء المهمة التي عمل عليها مؤلف المصدر ؛ إذا كتبت الكود من الصفر ، فعلت ما بدا لي حقًا.
لكن في الآونة الأخيرة ، واجهت مشكلة ، خلل تجلى بسبب خطأ "صامت" في الكود. أدركت أن هناك شيء للتفكير فيه. ربما لا يمكنني تغيير الطريقة التي أتعامل بها مع الأخطاء في قاعدة التعليمات البرمجية بأكملها التي أعمل عليها ، ولكن يمكن تحسين شيء ما بالتأكيد.
نذكرك: لجميع قراء "Habr" - خصم بقيمة 10،000 روبل عند التسجيل في أي دورة تدريبية في Skillbox باستخدام الرمز الترويجي "Habr".
توصي Skillbox بما يلي: الدورة التعليمية عبر الإنترنت "Profession Java-developer" .
لا يستحق تقطيع الكتف دائمًا
يجب أن تكون الخطوة الأولى في معالجة الأخطاء هي الفهم عندما يكون "الخطأ" ليس "خطأ". بالطبع ، كل هذا يعتمد على منطق عمل التطبيق الخاص بك ، ولكن بشكل عام ، بعض الأخطاء واضحة ويمكن إصلاحها دون مشاكل.
- هل لديك نطاق زمني حيث "قبل" قبل "من"؟ إعادة ترتيب.
- هل لديك رقم هاتف يبدأ بـ + أو يحتوي على شرطة حيث لا تتوقع ظهور أحرف خاصة؟ إزالتها.
- مشكلة جمع فارغة؟ تأكد من تهيئة هذا قبل الوصول (باستخدام التهيئة البطيئة أو المُنشئ).
لا تقاطع تنفيذ التعليمات البرمجية بسبب الأخطاء التي يمكنك إصلاحها ، وبالطبع لا تتداخل مع تصرفاتك لمستخدمي خدمتك أو تطبيقك. إذا كنت قادرًا على فهم المشكلة وحلها أثناء الطيران - فقط قم بذلك.

عودة خالية أو أرقام سحرية أخرى
القيم الصفرية ، –1 حيث يتوقع رقم موجب ، وقيم الإرجاع السحرية الأخرى - كل هذا هو نتاج الشيطان ، الذي ينقل المسؤولية عن التحقق من الأخطاء إلى وظيفة الاتصال.
معهم ، سوف يكون رمزك مليئًا بهذه الكتل التي ستجعل منطق التطبيق غير واضح:
return_value = possibly_return_a_magic_value() if return_value < 0: handle_error() else: do_something() other_return_value = possibly_nullable_value() if other_return_value is None: handle_null_value() else: do_some_other_thing()
حسنا ، أو أبسط ، ولكن أيضا سيئة:
var item = returnSomethingWhichCouldBeNull(); var result = item?.Property?.MaybeExists; if (result.HasValue) { DoSomething(); }
كما يمثل الانتقال إلى الأساليب مشكلة ، على الرغم من أنه في تعليمات بعض المطورين قد تجد أماكن تبدأ فيها الطرق بعدة سطور من التحقق من صحة الإدخال. ولكن في الواقع ، كل هذا ليس ضروريا. معظم اللغات الحديثة لا توفر واحدة ، ولكن العديد من الأدوات في وقت واحد ، والتي تتيح لك أن تشير بوضوح إلى ما تتوقعه. كما يقومون بتخطي الشيكات غير المجدية ، على سبيل المثال ، تحديد المعلمات على أنها غير صفرية أو مع الديكور المقابل.
رموز الأخطاء
هذه هي نفس المشكلة كما هو الحال مع القيم الفارغة وغيرها من القيم المماثلة عند إضافة تعقيدات التعليمات البرمجية غير الضرورية.
على سبيل المثال ، يمكنك استخدام هذا البناء لإرجاع رمز الخطأ:
int errorCode; var result = getSomething(out errorCode); if (errorCode != 0) { doSomethingWithResult(result); }
يمكن عرض النتيجة على النحو التالي:
public class Result<T> { public T Item { get; set; }
هذا هو في الحقيقة ليس أفضل رمز. اتضح أن العنصر لا يزال من الممكن أن يكون فارغًا. في الواقع ، لا يوجد أي ضمان (بخلاف الاتفاق) بأنه عندما لا تحتوي النتيجة على خطأ ، يمكنك الوصول إلى العنصر بأمان.
بعد الانتهاء من كل هذه المعالجة ، لا يزال عليك تحويل رمز الخطأ إلى رسالة خطأ والقيام بشيء ما باستخدامه. يحدث في بعض الأحيان أنه بسبب التعقيد المفرط للرمز ، لا يتم عرض هذه الرسالة نفسها كما ينبغي.
هناك مشكلة أكثر خطورة: إذا قمت أنت أو أي شخص آخر بتغيير التطبيق الداخلي للتعامل مع حالة غير صالحة جديدة برمز خطأ جديد ، فسيتوقف الهيكل بالكامل عن العمل على الإطلاق.
إذا لم ينجح ذلك في المرة الأولى ، فحاول مرة أخرى
قبل المتابعة ، يجدر التأكيد على أن فشل البرنامج "الصامت" أمر سيء. يمكن أن تحدث مشكلة غير متوقعة في أكثر اللحظات غير المناسبة - على سبيل المثال ، في عطلة نهاية الأسبوع ، عندما لا يمكنك إصلاح كل شيء بسرعة.

إذا قرأت
Clean Code ، فأنت على الأرجح تتساءل لماذا لا تتخلص من ذلك؟ إذا لم يكن الأمر كذلك ، فمن الأرجح أنك تعتقد أن الاستثناءات هي أصل الشر. اعتدت أن أفكر بذلك أيضًا ، لكنني الآن أفكر بشكل مختلف قليلاً.
هناك نقطة مثيرة للاهتمام ، على الأقل بالنسبة لي ، وهي أن التطبيق الافتراضي للطريقة الجديدة في C # هو رفع NotImplementedException ، بينما الافتراضي للطريقة الجديدة في Python هو "pass".
نتيجة لذلك ، غالبًا ما ندخل إعداد "خطأ صامت" لبرنامج Python. أتساءل عن عدد المطورين الذين قضوا الكثير من الوقت لفهم ما يحدث ولماذا لا يعمل البرنامج. لكن في النهاية ، بعد ساعات عديدة ، اكتشفوا أنهم نسوا تطبيق طريقة العنصر النائب.
لكن إلقاء نظرة على هذا:
public MyDataObject UpdateSomething(MyDataObject toUpdate) { if (_dbConnection == null) { throw new DbConnectionError(); } try { var newVersion = _dbConnection.Update(toUpdate); if (newVersion == null) { return null; } MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { throw new DbConnectionError(); } catch (MyDataObjectUnhappyException dou) { throw new MalformedDataException(); } catch (Exception ex) { throw new UnknownErrorException(); } }
بالطبع ، لن تحميك الاستثناءات وحدها من إنشاء تعليمات برمجية غير مقروءة وغير مُدارة. يجب عليك تطبيق استثناءات كاستراتيجية مدروسة جيدًا ومتوازنة. وإلا ، إذا كان المشروع كبيرًا جدًا ، فقد يكون تطبيقك في حالة غير متسقة. على العكس ، إذا كان المشروع صغيرًا جدًا ، فستحصل على فوضى.
لمنع حدوث ذلك ، أنصحك أن تضع ذلك في الاعتبار:
الاتساق قبل كل شيء. يجب عليك دائمًا التأكد من أن التطبيق ثابت. إذا كان هذا يعني أنه يجب عليك التفاف كل سطرين مع كتلة try / catch ، فما عليك سوى إخفاء كل شيء في وظيفة أخرى.
def my_function(): try: do_this() do_that() except: something_bad_happened() finally: cleanup_resource()
توحيد الأخطاء ضروري. من الجيد أن تقدم أنواعًا مختلفة من التعامل مع أنواع مختلفة من الأخطاء. ومع ذلك ، هذا هو لك ، وليس للمستخدمين. بالنسبة لهم ، قم برمي الاستثناء الوحيد حتى يعرف المستخدمون ببساطة حدوث خطأ ما. التفاصيل هي مجال مسؤوليتك.
public MyDataObject UpdateSomething(MyDataObject toUpdate) { try { var newVersion = _dbConnection.Update(toUpdate); MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { HandleDbConnectionClosed(); throw new UpdateMyDataObjectException(); } catch (MyDataObjectUnhappyException dou) { RollbackVersion(); throw new UpdateMyDataObjectException(); } catch (Exception ex) { throw new UpdateMyDataObjectException(); } }
يجدر اصطياد الاستثناءات على الفور. من الأفضل اعتراضها على أدنى مستوى. الحفاظ على متسقة وإخفاء التفاصيل يستحق كل هذا العناء كما هو موضح أعلاه. يجب ترك معالجة الأخطاء إلى المستوى العلوي من التطبيق. إذا تم تنفيذ كل شيء بشكل صحيح ، يمكنك فصل التدفق المنطقي للتطبيق نفسه عن تدفق معالجة الأخطاء. سيتيح لك ذلك كتابة رمز واضح وقابل للقراءة.
def my_api(): try: item = get_something_from_the_db() new_version = do_something_to_item(item) return new_version except Exception as ex: handle_high_level_exception(ex)
هذا كل شيء الآن ، وإذا كنت ترغب في مناقشة موضوع الأخطاء ومعالجتها - Wellcome.
توصي Skillbox بما يلي: