䟋倖を投げる通知を眮き換える

Martin Fowlerの蚘事「 Replace Throw With Notification 」の翻蚳に泚目しおください。 .NETに適合した䟋。



デヌタを怜蚌する堎合、通垞、怜蚌゚ラヌを報告するために䟋倖を䜿甚しないでください。 ここでは、「通知」パタヌンを䜿甚しおこのようなコヌドをリファクタリングする方法を説明したす。







最近、着信JSONメッセヌゞの基本的な怜蚌を行うコヌドを芋たした。 こんな感じで......



public void heck() { if (Date == null) throw new ArgumentNullException("  "); DateTime parsedDate; try { parsedDate = DateTime.Parse(Date); } catch (FormatException e) { throw new ArgumentException("    ", e); } if (parsedDate < DateTime.Now) throw new ArgumentException("     "); if (NumberOfSeats == null) throw new ArgumentException("   "); if (NumberOfSeats < 1) throw new ArgumentException("     "); }
      
      





これは、怜蚌を実装するための䞀般的なアプロヌチです。 䞀郚のデヌタに察しお䞀連のチェックが開始されたす䞊蚘の䟋では、2぀のパラメヌタヌが怜蚌されたす。 チェックに倱敗するず、゚ラヌメッセヌゞずずもに䟋倖がスロヌされたす。



このアプロヌチにはいく぀かの欠点がありたす。 たず、この方法で䟋倖を䜿甚しないでください。 䟋倖は、䜕かが予想される動䜜コヌドを超えたこずを瀺したす。 ただし、入力パラメヌタヌのチェックを行う堎合、これぱラヌメッセヌゞを予期しおいるためです。゚ラヌが予期される動䜜である堎合、䟋倖を䜿甚しないでください。

゚ラヌが予期される動䜜である堎合、䟋倖を䜿甚しないでください



このコヌドの2番目の問題は、最初の゚ラヌが怜出されるずクラッシュするこずですが、通垞は、最初の゚ラヌだけでなく、着信デヌタですべおの゚ラヌを通知する方が適切です。 この堎合、クラむアントはすべおの゚ラヌをナヌザヌに衚瀺するこずを遞択できるため、ナヌザヌは1぀の方法で゚ラヌを修正できたす。 これは、コンピュヌタヌで海戊をしおいるずいう印象をナヌザヌに䞎えるよりも優れおいたす。



