以前(2〜4年前)に、社内向けにコーディング規約案を書いたことが
あり、その残骸?があったのでコーディングスタイルの例として
出しておきます。
規約といってもラフなものですし、その当時の自分の
スタイル/自衛手段を書き出し、これを叩き台にして他の人の要望
などを反映させたりしました。以下は、
社内事情や自分のスタイルに合わない個所をいくつか
削除しています(自分の習慣でない記述も残っていますが)。
単なる一般的な自衛手段だったり 完全に自分の好みのこともありますが、 一応、なぜ、そのようにするのか、を、書くようにしていたので、 何かの参考になるかもしれません。
あと、スタイルに関しては 『プログラミング作法』(書籍ですが)を みるのがいいでしょう。オンラインでは こちらなんか も結構いいです。
これは、共同でプログラム作業をするためのコーディング規約案です。
プライベートでのコーディングを束縛するものではありません (が、結果的にある程度同じにしておかないと、自分自身が混乱するかもしれません)。
人数が増えプロジェクトの規模がでかくなるにつれ些細なことでも統一されていたほうが、 どれでもいいようなことでも一つに決めてあるほうが、 特にデバッグ時にバグに気づき易くなり労力が少なく済みます。
今現在の社内の人間のスタイルはK&Rスタイルに近いものなので、 それほど混乱は無いと思いますが、ささやかな違いはあるので 戸惑われる可能性はあります。が、 最終的な、プロジェクトの安全性を思うと統一しておいたほうが 絶対的によいと思われますので、よろしくお願いします。
念頭にあるのは、以下のようなことです。
- ソース・ファイルを読みやすくしたい。
- プログラムを理解しやすくしたい。
- 勘違い、誤読をしないようにしたい。
- バグの発生する要素をへらしたい。
このように思うので、このみと思われることでも多少でも、デバッグ等に有利になるならば、有利になるほうを選択しています。
また前提として以下のような考えもあります。
- 規則的なことは、規則的に表現し、例外的なことは、例外とわかるよう例外的な表現にする。
- 人が一度に把握できることには限りがある。順序だてたりグループ化(抽象化)して考えたり把握することになる。
- 読み手にとって紛らわしい/判別しにくい類のことは書き手が意図を表すようにしておく。
- ソースといえど、人が読むものなので、普通の本や書類と同様に、読むということに気を使う。レイアウト等を意識する。
なお、ものによっては、ぎちぎちにルールを守り通すよりも、 その他もろもろの状況/都合を照らし合わせ(プロジェクト全体に)十分な メリット(理由)がある場合の例外を、ルール違反だから駄目だ、というものではないです。
・エディタのTAB設定は4桁に設定しておいてください。
8桁のタブは .c .h では不可とします。
(逆にそれ以外のテキスト等は基本的に 8桁タブです)
・一つのソースファイルのレイアウトは
だいたい以下のようにしてください。
1.ファイル先頭コメント部(ソースファイルの内容説明、担当者名)
2.インクルードファイル記述部
3.型宣言(structなど),マクロ宣言部
グローバル(static)変数宣言部
4.プロトタイプ宣言部
5.コード部(関数定義)
・基本的に、ソース1行につき、C言語の1文です。
必要以上のマルチステートメント(複文)は避けてください。
(可読性を上げるマルチステートメントを否定するわけではないです)。
・プロトタイプ宣言は必ず1つ書いてください。複数書くのは不可とします。
ファイル内のstatic関数は、そのファイル内に、グローバル関数はヘッダにのみ、 書くようにします。(コンパイラがgccの場合、エラーチェックが期待できます)
プロトタイプ宣言での関数の引数名は、省略不可です。
void putObj(int x, int y, int col, int objNo); ○
void putObj(int,int,int,int); ×
・ファイル内のみの変数、関数には必ず static をつけ、範囲を限定しておいてください。 ホントに外部に見せる必要のあるものだけグローバルにし、それ以外のものには、 めんどくさがらずに static をつけてください。 何でもかんでもグローバルにするのは不可です。
・ソースファイル中に、タブや改行コード以外の特殊な文字コードを埋め込むのは, (たとえ'文字'や"文字列中"だとしても)不可とします。 必ず\エスケープ文字で記述してください。たとえば(ここでは^をコントロール文字の表記とする)、
"^H^H^HEND^H^H^H"
というふうに、エディタで ^H を直接入力するのでなく
"\b\b\bEND\b\b\b"
のようにCのほうで用意されている表記を用いてください.
・ '文字'や"文字列"中には、改行コードも直接埋め込むのは不可とします。\nを用いてください。自動生成等で止む得ない場合を除いて、タブに関しても可能な限り\tにしてください。
古いCコンパイラで多用された
"usage>test file(s)
-h help
-o output
"
のような記述は不可とし、必ず "文字列"はソース1行で閉じるようにしてください。上記とどうような文字列を定義するときは
"usage>test file(s)\n"
"-h help\n"
"-o output\n"
のように ANSI-C以降の文字列連結にたよってください。
古いスタイルの""の記述はソースのインデントがくづれますし、 C言語を補助するエディタやツールで対応しきれない類のものなので、やめてください。
・書き終わっただけ、コンパイルチェックが済んだだけでは、まだバグがあるものと思ってください。動作チェックが済んではじめてそのルーチンができたということになります。
動作チェックできていないルーチンはどれだけ記述者に自信があろうと第三者からすればバグ付ルーチンと同じ存在です。
・動作チェックが済んでるルーチンに修正を加えた場合は、それがどんな他愛ない修正でも、もう一度動作チェックをしてください。
手を入れればそこは、未チェックルーチンです。
多量の修正を一気に行わなければならない場合は、変更箇所すべての動作チェックを行う覚悟で修正してください。
・C言語の文法で、動作が不定と定義されているような記述は絶対しないでください。
気づければバグなので修正してください。
たまたま、そのコンパイラで、意図どおりに動いたとしても、ほかの状況/コンパイラで同様な働きをするわけでないので、そのことがわからず間違ったことを覚えないようにしてください(こういうのが、目の前にしても気づかないバグになる)。
・CPUやCの処理系依存要素(エンディアンやcharの符号等)やコンパイラの拡張要素は、とくに上回りでは、かってに用いないでください。
依存要素やコンパイラの拡張要素はモノによっては上記不定要素と同等なものもあるので、そのようなものは修正してもらうことになります(例:gccで(void*)型の値で(char*)型のようにアドレス計算ができる等)
(下回り等で、効率のため、記述者がわかってやる分はもちろん、別。 ただ用いるにしても1クッションかまして依存個所を減らす工夫はすべき)。
・コンパイラのチェックはきつめに設定し、エラーメッセージは警告を含め、基本的にすべて修正してください。
ただし、わけもわからず、警告を消すために、キャストするのはもっと不可です。
とくにポインタの場合。この場合は、逆に警告を残しておき、適度なときに、わかっている人に聞いてください。
・自分の担当分以外のソースは絶対にいじらないでください。
・修正が必要な場合は担当者に説明して、担当者に実作業してもらうようにしてください。
・やむなく担当者以外が手を加える場合は, 修正個所に修正者名(日付,修正内容・理由なども)を記述して、担当者に修正したことを伝え、その内容を確認してもらってください。そして、担当者がその修正を把握し、後に修正者への問い合わせが不用な類(些細なこと)ならば、修正者名とかのコメントは削除してください。いつまでも残ってるとかえって手間になる場合もあります。
・必要なことをかいてください
極力、必要なコメントのみかき、不要/紛らわしいなことは書かないようにしましょう。
コメントが嘘になった場合は、速やかに修正してください。修正するのが手間なら削除してください。
変数名やラベル名等で、名前に意味を持たせれば済むことは、極力そうして、コメントを書かずにすましてみましょう。
プログラム(定義行)をみればわかる情報はコメントにする必要はないです。たとえば
a = a + 1; /* a に1たす */
や
void nanigasi(void)
{
/* 入力: 無し 出力:無し */
は、読むほう、書くほう手間なだけでしょう。
・ファイル全体のコメントをファイル先頭に書いてください
ソースファイル名、主担当者名、ソースの説明を記述してください。
・グローバル関数や変数には必ずその説明をつけてください。
関数の場合、引数や戻り値の説明を記述してください。
・関数固有のコメントは関数内に書いてください。
int nanigasi(int x, int y, int s)
{
/* 座標 x,yに メッセージ s を表示. */
/* 戻り値は0はok. 以外ならエラー */
int a,b;
a = y * 10 + x;
のように、関数内に記述してください。
慣れないと気持ち悪いでしょうが、コメントの有効範囲が確定するので便利です。
関数定義の直前(上)のコメントは、デバッガで表示してるときや grep 等をして ソースを追っかける人の読む範囲から少しはずれるため、以外に見落としがちに なります。
・複数の関数に関するコメントは、関数郡の上に書いてください。
少量ならば, グループ/モジュールわけのつもりで/*--*/などで敷居分けをして、関連範囲を見た目で判別できるようにしてください。
/*----------------------------------------------------------*/
/* xxxに関する... */
vouid xxx_func1(...)
{
:
}
vouid xxx_func2(...)
{
:
}
vouid xxx_nani(...)
{
:
}
/*----------------------------------------------------------*/
量が増えてきたら、ファイル分割をすることになります。
・コメントもプログラムのインデントにあわせてインデントしてください。
よほど特殊なものでない限り、制御の流れを分断するような
コメントをつけないでください。
for (; ;) {
aaa();
bbb();
/***コメント1*****/
ccc();
/***コメント2****/
if(x) {
/***コメント3****/
ddd();
}
}
下記のようにしてください。
for (; ;) {
aaa();
bbb();
/*コメント1*/
ccc();
/*コメント2*/
if(x) {
/*コメント3*/
ddd();
}
}
もちろん、一時的なデバッグで使用し、デバッグ後削除するぶんにはかまいません。
・必要以上に、コメントに飾りをつけないでください。
習慣的になんでもかんでも、飾るのはやめてください。
/*****************/
/*** コメント ***/
/*****************/
や
/*============ */
で、飾ると、そのコメントをめだたせられますが、 あまりにも数が多いと返って読みにくいですし、 全体への説明や注意書きよりも、 ささいなメモがきのほうがめだってしまう場合もあり問題です。
少なくとも、関数中のコメントに飾りをつけるのは、 よほどのことが無い限り避けてください。
一時的に用いて後で削除するものや、ホントに要注意のもの、 ファイルの先頭のコメントやグループ化した部分の先頭等のみにしてください。
・外部にみせるヘッダに記述する構造体での private, publicコメント。
構造体をグローバル(ヘッダ)に置く場合、c++の記述を真似て /*public:*/ /*private:*/ をコメントで、できれば、つけてやってください。たとえば
typedef struct foo_t {
/* public: */
int exchangeFlag;
int x,y;
/* private: */
int dx,dy;
char buf[50];
} foo_t;
のようにして、fooモジュール以外のルーチンがアクセスする変数には public を、fooモジュール内でのみアクセスされ外部にはいじってほしくない変数には private をコメントで付けてあれば、外部からアクセスする側からすれば気にすべきことが減るので便利です。
・インデントにはTABを使用してください。
・桁間の空白の位置については、各自の好みでよしとしておきます。
ただ、見ずらい場合は適度に空白を挿入してくださったほうが読みやすいでしょう.
・中カッコ{ が一つ現れるたびに、必ずインデントを1つ(TAB1個)増やし、}が現れれば、1つ減らしてください。
不必要にインデントするのも不可です。
if (〜) {
printf("a\n");
}
や
if (〜) {
printf("a\n");
}
は不可で、
if (〜) {
printf("a\n");
}
のようにしてください。
また、
soyri1();
soyri2();
soyri3();
soyri4();
のように、理由もなくインデントするのはやめてください。
インデントの深さで、プログラムの流れ/構造/複雑を認識しているので (というか、そのためにインデントしているので)。
もちろん、start(),end()的関数を用いる場合には、 インデントをつけたほうがわかりやすい場合もあるので、 そういうのまで禁止するわけでわないです。
・ if や for , while につける{カッコ は、基本的に行末に置き、 } はif,for,whileと同じ桁にそろえてください。ようは
if (条件) {
内容;
}
にしてください。少なくとも、
if (条件)
{
内容
}
や、GNUスタイル
if (条件)
{
内容
}
は末の}の位置が違い、誤読が発生しやすいのでしないでください。
if (条件)
{
処理
}
else
{
処理
}
も不可とします. すくなくとも
do
{
処理
}
while (条件);
は、誤読の危険もあるので避けてください。
ただし、条件式等が長くなり、複数行に跨る場合は特別なので目立たせるために {を前にもってきたほうがよい場合もあります。
if ( a == 0 && .....
&& ....
&& ....
{
:
:
のような場合.
・最内側での{}
if や、for 等でネストするとき、少なくとも最内側の文が表示上一行で済む場合以外は {} をすべてつけてください。
たとえば
for (i = 0; i < 10; i++)
if (i == 5) {
:
:
}
は不可としときます。
{} を必ず付ける習慣があれば、複数行にわたってインデントしてその終端に } がなければ、タイプミスや編集ミスかもという可能性に気づき安いです。
最内側のみ{}なくてもよいというのは、習慣的なものもありますが、 本文が(表示上)1行だけならば if 文の有効範囲を知る苦労が{}をつけた場合と かわらないか楽なくらい、と、と思えるためです。 {} や () がネストしすぎると読みにくいので、 それを軽減して見た目を軽くしたい、というのもあるでしょう。
念のためですが、else if の場合は、これで一つの構文と見なすモノなので else { if にする必要はないです。というか、しないでください。
考えるのが面度くさければ、すべて制御構文には {} を付けるという方針でもかまいません。
if文は以下のスタイルにしてください。
if (条件) {
内容
} else if (条件) {
内容
} else {
内容
}
"} else "になれない人もいるかもしれませんが、慣れれるし可読性が損なわれることはないです。
・if 〜 else でどこか1文でも {} になる場合は全体にそろえて{}をつけてください。
以下のようなのは不可です。
if (a == 0) {
処理
} else
return;
バランスを取っておいたほうが見栄えがいい、というのもありますが、書き足すときに、{}があるものと 誤認する確立が高いかもしれません。とくに
if (a == 0) {
処理
} else if (a == 1)
b = 10;
else {
return;
}
で、真中の else if の部分に処理を追加したりすると、です。
もちろん単独 else でコンパイルエラーになって気づけるでしょうが追加したのが if 文だと、意図しない if else になってコンパイルできてしまいます。
・switch 文は以下のスタイル.
switch (変数) {
case 値1:
内容
case 値2:
内容
default:
内容
}
caseにインデントを付けないことに抵抗がある人も居られるでしょうが慣れれます。
極力、インデントを深くしたくないというのがあります。見た目のインデントの深さは、プログラム把握で影響がでるので、caseで一個インデントが深くなるメリットよりもデメリットのほうが多いと思います。また、
if (条件) {
内容
} else if (条件) {
内容
} else {
内容
}
の代用の側面も強いので、else if と case のレベルが一致し内容部分のインデントも同じになるほうが構造が変わらず便利です。
一般的に、else if 連用よりも switch caseのほうが読みやすいものなので、単純に置き換わるほうが楽です。
(昔、caseでインデントを付けていたころがありましたが、case のインデントが深くなりすぎるのを避けるために else if に置き換えたことがあり....本末転倒で馬鹿らしくなったので、K&Rスタイルにした経験があります:-)
各case で break,goto,return等で終わらず下に通過する場合は、意図的に通過してることを示してください。
コメントで /* 通過 */ とか /* though */ とか書いてください。
switch (c) {
case 1:
case 2:
処理1
break;
case 3:
処理2
/* 通過 */
case 4:
処理3
break;
default:
break;
}
のような感じです。全く同じ処理をする場合にcaseを列挙する場合は、わざわざ コメントする必要はないです。
・default について
switch の default は、不要でも書くようにしてください。
少なくとも、switch で分岐するとき、case で指定した値以外を switch式が持つならば、必ず付けてください。 それはdefault処理が、たまたま何もしなくてもいい状態だったというだけで、default状態を通るルートが存在するということです。その、ルートが存在することを明示していただきたいわけです。
絶対に通過しない default の場合のみ、defaultをはずすのはよいと思いますが、たとえば
switch (c) {
case 'A':
処理
case 'B':
処理
default:
assart(0); /* ここを通過したらバグなので、デバッグメッセージ表示 */
}
のように、defaultにデバッグ用ルーチンを埋めこむなりしたほうがより安全でしょう(pascal系言語ではデバッグ時用のコンパイルをするとdefaultに相当する個所がない場合に自動的にデバッグルーチンがつきます)
・while,do while, for のインデント
インデントは以下のようにしてください。
for (式1;式2;式3) {
処理
}
while (式) {
処理
}
do {
処理
} while (式);
forやwhileは、処理が1行のみのとき、{}無しでの記述をありということにしますが、do while に関しては、必ず {} を付けてください。こうしておけば、必ず } while (式) になるので、 whileループかdo whileループか、見間違えずにすみます。
・処理なしループを記述するとき
for や while で本文のないループを書くとき、
for (〜); ×
while (〜) ; ×
のように、同一行の行末に ; を置かないでください。
慣れてしまえばなんとも無いことでしょうが、; の使い方になれて無い初心者の方がやってしまうバグのパターンの一つなので、このような表現があれば即バグと疑うものにしたいので、さけて、以下のように
while (〜)
;
か、{} を用いて
while (〜) {}
のようにして、;のうち間違いでないということを表現してください。
・関数定義の {
・関数定義の行の前には必ず空行を置き、{ は行末でなく、
次の行の行頭に置いてください。
void fnc1(void) {
内容
}
は不可で
void fnc1(void)
{
内容
}
にしてください。
古くからの習慣ということもありますが、行頭のほうをみて関数と認識しやすく、空行を必ずおくのは定義行が他の行と接しないことで関数定義がみやすく、かつ { のおかげで本文との密着度が多少あるでしょう。(C++に移行した場合は、そのときはそのとき:-)
・関数間の空行
各関数の把握しやすさのため、関数と関数の間は2,3行あけてください。
同様に、グローバル変数と関数定義の間も2,3行開けてください。
static int nanigasi;
static int nanigasi2;
void fnc1(void)
{
処理
}
void fnc2(void)
{
処理
}
は駄目で、
static int nanigasi;
static int nanigasi2;
void fnc1(void)
{
処理
}
void fnc2(void)
{
処理
}
のように行を開けてください。
・引数のない関数宣言
引数のない関数宣言は必ず (void) にしてください。
C言語では () では、(...)と同じでどんな引数でも持てる関数となってしまいます。つまり引数チェックがなくなるということです(C++では()いいけど、あくまで Cでの作業なので)
void fnc(void) ○
void fnc() ×
関数の配置は、ボトム・アップでなくトップ・ダウンににしてください。
念のためにいうとトップ・ダウンは
void fnc1(void);
void fnc2(void);
void fnc3(void);
void fnc1(void)
{
fnc2();
}
void fnc2(void)
{
fnc3();
}
void fnc3(void)
{
処理
}
のように、ソースの上から順に、プログラムの流れを追える書き方。
ボトムアップは
void fnc3(void)
{
処理
}
void fnc2(void)
{
fnc3();
}
void fnc1(void)
{
fnc2();
}
のように、使われるより先に必ず関数定義が書かれるような配置にしたものです。一人で組み、比較的、小規模でファイル本数の少ないプログラムの場合、ボトム・アップのほうが(C言語の仕様上)記述の手間や名前のスコープの兼ね合いで、作業効率や安全度がよい場合もあるのですが、大規模な多人数作業では、上から流れを追えるトップ・ダウンのほうが他の人にとって読みやすく、結果的によいです。
goto文は、基本的に使用しないでください。
ただし、多重ループ脱出に関してはどちらかといえば使用を推奨します。また、エラー処理脱出の場合も有効でしょう。
for (i = 0; i < 10; i++) {
for (j = 0; j < 5; j++) {
if (buf[i][j] < 0)
goto EXIT_LOOP;
}
}
EXIT_LOOP:
や
int fnc1(cmn_t *t)
{
if (t->flag < 0)
goto ERR;
処理
if (t->x < 0 || t->x >= X_MAX)
goto ERR;
処理
return 0; /* 正常終了 */
ERR:
printf("エラーです\n");
return -1;
}
のような場合(ERRに関しては、処理規模や複雑さによっては関数化のほうがいい場合も多いです)。
下手にフラグ変数を用意して同様の処理をするよりも直接的で、かつgotoラベル名は関数内でのみ有効で関数外を気にしなくていい、ためです。
前方(ソースの下)へ、{}ブロックの外へ飛び出す goto のみ可としておきます。
ループを形成するような後方(ソースの上)へのgotoや、{}ブロックの中に飛び込むgotoは不可とします。
※エラーリトライのための後方goto(ループ状態)は読みやすいこともある(と感じる人もいる)のですが、これは許容判定に個人差がありすぎシロクロの決着がつきにくいので不可とします。ので、見た目で判断できるループ/エラー脱出のみ可です。
(もちろん、下回り等で、速度アップのため生成コードを気にしながら創る個所を否定するものではないです)
ただそうはいってもgotoの数が多すぎると、変数以上に分けわからなくなるので、ほどほどに、ですが。
gotoラベルが増えすぎてたら関数分けをしたほがよい場合が多いです。
・gotoラベルは大文字で記述してください。
・インデントは、ラベルをつけた文のインデントを一つ(または0.5個分)戻した位置にしてください。
gotoが横断するブロックがどの範囲か見やすいようにする意味もあるので、なんでもかんでも行頭にgotoラベルを記述するのは不可とします。
(0.5個つまり空白2個もありとするのは、私の習慣ですが、これは行頭に空白なくおかれるgotoラベルによって、関数が見た目に分断されるのを軽減するためです(なので、同様に#行も0.5個ずらしたりしてます)。
タブ一個でないので多少記述がめんどくさいかもしれませんが、gotoラベルの出現頻度からすれば手間よりもメリットが多い、と感じています。
式を1行に書ききれず複数行にわけるばあい、
a = 10 * b + c
+ d / 2;
や
if (b == a
&& c == a)
のように、普通は行頭にくることのない演算子を頭にもってきて行をわけたことを目立たせてください。
普通、読み手は、行末より行頭のほうを、より注目していますし、文頭にくることのない記号が行頭にあることで、その行が行分割したものであることが視認しやすくなります。
#defineマクロ名は、基本的には大文字のみで名づけてください。
副作用対策された全く関数と置き換え可能なマクロは関数と同じにしてよいでしょうが、副作用のあるマクロは、大文字のみにして、注意を促してください。
#define で数値を定義する時は必ずカッコで括るようにしてください。
例) #define LBL1 5 ・・・・・NG
#define LBL1 (5) ・・・・・Good
数字一つだけならば問題ですが、式を書くときに必要なカッコを忘れないように習慣付けるため、です。たとえば
#define LBL1 1+4
#define LBL2(a) a+2
のようにしてしまい、LBL1*LBL2 のような使いかたをするとバグになるから、です。ちゃんと
#define LBL1 (1+4)
#define LBL2(a) ((a)+2)
のように忘れず書けるように。
ただし、定数の定義のばあいは enum {} を用いたほうが安全でしょう。
とくに、一関数内でのみ用いる定数を定義する場合は#define でなく、関数内で enum {} を用いたほうがいいです(#defineはファイル内すべてに影響しますが、enumは関数内で宣言すれば関数内のみ有効だから、です)
enum {
LBL1 = 1+4,
LBL2 = 10
};
のようにして定義したほうが確実です。
マクロ名は思わぬ置換が発生してしまう場合があるので名前の衝突には気を付ける必要があります。極力、定数ラベルを定義するときは、enumを使うように したほうがよいでしょう。
なお enum に限らないですが、名前の数が膨大に増えると開発環境(デバッガやコンパイラ)に要求するメモリが多くなり、コンパイルできなかったり(swap等で)実用的なデバッグができなかったりする場合があります。
もし大量の定数名を使っていて影響が大の場合は意図的にenumでなく#defineを持ちいて回避する場合もあります(コンパイルと独立して#defineラベルが前処理系で処理される場合や、#defineラベルがデバッグ情報にならない場合に限りますが)。
#if のインデントや記述は、なかなか、難しい要素があり、一概にはいえない面も多いのですが、すくなくとも、
・たとえ #if 0 であろうと、その範囲に記述するものは、Cの文法の成立するものとし、コメント等を直接かいてはいけない(コメントは、/* */か、//を必ず用いること)。
#if 0
これは駄目
#endif
・Cの {} のブロック(レベル)を分断したり、個数を違える書き方をしない。
#if はただでさえ見た目のインデントを崩すものなので、2箇所になるよりも1箇所ですむならそのほうが見やすいことも多いですし、エディタや関数フロー作成ツール等を用いる場合#ifの扱いが不充分なことがおおく { } のペアをちゃんと認識できずおかしな結果になることがあるので機械的に判断つく状態にしておいたほうがよいでしょう。
たとえば
#if 0
if (flg == 0) {
#endif
処理
/* } */
は絶対に駄目で、
#if 0
if (flg == 0) {
#endif
処理
#if 0
}
#endif
も駄目とは言いきらないが、
#if 0
if (flg == 0)
#endif
{
処理
}
のほうがよいです。
また
#ifdef XXX
if (flg == 0) {
処理
} else {
#endif
処理
#ifdef XXX
}
#endif
も
#ifdef XXX
if (flg == 0) {
処理1
} else
#endif
{
処理2
}
のほうがいいでしょうし、場合によっては
#ifdef XXX
if (flg == 0) {
処理1
} else {
処理2
}
#else
処理2
#endif
のほうがよい場合もあります(でない場合もあります)
・関数内や構造体内など {} ブロック内では、#ifで囲むべき内容に対して一つ(または0.5)だけインデントを戻した位置に記述してください。goto ラベルと同じ法則です。
さらに、その範囲で多重に#if する場合は、空白1個づつでいいからインデントしてください。
場合によっては&&演算子で一つにまとめるのもよいでしょう。
たとえば
#ifdef XXX
#ifdef YYY
処理
#endif
#endif
のようにしたり、あるいは
#if defined(XXX) && defined(YYY)
処理
#endif
のようにしてください。
・ヘッダやソースでの変数や型、マクロの定義部などで、#if〜#elseや#ifのネストをする場合は、難しいですが、インデントをつけるよう、工夫してみてください。
#ifdef WIN32
#define BFSZ (0x100000)
#else
#define BFSZ (0x7FFF)
#endif
のようにしてください。
・ルーチンのコメントアウト化で、とくに、もう復活させる予定のないルーチンとか(だけど参考的に残しておきたいものとか)に関しては
#if 0
// funcX(t);
// abcd(a,b);
#endif
のような感じで、#if 0と //を用いてください。
#if 0してるので、//が使えないコンパイラでも問題ありませんし、grep等を行ったときに意図せずマッチしてしまったとしても、//がついてることで無視できるかどうかの判断がすぐつき安いです。
・名前(変数名や関数名、ラベル名など)は、とくにグローバルなものや同一関数内でも広い範囲で用いられるものには、意味のある名前をつけてください。決して、読み手が他の意味に受け取り動作や機能を勘違いするような名前はつけないでください。
創りこんでいくうちに機能や意味が代わってきて(関数や変数の仕様が変更になって)、読み手に勘違いされるようならば現状に会うように変名してください。削除するわけにいかず嘘化コメントよりもタチが悪い存在になるので。
ただ、自分担当外のソースに及ぶ場合は、ケースバイケースで、時期や修正規模により修正できないでしょう(残り作業量/時間との兼ね合いで、他人が誤読/誤使用する可能性よりも修正時のエンバグの危険性が大きい場合...当然変更箇所の動作チェックが発生するので、あとになればなるほど不可能になるでしょう)
なのでグローバル名の命名には、最初から、かなり気をつけてください。スペルミス等しないように念を入れてください。
たとえテストルーチンやデバッグ用だとしても。軽い気持ちのテストルーチンや一時使用の変数のつもりが、いつのまにか本採用され修正できない状況も多々あるので。
・構造体のメンバ名も、名前スコープ(有効範囲)はグローバルでは有りませんが、読み手にとってはグローバルな存在なので、構造体をグローバル(ヘッダ)に置く場合は必ず意味のある名前をつけてください。省略しすぎで意味不明になるのはさけてください。
・Cでの標準関数(やディフェクトスタンダードなもの)に似た名前を付けるときは、同様の機能/引数になるようにしてください。
・余りに短すぎる 3文字位以下の変数名は、たとえ static でもグローバル変数名につけないでください。とくにローカル変数でよく使うような名前は...
たとえば、あるファイルでグローバルに
int d;
とあり、別のファイルで
void fnc1(void)
{
int d;
処理
d = ...
処理
}
なプログラムを書いたつもりで
void fnc1(void)
{
処理
d = ...
処理
}
とint d; を忘れてしまったとしても、コンパイルでき、意図せず動いてしまいます。
(しかもdがほかの関数でも使われているだろうから不安定な挙動を示すことになることに) 警告メッセージで気づけますが、きづけずバグになったことは社内でも1度では済まないので。
・英語またはローマ字表記。
なるべく単語の区切りは 大文字小文字分けか _ を用いてください。
人が見る区切りごとの単語が判別できるならば英語とローマ字が混ざっても可とします。
大文字のみ小文字のみで、単語を連結するときは、誰もが容易に判別できる場合のみにしてください。区切りを勘違いされ別の単語に読み間違えられそうならば、区切りが見える表現にしてください。
大文字のみで連結した名前は字体の見栄え上、区切りを認識するのはかなり困難なのでかなり避けてください。
ボキャブラリの個人差はどうしてもあるので、一般的でない単語を組み合わせるときとか他の人に勘違いされてしまった場合は区切りをつけてやってください。
たとえば
usagi_check ○
usagiCheck ○
UsagiCheck ○
USAGI_CHECK ○
USAGICHECK ×
というところでしょか。この場合の小文字のみの usagicheckは 〇 でしょうが構成単語次第です。
ローマ字表現に関しては、たとえば "負け"のつもりで make と書いて"メイク"と勘違いされるような、一般的な英単語に化けてしまうようなローマ字表現はさけてください(せめて makeruのようにするのがよいでしょう)。
また日本語の場合、複合語の場合は1単語として扱い、構成単語で区切るようなことはしないようにしてください。たとえば, 竹とんぼ、は、
taketonbo
にし、
TakeTonbo や take_tonbo
のようにはしないでください。この例の場合takeが英単語に化けるので、可能ならばtaketonbomoも避けて別の名前のほうがいいかもしれません。
・外来語のローマ字表記は不可です。
puroguramu や kyarakuta は×
(普通は、こんなことワザワザ言う必要もないでしょうが、 以前、本当にこのような名前付けをする人物が社内にいたことがあるので)。
・単語の省略
単語の省略形はありとします。
母音抜きや判別可能範囲での後ろ省略など。
ただし、ローマ字表記の省略は不可です。
fmt や val, fp など慣用句的な/一般的な省略記法があるものは、それを用いるのがよいでしょう。
・紛らわしい文字の使い方は不可です。特に「オー」と「ゼロ」、 「アイ」と「エル」と「イチ」には注意してください。
例)LBL_l0 (エル・ゼロ)
LBL_IO (アイ・オー)
・変数はなるべく用途ごとに、宣言してみてください。たとえ型が同じだとしても、用途が切り替わるなら、別にしてみてください。
造り足していくうちに、各用途の有効範囲が広がったりしてバグの原因になりますし、用途ごとに変数を用意してると変数が多くなりますが、一つのルーチンで複数のことをやりすぎているのに、気づき安くなります。気づけば、関数わけをしたらよい、ということになりますし、変数の使い回しが少なければ関数分け作業自体もはかどります。
関数内の場合、単純変数の効率は今のコンパイラなら、かなりコンパイラに任せられます。それに、使い回しによる変数のスコープが広がることよりも、変数が多くとも用途ごとの生存期間がはっきりして返ってオプティマイズしやすいのではないか?との期待も持てます。
ANSI-Cの規約やC++の規約にも触れないようにしてください。また ANSI/ISO で定義されてなくてもそれに準拠する扱いのものや標準的習慣となっているものはそれにあわせてください。
・習慣
基本的に
マクロや定数ラベルは 大文字のみ。
変数、関数は小文字のみか、大文字小文字交じり。
で、大文字のみ変数や関数は用いないでください。
大文字のみの関数なようなものが現れたら、それはマクロだろうで、普通の関数でないことに気づける状態にしておきたいわけです。
型名は微妙です。#defineの延長的でもあり実体のないものなので大文字のみにする習慣や、int16_t のように型名には _t をつけて見た目にわかるようにする習慣等ありようで、、、少なくとも、_t とつく名前は型名以外には用いないようにしたほうがいいでしょう。
・ _ で始まる名前を使わないでください。
処理系(コンパイラ),ライブラリベンダが定義するときに使うこと
になっています。
_nanigasi ×
・ __ を名前に含めない( _ が二つの場合)
これは C++ の処理系が生成する名前として予約されています。
nanigasi__f10 ×
・c++の予約語は使わない
catch class delete friend inline new
operator private protected public
template this throw try virtual overload
namespace export explicit using
dynamic_cast static_cast const_cast reinterpret_cast
true false bool asm typeid typename
wchar_t and and_eq bitand bitor compl not not_eq
or or_eq xor xor_eq
もかってに使わないでください(Cのライブラリで提供されているものもあります)
あと、C++の標準クラス・ライブラリ名もさけたほうが無難かもしれません。 (std, Stringなど...)
・比較的有名な MSC(VC)やgccの拡張予約語も避けたほうが無難でしょう。
far,near,huge,typeof
などなど。
構造体や共有体を定義する場合は
typedef struct ST_NAME {
メンバ定義
} ST_NAME;
のように なるべく typedef を併用して、かつ、struct/unionのタグ名とtypedef名を同じにしてください。
structの後にある名前は型名でなくタグ名と呼ばれるもので普通の名前とは別に管理されており、
struct ST_NAME
のようにペアでのみ使用できるものなのですが、毎度structを記述する不便さや見づらさを避けるためにtypedefで別の型名をわりあてている訳です。
もちろん、structタグ名とtypedef名を違えるのも可能ですし、タグ名を指定せず
typedef struct {
メンバ定義
} ST_NAME;
のようにするのも可能ではありますが、
(struct ST_NAME)型 と (ST_NAME)型が、別の型
ということに気づけず、なぜ警告やエラーがでるか、わからずにいる場合があります。また
struct ST_NAME { typedef struct {
int x,y; int x,y;
}; } ST_NAME;
の2つ定義しても, (struct ST_NAME)型 と (ST_NAME)型は違う型で、また
struct ST_NAME {
int x,y;
};
typedef struct {
int a,b,c;
} ST_NAME;
と違うメンバ名がかかれていても別の型ゆえ、ひとつのソース中にあってもエラーになりません
(並べて書くことはなくても別々のヘッダに記述されていたら...)。
なので、なるべく両方同じものを設定して矛盾が発生しないようにしてください。
(とくにC++では構造体関係の仕様が変わっており、C++では structの直後の名前が型名となるので...)
あとtypedef せず structのみで構造体を宣言するのは場合によりありでしょうが少なくとも、
struct ST_NAME {
メンバ定義
} stVar;
のようにして、型宣言と、変数宣言を同時に行う文を記述するのは不可としておきます。
また、
struct {int x,y;} *fnc1(struct {int r,t;} *a);
のように、引数や戻り値に、構造体宣言を使うのは不可とします(これは、Cでは可能ですが C++ではエラーになります)。
・なるべくキャストしないですましてください。
本来キャスト指定は特殊な存在です。基本的にキャストをしないですむよう、変数等の型宣言をしてください。
キャストが多くなりすぎるような型に設定し間違えた場合、さらにキャストを続けるのでなく、極力、宣言を修正してください。
宣言の修正をかってにできない立場なら、担当者と交渉してください。めんどくさがらない:-)
・やみくもにキャストしないでください。
コンパイラのエラーメッセージは警告を含め、基本的にすべて修正するコンパイラのチェックはきつめにしておいてください。
ただし、わけもわからず、警告を消すために、キャストするのはもっと不可です。
とくにポインタの場合。 この場合は、逆に警告を残しておき、適度なときに、わかっている人に聞いてください。
・ (void*)を乱用しないでください
C++での事情や、型をより意識するため、ポインタ代入時のキャストで必要以上に(void*)を使うのは禁止します。 ちゃんと代入先の型にキャストしてください。(もちろんvoid*にすべきものは void*にする)
ex) cmn_t *a6; Uint8 *a0; のとき
a6 = (void*)a0;
のような代入は駄目とし、
a6 = (cmn_t*)a0;
にする。
・char,short,float
普通に C言語仕様のコンパイラを使う限り、 char や short, float 宣言は、よほどのことがなければ、ポインタや配列、構造体メンバ以外では使わないようにしてください。
これらは、ファイル等外部とのやり取りやメモリ容量を気にしなければいけないような場合に用いる存在なので...
コンパイラの生成コードを把握せず使用すると、C言語の型変換ルールのため結果的に遅くなったりプログラムサイズが大きくなったりします。 また、型変換ルールを把握し意識していない場合、思わぬ落とし穴にはまることがあるので、暗黙の型変換が少なくなるよう、不都合がなければ 整数は int, 実数は double を用いてください。
たとえば
void fnc1(short x, short y)
{
char a, b;
float ff;
処理
}
のようには書かず、
void fnc1(int x, int y)
{
int a, b;
double ff;
処理
}
のようにしてください。
long も、int とビット幅が同じ限り、int を用いたほうがいいでしょう。
(たとえば lsi-cを用いる場合, C言語の仕様から外れるけど、引数にchar等を用いればより効率のよいコードを生成する、というコンパイラもあるので、使用するコンパイラの仕様にあわせて、方針を決定することになるでしょう)
・|,&,^, << , >> などの注意
C言語では、&,|,^ , <<, >> の優先順位が四則演算や比較演算に対し紛らわしい状態にあります。とくにアセンブラの感覚/優先順位で用いると勘違いされる場合が多いです。
<<,>> が+,-,より優先順位が下だったり、&,|,^ が >,>=,<,<=,==,!=より下だったり、さらに&,|,^間でも優先順位がちがったり、と。
このようなものを用いるときは明示的に()をつけて対処してください。
たとえば、
a = c >> 8 + 1;
や
if (a & b == 0)
と書かれてあった場合、文法的には
a = c >> (8 + 1);
や
if (a & (b == 0))
なのですが、記述者は
a = (c >> 8) + 1;
や
if ((a & b) == 0)
のつもりだった可能性があります。
たとえ、わかって前者のつもりで書いたとしても, 他の人が見た場合は、記述者に確認が必要になりますので、意図を明確にするためにも () をつけてください。
(四則演算以外には優先順位を明示するカッコを付ける...?... ただ、()が増えすぎると逆に、読みづらくわかりにくくもなる。そういうときは、式を分ける... )
・副作用のある式は極力さけてください。
一個の(ポインタの)オート・インクリメント/デクリメント程度なら大丈夫でしょうが、複雑になる場合は。
a = *s++; ○
のようなものなら可でしょうが、
a = abx * (s &= 4) - 2;
if ((a += 2) > 10)
のようになってくると、見落としやすいので式を分けてください。 ( forやwhile 中の場合は場合によっては可、でしょうか)
・三項演算子
単純な一条件により変数の値が変わるような場合で、 その式が独立した一行になる場合、三項演算子を積極的に使用してみてください。
※三項演算子を使用した方が良い例)
if(d1==0) {
d0 = 5 ;
}else{ → d0 = (d1 == 0) ? 5 : 6;
d0 = 6 ;
}
・ただし、条件が複数存在する場合や、三項演算子が多段になりすぎる場合、 他の式の一部として使われる場合(可読性が悪くなる場合)、 三項演算子は使わないようにしてみてください。
・extern 宣言は、.cの中ではしないでください。
外部参照名は、必ずヘッダに置くようにしてください。
(もちろん、下回りやただ一箇所でのみデータの集中管理部分等でのみ参照されるデータテーブル名などはそのソース中でのみexternされてもよい場合もあります)
複数箇所(ソース)から参照される名前は絶対に何某かのヘッダを経由するようにしてください。プロジェクト全ソース中での extern 宣言がただ一箇所になるようにしてください。
・extern 宣言する変数やプロトタイプ宣言だけでなく、構造体宣言や定数ラベル等、各ソース間で共有する情報は必ずヘッダに記述し、そのヘッダを#includeすることで、ソース間で矛盾が発生しないようにしてください。
各ソースファイル中や複数のヘッダファイルで、各々が同じ構造体宣言や定数定義,extern宣言をするのは不可です。
変更があったときに、変更による矛盾がをコンパイル時に検出しきれず、なかなか気づけないバグになる場合が多々あります(とくに担当者が違う場合)。
・.hヘッダに実体を置かないでください。
.hファイルはincludeファイルの拡張子ではなく、宣言を集めた存在とします。
(もちろん、マクロを用いたextern宣言/0初期化実体定義の定番テクニックを否定するものでく、むしろ定番テクニックで済ませられるものは済ましたほうがいいので)
・極力、実体のinclude はしないでください。
もちろん、下回りやデータ集中管理部分等(とくに自動生成したデータ等)の兼ね合いによる例外はありです。
ただ、その場合も、依存関係をmakefileに記述する等は忘れないでください。(可能な限りmakefileやide環境の管理下にソースがすべてあるほうが安全なので)(また、デバッガによってはincludeした.cソースを上手く扱えないものもあったので)。
・0を利用してください。
フラグ変数や関数の戻り値で、値が真か偽のとき、真が1,偽が0,と必ずなるとしても、とくにどちらを用いてもよい場合ならば 0と比較してください。
if (flag == 1)
とするのでなく、
if (flag)
や
if (flag != 0)
にしてください。
0はどのCPUにとっても特殊な存在で、利用するとお徳なことが多いので、基本テクニックとして習慣にしてください。
また、何か自分で変数や戻り値を考えるときも、0を利用できそうなら0を絡めた値を決めてみてください。
好みの類でもありますが、おのずと値の決め方が似てくるので、このほうが、よいでしょう。
もちろん、意味的なものを曲げて無理に0に絡める必要はないです。