12

Haskell やその他の関数型言語をいじってみると、一般的な用語で問題を記述することによる設計の単純さに感謝するようになりました。テンプレート プログラミングの多くの側面は決して単純ではありませんが、一部の使用法は十分に一般的であるため、明確化の妨げにはならないと思います (特に関数テンプレート)。テンプレートを使用すると、現在のデザインを単純化しながら、将来への抵抗を自動的に追加できることがよくあります。それらの機能をライブラリの作成者に委ねる必要があるのはなぜですか?

一方で、疫病のようにテンプレートを避ける人もいるようです。ジェネリック型の概念そのものがプログラミング コミュニティの多くにとってなじみのないものであった 10 年前に、私はこれを理解することができました。しかし現在、一般的な静的型付けオブジェクト指向言語はすべて、何らかの形式のジェネリックをサポートしています。親しみやすさが増したことで、保守的な態度を調整する必要があるようです。

そのような保守的な態度の 1 つが、最近私に表明されました。

必要以上に一般化してはいけません - ソフトウェア開発の基本ルール。

これが自明であるべきかのように否定的に述べられているのを見て、私は非常に正直に驚いた. 個人的には、特に指定しない限り、すべてがジェネリックである Haskell のような言語では、自明とはほど遠いと思います。そうは言っても、私はこの視点がどこから来ているのか理解していると思います。

心の奥底では、そのようなルールがぐるぐる回っています。それが最前線にある今、私は常に全体的なアーキテクチャに照らしてそれを解釈してきたことに気づきました. たとえば、クラスがある場合、いつか使用する可能性のある大量の機能をクラスにロードしたくありません。具体的なバージョンが 1 つだけ必要な場合は、わざわざインターフェイスを作成しないでください (ただし、モック可能性はこれに対する反論になる可能性があります)。そういうもの...

ただし、私はこの原則をミクロレベルで適用することはしません。特定の型に依存する理由のない小さなユーティリティ関数がある場合は、テンプレートを作成します。

それで、あなたはどう思いますか?何を過度に一般化していると思いますか? この規則は、文脈によって適用可能性が異なりますか? これが規則であることに同意しますか?

4

7 に答える 7

14

一般化しすぎると私は夢中になります。私はテンプレート(どこにも近くない)を恐れていません、そして私は一般的な解決策が好きです。しかし、私はクライアントが支払っている問題を解決することも好きです。それが1週間のプロジェクトである場合、なぜ私は今、新しい税金のような明らかな将来の変化だけでなく、おそらく新月や火星での生活の発見を通しても機能し続ける1か月の贅沢に資金を提供しているのですか?

これをテンプレートに戻すと、クライアントは、文字列と数値を受け取る関数の記述を含むいくつかの機能を要求します。あなたは私に任意の2つのタイプを取り、私の特定のケースに対して正しいことをし、残りのケースでは正しいかもしれないし、そうでないかもしれない(要件がないために)何かを行うテンプレートソリューションをくれます、そして私は感謝しません。私はあなたに支払うことに加えて、それをテストするために誰か、それを文書化するために誰か、そしてもっと一般的なケースが起こった場合に将来あなたの制約の範囲内で働くために誰かに支払わなければならないことに気づきます。

もちろん、すべての一般化が一般化を超えているわけではありません。すべてが可能な限りシンプルである必要がありますが、それ以上シンプルであってはなりません。必要に応じて一般的ですが、それ以上一般的ではありません。可能な限りテストされていますが、これ以上テストされていません。など。また、「何が変わるかを予測し、それをカプセル化する」。これらのルールはすべて単純ですが、簡単ではありません。そのため、開発者とそれを管理する人には知恵が重要です。

于 2010-07-14T02:53:24.360 に答える
11

同時にそれを行うことができ、コードが少なくとも同じくらい明確である場合、一般化は常に特殊化よりも優れています。

XPの人々が従うYAGNIという原則があります-あなたはそれを必要としないでしょう。

ウィキには次のように書かれています。

後で機能が必要になると完全に、完全に、完全に確信している場合でも、今は実装しないでください。通常、a)結局は必要ないか、b)実際に必要なものは、以前に必要であると予測したものとはかなり異なります。

これは、コードに柔軟性を組み込むことを避ける必要があるという意味ではありません。これは、後で必要になる可能性があると思われるものに基づいて、何かをオーバーエンジニアリングしてはならないことを意味します。

于 2010-07-14T02:54:03.583 に答える
6

あまりにも一般的ですか?私は (原則として) ジェネリック プログラミングのファンであることを認めなければならず、Haskell と Go がそこで使用しているアイデアが本当に気に入っています。

ただし、C++ でプログラミングする場合は、同様の目標を達成するための 2 つの方法が提供されます。

  • ジェネリック プログラミング: コンパイル時間、実装への依存性などの問題はありますが、テンプレートを使用します。
  • オブジェクト指向プログラミング: その祖先はある意味で、関数ではなくオブジェクト自体 (クラス/構造体) に問題を置きます...

さて、いつ使用するのですか?確かに難しい質問です。ほとんどの場合、それは直感以上のものではなく、私は確かにどちらかの乱用を見てきました.

