Coding Style

このドキュメントは Mozilla コードベースで使われる基本スタイルとパターンを説明するためのものです。 新しいコードはこれらの標準に沿うよう試みるべきです。それは、既存のコード同様にメンテナンスが楽になるためです。 もちろん、どのルールにも例外はあります。しかし、にもかかわらずルールを知ることは重要なのです!

これは、新しく Mozilla コードベースへ向かっている、コードをレビューされるプロセスにある人へ特化して向けられています。 レビューを受ける前に、このドキュメントを読み通して、あなたのコードがここで推奨されている内容に沿っていることを確認してください。

一般的 C/C++ 慣習

  • コンパイラのウォーニングをチェックしていますか?ウォーニングは本物のバグをしばしば指摘します。
  • 変更は 64-ビットで安全 ですか?
  • 変更は C++ 移植性ガイド を満たしていますか?
  • ポインタに NULL を使わないでください。void * として宣言されているいくつかのシステムでは、ポインタ代入時にコンパイラがウォーニングを出します。 0 か nsnull を代わりに使ってください。
  • ポインタをテストするとき、!myPtr や (myPtr) を使ってください。 myPtr != nsnull や myPtr == nsnull を使わないでください。
  • == PR_TRUE や PR_FALSE の比較をしないで下さい。 (x) や (!x) を代わりに使ってください。 == PR_TRUE は実際のところ、if (x) とは異なっています!
  • return の直後に else を使わないでください。 else を削除してください。 それは不要でインデントのレベルを増やすものです。
  • new の戻り値が null かどうか常にチェックしてください。
  • デバッグ用の printf() を放置しないでください。
  • どの新しいクラスのヘッダファイルにも JavaDoc スタイルのコメント を使ってください。
  • 問題を修正するとき、同じファイルの別の場所に問題がないかチェックしてください。 可能なら、他のものも修正してください。
  • nsFoo aFoo (bFoo) を使うか nsFoo aFoo = bFoo を使うかということについて: 最初の階層のプラットフォームに関しては、前者の方が理論的にはより良いかも知れませんが、 後者ではなく前者を使用する理由は多分ないでしょう。 "=" を使用した形式の方が見やすいという事は誰もが同意する所であり、 第二階層のプラットフォームのためにはよりデータが多い方が良いでしょう。
  • ヘッダファイルにおいて、できる限りいつでも、 クラスのヘッダファイルをインクルードする代わりにクラスの前方宣言 (forward declaration) してください。 例えば、void DoSomething(nsIContent* aContent) 関数をともなうインタフェースがあるとき、#include "nsIContent.h" の代わりに class nsIContent; を先に宣言してください。

COM とポインタ

  • nsCOMPtr<> を使ってください
    もし、使い方が分からなければ、コードの例を読み始めてください。 NS_RELEASE をタイプすることが、コーディングに際して"ここで私は nsCOMPtr を使うべきだろうか?"と自問をうながす形のシグナルになるという、一般的なルールがあります。 一般的に、 NS_RELEASE の有効な使い方は、あなたが生存期間の長いデータ構造のなかに参照をカウントされたポインタを貯えているときだけです。
  • Declare new XPCOM interfaces using XPIDL so they will be scriptable.
  • Use nsCOMPtr for strong references, and nsWeakPtr for weak references.
  • String arguments to functions should be declared as nsAString.
  • Use str.IsEmpty() instead of str.Length() == 0.
  • Don't use QueryInterface directly. Use CallQueryInterface or do_QueryInterface instead.
  • nsresult should be declared as rv. Not res, not result, not foo.
  • For constant strings, use NS_LITERAL_STRING("...") instead of NS_ConvertASCIItoUCS2("..."), AssignWithConversion("..."), EqualsWithConversion("..."), or nsAutoString()
  • Use contractids instead of progids or class IDs.

IDL

"interCaps"、つまり小文字で始めるルールを使ってください

メソッドや IDL 属性を定義するとき、最初の文字は小文字であるべきです。そして、続くいずれの単語も大文字で始まるべきです。例えば、

long updateStatusBar();

可能なところでは attribute (属性) を使ってください

特に理由もなく単独の値を参照したりセットしたりするときは、attribute (属性) を使ってください。 一つの属性を使うだろうときには二つのメソッドを使わないで下さい。 属性を使うことは、必然的に値の参照や代入につながり、スクリプトされたコードを明確にします。

この例はメソッドが多すぎます:

interface nsIFoo : nsISupports {
    long getLength();
    void setLength(in long length);
    long getColor();
};

以下のコードはまったく同じ C++ シグネチャを生成するでしょう。しかし、よりスクリプトフレンドリです。

