範囲外の意味のある値の場合に例外をスローするか、自分で処理する必要がありますか?


42

緯度/経度座標を表す構造体を作成しました。値の範囲は、経度では-180〜180、緯度では90〜-90です。

その構造体のユーザーがその範囲外の値を私に与えた場合、2つのオプションがあります:

  1. 例外をスロー(引数が範囲外)
  2. 値を制約に変換します

-185の座標には意味があるため(極座標なので+175に非常に簡単に変換できます)、受け入れて変換できます。

例外をスローして、彼のコードが持つべきではない値をユーザーに与えたことをユーザーに伝える方が良いでしょうか?

編集:緯度/経度と座標の違いも知っていますが、議論を簡単にするためにそれを単純化したかったのです。


13
ユーザーが範囲外の値を挿入できるようにする必要がありますか?答えがいいえの場合、例外をスローします。規則がそれほど厳密でない場合は、変換を行いますが、ドキュメントで変換が発生する可能性があることを明示的に記載してください。また、一部の言語では例外処理に非常にコストがかかることにも注意してください。
アンディ

C#、それは重要ですか?これがあなたが意味するものである場合、ネイティブに制約をサポートしません。
K.グキニス

2
私にはかなり明確に思えますが、正しいアプローチは、ユーザーが範囲外のものを入力し、そうするときに例外をスローすることを許可することです(標準がabs値が180を超えてはならない場合、それよりも大きい値を入れると明らかな違反)。ちなみに、C#は実際には例外のコストが非常に高い言語の1つであるため、例外は本当に例外的な状況でのみ使用してください。つまり、キャッチしないとアプリケーションが壊れます。
アンディ

2
私は、特定のパラメーター値、特に私のコードが対応していないパラメーター値を渡すことで、ユーザーが「意味する」ことについての推測を避ける傾向があります。同様のケースのように聞こえます。
JᴀʏMᴇᴇ

2
Webメルカトルの座標は-180〜180および-90〜90ではありません。これは緯度/経度です(さらに、そのための座標系もいくつかあります)。メルカトル図法は通常数十万または数百万単位で、度ではなく「メートル」の単位を持ちます(厳密には、各単位の長さは極に近づくにつれて実際の地上距離の増加をカバーするためです)。度単位でも、極では投影が無限に広がるため、±85.051129度に制限されます。(質問の核心ではないため、これを修正する編集を提出しました。)
jpmc26

回答:


51

あなたの質問の中核がこれなら...

データ構造がモデリングしているものに対して値が無効な引数をクライアントコードが渡す場合、値を拒否するか、適切な値に変換する必要がありますか?

...私の一般的な答えは「拒否」になります。これは、無効な値を実際にプログラムに表示してコンストラクターに到達させるクライアントコードの潜在的なバグに注意を引くのに役立つからです。バグに注意を引くことは、少なくとも開発中はほとんどのシステムで一般的に望ましいプロパティです(エラーが発生した場合にシステムを混乱させる必要がある場合を除く)。

問題は、あなたが実際にそのケースに直面しているかどうかです

  • データ構造が一般に極座標をモデル化することを目的としている場合、-180および+180の範囲外の角度は実際には無効ではないため、値を受け入れます。それらは完全に有効であり、常に-180から+180の範囲内にある同等物を持っています(そして、それらをその範囲をターゲットに変換したい場合は、お気軽に-クライアントコードは気にする必要はありません) 。

  • データ構造が明示的にWebメルカトル座標をモデリングしている場合(初期形式の質問による)、仕様に記載されている規定に従うことをお勧めします(わかりませんので、何も言いません)。 。場合のものあなたしているモデルの仕様は、いくつかの値が無効であることを述べている、それらを拒否します。賢明なものと解釈できる(したがって実際に有効である)と解釈できる場合は、それらを受け入れます。

メカニズムあなたは値が受け入れられたかどうかを知らせるために使用している言語、その一般的な哲学やパフォーマンス要件の機能に依存します。そのため、(コンストラクターで)例外をスローするか、構造体のNULL可能バージョンを返す(プライベートコンストラクターを呼び出す静的メソッドを介して)か、ブール値を返し、構造体をoutパラメーターとして呼び出し側に渡す(再びプライベートコンストラクターを呼び出す静的メソッドなど)。


12
-180〜+180の範囲外の経度は、おそらく許容範囲内であると考えられるべきですが、-90〜+90の範囲外の緯度は無意味に思えます。北極または南極に到達すると、さらに北または南に移動することは定義されていません。
-supercat

4
@supercat私はあなたに同意する傾向がありますが、Webメルカトル仕様では実際にどの値が無効であるかがわからないため、難しい結論を出さないようにしています。OPは私よりも問題の領域をよく知っています。
セオドロスチャツィジアンナキス

1
@supercat:子午線が赤道に到達するところから始めます。緯度に従って子午線に沿って移動します。緯度が> 90の場合、同じ大円に沿って進むだけです。問題ない。それを文書化し、残りをクライアントに任せます。
ケビンクライン

