Javaの真のソリューション:2つの文字列から2つの数値を解析し、それらの合計を返します


84

かなり愚かな質問です。与えられたコード:

public static int sum(String a, String b) /* throws? WHAT? */ {
  int x = Integer.parseInt(a); // throws NumberFormatException
  int y = Integer.parseInt(b); // throws NumberFormatException
  return x + y;
}

それが良いJavaかどうかわかりますか?私が話しているのNumberFormatExceptionは、チェックされていない例外です。署名の一部として指定する必要はありませんsum()。さらに、私が理解している限り、チェックされていない例外の概念は、プログラムの実装が正しくないことを示すだけであり、さらに、チェックされていない例外をキャッチすることは、実行時に不良プログラムを修正するようなものであるため、悪い考えです。

誰かが次のことを明確にしてください:

  1. NumberFormatExceptionメソッドのシグネチャの一部として指定する必要があります。
  2. 独自のチェック済み例外(BadDataException)を定義し、NumberFormatExceptionメソッド内で処理して、として再スローする必要がありBadDataExceptionます。
  3. 独自のチェック済み例外(BadDataException)を定義し、正規表現のように両方の文字列を検証BadDataExceptionし、一致しない場合はスローする必要があります。
  4. あなたの案?

更新

想像してみてください。何らかの理由で使用する必要があるのはオープンソースフレームワークではありません。メソッドのシグネチャを見て、「OK、スローされることはない」と考えます。その後、ある日、例外が発生しました。正常ですか?

アップデート2

sum(String, String)は悪いデザインだというコメントがいくつかあります。私は絶対に同意しますが、私たちが良いデザインを持っていれば元の問題は決して現れないと信じている人のために、ここに追加の質問があります:

問題の定義は次のようになりますString。数値がsとして格納されるデータソースがあります。このソースは、XMLファイル、Webページ、2つの編集ボックスを備えたデスクトップウィンドウなどです。

あなたの目標は、これらの2を取りString、それらをintsに変換し、「合計はxxxです」というメッセージボックスを表示するロジックを実装することです。

これを設計/実装するために使用するアプローチが何であれ、内部機能の次の2つのポイントがあります

  1. あなたが変換する場所Stringint
  2. 2int秒追加する場所

私の元の投稿の主な質問は次のとおりです。

Integer.parseInt()正しい文字列が渡されることを期待します悪い文字列を渡すときはいつでも、それはあなたのプログラムが正しくないことを意味します(「あなたのユーザーはばかです」ではありません)。一方でMUSTセマンティクスを持つInteger.parseInt()があり、他方で入力が正しくない場合に問題がない必要があるコードを実装する必要があります-SHOULDセマンティクス

したがって、簡単に言うと、MUSTライブラリしかない場合にSHOULDセマンティクスを実装するにはどうすればよいですか。


13
それは決して愚かな質問ではありません。実際、それはかなり良いものです!どのオプションを選択するかは完全にはわかりません。
マーティン2011

1
あなたの更新へ:はい、フェイルファストの原則によれば、これも必ずしも悪いことではありません。
ヨハン・シェーベルイ

1
私はマーティンに同意します。これは、Javaプログラミングの側面の1つであり、最も理解されていないものであり、いくつかのベストプラクティスが必要です。これは、オライリー以外に「これがどのように行われるべきか」と言っているページがほとんどないことからわかります。
James P.

2
実際、これは素晴らしい質問です。
Chris Cudmore 2011

1
素晴らしい質問です!また、いくつかの素晴らしい回答を提起しました:D
ケルダー2011

回答:


35

これは良い質問です。もっと多くの人にそのようなことを考えてもらいたいです。

私見ですが、ごみパラメータが渡されている場合は、チェックされていない例外をスローすることは許容されます。

一般的に言って、BadDataExceptionプログラムの流れを制御するために例外を使用するべきではないので、投げるべきではありません。例外は例外のためのものです。メソッドの呼び出し元は、文字列が数値であるかどうかを呼び出すに知ることができるため、ゴミを渡すことは避けられ、プログラミングエラーと見なすことができます。つまり、チェックされていない例外をスローしても問題ありません。

宣言に関してthrows NumberFormatException-NumberFormatExceptionがチェックされていないために気付く人はほとんどいないため、これはそれほど有用ではありません。ただし、IDEはそれを利用して、try/catch正しくラップすることを提案できます。良いオプションは、javadocも使用することです。例:

/**
 * Adds two string numbers
 * @param a
 * @param b
 * @return
 * @throws NumberFormatException if either of a or b is not an integer
 */
