どのような場合に、コードが少ないほど良くないのですか?[閉まっている]


55

私は最近仕事でいくつかのコードをリファクタリングしました、そして私は良い仕事をしたと思いました。980行のコードを450行に落とし、クラスの数を半分にしました。

これを同僚に見せたとき、これが改善であることに同意しなかった人もいました。

彼らは言った-「より少ないコード行が必ずしも良いとは限らない」

人々が本当に長い行を書いたり、いくつかの行を保存するためにすべてを単一のメソッドに入れたりする極端なケースがあるかもしれませんが、それは私がやったことではありません。私の意見では、コードは適切に構造化されており、サイズが半分であるため、理解/保守が簡単です。

仕事を遂行するために必要なコードを2倍にしたい人がいる理由を理解するのに苦労しています。 ?


145
コードサイズは、行や文字数ではなく、読み取りおよび理解に必要な時間で測定されます。
ベルギ

13
書かれているあなたの質問は、カテゴリー的に広すぎます。代わりに、行った特定の変更について新しいものを書くことをお勧めします。
jpmc26

8
高速逆平方根アルゴリズムを検討してください。適切な変数名を付けて完全なニュートンメソッドを実装すると、コードの行数が増える可能性が高い場合でも、より明確で読みやすくなります。(この特定のケースでは、スマートコードを使用することはパフォーマンス上の懸念から正当化できることに注意してください)。
マチェイピエチョトカ

65
あなたの質問に答える専用のスタック交換サイトがあります:codegolf.stackexchange.com。:)
フェデリコポロニ

回答:


123

thinせた人は、太りすぎの人よりも必ずしも健康的ではありません。

980行の子供向けのストーリーは、450行の物理学の論文よりも読みやすくなっています。

コードの品質を決定する多くの属性があります。Cyclomatic ComplexityHalstead Complexityのように、単純に計算されるものもあります。凝集度、可読性、理解可能性、拡張性、堅牢性、正確性、自己文書化、清潔さ、テスト容易性など、その他の定義はより大まかに定義されています。

たとえば、コードの全体の長さを短縮する一方で、追加の不当な複雑さを導入し、コードをより暗号化した可能性があります。

長いコードを小さなメソッドに分割することは、有益であるのと同じくらい有害です。

リファクタリングの努力が望ましくない結果を生み出したと考える理由について、同僚に具体的なフィードバックを提供するよう依頼してください。


1
@PiersyPは単なるFYIであり、優れたリファクタリングについて教えられたガイドラインの1つは、循環的複雑度が元の平方根に減少することを確認することです。
MAハニン

4
@PiersyPも、あなたのコードがそれよりも悪いか良いと言っているわけではありません。部外者として私は本当に言うことができません。また、同僚が過度に保守的であり、変更をレビューして検証するために必要な努力をしなかったという理由だけで、あなたの変更を恐れている可能性もあります。そのため、追加のフィードバックを求めることを提案しました。
MAアナン

6
良い仕事です、皆さん-どこかで「正しい」重みがあることを確認しました(正確な数は異なる場合があります)。@Neilの元の投稿でさえ、「重い人」ではなく「太りすぎ」と言っていますが、それはプログラミングと同じようにスイートスポットがあるからです。「適切なサイズ」を超えてコードを追加するのは煩雑であり、そのポイントより下の行を削除すると、簡潔さのために理解が犠牲になります。正確にそのポイントがどこにあるかを知ること...それは難しいビットです。
AC

1
必要ではないからといって、価値がないわけではありません。
クリスウォーラート

1
@Neilあなたは一般的に正しいですが、あなたが暗示する絶え間ない「バランス」は、客観的に言えば神話のようなものです。誰もが「良いバランス」とは何かについて異なる考えを持っています。明らかに、OPは彼が何か良いことをしたと思っていて、同僚はそうしなかったと思いますが、コードを書いたときに「正しいバランス」を持っていると考えていたのは間違いないでしょう。
code_dredd

35

興味深いことに、同僚と私は現在、クラスと関数の数を2倍弱増やすリファクタリングを行っています、コード行はほぼ同じままです。ですから、良い例があります。

私たちのケースでは、抽象化のレイヤーが1つあり、実際には2つである必要がありました。すべてがUIレイヤーに詰め込まれました。それを2つのレイヤーに分割することにより、すべてがまとまりやすくなり、個々のピースのテストと保守がはるかに簡単になります。

同僚を悩ませているのはコードのサイズではなく、他の何かです。彼らがそれを明確にできない場合は、古い実装を見たことがないかのように自分でコードを見てみて、単に比較するのではなく、独自のメリットで評価してください。長いリファクタリングを行うと、元の目標を見失い、行き過ぎてしまうことがあります。重要な「全体像」を見て、おそらくアドバイスを大切にしているペアプログラマーの助けを借りて、軌道に乗せてください。


