メンテナンスしづらいC++ソースコードの特徴の1つに、
・マクロ定数が多すぎる。
・マクロ定数を含むヘッダファイルを#includeするファイルが多すぎる。
というのがあると思っている。
そこで、なぜそう思うのかと、どのように書くべきと考えているのかを述べたいと思う。
C++の書き方の本におそらく書いてあるだろうと思うのですが、自分の経験に基づいて書いてみようと思う。
-
#ifdef #else #endifのコードは、ビルドを確かめていないコードになっている。
自分がよく使う組み合わせについてだけは、ビルドを確認しているが、そうでない組み合わせでは、ビルドが通るかどうかさえ、確かめられていないコードになりがち。
この場合、ビルドできないコードは基本この#ifdef #else #endifの範囲に生じる。このブロックの中で#defineを定義していなければ、このコードの範囲でビルドできないが生じる。 -
#ifdef \と#define マクロ定数の組み合わせはさらに危険。
いつの間にかビルドできない部分がさらに広がる。
次の例は、#ifdefによって、マクロ定数を定義している例。
#ifdef WIN32
#define SPAM "SPAM"
#define HAM "HAM"
#define EGG "EGG"
#else
#define SPAM "spam"
#define HAM "ham"
#define EGG "egg"
#endif
このくらいの範囲だったらまだいいが、マクロ定数の定義をやりすぎると、それぞれのブロックの中で、数十個以上のマクロ定数を定義することになる。
このexample.hを#includeするファイルの中では、どちらの条件でも、SPAM,HAM,EGGの全てがマクロ定義されていることを前提とする。
しかし、数十個のマクロ定数を記述しているうちに、2つのブロックの間の整合性がとれなくなってしまっていることを生じやすくなる。
#ifdef WIN32
#include "case_win.h"
#else
#include "case_linux.h"
#endif
このようなファイルでも、case_win.h, case_linux.h の中の記述、とりわけマクロ定数の記述が増えていくと、一方だけ定義してあって、他方には定義がないという状況を生じやすくなっていく。
#if~#elif~#else も記述が増えていくと整合性が保たれにくくなっていく。
#if defined(MODEL_A)
MODEL_A用の記述
#elif defined(MODEL_B)
MODEL_B用の記述
#elif defined(MODEL_C)
MODEL_C用の記述
#elif defined(MODEL_D)
MODEL_D用の記述
#else
その他の場合の記述
#endif
このような各記述の部分が数十個におよぶと、どこかで記述の整合性が壊れやすい。
そして、ビルドしたときに、コンパイルエラーが出る範囲は、このヘッダファイルをinclude しているファイル全体に影響を生じやすくなっていく。
繰り返し言う。試されていない組み合わせでは、ソースコードがビルドできるかどうかは検証されていないということだ。
- #ifdef DEBUG #endif のようなコードも書かれてくる。
- さらに#if 0 #else #endif という気まぐれなコードが書かれやすい。
その中に記述されるマクロ定数の数が増えていくと、一方の組み合わせでは動作するのに、他方の組み合わせではビルドさえ通らないコードが簡単に作れてしまう。いつの間にか、本当にビルドできるのか不明なコードになっていく。
C++を使っている限り、このようなマクロ定数を使わなければならない状況は、とても少なくなっている。
配列の大きさを定義するのに、const intを使える。
const int array_size = 10;
int array1[array_size]; // OK!!
- include されるマクロ定数は、広い範囲でスコープを持ちすぎる。
#define DATA_SIZE 100
などとしたヘッダファイルをincludeすると、DATA_SIZEというマクロ定数がincludeした全てのソースコードの中で影響を受ける。DATA_SIZEなどというマクロ定数名は、容易に書きがちな名前であるので、includeするヘッダファイルの中でぶつかるということがおきやすい。
マクロ定数の他、マクロ関数もそうです。
有名どころでは、#include <windows.h>
とすると、std::max()のmax()さえ文字列の置き換えを受けてしまうというのがある。(そういう意味でもWindows.h は利用したくない。)
このようなバグを生じなくても、マクロ定数、マクロ関数の使用は、広い範囲でスコープを持ちすぎるので、そのため、依存性を減らすことが難しくなる。依存性が多すぎるので、単体テストを書きにくくなっている。
追記:
安易なマクロ定数の追加は、#ifdef の組み合わせによっては、ある場合にはビルドできて、別な場合にはビルドできないという状況を生じやすくなります。
###参考にした記事
C++では定数はヘッダではなくソースファイルに書こう