C言語講座14回目。
C言語講座としていますが、今回はどのような言語でも通じる話だと思っています。
この業界で長くシステム開発にかかわっていると、中にはすごく長いプログラムに出会うこともあります。
今まで出会った中で、一つの関数の最大長が、7000行以上あるものがありました。
再構築・作り直しのプロジェクトでであった過去のプログラムでしたが、どこで変数が宣言されているのかもわからなくなるし、とても追い切れないというもので、移行では大変な思いをしました。
そんなこともあり、今回は、関数の長さについてです。
関数の長さはどの程度が良いか

プログラミングする関数は、1画面におさめよう
1関数は、1機能がいい。さらに最近はあまり聞かなくなりましたが、一つの関数、モジュールは、1画面に収まるようにコーディングするのが、基本だと考えています。
20年くらい前はプログラミング雑誌が月に何冊も発行されていて、既にほとんどが廃刊になっていますが、その中で何度もそんな記述を見かけました。
当時の画面とすると、解像度も低いし、1画面でみれる行数は、せいぜい25行程度です。
25行に収めるのは、場合によっては多少苦しいですが、今でもこの考え方はそう間違っていないと思っています。
冒頭で書いた7000行のプログラムは極端な例だとしても、数百行のプログラムはよく見かけます。
構造化プログラミングは、順接-分岐-繰返し、といって、延々とそのパターンでコーディングされたプログラムで、一関数が500行も800行もあるものは、他人が開発したものだと追うのが大変です。
人間、そんなに多くのことを一度で理解はできないし、理解できないようなものを一生懸命作るから、バグが入り込んでしまうという悪循環に陥ります。
それでも懸命にコメントをつけて、体裁を整えるのが通常のプログラムかも知れなませんが、コメントの下に延々とプログラムが書いてあって、とても追いきれなくなって触らずにそっとしておこう、という話がときどきつぶやかれています。
プログラミングで関数を短くする6つの理由
プログラミングで1つの関数を1機能で、短くする理由をまとめてみました。
既に述べた、「わかりやすさ」というものが、全てに優先する理由ですが、短くした場合の利点はそれだけではありません。
(1)関数単位で考えた場合、単一機能のみに思考を限定できる。
(2)関数単位にバラバラにテストすることが可能である。
(3)開発を進めていくと、先につくった関数の再利用ケースがでてくる可能性がある。
(4)ある機能が、再利用部品にできる可能性がある。
(5)そのままで、再利用できない場合でも、一部の関数(機能)のみのコピー変更で対応できる場合もある。
(6)仕様変更が発生した場合の対応が関数に限定できる。
これだけ理由もあるし、関数に分けて作ると、開発効率は確実にあがります。
欠点は、(3)、(4)を突き詰めすぎるとプログラミングをした人以外が理解するのに、逆に時間がかかってしまう場合があることです。
共通化をするのであれば、きちんとした設計とドキュメントを残すことが大切です。
プログラミング:関数を分割する方法
ここからは、関数を小さくする方法、分割する考え方について書きます。
例えば、とあるロジックの中で、一度長さを計算して、その結果を使って何かの処理する関数があるとします。
仕様書では、例えばこんな記述
ABCロジックでは、以下の処理を行う。
・初期化処理(開始ログの出力...)
・表示文字($以降の文字)列の長さを計算
・$で始まる文字列を表示
まず仕様書にあわせて、そのままコーディングします。
int ABCLogic(char *pAry[])
{
    int iSize, i;
    char* p;
    
    /* いろいろな初期化 */
     ~
    /* $ 以降の文字列の長さを調べる */
    iSize = 0;
    for (i = 0; pAry[i] != NULL; i++) {
		/* $を見つけて、見つかった場合、それ以降の文字列の長さを加算 */
		p = strchr(pAry[i], '$');
		if (p != NULL) {
		    ++p;
		    iSize += strlen(pAry[i]);
		}
    }
    if (iSize <= 0) {
	/* エラー処理とか... */
	return ERROR;
    }
    
    /* 表示処理 */
    ~複雑でとっても長い処理 ~
    return NOERR;
}
この程度ならまだ許せる、と言って気を抜いていると、プログラムはどんどん肥大化し、いつのまにやら、300行、400行、500行と増えていきます。
仕様書は、初期化、長さ取得、と書いてあります。
実際にプログラムにしていくと、初期化で数十行とか、長くなることもよくあります。
一方、人の理解は、初期化するという理解です。
とすれば、プログラムもそのように分割して記述してしまった方が、わかりやすくなります。
内部関数は、通常staticを付けて、モジュール外からは、参照できないようにするのは、大規模開発では実施した方がいい、テクニックです。
/* いろいろな初期化 */
static int ABC_Init(必要な引数)
{
  /* 初期化処理 */
}
/* $ 以降の文字列の長さを調べる */
static int ABC_CalcSize(char* pAry[])
{
    int iSize, i;
    char* p;
    iSize = 0;
    for (i = 0; pAry[i] != NULL; i++) {
	/* $を見つけて、見つかった場合、それ以降の文字列の長さを加算 */
	p = strchr(pAry[i], '$');
	if (p != NULL) {
	    ++p;
	    iSize += strlen(pAry[i]);
	}
    }
}
static int ABC_Disp(/* 必要な引数 */)
{
    /* 表示処理 */
    ~複雑でとっても長い処理 ~
}
/* メインのABCロジック */
int ABCLogic(char *pAry[])
{
    /* いろいろな初期化 */
    if (ABC_Init(/* 必要な引数 */) == ERROR) {
	/* エラー処理とか... */
	return ERROR;
    }
    /* $ 以降の文字列の長さを調べる */
    if ((iSize = ABC_CalcSize(pAry)) <= 0) {
	/* エラー処理とか... */
	return ERROR;
    }
    /* 表示処理 */
    if (ABC_Disp(/* 必要な引数 */) == ERROR) {
	/* エラー処理とか... */
	return ERROR;
    }
    return NOERR;
}
処理単位に分割することで、関数を短くすることができます。