俺コーディング規則

最近,他人が自分のコードを読んだり修正したりする機会が増えてきたので,意思疎通のために自分のコーディング規則をメモしておきます.CLX C++ Libraries も(たまにブレてますが)ここに挙げる規則に従って書いているので,コードを読む際の参考にでも.

尚,以下は基本的に自分が守っているだけで,他人は(単一プロジェクト内のコード間で整合性が取れる範囲で)自分の信念に基づいてコーディングしていけば良いんじゃないかな,と思っています.

名前空間

グローバル名前空間には,クラス/関数を(極力)定義しないようにします.通常は,プロジェクト毎に適当な名前空間を考えて,その名前空間内に各種クラス/関数を定義します.ただし,ユーザに使用されることを意図していないクラス/関数に関しては,使用する名前空間の中でさらに detail 名前空間を定義して,その中で定義します.

クラス設計

クラスは,

x.init();
x.main_method();
x.terminate();

のような,使用の際にユーザに一連の順序を強いるような設計には(なるべく)ならないように気を付けています(init() 処理はコンストラクタ,terminate() 処理はデストラクタに受け持ってもらう).ただし,クラスによっては複数の使い方が想定されている場合もあるので,その場合はデフォルトの動作とそれ以外の(カスタマイズ的な)動作に分けて,ユーザが後者の使い方を望む場合には一連の順序を踏む必要があるように設計します.

また,各種関数/メソッドの引数は,高々 4 個程度になるように設計しています.引数が多くなる場合にはいくつかの引数にデフォルト値を与え,デフォルトの動作を想定している限りは,それらの引数の存在は気にしなくて良いように設計します.

分かりにくくなったので,クラス設計方針のまとめ.

  1. クラス毎にそのクラスの「デフォルトの挙動」を考える.
  2. ユーザが「デフォルトの挙動」で使用する限りは,コンストラクタ,メインとなるメソッド(ユーザが目的とするメソッド)の 2 step(正確には,+ デストラクタの 3 step)で終了するように設計する.
  3. 「デフォルト以外の挙動」は,デフォルト値が設定されている引数,カスタマイズするためのメソッドを用いる事で対処する.

命名規則

マクロ

マクロは全て大文字で単語間は"_"で繋げます.ただし,マクロはインクルードガード,デバッグフラグ,OS 別の振り分け位しか使っていません.

テンプレート引数

テンプレート引数は,各単語の最初の文字を大文字にして繋げます(例:FooType).ただし,単語の省略形(例:XML, TCP, ...)は全て大文字で記述します(例:TCPType).

それ以外

クラス,メソッド,関数,変数, typedef などそれ以外のものは全て小文字で表記し*1,できるだけ直に繋げるようにしています(例:getfoo).ただし,長くて分かりにくいと(個人的に)感じたものは単語間を "_" で繋げます(例:get_typename_instance)."_" で繋げるかどうかの判断基準にはブレがあります.

それ以外のものの命名規則で特徴的なものは次の通り.

  • テンプレートクラス名は,全てのテンプレート引数にデフォルト値が与えられる場合は,basic_xxx と言う名前で定義して,デフォルト値を与えたものを xxx ("basic_" を取り除いた名前)として typedef する.
  • クラス内で typedef する場合,typedef 名には "_type" サフィックスを付与.ただし,この規則はかなりブレていて,付けていないものも多数あります・・・
  • private メンバ変数には,"_" サフィックスを付与.
  • private メソッドには,"x" プレフィックスを付与. 最近,これはやっていない.
  • ひとつのクラス/関数/メソッド名に "_" は高々 1 個しか出現しない程度の長さの名前にする.それ以上長くなる場合は,名前空間を併用する.
  • 関数名/メソッド名は,名前だけで全てを表現しようとはせず,オーバーロードを含め,引数(型,名前,const 修飾子)を最大限に活用するようにする.

ポインタ

ポインタはできるだけ使用しないようにします.文字列や動的な配列などは,STL の std::string, std::vector などを極力利用するようにし,自分で動的に領域を確保したり管理する事はなるべく避けます.動的に領域を確保する必要のある場合は,shared_ptr を利用します.

また,関数/メソッドの引数においても(できるだけ)ポインタではなく参照型を使用するようにします.