1
はい、間違いなくUIを他のものから分離します。これは常に価値があります。元の目標を見失うことについてのあなたの論点では、私はいくらか同意しますが、あなたはより良い何かに、またはより良い方法に再設計するかもしれません。進化についての古い議論のように(「翼の一部は何が良いのか?」)、時間をかけて改善しなければ物事は改善しません。あなたは、あなたがどこに向かっているのかを、途中までわからないことがあります。私は同僚がなぜ不安なのかを探ろうとすることに同意しますが、それは本当にあなたの問題ではなく「彼らの問題」なのかもしれません。

17

しばしばアルバート・アインシュタインに起因する引用が思い浮かびます:

すべてをできるだけシンプルにしますが、シンプルではありません。

物事を削減するために船外に出ると、コードを読みにくくすることができます。「読みやすい/読みにくい」は非常に主観的な用語になる可能性があるため、これが何を意味するのかを正確に説明します。特殊なツールの支援なしで、ソースを見るだけで。

JavaやPascalなどの言語は、その冗長性で有名です。人々はしばしば特定の構文要素を指摘し、「コンパイラーの仕事を楽にするためだけにいる」とばかに言っています。「ちょうど」の部分を除いて、これは多かれ少なかれ真実です。明示的な情報が多いほど、コンパイラだけでなく人間もコードを読みやすく、理解しやすくなります。

私が言うならvar x = 2 + 2;、それxが整数であるはずであることがすぐに明らかです。しかし、私が言うならvar foo = value.Response;、それが何をfoo表すか、そのプロパティと機能が何であるかはまったく明確ではありません。コンパイラーがそれを簡単に推測できたとしても、人により多くの認知的努力を注ぎます。

プログラムは、人々が読むために書かれなければならず、偶然にマシンが実行されるためだけに書かれなければならないことを忘れないでください。(皮肉なことに、この引用は非常に読みにくいことで有名な言語に捧げられた教科書から来ています!)冗長なものを削除することは良い考えですが、あなたの仲間の人間がそれを容易にするコードを取り去らないでください書かれているプログラムにとって厳密に必要ではない場合でも、何が起こっているかを把握してください。


7
varほとんどの場合、コードの読み取りと理解には特定の抽象化レベルでの動作の把握が含まれるため、特定の変数の実際のタイプを知っても通常は何も変更されないため、この例は単純化の特に良いものではありませんより低い抽象度を理解するのに役立ちます)。より良い例は、単一行の複雑なステートメントに押しつぶされた単純なコードの複数行です。例えば、理解する if ((x = Foo()) != (y = Bar()) && CheckResult(x, y)) のに時間がかかり、タイプを知っているxy、少しでも助けになりません。
ベンコットレル

15

コードが長いほど読みやすくなります。通常は逆ですが、多くの例外があります-それらのいくつかは他の回答で概説されています。

しかし、別の角度から見てみましょう。新しいコードは、会社の文化、コードベース、またはロードマップについての追加知識がなくても2つのコードを見るほとんどの熟練したプログラマーによって優れていると見なされると思います。それでも、新しいコードに反対する理由はたくさんあります。簡潔にするために、「新しいコードを批判する人々」Pecritencを呼び出します。

  • 安定。古いコードが安定していることがわかっている場合、新しいコードの安定性は不明です。新しいコードを使用する前に、テストする必要があります。何らかの理由で適切なテストが利用できない場合、変更はかなり大きな問題です。テストが利用可能であっても、ペクリテンクは、その努力はコードの(わずかな)改善に値しないと考えるかもしれません。
  • パフォーマンス/スケーリング。古いコードはより適切に拡張されている可能性があり、Pecrittencは、クライアントと機能がすぐに積み重なるにつれて、今後パフォーマンスが問題になると考えています。
  • 拡張性。古いコードにより、Pecrittencがすぐに追加されると想定しているいくつかの機能を簡単に導入できた可能性があります*。
  • 親しみやすさ。古いコードは、会社のコードベースの他の5つの場所で使用されているパターンを再利用した可能性があります。同時に、新しいコードでは、この時点で会社の半分しか聞いたことのない派手なパターンを使用しています。
  • 豚の口紅。Pecritencは、古いコードと新しいコードの両方がゴミである、または無関係であると考えている可能性があるため、それらの間の比較は無意味です。
  • 誇り。Pecritencはコードの元の作成者である可能性があり、彼のコードに大幅な変更を加える人々を嫌います。彼は改善が軽いin辱だとさえ思うかもしれません。

4
「Pecritenc」の+1、およびプリファクタリングの前に事前に考慮されるべき合理的な異議の非常に素晴らしい要約。

1
そして、「拡張性」のために+1-オリジナルのコードには将来のプロジェクトで使用するための関数またはクラスがあるかもしれないと考えていたので、抽象化は冗長または不要に見えたかもしれませんが、単一のプログラムのコンテキストでのみでした。
ダレンリンガー

