技術的負債に対する視力を得る技術
こんにちは、羽山です。
今回は先日開催した弊社主催の技術イベント「Raccoon Tech Connect #1 レガシーシステムに立ち向かう技術」で登壇した内容を当ブログで公開します。
イベントのテーマは「レガシーシステムに立ち向かう」ということで、システムを長らく運用していくと自然と溜まっていく 技術的負債 との付き合い方に着目しました。
技術的負債に対する理解を深め、技術的負債を可視化することによって自然と対処される状態を作る方法を解説します。
アーカイブ映像(YouTube)
スライド(Speaker Deck)
戦略シミュレーションゲームで例える
今回の主題は「技術的負債」ですが、最初はたとえ話から始めます。
みなさんは戦略シミュレーションゲームをプレイした経験がありますか?
次のスライドは戦略シミュレーションゲームによくありがちなユニット生産画面を表しています。
そして、多くの戦略シミュレーションゲームには共通要素として以下3項目があります。
- ユニット生産コスト
- ユニット維持コスト
- 所持金(収益)
ユニットには強さの違いがあり、その強さに比例した生産コストが設定されます。またユニットには維持コストが必要なので、多くのユニットを生産しすぎると収益を圧迫します。
生産コスト・維持コスト・所持金のバランスを考慮しつつ、必要最低限のユニット編成で他勢力と戦い、勝利することで勢力が拡大します。
勢力が拡大すると収益が増加してより多くのユニット、強力なユニットの生産・維持が可能になります。
戦略シミュレーションゲームとウェブサービス運営の共通項
何故この話をしているかというと、これら戦略シミュレーションゲームの要素はウェブサービス運営と共通項があります。
まず、ゲームにおけるユニットがプログラムコードにあたります。
私たちはより便利なサービスを提供するために開発力を消費してプログラムコードを生み出します。
そのプログラムコードの正体は何らかの機能であり、ウェブサービスの価値を高め、シェア拡大の原動力となります。これが戦略シミュレーションゲームにおけるユニット生産にあたります。
より優れた機能は魅力的な反面、多くの開発工数を消費する傾向にある点も類似しています。
ゲームにおける所持金は開発力(=人員・スキルセット)にあたります。
プログラムコードをウェブサービスにデプロイすることで便利・魅力的になると利用者が増え、得られる収益が増加します。
収益が増加すると会社規模が拡大して開発力を増加する要因になります。
しかしプログラムコードは一度作ったら終わりではなく、定期的なメンテナンスが必要でそれを技術的負債と呼びます。
開発時に選定したウェブアプリケーションフレームワークも数年経てばメジャーバージョンアップが必要になったり、ミドルウェアに脆弱性が発見されて対応が必要になったり、OSのサポート期限が間近など様々な形で技術的負債は現れます。
これらの技術的負債がゲームにおけるユニット維持コストにあたります。
必要性の低い機能で無計画にシステム規模を拡大させると技術的負債という名の維持コストは増大していきます。
- 開発力 → 所持金
- プログラムコード → ユニット
- 技術的負債 → 維持コスト
さて、戦略シミュレーションゲームでは維持コストをうまくコントロールしながら勢力拡大しますが、ウェブサービス運営では技術的負債に振り回されがち です。
それはなぜかというと 可視化の問題 だと考えています。
戦略シミュレーションゲームでは所持金・ユニットの生産コスト・維持コストはいずれも可視化されて、ステキで分かりやすいUIで表示されます。
一方でウェブサービス運営を考えると、開発力は所属メンバー数やスキルセットなので把握できます。開発工数も工数見積もりで把握できます。しかし生み出された プログラムコードから発生する技術的負債だけは把握できません 。
技術的負債だけは見えない んです。
次のスライドはローンチして間もないウェブサービスをノリノリで開発している状況を示しています。
まだシステム規模が小さくフットワーク軽く開発していて、冷静に考えたら「本当に必要?」と思える機能も精査されずにイケイケで組み込まれています。
しかし数年後に必ず発生するのが技術的負債の返済です。
機能の数だけ様々な依存関係が増えて、機能同士の依存関係もごちゃごちゃとなり、無計画に追加された機能たちは技術的負債と名を変えて、維持コストとしてのしかかります。
技術的負債に対する視力を得る
そこで改めまして、今回のお題がこちらの 技術的負債に対する視力を得る です。
ここからが本題です。
技術的負債に対する理解を深めるために1つ例を出します。
次のスライドは2019年に開発、2020年前半にローンチした架空のウェブサービスのシステム構成です。
当時としては最新バージョン・トレンドを採用しているはずですが、2023年10月現在で考えると Django, Ubuntu はすでにサポート切れ、React も古いバージョンなのでアップデートしたい状況下にあります。
おそらく多くの方にとっての 技術的負債とはすでに腐臭を放っているもの だと思われます。
この認識方法は次のスライド上部のように、とある時点のスナップショットに着目して技術的負債を調査・検出・対応、つまり技術的負債を「点」でとらえています。
一方で今回お話しするのは 技術的負債を「線」でとらえる 考え方です。
スライド下部に示したタイムラインはウェブサービスのローンチから4年経過してリリースされた Python のマイナーバージョンと Django のLTS版を赤い丸印でプロットしたもので、 移行の必要性が発生 したタイミングです。
新しいバージョンが登場すると旧バージョンのサポート期限は有限になり、ライブラリも徐々に対応しなくなるので、生きているウェブサービスの場合は必ず新しいバージョンへアップデートが必要です。
言語(Python)とウェブアプリケーションフレームワーク(Django)だけに着目してもこれだけのリリースがあるのでシステム全体で見たら膨大な「移行の必要性」が常時発生することが想像できます。
技術的負債を「面」でとらえる
さきほどは Python, Django を1つの線で表しましたが、実際には 技術的負債を「面」でとらえる のがより実状を表しています。
ウェブサービスの主要な構成要素を抜粋して並べたのが次のスライドです。
Python, Django, Ubuntu, React, nginx は実際のリリースタイミングでプロットしており、外部API以下の項目は「3~4年程度経過したら何らか発生することが多い」という意味でプロットしています。
例えば協力会社さんの提供するAPIを利用していると「エンドポイントのURLを変更してください」、「TLS1.1を無効化します」、「新APIがリリースされました、旧APIのサポート期限は1年間なのでお早めに移行お願いします」などの事象は発生します。インフラは物理なら経年劣化したり、クラウド環境でも「メンテナンスウインドウ内で再起動してください」などの対応が必要です。パフォーマンス不足でスケールアウトが必要となり、アーキテクチャレベルの大幅改修に発展することもあります。
全体のごく一部を示した図でも数年間でこれだけ維持コストを支払うタイミングが発生しています。
これらの技術的負債への対応はいずれもコストが必要ですが、顧客に直接的な価値を提供しない点が特徴です。顧客から見たらDBがシングルインスタンスなのかレプリケーションで負荷分散されているのかは関係ありません。「3年前はキビキビ表示されたのに最近重いな~、でも先日のメンテナンスで昔のように速くなった」のようにマイナスからゼロに戻しただけです。
ウェブサービス運営では顧客に提供する価値を減じないように同じ価値を維持しつづけるために継続的なコストがかかり、それがいわゆる「技術的負債」の正体です。
過去に着目するか、未来に着目するか
技術的負債を「スナップショットでとらえる方法」と「面でとらえる方法」の差は着目する時間軸です。
スナップショットでとらえる方法は、過去から現在に着目しています。
一方、面でとらえる方法は、現在から未来に着目しています。
技術的負債は溜まってから慌てがちですが、実際にはプログラムコードと常に共にあるものです。
技術的負債の規模を図解
次のスライドはとあるウェブサービスのプログラムコードの行数の時間軸変化を示しています。
今回は問題を単純化するためにウェブサービスの複雑さや規模を「プログラムコードの行数」で表すこととします。
徐々にプログラムコードの増加割合が減少している点は今は気にしないでください。
次に登場する緑のラインは技術的負債という名の維持コストです。
本来はプログラムの設計やコーディングに応じて技術的負債の蓄積具合は上下しますが、ここでは単純化のためにウェブサービスの規模(=プログラムコードの行数)に応じて一定割合のコストがかかるとしました。
赤いラインは開発力(=人員)です。
ウェブサービスが利益を出せば増員されるケースが多いですが、この例では昨今の売り手市場ではエンジニアの増員が難しく、なんとか一定値を維持した状況を表しています。
最後に登場する黄色のラインがプログラムコードの生成量です。
すでに解説不要なほど必然的な結果です。開発力は一定なのに技術的負債が増加した結果、エンジニアは日々フレームワークのバージョンアップやら脆弱性の対応やらパフォーマンスの改善などに追われている状況で、新しい機能開発に当てられる時間はほとんどゼロになっています。
この状況に陥ってしまうのは緑のラインである「技術的負債」が目に見えないからであり、気付かない間に開発力のほぼ全てを費やすまでに増加しています。
こうならないためにも技術的負債を見える状態にすることが必要です。
技術的負債とはなにか
技術的負債に対する視力を得るために、まずはその内部構造の理解から始めます。
そこで今まで対応してきた数多くの技術的負債を分析し、因数分解すると次の2要素が残りました。
- 変化の必要性
- 変化の難易度
この二つのうち片方だけなら技術的負債としては小さい一方で、両方がそろうと大きな技術的負債となる傾向も分析過程で分かってきました。
そこで導出した技術的負債の大きさを求める計算式が以下です。
「技術的負債」=「変化の必要性」x「変化の難易度」
計算式だけではよく分からないので具体例を考えます。
次のスライドはウェブサービス開発に必要な技術選定の様子を示しています。
何の制約条件もなく、言語・フレームワーク・インフラ・ミドルウェア・データベース・ライブラリ・実装する機能などを全て好きに選ぶことができます。
この時点では技術的負債がゼロです。
数ある選択肢から PHP8 を選定して開発を始めたとします。
ある程度開発した段階で言語を Python3 へ変更するとしたらどうでしょうか?
クラスやDB設計などは流用できますが、言語自体には互換性がないので実装コードは全て捨てて Python3 での再実装が必要で工数(=変化の難易度)は大きいです。
一方で Python3 への変更については、よほど致命的な問題がない限り PHP8 を捨てて Python3 でやり直す必要はありません。
つまり(変化の)必要性はほとんどゼロです。
結果として以下の計算式で技術的負債はほぼゼロです。
「技術的負債」=「高」x「ほぼゼロ」 = 技術的負債はほぼゼロ
次のスライドではローンチから1年間経過して PHP8.1 が登場しました。(※実際の PHP8.1 リリーススケジュールに準じています)
マイナーバージョンアップとはいえ言語のバージョンアップは影響範囲の大きい作業です。変化の難易度としては「低~中」あたりだと考えられます。
変化の必要性はというと、前述の通り新しいバージョンのリリースにより現行バージョンを利用できる期間は有限になるため、どこかのタイミングでは必ず移行が必要です。つまり変化の必要性は高いです。
結果として以下の計算式で、技術的負債はボチボチある状況、みなさんが実際に PHP8.1 への移行を担当するとしたら、そのときに感じる肌感覚に近いのではないでしょうか?
「技術的負債」=「低~中」x「高」= 技術的負債はさほど大きくないけど、ボチボチある
さらに2年間経過して PHP9 が登場したとします。
PHP は3~5年間隔でメジャーバージョンアップされるケースが多いのでこれは約束された未来です。
破壊的変更を含むプログラム言語のメジャーバージョンアップに必要な工数(=変化の難易度)は大きくなりがちです。システム全体が影響範囲となり、利用ライブラリの対応状況も確認して未対応のものは代替手段を探すなど、システム規模に応じた多大な工数が必要です。
変化の必要性も前述の理由と同様に高いです。
結果として両者を掛け算した「技術的負債」は非常に大きなものになります。
「技術的負債」=「高」x「高」= 技術的負債は非常に大きい
変化の必要性
「変化の必要性」と「変化の難易度」について、なんとなく分かってきたので次は各項目の具体的な中身を考えます。
まずは「変化の必要性」です。
変化の必要性を上昇させる項目は多数あるので実際にありそうな実例と共に解説します。
プログラム言語・フレームワーク・ライブラリ
プログラムコードはあらゆるものから独立して単独で動作するのではなく、いずれも様々な外部リソースに依存しています。
- 特定の言語仕様に基づくコンパイラ実装に合わせて書かれたプログラムコード
- 特定のインタプリタ実装上で動作するように意図されたプログラムコード
- 仮想マシン(例: Java VM)上で動作するバイトコードへ変換するためのプログラムコード
言語仕様がバージョンアップされるとアップデート対応に迫られます。実装や仮想マシンの交換が必要なケースも出てきます。OracleJDK で動作していたアプリを OpenJDK へ移行するには一通りの確認作業が必須ですし、Ruby2.7 から Ruby3系にアップデートするのは一苦労です。
時として言語以上に大変なのがウェブアプリケーションフレームワークです。プログラムコードと密結合の存在でありメジャーバージョンアップの際には既存コードへ大きな変更が必要となりがちです。サポート期限の問題で変化の必要性が高い上に、変化の難易度も高くなるケースが大半です。
言語のメジャーバージョンアップ対応の際に依存ライブラリの開発終了に気付くケースもあります。代替ライブラリを探して互換性のない動作をテストしたり、いっそ自力での再実装が必要となり変化の必要性・変化の難易度共に高くなります。
非トレンド化の問題もあります。jQuery は今もアクティブに開発されているライブラリで問題なく利用できます。しかし工数を使ってでも React や Vue.js などに移行したくなります。これが変化の必要性と呼ばれるものの中身です。
このカテゴリで発生する変化の必要性は変化の難易度も高めなケースが多いので、要件の精査には気を遣いましょう。その要件は大きな技術的負債に見合う価値があるのかを検討しましょう。
外部API・外部リソース
外部API・外部リソースは変化の必要性を誘発しやすい要素です。
前述のような「エンドポイントのURLを変更してください」的なものもありますし、外部の決済APIで障害やシステムメンテナンスがあれば、自社サイトでも工数を使って連鎖的に対応が必要です。
日本郵政のサイトで配布されている KEN_ALL.CSV
が、先日ついに UTF-8 の1レコード1行形式に対応しました。これはもちろん良い変化です。しかし現行バージョンの CSV で動作するシステムとは互換性が無くなるので、どこかのタイミングで新形式に対応するアップデートを実施する必要性は生まれます。
スクレイピング系の機能を利用して外部から情報を取得している場合は、相手のちょっとした変化で影響を受けるケースもあるでしょう。
これらはいずれも外部API・外部リソースによる恩恵を得るためには継続的に支払う必要がある維持コスト(=技術的負債)です。トレードオフとして変化の必要性を発生させるので、それでも利用する価値があるのかをしっかり判断したいところです。必要性の低い要件ならば、あえて実装を見送る判断も必要です。
インフラ・OS・ミドルウェア
- 経年劣化
- パフォーマンス劣化
- メンテナンス対応
- サポート終了
- 脆弱性の発見
インフラの属するレイヤーは脆弱性の発見など、変化の必要性が突発的に発生するケースが多いです。
一方でコンテナの利用などプログラムコードとレイヤーが上手に分離されていれば影響範囲を限定して対応可能です。
例えば nginx のようなミドルウェアに脆弱性が発見されたとしても、単にバージョンアップするだけで済むケースが多く、変化の必要性は高いけど変化の難易度は低めな傾向です。
一方でアーキテクチャレベルでの変化が必要になると、プログラムコードへ大きな影響をおよぼすことがあります。
当初シングルインスタンスのDBで稼働していたが、リードレプリカを導入したらデータの整合性など大幅な考慮事項が出てきます。
なるべく長期的な目線でパフォーマンス計画や制約条件を設定しておくとアーキテクチャ変更の際のバッファとして利用できます。
ローンチ当初はユーザーが少ないのでシンプルなアーキテクチャを採用しており、結果としてユーザーの更新がリアルタイムで反映できたとします。しかしその段階から将来を見越して更新反映を別モジュールにしておいたり、あえてキャッシュを利用して反映に時間がかかる状態になっていれば、いずれアーキテクチャ変更で実際に更新に時間がかかるようになった際の難易度を下げることができます。
開発環境
意外と見過ごされがちなのが開発環境ですが、開発者が日々向き合うものなので実は重要です。
時間経過と共に開発環境のトレンドも変化するので、Eclipse で開発しているなら VSCode を利用したくなるし、Docker を導入して開発環境構築を楽にしたくなります。
より良い Linter が登場したら移行したくなるけど、いざ適用してみると警告が大量に出て対応工数がかかります。数多くの開発者達と合意を取って進めるコミュニケーション力も必要です。
これらは開発者からのニーズという形で変化の必要性を発生させます。そして開発環境一式を置き換えるのは簡単な作業ではないので、変化の難易度もそれなりに高いです。
サービス・ビジネスの変化
ウェブサービスは利用者がいてこそ成り立つものなので、市場やニーズ・環境などの影響を受け、それ起因でも変化の必要性は上昇します。
例えば消費税の増税やインボイス制度への対応など、顧客へ提供する価値とは直接結びつきませんが、ウェブサービス運営を維持するためには必ず対応が必要です。
変化の難易度
次に解説するのは「変化の難易度」で、主に「調査工数」「開発工数」など開発フェーズにおける難易度に紐づきます。
これらを上昇させる要素の一部を挙げると以下のようなものがあります。
- ドキュメントの未整備
- テストコードの欠如
- 汎用的な機能(実際の使われ方が千差万別)
- そのシステムをよく知る人が少ない
- 利用頻度の低い機能や不要ファイルが残っている
- greppability の低いプログラムコード・インフラ設定
- 言語・フレームワークのバージョンアップ(破壊的変更の多さ)
- 非機能要件の考慮不足(アーキテクチャレベルの変更におよぶ可能性)
greppability とはコードの検索しやすさです。
例えば member_emails
テーブルへカラム追加するためにソースコードを全文検索して利用箇所を調査するとします。
しかしとあるシステムでは「設定より規約」で自動マッピングされた MemberEmail
モデル経由で操作されていたため全文検索ではヒットせず、気付かぬままカラム変更を適用すると障害になります。
コーディングの際には 後からそのソースコードを探してたどり着く人がいる ことを考慮しましょう。
キーワードの動的生成も最小限にすべきです。
例えば singup_business.template
というテンプレートファイルの利用箇所を調査して修正したら1箇所だけ洗い出し漏れていました。
その箇所ではフリープランとビジネスプランの処理共通化がされていて、ファイル名が singup_${plan}.template
で動的生成されていました。
こういったキーワードになる要素は 動的生成してプログラムコードを簡潔にするよりも、べた書きして見つけやすくする優先度の方が高い です。
汎用的な機能も変化の難易度を上昇させます。社内メンバーが利用する顧客管理システムにメモ欄カラムを用意したとします。
数年後にそのシステムの機能改善を行うために現在の運用を洗い出したところ、メモ欄に呪文のような文字列が多数見つかりました。よくよくヒアリングしてみると特定の顧客グループを識別して検索するタグのようなものや、標準カラムでは表現できない追加データを半ば構造化された呪文で記述する運用になっているようでした。結果として RDBMS のはずが文字列ベースのドキュメントDBのようになっていてすべての運用パターンをヒアリングするのに多大な時間がかかったりします。
プログラムコードを生み出す際には「変化の必要性」と「変化の難易度」の両側面から考慮して、生み出される価値に対して技術的負債が少なくなるようにしましょう。
技術的負債を多角的に理解する
話が脱線しますがみなさんは株式投資の EPS(Earnings Per Share)指標をご存じでしょうか?
1株あたりいくら稼いだか?を表しています。
株式は取得にコストがかかり、その代わりとして保有する株式は利益を生みます。
EPS は、株式をより少ない費用で取得し、より多くのリターンを得るための指標の一つとして利用されます。
この関係性はプログラムコードと技術的負債の関係性に似ています。
プログラムコードの生成と維持には工数(コスト)が必要ですが、そのリターンとしてプログラムコードはウェブサービスの価値を高めて利益を上昇させてくれます。この構図は株式と同様で、より少ないプログラムコードでより多くの利益を生み出せると良さそうです。
そこでプログラムコードの1行1行が少しずつ利益を生み出すと考え、EPS と似た指標である EPL=Earnings Per Line(1行あたり純利益) を導入したら何らかの判断材料になるのではという思考実験的な提案です。
プログラムコードの行数はおおよそのシステム規模を表し、技術的負債の大きさとも相関があるならば、その1行ごとにいくら稼いでいるのかという指標には妥当性がありそうです。そして EPL の最大化を目指すモチベーションにもなります。
もし利益の伸長があっても EPL が上昇していないなら、それは 労働集約型のプログラムコード になっている証拠です。
顧客1社ごとにカスタマイズして専用コードを提供したり、利用者の少ないニッチな機能を提供すると利益が増えても EPL は上昇しません。
EPL を上昇させるには多くの人に必要とされるニーズのある機能が過不足なく提供される必要があります。
また EPL という考え方には副産物として価値を生み出さないレガシーコードを削除する動機になるメリットもあります。
不要なコードをガツガツ削除すると分母が減って、同等の利益でも EPL が上昇します。
あらゆるプログラムコードは技術的負債です。そのためなるべく少ない規模のプログラムコードで最大の利益を得る必然性があります。
本項では EPL という株式投資のような指標で解説しましたが、これは同じ事象を様々な角度から理解することで、その事象に対する解像度が高まることを狙っています。
まとめ
今回は技術的負債の見える化というお題目で解説しました。
以下の点に注意しながら技術的負債とお付き合いしていきましょう。
- 見えていないことが問題
- 現在から未来への「面」で考える
- 維持コストであり、必ず発生するものと考える
- 可視化できれば自然と削減を考えるはず
- 「変化の必要性」と「変化の難易度」の掛け算で求める
- 各要素が増加する要因を把握すれば削減もできる
- 単純にソースコードの行数を減らすことから始める
さて、ラクーンホールディングスではエンジニア・デザイナーを大募集中です!
興味を持っていただいた方は是非、お話ししましょう!