簡潔さがもはや美徳ではなくなったのはどの時点ですか?


102

最近のバグ修正では、他のチームメンバーによって記述されたコードを調べる必要がありましたが、そこで見つけました(C#です)。

return (decimal)CostIn > 0 && CostOut > 0 ? (((decimal)CostOut - (decimal)CostIn) / (decimal)CostOut) * 100 : 0;

さて、これらすべてのキャストに正当な理由があるので、これを理解するのは非常に難しいようです。計算に小さなバグがあり、問題を解決するためにそれを解く必要がありました。

私はこの人のコーディングスタイルをコードレビューから知っていますが、彼のアプローチは、短い方が常に良いということです。そしてもちろん、そこには価値があります。私たちはすべて、適切に配置されたいくつかの演算子で整理できる条件ロジックの不必要に複雑なチェーンを見てきました。しかし、彼は明らかに、私よりも単一のステートメントに詰め込まれた一連の演算子に精通しています。

もちろん、これは最終的にはスタイルの問題です。しかし、コードを簡潔にするための努力が役に立たなくなり、理解の障壁となる点を認識することに関して何か書いたり、研究したりしましたか?

キャストの理由はEntity Frameworkです。データベースはこれらをnull許容型として保存する必要があります。小数?C#のDecimalとは異なり、キャストする必要があります。


153
簡潔さが読みやすさに勝るとき。
ロバートハーヴェイ

27
あなたの特定の例を見ると:キャストは、(1)開発者がコンパイラー以上のことを知っていて、推測できない事実をコンパイラーに伝える必要がある場所、または(2)「間違った"実行する必要がある操作の種類を入力します。どちらも、何かをリファクタリングできる強力な指標です。ここでの最善の解決策は、キャストなしでコードを記述する方法を見つけることです。
エリックリッパー

29
特に、CostInを10進数にキャストしてゼロと比較する必要がありますが、CostOutは比較する必要がないのは奇妙に思えます。何故ですか?一体何がCostInのタイプで、10進数にキャストすることによってのみゼロと比較できるのですか?そして、なぜCostOutはCostInと同じタイプではないのですか?
エリックリッパー

12
さらに、ここのロジックは実際には間違っているかもしれません。CostOutがに等しくDouble.Epsilon、したがってゼロより大きいと仮定します。しかし(decimal)CostOut、その場合はゼロであり、ゼロ除算エラーがあります。最初のステップは、コードを正しくすることです。これは正しくないと思います。正しく取得し、テストケースを作成してから、エレガントにします。エレガントなコードと短いコードには多くの共通点がありますが、簡潔さは優雅な魂ではない場合があります。
エリックリッパー

5
簡潔さは常に美徳です。しかし、私たちの目的関数は簡潔さと他の美徳を兼ね備えています。他の美徳を損なうことなく簡潔にできるなら、常にそうすべきです。
ソロモノフの秘密

回答:


163

現在の研究に関する質問に答えるために

しかし、コードを簡潔にするための努力が役に立たなくなり、理解の障壁となる点を認識することに関して何か書いたり、研究したりしましたか?

はい、この分野で作業が行われています。

このようなものを理解するには、メトリックを計算する方法を見つけて、定量的に比較できるようにする必要があります(他の回答のように、ウィットと直感に基づいて比較を実行するだけではありません)。検討されている潜在的な指標の1つは

循環的複雑度 ÷コードのソース行(SLOC

コード例では、すべてが1行に圧縮されているため、この比率は非常に高くなっています。

SATCは、最も効果的な評価はサイズと[サイクロマティック]複雑さの組み合わせであると判断しました。複雑度が高く、サイズが大きいモジュールは、信頼性が最も低くなる傾向があります。また、サイズが小さく複雑なモジュールは、非常に簡潔なコードになる傾向があり、変更や修正が難しいため、信頼性のリスクです。

リンク

興味のある方のための参考文献をいくつか紹介します。

McCabe、T. and A. Watson(1994)、Software Complexity(CrossTalk:The Journal of Defense Software Engineering)。

ワトソン、AH、およびマッケイブ、TJ(1996)。構造化テスト:循環的複雑度メトリックを使用したテスト方法(NIST Special Publication 500-235)。2011年5月14日、McCabe Software Webサイトから取得:http : //www.mccabe.com/pdf/mccabe-nist235r.pdf

Rosenberg、L.、Hammer、T.、Shaw、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がなくなります。


4
ゼロ未満の場合のガード句が本当に好きです。コメントに値するかもしれません。どのようにコストをゼロ未満にすることができますか、それはどのような特別な場合ですか?
user949300

22
+1。私はおそらくのようなものを追加するif ((costIn < 0) || (costOut < 0)) throw new Exception("costs must not be negative");
ドク・ブラウン

13
@DocBrown:それは良い提案ですが、例外的なコードパスをテストで実行できるかどうかを検討します。はいの場合、そのコードパスを実行するテストケースを作成します。いいえの場合は、すべてをアサートに変更します。
エリックリッパー

12
これらはすべて良い点ですが、この質問の範囲はロジックではなくコードスタイルです。私のコードスニペットは、ブラックボックスの観点からの機能的同等性の取り組みです。例外をスローすることは0を返すことと同じではないため、ソリューションは機能的に同等ではありません。
ジョン・ウー

30
「個人的には、簡潔さを評価した ことはなく、読みやすさだけを評価しました。簡潔さは読みやすさを助けることもあれば、しないこともあります。」素晴らしい点。そのために+1。私もあなたのコードソリューションが好きです。
ワイルドカード

49

簡潔さは重要なことの周りの乱雑さを軽減するのに適していますが、簡潔になり、関連するデータの密度が高すぎて簡単に追跡できない場合、関連するデータ自体が乱雑になり、問題が発生します。

この特定のケースでは、キャストdecimalが何度も繰り返されています。おそらく全体的に次のように書き直す方が良いでしょう:

var decIn = (decimal)CostIn;
var decOut = (decimal)CostOut;
return decIn > 0 && CostOut > 0 ? (decOut - decIn ) / decOut * 100 : 0;
//                  ^ as in the question

突然、ロジックを含む行がはるかに短くなり、1本の水平線に収まるので、スクロールせずにすべてを見ることができ、その意味がはるかにわかりやすくなります。


1
私はおそらくさらに進んで((decOut - decIn ) / decOut) * 100別の変数にリファクタリングしたでしょう。
FrustratedWithFormsDesigner

9
アセンブラーははるかに明確でした:1行につき1つの操作のみ。ど!

2
@FrustratedWithFormsDesignerさらに一歩進んで、条件付きチェックを括弧で囲みます。
クリスクレフィス

1
@FrustratedWithFormsDesigner:条件の前にこれを抽出すると、ゼロによる除算チェック(CostOut > 0)がバイパスされるため、条件をif-statement に展開する必要があります。これに何か問題があるわけではありませんが、ローカルの紹介よりも詳細な情報を追加します。
-wchargin

1
正直に言うと、キャストを取り除くだけで、単純な基本的なレート計算を認識できないために読みにくいと感じた場合、IMOを十分に放映したようです。
ウォルフラット

7

私はこのテーマに関する特定の研究を引用することはできませんが、あなたのコード内のすべてのキャストが自分自身を繰り返さないという原則に違反していることをお勧めします。あなたのコードがやろうとしているのは、costInand costOutをtype Decimalに変換し、そのような変換の結果に対していくつかの健全性チェックを実行し、チェックに合格した場合、それらの変換された値に対して追加の操作を実行することです。実際、コードは変換されていない値に対して健全性チェックの1つを実行し、costOutがゼロより大きく、Decimal表現できる最小のゼロ以外のサイズの半分未満の値を保持する可能性を高めます。コードはDecimal、変換された値を保持する型の変数を定義し、それらに基づいて動作する場合、はるかに明確になります。

それはあなたがの割合で、より興味があるという好奇心に見えるんDecimalの表現costIncostOutの実際の値の比率よりcostIncostOut、コードはまた、いくつかの他の目的のために、小数点の表現を使用する予定されていない限り。コードがこれらの表現をさらに使用する場合、それは、コード全体で連続したキャストのシーケンスを持つのではなく、それらの表現を保持する変数を作成するためのさらなる引数になります。


(10進数)キャストは、おそらくビジネスルールの要件を満たすためのものです。会計士を扱うとき、あなたは時々愚かなフープを飛び越えなければなりません。要求された機能を考えれば避けられない「Use tax roundoff correction-$ 0.01」の行を見つけたとき、CFOが心臓発作を起こすと思った。(提供:税引後価格。税引前価格を
計算

@LorenPechtel:Decimalキャストが丸める精度は問題の値の大きさに依存することを考えると、キャストの実際の動作方法を要求するビジネスルールを想像するのは難しいと思います。
-supercat

1
良い点-私は、10進型の詳細を忘れていました。そのようなものが欲しい機会がなかったからです。私は今、これはビジネスルールに対するカーゴカルトの従順であると考えています。具体的には、お金は浮動小数点であってはなりません。
ローレンペクテル

1
@LorenPechtel:最後のポイントを明確にするために、固定小数点型はx + yyがオーバーフローするかyを生成することを保証できます。Decimalタイプにはありません。値1.0d / 3.0には、大きい数値を使用するときに維持できる桁数よりも小数点以下の桁数が多くなります。したがって、同じ大きい数を加算してから減算すると、精度が失われます。固定小数点型は、分数の乗算または除算で精度を失う可能性がありますが、加算、減算、乗算、または剰余除算ではそうではありません(たとえば、1.00 / 7は0.14剰余0.2、1.00 div 0.15は6剰余0.10)。
-supercat

1
@ハルク:はい、もちろん。私はx + yyを使用してxを生成するか、x + yxを使用してyを生成するか、x + yxyを使用するかを議論していましたが、最初の2つはミッシュモッシングになりました。重要なのは、固定小数点型は、操作の多くのシーケンスが検出されないの丸め誤差が発生しないことを保証できるということである任意の大きさを。合計が一致することを検証するためにコードがさまざまな方法で物事を追加する場合(たとえば、行の小計の合計を列の小計の合計と比較する)、結果が正確に等しくなることは、それらを「閉じる」よりもはるかに優れています。
-supercat

5

そのコードを見て、「コストを0(またはそれ以下)にするにはどうすればよいですか?」それはどのような特別なケースを示していますか?コードは

bool BothCostsAreValidProducts = (CostIn > 0) && (CostOut > 0);
if (!BothCostsAreValidProducts)
  return NO_PROFIT;
else {
   // that calculation here...
}

私はここで名前について推測しています:変更BothCostsAreValidProductsNO_PROFIT、必要に応じて。


もちろん、コストはゼロにすることができます(Xmasのプレゼントを考えてください)。そして、負の金利の場合、負のコストも驚くべきことではなく、少なくともそれを処理するためのコードを準備する必要があります(そして、エラーをスローすることによって)
ハーゲンフォンエイ

正しい軌道に乗っています。
danny117

それはばかげている。if (CostIn <= 0 || CostOut <= 0)完全に大丈夫です。
マイル

はるかに読みにくい。変数名は恐ろしいです(BothCostsAreValidの方が良いでしょう。製品については何もありません)。ただし、CostIn、CostOutを確認するだけで問題ないため、それでも読みやすさは向上しません。テストされている式の意味が明らかでない場合は、意味のある名前を持つ追加の変数を導入します。
gnasher729

5

簡潔さは、それ自体が美徳ではなく目的を意味することを忘れると、美徳になるのをやめます。簡潔さと相関しているため、簡潔さが好きです。また、単純なコードは理解しやすく、修正しやすく、バグが少ないため、単純さが好きです。最後に、これらの目標を達成するためのコードが必要です。

  1. 最小限の作業でビジネス要件を満たします

  2. バグを避ける

  3. 将来的に変更を加えて、1および2を満たすようにします。

これらが目標です。設計の原則または方法(KISS、YAGNI、TDD、SOLID、証明、型システム、動的メタプログラミングなど)は、これらの目標を達成するのに役立つ範囲でのみ有益です。

問題の行は、最終目標を見失っているようです。短いですが、単純ではありません。実際には、同じキャスト操作を複数回繰り返すことにより、不必要な冗長性が含まれています。コードを繰り返すと、複雑さが増し、バグが発生する可能性が高くなります。キャストと実際の計算を混合すると、コードを追跡しにくくなります。

ラインには3つの懸念事項があります:ガード(特別なケーシング0)、型キャスト、計算 それぞれの懸念は単独で取られた場合は非常に単純ですが、すべてが同じ表現に混在しているため、追跡するのが難しくなります。

なぜCostOut初めて使用されるときにキャストされないのかは明らかではありませんCostIn。正当な理由があるかもしれませんが、意図は明確ではありません(少なくともコンテキストがないわけではありません)。そして、これは保守性への嫌悪感です。

CostIn0と比較する前にキャストされるため、浮動小数点値であると想定します。(intの場合、キャストする理由はありません)。しかしCostOut、floatの場合、浮動小数点値は小さいがゼロではないかもしれませんが、10進数にキャストするとゼロになるため、コードは不明瞭なゼロ除算バグを隠す可能性があります(少なくともこれは可能だと思います)。

そのため、問題は簡潔さや不足ではなく、保守が困難なコードにつながる懸念事項の論理と混同が繰り返されることです。

キャストされた値を保持する変数を導入すると、おそらく、トーク数でカウントされるコードのサイズが大きくなりますが、複雑さを減らし、懸念を分離し、明快さを改善します。これにより、理解しやすく保守しやすいコードの目標に近づくことができます。


1
重要な点:CostInを2回ではなく1回キャストすると、読みにくくなります。これは、明らかな修正が加えられた微妙なバグなのか、意図的に行われたのかが読者にわからないためです。筆者が文によって何を意味したのか確実に言えない場合、それは判読できません。2つのキャスト、またはCostInの最初の使用がキャストを必要としない、またはすべきではない理由を説明するコメントが必要です。
gnasher729

3

簡潔さは美徳ではありません。読みやすさが美徳です。

簡潔さは、美徳を達成するためのツールになる可能性があります。また、あなたの例のように、正反対の何かを達成するためのツールになる可能性もあります。この方法でも、別の方法でも、独自の価値はほとんどありません。コードを「できるだけ短く」するというルールは、「できるだけわいせつなもの」に置き換えることもできます。これらはすべて意味がなく、より大きな目的に役立たない場合は損害を与える可能性があります。

その上、あなたが投稿したコードは簡潔さの規則さえも守っていません。定数がMサフィックスで宣言されていた場合(decimal)、コンパイラが残りintをにプロモートするため、恐ろしいキャストのほとんどを回避できましたdecimal。あなたが説明している人は、簡潔さを言い訳に使っているだけだと思います。ほとんどの場合、故意ではありませんが、それでもです。


2

私の長年の経験では、究極の簡潔さは時間のそれであると信じるようになりまし -時間は他のすべてを支配します。これには、パフォーマンスの時間(プログラムがジョブを実行するのにかかる時間)とメンテナンスの時間(機能を追加したりバグを修正するのにかかった時間)の両方が含まれます。(これら2つのバランスをどのように取るかは、問題のコードが実行される頻度と改善の度合いに依存します- 早すぎる最適化は依然としてすべての悪の根源であることを忘れないでください。)短いコードは通常より高速に実行され、通常は理解しやすく、したがって保守が簡単です。どちらも実行しない場合、それは正味のマイナスです。

ここに示されているケースでは、読みやすさを犠牲にして、テキストの簡潔さが行数の簡潔と誤解されていると思います。(キャストの実行方法にもよりますが、実行に時間がかかる場合がありますが、上記の行が数百万回実行されない限り、おそらく問題ではありません。)最も重要な計算が何であるかを参照してください。私は次のように書いたでしょう:

decimal dIn = (decimal)CostIn;
decimal dOut = (decimal)CostOut;
return dIn > 0 && CostOut > 0 ? ((dOut - dIn) / dOut) * 100 : 0;

(編集:これは他の回答と同じコードなので、そこに行きます。)

私は三項演算子のファンな? :ので、そのままにしておきます。


5
特に戻り値に単一の値または変数を超える式がある場合、3進数は読みにくいです。
アルモ

それが否定的な票を駆り立てているのだろうか。私が書いたものを除いては、現在10票のMason Wheelerと大いに一致している。彼も三人組を残しました。なぜこんなに多くの人が問題を抱えているのか分かりません? :-上記の例は十分にコンパクトだと思います。if-then-elseと比較して。
ポールブリンクリー

1
本当にわからない。私はあなたに投票しませんでした。三位一体が好きではありません。なぜなら、どちらの側にあるのかが明確ではないから:です。if-else英語のように読む:それが何を意味するのかを見逃すことはできません。
アルモ

FWIWこれはメイソン・ウィーラーの答えと非常によく似ていますが、彼が最初に得たので、あなたがダウン票を得ていると思います。
ボブツウェイ

1
三項演算子の死!! (また、タブの死、スペース、およびAllmanを除くすべてのブラケットおよびインデントモデル(実際、ドナルド(tm)は、これらが彼が20日に制定する最初の3つの法律になるとツイートしています)
-Mawg

2

読みやすさの上のほぼすべての答えと同様に、常にあなたの主な目標でなければなりません。ただし、変数や新しい行を作成するよりも、書式設定の方がより効果的な方法になると考えています。

return ((decimal)CostIn > 0 && CostOut > 0) ?
       100 * ( (decimal)CostOut - (decimal)CostIn ) / (decimal)CostOut:
       0;

ほとんどの場合、循環的複雑性の議論に強く同意しますが、あなたの関数は小さくてシンプルで、良いテストケースでよりよく対処できるように見えます。好奇心から、なぜ小数にキャストする必要があるのですか?


4
キャストの理由はEntity Frameworkです。データベースはこれらをnull許容型として保存する必要があります。ダブル?C#のDoubleと同等ではないため、キャストする必要があります。
ボブ・トウェイ

2
@MattThrowerつまりdecimal、そうですか?double!= decimal、大きな違いがあります。
pinkfloydx33

1
@ pinkfloydx33はい!脳の半分しか関与していない状態で電話で入力する:)
ボブ・トウェイ

SQLデータ型は、プログラミング言語で使用される型とは奇妙に異なることを生徒に説明する必要があります。私は彼らに理由を説明することができませんでした。"知りません!" 「リトルエンディアン!」

3
これはまったく読めません。
アルモ

1

私には、ここでの読みやすさの大きな問題は、フォーマットの完全な欠如にあるように見えます。

次のように書きます。

return (decimal)CostIn > 0 && CostOut > 0 
            ? (((decimal)CostOut - (decimal)CostIn) / (decimal)CostOut) * 100 
            : 0;

CostInand の型がCostOut浮動小数点型か整数型かによって、キャストの一部も不要になる場合があります。floatおよびとは異なりdouble、整数値は暗黙的にに昇格されdecimalます。


これが説明なしでダウン票されたことは残念ですが、バックパックコードの答えから彼の発言のいくつかを差し引いたものと同一であるように思われるので、正当化されたと思います。
PJTraill

@PJTraill私はそれを見逃したに違いありません、それは確かにほとんど同一です。ただし、新しい行に演算子を配置することを強くお勧めします。そのため、バージョンをそのままにします。
フェリックスドンベック

他の答えについてのコメントで述べたように、私は演算子について同意します—私があなたが好きなようにそれをやったとは見ていませんでした。
PJTraill

0

コードは急いで書くことができますが、私の世界では上記のコードはもっと良い変数名で書かれるべきです。

そして、コードを正しく読んだ場合、粗利益の計算を試みています。

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
}

3
ゼロによる除算が失われ、ゼロが返されます。
danny117

1
そして、それが簡潔さのために最大化された他の誰かのコードをリファクタリングするのが難しい理由であり、なぜ/どのように物事が機能するかを説明する追加のコメントがあるのが良い理由です
ルドルフ・オラ

0

私は整数でコスティン* CostOutを想定しています
。これは、私はそれを書くだろうかである
10進数ですM(マネー)

return CostIn > 0 && CostOut > 0 ? 100M * (CostOut - CostIn) / CostOut : 0M;

1
彼らは両方の否定的な考えではないことを期待:p-
ウォルフラット

2
ゼロ除算はまだそこにありますか?
-danny117

@ danny117簡潔さから間違った答えが出た場合、それははるかに進んでいます。
パパラッチ

質問を更新してアクティブにしたくない。100Mおよび0Mは小数点を強制します。(CostOut-CostIn)は整数演算として実行され、その差は10進数にキャストされると思います。
パパラッチ

0

コードは、人々が理解できるように書かれています。この場合の簡潔さはあまり買わず、メンテナーの負担を増やします。この簡潔さのために、コードをより自己文書化する(変数名を改善する)か、このように機能する理由を説明するコメントを追加することにより、絶対に拡張する必要があります。

今日問題を解決するコードを書くとき、そのコードは明日、要件が変わるときに問題になる可能性があります。メンテナンスを常に考慮する必要があり、コードの理解度を向上させることが不可欠です。


1
これは、ソフトウェアエンジニアリングのプラクティスと原則が作用する場所です。非機能要件
hanzolo

0

簡潔さはもはや美徳ではありません

  • ゼロの事前チェックなしの部門があります。
  • nullのチェックはありません。
  • クリーンアップはありません。
  • TRY CATCH vsエラーを処理できるフードチェーンをスローします。
  • 非同期タスクの完了順序について想定されています
  • 将来のスケジュール変更の代わりに遅延を使用するタスク
  • 不要なIOが使用されています
  • 楽観的更新を使用しない

十分な長さの回答がない場合。

1
わかりました、これはコメントであり、答えではないはずです。しかし、彼は新しい男なので、少なくとも説明してください。ただ投票して逃げないでください!ようこそ、ダニー。私は下票を取り消しましたが、次回はそれをコメントにします:
Mawg

2
わかりました。答えを拡張して、簡単なコードを書くのが難しい方法と簡単な方法を学んだより複雑なことをいくつか含めました。
danny117

ようこそ@Mawgに感謝します。nullのチェックは、簡単なコードで最も問題を引き起こす問題に遭遇することです。
danny117

Androidで編集したところ、編集の説明を求められませんでした。楽観的な更新を追加しました(変更を検出して警告)
danny117

0

これが検証ユニットテストに合格している場合、新しい仕様が追加され、新しいテストまたは拡張された実装が必要であり、コードの簡潔さを「解く」必要がある場合は問題ありません。問題が発生します。

明らかにコードのバグは、Q / Aに別の問題があることを示していますが、これは見落としでした。したがって、キャッチされなかったバグがあったという事実は懸念の原因です。

コードの「読みやすさ」などの非機能要件を処理する場合、開発マネージャーが定義し、リード開発者が管理し、開発者が適切な実装を保証するように尊重する必要があります。

「読みやすさ」と「保守性」の容易さを確保するために、コードの標準化された実装(標準と規則)を確保するようにしてください。ただし、これらの品質属性が定義および適用されていない場合、上記の例のようなコードになります。

この種のコードを見たくない場合は、実装の標準と規則についてチームに同意してもらい、書き留めて、規則が尊重されていることを検証するためにランダムなコードレビューまたはスポットチェックを行ってください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.