下町柚子黄昏記 by @yuzutas0

したまち発・ゆずたそ作・試行錯誤の瓦礫の記録

技術的負債:予防と返済のプラクティス

概要

技術的負債をいかに予防し、いかに返済し続けるか。自分なりの考えの整理です。 「原理原則をプラクティスとして運用する」という観点にフォーカスしています。

上記をいかに仕組みとして担保するかという点をまとめます。

もくじ

技術的負債とは何か

Wikipediaより具体例の引用

文書化、テストコードの記述、ソースコード中の積み残し(TODO)項目の解決やコンパイラの警告、静的コード解析ツールの解析結果への対応

組織で共有されない知識や、複雑すぎて変更が難しいコード

なお、ヒューマンエラーや運用ミスであっても、その失敗を誘発するような仕組みが問題だと考える。

また、明確に期限性があるEOSL(OSやライブラリ、APIのサポート期限切れ)対応は別のエントリーにまとめる。

リードタイムや品質に悪影響を与える

例えば、クラス設計に一貫性がないアプリケーションを改修する場合、影響範囲の洗い出しが手間となる。 すると、調査・実装・テストに時間が掛かり(リードタイムに影響)、考慮漏れで不具合が発生するリスクもある(品質に影響)。 結果としてビジネスやカスタマー体験にも悪影響を与えることになる。

3つの特徴がある

アラート通知のノイズ除去を例にする。 キャパシティやエラーログを監視してSlackに通知するように設定したが、対処する必要のない通知(ノイズ)が混じったとしよう。

1. ビジネス効果の見立て・計測が難しい

アラート通知はシステム運用者向けのものだ。 どれだけノイズがあろうと、サービス利用者に直接的な影響はない。

かといって、除去しないままだと、致命的な障害の検知が遅れる(漏れる)可能性がある。 また、大量の通知に目を通す負担や、高頻度の通知による集中の阻害は、実務担当者にとって辛い。 ヒューマンエラーの温床だ。

ビジネス観点の機能追加か、アラート通知のノイズ除去か、どちらを先に実施すべきか。 優先順位を判断するのは難しい。ビジネス効果の見立て・計測が困難だからだ。

ロジックと数字を組み立てることはできるかもしれないが、技術課題1つ1つに多種多様で複雑怪奇な算出式を並べる羽目になる。 そのため、ビジネス観点とは別のトピックとして対処することが望ましい。

2. 新しい課題が常に発生し続ける

除去すべきノイズはどんどん増えていく。

アプリに機能を追加するにつれて、コードベースは複雑になり、アラートの発生箇所は増えることになる。 アプリに機能を追加しなくても、依存ライブラリや連携システムのEOSL、利用者増加に伴う高負荷で新たなアラートが投げられる。 単純に同じノイズが大量に通知されることだってあるだろう。

機能追加をストップしてノイズ除去のためのプロジェクトを実施することは根本的な解決策にならない。 1度除去しても終わりではないからだ。 そのため、継続的に対応し続けることが望ましい。 したがって、予防・解消のプラクティスは、開発フローの中に組み込まれることになる。

3. 時間経過と共に修正が困難になる

どれが必要な通知でどれが除去すべきノイズか、切り分けが対処の第一歩だ。 システムが稼働を始めたばかりでアラートの種類も少なければ、専任者が1週間で切り分けできるだろう。

しかし、改修しないままだと悲惨だ。時間経過と共に大量・多様なアラート通知が飛ぶようになる。 仕様やコードベースは複雑になっており、特定パターンのアラートを1つ再現するために1週間掛かる可能性もある。

成功するようなプロダクトは特に、ビジネス・チーム・システムが急激に拡大・複雑化しうる。 借金が雪だるま式に積み上がる前に、なるべく早い段階から予防・返済していくことが望ましい。 継続的な予防・返済を仕組み化できずにレガシー化した場合は、機能追加を停止して書き直すことになる。

技術的負債をいかに予防するか

上記の特徴を踏まえると、なるべく早い段階から開発フロー内に予防のプラクティスを組み込むことになる。

スクラムセレモニー

スクラム開発を行う。

メンバがT字型人材の集まりで、誰もがチケットを上から(優先順位の通りに)取れること。 少なくとも、透明性・検査・適応の原則を理解し、セレモニーを通して、理想に近付くための継続的改善ができていること。

