Simple Design and Testing Conference から来ました。セッションの1つで、プログラミング言語の邪悪なキーワードについて話していました。 Corey Haines は、この主題を提案しましたが、if
ステートメントは絶対的な悪であると確信していました。彼の代替手段は、 述語 で関数を作成することでした。 if
が悪である理由を説明してください。
if
を乱用する非常にいコードを記述できることを理解しています。しかし、私はそれがそんなに悪いとは思わない。
条件句により、コードの管理が難しくなる場合があります。これにはif
ステートメントだけでなく、より一般的にはswitch
ステートメントが含まれます。通常、対応するif
よりも多くのブランチが含まれます。
if
を使用することが完全に合理的である場合がありますユーティリティメソッド、拡張機能、または特定のライブラリ関数を作成する場合、if
sを避けることができない可能性があります(そうすべきではありません)。この小さな関数をコーディングするより良い方法はありませんし、それ以上に自己文書化することもできません:
// this is a good "if" use-case
int Min(int a, int b)
{
if (a < b)
return a;
else
return b;
}
// or, if you prefer the ternary operator
int Min(int a, int b)
{
return (a < b) ? a : b;
}
一方、ある種の型コードをテストするコード、または変数が特定の型であるかどうかをテストするコードに遭遇した場合、これはリファクタリングの良い候補である可能性が高いです。つまり、 条件付きのポリモーフィズム 。
これは、呼び出し元が特定の型コードで分岐できるようにすることで、コード全体に多数のチェックが散在する可能性が生じ、拡張機能と保守がはるかに複雑になるためです。一方、ポリモーフィズムを使用すると、この分岐決定をプログラムのルートにできるだけ近づけることができます。
考慮してください:
// this is called branching on a "type code",
// and screams for refactoring
void RunVehicle(Vehicle vehicle)
{
// how the hell do I even test this?
if (vehicle.Type == CAR)
Drive(vehicle);
else if (vehicle.Type == PLANE)
Fly(vehicle);
else
Sail(vehicle);
}
共通だがタイプ固有の(つまり、クラス固有の)機能を個別のクラスに配置し、仮想メソッド(またはインターフェース)を通じて公開することにより、プログラムの内部部分がこの決定を呼び出し階層の上位に委任することができます(潜在的にコードの単一の場所で)、テスト(モック)、拡張性、およびメンテナンスがはるかに簡単になります。
// adding a new vehicle is gonna be a piece of cake
interface IVehicle
{
void Run();
}
// your method now doesn't care about which vehicle
// it got as a parameter
void RunVehicle(IVehicle vehicle)
{
vehicle.Run();
}
RunVehicle
メソッドが正常に機能するかどうかを簡単にテストできるようになりました。
// you can now create test (mock) implementations
// since you're passing it as an interface
var mock = new Mock<IVehicle>();
// run the client method
something.RunVehicle(mock.Object);
// check if Run() was invoked
mock.Verify(m => m.Run(), Times.Once());
if
条件のみが異なるパターンは再利用できます質問のif
を「述語」に置き換えることについての議論に関して、ヘインズはおそらく、条件式のみが異なる類似したパターンがコード上に存在することを言及したかったでしょう。条件式はif
sと一緒に出現しますが、全体の考え方は、式をパラメーターとして残して、繰り返しパターンを別のメソッドに抽出することです。これはLINQが既に行っていることです。通常、代替のforeach
と比較してよりクリーンなコードになります。
これら2つの非常によく似た方法を検討してください。
// average male age
public double AverageMaleAge(List<Person> people)
{
double sum = 0.0;
int count = 0;
foreach (var person in people)
{
if (person.Gender == Gender.Male)
{
sum += person.Age;
count++;
}
}
return sum / count; // not checking for zero div. for simplicity
}
// average female age
public double AverageFemaleAge(List<Person> people)
{
double sum = 0.0;
int count = 0;
foreach (var person in people)
{
if (person.Gender == Gender.Female) // <-- only the expression
{ // is different
sum += person.Age;
count++;
}
}
return sum / count;
}
これは、条件を述語に抽出できることを示しており、これらの2つのケース(および他の多くの将来のケース)に対して単一のメソッドを使用できます。
// average age for all people matched by the predicate
public double AverageAge(List<Person> people, Predicate<Person> match)
{
double sum = 0.0;
int count = 0;
foreach (var person in people)
{
if (match(person)) // <-- the decision to match
{ // is now delegated to callers
sum += person.Age;
count++;
}
}
return sum / count;
}
var males = AverageAge(people, p => p.Gender == Gender.Male);
var females = AverageAge(people, p => p.Gender == Gender.Female);
そして、LINQには既にこのような便利な拡張メソッドがたくさんあるので、実際には独自のメソッドを記述する必要さえありません。
// replace everything we've written above with these two lines
var males = list.Where(p => p.Gender == Gender.Male).Average(p => p.Age);
var females = list.Where(p => p.Gender == Gender.Female).Average(p => p.Age);
この最後のLINQバージョンでは、if
ステートメントは完全に「消失」しましたが、
if
自体にではなく、コードパターン全体にありました(単に重複していたためです)。if
は実際にはまだ存在していますが、LINQ Where
拡張メソッド内に記述されており、テストされ、変更のために閉じられています。独自のコードを少なくすることは常に良いことです。テストするものが少なくなり、間違ったものが少なくなり、コードは追跡、分析、および保守が簡単になります。これをすべて言ったので、あなたは 眠れぬ夜を過ごすべきではありません いくつかの条件をあちこちに持つことについて。これらの答えはいくつかの一般的な経験則を提供しますが、リファクタリングを必要とする構成を検出できる最良の方法は経験を通してです。時間が経つにつれて、同じ句が何度も何度も変更されるパターンが出現します。
if
が悪である可能性がある別の意味があります:ポリモーフィズムの代わりに来るとき。
例えば。
if (animal.isFrog()) croak(animal)
else if (animal.isDog()) bark(animal)
else if (animal.isLion()) roar(animal)
の代わりに
animal.emitSound()
しかし、基本的にifは、それが何をするのかについて完全に受け入れられるツールです。もちろん悪用されたり悪用されたりする可能性はありますが、gotoのステータスにはほど遠い状態です。
Code Completeからの良い引用:
あなたのプログラムを維持している人があなたがどこに住んでいるかを知っている暴力的なサイコパスであるかのようにコードします。
—匿名
IOW、シンプルにしてください。特定の領域で述語を使用することでアプリケーションの可読性が向上する場合は、それを使用します。それ以外の場合は、「if」を使用して先に進みます。
正直に言うと、あなたが何をしているかにかかっていると思います。
単純なif..else
ステートメントがある場合、なぜpredicate
を使用するのですか?
できれば、switch
を使用して大きなif
置換を行い、その後、大規模な操作に述語を使用するオプションがある場合(それが理にかなっている場合、コードは維持するのが悪夢です) 、 これを使って。
この男は私の好みに少しつまらなかったようです。すべてのif
をPredicatesに置き換えることは、おかしな話です。
年の初めに開始された Anti-If キャンペーンがあります。主な前提は、多くの場合、ネストされたifステートメントが多くの場合ポリモーフィズムに置き換えられることです。
代わりにPredicateを使用する例に興味があります。これは、関数型プログラミングのラインに沿ったものですか?
if
は悪ではありません(私はまた、コードを書く習慣に道徳を割り当てることは素晴らしいと思います...)。
ヘインズ氏は愚かであり、笑われるべきです。
お金についての聖書の節のように、もし文が悪でなければ-if文の愛は悪です。 ifステートメントのないプログラムはとんでもないアイデアであり、必要に応じて使用することが不可欠です。しかし、100個のif-else ifブロックが連続してあるプログラム(悲しいことに、私は見たことがあります)は間違いなく悪です。
私は最近、ifステートメントをコードの匂いとして見るようになったと言わざるを得ません。特に、同じ条件を何度か繰り返していることに気づいたときです。ただし、コードのにおいについて理解する必要があるものがあります。コードが悪いことを必ずしも意味するわけではありません。彼らはただ良いチャンスが悪いということを意味しています。
たとえば、コメントはMartin Fowlerによってコードの匂いとしてリストされていますが、「コメントは悪です。使用しないでください」と言う人を真剣に受け止めません。
ただし、一般的には、可能であればifステートメントの代わりにポリモーフィズムを使用することを好みます。これにより、エラーの余地が非常に少なくなります。多くの場合、条件を使用すると、多くのtramp引数にもつながることがわかります(条件を形成するために必要なデータを適切なメソッドに渡す必要があるため)。
私はあなたに同意します。 彼は間違っていた。あなたはそのようなもので行き過ぎて、あなた自身の利益のためにあまりにも賢いことができます。
Ifの代わりに述語を使用して作成されたコードは、維持およびテストするのが恐ろしいでしょう。
おそらく量子コンピューティングでは、IFステートメントを使用せず、計算の各レッグを続行し、終了時に関数「崩壊」のみを有効な結果にすることが賢明な戦略になります。
述部は、PROLOGなどの論理/宣言型プログラミング言語からのものです。制約解決などの特定のクラスの問題では、それらは多くの段階的な描画されたif-this-do-that-then-do-this-crapに比べて間違いなく優れています。命令型言語で解決するのが長く複雑な問題は、PROLOGのほんの数行で実行できます。
また、スケーラブルなプログラミングの問題もあります(マルチコア、Webなどへの移行による)。 if文と命令型プログラミングは、一般に段階的な順序で配置される傾向があり、スケーラブルではありません。ただし、論理宣言とラムダ計算では、問題を解決する方法と、問題に分解できる部分について説明しています。その結果、そのコードを実行するインタープリター/プロセッサーは、コードを効率的に断片に分割し、複数のCPU /コア/スレッド/サーバーに分散できます。
どこでも絶対に役に立たない。 ifステートメントの代わりに述語を使用してデバイスドライバーを記述してみたくありません。しかし、はい、主なポイントはおそらく健全であり、常に使用していなければ、少なくとも慣れる価値があると思います。
(if
ステートメントの置き換えに関する)述語の唯一の問題は、それらをテストする必要があることです:
function void Test(Predicate<int> pr, int num)
{
if (pr(num))
{ /* do something */ }
else
{ /* do something else */ }
}
もちろん、三項演算子(?:
)、しかしそれは変装したif
ステートメントです...
おそらく、コードの循環的な複雑さを抑え、関数内の分岐点の数を減らしたいという願望に帰着するでしょう。関数を簡単に多数の小さな関数に分解して、それぞれをテストできる場合、複雑さを軽減し、コードをより簡単にテストできるようにすることができます。
IMO:彼は議論を呼び起こし、人々に「if」の誤用について考えさせようとしたのではないかと思います。プログラミング構文のこのような基本的な構築を完全に回避することを真剣に提案する人はいませんでしたか?
時には、あなたの主張をするために極端な立場を取る必要がある。この人はif
を使用していると確信していますが、if
を使用するたびに、異なるパターンがコードを明確にするかどうかについて少し考えてみる価値があります。
ポリモーフィズムをif
に優先させることがこの中核です。のではなく:
_if(animaltype = bird) {
squawk();
} else if(animaltype = dog) {
bark();
}
_
... 使用する:
_animal.makeSound();
_
しかし、それは、Animalクラス/インターフェースを持っていることを前提としています。したがって、if
が実際に伝えているのは、そのインターフェースを作成する必要があるということです。
現実の世界では、どのようなif
sがポリモーフィズムの解決につながるのでしょうか?
_if(logging) {
log.write("Did something");
}
_
コード全体を見ると本当にイライラします。代わりに、Loggerの実装を2つ(またはそれ以上)持つのはどうですか?
_this.logger = new NullLogger(); // logger.log() does nothing
this.logger = new StdOutLogger(); // logger.log() writes to stdout
_
Strategy Patternにつながります。
の代わりに:
_if(user.getCreditRisk() > 50) {
decision = thoroughCreditCheck();
} else if(user.getCreditRisk() > 20) {
decision = mediumCreditCheck();
} else {
decision = cursoryCreditCheck();
}
_
...あなたが持つことができる...
_decision = getCreditCheckStrategy(user.getCreditRisk()).decide();
_
もちろん、getCreditCheckStrategy()
にはif
が含まれている場合があります。これは適切な場合があります。あなたはそれが属するきちんとした場所にそれを押しました。
If文は悪だと思いますが、If式はそうではありません。この場合のif式の意味は、C#三項演算子(条件?trueExpression:falseExpression)のようなものです。これは純粋な関数であるため(数学的な意味で)悪ではありません。新しい値に評価されますが、他には何の影響もありません。このため、代替モデルで機能します。
命令型のIfステートメントは、必要のないときに副作用を引き起こすことを強制するため、悪です。 Ifステートメントを意味のあるものにするには、条件式に応じて異なる「効果」を生成する必要があります。これらの影響は、プログラム、グラフィックレンダリング、またはデータベーストランザクションのようなものであり、プログラムの外にあるものを変更します。または、既存の変数の状態を変更する代入ステートメントである可能性があります。通常、これらの影響を最小限に抑え、実際のロジックから分離する方が適切です。しかし、Ifステートメントのため、これらの「条件付きで実行される効果」をコードのどこにでも自由に追加できます。それは悪いことだと思います。
Rubyにありますnless;)
しかし、真剣におそらくifが次のgotoであり、ほとんどの人がそれが悪であると考える場合でも、物事を簡素化/高速化することです(そして、低レベルの高度に最適化されたコードは必須です。