はじめに
こんにちは、ヱビスです。
既存システムの保守や改修をしていると、
「修正する場所は分かったので、さっそくコードを書こう!」
となりがちです。
ただ、既存システムの場合は、実際にコードを書く前の確認がかなり重要です。
新規開発であれば、自分たちで仕様や構成を決められることも多いですが、既存システムではそうはいきません。
例えば、こんなことがあります。
- なぜこの実装になっているのか分からない
- 仕様書と実際の動作が一致していない
- 一見関係なさそうな場所にも影響する
- 本番環境でしか動いていない処理がある
- 外部サービスやバッチ処理とつながっている
こうした状態で変更すると、修正した機能そのものは正常でも、別の場所で不具合が発生することがあります。
そこで今回は、既存システムを保守・改修するときに、実装を始める前に確認しておきたい内容をチェックリストとして整理してみます。
特定の案件だけで使うものではなく、日々の保守作業で使いやすい形を意識しています。
なぜ改修前にチェックリストが必要なのか
既存システムの改修で難しいのは、変更するコードそのものよりも、その変更がどこまで影響するのかを把握することだと思っています。
例えば、次のような依頼があったとします。
顧客編集画面に項目を1つ追加する
一見すると、それほど大きな変更には見えません。
ところが実際に調べてみると、
- データベース
- バリデーション
- 登録・更新処理
- CSV出力
- API
- 帳票
- 検索処理
- バッチ処理
- 他システムとの連携
などにも影響する可能性があります。
画面だけ修正して問題なく動いたので完了、と思っていたら、夜間バッチでエラーになった、ということもあり得ます。
そのため改修前には、
「何を変更するか」だけではなく、「何を壊す可能性があるか」
まで確認しておく必要があります。
その確認漏れを減らすために、チェックリストを使います。
1. まず「今回何を変えるのか」を整理する
最初に確認したいのは、変更対象のファイルではありません。
まずは、今回システムの何を変えるのかを整理します。
例えば、
顧客編集画面へ「請求書送付方法」を追加する
というだけでは、少し曖昧です。
もう少し具体的にすると、
顧客ごとに請求書送付方法を「メール」「郵送」から選択できるようにし、登録した値を請求処理で利用する
となります。
ここまで整理すると、少なくとも、
- 顧客編集画面
- 顧客情報の保存処理
- 請求処理
は確認が必要だと分かります。
逆に、変更内容そのものが曖昧なままだと、影響範囲も曖昧になります。
確認したいこと
- 変更の目的を説明できるか
- 変更後にどう動けばよいか分かっているか
- どこまで対応すれば完了なのか決まっているか
まずはこの3つを説明できる状態にしておくと、その後の調査が進めやすくなります。
2. 現在の動作を確認する
仕様書が存在していても、既存システムの場合は、現在実際にどう動いているのかも確認しておいた方が安心です。
長く運用されているシステムでは、
- 仕様書が更新されていない
- 過去の障害対応で処理が変更されている
- 特定の利用者向けに例外処理が追加されている
といったことがあります。
そのため、改修前に対象機能を実際に操作してみます。
例えば、
- 画面を操作する
- 入力値を確認する
- 保存結果を確認する
- 関連する画面を確認する
- 必要に応じてログやDBの変化を見る
という流れです。
ここで正常系だけを見るのではなく、
- 空欄の場合
- 不正な値の場合
- 権限が異なる場合
- 対象データが存在しない場合
なども確認できると、現在の仕様をつかみやすくなります。
確認したいこと
- 現在の正常な動きを再現できるか
- エラー時の動きを確認したか
- 仕様書と実際の動作に差がないか
- 差がある場合、どちらを正しい仕様として扱うか決まっているか
「仕様書にこう書いてあるから」だけで進めず、実装と実際の動作も確認するようにしています。
3. 変更箇所ではなく「影響範囲」を探す
改修するときに注意したいのが、変更するファイルだけを見て終わらないことです。
例えばLaravelで、次のような検索条件を変更するとします。
Customer::where('status', 1)->get();
このコードだけを直せばよいとは限りません。
同じ条件を使っている処理が別の場所にもあれば、そちらも確認が必要です。
影響範囲を調べるときは、いくつかの観点に分けて確認すると探しやすくなります。
データ
- 対象テーブル
- カラム
- リレーション
- 制約
- デフォルト値
処理
- Controller
- Service
- Model
- Job
- Command
- Event / Listener
- Queue
入出力
- 画面
- API
- CSV
- Excel
- メール
外部連携
- 外部API
- Webhook
- SFTP
- ストレージ
- 決済サービス
特に見落としやすいのが、画面から直接見えない処理です。
バッチやQueue、Commandなどは、ブラウザから画面を操作しているだけでは気づきにくい部分です。
確認したいこと
- 同じテーブルやカラムを使っている処理を調べたか
- 同じクラスやメソッドを利用している箇所を調べたか
- バッチやQueueへの影響を確認したか
- API、CSV、帳票などへの影響を確認したか
- 外部サービスとの連携に影響しないか
「このファイルを変更する」で調査を止めず、「このデータや処理を使っている場所はどこか」という視点で見るようにしています。
4. 「変えてはいけない動作」も決める
改修では、新しい動作を追加するだけではなく、既存の動作を維持することも重要です。
例えば、
顧客検索画面に新しい検索条件を追加する
という改修であれば、新しい検索条件が使えることはもちろんですが、
既存の検索条件だけを使った場合は、これまでと同じ検索結果になる
ことも確認したいところです。
このような、今回の改修でも維持する必要がある動作を、事前に整理しておきます。
例えば、
- 既存ユーザーのログイン方法は変わらない
- CSVの既存列や並び順は変わらない
- APIレスポンスの既存フィールドは変わらない
- 過去に登録したデータもそのまま表示できる
といった内容です。
ここを先に整理しておくと、後からテストケースを考えるときにも役立ちます。
確認したいこと
- 今回変えてはいけない既存動作を整理したか
- 既存データへの影響を確認したか
- 外部向けのインターフェースを壊さないか
- 後方互換性が必要か確認したか
「新しくできるようになったこと」だけでなく、「これまで通りできること」も確認対象にします。
5. 安全に検証できる環境があるか確認する
修正コードを書けたとしても、十分に検証できなければ安心してリリースできません。
そのため、実装前に、
今回の変更を、どこで、どのように確認するのか
を考えておきます。
例えば、
- ローカル環境
- Docker環境
- 開発環境
- ステージング環境
などです。
ここでもう一つ気をつけたいのが、検証環境から本番環境へ副作用を与えないことです。
例えば検証環境から、
- 本番利用者へメールを送ってしまう
- 本番の決済APIを実行してしまう
- 本番Webhookを呼び出してしまう
- 本番ストレージへファイルを書き込んでしまう
といった構成になっていると危険です。
「ステージング環境だから大丈夫」とは限らないため、接続先まで確認しておきます。
確認したいこと
- 今回の変更を検証できる環境があるか
- 必要な条件やデータを再現できるか
- 検証用データを用意できるか
- 本番DBへ接続しないか
- 本番利用者へメールを送信しないか
- 本番APIや決済環境を呼び出さないか
- 本番ストレージへ書き込まないか
検証環境が「存在すること」だけではなく、安全に使えることまで確認するのが大切です。
6. テスト方法を実装前に決める
実装が終わってから、
さて、どう確認しよう
と考えると、どうしても自分が変更した内容を中心にテストしがちです。
できれば実装前に、
何が確認できれば、この変更を完了としてよいのか
を決めておきます。
例えば、「顧客情報へ請求書送付方法を追加する」という変更であれば、次のように整理できます。
正常系
- 「メール」を選択して保存できる
- 「郵送」を選択して保存できる
- 保存した値を再表示できる
異常系
- 許可されていない値を保存できない
- 必須項目であれば未選択時にエラーになる
既存動作
- 既存の顧客データを表示できる
- 既存の顧客登録・更新処理がこれまで通り動く
関連処理
- 請求処理が設定値を正しく参照する
- 必要であればCSVなどにも正しく反映される
自動化できるものはテストコードへ落とします。
ただし、すべてを自動テストにすること自体が目的ではありません。
大切なのは、今回の変更によって壊れる可能性があるものを確認できる状態にしておくことです。
7. DB変更がある場合は「戻せるか」も考える
データベース変更を伴う改修では、migrationが正常に実行できるだけでは十分ではありません。
例えば、
$table->string('invoice_delivery_method')->nullable();
のようなカラム追加であれば比較的影響は小さいですが、
- カラム削除
- 型変更
- NULL禁止
- データ変換
- 大量データの一括更新
などがある場合は、より慎重に確認する必要があります。
特に気をつけたいのが、
migrationをrollbackすれば元に戻せる
とは限らないことです。
データそのものを削除したり変換したりした場合、テーブル定義だけ元に戻してもデータは復元できません。
確認したいこと
- migrationの影響を確認したか
- 既存データが入った状態でも実行できるか
- 実行時間に問題がないか
- rollback可能か
- rollbackだけでは戻せないデータ変更がないか
- 必要に応じてバックアップや復旧手順を決めたか
「変更できるか」だけでなく、「問題があったときに戻せるか」まで考えておきます。
8. リリース方法まで確認してから実装する
最後に、リリースについても実装前に一度確認しておきます。
変更内容によっては、コードを配置するだけでは終わりません。
例えば、
- migrationはいつ実行するのか
- キャッシュの更新が必要か
- Queue workerの再起動が必要か
- メンテナンスモードが必要か
- フロントエンドのbuildが必要か
などがあります。
Laravelでも変更内容によっては、例えば次のような操作が必要になります。
php artisan migrate
php artisan config:cache
php artisan queue:restart
実装担当者とリリース担当者が異なる場合は、特に手順を明文化しておいた方がよいです。
確認したいこと
- リリース手順が分かっているか
- migrationの実行が必要か
- キャッシュ更新が必要か
- Queue workerの再起動が必要か
- リリース後に何を確認するか決まっているか
- 問題が起きた場合の切り戻し方法が分かっているか
実装完了とリリース可能は、必ずしも同じではありません。
実務で使うための改修前チェックリスト
ここまでの内容を、簡単なチェックリストにまとめると次のようになります。
■ 変更内容
[ ] 変更目的を説明できる
[ ] 変更後の期待動作が明確
[ ] 完了条件が決まっている
■ 現状確認
[ ] 現在の動作を確認した
[ ] 正常系を再現できる
[ ] 異常系を確認した
[ ] 仕様書と実装の差を確認した
■ 影響範囲
[ ] 関連するDBを確認した
[ ] 関連処理を検索した
[ ] バッチ・Queueを確認した
[ ] APIを確認した
[ ] CSV・帳票を確認した
[ ] 外部連携を確認した
■ 既存動作
[ ] 維持すべき既存動作を整理した
[ ] 既存データへの影響を確認した
[ ] 後方互換性を確認した
■ 検証環境
[ ] 変更を検証できる環境がある
[ ] 必要なテストデータがある
[ ] 本番DBへ接続しない
[ ] 本番メールを送信しない
[ ] 本番APIへ接続しない
[ ] 本番環境へ副作用を与えない
■ テスト
[ ] 正常系の確認方法を決めた
[ ] 異常系の確認方法を決めた
[ ] 既存動作の確認方法を決めた
[ ] 自動テスト対象を決めた
■ DB
[ ] migrationの影響を確認した
[ ] 既存データへの影響を確認した
[ ] rollback可能か確認した
[ ] 必要な復旧方法を決めた
■ リリース
[ ] リリース手順を確認した
[ ] リリース後の確認方法を決めた
[ ] 切り戻し方法を確認した
もちろん、案件によって不要な項目もあります。
すべての項目を機械的に確認することが目的ではなく、今回の変更で起こりそうな事故を、実装前に見つけるために使うものだと考えています。
チェックリストを増やしすぎない
保守を続けていると、
前回ここで不具合が出たので、チェック項目を追加しよう
ということが増えてきます。
これは良い改善なのですが、何でも追加していると、チェックリストがどんどん長くなります。
100項目、200項目と増えてしまうと、今度は確認すること自体が負担になり、形だけのチェックになりがちです。
そのため、例えば次のように分けておくと使いやすくなります。
- すべての改修で確認する項目
- DB変更時に確認する項目
- API変更時に確認する項目
- バッチ変更時に確認する項目
- 本番リリース時に確認する項目
つまり、
すべての案件ですべて確認する
のではなく、
今回の変更に必要なチェック項目を選ぶ
という使い方です。
チェックリストそのものを保守していく、という考え方も必要になります。
「確認した」で終わらせない
もう一つ、チェックリストを使うときに意識したいことがあります。
例えば、
[✓] 影響範囲を確認した
だけでは、後から見たときに何を確認したのか分かりません。
できれば、
影響範囲:
- CustomerController
- CustomerService
- InvoiceService
- customersテーブル
- 請求CSV
というように、簡単でもよいので確認結果を残しておきます。
こうすると、チェックリストが単なる作業確認ではなく、改修時の判断記録としても使えるようになります。
コードレビューのときにも役立ちます。
レビューする側は、コードだけでなく、
実装担当者がどこまでを影響範囲として考えているのか
を確認できるからです。
まとめ
既存システムの保守・改修では、コードを書く前に確認しておきたいことがいくつもあります。
特に、
- 何を変更するのか
- 現在どう動いているのか
- どこまで影響するのか
- 何を変えてはいけないのか
- どこで安全に検証するのか
- どうテストするのか
- どうリリースし、問題があればどう戻すのか
あたりは、実装前に一度整理しておくと安心です。
チェックリストの目的は、項目をすべて埋めることではありません。
変更前にリスクを見つけて、確認漏れによる不具合を減らすことです。
既存システムの場合、「変更した機能が動いた」だけでは、改修が完了したとは言い切れません。
変更していない既存機能についても、これまで通り動いていることを確認する必要があります。
毎回ゼロから確認事項を考えるのではなく、基本となるチェックリストを持っておき、案件に合わせて必要な項目を選ぶ。
そんな形にしておくと、日々の保守・改修でも使いやすいのではないかと思います。