経験上、関数/クラスが小さいほど、その目標がより基本的であるほど、一般化が容易になります。例として、私はほとんどのお気に入りのプロジェクトと職場でツールボックスを持ち歩いています。そこにある関数/クラスのほとんどはジェネリックです...ある意味でBoostに少し似ています;)

// No container implements this, it's easy... but better write it only once!
template <class Container, class Pred>
void erase_if(Container& c, Pred p)
{
  c.erase(std::remove_if(c.begin(), c.end(), p), c.end());
}

// Same as STL algo, but with precondition validation in debug mode
template <class Container, class Iterator = typename Container::iterator>
Iterator lower_bound(Container& c, typename Container::value_type const& v)
{
  ASSERT(is_sorted(c));
  return std::lower_bound(c.begin(), c.end(), v);
}

一方、ビジネス固有の仕事に近づくほど、一般的である可能性は低くなります。

だからこそ、私自身、最小努力の原則を高く評価しています。クラスやメソッドについて考えるとき、私はまず一歩下がって少し考えます。

  • より一般的なものにすることは理にかなっていますか?
  • 費用は?

anwsers に応じて、ジェネリック性の程度を調整し、時期尚早のロックを回避するのに苦労しています。

例:

void Foo::print() { std::cout << /* some stuff */ << '\n'; }

// VS

std::ostream& operator<<(std::ostream& out, Foo const& foo)
{
  return out << /* some stuff */ << '\n';
}

より一般的であるだけでなく (出力先を指定できます)、より慣用的でもあります。

于 2010-07-14T09:22:28.223 に答える
4

あなたがそれを一般化するのに時間を無駄にしているとき、何かが過度に一般化されています。将来的に汎用機能を使用する場合は、おそらく時間を無駄にすることはありません。それは本当にとても簡単です[私の心の中で]。

注意すべきことの1つは、ソフトウェアを一般化することは、それがさらに混乱を招く場合でも、必ずしも改善ではないということです。多くの場合、トレードオフがあります。

于 2010-07-14T02:53:06.420 に答える
2

プログラミングの 2 つの基本原則を考慮する必要があると思います。それは、KISS (単純明快にすること) と DRY (同じことを繰り返さないこと) です。ほとんどの場合、最初の方法から始めます。つまり、必要な機能を最も簡単でシンプルな方法で実装します。私の要件をすでに満たすことができるので、それで十分なことがよくあります。この場合、単純なままです (一般的ではありません)。

2回目(または最大3回目)に同様のものが必要になった場合、具体的な実例に基づいて問題(関数、クラス、デザインなど)を一般化しようとします->それ自体のためだけに一般化することはほとんどありません。次の同様の問題:現在の画像にエレガントに収まる場合は、簡単に解決できます。そうでない場合は、現在のソリューションをさらに一般化できるかどうかを確認します (複雑になりすぎたり、エレガントでなくなったりすることはありません)。

一般的な解決策が必要になることが事前にわかっていても、同様のことを行う必要があると思います。具体的な例をいくつか取り上げ、それらに基づいて一般化を行います。そうしないと、「適切な」一般的な解決策が得られたとしても、実際の問題を解決するためには使用できないという行き止まりに陥りやすくなります。

ただし、これにはいくつかの例外的なケースがあるかもしれません。
a)一般的な解決策がほぼ同じ労力と複雑さである場合。例: ジェネリックを使用して Queue 実装を作成することは、文字列に対して同じことを行うよりもそれほど複雑ではありません。
b) 一般的な方法で問題を解決する方が簡単で、解決策も理解しやすい場合。あまり頻繁に発生するわけではありません。現時点では、これの単純で実際の例を思いつくことはできません:-(。しかし、この場合でも、以前に具体的な例を持っている/分析することは、IMO でのみ確認できるため、必須です。あなたが正しい軌道に乗っていること。

経験は具体的な問題を抱えているという前提を乗り越えることができると言えますが、この場合の経験とは、具体的な同様の問題や解決策をすでに見て考えたことを意味すると思います。

時間があれば、Structure and Interpretation of Computer Programs をご覧ください。一般性と複雑さの間の適切なバランスを見つける方法、および問題で実際に必要な複雑さを最小限に抑える方法について、興味深いことがたくさんあります。

そしてもちろん、さまざまなアジャイル プロセスでも同様の方法が推奨されています。必要に応じて、単純なリファクタリングから始めてください。

于 2010-07-14T07:12:47.100 に答える
2

私にとって、過度の一般化とは、それ以上のステップで抽象化を破る必要がある場合です。プロジェクト内の例、私は住んでいます:

Object saveOrUpdate(Object object)

このメソッドは 3 層アーキテクチャ内でクライアントに提供されるため、一般的すぎるため、コンテキストなしでサーバーに保存されたオブジェクトを確認する必要があります。

于 2011-07-27T12:02:44.220 に答える
0

過剰な一般化には、マイクロソフトの例が 2 つあり
ます。1.) CObject (MFC)
2.) オブジェクト (.Net)

どちらも、ほとんどの人が利用していない c++ のジェネリックを「実現」するために使用されます。実際、誰もがこれら (CObject/Object) を使用して指定されたパラメーターの型チェックを行いました ~

于 2010-07-14T03:05:35.967 に答える