マクロ定数を多すぎるへの対策案です。
マクロ定数の多すぎるソースコードは、問題の温床になることが前回の記事でわかっていただけたでしょうか。それでは、それらへの対策方法を示さなくてはなりません。
defineで設定したマクロ定数を記述しているヘッダファイルの依存性は、doxygenで視覚化すると、その波及範囲の広がりをみることができます。その範囲が思った以上に広がっていることに、自分自身の関わっているソースコードを見て驚きを感じています。
そのプログラムの中のある機能の単体動作ツールを作ろうとして、ある範囲を切り出して、別のmain関数を用意してビルドを試みました。そうすると、たちまち、その依存性のために、別のモジュールもリンクの対象に追加しなくてはならなくなり、そのモジュールの追加は、また別のモジュールを必要として、元になったプログラムのほとんど全てのモジュールを追加するはめになりました。このように、依存性を持ちすぎるモジュールの構成は、その波及範囲の広がりが単体テストをしづらくさせてしまいます。マクロ定数を記述するヘッダファイルは、そのような不都合を生じることがあるのです。
###やってはいけないこと
マクロ定数を使うのではなくて、const属性をつけた変数にすればいいのだろうと早合点して、ヘッダファイルに書いてあるまま、
#define N 100
をconst int N=100;
に置き換えること。そして、そのままinclude すること。
理由:各ソースコードのファイルのスコープの中に、それぞれ異なる実体のconst int型のNという実体を作ってしまう。
これは、解決しようと思っていることではありませんね。
###配列の大きさの指定にはマクロ定数ではなくconst int を使おう。
このように書く。
const int array_size = 10;
int array1[array_size]; // OK!!
###他のモジュールで利用していないマクロは、cppファイルに直書きして参照される範囲を減らそう
症状:あるソースコードmoduleA.cpp の中でしか利用されないマクロ定数なのに、moduleA.hに記述している。
対策(段階1):moduleA.hの中に記述するのではなく、moduleA.cppの中で直接マクロ定義する。
そうすることで、不必要なマクロの影響範囲を狭くすることができます。
対策(段階2):moduleA.cppの中で 無名名前空間で constで定義する。(C言語の範囲だったら static属性を付ける)
そうすれば、moduleAの中でしか影響しないことが確実になります。
###引数で渡せば十分なものは、引数で渡す。
引数で渡せば十分なものは、関数やメソッドの引数で渡す。あるいはクラスのコンストラクタへの引数で渡すという方法がある。そうすれば、その関数やクラスを実装しているソースコードの中に、そのマクロ定数を持ち込むことはなくなります。そうすれば、その関数やクラスを実装しているソースコードに不自然な依存性を持ち込むことがなくなり、単体テストがしやすくなります。
###構造体やクラスにまとめ、そのマクロ定数を参照する場所を1箇所する。
そして、その構造体やクラスをまずは、グローバルスコープで持つ。
次に、その構造体やクラスを本当に必要とするスコープの中に押し込めていく。
そうすることで、そのマクロ定数を記述しているヘッダファイルをinclude する必要はなくなる。
###OS依存性のないライブラリを使う
Boostライブラリを使うことで、ファイル操作やsocketの操作であってもOS依存性のない記述をすることができます。そうすることで#ifdef WIN32 #else #endifなどの記述をしなくればならないことは大幅に減ります。UNCODE以前のライブラリやIPv6以前のライブラリを使うことによるトラブルも避けることができます。
付記:windows用とlinuxとで異なるライブラリを使う必要は、実際的には減ってきています。
そのことを次の記事の中に書いています。
OSごとに#ifdef を書く必要は本当にあるのか
###ヘッダファイルを分割する。
####症状:ヘッダファイルに定義されているマクロ定数の数が多すぎる。
そのため、あるヘッダファイルをinclude すると、欲しくない内容まで含んでしまうようになる。
例:windows用の設定とlinux用の設定だからといって、全てを"setting_win.h", "setting_linux.h" として、全て含めてしまうこと。使う範囲が違うようなものは、ファイルシステムに関するもの、ソケット通信に関係するものを全て1つのヘッダファイルにまとめるようなもの。
####対策案1:同じ#ifdef マクロ名で設定されるものでも、使う範囲が違うヘッダファイルは別のヘッダファイルに分割する。
####結果:
include で持ち込まれるマクロの数が少なくなるので欲しくない内容まで含んでしまうことは減ります。
そのヘッダファイル中のマクロのスコープが狭くなります。
参考記事:
付記:
「マクロ定数が多すぎるへの対策」は、あまりにも常識的なことの積み重ねにすぎません。
でも、それが実現できていないことの背景には次のようなことがありがちです。
- コードの実装についての方針についての部署に「Effective C++(第3版)」のような本を紹介しきれていない。
- そのために、「自分が担当していないコードの設計に口出しするんじゃないよ」という状況になってしまって、その不便な状況が継続してしまっている。
この記事を書いて1年以上たってからの付記:
C++ でもコードを書くことがあるが、C++11以降を想定する開発環境に中にいることもあって、
マクロ定数や、マクロ関数を使うコーディングはほとんど見かけなくなりました。
#include <windows.h>
を必要とするようなライブラリは使うべきではありません。
ネットワーク環境が洗練されたものになる以前のネットワークライブラリも使うべきではありません。
#include <winsock2.h>
Boost C++ Libraries を使うことです。