ヨーダ命名規則のススメ【コーディング/プログラミング】

マイナー・コーディングスタイル・シリーズの完結編です。

今回オススメする命名規則は、名前空間をサポートした言語(C++, Swift)や、メソッドをサポートしたオブジェクト指向言語(Java, JavaScript)ではあまりメリットのない命名規則ですが、関数を主体としたC言語やLisp等の言語では比較的有益な命名規則となります。

従来の書き方

void BeginTransaction();
void EndTransaction();
bool _isInTransaction;
int  GetDefaultAnimationDelay();
void SetDefaultAnimationDelay(int value);
long EventGetModifierFlags(Event e);
long EventSetModifierFlags(Event e, long flags);
Color RedColor();
Color BlueColor();

今回推奨する書き方

void TransactionBegin();
void TransactionEnd();
bool Transaction_isIn;
int  DefaultAnimationDelayGet();
void DefaultAnimationDelaySet(int value);
long EventModifierFlagsGet(Event e);
long EventModifierFlagsSet(Event e, long flags);
Color ColorRed();
Color ColorBlue();
#define NAMESPACE_BEGIN(a, b) namespace a { namespace b {
#define NAMESPACE_END(a, b) }}
#include "extensions_string"
#include "extensions_vector"

一般的な「動詞 + 目的語」とは違い、「目的語 + 動詞」と言う形式で命名する点が特徴です。また関連する複数の名称における共通部分が左辺側に記述・集約されている点も特徴的です。

一般的な表現: I play baseball.
ヨーダ語: Baseball, I play.

目次

動詞が見つかりやすい

関数本来の目的であるGetSet, Postという部分が常にシンボル名の末尾に書かれているため、規則性や直感性に優れているという特徴があります。

int flags = EventModifierFlagsGet(f);
if (flags & EventModifierFlagsGet(e)) {
  EventModifierFlagsSet(e, flags);
}
EventTransactionBegin(EventLoopMain(), e);
EventPost(e);
EventTransactionEnd(EventLoopMain(), e);

このようにGetSet, Postという処理の目的の部分が常に関数呼び出し演算子()の隣に書かれているという点が特徴的です。

検索がしやすい

機能名を後置するこれらのスタイルは、コード検索がし易いという特徴があります。プレフィックスの共通部分がある種の名前空間やカテゴリーのような役割をしているためです。

以下のコードでEvent関連のセッターとゲッター関数のみを抽出したいとなった場合、文字列eventmodifierflagsとタイプするだけで簡単に目的の関数群を抽出できます。

int modifierFlags = EventModifierFlagsGet(e);
EventModifierFlagsSet(e, modifierFlags);
WindowModifierFlagsSet(e, modifierFlags);

従来の命名規則を採用した場合は正規表現を用いて/event...modifierflags/iと検索する必要があります。

int modifierFlags = EventGetModifierFlags(e);
EventSetModifierFlags(e, modifierFlags);
WindowSetModifierFlags(e, modifierFlags);

正規表現を用いずにmodifierflags(と検索することも可能ですが、その場合はWindowGetModifierFlagsが余計にマッチしてしまいます。

規則性のあるコードが生まれる

コード内に共通部分が生まれるため、コードの規則性を見出しやすくなります。

switch (n->kind()) {
  case .If:
    auto m = static_cast<StmtIf<Node>*>(n);
    ...
  case .Call:
    auto m = static_cast<StmtCall<Node>*>(n);
    ...
  case .Number:
    auto m = static_cast<LitrNumnber<Node>*>(n);
    ...
  case .String:
    auto m = static_cast<LitrString<Node>*>(n);
    ...
}

上記のコードにはauto m = static_cast<Stmt...というグループとauto m = static_cast<Litr...という二つのグループが存在することが直感的に理解できます。

一般的な命名規則を採用してしまうと、上記のような規則性や法則性は生まれないため、以下のように、コメントを用いて明示的なグルーピングを行う必要が出てきます。

switch (n->kind()) {
  // ここから先ステートメント関係の処理
  case .If:
    auto m = static_cast<IfStmt<Node>*>(n);
    ...
  case .Call:
    auto m = static_cast<CallStmt<Node>*>(n);
    ...
  // ここから先リテラル関係の処理
  case .Number:
    auto m = static_cast<NumnberLitr<Node>*>(n);
    ...
  case .String:
    auto m = static_cast<StringLitr<Node>*>(n);
    ...
}

コードそのものに規則性が生まれるやすくなるという点も本命名規則の特徴です。

メソッドの命名規則として採用する場合

名前空間やメソッドを採用した言語で本命名規則を採用するのも悪くないと思います(職場での利用はオススメしません)。

// Before
int  getValue();
void setValue(int value);
Font boldFont();
Font italicFont();
Color boldColor();
Color italicColor();
// After
int  valueGet();
void valueSet(int value);
Font fontBold();
Font fontItalic();
Color colorBold();
Color colorItalic();

ただしテキストエディタやIDEのサジェスト機能や検索機能の使い勝手が大きく変わってしまいます(getVsetVetVによる絞り込みとvalueGvalueS, /ue(G|S)/による絞り込みでは労力が全く異なる点に注意)。

スネークケースとの組み合わせ

スネークケースとの組み合わせもオススメです。カテゴライズの実現が可能となります。

class Theme {
  Color color_Heading();
  Color color_Bold();
  Color color_Italic();
  Font font_Bold();
  Font font_Italic();
}

// Before
void selectNextWindow();
void selectPreviousWindow();
// After
void selectWindow_next();
void selectWindow_previous();

enum StringEncoding {
  StringEncoding_ASCII,
  StringEncoding_UTF8,
  StringEncoding_SJIS,
};

この手の書き方はあまり見かけないため正式な名称があるかどうかは分からないのですが、とりあえず「ハイブリッドケース」や「ミックススネークケース(Mixed_snakeCase)」等と呼ぶと良いのではないかと思います。

なお後置法による命名規則は英文法に慣れた人達にとっては違和感がありますが、スネークケースを組み合わせることでこの違和感を解消できます。

// 慣れないと違和感がある
class Statement {}
class StatementIf    extends Statement {}
class StatementFor   extends Statement {}
class StatementWhile extends Statement {}

// とりあえず意図は理解できる
class Statement_if    extends Statement {}
class Statement_for   extends Statement {}
class Statement_while extends Statement {}

size_t  CString_size(CString);
CString CString_substringFromIndex(CString, size_t);
CString CString_substringToIndex(CString, size_t);

名前空間やグループ/カテゴリーの区切りをアンダースコアで表現する点が本イディオムの特徴です。

備考

本当はこのような書き方が出来れば良いのですが、現代のプログラミング言語でもこの手の機能はサポートされていないようです。

class Eclipse {
  /* Before */
  // Color color_Heading();
  // Color color_Bold();
  // Font font_Heading();
  /* After */
  colors {
    Color heading();
    Color bold();
  }
  fonts {
    Font heading();
    Font heading(int);
  }
}

var t = new Theme.Eclipse();
Color bold = t.colors.bold();
Font fontH1 = t.fonts.heading();
Font fontH2 = t.fonts.heading(2);

なお現在では同等のことを連想配列を活用することで体現できます。

var t = ThemeStore("Eclipse", {
  colors: {bold: Color.black, italic: Color.gray},
  fonts:  {bold: Font.bold  , italic: Font.italic},
})
assert(t.colors.bold == Color.black)
広告