(たとえば、bool / intの代わりに)voidを使用する必要がある場合と理由


30

開発者が関数にとって重要ではない何かを返すことを選択したメソッドに時々出くわします。つまり、コードを見ると、どうやら同じように機能しているように見えvoid、少し考えてから「なぜ?」これはおなじみですか?

時には、単にaを実行するのではなく、boolまたはのようなものを返す方が良い場合が多いことに同意します。しかし、全体像では、賛否両論についてはわかりません。intvoid

状況によっては、an intを返すことにより、呼び出し側はメソッドの影響を受ける行またはオブジェクトの量を知ることができます(たとえば、5つのレコードがMSSQLに保存されます)。「InsertSomething」のようなメソッドがブール値を返すtrue場合、成功した場合、そうでない場合に返すように設計されたメソッドを持つことができますfalse。発信者は、その情報に基づいて行動するかどうかを選択できます。

一方、

  • メソッド呼び出しのあまり明確でない目的につながる可能性がありますか?悪いコーディングはしばしばメソッドの内容を再確認せざるを得ません。何かを返す場合、メソッドは返された結果で何かをしなければならない型であることを知らせます。
  • もう1つの問題は、メソッドの実装が不明な場合、開発者は機能的に重要ではないものを返すことにしたのですか?もちろんコメントできます。
  • メソッドの閉じ括弧で処理を終了できる場合、戻り値を処理する必要があります。
  • ボンネットの下で何が起こりますか?falseスローされたエラーのために、呼び出されたメソッドは取得されましたか?または、評価結果のためにfalseを返しましたか?

これについてのあなたの経験は何ですか?これにどのように対応しますか?


1
voidを返す関数は、技術的にはまったく関数ではありません。これは単なるメソッド/手順です。void少なくとも返すことにより、開発者はメソッドの戻り値が重要でないことを知ることができます。値を計算するのではなく、アクションを実行します。
KChaloux

回答:


32

メソッドの成功または失敗を示すブール戻り値の場合Try、さまざまな.NETメソッドで使用される-prefixパラダイムを好みます。

たとえばvoid InsertRow()、同じキーを持つ行が既に存在する場合、メソッドは例外をスローできます。問題は、InsertRowを呼び出す前に、呼び出し側が行が一意であることを保証すると仮定することは合理的ですか?答えがいいえのbool TryInsertRow()場合、行が既に存在する場合にfalseを返すを提供します。データベース接続エラーなどのその他の場合、データベース接続の維持が呼び出し側の責任であると想定して、TryInsertRowは引き続き例外をスローする可能性があります。


4
予想される失敗と予期しない失敗の処理を区別するための+1- 私自身の答えはこれを十分に強調していませんでした。
ペテルトレック

tryXXメソッドとmainメソッドの両方を維持しやすく、目的を理解しやすくするように見えるTryXXメソッドの原則を発明しようとすることに対する絶対的な+1です。
独立

+1そうですね。この質問に対する最善の答えの鍵は、例外をスローするアサーティブメソッドのオプションを呼び出し側に与えたい場合があるということです。ただし、これらの場合、例外はアサーティブメソッドによってキャッチおよび偽処理されるべきではありません。重要なのは、呼び出し元が責任を負うことです。一方、Try-paradigm(またはMonad)は例外をキャッチして処理し、boolを返すか、詳細が必要な場合は記述的なEnumを渡す必要があります。呼び出し元は、Try / Monadが例外をスローしないことを期待していることに注意してください。それはエントリーポイントのようなものです。
スアメア

したがって、Try / Monadから例外が発生することはないと予想されるため、boolは呼び出し側にdb接続が失敗したこと、またはhttpリクエストが異なる動作をするために必要な多くの戻り値の1つを通知するのに十分な記述的ではないため、 Try / Monadにはboolの代わりにEnumを使用できます。
スアメア

TryInsertRowがある場合-boolを返す-たとえば、データベースに複数の一意の制約がある場合-エラーが何であったかを正確に知るには-そしてそれをユーザーに伝えますか?エラーメッセージ/コードおよび成功ブールを含むより豊富な戻り値型を使用する必要がありますか?
niico

28

「ステータスコード」を返すIMHOは、C#などの中〜高レベル言語で例外が一般的になる前の歴史的な時代に由来します。最近では、何らかの予期しないエラーが原因でメソッドが成功しなかった場合に例外をスローする方が適切です。そうすれば、エラーが気付かれないことが確実になり、呼び出し元は適切に対処できます。そのため、これらの場合、voidブール値のステータスフラグまたは整数のステータスコードの代わりにメソッドが何も返さない(つまり)ことはまったく問題ありません。(もちろん、メソッドがスローする例外とそのタイミングを文書化する必要があります。)

一方、厳密な意味での関数、つまり入力パラメーターに基づいて何らかの計算を実行し、その計算の結果を返すことが期待される場合、戻り値の型は明らかです。

場合1は、2つの間の灰色の領域は、存在するかもしれません、呼び出し元にとって有用であると思われる場合に、メソッドから「余分な」情報を返すます。挿入の影響を受ける行の数に関する例と同様です。または、メソッドを使用して要素を連想コレクションに入れる場合、そのキーに関連付けられた以前の値があれば、それを返すと便利です。このような使用は、APIの(既知および予想される)使用シナリオを慎重に分析することで特定できます。


4
ステータスコードに対する例外の場合は+1。これの例外(意図しない駄洒落)は、よく使用されるTryXXパターンです。
アンディローリー

