✈️

技術負債は悪か?──負債を武器にするための考え方

に公開

概要

この記事は技術負債について筆者の以下の考えを記載している。

  • 負債の分類
    • いつのまにか借金型
    • 銀行ローン型
  • 負債を背負えば短期的な利益(開発スピード)を得ることができる
  • 負債は時間と共に"複利"で利子(返済コスト)がかかる
  • 負債は以下の返済方法がある
    • コツコツ返す(リファクタリング/システム分割)
    • 踏み倒す(リプレイス)
  • 負債を分類しそれぞれに合わせた返済計画を

(書き物という特性上、記事全体を通して断定口調で書いてますが、ただの一意見として受け止めていただけると嬉しいです。)

ますは負債を分類しよう

あなたはこう思ったことはないだろうか?

  • 「Vueって最近なんか廃れたよね?次のシステムはReactにする?」
  • 「WebFrameworkのFiberは応答速度めちゃ速!Echoから乗り換える?」

確かにこれらも負債だろう。
だが致命的な負債だろうか?VueだろうがReactだろうが、ユーザーにリッチなUIは提供できる。FiberだろうがEchoだろうが、ユーザーにデータは届けられる。
どちらもユーザーに価値を提供するという目的を達成できているのだ。(確かに応答速度や将来性には不安もあるが)

では致命的な負債とはなんだろうか?
例えば、新規サービス立ち上げ時、開発スピードを重視してフルスタックWebFramework(LaravelやRuby on Rails)を選定したとする。
そのプロダクトが大成功し、新機能追加の要望が次々に舞い込む。

  • ユーザ同士がライブのように相互アクションする機能:Laravelに双方向通信に強い機能はあっただろうか?
  • 機能増加に伴い定期バッチ処理増:FrameworkのWorker機能のジョブを追加してもWorkerインスタンスの負荷は大丈夫だろうか?
  • ユーザー数が爆増したためにDBが悲鳴:RDBのPartition機能を利用したいが今のテーブル設計で可能か?RDBは悲観ロックが必要だから応答時間は大丈夫か?

なかなかに致命的ではないだろうか?LaravelからFrameworkを乗り換えるとか、DBをRDBからDyanamoDBに変更するとかが対処法としては浮かぶだろう。
無理した設計をするなら双方向な通信ではLaravelでは、Broadcast機能でユーザに一斉にイベントを送信し、クライアントからの通信はサーバーのRestAPIを叩いてもらうとかも可能だろうがこれは効率的なのだろうか...。RDBからいきなりDyanamoDBに移行するのってコスト激高だよね...とか。

ここでは負債のコストの多寡について論じたいのではなく、その負債は"計画"できたか?について論じたい。
例えば、10年前に宣言的UI Frameworkの主流がReactになると予想できたか?5年前にFiberというFrameworkがリリースされると予想できたか?
逆にシステム立ち上げ時に、双方向通信な機能は追加されそうかは予想できた?定期実行バッチがたくさん必要になると予想できたか?

おそらく回答はこうなるだろう。
前者は予想できない。が後者はビジネスサイドに丁寧に聞き取りすれば予想できただろう。

負債の分類

ここで負債を分類しよう。

  1. いつのまにか借金型負債
  2. ローン申込型負債

1.いつのまにか借金型負債

あなたは買い物にいつもカードを使っている。ただなんとそのカードは支払いをする度に5%手数料が上乗せされるのだった!
この1年このカードで支払いを続けたため、いつのまにかそれなりの額の借金ができていたのであった。。。

これは予想ができないタイプの借金の例である。あなたが日々開発行為をする中で、おそらく「とりあえず、ディレクトリはこの分け方にしよ」とか「とりあえずは、このロジックは共通化しないでいいよね」の経験があるだろう。
この「とりあえず」に「いつのまにか借金手数料」が含まれているのである。

だから、1年後にコードを見た時に以下の事象が起きる

  • 「あれ?このロジック...色んなところに散らばってるぞ」
  • 「なんかディレクトリ分け統一感なくね?」

そして3年後には以下の事象が顔を出す。

  • 「ロジックを変更したつもりが、B画面からユーザ登録するとキャンペーンが適用されない!」
  • 「新規メンバーが参画した時のキャッチアップコストがやべー」

2.銀行ローン型負債

今度は、予想できる負債について話そう。
言わずもながだが技術者は技術を選択できる。が、そもそも知らない技術は選択できない。
前提として、このローン型負債は技術者によっては「いつのまにか借金」となることに注意したい。
そもそもLaravelしか知らない技術者にSPA+API Serverやサーバーレス開発の技術選択はできないのだ。なので日々の技術のキャッチアップをしなければならないと自分を戒めたい。

