36
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

リーダブルコードチートシートPython版

Posted at

第Ⅰ部 表面上の改善

2章 名前に情報を埋め込む

明確な単語を選ぶ

  • 動作や目的に適した明確な名前を付ける
  • 類義語をよく調べる
明確な名前付け
# Bad
def get_page():  # どこからページを取得するのか曖昧

# Good
def fetch_page():  # ネットからページを取得する場合
def download_page():
明確な名前付け
# Bad
def size():  # 何のサイズか分からない

# Good
def height():  
def num_nodes():
def memory_bytes():
類義語
send   # deliver, dispatch, announce, distribute, route
find   # search, extract, locate, recover
start  # launch, create, begin, open
make   # create, set up, build, generete, compose, add, new

tmpやretvalなどの汎用的な名前を避ける

  • エンティティ値や目的を表した名前を選ぶ
  • tmpは生存期間が短く,一時的な保管が最も大切な変数にだけ使う
  • イテレータが複数ある時は,インデックスにもっと明確な名前を付けるとよい
目的を表した名前
# Bad
retval += v[i] * v[i]  # 2乗した値の合計を保存しておく変数

# Good
sum_squares += v[i] * v[i]
一時的な保管
# Good
tmp = right
right = left
left = tmp
一時的な保管が目的ではない
# Bad
tmp = user.name()
tmp += " " + user.phone_number()

# Good
user_info = user.name()
user_info += " " + user.phone_number()
インデックスに名前を付ける
# Bad
for i in range(clubs.size()):
  for j in range(members.size()):

# Good
for club_i in range(clubs.size()):
  for member_i in range(members.size()):
  
for ci in range(clubs.size()):
  for mi in range(members.size()):

抽象的な名前よりも具体的な名前を使う

# Bad
server_can_start():  # TCP/IPポートをサーバがリッスンできるかどうかを確認するメソッド

# Good
can_listen_on_port():

変数名に情報を追加する

  • 変数名に単位を入れる
  • 危険や注意を喚起する情報を追加する
変数名に単位を入れる
import time
# Bad
start = time.time()
# 計測したい処理
elapsed = time.time() - start
print("処理時間:", elapsed, "")

# Good
start_sec = time.time()
# 計測したい処理
elapsed_sec = time.time() - start_sec
print("処理時間:", elapsed_sec, "")
危険や注意を喚起する情報を追加する
# Bad
password  # プレインテキストであるため,処理前に暗号化すべき
comment   # ユーザが入力したcommentは表示する前にエスケープする必要あり
html      # htmlの文字コードをUTF-8に変えた
data      # 入力されたdataをURLエンコードした

# Good
plaintext_password
unesvaped_comment
html_utf8
data_ulrenc

名前の長さを決める

  • スコープが小さければ短い名前でもいい
  • プロジェクト固有の省略形はダメ
  • 不要な単語を排除する
スコープが小さい
# Good
if debug:
  m = {}
  LookUpNamesNumbers(m)
  print(m)
プロジェクト固有の省略
# Bad
be_manager  # back_end_manager

# Good
eval  # evaluation
doc  # document
str  # string
不要な単語を削除
# Good
to_string():  # convert_to_string():

名前のフォーマットで情報を伝える

  • クラス,関数,変数,メンバ変数等で異なるフォーマットを使う
  • 基本的にはコーディング規約やプロジェクト内の規約に従う
PythonClass   # クラス,例外など
python_class  # メソッド,関数,変数
PYTHON_CONST  # 定数

3章 誤解されない名前

曖昧な表現を避ける

# Bad
filter():  # 選択するのか除外するのか不明

# Good
select():   # 選択する
exclude():  # 除外する
# Bad
length  # 何の長さか不明

# Good
max_lenght  # 最大の長さ
max_chars   # 文字の長さ

限界値を含めるときはminとmaxを使う

# Bad
CART_TOO_BIG_LIMIT = 10  # 未満か以下か不明

# Good
MAX_ITEMS_IN_CART = 10  # 10以下となる