また、問題のコードは重要なコードではない可能性があるため、クリーンアップするためのエンジニアリングリソースの浪費と見なされます。
エリックエイド

@nocomprende合理的で、事前に考慮された、プリファクタリングを使用した理由は何ですか?多分ペクリテンクに似た方法?
本部Milind R

@MilindRおそらく先入観、偏見、あるいは個人的な好みですか?または、まったく理由がないかもしれません。コファクターの宇宙的合流であり、陰謀の条件を混乱させています。わからない、本当に。あなたはどう?

1

どのようなコードが優れているかは、プログラマーの専門知識と、使用するツールによって異なります。たとえば、継承を最大限に活用した適切に記述されたオブジェクト指向のコードよりも、通常は不十分なコードと見なされるものが、状況によってはより効果的である理由を次に示します。

(1)一部のプログラマーは、オブジェクト指向プログラミングを直感的に把握していないだけです。ソフトウェアプロジェクトの比phorが電気回路である場合、多くのコードの重複が予想されます。多くのクラスで多かれ少なかれ同じメソッドを見たいと思うでしょう。彼らはあなたが自宅で感じることができます。そして、何が起こっているのかを見るために親クラスまたは祖父母クラスでメソッドを調べなければならないプロジェクトは敵意を感じるかもしれません。親クラスがどのように機能するかを理解してから、現在のクラスがどのように異なるかを理解する必要はありません。現在のクラスがどのように機能するかを直接理解したいのですが、情報がいくつかのファイルに分散しているという事実がわかりにくいです。

また、特定のクラスの特定の問題を修正するだけの場合、基本クラスで直接問題を修正するか、現在の対象クラスのメソッドを上書きするかを考える必要はありません。(継承がなければ、意識的な決定をする必要はありません。デフォルトは、バグとして報告されるまで、同様のクラスの同様の問題を無視することです。)反対。

(2)一部のプログラマーはデバッガーを頻繁に使用します。一般的に私はコードの継承と重複防止の面でしっかりしていますが、オブジェクト指向コードをデバッグするときに(1)で説明したフラストレーションの一部を共有しています。コードの実行に従うと、同じオブジェクトにとどまっている場合でも、(先祖)クラス間を飛び回っていることがあります。また、適切に記述されたコードにブレークポイントを設定すると、役に立たないときにトリガーされる可能性が高くなるため、条件付きにする(実用的な場合)か、関連するトリガーの前に何度も手動で継続することに労力を費やす必要があります。


3
「祖父母クラス」!サンザシ!アダムとイブのクラスに注意してください。(そしてもちろん、神のクラス)それ以前は、それは形がなく、空でした。

1

それは完全に依存します。私は、ブール変数を関数パラメーターとして許可しないプロジェクトに取り組んでいますが、代わりenumに各オプションに専用のものが必要です。

そう、

enum OPTION1 { OPTION1_OFF, OPTION1_ON };
enum OPTION2 { OPTION2_OFF, OPTION2_ON };

void doSomething(OPTION1, OPTION2);

よりもはるかに冗長です

void doSomething(bool, bool);

しかしながら、

doSomething(OPTION1_ON, OPTION2_OFF);

よりずっと読みやすい

doSomething(true, false);

コンパイラは両方に対して同じコードを生成する必要があるため、短い形式を使用しても何も得られません。


0

結束が問題になる可能性があると思います。

たとえば、Webアプリケーションでは、ホームページの状況で使用するのと本質的に同じコード(インデックス)であるすべての製品のインデックスを作成する管理ページがあるとします。

DRYで洗練された状態を保つためにすべてを部分化することにした場合、ユーザーのブラウジングが管理者であるかどうかに関する多くの条件を追加し、不必要なものでコードを乱雑にしなければなりません。デザイナー!

したがって、このような状況では、コードがほぼ同じであっても、他の何かにスケーリングでき、ユースケースがわずかに変更される可能性があるため、条件とifを追加してそれぞれを追跡するのは悪いでしょう。したがって、良い戦略は、DRYコンセプトを捨て、コードを保守可能な部分に分割することです。