すると、以下のような予防が可能となる。

  • ドメイン言語が確立しやすくなる。
    • AさんとBさんが別々の言葉を使っていたら議論が混乱するからだ。
    • 辞書やWikiを作ることが悪いとは言わないが、定期的なセレモニーで言葉を交わすことで定着しやすくなる。
  • 筋の悪い設計を回避しやすくなる。
    • スプリントプランニング第2部にて、全員で設計を実施するからだ。
    • 良い設計を維持するための観点や価値観がチーム全体に浸透する。
    • 1人が設計して、後から他の人がレビューする場合に比べて、前工程で品質を作り込める。後工程になると手戻りしにくいので、前工程での品質担保が重要。
  • 早すぎる最適化を防ぐことができる。
    • 要件や作業チケットを最小限に分割することで、YAGNIの原則に従うからだ。
    • Pull Requestとチケットが連動していれば、余計な実装は入り込まない。
    • 計画外の実装を入れ込んだ場合、施策効果やベロシティの計測で槍玉に挙げられる可能性がある。矯正されやすい。
  • コードの統合リスクを抑制できる。
    • トーリー分割によって継続的インテグレーションが可能となるからだ。
    • マージ待ちの在庫を積み上げないようにすれば、コンフリクト解消の手間を減らし、予期せぬ影響を最小化できる。
    • 段階リリースや機能トグルなど、利用者から変更が見えない場合でも、負債予防の観点では有益となる。
  • インフラのコード化やCI/CD基盤の整備をアクション化しやすい。
    • インフラの設定が可視化されにくい・スケーラブルではない場合、あるいはリリース作業に時間が掛かる場合、属人性排除やベロシティ安定化のブロッカーとして問題提起することができる。

なお、チームの技術スキルが未熟な場合は、負債を蓄積せざるをえない。

中途半端にアーキテクトを引っ張ってきて、チームが理解できない設計をしても、その設計自体が負債となる可能性がある。 チームへの装着まで徹底できる人がいると理想だが、そうでなければ可能な範囲でベストを尽くして、あとは前に進むしかない。

技術的負債とは別に、個人学習、勉強会、ペアプロ、負債解消、問い合わせ対応、障害対応など、スキル向上の機会を活かすことが大切。

静的解析

静的解析ツール(RubocopやSonarQube)で自動的に指摘が入るようにする。 コーディング規約を見ながらの目視レビューは現実的ではない。

  • 指摘項目を0件にする。
    • 一般的に非推奨とされるコーディングを防ぐため。
    • レガシーシステムの場合は、悪化させないことを目標にする。いきなり0件は無理なので徐々に改善。
  • 複雑度や負債スコアを悪化させないようにする。
    • 保守性の低いコーディングを防ぐため。
    • クラス・メソッドを適切に分離・結合することになる。結果としてKISSの原則が保たれる。
    • まだ若いシステムの場合は悪化せざるを得ないので、閾値を超えないか・推移が緩やか(異常値でない)かを確認する。
  • テストカバレッジを悪化させないようにする。
    • 自動テストのメリットを享受し続けるため。

悪化させない≒(ぴったり同じスコアにはならないはずなので)僅かに改善となる。 結果としてボーイスカウトの原則が保たれる。

内容によってはコミットやプルリクを分割することになる。 チケットはリファクタリング(前)→主改修→リファクタリング(後)として型化できる。

  • 最初のリファクタリングでは既存の非推奨な設計・実装を修正する。
    • そうしないとテストコードが書けない場合。
    • 既存構成に流されて低品質となる場合。
    • まだレガシー化していないようであれば不要。
  • 後のリファクタリングでは適切な分離・結合を実現する。
    • コーディング時に気付いた問題があれば、早期にその発見をコードに反映する。
    • 事前に内部設計まで見通せていれば不要。ホワイトボードコーディングが活発なら最小化できる。

なお、静的解析ツールを自動反映するに当たって

  • ローカルのコミット時検査が理想だが、設定が難しければプルリクで最終担保することになる。
  • デフォルトのルールだと過不足があることが多いので、必要に応じてカスタマイズする。

また、Lint系ツールやコードフォーマッターも同様に運用に乗せるのが望ましい。

自動テスト

外部品質・内部品質を維持し続けるためにテストコードを書く。 なお、機能追加に伴う動作確認・手動検査(特に探索的テスト)は技術的負債とは別トピックなので言及しない。

内部品質

単体テストRSpecJUnitを想定。

Pull Requestごとに担保する。 テストしやすいコードとなるので内部品質が担保される。

静的解析でのカバレッジ担保と合わせてKISSの原則が保たれる。 コードレビューを待たずして、実装者自身が自工程で品質を担保するようになる。

実装・テストを細かく刻み慣れることで、スプリント計画会議でのチケット分割も細かくなる。 1つの案件に全員で群がることができるまでチケットを小さく分割すると、互いの作業内容が見えるようになり、相互レビューの精度が上がる。 また、小さなバッチサイズでの一個流しが実現できると、リーン原則に基づくリードタイム・品質の改善に繋がる。

外部品質(機能)

E2Eテスト。SeleniumやAppiumを想定。

リリースごとに担保する。理想は毎晩回って毎朝チェックできること。 重要機能が正常に動作することを担保する。 デグレ観点。その時々のリリースの改修内容とは無関係で毎回実施するテスト。

主要導線や重要機能、なおかつ主要ケースがメンテナンス対象となる。 相対的に重要度が低い機能、レアケースでの挙動、動作保証外環境は対応しない。 テストコードの保守コストが過剰になるため。

