テンプレートの混乱からクリーンなクラスアーキテクチャ(C ++)に切り替える最良の方法は?

StackOverflow https://stackoverflow.com/questions/324043

  •  11-07-2019
  •  | 
  •  

質問

全体で200,000行を超えるコードを含む約100個のテンプレートを含む約100個のファイルを含む大きなテンプレートライブラリを想定します。一部のテンプレートは、多重継承を使用してライブラリ自体の使用をかなり単純にします(つまり、一部のベーステンプレートから継承し、特定のビジネスルールのみを実装する必要があります)。

存在するすべて(数年にわたって成長)、<!> quot; works <!> quot;プロジェクトに使用されます。

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

今、私は本当により少ないテンプレートとより特化したより小さなクラスを使用するためにアーキテクチャを単純化したいです。

そのタスクについて実証済みの方法はありますか?始めるのに適した場所は何ですか?

役に立ちましたか?

解決

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

アーキテクチャの簡素化、さまざまなクラスとテンプレート間の依存関係のリファクタリングと削除を理解できますが、<!> quot;少ないテンプレートがアーキテクチャを改善することを自動的に仮定します<!> quot; imoに欠陥があります。

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

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

もちろん、追加されたコンパイル時間は、場合によってはテンプレートを避ける正当な理由かもしれません。そして、あなたが<!> quot; traditional <!> quot ; OOP用語、テンプレートはそれらを混同するかもしれません。これはテンプレートを避けるもう一つの正当な理由になる可能性があります。

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

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

他のヒント

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

それ以外、<!> quot; divide and conqueor <!> quot;

単体テストを作成します。

新しいコードが古いコードと同じことをする必要がある場合。

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

編集:

新しい機能に置き換えた古いコードを廃止する場合、 新しいコードに少しずつ段階的に移行できます。

まあ、問題は、テンプレートの考え方がオブジェクト指向の継承ベースの考え方とは非常に異なることです。 <!> quot;全体を再設計し、ゼロから始める<!> quot;。

以外に答えることは難しい

もちろん、特定の場合には簡単な方法があるかもしれません。あなたが持っているものについてもっと知ることなく私たちは知ることができません。

テンプレートソリューションの保守が非常に難しいという事実は、とにかくデザインが貧弱であることを示しています。

いくつかのポイント(ただし、これらは実際には悪ではありません。ただし、非テンプレートコードに変更する場合は、これで解決できます):


静的インターフェースを調べます。テンプレートは、どの機能が存在するかに依存しますか?どこでtypedefが必要ですか?

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

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

ルックアップタイプの特性。いくつかのtypedefが存在するかどうか、または典型的なテンプレートコードに何らかのメソッドが存在するかどうかを確認するコードがたくさんあります。抽象ベースクラスは、純粋な仮想メソッドを使用し、ベースにtypedefを継承することにより、これら2つの問題を解決します。多くの場合、typedefは SFINAE のような恐ろしい機能をトリガーするためにのみ必要です。

ルックアップ式テンプレート。コードで式テンプレートを使用して一時的なものを作成しないようにする場合、テンプレートを削除し、関連する演算子に一時的なものを返す/渡す従来の方法を使用する必要があります。

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

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

最初に、<!> quot; fix <!> quot;を試みないでください。一度に。

次に、修正内容を理解します。多くの場合、テンプレートの複雑さには理由があります。特定の使用を強制し、コンパイラーが間違いを犯さないようにします。その理由はときどき遠ざかるかもしれませんが、<!> quot;だれも彼らが何をするのかを本当に知らないので100行を捨てます<!> quot;軽くとるべきではありません。ここで提案するすべてが本当に厄介なバグを持ち込む可能性があると警告されています。

3番目に、安価な修正を最初に検討します。より高速なマシンまたは分散ビルドツール。少なくとも、ボードが使用するすべてのRAMを投入し、古いディスクを廃棄します。違いはありません。 OS用の1つのドライブ、ビルド用の1つのドライブは、安価なマンRAIDです。

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

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


C ++を理解する モデルを構築 :すべての.cppは個別にコンパイルされます。つまり、多くのヘッダーを持つ巨大な.cppファイル=巨大なビルドです。ただし、すべてを1つの.cppファイルに入れることはお勧めしません!ただし、ビルドを非常に高速化できる1つのトリック(!)は、多数の.cppファイルを含む単一の.cppファイルを作成し、その<!> quot; master <!> quot;のみをフィードすることです。コンパイラへのファイル。しかし、盲目的にそれを行うことはできません-これが引き起こす可能性のあるエラーの種類を理解する必要があります。

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

プリコンパイル済みヘッダーを使用します。 (高速マシンでより良いスケーリング、上記参照)

ヘッダーの包含ポリシーを確認します。すべてのファイルは<!> quot; independent <!> quot; (つまり、他の誰かがインクルードする必要があるすべてのものを含める)、自由にインクルードしないでください。残念ながら、不要な#incldueステートメントを見つけるツールはまだ見つかりませんでしたが、<!> quot; hotspot <!> quot;の未使用のヘッダーを削除するのに少し時間をかけると役立つかもしれません。ファイル。

使用するテンプレートの

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

少数のタイプのテンプレートのオーバーロード:このようなごく少数のタイプにのみ役立つ複雑な関数テンプレートがある場合:

// .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ヘッダーを使用して、外部に知るべきことを達成方法から精神的および物理的に分離します。これにより、ライブラリの本当の価値が明らかになり(実際に複雑さをカプセル化しますか?)、<!> quot; easy targets <!> quot;を置き換える機会が与えられます。最初。


より具体的なアドバイス-そして与えられたものがどれほど有用かis-実際のライブラリに大きく依存します。

がんばって!

前述のように、単体テストは良いアイデアです。実際、<!> quot; simple <!> quot;を導入してコードを壊すのではなく、波及する可能性が高い変更、テストのスイートの作成に焦点を当て、テストの違反を修正します。バグが明らかになったらテストを更新するアクティビティを用意してください。

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

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

考慮すべきもう1つのことは、テンプレートを作成して継承階層をクリーンアップする頻度です。<!> quot; mixin <!> quot;多重継承の集合ではなくスタイル。テンプレート引数の1つを、派生元の基本クラスの名前にすることで、いくつの場所を逃れることができるかを確認します(boost::enable_shared_from_thisの仕組み)。もちろん、これは通常、コンストラクターが引数を取らない場合にのみ正常に機能します。何も正しく初期化することを心配する必要はありません。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top