コーディングについて(基礎編)


はじめに

とある会社にてシステムの保守・更改の仕事をしている中、ツール修正の仕事が回ってきました。
そこでプログラム解析を行っていたところ、コメントが全くなかったりネストが非常に深かったりとお世辞にもいいプログラムとは言えませんでした。
綺麗なコーディングを意識することで、生産性やメンテナンス性の向上に繋がると私は考えております。
ここでは、そういった汚いコーディングを避ける事および、可読性の高いコーディングを意識する為とアウトプットの目的のために自分なりに記載していきます。

1. コメントを書こう

各変数や関数、処理に対して内容のコメントを書くようにしましょう。
他人へ引継いだり、修正を行う際にコメントが記載してあればすぐに把握できるからです。

例 (C++)

#include <stdio.h>
#include "stdafx.h"

//画面出力の処理を行う
//val:出力する値
void OutputValue(int val)
{
    printf("%d", val);
}

int main()
{
    int value = 10; //画面に出力する値

    OutputValue(value);
    return 0;
}

関数に対しては引数の説明も記載しましょう。
※処理が明確なものには記載しない。ここではmain関数やprintf関数など

2. 命名規則

これに関しては言語またはプロジェクトや開発チームによって変わってくるが、一貫して言えることは、
命名規則に沿ってコーディングしていくことでどういった処理を行っているか、
どう修正・拡張させていくか容易に判断できるようになる

//--------------------
//    悪い例
//--------------------
#include <stdio.h>
#include "stdafx.h"

//引数名が冗長でどういった値を渡せばいいかわかりづらい
//関数名が変数と見分けがつかない恐れがある。
void output(int d)
{
    printf("%d", d);
}

int main()
{
    //変数がどういった値なのか分かりづらい
    int a = 10;
    int b = 20;
    int c = 30;

    output(a);
    output(b);
    output(c);

    return 0;
}
//--------------------
//    良い例
//--------------------
#include <stdio.h>
#include "stdafx.h"

//関数名を見ただけでどういった処理なのか理解しやすい
//それに伴い引数に何を渡せばいいかわかりやすい
void OutputValue(int value)
{
    printf("%d", value);
}

int main()
{
    int value;

    value = 10;
    output(value);

    value = 20;
    output(value);

    value = 30;
    output(value);

    return 0;
}

命名規則に関してはウィキペディアなどで詳しく載っているのでこちらを参考にしてほしい。
https://ja.wikipedia.org/wiki/命名規則_(プログラミング)#C/C++

3. マジックナンバーは避ける

マジックナンバーはプログラムを書いた本人しかわからないので保守性に欠けてしまう。
プログラムを見た時に何の数値かわかるように、定数を作成しそこに値を入れてプログラム内で使用しましょう。

//--------------------
//    悪い例
//--------------------
int main()
{
    for(int i = 0; i < 10; i++)
    {
        printf("%d回目の出力です。\n", i);
    }
}
//--------------------
//    良い例
//--------------------
#include <string> //文字列を使用するための宣言
using namespace std;

int main()
{
    const int count = 10;
    const string text = "%d回目の出力です。\n";

    for(int i = 0; i < count; i++)
    {
        printf(text, i);
    }
}

4. 深いネストを避け関数化する

コーディングしているとループ文内で条件分岐を行い、さらに条件を付けたりと処理が複雑になることがある。
ネストが深くなることにより可読性が下がりメンテナンス性も著しく低下する。
それを回避するため、処理ごとに関数化し可読性、メンテナンス性の向上に努める。

//--------------------
//    悪い例
//--------------------

#include "stdafx.h"
#include <stdio.h>

#define WIDTH 5    //横の配列の長さ
#define HEIGHT 5   //縦の配列の長さ

