はじめに
私はエンジニアを目指して勉強している学生です。前回「可読性とは何だろうか」という記事を公開た勢いで、再び記事を書こうと思います。
前回の「可読性とは何だろうか」に引き続き、全くの実務未経験の考えですので、「ここおかしいんだが?」「そんなわけない」等ありましたら、ぜひ温かい目での指摘をお願いします。
なお、本文のコード例はUnity/C#です。
対象者
- プログラミング初学者
- 命名をあまり考えたことがない人
- 命名を適当に済ませてしまう人
前提知識
設計
設計とは簡単に言うとコードがごちゃごちゃしないようきれいに書くことと、そのコードをうまく組み合わせて一つのシステムを作る計画をすることを言います。ピタゴラスイッチを考えていただくと良いかと思います。別々の装置をうまく組み合わせて「ピタッゴラッスイッチ♪」を目指します。これを一つの装置で作ろうとしてしまうとどう考えても難しすぎます。大きなピラミッドでさえ、構成する石は比較すると小さいものです。
SOLID原則
SOLID原則とは、良い設計の指針です。単刀直入に言えば初学者で手を出してよいものだとは思いませんが、できるだけ初心者が意識しやすいレベルの行いがのちにSOLID原則へつながる、というスタンスで書いています。
命名と設計
命名について深く考えたことはありますか?
コーディングにおいて命名は切っても切り離せませんが、無意識のうちに行うことも多いと思います。現に様々な試験のコード、もしくは疑似コードで a, b, c はもちろん、 flg, temp などおおよそ命名とは言えない名前を見かけることがあります。エンジニアというものは初めて見たものを親と思う習性があるので、そのような命名を自らのコードに施してしまうことがあるかもしれません。
しかし命名は、実は設計にもかかわるプログラミングの重要な要素の一つです。設計とまではいかずとも、スパゲッティコードを未然に防いでくれるし、将来設計を勉強するときに助けてくれます。命名をおろそかにしてしまってはきっと後悔してしまうでしょう。
命名と設計が関わる観点は3つあります。
- 名前の意味
- 過剰な意味
- 不明な意味
それでは一つずつ見ていきましょう。
名前の意味
人の名前には意味が込められています。おそらくもっとも有名な日本人の名前「太郎」には「長男である」という代表的な意味があります。基本的に長男のみしか付けられない特別な名前です。これに限らず、日本において親は子供に「名前の通り生きてほしい」と願い命名します。
これはプログラミングも全く同じです。
コーディングの命名では、その命名に「何をしてほしいのか」を込めます。例えば以下の様なコードを指します。1
private int _price = 100;
private float _taxRate = 0.1f;
このような命名をしたとき、前者は「価格」として、後者は「税率」としての働きを期待して命名するはずです。したがってこの変数が使われる場所は価格計算のはずです。もしこの変数を価格計算以外の場所で使っているのなら、命名者の意図にそぐわない事をしているはずで、つまり「変数の使われ方がおかしい」という結論になるでしょう。
この「変数の使われ方がおかしい」という気付きは、設計に大きく関与しています。
変数の使われ方がおかしいという事は、変数が置かれたクラス以外のクラスがその変数を欲しがっている、または使っているという事が多いでしょう。つまりアクセサが立っているはずです。
アクセサは、クラスが持つ変数へのアクセスを提供する関数や構文のことを言います。
public float Price => _price;
public float GetPrice() {
return _price;
}
アクセサの乱立は良くない設計の兆候になります。もちろんアクセサ自体を悪とは言いませんが、カプセル化が守られにくくなってしまいがちです。
クラスは、自分のクラスの中の情報を他のクラスに見せたくないのが基本です。クラスには基本的にそのクラスのためだけの情報を持ちます。他人に勝手に情報を書き換えられるのならそれは公開情報というべきで、そのクラスだけのものではないはずです。
また、クラス同士がお互いを知らないで動くことができれば、お隣のクラスがいつの間にか引っ越して別人になっていたとしても問題なく動くことができます。
これはテスト2がまさにその例で、これを疎結合状態と言います。
アクセサを提供すると、その情報を公開していることになるからカプセル化、疎結合状態が守られにくい傾向にあります。3という事はアクセサが乱立するのなら「そのアクセサを使う関数は、本当にその変数を欲しがっているのか?」「実はその変数が置かれるクラスは、そのアクセサを使っているクラスの中なのではないか?」という疑問が次々生まれてきます。
こういったときはコードを振り返って、検証するときです。もしかすると悩んでいた箇所がすっきり解決するかもしれません。
このように、はっきりとした命名を施すことで、その役割の境界線を引くことができます。境界線を引くことにより、このような「違和感のある使い方」をキャッチすることができます。
過剰な意味
前節では「太郎」について考えましたが、次は「太朗」で考えてみましょう。太朗という命名の代表的な意味は以下の通りです。
- 太 -> 太く、強く、おおらかに
- 朗 -> ほがらかに、明るく
それぞれの漢字に意味を込め、その組み合わせで名前をなしています。素晴らしい名前で、ぜひともPM、リーダーになってもらいたい意味が込められていますね。全国の太朗さんにはぜひ名前の通り生きてほしいものですが、しかしプログラミングではそうはいきません。
先ほどはpriceと名付ければ「価格」として生きてほしいという事を説明しましたが、このような名前はどうでしょうか。
//仮引数仕様は省略
public void GetPlayerDataAndStatus(out string name, out int level, out Status status, ... );
プレイヤーのデータとステータスを参照渡しによって返す関数です。4
先ほどの例のように、この関数にはそれぞれ違う意味が二つ込められています。もちろん、「プレイヤー情報を返して欲しい」と「ステータスを返して欲しい」という意味です。ゲームを作る際、このような状況に出くわすことがあるかもしれませんが、これはSRP違反です。
"Ancle Bob"「ボブおじさん」ことロバート・C・マーチンが提唱したSOLID原則の中でもおそらく最も存在感のある原則のSRPは、"Single Responsibility Principle"、日本語で「単一責任の原則」と言います。
SRPが言いたいことは、「オブジェクトは、一つのアクタに対して責任を持つべき」という事です。5
要するにクラスに「君は誰のためのクラスかな?」と問いかけた時、「私は○○のためのクラスです」と言えるかどうか、という事です。6「○○と○○のためのクラスです」と返事が来たら、何かがおかしいサインです。
先ほどの関数 GetPlayerDataAndStatus は「プレイヤー情報が欲しいクラス」と「ステータスが欲しいクラス」に対してそれぞれの情報を提供するという2つの責任を負っており、それを同時に果たそうとしてしまっています。この時何が起こるかというと、「片方のクラスで欲しい情報の更新があった時、関数の仮引数仕様を変え、もう片方のクラスの記述も変え、関数の処理を変え...。」と沢山の変更が必要になってしまうのです。
片方の変更がもう片方にも影響を与えてしまう、「良くない」というのは火を見るよりも明らかでしょう。
名前に複数の意味が込められているとき、それはしたがって関数の意味も複数になりがちです。気付いた時から分割して、「一つの意味を込めた名前」を目指しましょう。
不明な意味
さてここまでは「名前があるもの」について見てきました。次はその逆、「名前がないもの」はどう扱うのでしょうか。考えてみましょう。
例えば今この時に私が変な動きをして、あなたに「それに名前を付けてください」とミッションを与えます。1分以内にその動きを適切にとらえた(意味が一つの)名前を付けられるでしょうか。
おそらく無理です。私の動きの単純さと、あなたの想像力次第ではありますが、まず名前は思いつかないはずで、最終的に無理やり似たような動きの名前をどんどん連結させた名前になると思います。その名前は前節の通り、そうSRP違反です。
内容が整理されていない、不明なものには命名をすることができません。直接表そうとするとSRP違反の名前か、意味が薄い名前になるはずです。意味の薄い名前というのは「叫ばないコード」つまり a, b, c といった命名です。プログラミングではこの時、分割を行います。似たような動きや情報をクラスにまとめて命名し、その代表としてManagerのクラスを作成します。78
名前が付けられないコードに出くわしたら、これもまた立ち止まり、振り返って、コードの見直しをしてみましょう。また、前述のように「本当に場所があっているのか?」という問いかけのきっかけにもなるはずです。
さいごに
普段命名に意識を置いていなければ意外な視点だったのではないでしょうか。
プログラミングにおいてすべての命名は意味を持ちます。つまり命名はコードに命を吹き込むことです。その名前が役割を示し、その積み重ねが設計になってゆきます。
だからこそ、処理の内容だけではなく、命名にも意識を配ってみてください。転ばぬ先の杖はもうすでに用意されているはずです。
結論
ローマ字命名や、抽象度の高い(なんにでも当てはまるような当たり障りのない)命名を多くしていると「つば」のごとく自分に返ってきます。そうなる前に、ノーリスクでリターンを得ることのできる命名を頑張ってみても良いのではないでしょうか。
参考
"リファクタリングとともに生きるラジオ" - https://refactoradio.com (2025/10/25)
"Clean Architecture" - Robert C. Martin 著
経験 - 偉大な先輩、偉大なエンジニア より
-
残念ながらこのコードは浮動小数点の関係で正確ではありません。本題とは関係ないので気にしないでください。 ↩
-
テストとは、単体テストや結合テストなど文脈によって指すものが変わりますが、ここでは単体テストを指します。単体テストとは、例えば「int型の引数を取ってint型で返す」という関数があった時、その「int型で返す」値がどんな引数でもあらかじめ期待される値かどうかを検証することを指します。関数が「int型の引数を取ってイイ感じに加工してint型で返す」という内容で、ハタから見ればが「イイ感じに加工して」がよくわからないにせよ、戻り値のintがどんな引数であれ必ず正しければ、わからなくても良いのです。 ↩
-
なお、二次元ベクトルや日付など情報を持って提供するだけ、ロジックを持つなら数学的に求められる計算(内積や分->秒変換など)のみ、といったクラスは例外です。また、疎結合にも限度があるので注意してください。 ↩
-
outキーワードが使われた関数を使うと、引数にとった変数を関数の中で書き換えることができます。したがって、この関数は疑似的に戻り値が複数ある、という事になります。 ↩
-
アクタは、ここではそのオブジェクトを使っているクラスを指すこととします。詳しくはユースケース図のアクタを調べてみてください。 ↩
-
「クラスに問いかける」というのは比喩ではなく、本当に話しかけてみてください。理由としてはラバーダック・デバッグと同じような原理が一つ。そしてクラスは擬人化ができ、我々プログラマはクラスとおままごとをしているから、というのが一つです。 ↩
-
Facadeパターンのような、「変な動きを行う」上での動作をまとめて一つの窓口にするクラスを作るはずです。 ↩
-
だからと言ってなんでもManagerクラスにまとめるのはダメですよ!それはそれでSRP違反です。アクセサの時もそうですが、適材適所が一番です。 ↩