0
  • より少ないコードでは、より多くのコードと同じ仕事をしません。単純化のためのリファクタリングは良いですが、このソリューションが満たす問題空間を単純化しすぎないように注意する必要があります。980行のコードでは、450よりも多くのコーナーケースを処理できます。
  • より少ないコードが、より多くのコードのように優雅に失敗しないとき。「不要な」try-catchやその他のエラーケース処理を削除するために、コードで実行される「ref *** toring」ジョブがいくつか見られました。避けられない結果は、エラーと、ユーザーができること、アプリがクラッシュまたはYSODしたことに関する素晴らしいメッセージを含むダイアログボックスを表示することではありませんでした。
  • より少ないコードは、より多くのコードよりも保守性/拡張性が低い場合。コードの簡潔さのためのリファクタリングは、LoCの利益のために「不要な」コード構成を削除することがよくあります。問題は、パラレルインターフェイス宣言、抽出されたメソッド/サブクラスなどのコード構成が、このコードが現在以上に行う必要がある場合、または異なる方法で行う必要がある場合です。極端な場合、問題の定義が少し変更されると、特定の問題に合わせてカスタマイズされた特定のソリューションがまったく機能しない場合があります。

    一例; 整数のリストがあります。これらの整数のそれぞれは、1つを除いて、リスト内で重複した値を持っています。アルゴリズムはそのペアになっていない値を見つける必要があります。一般的な解決策は、リストに重複のない番号が見つかるまで、すべての番号を他のすべての番号と比較することです。これはN ^ 2回の操作です。ハッシュテーブルを使用してヒストグラムを作成することもできますが、それはスペース効率が非常に悪いです。ただし、ビット単位のXOR演算を使用して、線形時間および定数空間にすることができます。すべての整数を実行中の「合計」(ゼロから始まる)とXORし、最後に実行中の合計がペアになっていない整数の値になります。とてもエレガント。要件が変更され、リスト内の複数の番号のペアが解除されるか、整数にゼロが含まれるまで。これで、プログラムはガベージまたはあいまいな結果を返します(ゼロを返す場合、それはすべての要素がペアになっていることを意味しますか、それともペアになっていない要素はゼロですか?)。これは、実際のプログラミングにおける「賢い」実装の問題です。

  • より少ないコードは、より多くのコードよりも自己文書化が少ない場合。コード自体を読み取って、コードの実行内容を判別できることが、チーム開発に不可欠です。あなたが書いたbrain-f ***アルゴリズムを若い開発者に美しく提供し、彼にそれを微調整して出力をわずかに変更するように頼むことは、あなたをあまり遠くに行かせません。多くの上級開発者もその状況に苦労するでしょう。いつでもコードが何をしていて何がうまくいかないかを理解できることは、チーム開発環境の作業の鍵となります(そして、ソロでさえ、5を書いたときの天才のフラッシュがんを治すためのオンライン法は、パーキンソン病も治すためにその機能に戻ってきたとき、長い間なくなるでしょう)

0

コンピュータコードは多くのことをする必要があります。これらのことを行わない「ミニマリスト」コードは、良いコードではありません。

たとえば、コンピュータープログラムはすべての可能な(または最低限、すべての可能性のあるケース)をカバーする必要があります。1つのコードが1つの「ベースケース」のみをカバーし、他のコードを無視する場合、短いコードであっても適切なコードではありません。

コンピューターコードは「スケーラブル」でなければなりません。暗号化されたコードは、1つの専用アプリケーションでのみ機能する場合がありますが、より長いが、よりオープンなプログラムであれば、新しいアプリケーションを簡単に追加できます。

コンピュータコードは明確でなければなりません。別の回答者が示したように、ハードコアコーダーは、仕事をする1行の「アルゴリズム」タイプの関数を生成できます。しかし、ワンライナーは、平均的なプログラマーに明確になる前に、5つの異なる「文」に分割する必要がありました。


義務は見る人の目にあります。

-2

計算パフォーマンス。パイプラインを最適化するか、コードの一部を並行して実行する場合、たとえば、1〜400ではなく1〜50のループを作成し、各ループに同様のコードの8つのインスタンスを配置すると便利です。私はこれがあなたの状況に当てはまるとは思っていませんが、それはより多くの行がより良い例です(パフォーマンスに関して)。


4
優れたコンパイラーは、特定のコンピューターアーキテクチャーのループを展開する方法を平均的なプログラマーよりもよく知っているはずですが、一般的なポイントは有効です。Cray高性能ライブラリの行列乗算ルーチンのソースコードを見たことがあります。行列乗算は3つのネストされたループと合計約6行のコードですよね?間違った-そのライブラリルーチンは約1100行のコードに実行され、それに加えて、なぜそれがそんなに長かったのかを説明する同様の数のコメント行がありました!
-alephzero

1
@alephzeroうわー、私はそのコードを見たいです、それはただクレイクレイでなければなりません。

@alephzero、優れたコンパイラーは多くのことを行うことができますが、悲しいことにすべてではありません。明るい面は、これらがプログラミングを面白くするものだということです!
ハンスヤンセン

2
@alephzero確かに、優れた行列乗算コードは、少しの時間を削るだけでなく(つまり、一定の係数で減らす)、漸近的な複雑さの異なるまったく異なるアルゴリズムを使用します。たとえば、StrassenアルゴリズムはおおよそO(n ^ 2.8) O(n ^ 3)ではなく。
アーサータッカ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.