レガシーシステムと向き合う

この記事で扱うこと

  • 自身が所属しているチームが開発・運用しているレガシーシステムに我慢ができなくなり、作り直しはできずともちょっとだけマシにするためにやったことのメモ
  • 既に対応実施から数年たったが「以前のほうがよかった」という声は一切ないので、取り組みは成功だったと思っている

実施前の状態について

システムについて

開発するための環境

コーディング環境

秘伝のVM運用

  • 会社から支給されたPC上にチームで管理してるVMイメージからVMを作成する

  • DBのインスタンスVM上に構築済みの状態

    • テーブルは作成済みで、データも投入済み(過去に使用したゴミデータも含まれる)
      • DBに更新があった場合は誰かが更新を適用したVMイメージを作成してチーム内に配布

コーディング規約

  • 口伝のルールがあったらしいが、実質ないようなものだった

エディタ

  • 支給されたPC上に好きなエディタを入れてそれを使う人
    • ローカルのディレクトリをVM上にマウントして動かしたりしてた
  • VM上にインストールされたエディタを使う人

コーディング環境の問題点

  • VMの運用がめんどくさい
  • コードを書く人やレビューする人によって書きっぷりが少しずつ異なっていた
  • VMイメージを作った人が .vimrcをめっちゃいじる人だったので死ぬほど使いにくかった

テスト環境

動作確認

  • 画面の表示はVM上で確認
  • VMで確認できない部分を共用の開発環境サーバにデプロイして動かす
    • 外部システムとの連携とか

自動テスト

  • なし

テスト環境の問題点

  • ローカルのVM環境でのテストが信頼できない
    • その人の環境でだけ動くコードなどが発生する
  • 共用開発環境は他の誰かが使っているときは使えない
  • VM上に過去の作業によって生じたゴミデータが永遠に蓄積される
    • DBにゴミを貯めたままVMイメージが配布される
  • 簡単な関数処理の動作確認ですら共用開発環境で動かさないとミスに気付けない
  • 想定していない箇所への影響が検知できない

運用

デプロイ

作業の流れ

  1. 更新対象のファイルを手動でリストアップ
  2. リストアップしたファイルについて、既存ファイルをサーバ上でバックアップ
  3. 更新対象ファイルを置き換え
  4. 切り戻しの際はリストアップしたファイルをバックアップファイルで置き換え

デプロイの問題点

  • リストアップした一覧に漏れが生じる
  • 作業を全て手作業で実施するため、ミスや実施漏れが生じる
  • 前述の共用の開発環境へのデプロイや戻し作業も同様の方法で行うため、戻し作業にも漏れが生じる
    • 後続作業で想定外の事象が起きた時の切り分けが困難
  • ミス防止のため(?)、チームメンバー全員で作業の見守りをしていた
    • 5人で60~90分くらい作業してたので工数的も問題あり
    • それでもミスは頻発していたのでマジで無意味な時間だった

目的意識

やりたかったこと

  • デプロイ作業の簡易化

    • 作業工数の削減
    • ミスを削減したかった
  • 自動テスト

    • 簡単な処理の動作保証
  • コードフォーマットの自動化

  • 静的解析

    • 文法チェック
    • linterの導入

やらないようにしたかったこと

  • VMの配布
    • 自分が配布するのはめんどくさい
    • 他人が配布したものに置き換えるのもめんどくさい
  • 他人がカスタマイズしたvimでの作業
    • 単に使いにくくて不快だった

取り組みについて

前提

  • システム全ての作り直しはしない

    • そんな時間も金もない
    • 最低限として「数年以内に破綻しないための施策」を検討
  • 今までの開発と並行して実施

    • 「内部改善をしてるのでエンハンスできません」はダメ

ゴール

開発のための環境的な話

  • VM配布を廃止し、dockerを利用できるようにする
    • 共用dockerイメージの作成
      • 動作環境としての仕組みだけを持たせて、DBの変更による更新は発生させない
    • 共用dockerイメージを使用した最低限の動作確認方法の確立

運用的な話

  • CIによるチェックで最低限の品質を担保する

  • デプロイスクリプトを作成する

  • デッドコードの削除

    • どこからも呼び出されてない処理の削除

やったこと

dockerイメージの作成

perl用イメージ

  • perlを実行するための環境をもつイメージ
    • plenvで特定のバージョンのperlをインストール
    • 共用開発環境にインストールされているモジュールのインストール

DB用イメージ

