Agileによる再構築

レガシーシステムとは?」と訊かれたら、自分は迷わず「Agileで作られてないシステム」と答える。

Java(servlet直呼び出し)で書かれた古いシステムをRailsで書き直したことがあるけど、非常にコンパクトなコードになってしまった。むしろ周囲が「これで旧システムと互換性あるの?」と心配したくらいである。

で、同じような案件がないか調べていたら次の短論文を発見。

An Agile Approach to a Legacy System(PDF、英語)

例によって無断適当に邦訳。長文注意。

An Agile Approach to a Legacy System
Chris Stevenson and Andy Pols


概要: 本稿では小規模かつ自発的に形成されたXPチームがいかにしてぐちゃぐちゃな問題を抱えたシステムをきれいにしていったかを解説する。我々はレガシーアプリケーションを、新たなフィーチャを加えつつ、従来とは相当異なった手法で書き換えてしまった。特筆すべきことは、ユーザー達がレガシーシステム側では決して得られなかった新機能を、短時間で使えるようになったことである。長期的に見れば、この方法はレガシーシステムを新たなものに置換える手法として使えることであろう。


1 背景

InkBlotは大規模な金融レガシーアプリケーションである。InkBotは何十とある外部システムからデータを供給され、日々最大100程度のインハウストレードを処理している。InkBlotは、1990年代に開発されたが、初期開発チームは随分前に解散している。InkBlotのデータベースを直接参照している外部システムが複数存在する。

外部インターフェース仕様は不明確で、他のどんなシステムがInkBlotに接続し、どんなことをしているのかを知る手段は存在しない。他のシステムがInkBlotにアクセスする際に、共通のユーザ名とパスワードを使っているのは確かである。ビジネスロジックは1600以上のストプロに分散され、それらのいくつかは3000行を越えるSQL文を抱えている。また、同じストプロは複数のバージョンに分岐している。ストプロに変更が加えられる際、旧バージョンは保存され、新しいバージョンが追加されるわけである。なぜなら、古いバージョンをどのコードが参照しているか、誰もわからないからである。

250以上存在するテーブルには、プライマリーキーもなければ外部キーもない。データ間の整合性を保つためにトリガ機能が使われている。コードの世代管理システムは存在せず、言語は4GL、C、SQL、そしてUnixのShellスクリプトが使われている。

2 経緯

InkBlot を改善しようという試みはこれまでも何回か存在した。最近になって、InkBlotの核となる部分を我々のよく知る言語(Java)で書き換えようという動きがあった。Javaなら読みやすく、今後のリファクタリングが楽になるであろう、という読みがあった。

が、この戦略は失敗した。

考えてみればこの失敗は当然であった。我々がやろうとしていたのはレガシーアプリケーションそのものの書き替えであり、作り替える前に壊してしまうのは目に見えていた。レガシーなコードを書き換える以上、成果として出てくるコードも当然ながらレガシーになってしまう。

例えば元のコードの75%は不要であるとしよう。(これはレガシーコードでは少なめの見積である。) そんなコードを書き換えるのは時間の無駄以外の何物でもない。さらに、レガシーコードは少なからずバグを含んでいる。そしてシステムの他の部分はこのバグに依存していることになる。つまり、これらのバグも新しいコードで再現される必要がある。

このJava化プロジェクトは短期集中プロジェクトとして始まったが、あっという間に数ヶ月を消費してしまった。こうならないよう、期間に上限を設けるべきであった。また、このJava化が成功したとしても、作業はとても遅く、職場でのモラルは失われたままであったろう。そして、成果物はビジネス的には何の改善ももたらさなかったはずである。

Java化をしようとしたのは開発側の観点であり、ユーザ側の観点ではなかった。開発マネージャはシステムの信頼性を向上させたかった。が、結果として開発チームに対して「現行保証」という間違ったメッセージを発信してしまった。実際、開発チームに「新しいビジネス価値はつけないように」と伝えられたこともある。

このJava化が失敗に終わったとき、何らかの新しい方法が必要だという結論に達した。

教訓: レガシーコードの焼き直し厳禁

