抽象化:問題の解決と一般的な解決策の間の戦争[終了]


33

プログラマーとして、私は自分のプログラムをできるだけ抽象的で一般的なものにするジレンマに陥っています。

そうすることで、通常、コードを再利用し、再び発生する可能性のある(または発生しない)問題のより一般的な解決策を得ることができます。

それから私の頭の中のこの声は、問題のダミーを簡単に解決するだけだと言います!なぜあなたが必要以上に多くの時間を費やしますか?

実際、私たちは皆、この疑問に直面してきました。そこでは、抽象化があなたの右肩にあり、Solve-it-愚かな人が左に座っています。

どれを、どのくらいの頻度で聞くべきですか?これに対するあなたの戦略は何ですか?すべてを抽象化する必要がありますか?


1
「抽象的で一般的」は一般にプログラマーのris慢として知られています:)ただし、マーフィーの法則により、次のタスクに取り組む際に必要な1度の適応は、提供されていないものになります。
マチューM.

1
史上最高の質問の1つ!
探検家

1
あなたはいつも間違っていると思うので、簡単に浸透させてください。「十分な時間」という単純なケースを展開すると、パターンが見え始めます。それまではいけません。

回答:


27

どれを、どのくらいの頻度で聞くべきですか?

必要になるまで抽象化しないでください。

たとえば、Javaでは、インターフェイスを使用する必要があります。それらは抽象化です。

Pythonにはインターフェイスがなく、Duck Typingがあり、抽象化の高いレベルは必要ありません。だからあなたはそうしません。

これに対するあなたの戦略は何ですか?

3回作成するまで抽象化しないでください。

1回は-まあ-1回です。解決して先に進みましょう。

2回は、ここにパターンがある可能性があることを示しています。またはありません。偶然かもしれません。

3回目はパターンの始まりです。今では偶然を超えています。これで正常に抽象化できます。

すべてを抽象化する必要がありますか?

いや

実際、正しい種類の抽象化を行っているという絶対的な証拠が得られるまで、何も抽象化しないでください。「3つの繰り返しのルール」がなければ、役に立たない方法で無駄に抽象化されたものを書くことになります。

通常、そうすることでコードを再利用できます

これは多くの場合間違っている仮定です。抽象化はまったく役に立ちません。それはひどく行うことができます。したがって、必要になるまで実行しないでください。


1
「必要になるまで抽象化しないでください。」-クラス、メソッド、ループ、またはifステートメントの使用は避けてください。これらはすべて抽象化されています。考えてみると、高水準言語で書かれたコードは、好むと好まざるとにかかわらず、非常に抽象的なものです。問題は抽象化するかどうかではありません。どの抽象化を使用するかです。
ジェイソンベイカー

9
@Jason Baker:「クラス、メソッド、ループ、またはifステートメントの使用は避けてください」それは問題が何であったかではないようです。設計を過度に抽象化しすぎない限り、すべてのプログラミングが不可能であるというばかげた主張をするのはなぜですか
S.Lott

1
私は、男性が女性と一緒に100万ドルで寝るかどうかを尋ねるという古いジョークを思い出します。彼女が「はい」と言うと、彼は5ドルで彼と一緒に寝るかどうか尋ねます。彼女が「どんな女性を私に連れて行ってくれますか」と言うとき。応答は「まあ、あなたはセックスのために誰かと一緒に寝ることをすでに確立しました。今、私たちはただ価格をめぐって苦しんでいます。」私のポイントは、抽象化するかしないかの決定についてではないということです。抽象化する量と選択する抽象化についてです。純粋なアセンブリを記述していない限り、抽象化を保留することはできません。
ジェイソンベイカー

9
@Jason Baker:「純粋なアセンブリを書いていない限り、抽象化を遅らせることはできません」それは些細な誤りです。アセンブリは、機械語の抽象化です。これは、ハードウェア回路の抽象化です。そもそも問題になっていない答えを読みすぎないようにしてください。問題は、概念または知的ツールとしての「抽象化」ではありませんでした。それは、オブジェクト指向の設計手法としての抽象化に関するもので、誤用が多すぎます。私の答えは、抽象化が不可能だということではありませんでした。しかし、その「過剰な抽象化」は悪いです。
S.Lott

1
@JasonBakerそれは誰かと寝る前代未聞の理由ではありません。おそらく「お金」を考えていたのですか?

19

ああ、ヤグニ。最も乱用されたプログラミングの概念。

コードを汎用化することと、余分な作業を行うことには違いがあります。コードを疎結合にし、他のことを行うのに簡単に適応させるために余分な時間を費やすべきですか?絶対に。不要な機能の実装に時間を費やすべきですか?いいえ。まだ実装されていない他のコードでコードを動作させるのに時間を費やすべきですか?いや

