皆さんの中には、ジョン・ラコスの基本的な仕事である大規模C ++の次の再リリースについてすでに最新のものがあると想定しています 。
以前の1巻版は20年近く前に発表されましたが、インターネット(およびAmazonでも)には、古い版でさえも十数年以上関連性があったことを説得力を持って証言する多くの肯定的なレビューがあります。 以下に、そのような記事の翻訳を示します。これは、大規模なC ++プロジェクトの物理設計に関する一般的な推奨事項の概要を示しています。 記事があなたの興味を引くことを願っています。来年の新版の翻訳であなたを喜ばせることができるでしょう。
私が偶然読んだC ++プログラミングに関する最も興味深い本の1つは、John Lakosの大規模C ++ソフトウェアデザインです。 1996年にリリースされましたが、残念ながら、C ++での物理設計と、大規模システムに対応するためのそのようなプロジェクトのスケーリングに関する唯一の本です。
それに記載されている原則は、10年以上経っても陳腐化しないのでしょうか? 以下は著者の最も重要なヒントに関する簡単なコメントです。実際のプロジェクトに関するこれらすべてのヒントを確認しました。
用語を扱います
- 物理設計は、ソフトウェアシステムの物理エンティティ(ファイル、ディレクトリ、ライブラリ)を使用した作業です。
- アナウンスは、プログラム内の名前の最初の言及です。 定義は、プログラム内のエンティティ(タイプ、インスタンス、関数など)の一意の説明を提供します
- 翻訳単位内でローカルに使用される場合、つまり、リンク中に別の翻訳単位内で定義された同一の名前との競合が除外される場合、名前には内部バインディングがあります。
- マルチファイルプログラムでリンク中にこの名前が他の翻訳単位と対話できる場合、名前には外部リンクがあります。
- コンポーネントは、物理設計の最小要素です。 通常、コンポーネントはヘッダーファイル+ソースファイルです。
- パッケージは、コンポーネントのグループです。
- サブシステムはパッケージのグループです。
推奨事項
- 教室の情報は非公開にする必要があります。
これは、オブジェクト指向設計と物理設計の両方に関する重要な推奨事項の1つです。 このように、コンポーネントの複雑さが部分的に隠されているため、アイデアは良いです。
インスタンス変数をプライベートとして宣言するだけでは、物理的な設計にはまったく影響しませんが、コンパイルファイアウォール(PIMPL / Cheshire cat)を使用してもう1つのステップを踏むと、コンパイル時の依存関係の数を減らして、コンパイル自体を高速化できます。
判定: タクシー
- ファイルのスコープ内で外部バインディングを使用してデータを回避する
リンクエラーやリンカのバグを回避しながら、非常に簡単に(「静的」を追加するだけで)実行できます。 たとえば、同じ名前で、相互に変換できるパラメーターを持つ一方で、外部バインディングを持つ2つの関数に問題がありました。 実行時に間違った関数が呼び出されましたが、コンパイル中に警告は発生しませんでした。
ここでの唯一の難点は、ほとんどのC ++コンパイラは、匿名の名前空間の文字を含めて、このメソッドが標準のメソッドとして推奨されており、静的メソッドが公式に望ましくない場合でも、
判定: タクシー
- .hファイルのスコープ内での自由な関数(演算子関数を含まない)を避けます。 .cppファイルでは、外部バインディングのある自由な関数(演算子関数であっても)を避けます。
本質的には、名前の競合を回避し、翻訳単位間の奇妙な相互作用から身を守ることについて話している。 私はいつもそうしています。
判定: タクシー
- .hファイルのスコープ内の列挙、型定義、および定数は避けてください。
上記と同じ考え。 列挙の名前は名前空間ではなく、各列挙の意味はグローバル名前空間で公開されるため、列挙は特に潜行的です。
判定: タクシー
- 接続保護を除き、ヘッダーファイルでプリプロセッサマクロを使用しないでください。
ヘッダーファイル内のマクロは、追跡が非常に困難なバグを引き起こす可能性があります。 古典的な例は、誤って選択された名前を持つマクロで、それを含むコードを誤って変更したり、あまり明白ではないことさえあります:特定のオブジェクトが削除されたときにデバッガーでプログラムがクラッシュしたときに私の同僚がメモリ内の破損した情報に問題があると。 しかし、最も興味深いのは、これはすべての削除操作ではなく、一部の操作でのみ発生したことです。 パッケージAから削除する場合、プログラムは常にクラッシュし、Bから削除する場合も動作し続けるようです。
あなたを苦しめないために、私はあなたに言います:このオブジェクトのいくつかのインスタンス変数をifdef-alします。 パッケージAは、パッケージBからサイズXのオブジェクトを受け取り、サイズX-sizeof(#ifdef-thインスタンス変数)を使用してそれらを削除しようとしたため、削除中に落ちました。
判定: タクシー
- .hファイルのファイルスコープで宣言するのは、クラス、構造体、共用体、および自由演算子関数のみです。 クラス、構造、関連付け、および組み込み関数(インスタンス関数または自由演算子関数)のみを.hファイルのファイルのスコープで定義する必要があります。
このルールは、前のルールに従います。 考え方は、クラス、構造、および関連付けが宣言されると、一種の名前空間を形成し、これが名前の競合を最小限に抑えることです。 演算子関数はファイルスコープで宣言または定義する必要はありませんが、一部の演算子関数はインスタンス関数に変換できないため、選択の余地はありません。
判定: タクシー
- 一意で予測可能な(内部)接続保護を適用します。これにより、各ヘッダーファイルの内容がカバーされます。
実際、ほとんどのプロジェクト(小さなプロジェクトでも)でこれが必要になります。なぜなら、ヘッダーファイルを複数回インクルードすると、コンパイルエラーが発生するからです。 接続セキュリティには、単一の予測可能な名前を付ける必要があることに注意してください。 私はかつて多くの命名規則が適用されたプロジェクトで働いていましたが、多くの混乱がありました。
INC_FILENAME_Hなど、ファイル名から派生したバリエーションを使用するのが好きです。 これを行うために、選択したテキストの接続保護を生成するIDEで小さなマクロを作成しました。
判定: タクシー
- コンポーネント内で宣言された論理エンティティは、このコンポーネントの外部で定義しないでください。
- 私はこの規則に違反する状況に遭遇したことはありませんが、おそらくこれは実際に起こります。 C ++は、おそらく、この方法で問題を解決できる数少ない言語の1つです。 しかし、なぜこれを行うのか想像できません...
評決: 自明
- 各コンポーネントの.cファイルには、コードの最初の重要な行に独自の.hファイルを含める必要があります。
この規則の意味は、不完全なヘッダーファイル(インクルードがないヘッダーファイル)が正常にコンパイルされないようにすることです。 そのようなファイルが最初に含まれている場合、それは間違いなくコンパイルされず、他のヘッダーファイルの後に含まれている場合、ヘッダーにないファイルが含まれている可能性があり、コンパイルは成功します。
このルールは、通常、最初のファイル(例:MSVC stdafx.h)として含まれるプリコンパイル済みヘッダーの使用に反します。 これを行う:最初にプリコンパイル済みヘッダー、次にコンポーネントヘッダー、次にプロジェクトヘッダー、外部ライブラリのヘッダー(例:boost、wxWidgetsなど)、最後にSTL / CRTヘッダーを含めます。 さらに、コンパイラーはそれらをスキップするインテリジェンスを持っているため、プリコンパイル済みヘッダーを構成するファイルを明示的に含めます。必要に応じて、プリコンパイル済みヘッダーなしでコンパイルできます。
判定: タクシー
- 対応する.hファイルで明示的に宣言されていない限り、.cファイルの外部バインディング定義を避けてください。
- ローカル宣言を介して別のコンポーネントの外部バインディングを使用してコンポーネントにアクセスすることは避けてください。 この場合、このコンポーネントの.hファイルを含めます。
これらの推奨事項は相互に関連しています。 名前を正しくインポートすると、変更点が1つだけになり、これらの名前の変更にエラーがあると、コンパイルエラーが発生します。 これは、暗黙のインポート操作中に発生する可能性があり、実際に発生します。
判定: タクシー
- 各グローバル識別子には、独自のパッケージをプレフィックスとして付ける必要があります。
- 各ソースファイルの名前には、対応するパッケージのプレフィックスが必要です。
過度の注意、あなたは思いませんか? ただし、この本は非常に大きなプログラムに関するものであることを忘れないでください。 プロジェクトに数千のファイルがある場合、すべてのファイルの名前を頭に入れられない可能性があり、ガイドラインは非常に役立ちます。
このプラクティスは、次の場合に特に役立ちました。
- 印刷物のコードを分析するとき(または非常に悪いIDE /エディター)
- パケットをフィルタリングするとき、ファイルへのパスを見つけようとするとき
- パケットをフィルタリングするとき、識別子へのパスを見つけようとするとき
判定: タクシー
- パッケージ間の循環依存関係を回避する
周期的な依存関係-これは良くありません、誰もが同意しますか? 大規模なプロジェクトでは、依存関係を管理することが非常に重要です。何かを見逃すと、モノリスが手に入るからです。
パッケージ間で循環依存関係が発生した場合に発生する可能性のある問題を次に示します。
- パッケージを個別にテストすることはできません。これにより、自動化された単体テストが妨げられます。
- コンパイル時間が増加します
- 変更は、プログラムのソースコード全体を通じてリークします。
評決: 信じられないほど操縦
- コンポーネント内の静的構造に割り当てられたすべての動的メモリを解放するメカニズムを提供します。
特に、メモリリークを追跡する方がはるかに簡単なので、このようなメモリを解放する必要があります。 ほとんどのメモリ検証ツールは次のように機能します。さまざまな時点でプログラムのメモリのスナップショットを取得します。 静的構造に割り当てられた未割り当てメモリがある場合、これはリークとして認定されます。
さらに、オブジェクトがメモリを使用している場合、プログラマが明示的にメモリを解放するまでデストラクタは呼び出されません。たとえプログラムの終了時にOSがメモリを解放できるとしてもです。
この問題を解決する最も簡単な方法は、auto_ptrやshared_ptrなどのスマートポインターを使用することです。
判定: タクシー
結論
ご覧のとおり、推奨事項のほとんどは依然として関連しています。 Lakosの本は多くの点で、大規模なC ++プログラミングのアイデアを決定しました。これらの推奨事項は、いくつかの実際のプロジェクトで役に立ちました。