1

全体で 200,000 行以上のコードを持つ約 100 個のテンプレートを含む約 100 個のファイルを含む大規模なテンプレート ライブラリを想定しています。一部のテンプレートは、複数の継承を使用して、ライブラリ自体の使用を単純化しています (つまり、いくつかの基本テンプレートから継承し、特定のビジネス ルールを実装するだけで済みます)。

存在するすべて (数年にわたって成長)、「機能」し、プロジェクトに使用されます。

ただし、そのライブラリを使用したプロジェクトのコンパイルには時間がかかり、特定のバグのソースを見つけるのにかなりの時間がかかります。一部の相互に依存するテンプレートを変更する必要があるため、修正はしばしば予期しない副作用を引き起こしたり、非常に困難になったりします。機能が非常に多いため、テストはほぼ不可能です。

ここで、アーキテクチャを単純化して、使用するテンプレートを減らし、より特化した小さなクラスを使用したいと考えています。

そのタスクを実行するための証明された方法はありますか? 始めるのに適した場所は何ですか?

4

8 に答える 8

13

テンプレートがどのように/なぜ問題なのか、テンプレート化されていない単純なクラスが改善される理由がわかりません。それは、クラスがさらに多くなり、型の安全性が低下し、バグの可能性が非常に大きくなることを意味するのではないでしょうか?

アーキテクチャを簡素化し、さまざまなクラスとテンプレート間の依存関係をリファクタリングして削除することは理解できますが、「テンプレートを減らすとアーキテクチャが改善される」と自動的に想定することには欠陥があります。

テンプレートを使用すると、テンプレートを使用しない場合よりもはるかにクリーンなアーキテクチャを構築できる可能性があると言えます。別々のクラスを完全に独立させることができるからです。テンプレートがない場合、別のクラスを呼び出すクラス関数は、そのクラスまたはそれが継承するインターフェイスについて事前に知っておく必要があります。テンプレートでは、この結合は必要ありません。

テンプレートを削除すると、依存関係が減少するのではなく、増加するだけです。追加されたテンプレートの型安全性は、コンパイル時に多くのバグを検出するために使用できます (この目的のために、コードに static_assert を自由に散りばめます)

もちろん、追加されたコンパイル時間は、場合によってはテンプレートを避ける正当な理由になる可能性があります。「従来の」OOP 用語で考えることに慣れている Java プログラマーがたくさんいる場合、テンプレートは彼らを混乱させる可能性があります。テンプレートを避けるもう 1 つの正当な理由です。

しかし、アーキテクチャの観点からは、テンプレートを避けることは間違った方向への一歩だと思います。

アプリケーションをリファクタリングします。確かに、それが必要なようです。ただし、元のバージョンのアプリが悪用されたという理由だけで、拡張可能で堅牢なコードを作成するための最も便利なツールの 1 つを捨ててはなりません。特に、既にコードの量に関心がある場合は、テンプレートを削除するとコード行が増える可能性が高くなります。

于 2008-11-27T16:06:44.813 に答える
7

自動化されたテストが必要です。10 年後に後継者が同じ問題を抱えている場合、コードをリファクタリングして (ライブラリの使用が簡単になると考えているため、おそらくテンプレートを追加するため)、すべてのテスト ケースを満たしていることを確認できます。同様に、マイナーなバグ修正の副作用はすぐにわかります (テスト ケースが適切であると仮定します)。

それ以外は「分割統治」

于 2008-11-27T15:50:29.540 に答える
3

単体テストを書きます。

新しいコードが古いコードと同じことをしなければならない場所。

それは少なくとも1つのヒントです。

編集:

新しい機能に置き換えた古いコードを非推奨にする場合は、少しずつ新しいコードに移行できます。

于 2008-11-27T15:54:07.567 に答える
2

いくつかのポイント (注意: これらは実際には悪いことではありません。ただし、テンプレート以外のコードに変更したい場合は、これが役立ちます):


静的インターフェイスを検索します。テンプレートは、存在する機能に応じてどこに依存しますか? typedef が必要な場所はどこですか?

