レガシーシステムと向き合う
この記事で扱うこと
- 自身が所属しているチームが開発・運用しているレガシーシステムに我慢ができなくなり、作り直しはできずともちょっとだけマシにするためにやったことのメモ
- 既に対応実施から数年たったが「以前のほうがよかった」という声は一切ないので、取り組みは成功だったと思っている
実施前の状態について
システムについて
開発するための環境
コーディング環境
秘伝のVM運用
-
- テーブルは作成済みで、データも投入済み(過去に使用したゴミデータも含まれる)
- DBに更新があった場合は誰かが更新を適用したVMイメージを作成してチーム内に配布
- テーブルは作成済みで、データも投入済み(過去に使用したゴミデータも含まれる)
コーディング規約
- 口伝のルールがあったらしいが、実質ないようなものだった
エディタ
コーディング環境の問題点
テスト環境
動作確認
自動テスト
- なし
テスト環境の問題点
- ローカルのVM環境でのテストが信頼できない
- その人の環境でだけ動くコードなどが発生する
- 共用開発環境は他の誰かが使っているときは使えない
- VM上に過去の作業によって生じたゴミデータが永遠に蓄積される
- DBにゴミを貯めたままVMイメージが配布される
- 簡単な関数処理の動作確認ですら共用開発環境で動かさないとミスに気付けない
- 想定していない箇所への影響が検知できない
運用
デプロイ
作業の流れ
- 更新対象のファイルを手動でリストアップ
- リストアップしたファイルについて、既存ファイルをサーバ上でバックアップ
- 何らかのサフィックスをつけてcpするだけ
- 更新対象ファイルを置き換え
- 切り戻しの際はリストアップしたファイルをバックアップファイルで置き換え
デプロイの問題点
- リストアップした一覧に漏れが生じる
- 作業を全て手作業で実施するため、ミスや実施漏れが生じる
- 前述の共用の開発環境へのデプロイや戻し作業も同様の方法で行うため、戻し作業にも漏れが生じる
- 後続作業で想定外の事象が起きた時の切り分けが困難
- ミス防止のため(?)、チームメンバー全員で作業の見守りをしていた
- 5人で60~90分くらい作業してたので工数的も問題あり
- それでもミスは頻発していたのでマジで無意味な時間だった
目的意識
やりたかったこと
デプロイ作業の簡易化
- 作業工数の削減
- ミスを削減したかった
自動テスト
- 簡単な処理の動作保証
コードフォーマットの自動化
静的解析
- 文法チェック
- インタプリタ言語がメインだったので実行前にエラーを検知
- linterの導入
- 文法チェック
やらないようにしたかったこと
取り組みについて
前提
システム全ての作り直しはしない
- そんな時間も金もない
- 最低限として「数年以内に破綻しないための施策」を検討
今までの開発と並行して実施
- 「内部改善をしてるのでエンハンスできません」はダメ
ゴール
開発のための環境的な話
- VM配布を廃止し、dockerを利用できるようにする
- 共用dockerイメージの作成
- 動作環境としての仕組みだけを持たせて、DBの変更による更新は発生させない
- 共用dockerイメージを使用した最低限の動作確認方法の確立
- 共用dockerイメージの作成
運用的な話
CIによるチェックで最低限の品質を担保する
- 文法チェック
- linter
- 単体テスト
デプロイスクリプトを作成する
- スクリプトを一つでファイル配置を実施
デッドコードの削除
- どこからも呼び出されてない処理の削除
やったこと
dockerイメージの作成
perl用イメージ
DB用イメージ
- DBインスタンスを起動するだけのイメージ
docker-compose.yml
- perlイメージのDBイメージからコンテナを起動し、お互いに疎通できる状態を作る
メリット
デメリット
- レガシーシステムをやってる人だとdockerに触れたことがない人が多いかも
- 「dockerよくわからん」って人でも容易に使えるように手順を固める必要はありそう
CI環境の構築
内容の定義
- 変更されたファイルに対して、
perl -cw
で文法チェックを実施- そもそも実行できないものをここで弾く
- 変更されたファイルに対して、
perlcritic
でlint- 推奨されない記述はここで弾く
- 変更されたファイルに対して、
perltidy
でフォーマットチェック- 動かすことはできるけど書き方に問題があるものはここで弾く
- リポジトリ全体のテストコードの実行
- 既存処理に影響を与える変更をした場合はここで検知する
"変更されたファイルに対して" について
- リポジトリ全体に
perl -cw
やperlcritic
の処理を実行すると大量のファイルで問題が検知される- 一つ一つ直すとエンハンスが止まるため、前提に反する
- →何らかの変更を加えた際、その人の書いたコードに問題がなくても既存コードのせいでエラーになることがある
- 運が悪かったと思ってその人が修正を実施するというルールを定めて運用
perltidy
だけは最初に全体に適用した- 同様に大量のファイルで問題が検知されるが比較的影響は少なくすぐに対応が可能だったため
perltidyについて
- perltidyは自動的にフォーマットを整えてくれるツールだと思っている
a.pl
を対象に実行すると元ファイルはa.pl.bak
みたいな名前に置き換えられ、a.pl
は整形された状態になるa.pl
が整形された状態であれば、a.pl.bak
は生成されない
- なのでCI上では
*.bak
が存在する場合にエラーとしている - → CIで整形したものをcommitすればいいのでは?という声もあった
- 同チーム内で 「perltidyで整形したものが信頼できないから一通り目で確認したい」という声もあった
- 「信頼できない」と言っているものを自動commitさせる意味がわからなかったため却下とした
- コードを書いた人間が責任を持って整形した状態をcommitすることとした
テストについて
- 今までテストコードなんて書いたことがないという人ばかりだったので、実際にサンプルとしてテストコードを作成して動かした
- 全ての既存コードのテストをいきなり書くことは無理なので、「今日以降変更したところはテストを書きましょう」というルールを定めて運用
- ただしどうしてもテストコードを書く手間が大きい部分(画面操作のテストとか)は無理してテストコードは書かず、共用環境などで打鍵テストを行うこととした
メリット
- 共用開発環境を使う前にある程度確認ができるため、共用環境が長時間占有されることがなくなった
- → 品質担保、共用環境の待ち行列の解消
デメリット
- 既存コードのせいでエンハンス時に余計な手間がかかる
- 最初に
perl -cw
/perlcritic
を実行し、エラーを解消してから実装に着手すると比較的マシ
- 最初に
CI環境のDBセットアップについて
- セットアップ用のスクリプトを作成した
メリット
- DB更新があってもdockerイメージ自体の更新は不要
デメリット
- CIを実行するたびにテーブルから作り直すので若干時間がかかる
デプロイスクリプトの作成
スクリプトの処理内容
- gitリポジトリの特定のブランチの状態を常に正とし、そのブランチに含まれるコードで全てを置き換える
置き換え処理について
git clone
で取得したリポジトリはリポジトリ名_バージョン
のディレクトリに保存し、実行されるcgiやperlスクリプトはこのcloneしたリポジトリ内のファイルをシンボリックリンクで参照するdockerコンテナや共用開発環境へのデプロイも同じスクリプトで実施
メリット
デメリット
- とくになし
デッドコードの削除
不要コード候補の抽出
- 全ての関数定義と関数呼び出し処理の抽出を行い、愚直に調査を行った。
- ひたすら
grep
を駆使
- ひたすら
- どこからも呼び出されてなさそうな処理を不要コード候補としてリストアップ
不要コード候補の監視
- 不要コード候補にログ出力処理を追加する
- 「ここの処理は不要コード候補だったけど、○○から呼び出されたぜ」みたいな
- この状態で一定期間運用を続けて、仕込んだログが出力されなければ不要コードと判断する
不要コードの削除
- 不要コードが確定したらコードを削除する
- もしかしたら監視期間中に偶々呼び出されなかっただけで、実際には不要ではないコードを消してしまう可能性もあるので、切り戻し準備はしておく
- そういうリスクがあることを理解し、過剰に恐れず対応を進めるしかない
繰り返し
- 不要コードを削除することにより、再度不要コード候補が発生する可能性がある
- 定期的に削除と抽出を繰り返していく必要がある
まとめ
- 対応を実施してから3年くらい経つが問題は発生していないし、むしろテストを書いてたおかげでトラブルを事前に防ぐことができたこともある
- とはいえ「やって!」と言ってやってくれる人なんていないと思うので、最初のうちは自らが行動を起こしていくしかない。具体的には以下のようなことをする。
- 取り組みを理解してくれる味方をつくる
- dockerイメージやCIの設定の作成
- テストコードのサンプル作成
- 既存コードのめっっっちゃ簡単な処理のテストでOK
perl -cw
やperlcritic
でエラー対応方法をまとめる- 他の担当者の対応ハードルを下げる
- 継続してもらうためにはできるだけめんどくさい手順を省くとよいかも
- 全てを作り直すことはできなくても最低限守るべきところを守ることで、作り直しができるようになるまでの延命は可能になると思う