ある日、InkBlotをフロント業務で使っているユーザの一人が、現行InkBlotの問題点を指摘してくれた。InkBlotはレポート作成の用途で使われていたが、ストプロが重いため、レポートが出力されるまで5分以上必要としていた。性能の低さにユーザ達はいらつき、レポート作成プログラムを常に動かすようになってしまった。これがシステムにさらなる負荷をかけていた。

我々開発チームがユーザ達にどうして欲しいかを聞いた所、彼らは何が問題なのかを非常に明確に答えてくれた。もちろん、彼らは背後で動いているシステムの中身には一言も触れていない。システムそのものは、彼らからは見えない存在なのだ。もし、Java化に固執していたら開発チームはこのユーザ達の指摘には対応できなかったことになる。そもそもJava化で拘っていた「課題」は、ユーザ達からみれば問題でもなんでもなかったのである。

教訓: 何が問題なのかはユーザに訊け

何が問題なのかはユーザの方がよくわかっている、という事実を元に、開発チームはユーザの要求ベースで動いていくことになった。既存InkBlotのデータベースからデータを抽出し、それを表示する新しいアプリケーションを作り、随時ユーザ達に見せていく試みが行われた。その結果、ユーザが必要なデータは待ち時間なしで表示できる、ということが一週間の集中的開発で実証された。ユーザ代表は開発チームを信頼するようになり、もっと続けてみようということになった。

とても簡単に作れそうだ、ということがわかり、我々チームは「そんなに難しいわけないじゃん」というモットーを掲げることになった。

既存のレガシーコードに手を入れない、という手段はリスクを低く抑えてくれる。仮に失敗しても、傷は小さくて済む。一方で、成功した場合の成果は非常に大きい。新しいシステムは旧システムの機能を徐々に置換えつつ、新しいフィーチャをユーザに提供できるのである。

新しいシステムは旧システムから必要なデータのみ抽出して構築された。不要なコードやデータは無視し、現行システムのコードに手を入れることもなかった。結果として開発チームは旧システムのどこが重要で、どこが不要であるかを理解できるようになった。実際、旧システムで250以上あったテーブルが、新システムでは10テーブルに減った。

教訓: レガシーアプリケーションは、新しいビジネス付加価値をつけながらリファクタリングするべきである

3 チーム形成

新アプリは、他のプロジェクトを終えた二人の開発者が作り始めた。前の仕事で飽き飽きしていた二人は、何か新しいことに挑戦したがっていたのだ。一方で、旧アプリチームには予算が全くなく、新しい手段でInkBlotを作り直すなどということが許される状況ではなかった。新アプリチームが経営陣に直訴を重ねた結果、新しい手段の実効性を証明するための非公式プロジェクトを許可された。

最初の仕事は、新しいシステムがデータを待ち時間なしで取得でき、なおかつ旧システムの性能には影響を与えないことをユーザ代表に実証することであった。これは簡単だったし、基本となる機能も次々に実装されていった。結果として、新しい開発手法がますます信頼を勝ち取っていった。そして、ユーザ達に新しいアプリケーションをデモするところまでこぎ着けた。

教訓: 信頼は徐々に勝ち取れ。そして、最も困難な部分もこなせることを示せ。

初期集中開発を終えた頃には、新チームに加わりたいという開発者が増えてきた。この非公式チームは6人に膨れ上がり、体制を維持するべく戦い始めた。志願者により形成されたチームというのは、独特な行動哲学を備えている。そのチームに所属したいがために成功を確約し、成功のために自発的な責任を負うようになるのである。

教訓: 小規模かつ志願者を集めたチームから始めよ

XP開発チームと異なり、我々はプログラマ達に作業をあてがうようなことはしなかった。作業支持カードは仕事場にあるホワイトボードに張り出され、作業を終えた開発者が自発的に次の仕事を選択するようにした。意外にも、つまらない作業が最後まで残るようなことはなかった。各自のプライドがそうさせたのであろう。

当初の計画ではイタレーション周期は一週間とされた。実際、一週間単位でほとんどの場面は回せたが、時として積残しは発生した。このような場合には「集中爆撃」を実施し、遅れを取り戻すようにした。つまり、3日以内という短いイタレーションもあった、ということになる。反対に、予定より早くイタレーションが完了したこともある。この場合は次の計画会議を前倒しした。開発に集中できる限りは、イタレーション周期を変えても問題ない、ということが証明された。