コストを掛けて守るべき品質とは何か、担保すべき重要機能とは何か。 関係者全員がサービスレベルの認識を揃えていることが前提となる。 SLAの設計・運用プラクティスは別のエントリーにまとめる。

とはいえ(特にネイティブアプリの)E2Eテストは技術的にまだまだ難しいところもあるように感じている。

外部品質(脆弱性

セキュリティテスト。OWASP ZAPを想定。

リリースごとに担保する。理想は毎晩回って毎朝チェックできること。 SLAを担保する。脆弱性はベストエフォートで0件を目指すことになるはず。

まだ私自身がセキュリティテストのツールを使いこなせていないので、見通せるようになったら追記する。

外部品質(パフォーマンス)

負荷テスト。Gatlingを想定。

定期的に実施する。理想は毎週末に回って週明けにチェックできること。 SLAを担保する。キャパシティが中間指標、レスポンスタイムが最終指標となる。

アクセス増加のシナリオを元にテストを設計することになる。

  • 過去のピーク時期のアクセス傾向に基づく今後のピーク予想。
  • 今後の事業計画を踏まえての増加。
  • ニュースに取り上げられたりバズったときの増加。

価格体系によってはサーバ費用が跳ね上がるので、負荷試験の実施頻度は財務計画とのご相談。 週次が難しくても月次。それがダメなら、せめて四半期に一度は実行したいところ。

技術的負債をいかに返済するか

予防をしても多少の負債は溜まる。小さく早く返済する仕組みを作る。

課題を一覧管理する

プロダクトバックログにアイデアを起票するように、技術的負債のトピックを起票して管理する。 所与のリソース内で継続的に返済し続けるためにはスクラムのような進め方が望ましい。

ポートフォリオを組む

専任の担当者をつけるか、タイムボックス内の一定時間でチーム全員が対応するか。

担当者をつけてしまうと、担当者以外のメンバの学習機会が奪われることになる。 負債解消を通して、予防動機と知識が身につく。同じような負債を生み出さないように注意できる。 それでも専任者を設けるなら、定期的に当番をシャッフルするのが良いかもしれない。 理想はチーム全員だ。各々が学習した上でスプリント計画会議に臨むことで、観点を洗い出しやすくなる。

チーム全員で一定時間を設ける場合、週X時間ではなく、特定の曜日・時間で固定する。 結局いつやるかを決めないと、他の作業に追われてスキップしがちになってしまうため。 「毎週金曜日の午後3時間は技術的負債を返済する」といった形で設けると話が早い。 差し込み業務でその時間に着手できない時は、レトロスペクティブで解決策を話し合う。

優先順位を付ける

ビジネス効果を明示できなくても、何かしら優先順位を付けて上から着手するのが正攻法。 「リードタイムや品質への悪影響」が問題なのだから、その影響度合いを比較する。 大中小かフィボナッチ数列で相対比較すれば十分だ。改修の規模感も見積もっておきたい。

とはいえ、この方法には限界がある。 そもそも日々のリファクタリングで解決できないような課題が残るので事前に見通しを立てにくい。 また、プロダクトオーナーのように優先順位を最終判断するロールが必要となる。

  • 1人のPOが持てるストーリーは50個までと言われている。が、技術課題リストはシステムの複雑化に伴って50個を超えるだろう。
  • PO配下にエリアPOを設けるか。定期的に課題リストをリセットするか。意思のあるメンバがPOに働き掛けて特定課題の優先順位を上げてもらうか。いずれにせよ管理コストが過剰に生じうる。
  • システムの複雑化に伴ってMicroservicesになるのが理想ではある。チームや課題リストも一緒に分割されるのであれば。ただ、リストが50個を超えるのが先か、リアーキテクティングすべき規模になるのが先か、やってみないと分からない。

もしくは……

もしくは、各チームメンバが心のままにチケットを選ぶという手もある。各々がやるべきだと判断したものを選ぶ。 日々の開発・運用の中で負に感じているもの、成果を出しやすいものが優先的に選ばれることを期待している。 少なくとも「リードタイムや品質への悪影響」を誰よりも肌で感じるのは、管理者ではなくチームメンバ1人1人のはずだ。

管理コストをゼロにして、価値の創出にエネルギーを費やすのが理想のあり方。 何よりも「自分の意思で選び、自分の手で改善する」成功体験の獲得こそが、技術的負債の予防と返済を継続するための、最高のプラクティスだと思う。

最後に

本エントリーは、某所でまとめた箇条書き草案に、殴り書きの解説を加えたものです。 1年後には、草案から大きく離れて、全く違うプラクティスに辿り着いているかもしれません。 この手のトピックはチームごとに試行錯誤しながら道筋を作っていくものだと思っています。 機会があれば、異論反論を含めて、ぜひ他の方の事例や考えを伺ってみたいです。

長くなってしまいましたが、最後まで読んでいただき、本当にありがとうございます。

エクストリームプログラミング

エクストリームプログラミング

リーン開発の本質

リーン開発の本質