int main()
{
    int values[HEIGHT][WIDTH] =
    {
        { 53, 4, 47, 45, 14 },
        { 3, 15, 0, 23, 32 },
        { 13, 5, 10, 13, 2 },
        { 23, 15, 0, 3, 2 },
        { 43, 35, 0, 23, 2 }
    };

        //二次元配列の列数を求める
    int valuesLine = (sizeof(values) / sizeof(int)) / HEIGHT;

        //二次元配列に格納してある値によって出力を変える
    for (int iy = 0; iy < valuesLine; iy++)
    {
        for (int ix = 0; ix < WIDTH; ix++)
        {
            //10以下
            if (0  < values[iy][ix] && values[iy][ix] < 10)
            {
                printf("10以下の値:%d\n", values[iy][ix]);
            }
            //10以上20以下
            if (10 < values[iy][ix] && values[iy][ix] < 20)
            {
                printf("10以上20以下の値:%d\n", values[iy][ix]);
            }
            //20以上30以下
            if (20 < values[iy][ix] && values[iy][ix] < 30)
            {
                printf("20以上30以下の値:%d\n", values[iy][ix]);
            }
            //30以上
            if (30 < values[iy][ix])
            {
                printf("30以上の値:%d\n", values[iy][ix]);
            }
        }
    }
    return 0;
}
//--------------------
//    良い例
//--------------------
#include "stdafx.h"
#include <stdio.h>
#include <string> //文字列を使用するための宣言
using namespace std;

//配列のサイズ
#define WIDTH 5
#define HEIGHT 5

//プロトタイプ宣言
void OutputValue(int value);
string GetTextByValue(int targetValue);

int main()
{
    int values[HEIGHT][WIDTH] =
    {
        { 53, 4, 47, 45, 14 },
        { 3, 15, 0, 23, 32 },
        { 13, 5, 10, 13, 2 },
        { 23, 15, 0, 3, 2 },
        { 43, 35, 0, 23, 2 }
    };

    int valuesLine = (sizeof(values) / sizeof(int)) / HEIGHT;
    printf("配列数は[%d]\n", valuesLine);

    for (int iy = 0; iy < valuesLine; iy++)
    {
        for (int ix = 0; ix < WIDTH; ix++)
        {
            OutputValue(values[iy][ix]);
        }
    }

    return 0;
}

//出力を行う
void OutputValue(int value)
{
    string outputText = GetTextByValue(value);
    printf(outputText.c_str(), value);
}

//値によって出力するテキストを取得する
string GetTextByValue(int targetValue)
{
    string retString;

    //10以下
    if (0  < targetValue && targetValue < 10)
    {
        retString = "10以下の値:%d\n";
    }
    //10以上20以下
    if (10 < targetValue && targetValue < 20)
    {
        retString = "10以上20以下の値:%d\n";
    }
    //20以上30以下
    if (20 < targetValue && targetValue < 30)
    {
        retString = "20以上30以下の値:%d\n";
    }
    //30以上
    if (30 < targetValue)
    {
        retString = "30以上の値:%d\n";
    }
    return retString;
}

注意点
関数化する際は1メソッド1処理を心がけ処理が増える場合はさらに関数化する。

5. 早期リターンを心がける

特に条件分岐やループ文などで処理を行っているとこれ以上処理をする必要がない場合が多々ある。
これは処理時間にも関係してくるので必要のない処理は避ける為早期リターンを意識する。

#include <stdio.h>

//int型配列の要素数を求める
//values:取得したいint型の配列
int GetArrayCount(int values[])
{
    //valuesがnullだった場合、要素数0を返す。※早期リターン
    if(values == null) return 0;

    //返却値用変数
    int retArrayCount;

    //要素数を計算する
    retArrayCount = sizeof(values) / sizeof(int);

    return retArrayCount;
}

int main()
{
    int values[5] = { 10, 4, 7, 9, 21 };

    //valuesの要素数を取得
    int arraySize = GetArrayCount(values);

    //valuesの要素数すべてを出力
    for(int i = 0; i < arraySize; i++)
    {
        printf("%d\n", values[i]);
    }

    return 0;
}

終わりに

何度も繰り返しになるが、コーディングの仕方は言語やプロジェクト、開発チームによって異なるが、
必ず修正やメンテナンスをしなくてはいけなくなる。
なので、他人が見ても理解できるコーディングを意識し生産性の高いプログラムを書いてほしい。