なにこれ
筆者がリーダブルコードを読んで、アウトプット用として書いてます!
独自で具体例を考えて記述している部分もあります。
解釈の間違いなど、不明点 or 懸念点がありましたら、コメントでご指摘いだだけますと幸いです!
1 理解しやすいコード
コードは理解しやすくしろ!
1−1「優れた」コードってなに?
簡潔か安心か。
三項演算子を頑張って使うか、if-else文で書くか。
1−2 読みやすさの基本定理
他の人が最短時間で理解できるコード
他の人とは、1年後の未来の自分かも知れない
短ければ良いけど、それより理解できるコードがどうか自問自答する
自分しか使わないコードでも、他の人が再利用して使うかも。
もしくは別のプロジェクトで使うかも。
だから怠慢にならず、しっかりと考えて書く。
2 名前の情報を詰め込む
別の言い方をすると、変数名に情報を詰め込む
2−1 明確な単語を選ぶ
例えばget
はあまり明確な単語じゃない
もっとカラフルな単語を探す
気取った言い回しよりも、明確で正確な方がいい。
2−2 tmpやretvalなど汎用的な名前を避ける
tmp, retVal, foo
これらは数行だけで終わる短命な変数だけ使う
2−3 抽象的な名前よりも具体的な名前を使う
明確な単語を使う。抽象的な単語は使うな
2−4 名前に情報を追加する
2−5 名前の長さを決める
スコープが小さければ、短い変数名でもおk
広く使う変数は、長くて良いから明確な名前にしてね。
省略形
例)
BackEndManager ➡️ BEManager
メリット:短い
デメリット:暗号みたいになる
新しくチームに来た人が理解できるならおk。ダメなら変えて。
書いた自分でも、後で見たら暗号に見えるかもだから。
不要な単語は消す
ConvertToStringをToStringにしても大きな問題はない!
2−6 名前のフォーマットで情報を伝える
3 誤解されない名前
「他の意味と間違えられることはないか?」と何度も自問自答する
3−1 filter
filter
とresults
をセットで使う時は、何の結果なのか書いて!
選択するならselect
、除外するならexclude
3-2 clip
次の2つの見分けがつかない
・最後からlength文字を切り取る(remove)
・最大length文字まで切り詰め(truncate)
lengthも良くない
バイト数、文字数、単語数なのか分からん。
文字数なら、max_length
よりmax_chars
の方が適切
3−3 minとmax
限界値を含める時に使う
3−4 firstとlast
範囲を指定
3−6 ブール値の名前
常にtrueかfalseの意味を明確にしろ
read_password
上記の変数名はアンチパターン。次の2つの意味がある。
・これからパスワードを読む
・既にパスワードを読んでる
is, has, can, should
などをつけろ。
変数名は否定形より肯定形にした方が読みやすい。
dissable_ssl = false;
よりuse_ssl = true
など
NotFind = false
よりIsFind = true
みたいな?右側はぱっと思いつきだけど。
3−7 ユーザーの期待に合わせる
先入観を持ってコードを読んだ時に読み間違える場合がある。
その場合は名前を変えるのも有り
get
getは簡単な処理と思い込まれてる。
それがもし重い処理だったら困る。
get_avg_all_product
よりcompute_avg_all_product
の方が良い
3−8 複数の名前を検討する
4 美しさ
コードは雑誌のように美しくあるべき。
そのために次の3つが必要
・読み手が慣れているパターンと、一貫性のあるレイアウトを使う
・似ているコードは似ているように見せる
・関連するコードをまとめてブロックにする
4−2 一貫性のある改行を心がける
・似たメソッドがある中で、1つだけ改行がずれてると違和感
・同じようなメソッドなら共通化できるか考える
wifi = Calc.new(
hoge: 'fuga'
)
mobile_calc_now =
Calc.new(
hoge: 'fuga'
)
wifi = Calc.new(
hoge: 'fuga'
)
mobile_calc_now = Calc.new(
hoge: 'fuga'
)
4−3 メソッドを使った整列
見た目をよくすれば、コードの構造も改善できることがある!
4−4 縦の線を真っ直ぐにする
checkUser('yamada', 'tarou', 21)
checkUser('tani', 'kyoukomaru', 100)
checkUser('yamada', 'tarou' , 21)
checkUser('tani', 'kyoukomaru', 100)
Q.手間をかけても整列すべきか?
A.整列すべき!
いちいち整列するのは面倒と思うけど、ほとんどのケースで対して負担にならない。
4−5 一貫性のある並び
並べる順番には気をつける。上から順に行くように
details = request.POST.get('details')
url = request.POST.get('url)
4−6 宣言をブロックにまとめる
4−7コードを段落に分割する
def bad_send_email
friend = User.find(1)
puts "comment"
friend.save
end
def good_send_email
friend = User.find(1)
friend.save
puts "comment"
end
個人的な好みと統一性
次の2つはどちらも差はないけど、このコードが混在すると理解に苦しむ。
なので、チームとして統一する書き方を決める
class Logger {
//
}
class Loger
{
//
}
一貫性のあるスタイルは、「正しい」スタイルよりも大切
5 コメントすべきことを知る
コメントの目的は、書き手の意図を読み手に知らせること
5−1 コメントするべきでは「ない」こと
コードからすぐに分かることをコメントに書かない
コメントのためのコメントをするな
コードから分かることはコメントするな
ひどい名前はコメントを付けずに名前を変えろ
5−2 自分の考えを記録する
映画のコメンタリーのイメージ
なぜこのコードはこのやり方なのか?
・ここの処理は訳ありだ
・この変数名は100%の意味ではない
・このクラスは汚い。別のsubclassを作って分割した方が良い
コードの欠陥にコメントを付ける
・TODO
(大きな修正)
・FIXME
(既知の不具合がある)
・HACK
(あまり綺麗じゃない解決策)
・XXX
(危険!大きな問題あり)
これからどうしたいかをコメントに書く
定数にコメントを付ける
定数にまつわる背景
5−3 読み手に立場になって考える
他の人とは、あなたほどプロジェクトを熟知していない人のこと
質問されそうなことを想像する
コードを読んだ人が「え?」と思うところを予想してコメントする
ハマりそうな罠を告知する
平均的な読み手が驚くような動作は文章化しておく
次の2つを考える。
・このコードを見て、びっくりすることは何だ?
・どんなふうに間違えて使う可能性がある?
全体像のコメント
・ソースコードを読んでも得られない情報のコメントなど
・短い文章でおk.何もないよりマシ
要約コメント
関数のブロックの処理ごとに書いてもおk。概要をまとめるイメージ
ただ重要なのは、優れたコメントより優れたコード
5−4 ライダーズブロックを乗り越える
とりあえず考えていることをコメントに書き出す
コメントを書く作業は3つに分割できる
1.頭の中にあるコメントをとにかく書き出す
2.コメントを読んで、改善が必要なものを見つける
3.改善する
やばい。これは面倒になる
⬇️
やばい➡️この処理は気をつけて。
これは➡️入力時にやると
面倒になる➡️実装の難易度が上がる。
6章 コメントは正確で簡潔に
コメントは領域に対する情報の比率を高くしろ
6−1 コメントを完結にしておく
3行で説明するより1行で簡潔に
6−2 あいまいな代名詞を避ける
「それ」や「これ」みたいな
6−3 歯切れの悪い文章を磨く
これまでクロールしたURLかどうかによって優先度を変える
⬇️
これまでにクロールしていないURLの優先度を高くする
6−4 関数の動作を正確に記述する
このファイルに含まれる行数を返す
⬇️
このファイルに含まれる改行文字\n
を数える
6−5 入出力のコーナーケースに実例を使う
変数src
の戦闘や行末にあるchars
を除去する
⬇️
strip(‘abba/a/ba’, ‘ab)は`’/a/‘を返す
⬇️
strip(‘ab’, ‘a’)は’b’を返す
長い説明コメントより、実例を交えて入出力の結果を書くと分かりやすい
6−6 コードの意図を書く
コメントは、コードを書く時に考えていることを読み手に伝えるためのもの
コードの意図は、詳細レベルより高レベルで記述する。
抽象度を上げるかは時と場による
listを逆順に居てレートする
⬇️
listを値段の高い順に表示する
6−7 「名前付き引数」コメント
Connect(/* timeout_ms */ 10)
ややこしい引数だったりに使うと良いかも
6-8 情報密度の高い言葉を使う
7章 制御フローを読みやすくする
条件やループなどの制御フローは、できるだけ「自然」にする。コードの読み手が立ち止まったり読み返したりしないように書く
7−1 条件式の引数の並び順
if (length >= 10)
左側 | 右側 |
---|---|
調査の対象。変化する。 | 比較の対象。あまり変化しない。 |
7-2 if-elseブロックの並び順
・条件は否定形if(!debug)
よりif(debug)
を使え
・単純な条件を先に書く。if-elseが同じ画面に表示されるので見やすい
・関心を引く条件や目立つ条件を先に書く。
「ピンクの象を考えないでください」現象と同じ。1回頭に入ると考えちゃうから、複雑な条件を書くな!
if(!url('pink_zoo')) {
p 'ng'
} else {
p 'ok'
}
if(url('pink_zoo')) {
p 'ok'
} else {
p 'ng'
}
7−3 三項演算子
賛否あり。
行数を短くするよりも、他の人が理解するのに掛かる時間を短くしろ
頑張って1行で書かなくても良い。分かりやすさのほうが重要
// good
time = (hour >= 12) ? 'pm' : 'am'
// bad
time = if(hour >= 12) {
'pm'
} else {
'am'
}
// bad
return exp >= 0 ? man * (1 << exp) : man / (1 << -exp)
# good
return if(exp >= 0) {
man * (1 << exp)
} else {
man / (1 << -exp)
}
基本的にはif/else
を使う。三項演算子で簡潔に書けそうならおk
7−4 do/whileループを避ける
コードを2回読む必要があったり、下から上に移動して読む必要があるので使うな
7−5 関数から早く返す
7−6 悪名高きgoto
スパゲティコードになる。
7−7 ネストを浅くする
修正する時はコードを新鮮な目で見る。一歩下がって全体を見る
ネストを変える時は特に!
// 初期
if(result1 == SUCCESS) {
done()
} else {
return
}
// 変更後(ややこしい)
if(result1 == SUCCESS) {
if(result2 != SUCCESS) {
return
}
done()
} else {
return
}
done()
早めに返してネストを削除する
if(result1 != SUCCESS) {
return
}
if(result2 != SUCCESS) {
return
}
done()
ループ内部のネストを削除
ガード節を使ったり、continueをワンライナーで書いたりする。
返す条件を先に書くこと!
直線的に分かるコードを書こう
8 巨大な式を分割する
巨大な式は、飲み込みやすい大きさに分割する
人間は同時に3〜4のことしか考えられない。
8−1 説明変数
・(巨大な)式を分割できる
・簡潔な名前で式を説明することで、コードを文書化できる
・コードの主要な概念を読み手が認識しやすくなる
if params[:user_name] == 'root'
user_name = params[:user_name]
if user_name == 'root'
8−2 要約変数
if (request.uesr.ud == document.owner_id) {
// ユーザーはこの文章を編集できる
}
if (request.user_id != document.owner_id) {
// 読み取り専用
}
user_owns_document = (request.uesr.ud == document.owner_id)
if (user_owns_document) {
// ユーザーはこの文章を編集できる
}
if (!user_owns_document) {
// 読み取り専用
}
8−3 ド・モルガンの法則を使う
if(!(file_exists && !is_protected)) Error
if(!file_exists || is_protected) Error
8−4 短絡評価の悪用
「頭がいい」コードには気をつける。後で他の人(未来の自分含む)が読む時にわかりにくくなる
1行で収めることが素晴らしいことじゃない!!
2行になっても最短時間で理解できる方が重要!
8−5 複雑なロジックと格闘する
return (begin >= other.begin && begin < other.end) ||
(end > other.begin && end <= other.end)
if(other.end <= begin) return false // 一方の終点が、この始点よりも前にある。
if(other.begin >= end) return false // 一方の始点が、この終点よりも後になる
return true
8-6 巨大な式を分割する
if($("#vote_value").html() === "Up") {
$("#thumbs_up).addClass("highlighed")
$("#thumbs_down).removeClass("highlighed")
} else if($("#vote_value").html() === "Down") {
$("#thumbs_up).removeClass("highlighed")
$("#thumbs_down).addClass("highlighed")
}
let thumbs_up = $("#thumbs_up")
let thumbs_donw = $("#thumbs_down")
let vote_value = $("#vote_value")
let hi = "highlighed"
if(vote_value.html() === "Up") {
thumbs_up.addClass(hi)
thumbs_down.removeClass(hi)
} else if("vote_value").html() === "Down") {
thumbs_up.removeClass(hi)
thumbs_down.addClass(hi)
}
8−7 四季を完結にするもう1つの創造的な方法
9 変数と読みやすさ
変数を雑に使うと起きる問題
・変数が多いと、変数を追うのが難しい
・変数のスコープが大きいと、スコープを把握する時間が長くなる
・変数が頻繁に変更されると、現在の値を把握するのが難しい
9−1 変数を削除する
邪魔な変数を削除する
役に立たない一時変数
次のコードの問題
・複雑な式を分割してない
・より明確になっていない。Date.nowでも伝わる
・一度しか使ってないので、重複コードの削除になっていない
now = Date.now()
message.created_at = now
message.created_at = Date.now()
私的メモ
すごいあるあるだと思ったので、こういったコードがないか見直す習慣をつける
逆に見つけられるようになる
中間結果を削除する
タスクはできるだけ早く完了するほうが良い
let remove_index = null
for (let i = 0; i < array.length; i++) {
if (array[i] === null) {
remove_index = array[i]
}
if (remove_index !== 0) {
array.splice(remove_index, 1)
}
}
for (let i = 0; i < array.length; i++) {
if (array[i] === null) {
array.splice(i, 1)
return
}
}
制御フロー変数を削除する
制御フロー変数は、ほとんどの場合でうまくプログラミングすれば削除できる。
done = false // 制御フロー変数
while(!done) {
if(処理) {
done = true;
continue
}
}
while (処理) {
if(処理) {
break;
}
}
面接時には変数が3つ以上あると言われてる。
コードを読む時に変数が3つ以上あったら、常に面接を受けるのと同じ心意気になってしまう。そんな思いをさせたいか?
9-2 変数のスコープを縮める
すべての変数のスコープを縮めるのは良いこと
変数のことが見えるコード行数をできるだけ減らす
・ミニグローバル変数をローカル変数に格下げする
JavaScriptのプライベート変数
submitted = false // グローバル変数
let submit_form = function(){
if(submitted) {
return // 2重投稿させないため
}
submitted = true
}
let submit_form = (function(){
submitted = false // グローバル変数
return function(){
if(submitted) {
return // 2重投稿させないため
}
submitted = true
}
}()) // 即時関数でプライベート化
JavaScriptのグローバルスコープ
letなどをつけずに変数を定義すると、グローバル変数になる?今はそもそも定義できない気がする
私的メモ
だからvueだとlet or constがないとエラーを吐くのかな?
9−3 変数は一度だけ書き込む
constなどイミュータブルな変数を使えば、コードが理解しやすくなる
変数を操作する場所が増えると、現在地の判断が難しくなる
Javaの作者いわく、イミュータブル(不変)はトラブルが少なくなる傾向
9-4 最後の例
中間変数を消して、すぐに返す。
elemの代入回数が多かったので、1回にして置いやすくした。
let setFirstEmptyInput = function(new_value) {
let found = false
let i = 0
let elem = document.getElementById('input' + i)
while(elem !== null) {
if(elem.value === ''){
found = true
break
}
i++
elem = document.getElementById('input' + i)
}
if(found) elem.value = new_value
return elem
}
let setFirstEmptyInput = function(new_value) {
for(let i = 0; i < 4; i++) {
elem = document.getElementById('input' + i)
if(elem.value === null) {
return null
}
if(elem.value === '') {
elem.value = new_value
return elem
}
}
}
10 無関係の下位問題を抽出する
無関係の下位問題を積極的に見つけて抽出する
1.関数やコードブロックを見て、「このコードの高レベルの目標は何か?」と自問する
2.コードは各行に「高レベルの目標に直接的に効果があるのか?」or「無関係の下位問題を解決してるか?」と自問する
3.無関係の下位問題を解決してるコードが相当量あれば、それらを抽出して別の関数にする
10−1 入門的な例
すごいざっくり言うと次の例。
メソッド内の別の処理を、他のメソッドに切り分けること。
これが下位問題
def puts_result
a = 1
b = 2
result = a * b
puts result
end
def addition(num1, num2)
num1 * num2
end
def puts_result
a = 1
b = 2
result = addition(a, b)
puts result
end
10−2 純粋なユーティリティーコード
「この関数ないなあ」と思った時には、作れば良い!
それが他でも使いまわせる。
10-3 その他の汎用コード
思いも寄らない恩恵
関数は、小さくて独立したものになっていれば、機能追加、読みやすさの向上、エッジケースの処理などが楽にできる!
例外の処理を切り分けたりも楽。
10−4 汎用コードをたくさん作る
プロジェクトから完全に切り離されているコードは3150!
メンテしやすい・影響を与えないことでコードが短く保てる!
大事なのは、下位問題を分離して処理できるか
10−5 プロジェクトに特化した機能
下位問題を取り除くだけでも効果あり!
10−6 既存のインターフェースを簡潔にする
理想とは程遠いインターフェースに妥協することはない。
自分でラッパー関数を用意して、汚いインターフェースを覆い隠せ!
10−7 必要に応じてインターフェースを整える
多くのコードはその他のコードを支援するためだけに存在する。
例えば、関数の事前処理や事後処理。
こういうグルーコードは、プログラムの本質的なロジックと関係ないことが多い。
10−8 やりすぎ
小さな関数を作りすぎると、逆に読みにくくなる。(当たり前だけど)
11 一度に1つのことを
コードは1つずつタスクを行うようにしろ
一度に1つのタスクをするために使ってる手順
1.コードが行ってるタスクをすべて列挙する。タスクは「オブジェクトが妥当かどうか」や「ツリーのすべてのノードをイテレートする」など曖昧~~(アイマイミーマイン)~~
2.タスクをできるだけ異なる関数に分割。少なくとも異なる領域に分割する
11−1 タスクは小さくできる
一度に1つのタスクを適用する
12 コードに思いをこめる
おばあちゃんに説明して分かるぐらい。
コードをより明確にする手順
1.コードの動作を簡単な言葉で同僚にもわかるように説明する
2.その中で使っているキーワードやフレーズに注目する
3.その説明に合わせてコードを書く
12−1 ロジックを明確に説明する
12-2 ライブラリを知る
13 短いコードを書く
最も読みやすいコードは、何も書かれていないコード
なるべく新しいコードを書かずに、ライブラリのコードを使う。
ライブラリのコードは素晴らしい。緻密に練られたコードだから。
エンジニアが1日に書く出荷用のコードは、平均10行!
定期的に組み込みライブラリの一覧を見て、使えそうなライブラリがないか頭にインデックスを作れ!
新しいコードを書くと、その分テスト・文書化・保守が必要になり、プロジェクトが重たくなり、開発も難しくなる。
新しいコードを書かない3つの原則
・不必要な機能をプロダクトから削除する。過剰な機能は持たせない
・最も簡単に問題を解決できるような要求を考える
・定期的に全てのAPIを読んで、標準ライブラリに慣れる
14 テストと読みやすさ
14−1 テストを読みやすくて保守しやすいものにする
他のプログラマが安心してテストの追加や変更ができるように、テストコードを読みやすくする
テストコードが大きくて恐ろしいことの弊害
1.本物のコードの修正を恐れる
2.新しいコードを書いた時にテストを追加しなくなる
14−3 テストを読みやすくする
大切ではない詳細はユーザ(自分含む)から隠し、大切な詳細は目立つようにする
テスト失敗時には、失敗した時の値が出力されるとデバックしやすい!確かに。
コードを完全にテストする、最も単純な入力値の組み合わせを選択しろ!
(テストは最もキレイで単純な値を選べ)
一つの機能に複数のテスト
1度ですべての項目をテストするんじゃなくて、複数回に分ける。
その方がデバッグしやすく読みやすい。他の人も修正しやすい。
assertだと失敗分がそのまま表示される。独自でエラー文を作るのも良いかもね!
14−9 やりすぎ
・テストのために本物のコードの読みやすさを犠牲にしてしまう
本末転倒
・テストのガバレッジを100%にしないと気がすまない
コードの90%をテストする方が楽。
残りの10%の多くは、ユーザインタフェースやどうでもいいエラーケースがほとんど。
コストが高くないので、わりに合わない!
・テストがプロダクト開発の邪魔になる
プロジェクトの一部にすぎないテストのはずが、プロジェクト全体を支配してることがある。テストを書くのが儀式になってる。
14−10 まとめ
・テストのトップレベルはできるだけ簡潔にする。入出力のテストはコード1行で記述できるとおk
・テストが失敗したら、バグの発見や修正がしやすいようなエラーメッセージを表示する
・テストは有効で最も単純な入力値を使え!
・テスト関数は長くてもおk。他で呼び出すことはないからね!ほぼ説明文でも良い
特に新しいテストの追加や修正を簡単にすることが大切。