読者です 読者をやめる 読者になる 読者になる

syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

boolとgetter/setterの命名

プログラミングTips C++ C#

C++命名規則に関する話です。

普通getter/setter(いわゆるアクセッサ)であるようなメソッド名(メンバー関数名)には接頭辞にGet/Setもしくはget/setを付け、その後に内部フィールド名(メンバー変数名)などを続けるような形で命名すると思いますが、boolを返すgetterメソッド名、boolを受け取るsetterメソッド名をどうしていますか?
いくつかパターンがあると思います。

class SomeClass
{
  bool m_isAvailable;

public:
  // [G1a] 接頭辞を付ける。メソッド名先頭は大文字。
  bool GetIsAvailable() const { return m_isAvailable; }

  // [G1b] 接頭辞を付ける。メソッド名先頭は小文字。
  bool getIsAvailable() const { return m_isAvailable; }

  // [G2] COM プロパティ風。
  bool get_IsAvailable() const { return m_isAvailable; }

  // [G3a] 接頭辞を付けない。メソッド名先頭は大文字。
  bool IsAvailable() const { return m_isAvailable; }

  // [G3b] 接頭辞を付けない。メソッド名先頭は小文字。※ JavaBeans はコレ。
  bool isAvailable() const { return m_isAvailable; }

public:
  // [S1a] G1a と同じように接頭辞を付ける。メソッド名先頭は大文字。
  void SetIsAvailable(bool isAvailable) { m_isAvailable = isAvailable; }

  // [S1b] G1b と同じように接頭辞を付ける。メソッド名先頭は小文字。
  void setIsAvailable(bool isAvailable) { m_isAvailable = isAvailable; }

  // [S2] COM プロパティ風。
  void put_IsAvailable(bool isAvailable) { m_isAvailable = isAvailable; }

  // [S3a] G3a と同じように接頭辞を付けない。オーバーロードを定義する。メソッド名先頭は大文字。
  void IsAvailable(bool isAvailable) { m_isAvailable = isAvailable; }

  // [S3b] G3b と同じように接頭辞を付けない。オーバーロードを定義する。メソッド名先頭は小文字。
  void isAvailable(bool inIsAvailable) { m_isAvailable = inIsAvailable; }
};

自分が採用している命名規則は [G1a] と [S1a] の組み合わせです。ちなみにメソッド名の先頭はC#同様に大文字で始めます。publicだろうがprivateだろうが関係ありません。JavaJavaScriptメソッド名の先頭を小文字で始めるのが慣例となっているのですが、自分の好きな言語はJavaなんぞではなくC#なので、命名規則もおのずとC#風(Microsoft .NET 命名規則)になることが多いです。

ちなみにJavaでbooleanのアクセッサに対してよく採用されるのは、

  • field = available
  • getter = isAvailable()
  • setter = setAvailable()

の組み合わせだと思いますが、自分は接頭辞を付けない [G3], [S3] を好みません。
接頭辞を付ける場合と付けない場合を比較したとき、付けないほうが一見英語(自然言語)的に自然な形で記述できることがあるのは確かですが、名前をグローバル検索しづらい、Visual C++Intel C++コンパイラーのプロパティ拡張構文(後述)と相性が悪い、などの弊害があります。Get/Setを機械的に付けることで、正規表現での検索・マッチングも可能となります。

SomeClass theObject;
...
if (theObject.GetIsAvailable()) // 英語的には不自然。だがそれがいい。
{
  ...
}
if (theObject.IsAvailable()) // 英語的に自然。(if the object is available...)
{
  ...
}

ちなみにboolだからといって、無理にいつもisで始めるというのはよろしくないです(そんなことをしてしまうと、結局ハンガリアンと大差ない)。
例えば、

bool m_isUserInputEnabled; // ユーザー入力が可能となっているか否かを示すフラグ。
bool m_isIdling; // 待機状態であるか否かを示すフラグ。
bool m_isHudVisible; // HUD (Head-up Display) が画面上に表示されているか否かを示すフラグ。
bool m_configFileExists; // 設定ファイルが存在するか否かを示すフラグ。
bool m_savesPersonalInformationOnExit; // 終了時に個人情報を保存するか否かを示すフラグ。
bool m_hasMasterSword; // マスターソードを持っているか否かを示すフラグ。
bool m_canFly; // 空を飛べるか否かを示すフラグ。

