コーディングルール
コーディングルールは常に議論の対象になるテーマです。
プログラマたる者、見通しがよく、わかりやすく、バグが入りにくいコーディングを常に心がけるべきです。
わかりやすいソースは変更も行いやすく、バグも発生しにくいものです。
というわけで、ここではMaxyソース内で採用しているコーディングルールを説明します。
コーディングスタイルについても少々。
コーディングスタイル
字下げ
- 制御文の後の中括弧は次の行に置く(中括弧は1行使う)
-
中括弧は同じレベルにあったほうが目で追いやすいため。
- 中括弧で囲まれたブロックは1段下げる
-
ブロックの開始と終了を明確にするため。
- 長い文を複数行にまたがって記述する際、2行目以降は1段下げる
-
もちろん文が終わったらインデントを元に戻す。
ただし、そもそも長い文を書かなくても済むように工夫する(一時変数を使うなど)。
- インデントにはタブを使う
-
エディタの設定により好きなインデント幅で表示できるため。
逆に、インデント以外の目的でタブを使ってはならない。
空白
- 二項演算子の左右は空白を入れる
-
読みやすさのため。
特に式がある程度長い場合、演算子が詰まっていると読みにくい。
- 1行に複数の文を入れる必要がある場合、セミコロンの後には空白を入れる
-
文の区切りを明確にするため。
ただし、基本的には1行に文を詰め込まないこと。
- 空白の数は1つだけである必要はない
-
前後の行との読みやすさを考えて適宜調節すること。
return 文
- 値を返すとき、値を括弧で囲まない
-
括弧をつけることで読みやすくなるわけでもないので、無駄なものはつけない。
- 値を返さない関数では、最後に "return" を書く必要はない
-
関数の終わりの中括弧があれば、誰でも関数の終わりがわかるため。
例
double poly(const double x, const double a[], const unsigned int n)
{
double v = 0;
for(unsigned int i = 0; i < n; i++)
{
v = v * x + a[i];
}
return v;
}
変数
命名規則
- 特殊な変数はプレフィックス(接頭語)をつける
-
サフィックス(接尾語)ではなくプレフィックスである理由は、コード補完やソースファイル解析などで変数名をソートした状態で表示した際に、グローバル変数やクラスのメンバ変数の一覧がすぐにわかるため。
プレフィックスをつける変数
変数の種類 | プレフィックス |
グローバル変数 | g_ |
クラスのメンバ変数 | m_ |
- 単語はなるべく省略しない
-
現在の処理系では識別子の長さに制限はほとんどないので、あまり気にしなくてよい。
変な省略形を作るより、省略のないフルスペルで書いたほうがわかりやすい。
- 大文字・小文字を混在させず、単語の間をアンダースコアでつなぐ
-
基本的に、ハンガリアン記法は使わない。
ただし、Windows依存の変数(ハンドル等)はWindowsの流儀に合わせる。
その他
- 変数は宣言時に必ず初期化する
-
初期化忘れによるバグは原因の特定が難しいことが多い(変数の値が実行するたびに変わったり、デバッグ中はバグが発生しなかったりするため)ので、これを予防するため。
- scanf()等に渡す変数でも必ず何かしらの値(0など)に初期化する
- ランダムな値が必要な時は、乱数発生器を使う(他の言語への移植性を考慮)
- 唯一の例外は、1クロックの遅延が致命的な欠陥を生む場合(アルゴリズムの改良やプロファイルによるチューニングを最大限まで行っていることが前提)
- 値を変更する必要のない変数には const 修飾子をつける
-
勘違いや変数名の間違い等によって値を書き換えてしまうバグを予防するため。
関数の引数でも、値を変更しないものは必ず const 修飾子をつけること。
- 「変数がとる可能性のある値域」と「変数が型のとる可能性のある値域」をなるべく一致させる
-
変数の役割上とることのない値があり、それを型によって制限できる場合はその制限を使う。
(例:「文字列の長さ」は負の値をとることがないので、"int" ではなく "unsigned int" を使う)
関数
命名規則
- 名前の最初には動詞を入れる
-
「何をする関数か」を明確にするために、動詞・目的語を入れる。
ただし、標準関数に倣った機能の関数を作る場合は、元の関数名を含めるだけでもよい。
- 単語はなるべく省略しない
-
現在の処理系では識別子の長さに制限はほとんどないので、あまり気にしなくてよい。
変な省略形を作るより、省略のないフルスペルで書いたほうがわかりやすい。
- 大文字・小文字を混在させず、単語の間をアンダースコアでつなぐ
-
ただし、Windows依存の関数(ラッパ関数等)はWindowsの流儀に合わせ、大文字・小文字を混在させた名前にする。
引数
- 構造体やクラスオブジェクト等、サイズの大きい引数は参照渡しを行う
-
無駄なコピーを減らすため。
ポインタでなく参照を使うのは、値渡しのように自然な形でコーディングできるということと、値がNULLかどうかのチェックを省けることが理由。
- 値を変更する必要のない引数には const 修飾子をつける
-
勘違いや変数名の間違い等によって値を書き換えてしまうバグを予防するため。
上記の参照渡しと合わせると、大抵の引数は "const &" で渡すことができる。
ブロックを活用しよう
- if文やfor文などのブロック内でしか使わない一時変数は、ブロック内で宣言する
-
変数の有効範囲を絞ることで、見通しがよくなりバグを減らす効果がある。
ほとんどの場合、最適化によりブロック外で宣言されたときと同じ速度になるので、わかりやすさを優先させる。
- 上記条件に限らず、関数内のさらに局所的な部分でしか使わない一時変数は、ブロックを作ってその内部で宣言する
-
一時変数が必要な範囲である程度まとまった処理をしていることが多いので、処理の区切りが明確になり、関数分割もやりやすくなる。
-
また、処理の区切りを明確にするために、一時変数を使わなくてもまとまった処理をしている部分を積極的にブロックで囲むようにする。
- ブロックを作ったときは、その前に "{block}" というコメントをつける
-
if文やfor文などを間違えて消してしまったのではなく、ブロックを明示的につくったというメッセージを伝える。
例
double hypotenuse(double x, double y)
{
{
if(x < 0) { x = -x; }
if(y < 0) { y = -y; }
if(x < y) { const double t = x; x = y; y = t; }
}
while(y >= 1e-15)
{
double t = y / x;
t *= t;
t /= 4 + t;
x += 2 * x * t;
y *= t;
}
return x;
}
- 「やっていること」ではなく「やりたいこと」を書く
-
「やっていること」は、ソースを見ればわかる。
「やりたいこと」を書けば、ソースとコメントが一致しないときにバグだとわかる。
- 1行1行にコメントをつけるのではなく、ある程度まとまった処理の前にコメントを書く
-
もちろん、必要であれば行にもコメントをつける。
リソース
- リソース(メモリ・ファイルハンドル等)を確保したあとは、必ず解放する
-
解放し忘れても現在のPC環境ではリソース不足に陥ることはあまりないが、ループ内で何度もリソースを確保したり、長時間常駐したりするプログラムの場合は陥ることがあるので、使用後には必ず解放するクセをつけること。
- リソースハンドルはクラスでカプセル化する
-
ハンドル変数の宣言時に確保・破棄前に解放という方法では、関数の途中で抜けるコードを書くと解放を忘れやすい。
特に、リソース確保後に呼び出した関数の内部で例外をスローされると気づかないことが多い。
ハンドルをカプセル化して、コンストラクタで確保・デストラクタで解放することでリソースリークを確実に防げる。
なお、継承されることを意図して設計しているクラスの場合は、デストラクタを仮想関数にするのを忘れずに。
-
また、カプセル化したリソースのオブジェクトをスタック上ではなく new で確保する場合は、スマートポインタを使うこと。
その他
- 条件式に、"||" と "&&" を混在させない
-
or条件とand条件の混在はわかりにくいので、式を分割するか、どちらかの条件のみで済むように条件式を工夫する。
bool is_leap_bad(const unsigned int year)
{
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
bool is_leap_good(const unsigned int year)
{
if(year % 400 == 0) { return true ; }
if(year % 100 == 0) { return false; }
if(year % 4 == 0) { return true ; }
return false;
}
-
ただし、以下の条件を全て満たす場合に限り、複雑な条件でも可とする(ただし、その場合はアルゴリズムの詳細をコメントとして記入すること)。
- 十分に枯れており、保守・改良の必要がない(上記の閏年判定アルゴリズムもその類)
- わかりやすく分割したものに比べ、速度面などで劣る
- その欠点が致命的な品質の欠陥につながる
- ヌルポインタは "0" ではなく、定数の "NULL" を使う
-
定数の 0 ではなく、ヌルポインタであることを明確にするため。
文法的な間違いではないが、0 を使うことには(短く書けるということ以外で)特に利点があるわけではないので、わかりやすさを優先する。
- 条件式にはbool値の式を入れる
-
可読性が低くなるので、
if(fp)
のような書き方はしない。
必ず定数(0 や NULL など)と比較した、if(fp != NULL)
のような条件文を入れること。
-
ただし、"isalpha()" や "IsWindow()" のように「○○の場合は0が、それ以外の場合は0以外の値が返る」という仕様の関数は、
if(isalpha(ch) != 0)
ではなくif(isalpha(ch))
と書く(そのような使い方を想定して設計されているため)。
自分でそのような仕様の関数を作る場合は、わかりやすいように "isXXX" や "canXXX" といった名前にすること。
- if文やfor文などで、後に続く文が1つだけの場合でも中括弧で囲む
-
後の仕様変更等で文が追加される可能性も考慮に入れ、あらかじめ中括弧で囲っておくこと。
参考
Copyright© 2003-2009 しまたろ