ライティングガード







こんにちは、Habr! 引数を検証する方法はいくつかあります。 たとえば、nullを確認するには、次を使用できます。







  1. if(!ReferenceEquals(arg、null))throw ...
  2. コードコントラクト :Contract.Requires(!ReferenceEquals(arg、null))
  3. Guard.IsNotNull(arg、nameof(arg))


この記事では、3番目のオプションのみを検討します(すべてのコード例はC#用ですが、そのうちのいくつかはJavaでも役立ちます)。







間違いその1:メソッド本体での引数チェックの準備ができていなかった



ほとんどの場合、プロジェクトでは、同じコードのコピーを排除するために、誰かがnull、ゼロより大きいなどのフィールドをチェックできる静的クラスを作成します。







ただし、この場合、引数の検証(この場合、引数名は追加パラメーターとして渡されます)およびメソッド内のチェック(この場合、別の例外がスローされます)にまったく同じチェックが非常に役立つことが忘れられます。







そのため、まず第一に、両方のケースの命名について事前に同意することが最善です。 たとえば、メソッド本体の場合はGuard.IsNotNull



、引数の場合はGuard.ArgumentIsNotNull









間違い2:チェックごとにstring.formatを呼び出す



すぐにエラーコードの例:







 Guard.IsNotNull(connection, $"Unable to setup connection with host {host} and port {port}") Guard.IsNotNull(connection, string.Format("Unable to setup connection with host {0} and port {1}", host, port)) //     
      
      





上記の両方の例では、チェックごとに1行が生成されます(理論上、すべてのGuardはバトルサーバーで例外をスローしません。つまり、ガベージコレクターによって食べられるだけの行を多数生成します)。







修正されたオプション:







 public static class Guard { public static void IsNotNull(object value, string format, params object[] formattingArgs) //        ,            }
      
      





実際、上記のオプションも悪いです。 文字列は作成されなくなりましたが、メソッドの呼び出しごとに新しいformatArgs配列が作成されます。 もちろん、割り当てられるメモリは少なくなりますが、そのようなメソッドはガベージコレクターをロードします。







最も厄介なことは、プログラムの速度が低下することですが、単純なプロファイリングでは問題は解決しません。 プログラムをより頻繁に停止してメモリをクリアするだけです(プログラムの1つでこのようなエラーを修正し、以前の15%ではなくガベージコレクションが占有し始めたのは5%だけです)。







したがって、 string.Formatで行われたのと同じことを行います。異なる数の引数に対してより多くのメソッドを生成します。







 public static class Guard { public static void IsNotNull(object value, string errorMessage) public static void IsNotNull(object value, string errorMessageFormat, object arg1) public static void IsNotNull(object value, string errorMessageFormat, object arg1, object arg2) public static void IsNotNull(object value, string errorMessageFormat, object arg1, object arg2, object arg3) }
      
      





したがって、配列は割り当てられません。 ただし、ボクシングという新しい問題が自動的に発生しました。

メソッド呼び出しGuard.IsNotNull(connection, "Unable to setup connection with host {0} and port {1}", host, port)



検討してください。 ポート変数はint型です(少なくともほとんどの場合)。







値によって変数を渡すために、.Netは毎回ヒープにintを作成し、それをオブジェクトとして渡します。 このような状況はそれほど一般的ではありませんが、まだ一般的です。







また、別の問題は、チェック対象の元のオブジェクトが値型である場合です(たとえば、型制限のないジェネリックメソッドでnullをチェックします)。

これは、チェック用の汎用メソッドを作成することで修正できます。







 public static class Guard { public static void IsNotNull<TObject>(TObject value, string errorMessage) public static void IsNotNull<TObject, TArg1>(TObject value, string errorMessageFormat, TArg1 arg1) public static void IsNotNull<TObject, TArg1, TArg2>(TObject value, string errorMessageFormat, TArg1 arg1, TArg2 arg2) public static void IsNotNull<TObject, TArg1, TArg2, TArg3>(TObject value, string errorMessageFormat, TArg1 arg1, TArg2 arg2, TArg3 arg3) }
      
      





エラー3:コード生成の欠如



上記からわかるように、nullの値を簡単に確認するには、次のものが必要です。







  1. 2つの関数セットを作成します-引数のチェックとメソッド本体のチェック用
  2. 各セットで-同じコードを含む複製の束を作成します


コード生成がなければ、新しい関数を追加することは比較的難しく、変更することはほとんどありません。







その他の改善



以下の点はプログラムの速度を上げるものではありませんが、読みやすさをわずかに向上させるだけです。







ReSharperアノテーション



多くの場合、ReSharperは値がnullであることを誓いますが、Guardの助けを借りてチェックされるようです。 この場合、コード内の警告を徐々に叩き始めるか(混乱する可能性があります)、検証者にすべてが正常であることを説明できます。 属性の完全なリストはここ見ることができますが、ここは便利です:









詳細情報を記録する



理論的には、バトルサーバーでの機能の例外の数は最小でなければなりません。 その結果、それらのトリガーは最大の情報を伝達する必要があります:実際に何が起こったのか(結局、状況はまれです)。 少なくとも、テキストに含めるのが最善です:







  1. 間違っていることが判明したタイプ(または、より良い-それからToStringを呼び出す)。
  2. 比較のための引数がまだある場合、それらに関する情報も
  3. 完全なスタックトレース(それ以外の場合は、例外がキャッチされた場所に切り捨てられます)


おわりに



この記事がコードの検証を比較的簡単に改善するのに役立つことを願っています。

少し前に、 ライブラリのすべての開発を実装しました(MITライセンスの下でソースコード) -それを使用するだけです(またはコードを自分にコピーするなど)。








All Articles