のように、英語的に自然な意味を持ち、理解しやすいように命名するのがよいでしょう。isだけでなく、三単現のsを付けた一般動詞や、助動詞canをうまく使います。これらを無理に一律isで始めるヘンテコな名前にしてしまうと、おそらくネイティブ英語圏プログラマーから失笑を買うような意味不明なコードとなるでしょう。場合によっては過去分詞や現在分詞を使うのも有効です。ちなみにEnable/DisableやExistという単語は形容詞ではなく一般動詞です。英語が苦手な人が書いたと思われるコードにおいて、特に誤用が目立つ単語なので、心当たりのある人は注意しましょう*1
もちろん、上記のフィールド群に対しても、getter/setterはフィールド名にそのままGet/Setを付ける形にして、対称性を保ちます。

getter/setterは誰のものか?

ちなみになぜかGetという接頭辞が付いているのにオブジェクトの内部状態を変えるメソッドを定義したり、Setという接頭辞が付いているのにオブジェクトの状態を変更しないどころか戻り値で状態を返すようなわけのわからないメソッドを定義している人をたまにみかけますが、これは他の一般動詞の原形で始めるようなメソッドとは違う、C++Javaといったオブジェクト指向言語におけるgetter/setterというものの特殊性に対する理解の欠如から始まっているように見受けられます。

class Car
{
  int m_fuel;
public:
  void Run(); // 実装は省略。
  void Crash(); // 実装は省略。
public:
  int GetFuel() const { return m_fuel; }
  void SetFuel(int fuel) { m_fuel = fuel; }
};

void TestCarClass()
{
  Car myCar;
  myCar.SetFuel(100);
  myCar.Run(); // クルマが走る。
  const int fuel = myCar.GetFuel();
  myCar.SetFuel(fuel + 1000);
  myCar.Crash(); // クルマが大破する。
}

上記コードで、Run()メソッドとCrash()メソッドは、主語がmyCarであると考えることができるため、英語的にも自然に見えるので、その挙動を想像しやすいでしょう。これがまさにオブジェクト指向の主旨ですが、GetFuel()とSetFuel()はどうでしょうか? もしRun(), Crash()同様に考え、myCarが主語であると考えると、

  • GetFuel() は「myCarが」(外部から)燃料残量データを得る、というものだろう
  • SetFuel() は「myCarが」(外部に対して)燃料残量データを設定する、というものだろう

誤解してしまうのではないでしょうか(おそらくこの誤解が前述のような意味不明なメソッド実装をする人達を生み出すものと思われます)。
しかし実際は逆です。つまり、

  • GetFuel() は「メソッドを呼び出した側が」myCarから燃料残量データを得る
  • SetFuel() は「メソッドを呼び出した側が」myCarに対して燃料残量データを設定する

という意味を持たせ、そのように実装するのが、C++/Javaオブジェクト指向における正しいgetter/setterの在り方です。つまり、getter/setterはそのメソッドが属するオブジェクトのものというよりはむしろ、メソッド呼び出し側が主語(所有者)であるような特殊メソッドと考えるべきでしょう。

プロパティ構文について

C#にはプロパティという機能がありますが、get/setキーワードの使い方はやはり前述のgetter/setterの考え方に基づいています。

class CSharpCar
{
  int _fuel;
  const int FuelCapacity = 100;
  public int Fuel
  {
    get { return this._fuel; }
    set { this._fuel = Math.Max(0, Math.Min(FuelCapacity, value)); }
  }
}

Visual C++Intel C++であれば、下記のようなプロパティ拡張構文を使って、C#ライクなプロパティを作成することができます。内部的にはコンパイル時にメンバー関数呼び出しに置き換えられるだけですが、C#と異なるのはgetter/setterの名前をプログラマーが明示的に指定してやる必要があるということです。ただしこの拡張構文はEmbarcadero RAD Studio(旧Borland C++ Builder)のC++プロパティとは互換性がなく、また標準C++規格ではプロパティ構文をサポートしていないので注意が必要です。なおC++/CLIC++/CXのプロパティは、どちらかというとC#に近い構文で、こちらも他の拡張構文とは互換性がありません。

class Car
{
  int m_fuel;
public:
  int GetFuel() const { return m_fuel; }
  void SetFuel(int fuel) { m_fuel = fuel; }
  __declspec(property(get=GetFuel, put=SetFuel)) int Fuel;
};

ちなみにVisual C++の#importディレクティブはこのプロパティ拡張構文を使用してラッパーコードを自動生成し、C++においてもCOMのプロパティをVBC#のプロパティのように扱えるようにしてくれます。

*1:他動詞/自動詞の違いによって、目的語をとることができるかどうかも変わってくるため、変数名の一部に動詞を使う場合はよく検討する必要があります。