public static int sum(String a, String b) throws NumberFormatException {
    int x = Integer.parseInt(a); 
    int y = Integer.parseInt(b); 
    return x + y;
}

編集済み
コメント投稿者は有効なポイントを作成しました。これがどのように使用されるか、およびアプリの全体的なデザインを検討する必要があります。

メソッドがあらゆる場所で使用され、すべての呼び出し元が問題を処理することが重要である場合は、チェックされた例外をスローする(呼び出し元に問題の処理を強制する)が、コードをtry/catchブロックで乱雑にするようにメソッドを宣言します。

一方、信頼できるデータでこのメソッドを使用している場合は、爆発することは想定されておらず、本質的に不要なtry/catchブロックのコードの乱雑さを回避できるため、上記のように宣言します。


6
問題は、チェックされていない例外がしばしば無視され、コールスタックに浸透して、何が下にあるのかわからないアプリの部分に大混乱をもたらす可能性があることです。問題は、実際には、入力が有効かどうかをコードのどのビットでチェックする必要があるかということです。
G_H 2011

1
@G_Hにはポイントがあります。Javaチュートリアルでは、「クライアントが例外からの回復を合理的に期待できる場合は、チェック済みの例外にします。クライアントが例外から回復するために何もできない場合は、チェックなしの例外にします」と述べています。問題は、どこRuntimeExceptionで処理する必要があるかです。それは呼び出し元である必要がありますか、それとも例外をフロートアップさせる必要がありますか?もしそうなら、それはどのように扱われるべきですか?
James P.

1
これに部分的に答えるために、私が見た2つのアプローチがあります。1つはExceptions、下位レイヤーからインターセプトし、エンドユーザーにとってより意味のある上位レベルの例外でラップすることです。2つ目は、で初期化できるキャッチされていない例外ハンドラーを使用することです。アプリケーションランチャー。
James P.

2
javaDocにNumberFormatExceptionがあるIMHOは、はるかに重要です。それをthrows句に入れることは素晴らしい追加ですが、必須ではありません。
ドルス2011

3
するために「自分の文字列が数値であるかない場合、彼らはそれを呼び出す前に、あなたの方法に発信者が知ることができるので、ゴミ中を渡すことは避けられる」:それは本当に真実ではありません。文字列がintとして解析されることを確認する唯一の簡単な方法は、それを試すことです。事前のチェックを行うこともできますが、正確なチェックは非常に重要です。
maaartinus 2011

49

私の意見では、例外ロジックを可能な限り処理することが望ましいでしょう。したがって、私は署名を好む

 public static int sum(int a, int b);

あなたのメソッドシグネチャでは、私は何も変更しません。あなたはどちらか

  • プログラムで誤った値を使用し、代わりにプロデューサーアルゴリズムを検証できます
  • または、たとえばユーザー入力から値を送信します。この場合、そのモジュールは検証を実行する必要があります

したがって、この場合の例外処理はドキュメントの問題になります。


4
私はこれが好き。これは、ユーザーにコントラクトに従うように強制し、メソッドに1つのことだけを実行させます。
Chris Cudmore 2011

このメソッドの使用法が、a + bを実行しているユーザーとどのように異なるのかわかりません。なぜこの方法が必要なのですか?
スワティ2011

4
@Swati:これはだと思います。これは物事を簡単に示すための優れたテクニックです。sumメソッドはmyVeryComplicatedMethodWhichComputesPhoneNumberOfMyIdealMatchGirl(int myID、int myLifeExpectancy)...
Kheldar

3
ここで完全に同意します。sum2つの数値を期待する関数は、2つの数値を取得する必要があります。それらを取得する方法は、sum関数とは関係ありません。ロジックを正しく分離します。だから、私はこれをデザインの問題に関連付けたいと思います。
c00kiemon5ter 2011

5

番号4。与えられたように、このメソッドは整数を取るべきパラメータとして文字列を取るべきではありません。その場合(Javaがオーバーフローする代わりにラップするため)、例外の可能性はありません。

 x = sum(Integer.parseInt(a), Integer.parseInt(b))

何を意味するのかについては、 x = sum(a, b)

例外がソース(入力)のできるだけ近くで発生するようにします。

オプション1〜3については、呼び出し元がコードが失敗しないと想定するため、例外を定義しません。例外を定義して、メソッドに固有の既知の失敗条件下で何が発生するかを定義します。つまり、別のオブジェクトのラッパーであるメソッドがあり、それが例外をスローしてからそれを渡す場合です。例外がメソッドに固有である場合にのみ、カスタム例外をスローする必要があります(frex、この例では、sumが肯定的な結果のみを返すことになっている場合は、それをチェックして例外をスローするのが適切です。ラップする代わりにオーバーフロー例外をスローした場合、それを渡すか、署名で定義したり、名前を変更したり、食べたりすることはありません)。