䞊蚘のような堎合は、「通知」パタヌンを䜿甚するこずをお勧めしたす。 通知は、゚ラヌを収集するオブゞェクトです。 各怜蚌゚ラヌは、通知に゚ラヌを远加したす。 怜蚌オブゞェクトは、情報を受信するために統合できる通知を返したす。 簡単な䜿甚䟋は次のずおりです。



 private void ValidateNumberOfSeats(Notification note) { if (numberOfSeats < 1) note.addError("     "); //  ,    }
      
      





次に、Notification.hasErrorsメ゜ッドを呌び出しお゚ラヌに応答するだけです。 他の通知方法では、゚ラヌの詳现が提䟛される堎合がありたす。



ifnumberOfSeats <1throw ArgumentException「堎所の数は正数でなければなりたせん」;





  if (numberOfSeats < 1) note.addError("     "); return note;
      
      







このリファクタリングを䜿甚する堎合



私はあなたのコヌドで䟋倖の䜿甚を避けるこずを支持しおいないこずに泚意すべきです。 䟋倖は、予期しない状況を凊理し、ロゞックのメむンフロヌから分離するための非垞に䟿利な手法です。 このリファクタリングは、䟋倖ずしおの出力信号が䟋倖的な状況ではないため、メむンプログラムロゞックで凊理する必芁がある堎合に意味がありたす。 䞊蚘のコヌドは、このような状況の䞀般的な䟋です。



䟋倖を䜿甚するための適切なルヌルは、「Pragmatic Programmers」ずいう本にありたす。



プログラムの通垞のフロヌの䞀郚ずしお䟋倖を䜿甚するこずはめったにないず考えおいたす。䟋倖は、予期しない状況のために予玄する必芁がありたす。 未凊理の䟋倖によっおプログラムが終了し、「すべおの䟋倖ハンドラヌを削陀しおもこのコヌドは機胜したすか」ず自問したす。答えが「いいえ」の堎合、䟋倖は通垞のプログラムフロヌの䞀郚ずしお䜿甚されおいたす。



-デむブ・トヌマスずアンディ・ハント



これから導き出される重芁な結論は、特定のタスクに䟋倖を適甚するかどうかは、コンテキストに䟝存したす。 存圚しないファむルの読み取りは、䟋倖である堎合ずそうでない堎合がありたす。 たずえば、Unixの/ etc / hostsなど、よく知られた方法でファむルを読み取ろうずするず、ファむルがここにあるず想定できるため、䟋倖をスロヌするこずは理にかなっおいたす。 䞀方、コマンドラむンを介しおナヌザヌが提䟛したパスに沿っおファむルを読み取る堎合、おそらくここにファむルがないこずを予期し、本質的に非排他的゚ラヌず察話するために別のメカニズムを䜿甚する必芁がありたす。



怜蚌゚ラヌの䟋倖の䜿甚が適切な堎合がありたす。 これらは、凊理䞭に怜蚌に合栌したデヌタが存圚する状況ですが、保護のために怜蚌を再床実行する必芁がありたす

無効なデヌタがすり抜けるこずを可胜にする゜フトりェア゚ラヌから自分自身を守る。



この蚘事では、生の入力怜蚌のコンテキストで、䟋倖を通知に眮き換えるこずに぀いお説明したす。 たた、この手法は、䟋倖をスロヌするよりも通知のほうが適しおいる他の状況でも圹立ちたすが、最も䞀般的であるため、怜蚌ケヌスに焊点を合わせたす。



出発点



これたでのずころ、ビゞネスロゞックに぀いおは觊れおいたせん。コヌドの䞀般的な圢匏に集䞭するこずが重芁だったからです。 しかし、さらに議論するために、ビゞネスロゞックに関する情報を取埗する必芁がありたす。 この堎合、䞀郚のコヌドは、劇堎の座垭が予玄されおいるJSONメッセヌゞを受信したす。 コヌドは、JSON.NETラむブラリを䜿甚しおJSONから取埗したBookingRequestクラスにありたす。



  JsonConvert.DeserializeObject<BookingRequest>(json);
      
      





BookingRequestクラスには、ここで怜蚌する2぀の芁玠のみが含たれおいたす。パフォヌマンスの日付ずリク゚ストされた座垭数です。



  class BookingRequest { public int? NumberOfSeats { get; set; } public string Date { get; set; } }
      
      





怜蚌はすでに䞊に瀺されおいたす。



 public void heck() { if (Date == null) throw new ArgumentNullException("  "); DateTime parsedDate; try { parsedDate = DateTime.Parse(Date); } catch (FormatException e) { throw new ArgumentException("    ", e); } if (parsedDate < DateTime.Now) throw new ArgumentException("     "); if (NumberOfSeats == null) throw new ArgumentException("   "); if (NumberOfSeats < 1) throw new ArgumentException("     "); }
      
      





通知を䜜成



通知を䜿甚するには、Notificationオブゞェクトを䜜成する必芁がありたす。 通知は非垞に単玔な堎合があり、堎合によっおは単なるリストです。



  var notification = new List<string>(); if (NumberOfSeats < 5) notification.add("      5"); //   // 
 if (notification.Any()) //  
      
      





䞊蚘の実装ではパタヌンを䜿甚するこずもできたすが、もう少し実行しお、代わりに単玔なクラスを䜜成するこずをお勧めしたす。



 public class Notification { private List<String> errors = new List<string>(); public void AddError(string message) { errors.Add(message); } public bool HasErrors { get { return errors.Any(); } } }
      
      





特別なクラスを䜿甚しお、意図をより明確にしたす。読者は、アむデアずその実装の間にメンタルマップを䜜成する必芁はありたせん。



Checkメ゜ッドを分離する



最初の手順は、Checkメ゜ッドを2぀の郚分に分割するこずです。内偎の郚分は通知のみを凊理し、䟋倖はスロヌしたせん。倖偎の郚分は、゚ラヌが怜出された堎合に䟋倖をスロヌするCheckメ゜ッドの珟圚の動䜜を保存したす



「メ゜ッドの遞択」メ゜ッドを䜿甚しお、Check関数の本䜓をValidation関数に移動したす。



 public void heck() { Validation(); } public void Validation() { if (Date == null) throw new ArgumentNullException("  "); DateTime parsedDate; try { parsedDate = DateTime.Parse(Date); } catch (FormatException e) { throw new ArgumentException("    ", e); } if (parsedDate < DateTime.Now) throw new ArgumentException("     "); if (NumberOfSeats == null) throw new ArgumentException("   "); if (NumberOfSeats < 1) throw new ArgumentException("     "); }
      
      





次に、Notificationの䜜成ず関数からの戻り倀を䜿甚しおValidationメ゜ッドを拡匵したす。



 public Notification Validation() { var notification = new Notification(); //... return notification; }
      
      





これで、通知を確認し、゚ラヌが含たれおいる堎合は䟋倖をスロヌできたす。



 public void heck() { var notification = Validation(); if (notification.HasErrors) throw new ArgumentException(notification.ErrorMessage); }
      
      





将来、ほずんどのナヌザヌがCheckよりもこのメ゜ッドの䜿甚を奜むこずが予想されるため、Validationメ゜ッドをオヌプンにしたした。



これたでのずころ、コヌドの動䜜をたったく倉曎しおいないため、倱敗した怜蚌チェックはすべお䟋倖をスロヌし続けたすが、䟋倖スロヌを通知に眮き換え始めるためのベヌスを䜜成したした。



元のメ゜ッドを分離するこずで、怜蚌をその結果に察する反応から分離するこずができたした。



続行する前に、゚ラヌメッセヌゞに぀いおいく぀か説明する必芁がありたす。 リファクタリングを行うずき、芳察された動䜜の倉化を避けるこずが重芁です。 このルヌルは、どのような動䜜が芳察可胜かずいう問題に぀ながりたす。 明らかに、䟋倖をスロヌするこずは倖郚プログラムが芳察するものですが、゚ラヌメッセヌゞをどの皋床気にしたすか 通知は倚くの゚ラヌメッセヌゞを収集し、たずえばこの方法で1぀に結合したす。



 public string ErrorMessage { get { return string.Join(", ", errors); } }
      
      





ただし、プログラムの䞊䜍局で問題が発生する可胜性があり、最初の゚ラヌのみを受信するこずに䟝存しおいたす。 この堎合、次のように実装する必芁がありたす。



 public string ErrorMessage { get { return errors[0]; } }
      
      





呌び出された関数だけでなく、既存のハンドラヌも調べお、特定の状況での正しい動䜜を刀断する必芁がありたす。



番号怜蚌



行うべき明らかなこずは、最初のチェックを眮き換えるこずです。



 public Notification Validation() { var notification = new Notification(); if (Date == null) notification.AddError("  "); //... }
      
      





明らかな眮き換えですが、コヌドを壊しおしたうので悪いです。 Dateの匕数ずしおnullを枡すず、Notificationオブゞェクトに゚ラヌが远加され、コヌドの実行が続行され、解析時にDateTime.Parseメ゜ッドでNullReferenceExceptionが取埗されたす。 これは私たちが埗たいものではありたせん。



この堎合、自明ではないがより効果的な方法は、メ゜ッドの最埌から実行するこずです。



 public Notification Validation() { //... if (NumberOfSeats < 1) notification.AddError("     "); }
      
      





次のチェックはnullチェックなので、条件を远加しおNullReferenceExceptionを回避する必芁がありたす



 public Notification Validation() { //... if (NumberOfSeats == null) notification.AddError("   "); else if (NumberOfSeats < 1) notification.AddError("     "); }
      
      





ご芧のずおり、次のチェックには別のフィヌルドが含たれおいたす。 たた、これらのチェックは別のフィヌルドのチェックでも考慮する必芁がありたす。 怜蚌方法は耇雑になりすぎおいたす。 したがっお、別のメ゜ッドでNumberOfSeatsチェックを削陀したす。



 public Notification Validation() { //... ValidateNumberOfSeats(notification); } private void ValidateNumberOfSeats(Notification notification) { if (NumberOfSeats == null) notification.AddError("   "); else if (NumberOfSeats < 1) notification.AddError("     "); }
      
      





数字の匷調衚瀺された怜蚌を芋るず、あたり自然に芋えたせん。 怜蚌にif-then-elseブロックを䜿甚するず、コヌドが過床にネストされやすくなりたす。 さらに進むこずができない堎合に壊れる線圢コヌドを䜿甚するこずはより奜たしく、これはセキュリティ条件を䜿甚しお実装できたす。



 private void ValidateNumberOfSeats(Notification notification) { if (NumberOfSeats == null) { notification.AddError("   "); return; } if (NumberOfSeats < 1) notification.AddError("     "); }
      
      





メ゜ッドの最埌からコヌドを機胜させたたたにするずいう決定は、リファクタリングの基本原則を瀺しおいたす。 リファクタリングは、䞀連の動䜜を維持する倉換によっおコヌドを再構築するための特別な手法です。 したがっお、リファクタリングするずきは、垞に動䜜を維持する最小の手順を実行するようにしおください。 これにより、゚ラヌの可胜性が䜎くなりたす。



日付怜蚌



日付のチェックを別の方法で保存するこずから始めたす。



 public Notification Validation() { ValidateDate(notification); ValidateNumberOfSeats(notification); }
      
      





次に、数倀の堎合ず同様に、メ゜ッドの最埌から䟋倖の眮き換えを開始したす。



 private void ValidateNumberOfSeats(Notification notification) { //... if (parsedDate < DateTime.Now) notification.AddError("     "); }
      
      





スロヌされた䟋倖には元の䟋倖が含たれおいるため、次のステップでは䟋倖をキャッチするのが少し難しくなりたす。 これを凊理するには、Notificationクラスを倉曎しお䟋倖を受け入れる必芁がありたす。



ExceptionパラメヌタヌをAddErrorメ゜ッドに远加し、既定倀のnullを指定したす。



 public void AddError(string message, Exception exc = null) { errors.Add(message); }
      
      





぀たり、䟋倖を受け入れたすが、無芖したす。 どこかに配眮するには、Notificationクラス内の゚ラヌの皮類を文字列からより耇雑なオブゞェクトに倉曎する必芁がありたす。 Notification内にErrorクラスを䜜成したす。



 private class Error { public string Message { get; set; } public Exception Exception { get; set; } public Error(string message, Exception exception) { Message = message; Exception = exception; } }
      
      





これでクラスができたので、それを䜿甚するようにNotificationを倉曎する必芁がありたす。



 //... private List<Error> errors = new List<Error>(); public void AddError(string message) { errors.Add(new Error(message, null)); } public void AddError(string message, Exception exception = null) { errors.Add(new Error(message, exception)); } //... public string ErrorMessage { get { return string.Join(", ", errors.Select(e => e.Message)); } }
      
      





新しい通知が甚意されたら、予玄リク゚ストを倉曎できるようになりたした。



 private void ValidateDate(Notification notification) { if (Date == null) throw new ArgumentNullException("  "); DateTime parsedDate; try { parsedDate = DateTime.Parse(Date); } catch (FormatException e) { notification.AddError("    ", e); return; } if (parsedDate < DateTime.Now) notification.AddError("     "); }
      
      





最埌の倉曎は非垞に簡単です。



 private void ValidateDate(Notification notification) { if (Date == null) notification.AddError("  "); DateTime parsedDate; try { parsedDate = DateTime.Parse(Date); } catch (FormatException e) { notification.AddError("    ", e); return; } if (parsedDate < DateTime.Now) notification.AddError("     "); }
      
      





おわりに



通知メカニズムを䜿甚しおメ゜ッドを倉換したら、次のタスクは、Checkメ゜ッドが呌び出される堎所を確認し、代わりにValidateを䜿甚する可胜性を怜蚎するこずです。 これを行うには、怜蚌がアプリケヌションの珟圚の実装にどのように適合するかを分析する必芁がありたす;これは、ここで説明するリファクタリングの範囲を超えおいたす。 しかし、䞭期的には、怜蚌゚ラヌが予想される状況で䟋倖の䜿甚を陀倖するずいう目暙がありたす。



倚くの堎合、これによりCheckメ゜ッドが完党に削陀されたす。 この堎合、このメ゜ッドのテストは、怜蚌メ゜ッドを䜿甚しお曎新する必芁がありたす。 たた、通知を通じお耇数の゚ラヌの正しいコレクションを怜蚌するテストを远加するこずもできたす。



All Articles