リファクタリングって何?一言でいうと「動きを変えずに中身だけキレイにする」
リファクタリングは、
「コードの外から見た振る舞い(仕様・動き)は変えずに、
中身の構造だけを良くすること」です。
バグ修正でも新機能追加でもなく、
あくまで「読みやすさ・変更しやすさ・テストしやすさ」を上げるための作業です。
ここで超重要なのは、
動きは変えない
テストで動きが変わっていないことを確認しながら進める
この二つです。
これが守られていないと、それは「リファクタリング」ではなく「改造」です。
まずは「悪い例」を見てから、リファクタリングしてみる
before:よくある「ごちゃっとした関数」
次のようなコードを考えます。
def process_user(user):
# 年齢チェック
if "age" not in user:
print("age is required")
return
if user["age"] < 18:
print("too young")
return
# メールアドレスチェック
if "email" not in user:
print("email is required")
return
if "@" not in user["email"]:
print("invalid email")
return
# 処理本体
print(f"register {user['email']}")
Python一つの関数の中に、
入力チェック
エラーメッセージ表示
本体の処理
が全部詰め込まれています。
動くことは動きますが、
テストしづらい
どこを直せばいいか分かりにくい
チェックロジックを再利用しづらい
という問題があります。
after:小さく分けて、名前で意味が伝わるようにする
これをリファクタリングしてみます。
from typing import TypedDict
class User(TypedDict):
age: int
email: str
def validate_age(user: User) -> str | None:
if "age" not in user:
return "age is required"
if user["age"] < 18:
return "too young"
return None
def validate_email(user: User) -> str | None:
if "email" not in user:
return "email is required"
if "@" not in user["email"]:
return "invalid email"
return None
def process_user(user: User) -> None:
error = validate_age(user)
if error is not None:
print(error)
return
error = validate_email(user)
if error is not None:
print(error)
return
print(f"register {user['email']}")
Python外から見た動きは同じです。
年齢が足りなければメッセージを出して終わる
メールが変ならメッセージを出して終わる
両方OKなら登録メッセージを出す
でも、中身の構造はかなり変わりました。
年齢チェックとメールチェックが関数として分かれた
User の形を TypedDict で明示したprocess_user は「流れ」だけを担当するようになった
これがリファクタリングです。
なぜリファクタリングが「テスト・品質」と直結するのか
テストしやすい形に分解されるから
さっきの before のコードは、
テストを書こうとすると、いきなりハードルが高いです。
process_user に辞書を渡して、
標準出力をキャプチャして、
メッセージを確認して……と、いきなり重い。
after のコードなら、validate_age と validate_email を個別にテストできます。
def test_validate_age_too_young():
user: User = {"age": 10, "email": "a@example.com"}
assert validate_age(user) == "too young"
Python小さな関数に分かれているほど、
テストは書きやすくなります。
リファクタリングの本質の一つは、
テストしやすい単位に分解する
責務を分けて、テストの対象を小さくする
というところにあります。
バグの「居場所」が狭くなるから
ごちゃっとした関数にバグがあるとき、
「どこが悪いのか」を探すのが大変です。
リファクタリングで関数を分けておくと、
年齢チェックのバグなら validate_age
メールチェックのバグなら validate_email
フローのバグなら process_user
というふうに、「怪しい場所」がすぐ絞れます。
これは、デバッグの速さ=品質の維持コストに直結します。
リファクタリングの超重要原則:「テストを壊さない小さな一歩」
一気に書き換えない。必ず「小さく動かす」
リファクタリングで一番やってはいけないのは、
大きな関数を見て
「うわ、汚い」と思って
一気に全部書き換える
ことです。
それをやると、
「動きが変わってしまったのかどうか」が分からなくなります。
理想的な流れはこうです。
今の動きをテストで押さえる(最低限でもいい)
小さな変更を一つだけする(関数を一つ抜き出す、など)
テストを回して、動きが変わっていないことを確認する
また小さな変更を一つだけする
……を繰り返す
この「小さく変えて、すぐ確かめる」が、
リファクタリングの生命線です。
「まずテストを書く」が難しければ「printでもいいから観察する」
理想はテストですが、
初心者のうちは、いきなり完璧なテストを書くのは難しいです。
そういうときは、
今のコードの入力と出力を、いくつか試してメモしておく
リファクタリング後に、同じ入力で同じ出力になるか確かめる
というだけでも、だいぶマシになります。
例えば、process_user に対して、
年齢なし
年齢が 10
メールなし
メールが不正
全部OK
のパターンを手で試して、
出力をメモしておく。
リファクタリング後に、
同じ入力で同じ出力になるかを確認する。
これは「手動テスト」ですが、
リファクタリングの原則はちゃんと守れています。
よくあるリファクタリングのパターンをPythonでイメージする
長い関数を「名前のついた小さな関数」に分ける
さっきの例がまさにこれです。
「何をしているか」がコメントで書かれているなら、
そのコメントを関数名にしてしまう、というのが定番です。
# 年齢チェック
if "age" not in user:
...
Pythonというコメントがあるなら、validate_age という関数に切り出す。
コメントが減って、
「名前で意味が伝わるコード」になります。
「同じようなコード」を一つにまとめる
例えば、こんなコード。
def send_welcome_mail(user):
print(f"send mail to {user['email']}")
def send_reset_mail(user):
print(f"send mail to {user['email']}")
Python明らかに重複しています。
これを、
def send_mail(user, subject: str):
print(f"[{subject}] to {user['email']}")
def send_welcome_mail(user):
send_mail(user, "welcome")
def send_reset_mail(user):
send_mail(user, "reset")
Pythonのようにまとめる。
重複が減ると、
修正漏れが減る
テストの対象も減る
というメリットがあります。
「型ヒント」を足して、設計をはっきりさせる
リファクタリングは、
コードの構造だけでなく「型の情報」を整えることも含みます。
def process_user(user):
...
Pythonよりも、
from typing import TypedDict
class User(TypedDict):
age: int
email: str
def process_user(user: User) -> None:
...
Pythonの方が、
「この関数は何を受け取って、どう振る舞うか」がはっきりします。
型ヒントを足して、mypy を通すようにするのも、
立派なリファクタリングです。
初心者がリファクタリングで絶対に意識してほしいこと
「きれいに書きたい」より「あとで自分が楽したい」
リファクタリングは、
「美しいコードを書くための儀式」ではありません。
一番の目的は、
未来の自分(とチーム)が楽になるようにすること
です。
バグを見つけやすくする
仕様変更に対応しやすくする
テストを書きやすくする
そのために、
今ちょっとだけ頑張ってコードを整える。
「今の自分のため」ではなく、
「未来の自分のため」にやる作業だと思うと、
やる意味が腹に落ちやすくなります。
「動いているから触りたくない」を少しずつ乗り越える
初心者ほど、
「今動いているコードを触るのが怖い」
と感じます。
それはとても健全な感覚です。
でも、そこで「だから一切触らない」と決めてしまうと、
コードはどんどん汚くなり、
本当に触れない状態になっていきます。
だからこそ、
小さな一歩だけリファクタリングする
そのたびにテスト(または手動確認)をする
という練習を、少しずつ積んでいくのが大事です。
まとめ(リファクタリングは「動きを守りながら未来を楽にする作業」)
リファクタリングを初心者目線で整理すると、こうなります。
リファクタリングは、「外から見た動きは変えずに、コードの中身の構造だけを良くする」作業で、目的は読みやすさ・変更しやすさ・テストしやすさを上げること。
長い関数を小さく分ける、重複をまとめる、型ヒントを足す、といった小さな変更を、テスト(または手動確認)で動きが変わっていないことを確かめながら積み重ねていくのが本質。
リファクタリングを続けると、バグの居場所が狭くなり、テストが書きやすくなり、仕様変更にも強いコードになっていくので、「未来の自分を助ける投資」だと考えると腹落ちしやすい。
