[DotNetBook] الاستثناءات: اكتب بنية النظام

مع هذه المقالة ، أواصل نشر سلسلة من المقالات ، ستكون نتائجه كتابًا عن عمل .NET CLR و .NET بشكل عام. للروابط - مرحبا بك في القط.


هندسة الاستثناء


ربما تكون واحدة من أهم القضايا المتعلقة بموضوع الاستثناءات هي مسألة بناء بنية استثناء في تطبيقك. هذا السؤال مثير للاهتمام لأسباب عديدة. أما بالنسبة لي ، فالشيء الرئيسي هو البساطة الواضحة التي ليس من الواضح دائمًا ما يجب فعله. هذه الخاصية متأصلة في جميع الإنشاءات الأساسية التي يتم استخدامها في كل مكان: فهي قابلة IObservable و 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; } } catch (CacheCorreptedException exception) { RecreateCache(); return GetFromCacheOrCalculate(); } } 

ودع CacheCorreptedException يكون استثناء بمعنى "ذاكرة التخزين المؤقت على القرص الصلب ليست متسقة." ثم اتضح أنه إذا كان سبب هذا الخطأ فادحًا للنظام الفرعي للتخزين المؤقت (على سبيل المثال ، لا توجد أذونات لملف ذاكرة التخزين المؤقت) ، فإن الرمز الإضافي إذا لم يتمكن من إعادة إنشاء ذاكرة التخزين المؤقت باستخدام الأمر 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 هذا الاستثناء ببساطة ، WildInvestment إلى مستوى أعلى وليس هناك حاجة للقيام بأي شيء آخر. ومع ذلك ، يرجى ملاحظة أن هناك مشكلة معمارية بحتة: الطريقة Main تستحوذ على استثناء من طبقة واحدة هندسياً من خلال استدعاء طريقة مختلفة هندسياً. كيف يبدو من حيث الاستخدام؟ نعم ، بشكل عام ، يبدو كما يلي:


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

ومع ذلك ، هناك استنتاج آخر يتبع من هذا الاستنتاج: يجب علينا ضبط catch في طريقة DoSomethingWild . وهذا غريب إلى حد ما بالنسبة لنا: يبدو أن WildInvestment تعتمد بشكل كبير على شخص ما. على سبيل المثال إذا لم يتمكن PlayRussianRoulette العمل ، فإن DoSomethingWild أيضًا: ليس لديه رموز إرجاع ، ولكن يجب أن يلعب الروليت. ماذا تفعل في مثل هذا الوضع الذي يبدو ميؤوس منه؟ الجواب بسيط في الواقع: كونه في طبقة أخرى ، يجب أن يقوم DoSomethingWild باستثناء خاص به ، والذي يشير إلى هذه الطبقة ويلف الأصل كمصدر أصلي للمشكلة - في 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 اسم استثناء ، ومن ناحية أخرى ، مساحة اسم نقطة رمي مختلفة تمامًا. بالإضافة إلى ذلك ، عند التفكير في قواعد طرح هذا الاستثناء ، ستنتقل إلى sourcesource.microsoft.com وستجد جميع الأماكن التي يتم طرحها فيها :


  • internal class System.IO.Compression.Inflater

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


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


 public class ParserException : Exception { 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 : Exception { 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 الذي تم تعريفه بالفعل في مساحة الاسم System.IO ؟ ويعني انتمائه إلى مساحة الاسم هذه أنه في جوهره يمكن استبعاد استثناء من هذا النوع من الفئات الموجودة في مساحة الاسم System.IO أو في واحدة أكثر تداخلًا. لكن الاستثناء نفسه تم طرده من مكان مختلف تمامًا ، مما أربك الباحث بالمشكلة التي نشأت. من خلال تركيز أنواع الاستثناءات على نفس مساحات الأسماء مثل الأنواع التي تطرح هذه الاستثناءات ، فإنك تحافظ على بنية النوع متسقة من ناحية ، ومن ناحية أخرى ، تجعل من السهل على المطور النهائي فهم أسباب ما حدث.


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


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

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


من خلال الجمع بين طريقتين للتجميع ، يمكننا استخلاص بعض الاستنتاجات:


  • داخل التجميع (التجميع) يجب أن يقدم النوع الأساسي من الاستثناءات التي يطرحها هذا التجميع. يجب أن يكون هذا النوع من الاستثناء في مساحة اسم الجذر للتجميع. ستكون هذه الطبقة الأولى من التجميع ؛
  • داخل التجميع نفسه ، قد يكون هناك واحد أو أكثر من مساحات الأسماء المختلفة. يقسم كل منهم التجميع إلى بعض المناطق الوظيفية ، وبالتالي تحديد مجموعات المواقف التي تنشأ في هذا التجميع. يمكن أن تكون هذه مناطق وحدات التحكم وكيانات قاعدة البيانات وخوارزميات معالجة البيانات وغيرها. بالنسبة لنا ، فإن مساحات الأسماء هذه هي مجموعة من الأنواع حسب الانتماء الوظيفي ، ومن وجهة نظر الاستثناءات ، فهي تجمع حسب مناطق المشاكل في نفس التجميع ؛
  • يمكن أن ينتقل وراثة الاستثناءات فقط من الأنواع الموجودة في نفس مساحة الاسم أو في الجذر الأكثر. وهذا يضمن فهمًا لا لبس فيه للوضع من قبل المستخدم النهائي وغياب اعتراض الاستثناءات اليسرى عند اعتراضها وفقًا للنوع الأساسي. : global::Finiki.Logistics.OhMyException , 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 wrong in parser } catch (FinancialPipeExceptionBase exception) { // Something else wrong. Looks critical because we don't know real reason } 

, : , , , XmlParserServiceException . , , , JetFinance.FinancialPipe.FinancialPipeExceptionBase , : XmlParserService , . , catch : .


?


  • . . — , : , -, UI. على سبيل المثال ;
  • , : , catch ;
  • – . ;
  • , . : , , , . , - : , — , , , ;
  • , : ;
  • , Mixed Mode c ErrorCode.


. , , :


  • unsafe , . : , (, ) ;
  • , , , .. . , , . , , . — — InnerExcepton . — ;
  • الكود الخاص بنا الذي تم إدخاله عشوائيًا في حالة غير متناسقة. تحليل النص هو مثال جيد. لا توجد تبعيات خارجية ، ولا يوجد انسحاب unsafe، ولكن هناك خطأ في التحليل.

رابط للكتاب كله



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


All Articles