プログラマーとして、デザインパターン、言語イディオム、高度な言語機能、有名なライブラリを活用したくなることがよくあります。それは確かに推奨されることです。しかし、飛び込む前に、これらの技術をKISSとYAGNIの原則のレンズを通して見ることが不可欠です。

「KISS」は「Keep It Simple, Stupid(シンプルに保て、愚か者)」の略です。これは、シンプルさが重要な目標であるべきで、不必要な複雑さは避けるべきであると述べる設計原則です。シンプルなソリューションは理解、保守、トラブルシューティングが容易だという考え方です。KISS原則は、エンジニアリング、ソフトウェア開発、ユーザーインターフェース設計、プロジェクト管理など、さまざまな分野で広く適用されています。
YAGNIは「You Ain't Gonna Need It(それは必要にならない)」の略です。 これはソフトウェア開発とアジャイル手法の原則で、特定の問題を解決したり要件を満たしたりするために実際に必要になるまで、コードベースに機能や特徴を追加すべきではないと示唆しています。
YAGNI原則は、不必要な機能を時期尚早に追加すると、いくつかの潜在的な問題につながる可能性があるという考えに基づいています。
OpenCV (Open Source Computer Vision)は、主にリアルタイムコンピュータビジョンを対象としたプログラミング関数のライブラリで、ニジニ・ノヴゴロドのIntelロシア研究センターによって開発されました。このライブラリはクロスプラットフォームで、主にリアルタイム画像処理に焦点を当てています。
OpenCVは、多数の複雑な機能を包含する巨大なプロジェクトです。それにもかかわらず、OpenCVの開発者たちは、コードベースを大幅に理解しやすく保守しやすくした基本原則を受け入れてきました。
OpenCVの設計上の選択をいくつか探ってみましょう:
モジュール性
1 - ライブラリベースのアーキテクチャ
ライブラリベースのアーキテクチャは、提供される機能の再利用と他のプロジェクトへの統合を容易にします。さらに、ライブラリベースのアーキテクチャはクリーンなAPIと関心事の分離を促進し、開発者が全体像の小さな部分に集中できるため、システムを理解しやすくします。
OpenCVはこのアプローチを採用し、多くのライブラリを定義しています。それぞれが特定の責任を持ち、すべてがopencv_coreライブラリを使用しています。

2 - 名前空間によるモジュール化
OpenCVは名前空間を広範に使用してコードベースを効果的に整理しています。opencv_coreプロジェクト内で使用されている名前空間の例は次のとおりです:

OpenCVは「機能ごとの名前空間」アプローチを使用しています。機能ごとの名前空間は、機能セットを反映するように名前空間を使用します。単一の機能に関連するすべての項目(その機能のみ)を1つの名前空間に配置します。これにより、高い凝集度と高いモジュール性を持ち、名前空間間の結合が最小限の名前空間が生まれます。密接に連携する項目は互いに近くに配置されます。
OpenCVの場合、名前空間は3つの主な理由で使用されています:
- ライブラリをモジュール化する。
- 「cv::detail」名前空間のように詳細を隠す。このアプローチは、この名前空間内の型は内部使用のみのため直接使用する必要がないことをライブラリユーザーに伝えたい場合に非常に興味深いものです。C#では「internal」キーワードがこの役割を果たしますが、C++ではライブラリユーザーからpublic型を隠す方法がありません。
- 匿名名前空間:名前のない名前空間。グローバルなstatic変数の作成を回避します。作成した「匿名」名前空間は、それを作成したファイル内でのみアクセス可能です。
データモデルをPOD型として定義する
各プロジェクトにはデータモデルがあり、このモデルはplain old data(POD)型を使って定義できます。POD型は、オブジェクト指向機能を使用せず、フィールド値(インスタンス変数)の受動的なコレクションとしてのみ表現されるデータ構造です。プログラミングでPlain Old Data(POD)型を使用する利点は多数あり、以下が含まれます:
- 効率性:POD型は通常シンプルなメモリレイアウトを持ち、多くの場合、より効率的なメモリ使用とより速いパフォーマンスにつながります。複雑なデータ構造とメンバ関数に伴うオーバーヘッドを回避します。
- 互換性:POD型は低レベルのプログラミング構造やデータ交換フォーマットと互換性があり、外部システムや言語とのインターフェースに適しています。
- 相互運用性:POD型は、システムの異なるモジュールやコンポーネント間、および異なるシステムやプログラミング言語間で容易に受け渡しでき、相互運用性を促進します。
- 使いやすさ:POD型は、基本的なデータ型やそのような型の集約を表すことが多いため、扱いや理解が簡単です。
- パフォーマンス:POD型は、より複雑なデータ型と比較して、実行速度とメモリ使用量の両方でより良いパフォーマンスにつながることが多いです。
- 予測可能性:POD型はシンプルで明確に定義された構造を持つため、その動作は一般により予測可能で、デバッグと最適化が容易になります。
全体として、POD型の使用は、特にパフォーマンス重視またはリソース制約のある環境で、よりシンプルで効率的、かつ保守しやすいコードに貢献できます。
OpenCVコードベースで、メソッドを持たずフィールドのみを含む構造体を検索してみましょう。