質問の更新に応じて更新します。

したがって、簡単に言うと、MUSTライブラリしかない場合、SHOULDセマンティクスを実装するにはどうすればよいですか。

これに対する解決策は、MUSTライブラリをラップし、SHOULD値を返すことです。この場合、整数を返す関数。文字列を受け取り、整数オブジェクトを返す関数を記述します-それが機能するか、nullを返します(guavaのInts.tryParseのように)。検証は操作とは別に行います。操作にはintが必要です。入力が無効な場合に操作がデフォルト値で呼び出されるか、他のことを行うかは、仕様によって異なります。これについて言えることは、その決定を下す場所が操作内にある可能性は非常に低いということです。方法。


ここであなたの考えは何ですか?とがあっても同じ問題が発生sum(int, int)parseIntFromString(String)ます。同じ機能を得るには、どちらの場合も、への呼び出しが2つとへの呼び出しがparseIntFromString()1つあるコードを記述する必要がありますsum()。それがあなたにとって理にかなっているなら、このケースを考えてください-私は元の問題の観点から違いはわかりません。
アンドレイアギバロフ2011

@ loki2302:ポイントは、発信者が番号でない場合に適切な動作を決定することです。また、エラーチェックを行わない場合、エラーは値が割り当てられた場所に1ステップ近く発生します。デバッグの目的では、割り当てに近いほど、デバッグが容易になります。また、TDDを実行している場合は、発信者に適切な単体テストを作成し損なう可能性が高くなります。基本的に、数値であるはずの文字列をメソッドxに指定したくない場合、5つのクラスと20のメソッド呼び出しは、数値を処理しようとすると例外をスローします。
jmoreno 2011

わかりません。のインターフェースを提供するint sum(String, String)ことは実際には不可能だと言っているのですか?
アンドレイアギバロフ2011

1
@ loki2302:示されているコードを考えると、それは可能ですが、悪い考えです。文字列内の数字が、「2」、「two」、「dos」、「deux」、「zwo」がすべて同じように扱われる単語である可能性がある場合、その署名が適切です。しかし、現状では、メソッドには2つの文字列が与えられ、それらを整数として扱います。なぜあなたはそれをしたいのですか?それは悪い考えです。
jmoreno 2011

2
@ loki2302:ごみが渡されたときにチェックされていない例外をスローしても問題ないという回答を受け入れましたが、強く型付けされた言語のポイントは、ごみが渡されないようにすることです。文字列が常に整数値であることを期待するメソッドがあると、問題が発生します。Integer.parseIntでさえ、それをうまく処理しません(Javaにはoutパラメーターがないため、多少実用的ではありませんが、入力が悪いと予想される例外、.Netのinteger.TryParseメソッドの方がはるかに優れています)。
jmoreno 2011

3

1.メソッドのシグネチャの一部としてNumberFormatExceptionを指定する必要があります。

私はそう思う。それは素晴らしいドキュメントです。

2.独自のチェック済み例外(BadDataException)を定義し、メソッド内でNumberFormatExceptionを処理して、BadDataExceptionとして再スローする必要があります。

時々そうです。チェックされた例外は、場合によってはより良いと見なされますが、それらを操作することは非常にPITAです。そのため、多くのフレームワーク(Hibernateなど)はランタイム例外のみを使用します。

3.独自のチェック例外(BadDataException)を定義し、正規表現のように両方の文字列を検証し、一致しない場合はBadDataExceptionをスローする必要があります。

決して。より多くの作業、より少ない速度(例外をスローすることがルールであると期待しない限り)、そしてまったく利益がありません。

4.あなたの考えは?

まったくありません。


2

Nr4。

方法は全く変えないと思います。例外からビジネスロジックを使用して正常に回復できるコンテキストにいるスタックトレースの呼び出しメソッド以上にtrycatchを配置します。

私はそれがやり過ぎだと思うので、私は確実に#3をすることはありません。


2

あなたが書いているものが(APIのように)他の誰かによって消費されると仮定すると、1を使用する必要があります。NumberFormatExceptionは特にそのような例外を伝達するためのものであり、使用する必要があります。


