メソッド入力パラメーターに検証制約を設定するにはどうすればよいですか?
-
05-07-2019 - |
質問
この目標を達成する一般的な方法は次のとおりです。
public void myContractualMethod(final String x, final Set<String> y) {
if ((x == null) || (x.isEmpty())) {
throw new IllegalArgumentException("x cannot be null or empty");
}
if (y == null) {
throw new IllegalArgumentException("y cannot be null");
}
// Now I can actually start writing purposeful
// code to accomplish the goal of this method
この解決策は見苦しいと思います。メソッドは、有効な入力パラメーターコントラクトをチェックするボイラープレートコードですぐにいっぱいになり、メソッドの中心がわかりにくくなります。
ここに私が持ちたいものがあります:
public void myContractualMethod(@NotNull @NotEmpty final String x, @NotNull final Set<String> y) {
// Now I have a clean method body that isn't obscured by
// contract checking
これらの注釈がJSR 303 / Bean Validation Specのように見える場合、それは私がそれらを借りたためです。残念ながら、彼らはこのように動作しないようです。インスタンス変数に注釈を付けてから、バリデーターを介してオブジェクトを実行するためのものです。
多くのJavaの設計ごとのフレームワーク私の「欲しい」に最も近い機能を提供します。例?スローされる例外はランタイム例外(IllegalArgumentExceptionsなど)である必要があるため、カプセル化は解除されません。
解決
完全な契約による設計メカニズムを探しているなら、 DBCのウィキペディアページ。
ただし、もっと簡単なものを探している場合は、前提条件クラス。checkNotNull()メソッドを提供します。投稿したコードを書き換えることができます:
public void myContractualMethod(final String x, final Set<String> y) {
checkNotNull(x);
checkArgument(!x.isEmpty());
checkNotNull(y);
}
他のヒント
Eric Burke による次のようなテクニックを見ました。これは、静的インポートのエレガントな使用法です。コードは非常に読みやすくなっています。
アイデアを得るために、ここに Contract
クラスがあります。ここでは最小限ですが、必要に応じて簡単に入力できます。
package net.codetojoy;
public class Contract {
public static void isNotNull(Object obj) {
if (obj == null) throw new IllegalArgumentException("illegal null");
}
public static void isNotEmpty(String s) {
if (s.isEmpty()) throw new IllegalArgumentException("illegal empty string");
}
}
そして、これが使用例です。 foo()
メソッドは、静的インポートを示しています。
package net.codetojoy;
import static net.codetojoy.Contract.*;
public class Example {
public void foo(String str) {
isNotNull(str);
isNotEmpty(str);
System.out.println("this is the string: " + str);
}
public static void main(String[] args) {
Example ex = new Example();
ex.foo("");
}
}
注:実験する際には、バグがある可能性があることに注意してくださいこれをデフォルトのパッケージで実行します。私は確かに脳細胞を失ってしまいました。
小さな Java引数検証パッケージがあり、プレーンJavaとして実装されています。いくつかの標準チェック/検証が付属しています。そして、誰かがより具体的な検証を必要とする場合には、いくつかのヘルパーメソッドが付属しています。複数回行われる検証の場合は、インターフェイスArgumentValidationを独自に拡張し、クラスArgumentValidationImplから拡張する実装クラスを作成します。
これはあなたの質問に直接答えるものではありませんが、あなたの問題の一部はあなたが検証をやりすぎていることだと思います。たとえば、最初のテストを次のように置き換えることができます。
if (x.isEmpty()) {
throw new IllegalArgumentException("x cannot be empty");
}
そして x
が null
の場合、Javaに依存して NullPointerException
をスローします。 「契約」を変更するだけです。特定の種類の「違法なパラメーターで私に電話をかけた」の場合、NPEがスローされると言う状況。
Jaredは、DBCのサポートをJavaに追加するさまざまなフレームワークを指摘しました。
私が最もうまくいくとわかったのは、JavaDocで契約を文書化するだけです(または、使用するドキュメントフレームワーク。DoxygenはDBCタグをサポートしています。)
たくさんのスローと引数のチェックでコードを難読化することは、読者にとってはあまり役に立ちません。ドキュメントは。
パラメーター注釈、リフレクション、および汎用バリデータークラスを使用して、アプリ全体の機能を作成します。たとえば、次のようなクラスメソッドをコーディングできます。
.. myMethod(@notNull String x、@notNullorZero String y){
if (Validator.ifNotContractual(getParamDetails()) {
raiseException..
or
return ..
}
}
クラスメソッドは「マークアップ」されています契約要件に注釈を付けます。リフレクションを使用して、パラメーター、その値、および注釈を自動的に検出します。すべてを静的クラスに送信して検証し、結果を知らせます。
完全に機能するソリューションではありませんが、JSR-303にはの提案があります。メソッドレベルの検証拡張機能。これは単なる拡張提案であるため、JSR-303の実装では自由に無視できます。実装を見つけるのはもう少し難しいです。 Hibernate Validatorはまだサポートしていないと思いますが、 agimatec-validation 実験的なサポートがあります。私はこの目的にも使用していませんので、どれだけうまく機能するのかわかりません。誰かがやってみたら、私はそれを見つけることに興味があります。
Java 8を使用している場合、ラムダを使用して、検証用の非常にエレガントで読みやすいソリューションを作成できます。
public class Assert {
public interface CheckArgument<O> {
boolean test(O object);
}
public static final <O> O that(O argument, CheckArgument<O> check) {
if (!check.test(argument))
throw new IllegalArgumentException("Illegal argument: " + argument);
return argument;
}
}
次のように使用します:
public void setValue(int value) {
this.value = Assert.that(value, arg -> arg >= 0);
}
例外は次のようになります。
Exception in thread "main" java.lang.IllegalArgumentException: Illegal argument: -7
at com.xyz.util.Assert.that(Assert.java:13)
at com.xyz.Main.main(Main.java:16)
最初の良い点は、上記のAssertクラスが本当に必要なものすべてであるということです:
public void setValue(String value) {
this.value = Assert.that(value, arg -> arg != null && !arg.trim().isEmpty());
}
public void setValue(SomeObject value) {
this.value = Assert.that(value, arg -> arg != null && arg.someTest());
}
もちろん、 that()
はさまざまな方法で実装できます。フォーマット文字列と引数を使用して、他の種類の例外をスローするなど。
ただし、異なるテストを実行するために実装する必要はありません。
必要に応じて、パッケージテストを事前に行うことはできません。
public static CheckArgument<Object> isNotNull = arg -> arg != null;
Assert.that(x, Assert.isNotNull);
// with a static import:
Assert.that(x, isNotNull);
これがパフォーマンスに悪いのか、他の何らかの理由で良いアイデアではないのかはわかりません。 (自分でラムダを調べ始めたのですが、コードは本来どおりに実行されるようです...)しかし、 Assert
は短く保つことができます(プロジェクトにとって重要ではない依存関係は不要です)テストが非常に目に見えること。
エラーメッセージを改善する方法を次に示します。
public static final <O> O that(O argument, CheckArgument<O> check,
String format, Object... objects)
{
if (!check.test(argument))
throw new IllegalArgumentException(
String.format(format, objects));
return argument;
}
次のように呼び出します:
public void setValue(String value) {
this.value = Assert.that(value,
arg -> arg != null && arg.trim().isEmpty(),
"String value is empty or null: %s", value);
}
そして出てくる:
Exception in thread "main" java.lang.IllegalArgumentException: String value is empty or null: null
at com.xyz.util.Assert.that(Assert.java:21)
at com.xyz.Main.main(Main.java:16)
更新:事前にパッケージ化されたテストで x = Assert ...
構造を使用する場合、結果は事前にパッケージ化されたテストで使用される型にキャストされます。したがって、変数の型にキャストバックする必要があります... SomeClass x =(SomeClass)Assert.that(x、isNotNull)