4
@supercatそれはそれより悪いです。Webメルカトル図法では、実際には±90が無限の幅に投影されます。したがって、標準では実際に±85.051129で切り捨てられます。また、lat / long!= Webメルカトル座標。メルカトル図法では、度ではなくメートルのバリエーションを使用します。(極に近づくにつれて、各「メートル」は実際にはますます大きな地面に対応します。)OP座標は純粋に緯度/経度です。Webメルカトルは、Webメルカトルのベースマップの上に表示される場合や、一部の空間ライブラリが内部で投影される場合を除き、それらとは何の関係もありません。
jpmc26

1
それは実際の座標ではないので、「±85.051129に相当」と言う必要があります。
jpmc26

10

それは大いに依存します。しかし、あなたは何かをしてそれを文書化することに決めなければなりません。

あなたのコードにとって唯一決定的に間違っていることは、ユーザー入力が期待される範囲外である可能性があることを忘れ、誤って何らかの動作をするコードを書くことです。なぜなら、コードの振る舞いについて間違った仮定を立ててバグを引き起こす人もいれば、コードが誤って持っている振る舞いに依存してしまう人もいるからです(その振る舞いが完全に狂っていても)後で問題を修正するとき。

この場合、どちらの方法でも引数を見ることができます。175度から+10度移動した場合、最終的には-175になります。ユーザー入力を常に正規化し、185を-175に相当するものとして扱う場合、クライアントコードは10度加算しても間違ったことをすることができません。常に正しい効果があります。あなたはエラーとして185を扱う場合は、クライアントコードは、正規化ロジックに入れて相対度を追加されたすべてのケースを強制的(あるいは、少なくとも、あなたの正規の手続きを呼び出すことを忘れないでください)、あなたは実際によ原因バグ(うまくいけば、すぐにつぶされるバグを簡単にキャッチできます)。ただし、経度番号がユーザー入力、プログラムで文字通りに書き込まれる、または常に[-180、180)になるように意図された何らかの手順で計算される場合、その範囲外の値はエラーを示す可能性が非常に高いため、 "変換すると問題が隠れる可能性があります。

この場合の私の理想は、おそらく正しいドメインを表す型を定義することです。抽象型を使用し(クライアントコードが内部の生の数値に単純にアクセスしないようにします)、正規化ファクトリーと検証ファクトリーの両方を提供しますクライアントがトレードオフできるようにします)。しかし、このタイプの値が何であれ、185はパブリックAPIを通して見たときに-175と見分けがつかないはずです(構築時に変換されるか、平等、アクセサー、および何らかの違いを無視する他の操作を提供するかどうかは関係ありません) 。


3

1つのソリューションを選択することが本当に重要でない場合は、ユーザーに決定させるだけで済みます。

構造体が読み取り専用の値オブジェクトであり、メソッド/コンストラクターによって作成された場合、ユーザーが持つオプションに基づいて2つのオーバーロードを提供できます。

  • 例外をスロー(引数が範囲外)
  • 値を制約に変換します

また、ユーザーに他のメソッドに渡す無効な構造体を決して持たせないでください。

編集:コメントに基づいて、私はあなたがC#を使用していると仮定します。


入力いただきありがとうございます!私はそれを考えますが、それを避けることができれば、例外を投げるコンストラクタの目的を損なうことを恐れています。でも面白い!
K.グキニス

このアイデアを使用する場合は、インターフェイスを定義し、スローまたは変換する2つの実装を用意することをお勧めします。あなたが説明したように、賢明な方法でコンストラクタをオーバーロードすることは難しいでしょう。

2
@Snowman:ええ、同じ引数型でコンストラクターをオーバーロードするのは難しいでしょうが、2つの静的メソッドと1つのプライベートコンストラクターを持つことは難しくありません。
wchargin

1
@ K.Gkinis:例外をスローするコンストラクターの目的は、「アプリケーションが終了することを確認する」ことではありません。結局、クライアントは常にcatch例外を処理できます。他の人が言ったように、もし望むならクライアントが自分自身制限できるようにすることです。あなたは本当に何も回避していません。
wchargin

この提案は、コードを複雑にし、メリットはありません。メソッドは無効な入力を変換するか、変換しない必要があります。2つのオーバーロードがあると、ジレンマを解決せずにコードが複雑になります。
ジャックB

0

入力がUIを介してユーザーから直接入力されるか、システムから入力されるかによって異なります。

UIを介した入力

無効な入力を処理する方法はユーザーエクスペリエンスの質問です。特定のケースについては知りませんが、一般的にいくつかのオプションがあります。

  • ユーザーにエラーを警告し、続行する前にユーザーに修正してもらいます(最も一般的)
  • 可能な場合は自動的に有効な範囲に変換しますが、変更をユーザーに警告し、続行する前にユーザーが確認できるようにします。
  • 有効な範囲に静かに変換して続行します。