教訓: 決められたプロセスに固執するな

変なコードや間違いを見つけたら、互いに声をかけるようにしている。ビルドに失敗したら、皆に声をかけてただちに犯人探しを始めるようにしている。ビルドの自動化は導入していないが、だいたい10分〜15分周期で手動ビルドを実施している。このため、ビルド失敗したら大声で叫べばよいのである。

チームの打ち合せはエゴのぶつかり合いではなく、意見を表明する場であり、考え違いを正してもらう場所である。システムに関する議論はいつも活気に溢れるが、結論に到達したらチーム一丸となってそのアイデアを実行に移す。アイデアはチームで共有され、個人に所属するものではない。

プログラミングをする各ペアは、方式レベルのリファクタリングでは常に不安を持つものである。方式レベルのリファクタリングに着手する場合、全員がホワイトボードを使って何をするのか合意し、それからリファクタリングにのみ専念する。これが終わるまで、他の作業には着手しない。

初回リリースまでに、バックエンド側の方式を4回見直した。だいたい二週間に一度の頻度である。つまり、方式設計や実装も柔軟であった、ということになる。

教訓: 方式レベルのリファクタリングはチーム全員を巻き込むこと。これによりチーム全体が素早く動けるようになる。

新しいチームは独自の文化風習を生み出すようになった。例えば、午後3時になったら近所のコーヒー屋に移動して30分ほど時間をつぶしても良い、というルールである。そこではコードの問題が議論されることもあったが、基本的には休憩に使われた。休むことも、生産性を高めるには必要なのである。休憩直後にすばらしい作業が生まれることもあった。コーヒーを飲みながらの議論と新鮮な空気がチームに活力を吹き込んだのである。

仕事以外の場でもチームの付き合いは維持された。リリースが完了した日には、バーに行ってアブサンを飲むこともあった。飲み過ぎて翌日後悔することもあったが...

教訓: 効率よく働くには休憩も重要

4 納品

新しいアプリケーションの納品に自信を持っているチーム員もいたが、失敗して責任取らされるんじゃないかとか、旧レガシーアプリケーションに悪影響を与えるんじゃないかとか、心配している人もいた。このような不安を抱えているチーム員に対しては、対ユーザと同じような対応をとった。不安の解決に必要な要件を洗いだし、タスクに落とし、一連のタスクカードに混ぜるのである。チーム員の不安をまとめることで、納品時の問題を洗いだし、対策を立てたのである。チームは新しいシステムに関する「政治的な」圧力も感じつつも、「みんなで渡れば怖くない」とも思っていた。

効果的な戦略は「Noとは今言わず、後で言え」である。何か不安があったらカードに書いて、ホワイトボードに張り、次以降のイタレーションで取り上げるようにする。次のイタレーション計画に入る時には、その不安はもう不安ではなくなっているかもしれないし、不安はまだ残っているかもしれない。不安が残っているのであれば、それに対処するタスクをおこし、他の作業と同様に優先度をつけ、処理していけば良い。

教訓: 政治的圧力も、ユーザ要件と同等に扱えば良い

開発の初期段階では、開発用データベースに静的なデータを放り込んでおけば良かった。これにより、旧アプリケーションの機能を代替することは可能だった。が、実システムの複雑さは本番データベースにつなぎこまないことには実現できなかった。

ユニットテストやシミュレータにいつまでも依存するわけにはいかなかった。ユニットテストもシミュレータも、「旧システムはこう動くはずである」という仮説にたったものであり、旧システムの挙動そのものを再現するわけではないからである。実際問題、いくつかの外部システムは我々が把握していない仕様で旧システムのデータを操作している可能性があった。

本番データベースにつないでわずか数分後には、二ヶ月もかけて開発してきた新アプリに問題があることが発覚した。つまり、バックエンドの方式を再び大幅に考え直す必要がでてきた、ということである。旧システムとの接続は納品の一ヶ月前に開始され、旧システムとのデータ不一致を検知する仕組も開発した。ここが最も重要なユーザ側受入れテストとなった。また、我々独自の負荷テストの仕掛も作ったが、機能テストとは別に走らせた。

