本記事は株式会社プライム・ブレインズによるアドベントカレンダー2日目の記事です。
HP: https://www.primebrains.co.jp/
Wantedly: https://www.wantedly.com/companies/primebrains
はじめに
私は現行運用されているAPIのリプレイス開発に携わっています。開発方式はいわゆるウォーターフォール開発ですが、新規での開発に比べてそれぞれの工程で考慮すべきポイントに特徴があるように感じています。本稿では、各工程で留意すべきポイントについて検討したいと思います。
システムリプレイスプロジェクト特有の難しさ
始めに、システムリプレイスプロジェクトの持つ特有の難しさについて分析します。
現行踏襲・現行保証という言葉の罠
リプレイスプロジェクトではよくざっくりと「現行踏襲」・「現行保証」という要件を提示されることがあります。しかし、具体的なAPIというレベルに落としこむと、この言葉がどのような仕様を意味しているかを考えることは難しいです。例えば、以下のような問題に突き当たります。
そもそも現行の仕様を表現するドキュメントが散逸している
システムリプレイスプロジェクトの難しさは、動いている現行システムのことが よくわからない ということです。例えば、詳細設計書が散逸しています。~~API という機能概要を表す名前はわかっても、その中でどのテーブルを参照しているか、どのテーブルを更新するのかといった情報を明らかにする資料は残っていません(部分的に残っていたとしてもメンテナンスの漏れから実態を正確に表現しているドキュメントではないかもしれません)。保守担当者のナレッジや尽力によって、辛うじてシステムは動き続けています。
現行の仕様通りに作ればいいというわけでもない
また、現行の仕様を知ることができたとしても、その仕様通りに作ればいいというわけでもありません。
詳細な仕様を紐解いていくと「とうの昔にサービスアウトした機能のテーブルを更新し続けている」といった事態が発覚することがあります。ほかにも、クライアントの業務フロー側に変更があり、APIの仕様がその仕様に合致してないという事態も考えられます(いわゆる 運用でカバー している状態)。
技術的な制約によって現行通り作れない部分もある
また、作る意味のある箇所に関しても、アーキテクチャ上の都合によって実現できないというケースが考えられます。例えば、オンプレミス・プライベートクラウド上に構築されたAPIが専用ネットで繋がれたNASのファイルを更新するといった機能を持っていた、ということがあります。リプレイス先はパブリッククラウドの場合、技術的な制約に直面することがあります。
リプレイス後と前でプログラミング言語やフレームワークが違う
言語についてはそろえることができても、例えばメジャーバージョンの相違によって非推奨になるAPIや記法があるかもしれません。プログラミング言語自体を変更する場合は、言語仕様の差異が罠になることがあります。nullな値を検査せずに扱うとき、暗黙でキャストする言語とランタイムエラーにする言語では、同じテーブルの同じカラムを見ていても挙動が変わってしまいます。
フレームワークについても同様で、当時流行った(すなわち今は使われていない)フレームワークであれば、乗り換えることになるでしょう。同じような機能を実現するAPIについてもデフォルト値が違うなど細かい部分で罠にハマることがあります。
では、上記のような課題に対して、各工程ではどのように向き合えばいいのでしょうか。
要件定義・設計工程
現行システム要件・仕様の再発見
ドキュメンテーションが不完全であれば、それらやソースコードの解析情報を組み合わせて現行システムの要件・仕様を再発見していく他ありません。当時の意思決定に利用した資料(開発当時の調査資料など)もまた有用です。加えて、現行保守担当者の知見(=暗黙知)も有効です。システム仕様の経緯に一番詳しいのは保守担当者です。静的なドキュメント情報に加えて、保守担当者へのヒアリング情報も組み合わせながら現行の仕様を明確にしていきます。
現行要件・仕様に対する批判的検討
一方で、先述の通り、現行そのままに作ることが必ずしも望ましいわけではありません。使用されていないテーブルへのCRUDを取り除くことが有用かもしれません。既存バグが放置され、運用でカバーされているかもしれません。業務自体を見直す過程で、詳細な仕様を変えていく必要があるかもしれません。一方で、よくわからないから廃止するというのは危険です。それは他の何かしら重要な処理で利用する可能性があります。現行がスパゲッティになっていればいるほど、他への影響は見定めにくくなります。要件や仕様のヒアリングと照らして、適切に新しい仕様や要件を見定めていくところに開発者の力量が宿ります。
アーキテクチャ上の制約と代替案の検討
現行システムと新システムのアーキテクチャ上の相違によって実現できない機能については、代替案を検討する必要があります。例えば、その機能だけを切り出して現行システム側のAPI化するという暫定対応はできないでしょうか。もしくは新システム側のアーキテクチャで同様の機能を実現する設計を検討する本格対応もありえるでしょう。工数やシステムリプレイス全体のマイルストンと照らし合わせて代替案を検討します。
実装工程
言語・フレームワークの差異吸収
リプレイスプロジェクトにおける言語・フレームワーク間の差異吸収は、最も技術的な繊細さが求められる領域の一つです。例えば、nullや空文字の扱いは注意が必要です。Javaではnullと空文字は別物として扱われますが、JavaScriptで条件文などで任意の値を論理値に暗黙的に変換します(この時falseとして扱われる値をfalsy値といいます)。例えば、
const x = "";
if(x) {
// xがnullでないことを想定した何かしらの処理
} else {
// xがnullであることを想定した何かしらの処理
}
というようなコードを書く場合、実行するとelse側の分岐に入ってしまいます。
これは空文字列はfalsy値であるため、条件式ではfalse
と評価されるためです。
一方で、文字列同士を比較する場合、Javaでは文字列はStringクラスのインスタンスになります。そのため、単純な文字列比較でも
var a = "a";
var a2 = new String("a");
if(a == a2) {
System.out.println("true");
} else {
System.out.println("false");
}
というようなコードを書き、実行するとelse側の分岐に入ってしまいます(同じ文字列ですがインスタンスとして同じかという意味では違うインスタンスであるため)。
文字列として同じであることを比較するためにはequals
メソッドを利用する必要があります。
var a = "a";
var a2 = new String("a");
if(a.equals(a2)) {
System.out.println("true");
} else {
System.out.println("false");
}
他にも、型変換、例外処理、非同期処理など、言語ごとの差異を上げればきりがありません。
一つの言語を習得するうえでは基礎的な知識なのですが、リプレイス開発では複数の言語に関する知識を身に着け、混乱なく運用する必要があります。これはかなり開発者の認知的負荷を高めます。
例えば、よくある読み替え例をリファレンスとして作成するなどして多少でも認知的負荷を改善する仕組みを検討する必要があるでしょう。
フレームワークについても同様です。よくある処理をナレッジとして蓄積することがチームとしての生産性を高めるためには必要でしょう。
テスト工程
現行システムとの徹底した突合
テスト工程では通常のシステム結合テストに加えて、現行システムとの徹底した突合が求められています。同一のテストデータに対して(意図的に仕様を変えた部分以外では、)同じようにデータは返ってくる状態でしょうか。先の通り、言語やフレームワーク間の差異が流出することで思わぬ障害につながるかもしれません。同一データに対して現行とリプレイス後のシステムが同様の出力をするかは重要なテスト観点です。
また、これらは数件のデータに対する突合だけではバリエーションが不足しているかもしれません。リプレイスが求められる規模のシステムであれば、保守担当者も把握していないバリエーションが存在している可能性があります(ゴミデータかもしれませんが)。なるべく大量のテストデータで現新の突合ができるような体制を検討しましょう。例えば、現行保守担当者他が様々なテストに利用してきたデータなどがあればそれを利用できないか交渉する、本番データを匿名化して利用するなどです。システム規模や制約に合わせて最大限テストをしておくことが品質を担保することにつながり、開発者としても安心材料になります。
終わりに
本稿ではシステムリプレイスプロジェクトにおける留意点について紹介しました。