範囲を指定するときはfirstとlastを使う

# Bad
integer_range(start=2, stop=4)  # [2,3] or [2,3,4] 

# Good
integer_range(start=2, stop=4)  # [2,3,4] 包含していることが明確

包含/排他的範囲にはbeginとendを使う

  • 4月中を表す場合,beginの4月1日は含み,endの5月1日は含まないとなる
# Bad
print_event_int_range(begin="04-01 00:00:00", end="04-30 23:59:59")

# Good
print_event_int_range(begin="04-01 00:00:00", end="05-01 00:00:00")

ブール値の名前

  • 頭にis, has, can, shouldなどを付ける
  • 否定形ではなく肯定形にする
頭にhasをつける
# Bad
space_left():  # 残りスペースを返すの?

# Good
has_space_left():  # 残りスペースがあればTrue
肯定形にする
# Bad
disable_ssl = False

# Good
use_ssl = True

軽い処理か重い処理かをユーザーに伝える

# Bad
get_mean()  # 一瞬で取得するイメージ
size()

# Good
compute_mean()  # 計算コストがかかるイメージ
count_size()  

4章 美しさ

一貫性のある簡潔な改行位置

# Bad
class PerformanceTester:
    wifi = TcpConnectionsSimulator(
        500,  # kbps
        80,  # millisecs latency
        200,  # jitter
        1  # packet loss
    )

    t3_fiber = \
    TcpConnectionsSimulator(
        4500,  # kbps
        10,  # millisecs latency
        0,  # jitter
        0  # packet loss
    )

# Good
class PerformanceTester:
    # TcpConnectionsSimulator(throughput, latency, jitter, packet_loss)
    #                             [kbps]     [ms]    [ms]    [parcent]
    
    wifi     = TcpConnectionsSimulator(500,  80, 200, 1)
    t3_fiber = TcpConnectionsSimulator(4500, 10,   0, 0)

メソッドを使った整列

# Bad
database_connection = database_connection()
error = ""
full_name = expand_full_name(database_connection, "Doug Adams", error)
assert full_name == "Mr. Douglas Adams"
assert error == ""
full_name = expand_full_name(database_connection, "John", error)
assert full_name == ""
assert error == "more than one result"

# Good
def check_full_name(partial_name, expected_full_name, expected_error):
    database_connection = database_connection()
    error = ""
    full_name = expand_full_name(database_connection, partial_name, error)
    assert full_name == expand_full_name
    assert error == expected_error
    
check_full_name("Doug Adams", "Mr Douglas Adams", "")
check_full_name("John"      , ""                , "more than result")

縦の線をまっすぐにする

# Bad
details = request.POST.get('details')
location = request.POST.get('location')

# Good
details  = request.POST.get('details')
location = request.POST.get('location')

一貫性と意味のある並び

  • 対応するHTMLフォームの<input>フィールドと同じ並び順にする
  • 最重要なものから重要度順に並べる
  • アルファベット順に並べる

宣言をブロックにまとめる・コードを段落に分割する

# Bad
def suggest_new_friends(user, email_password):
    friends = user.friends()
    friends_emails = set(f.email for f inf friends)
    contacts = import_contacts(user.email, email_password)
    contact_emails = set(c.email for c in contacts)
    
# Good
def suggest_new_friends(user, email_password):
    # ユーザーの友達のメールアドレスを取得する
    friends = user.friends()
    friends_emails = set(f.email for f inf friends)
    
    #ユーザのメールアカウントからすべてのメールアドレスをインポートする 
    contacts = import_contacts(user.email, email_password)
    contact_emails = set(c.email for c in contacts)

5章 コメントすべきことを知る

コメントするべきではないこと

  • コードから分かることをコメントに書かない
  • ひどいコードを補う補助的なコメント
コードから分かる
# Bad
# 2番目の'*'以降をすべて削除する
name = '*'.join(line.split('*')[:2])
補助的なコメントを記述するのではなく適切な変数名をつける
# Bad
# レジストリキーのハンドルを開放する.実際のレジストリは変更しない.
delete_registry()

