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

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

boolとgetter/setterの命名規則

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; }

  // [G4a] G1a と G3a の中間。メソッド名先頭は大文字。
  bool GetAvailable() const { return m_isAvailable; }

  // [G4b] G1b と G3b の中間。メソッド名先頭は小文字。
  bool getAvailable() 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 available) { m_isAvailable = available; }

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

  // [S4a] S1a と S3a の中間。メソッド名先頭は大文字。
  void SetAvailable(bool isAvailable) { m_isAvailable = isAvailable; }

  // [S4b] S1b と S3b の中間。メソッド名先頭は小文字。※ JavaBeans はコレ。
  void setAvailable(bool isAvailable) { m_isAvailable = isAvailable; }
};

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

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

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

の組み合わせだと思いますが*1*2*3、自分は接頭辞を付けない [G3], [S3] を好みません。

接頭辞を付ける場合と付けない場合を比較したとき、付けないほうが一見英語(自然言語)的に自然な形で記述できることがあるのは確かですが、名前をグローバル検索しづらい、Visual C++Intel C++コンパイラーのプロパティ拡張構文(後述)と相性が悪い、などの弊害があります。Get/Setを機械的に付けることで、正規表現での検索・マッチングも可能となります。GetIs~は「~かどうかを取得する」、SetIs~は「~かどうかを設定する」、という意味に解釈すれば、それほど違和感はないと思っています。

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

ちなみにboolだからといって、無理にいつもisで始めるというのはよろしくないです(そんなことをしてしまうと、結局ハンガリアンと大差ない)。
例えば以下のように、英語的に自然な意味を持ち、理解しやすいように命名します。

bool m_isIdling; // 待機状態であるか否かを示すフラグ。
bool m_isHudVisible; // HUD (Head-up Display) が画面上に表示されているか否かを示すフラグ。
bool m_configFileExists; // 設定ファイルが存在するか否かを示すフラグ。
bool m_savesPersonalInformationOnExit; // 終了時に個人情報を保存するか否かを示すフラグ。
bool m_shouldSavePersonalInformationOnExit; // 一般動詞の三単現の代わりに助動詞を使うパターン。
bool m_hasMasterSword; // マスターソードを持っているか否かを示すフラグ。
bool m_hasFlyingAbility; // 空を飛ぶ能力を持っているか否かを示すフラグ。
bool m_canFly; // 空を飛べるか否かを示すフラグ。一般動詞の三単現の代わりに助動詞を使うパターン。
bool m_isUserInputEnabled; // ユーザー入力が可能となっているか否かを示すフラグ。
bool m_acceptsUserInput; // ユーザー入力を受け付けるか否かを示すフラグ。be動詞の代わりに一般動詞の三単現を使うパターン。
bool m_buttonEnabledStates[AllButtonsCount]; // ボタンが有効になっているか否かを示す状態フラグの集合。

三単現のsやesを付けた一般動詞や、助動詞(canやshould)をうまく使います。be動詞の後は名詞・形容詞・分詞(過去分詞や現在分詞)が来ます。単数形の場合はisですが、複数形の場合はareを使うこともあります*4
オブジェクト自身の状態を表すフラグではなく、何らかの特性XがYであるかどうかを表すフラグの場合、疑問文「Is X Y?」または「Are X Y?」の形で命名します*5

これらを無理に一律isで始めるヘンテコな名前にしてしまうと、おそらくネイティブ英語圏プログラマーから失笑を買うような意味不明なコードとなるでしょう。ちなみに enable / disable や exist という単語は形容詞ではなく一般動詞です。英語が苦手な日本人が書いたと思われるコードにおいて、特に誤用が目立つ単語なので、心当たりのある人は注意しましょう*6。また、他動詞/自動詞の違いによって、目的語をとることができるかどうかも変わってくるため、変数名の一部に動詞を使う場合はよく検討する必要があります。