共通部分を抽象基本クラスに入れます。良い例は、たまたま CRTP イディオムに出くわしたときです。仮想関数を持つ抽象基本クラスに置き換えることができます。

整数リストを検索します。コードが のような整数リストを使用していることがわかった場合、それらを使用するすべてのコードが定数式ではなくランタイム値を使用して動作できる場合は、list<1, 3, 3, 1, 3>それらを に置き換えることができます。std::vector

ルックアップ型の特徴。何らかの typedef が存在するかどうか、または典型的なテンプレート化されたコードに何らかのメソッドが存在するかどうかをチェックするコードが多数あります。抽象基底クラスは、純粋仮想メソッドを使用し、typedef を基底に継承することによって、これら 2 つの問題を解決します。多くの場合、typedef はSFINAEのような厄介な機能をトリガーするためだけに必要であり、それも不要になります。

ルックアップ式テンプレート. コードで式テンプレートを使用して一時変数の作成を回避する場合は、それらを削除し、関係する演算子に一時変数を返す/渡す従来の方法を使用する必要があります。

ルックアップ関数オブジェクト。コードで関数オブジェクトを使用していることがわかった場合は、抽象基底クラスも使用するようにそれらを変更し、それらvoid run();を呼び出すようなものを作成できます (または、 を使用し続けたい場合はoperator()、そのほうがよいでしょう! 仮想化も可能です)。

于 2008-11-27T16:03:53.023 に答える
2

問題は、テンプレートの考え方がオブジェクト指向の継承ベースの方法とは大きく異なることです。「全体を再設計してゼロから始める」以外に答えるのは難しいです。

もちろん、特定のケースでは簡単な方法があるかもしれません。あなたが持っているものについてもっと知らなければ、私たちは何とも言えません。

テンプレート ソリューションの保守が非常に困難であるという事実は、設計が不十分であることを示しています。

于 2008-11-27T15:45:48.007 に答える
1

私が理解しているように、あなたはビルド時間とライブラリの保守性に最も関心がありますか?

まず、一度にすべてを「修正」しようとしないでください。

次に、何を修正するかを理解します。テンプレートの複雑さは、多くの場合、特定の使用を強制したり、間違いを犯さないようにコンパイラーを支援したりするなどの理由で存在します。その理由は時々遠ざかるかもしれませんが、「誰も自分が何をしているのか本当に分かっていない」という理由で 100 行を捨てることは軽視すべきではありません。ここで提案することはすべて、本当に厄介なバグを引き起こす可能性があることを警告しました.

第 3 に、最初に安価な修正を検討します。たとえば、マシンの高速化やビルド ツールの分散などです。少なくとも、ボードが使用するすべての RAM を投入し、古いディスクを捨てます。それは違います。OS用に1ドライブ、ビルド用に1ドライブという安価なマンズRAIDです。

ライブラリは十分に文書化されていますか? これは、ドキュメントを作成するための最高のチャンスです。そのようなドキュメントを作成するのに役立つ doxygen などのツールを調べてください。

すべて考慮?OK、ビルド時間に関するいくつかの提案;)


C++ビルド モデルを理解する: すべての .cpp は個別にコンパイルされます。つまり、多くのヘッダーを持つ多くの .cpp ファイル = 巨大なビルドです。ただし、これはすべてを 1 つの .cpp ファイルに入れるようにというアドバイスではありません。ただし、ビルドを大幅に高速化できる 1 つのトリック (!) は、多数の .cpp ファイルを含む単一の .cpp ファイルを作成し、その「マスター」ファイルのみをコンパイラに供給することです。ただし、やみくもにこれを行うことはできません。これにより発生する可能性のあるエラーの種類を理解する必要があります。

まだ持っていない場合は、リモート接続できる別のビルド マシンを入手してください。一部のインクルードが壊れていないかどうかを確認するには、ほぼ完全なビルドを多数実行する必要があります。これを別のマシンで実行することをお勧めします。これにより、他の作業を妨げることはありません。とにかく、長期的には、毎日の統合ビルドに必要になります;)

プリコンパイル済みヘッダーを使用します。(高速なマシンでより適切にスケーリングします。上記を参照してください)

