リーダブルコードの要点整理と活用例まとめ
はじめに
最近コードレビューの機会が増えてきたので、「リーダブルコード」を読み直しました。
リーダブルコードを読んでいく中で要点を整理し、実務の現場でコードを書いたりレビューをする際にどのように活用していくべきかを自分なりにまとめてみました。
この記事を読むことで、リーダブルコードの要点の把握と実際の活用例を学ぶことができます。
この記事の主な対象者
- リーダブルコードの要点をサクッと知りたい人
- 初級~中級者(実務歴1~3年目)の人
- コードレビューの機会が増えてきた人
- これまで我流でコードを書いてきた人
リーダブルコードについて
リーダブルコードはあくまで「こう書きなさい」と押し付け口調ではなく「こう書いた方がもっとよくなるよ」といった丁寧な語り口で書かれています。
それを前提として要点や活用方法をまとめていきます。
1章 理解しやすいコード
優れたコードについて
リーダブルコードで優れたコードとは「他の人が読んだ時に、コードの意味を理解するまでの時間を短くできるコード」と定義されている。
リーダブルコードの思想として、
- コードは他の人が最短時間で理解できるように書かなければならない
- コードは短く書いたほうがよいが「理解するまでの時間が短くなる」ほうが優先度は高い
と挙げられている。
コードを短く書くことは大切だが、短く書くことによって他の人が読んだ時に理解に苦しむ(理解に時間がかかる)ようなコードでは本末転倒になってしまう。
後に紹介されますが例としてif文を短くできる三項演算子は、コードを短くできる一方で使い方によっては他の人が読んだ時に理解しづらいコードになってしまう可能性もある。
1章のまとめ
- 優れたコードとは「他の人が最短時間で理解できるコード」
- 短く書くことも大切だが、他の人が読んだ時に理解するまでの時間が短くなるほうが優先度は高い
次の章から具体的に「他の人が読んだ時に最短時間で理解できるコード」を書くためにはどうすればよいのかの解説が行われる。
第I部 表面上の改善
この部では「表面上」の改善とし、
- 適切な名前の選び方
- 優れたコードのコメント
- コードを読みやすくするフォーマット
の解説を行っていく。
2章 名前に情報を詰め込む
名前に情報を詰め込むためには、
- 名前は気取った言い回しより、明確で正確にする
- tmpやretvalのような汎用的な名前は避ける
- 複数あるループイテレーターは明確な名前をする
- 名前に情報を追加する
- 名前の長さを決める
を意識することが大切である。
一つずつ詳しく見ていく。
名前は気取った言い回しより明確で正確にする
関数に「getPage
」という命名をした場合、この関数getPage
は何かしらのページ情報を取得してくることは名前から推測できる。
しかしgetPage
という命名だと、下記のような疑問が出てくる可能性がある。
- ページ情報はキャッシュから取得するのか
- ページ情報はデータベースから取得するのか
- ページ情報はインターネットから取得するのか
具体的にインターネットからデータを取得するのであれば「fetchPage
」「DownLoadPage
」といった命名の方が理解しやすい名前になる。
以上のことから、関数等を命名するときは「気取った言い回しよりも、明確で正確なほうを意識する」ことが重要になってくる。
実際に明確で正確な名前を命名するための参考資料として、「シソーラス(類語辞典)」がおすすめされている。
単語 | 代替案 |
---|---|
send | deliver, dispatch, announce |
find | search, extract, recover |
start | launch, create, open |
make | create, set up, build |
自分も命名ではget
などの使いやすい命名に逃げがちなので、処理が明確な関数を命名する際は、明確で正確な命名をしていくことを改めて心がけていきたい。
tmpやretvalのような汎用的な名前は避ける
まずtmp
は一般的に「一時的な保管」という文脈で使われることが多い。
【tmpの怠慢な使い方】
let tmp = user.name
tmp += user.phone_number
tmp += user.email
tmp += user.address
// 以下同様の処理
template.set("user_info", tmp)
この例では、「一時的な保管」ではなく、「何度も書き換えられる変数」として使われているのでtmp
と命名をするのは怠慢。
この例では「ユーザーに関する情報を格納する変数」なのでuser_info
といった命名の方がより正確になる。
一方で以下のような「一時的な保管」として利用する場合は適している。
【tmpのよい使い方】
// 2つの変数の中身を入れ替えるコード
if(right < left){
tmp = right
right = left
left = tmp
}
tmp
は生存期間が短く一時的な保管としての意味合いがある場合のみ利用する
複数あるループイテレーターは明確な名前をつける
【イテレーターが追いにくいコード】
ネストが多いループ処理で利用されるイテレーター。
for (let i = 0; i < clubs.size; i++) {
for (let j = 0; j < clubs[i].members.size; j++) {
for (let k = 0; k < users.size; k++) {
if(clubs[i].members[k] === users[j]){
}
}
}
}
ループ処理のネストが増えるとイテレーターの数が多くなってしまい、最後のif
文においてはmembers
とusers
のインデックス(k, j)が逆になっている。
またループ処理内でのイテレーター数が多い場合、エラーも見つけづらくなってしまう。
以上のことからイテレーターが複数ある場合は、インデックスに対して明確な名前をつけるとこのようなミスを防ぎやすくかつ、他の人も理解しやすくなる。
今回の場合はイテレーターをclub_i
、member_i
、user_i
と命名することで「このイテレーターはどの変数のものなのか」が理解しやすくなる。
また「ci
, mi
, ui
」といった省略形もイテレーターを定義する際は利用される。
自分も現場で2つ以上のイテレーターがループ処理内で登場した場合は、できるだけ頭文字をイテレーターの前に付与するようにしている。
後の章でも紹介されるが、そもそもイテレーターの数を増やさないようにするためにネストの数を減らす方法を先に考え、その上でどうしても複数のイテレーターを使わないといけない場合は上記で紹介した命名を付与することが推奨されている。
【イテレーターが追いやすいコード】
if(clubs[ci].members[ui] === users[mi]){} // 頭文字が異なるバグが発見しやすい
名前に情報を追加する
【名前に情報が少ない変数】
var start = (new.Date()).getTime()
console.log(`開始時間は${start}秒`)
ここで使われている変数start
は何かしらの開始時間であるということはわかる。
しかしgetTime()
が返す値はミリ秒なのに出力では秒と記述ミスをしてしまっている。
このようなことが起きないように「start_ms
」といった変数に情報を付与した命名をすることが好ましい。
このように単位があるような変数には変数名に単位情報を付与した命名にするのが良い。
付与する単位 | |
---|---|
時間関係 | 時間: h 分: min 秒: sec ミリ秒: msec |
サイズ | GB: gb MB: mb KB: kb Byte: byte |
距離 | メートル: m センチ: cm ミリメートル: mm |
名前の長さを決める
スコープが小さい場合は「短い名前」も許容される。
【スコープが短い例】
if(user){
let u = user.info.telephoneNumber
alert(`ユーザーの電話番号:${u}`)
}
スコープが小さいので「多くの情報を詰め込む必要はない」。u
という変数名だと情報が全くないがコードを理解するための情報がすぐそばにあるので許容される。
もしこのu
がグローバル変数として利用される場合は、user_telephoneNumber
といった明確な情報を持たせた命名にするのが好ましい。
2章のまとめ
- 明確な単語を選ぶ。GetではなくFetchやDownloadなど明確なものを使う
- tmpなどの汎用的な名前は避ける
- 具体的な名前を使って詳細に説明する。
- 名前に情報を追加する。startではなくstart_msなど単位をつける
- スコープ範囲によって命名する名前の長さを変える
3章 誤解されない名前
2章では名前に情報を加えることを解説したが3章では、誤解されないような名前をつけるためにはどうすればよいのかを解説していく。
誤解されない名前を付与するためには、
- 限界値はlimtではなく、max・minを使う
- 範囲指定はfirst・lastを使う
- ブーリアン型の命名ではisやhasなどを使う
を意識することが大切である。
一つずつ解説していく。
限界値はlimtではなく、max・minを使う
【限界値が曖昧な命名】
const items_in_cart_limt = 10
limt
は限界という意味合いを持っているが、10を含めるのか含めないのか(以下なのか未満なのか)が明確にわからない。
【限界値を明確にした命名】
const max_items_in_cart = 10
このように限界値をmax
やmin
で表現することでより理解するコードになる。
範囲指定はfirst・lastを使う
【範囲指定をstartとstopで命名】
const array = ["a", "b", "c", "d", "e"]
const start = 0
const stop = 4
// 範囲内の値を返す処
integerRange(start, stop)
この場合start
は0だとわかるが、stop
という命名だと指定範囲が[0, 3]なのか[0, 4]なのかが明確にわからない。
【範囲指定をfitstとlastで命名】
const array = ["a", "b", "c", "d", "e"]
const first = 0
const last = 4
// 範囲内の値を返す処
integerRange(first, last)
この場合はlast
で定義されている4は包括内だとわかり、指定範囲が[0,4]であると変数名を見ただけで理解できる。
以上のことから範囲や限界値を指定する際は、指定範囲(以下なのか未満なのか)が明確に理解できる命名にすることが奨められている。
ブーリアン型の命名ではisやhasなどを使う
最後にブーリアン型(true・false)を変数として命名する時は頭文字としてis
やhas
などを使うのがよい。
具体的にはisVisible
やisOpen
、isEnabled
等の相反する2つの状態変数を定義する際に利用する。
また新規追加を変数名として利用する際は肯定側を定義するのが好ましい。
3章のまとめ
誤解されないような名前をつけるためには、
- 限界値はlimtではなく、max・minを使う
- 範囲指定はfirst・lastを使う
- ブーリアン型の命名ではisやhasなどを使う
4章 美しさ
- インデントが整って適切に改行された美しいコードを目指す
コードの整列に関しては「Prettier」「ESLint」といった整形ツールである程度は補えるのでこの章は割愛する。
5章 コメントすべきことを知る
この章では何をコメントすべきなのかについて解説をしていく。
具体的な内容は
- コメントすべきではないこと
- コメントすべきこと
- 読み手の立場になって考える
となっている。
コメントすべきでは「ない」こと
まずコメントすべきで「ない」ことの具体例を紹介する
【コードからすぐに推測できるようなこと】
// ユーザー情報を更新
updateProfile(user.info)
// ユーザー情報を取得する
getProfile(user.id)
上記のように変数から容易に推測できる処理はコメントは不要である。
【コメントのためのコメントをしない】
// 以下コメントアウト
// ユーザー情報を更新
setProfile(user.info)
これはかなり大胆に書きましたが、進次郎構文みたいになっていますね。
コメントすべきこと
【自分の考えを記録する】
下記のように現状のコードの状態を認め自分の考えを記録する
// このクラスは汚くなってきている
// サブクラスを作成し、整理した方が良い
class xxx {
// 処理
}
このコメントでは、コードが汚いことを認め誰かに修正を促している。そして簡単な仕様書もついている。
もしもこのコメントがなかったらコードが汚かったとしても誰も近づかない可能性が高くなる。
【コードに改善が必要な場合はTODOを残しておく】
{
// TODO: もっと早いアルゴリズムを使う
}
TODO関連のコメント記法を下記にまとめておく
記法 | 典型的な意味合い |
---|---|
TODO: | あとで手をつける |
FIXME: | 既知の不具合があるコード |
HACK: | あまり綺麗じゃない解決策 |
XXX: | 危険!大きな問題がある |
読み手の立場になって考える
コード量が長い場合、途中でコードの理解が追いつかなくなってしまうことがある。
そのため処理の最初に要約コメントを残して読む人の理解を手助けする。
【要約コメントを残す】
for(let i=0; i<users.length; i++){
// 管理者ユーザー情報を取得し整形した上で配列へ格納
if(users[i].admin){
// 長い処理
}else{
// 長い処理
}
}
後に続く処理が長いor複雑な場合は処理の要約を付与しておくのが好ましい。
自分もフロントでAPIコールしてデータを整形する際は、処理の最初にコメントでどのような整形を行っているのか等を明記するように心がけている。
【ブロック単位で要約コメントを残す】
const GenerateUserReport = () => {
// ユーザーのロックを獲得する
{
// 処理
}
// ユーザー情報をDBから読み込む
{
// 処理
}
// 情報をファイルへ書き出し
{
// 処理
}
// このユーザーのロックを解放する
}
関数の中で複数の処理が走っている場合はブロック単位でコメントを書いておくと見やすくなる。
5章のまとめ
- コメントすべきでないこと
- コードから推測が容易なもの
- コメントのためのコメント
- コメントすべきこと
- 自分の考え
- TODOコメント
- 長い処理の要約
6章コメントは正確で簡潔に
この章ではコメントが領域に対する情報の比率を高くするためにはどうすべきかを解説している。
- 指示語の使用は避ける
- コードの意図を書く
指示語の使用は避ける
【指示語が入っている場合】
// データをキャッシュに入れる。ただし、先のそのサイズをチェックする
この場合「その」が指しているものが「データ」なのか「キャッシュ」なのか分からない。
【指示語を入れない場合】
// データをキャッシュに入れる。ただし、先のデータサイズをチェックする
このように指示語は自分にとっては便利なものになるが、読み手にとっては一度考えないといけないものになるので、明確でないものに関しては使わないほうがよい。
コードの意図を書く
【コードの処理をただコメントした場合】
// listをイテレートして逆順に変える
for (let i=0; i<list.length; i++){
// 処理
}
【コードの意図を書く】
// 値段が高い順に並び替える
for (let i=0; i<list.length; i++){
// 処理
}
6章のまとめ
- 指示語の使用は避ける
- コードの意図を書く
第II部 ループとロジックの単純化
第II部では、プログラムのループとロジックについての解説をする。
7章 制御フローを読みやすくする
条件やループなどの制御のコードは、読み手が立ち止まらないように書く。
- 条件式の引数の並び順は左側に「調査対象」で右側に「比較対象」
- if/elseブロックの並び順は否定形よりも肯定形を使う
- 関数から早く返す
- ネストを浅くする
一つずく解説していく
条件式の引数の並び順は左側に「調査対象」で右側に「比較対象」
if(price < 1000){} // 調査対象priceが左側
if(1000 > price){} // 調査対象priceが右側
左側 | 右側 |
---|---|
「調査対象」の式。変化する | 「比較対象」の式。あんまり変化しない |
また、「もし価格が1000円以下なら」というように英語の用法とも合っているので読みやすい。
if/elseブロックの並び順は否定形よりも肯定形を使う
【否定系の場合】
if(!isOpen) {}
【肯定系の場合】
if(isOpen) {}
関数から早く返す
関数で複数のreturn
を利用することは悪いことではない。むしろ関数から早く返した方が良い。
const contains = (str, substr) => {
if (str === null || substr === null) return false;
if (substr.equals("")) return true;
// 処理
};
ネストを浅くする
if文を複数使うとどうしてもネストが深くなり、理解しづらいコードになってしまう。
if文の書き方を工夫することで、ネストを浅くできるのその例を見てみる。
【ネストが深いコード】
if (user_result == "SUCCESS") {
if (permission_result !== "SUCCESS") {
reply.WriteErrors("reading permissions");
reply.Done();
return false;
}
reply.WriteErrors("");
} else {
reply.WriteErrors("user_result");
}
この場合、最初の処理ではuser_result
とpermission_result
の値を常に覚えておかないといけない。またif{}
ブロックが終了するたびに、覚えておいた値を反対にしなければならない。
そのため、読み手にとって読みにくいコードになっている。
上記のコードの場合はelse部を早めに返すことでネスト数を減らすことができる。
【早めに返してネストを削除する】
if (user_result !== "SUCCESS") {
reply.WriteErrors("user_result");
reply.Done();
return;
}
// この部分のネストを減らせる
if (permission_result !== "SUCCESS") {
reply.WriteErrors("reading permissions");
reply.Done();
return false;
}
reply.WriteErrors("");
reply.Done();
ネストを少なくするするために、「失敗ケース」を関数から早めに返す。
これによってネストの深さがレベルが2から1に下がった。
8章 巨大な式を分割する
- 説明変数を利用する
- 要約変数を利用する
- 巨大な文を分割する
説明変数を利用する
【説明変数を利用しない場合】
if(line.split(":")[0].trim() === "admin"){
// 処理
}
if
の条件判定が長くなり読み手も立ち止まる必要がある
【説明変数を利用する場合】
// 説明変数を定義する
const user_authority = line.split(":")[0].trim()
if(user_authority === "admin"){
処理
}
要約変数を利用する
【要約変数を使わない場合】
if(request.user.id === document.owner_id){
// ユーザーは文書を編集できる
}
if(request.user.id !== document.owner_id){
// 文章は読み取り専用
}
request.user.id
とdocument.owner_id
はそこまで大きな式ではないが、変数が5つ入ってるので考えるのに少し時間がかかってしまう。
このコードが伝えたいことは「ユーザーが文書を所持しているか?」なので要約変数を使って、より明確に表現する。
【要約変数を使う場合】
const user_owns_document = (request.user.id === document.owner_id)
if(user_owns_document){
// ユーザーは文書を編集できる
}
if(!user_owns_document){
// 文章は読み取り専用
}
巨大な文を分割する
巨大な式を分割せずそのまま書いてしまうと下記のように長いコードになってしまう。
【コードの文を分割していない場合】
if (item.info.name === "apple") {
item.info.price = 300;
item.info.type = "fruits";
} else if (item.info.name === "tomato") {
item.info.price = 600;
item.info.type = "vegetable";
}else if(item.info.name === "beef"){
item.info.price = 1500;
item.info.type = "meat";
}
【コードの文を分割した場合】
理解しやすくするために先ほど紹介した要約変数を最上位に定義しコードを分割する。
const item_name = item.info.name;
const item_price = item.info.price;
const item_type = item.info.type;
if (item_name === "apple") {
item_price = 300;
item_type = "fruits";
} else if (item_name === "tomato") {
item_price = 600;
item_type = "vegetable";
} else if (item_name === "beef") {
item_price = 1500;
item_type = "meat";
}
このように文を分割することで、以下のような恩恵を得ることができる。
- タイプミスを減らすことができる
- 横幅が縮まるのでコードが理解しやすい
8章のまとめ
巨大な式を
- 説明変数
- 要約変数
を使って分割することで、読み手が理解しやすいコードになる。
9章 変数と読みやすさ
変数を適当に使うと下記のような問題が出てしまう
- 変数を追跡するのが難しくなる
- 変数のスコープが大きいとスコープを把握する時間が長くなる
- 変数が頻繁に変更されると今の値を把握するのが難しくなる
これらの解決策として、
- 不要な変数を削除する
- 変数のスコープを縮める
一つずつ解説していく。
不要な変数を削除する
8章で紹介した要約変数では読む人の理解を助けるために変数を増やした。
一方で、変数を増やしすぎるとその分コードが読みづらくなってしまう。
そのため不要な変数を削除することでコードを簡潔にし理解しやすいものにする
【削除すべき変数】
const now = new Date();
const root_message.last_view_time = now
上記で定義した変数now
は以下の理由で不要である
- 複雑な式を分割していない
-
new Date()
のままでも意味は通じる - 一度しか使っておらず、重複コードの削除になっていない
【不要変数を削除】
const root_message.last_view_time = new Date();
変数のスコープを縮める
リーダブルコードでは「グローバル変数は避けるべき」とアドバイスが書かれている。
グローバル変数はどこでどのように使われているのかが追いにくい。
グローバル変数に限らず、全ての変数の「スコープを縮める」のはいい考えである。
【長い処理の中にグローバル変数を利用した場合】
let str = "";
const method1 = () => {
str = "method1";
};
const method2 = () => {
// strを利用しない処理
};
const method3 = () => {
// strを利用しない処理
};
// 以下strを利用しないメソットが複数続く
上記の処理の場合str
はグローバル変数にもか関わらず、method1
のみでしか利用されていない。
読み手はstrが「他の処理でも使われているのではないか」という意識を持ってコードを読まなくてはいけない。(実際は使われていないのに)
今回の場合はmethod1でしかstrは利用されていないので、変数str
をローカル変数へ格下げる。そうすることでスコープ範囲が縮まり読み手もよりコードの理解がしやすくなる。
【グローバル変数をローカル変数に置き換え】
const method1 = () => {
let str = "method1";
};
// 以下strを利用しないメソットが複数続く
9章のまとめ
変数の読みやすさを上げるために、
- 複雑な式を分割しておらず、再利用もされていない変数を削除する
- 変数のスコープを縮める
第III部 コードの再編成
第III部ではコードを大きく変更する技法を紹介する。
具体的にコードを再構成する方法は、
- プログラムの主目的と関係のない「無関係の下位問題」を抽出する
- コードを再編成して、一度に1つのことをやるようにする
- 最初にコードを言葉で説明する。その説明を元にして綺麗な解決策を作る。
1つずつ詳しく解説をしていく
10章 無関係の下位問題を抽出する
10章では、無関係の下位問題を積極的に見つけて抽出することだ。以下の3つを考える。
- 関数やコードブロックを見て「このコードの高レベルの目標は何か?」と自問する
- コードの改行に対して「高レベルの目標に直接効果はあるか」「無関係の下位問題を解決しているか」と自問する
- 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする
具体的なコードと共に見ていく
入門的な例 : findCloseLocation()
【与えらた地点から最も近い場所を見つける関数findClosestLocation()
】
// 与えらた軽度・緯度に最も近いarrayの要素を返す
// 地球が完全な球体であることを前提としている
const findCloseLocation = (lat, lng, array) => {
var closest;
var closest_dist = Number.MAX_VALUE;
for (var i = 0; i < array.length; i += 1) {
// 2つの地点をラジアンに変換する。
var lat_rad = radians(lat);
var lng_rad = radians(lng);
var lat2_rad = radians(array[i].latitude);
var lng2_rad = radians(array[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 = array[i];
closest_dist = dist;
}
}
return closet;
};
この関数の中にあるループ処理内には、2つの地点(緯度・経度)の球面距離を算出する下位問題を扱っている。
この下位問題の部分を抽出しspherical_distance()
という新しい関数を作成する。
// 2つの地点(緯度・経度)の球面距離を算出
const spherical_distance = (lat1, lng1, lat2, lng2) => {
var lat1_rad = radians(lat1);
var lng1_rad = radians(lng1);
var lat2_rad = radians(lat2);
var lng2_rad = radians(lng2);
// 球面三角法の第二余弦定理の公式を使う。
return Math.acos(Math.sin(lat11_rad) * Math.sin(lat2_rad) +
Math.cos(lat1_rad) * Math.cos(lat2_rad) *
Math.cos(lng2_rad - lng1_rad));
};
const findClosestLocation = (lat, lng, arry) => {
var closest;
var closest_dist = Number.MAX_VALUE;
for (var i = 0; i < array.length; i += 1) {
// 先程の長いロジックは以下のように1行で済む
var dist = spherical_distance(lat, lng, array[i].latitude, array[i].longitude);
if (dist < closest_dist) {
closest = array[i];
closest_dist = dist;
}
}
return closest;
};
このように抽出することで、難しめの幾何学の計算を意識せずに、高レベルの目標(与えらた地点から最も近い場所を見つける処理)に集中できる。
純粋なユーティリティーコード
以下のような再利用を前提とするユーティリティーコードは関数切り出しておく。
【2桁をゼロ埋めにする関数】
const zeroPadding = (num: string) => {
const ret = ("00" + num).slice(-2);
return ret;
};
上記は入力した生年月日の1桁の月日を整形する際に再利用する可能性が高い。
無関係の下位問題は、別関数として切り出しておくことで「予め用意されている関数」のようなコードとして利用することができる。
他にも自分はプロジェクトで以下のような再利用を前提とする関数をあらかじめ作成をしている。
- DBのtime型から時間を(HH:MM)で返す関数
- 日付をHH:MM:SSの書式で返すメソット
- 日付をYYYY-MM-DDの書式で返すメソット
- string型の曜日をnumber型に直す
等、日付が絡んだデータの整形は頻出なので自分は再利用を前提に関数化している。
10章のまとめ
- プロジェクトの固有のコードから汎用コードを分離する
- 小さい関数を作りすぎると返って読みづらくなるので、再利用がある場合に関数化させる
11章 一度に1つのことを
一度に複数のことは理解しづらい。
例えば、オブジェクトを生成し、データを綺麗にし、入力をパースし、ビジネスロジックを適応するといったコードは一度に複数の処理をしているので理解がしづらい。
そのため「コードは1つずつタスクを行うようにすることが大切」である。
11章ではこの考えについて解説をしていく。
タスクを分割する手順
- コードが行っているタスクを全て列挙。
- タスクをできるだけ異なる関数に分割する。少なくとも異なる領域に分割する。
この方法についてより詳しく見ていく。
タスクは小さくできる
例えば、投票機能を例に考える。
具体的な機能としては、
- ユーザーが賛成or反対のどちらかをクリックする
- 現時点の投票スコアを取得する
- 賛成がクリックされたなら投票スコアを+1、反対なら-1をする
この処理を1つの関数内で行うと、やるべき処理が複数個になってしまい理解がしづらくなってしまう。
そのため以下の2つの関数を作成して処理を分割する。
- ユーザーが賛成を押したら+1、反対を押したら-1を返す関数
- 現時点でのスコアを取得し1で返ってきた値を計算
読みにくいコードがあれば、そこで行われているタスクを全て列挙する。そこには別の関数に分割できるに分割できるタスクがあるはずだ。
コードに思いを込める
複雑なロジックを説明するときに、詳しく話しすぎるとかえって相手を混乱させてしまう。
自分よりも知識が少ない人でも理解できるような「簡単な言葉」で説明する能力が大切である。
コードを「簡単な言葉」で書く手順は
- コードの動作を簡単な言葉で同僚にもわかるように説明する
- その説明の中で使っているキーワードやフレーズに注目する
- その説明に合わせてコードを書く
ロジックを明確に説明する
【ユーザーにページを閲覧する権限があるかを確認し、無ければ権限がないことをユーザーに知らせる】
<?php
$is_admin = is_admin_request();
if ($document) {
if (!$is_admin && ($document['username'] != $_SESSION['username'])) {
// 管理者でなく文章の所有者でもない
return not_authorized();
}
} else {
if (!$is_admin) {
// 管理者でない
return not_authorized();
}
}
// 引き続きページのレンダリング
?>
このコードのロジックをより簡略化して他の人にも理解されやすいコードにする
権限があるのは以下の2つ
- 管理者
- 文書の所有者(文書がある場合)
下記を意識してロジックを「理解しやすいコード」に組み立て直す。
- 条件式の中身は肯定系を使う
- ネストの数を下げる (if文)
- 不要な変数を減らす ($is_admin)
<?php
if (is_admin_request()) {
// 管理者 (権限あり)
} elseif ($document && ($document['username'] == $_SESSION['username'])) {
// 文章の所有者 (権限あり)
} else {
return not_authorized();
}
// 引き続きページのレンダリング
?>
このように書き換えることで、コードが小さくなりロジックも単純になった。かつ否定系も無くなったのコードの理解も容易にできる。
最後に
今回リーダブルコードを改めて読み直しましたが、感想としては
「やっぱり難しい」。
自分自身も読んでいく中でまだ腑に落ちていない(うまく自分で言語化できていない)箇所が色々出てきました。
リーダブルコードは読めば読むほど味が出る書籍なので、1~2周した程度じゃ自分のものにできないと甚だ感じました。
他にも記事を書いているのでぜひ読んでみてください
Discussion