目的
リーダブルコードの内容をざっくりまとめて、重要な部分をふり返れるようにする。
※一気に書いたわけではないので、所々読みづらいです。
一章 理解しやすいコード
優れたコードとは?
- 他の人が短時間で理解できる
- 半年前に書いたコードを最短で理解できなければならない
- 短時間で読みやすいコード > 短いコード
- 「このコードは理解しやすいだろうか?」と常に問いかける
第二章 名前に情報を詰め込む
- 明確な単語を選ぶ
- 汎用的な名前を避ける
- 抽象的な名前よりも具体的な名前を使う
- 接尾辞や接頭辞を使って情報を追加する
- 名前の長さを決める
- 名前のフォーマットを情報で伝える
以下に例を挙げる。
明確な単語を選ぶ
対象のメソッドや変数がどのような動きをするのか明確にしないといけない。
命名するときは抽象的な名前よりも明確で何をするか分かる名前にすることが重要である。
get_name()
メソッドがあるとして、これはページ情報を取得するメソッドのように見える。
しかしどこから取得するのか一目で理解するのは難しい。
- ローカルキャッシュ
- データベース
- インターネット
仮にインターネットから取得してくるのであれば、fetch_page()
やdownload_page()
の方が明確である。
類似した動きが多い場合はもっと良い名前がないか類似時点等で調べながら命名すると、動作と名前が明確になる。
find → search, extract, locate, recover
汎用的な名前は避ける
いい名前というのは変数の目的や値を表すものである。
そのため変数名と挙動が一致しないような名前を付けるべきではない。これは名前を見て実際の挙動と考えたとき、間違えがあるかを判断しやすくするためである。
悪い例:足し算をするなら変数名なのにretval
を用いる
良い例:足し算をするなら変数名addNum
を用いる
tmp
は一時的に情報を保管するために用いられることが多いためこの変数には他に役割がないといった明確な意味が存在する。
逆に明確な役割がないのにtmp
を使うべきではない。
ループイテレータ
i, j, k, iter
などの名前はインデックスやループイテレータでよく利用される。
イテレータが複数ある場合にはどの変数名に対して処理を行っているか補足してあげるとバグを発見しやすい。
例:メンバー、ユーザーのイテレータi, j
をmember_i, user_i
などにする
長くなるのでmi, ui
でも良い。
名前に情報を追加する
絶対に知らせなきゃいけない大切な情報は「単語」を変数名に追加する。
単位だけでなく危険や注意を喚起位する情報も追加したほうがいい。
例:遅れを知らせる delay
をdelay_secs
文字コードをutf-8に変更したらhtml
をhtml_utf8
名前は長過ぎない方がいい
かといって短すぎるのも良くない。
例外はありスコープが小さければ短い場合でも良い
二章のまとめ
- 名前に情報を詰め込もう。
- 明確な単語を選ぼう。
- 具体的な名前を用いて物事を詳細に説明しよう。
- 変数名には大切な情報を追加しよう。
第三章 誤解されない名前
名前が「他の意味と間違えられる可能性がないか」を常に考える
- 限界値を含めるときには
min
とmax
を使う - 範囲を指定するときは
first
とlast
を使う
cart_too_big_limit = 10
↓
max_items_in_cart = 10
limit
は限界という意味合いを持っているが、10を含むのか含まないのかどっちなんだい!?となるため、変数名にmax
を加えることで限界値がひと目で理解できる。
array = ["a", "b", "c", "d", "e"]
start = 0
stop = 4
// 範囲内の値を返す
integerRange(start, stop)
三章のまとめ
- 最善の名前というのは誤解されない名前である
- 半年後の自分が見ても同じ解釈ができるように命名すること
第四章 美しさ
本章ではコードを読みやすくするための余白、配置、順列について説明していく
優れたコードとは「目に優しい」ものでないとならない
具体的には4点が挙げられる
- 一貫性のあるレイアウトで記述する
- 似ているコードは似ているように見せる
- 関連するコードをまとめてブロックにする
- 空行を使って大きなブロックを論理的な「段落」に分ける
コードの整列に関してはVScode(Windows)の場合、Shift + Alt + Fで成形することができるため、この章の内容は省略とする
蛇足
以前Qiitaで投稿したコードを自動整形してみる
元のコードがこれで
import requests
from bs4 import BeautifulSoup
from pprint import pprint
from time import sleep
def getStoreInformation():
# AppleStoreのURLを取得する
location_lists = ["marunouchi", "ginza","shinjuku","shibuya","omotesando","kawasaki"]
url_lists = ["https://www.apple.com/jp/retail/{}/".format(l) for l in location_lists]
# 店舗名と営業時間を出力する
for url_list in url_lists:
sleep(2)
r = requests.get(url_list)
soup = BeautifulSoup(r.text, 'html.parser')
locationData = soup.find(class_='store-detail-heading-name').find('h1')
print(locationData.text)
dateKey = [d.find(class_="visuallyhidden").text for d in soup.find_all(class_='store-hours-table-date')]
hourValue = [h.text for h in soup.find_all(class_='store-hours-table-hours')]
storeInformation = dict(zip(dateKey, hourValue))
pprint(storeInformation)
pprint("-----------------------------------------")
if __name__ == '__main__':
getStoreInformation()
自動整形した後がこれ
import requests
from bs4 import BeautifulSoup
from pprint import pprint
from time import sleep
def getStoreInformation():
# AppleStoreのURLを取得する
location_lists = ["marunouchi", "ginza",
"shinjuku", "shibuya", "omotesando", "kawasaki"]
url_lists = [
"https://www.apple.com/jp/retail/{}/".format(l) for l in location_lists]
# 店舗名と営業時間を出力する
for url_list in url_lists:
sleep(2)
r = requests.get(url_list)
soup = BeautifulSoup(r.text, 'html.parser')
locationData = soup.find(class_='store-detail-heading-name').find('h1')
print(locationData.text)
dateKey = [d.find(class_="visuallyhidden").text for d in soup.find_all(
class_='store-hours-table-date')]
hourValue = [h.text for h in soup.find_all(
class_='store-hours-table-hours')]
storeInformation = dict(zip(dateKey, hourValue))
pprint(storeInformation)
pprint("-----------------------------------------")
if __name__ == '__main__':
getStoreInformation()
このレイアウトが正しいかは別として、規則的に整形されるため一貫性のあるコードが出来上がる
第五章 コメントすべきことを知る
コメントの目的は「コードの動作を説明する」だけではなく書き手の意図を読み手に知らせることである
- コメントするべきでは「ない」ことを知る
- コードを書いているときの自分の考えを記録する
- 読み手になって「何が必要な情報か」を考える
コメントすべきでは「ない」ことを知る
結論:見てわかることなら書かない
# メインメソッド
def main():
x = 5 + 1
# xを返す
return x
もしひと目見てもわからないメソッド名や変数名がある場合はコメントをせず名前を変更する
自分の考えを記録する
何をコメントすべきか → コードに対する考え方を記録する
またコードが汚い理由も記載することで、自他ともに認めると共に誰かに修正を促している
「何が必要な情報か」を考える
読み手というのは自分以外の読み手を示しているが、数日数カ月後の自分に対してのコメントでもある。
ハマりそうな罠を告知することで「どんなふうに間違えて使う可能性があるだろう」と問いかけ前もって予測し記載しておく。
また読み手が細部にまで時間をとらわれないようにコードブロックを設けて概要をまとめる。
第六章 コメントは正確で簡潔に
コメントを書くからには正しく読み手に伝わらないければならい。
そのためには、どうすれば正確で簡潔なコメントを書くことが出来るかをこの章で学んでいく。
- 代名詞の「それ」「これ」などは複数を指すことがあるので使用しない
- 何をする関数かを正確に説明する
- コメントを簡潔に保つ
曖昧な代名詞を避ける
-
悪い例:データをキャッシュに入れる。ただし、先にそのサイズをチェックする。
この表現は代名詞「その」がデータを指名しているのか、キャッシュを示しているのか直感的に理解することが出来ない。 -
良い例:データをキャッシュに入れる。ただし、先にデータのサイズをチェックする。3
こうすることで「その」がデータを示していると明確にできる。
何をする関数化を正確に伝える
関数で何かを数えるときは、基準を明確にすると意味が伝わりやすい。
- 悪い例:このファイルに含まれる行数を返す
行数が何行あるか不明なため、どういった基準で数えているのか分からない。
- 良い例:このファイルに含まれる改行文字(’\n’)を数える
こうすることで何を基準に行数を数えているかを明確にできる。
また、コードの情報をそのまま伝えるではなく、動作を説明できるようにする。
- 悪い例:list を逆順にイテレートする
- 良い例:list を値段の高い順に表示する
このように記述することで、list が値段の高い順なのに低い順に表示するコードを書いたらバグの原因を特定しやすい。
コメントを簡潔に保つ
複数行にまたがる改行はせず、極力一行で説明する。
第七章 制御フローを見やすくする
条件やループなどの制御フローがない式は比較的に読みやすい
この章ではコードの制御フローを読みやすくすることについて解説する
- 比較をするとき(if文)は変化する値を左に、不変な値を右に配置する
- 条件式は肯定形から記述する
- ≠からかかない
- 三項演算子等の条件式を短いからという理由で無闇矢鱈に使用しない
- 重要なのは短さではなく見やすさである
- ネストしている文が多いと見づらくなるため、早めにreturnする
以下、解説していく
比較をするとき(if文)は変化する値を左に、不変な値を右に配置する
if (length > 10)
if (10 < length)
両者ともに同じことを書いているが、恐らく前者のほうが読みやすいと判断する人が多いと思う
君が18歳以上ならばといった文が自然なように、18年が君の年齢以下ならばといった文章はなんだか読みづらい
こういった観点から条件式の左側には変化する値を配置し、右側には変化しない不変な値を配置する方が頭が混乱しづらい
条件式は肯定形から記述する
以下の式を見てほしい
a = 5
b = 6
# 式1
if a == b:
print("true")
else:
print("false")
# 式2
if a != b:
print("false")
else:
print("true")
式1と式2は同じ動きをするが、条件式の並び順は優劣が存在する
- 条件は否定形よりも肯定形を使う(式1)
- 単純な条件を先に記述する
- 関心を引く条件や目立つ条件を先に記述する
この優劣は衝突することもあるので絶対にこれ!とは言えない
それでも優劣は明確に決まることが多い
例外:
否定形を先に記述するときは単純で関心や注意を引く場合に限定しよう
状況によって判断基準が変化するが、ここで挙げた判断基準に注意していこう
三項演算子等の条件式を短いからという理由で無闇矢鱈に使用しない
三項演算子は読みやすくて簡潔に記述できるため、複数行が一行にまとまるので支持されることが多い(しらんけど)
むやみに使用すると読みにくくなってしまうため注意すると同時に、重要なのは短さではなく見やすさということを忘れないこと
簡潔にYesNoで記述できるときに限り使うべきだと感じている
ネストしている文が多いと見づらくなるため、早めにreturnする
ネストが深くなってくると読み手はいつも以上に考えながら読まなければならないため疲れる
そのためネストが深くなってしまう場合は、早めに関数から値を返すことで解消できる
早めに返すことで関数の上部で単純な処理を先に処理するのが便利
第八章 巨大な式を分割する
コードの塊が大きいと思わにエラーを発生させる可能性があり悪影響を及ぼす
最近の研究によると、人間は一度に3~4つの物事しか考えられないことがわかっている
コードの式も大きくなればなるほど理解が難しくなるため、小さく分割することが重要
この章では以下の内容を学ぶ
- 巨大な式を分割する
- 簡潔な名前で式を説明することで、コードを文章化する
- コードの主要な「概念」を正しく読み手に認識させる
巨大な式を分割する
説明変数
最初に式を簡単にする方法として、説明変数というものがある
ざっくりまとめると読みにくいコード(直感的に理解し辛いコード)は変数を作ること
// before
if line.split(':'[0].strip() == "root";
// after
username = line.split(':'[0].strip();
if username == "root"
上記のコードではusernameが説明変数の役割を担っている
要約変数
式を説明する必要がなかったとしてもコードを小さな名前に置き換えて、管理や理解を簡単にすることを要約変数という
// before
if (request.user.id == document.owner_id) {
// Onwerの場合
}
...
if (request.user.id != document.owner_id) {
// Ownerでない場合
}
// after
final boolean user_owns_document = (request.user.id == document.owner_id);
if (user_owns_document) {
// Onwerの場合
}
...
if (!user_owns_document) {
// Ownerでない場合
}
それほど大きな式ではないが、変数が5個入っており考えるのに時間がかかる
そこで要約変数を用いることで共通部分を最初に定義しまとめることで、この関数で参照する概念を事前に伝えることができる
ドモルガンの法則を使う
むかし理科の授業でやったやつ
覚えていなかったら「notを分配してand/orを反転する」逆は「notをくくりだす」と覚えればいい
なんでも一行に収めようとしない
スッキリとしたコード→いいと思う
スッキリしているけど読みづらいコード→NO!
複数行だけど理解しやすく読みやすいコード→いいと思う
コードは書いている時間よりも変更や理解に時間が一番かかる
その時間を一番短くするためには?→ 複数行でも理解しやすいコードのほうが優れている
巨大な式を分割する
同じような内容が続く場合は要約変数として関数の上部に抽出する
これによるメリットは以下の通り
- タイプミスを減らせる
- 横幅が縮まるためコードが見やすくなる
- 変更する箇所が短くて済む
第九章 変数と読みやすさ
変数に当てはめようと八章では説明したが、逆に変数を使いすぎるとコードが理解しづらくなり、以下のようなデメリットが発生する
- 変数が多いと変数を追跡するのが難しくなる
- 変数のスコープが大きいとスコープを把握するのが難しくなる
- 変数が頻繁に変更されると現在の値を把握するのが難しくなる
プログラムの変数はすぐに増えてしまうため、できる限り変数を減らして「軽量」にすることでコードが読みやすくなる
具体的には
- 邪魔な変数は削除する
- 中間結果だけしか使わない変数はできる限り削除する
- 変数のスコープはできる限り小さくする
- 変数を数行のコードからしか見えない位置に移動する
- 一度だけ書き込む変数を使う
- 変数に一度だけ値を設定すればコードを理解しやすくなる
変数を削除する
なんでも変数に当てはめようとすると無駄な変数が生まれ逆にコードが見づらくなる
具体的には
package Code;
import java.util.Date;
public class ChapterEight {
public static void main(String[] args) {
Date date = new Date();
Date now = date;
System.out.println(now);
}
}
このnowは使う意味がない
上記で定義した変数不要な理由は
- 複雑な式を分割していない
-
new Date()
のままでも意味は通じる - 一度しか使っていない、かつ重複の排除になっていない
一度しか使わないのであればこうでいい
package Code;
import java.util.Date;
public class ChapterEight {
public static void main(String[] args) {
System.out.println(new Date());
}
}
変数のスコープを縮める
グローバル変数を縮める、といった考えは聞いたことがあると思う
しかしグローバル変数に限らず、全ての変数のスコープを縮めたほうが良い
変数は一度だけ書き込む
変数を作業する箇所が増えると現在地の判断が難しくなる
第十章 無関係の下位問題を抽出する
この章では、無関係の下位問題を積極的に見つけて抽出することが重要である
以下の3つがこの章で伝えたいこと
- 関数やコードブロックを見て「このコードの高レベルの目標は何か?」と自問する
- コードの改行に対して「高レベルの目標に直接効果はあるか」「無関係の下位問題を解決しているか」と自問する
- 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする
といっても無関係の下位問題って言葉が難しいので、実際のコードを見てほしい
// 与えられた緯度経度に最も近い'array'の要素を返す
// 地球が完全な球体であることを前提としている
var findClosestLocation = function(lat, lng, array) {
var closest;
var closest_dist = Number.MAX_VALUE;
for(var i = 0;i < array.length;i++) {
var lat_rad = radians(lat);
var lng_rad = radians(lng);
var lat2_rad = radians(array[i].latitude);
var lng2_rad = radians(array[i].lngitude);
// 「球面三角法の第二余弦定理」を使う
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 closest;
}
この関数の中にあるループ処理は、2つの地点の球面距離を算出するといった下位問題を扱っている
コード量が多いため、この下位問題の部分を抽出しspherical_distance()
という新しい関数を作成する
// 与えられた緯度経度に最も近い'array'の要素を返す
// 地球が完全な球体であることを前提としている
var findClosestLocation = function(lat, lng, array) {
var closest;
var closest_dist = Number.MAX_VALUE;
for(var i = 0;i < array.length;i++) {
var lat_rad = radians(lat);
var lng_rad = radians(lng);
var lat2_rad = radians(array[i].latitude);
var lng2_rad = radians(array[i].lngitude);
// 「球面三角法の第二余弦定理」を使う
var dist = spherical_distance(lat, lng, array[i].latitude, array[i].longtude);
if(dist < closest_dist) {
closest = array[i];
closest_dist = dist;
}
}
return closest;
}
幾何学の計算も新しく関数として抽出することで、高レベルの目標(与えらた地点から最も近い場所を見つける処理)に時間を費やすことができる
この画像がとても参考になる
再利用することが前提のコードは予め関数にしておく
関数のように何度も使い回す前提のものは予め作成しておく
小さな関数を作りすぎない
せや!めっちゃ分けたろ!!と小さな関数を作りすぎないこと
新しく関数を作ると読みづらさのコストが発生する
第十一章 一度に一つのことを
一度に複数のことをするコードは理解しにくい
例えば、オブジェクトを生成して、データをきれいにして、入力をして…….といったコードでタスクが個別に完結しているコードよりも理解するのが難しい
読みにくいコードがあれば、そこで行われているタスクを全て列挙し別に分割できるタスクがないか今一度確認するべきである
具体的な例を挙げる
タスクは小さくできる
本書では投票機能を例に挙げている
具体的な機能は
- ユーザーが賛成or反対のどちらか選択する
- 現時点の投票スコアを取得する
- 賛成が選択されたなら投票スコアを+1、反対なら-1をする
この処理を1つの関数内で行うと、やるべき処理が複数個になってしまい理解がし辛いコードが生まれる
そのため処理を関数でわける
- ユーザーが賛成を押したら+1、反対を押したら-1を返す
- 現時点でのスコアを取得して、1で返ってきた値を計算する
読みづらいと感じるコードでもタスクを全て列挙することで別のタスクにすることができるものがあるはず
第十二章 コードに思いを込める
誰かに複雑な考えを伝えるときは、細かい事まで話すと相手の理解が追いつかず混乱させてしまう。そのため相手に何かを説明するときは小学生でも理解ができるほど簡単に説明するべきだと思う。現在書いているコードを赤の他人が見たときでも直感的に理解できるような簡単な言葉で書くべきである。
本章ではコードをより明確にする簡単な手順を紹介する
- コードの動作を簡単な言葉で説明する
- その説明の中で使っているキーワードに注目し、その説明に合わせてコードを書く
ロジックを明確にする
以下のコードはユーザーにページを閲覧する権限があるかを確認し、無ければ権限がないことをユーザーに知らせるといったもの
$is_admin = is_admin_request();
if ($document) {
if (!$is_admin && ($document['username'] != $_SESSION['username'])) {
// 管理者でなく文章の所有者でもない
return not_authorized();
}
} else {
if (!$is_admin) {
// 管理者でない
return not_authorized();
}
}
// 引き続きページのレンダリング
ロジックが複数あり理解に時間がかかってしまう
簡単な言葉でロジックを説明してみる
権限があるのは
- 管理者
- 文書の所有者(文書がある場合)
上記の二者でありその他は権限がない
if (is_admin_request()) {
// 管理者 (権限あり)
} elseif ($document && ($document['username'] == $_SESSION['username'])) {
// 文章の所有者 (権限あり)
} else {
return not_authorized();
}
// 引き続きページのレンダリング
このように書き換えることで、コードが小さくなりロジックも単純なるし、否定系も無くなったのでコードの理解も早まるだろう
言葉として説明することでコードがより自然になっていく。この技法は思っているより簡単で強力だし分割する下位問題がどこにあるかも理解しやすくなる。
問題や設計をうまく言葉で説明できないのであれば、何かを見落としているか詳細が明確になっていないということ。
プログラムを言葉にすることで明確な形になる。
第十三章 短いコードを書く
今までどうしたら読みやすいコードを書くことができるかについて学んできた。しかしプログラマにが学ぶべき最も大切な技能とはコードを書かないときを知ることかもしれない。
コードの行数が増えるに連れて開発や保守をする時間がかかる。巻き戻しに一番コストがかかることから、極力コードはかかず最低限の処理だけにするべきだろう。
新しいコードを書かないようにするには、
- 不必要な機能をなくし過剰な機能は持たせない
- 最も簡単に問題を解決できるような要求を考える
- 定期的に全てのAPIを読み、標準ライブラリを知っておく
その機能は本当に必要?
プロジェクト開始時にこれから実装する機能のことを考えているとする。そのとき多くの機能が完成しないか、全く使われないか、アプリケーションを複雑にしてしまうことが多い。
プログラマは実装にかかる労力を過小評価する、というか人間自身こういった物事を過小評価してしまう正常性バイアスを持ち合わせているため、将来的に必要になる保守や文章化などのコードを書いている時間以外の負担を忘れやすい。
短いコードを書くためには
まずは求められることを小さく考えて、本当に必要な実装だけを考える。
プロジェクトが成長しても、コードをできるだけ軽く小さく維持する必要がある。
そのために行うべきことは以下の通り
- 汎用的な「ユーティリティ」コードを作って、重複コードを削除する。
- 未使用のコードや無用の機能を削除する。
- プロジェクトをサブプロジェクトに分割する。
- コードの「重量」を意識する。軽量で機敏にしておく。
また、既存のライブラリでできることを熟知することで、コードを軽量化することができる。
例)乱数を発生させるならRandomを使う
終わりに
自分のコードがいかに読みづらい内容なのかを理解させられてしまった。
独学で勉強していたこともあり、よくない型のようなものが染み付いているため、一度だけでなく何度も復習を重ねていきたい。