くまくまの業務日誌

Markdown記法で徒然に書いてみましょう。

C/C++コーディング規約(草案)

コーディング規約

■前書き

 昨今のプログラム開発における記載ルールは、言語特性やIDEのタイピング補完機能などにより、統一した記載ルール(コーディング規約)を明確にすることで、高速で品質の高いプログラム作成が可能となる。OSや言語の違いにより、使われるルールが変わってくるため、どの場合にはどのようなルールで記載するのかを明確にし、統一された品質の高いプログラム作成の一助となるよう、細部にまでルールを検討したものである。

■基準となる命名規約

 現在、プログラム開発における命名規約は、「ハンガリアン」「パスカル」「キャメル」「スネーク」の4種類が存在している。 それぞれの特徴を把握し、ファイル名、クラス名や構造体名、関数名、変数名の命名ルールを明確にして、統一した開発標準策定を目標とする。

■■ハンガリアン記法

 ハンガリアン記法は、WindowsC/C++開発における標準の命名規約である。WIN32APIは、ほぼこの「ハンガリアン記法」によって、作成されている。型名を変数名先頭に修飾するため、変数の型をコーディング中に推測しやすくなるが、プログラムの改造中に変数の型を変更することになった場合、IDEの機能で安全に変更できるとしても、使用箇所だけ変数名の変更が発生するため、リファクタリングには不向きである。また、Windows開発では、C/C++でのプリミティブな変数をあまり使用せず、#defineやtypedefで置き換えた、大文字の変数型を使用することが多い。

 このハンガリアン記法の概要は以下のようになる。

  1. グローバル変数の場合、変数名先頭に'g_'を付ける。
  2. クラスのインスタンス変数の場合、変数名先頭に'm_'を付ける。
  3. 変数の型がポインタの場合、変数名先頭にポインタを表す'p'または'lp'を付ける。
  4. 変数名先頭にその変数の型を表す修飾子をつける。
実際の型宣言 修飾
char CHAR 'c' cKeyHit
unsigned char BYTE 'by' byBuffer
short SHORT 's' sLength
unsigned short WORD 'w' wSize
int INT 'i' iCounter
unsigned int DWORD 'dw' dwSize
long LONG 'l' lCounter
unsigned long ULONG 'ul' ulSize

例)

LPVOID g_lpvStartPoint;
INT g_iIndexCount;

INT m_iCurrentStatus;
INT m_iPrevStatus;

LPCTSTR lpctszFilePath;
LPBYTE lpbyBuffer;

■■パスカル記法

 パスカル記法は、各単語の先頭を大文字にして連結した命名規約である。後述のキャメル記法の対向的な存在として表現されることが多いが、Windows C/C++開発のメインの記法の1つである。

 このパスカル記法の概要は以下のようになる。

  1. 一貫して単語の先頭は大文字で始める。
  2. 単語の間に記号は使用しない。

例)

CurrentStateProducer.cpp
PrivateDataGenerator.cpp

int MaxStatus;
long MaxKeywords;

■■キャメル記法

 キャメル記法は、Java開発における標準の命名規約である。Java言語だけでなく、C#でも一部はキャメル記法をルールとしている。この記法の特徴は、「IDEの変数名補完機能を使用する際、SHIFTキーすらも使わずに済む。」「動詞や形容詞を先頭に持ってくることで、設定するのか/取得するのか キーワードで関数名候補が絞られて表示できる」という現代のIDEの機能を引き出すための記法でもある。

 このキャメル記法の概要は以下のようになる。

  1. 変数先頭の単語は小文字で始まる。
  2. 以降の単語の先頭は大文字となる。
  3. ハンガリアン記法のような型を表す修飾子を使用しない。
  4. 関数名の場合、先頭は動詞、以降は名詞を使用する。
  5. 変数名の場合、先頭は形容詞、以降は名詞または形容詞を使用する。

例)

int currentStatus; // 現在のステータス
int previousStatus; // 以前のステータス
short minSelectValue; // 最小の選択値
short maxCounter; // 最大のカウンタ値

int getCurrentStatus() { return currentStatus; };
void setPreviousStatus(int value) { previousStatus = value; };
short calcMinSelectValue();
short clearMaxCounter();

■■スネーク記法

 スネーク記法は、Linux開発、特にシェルスクリプトにて見られる命名規約である。C言語では、#defineなどの定義名に使用される。定数定義では大文字が使用され、変数や関数名では小文字が使用されるパターンがほとんどである。

 このスネーク記法の概要は以下のようになる。

  1. 単語と単語の間にアンダーバー '_' を使用して連結する。
  2. 単語に大文字を使用しない。
  3. 大文字と小文字を混同しない。