自分自身、あまり偉そうなことを言えるほど英語が得意なわけではありませんが、概して日本人は中学・高校で英文法を6年間も学んでいるはずなのに前置詞や文型の誤用が多く、特にin, at, on, of, from, to, as, with, byなどを適切に使い分けることが苦手な傾向にあります*7。些細なことに思えるかもしれませんが、英語として間違っている、あるいは不自然な命名をすると、ただ分かりづらい(可読性が下がる)だけでなく、最悪の場合誤解によるバグを招くこともあるので、単純に個々の単語の意味を知っているだけでは不十分であり、具体的な例文や用法を参考にして近いものを選ぶべきです。たとえ日本人であっても、もともとネイティブ英語圏で開発されたJavaや.NETの標準クラスライブラリなどを普段から使い込んで参考にしていれば、頓珍漢なJapanese-Englishによるオレ様ネーミングをしてしまうようなミスは随分減らせるはずです。一方、日本人が書いたコードを参考にして命名すると負のスパイラルに陥るので、日本人が書いたコードは参考にしてはいけません。

なお、自分の場合、上記の例のようなフィールド群に対しても、getter/setterはフィールド名にそのままGet/Setを機械的に付ける形にして、対称性を保つようにしています。

getter/setterは誰のものか?

ちなみになぜかGetという接頭辞が付いているのにオブジェクトの内部状態を変えるメソッドを定義していたり、Setという接頭辞が付いているのにオブジェクトの状態を変更しないどころか戻り値で状態を返すようなわけのわからないメソッドを定義していたり、という非常識極まりないコードを見かけたことがありますが、これは一般的なオブジェクト指向プログラミング(以下OOP)における命名原則に対する理解の欠如から始まっているように見受けられます。

class Car
{
  int m_fuel;
public:
  Car() : 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(); // クルマが大破する。
}

上記コードで、主語が「my car」のような単数のオブジェクトなのであれば、英語的に自然となるためには動詞も三単現でなければならないので、Runs()Crashes()にしたほうがよさそうに思えます。ですが、オブジェクトが実際に「走る」「大破する」といった動作をする場合、そのメソッドの名前は一般的に動詞の原形で始めます。もし三単現のsを付けてしまうと、前述のように「走るか否か」「大破するか否か」といったbool値を返すメソッドになってしまいます。
実はOOPにおいて、動詞の原形で始まるメソッド名というのは、「オブジェクトに対する命令/指令」を意味します。つまり、上記の例における「myCar.Run()」というメソッド呼び出しコードは、英語で言うと「My car, run!」(クルマよ、走れ!)という命令文になっているわけです。

さて、同じように動詞の原形で始まっているGetFuel()SetFuel()はどうでしょうか?
もしここで、myCarがただの主語であると考えると、

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

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

しかし実際は逆です。つまり、

  • GetFuel() はmyCarが「自分自身から」燃料残量データを得る(取得して外部に返す)
  • SetFuel() はmyCarが「自分自身に対して」燃料残量データを設定する(外部から渡す)

という意味を持たせ、そのように実装するのが、C++/Javaオブジェクト指向における正しいgetter/setterの在り方です。

あるいは、以下のように考えることもできます。

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

つまり、getter/setterについてはそのメソッドが属するオブジェクトのものというよりはむしろ、メソッド呼び出し側が主語(所有者)であるような特殊メソッドと考えることもできます。

ちなみにC++の場合、非静的メンバー関数(インスタンスメソッド)の第1引数には暗黙的にthisポインタを受け取ります。他のオブジェクト指向言語も、.演算子を使ってあたかもオブジェクトからメソッドを実行しているように見えていても、内部的にはそのような実装にコンパイラが変換します。
もしCでオブジェクト指向的に書き下すとすると、以下のような実装になります。

struct Car
{
  int m_fuel;
};
int Car_GetFuel(const Car* this)
{
  assert(this);
  return this->m_fuel;
}
void Car_SetFuel(Car* this, int fuel)
{
  assert(this);
  this->m_fuel = fuel;
}