2
  1. まず、自分自身に尋ねる必要があります。私のメソッドのユーザーは、間違ったデータを入力することを心配する必要がありますか、それとも適切なデータ(この場合は文字列)を入力することが期待されていますか。この期待は、契約による設計としても知られています。

  2. 3.はい、おそらくBadDataExceptionを定義するか、NumberFormatExceptionのようないくつかの削除されたものを使用する必要がありますが、標準メッセージを表示したままにしておきます。メソッドでNumberFormatExceptionをキャッチし、元のスタックトレースを含めることを忘れずに、メッセージとともに再スローします。

  3. 状況によって異なりますが、NumberFormatExceptionを追加情報とともに再スローすることになるでしょう。また、期待値が何であるかについてのjavadocの説明が必要です。String a, String b


1

あなたがいるシナリオに大きく依存します。

ケース1.コードをデバッグするのは常にあなたであり、他の誰も例外も悪いユーザーエクスペリエンスを引き起こしません

デフォルトのNumberFormatExceptionをスローします

ケース2:コードは非常に保守可能で理解しやすいものでなければなりません

独自の例外を定義し、それをスローしながらデバッグするためにさらに多くのデータを追加します。

とにかく悪い入力で例外になるので、正規表現チェックは必要ありません。

それが本番レベルのコードである場合、私の考えは、次のような複数のカスタム例外を定義することです。

  1. 数値形式の例外
  2. オーバーフロー例外
  3. ヌル例外など...

これらすべてを個別に処理します


1
  1. これは、誤った入力で発生する可能性があることを明確にするために行うことができます。あなたのコードを使用している誰かがこの状況の処理を覚えておくのに役立つかもしれません。具体的には、コードでそれを自分で処理しないこと、または代わりに特定の値を返すことを明確にします。もちろん、JavaDocもこれを明確にする必要があります。
  2. 呼び出し元にチェック済みの例外を処理させる場合のみ。
  3. それはやり過ぎのようです。不正な入力を検出するには、解析に依存します。

全体として、正しく解析可能な入力が提供されることが期待されるため、NumberFormaExceptionはチェックされていません。入力検証はあなたが扱うべきものです。ただし、実際に入力を解析するのがこれを行う最も簡単な方法です。メソッドをそのままにして、正しい入力が期待されていることをドキュメントで警告することができます。関数を呼び出す人は、使用する前に両方の入力を検証する必要があります。


1

例外的な動作は、ドキュメントで明確にする必要があります。失敗した場合(null戻り値の型をに変更するなどInteger)にこのメソッドが特別な値を返すことを示すか、ケース1を使用する必要があります。メソッドのシグネチャで明示的にすると、他の方法で正しい文字列を保証する場合、ユーザーはそれを無視できますが、メソッドがこの種の失敗を単独で処理しないことは明らかです。


1

更新された質問に答えます。

はい、「サプライズ」の例外が発生するのはまったく正常です。プログラミングを始めたばかりのときに発生したすべての実行時エラーについて考えてみてください。

e.g ArrayIndexOutofBound

また、foreachループからの一般的な驚きの例外。

ConcurrentModificationException or something like that

1

ランタイム例外の浸透を許可する必要があるという答えには同意しますが、設計と使いやすさの観点から、NumberFormatExceptionとしてスローするのではなく、IllegalArgumentExceptionにラップすることをお勧めします。これにより、メソッドのコントラクトがより明確になり、不正な引数が渡されたために例外がスローされたと宣言されます。

「想像してみてください。何らかの理由で使用する必要があるのはオープンソースフレームワークではありません。メソッドのシグネチャを見て、「OK、スローされることはありません」と考えます。その後、ある日、例外が発生しました。 。それは正常ですか?」メソッドのjavadocは、常にメソッドの動作(前後の制約)をこぼしてしまうはずです。nullが許可されていない場合、javadocは、メソッドシグネチャの一部ではないものの、nullポインタ例外がスローされると言っているコレクションインターフェイスの行を考えてみてください。


2
NumberFormatExceptionはIllegalArgumentExceptionのサブクラスであるため、このラッピングは情報を追加しません。
ドンロベイ2011

これは、例外をそのまま浸透させることができ(この場合、nfeはすでに不正な引数の例外であるため)、呼び出し階層のどこかで不正な引数を処理するための一般的な処理が可能であることを意味します。したがって、これが引数がユーザーによって渡された例である場合、不正な引数のすべての処理をラップしてユーザーに通知する汎用コードが存在する可能性があります。
スコーピオン

1

あなたが良いJavaプラクティスについて話しているように、私の意見ではそれは常により良いです

  • 未チェックの例外を処理するには、それを分析し、カスタムの未チェックの例外を使用します。

  • また、カスタムのチェックされていない例外をスローしているときに、クライアントが理解できる例外メッセージを追加したり、元の例外のスタックトレースを出力したりすることもできます。

  • チェックされていないため、カスタム例外を「スロー」として宣言する必要はありません。

  • このようにして、チェックされていない例外が作成された目的の使用に違反することはありません。同時に、コードのクライアントは例外の理由と解決策を簡単に理解できます。

  • また、java-docで適切に文書化することは良い習慣であり、大いに役立ちます。