ことわざにあるように、「おそらく動作する可能性のある最も単純なことを行う」。事は人々がいつも簡単簡単を混同するということです。シンプルさには手間がかかります。しかし、努力する価値はあります。

船外に行けますか?もちろん。しかし、私の経験では、ほとんどの企業は既に持っている以上の「今すぐやろう」という態度を必要としています。ほとんどの企業には、事前に物事を考える人がもっと必要です。さもなければ、彼らは常に誰も何もする時間がなく、誰もがいつも急いでいて、誰も何もすることができないという自立した問題を抱えています。

このように考えてください:あなたのコードが何らかの形で再び使用される可能性が高いです。ただし、そのように正しく予測することはできません。したがって、コードをクリーンで抽象的で、疎結合にします。ただし、まだ存在しない特定の機能を使用してコードを機能させようとしないでください。たとえそれが将来存在することを知っていても。


ニースの区別の間simpleeasy
マチューM.11年


「しかし、私の経験ではその数少ない企業です」-興味深いことに、学界では逆のことが当てはまるかもしれません。
スティーブベネット

12

三段論法:

  1. 一般性は高価です。

  2. あなたは他の人のお金を使っています。

  3. したがって、一般性の費用は利害関係者に正当化される必要があります。

後で利害関係者のお金を節約するために、より一般的な問題を本当に解決しているの、それとも不必要に一般的なソリューションを作成するのが知的挑戦であると思うのかを自問してください。

一般性が望ましいと判断された場合、他の機能と同様に設計およびテストする必要があります。実装した汎用性が仕様で必要な問題をどのように解決するかを示すテストを作成できない場合は、気にしないでください!設計基準を満たさず、テストできない機能は、誰も信頼できない機能です。

そして最後に、「可能な限り一般的」というものはありません。議論のためだけに、C#でソフトウェアを作成するとします。すべてのクラスにインターフェースを実装させますか?すべてのクラスは抽象基底クラスであり、すべてのメソッドは抽象メソッドですか?それはかなり一般的ですが、「可能な限り一般的」にはほど遠いです。これにより、サブクラス化によってメソッドの実装を変更できます。サブクラス化せずにメソッドの実装を変更したい場合はどうなりますか?実際には、すべてのメソッドを別のメソッドに変更できるように、セッターを使用して、すべてのメソッドをデリゲート型のプロパティにすることができます。しかし、誰かがさらにメソッドを追加したい場合はどうでしょうか?ここで、すべてのオブジェクトは拡張可能でなければなりません。

この時点で、C#を放棄してJavaScriptを採用する必要があります。しかし、あなたはまだ十分に一般的なものに近づいていません。誰かがこの特定のオブジェクトに対するメンバー検索の動作を変更したい場合はどうなりますか?代わりに、すべてをPythonで記述する必要があります。

一般性の向上とは、多くの場合、予測可能性を放棄し、テストコストを大幅に増加させて、実装された一般性が実際のユーザーのニーズを満たすことを確実にすることを意味します。それらの費用は、それを支払っている利害関係者への利益によって正当化されますか?おそらくそうです。利害関係者と話し合うのはあなた次第です。私の利害関係者は、完全に不必要で非常に高価な一般性を追求するために静的型付けを放棄するつもりはまったくありませんが、おそらくあなたはそうです。


2
有用な原則の場合は+1、金銭の問題については-1。
メイソンウィーラー

4
@メイソン:それはお金のことではなく、努力のことです。お金は実践的な努力の尺度です。効率は、生み出される努力ごとに生じる利益です。繰り返しますが、お金の利益は、発生し利益の実際的な尺度です。お金の抽象化について議論する方が、努力、コスト、利益を測定する何らかの方法を考え出すよりも簡単です。他の方法で労力、コスト、利益を測定したいですか?より良い方法の提案をしてください。
エリックリッパー

2
@Mason Wheelerの主張に同意します。ここでの解決策は、プロジェクトのそのレベルに関係者を巻き込まないことです。それは明らかに業界に大きく依存していますが、クライアントは通常コードを見ません。また、これらの答えはすべて反努力のようで、怠zyなようです。
11

2
@Orbling:私は、価値のある重要なプロジェクトからリソースを奪う不必要で高価で無駄な努力に強く反対しています。これが私を「怠laz」にすることを提案している場合、私はあなたが異常な方法で「怠y」という言葉を使用していることを提出します。
エリックリッパー

1
私の経験では、他のプロジェクトがなくなることはないので、通常、先に進む前に現在のプロジェクトに必要な時間を投資する価値があります。
11

5

明確にするために、一般化されたものを作成し、抽象化を実装することは、まったく異なる2つのことです。

たとえば、メモリをコピーする関数を考えます。

この関数は、4バイトのコピー方法を隠す抽象化です。

int copy4Bytes(char * pSrc、char * pDest)

一般化すると、この関数は任意のバイト数をコピーします。