# Good
release_registry_handle()

自分の考えを記録する

  • 考えを記録する
  • コードの欠陥にコメントをつける
  • 定数にコメントを付ける
考えを記録
# Good
# このデータだとハッシュテーブルよりもバイナリツリーの方が40%速かった
# 左右の比較よりもハッシュの計算コストの方が高いようだ
コードの欠陥コメント
# Good
# TODO: あとで追加,修正する
# FIXME: 既知の不具合があるコード
# HAXK: あまりきれいじゃない解決策
# XXX: 危険!大きな問題がある
定数にコメント
# Good
NUM_TUREADS = 8  # 値は「>= 2 * num_processors」で十分
MAX_RSS_SUBSCRIPTIONS = 100  # 合理的な限界値.人間はこんなに読めない
IMAGE_QUALITY = 0.72  # 0.72ならユーザはファイルサイズと品質の面で妥協できる

読み手の立場になって考える

  • コードを読んだ人が疑問に思うところを予想してコメント
  • 読み手が驚くような動作は文書化
  • ファイルやクラスには「全体像」のコメント
  • 読み手が細部にとらわれないよう,コードブロックにコメントを付け要約

6章 コメントは正確で簡潔に

  • 複数のものを指す可能性がある「それ」や「これ」などの代名詞は避ける
  • 関数の動作はできるだけ明確に説明する
  • コメントに含める入出力の実例慎重に選ぶ
  • コードの意図は,詳細レベルでなく,高レベルで記述する
  • よくわからない引数にはインラインコメントを使う
  • 多くの意味が詰め込まれた言葉や表現を使って,コメントを簡潔に保つ

第Ⅱ部 ループとロジックの単純化

7章 制御フローをよみやすくする

条件式の引数の並び方

# Bad:
if 10 == length:
while bytes_received < bytes_expected:

# Good: 左に期待値や比較対象の式を置く
if length == 10:
while bytes_expected > bytes_received:

if/elseブロックの並び順

  • 条件は否定形よりも肯定形を使う
  • 単純な条件を先に書く
  • 関心を引く条件や目立つ条件を先に書く
肯定形を使う
# Bad
if not debug:

# Good:
if debug
関心を引く条件を先に処理
# Bad
if not url.has_query_parameter("expand_all"):
    response.render(items)
else:
    for item in items:
        item.expand()

# Good: "expand_all"が関心を引く条件なため,これを先に処理する
if url.has_query_parameter("expand_all"):
    for item in items:
        item.expand()
else:
    response.render(items)

三項演算子

  • 基本的には誰もが分かりやすいif/elseを使い,三項演算子は簡潔になるときのみ使う
# Bad
return mantissa * (1 << exponent) if exponent >= 0 else mantissa / (1 << -exponent)

# Good
time_str += "pm" if hour >= 12 else "am"

関数から早く返す

# Good: 早期returnを使用する
def contains(str, substr):
    if str is None or substr is None: return False
    if substr == "": return True

ネストを浅くする

  • 関数内で早期returnを使う
  • ループ内でcontinueを使う
continueを使う
# Bad
for result in results:
    if result is not None:
        non_null_count += 1

        if result != "":
            print("considering")

# Good
for result in results:
    if result is None:
        continue
    non_null_count += 1
    
    if result == "":
        continue
    print("considering")

実行の流れを追えるか?

  • スレッド:どのコードがいつ実行されるかよくわからない
  • シグナル/割り込みハンドラ:他のコードが実行される可能性がある
  • 例外:いろんな関数呼び出しが終了しようとする
  • 関数ポインタと無名関数:コンパイル時に判別できないので,どのコードが実行されるかわからない
  • 仮想メソッド:object.virtulMethod()は未知のサブクラスのコードを呼び出す可能性がある

使わない方が良い構文

  • 基本的にdo/whileは避け,whileループを使う
  • 基本的にgotoは使わない

8章 巨大な式を分割する

説明変数

