「リーダブルコード」で再発見!読みやすいコードの真髄とは?
この記事は、業務をするにあたっての基本を復習するため、O'Reillyの名著「リーダブルコード」を再読した時に、改めて重要だと感じた点をまとめた記事になっております。
1. リーダブルコードの基本原則
読みやすさの基本定理
「リーダブルコード」の核心は、以下の原則にあります。
コードは他の人が最短時間で理解できるように書かなければいけない
ここでいう「理解」とは、コードに変更を加えたり、バグを見つけたりできる状態を指します。
つまり、コードを短くすることよりも、他の人が理解するまでにかかる時間を短くすることが重要なのです。
業務にあたって、コードを記述しているよりもコードを読んでいる時間の方が長くなるため、コードを書く際はこの原則を意識する重要性を改めて感じました。
2. 名前に情報を詰め込む
明確な単語を選ぶ
例えば、get
という単語は曖昧です。
def get_page(url):
...
ページの取得先がローカルキャッシュなのか、データベースなのか、それともインターネットなのか... 様々な可能性が考えられます。
そのため、これをより具体的にするには
def fetch_page(url): # インターネットから取得する場合
...
def find_page(url): # データベースから探す場合
...
のような命名の候補が考えられます。
汎用的な名前を避ける
変数名は短いコメントだと考えましょう。
命名には目的や値を表す名前を選びます。
# Before
def calculate_total(v):
retval = 0.0 ←ここ
for i in range(len(v)):
retval += v[i] * v[i]
return math.sqrt(retval)
# After
def calculate_vector_length(vector):
sum_squares = 0.0 ←変更
for value in vector:
sum_squares += value * value
return math.sqrt(sum_squares)
接頭辞や接尾辞を使って情報を追加する
単位や状態を名前に含めることで、より多くの情報を伝えられます。
delay_secs = 30 (秒)
size_mb = 512 (メガバイト)
max_kbps = 1000 (kbps)
ループイテレータの命名にも気をつけよう
汎用的な命名でも、ループ処理内で使う場合は特に問題はありません。
しかし、ネストしたループでは、単純なi
、j
、k
よりも、説明的な名前を使うと分かりやすくなります。
以下の例では、members
とusers
のインデックスが逆になっておりますがパッと見では気づきにくい実装です…
# 改善前
for(int i = 0; i < clubs.size(); i++)
for(int j = 0; j < clubs[i].members.size(); j++)
for(int k = 0; k < users.size(); k++)
if(clubs[i].members[k] == users[j]) # バグ!
# 改善後
for(int club_i = 0; club_i < clubs.size(); club_i++)
for(int member_i = 0; member_i < clubs[club_i].members.size(); member_i++)
for(int user_i = 0; user_i < users.size(); user_i++)
if(clubs[club_i].members[user_i] == users[member_i]) # バグが見つけやすい!
しかし、説明的な命名にすることで、インデックスが逆になっていることが分かりやすくなります!
バグが見つけやすくなる = 読みやすいコード
ということになるのです。
その他の命名のコツ
- 抽象的な名前より具体的な名前を使おう
- 計測できるものには単位を入れよう(例:
timeout_msecs
、size_mb
) - 危険や注意を喚起する情報も追加しよう(例:
untrusted_url
) - 接尾辞や接頭辞を使って情報を追加しよう
そして何より大切なのは、「他の意味と間違えられることはないだろうか?」と何度も自問自答することです!
第三者での目線を意識して、自分が関数につけた名前を確認する癖をつけることで、読みやすいコードを書くことができる重要な要素であると感じました。
3. 誤解されない名前
限界値を明確にする
max_
やmin_
を使って限界値を明確にします。
CART_TOO_BIG_LIMIT = 10 # 良くない例
MAX_ITEMS_IN_CART = 10 # 良い例
範囲を明確にする
範囲の始まりと終わりを明確にするには、first
とlast
、またはbegin
とend
を使います。
# 含む範囲
print_range(first=2, last=5) # 2, 3, 4, 5を出力
# 排他的範囲
print_range(begin=2, end=5) # 2, 3, 4を出力
ブール値の命名
is_
、has_
、can_
、should_
などを使って、ブール値であることを明確にします。
is_valid = True
has_permission = False
can_edit = True
should_update = False
個人的に、ブール値の命名にはis
を使うように心がけていたのですが、has
やcan
にもブール値のニュアンスが含まれていることを学びました!
また、もう一つ重要なこととして否定形は避けるようにします。
disable_ssl = False # 良くない例
use_ssl = True # 良い例
否定系で書かれてしまうと、パッと見てどの値を持っているのかが分かりにくくなってしまいます。
4. 美しさ
コードの見た目も重要です。
「目に優しい」コードを書きましょう。
一貫性のあるレイアウト
# 整列させる
long_variable = 10
short_var = 5
tiny = 1
# コメントも整列
x = 5 # 初期値
y = x + 10 # xに10を加算
関連するコードをブロックにまとめる
関連するコードは以下のように、一つのブロックになるように記述していくことで、関連するコードであることが視覚的にもわかるようになります。
# ユーザー情報の取得
name = get_name()
age = get_age()
email = get_email()
# データベースへの保存
save_to_database(name, age, email)
# 確認メールの送信
send_confirmation_email(email)
5. コメント
コメントすべきこと
- コードの背後にある意図
- コードの欠陥
- 定数の意味
コメントしなくて良いこと
- コードから明らかなこと
- 悪いコードの言い訳
- コメントのためのコメント
良いコメントの例
# カスタマーデータをキャッシュするクラス
# スレッドセーフではないので、シングルスレッド環境でのみ使用すること
class CustomerData:
...
# TODO(username): ここのパフォーマンスを改善する
# 現在のO(N^2)アルゴリズムをO(N log N)に変更する
def slow_function():
...
「リーダブルコード」には、以下のような記述がされておりました。
優れたコード > ひどいコード + 優れたコメント
つまり、他人が理解しにくいコードに優れたコメントを書いていたとしても、”理解のしやすいコードを書く”ことの方が重要であると言及されています。
※ 個人的には、優れたコードにもコメントを書くことは重要であると考えております。
6. 制御フローを簡潔に
条件文
- 否定形よりも肯定形を使う
- 複雑な条件は変数に格納する
# Before
if not ((file_exists and not is_protected) or is_special):
return
# After
is_deletable = file_exists and not is_protected
should_delete = is_deletable or is_special
if should_delete:
return
早期リターン
ネストを減らすために、エラーケースを早めにリターンします。
# Before
def process_payment(payment):
if payment.is_valid():
if payment.amount > 0:
...
else:
raise ValueError("Payment amount must be positive")
else:
raise ValueError("Invalid payment")
# After
def process_payment(payment):
if not payment.is_valid():
raise ValueError("Invalid payment")
if payment.amount <= 0:
raise ValueError("Payment amount must be positive")
...
これによって、コード自体も見やすくなりますし、条件の抜け漏れを把握しやすくなります。
まとめ
今回改めて、「リーダブルコード」を読んだ要点としては以下になるかなと思いました。
- 読みやすさの基本定理: コードは他の人が最短時間で理解できるように書くべき
- 命名の重要性: 変数名や関数名に情報を詰め込み、誤解を招かない明確な名前を選ぶ
- 美しさとレイアウト: コードの見た目も重要。一貫性のあるフォーマットで、関連するコードをブロックにまとめる。
- 適切なコメント: コードの意図や背景を説明するコメントは有用ですが、コード自体の改善の方が重要。
- 簡潔な制御フロー: 条件文は肯定形を使い、早期リターンでネストを減らすなど、理解しやすい制御フローを心がける。
一度読んだ本でも時間を空けて読み直すことで、当時は特に引っ掛からなかった部分でも新たに重要な点に気づかせてくれたり、理解できる部分が増えていたりと、違った視点で味わえることが良いですね!