docker-compose.yml

  • perlイメージのDBイメージからコンテナを起動し、お互いに疎通できる状態を作る

メリット

  • 全員が同じ環境で動作確認を行うことになるので、実施者依存の変なことが起きない
    • 一度イメージを作成しておけば、よっぽどのことがないと更新作業は発生しない
    • VM配布廃止、不快なvimも使わずに済む

デメリット

  • レガシーシステムをやってる人だとdockerに触れたことがない人が多いかも
    • 「dockerよくわからん」って人でも容易に使えるように手順を固める必要はありそう

CI環境の構築

内容の定義

  1. 変更されたファイルに対して、 perl -cwで文法チェックを実施
    • そもそも実行できないものをここで弾く
  2. 変更されたファイルに対して、 perlcriticでlint
    • 推奨されない記述はここで弾く
  3. 変更されたファイルに対して、 perltidyフォーマットチェック
    • 動かすことはできるけど書き方に問題があるものはここで弾く
  4. リポジトリ全体のテストコードの実行
    • 既存処理に影響を与える変更をした場合はここで検知する

"変更されたファイルに対して" について

  • リポジトリ全体にperl -cwperlcriticの処理を実行すると大量のファイルで問題が検知される
    • 一つ一つ直すとエンハンスが止まるため、前提に反する
    • →何らかの変更を加えた際、その人の書いたコードに問題がなくても既存コードのせいでエラーになることがある
      • 運が悪かったと思ってその人が修正を実施するというルールを定めて運用
  • 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セットアップについて

  • セットアップ用のスクリプトを作成した
    • DDLが格納されたディレクトリ配下から findコマンドで .sqlなファイルを探して順に実行する
      • 実行順序の制御はファイル名で縛る( 001_hogehoge.sql, 002_fugafuga.sql, ... みたいに)

メリット

  • DB更新があってもdockerイメージ自体の更新は不要

デメリット

  • CIを実行するたびにテーブルから作り直すので若干時間がかかる

デプロイスクリプトの作成

スクリプトの処理内容

  • gitリポジトリの特定のブランチの状態を常に正とし、そのブランチに含まれるコードで全てを置き換える

置き換え処理について

メリット

  • デプロイ/切り戻しの作業工数の削減
  • 共用開発環境に蓄積されたゴミファイルによる影響を排除

デメリット

  • とくになし

デッドコードの削除

不要コード候補の抽出

  • 全ての関数定義と関数呼び出し処理の抽出を行い、愚直に調査を行った。
    • ひたすら grepを駆使
  • どこからも呼び出されてなさそうな処理を不要コード候補としてリストアップ

不要コード候補の監視

  • 不要コード候補にログ出力処理を追加する
    • 「ここの処理は不要コード候補だったけど、○○から呼び出されたぜ」みたいな
  • この状態で一定期間運用を続けて、仕込んだログが出力されなければ不要コードと判断する

不要コードの削除

  • 不要コードが確定したらコードを削除する
    • もしかしたら監視期間中に偶々呼び出されなかっただけで、実際には不要ではないコードを消してしまう可能性もあるので、切り戻し準備はしておく
    • そういうリスクがあることを理解し、過剰に恐れず対応を進めるしかない

繰り返し

  • 不要コードを削除することにより、再度不要コード候補が発生する可能性がある
  • 定期的に削除と抽出を繰り返していく必要がある

まとめ

  • 対応を実施してから3年くらい経つが問題は発生していないし、むしろテストを書いてたおかげでトラブルを事前に防ぐことができたこともある
  • とはいえ「やって!」と言ってやってくれる人なんていないと思うので、最初のうちは自らが行動を起こしていくしかない。具体的には以下のようなことをする。
    • 取り組みを理解してくれる味方をつくる
    • dockerイメージやCIの設定の作成
    • テストコードのサンプル作成
      • 既存コードのめっっっちゃ簡単な処理のテストでOK
    • perl -cwperlcriticでエラー対応方法をまとめる
      • 他の担当者の対応ハードルを下げる
  • 継続してもらうためにはできるだけめんどくさい手順を省くとよいかも
    • それ用のスクリプトを作っておくとか、タスクランナー的なツールを使うとか
    • 我々はMakefileにいろんな処理を定義してそれを使っている
      • make formatとか make testとかで必要な処理を自動化
  • 全てを作り直すことはできなくても最低限守るべきところを守ることで、作り直しができるようになるまでの延命は可能になると思う

参考

metacpan.org

metacpan.org