要するに、私はJavaこのようなメソッドで作られたコードの一部を継承しました:
@Override
public Action decide() {
if (equalz(in.a, "LOC")) {//10
if(( //20
equalz(tmp.b, "BA")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
)||(
equalz(tmp.b, "HV")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
&& varEqualsOneOf(in.e,"FLG","FLR","RRG"))
) {
if(equalz(tmp.b, "BA")) {//30
if(varEqualsOneOf(in.e,"RRG","NL")//40
&& lessThan(in.f,in.g)) {
return Action.AC015;
} else {
if(varEqualsOneOf(in.e,"FLG","FLR")) {//50
return Action.AC015;
} else {
return Action.AC000;
}
}
} else {
if (equalz(in.e,"RRG")) {//60
return Action.AC014;
} else {
return Action.AC010;
}
}
} else {
if(equalsOrMissing(in.c,"U")
&& varEqualsOneOf(in.e,"FLG","FLR")) {//70
return Action.AC011;
} else {
return Action.AC000;
}
}
} else {
if(varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA","INS")) {//80
if (//90
equalz(in.h,"A")
|| equalz(in.i,"Y")
) {
return Action.AC000;
} else {
if(notEquals(in.h,"U")) {//100
if(greaterThan(in.j,in.k)) {//110
return Action.AC000;
} else {
if(varEqualsOneOf(in.e,"FLG","FLR")) {//120
if(notEquals(in.l,"U")) {//130
return Action.AC004;
} else {
if(oneOfVarsEqual(in.m,in.n,"Y")) {//140
return Action.AC012;
} else {
return Action.AC002;
}
}
} else {
if(//150
(
equalz(in.e,"RRG")
&& varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA")
)
||
(
varEqualsOneOf(in.e,"RRG","NL")
&& equalz(in.a,"INS")
)
) {
if (notEquals(in.l,"U")) {//160
if (notEquals(in.o,"U")) {//170
return Action.AC005;
} else {
return Action.AC018;
}
} else {
bp(false);
if (equalz(in.n,"Y")) {//180
return Action.AC012;
} else {
return Action.AC002;
}
}
} else {
if (equalz(in.p,in.q)) {//190
if (notEquals(in.o,"U")) {//200
return Action.AC006;
} else {
return Action.AC008;
}
} else {
return Action.AC000;
}
}
}
}
} else {
bp(false);
if (notEquals(in.l,"U")//210
&& varEqualsOneOf(in.e,"FLG","FLR")) {
return Action.AC004;
} else {
return Action.AC000;
}
}
}
} else {
return Action.AC000;
}
}
}
ここで、実行する正しいアクションを返すために一連の条件がチェックされます。この解決策はあまり洗練されていません。さらに、特定の出力(DBに記録する)につながるパスをすばやく確認する方法が必要です。
コードをリファクタリングし、条件ツリーをコンパクトに表す方法を考えています。これにより、読み取り、保守、およびログが少し簡単になる可能性があります。
私は、4つの要素で構成されるすべての条件の行を持つ2次元マトリックスについて考えました
だから、最後に私はこの行列を持っています
Object[][]matrix=
{
{10,20,70,
equalz(in.a, "LOC")},
{20,30,80,
(equalz(tmp.b, "BA")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
)||(
equalz(tmp.b, "HV")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
&& varEqualsOneOf(in.e,"FLG","FLR","RRG"))},
{30,40,70,
equalz(tmp.b, "BA")},
{40,Action.AC015,50,
varEqualsOneOf(in.e,"RRG","NL")
&& lessThan(in.f,in.g)},
{50,Action.AC015,Action.AC000,
varEqualsOneOf(in.e,"FLG","FLR")},
{60,Action.AC014,Action.AC010,
equalz(in.e,"RRG")},
{70,Action.AC011,Action.AC000,
equalsOrMissing(in.c,"U")
&& varEqualsOneOf(in.e,"FLG","FLR")},
{80,90,Action.AC000,
varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA","INS")},
{90,Action.AC000,100,
equalz(in.h,"A")|| equalz(in.i,"Y")},
{100,110,210,
notEquals(in.h,"U")},
{110,Action.AC000,120,
greaterThan(in.j,in.k)},
{120,130,150,
varEqualsOneOf(in.e,"FLG","FLR")},
{130,Action.AC004,140,
notEquals(in.l,"U")},
{140,Action.AC012,Action.AC002,
oneOfVarsEqual(in.m,in.n,"Y")},
{150,160,190,
(equalz(in.e,"RRG")
&& varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA")
)||(
varEqualsOneOf(in.e,"RRG","NL")
&& equalz(in.a,"INS"))},
{160,170,180,
notEquals(in.l,"U")},
{170,Action.AC005,Action.AC018,
notEquals(in.o,"U")},
{180,Action.AC012,Action.AC002,
equalz(in.n,"Y")},
{190,200,Action.AC000,
equalz(in.p,in.q)},
{200,Action.AC006,Action.AC008,
notEquals(in.o,"U")},
{210,Action.AC004,Action.AC000,
notEquals(in.l,"U")
&& varEqualsOneOf(in.e,"FLG","FLR")}
}
;
条件間をジャンプするメソッドによって駆動され、条件のIDを持つパスを表す実行全体を記録します。
初めはこれは良いアイデアかもしれないと思っていましたが、今ではそれが書き留められているかどうかはわかりません。
この問題をどのように処理しますか?私のソリューションはまったく価値がありませんか?目標を達成する他のより良い方法はありますか?
私はマトリックスの考えには行きません。コードは少ないですが、読みやすくなるとは思いません。どちらかといえば、1つのことを精神的に評価できず、メソッドに残っているコードの半分を無視できないため、読みにくいと思います。私はすべてをチェックしなければなりません。
まずはネストの削減に取り組みます。たとえば、最も外側のifブロックとそれ以外のブロックを見ると、次のようになります。
if (equalz(in.a, "LOC")) {
...
} else {
if(varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA","INS")) {
...
} else {
return Action.AC000;
}
}
最も外側のelseブロックには、ネストされた別のif-elseのみがあり、一般的なコードはないため、これをレベルの外に移動して、次のようにすることができます。
if (equalz(in.a, "LOC")) {
...
} else if(varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA","INS")) {
...
} else {
return Action.AC000;
}
この同じ原則をこのブロックに適用すると、次のようになります。
if(varEqualsOneOf(in.e,"RRG","NL")//40
&& lessThan(in.f,in.g)) {
return Action.AC015;
} else {
if(varEqualsOneOf(in.e,"FLG","FLR")) {//50
return Action.AC015;
} else {
return Action.AC000;
}
}
我々が得る:
if(varEqualsOneOf(in.e,"RRG","NL")//40
&& lessThan(in.f,in.g)) {
return Action.AC015;
} else if(varEqualsOneOf(in.e,"FLG","FLR")) {//50
return Action.AC015;
} else {
return Action.AC000;
}
ちょっと待って。 ifとelse ifブランチは同じものを返します。それらを組み合わせることができます。そうすると、次のようになります。
if((varEqualsOneOf(in.e,"RRG","NL")/*40*/ && lessThan(in.f,in.g))
|| (varEqualsOneOf(in.e,"FLG","FLR") /*50*/)) {
return Action.AC015;
} else {
return Action.AC000;
}
条件が少し厄介になっている場合、特にすべての貧しい人々の名前とマジックナンバーがそうです。これが、条件をカプセル化して意味のある名前を付けるヘルパー関数の作成を開始する場所です。だから私はこのようなものを得るでしょう:
if(IsCondition40(in) || IsCondition50(in)) {
return Action.AC015;
} else {
return Action.AC000;
}
.....
// obviously these still need better names but you get the idea. Magic strings and numbers should be replaced, etc.
// also, forgive my poor Java, not a language I use often enough to be any good at it
private boolean IsCondition40(In in) {
return varEqualsOneOf(in.e,"RRG","NL") && lessThan(in.f,in.g);
}
private boolean IsCondition50(In in) {
return varEqualsOneOf(in.e,"FLG","FLR");
}
そして今、それはどこかに行き始めています。それを一歩一歩分けていってください、そしてそれはより良くなり始めます。あなたが何かを壊していないことを確認するために、毎回行くたびにテストを実行することを忘れないでください(これを始める前にテストをしましたよね?)。
一般的な戻り値と条件を探し、それらを組み合わせるか、ヘルパー関数を作成します。入れ子を減らします。最終的には、もっと読みやすいものになります。
その後、他の回答で言及されているリファクタリングのいくつかを試してください。ゴミをリファクタリングすると、ゴミがリファクタリングされます。 :-)
そしてOOPに関するもう1つのこと
_tmp.b
_の値以外に、ifステートメントのallはinオブジェクト。 (私がそれを逃したのでなければ...) Tell Do n't Ask (またはこれを参照してください SO answer )は、decide()
メソッドを提案しますが、書き直され、inオブジェクトに移動されます。例えば.
public Action decideAction(String whateverTmpBReallyMeans);
これはプロジェクトで理にかなっている場合もあれば、そうでない場合もありますが、検討する価値はあります。
私はここであなたの質問に答えないかもしれませんが、コードを読んだときに私が持っていたいくつかのひどい反射を与えることを許可します。
このコードには多くのことがあり、一見して読みにくくなっています。これは、セグメントの動作に関する理解の欠如と、奇妙に因数分解されていることが原因です。特定の出力に至るまでの流れをたどることができるとのご要望を理解しましたが、これは単に形式が正しくないコードの影響のようです。コードが追跡しやすい場合は、returnステートメントへのパスを決定するための複雑な行列は必要ありません。
手始めにこれを見てみましょう:
if(( //20
equalz(tmp.b, "BA")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
)||(
equalz(tmp.b, "HV")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
&& varEqualsOneOf(in.e,"FLG","FLR","RRG"))
) {
これは、実際に起こっていることをよりよく説明するために、関数呼び出し内に含めることができます。次のような簡単なものでうまくいきます。
private boolean isFoobar(In in, Tmp tmp) {
return (equalz(tmp.b, "BA") && notEquals(in.c,"U") && equalz(in.d,"Y"))
|| (equalz(tmp.b, "HV") && notEquals(in.c,"U") && equalz(in.d,"Y") && varEqualsOneOf(in.e,"FLG","FLR","RRG"));
}
これにより、チェックの再利用性が高まり、コードが読みやすくなります。これは、視覚的な混乱を減らすと同時に、関数にわかりやすい名前を付けて、実際に確認しようとしていることを読者に説明するためです。
次のセグメントも、リファクタリングされると、上記のアプローチの恩恵を受けることができます。
if(varEqualsOneOf(in.e,"RRG","NL")//40
&& lessThan(in.f,in.g)) {
return Action.AC015;
} else {
if(varEqualsOneOf(in.e,"FLG","FLR")) {//50
return Action.AC015;
} else {
return Action.AC000;
}
}
論理的に同等です:
if((varEqualsOneOf(in.e,"RRG","NL") && lessThan(in.f,in.g))
|| varEqualsOneOf(in.e,"FLG","FLR")) {
return Action.AC015;
} else {
return Action.AC000;
}
ステートメント:
return Action.AC000;
いたるところに散らばっており、デフォルトの終点のようです。いくつかのケースでは、それをifステートメントの1つに含めるのが妥当と思われますが、代わりにデフォルトのreturnステートメントとして使用し、以下の多くを削除することを検討してください。
else {
return Action.AC000;
}
これらのAction
アイテムが少数のケースで重要な場合、ここで別のアクションを作成することは不可能です。次のステートメントを検討してください。
if(greaterThan(in.j,in.k)) {//110
return Action.AC000;
}
次のいずれかになります。
if(greaterThan(in.j,in.k)) {//110
return Action.AC123;
}
私の一番下の行は次のとおりです。
Action
要素を一意に返すようにしてください。これにより、条件のすべての追跡を行う必要がなく、単にそれが返された場所を見つけることができます。いつものように、それは当面の問題に依存するので、パスを追跡することは本当にあなたの場合に行く方法であるかもしれませんが、もし私があなただったら私は他の場所に私の努力を最初に集中します。
追伸このセグメントで使用されているさまざまな書式設定スタイルは、実際に私のOCDをトリガーしています。
1つのオプションは、ルールセットの実装を使用することです。基本的に、評価する必要があるすべての値のインターフェースを定義します。何かのようなもの:
interface Rule {
boolean matches(Thing a, Thing b); // etc...
}
次に、シナリオごとにインスタンスを作成します。これは基本的に、提案したマトリックスソリューションと同等です。ネストは、各ルールがそれ自体に依存すると消えます。注文はおそらく重要であり、それはマイナス面になる可能性があります。
これが良い考えであるかどうかは、この混乱を定義するために来た実際のルールが何であるかを知っているか、それらに戻ることができるかによって異なります。大きな利点の1つは、DBにルールを配置したり、DSLを作成したり、実際のルールエンジンを作成したりするなど、よりダイナミックなソリューションに向いていることです。
コードレビューであなたの質問に答えようとしていましたが、トピックから外れているのでそこで閉じられたため、この質問をここで追跡しました。私はRobzorと同じ疑念を抱いています。実際の問題は、if
ツリーが長いことでも、if
ツリーであるということでもありません。むしろ、問題は単にif
ツリーが恐ろしい混乱であることだと思います。「判読不能」であるというRobzorの説明は、私にはまったく誇張ではないようです。他の2つの回答はすでにコードに関する多くの問題を指摘していますが、たとえば、最初と同じように、いくつかの重複チェックがあるなど、まだいくつか追加する必要があります。
_if((
equalz(tmp.b, "BA")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
)||(
equalz(tmp.b, "HV")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
&& varEqualsOneOf(in.e,"FLG","FLR","RRG"))
)
_
ここで、notEquals(in.c,"U") && equalz(in.d,"Y")
のチェックは、OR引数の両方に共通なので、外部に抽出できます。
また、_in.c
_がない場合、notEquals(in.c,"U")
は何を返すのでしょうか。 false
を返す場合、notEquals
は_!equalsOrMissing
_と同等であり、2つのメソッドのいずれかが冗長であることを意味します。そして、それがtrue
を返す場合、notEquals
は_!equalz
_と同等であるため、1つのメソッドはどちらの方法でも冗長です。
これらの非人道的な怪物をもっと扱う必要がある場合、絶望と狂気の段階以外にそれらと一緒に行く唯一の方法は、実際に見ることができるようにif
ツリーをリファクタリングすることだと思います完全な恐怖から泣くことなく彼らを。私はあなたが提供したコードを使用してこれを実行しようとしましたが、実際にはスパゲッティコードのこのほぼ貫通できないファサードの背後にどのロジックが隠されているのかという印象を得るために、以下が得られました。
_@Override
public Action decide() {
if (equalz(in.a, "LOC")) {
if (notEquals(in.c, "U")
&& equalz(in.d, "Y")) {
if (equalz(tmp.b, "BA")
&& (varEqualsOneOf(in.e, "RRG", "NL")
&& lessThan(in.f, in.g)
|| varEqualsOneOf(in.e, "FLG", "FLR"))) {
return Action.AC015;
} else if (equalz(tmp.b, "HV")) {
if (equalz(in.e, "RRG")) {
return Action.AC014;
} else if (varEqualsOneOf(in.e, "FLG", "FLR")) {
return Action.AC010;
}
}
} else if (!varEqualsOneOf(tmp.b, "BA", "HV")
&& equalsOrMissing(in.c, "U")
&& varEqualsOneOf(in.e, "FLG", "FLR")) {
return Action.AC011;
}
} else if (varEqualsOneOf(in.a, "MNC", "BAN", "LCI", "CTV", "LEA", "INS")
&& !equalz(in.h, "A")
&& !equalz(in.i, "Y")
&& (notEquals(in.h, "U")
&& !greaterThan(in.j, in.k)
|| !notEquals(in.h, "U")
&& notEquals(in.l, "U")
&& varEqualsOneOf(in.e, "FLG", "FLR"))) {
if (varEqualsOneOf(in.e, "FLG", "FLR", "RRG")
|| equalz(in.e, "NL")
&& equalz(in.a, "INS")) {
if (!notEquals(in.l, "U")) {
if (equalz(in.n, "Y")
|| varEqualsOneOf(in.e, "FLG", "FLR")
&& equalz(in.m, "Y")) {
return Action.AC012;
} else {
return Action.AC002;
}
} else if (varEqualsOneOf(in.e, "FLG", "FLR")) {
return Action.AC004;
} else if (notEquals(in.o, "U")) {
return Action.AC005;
} else {
return Action.AC018;
}
} else if (equalz(in.p, in.q)) {
if (notEquals(in.o, "U")) {
return Action.AC006;
} else {
return Action.AC008;
}
}
}
return Action.AC000;
}
_
これより簡単になるとは思いません。それはまだ複雑に見えますが、アルゴリズム自体は複雑に見え、それを回避する方法はありません。私も途中で間違いをしたかもしれないので、このコードがあなたのものと同等であると信頼しないでください。しかし、バグがないかどうかに関係なく、元のコードのこの簡略化されたバージョンは、if
ツリーに不満があるか、または単に構造である場合、それが本当の性質であるかどうかを再考するための基礎を与える可能性がありますあなたの幸運な継承者である特定のif
ツリーの。また、コードを再構築することにした場合でも、if
ツリーの多くのブランチのもつれを解くと、桁違いに簡単になります。
データベースを検討します。
{20,30,80,
(equalz(tmp.b, "BA")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
)||(
equalz(tmp.b, "HV")
&& notEquals(in.c,"U")
&& equalz(in.d,"Y")
&& varEqualsOneOf(in.e,"FLG","FLR","RRG"))},
データについてはよくわかりませんが、これらのオブジェクトはほとんど変化しないようです。多分、
Action
action_id (auto-increment)
action
description
1, AC000, something
2, AC015, somethingelse
action_condition
action_condition_id (auto-increment)
condition_id
action_id
condition
1, 40, 2, RRG
2, 40, 2, NL
...
SELECT * FROM action a, action_condition ac WHERE a.action_id = ac.action_id AND ? = ?
//Build your SELECT, used Prepared Statements,
//be careful constructing to disallow SQL Injection.
ただし、試す前にeverythingに名前を変更しました。