初回接続を終えてから、旧システムからテストシステムの接続は解除され、新チームは信用を失った。二度目の接続でも、稼働後数分でバグが発覚し、旧戻しを余儀なくされた。皮肉なことに、二度目の接続で記録した旧システムの挙動が、テスト品質を向上させることになった。三度目の正直で、旧システムとの接続は安定した。

教訓: レガシーシステムとの接続が必要なシステムは、レガシー本番データでテストせざるを得ない。

本番データの供給が得られるようになり、ビジネスロジックのバグは探しやすくなった。だが、ユニットテストをすり抜けたGUIのバグで悩んでいる状態は続いている。GUI受け入れをしっかりと、かつ柔軟にテストする仕組について、もっと早い段階から取り組むべきであった。GUIテストについては、まだ課題が残っているという認識のままである。GUIテスト漏れによりたびたび痛い目にあっているが、まだ解決策は見つかっていない。

プロジェクト開始後三ヶ月(第12イタレーション)で、我々は新アプリケーションをユーザの意思決定者にデモし、しばらく使ってもらうことができた。この時点では新アプリは旧システムと一ヶ月以上接続されていた。新システムはユーザ達の間に広がっていたので、むしろ停止するのが困難となっていた。最終版を納品する時点では、暫定納品版を使っているユーザが(開発チームが許可したわけではないのに)20名ほどいた。暫定版から最終版に移行する工程を自動化する仕組も納品したので、最終的な納品は苦労なく終わった。

ユーザ達に新しいシステムを使えという指示はしなかったし、旧システムへのアクセスを止めるようなこともしなかった。新しいシステムはユーザの声を取り入れたものなので、ユーザが旧システムを使い続ける理由はなかった。

現実的には一ヶ月前に「本番」が始まったようなものなので、納品そのものは仰々しい儀式ではなかった。他案件のプロジェクトマネージャは、我々が納品したという事実にすら気づかなかったそうである。我々は残業も週末出勤もしていなかったし、午後三時のコーヒー休憩はずっと続いていたのだから無理はない。

教訓: ユーザを巻き込み、ユーザを味方に付けよ。

初回納品が完了した後、チームは「納品後鬱状態」に陥った。基盤の問題点対応とリファクタリングに専念し、ビジネス的な価値はなにも生み出さなかったのだから無理はない。チームは活力を失った。

新しいビジネス価値を生む作業に入ることで、チームには活力が戻ってきた。ただし、活力を失った間、イタレーションは中断せざるを得なかった。このような自発的チームには、常に課題を与える必要がある。そうしないと、士気を保てないのだ。

教訓: 良きチームを失わないためにも、常に新たな課題を与えよ。

5 その後

プロジェクトは7ヶ月目に突入している。開始後二ヶ月で、一部ユーザにお試し版を納品し、二ヶ月使ってもらった。正式版の納品は、プロジェクト開始後4ヶ月後だった。現時点で、第4版を納品しようとしている。

新システムは、旧システムと並行して稼働している。旧システムで実現できていなかった新しいフィーチャを新システムに追加している所である。

そして、新システムが旧システムに依存しないような形に移行しようとしている所でもある。これが完成すれば、旧システムは廃止できる。

本プロジェクトは、当初は短期集中的な対策として始まったものである。元々は、旧システムを「数年かけて」新しいものに置換えていく計画があった。だが、本プロジェクトの成功により、もともとの書き替えプロジェクトは中断となった。

当チームには他の問題解決のために声がかかるようにもなった。これにより、チームの士気はさらにあがっている。

振り返ってみると、我々が得た「教訓」は充分役に立っている。他のプロジェクトに放り込まれた時にも、これらの教訓が活かされるかどうか、試していくことになろう。

追記

自分の経験と重ねて、「そうだよな〜」と思った部分。

  • レガシーコードは、どう焼きなおしてもレガシー。
  • レガシーコードは、何か修正すると、それ以上に壊れる。