ROS C++ スタイルガイド
Contents
このページはCppStyleGuide 2010-03-12 00:13:07 版を元にしています。
このページはROSのためにC++コードを書く場合に従うべきスタイルガイドを定めています。このガイドはROSコアとそれ以外の両方について適用されます。
Pythonにいては PyStyleGuide を参照して下さい。
ROS開発者向けの一般的なガイドラインについては開発者ガイドを参照して下さい。
動機
コーディングスタイルは重要です。簡潔で一貫したスタイルはコードをより読みやすく、デバッグしやすく、メンテナンスしやすくします。今日では私達は要求される機能を実現するだけでなく、何年にも渡って他の開発者に再利用や改善を施され生きつづけるようにエレガントなコードを書こうと努めるのです。
このページの終わりまで、私たちは様々な事例について規程(禁止)をします。私たちのゴールは、容易に他開発者が理解できるコードをアジャイルかつ合理的に開発することの奨励にあります。
これらはガイドラインであり、規則ではありません。極少数の例外については、この文書はあらゆる特定のC++のパターンや機能を完全に禁止していませんが、大部分の事例でうまくいく最良の例を提示します。 ここで示されるガイドラインから逸脱する場合は、その選択について熟慮した上で確実にその理由をコードの中に文書化してください。
何にもまして、一貫しましょう。従えるときはいつでもこのガイドに従い、他の誰かが書いたパッケージを変更する時はそのパッケージの既存のスタイル規約に従って下さい。(もしあなたがそのパッケージ全体をこのガイドに従うように変更するという表彰ものの作業をしない限り)
このガイドを通して、私たちはGoogle C++ style guideをこの話題についてよく書かれた文献として参照しています。ここにそのような参照の最初の例があり、一貫したスタイルの必要性についてより長い動機づけを提供しています:
この非準拠のコードは一体何?
たくさんのROSのC++コードがこのスタイルガイドのリリースに先駆けて書かれました。そのため、ROSのコードベースにはこのガイドに非準拠のコードがたくさんあります。以下のアドバイスはガイド非準拠のコードについて作業する開発者に向けたものです。
- すべての新しいパッケージはこのガイドに準拠するべきです。
- あなたが自由時間を持て余していない限り、既存のコードベースをこのガイドに準拠させる作業をしてはいけません.
- もしあなたが非準拠なパッケージの著者ならば、コードをガイドに準拠させる時間を確保するよう試みてください。
- 非準拠なパッケージに対して子細な変更を加える場合、何であれそのパッケージの既存のスタイル規約に従いましょう。スタイルを混在させてはいけません。
- 非準拠なパッケージに対して大規模な変更を加える場合、スタイルをガイドに準拠させる機会を設けて下さい。
命名
命名方法について記述するため、本章では以下の省略表記を使用します。
CamelCased: 各単語毎にその先頭の一文字を大文字にし、アンダースコアなしで接続したもの。
camelCased: CamelCase と似ているが、こちらは最初の単語の1文字目が小文字で始まる。
under_scored: 全て小文字の単語をアンダースコアで接続したもの。(本来は一つの単語なのでunder_scoredはunderscoredとするべきですが、ここでは便宜上こう表記します)
ALL_CAPITALS: 全て大文字の単語をアンダースコアで接続したもの。
パッケージ
ROSパッケージ名はunder_scoredです。
これはC++に限ったことではありません。より詳細な情報は開発者ガイドを参照して下さい。
トピック / サービス
ROSにおけるトピックとサービスの名前はunder_scoredです。
これはC++に限ったことではありません。より詳細な情報は開発者ガイドを参照して下さい。
ファイル
全てのファイル名はunder_scoredです。
ソースファイルの拡張子は.cppです。
ヘッダファイルの拡張子は.hです。
説明的な名前をつけましょう。laser.cppではなくhokuyo_topurg_laser.cppのように命名しましょう。
ファイルのが主に一つのクラスを実装する場合、そのクラスにちなんだ名前にして下さい。
ライブラリ
ライブラリはファイルの一種であり、したがってその名前はunder_scoredです。
ライブラリ名のうち、lib接頭辞の直後にはアンダースコアを挿入しないで下さい。
例:
libmy_great_thing
クラス / 型
クラス名(および他の型名)はCamelCasedです。
例:
class ExampleClass;
- 例外
- クラス名が短い頭字語を含む場合には、頭字語は全て大文字にして下さい。例:
class HokuyoURGLaser;
クラス名はそれが表す<何ものか>にちなんで命名しましょう。もしそのクラスの表す<何ものか>がわからないとしたら、恐らく設計が十分なされていないのです。
三単語以上からなる複合名は、設計が不必要に入り組んでいる可能性を示唆しています。
関数 / メンバー関数
一般に、関数とメンバー関数名はcamelCasedで、引数はunder_scoredです。 例:
int exampleMethod(int example_arg);
関数とメンバー関数は通常何かの動作を行うので、その名前はそれが何を行うかを明確にしなければなりません。 errorCheck()ではなくcheckForErrors()、dataFile()ではなくdumpDataToFile()のように命名します。 クラス名は大抵名詞のため、関数名を動詞にし他の命名規約に従うことでプログラムはより自然に読めるようになります。
変数
一般に、変数名はunder_scoredとして下さい。
無理なく説明的な名前をつけ、暗号のような名前をつけないようにしましょう。変数名が長くても使うメモリ量は増えません。
整数の反復用変数はi、j、kのような非常に短い名前を許されます。反復子の使い方には一貫性を持たせるようにしましょう。(例:iを外側のループで使い、次に内側にあるループではjを使う)
STL反復子の変数はそれらが反復する対象を示唆する名前を持つべきです。 例:
std::list<int> pid_list; std::list<int>::iterator pid_it;
このようにする代わりに、STL反復子はそれが指す要素の型を示唆するように命名することができます。 例:
std::list<int> pid_list; std::list<int>::iterator int_it;
定数
定数はそれがどこで使われるものであってもALL_CAPITALSの名前にして下さい。
メンバー変数
メンバー変数(あるいはフィールド)はunder_scoredで命名し、接尾語としてアンダースコア"_"を加えて下さい。
例:
int example_int_;
大域変数
大域変数(グローバル変数)は基本的に使うべきではありません。(詳細は後述) もし使用する場合、大域変数はunder_scoredで命名し、接頭語"g_"を加えて下さい。
例:
// 私はあらゆる方法を試したが、どうしてもこの大域変数が必要だ。 int g_shutdown;
名前空間
名前空間はunder_scoredで命名してください。
ライセンス文
全てのソースおよびヘッダファイルはファイル先頭にライセンスおよび著作権に関する記述を持たなければなりません。
ros-pkg および wg-ros-pkg リポジトリ内の LICENSE ディレクトリにライセンス文のテンプレート及びC/C++コードにおけるインクルードに関する記述があります。
許容されるライセンスおよびライセンス戦略についての情報はROS開発者ガイドを参照して下さい。
コードフォーマット
お使いのエディタは大抵のフォーマット作業をこなしてくれると思います。エディタ設定ファイルの例についてはEditorHelpを参照して下さい。
ブロックは2つのスペースでインデントします。タブ文字は使用してはいけません。
名前空間の内容物はインデントしません。
開き及び閉じの波括弧("{"と"}")は単独の行に置いてください。
例:
if(a < b) { // 何かする } else { // 他のことをする }
波括弧はブロックが一つの文しか含まない場合には省略して構いません。例えば:
if(a < b) x = 2*a;
ブロックがより複雑な場合には常に波括弧を使いましょう。例:
if(a < b) { for(int i=0; i<10; i++) PrintItem(i); }
より大規模な例:
1 /*
2 * ブロックコメントはこのようになります...
3 */
4 #include <math.h>
5 class Point
6 {
7 public:
8 Point(double xc, double yc) :
9 x_(xc), y_(yc)
10 {
11 }
12 double distance(const Point& other) const;
13 int compareX(const Point& other) const;
14 double x_;
15 double y_;
16 };
17 double Point::distance(const Point& other) const
18 {
19 double dx = x_ - other.x_;
20 double dy = y_ - other.y_;
21 return sqrt(dx * dx + dy * dy);
22 }
23 int Point::compareX(const Point& other) const
24 {
25 if (x_ < other.x_)
26 {
27 return -1;
28 }
29 else if (x_ > other.x_)
30 {
31 return 1;
32 }
33 else
34 {
35 return 0;
36 }
37 }
38 namespace foo
39 {
40 int foo(int bar) const
41 {
42 switch (bar)
43 {
44 case 0:
45 ++bar;
46 break;
47 case 1:
48 --bar;
49 default:
50 {
51 bar += bar;
52 break;
53 }
54 }
55 }
56 } // end namespace foo
57
1行の長さ
1行の長さの最大値は120文字とします。
#ifndef ガード
全てのヘッダファイルは複数回のインクルードから #ifndef ガードによって保護されなければなりません。例:
#ifndef PACKAGE_PATH_FILE_H #define PACKAGE_PATH_FILE_H ... #endif
ガードはライセンス文の直後の行から始まり、ファイル内のいかなるコードもガードの内側に置かれるべきです。
ドキュメンテーション
コードはドキュメント化されなければなりません。ドキュメントのないコードはどれほど良く機能したとしても保守されません。
私たちはコードの自動的ドキュメント化にdoxygenを利用しています。Doxygen はあなたのコードを解析し、関数、変数、クラスなどに隣接する特定形式に従ったコメントブロックからドキュメンテーションを抽出します。Doxygenはより説明的で、自由な形式のドキュメンテーション作成にも利用することができます。
Doxygenスタイルのコメントをコードに挿入する事例についてはrosdocページを参照してください。
全ての関数、メンバー関数、クラス、クラス変数、列挙体、定数はドキュメント化されるべきです。
コンソール出力
printf やその同類(例: cout)の使用は避けてください。出力が必要なときには常にrosconsoleを使いましょう。 rosconsole は printf および stream 形式の引数に対応したマクロを提供します。printf のように rosconsole の出力はスクリーンに表示されますが、一方で次のような違いがあります:
- カラー表示
- 冗長度レベルと設定ファイルによるコントロール
/rosoutへの出版され、ネットワーク上の誰でも読むことができる。(roscppを利用している場合のみ)
- ファイルへのログ保存オプション
マクロ
プリプロセッサマクロはどんな時も可能な限り使用を避けてください。インライン関数やconstな変数と違い、マクロには型チェックもスコープもありません。
プリプロセッサ指令 (#if 対 #ifdef)
条件付きコンパイルには常に #ifdef ではなく #if を使いましょう。(上述の #ifndef ガードを除く)
誰かがこの様なコードを書くかもしれません:
#ifdef DEBUG temporary_debugger_break(); #endif
他の誰かがデバッグ情報を無効化しようとしてこのコードを次のようにコンパイルしようとするかもしれません:
cc -c lurker.cpp -DDEBUG=0
プリプロセッサを使うときは常に #if を使う必要があります。この方法はうまく動き、正しい挙動をします。(例え DEBUG がまったく define されていない場合でも)
#if DEBUG temporary_debugger_break(); #endif
名前空間
名前空間を使用してコードに有効範囲を設定することを推奨します。パッケージの名前に基づいて、説明的な名前を採用して下さい。
using指令をヘッダーファイルで使用してはいけません。これを行ってしまうとそのヘッダーファイルをインクルードする全てのコードの名前空間が汚染されてしまいます。
using指令をソースファイル内で使用することは許容されますが、意図した名前のみを引き出すことのできるusing宣言の方がより好ましい方法です。
例えばこのような使い方:
using namespace std; // ダメ。std名前空間から全ての名前をインポートしてしまう。
ではなくこうしましょう:
using std::list; // std::list を list として参照したい using std::vector; // std::vector を vector として参照したい
継承
継承は共通インターフェースを定義・実装するのに適した方法です。ベースクラスにおいてインターフェースを定義し、サブクラスでそれを実装します。
継承は共通コードをベースクラスからサブクラスに提供するためにも使うことができますが、このような継承の使い方は推奨されません。大抵の場合、"サブクラス"に"ベースクラス"のインスタンスを含めることで問題を起こす可能性を抑えつつ同じ結果を得ることができます。
サブクラスで仮想メンバー関数をオーバーライドする場合、常にvirtual修飾子をつけるようにして下さい。これによってコードの読者に何を意図しているか伝えることができます。
多重継承
多重継承は度を越えた混乱を引き起こす可能性があるため、禁止に近い非推奨です。
例外
整数のエラーコード戻り値に対して、例外はエラーを報告するための好ましい機構です。
あなたのパッケージ内の関数/メンバー関数について、常に発生する可能性のある例外について文書化を行って下さい。
デストラクタから例外を投げてはいけません。
また、あなたが直接呼び出さないコールバック関数からも例外を投げてはいけません。
もしあなたが自分のパッケージ内で例外の代わりにエラーコードを使う場合、エラーコードだけを使いましょう。一貫するようにしましょう。
例外安全なコードを書く
あなたのコードが例外によって中断された時、その時点で確保されたリソースはスタック変数がスコープアウトした時点で確実に解放されるようにする必要があります。特にミューテックスの類や、ヒープに確保されたメモリは必ず解放されなければなりません。以下のようなミューテックスガードやスマートポインタを利用してこのような安全性を達成しましょう:
- TODO
- TODO
列挙体
enumを名前空間に入れましょう。例えば:
namespace Choices { enum Choice { Choice1, Choice2, Choice3 }; } typedef Choices::Choice Choice;
このようにすることで列挙体が名前空間内部を汚染することを防げます。各列挙子は Chioices::Chioice1 のように参照されることになりますが、typedef によって Choice 型変数を名前空間指定子無しで宣言できます。
大域要素
大域変数および関数は非推奨です。これらは名前空間を汚染しコードの再利用性を低下させます。
特に大域変数はほぼ禁止であると考えて下さい。これらはコードの複数インスタンス化を阻害し、マルチスレッドプログラミングを悪夢に変えます。
ほとんどの変数や関数はクラスの内部で宣言されるべきです。それ以外は名前空間の内部において宣言されなけばなりません。
例外: main()関数を含むファイル内では、少数の小さなヘルパー関数を大域スコープに置いても構いません。しかし未来のある日それらのヘルパー関数が他の誰かにとっても有用になるかもしれないことを心に留めておいて下さい。
静的クラス変数
静的クラス変数は非推奨です。これはコードの複数インスタンス化を阻害し、マルチスレッドプログラミングを悪夢に変えます。
exit() の呼び出し
exit()はアプリケーションにおいてよく定義された出口点でのみ呼び出すようにしましょう。
ライブラリ内でexit()を呼び出してはいけません。
アサーション
前提条件やデータ構造の整合性、メモリアロケータの戻り値などをチェックする時にはアサーションを使いしましょう。アサーションは滅多に呼ばれない条件を書くより優れています。
assert()を直接呼び出してはいけません。代わりにros/common.h(roscppパッケージの一部)内のros名前空間で宣言されている以下の関数のうちどれかを使用してください。
/** ROS_ASSERT は引数として与えられた式が真であるとアサートします。 * 式が偽の場合プログラムの実効は中止され、 * どのファイルのどのアサーションが失敗したかが表示されます。 * assert() を直接使うのではなく、ROS_ASSERT を使って下さい。 */ void ROS_ASSERT(bool expr);
/** ROS_BREAK はプログラムの実効を中止し、 * どのファイルのどのアサーションが失敗したかが表示されます。 * assert(0) や ROS_ASSERT(0) の代わりに ROS_BREAK を使用して下さい。 */ void ROS_BREAK();
アサーションの内部で何か処理をしてはいけません。論理表現のチェックを行って下さい。コンパイル設定によってはアサーションは実行されないかもしれないのです。
テスト
gtestを参照。
移植性
私たちは現在 Linux と OS X をサポートしており、最終的には他の OS (可能ならば Windows も)のサポートを計画しています。そんためには C++ のコードを移植性を維持することが重要です。注意点の例をいくつか挙げます:
uintを型名として使わない。代わりにunsigned intを使う。
isnan()をstd名前空間内から呼び出す。例: std::isnan()
廃止勧告
パッケージないのあるヘッダファイル全体を廃止勧告対象にしたい場合、適切な警告を含めるようにしましょう。
#warning mypkg/my_header.h has been deprecated
ある関数を廃止勧告対象にする場合、deprecated 属性を追加して下さい。
__attribute__ ((deprecated)) int myFunc();
あるクラスを廃止勧告対象にする場合、そのコンストラクタおよび全ての静的メンバ関数に deprecated 属性を追加して下さい。
class MyClass { public: __attribute__ ((deprecated)) MyClass(); __attribute__ ((deprecated)) static int myStaticFunc(); };