ここからが本題だが、負債(利子付き)と分かっててなぜ僕たちは何故ローンを申し込むのだろうか?
それは短期的には合理的だからである。

フルスタックFrameworkを選択すれば、サーバー1つでフロント、バック、メール送信、ジョブ管理が全部出来るのである。
エンジニアに求められるスキルもPHPさえ出来れば開発は可能になる。インフラもこれ一つで完結するため開発量も圧倒的に少ないであろう。

つまり早くリリース(製品提供)するために技術的負債(ローン申込)をするのである。

負債には複利が効く

別の側面からも負債について見よう。
それは負債には利子が、それも複利で効いてくるのである。

簡単な例だが、先ほどの「ロジックの分散」では、

  • 1年後:コード改修する時に「なんかイケてないな」
  • 3年後:「ユーザにキャンペーンが適用されてねー!ユーザに謝罪と返金対応しなきゃ!」
  • 5年後:「もはや何がバグの原因か分からず修正できません😭エンジニアがDBを直修正対応に忙しくて修正工数さえ取れません😭」

工数は以下のようになる

  • 1年後:修正工数1h
  • 3年後;修正工数3h + 保守対応工数3h/月
  • 5年後:修正工数∞h + 保守対応工数160h/月

時間が経つにつれ明らかに負債額が大きくなっている。つまり複利で利子が発生している。

なぜ複利になるのか?それはあなたが追加開発するコードは今のコードの上に書くからである。
つまり今のコードが腐っていれば、その上に書いたコードがいくら綺麗でも、そのコードは必然的にリファクタリング対象に含まれるからだ。

こぼれ話(Adaptorパターン)

土台が腐っており追加開発分が機能的に元コードと分断されている場合、以下のような開発もできる。
a5mk2の画面
腐ったコードに直接追加せずに、Adaptorコードを被せその上に追加機能のコードを手法である(左図)。この手法では追加開発したコードは腐ったコードの影響を受けずに開発ができるため保守性を保てる。
また元コードを含んでリファクタリングする際も追加開発分のコードを再利用できるため、工数的にお得になる(右図)。

追加開発時に常にこの手法を採用するべきと言う意味でなく、追加開発分が元コードと機能的に分断されている場合はこの方法を検討しても良いだろう。

このAdaptor的な考え方はClean Architectureにも通じるものがあるが、それはまた別のお話。

次の章では負債への対応を考えていこう

負債には返済計画を

一般生活で考えよう。あなたは月給30万として、月々5万円のローンなら申し込むだろうか?それが3年後の支払額が月々50万になるとしても?
きっとあなたはローンを申し込まないだろう。
(残クレアルファードなら手取り20万でも申し込む?トヨタは夢を与えてくれる。)

つまり返済計画が建てられるか?が大事なのである。
そして負債の分類に応じた返済計画を予定しよう。

いつのまにか借金型

返済計画を建てよといったがこのタイプは予測が付かない。(即落ち2コマ展開)
つまり問題になった時に初めて返済計画が建てられるようになる。では何も対策する必要はないのであろうか?そういう訳ではない。

「ロジックの分散」の例で考えよう。これを負債として認識するのはいつだろうか?
3年後のキャンペーン不適用バグからだろうか?確かにこの段階ではバグという形を表すので必ず認識するだろう。

ただ、1年経過の時点で「イケてないな」という感覚はあったはずである。私のおすすめとして「いつのまにか借金型」はこのタイミング、つまり気付いた時点で負債を返却すべきだ。

負債は複利が効く、1年目の時点での返済工数は1hである。ここで返済しなければバグになり保守対応工数として3h/月という月々払いのコストに性質変化する。

あなたが使っていたカードはリボ払いだったのだ!リボ払いと知らずに月々の返済を踏み倒していたために、1円の借金がいつのまにか3万/月に膨れ上がっていた!
ならばあなたが取るべき行動は、一括返済である。今一括返済しなければ利子が無限増殖することになる。

あなたは開発作業する中で意図せずこのカードを契約している。ならば、リボ払いと気付いた時点で一括返済してすぐに解約するのだ。
それがコスト的に一番お得だ。技術的な言い回しをすると小規模なリファクタリングで日々対処しよう。
(そのために各メンバーが作業中に自然とリファクタリングするチーム文化を醸成するのだ)

銀行ローン型

銀行ローン型は予測がつくため返済計画を事前に建てられるのである。返済計画が建てられないのであればそれは申し込むべきない。