interface nsIFoo : nsISupports {
    attribute long length;
    readonly attribute long color;
};

java スタイルの定数を使う

スクリプト可能な定数を IDL の中で定義するとき、名前はすべて大文字にし、単語の間はアンダースコア (_) でつなぐべきです。

const long ERROR_UNDEFINED_VARIABLE = 1;

エラーのハンドリング

エラーのチェックは早めに、頻繁に

XPCOM 関数への呼び出しをするときはいつでも、エラー状態のチェックをすべきです。 たとえ関数呼び出しが絶対に失敗しないとわかっていても、それをすべきです。なぜ?

  • だれかが将来、呼ばれる先を失敗状態を返すように変更するかもしれません。
  • 問い合わせのオブジェクトは別のスレッド上や他のプロセス上、ことによると他のマシンに存在するかも知れません。 プロキシはあなたの呼び出しを最初の場所で実際におこなうのに失敗するかも知れません。

よいマクロを使ってください

if (NS_FAILED(rv)) { return rv; }if (!expr) { return rv; } の代わりに、NS_ENSURE_SUCCESS(rv, rv)NS_ENSURE_TRUE(expr, rv) を使ってください。 失敗が通常の条件である (例、assert を出したくないとき) 場合を 除きます。

エラーからはすぐにリターンしてください

たいていの場合、あなたの条件反射行動はエラー状態が発生したときに現在の関数からリターンするというのであるべきです。 このようにはしないでください。

rv = foo->Call1();
if (NS_SUCCEEDED(rv)) {
    rv = foo->Call2();
        if (NS_SUCCEEDED(rv)) {
            rv = foo->Call3();
        }
    }
}
return rv;

かわりに、このようにしてください。

rv = foo->Call1();
NS_ENSURE_SUCCESS(rv, rv);

rv = foo->Call2();
NS_ENSURE_SUCCESS(rv, rv);

rv = foo->Call3();
NS_ENSURE_SUCCESS(rv, rv);

なぜ? エラーのハンドリングでコードのロジックを混乱させないためです。 最初の例での書き手の意図は、続けざまに3つの呼び出しをすることでした。しかし、 if() 構文をネストして呼び出しをラップすることは、たいていコードの振る舞いをわかりにくくします。

実際にバグの潜んでいるもっと複雑な例を考えてください。

PRBool val;
rv = foo->GetBooleanValue(&val);
if (NS_SUCCEEDED(rv) && val)
    foo->Call1();
else
    foo->Call2();

書き手の意図は val が false の値を取るときにだけ foo->Call2() がよばれるということでしょう。 実は、foo->Call2() は foo->GetBooleanValue(&val) が失敗したときにも呼び出されます。 これが書き手の意図かもしれませんし、そうでないかもしれませんが、このコードからははっきりわかりません。 修正版を示します。

PRBool val;
rv = foo->GetBooleanValue(&val);
if (NS_FAILED(rv)) return rv;
if (val)
    foo->Call1();
else
    foo->Call2();

この例では、書き手の意図ははっきりしています。そして、エラー状態では foo->Call1() も foo->Call2() も呼ばれません。

例外 (エクセプション) の可能性:ときおり、呼び出しの失敗は致命的ではありません。 たとえば、もしあなたがイベントが引き起こされたことを一連のオブザーバーに通知しているなら、その通知の一つが失敗していることは重要でないでしょう。

for (i=0; i<length; i++) {
    // 個別のオブザーバーが失敗しても、気にしない
    observers[i]->Observe(foo, bar, baz);
}

他の可能性は、あなたがコンポーネントが存在しているか、もしくはインストールされているかを知らないで、そのコンポーネントがもしなくても、通常通り続けたい場合です。

nsCOMPtr<nsIMyService> service = do_CreateInstance(NS_MYSERVICE_CID, &rv);
// このサービスはインストールされていて、使うでしょう
if (NS_SUCCEEDED(rv)) {
    // これが失敗しても致命的ではないでしょう。このエラーを無視してください。
    service->DoSomething();

    // これは重要です。このエラーについてハンドリングしてください!
    rv = service->DoSomethingImportant();
    if (NS_FAILED(rv)) return rv;
}
    
// サービスが存在しても、いなくても、処理を通常通り続けてください。

文字列

ローカル変数として使う文字列の型には Auto を使ってください

ローカルで、生存期間の短い nsString クラスを宣言するときは、いつでも nsAutoString か nsCAutoString を使ってください。 これらのバージョンは事前に 64 バイトのバッファをスタックにアロケートし、ヒープのフラグメント化を防ぎます。 このようにしないで下さい。

nsresult foo() {
  nsCString bar;
  ..
}

かわりに