Éter私はあなたと一緒にいます。TcFとエラーを沈黙しない!ただし、おそらくグレーゾーンです。そこ常に利用リターンの何かであること。影響を受ける行、最後に挿入されたID、実行されたソフトウェアのPIDですが、それでも開発中に「あ、そうだ、これを返す」と考える方が簡単だと思います。それから1年後、拡張すると、「何?これは「int someThing = RunProcess(x)」を実行します。実行できない場合はどうなりますか?」
独立

+1の追加情報が、C#のような高レベル言語でこのようなメソッドをコーディングする理由です。これにより、必要なときに追加情報を簡単に使用できます。
ashes999

2
-1うわー、あなた自身の言葉はとても矛盾していて間違っています。制御フローまたは通信に例外を使用しないでください。それらは条件付きよりも無限に高価です。「例外が一般的になる前」はいつですか?... try / catchブロックが一般的になる前のことですか?例外は永遠に一般的です。「予期しないエラーが発生した場合に例外をスローする...」予期しないものに対してどのように対処しますか?なぜ例外をキャッチし、それを再スローするのですか?それはとても高価です。なぜ「エラー」と言うのですか?エラーと例外は別のものです。
スアメア

1
そして、スローされた例外が気付かれないと誰が言うのでしょうか?誰かがcatchブロックにログインすることを強制するものは何もありません。「then」ブロックにログインしてみませんか?なぜ「メソッドがいつ例外をスローするかを文書化する」のですか?自己文書化コードを記述し、メソッドがそのメソッドの予想される終了状態を列挙型で返す場合はどうでしょうか?状態をチェックすることで例外の可能性を回避できる場合は、スローせずにキャッチして処理する必要があります。
スアメア

14

ピーターの答えは例外をうまくカバーしています。ここでのその他の考慮事項には、コマンドとクエリの分離が

CQSの原則では、メソッドはコマンドではなく、クエリではなく、両方である必要があります。コマンドは値を返すことはなく、状態を変更するだけで、クエリは状態を返すだけで、変更しないでください。これにより、セマンティクスが非常に明確になり、コードの可読性と保守性が向上します。

CQSの原則に違反することは良い考えです。これらは通常、パフォーマンスまたはスレッドセーフに関するものです。


1
-1間違っています。まず、CQSのポイント全体がQにあります。呼び出し側は、サービスの状態を変更することを恐れずに、何らかのステートフルサービスを通じて計算または外部呼び出しを取得できる必要があります。また、CQSは大まかに従うのに役立つ原則ですが、特定の設計でのみ厳密に従います。最後に、厳密なCQSであっても、コマンドが値を返せないということはなく、外部の計算を計算したり呼び出したりしてデータを返すべきではないと言うことはありません。CまたはQのいずれかは、呼び出し側にとって有用な場合、エンドステートを自由に返すことができます。
スアメア

ああ、しかしコマンドの影響を受ける行の数により、古いコマンドから新しいコマンドを簡単に作成できます。出典:長年の経験。
ジョシュア

@Joshua:同様に、「新しいレコードを追加し、追加中に生成されたID(一部の構造では行番号)を返す」は、他の方法では効率的に達成できない目的に使用できます。
-supercat

影響を受ける行を返す必要はありません。コマンドは、影響を受ける数を知っている必要があります。それが#以外の場合、奇妙なことが起こったのでロールバックして例外をスローする必要があります。したがって、それは、呼び出し元が実際に気にかけない情報です(ユーザーが真の#を知る必要があり、与えられたデータから判断できない場合を除く)。DB生成ID、データを取得する状態が変わるスタック/キューなどの灰色の領域はまだありますが、これらのシナリオで「CommandQuery」を作成するのが好きなので、誰も「奇妙な」ことを試みません。
ダニエル・ローレンツ

3

どんな道でも、あなたはこれと一緒に歩きます。将来使用するために戻り値を入れないでください(たとえば、関数が常にtrueを返すようにする)。これはひどい労力の無駄であり、コードを読みやすくしません。戻り値がある場合は常にそれを使用する必要があり、それを使用できるようにするには、少なくとも2つの可能な戻り値が必要です。


1これは質問に答えていない、しかし、それは間違いなく正しい情報です。決定を下す場合、その決定は必要性に基づいている必要があります。呼び出し元が戻り値のデータを役に立たない場合は、実行しないでください。そして、「将来の校正」のために真の時間の100%を返すことに誰も使い道がありません。確かに、もし「未来」が...数日後で、それに対するタスクが存在するなら、それは別の話です、笑。
スアメア

1

以前は、多くのvoid関数を記述していました。しかし、メソッドチェーンクラック全体に乗ったので、ボイドではなくこれを返し始めました。ボイドでスクワットできないリターン原因を誰かが利用できるようにするかもしれません。そして、彼らがそれで何もしたくない場合、彼らはそれを単に無視することができます。


1
メソッドチェーンは、継承を面倒で困難にする可能性があります。メソッドを "void"にしたくない以外に、それを行う正当な理由がない限り、メソッドチェーンは行いません。
KallDrexx

本当です。ただし、継承は面倒で使いにくい場合があります。
ワイアットバーネット

...(オブジェクト全体を述べた)何でも復帰したばかりのフローの内側と外側の方法をご確認ください多くの注意を払って、未知のコードを見てみると
独立した

いつか入ったとは思わないRuby?それが、メソッドチェーンを紹介した理由です。
-KChaloux

メソッドチェーンの問題の1つは、次のようなステートメントreturn Thing.Change1().Change2();が変更Thingして元の参照を返すことを期待しているのか、元のオブジェクトとは異なる新しいものへの参照を返すのを期待しているのかを明確にしないことですオリジナルのまま。
supercat 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.