インスタンスメソッドではなく、クラスメソッド(staticメソッド)においても、引数オブジェクトに対するgetter/setterは、上記のルールに基づいて命名されていることが多いです。

従来のWindows API (Win32 API) も、関数インターフェイスはCですが、命名OOPに基づいています。HWNDHDCといったハンドルは不透明なオブジェクトへのポインタであり、これらを引数に受け取って状態の取得・設定や各種操作を実行します。

プロパティ構文について

C#にはプロパティという機能がありますが、これはメソッド呼び出しをあたかもフィールドへの直接アクセスであるかのように見せかける構文です。ただしgetter/setterメソッドを直接呼び出すことはできず、コンパイラが暗黙的にメソッドを呼び出すコードを生成します。プロパティはGUIプログラミング(RAD)と相性が良く、もともとDelphiVBでもサポートされていました。

プロパティの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)); }
  }
}

前述のWin32 APIではウィンドウを無効化するときもEnableWindow()を使って引数にFALSEを渡す必要があるなど、微妙に名前が直感的ではありませんでしたが、WPFSystem.Windows.Windowはメソッドのほかにプロパティによって直感的な操作ができる設計になっています。
一部はSystem.Windows.UIElementなどから継承したプロパティとなっています。

Win32 APIGetForegroundWindow()に相当する操作は直接用意されていませんが、Application.Current.WindowsWindow.IsActiveを組み合わせることで所望のウィンドウを取得できます。

Visual C++Intel C++であれば、下記のようなプロパティ拡張構文を使って、C#ライクなプロパティを作成することができます。内部的にはコンパイル時にメンバー関数呼び出しに置き換えられるだけですが、C#と異なるのはgetter/setterの名前をプログラマーが明示的に指定してやる必要があるということです。ただの糖衣構文なのでメンバー関数を直接呼び出すこともできるし、メンバー関数へのポインタも普通に取得できます。

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#のプロパティのように扱えるようにしてくれます。

ただしこの拡張構文はEmbarcadero RAD Studio(旧Borland C++Builder)のC++プロパティとは互換性がなく、また標準C++規格ではプロパティ構文をサポートしていないので注意が必要です。

C++/CLIC++/CXのプロパティは、どちらかというとC#に近い構文で、こちらも他の拡張構文とは互換性がありません。

*1:Javaでは、java.nio.ByteBufferのposition()やorder()のように、getter/setterを同じ名前にしてオーバーロードで実装しているケースもあります。

*2:C++/WinRTでは、プロパティのgetter/setterは同じ名前の関数オーバーロードとなります。Move to C++/WinRT from C# - UWP applications | Microsoft Learn

*3:Objective-Cでも、形容詞の場合はプロパティ名の "is" を省略し、getterには "is" を付ける、というスタイルがあるようです。Naming Properties and Data Types - Coding Guidelines for Cocoa

*4:PexAssert.AreEqual Method (Microsoft.Pex.Framework) | Microsoft Learn

*5:AndroidLocationManager.isLocationEnabled()メソッドや、WPFUIElement.AreAnyTouchesCapturedプロパティなど。

*6:こういった初歩的な品詞の間違いを犯すのも日本人だけです。ただし、たとえ日本人でも、JavaFile.exists()メソッド、Boostのboost::filesystem::exists()関数、ATLのCPathT::FileExists()メソッド、.NETのFile.Exists()メソッドなど、比較的モダンなライブラリに慣れていれば、exist という単語の品詞と使い方を間違えることはないはずです。

*7:日本語は英語と比べると語彙が貧弱な言語で、特に同じ助詞に複数の意味やニュアンスを持たせるような曖昧な言語であることが、日本語を母語とする人にとって英語の前置詞を習得する妨げとなっているそうです。おそらく日本語と英語の間の機械翻訳の品質がなかなか向上しない原因もそのあたりにあります。前置詞に弱い日本人 | 福井県立大学