nsresult foo() {
  nsCAutoString bar;
  ..
}

char* や PRUnichar* を返す XPCOM でない関数からの値のリークを警戒してください

それは、内部ヘルパー関数からのアロケートされた文字列を返すという簡単なトラップで、値を解放することなく、あなたのコードの中でインラインにその関数を使います。 このコードを考えてみてください。

static char *GetStringValue() {
    ..
    return resultString.ToNewCString();
}

    ..
    WarnUser(GetStringValue());

上の例では、WarnUser は resultString からアロケートされた文字列を得るでしょう。 ToNewCString() し、ポインタを投げます。 結果の値が、解放されることはありません。 そのかわりに、スコープの外に出たときにあなたの文字列が自動的に解放されることを確証するために文字列クラスを使うか、あなたの文字列が解放されていることを確証してください。

自動クリーンアップ

static void GetStringValue(nsAWritableCString& aResult) {
    ..
    aResult.Assign("resulting string");
}

    ..
    nsCAutoString warning;
    GetStringValue(warning);
    WarnUser(warning.get());

手動文字列解放

static char *GetStringValue() {
    ..
    return resultString.ToNewCString();
}

    ..
    char *warning = GetStringValue();
    WarnUser(warning);
    nsMemory::Free(warning);

NS_LITERAL_STRING() を実行時文字列変換を避けるために使ってください

"Some String"のような文字列の値を unicode バッファに代入する必要があるのは共通することです。 nsString の AssignWithConversion や AppendWithConversion を使うかわりに、NS_LITERAL_STRING() を使ってください。 たいていのプラットフォームで、これはコンパイラに生の unicode 文字列でコンパイルし、直接代入するようにさせるでしょう。

間違い

nsAutoString warning; warning.AssignWithConversion("danger will robinson!");
..
foo->SetUnicodeValue(warning.get());

正しい

NS_NAMED_LITERAL_STRING(warning,"danger will robinson!");
..
// もし、'warning'文字列を使っているなら、以下のようにまだそれを使うことができます
foo->SetUnicodeValue(warning.get());

// 代わりに、直接 wide 文字列を使ってください
foo->SetUnicodeValue(NS_LITERAL_STRING("danger will robinson!").get());

命名とコードのフォーマット

注意:以下のものはすべて、絶対的なものではありません。 第三者がふとみられるようにするための暫定的なものです

ファイルやモジュールでは一般的なスタイルを使ってください。 または、他の人のなわばりにいないかどうかオーナーに聞いてください。 モジュールオーナーのルールはすべてです。

ホワイトスペース

タブを入れてはいけません。行末にホワイトスペースを入れないでください。

行の長さ

80 文字以下にしてください (Bonsai と印刷のために)。

コントロール構造

if (...) {
} else if (...) {
} else {
}

while (...) {
}

do {
} while (...);

for (...; ...; ...) {
}

switch (...)
{
  case 1:
    {
      // switch 内で変数宣言をするとき、ブロック区切りを入れてください
      int var;
    } break;
  case 2:
    ...
    break;
  default:
    break;
}

if ( のようにスペースを入れることに注意してください。特に、switch では完全な合意はありません。。。そのためのモジュールスタイルをとても頑張って探してください (^^;

クラス

class nsMyClass : public X,
                       public Y
{
public:
  nsMyClass() : mVar(0) { ... };
  
private:
  int mVar;
};

クラス宣言の中の小さな関数のために、上記のようにするのも OK です。大きな関数のためには以下のメソッド宣言に類似したものを使ってください。

メソッド

int
nsMyClass::Method(...)
{
  ...
}

行モード

ファイルには、Emacs モードの行コメントをファイルの先頭行として置いてください。 それによって indent-tabs-mode を nil にするべきです。 新しいファイルのために、これを使って、2-space インデントを定義してください。

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */

演算子

演算子で行が変わるとき、演算子は行の最後にしてください。次の行の先頭にしないでください。

接頭辞

接頭辞についての慣習に従ってください。

変数接頭辞
  • k=constant (e.g. kNC_child)
  • g=global (e.g. gPrefService)
  • m=member (e.g. mLength)
  • a=argument (e.g. aCount)
  • s=static member (e.g. sPrefChecked)
グローバル関数・マクロ・その他
  • マクロは NS_ で始め、すべて大文字とする (例、NS_IMPL_ISUPPORTS)
  • (export された) グローバル関数は NS_ で始め、LeadingCaps ルールに従う (例、NS_NewISupportsArray)

原文書の情報

 

ドキュメントのタグと貢献者

タグ: 
Contributors to this page: jezdez, saneyuki_s, Kohei, Mgjbot
最終更新者: jezdez,