ヘッダーの包含ポリシーを確認してください。すべてのファイルは「独立」している必要がありますが (つまり、他の誰かが含める必要のあるすべてのファイルを含める必要があります)、むやみに含めないでください。残念ながら、不要な #incldue ステートメントを見つけるツールはまだ見つかっていませんが、「ホットスポット」ファイルで使用されていないヘッダーを削除するのに時間がかかる場合があります。

使用するテンプレートの前方宣言を作成して使用します。多くの場合、多くの場所で forwad 宣言を含むヘッダーをインクルードし、いくつかの特定の場所でのみ完全なヘッダーを使用できます。これにより、コンパイル時間が大幅に短縮されます。<iosfwd>標準ライブラリが I/O ストリームに対してそれを行う方法をヘッダーで確認してください。

少数の型のテンプレートのオーバーロード: 次のような非常に少数の型にのみ役立つ複雑な関数テンプレートがある場合:

// .h
template <typename FLOAT> // float or double only
FLOAT CalcIt(int len, FLOAT * values) { ... }

ヘッダーでオーバーロードを宣言し、テンプレートを本文に移動できます。

// .h
float CalcIt(int len, float * values);
double CalcIt(int len, double * values);

// .cpp
template <typename FLOAT> // float or double only
FLOAT CalcItT(int len, FLOAT * values) { ... }

float CalcIt(int len, float * values) { return CalcItT(len, values); }
double CalcIt(int len, double * values) { return CalcItT(len, values); }

これにより、長いテンプレートが単一のコンパイル単位に移動します。
残念ながら、これはクラスでの使用が制限されています。

PIMPL イディオムがコードをヘッダーから .cpp ファイルに移動できるかどうかを確認します。

その背後に隠れている一般的なルールは、ライブラリのインターフェイスを実装から分離することです。コメント、detail名前空間、および個別の.impl.hヘッダーを使用して、外部に知られるべきことを、それがどのように達成されるかから精神的および物理的に分離します。これにより、ライブラリの真の価値が明らかになり (複雑さを実際にカプセル化していますか?)、最初に「簡単なターゲット」を置き換える機会が得られます。


より具体的なアドバイス、および与えられたアドバイスがどれほど役立つかは、実際のライブラリに大きく依存します。

幸運を!

于 2008-11-27T23:49:25.423 に答える
0

前述のように、単体テストは良い考えです。実際、波及する可能性のある「単純な」変更を導入してコードを壊すのではなく、一連のテストを作成し、テストの非準拠を修正することに集中してください。バグが明らかになったときにテストを更新する活動を行います。

それ以上に、テンプレート関連の問題のデバッグに役立つように、可能であればツールをアップグレードすることをお勧めします。

于 2008-11-27T15:55:20.047 に答える
0

巨大で、インスタンス化するのに多くの時間とメモリを必要とするが、その必要はなかった従来のテンプレートによく出くわします。そのような場合、脂肪を取り除く最も簡単な方法は、テンプレート引数のいずれにも依存しないすべてのコードを取得し、通常の翻訳単位で定義された別の関数に隠すことでした。これには、このコードをわずかに変更したり、ドキュメントを変更したりする必要がある場合に、再コンパイルのトリガーが少なくなるというプラスの副作用もありました。当たり前のように聞こえるかもしれませんが、クラス テンプレートを作成して、テンプレート化された情報を必要とするコードだけでなく、それが行うすべてのことをヘッダーで定義する必要があると考えている人が多いのは、本当に驚くべきことです。

考慮すべきもう 1 つのことは、複数の継承の集約ではなく、テンプレートを "mixin" スタイルにすることによって、継承階層をクリーンアップする頻度です。テンプレート引数の 1 つを派生元の基本クラスの名前にすることで、どれだけ多くの場所を回避できるかを確認してください (方法はうまくいきますboost::enable_shared_from_this)。もちろん、これは通常、コンストラクターが引数を取らない場合にのみうまく機能します。何かを正しく初期化することを心配する必要がないからです。

于 2008-11-27T20:33:19.127 に答える