例えば、「プロダクト案はあるがプロダクトが市場に需要があるか調査ができていない(PMF前)」であれば、大量工数をかけて高負荷にも完璧に対応できるマイクロサービスアーキテクチャを作ったぜ!としても、誰も使ってくれないかも知れない。
最新設備の工場を開設してクマちゃん人形を大量生産したが、誰もクマちゃん人形を買ってくれなかったという状況だ。

であれば、最初は手作業でクマちゃん人形を作って、メルカリで売るのである。
売れたら、今度は人を雇ってクマちゃん人形を作りECサイトを開設するのである。工場を作るのは大ヒットしてからである。

サービス開発では手作業&メルカリがフルスタックFrameworkである。(今ではサーバーレス設計が採用することが多いでしょうが)
もちろん、ある程度の売上げが見込める!というのであれば、SPA+APIServerでも良いし、絶対に大人気になる確証があればマイクロサービスアーキテクチャを採用してもいいだろう。

大事なのは現実的な負債の返済計画を立てられるかである。
そして多くの場合、この返済計画はとても大変なものとなる。
概要程度だが筆者の思う代表的な返済計画を例示しよう。

返済計画の種別(銀行ローン型)

先に結論を書く。以下の返済計画がある。

  1. コツコツ返却(段階的リファクタリング/段階的システム分割)
  2. 踏み倒す(即落nry...(システムリプレイス)

1については、稼働している負債システムを稼働させながら徐々に現状に適した形に変えていくのである。
だが、これは言うは易し行うは難しである。
そもそも稼働させながらになるので、移行途中の中途半端な状態でもバグが起きてはいけない。つまり移行中でもシステムは正常稼働させるという超絶難易度の制約が発生する。

これの難易度を説明すると、これが出来る人はそもそも僕のこんな記事を読む必要が全くない人である。
システムを新規作成した段階で、すでに移行計画のあらましは計画済みでないとこれは不可能だ。
もちろん途中参画でも段階的リファクタリングの計画を建てられる場合はあるだろう。
だがそれは、対象システムがどれだけ綺麗に作られているかに大きく依存する。

段階的リファクタリングの亜種として段階的システム分割も存在する。
僕が例として挙げていたフルスタックFrameworkからのリファクタリングではこちらの手段が取られるだろう。
フルスタックFrameworkの問題点はスケールの限界にある。そうなるとシステム構成自体をSPA+APIServerやマイクロサービスに変更せざるを得ないため、システムの分割が発生する。

2については、現行システムと別個に移行先システムを作成する。
移行先システムが完成した時点で、ドメインに紐づくサーバーを移行先に切り替えることでガツンと移行するのである。
メリットとしては、段階的リファクタリングの「移行中でも正常稼働させる」という強烈な制約が発生しない。つまり開発段階では品質に対する要件はなくなる。

これは開発者にとって非常にありがたい。
適当に開発してバグが発生しても後の工程でそのバグを取り除ければ問題ないからである。段階的リファクタリングではバグを含むコードを作った時点で、ユーザに新鮮なバグをお届けしてしまうである。

ただデメリットも存在する。まずシステムに切替ダウンタイムが発生する。
次に移行したタイミングで全てのバグが表出する。つまり開発規模が大きいほどに移行直後に問題が発生しやすい。最悪のケースでは、移行した直後にシステム構成に致命的な問題が発覚し、作業の大部分がパーになるリスクがある。

つまりどちらの手法を選んでも相当なリスクを背負って実施することになるのだ。
今回は分かりやすいように2分割して説明したが、実際には1,2を合わせたような対応が多いだろう。リプレイス対応するが、リプレイス範囲を絞り段階的リプレイスをするなどである。

まとめ

筆者の負債に対する考え方は以下。

  • 負債の分類
    • いつのまにか借金型
    • 銀行ローン型
  • 負債を背負えば短期的な利益(開発スピード)を得ることができる
  • 負債は時間と共に"複利"で利子(返済コスト)がかかる
  • 負債は以下の返済方法がある
    • コツコツ返す(リファクタリング/システム分割)
    • 踏み倒す(リプレイス)
  • 負債を分類しそれぞれに合わせた返済計画を

負債を武器(開発速度)にするには、それに合わせた返済計画が必要なのである。
そして何より大切なことは「いつのまにか負債」を産まないための自己研鑽と、常にユーザ視点で考えることである。

技術力がなければ知らず知らずに生み出す負債が大きくなる。ユーザ視点がなければユーザに何の価値もない作業に大量の工数を投下してしまう。
これが出来ていれば、開発チームは自然と日々負債を返却し、どうしても大量の負債を背負うときは計画を持って開発できる。
そして振り返れば、ユーザに継続的に価値を届けられるチームになっているはずだ。

Discussion