C言語 プログラミング

関数の長さはどの程度がいいか【C言語プログラミング講座(14)】

C言語講座14回目。

C言語講座としていますが、今回はどのような言語でも通じる話だと思っています。

この業界で長くシステム開発にかかわっていると、中にはすごく長いプログラムに出会うこともあります。

今まで出会った中で、一つの関数の最大長が、7000行以上あるものがありました。

再構築・作り直しのプロジェクトでであった過去のプログラムでしたが、どこで変数が宣言されているのかもわからなくなるし、とても追い切れないというもので、移行では大変な思いをしました。

そんなこともあり、今回は、関数の長さについてです。

 

関数の長さはどの程度が良いか

C言語

プログラミングする関数は、1画面におさめよう

1関数は、1機能がいい。さらに最近はあまり聞かなくなりましたが、一つの関数、モジュールは、1画面に収まるようにコーディングするのが、基本だと考えています。

20年くらい前はプログラミング雑誌が月に何冊も発行されていて、既にほとんどが廃刊になっていますが、その中で何度もそんな記述を見かけました。

当時の画面とすると、解像度も低いし、1画面でみれる行数は、せいぜい25行程度です。

25行に収めるのは、場合によっては多少苦しいですが、今でもこの考え方はそう間違っていないと思っています。

冒頭で書いた7000行のプログラムは極端な例だとしても、数百行のプログラムはよく見かけます。

構造化プログラミングは、順接-分岐-繰返し、といって、延々とそのパターンでコーディングされたプログラムで、一関数が500行も800行もあるものは、他人が開発したものだと追うのが大変です。

人間、そんなに多くのことを一度で理解はできないし、理解できないようなものを一生懸命作るから、バグが入り込んでしまうという悪循環に陥ります。

それでも懸命にコメントをつけて、体裁を整えるのが通常のプログラムかも知れなませんが、コメントの下に延々とプログラムが書いてあって、とても追いきれなくなって触らずにそっとしておこう、という話がときどきつぶやかれています。

 

プログラミングで関数を短くする6つの理由

プログラミングで1つの関数を1機能で、短くする理由をまとめてみました。
既に述べた、「わかりやすさ」というものが、全てに優先する理由ですが、短くした場合の利点はそれだけではありません。

(0)全体が、わかりやすくなる(人間の思考に近づくため)
(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;
}

 

処理単位に分割することで、関数を短くすることができます

-C言語, プログラミング

Copyright© SIMPLEONESOFT (BLOG) , 2022 All Rights Reserved.