効率的な開発手法の実践


はじめに

効率的な開発手法の提案」の実践編です。
今回は、1~10の足し算の結果を出力するプログラム(Windowsのコンソールアプリーション)をCで作成します。

複雑なものをシンプルにすることで簡単に問題解決ができるヒントとなります。

開発環境

Visual Studio 2017」をダウンロードしてインストールします。

今回はコマンドラインでコンパイルを行います。
参考: チュートリアル: コマンド ラインでのネイティブ C++ プログラムのコンパイル

最低限のワンパス

まずは最低限の処理を書いたプログラムを作成します。

test.c
#include <stdio.h>
int main()
{
    printf("test");
    return 0;
}

Windowsメニューにある開発者コマンド プロントを開き、プログラムを保存しているフォルダに移動します。

開発者コマンド プロンプトで「cl test.c」を入力してビルドします。

C:\test>cl test.c
Microsoft(R) C/C++ Optimizing Compiler Version 19.15.26730 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

test.c
Microsoft (R) Incremental Linker Version 14.15.26730.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj

「test.exe」ができるので実行した結果が以下になります。

C:\test>test.exe
test

以上が最低限のワンパスで、ここまで動けば環境が原因で動かないというのを排除できます。

処理の追加

処理の追加時は逐一「ビルド+実行」します。
まずは10回ループしてループカウンタを出力します。

test.c
#include <stdio.h>
int main()
{
    int i;
    for (i = 0; i < 10; i++) {
        printf("%d\n", i);
    }
    return 0;
}
ビルドして実行した結果
C:\test>test.exe
0
1
2
3
4
5
6
7
8
9

上記を1~10の足した結果にするためにはそれぞれ+1する必要がありそうなのでループの条件を変更します。

test.c
#include <stdio.h>
int main()
{
    int i;
    for (i = 1; i <= 10; i++) {
        printf("%d\n", i);
    }
    return 0;
}
ビルドして実行した結果
C:\test>test.exe
1
2
3
4
5
6
7
8
9
10

ここまでで、ループ処理は正しく動いている事が確認済みとなります。
あとはループカウンタを足して出力するようにして完成です。

test.c
#include <stdio.h>
int main()
{
    int result;
    int i;
    for (i = 1; i <= 10; i++) {
        result += i;
    }
    printf("result=%d\n", result);
    return 0;
}
ビルドして実行した結果
C:\test>test.exe
result=14771018

上記はバグがあり正しい結果が出ていません。
ビルド時には以下の警告が出ています。

c:\test\test.c(7) : warning C4700: 初期化されていないローカル変数 'result' が使用されます

上記を修正した結果が以下です。

test.c
#include <stdio.h>
int main()
{
    int result = 0; /* ここを修正 */
    int i;
    for (i = 1; i <= 10; i++) {
        result += i;
    }
    printf("result=%d\n", result);
    return 0;
}
ビルドして実行した結果
C:\test>test.exe
result=55

以上で完成です。

まとめ

デバッグや結果の出力処理が入るため1行単位で動かせてはいないですが、大体3行程度でビルド+実行を繰り返しています。
これによりresultの初期化を忘れたとしてもループ処理は正しく動いていることは確認済のため、結果を蓄積するところにバグがあると切り分けやすくなっています。(今回はコンパイラが教えてくれていますが。。。)

おまけ:アンチパターン

例えば一気にプログラムを作ってしまった事を想定していくつかバグを仕込んだプログラムを用意しました。

test.c
int main()
{
    int result;
    int i;
    for (i = 0; i < 10; i++) {
        result = i;
    }
    printf("result=%d\n", result);
    return 0;
}
ビルドした結果
C:\test>cl test.c
Microsoft(R) C/C++ Optimizing Compiler Version 19.15.26730 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

test.c
Microsoft (R) Incremental Linker Version 14.15.26730.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj
test.obj : error LNK2019: 未解決の外部シンボル _printf が関数 _main で参照されました。
test.exe : fatal error LNK1120: 1 件の未解決の外部参照

ビルドした結果_printfが見つからないとエラーが出ています。
ある程度Cに慣れていれば簡単に解決できますが、初心者であれば何が問題かがわかりにくいエラーメッセージとなっています。
例えば「#include <stdio.h>」が無いと言ってくれれば分かりやすいですが、初心者であればmain()の中の処理に問題があるのかと思ってしまいそうです。
同じ状況で「最低限のワンパス」の場合は他に処理が無いので問題箇所を絞りやすいです。

上記を修正したプログラムが以下です。

test.c
#include <stdio.h> /* ここを修正 */
int main()
{
    int result;
    int i;
    for (i = 0; i < 10; i++) {
        result = i;
    }
    printf("result=%d\n", result);
    return 0;
}
ビルドして実行した結果
C:\test>test.exe
result=9

実行すると意図した結果となっていません。
結果の数字が意図した結果より小さいためうまく足せていないと気づけそうです。
上記を修正したプログラムが以下です。

test.c
#include <stdio.h>
int main()
{
    int result;
    int i;
    for (i = 0; i < 10; i++) {
        result += i; /* ここを修正 */
    }
    printf("result=%d\n", result);
    return 0;
}
ビルドして実行した結果
C:\test>test.exe
result=811840

今度は意図した結果よりも大きな数字が出ています。
変数が初期化されていないと気づけそうです。
上記を修正したプログラムが以下です。

test.c
#include <stdio.h>
int main()
{
    int result = 0; /* ここを修正 */
    int i;
    for (i = 0; i < 10; i++) {
        result += i;
    }
    printf("result=%d\n", result);
    return 0;
}
ビルドして実行した結果
C:\test>test.exe
result=45

おしい結果が出ています。
意図した数字が足されていないと気づけそうです。
上記を修正したプログラムが以下です。

test.c
#include <stdio.h>
int main()
{
    int result = 0;
    int i;
    for (i = 1; i <= 10; i++) { /* ここを修正 */
        result += i;
    }
    printf("result=%d\n", result);
    return 0;
}
ビルドして実行した結果
C:\test>test.exe
result=55

以上で完成です。
簡単なプログラムなのに複雑な問題解決をしているのがわかります。

うまく問題に気づくことができればスムーズに問題解決できますが、ひとつ修正しただけでは意図した結果とならないため修正自体を疑ってしまい次の問題解決へスムーズに移れない可能性が高いです。
また複数の問題を同時に対応しない方が良いです。対応した問題が解決しているかが判断し難くなるためです。