最近のバグ修正により、他のチームメンバーによって記述されたコードを調べる必要がありました。これを見つけました(C#です)。
return (decimal)CostIn > 0 && CostOut > 0 ? (((decimal)CostOut - (decimal)CostIn) / (decimal)CostOut) * 100 : 0;
さて、これらすべてのキャストに正当な理由があることを認めると、これはまだ従うのが非常に難しいようです。計算にマイナーなバグがあり、問題を解決するためにそれを解く必要がありました。
私はコードレビューからこの人のコーディングスタイルを知っています。彼のアプローチは、短いほど良いということです。そしてもちろん、そこにも価値があります。条件付きロジックの不必要に複雑なチェーンがいくつかあり、それらはいくつかの適切に配置された演算子で整理することができます。しかし、彼は単一の声明に詰め込まれたオペレーターのチェーンをフォローすることで、私よりも明らかに上手です。
もちろん、これは最終的にはスタイルの問題です。しかし、コードの簡潔さを求める努力が役に立たなくなり、理解の障害になるポイントを認識することについて、何か書かれたり調査されたりしたことがありますか?
キャストの理由はEntity Frameworkです。 dbはこれらをnull許容型として格納する必要があります。 10進数? C#のDecimalと同等ではなく、キャストする必要があります。
しかし、コードの簡潔さを求める努力が役に立たなくなり、理解の障害となる点を認識することについて、何か書かれたり調査されたりしたことがありますか?
はい、この分野で研究が行われています。
このことを理解するには、metricを計算して、量的基準で比較できるようにする方法を見つける必要があります(ウィットに基づいて比較を実行するだけでなく、そして他の答えがするように直感。検討されている潜在的なメトリックの1つは、
循環的複雑度 ÷コードのソース行( [〜#〜] sloc [〜#〜] )
コード例では、すべてが1行に圧縮されているため、この比率は非常に高くなっています。
SATCは、最も効果的な評価は、サイズと[Cyclomatic]の複雑さの組み合わせであることを発見しました。複雑度が高くサイズが大きいモジュールは、信頼性が最も低くなる傾向があります。サイズが小さく、複雑度が高いモジュールも、変更や修正が難しい非常に簡潔なコードになる傾向があるため、信頼性のリスクになります。
興味がある場合は、以下を参考にしてください。
McCabe、T. and A. Watson(1994)、Software Complexity(CrossTalk:The Journal of Defense Software Engineering)。
Watson、A. H.&McCabe、T. J.(1996)。構造化テスト:循環的複雑度メトリックを使用したテスト方法(NIST Special Publication 500-235)。 2011年5月14日、McCabe Software Webサイトから取得: http://www.mccabe.com/pdf/mccabe-nist235r.pdf
ローゼンバーグ、L。、ハンマー、T。、ショー、J。(1998)。ソフトウェアメトリックと信頼性(ソフトウェア信頼性エンジニアリングに関するIEEE国際シンポジウムの議事録)。 2011年5月14日、ペン州立大学のWebサイトから取得: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.104.4041&rep=rep1&type=pdf
個人的に、私は決して値を簡潔にすることはなく、読みやすさだけを重視しています。簡潔さは読みやすさを向上させる場合とそうでない場合があります。さらに重要なことは、書き込み専用コード(WOC)ではなく、 Really Obvious Code(ROC) を記述していることです。
ただ面白くするために、ここにそれを書いて、チームのメンバーに書いてもらうようにしています
if ((costIn <= 0) || (costOut <= 0)) return 0;
decimal changeAmount = costOut - costIn;
decimal changePercent = changeAmount / costOut * 100;
return changePercent;
また、作業変数の導入には、整数演算ではなく固定小数点演算をトリガーするという副作用があるため、decimal
へのキャストをすべて行う必要がなくなります。
簡潔さは、重要な事柄の乱雑さを軽減する場合に優れていますが、簡潔になると、あまりに多くの関連データが密にパックされすぎて簡単に追跡できなくなり、関連データ自体が乱雑になり、問題が発生します。
この特定のケースでは、decimal
へのキャストが繰り返し繰り返されています。おそらく次のように書き直す方が全体的に良いでしょう:
var decIn = (decimal)CostIn;
var decOut = (decimal)CostOut;
return decIn > 0 && CostOut > 0 ? (decOut - decIn ) / decOut * 100 : 0;
// ^ as in the question
突然、ロジックを含む行がはるかに短くなり、1つの水平線に収まるので、スクロールせずにすべてを見ることができ、意味がはるかに容易に明らかになります。
この件について特定の調査を引用することはできませんが、コード内のすべてのキャストは、Do n't Repeat Yourself原則に違反することをお勧めします。あなたのコードがしようとしているように見えるのは、costIn
とcostOut
をDecimal
に変換してから、そのような変換の結果に対して健全性チェックを実行し、チェックに合格した場合、変換された値に対する追加の操作。実際、コードは変換されていない値に対して健全性チェックの1つを実行し、costOutがゼロより大きいが、最小の非ゼロよりもサイズが半分未満の値を保持する可能性が高くなりますDecimal
は表すことができます。変換された値を保持するためにDecimal
型の変数を定義し、それらに作用した場合、コードははるかに明確になります。
Decimal
の実際の値の比率よりも、costIn
とcostOut
のcostIn
表現の比率に関心があることに興味があるようです。およびcostOut
(ただし、コードが他の目的で10進表記を使用する場合を除く)。コードがこれらの表現をさらに使用する場合、それは、コード全体にキャストの連続シーケンスを持たせるのではなく、それらの表現を保持する変数を作成するための追加の引数になります。
そのコードを見て、「コストを0(またはそれ以下)にするにはどうすればよいですか?」と尋ねます。それはどのような特別な場合を示していますか?コードは
bool BothCostsAreValidProducts = (CostIn > 0) && (CostOut > 0);
if (!BothCostsAreValidProducts)
return NO_PROFIT;
else {
// that calculation here...
}
ここでは名前について推測しています:BothCostsAreValidProducts
とNO_PROFIT
を適宜変更してください。
簡潔さは、それ自体が美徳ではなく、(意味するところ)であることを忘れると、美徳ではなくなります。簡潔さと相関関係があるので簡潔さを好み、単純なコードは理解しやすく、修正が容易でバグが少ないため、単純さを好みます。最後に、コードでこれらの目標を達成する必要があります。
最小限の作業でビジネス要件を満たす
バグを避ける
1と2を満たし続ける将来の変更を可能にする
これらが目標です。設計の原則または方法(KISS、YAGNI、TDD、SOLID、証明、型システム、動的メタプログラミングなど)は、これらの目標を達成するのに役立つ範囲でのみ有効です。
問題の行は、最終目標を見落としているようです。短いですが、簡単ではありません。同じキャスト操作を複数回繰り返すことにより、実際には不要な冗長性が含まれています。コードを繰り返すと、複雑さが増し、バグが発生する可能性が高くなります。キャストと実際の計算を混合すると、コードを追跡するのが難しくなります。
この行には3つの懸念があります。ガード(特別なケーシング0)、型キャスト、および計算です。個々の懸念事項を個別に考えるとかなり単純ですが、すべて同じ表現に混在しているため、理解するのが難しくなります。
CostOut
が初めて使用されるときにCostIn
がキャストされない理由は明らかではありません。正当な理由があるかもしれませんが、その意図は明確ではありません(少なくともコンテキストなしではありません)。つまり、いくつかの隠された仮定がある可能性があるため、メンテナはこのコードの変更に慎重になるでしょう。そして、これは保守性に対する嫌悪感です。
CostIn
は0と比較する前にキャストされるため、浮動小数点値であると想定しています。 (intの場合、キャストする理由はありません)。しかし、CostOut
が浮動小数点数の場合、浮動小数点値は小さいがゼロではないが、10進数にキャストするとゼロになる可能性があるため、コードはあいまいなゼロ除算バグを隠す可能性があります(少なくとも私は信じています)これは可能でしょう。)
したがって、問題は簡潔さや欠如ではなく、問題は繰り返されるロジックと、保守が困難なコードにつながる懸念の融合です。
キャストされた値を保持する変数を導入すると、おそらくトークの数で数えられるコードのサイズが大きくなりますが、複雑さが軽減され、懸念が分かれ、明快さが向上します。これにより、理解と保守が容易なコードの目標に近づくことができます。
簡潔さは美徳ではありません。読みやすさは美徳です。
簡潔さは、美徳を達成するためのツールになる場合もあれば、例のように、正反対の何かを達成するためのツールになる場合もあります。この方法または別の方法では、それ自体の価値はほとんどありません。コードを「できるだけ短く」するというルールは、「できるだけ卑猥」に置き換えることもできます。これらはすべて、意味がなく、大きな目的を果たさなければ、潜在的に有害です。
その上、あなたが投稿したコードは簡潔さの法則さえも守っていません。定数がMサフィックスで宣言されていた場合、恐ろしい(decimal)
キャストは、コンパイラが残りのint
をdecimal
に昇格するため、回避できます。あなたが説明している人は単に簡潔さを言い訳に使っているだけだと思います。ほとんどの場合、故意ではありませんが、まだです。
私の長年の経験の中で、究極の簡潔さは時間の簡潔さ-時間は他のすべてを支配すると信じるようになりました。これには、パフォーマンスの時間(プログラムがジョブを実行するのにかかる時間)とメンテナンスの時間(機能の追加またはバグの修正にかかる時間)の両方が含まれます。 (これら2つのバランスを取る方法は、問題のコードが実行されている頻度と改善されている頻度に依存します。時期尚早な最適化が依然としてすべての悪の根源であることに注意してください。)コードの簡潔さは、簡潔さを向上させるためです両方の;通常、コードが短いほど実行速度が速くなり、通常は理解しやすく、保守も容易になります。どちらも行わない場合、それは正味のネガティブです。
ここに示すケースでは、テキストの簡潔さは、読みやすさを犠牲にして、行数の簡潔さとして誤って解釈されていると思います。 (キャストの方法によっては、実行に時間がかかる場合もありますが、上記の行が何百万回も実行されない限り、それはおそらく問題ではありません。)この場合、繰り返し10進数キャストを使用すると、読みにくくなります。最も重要な計算が何であるかを参照してください。私は次のように書いたでしょう:
decimal dIn = (decimal)CostIn;
decimal dOut = (decimal)CostOut;
return dIn > 0 && CostOut > 0 ? ((dOut - dIn) / dOut) * 100 : 0;
(編集:これは他の回答と同じコードなので、そこに行きます。)
私は三項演算子? :
のファンなので、そのままにしておきます。
上記のほとんどすべての回答と同様に、可読性は常にあなたの第一の目標であるべきです。ただし、書式設定は、変数や新しい行を作成するよりも効果的な方法であると考えています。
return ((decimal)CostIn > 0 && CostOut > 0) ?
100 * ( (decimal)CostOut - (decimal)CostIn ) / (decimal)CostOut:
0;
ほとんどの場合、循環的複雑度の引数に強く同意しますが、あなたの関数は小さく、良いテストケースでより適切に対処できるほど単純であるように見えます。好奇心から、なぜ10進数にキャストする必要があるのですか?
私にとって、読みやすさの大きな問題は、書式設定が完全に欠けていることにあるように見えます。
私はそれをこのように書きます:
return (decimal)CostIn > 0 && CostOut > 0
? (((decimal)CostOut - (decimal)CostIn) / (decimal)CostOut) * 100
: 0;
CostIn
およびCostOut
の型が浮動小数点型か整数型かによって、一部のキャストが不要になる場合もあります。 float
およびdouble
とは異なり、整数値は暗黙的にdecimal
に昇格されます。
簡潔さはもはや美徳ではありません
CostIn * CostOutは整数であると想定しています
これは私がそれを書く方法です
M(お金)は小数です
return CostIn > 0 && CostOut > 0 ? 100M * (CostOut - CostIn) / CostOut : 0M;
コードは急いで書くことができますが、上記のコードは私の世界でははるかに優れた変数名で書く必要があります。
そして、コードを正しく読んだ場合、それは粗利益の計算を試みています。
var totalSales = CostOut;
var totalCost = CostIn;
var profit = (decimal)(CostOut - CostIn);
var grossMargin = 0m; //profit expressed as percentage of totalSales
if(profit > 0)
{
grossMargin = profit/totalSales*100
}
コードは、人々が理解できるように書かれています。この場合の簡潔さはあまり購入せず、メンテナの負担を増やします。この簡潔さのために、コードをより自己文書化する(変数名を改善する)か、説明するコメントを追加することで、絶対に拡張する必要がありますwhyこのように機能します。
今日問題を解決するためのコードを書くとき、要件が変更されると、そのコードは明日の問題になる可能性があります。メンテナンスは常に考慮に入れられる必要があり、コードの理解しやすさを改善することが不可欠です。
これが検証ユニットテストに合格した場合は問題ありません。新しい仕様が追加された場合、新しいテストまたは拡張された実装が必要であり、コードの簡潔さを「ほどく」必要がありました。問題が発生します。
明らかに、コードのバグは、Q/Aに別の問題があり、それが見落としであることを示しているため、捕捉されなかったバグがあったことが懸念の原因です。
コードの「可読性」などの機能以外の要件を処理する場合は、開発マネージャーによって定義され、主要開発者によって管理され、適切な実装を保証するために開発者によって尊重される必要があります。
「可読性」と「保守性」の容易さを確実にするために、コードの標準化された実装(標準と規則)を確実にするようにしてください。ただし、これらの品質属性が定義および適用されていない場合は、上記の例のようなコードになります。
この種のコードを見たくない場合は、実装の標準と規則についてチームの合意を得て、それを書き留め、ランダムコードレビューまたはスポットチェックを行って、規則が順守されていることを確認してください。