例)

int previous_current_status;
long current_file_pos;

#define MAX_FILE_SIZE 256
#define DATA_SIZE 1024

■ソリューション名

 「ソリューション」はVisual Studioにおけるモジュール(成果物)の一括作成単位の名称である。システム全体の名称となるため、名称が長くなる場合は、そのシステム名の「コードネーム」などを検討し、適切な長さの名称となるようにする。

■プロジェクト名

 「プロジェクト」は1つのバイナリモジュールを作成する単位の名称である。プロジェクトの成果物は、以下を指す。

  • 単体で実行可能なバイナリモジュール
  • 上記のバイナリモジュールから利用される、共有モジュール
  • バイナリモジュールにコンパイル済みロジックを提供する共通ライブラリ
  • 上記モジュールを自動でテストするテストプロジェクト

 上記のプロジェクトを生成するプロジェクト名は、以下の規約を用いる。

  1. 共有モジュール名の最後には、サフィックスとして"Library"をつける。
  2. 共有モジュールで、上記の"Library"の機能を制御する事を目的としたものは、サフィックスに"Control"をつける。
  3. 単体実行可能なモジュールのサフィックスに、"Driver"をつける。
  4. 上記のプロジェクトのテストプロジェクトにはサフィックスに、"Test"をつける。

例)

Alternate  
├ConsoleDriver  
├XamlDriver  
├MiddleControl  
├WindowsLibrary  
├WindowsLibraryTest  
├OSSLibrary  
└OSSLibraryTest

名前空間名規約

 名前空間はこれから作成する機能を、プロジェクトをまたいでグルーピングする際のグループ名に当たる。STL(Standard Template Library)は広範囲に高機能なライブラリを提供するが、使用される名前空間は、"std"にまとめられる。Boostライブラリも同様に"boost"にまとめられる。

 このように、名前空間は複数のプロジェクトをまたいで統一した名前を提供することが望ましい。また、名前空間名はコーディングの煩雑さを軽減するためにも3文字程度に留める事を推奨する。

 名前空間入れ子で作成することも可能であるが、上記同様コーディングの煩雑さを持ち込むことになるので、階層構造を十分に吟味して採用する必要がある。

例)

alt  
├alt::db  
│├alt::db::Oracle  
│├alt::db::SqlServer  
│└alt::db::SqLite3  
├alt::file  
│├alt::file::csv  
│└alt::file::txt  
├alt::process  
│└alt::process::priority  
└alt::thread  
 └alt::thread::priority  

 データベース名などは、メジャーな名前なのでコーディング時に思いつきやすいが、それ以外は全機能を把握しないとIDEの名前補完機能の恩恵を受けられない。名前空間の階層化は、名前空間の全体設計とプロジェクト構成、ファイル構成のすべてを検討する必要がある。また、"std::"や"boost::"のように単一名前空間提供で問題ない場合は、採用すべきである。

 データベースプログラムは、ベンダー別にプログラムした部分と、それらを抽象化して実際に利用する部分を明確に分けることで、抽象化コードと具現化したコードが混在していることを明確化することができる。この場合、抽象化が不足していることが原因であり、コードを見直す必要を名前空間から判断することができる。

■ヘッダーファイル規約

 ヘッダーファイルの構成は以下の順に宣言を記載する。

  1. ファイルヘッダーコメント
  2. #pragama once
  3. 空白
  4. #include <...>
  5. 空白
  6. #include "..."
  7. 空白
  8. #define定義群
  9. 空白
  10. const 宣言群
  11. 空白
  12. 構造体定義群
  13. 空白
  14. クラス定義群(本来は独立したファイルにクラスの定義を行う)
//------------------------------------------------------------------------------
// sample機能を実装したファイル
//------------------------------------------------------------------------------
#pragma once

#include <stdio.h>
#include <stdlib.h>

#include "BaseDefine.h"

#define POS position

const int __maxFile 10;
const char* __baseFilePath "C:\\Work"

■ソースファイル規約

 ソースファイルの構成は、以下の順に実装を行う。

  1. ファイルヘッダーコメント
  2. 空白
  3. #include "これから定義を行うヘッダーファイル"
  4. 空白
  5. 関数定義
//------------------------------------------------------------------------------
// sample機能を実装したファイル
//------------------------------------------------------------------------------

#include "sample.h"

