NGW開発BLOG
← 記事一覧へ
·#011

動いているものを止めずに次を作る本番と次期版の二本立て開発

稼働中の本番版と大きく作り替え中の次期版を並行開発する。同じ論理修正を食い違う2つのコードベースへ確実に当てる運用の設計判断。

二つのコードベースが同時に動いている

NGWのように長く使われるソフトウェアには、避けがたい局面がある。現行版が現場で毎日動き続けている一方で、次のバージョンを別環境で大きく作り替えている、という二本立ての状態だ。次期版はメニュー体系の刷新をはじめとして、あちこちが根本から書き換わっている途中にある。

このとき最初に決めなければならないのは、優先順位である。我々の原則はシンプルだ。バグ修正や機能要望は、常に本番・現行側を最優先で直す。理由は現場にある。不整合や不具合が実際に表面化するのは、業務が回っている本番側だからだ。開発途中の次期版がどれだけ魅力的でも、それは「まだ誰も使っていないコード」であり、今日困っている利用者はいない。

だから流れはこうなる。まず本番で直し、実際の運用の中で検証する。振る舞いが正しいと確認できて初めて、同じ修正を次期版へも当てる。この順序を崩さない。とりわけ、開発途中の次期版の都合を理由に、本番の修正を遅らせたり、直し方を歪めたりしないことを徹底している。本番の修正は本番の論理として最も素直な形で入れる。次期版への反映は、そのあとの話だ。

コピーでは同期できない

やっかいなのはここからである。同じ「ひとつの論理修正」を、実装がすでに食い違った二つのコードベースへ確実に反映しなければならない。

次期版では言語ランタイムの世代交代が進んでいる。たとえば配列記法の近代化のように、機能は同じでも書き方が変わった箇所が数多くある。結果として、同じ役割を担う同じ名前の関数であっても、本番版と次期版ではバイト単位で異なるソースになっている。

// 本番版に残る旧い記法
$rows = array();
foreach ($src as $k => $v) {
    $rows[$k] = trim($v);
}

// 次期版の近代化された記法(振る舞いは同一)
$rows = [];
foreach ($src as $k => $v) {
    $rows[$k] = trim($v);
}

この二つは振る舞いとして等価だが、テキストとしては別物だ。だから本番で当てた修正をそのままファイルとして持っていっても、そもそも土台が違うために綺麗には収まらない。パッチをあてる、ましてやファイルを丸ごと上書きコピーで同期する、という発想は最初から成り立たない。

我々が取っているのは、各環境の実際のコードを土台にして、論理単位で当て直すやり方だ。「本番でこの関数のこの分岐に、この条件を足した」という修正の意味を抽出し、次期版のその関数の、対応する分岐へ手で移植する。同じ差分ではなく、同じ意図を再現する。移植のあとは、本番と同じ入力で同じ結果になるかを次期版側でも確かめる。手間はかかるが、これ以外に安全な方法がない。

目標は「構成の一致」ではなく「振る舞いの一致」

この運用を続けて見えてきたのは、並行開発で守るべきものの正体だ。二つのコードベースを、ファイル構成やコードの見た目で揃えようとすると必ず破綻する。次期版は作り替えの途中であり、そもそも構造を変えるために分岐させたのだから、構成が一致しないのは当然なのだ。

揃えるべきなのは振る舞いのほうである。同じ入力に対して同じ出力を返し、同じ業務ルールを満たす。内部の記法やファイルの並びが違っていても、外から見た挙動が一致していればよい。この一点に目標を絞ると、日々の判断が驚くほど単純になる。「コードを同じにする」ではなく「結果を同じにする」を合言葉にすれば、記法の差に振り回されずに済む。

# 同期の指針
NG: 本番のファイルを次期版へコピーして「構成を合わせる」
OK: 本番の修正の意図を、次期版の実コードに論理単位で当て直す
検証: 双方で同じ入力 → 同じ出力・同じ業務ルールを確認

動いているものを止めない、という制約は重い。止めてよいなら、いったん本番を凍結し、次期版に一本化して作り直せば話は早い。だが現実のNGWにその贅沢は許されない。今日の業務を一日たりとも止めずに、同じ製品を二つの姿で並走させながら進化させ続ける。構成をコピーで揃える誘惑を断ち、振る舞いの一致だけを頼りに二本の線を引き続けること — それ自体が、地味だが確かな技術課題なのだと思う。