はじめに
この記事は、僕が配属当初に先輩からよく言われた「ソースコードはしゃべるように書け」について、それが具体的に何を意味するのかを、配属から6年経った今改めて考えてみる記事です。
その先輩はすでに辞め新しいステップへ進まれてしまったためにその真意を直接聞き直して確認することはできないのですが、今の僕なりの解釈、ということで書いてみます。
とは言え、「ソースコードはしゃべるように書け」は「ソースコードの読みやすさ」という意味では役に立つ考え方ですが、それがどんな場面でも正しいかというとそうではないと思っています。おそらく、自然言語に近づけた書き方よりも、より機械の仕組みに近づけた書き方をした方がはるかに効率がよかったり、安全な言語や場面もあると思います。
また、仕様自体が複雑だったり、既存のソースがすでに汚いなどの理由で、この記事に書いたようなことがすんなり実行できる環境というもの自体が稀であるというのも事実です。
しかしながら、目の前のコードが読みづらいけどどう修正すれば良いか、後々分かりやすいコードを書きたいけどどんなことに気をつければ良いか、など、考えればソースコードの見た目を改善できる場面で具体的な案を考えるための一つの材料として読んでいただければ幸いです。
なお、この記事では僕の知識や経験の都合上、基本的に言語はJavaで説明させていただきます。
本文
しゃべるように書いてみる
まずは例として、しゃべるように書く、を実践してみたいと思います。
今回は、「記事リストを画面に表示する機能が欲しいよねー」という会話からスタートします。
説明の都合上、処理はmain
メソッドに直接書きます。
public static void main(String args[]) {
Main main = new Main();
}
先ほどの会話のを分解すると、処理の内容は
- 記事リストを取得して
- 画面に出力する
となりますので、必要なメソッドは挙げた項目をそのまま英訳し、getArticleList
とprint
の2つになるでしょう。
public static void main(String args[]) {
Main main = new Main();
List<Article> articleList = main.getArticleList(); // 記事を取得し
main.print(articleList); // 全件出力するよ
}
private List<Article> getArticleList() {
// コンパイルエラーにならないよう、とりあえず空のリストを返却。
return new ArrayList<Article>();
}
private void print(List<Article> articleList) {
}
さて、ここまで書いたところで、いくつか疑問の声が聞こえてきます。
- 「記事リストを取得」ってどこから取得するの?DB?Web上のAPI?
- 画面に出力するのは何?記事リスト?記事だとしたら全件?一部だけ?
確かに、話し忘れていましたね。後述しますが、なるべく処理は曖昧さがないようにちゃんと内容を決め、名付けしておきたいところです。
とりあえず、今回は 1. をWeb上のAPI、2. を記事タイトルの全件出力に決まったという想定で進めます。
すると、上記のコードは以下のように修正されます。修正内容はコメントに記載の通りです。
public static void main(String args[]) {
Main main = new Main();
List<Article> articleList = main.requestArticleList(); // 記事リストをリクエストして取得するよ
main.printTitles(articleList); // 取得したリストはすべて表示対象にするよ
}
/**
* "request"の単語により「通信して取得する」のニュアンスを追加
*/
private List<Article> requestArticleList() {
return new ArrayList<Article>();
}
/**
* "Titles"をつけることでタイトルのみ出力することを説明
*/
private void printTitles(List<Article> articleList) {
}
おっと、何か仕様について話しが出ました。
偉い人 「あ、ちょっと全件表示すると件数が多くなった時に大変だから、最大20件に制限しておこうか」
書く人 「追加仕様っすか。。。じゃあ無駄な通信しないよう、取得する時に20件にしぼっちゃいますね」
仕様変更で出力するのは最初の20件だけということになりました。今回は通信量を抑えるためにも、リクエストの段階で制限してしまうことにします。
public static void main(String args[]) {
Main main = new Main();
List<Article> articleList = main.requestArticleList(20); // 20件だけ取得するよ
main.printTitles(articleList);
}
private List<Article> requestArticleList(int limit) {
return new ArrayList<Article>();
}
private void printTitles(List<Article> articleList) {
}
というように、会話で決まった内容をそのままメソッドやクラスに落とし込んでいきます。
これが、「しゃべるように書く」のイメージです。
ここで気をつけているのは以下の通りです。
まだ具体的な処理は書かない。
この時点では、必要なメソッド、クラス、仕様等を洗い出すだけにしておき、具体的な通信処理や表示処理は書きません。一度細かなところに目を向け始めると、なかなか流れの大枠に意識を戻すことが難しくなってしまうからです。
僕の先輩も、新しいクラスを書くときはまずは必要なメソッドを宣言するところから、必要なクラスやその親子関係を作ってしまうところから始めていた記憶です。
話した内容と違うことは書かない
例えば件数を20件に絞る部分ですが、たとえ結果が同じになるからといって、requestArticleList()
の中に上限である20件を定義するようなことはしません。
private List<Article> requestArticleList() {
int limit = 20; // ここで定義したりはしない
return new ArrayList<Article>();
}
なぜなら、このようにすると呼び出し側のソースコードにはrequestArticleList()
としか記述されず、main
メソッドを読んだときにリミットが20件に設定されているという仕様を伝えることができなくなるからです。
これを会話に直すと、こんな感じです。
書いた人 「`main`メソッドで記事リストを取得し、タイトルを画面に出力するよ」
読む人 「ふむふむ、なるほど。記事リストを取得してタイトルだけを画面に出力するのか」
...
読む人 「あれ、なんかいくら記事が増えても20件しか出力されないんだけど。さっきはそんあこと言ってなかったよね。あれ、書いた人いない。」
読む人 「どこかで切り捨てられてるのかな。。。自分でもっと詳しく調べなきゃダメか。。。」
...
書いた人 (あ、そういえば`requestArticle()`は上限を20件って決めたんだった。言い忘れたけど、まあ気づいてくれるよね)
自分にとって重要な情報を(意図的にではないにしろ)隠されるのはとてもツラいものです。
今回はとても単純な構造なのでどこで20件に絞っているのかはすぐに見つけられそうですが、これがどんどん複雑化していくと現状の調査・理解だけで1日終わる、みたいなことも珍しくなくなってしまいます。
話した内容はそのままクラス名なり、メソッドのシグネチャに落とし込み、実装はかならずそこから分かる内容にしましょう。
Javaに登場する要素の役割について考えてみる
さて、話すようにコーディングする例を少し見てみたところで、クラスやメソッドなどのJavaでよく登場する要素が、「しゃべる」言語のどのような役割に当たるのかについて少し考えてみたいと思います。
メソッド
一番わかりやすいのはメソッドかと思います。常に、「〜する」のような動詞句にあたります。
requestArticle()
であれば「記事を通信で取得する」ですし、printTitles()
であれば「タイトル(複数)を表示する」となります。
動詞「句」と言ったのは、メソッドの作り方によってはメソッドの引数を補語のように使うと分かりやすい場合があるからです。
例えば、「記事DBへのアクセサが「IT」カテゴリの記事を取得する」メソッドであれば、
ArticleDatabaseAccessor accessor = new ArticleDatabaseAccessor();
// accessorが、Category.ITというカテゴリの記事をselectする
List<Article> articleList = accessor.selectBy(Category.IT);
と書けます。
メソッドのシグネチャは
public List<Article> selectBy(Category category);
となり、これを見るだけで「カテゴリで絞った記事リストを取得する」というメソッドの処理内容が伝わります。
メソッド名と引数、そして戻り値がしっかりと言葉で説明できる形(「このメソッドは『メソッド名』という処理を『引数』を使ってし、結果として『戻り値』を出力します」)になっていれば、それだけでぐんと読みやすいコードになるはずです。間違っても説明した内容以外の処理を入れたりしてはいけません。
クラス
クラスは主に「誰が」の主語の役割を果たします。
また、インスタンス変数を保持するだけのデータクラスであれば、主に引数として「何を使って」の部分を表します。
まずは、「誰が」の主語にあたる部分について考えます。
ArticleDatabaseAccessor accessor = new ArticleDatabaseAccessor();
List<Article> articleList = accessor.selectAll();
主語になるクラスにしろ、データを表すクラスにしろ、クラスにとって重要なのは「そのクラスが何を表すのか」がわかるクラス名を完結に、過不足なく名付けることだと思います。
上記の例ではArticleDatabaseAccessor
で、直訳すれば「記事データベースにアクセスする人(もの)」
という役割が過不足なく表現できているのではないかと思います。こうすれば、記事データベース以外のデータベース(例えばユーザーデータベース)などへのアクセス処理が含まれないことは明らかですし、逆に記事データベースへの操作(検索、追加、削除など)をしたければこのクラスのメソッド一覧を調べれば良いことがわかります。
名前のつけ方のコツとしては、物理的に存在しないもの(データベースへのアクセサ)などは末尾に"er", "or"をつけて「〜する人(もの)」を表す名前に、物理的に存在するもの(ユーザー、記事)などは、"User", "Article"などのように、英単語一文字のシンプルなものにすると良いでしょう。「ユーザーはユーザーでもAndroidユーザーとiOSユーザーで保持するデータが違う」であれば、AndroidUser
, IosUser
のように違いが正確にわかる名付けるだけです。
とにかく、そのクラス名をそのまま口に出してちゃんと書き手、読み手間で同じものをイメージできるか、が重要になるかと思います。
変数名
ArticleDatabaseAccessor
のように、クラス名がそのままインスタンス名(変数名)になるのであれば、特に問題はありません。しかし、List<Article>
のように、同じ型でも文脈によってその変数が表すデータが「記事全件」だったり「ITカテゴリの記事」だったりする場合があります。
変数名は前述のメソッド呼び出し部分を見た時にその処理内容を理解する大きな役割を果たしますので、ここでも「しゃべるように」を意識して名付けていく必要があります。
例えば、「記事全件」であればarticleList
ではなくallArticleList
が分かりやすいでしょう。「ITカテゴリの記事」であれば、itArticleList
となります。「ITカテゴリの記事」をそのまま訳すとitCategoryArticleList
となりそうではありますが、実際の会話でも「ITの記事」というように「IT」と言えばそれがカテゴリを表すのが共通認識であることと、あまり長々と名付けてもかえって分かりづらくなってしまいますので、あえて省略してitArticleList
としています。(ただし、後述の通り省略した結果曖昧になりすぎる場合は長い変数名の方が安全な場合もあります。あくまでバランスです)
いずれにしても、口にして違和感のない名付けであれば、それをそのまま英訳して変数名にしてしまえば大きな問題にはならないはずです。
プログラムの語順
さて、プログラムに登場する要素を整理したら、次はその要素を並べる語順について考えてみます。
プログラムは基本的に英語の「主語」+「動詞」+「目的語」+「補語」の語順に似ています。つまり、「アクセサ」+「メソッド名」+「引数」という構造です。
ソースコードを左から読んだ時、きれいに「誰が」.「どうした」「何を」(「どうやって」)
の構造になっているのが一番しゃべる感覚に近く、理解しやすいです。戻り値がその動作の結果そのものであれば、よりわかりやすいコードになるでしょう。
逆に、この順序が入れ替わっている場合、そこで一旦理解がストップし、正確な理解のためにしばらく頭をつかわなければならなくなってしまいます。例えば以下のようなコードです。
ArticleDatabaseAccessor accessor = new ArticleDatabaseAccessor();
// 誰がDBアクセスするの?accessorの役割ではないの?
selectAll(accessor, articleList);
DBから全件を取得するよと言っているのに、それは誰の(どのクラスの)処理なのかが曖昧で、なぜかアクセサと記事リストのデータを使う感じになってしまっています。直訳すると「アクセサと記事リストを使って全件取得するよ」と言っているように聞こえてしまいます。
おそらく実装は以下のようになっているのでしょう。
private void selectAll(ArticleDatabaseAccessor accessor, List<Article> articleList) {
// accessorが記事を全件取得し、articleListインスタンスへ追加している
List<Article> selectedArticleList = accessor.selectAll();
articleList.addAll(selectedArticleList);
}
これはさすがに実装をちゃんと読んでみないと何が起きているのか正確に予想することは困難です。
もしかしたらarticleList
だけでなくaccessor
のデータも何か変更しているかもしれませんし、もともとarticleList
にデータが入っていた場合にどのような挙動になるのかが予測できません。
予測ができないと、引数のarticleList
もどんな状態で(空のリストが良いのか、すでに記事データが何件か含まれていても良いのか)渡せば良いのかを理解するために無駄にJavaDocや実装を読んでみなければならなくあります。そもそも戻り値がvoid
のため、メソッドを実行した結果本当に記事データが手に入るのかも謎です。さらに言うと、記事リストを取得する以外の処理がされていないとも限りません。
書いた人は分かるのかもしれませんし、実際にこれでも動作はしますが、後から知らない人が読む、ということを考えるとこれでは「何を言っているんだおまえは」状態になってしまいます。
「主語(アクセサ) + 動詞(メソッド名) + 目的語(引数)」の形でメソッドが表現できる限り、そのような作りを優先するのが、読みやすいコードに近く第一歩なのではないかと思います。
ひとつの要素はひとつの役割
これはオブジェクト指向を考える上でよく言われることです。
これを「しゃべる」に当てはめてみると、以下のようになります。
良い例
読む人 「これ、何?」
書いた人 「これは記事DBへのアクセサだよ」
悪い例
読む人 「これ、何?」
書いた人 「これは記事DBへのアクセサだよ。あ、便利だからユーザーDBへもアクセスできるよ。(名前は`ArticleDatabaseAccessor`だけどね)」
悪い例その2
読む人 「これ、何?」
書いた人 「これはなんだろうなぁ、、、記事DBへもアクセスできるし、取得した記事リストを表示する処理もちょっと入ってるし、、、」
上の例の通り、クラス名やメソッド名など、一つの要素に複数の役割をもたせたり、そもそも役割が曖昧だったりすると、途端に説明も理解も難しくなります。さらに、実際の仕事の現場では聞こうと思ったらすでに「書いた人」がいない、ということも珍しくありませんので、そうなるともはや自分でそれを解読し、理解しなければならなくなってしまいます。
一つの要素が持っている役割を一つきっぱりと言い切れると、そのプログラムは読みやすく、処理を予想しやすくなります。逆に、二つ以上の役割を持っていたり、そもそも役割が曖昧だったりする場合は、再度誰かとその役割について話しながら要素を分割し、整理すべき場面である可能性が高いです。
その修正に時間がかかったとしても、後々かならずそれ以上のロスを回避できるでしょう。
「しゃべる」際は具体的に、正確に
「ソースコードはしゃべるように書け」は、「しゃべる」ということが人間にとって理解しやすいことが前提になっています。生まれてこのかた、しゃべるよりもコードを読み書きする方が多いという方は稀かと思いますので、この考え方は確かに正しいと思います。
しかし、「しゃべる」ということが常に「分かりやすい」かというと、そうではなかったりします。
例えば、「この仕事、アレしといて」のように、指示語や抽象的な言葉だらけで曖昧な発言では、いくら「しゃべる」内容であっても分かりやすくはありません。分かりやすくないどころか、不要な誤解からトラブルを招くこともあるでしょう。
そこまで極端でなくても、例えば「僕のマシン、使い勝手がとてもよくてさ」という会話があったとして、そのマシンとは何をさすのでしょうか。もしかしたらMacかもしれないですし、Windowsかもしれません。OSではなく、スペックの良さについて使い勝手が良いと言っているのかもしれませんし、そもそもPCですらなくラジコンか何かの話かもしれません。
このように、「しゃべる」ように書けば必ずしも分かりやすく、正確に理解できるのかというと、そうではないのです。
これをコードにすると以下のようになります。
// 「この仕事、アレしといて」
public void doSomeWork() {
Task task = new Task();
Worker worker = new worker();
worker.exec(task);
}
task
がどんな内容なのか分からなければ、exec
メソッドが何をするものなのかもわからないでね。しかしながらこの手の曖昧な書き方、経験上少なからず遭遇しています。exec
メソッドなんて何度見たかわからないですし、曖昧という意味では、id
という変数名は頻繁に出てきては読み手を困らせるクセモノです。ひとりのユーザーに対して様々なID(システムが採番したIDだったり端末IDだったりGoogleのアカウントIDだったり)が振られることがあるためです。面倒臭がって全部String
型のid
という曖昧な変数名にすると、後々大変なことになる良い例です。
冒頭の例でgetArticleList()
ではどこから取得するのか分かりづらいという理由でrequestArticleList()
に修正したり、print()
では何を出力するのか分かりづらいという理由でprintTitles()
に修正したように、なるべく内容は具体的であることが望ましいです。また、会話と実際の行動(つまりメソッド名と実際の処理)に乖離があってもいけません。嘘つきになってしまいます。
このあたりを考えると、「しゃべるように」というよりは「仕様を文書で説明するように」と言った方がイメージが近いかもしれません。
なんにしても、誤解がなく、人間が理解できる流れや言葉で説明されるのが、「分かりやすい」ソースコードにつながるのではないかと思います。
さいごに
なんだか当たり前のことばかり書いたような気がしてきましたが、実際仕事をしていると、時間がなかったりとりあえず書いてみないとわからなかったり、急遽で既存の処理に手を加えなければならなかったりと、ここに書いたようなことを考えている暇がないことなんて頻繁に遭遇します。結果、全然声に出して読んでも意味が分からないコードになってしまう、というのはよく見る光景です。
よく「1ヶ月前に自分は他人」というようにソースコードは自分が書いたものであっても1ヶ月後には改めて読んで理解しなければならない状態まで忘れてしまうものです。ましてやそのソースコードを始めて読む運用担当者、もしくはプルリクのレビュワーにとっては全く未知の世界です。
コードの読みやすさは、その時はなんとかなっても、後々負債となって大きくのしかかる部分ですので、後々楽をするためにもなるべく書く時点でコーディングすべきアプリケーションの仕様について、まずは口頭で誰かに説明することができ、その説明をそのままソースコードに落とし込めば良いだけ、という状態にしておきたいところです。そうすることによって、書いた人が後で何も言わなくてもソースコードが勝手に説明してくれる、という状況を作り出せれば最高に開発環境は明るくなるでしょう。
余談ですが、そんな考えの先輩方だったため、配属先のチームでは基本的にコメント無し、ドキュメント無しでの開発でした。ソースコードを読めば全部わかるし、それが一番正確だからドキュメント作る暇があったら少しでもソースコードを読みやすくすることを考えろ、的な発想だったと記憶しています。
さすがにそれは極端な例だったと今になってみれば思いますが、一方で「読みやすさ」への強いこだわりは今思い出しても勉強になる部分がたくさんあります。
この記事に書いたことがどれだけその先輩の思いと合っているかはわかりませんが、ひとまず現在の僕の理解として、文章にして残しておこうと思います。
参考
処理の内容を正確に表す英単語はなんだろう?と思った時、以下の記事が役に立ちます。
だいたいにおいて、ソースコード中に必要になる英単語はどんなプログラムでも同じようなものだったりしますので、(特に日本人の)プログラマの間で一般的に使われている英単語を覚えることは意思疎通をスムーズにすることができます。
(11/22 追記)
同じように、僕が新人のころ言われた別の言葉についても記事にしました。
併せて読んでいただけたらと思います。
ソースコードの一行一行に意味を込めろ
(/追記)