ソースの分割は論理構造の分割だ(名前空間の併用の勧め)


 C/C++言語の開発で意識しておきたいことは、「ソースコードを複数のファイルに分割するのは、論理構造の分割だ」ということです。ただ、単にソースコードの関数の数が多いから分割するものではないということです。

ソースの分割が論理構造の分割であることは次の理由でわかります。
・グローバル変数のスコープが、分割されたソースコード単位であること。
 同一のグローバル変数として扱うためには extern 変数名;が必要になる。
・関数や変数にstatic 属性をつけると、他のファイルからは参照できない。

 そのことを考えると、論理構造の分割であることを明示的にするには、各ファイルごとに名前空間を用いるようにすることが考えられます。
名前空間を別にすることで、論理構造の分割であることが明確になりますし、モジュールの動作の責任の所在が明確になります。論理空間の分割であるので、単体テストもその論理空間ごとに行うことになります。
 論理空間の分割であるので、その論理構造に固有のマクロ関数を外部に公開する必要はないはずです。また公開すべき定数とそうでない定数とを明示することになります。

Boostに見る実例

実際
boost::filesystem
という名前空間は
boost/filesystem
というディレクトリ構造と対応しています。

このように論理構造が分離されて設計されているから、
boost::filesystem
だけを使うときは、それに対応したlibを使うだけで済むという利点につながっています。

 名前空間を積極的に用いると、論理構造の分割であることが明確であるので、あたかもクラスを導入し、データメンバーとメソッドを導入したような効果をもたらすことができそうです。無理にオブジェクト指向のプログラミングをする必要はなさそうです。
 ですから、これから作成し公開するヘッダには、関数マクロを含んではいけません。

マクロの問題点:

 ソースコードの分割は論理構造の分割であることを上に述べました。
しかし、プリプロセッサはC/C++言語ではないので、C/C++の論理構造とは無関係にソースコードを改変してしまいます。そのためマクロ定数・マクロ関数にはスコープが存在せず、includeされた全てのソースコードで単語を置き換えてしまいます。
 あるライブラリにあるmax()関数を利用しているソースコード
double c=somelib::max(x,y);
でも、プリプロセッサはmax(x,y)をマクロ関数で置き換えてしまいます。

windows.hのmin/maxマクロ回避策4パターン

 今まで、普通に動いていたソースコードに一箇所windows.hをincludeする箇所があると、そのincludeが伝播して、論理構造をぶち壊してしまうのです。
 C言語のシステムの提供するヘッダファイルは、極めてあぶなっかしいシステムです。標準のヘッダファイルでも、includeする順番を変えるだけでコンパイルが通らなくなることを経験したことがあります。しかもヘッダファイルでincludeされる中身は_DEBUGマクロが定義されているときとそうでないときで異なっていたりします。
 私が、C++の流儀を好み、Cの流儀を避けたがっているのはそのようなところが影響しているのかもしれません。

まとめ:

・ソースの分割は論理の分割。
・ソースごとに名前空間にする方法がある。
・名前空間ごとに単体テストする。
・新規作成し公開するヘッダにマクロ関数を含めない。
・新規作成し公開するヘッダで定数を含めるにはマクロ定数を用いない。

追記:
 上記の考えを利用して、積極的に名前空間を導入してみました。名前空間には、その主旨にしたがった定数・変数・関数をぶち込んでみました。そうすると、その名前空間にある定数・変数・関数のすべき単一の責務の内容が明確になりました。必ずしもクラスに実装する必要はないことがわかりました。
 クラスでなければならない状況は、マルチインスタンスを必要する状況です。そうでなければ、名前空間を作ってそこに閉じ込めるだけで十分です。
 たくさんの種類の関数が多数のモジュールファイルにあって、たくさんのマクロ定数とたくさんのグローバル変数とexternがあるというやっかいな状況から、脱却できそうです。

追記:
 名前空間を導入して、その名前空間に対応する形でソースコードのディレクトリ構成も作りました。その結果、その名前空間内部での改変と、その名前空間の外の改変とが、ディレクトリの中と外という形で明確に区別されるようになりました。複数の人間で開発しているときに、改変がぶつかりにくくなるという利点につながっています。

追記:
 名前空間を作って分離したコードは、他のメンバーにとっても理解がしやすくなります。「全てを正しく理解する」よりも、「必要な範囲をそのつど理解する」方がはるかに容易です。