# Bad
if line.split(':')[0].strip() == "root":

# Good: 式を表す変数を使う
user_name = line.split(':')[0].strip()
if user_name == "root":

要約変数

# Bad
if request.user.id == document.owner_id:
    # 編集可能
if request.user.id != document.owner_id:
    # 読取専用

# Good: 式を変数に代入しておく
user_own_document = request.user.id == document.owner_id

if user_owns_document:
    # 編集可能
if not user_owns_document:
    # 読取専用

ド・モルガンの法則を使う

# Bad
if not (file_exists and not is_protected):

# Good
if (not file_exists) or (is_protected):

9章 変数と読みやすさ

変数を削除する

  • 役に立たない一時変数
  • 中間結果を保持する変数
  • 制御フロー変数
役に立たない一時変数
# Bad
now = datetime.datetime.today()
root_message.last_view_time = now

# Good
root_message.last_view_time = datetime.datetime.today()
制御フロー変数
# Bad
done = False

while 条件 and not done:
    ...
    if 条件:
        done = Ture
	continue

# Good
while 条件:
    ...
    if 条件:
        break

変数のスコープ縮める

  • グローバル変数をできるだけ使わない
  • 大きなクラスを小さなクラスに分割する
  • 定義の位置を下げる

変数は一度だけ書き込む

  • 変数に一度だけ値を設定する
  • イミュータブルにする

第Ⅲ部 コードの再構成

10章 無関係の下位問題を抽出する

抽出すべき機能

  • 高レベルの目標と関係のない下位の処理
  • 純粋なユーティリティコード(文字列操作,ファイルの読み書きなど)
  • その他汎用的な機能
  • やりすぎには注意
# Bad
def find_closest_location(lat, lng, array):
    closest_dist = MAX_VALUE
    for i in array:
        ...
	# 高レベルの目標と関係ない複雑な処理
	...
	if dist < closest_dist:
	    ...
    return closest
    
# Good
def spherical_distance(lat1, lng1, lat2, lng2):
    ...
    # 高レベルの目標と関係ない複雑な処理
    ...

def find_closest_location(lat, lng, array):
    closest_dist = MAX_VALUE
    for i in array:
        dist = spherical_distance(...)
	if dist < closest_dist:
	    ...
    return closest
