メソッドに切り出すタイミング2

リーダブルコードの10.7の139ページで、ユーザ情報を暗号化してURLに含めるコードが紹介されています。このコードを小さく分けてみましょう。

# リーダブルコード p139

user_info = { "username": "...", "password": "..." }
user_str = json.dumps(user_info)
cipher = Cipher("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE)
encrypted_bytes = cipher.update(user_str)
encrypted_bytes += cipher.final() # 現在の128ビットブロックをフラッシュする
url = "http://example.com/?user_info=" + base64.urlsafe_b64encode(encrypted_bytes)

ここで取り組んでいるのは「ユーザ情報を暗号化してURLに含める」だけど、コードの大部分は「PythonのオブジェクトをURLセーフな文字列」にするものになっている。こうした下位問題は抽出しておこう。

リーダブルコード p139

ということで、改善後のコードも紹介されています。

# リーダブルコード p139

def url_safe_encrypt(obj):
  obj_str = json.dumps(obj)
  cipher = Cipher("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE)
  encrypted_bytes = cipher.update(user_str)
  encrypted_bytes += cipher.final() # 現在の128ビットブロックをフラッシュする
  return base64.urlsafe_b64encode(encrypted_bytes)

user_info = { "username": "...", "password": "..." }
url = "http://example.com/?user_info=" + url_safe_encrypt(user_info)

続いて、やりすぎのコードを紹介しています。

# リーダブルコード p140

user_info = { "username": "...", "password": "..." }
url = "http://example.com/?user_info=" + url_safe_encrypt(user_info)

def url_safe_encrypt(obj):
  obj_str = json.dumps(obj)
  return url_safe_encrypt_str(obj_str)

def url_safe_encrypt_str(data):
  encrypted_bytes = encrypt(data)
  return base64.urlsafe_b64encode(encrypted_bytes)

def encrypt(data):
  cipher = make_cipher()
  encrypted_bytes = cipher.update(data)
  encrypted_bytes += cipher.final() # 現在の128ビットブロックをフラッシュする
  return encrypted_bytes

def make_cipher():
  return Cipher("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE)

 小さな関数を作りすぎると、逆に読みにくくなってしまう。あちこちに飛び回る実行パスを追いかけることになるからだ。

 プロジェクトの他の部分から再利用できるのであれば、小さな関数を追加するのも意味のあることかもしれない。でも、それまでは必要ない。

リーダブルコード p140

小さく分けることと「必要になるまで作るな(YAGNI You ain't gonna need it)」は反しませんし、ぼくは、これぐらい小さく分けてもいいと思います。

そうは言っても、やりすぎの指摘を受け止めて、少し控えめのバージョンを考えてみましょう。

url_safe_encryptは、3つの処理
(1)オブジェクトの文字列化
(2)暗号化
(3)Base64エンコード
に分けることができます。

そこで、encryptだけを分けたバージョンが、次のコードです。

# Ninton aoki作成

user_info = { "username": "...", "password": "..." }
url = "http://example.com/?user_info=" + url_safe_encrypt(user_info)

def url_safe_encrypt(obj):
  obj_str = json.dumps(obj)
  encrypted_bytes = encrypt(obj_str)
  return base64.urlsafe_b64encode(encrypted_bytes)

def encrypt(data):
  cipher = Cipher("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE)
  encrypted_bytes = cipher.update(data)
  encrypted_bytes += cipher.final() # 現在の128ビットブロックをフラッシュする
  return encrypted_bytes

encrypt内のCiper行がゴチャゴチャしている感じがします。やりすぎバージョンで make_cipher を分けていたのは悪くない気がします。

make_cipherを分けたほうがいい理由としては、暗号化だけでなく復号化も同じCipherを使いたいかもしれないことです。テストケースを作って検証するとき、元データと復号化したデータを比較検証したいかもしれません。

そこで、やりすぎバージョンのmake_cipherに、op引数をつけたバージョンが、次のコードです。

# Ninton aoki作成

user_info = { "username": "...", "password": "..." }
url = "http://example.com/?user_info=" + url_safe_encrypt(user_info)

def url_safe_encrypt(obj):
  obj_str = json.dumps(obj)
  encrypted_bytes = encrypt(obj_str)
  return base64.urlsafe_b64encode(encrypted_bytes)

def encrypt(data):
  cipher = make_cipher(ENCODE)
  encrypted_bytes = cipher.update(data)
  encrypted_bytes += cipher.final() # 現在の128ビットブロックをフラッシュする
  return encrypted_bytes

def make_cipher(op):
  return Cipher("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=op)
タイトルとURLをコピーしました