変数名として「役割」を採用するかをつらつら考える
変数(や引数や属性)の名前を考える上でベースとなる手段は、今時だと基本以下のどちらか[1]だと思われる。
- オブジェクトそのものの型や性質を書く
- その文脈におけるオブジェクトの役割を書く
戦術1: 変数のデータの型や性質を書く
例としてツイートする関数を考える。
function tweet(userId: number, body: string) { … }
データの性質を使って名前の付けるのは比較的シンプルな方法。しかしこれでは書けない事もある。例えばフォロー関数を考える場合、ユーザーIDを二つ渡さないといけないので、片方は userId
を使えない。
戦術2: その文脈における変数の役割を書く
ということで、フォロー関数のような場合は役割を変数名に採用したりする。
function follow(followeeId: number, followerId: number) { … }
-
プレフィックスつけるなどの追加の命名規則がある場合や、省略形を採用する場合もあるが、とりあえず今回は考えない。 ↩︎
「データの型や性質」と「役割」を変数で伝えるべきか
状況により程度の差はあれど、変数の使用者は「データの型や性質」も「役割」も知っている必要がある。上の例で言えば、
-
follow(userId1, userId2)
… フォローするユーザーをどちらに渡せば良いのかがわからない -
follow(followee, follower)
… ユーザー情報を渡すのはわかるが、IDを渡すのか、User
オブジェクトを渡すのかはわからない
とはいえ、これらは変数名で表現しなくても伝わる事も多い。例えば、
-
tweet(userId, …)
の役割 … あえて投稿者という役割を変数で書かずとも十分伝わる - ローカル変数の役割 … もはや変数名で役割を説明するよりコードを見た方が早くて正確な場合もある
変数名で伝えきれない物
一方で、「データの型や性質」にしても「役割」にしても変数名が長くなったり変数名で十分に説明しきれないケースもある。例えば、
- データに関する細かい制約 … 管理者のユーザーIDじゃないといけないとか、25文字以内のテキストじゃないといけないとか。
- 複雑な条件に絡んでくる役割 … 関数の処理が途中で失敗した時の代替処理で使われる特殊な情報とか。
その場合は下手に凝った変数名にする事は考えず、「ドキュメントに書く」「関数やクラスを分割するなど再設計を行う」などの代替案を検討する事になる。
しかし、表現力が得られる反面、考えたりメンテしたりするコストは上がる可能性が割とある。
より低コストな代替案
1: 関数呼び出しの慣習に従う
多くの言語やプラットフォームでは「関数の第一引数には主要な対象となるオブジェクトを渡す」「コールバック関数は on〜
や handle〜
のような名前にする」みたいな文化があったりする。そのような場合はその慣習に従った方が意図が伝わりやすいし、変数名に凝る必要性も減る。
場合によっては他の言語の習慣を拝借するのも良いし、新しく作るのも良い。しかしここら辺になるとルール作りや浸透が高コストになってくる。
これより以下の代替案は基本的には慣習の一例に過ぎないとも言える。プラットフォームの文化に合ってない場合は採用しにくい。
2: 「主語」「述語」「目的語」
変更を行う関数では、多くのオブジェクト指向言語ではメソッドを活用したりする。
例えば、follow(user1, user2)
の代わりに user1.follow(user2)
と呼び出すと役割がより明白になる。その結果 followee
follower
という風に変数で役割を明記するメリットも比較的薄れる。
該当する機能が無かったりプラットフォームの文化に沿わない場合でも、引数を「主語」「目的語」の順にする習慣があるプラットフォームは多い。
3: 「過去分詞」と「名詞」
何かに対して副作用の無い加工を行う関数では、過去分詞を使う形式が多い。例えばソートする関数は sorted(array)
や array.sorted()
などとなっている事が多く、役割がより明白になる。
4: 前置詞
前置詞を活用してキーワード引数を定義したりする事で変数の役割を伝える事ができる。例えば、text1.replace(text2, text3)
の代わりに text1.replace(text2, with: text3)
と呼び出せるようにする。
とはいえこのような書き方をするために with
などを変数にする必要がある場合や、言語の文化に合わない場合は採用するのが難しい。
5: 手抜きのローカル変数名
役割を言語化し気の利いた変数名に落とし込めるために考える作業は、それなりに難易度が高い事がある。特にローカル変数はその処理の中の文脈が分かってないと十分に説明できないものもある。
その場合、変数名を考える事を諦めて一文字変数にしてしまうのも一つの手段として存在する。
また、配列の各要素に処理をする場合や、関数を引数として渡す場合に簡易的な変数が使える事も多い。 $_
とか $0
とか it
とか。
ここら辺は現場によっても受け入れられるかどうかは変わりがち。何かと宗教論争になりがちなので、うまくやっていきましょう。
個人的には許容派だけど、みんなが許容すべきと言うほどの意思はない。
より強力だけど高コストな代替案
1: コメントやドキュメントを書く
引数や属性に関しては詳細をコメントやドキュメントで補うのが、比較的場面を選ばずに使え、かつ(有用かどうかはともかく書くだけなら)難易度も低い代替案。ただし結構欠点も言われがち。
- 書かれた内容に有用な情報が無い事がある。
- 引数や属性の仕様変更に追従するコストや対応漏れ。
- 処理系のサポート(構文や型のチェック)が無い事が多い。
ドキュメントに関しては様々な意見がありそうだし、状況にもよるのだろう。個人的な意見としては「必要がないなら書かない」「書く前に他に方法がないかは考える」をベースとした上で、「他の方法がコスパ的に良くなさそうであれば素直にコメントに書く」辺りが好み。
2: 関数やクラスを整理する
変数名を付けようとしたら長くなり過ぎて難儀し出したら、関数やクラスの設計に問題無いかを考えるのは良い習慣。できるやつはみんなやっている。知らんけど。
例えば以下のようなやり方で回避できないかを考えると良さそう。
- 不要な変数を宣言していないか。
- 関数やクラスを分割する事で解決できないか。
- クラスの変数をローカル変数にしたり、変数のスコープを小さくする事で解決できないか。
ここら辺で綺麗に解決すると、変数だけの問題ではなく、バグが直ったり変更に強くなったりする事があり、そうなったらアドレナリンドバドバですよ。
どうしてもうまい設計が思いつかない時がある。その時は言語の機能をうまく使いこなせてなかったり、有効なパターンを知らないケースだったりする。設計の本を読んだり、エキスパートの意見に耳を傾けたり、あるいは他の言語やプラットフォームを参考にすると解決する事も多い。たぶんね。
あと、あまりに構造化をしすぎると冗長になる事もある。その場合は変数で表現した方が良い事も多い。ほとほどに。
3: 型でデータを保証する
今までは変数の「役割」の方に関する物が多かったが、今回は「データの型・性質」の方に関する手段。
前のツイートの例を見てみる。
function tweet(userId: number, body: string) { … }
この body
は 140 字の制限がある。しかしこの関数定義ではそれは伝わらない。
ではデータを詳細に書くと良いのか?
function tweet(userId: number, bodyTextOf140charLimit: string) { … }
流石に厳しい命名だ。そこで Body
型を導入する。
class TweetBody { text: string }
function tweet(userId: number, body: TweetBody) { … }
こうすると、渡さなければいけないデータは明確になる。しかも TweetBody
の生成時に 140 字のチェックを行うように定義すれば、140字以内に収まっている事が保証できる。
一方で、属性が一つしかない型をたくさん定義する事は何かとコストがかかる。一々定義するコストもそうだし、パフォーマンスも下がるかもしれない。
個人的にはメリットを実感する事が多いので積極的に採用する派だが、採用する人は比較的少ないので、チームで採用する際にはうまくやっていきましょう。
どうすると良いのか?
色々整理した結果、個人的にはこれが好み。
「役割」を敢えて伝える必要がなくても伝わるなら考える必要はない
tweet(userId)
でも tweet(authorId)
でも実質的な違いはほとんど無い。どちらでも良い。恐らく先に思いつくのは多くの場合は前者なので、それを採用する。
とは言え tweet(id)
とか tweet(u)
は流石にちょっと何を渡せば良いのかわからないので、データの制約はわかるようにしておいた方が良い。
変数の役割を伝える選択肢の一つとして名称を考える
「userId
じゃ通じないな、引数の順番を工夫しても不安だな」と思ったら(有力な)代替案の一つとしてその役割を表す変数名を考える。