コードの書き方によって、その読みやすさには大きな違いが出ます。特別な事情でもない限り、他人が見ても読みやすいコードを書いた方が、保守性の面では大幅に有利になります。複数人数で開発するときはもちろん、1人でやっていても過去に書いたソースのメンテをしたいときなどに差が出ます。
おしながき
コメントの書き方.
保守性 | 処理速度 | メモリ効率 | 難易度 |
入門書などにも書かれており、あまり多く書くことはないと思いますので、要点だけ。
コメントには処理の目的や、コードからは読み取りにくい特別に注意が必要なことを書くようにします。例があった方がわかりやすいと思うので...
- 良い例
TPoint clpos, scpos; // ポップアップメニューの位置を決める clpos.x = StartBitBtn->Left; clpos.y = StartBitBtn->Top + StartBitBtn->Height; // コピー開始ボタンのすぐ下 scpos = CommandPanel->ClientToScreen(clpos); // ポップアップメニューを表示 StartPopup->Popup(scpos.x, scpos.y);
3行目・7行目では、それ以降の処理の局所的な目的を書いています。 5行目ではy座標計算の目的を書いています。またポップアップメニューを出す位置に注意させるため、コメントを記述しています。
- 悪い例
TPoint clpos, scpos; clpos.x = StartBitBtn->Left; // clpos.xにStartBitBtn->Leftを代入 clpos.y = StartBitBtn->Top + StartBitBtn->Height; // clpos.yはStartBitBtnのTop + Height scpos = CommandPanel->ClientToScreen(clpos); StartPopup->Popup(scpos.x, scpos.y);
この例では、コメントで説明されていることは、コードを見れば即理解できます。つまりコメントを読む意味がありません。書くだけ労力の無駄です。
理想はコメントだけ読んでいけば大体の処理の流れが理解出来るように、コメントの内容・分量があればいいと思います。
企業などではコメントのフォーマットが決まっていることもあると思います。個人でやっていても、ある程度自分なりのフォーマットを決めた方が、後になってメンテしたいときに役立ちます。
記述の規則.
どのような規則がよいのかは言語依存すると思いますが、規則を決めた方がよいという点では言語によらず共通。
識別子の命名規則.
保守性 | 処理速度 | メモリ効率 | 難易度 |
変数名・関数名・マクロ名などの命名規則を決めることも、複数人数での開発や後になってメンテしたいときに役立ちます。
ハンガリアン表記が有名になったのはいい例だと思ったのですが、最近はハンガリアン表記は使われない傾向にあるようですね。私もハンガリアン表記は使いません。理由は、変数名から変数の内容が推測できれば、型も推測できて当然だから、内容を推測しやすい変数名を付ければ十分だと考えているからです。また、静的にプログラマが型を決めなければならない言語でしか使えず、実行時に動的に型が変化するような言語では意味がありません。
そのほかには有名なのはマクロ名はすべて大文字で定義するとか。
ハンガリアン表記が有名になる前には、ローカル変数はすべて小文字で、グローバル変数はキャピタライズして、マクロ名などはすべて大文字でなんてのも、一部の人たちの間で使われていたようです。
企業などでは命名規則が決まっているかもしれません。センスが影響する部分だけに、何がいいなんて答えは出しませんが。
インデント.
保守性 | 処理速度 | メモリ効率 | 難易度 |
インデントの規定されているアセンブリ言語は例外ですね。
制御構文によってネスト(入れ子)された部分を、ネストの階層分だけインデントするのも常識となっています。単純なことなのでサンプルソースもなし。
インデントにはタブ文字を使ってください。スペースを何度もたたいてインデントしている人を見たことがありますが、桁が合わないといってマヌケな苦労してました(実話)。
カッコ.
保守性 | 処理速度 | メモリ効率 | 難易度 |
C/C++言語ではブロックをカッコで表します。さらにC/C++言語はフリーフォーマットなので、カッコをどこに書くかは人によって違いがあります。同様のものにはswitch〜case文とか。
- 例1
void func(int mode, int val) { int result; switch (mode) { case MODE_MANUAL: if (val == 0) { dummy1(); } else if (val == check()) { dummy2(); } break; case MODE_AUTO: if (val == 4) dummy1(); break; default: dummy2(); break; } } |
規則
|
- 例2
void func(int mode, int val) { int result; switch (mode) { case MODE_MANUAL: if (val == 0) { dummy1(); } else if (val == check()) { dummy2(); } break; case MODE_AUTO: if (val == 4) { dummy1(); } break; default: dummy2(); } } |
規則
|
考え方や価値観は色々なので何がいいとは言いません。ここでは「こんな書き方もある」という例を示すだけです。この辺りも自分なりの規則を決めた方が見やすくなります。
無限ループ.
保守性 | 処理速度 | メモリ効率 | 難易度 |
C言語のように無限ループを組む手段が複数(for/while/do〜while)ある場合、無限ループをどの様に表現するかにも差が出ます。
- forを使う場合
for ( ;; ) { (処理) }
- whileを使う場合
while ( 1 ) { (処理) }
- do〜whileを使う場合
do { (処理) } while (1);
C言語の場合はforを使うことをお勧めします。
コードを読むのは先頭からでしょうから、ループの最後でしか無限ループであることがわからないdo〜whileは問題あり。
whileの場合、ループの条件式を必ず書く必要があります。条件式は必ず真(≠0)ですが、コンパイラが真であるか否かを確認するコードを生成してしまうと、その分処理速度とメモリ効率が低下します。コンパイラの出来がよければ無駄なコードを生成せず、forの場合と同じコードを生成すると考えられますが。常に最低限のコードが生成されるであろうfor文を使うのが、保守性・性能の両面で理想だと考えられます。
他の言語でも無限ループを組む手段が複数あるなら、同様に最適なものを判断できます。
マクロの書き方.
C/C++言語に限定。アセンブリ言語はアセンブラの仕様次第だし、他の言語では使えなかったり、よく知らなかったりするので。
実体の定義に演算を含む場合.
保守性 | 処理速度 | メモリ効率 | 難易度 |
C/C++言語のマクロは単なる文字列置換として定義されています。そのため、マクロの実体に計算式を書く場合には、演算の優先順位に注意する必要があります。
- 悪い定義
#define CALCVALUE 1 + 2 * 3 #define CALCVARIABLE(a, b) a * b void func(void) { int resultvalue, resultvariable; resultvalue = CALCVALUE * 4; resultvariable = CALCVARIABLE(1 + 2, 3) * 4;
8行目は明らかに (1+2*3)*4 =28 を期待したコードですが、実際のマクロの展開結果は 1+2*3*4 =25となります。9行目は (1+2)*3*4 =36を期待していますが、実際には 1+2*3*4 =25となります。どちらも期待どおりの結果は得られません。
- 良い定義
問題を解決するには、マクロの定義に演算を含む場合は定義全体をカッコでくくります。引数付きマクロの場合は、マクロ定義内のそれぞれの仮引数の参照箇所で、仮引数をカッコでくくります。
#define CALCVALUE (1 + 2 * 3) #define CALCVARIABLE(a, b) ((a) * (b))
このように定義すると展開結果は、8行目では (1+2*3)*4 =28 、9行目では ((1+2)*(3))*4 = 36 となり、どちらも期待どおりの結果を得られます。
- 問題のある定義
引数付きマクロの定義では、次の例のように仮引数の宣言自身もカッコでくくっているのを見かけることがあります。
#define CALCVARIABLE((a), (b)) ((a) * (b))
マクロの展開は単なる文字列置換であることを考えると、仮引数の宣言をカッコでくくっても、展開結果には影響しません。つまりこの書き方は、ここで述べた問題があることとその解決方法をなんとなく知ってはいるものの、なぜカッコでくくるのかを理解しきっていないことの現れです。
記述が冗長なだけだと思ったのですが、C++Builder6で確認したところ、マクロ定義で文法エラーになりました。他のコンパイラではエラーも警告も出ないことがあるので、移植性に問題のある書き方ということになります。
定義が長い場合.
保守性 | 処理速度 | メモリ効率 | 難易度 |
複雑な定義はどうしても長くなってしまいます。#defineは行末までをマクロ定義と解釈するため、定義が長い場合は1行で書くと、横スクロールしないと定義全体を読めなくなったりして、読みにくくなります。
マクロ定義の途中を'\'(円マーク)またはバックスラッシュで区切ると、次の行に定義が続いているものと解釈してくれます。
#define LONGMACRODEFINITION(val1, val2, val3) \ (((MAINFORMWIDTH - EDGEWIDTH - (val1)) / (val2)) - ICONWIDTH - (val3))
この例ではマクロの宣言と実体定義の間に'\'を入れて2行に分割しています。
定義の一部を他のマクロで置き換えても1つ1つの定義を短くすることはできますが、コードを書いた本人以外にとっては、こうして定義された本来必要でないマクロがどこで使われるのかを意識しなくてはならなくなりますので、保守性が低下します。
長い文字列定数の書き方.
保守性 | 処理速度 | メモリ効率 | 難易度 |
上記、定義の長いマクロの書き方と同様、知っていると便利。
長い文字列定数も、複数に分割して記述できます。ただしマクロの場合とは記述方法は異なります。説明しにくいので、サンプルを。
const char *string = "ながいナガイ長い" "ながいナガイ長いながいナガイ長〜いつもりの 文字列定数";
文字列を2つに分けて、それぞれをダブルクォーテーションで囲むだけ。ただし、2つの文字列の間には空白文字(スペース、改行、タブ)以外の文字を入れては行けません。コンマや'+'を書いたりしないように。文法上、意味が違ってしまいます。
3つ以上にも分割できます。
上記、マクロが単なる文字列置換であることと併用すると、以下のような記述も可能です。
#define STRING "文字列の後半" const char *string = "文字列の前半" STRING;
Copyright 2005-2016, yosshie.