Edited at

リーダブルコードを3年ぶりに読み返してみて見つけた たった1つの大原則


はじめに

これはCyberAgent 19新卒 エンジニア Advent Calendar 2018の11日目の記事です。

リーダブルコードを3年ぶりに読み返してみたお話をします。


リーダブルコードを読み直してみた

つい先日、友達が「リーダブルコードを数年ぶりに読み返してみたけど、やっぱりバイブルだな」みたいなことを言っていました。

僕もエンジニアになったばかりのころ、先輩にリーダブルコードを渡されました。読んで感動した覚えがあります。あれから一度も読んでいないリーダブルコードをもう一度読み返してみたくなりました。

そうしたら、なんと、、、当時は様々なテクニックの寄せ集めだと思っていた本から、たった1つの大原則が浮かび上がってきました。

それは、

正しく命名せよ

たったそれだけです。

「コードは他の人が最短時間で理解できるように書かなければならない」と、リーダブルコードは述べています。そして、他の人が最短時間で理解できるようにコードを書くためのテクニックを本文中で述べています。たくさんのテクニックが出てきます。しかし、その大部分が正しく命名するというたった一つのメッセージを述べているのです。


正しく命名すると何がいいのか

まずはこのコードを読んでみてください。

var findClosestLocation = function(lat, lng, candidates) {

var closest;
var closest_dist = Number.MAX_VALUE

for (var i = 0; i < candidates.length; i += 1) {
var lat_rad = radians(lat);
var lng_rad = radians(lng);
var lat2_rad = radians(candidates[i].latitude);
var lng2_rad = radians(candidates[i].longitude);

var dist = Math.acos(Math.sin(lat_rad) * Math.sin(lat2_rad) +
Math.cos(lat_rad) * Math.cos(lat2_rad) *
Math.cos(lng2_rad - lng_rad));
if (dist < closest_dist) {
closest = candidates[i];
closest_dist = dist;
}
}
return closest;
}

分かりにくいと思うので、コメントをつけてみます。

// 与えられた緯度経度に最も近いcandidatesの要素を返す

// 地球が完全な球体であることを前提としている
var findClosestLocation = function(lat, lng, candidates) {
var closest;
var closest_dist = Number.MAX_VALUE

for (var i = 0; i < candidates.length; i += 1) {
// 2つの地点をラジアンに変換する
var lat_rad = radians(lat);
var lng_rad = radians(lng);
var lat2_rad = radians(candidates[i].latitude);
var lng2_rad = radians(candidates[i].longitude);

// 「球面三角法の第二余弦定理」の公式を使う
var dist = Math.acos(Math.sin(lat_rad) * Math.sin(lat2_rad) +
Math.cos(lat_rad) * Math.cos(lat2_rad) *
Math.cos(lng2_rad - lng_rad));
if (dist < closest_dist) {
closest = candidates[i];
closest_dist = dist;
}
}
return closest;
}

コメントがあればだいぶ読みやすくなると思います。このコードは緯度経度とcandidatesを与えると、地点をラジアンに変換して球面三角法の第二余弦定理の公式を用いて、最も近いcandidatesの要素を返す関数です。

次は、このように改良してみます。

var findClosestLocation = function(lat, lng, candidates) {

var closest;
var closest_dist = Number.MAX_VALUE

for (var i = 0; i < candidates.length; i += 1) {

// ココに注目
var dist = spherical_distance(lat, lng, candidates[i].latitude, candidates[i].longtitude);

if (dist < closest_dist) {
closest = candidates[i];
closest_dist = dist;
}
}
return closest;
}

コードがずっと読みやすくなったのを感じませんか?幾何学の計算がコードから消えたからです。いままで「地点をラジアンに変換して球面三角法の第二余弦定理の公式を用いて」とコードをゆっくりと読んで理解しなければ行けなかった部分がspherical_distance(lat, lng, candidates[i].latitude, candidates[i].longtitude)という4つの引数を必要とした関数に代わり、返り値をdistという変数に入れています。これにより、何をやっているか詳細は知らないけれど、きっと指定した緯度経度とcandidatesの要素の緯度経度から球面距離を計算して、その結果がdist(距離)なんだろうなという予想がつきます。

