コードの保守性を高めるために気を付けるべきこと | C言語

ソフトウェアやそのソースコードは一度完成したら終わりではなく、バージョンアップや不具合修正、一部流用した製品開発などたびたび手を加える機会がやってきます。このようなときに、どれだけ修正を加えやすいかという特性を示すのが保守性です。

ここでは保守性を高めるために、ソースコードレベルで気を付けることをまとめていきます。

ヘッダーファイルはインクルードガードする

このとき「_(アンダースコア1つ)」で始まる名前、または「__(アンダースコア2つ)」を含む名前は付けないでください。これらはC/C++で予約されています。

[不適合例]

#ifndef __HEADER_H__
#define __HEADER_H__

...

#endif

[適合例]

#ifndef HEADER_H
#define HEADER_H

...

#endif
ヘッダーファイルは自己完結していること

[不適合例]

-- main.h --
#ifndef MAIN_H
#define MAIN_H

// <stdint.h>をインクルードしていない
// "main.c"から呼ばれてもエラーは出ないが
// "calc.c"から呼ばれるとエラーが出る
void func(int16_t num);

#endif


-- main.c --
#include <stdint.h>
#include "main.h"
...


-- calc.c --
#include "main.h"
...

[適合例]

-- main.h --
#ifndef MAIN_H
#define MAIN_H

#include <stdint.h>

void func(int16_t num);

#endif
extern宣言はヘッダーファイル内で行う

[不適合例]

-- file1.c --
extern int number;
extern int func1(void);


-- file2.c --
int number;
int func1(void);
int func1(void) {
  ...
}

[適合例]

-- file1.c --
#include "file2.h"


-- file2.c --
int number;
int func1(void) {
  ...
}


-- file2.h --
extern int number;
int func1(void);
ソースファイル内の配置に気を付ける

ソースファイル内の配置は、下記のことに気を付けると良いでしょう。

  1. 抽象度の高い関数から始まり、下に進むに連れて抽象度の低い関数が並ぶようにします。
  2. static関数は下の方、または関連する関数の下に配置します。
コードの一部をコメントアウトしない

未使用のコードは削除してください。 特にバージョンアップ時に変更前のコードをコメントアウトして残すようなことはしてはいけません。バージョン管理ツールに任せてください。

意味のある定数はマクロやenum定数で定義する

修正箇所が1個所にまとまっていれば、修正漏れを防ぐことができます。

[不適合例]

if (count >= 8)

[適合例]

#define MAX_COUNT (8)

if (count >= MAX_COUNT)
関数は短く焦点の絞ったものにする

これを実現するため、下記のことに気を付けると良いでしょう。

  1. 関数の長さを40行以内に収めるように心がけてください。
  2. 制御文の内部が40行を超える場合、関数化することを検討してください。

開始と終了をスクロールなしで見渡すことができなければ、保守性は著しく低下します。

1つの関数内でのみ使用する変数は関数内で宣言する

外に見せる必要のない変数は、隠しておくことで保守性が向上します。

[不適合例]

int g_x = 0;  // funcしかアクセスしない

void func(void) {
  if (g_x != 0) {
    g_x++;
  }
  ...
}

[適合例]

void func(void) {
  static int x = 0;

  if (x != 0) {
    x++;
  }
  ...
}
同じファイルで定義した関数からのみ呼ばれる関数は、static関数とする

外に見せる必要のない関数は、隠しておくことで保守性が向上します。

[不適合例]

-- file.h --
void func1(void);
void func2(void);


-- file.c --
// func1は file.c 内の関数しか呼ばない
void func1(void) {
  ...
}
void func2(void) {
  ...
  func1();
  ...
}

[適合例]

-- file.h --
void func2(void);


-- file.c --
static void func1(void);
static void func1(void) {
  ...
}
void func2(void) {
  ...
  func1();
  ...
}
参照しかしない引数はconst宣言する

内部で書き換えられないことが保証されていると、安心して使用することができます。

[不適合例]

void func(char *arg, int n) {
  int i;

  for (i = 0; i < n; i++) {
    put(*arg++);
  }
}

[適合例]

void func(const char *arg, int n) {
  int i;

  for (i = 0; i < n; i++) {
    put(*arg++);
  }
}
関数の戻り値で成否を表す場合

関数の戻り値で正否を表す場合、下記のことに気を付けると良いでしょう。

  1. 成功を「0」、失敗を「0以外」とします。
  2. 戻り値に負の値を使用しません。
ポインター演算ではなく、配列形式の簡便記法を用いる

これにより人がコードを認識しやすくなり、領域外へのアクセスを引き起こす恐れが低くなります。

[不適合例]

void func(int *p, int size) {
  int i;

  for (i = 0; i < size; i++) {
    *(p + i) = 0;
  }
}

[適合例]

void func(int p[], int size) {
  int i;

  for (i = 0; i < size; i++) {
    p[i] = 0;
  }
}
goto文の使用は限定的とする

goto文を多用すると処理の流れが追いづらくなるため、基本的には使用を避けるべきです。しかしgoto文を使用することで、簡便に記述できることもあります。このため絶対に使うべきではない、ということはりません。goto文を使用する場合には、下記のルールに気を付けてください。

  1. goto文は、関数を異常終了で抜ける場合と多重ループを抜ける場合にのみ使用します。
  2. goto文による飛び先は、goto文より後方に宣言されたラベルとしてください。

[不適合例]

void func1(void) {
  int retry = 0;

LOOP:  // goto文より前方にラベルがある
  ...
  if (ループの継続条件) {
    goto LOOP;
  }
}

[適合例]

void func1(void) {
  open();
  ...
  if (err != 0) {
    goto ERR_RET;
  }
  ...

ERR_RET:
  close();
}
if-else if文は最後にelse節を置く

else節を通らないことや、通っても何もしないことが明らかな場合は、コメントを残しておくと良いでしょう。

[不適合例]

if (var1 == 0) {
  ...
} else if (0 < var1) {
  ...
}

[適合例]

if (var1 == 0) {
  ...
} else if (0 < var1) {
  ...
} else {
  // 何もしない
}
switch文は最後にdefault節を置く

default節を通らないことや、通っても何もしないことが明らかな場合は、コメントを残しておくと良いでしょう。

[不適合例]

switch (var) {
case 0:
  ...
  break;

case 1:
  ...
  break;
}

[適合例]

switch (var) {
case 0:
  ...
  break;

case 1:
  ...
  break;

default:
  // ここには来ない
  break;
}
定数には接尾語を付ける

接尾語はすべて大文字とします。基本的に接尾語がない場合は、整定数はint型、浮動数点定数はdouble型となります。

[不適合例]

unsigned char uc;
unsigned int  ui;
long sl;
unsigned long ul;
long long sll;
unsigned long long ull;
float  f;
double d;
long double ld ;

uc  = uc - 1;
ui  = ui - 1;
sl  = sl - 1;
ul  = ul - 1;
sll = sll – 1;
ull = ull – 1;
f   = f + 1.0;
ld  = ld + 1.0;

// int型が2バイトの場合70000を表せられないため
// 下記は期待した結果にならない
sl = 700 * 100;

[適合例]

unsigned char uc;
unsigned int  ui;
long sl;
unsigned long ul;
long long sll;
unsigned long long ull;
float  f;
double d;
long double ld;

uc  = uc - 1U;
ui  = ui - 1U;
sl  = sl - 1L;
ul  = ul - 1UL;
sll = sll – 1LL;
ull = ull – 1ULL;
f   = f + 1.0F;
d   = d + 1.0;
ld  = ld + 1.0L

sl = 700L * 100L;
目次