命名について;特にPHPにおける命名について
単語
単語に関して明確なルールを作成する。用語集を作成し、単語にプロジェクト固有の意味を込める。
用語集に定義されていない単語は一切使わず、必ず逐一定義を決めてから使うようにする、といったくらい厳密にするのが好ましい。しかし現実的には、効果に見合わない手間だと感じる人も多数存在するのかもしれない。
この記事では、英語としての妥当性を追求しない立場をとる。プロジェクト内で意味が気になる単語が現れたとき、参照すべきは辞書ではなく、用語集であるべきとする。デメリットとしては手間が生じること、英語力の高いひとが読んだとき齟齬が生じやすくなること。メリットとしては英語力の低い人が読んだとき齟齬が生じづらくなること、誰が読んでも同じ理解に/誰が記述しても同じコードになる可能性が高まること。
この記事で例示されているコードブロック内での命名についても、英語として妥当なのかそもそも自信があまりない
名詞について
PHP を用いているのならばサーバサイドの開発を行っているチームであることが多いだろうが、ここではそうだとして話をすると、サーバサイドのメンバーにおける共通認識としての用語集を作成する。サーバサイドを境界づけられたコンテキストとすると、そのコンテキスト内で有効なユビキタス言語を適切に定義していくとも言える。
たとえば、レイドバトルという仮の名称で開発が初まり、サーバサイドでは RaidBattle
という英名を割り当てたとする。開発が後半になって、異なる正式名称;ここでは協力戦と決定されたとしても、サーバサイドの記述をすべてそう置換するということはおそらくしないだろう。
ユーザに公開される名称としての協力戦と、サーバサイドにおけるレイドバトル ( RaidBattle
)
の変換表;つまり用語集を適切に作成・保守しておけば、メンバーが忘れてしまったときも新規メンバーがアサインされたときも、混乱を少なくすることができる。
動詞について
日本人が複数人で開発しているプロジェクトにおいて、英語の微妙なニュアンスに忠実な動詞を、その時々で適切に選択するというのは、現実的に考えると困難であるように思われる(もちろん全く無視するのも望ましくなく、原義に近付けるよう努力すべきではある)。
そこで、動詞についても名詞と同じように、その単語に固有の意味を与えて用語集をつくり、明確な区別に従って使い分けることをする。これには大別して 2 つの作用がある。 1 つは、プロジェクトで使用される動詞の種類を限定すること。1 つは、似たような訳を持つ動詞に異なる明確な意味を与えること。
依存パッケージで頻出して紛らわしいので使用を避けるなどということはあり得るが、一概にこの単語の使用を避けるべきといった意見は適当でないように思われる。避けるべきはプロジェクト内で明確な意味が与えられていない単語であり、明確な意味が与えられているのならば避ける必要は通常ないはずである。
📝
- https://gist.github.com/maxtruxa/b2ca551e42d3aead2b3d
- https://php-archive.net/php/words-in-function-names/
- https://dev.to/maikomiyazaki/beginner-s-cheat-sheet-commonly-used-verbs-for-naming-functions-methods-and-variables-509i
その他;形容詞などについて
同様に用語集として管理するほうが好ましい。
複数形、およびデータ構造に付随する接尾辞について
ある要素を複数もつ要素について、複数形で命名するのは以下の理由で好ましくない。
-
data
など複数形として用いられることが一般化している単語があること -
information
など単複同型の単語があること - どうしても適当な訳語が見つからず、ローマ字や造語で単語を定義しなければならない場合があること
そこで、そういった要素に使用する接尾辞を定義する。データ構造によって使い分けることが好ましい。以下に例示する。
接尾辞 | 意味 |
---|---|
Dict ( Dictionary ) Map Hash |
key が文字列である array ( 連想配列 ) |
List | key が数値である array |
Set | key が数値であるが、順序に意味がない array |
そのほか、たとえば laravel の Collection 型が格納されているのならば接尾辞として Collection
を使用する、Stack 型ならば Stack
を使用する、などするのも良い。
関数
命名について
原則として、動詞からはじめる。このとき用いる動詞は、上述したように用語集を参照すること。
bool を返す関数について
[ 命名 boolean ] 🔍 などすると記事が大量に見つかるので、それらを参考にプロジェクト内のルールを決める。
日本人が複数人で開発しているプロジェクトにおいては、英語としての妥当性をより放棄する方法も考えられる。 たとえば、bool を返す副作用のない関数はすべて is
+ 形容詞にするというルールにし、妥当な形容詞がなければ startable などと -able のような接尾辞を動詞につけて無理矢理に形容詞化してしまう。メリットとしては、必然的に動詞と対応関係になるため意味を推察しやすくなる。デメリットは上述した通り。
PHPUnit では厳密でない比較を assertEquals, より厳密な比較を assertSame とするのに合わせて、厳密なら isSameAs, そうでないならば isEqualTo とするもの驚きが少なくなりそうで好ましい。
例外的な命名
- 状態をもつオブジェクトからプロパティの値を取得する getter について、get を省略しプロパティ名を命名する
- 副作用のない変換するメソッドについて、たとえば
A
に変換するのならばtoA
と前置詞からはじめる
なぜ ( PHP の ) 関数名は長くなるのか?
長くなること自体に問題があるわけではないが、大抵は何らかの好ましくない原因によって長くなっている。
型付けする文化がない?/言語仕様的に型付しづらい?
interface Example
{
public function getBy(Foo $foo): Example;
}
と記述することをせずに、array のまま以下のように扱うことが多いように思われる。
interface Example
{
public function getByFoo(array $foo): Example;
}
この Foo
のように、構造体をクラスなどとして明示することが徹底されることは少ないだろう。
型やデータ構造の概要を、メソッド名によって表現しようという文化があるように思われる。とはいえ型付けされていないのならば、無理に短くするより、長くなってもメソッド名でそれを表現してくれた方が好ましい。
ロジックを記述するところが適当でない?
home 画面に戻ったとき、ユーザの何らかの時間を加算する仕様であったとして
// トランザクションスクリプト(手続き型)
HomeService::addAnyTimeOfUser(User $user, \DatetimeInterface $datetime): void
// ドメインモデル
Home::addAnyTimeOfUser(User $user, \DatetimeInterface $datetime): void
みたく home 画面のほうに引っ張られた記述をしてしまうと、User
を明示する必要が生じる。
// トランザクションスクリプト
UserService::addAnyTime(User $user, \DatetimeInterface $datetime): void
// ドメインモデル
User::addAnyTime(\DatetimeInterface $datetime): void
User
が対象だと明らかなクラスに記述すれば、目的語を省略することもできる。
一概にどちらが良いと断ずることはできないかもしれないが、ひとつの指針にはなる。
1 つのクラスに public なトランザクションスクリプトを複数記述している?
interface UserServiceInterface
{
public function addStamina(User $user, int $stamina): void
public function addAnyTime(User $user, \DatetimeInterface $datetime): void
}
などと 1 つのクラスに public なトランザクションスクリプトを複数記述すると、クラスは抽象的な命名になり関数は具体的な命名となる。
interface AddStaminaToUserInterface
{
public function execute(User $user, int $stamina): void
}
と public なトランザクションスクリプトを 1 つにすれば、クラスは具体的な命名になり関数は抽象的な命名となる。
1 つの関数で複数のことをしている?
User::addStaminaAndAnyTime(int $stamina, \DatetimeInterface $datetime): void
というのは分割可能であろう。
オーバーロードがない
interface Example
{
public function getBy(Foo $foo): Example;
public function getBy(Bar $bar): Example;
}
と記述することは言語仕様的にできないので、実際は以下のようにするだろう;
interface Example
{
public function getByFoo(Foo $foo): Example;
public function getByBar(Bar $bar): Example;
}
Discussion