「とりあえず動いています」——このセリフを受け取った時の感触は、年数を重ねるほどに複雑になってきます。
最初にそういう引き継ぎを経験した頃は、「じゃあ大丈夫ですね」と素直に受け取っていました。今は違います。「とりあえず動いている」は、「動いていない部分がある」という意味を含んでいる場合が多い。あるいは、「動いているが誰も理由を知らない」か。
先日、社内の別プロジェクトから回ってきたバッチ処理のコードを引き取りました。受注データを日次で集計してレポートを生成するPythonスクリプト群です。引き継ぎの情報はSlackのDMが1本。
「毎朝8時にcronで動いています。特に問題ないと思います」
テストコードはゼロ。設計ドキュメントもゼロ。ソースコードと、crontabの設定ファイルだけがそこにありました。
1日目:書かずに読む
正直なところ、コードを渡された最初の反応は「直したい」でした。
ファイルを開いた瞬間から気になる箇所が目に入ります。変数名が略語だらけ、関数が200行近い、グローバル変数がちらほらある。「変数名だけでも整理しよう」と手が動きそうになったのを、意識的に抑えました。
1日目のルールとして、コードは一切書かないと決めました。
ひたすら読む。そして「なぜこの形になったのか」を推測する作業です。
「下手なコードだ」という評価をする代わりに、「これを書いた人は何を考えていたのか」を想像する——この姿勢が重要でした。感情的な反応から入ると、設計意図が見えなくなります。
たとえば、200行の関数を見て最初に思ったのは「分割されるべきだ」でした。でもよく読むと、処理の途中でDBの接続を使い回している構造があり、単純に関数を分割するとトランザクションが壊れるようになっていました。
# 引き継ぎ時のコード(一部)
def generate_daily_report(date_str):
conn = get_db_connection()
# 受注集計
orders = fetch_orders(conn, date_str)
totals = calc_totals(orders)
# 在庫照合(同一接続を使用)
inventory = fetch_inventory(conn, [o['product_id'] for o in orders])
# レポート出力(同じconnでDB書き込み)
output = build_report(totals, inventory)
save_report(conn, date_str, output)
conn.close()
return output
「この関数を分割しよう」と思った瞬間に、connの扱いを考えなければならない。適当に分割すると、どこかで接続が閉じられて後続処理が死ぬ。最初に全体を読まずに手を動かしていたら、ここで確実に壊していたと思います。
1日目の終わりには、コードの地図のようなメモをテキストで作りました。「この関数はここを呼んでいる」「このグローバル変数はここで変わる」という関係性の書き出しです。書いていると、設計の意図らしきものが浮かんでくることがあります。
2日目:テストを「書く」より「選ぶ」
テストが1つもない状態で最初に書くテストは、何でもいいわけではありません。
このとき私が最初に書いたのは、特性化テスト(Characterization Test)と呼ばれる種類のものです。既存の動作をそのまま記録することが目的で、「正しい動作を検証する」ためではありません。
def test_generate_daily_report_current_behavior():
"""
既存の動作を記録するテスト。
このテストが壊れた = 動作が変わった、という検知器として使う。
正しい動作を保証するものではない点に注意。
※ 改善の余地がある: 本来はフィクスチャを使うべき。
現状はDBに実データが入っていることを前提にしており、
環境依存が強い。ひとまずの記録として割り切っている。
"""
report = generate_daily_report("2026-05-01")
# 実際に動かして得た出力をそのまま期待値として書く
assert report['total_amount'] == 142560
assert len(report['items']) == 14
assert report['status'] == 'completed'
これを最初に書く理由は、後でリファクタリングをしたときに「何かが変わったかどうか」を検知するためです。正しいかどうかは関係ない。変化を検知できればいい。
どのテストを最初に書くかの選び方は、「外側から見た出力が最も重要な処理」を選ぶことです。このケースでは、生成されるレポートのフォーマットと数値でした。毎朝8時に動いているということは、誰かがそのレポートを使っているはずです。そこが壊れると誰かが困る——という推論で選びました。
ここは経験則なんですよね。影響範囲が見えていない状態での優先順位付けは、理論より勘に頼る部分が多い。「まず核心に近いところから」とは言えるのですが、「核心がどこか」の判断は現場でしか鍛えられない気がしています。
3日目:触ってよい範囲と触ってはいけない範囲を決める
3日目に行ったのは、「境界の定義」です。
コードの技術的な良し悪しではなく、「ビジネス的に壊れたら困る処理か」という軸で分類する作業です。書き出したものをチームに共有しました。
| 処理 | 判定 | 理由 |
|---|---|---|
| レポート生成ロジック(集計・フォーマット) | 触らない | 毎朝参照される。壊れると即アラート |
| DB接続・クローズ処理 | 触らない | 接続プールの挙動が不明。慎重に |
| ログ出力処理 | 触れる | 本番動作への影響なし |
| 変数名のリネーム | 触れる(段階的に) | 動作に影響しない |
| エラーハンドリング追加 | 触れる(条件付き) | 追加するだけで削除しない限り安全 |
「触ってはいけない」に分類したものは、テストカバレッジが十分に上がるまで手をつけない。この約束を自分に課しました。
最初のアプローチとして「全体をリファクタリングする」という選択肢もありました。でも経験上、全体を一度に変えると「何が壊れたか」の特定が難しくなります。引き継ぎ直後は、自分のコードへの理解が浅い状態です。変えた場合と元のままだった場合の比較判断ができない。
段階的に進める。まず観察、次に特性化テスト、それから境界定義。この順番が崩れると、後から何かが壊れたときに「自分のせいか、元々壊れていたのか」が追えなくなります。
「壊さない」が後から効いてきた
この初動から1ヶ月後、別の開発者がこのバッチ処理に機能追加をする場面がありました。
1日目のコードの地図、2日目の特性化テスト、3日目の境界定義があったことで、「ここまでは安全、ここから先は注意」という判断が明確にできた。機能追加のPRレビューをしたとき、境界定義のメモを参照しながら「この変更がDB接続処理に触れているから確認が必要」という具体的なコメントができました。
初動でそこまで効いてくるとは、正直想像していませんでした。
今思えば、「壊さない」を優先することには、技術的なメリットだけでなく、チームへの信頼という側面もありました。引き継ぎ直後に何かが壊れると、「引き継いだ人が壊した」という印象がつきます。慎重な初動は、実績がない状態での信頼の担保でもある。
一点だけエッジケースの話をしておくと(これは本当に脱線しそうになる癖なのですが)、境界定義の「触れる(条件付き)」に入れたエラーハンドリング追加は、実は一番丁寧に進める必要がある変更でした。エラーハンドリングを追加する際、例外を呑み込んでいた元の処理を「見える化」するだけで、呼び出し元の挙動が変わる場合があるからです。「追加するだけで安全」は少し甘い分類でした。
この点は引き続き認識として持ちながら進めています。
「とりあえず動く」コードに価値がないとは思いません。動いているということは、ある時点で誰かが真剣に作ったということです。「なぜこの形になったか」を推測する作業は、その人の判断を尊重することでもある、と考えています。