選択は、ユーザーの期待とデータの重要性に依存します。たとえば、Googleはクエリのスペルを自動的に修正しますが、役に立たない変更は問題ではなく、簡単に修正できるため、リスクは低くなります(さらにクエリが変更されたことは結果ページで明確になります)。一方、核ミサイルの座標を入力する場合は、より厳密な入力検証が必要であり、無効なデータのサイレント修正は不要です。したがって、普遍的な答えはありません。

最も重要なことは、入力を修正することでユーザーにとってもメリットがあるかどうかを検討することです。ユーザーが無効なデータを入力するのはなぜですか?誰かがつづりを間違えるのは簡単ですが、なぜ-185の経度を入力するのでしょうか?ユーザーが本当に+175を意味する場合、おそらく+175と入力しているでしょう。無効な経度は単に入力エラーであり、ユーザーは-85などを意味していた可能性が高いと思います。この場合、黙って変換するのは良くなく、役に立たない。あなたのアプリにとって最もユーザーフレンドリーなアプローチは、おそらくユーザーに無効な値を警告し、ユーザーに自分で修正してもらうことでしょう。

APIを介した入力

入力が別のシステムまたはサブシステムからのものである場合、質問はありません。例外をスローする必要があります。他のシステムからの無効な入力をサイレント変換することは絶対にしないでください。システムの他の場所でエラーがマスクされる可能性があるためです。入力が「修正」されている場合、システムの奥深くではなく、UIレイヤーで発生するはずです。


0

例外をスローする必要があります。

185を送信し、それを-175に変換した例では、その機能を提供すると便利な場合があります。しかし、発信者が100万を送信した場合はどうなりますか?彼らは本当にそれを変換するつもりですか?それはエラーである可能性が高いようです。そのため、185ではなく1,000,000の例外をスローする必要がある場合、例外をスローするための任意のしきい値について決定する必要があります。呼び出し元のアプリがしきい値付近の値を送信しているため、そのしきい値はいつかはトリップします。

範囲外の値については例外をスローする方が適切です。


-1

開発者にとって最も便利なオプションは、範囲外の値に対するプラットフォームでのコンパイル時エラーのサポートです。この場合、範囲は、パラメーターのタイプと同様に、メソッドシグネチャの一部でもある必要があります。メソッドシグネチャが整数を取るように定義されている場合、APIユーザーが文字列を渡すことができないのと同じように、値がメソッドシグネチャで指定された範囲内にあるかどうかを確認せずに値を渡すことはできません。チェックされていない場合は、コンパイル時エラーが発生するため、実行時エラーを回避できます。

しかし、現在、このタイプのコンパイル時チェックをサポートしているコンパイラ/プラットフォームはほとんどありません。したがって、それは開発者の頭痛の種です。しかし理想的には、メソッドはサポートされていない値に対して意味のある例外をスローし、それを明確に文書化する必要があります。

ところで、私はジョー・ダフィーがここで提案したエラーモデルが本当に大好きです


ユーザー入力のコンパイル時エラーをどのように提供しますか?
ジャックB

@JacquesB 実際の値が範囲内にあるかどうかに関係なく、ユーザー入力が挿入後に範囲内にあることが確認さない場合、コンパイラはエラーを発行します。
グルシャン

ただし、入力を拒否するか、有効な範囲に変換するかを決定する必要があるため、元の質問には答えられません。
ジャックB

@JacquesB最初に言っておきたいのですが、OPはAPIを開発していて、UIは他の誰かが開発し、APIを消費しているといつも思っていました。私はこの点について間違っていました。質問をもう一度読んで、これに気付きました。私が言おうとしていることは、検証はAPIコンシューマーで行われるべきであり、そうでない場合はコンパイラーがエラーをスローするということです。
グルシャン

そのため、入力と検証を含む新しいクラスを作成する必要があります。生の入力からクラスオブジェクトを構築するとき、どうなりますか?例外を投げますか?黙って通る?あなただけの問題を移動します。
-djechlin

-1

デフォルトでは、例外がスローされます。strict=falseもちろんstrict=true、デフォルトのフラグのようなオプションを許可し、強制的に強制することもできます。これはかなり一般的です:

  • Java DateFormatlenientをサポートします。
  • GsonのJSONパーサーは、寛容なモードもサポートしています。
  • 等。

これにより、コードが複雑になり、メリットがありません。
ジャックB

@JacquesBはデフォルトで例外をスローするstrict=falseか、許可しますか?
-djechlin

@JacquesB厳格モードと寛容モードをサポートするAPIの例をいくつか追加しました。ご覧ください。
-djechlin

-2

私にとってのベストプラクティスは、ユーザー入力を変更しないことです。私が通常とるアプローチは、検証と実行を分離することです。

  • 指定されたパラメーターを使用するだけの単純なクラスを用意します。
  • デコレーターを使用して、実行クラスに影響を与えることなく自由に変更できる検証レイヤーを提供します(または、このアプローチが難しすぎる場合は検証ツールを挿入します)。

質問が参照している「ユーザ」APIを使用して、開発者、エンドユーザではなく...である
ジェイ・エルストン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.