『リーダブルコード』の豆知識拾い

https://amzn.asia/d/7vOWxeZ

私はプログラミングを始めてしばらく経ちますが、今更『リーダブルコード』を読みました。新人におすすめされることが多い本なのですが、評判の通り新人に知っておいて欲しいことが多く載っていました。

反面、現役のプログラマーとして仕事をしている人が読むとちょっと今更感が漂う知見が多いかもしれません。邦訳が出版されて10年以上経っていますので、当時はまだ常識じゃなかったプラクティスが今では常識になってしまっているというのもあるかもしれません。変数を使わず再代入不可な定数を使おうというアイデアなどは、関数型言語が流行ってJavaScriptにconstが導入された今ではみんな当たり前にやってることになった感があります。

とはいえ、読んで全く無駄だったのかというとそんなことはありません。ためになることもたくさんあり、中でも豆知識的なやつで覚えておきたいものが何個かあったのでメモ代わりにブログに残しておくことにします。

Getは使うな

APIからデータを取得する処理とかを書いているときに、思わず getData()とかいうメソッドを定義してしまいがちです。しかしこの本では get〇〇 はやめろと書いてあります。getでは意味が広すぎる上に、アクセサの接頭辞としても使われることが多いからです。こういう場合は fetchData()とかのほうが適しているということになります。

同様に、sizeという単語も意味が曖昧すぎて避けるべきということになります。せめて何のサイズなのかとか、単位は何なのかとかも明らかにしたほうが良さそうです。例えば sizeKB()とかになるでしょうか。

以上・以下はminとmaxで表現する

変数名で値の範囲を表現したい場合に、例えば1以上10以下という境界値を含む場合は、minとmaxを使えば表現できます。startとstopとかfromとtoとかだと曖昧ですが、minとmaxなら境界値を含むということがはっきりわかります。

似たような考え方として、リストがあったとしてリストからある範囲のサブセットを取り出したいときに関数の引数名として何が適切かというと、firstとlastだとわかりやすいみたいです。これもstart・stopとかfrom・toのペアだと曖昧ですが、firstとlastならそれら自身を含むサブセットを返すということがはっきりわかります。

名前付き引数が使えない言語で名前付き引数みたいなことをやる方法

今どき名前付き引数が使えない言語とか使いたくないわけですが、巨大企業の作った某言語とかでは名前付き引数を使った関数呼び出しができません。そんなときどうするかと言えば、以下のような書き方をすると良いみたいです。

func hoge(x int, y int) int {
	return x + y
}

h := hoge(/* x = */ 1, /* y = */ 2)

ちょっと独特な書き方で読みづらいし、docコメント書けば良いだけの話かもしれませんが、慣れたらいい感じなのかもしれません。

2つの範囲に重複部分があるかどうかを判定する方法

例えば、{1, 2, 3, 4} という配列と、{3, 4, 5, 6} というような配列があった場合、2つの配列の中に重複部分があるかどうかをプログラムで判定したくなったとします。どういうコードを書けばいいかというと、それぞれの始点と終点を使って判定するのが良さそうなのですが、何も考えずに実装すると読みづらいコードになります。

const list1 = [1, 2, 3, 4];
const list2 = [3, 4, 5, 6];

function areOverlapped(list1, list2) {
  const list1_first = list1[0];
  const list1_last = list1[list1.length - 1];
  const list2_first = list2[0];
  const list2_last = list2[list2.length - 1];

  // list1の始点がlist2の始点と終点の間にある場合
  if (list1_first >= list2_first && list1_first <= list2_last) {
    return true;
  }

  // list1の終点がlist2の始点と終点の間にある場合
 if (list1_last >= list2_first && list1_last <= list2_last) {
    return true;
  }

  // list2についても同様の判定...

  // どの条件にも合致しなかった場合
  return false;
}

console.log(areOverlapped(list1, list2));

しかしこのコードは下記のような単純なコードに書き換え可能です。

const list1 = [1, 2, 3, 4];
const list2 = [3, 4, 5, 6];

function areOverlapped(list1, list2) {
  const list1_first = list1[0];
  const list1_last = list1[list1.length - 1];
  const list2_first = list2[0];
  const list2_last = list2[list2.length - 1];

  // 一方の始点が他方の終点より後にある場合は重複しない
  if (list1_first > list2_last) return false;
  if (list1_last < list2_first) return false;

  // それ以外の場合は必ず重複部分がある
  return true;
}

console.log(areOverlapped(list1, list2));

これは日付範囲の比較とかでよく使うロジックです。管理画面を作ることが多い人なら一度は作ったことがあるのではないでしょうか。ゴリゴリ書くよりも一度図などを書いて原理的に考えてコードを書いたほうがシンプルになる良い例だと思いました。

読みやすいテストコードを書く

個人的に一番盲点だったのがこれで、テストコードは読みづらくてナンボだと思っていました。なぜなら、関数定義や制御構造の入った読みやすいテストコードを書くことによって、「テストコードのテスト」が必要になってしまうからです。配列で入ってきた入力値と期待値のセットを、インデックスもベタ書きで一個一個検証していくのが正義だという考えのもと、1000行近くあるほとんどCSVファイルみたいなテストコードを書いたこともあります。

しかし、言われてみれば読みづらいテストコードには以下のデメリットがあります。

  • テストコードで何をやっているかわからないので、テストが書かれなくなる
  • テストコードを触るのが怖くなり、テストがメンテされなくなる
  • 間違ったテストコードが追加されることにより、テストの意味がなくなる

場合によっては「テストコードのテスト」を書いてでもテストを読みやすくしたほうが良いかもしれません。今度から実践してみようと思います。

『リーダブルコード』は新人におすすめか?

この本は新人におすすめされることが多く、実際新人に知っといて欲しいことがたくさん書いてあるのですが、いざ新人がこれを読んだからといって書いてあることをすぐ体得できるわけでもないし、そもそもなぜこういう書き方をしないといけないのか理解できないのでは? と思いました。

なぜ読みやすいコードを書くべきかは、書いた人間(すでにプロジェクトからいなくなってる)を呪いたくなるような思いとともに複雑に絡まりあったどこを触っても壊れるようなコードと戦った経験がないと身にしみてわかることはないのではないかと思います。

そういった意味で本書はおっさんプログラマーが「あるある」と深く頷きながら溜飲を下げるための本なのではないかと思いました。新人プログラマーにこれをおすすめするのは一種の無意味なマウンティングかもしれないと言ったら言いすぎかもですが、なぜ読みやすいコードを書かねばならないのかは現場で一緒にク○コードを読みながら教えるのが一番だと思います。