このクエリの結果は、OpenCVプロジェクトで定義された型の25%に関係しています。OpenCVは、ほぼすべてのデータモデルをフィールドのみの構造体で定義しています。
多重継承の回避
多重継承は設計を複雑にし、デバッグをより困難にする可能性があるため、多くのC++の専門家はそれを避けることを推奨しています。
OpenCVコードベースで、複数の具象基底クラスから継承するクラスを見つけてみましょう。

テストプロジェクトのわずかなクラスのみが多重継承を使用しています。この概念はOpenCVコードベース全体で回避されています。
複雑な関数の定義を避ける
複雑な関数を検出するための多くのメトリクスが存在します。NBLinesOfCode、パラメータ数、ローカル変数の数は基本的なものです。
複雑な関数を検出するための他の興味深いメトリクスもあります:
- 循環的複雑度は、プロシージャ内で取り得る決定の数に等しい、一般的な手続き型ソフトウェアメトリクスです。
- ネストの深さは、メソッド本体内の最もネストされたスコープの最大深度に関する、メソッドに定義されたメトリクスです。
- 最大ネストループは、関数内のループネストの最大レベルに等しいです。
これらのメトリクスの許容最大値はチームの選択によって異なり、普遍的なしきい値はありません。
OpenCVコードベースで複雑と見なせるメソッドを検索してみましょう。

複雑さを減らすためのリファクタリングの候補はわずか1%です。
結合
疎結合が望ましいのは、アプリケーションのある領域の変更が、アプリケーション全体での変更を少なく済ませるためです。長期的には、アプリケーションの修正や新機能追加の際に、かなりの時間、労力、コストを節約できます。
疎結合は抽象クラスを使用することで実現できます。それらを使用することから得られる3つの主要な利点は次のとおりです:
- 抽象クラスは、再利用を促進する契約を定義する方法を提供します。オブジェクトが抽象クラスを実装する場合、そのオブジェクトは標準に準拠しなければなりません。別のオブジェクトを使用するオブジェクトはコンシューマと呼ばれます。抽象クラスは、オブジェクトとそのコンシューマ間の契約です。
- 抽象クラスは、プログラムを理解しやすくする抽象化のレベルも提供します。抽象クラスにより、開発者は詳細な具体性に深入りせずに、コードがどう振る舞うかの一般的な方法について話し始めることができます。
- 抽象クラスはコンポーネント間の疎結合を強制し、抽象クラスを実装するクラスの実装変更から抽象クラスのコンシューマを保護しやすくします。
OpenCVが定義するすべての抽象クラスを検索してみましょう:

疎結合を強制することが主な目標である場合、抽象クラスを使用する際の一般的な間違いがあり、それらの有用性を台無しにする可能性があります。抽象クラスの代わりに具象クラスを使用することです。この問題をよりよく説明するために、次の例を見てみましょう:
クラスAは、calculate()メソッドを含む抽象クラスIAを実装しています。コンシューマクラスCは次のように実装されています:
public class C
{
….
public:
void calculate()
{
…..
m_a->calculate();
….
}
A* m_a;
};
クラスCは、抽象クラスIAを参照する代わりに、クラスAを参照しています。この場合、疎結合の利点を失います。この実装には2つの大きな欠点があります:
- IAの別の実装を使用することにした場合、クラスCのコードを変更しなければなりません。
- IAに存在しないメソッドがAに追加され、Cがそれらを使用する場合、インターフェース使用の契約上の利点も失います。
C#は、IAのメソッドが具象クラスへの参照からは決して呼び出されず、インターフェースへの参照からのみ呼び出されることを保証するために、言語に 明示的なインターフェース実装 機能を導入しました。この技術は、開発者がインターフェース使用の利点を失うことを防ぐのに非常に有用です。
凝集度
単一責任の原則は、クラスは変更する理由を1つだけ持つべきであると述べています。そのようなクラスは凝集していると言われます。高いLCOM値は一般に凝集度の低いクラスを示します。LCOMにはいくつかのメトリクスがあります。LCOMは[0-1]の範囲の値を取ります。LCOM HS(HSはHenderson-Sellersの略)は[0-2]の範囲の値を取ります。1を超えるLCOM HS値は警告と見なすべきです。LCOMメトリクスの計算方法は次のとおりです:
LCOM = 1 – (sum(MF)/M*F)
LCOM HS = (M – sum(MF)/F)(M-1)
ここで:
- Mはクラスのメソッド数(staticメソッドとインスタンスメソッドの両方をカウントし、コンストラクタ、プロパティのgetter/setter、イベントのadd/removeメソッドも含みます)。
- Fはクラスのインスタンスフィールド数。
- MFは特定のインスタンスフィールドにアクセスするクラスのメソッド数。
- Sum(MF)はクラスのすべてのインスタンスフィールドにわたるMFの合計。
これらの式の背後にある基本的な考え方は次のように述べられます:クラスは、そのすべてのメソッドがすべてのインスタンスフィールドを使用する場合、完全に凝集しています。つまりsum(MF)=M*Fとなり、LCOM = 0かつLCOM HS = 0になります。
1を超えるLCOM HS値は警告と見なすべきです。

凝集していない型はわずかです。
結論
OpenCVのソースコードを見ると、その実装のシンプルさに驚くでしょう。不必要に高度な設計概念や過剰なエンジニアリングはなく、一貫して適用された健全な基本原則だけがあります。
