بنية الحالة الاستثنائية: نقطة 2 من 4

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


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


بناءً على إمكانية نظرية للقبض على استثناء في المستقبل.


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


أولاً ، دعونا نصف المجموعة الأولى من الاستثناءات - تلك التي يجب اكتشافها.


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


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


 void SomeMethod(object argument) { try { AnotherMethod(argument); } catch (ArgumentNullException exception) { // Log it } } 

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


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


 T GetFromCacheOrCalculate() { try { if(_cache.TryGetValue(Key, out var result)) { return result; } else { T res = Strategy(Key); _cache[Key] = res; return res; } } cache (CacheCorruptedException exception) { RecreateCache(); return GetFromCacheOrCalculate(); } } 

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


بناءً على المنطقة التي يتم فيها اصطياد وضع استثنائي بالفعل


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


 namespace JetFinance.Strategies { public class WildStrategy : StrategyBase { private Random random = new Random(); public void PlayRussianRoulette() { if(DateTime.Now.Second == (random.Next() % 60)) { throw new StrategyException(); } } } public class StrategyException : Exception { /* .. */ } } namespace JetFinance.Investments { public class WildInvestment { WildStrategy _strategy; public WildInvestment(WildStrategy strategy) { _strategy = strategy; } public void DoSomethingWild() { ?try? { _strategy.PlayRussianRoulette(); } catch(StrategyException exception) { } } } } using JetFinance.Strategies; using JetFinance.Investments; void Main() { var foo = new WildStrategy(); var boo = new WildInvestment(foo); ?try? { boo.DoSomethingWild(); } catch(StrategyException exception) { } } 

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


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

ومع ذلك ، هناك استنتاج آخر ناتج عن هذا واحد: يجب علينا استخدام catch في طريقة DoSomethingWild . وهذا غريب بعض الشيء بالنسبة لنا: WildInvestment تعتمد بالكاد على شيء ما. أعني إذا لم PlayRussianRoulette ، فسيحدث نفس الشيء بالنسبة لـ DoSomethingWild : إنه لا يحتوي على رموز إرجاع ، ولكن يجب أن يلعب الروليت. لذا ، ماذا يمكننا أن نفعل في مثل هذا الوضع الذي يبدو ميئوسا منه؟ الإجابة بسيطة في الواقع: كونك على مستوى آخر ، يجب على DoSomethingWild رمي استثناء خاص بها ينتمي إلى هذا المستوى InnerException في InnerException باعتباره المصدر الأصلي للمشكلة:


 namespace JetFinance.Strategies { pubilc class WildStrategy { private Random random = new Random(); public void PlayRussianRoulette() { if(DateTime.Now.Second == (random.Next() % 60)) { throw new StrategyException(); } } } public class StrategyException : Exception { /* .. */ } } namespace JetFinance.Investments { public class WildInvestment { WildStrategy _strategy; public WildInvestment(WildStrategy strategy) { _strategy = strategy; } public void DoSomethingWild() { try { _strategy.PlayRussianRoulette(); } catch(StrategyException exception) { throw new FailedInvestmentException("Oops", exception); } } } public class InvestmentException : Exception { /* .. */ } public class FailedInvestmentException : Exception { /* .. */ } } using JetFinance.Investments; void Main() { var foo = new WildStrategy(); var boo = new WildInvestment(foo); try { boo.DoSomethingWild(); } catch(FailedInvestmentException exception) { } } 

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


استنادا إلى قضايا إعادة الاستخدام


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


عند اختيار نوع الاستثناء ، يمكننا استخدام حل تم إنشاؤه مسبقًا ، أي للعثور على استثناء بالاسم الذي يحتوي على معنى مماثل واستخدامه. على سبيل المثال ، إذا حصلنا على كيان عبر معلمة ولم نحب هذا الكيان ، فيمكننا InvalidArgumentException ، مع الإشارة إلى سبب وجود خطأ في الرسالة. يبدو هذا السيناريو جيدًا خاصة وأن InvalidArgumentException في مجموعة الاستثناءات التي قد لا يتم InvalidArgumentException . ومع ذلك ، سيكون اختيار InvalidDataException خاطئًا إذا كنت تعمل مع بعض أنواع البيانات. فذلك لأن هذا النوع يقع في منطقة System.IO ، وهو على الأرجح ليس ما تتعامل معه. وبالتالي ، سيكون من الخطأ دائمًا البحث عن نوع موجود بدلاً من تطوير نوع بنفسك. لا توجد استثناءات تقريبًا لمجموعة عامة من المهام. جميعها تقريبًا مخصصة لمواقف معينة ، وإذا قمت بإعادة استخدامها في حالات أخرى ، فسيكون ذلك انتهاكًا خطيرًا لمهارات المواقف الاستثنائية. علاوة على ذلك ، يمكن أن يخلط المستخدم بين نوع معين (على سبيل المثال ، System.IO.InvalidDataException ): من ناحية ، سيرى أن الاستثناء ينتمي إلى مساحة اسم System.IO ، بينما من ناحية أخرى يتم طرحه من مساحة اسم مختلفة تماما. إذا بدأ هذا المستخدم في التفكير في قواعد رمي هذا الاستثناء ، فقد ينتقل إلى referenceource.microsoft.com ويعثر على جميع الأماكن التي تم طرحها فيها :


  • internal class System.IO.Compression.Inflater

سوف يفهم المستخدم ذلك شخص ما هو كل الابهام هذا النوع من الاستثناء يربكه لأن الطريقة التي ألقى بها هذا الاستثناء لم تتعامل مع الضغط.


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


 public class ParserException { public ParserError ErrorCode { get; } public ParserException(ParserError errorCode) { ErrorCode = errorCode; } public override string Message { get { return Resources.GetResource($"{nameof(ParserException)}{Enum.GetName(typeof(ParserError), ErrorCode)}"); } } } public enum ParserError { MissingModifier, MissingBracket, // ... } // Usage throw new ParserException(ParserError.MissingModifier); 

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


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


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


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


 public abstract class ParserException { public abstract ParserError ErrorCode { get; } public override string Message { get { return Resources.GetResource($"{nameof(ParserException)}{Enum.GetName(typeof(ParserError), ErrorCode)}"); } } } public enum ParserError { MissingModifier, MissingBracket } public class MissingModifierParserException : ParserException { public override ParserError ErrorCode { get; } => ParserError.MissingModifier; } public class MissingBracketParserException : ParserException { public override ParserError ErrorCode { get; } => ParserError.MissingBracket; } // Usage throw new MissingModifierParserException(ParserError.MissingModifier); 

باستخدام هذا النهج نحصل على بعض الخصائص الرائعة:


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

أعتقد أنها مريحة للغاية.


بناء على الانتماء إلى مجموعة محددة من المواقف السلوكية


ما هي الاستنتاجات التي يمكن أن نستخلصها بناءً على المنطق السابق؟ دعونا نحاول تحديدها.


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


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


لنفحص الكود التالي:


 namespace JetFinance { namespace FinancialPipe { namespace Services { namespace XmlParserService { } namespace JsonCompilerService { } namespace TransactionalPostman { } } } namespace Accounting { /* ... */ } } 

ما يشبه؟ أعتقد أن مساحة الاسم هي طريقة مثالية لتجميع أنواع الاستثناءات بشكل طبيعي بناءً على المواقف السلوكية: كل شيء ينتمي إلى مجموعات معينة يجب أن يبقى هناك ، بما في ذلك الاستثناءات. علاوة على ذلك ، عندما تحصل على استثناء معين ، سترى اسم نوعه وكذلك مساحة الاسم الخاصة به التي ستحدد المجموعة التي ينتمي إليها. هل تتذكر إعادة الاستخدام غير InvalidDataException لـ InvalidDataException والتي تم تعريفها بالفعل في مساحة اسم System.IO ؟ حقيقة أنه ينتمي إلى مساحة الاسم هذه يعني أن هذا النوع من الاستثناء يمكن طرحه من الفئات الموجودة في مساحة اسم System.IO أو في مساحة أكثر تداخلًا. لكن تم الاستثناء الفعلي من مساحة مختلفة تمامًا ، مما يربك الشخص الذي يتعامل مع المشكلة. ومع ذلك ، إذا وضعت أنواع الاستثناءات والأنواع التي تطرح هذه الاستثناءات في مساحات الأسماء نفسها ، فإنك تبقي بنية الأنواع متسقة وتجعل من السهل على المطورين فهم أسباب ما يحدث.


ما هي الطريقة الثانية للتجميع على مستوى الكود؟ الميراث:


 public abstract class LoggerExceptionBase : Exception { protected LoggerException(..); } public class IOLoggerException : LoggerExceptionBase { internal IOLoggerException(..); } public class ConfigLoggerException : LoggerExceptionBase { internal ConfigLoggerException(..); } 

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


من خلال الجمع بين هاتين المجموعتين ، يمكننا أن نجعل الاستنتاجات التالية:


  • يجب أن يكون هناك نوع أساسي من الاستثناءات داخل Assembly سيتم طرحه بواسطة هذا التجميع. يجب أن يكون هذا النوع من الاستثناءات في مساحة اسم الجذر للتجميع. ستكون هذه هي الطبقة الأولى من التجميع.
  • كذلك ، يمكن أن يكون هناك واحد أو عدة مساحات أسماء داخل التجميع. يقسم كل منهم التجميع إلى مناطق وظيفية ، تحدد مجموعات المواقف ، التي تظهر في هذا التجميع. قد تكون هذه مناطق من وحدات التحكم وكيانات قواعد البيانات وخوارزميات معالجة البيانات ، إلخ. بالنسبة لنا ، تعني مساحات الأسماء هذه أنواع التجميع استنادًا إلى وظيفتها. ومع ذلك ، من حيث الاستثناءات يتم تجميعها بناءً على مشاكل داخل نفس التجميع ؛
  • يجب أن تكون الاستثناءات موروثة من أنواع في نفس مساحة الاسم المستوى الأعلى. هذا يضمن أن المستخدم النهائي سوف يفهم بشكل لا لبس فيه المواقف ولن يلاحظ الاستثناءات القائمة على النوع الخطأ . أعترف أنه سيكون من الغريب اللحاق بـ global::Finiki.Logistics.OhMyException by catch(global::Legacy.LoggerExeption exception) ، في حين أن الكود التالي يبدو مناسبًا تمامًا:

 namespace JetFinance.FinancialPipe { namespace Services.XmlParserService { public class XmlParserServiceException : FinancialPipeExceptionBase { // .. } public class Parser { public void Parse(string input) { // .. } } } public abstract class FinancialPipeExceptionBase : Exception { } } using JetFinance.FinancialPipe; using JetFinance.FinancialPipe.Services.XmlParserService; var parser = new Parser(); try { parser.Parse(); } catch (XmlParserServiceException exception) { // Something is wrong in the parser } catch (FinancialPipeExceptionBase exception) { // Something else is wrong. Looks critical because we don't know the real reason } 

هنا ، يستدعي رمز المستخدم طريقة مكتبة ، كما نعلم ، يمكنها إلقاء XmlParserServiceException في بعض الحالات. وكما نعلم ، يشير هذا الاستثناء إلى مساحة الاسم الموروثة JetFinance.FinancialPipe.FinancialPipeExceptionBase ، مما يعني أنه قد يكون هناك بعض الاستثناءات الأخرى - هذه المرة ، XmlParserService microservice XmlParserService استثناءًا واحدًا فقط ولكن قد تظهر استثناءات أخرى في المستقبل. نظرًا لأن لدينا اتفاقية لإنشاء أنواع من الاستثناءات ، فنحن نعرف الكيان الذي سيتم توريث هذا الاستثناء الجديد ونضعه في مقدمة شاملة. وهذا يمكّننا من تخطي كل الأشياء التي لا صلة لنا بها.


كيفية بناء مثل هذا التسلسل الهرمي للأنواع؟


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

بناءً على مصدر الخطأ


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


  • استدعاء رمز غير آمن مع وجود خطأ. يمكن التعامل مع هذا الموقف عن طريق التفاف استثناء أو رمز خطأ في نوع الاستثناء الخاص به أثناء حفظ البيانات التي تم إرجاعها (على سبيل المثال رمز الخطأ الأصلي) في خاصية عامة للاستثناء ؛
  • استدعاء رمز من التبعيات الخارجية ، التي ألقت استثناءات لا يمكن أن يكتشفها مكتبتنا لأنها تقع خارج نطاق مسؤوليتها. يمكن أن تتضمن هذه المجموعة استثناءات من أساليب تلك الكيانات التي تم قبولها كمعلمات للطريقة الحالية أو استثناءات من مُنشئ فئة تسمى هذه الطريقة الاعتماد الخارجي. على سبيل المثال ، تسمى طريقة لفصلنا طريقة لفئة أخرى ، تم إرجاع مثيلها عبر معلمات طريقة أخرى. إذا كان هناك استثناء يشير إلى أننا مصدر المشكلة ، فيجب علينا إنشاء استثناء خاص بنا مع الاحتفاظ بالاستثناء الأصلي في InnerExcepton . ومع ذلك ، إذا فهمنا أن المشكلة ناجمة عن تبعية خارجية ، فإننا نتجاهل هذا الاستثناء على أنه ينتمي إلى مجموعة من التبعيات الخارجية الخارجة عن سيطرتنا ؛
  • لدينا رمز الخاصة التي وضعت عن طريق الخطأ في حالة غير متناسقة. ومن الأمثلة الجيدة على ذلك تحليل النص - لا توجد تبعيات خارجية ، ولا نقل إلى عالم unsafe ، ولكن تحدث مشكلة في التحليل.

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

وأيضًا ، إذا كنت تريد شكراً منا ، فإن أفضل طريقة للقيام بذلك هي منحنا نجمًا على github أو لتخزين المستودع github / sidristij / dotnetbook .

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


All Articles