# Bad
user_info = {"username": "...", "password": "..."}
user_str = json.dumps(user_info)
cipher = Chiper("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE)
encrypted_bytes = chpher.update(user_str)
encrypted_bytes += chiper.final()
url = "http://exmaple.com/?user_info=" + base64.urlsafe_b64encode(encrypted_bytes)

# Good
def url_safe_encrypt(obj):
    obj_str = json.dumps(obj)
    cipher = Chiper("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE)
    encrypted_bytes = chpher.update(obj_str)
    encrypted_bytes += chiper.final()
    return base64.urlsafe_b64encode(encrypted_bytes)
    
user_info = {"username": "...", "password": "..."}
url = "http://exmaple.com/?user_info=" + url_safe_encrypt(user_info)

11章 一度に1つのことを

  • 一度に1つのタスクを行う
  • タスクをできるだけ異なる関数に分割,もしくは異なる領域に分割する

12章 コードに思いを込める

  • ロジックを明確に説明する
  • 既存のライブラリが何を提供してくれるかを理解し活用する
ロジックを明確に説明する
# Bad
if document:
  if not is_admin_request() and (document["username"] != SESSION["username"]):
        return not_authorized()
else:
    if not $is_admin:
        return not_authorized()

# Good
if is_admin_request():  # 管理者
    pass
elif document and (document["username"] != SESSION["username"]):  # 文書の所有者
    pass
else:  # その他
    return not_authorized()

13章 短いコードを書く

  • 汎用的なユーティリティコードを作って,重複コードを削除する
  • 未使用のコードや機能を削除する
  • プロジェクトをサブプロジェクトに分割する
  • 定期的にすべてのAPIを読んで,標準ライブラリに慣れ親しんでおく
標準ライブラリに慣れ親しんでおく
# Bad: リストから重複を取り除くため関数を実装
def unique(elements):
    temp = {}
    for element in elements:
        temp[element] = None
    return list(temp.keys())
    
print(unique([2,1,2]))  # [2,1]

# Good: set型を使えば解決
print(list(set([2,1,2])))  # [1,2]

14章 テストと読みやすさ

テストを読みやすく保守しやすいものにする

  • テストのトップレベルはできるだけ簡潔にする
  • 入出力のテストはコード1行で記述できるとよい
入出力のテストを1行で記述
# テスト対象の関数
class ScoredDocument:
    def __init__(self):
        self.url = ""
        self.score = 0.0

def sort_and_filter_docs(docs):
    docs.sort(key=lambda x: x.score, reverse=True)  # スコアに基づいて昇順にソート
    docs[:] = [doc for doc in docs if doc.score >= 0]  # 負の値を削除

# Bad  
def Test1():
    docs = [ScoredDocument() for _ in range(5)]
    docs[0].url = "http://example.com"
    docs[0].score = -5
    docs[1].url = "http://example.com"
    docs[1].score = 1
    docs[2].url = "http://example.com"
    docs[2].score = 4
    docs[3].url = "http://example.com"
    docs[3].score = -5999
    docs[4].url = "http://example.com"
    docs[4].score = 3.0

    sort_and_filter_docs(docs)
    assert len(docs) == 3
    assert docs[0].score == 4
    assert docs[1].score == 3.0
    assert docs[2].score == 1

Test1()

# Good
def check_score_before_after(scores, expected):
    docs = [ScoredDocument() for _ in scores]
    for doc, score in zip(docs, scores):
        doc.score = score
    
    sort_and_filter_docs(docs)
    actual = [doc.score for doc in docs]
    return actual == expected

check_score_before_after([-5, 1, 4, -5999, 3.0], [4,3,1])

エラーメッセージを読みやすくする

unittestモジュールを使ってより詳細なエラーメッセージを表示する
# Bad
a = 1
b = 2
assert a == b

# エラーメッセージ:aとbの中身が分からない
Traceback (most recent call last):
  File "Main.py", line 5, in <module>
    assert a == b
AssertionError

# Good
import unittest

class NormalTest(unittest.TestCase):
    def test_normal_test(self):
        a = 1
        b = 2
        self.assertEqual(a, b)

if __name__ == '__main__':
unittest.main()

# エラーメッセージ:詳細が表示される
F
======================================================================
FAIL: test_normal_test (__main__.NormalTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "Main.py", line 17, in test_normal_test
    self.assertEqual(a, b)
AssertionError: 1 != 2

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

テストの適切な入力値を選択する

  • 入力値を単純化
  • 1つの機能に複数のテスト
入力値を単純化
# Bad
check_score_before_after([-5, 1, 4, -5999, 3.0], [4, 3, 1])

# Good
check_score_before_after([1, 2, -1, 3], [3, 2, 1])
1つの機能に複数のテスト
# Good: 小さなテストを複数作る方が良い
check_score_before_after([2, 1, 3], [3, 2, 1])    # ソート
check_score_before_after([-10, -1, 3], [3])       # マイナスは削除
check_score_before_after([1, -2, 1, -2], [1, 1])  # 重複は許可
check_score_before_after([], [])                  # 空の入力は許可

テストの機能に名前を付ける

  • クラス名や関数名,状況やバグなどを名前に含める(テストの機能名は長くて良い)
# Bad
Test1()
Test2()

# Good
test_sort_and_filter_docs()
test_sort_and_filter_docs_basic()

テスト容易性の低いコード

  • グローバル変数を使っている
  • 多くの外部コンポーネントに依存
  • コードが非決定的な動作をする

テスト容易性の高いコード

  • クラスが小さい.あるいは内部状態を持たない
  • クラスや関数が1つのことをしている
  • クラスは他のクラスにあまり依存していない
  • 関数は単純でインターフェースが明確
36
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?