Cソースで守ってほしいこと。

C言語、CPU命令と対応した非常によくできた言語だと思います。C++, Java, C#と続くプログラミングの王道、”C系”のルーツ。

ただそのC言語、簡単なはずなのにちゃんと使われていない!プログラミングのマナーである、わかりやすい関数名・変数名や、SOLID原則や、getterの中では値のsetはしない(コマンド照会分離原則)…等のどの言語でも当てはまることは除いて、C固有(C++すら除外!)の内容についてC使いにどうしても守ってほしいことを書いてみたいと思います。

守ってほしいこと

  1. .cに対応した.hファイルは.cの先頭でincludeすること
  2. static関数のプロトタイプ宣言は必ず書くこと
  3. 変数の宣言時初期化を意識すること

コードを読むのは学習って言うけど、これらも守られていないコードからは何も学べる気がしない。

 

1..cに対応した.hファイルは.cの先頭でincludeすること

間違い例(waveprocess.c): 

#include "type.h"
#include "common.h"
#include "doubleCalc.h"
#include "customheap.h"
#include "filewrap.h"
#include "wavetypes.h"
#include "waveprocess.h"

// 以下の関数は外部公開externされている前提
void DoSomething(profile* waveData){
    ...
}

理由:

DoSomething()を呼び出したい人はwaveprocess.hをインクルードしますよね?(includeせずextern宣言で利用できるけどそんなことしたらカオスだよ) そうするとwaveprocess.hをインクルードする。けど上のコードだとそれだけで使える保証がない。少なくとも引数に使っているprofile型の型定義が参照できなければビルドできないけどそれがはどこで定義されているかわかりませんよね、呼び出し側はそれを探してその定義があるファイルもインクルードする必要がある、しかもそれはwaveprocess.hの前にインクルードしなきゃいけない。外部公開関数が必要とする型はどうせインクルードしないといけないのだから、そんなものはヘッダ側で用意しましょう。それを保証するために.cに対応する.hを.cの先頭でインクルードする、のです。先頭でincludeしたときに外部公開に必要な型定義がなければエラーとなるため、対応する.hを先頭でインクルードすることが必要なヘッダをインクルードした状態であると保証できるのです。

正しい例(waveprocess.c):

#include "waveprocess.h"
// #include "type.h"  -> 基本型の定義だからwaveprocess.hでインクルードしなきゃいけなかった!
#include "common.h"
#include "doubleCalc.h"
#include "customheap.h"
// #include "wavetypes.h" -> ここにprofile型が定義されていた!
#include "filewrap.h"

// 以下の関数は外部公開externされている前提
void DoSomething(profile* waveData){
    ...
}

(waveprocess.h)

#ifndef WAVE_PROCESS_HEADER
#define WAVE_PROCESS_HEADER
#include "type.h"
#include "wavetypes.h"

void DoSomething(profile* waveData);

#endif

コメントアウトは説明のために書いてるだけで本番コードではもちろん書きませんよ。こうすれば、DoSomething関数を呼び出す側はそれが定義されているwaveprocess.hをインクルードするだけでいいのです。絶対そのほうがいいでしょ?

 

2.static関数のプロトタイプ宣言は必ず書くこと

間違い例(Test.c):

static void SubFunc1(){
    ...
}
static void SubFunc2(){
   ...
}
void ExternFunc(){
    SubFunc1();
    SubFunc2();
}

理由:
第一に、上から下に読むという基本に反している。でも「慣れ」って言うかもしれないからこれにはあまり触れない。第二に、関数の変更時に面倒なことになる。例えば、SubFunc1でSubFunc2を使いたいとしたらどうする?上の例で関数呼び出ししたんじゃそんな関数はないってエラーになる。

正しい例(Test.c):

static void SubFunc1();
static void SubFunc2();

static void SubFunc1(){
    ...
}
static void SubFunc2(){
   ...
}
void ExternFunc(){
    SubFunc1();
    SubFunc2();
}

もう定義順なんて気にしない、させない。

 

3.変数の宣言時初期化を意識すること
C++のソースですら下のような例をみる。

間違い例(Test.c):

void TestFunc(waveprofile* profile, rawwave* wave){
    DWORD dwCount;
    BOOL bFlag;
    if( profile->failed){
        dwCount = 2;
    }else{
        dwCount = 4;
    }
    bFlag = IsPeakExist(wave, dwCount);
    ...
}

理由:

確かにCは変数宣言が先頭にないとダメだけど、初期化漏れ・状態不整合についての意識が欠けていないだろうか。状態不整合ってのは値が代入されるまでの間、存在するけど使えない状態があること。上のコードくらいならまだマシかもしれないけど、変数を何個も使い出したらこれは初期化されてるとかこれはまだとか頭にキャッシュしないといけない。状態不整合をカプセル化するためのオブジェクト指向言語であり、状態不整合が副作用を及ぼすから関数型言語があるんだと思っている、私は。

正しい例(Test.c):

void TestFunc(waveprofile* profile, rawwave* wave){
    DWORD dwCount = (profile->failed)? 2 : 4;
    BOOL bFlag = IsPeakExist(wave, dwCount);
    ...
}

条件? 真時の値 : 偽時の値っていう3項演算子を嫌う人もいるけど、不整合を生まない、宣言時に書ける(ifステートメントは書けない)ってことを考えてメリットないって考えるほうがどうかしてる。他にも安易にstatic変数使うなとか、言いたいことはいっぱいある。こんな内容が常識として扱われたらいいんだけど。・・・グチでした。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中