1

それはあなたの目的に依存すると思いますが、少なくともそれを文書化します:

/**
 * @return the sum (as an int) of the two strings
 * @throws NumberFormatException if either string can't be converted to an Integer
 */
public static int sum(String a, String b)
  int x = Integer.parseInt(a);
  int y = Integer.parseInt(b);
  return x + y;
}

または、java.lang.IntegerクラスのJavaソースコードからページを取得します。

public static int parseInt(java.lang.String string) throws java.lang.NumberFormatException;

1

Googleの「Guava」ライブラリまたはApacheの「Validator」ライブラリによって実装された入力検証パターンはどうですか(比較)。

私の経験では、関数の先頭で関数のパラメーターを検証し、必要に応じて例外をスローすることをお勧めします。

また、この質問は主に言語に依存しないと思います。ここでの「グッドプラクティス」は、有効な場合と無効な場合があるパラメータを受け取ることができる関数を持つすべての言語に適用されます。


1

「かなりばかげた質問」の最初の文は非常に関連性があると思います。そもそも、なぜそのシグネチャを使用してメソッドを作成するのでしょうか。2つの文字列を合計することも意味がありますか?呼び出し元のメソッドが2つの文字列を合計する場合、メソッドを呼び出す前に、それらが有効なintであることを確認し、変換するのは呼び出し側メソッドの責任です。

この例では、呼び出し元のメソッドが2つの文字列をintに変換できない場合、いくつかのことを実行できます。これは、この合計がどのレイヤーで発生するかによって異なります。文字列変換はフロントエンドコードに非常に近いと思います(適切に行われた場合)。そのため、ケース1が最も可能性が高くなります。

  1. エラーメッセージを設定して処理を停止するか、エラーページにリダイレクトします
  2. falseを返します(つまり、合計を他のオブジェクトに入れ、それを返す必要はありません)
  3. あなたが提案しているようにいくつかのBadDataExceptionをスローしますが、これら2つの数値の合計が非常に重要でない限り、これはやり過ぎであり、上記のように、変換が間違った場所で行われていることを意味するため、これはおそらく悪い設計です

1

この質問には興味深い答えがたくさんあります。しかし、私はまだこれを追加したいです:

文字列の解析には、常に「正規表現」を使用することを好みます。java.util.regexパッケージは私たちを助けるためにあります。したがって、例外をスローすることのない、このようなものになります。エラーをキャッチしたい場合は、特別な値を返すのは私次第です。

import java.util.regex.Pattern;
import java.util.regex.Matcher;

public static int sum(String a, String b) {
  final String REGEX = "\\d"; // a single digit
  Pattern pattern = Pattern.compile(REGEX);
  Matcher matcher = pattern.matcher(a);
  if (matcher.find()) { x = Integer.matcher.group(); }
  Matcher matcher = pattern.matcher(b);
  if (matcher.find()) { y = Integer.matcher.group(); }
  return x + y;
}

ご覧のとおり、コードは少し長くなっていますが、必要なものを処理できます(xとyのデフォルト値を設定したり、else句で何が起こるかを制御したりできます...)さらに一般的な変換を作成することもできます。文字列を渡すことができるルーチン、デフォルトの戻り値、コンパイルするREGEXコード、スローするエラーメッセージ、...

それが役に立ったことを願っています。

警告:このコードをテストできなかったので、最終的な構文の問題を許してください。


1
私たちはJavaについて話しているのですか?何Integer.matcherですか?privateメソッド内の変数?不足している()たくさんの行方不明、IFSのために;xy宣言されていないが、matcher2回宣言、...
user85421

確かにカルロス、私は急いでそれをしました、そして私が言ったように、それをすぐにテストすることができませんでした。ごめんなさい。正規表現のやり方を示したかったのです。
Louis

OK、しかしこれはNumberFormatExceptionの問題を解決しません-主な質問Integer.matcher.group()Integer.parseInt(matcher.group())
IMO-

0

この問題が発生するのは、ユーザーエラーがアプリケーションのコアに深く浸透しすぎているためと、Javaデータ型を悪用しているためです。

ユーザー入力の検証とビジネスロジックをより明確に分離し、適切なデータ入力を使用する必要があります。そうすれば、この問題は自然に解消されます。

事実はInteger.parseInt()既知のセマンティクスです-有効な整数を解析することが主な目的です。明示的なユーザー入力の検証/解析ステップがありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.