一方で、このような極端な例をみてみましょう。

var func1 = function(arg1, arg2, arg3) {

var var1;
var var2 = Number.MAX_VALUE

for (var i = 0; i < arg3.length; i += 1) {

var var3 = func2(arg1, arg2, arg3[i].latitude, arg3[i].longtitude);

if (var3 < var2) {
var1 = arg3[i];
var2 = var3;
}
}
return var1;
}

このコードも同じように動きます。しかし、何をやっているのか全くわからないです。

以上が正しく命名することでどれだけコードが分かりやすくなっているのかの証明です。コードを書くときに変数・関数などに正しく名前をつければ、「この変数はこのような役割なんだろうな」「この関数ではこのような引数をとって、きっとこんな処理を中でしていて、このような値を返すのだろうな」と想像がつきます。そうです、抽象化しているのです。正しく命名すれば、具体的なコードをある意味抽象化することができ、コードの理解度や読まなければ行けない量を大幅に減らすことができます。

なぜ2番目に紹介したコメントをつけまくった例が分かりやすかったか?それはコメントからそのコードが何をやっているか大局的に把握できていたからにすぎません。


正しく命名するために


命名を工夫する

page = GetPage(url);

これだけ見ても、urlからどのようなページをどこからどのように持ってくるのかがわからず、関数の中身を見たくなります。それでは命名として不十分です。正しい命名をすれば、「きっとこれをしているのだな」と想像でき、関数の中身を見ることなく次に進めます。

markedup_body = GetHtmlMarkedupBody(url);

このように変更してみました。こうすると、「urlを渡したらHTMLのbodyの部分をmarkupされた状態のまま返してくれるのだろう」と想像がつきます。コードを抽象化して分かりやすくすることができ、明らかに読みやすくなっています。いちいち関数の中身を見ることなく実装を予想できます。


本当にそのコメントは必要か?

コードが一番のドキュメントであるという認識が大切です。コードを読めば分かるようなコメントをコード中に入れているということは、分かりやすいコードを書こうとしていない証拠です。本当に分かりやすいコードは正しく命名されているため、命名から何をしているのかが分かるのです。

先ほどの例で説明すると、

// urlを渡すと、HTMLのbody部分をmarkupされたまま返す

page = GetPage(url);

こんな感じです。そりゃ分かりやすいです、コメントがあるので。

markedup_body = GetHtmlMarkedupBody(url);

命名を工夫するだけで多くのコメントは不要になります。そして、こちらの方が圧倒的に優れています。


説明変数の導入

次の例では、if文の条件の左辺は何なのか読み込まないといけません。

if line.split(':')[0].strip() == "root"

...

しかし、説明変数を導入すると、

username = line.split(':')[0].strip();

if username == "root"
...

line.split(':')[0].strip();の処理で、usernameつまりユーザー名を生成しているのだなと分かります。正しい命名によりコードを読む労力が減っています。usernameはどのように生成しているのだろう、usernameの生成過程にバグがあるのでは?などという時以外、usernameの生成方法など気にしなくてよいのです。


まとめ

コードリーディングの際にこのような経験はないでしょうか?「関数が呼び出されているが、中で何をやっているのかよくわからず、定義元に移動するが、そこでも何をやっているかわからずジャンプを繰り返して最後嫌になる」というものです。

きちんと命名されていれば、「きっとこのような処理をしてこれを返しているのだな」と何となく分かります。これはコードを読む立場からしてとてもありがたいことです。

他人が最短時間で理解できるコードは、正しく命名されたコードであり、それこそがリーダブルコードのたった1つの大原則である

3年ぶりにリーダブルコードを読みましたが、やはりリーダブルコードは素晴らしいです。ぜひ皆さんももう一度読んでみてはどうでしょうか。


最後に

Twitterでも色々と発信しているのでぜひ見てください。

👉https://twitter.com/hiromu_bdy