int copyBytes(char * pSrc、char * pDest、int numBytesToCopy)

抽象化は再利用に役立ちますが、一般化により多くの場合に抽象化が有用になります。

より具体的に質問に関連して、抽象化はコードの再利用の観点からのみ有用ではありません。多くの場合、正しく行われれば、コードがより読みやすく保守しやすくなります。上記の例を使用すると、コードcopyBytes()をざっと読んだり、配列を反復処理してデータを1インデックスずつ移動したりする場合に、読みやすく理解しやすいものは何でしょうか。抽象化は、私の意見では、コードを扱いやすくするための自己文書を並べ替えることができます。

私自身の経験則として、コードの一部の意図を正確に説明する適切な関数名を思い付くことができれば、再度使用するかどうかに関係なく、そのための関数を記述します。


抽象化と一般化を区別するための+1。
ジョリスメイズ

4

この種の物に対する一般的なルールは、ゼロ、ワン、インフィニティです。つまり、書いているものを複数回必要とする場合、それがさらに何度も必要になると仮定して、一般化することができます。このルールは、何かを最初に書くときに抽象化に煩わされないことを意味します。

このルールのもう1つの正当な理由は、コードを初めて作成するときに、抽象化する対象が1つしかないため、必ずしも何を抽象化するかがわからないことです。マーフィーの法則は、最初に抽象コードを記述する場合、2番目の例には予期しなかった違いがあることを意味します。


1
これは、私のバージョンのリファクタリングルールによく似ています:ストライク2!リファクタリング!
フランクシェラー

@フランク:おそらくあなたのリファクタリングルールですが、個人的な経験から2番目の段落が追加されています。
ラリーコールマン

+1:これは、The Pragmatic Programmerのほぼ逐語的なIIRC でもかなり早い段階で指摘されていると思います。
スティーブンエバーズ

間違いなく。1つの例だけで抽象化することはありません。2番目の例があるとすぐに、一般的な(共有されている)ものとそうでないものを確認でき、抽象化できます!
フランクシェラー

私の意見では、2つのサンプルは無限大に一般化するには小さすぎます。言い換えれば、非常に早すぎます。

2

Eric Lippertは、彼の記事で将来設計を証明する 3つのことを指摘しています。あなたがそれに従うなら、あなたは良い状態になると思います。

最初:時期尚早の一般性は高価です。

2番目:常に問題領域にあり、クラスの関係が変わらないものだけをモデルに表現します。

第三:ポリシーをメカニズムから遠ざけます。


1

コーディングの理由、プロジェクトの目的によって異なります。コードの価値が具体的な問題を解決するものである場合、それを完了させて次の問題に進みたいと思うでしょう。コードの将来のユーザー(自分を含む)が簡単にできるようにするためにできる迅速で簡単なことがあれば、ぜひとも、合理的な配慮をしてください。

一方、記述しているコードがより一般的な目的に役立つ場合があります。たとえば、他のプログラマーのためにライブラリを作成している場合、さまざまなアプリケーションでライブラリを使用します。潜在的なユーザーは不明であり、ユーザーが何を望んでいるかを正確に尋ねることはできません。しかし、あなたはあなたのライブラリを広く有用にしたいです。そのような場合、一般的なソリューションをサポートするためにより多くの時間を費やすことになります。


0

私はKISSの原則の大ファンです。

最善の解決策ではなく、求められていることを提供することに重点を置いています。私は悲惨になったので、完璧主義(およびOCD)を手放さなければなりませんでした。


なぜ完全主義が嫌いなのか?(ちなみに投票しませんでした)
11

完璧を目指しているのではありません。どうして?私のプログラムは決して完璧ではないことを知っているからです。完璧さの標準的な定義はないので、うまく機能するものを提供することにしました。
パブロ

-1

ここで、Abstractionは右肩にあり、Solve-it-st-upidは左側にあります。

「愚かな解決」には同意しませんが、賢く解決する」ことができると思います。

よりスマートなもの:

  • 複数のケースをサポートできる複雑な汎用ソリューションを作成する
  • 手で問題を解決し、保守が容易で、短いとeffecientコードを書くと、そのことができますされ、拡張将来的にはIFことが必要です。

デフォルトの選択肢は2番目の選択肢です。世代の必要性を示すことができなければ。

一般化されたソリューションは、複数の異なるプロジェクト/ケースで使用されるものであることがわかっている場合にのみ使用してください。

通常、一般化されたソリューションは、「コアライブラリコード」として最適です


ここですべての仮定を立てることができれば、問題はありません。短くて維持しやすいことは相互に排他的です。何かが再利用されることがわかっている場合は、一般的な解決策で対処します。
ジェフ

私はかなりよく分からない@Jeff私はあなたのコメントを理解して...
Darknight

コンテキストから「Solve it Stupid」を削除しました。
ブライアンハリントン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.