C 関連

C と関連する項目について列挙します.

  • C++ 型のインクルードファイルをインクルードする(stdio.h, stdlib.h, ... ではなく cstdio, cstdlib, ... をインクルードする).
  • C の標準関数を使用する場合も std 名前空間を明記する(例:isalpha() ではなく,std::isalpha() を使用する).
  • C 型のキャストは使わず,static_cast, const_cast, dynamic_cast, reinterpret_cast を使用する.
  • C の printf 系,FILE* 操作関数群は使わず,istream/ostream を使用する.

コメント

コメントは書いたら負けかなと思っている.

クラス/関数定義の直前と(必要に応じて)メソッド定義の直前に識別線,クラス名/関数名/メソッド名とともに概要を書きます.識別線の書き方は以下の形を取っています.

/* ------------------------------------------------------------------------- */
/*
 *  comment
 */
/* ------------------------------------------------------------------------- */

これは,doxygen を使ってリファレンスを生成する必要のあるときに,識別線が doxygen で生成される html に紛れ込まないようにするためです.

その他

細かいもの.思いついた順番に列挙しています.

  • インデントは,4 文字のハードタブで揃える(なので,タブ文字が 4 以外だと多分表示が崩れます・・・).
  • using namespace は基本的には使用しない.
  • 後置インクリメント/デクリメントは使用しない(x++ ではなく,++x を使用する).
  • 演算子の前後には空白を挿入する(例: x = y + z;).
  • if, switch, while, for と "(" の間には空白を挿入し,"(" と条件式の間には空白を挿入しない(例: if (expr1 && epr2) { ... }).
  • if, switch, while, for 文のブロックは,1 行で書ききれるときのみ "{}" を省略する.1 文であっても(長さ的に)複数行に分ける必要がある場合は,"{}" を明示する.
  • 引数の IN/OUT の識別は,なるべく参照型か値型か,const が付与されているかどうかで判断できるようにする.
  • 定数定義には enum を使用する.
  • メンバ変数へのアクセスのためのメソッドに get/set プレフィックスを付与しない(xxx_ と言うメンバ変数へのアクセスメソッドは,getter -> const xxx_type& xxx() const; setter -> void xxx(const xxx_type& cp);).

クラス定義スケルト

最後に,私がよく定義する形のスケルトンを記述しておきます.

/* ------------------------------------------------------------------------- */
/*
 *  basic_skelton
 *
 *  このクラスの概要を書く.
 */
/* ------------------------------------------------------------------------- */
template <
    class Type1,
    class Type2,
    ...
    class TypeN = default_type
>
class basic_skelton {
public:
    // typedef
    typedef Type1 xxx_type;
    ...
    
    // 定数定義.定数は enum で定義している.
    enum { foo, bar, ... };
    
    // コンストラクタやデストラクタ.
    basic_skelton();
    virtual ~basic_skelton() throw();
    
    // コピーコンストラクタと代入演算子.
    basic_skelton(const basic_skelton& cp);
    basic_skelton& operator=(const basic_skelton& cp);
    
    // クラスのメインとなるメソッド
    ...
    
    // メンバ変数へのアクセスメソッド(必要に応じて).
    const xxx_type& xxx() const;
    ...
    void xxx(const xxx_type& cp);
    ...
    
protected:
    // protected はあまり使わないので適当.

private:
    // typedef
    typedef yyy_zzz internal1_type;
    ...
    
    // メンバ変数.private なメンバ変数は "_" サフィックスを付与.
    int x_;
    char y_;
    ...
    
    // メソッド群.private なメソッドは "x" プレフィックスを付与.
    type1 xhoge();
};

// 全てのテンプレート引数にデフォルト値を与えたものを typedef
typedef basic_skelton<type1, type2, ...> skelton;

まだまだ足りない部分も多いと思うので,気が付いたら順次加筆していきます.コーディングスタイルに関しては,一貫しておけば何でも良いと思うのですが,一貫と言うのは想像以上に難しいなぁとしばしば痛感します.

*1:大文字を使わないのは,単語の省略形を全部大文字で書くか最初の文字だけ大文字にするかで悩むのが嫌だったのと,メソッド名の最初の 1 文字は小文字と言う規則が生理的に受け付けない,と言う個人的な理由です.