1. はじめに
最近、リーダブルコードを読み直しました。この書籍はプログラミングを勉強したての頃に最初読んだのですが、改めて今読むと、色々と自分に刺さる部分がありました。そこで、自分が過去に書いたTypeScriptのコードの実例を交えつつ、ポイントをつまみぐいして、軽くまとめてみます。
2. 私的に大事だと思ったポイント
以下では、私的に大事だと思ったポイントをリーダブルコードからつまみぐいしていく形になります。それが嫌な方はブラウザバック推奨です!
明確な単語を選ぶ (2.1)
これは関数など命名する際に、その担う役割をよく考えたうえで明確な単語を選ぶべきだということです。実際に自分が書いたひどい例を用いて、考えてみます。
// before
export const getDiffTime = (targetTime: string) => {
...
};
これは、ある時刻と現在の時刻を比較して、その差を返す関数なのですが、getDiffTime
だけではその情報が伝わらないと分かります。読み手には、何の時刻の差なのか一目で理解できません。では、「ある時刻と現在の時刻を比較して、その差を返す関数」という役割を踏まえ、明確な単語を考えてみることにしましょう。
すると、現在とある時刻の差を算出したいという意図がありますから、get
ではなくcalculate
を選んだり、単にdiffTime
ではなく、difference between a and b
といった表現を使った方がより明確でわかりやすいのではないかと考えられます。よって、最終的にはこのような命名に直しました。
// after
export const calcDiffTimeBetweenNowAndTargetTime = (targetTime: string) => {
...
}
beforeよりは多少長くなりましたが、それでも明確になったと言えるでしょう。なお、calculate
をcalc
としたり、difference
をdiff
としたりしていますが、これらは一般的に定着している略称だと考え、採用しました。
コードを「段落」に分割する (4.7)
これはどういうことか、本書籍ではまず「文章」という観点から説明しようとしています。
文章は複数の段落に分割されている。それは
・似ている考えをグループにまとめて、他の考えと分けるためだ。
・視覚的な「踏み石」を提供できるからだ。これがなければ、ページの中で自分の場所を見失ってしまう。
・段落単位で移動できるようになるからだ。
確かに、文章が段落で構成されているのと同じようにして、コードも段落、つまり意味のまとまりごとに整理していくことが良いのではないかと考えさせられます。再度、私が過去に書いたコードを用いて、考えてみます。
// before
export const convertToArrayFromScoreString = (stringScoreData: string) => {
const scoreRegex = /\_\_(.*?)\_\_/g
const scoreData = stringScoreData.match(dataRegex)?.map((v) => v.slice(2, v.length - 2))
const labelRegex = /\*\*(.*?)\*\*/g
const labelData = stringScoreData.match(labelRegex)?.map((v) => v.slice(2, v.length - 2))
if (!scoreData || !labelData) {
return { scoreData: [], labelData: [] }
}
return { scoreData, labelData }
}
これは、とある文字列から正規表現でデータを抽出し、配列として返すという処理をしたものです。ちょっと読みにくいですね。では、コードを段落ごとに分割してみます。
// after
export const convertToArrayFromScoreString = (stringScoreData: string) => {
// スコアを抽出
const scoreRegex = /\_\_(.*?)\_\_/g
const scoreData = stringScoreData.match(dataRegex)?.map((v) => v.slice(2, v.length - 2))
// ラベルを抽出
const labelRegex = /\*\*(.*?)\*\*/g
const labelData = stringScoreData.match(labelRegex)?.map((v) => v.slice(2, v.length - 2))
// undefinedの場合は、空の配列で返す
if (!scoreData || !labelData) {
return { scoreData: [], labelData: [] }
}
return { scoreData, labelData }
}
簡単に段落分けをしてみました(コメントはあえてつけています)。afterの方が、見通しが良くなったように思います。
if/elseブロックの並び順 (7.2)
以下のAとBは当然、同じ意味をなしますがこれらには優劣があるといいます。
// A
if (a == b) {
...
} else {
...
}
// B
if (a != b) {
...
} else {
...
}
優劣とはどういうことなのか。本書籍ではこのように述べられていました。
・条件は否定形よりも肯定形を使う。例えば、if (!debug)ではなく、if (debug)を使う。
・単純な条件を先に書く。ifとelseが同じ画面に表示されるのでみやすい。
・関心を引く条件や目立つ条件を先に書く。
このことについて、本書籍でのサンプルコードが分かりやすかったので、それを若干改変したコードをもとに考えてみます。
// before
if (!url.hasQueryParameter("expand_all")) {
response.render(items);
...
} else {
for (let i = 0; i < items.length; i++) {
items[i].expand();
}
...
}
このコードは、URLにクエリパラメータexpand_all
が含まれているかどうかを判断して、responseを構築するウェブサーバーの例です。このコードを見た時に、まず、expand_all
というワードが情報として頭に入ってくるのではないでしょうか。それは関心を引く条件だからです。でも、それを!
で真偽をひっくり返しています。つまり、expand_all
のことは考えないように!、と読み手に伝えています。あまり読みやすいコードとは言えないでしょう。
そこで、「条件は否定形よりも肯定形を使う」のです。
// after
if (url.hasQueryParameter("expand_all")) {
for (let i = 0; i < items.length; i++) {
items[i].expand();
}
...
} else {
response.render(items);
...
}
beforeとafterは同じ処理をしているのにも関わらず、afterのほうがずっと読みやすくなったように感じます。
おわりに
以上、リーダブルコードを読み直して、簡単に大事だと思ったポイントをまとめてみました。もちろん本書籍には、他の方が記事で紹介なさっているように、コードを書く上での大事なことが他にも多く記述されています。まだ読んでみたことの無い方は、一度手に取ってみることを強くおすすめします!