数年前(おそらく数年前)に、できるだけ少ないifテストでプログラミングの魅力についてStackoverflowで読んだことを覚えています。 この質問 ある程度関連性がありますが、受け取ったパラメーターに応じてテストによって決定された値を返す多くの小さな関数を使用することに重点が置かれたと思います。非常に簡単な例は、これを使用することです。
_int i = 5;
bool iIsSmall = isSmall(i);
_
isSmall()
は次のようになります:
_private bool isSmall(int number)
{
return (i < 10);
}
_
これを行う代わりに:
_int i = 5;
bool isSmall;
if (i < 10) {
isSmall = true;
} else {
isSmall = false;
}
_
(論理的には、このコードは単なるサンプルコードです。私が作成しているプログラムの一部ではありません。)
これを行う理由は、見栄えが良く、プログラマーが論理エラーを起こしにくくなるためだと思います。このコーディング規約が正しく適用されている場合、唯一の目的がそのテストを行うことである関数を除いて、どこにもif-testは事実上表示されません。
さて、私の質問は次のとおりです。この規則に関するドキュメントはありますか?このスタイルの支持者と反対者の間で激しい議論を見ることができる場所はありますか?これを紹介したStackoverflowの投稿を検索しようとしましたが、見つかりません。
最後に、私は問題の解決策を求めていないので、この質問が取り下げられないことを願っています。私は単にこのコーディングスタイルについてもっと聞き、将来行うすべてのコーディングの品質を向上させたいと思っています。
この全体の「if」と「noif」のことで、私は Expression Problem1。基本的に、ifステートメントを使用するプログラミングと使用しないプログラミングはカプセル化と拡張性の問題であり、ifステートメントを使用する方が良い場合があるという観察結果です。2 また、メソッド/関数ポインタで動的ディスパッチを使用する方がよい場合もあります。
何かをモデル化する場合、心配する2つの軸があります。
この種のことを実装する1つの方法は、ifステートメント/パターンマッチング/ビジターパターンを使用することです。
data List = Nil | Cons Int List
length xs = case xs of
Nil -> 0
Cons a as -> 1 + length x
concat xs ys = case ii of
Nil -> jj
Cons a as -> Cons a (concat as ys)
もう1つの方法は、オブジェクト指向を使用することです。
data List = {
length :: Int
concat :: (List -> List)
}
nil = List {
length = 0,
concat = (\ys -> ys)
}
cons x xs = List {
length = 1 + length xs,
concat = (\ys -> cons x (concat xs ys))
}
Ifステートメントを使用する最初のバージョンでは、データ型に新しい操作を簡単に追加できることを理解するのは難しくありません。新しい関数を作成し、その中でケース分析を行うだけです。一方、これにより、データ型に新しいケースを追加することが困難になります。これは、プログラムをさかのぼってすべての分岐ステートメントを変更することを意味するためです。
2番目のバージョンはその逆です。データ型に新しいケースを追加するのは非常に簡単です。新しい「クラス」を作成し、実装する必要のある各メソッドに対して何をすべきかを指示するだけです。ただし、インターフェイスを実装したすべての古いクラスに新しいメソッドを追加することを意味するため、インターフェイスに新しい操作を追加するのは困難になりました。
言語がExpressionProblemを解決し、新しいケースと新しい操作の両方をモデルに簡単に追加できるようにするために使用するさまざまなアプローチがあります。ただし、これらのソリューションには長所と短所があります3 したがって、一般的には、OOと、どの軸に応じてステートメントを拡張するのを簡単にするかによって、ifステートメントを選択するのが良い経験則だと思います。
とにかく、あなたの質問に戻ります私が指摘したいことがいくつかあります:
1つ目は、すべてのifステートメントを削除してメソッドディスパッチに置き換えるというOO "mantra"は、ほとんどのOO =言語には、「ifstatemsnts」がカプセル化に適していないことに関係するタイプセーフ代数的データ型がありません。タイプセーフにする唯一の方法はメソッド呼び出しを使用することなので、ifステートメントを使用するプログラムを ビジターパターン4 またはさらに悪いことに、ビジターパターンを使用する必要があるプログラムを単純なメソッドディスパッチを使用するプログラムに変換するため、間違った方向への拡張が容易になります。
第二に、私はあなたができるという理由だけで物事を機能にブレーキをかけることの大ファンではないということです。特に、すべての関数が5行しかなく、他の関数を大量に呼び出すスタイルは、かなり読みにくいと思います。
最後に、あなたの例ではifステートメントが実際に削除されていないと思います。基本的に、実行しているのは整数から新しいデータ型への関数(Big用とSmall用の2つのケース)であり、データ型を操作するときにifステートメントを使用する必要があります。
data Size = Big | Small
toSize :: Int -> Size
toSize n = if n < 10 then Small else Big
someOp :: Size -> String
someOp Small = "Wow, its small"
someOp Big = "Wow, its big"
式の問題の観点に戻ると、toSize/isSmall関数を定義する利点は、数値が1つの場所に収まるケースを選択するロジックを配置し、関数はその後のケースでのみ動作できることです。ただし、これは、コードからifステートメントを削除したことを意味するものではありません。 toSizeがファクトリ関数であり、BigクラスとSmallクラスがインターフェイスを共有している場合は、はい、コードからifステートメントを削除します。ただし、out isSmallがブール値または列挙型を返すだけの場合は、以前と同じ数のifステートメントがあります。 (将来、新しいメソッドまたは新しいケース(たとえば、Medium)を簡単に追加できるようにするかどうかに応じて、使用する実装を選択する必要があります)
1-問題の名前は、「式」データ型(数値、変数、部分式の加算/乗算など)があり、評価関数などを実装したいという問題に由来します。
2-または、よりタイプセーフにしたい場合は、代数的データ型のパターンマッチング...
3-たとえば、「ディスパッチャ」が表示できる「トップレベル」ですべてのマルチメソッドを定義する必要がある場合があります。 ifステートメント(およびラムダ)を他のコードの奥深くにネストして使用できるため、これは一般的な場合と比較した場合の制限です。
4-本質的に代数的データ型の「チャーチエンコーディング」
それは本当にコンベンションですか?欲求不満があるかもしれないという理由だけで、最小限のif-constructを殺す必要がありますか?
わかりました。ステートメントが制御不能になる傾向がある場合、特に多くの特殊なケースが時間の経過とともに追加された場合はそうです。ブランチが次々と追加され、最後に、このスパゲッティコードのgrwonインスタンスに何時間もの時間とコーヒーを何杯か費やさずに、すべてが何をするのかを理解することはできません。
しかし、すべてを別々の機能に入れるのは本当に良い考えですか?コードは再利用可能である必要があります。コードは読み取り可能である必要があります。ただし、関数呼び出しでは、ソースファイルでさらに検索する必要があります。すべてのifがこのように片付けられている場合は、ソースファイルを常にスキップするだけです。これは読みやすさをサポートしていますか?
または、どこにも再利用されていないifステートメントを検討してください。慣例のために、それは本当に別の機能に入る必要がありますか?ここにもオーバーヘッドがあります。パフォーマンスの問題は、このコンテキストにも関連している可能性があります。
私が言おうとしていること:コーディング規約に従うのは良いことです。スタイルは重要です。ただし、例外があります。プロジェクトに合った優れたコードを書いて、将来を念頭に置いてください。結局のところ、コーディング規約は、私たちに何も強制することなく、優れたコードを作成するのに役立つガイドラインにすぎません。
私はそのような対流について聞いたことがありません。とにかく、それがどのように機能するのかわかりません。確かに、iIsSmall
を持つことの唯一のポイントは、後で分岐することです(おそらく他の値と組み合わせて)?
私が聞いたことは、iIsSmall
まったくのような変数を持つことを避けるための引数です。 iIsSmall
は、行ったテストの結果を保存しているだけなので、後でその結果を使用して何らかの決定を下すことができます。では、決定を下す必要がある時点でi
の値をテストしてみませんか?つまり、代わりに:
_int i = 5;
bool iIsSmall = isSmall(i);
...
<code>
...
if (iIsSmall) {
<do something because i is small>
} else {
<do something different because i is not small>
}
_
書くだけ:
_int i = 5
...
<code>
...
if (isSmall(i)) {
<do something because i is small>
} else {
<do something different because i is not small>
}
_
そうすれば、分岐点にあるので、実際に分岐しているものを分岐点で知ることができます。とにかく、この例ではそれは難しいことではありませんが、テストが複雑な場合は、変数名ですべてをエンコードすることはおそらくできないでしょう。
また、より安全です。 iIsSmall
という名前が誤解を招く恐れはありません。コードを変更して別のテストを行ったため、またはi
を呼び出した後にisSmall
が実際に変更されたためです。必ずしも小さいとは限りません。または、誰かがダム変数名などを選択したためです。
明らかに、これは常に機能するとは限りません。 isSmall
テストに費用がかかり、その結果を何度も分岐する必要がある場合は、何度も実行する必要はありません。また、些細なことでない限り、その呼び出しのコードを何度も複製したくない場合もあります。または、i
について知らない呼び出し元が使用するフラグを返すこともできます(ただし、変数に格納してからisSmall(i)
を返すのではなく、単に返すことができます。変数を返します)。
ところで、別の関数はあなたの例では何も保存しません。 bool
関数のreturnステートメントと同じくらい簡単に、bool
変数への割り当てに_(i < 10)
_を含めることができます。つまり、_bool isSmall = i < 10;
_を簡単に書くことができます-これが、個別の関数ではなく、ifステートメントを回避します。 if (test) { x = true; } else { x = false; }
またはif (test) { return true; } else { return false; }
の形式のコードは常にばかげています。 _x = test
_または_return test
_を使用してください。