void sample::sample()
{
…

■定数定義規約(#define)

 定数の宣言は、以下のルールに準拠する。

  1. 名前規約はスネーク記法(大文字)とする。
  2. できるだけ先頭に形容詞を設けて、どの値を定数宣言しているのかを明確する。

例)

#define MAX_CONFIG_FILE_PATH 256
#define MIN_SEQUENCE_DATA_LENGTH 12

■定数定義規約(constグローバル定義)

 定数の宣言は、以下のルールに準拠する。

  1. グローバル変数ルールの"__"をプレフィックスに付ける。
  2. ハンガリアン記述で変数型をプレフィクスに付ける。

例)

const int __iMaxSequenceCount = 750;
const char* __lpctszDefaultConfigFilePath = ".\\config\config.ini";

■クラス・構造体名/ファイル名規約

 クラス・構造体名は、「パスカル記法」を使用して作成する。以下の機能に該当する場合は、プレフィックスサフィックスルールに留意する事。

  • クラス名のプレフィックスに’C'または'c'を付けない。
  • 構造体名のプレフィックスに’S'または's'を付けない。
  • 広範囲な機能をインスタンス生成不要で提供する場合、サフィックスに"Utility"をつける。
  • 広範囲な機能をインスタンス生成を前提に提供する場合、サフィックスに"Helper"をつける。
  • 仮想関数のみで構成され、Javaのインターフェースに相当する機能を提供する場合、プレフィックスに"I"をつける。
  • 上記のインターフェースに対する、機能の実装を行うクラスの場合、プレフィックスに"I"を、サフィックスに"Impl"をつける。
  • 自動テストを実装する場合、サフィックスに"Test"をつける。
  • ファイル名はそれを内包するクラス、構造体の名称を使用する。
  • 複数のクラス・構造体を1つのファイルに同梱しない。ただし、Strategyなどの密接なクラス構成を1つのファイルにまとめる(他の機能で使われることがない)場合はこの限りではない。

■■public, protected, privateキーワード

 クラスのスコープ(public, protected, private)と、スコープに所属する関数、変数は、以下の順序で宣言する。クラスの関数宣言時に関数本体も実装する事が可能であるが、その関数が1行(セミコロン1つ)で実装できる場合を除き、クラス内に関数を実装する事は避ける事。

public宣言

  1. デフォルトコンストラクタ宣言
  2. 引数ありコンストラクタ宣言
  3. コピーコンストラクタ宣言
  4. デストラクタ宣言
  5. 継承が必要となる関数宣言
  6. その他のpublic関数宣言

protected宣言

  1. 隠蔽するコンストラクタ宣言
  2. 継承が必要となる関数宣言
  3. protected関数宣言
  4. protected変数宣言

private宣言

  1. 継承が必要となる関数宣言
  2. private関数宣言
  3. private変数宣言

■コンストラク

 コンストラクタは実装方法が複数あるため、当規約では以下のスタイルを推奨する。コンストラクタで、処理の実装を行うことは禁止とする。コンストラクタが戻り値を渡せないため、処理の成否を確認できないためである。また、継承元クラスがある場合は、その継承元クラスのどのコンストラクタを呼び出すのかを明確にする。

ExtendObject::ExtendObject(int initialValue)
    : BaseObject(initialValue) // 引数付き継承元コンストラクタを指定する。
{
    this->Init(); // 初期化関数を使用する。
}

bool ExtendObject::Init()
{
    // 内部のメンバー変数を初期化する。
    // Init()単体で使用することで、再初期化が可能となる。
}

■デストラク

 デストラクタは実装するクラスが継承されない場合、"virtual"をつけない。実装するクラスが継承される場合、"virtual"を必ずつける。

■その他のpublic関数

 コンストラクタ、デストラクタ以外に以下の関数の実装を検討する。

  • クラス内の変数を初期化するためのInit()関数
  • クラスの終了時に使用したヒープ領域やファイルのクローズを行うExit()関数

■関数名規約

 関数名は、スコープに関わらずパスカル記法で記載する。キャメル記法のように先頭は動詞で以降は名詞を使用する。

 できる限り、KatakanaJikkou()のようなローマ字表記を行わず、英単語を使用する。

■変数名規約

■■グローバル変数

 グローバル変数プレフィックスとしてアンダーバー2つを使用する。

 そもそもであるが、グローバル変数以外の実装方法(スコープの限定化)を検討する。main()の内部は、グローバル関数と生存期間がほぼ同じである。うまく引数を使って、グローバル変数の宣言を使わずに済む方法を検討する。

■■クラス変数

 クラスのインスタンス変数、クラス変数のプレフィックスとしてアンダーバー1つを使用する。この規約により、IDE名前補完機能からクラスのインスタンス変数を探す場合は、アンダーバーを入力することで、一覧表示されるようになる。なお、インスタンス変数とクラス変数を区別するような規約は設けない。

■その他の規約

現時点では、記載事項なし。