まえがき
ちょうど2年目に差し掛かった頃、少しプログラミングという仕事に慣れてきた私の前に立ちはだかったのは、複雑怪奇な魔物システムでした。
そのとある案件は、ざっくり言えば予約状況と領収状況を記録するための管理者用WEBシステムです。
代々受け継がれ、管理者も保守担当もたくさんの人が携わってきたようでした。
しかし長きに渡って保守を担当していた企業がなくなってしまい、いざ機能改修をしようと依頼した先に私がいたのでした。さて。
このシステムを保守改修するにあたって、どんな困難に直面し、どう乗り越えてきたのかを書き残したいと思います。
背景
なんとなく、このプロジェクトをちらっと覗いたときに恐ろしい雰囲気を感じ取りました。
しかも見た感じ、主要機能の改修で難しそう。
「これを改修するより、作り直した方が結果的に良いのではないでしょうか……」と提案しましたが、
「君が一人で全部できるならいい、でも誰も手伝る人がいないし予算的にも現実的ではないだろう?」と一蹴されてしまいました。
いずれ工数が膨らむのに、なぜ腹を決めて技術的負債を解消しようとしないのか。
その時は技術が未熟で具体的な提案をできない自分を恨みましたが、世間の声を聴いてみるとよくあるやり取りなんだと知りました。
保守とはこういうもの。実際にお金を生み出しているシステムに文句は言えないと意を決し、恐る恐る一人改修の旅に出るのでした。
そもそもこのプロジェクトが始まったのが数か月前だったのですが、社内で色々なことがあり、たらい回しになって納期が近くなったところでピカピカの仕様書が私の手元に参りました。
半年以上あった納期が、残り2か月!?
声にならない叫びをあげました。
ケース1: 残っていないドキュメント
「一応引き継いだドキュメントがあるみたいなので、お渡しします」
そうして上司から送られてきたドキュメントは、接続情報とDB設計書、あとは操作マニュアルのみ。
しかもDB設計書と実際のテーブルの内容、かなり違う。(残ってた設計書が古すぎて)
定数についても少し記載がありましたが、実際と異なっていました。(定数とは?)
「いまシステムを使用している方も前任から引き継がれたばかりで、あまり詳しくないようです」
どうやらこのシステムについて詳しく聞ける人がいなさそうなので、ソースを読んでみるしかありません。
// 新規機能追加 2002.3 Aさん
おおっ……Gitが無い時代のバージョン管理だ…!
こんな感じで、ようやくこのシステムが作られた年代がわかりました。
おそらくPHP4から続いているようです。(※ PHP4.0のリリースは2000年5月)
聞いた話によると、なんとか前任が最後の力を振り絞ってPHP5.4→7.3へバージョンアップしてくれたようです。非常に助かりました。
しかし、情報が少ないので調査は非常に難航しました。ルーティングが特殊だったので、どのページとどのコードが一致しているのか探るのもひと苦労。
時間を膨大に消費してしまいました。
まず最初の教訓はありきたりですが、「ドキュメントを残そう、そして更新しよう」です。
口頭伝聞は、どうしても伝言ゲームになってしまいます。完璧に引き継げたと思っても実際はせいぜい70%程度で、時間と人を経るたびにどんどん歪んでいきます。
なのでどうか、新鮮な思想をドキュメントに残してください。そして更新してください。そうすれば色あせないのです。その時間を人時で確保してください。
20年後の後任が泣いてます。面倒だけれど、どうか取り返しがつかなくなる前に軌道修正を。
ケース2: 独自フレームワーク
レガシーなプロジェクトにありがちなのが、会社独自の秘伝フレームワークです。(※Laravelは2011年6月~)
立派な設計思想があって、それが納得いくものだったら良いのですが、肝心のドキュメントは欠落しているし、度重なる改修でデメリットだけが強調される結果になっていました。
開発者まで居なくなったとなれば、引き継ぐ人は職人技を一から学ぶ必要があって大変です。
読んでみたところ「大きなクラスで処理の流れを固定することでインスタンスの設計に悩む必要がない」という思想が読み取れました。
例を挙げると、「画面データ取得→ヘッダー出力→メイン出力→フッター出力」のようになっており、例えその画面にヘッダーが無くてもヘッダーをレンダリングする処理が走って、無いからfalseで返すという感じになっています。
シンプルなシステムならそれでもよかったのですが、こんなことが起こり始めていました。
- クラスに新しい処理を1つ追加したい場合、すべてのインスタンスで処理が1回増える
→ 1画面をレンダリングするために50くらいの処理が走っていて重い
(1画面で使われるのは10くらい)
- 基底クラスBaseをまとめるため、その基底クラスBaseBaseができたり、継承でNewBaseができたりして多重ネストしている
→ データがネストの中で揉みくちゃにされて謎の化学反応が起こる
- データを制御するために、URLからグローバル変数をたくさん渡して条件分岐をたくさん入れて頑張る
→ 処理が多すぎてブラックボックス化しており、何が返ってくるかくじ引き状態
こんな状態になっていたので、
「既存機能のここをちょっと直してほしい」が、ちょっとじゃ済まないパンドラの箱を開ける作業でした。
改修内容は一般的なものが多かったので、理屈上何をすれば直るかはわかっていましたが、システムを理解するまで中身に触れることができませんでした。
進捗を問われて、つまづいている旨を相談しても
「一般的にデータを操作したければ、SQL流せば解決するはず。なぜそれができないのか」
と言われてしまう始末。
こんな状況は初めてだったのでうまく理由を説明できず
「経験がある人には簡単なことなんだろうな……」
と思ってずっと自己嫌悪になってました。
読んで、ログを出して、読んで、ログを出して……。
経験の無さゆえ、概要を把握するだけで数週間かかってしまいました。
まだベテランではないので断言できませんが、ひとつ。
「レガシーコードは難しい」です。だから詰まるのも当然、ゆっくり読んでデータを追いましょう。
技術が足りないからできないというよりは、普通に読むだけだと人間の認知能力が限界になって詰む。というのが近いと思います。
なので「ベテランの特別な能力が必要……」というのではなく、「自分にあった処理の追い方やデータの整理方法を見つければ大丈夫だよ」と伝えたいです。
私は脳内で表・配列形式のデータを記憶したり、データ同士の結びつきを覚えることがすごく苦手です。(それでプログラマに向いてるのか?はさておき)
なのでExcelにデータの流れを記録してみたり、手書きでイメージを描いてみたりすることでいくらか緩和できました。
とにかく言語化、そして可視化を試す日々でした。
ケース3: 教科書で見たアンチパターンの宝庫
ちょうどこのプロジェクトに携わる前、話題になっていた
「良いコード/悪いコードで学ぶ設計入門」を読んでいました。
それで悪いコードに敏感になっていたせいか、胃がひっくり返りそうになりました。
- 動いてるのかわからないPHP4で書かれたデットコード
- 統一されていない命名("〇〇区分": div, dv, cat, kbn)
- 引数が10個単位で抱えるメソッド
- evalで生み出される数多の呪文
- 隠されたバッチ処理
- 不安を煽るコメント( // ここから下は変更してはいけない!)
……などなど、語りつくせば三日三晩は酒のつまみに困らないほどのアンチパターン遊園地でした。
おかげでエラーログを出力したらNoticeが2000件出てきます。
ここで私は我慢できなくなり、「少しだけリファクタリングしたい」と言い出しました。
しかし「お客さんから要望の無いことは勝手にしないでください」と釘を刺されて断念しました。
このときは「なんでだよ!」と思いましたが、冷静になってみるとやらない選択で正解でした。
レガシー同志を探して記事を漁っていたら、同じくリファクタリングに挑戦して失敗してひどい目にあったという人がたくさんおりまして。
ただし「リファクタリングはいらない」のではなくて、
「リファクタリングするなら入念な準備とメンバーをそろえて、計画的に!」ということです。
影響範囲が計り知れない以上、個人ができるのはコメントを書き換えたり、これから書く処理をきれいにするくらいです。
ここで一つの心の支えになる事実があるとすれば
「生のアンチパターンに触れられるのは貴重」というものです。
直したくても直せない、うずうずした状態でその蟻地獄に呑まれながら作業していく。
そんな経験は、とても自分から進んでやろうとは思えない辛い経験ですし、新規開発ではそこまでひどくなる前にストップがかかるはずです。
アンチパターンがいかに開発に支障をきたすのか、身を以って体感できました。
おかげで耐性がついたのか、他にもややこしいと言われていた保守案件のコードがもはや苦に感じません。
だからこそ、もし任されてしまったのなら滝に打たれる気分で一度やってみるのは良いと思います。
ただ、経験は1回だけで十分だと思いますが。(もうやりたくないです)
みんなとは違うプログラミング体験
世の中のプログラミングは2種類あって、「新規開発」と「保守」に分かれると思います。
だいたい耳に入ってくる情報は、新規開発のこと。
キャリアを積むためには、新しい技術や手法の議論についていきたいと考えていました。
それこそ、システムエンジニアという言葉が当てはまりそうです。
でも、そこそこ長い時間を保守に費やしてきた自分にはずっと、どこか遠い話です。
私にとってプログラミングとは理想を実現するものではなく「壊れているものを直してとにかく動くようにすること」なのです。
「ものづくり」というよりは「ものなおし」です。
時にビジネスでは、良いものを作る必要はなくて、早く直して動けばいいときもあります。
こう思っているので、これから提示することは人によってはまったく間違っているように感じられるかもしれませんが、レガシーコードをレガシーなまま直す上での私なりの苦肉の策です。
レガシーに対抗する方法
- 共通処理には絶対に触らない
1件のリクエストに対して必ず1件のSQLが実行される。
しかし、改修後どうしてもDELETE→INSERTを行う必要ができて2件のSQLを流さなくてはいけない。
こういうときは、リクエストを捻じ曲げて
「この条件の時だけ2件リクエストが来たように見せかける」という手法が必要かもしれません。
共通処理を直して他所に影響が出るくらいなら、その場しのぎをします。
ブラックボックスにどんどんインプットして行って、そのアウトプットから動きを推測して、思い通りの動きをするパターンを見つけます。
異常系は条件分岐で頑張って弾きます。
すでにあるものを、いかに壊さずに修正を加えられるかが勝負です。
そのためには、上塗りに上塗りを重ねるしかありません。
- 全く別の場所で処理を定義する
新規機能を入れたいけれど、システム全体が一本の川のようになっているので
どこにも入れる余地がない。
こういうときは、リクエストを全く違うファイルで受け取るようにして、
理想的な形式で返すように限定的な処理を書きます。
メインの流れに入れると予期せぬ動きになってしまう場合は、設計思想に背いて小部屋のような小さなシステムを作って独立させます。
特に、新しくバリデーションを入れてほしいなどの要望があったときは役立ちました。
- コメントで長文を入れる
一つ一つの処理が難解すぎて、翌日にはどういう動きだったのか忘れてしまう。
1つの関数が数百行あり、その中にも深い関数が何個もあるなど。
こういうときは、ためらわずに日本語でガシガシ書き入れます。
もはや1行ごとに全部コメントを入れていってもいいです。
おそらく後で担当する人も同じ場所でつまづくだろうからです。
引き継ぎ資料などで一つの文書にまとめたり、図示でまとめられる量じゃないので
とにかくコードに思いの丈を綴ります。
実際引き継いだコードも、日記のようになっていて、
// 2008 B氏
// いつも通り〇〇をしたかったんだけど、やり方がわからなかった。
// やむを得ず妥協して〇〇を使う。
// 何か良い方法はないものか。無念。(後で直す予定)
こんなコメントがたくさんありました。
色々な原因で無念がたくさん積み重なってきたのだと思うと心が痛みますが、ドキュメントが無い以上、日本語で設計について書いてある部分がすべて貴重でした。
なので、処理に関する部分でわかったことがあれば、なるべく書くようにしました。
// データベースAで〇〇を登録する。ただし新規登録の場合は$value=1を入れると正しく登録される。
$this->setItem($value)
こんな風に。
本来は関数名でなんとなくわかったり、ジャンプした先でわかったりするはずですが、かなり苦労して追わないと処理がわからなかったので、具体的にコメントしていました。
上記で示したのは、「本来はやってはいけないこと」です。
できれば、時間をかけて原則に従って綺麗な形にした方がいいのです。
そんなことはわかってるんです。
でも、納期が迫ってるんです。
だから葛藤してるんです。
目の前のシステムがあまりにも動かなくて焦ってるんです。
少しでも良くしたいけど、少し良くするために費やす時間に対価が見合わないんです。
交渉したところで、これ以上お金は出せないし待てないと言われます。
顧客としては、とりあえず期日までに動いてくれればいいのです。
そりゃそうです。そのためだけの予算です。
……そうやって積み重なってきた結果が今のコードだし、私もまたレガシーを加速させる一人になってしまいました。
私の技術不足のせいもあり後任には本当に申し訳ないのですが、今回ばかりは仕方なかったのです。
けれど、納期にはギリギリ間に合いました。
プログラマーとしての信頼はやや失いましたが、顧客との信頼は保ちました。
それで手を打っていただけないでしょうか。
長々と思いを綴ってしまいましたが、これが一人で思い悩んだ末での結果でした。
「どうしようもないときは例外的な手法でどうにかする」
というのは、なかなか公にしにくいので教わる機会が少ない技術だと思いますが、仕事でプログラミングをする以上必要な力なのだと思っています。
これはレガシー改修ならではの現象だと思っており、「ものなおし」の技術の一つだと思って学びました。
まとめ
以上が、私がレガシーコードで直面した困難とそれを乗り越えるための心構えや具体的な方法でした。
あまり推奨されない解決策もあったと思いますが、どうしても今を乗り越えないといけないという人のために一瞬の支えになればと思います。
一番良いのは、頼れる先輩に聞くことです。その会社で乗り切ってきた先輩からは、きっといいアドバイスがもらえると思います。
そして、すでに負債を抱えたシステムは企業としても大きな損失を生むため、どこかで直すなり手放すなりするしかありません。
そのために十分な資金か、時間を提供すべきです。
社内のエンジニアの為にも、営業目標を達成するためだけに目先のレガシーコードをたくさん引き受けないよう、お願いしたいところであります。
経営を知らないわがままな一人の願いではありますが。
というわけで、しがないエンジニアの記録でした。