IT業界で気づいたことをこっそり書くブログ

くすぶってるアプリエンジニアが、日々気づいたことを適当に綴っていきます(受託→ベンチャー→フリー→大企業→ベンチャー→起業)

技術的負債に立ち向かう

ブログを読んだ人からTwitterで相談されたので、改めて考えてみました。
(※よく聞いたらちょっと事情違いましたが)

 

ちょっとまとめ方がまだ粗いです。非常に難しい問題ですし。
正直ここに真正面から取り組んだことはないのですが、これまでの色んな経験から述べてみます。(そもそも取り組めるケースが稀)

 

想定読者:エンジニア歴3年〜くらい?

 

 

0.議題

スタートアップ2〜5年目あたりの苦しいフェーズをどう乗り切るかという話題です。
特にバグがどんどん増えていく状況。ジェンガコードにどう立ち向かうか周り。

皆同じように苦しんでいる印象がありますが、苦しんだ末にどうにかなっているサービスと、どうにもならなくなったサービスがあると思います。

元になったのはこの話でした。

 

otihateten.hatenablog.com

 

1.技術的負債とは何者なのか?

平たく言えば、開発における技術的な足枷であり、雪だるま式に増えていく複雑さだと思います。

負債とはよく言ったもので、実は開発リソースに余裕があるなら問題にならないことが多いです
例えば「1億円の負債がある会社はダメなのか」と同じ話です。よく言われる話ですが会社が負債=借り入れを持ってるのは当たり前で、制御可能なら問題ないわけです。
例えば大規模なプロジェクトだと、少しのコードを変更するのに1人月以上あったりします。

問題となるのは余裕が無いときです。
負債が1億円、年商が1000万円、だとしんどいのと同じで。

なので技術的負債がきつくなるのはベンチャーとか、開発リソースの少ないプロジェクトであることが多いと思います(まあ大多数はそうですけどね!)

継続期間が長いことも技術的負債が問題になる要因の一つですね。

 

1.1.技術的負債の単位は「困る度」

上で「複雑さ」と書きましたが、厳密ではないと思います。
非常に複雑なコードでも、それを後で触らないなら別に負債とは呼べないわけです。
負債となるのはプロジェクトにとって困るからです。

技術的負債はプロジェクトの都合に依存する

これ、結構語られないと思うんですがどうでしょう。

ただ一般的にコード等が複雑であるほど困るので、複雑度に注目することが多いということだと思います。
(厳密に言えば「複雑」以外にも、「差し替えコストが大きい」などがあります。例えばDBの設計だとか。一言で表現しづらいですね)

 

ちなみに「新規開発して、納品して完了」な受託開発などは複雑でも困らないので、発狂するほど複雑度が高いものが出てきたりします。

それでこうなるわけです↓ 引き継ぐの人が死ぬ。

otihateten.hatenablog.com

 

世の中の、仕事に困ってない一部のエンジニアは引き継ぎ案件を明確に拒否しています。それくらい面倒です。 

 

1.2.プロジェクトはなぜ「困る」のか?

技術的負債というのは、それだけで複雑系です。
分解しないと議論ができません。

プロジェクトはどういうときに困るのでしょうか?

  • 改修スピードを上げたい
  • 仕様変更に対応したい
  • バグを出したくない
  • クオリティを上げたい
  • お金をかけたくない
  • スケールさせたい

でもできない! そういう時ですね。

 

1.3.なぜ皆が技術的負債に悩んでいるのか?

プログラムというのが、指数関数的に複雑度が上がるのに、人員をふやしても作業量は線形ですら上がらないからです。
なので誰でも簡単にこの問題に遭遇ができます

 

f:id:otihateten3510:20190330114423j:plain

イメージ

問題は線形で起こらないため、対処しないとどこかで詰みます

 

1.4.一旦まとめ

混乱してきそうなので一旦話をまとめます。

 

f:id:otihateten3510:20190330115803p:plain

こんな感じですかね?
「お金」と「スケール」は開発の場合意味が重複するため割愛。

 

2.技術的負債の返済方法

2.1.ゴリ押し

そもそもの目標はコードではなくプロダクトに紐付いていて、プロダクトはプロジェクトに紐付いているわけです。
なので、こういう事が考えられます。

  • 負債の踏み倒し → コードを捨てる
  • 負債の一括返済 → 最初から作り変える

これらが出来るなら、チャンスだと思います。技術的負債とちまちま戦うよりは楽です。
実際にベンチャー3年目でフルリプレイス」みたいなのはよく聞きますし。うまくいっていないプロダクトをクローズした例もよく聞きます。
真の目標はもっと上流に存在するはずです。

 

2.2.分割返済

次に考えるのは分割返済です。
例えばコードの一部を切り出して、そこだけぶっ壊して作り変えるという方法があります。これはよく見ますね。

私もしょっちゅうこれをやっています。

 

分割しやすさが問題になってきます。

qiita.com

 

2.3.それ以外の方法

たぶん、それ以外はまともに戦う方法になります。

ようやく本題です😂

 

3.「技術的負債≒困る度≒複雑さ」の主敵はなにか

これも一言で言うのは非常に難しいですが、乱暴に「状態数」と「依存関係」と言ってしまいたいです。

実はこれでQiitaに投稿しようと思ったんですが、テーマが重すぎて止まってます・・・

 

3.1.状態数が増えると複雑になり負債が貯まる

状態数が増えると複雑度が増して、負債がたまります。
簡単に言えば条件分岐が増えると考えてもらえばいいです。

状態数が爆発する要因は、属性が多いからです。
例えば「性別」でわけるなら2通りですが、これを「年齢」で5通りに分けたら状態数は2*5=10です。さらに住んでいる都道府県で分けたら2*5*47=470通りです。

このように属性を増やしすぎると簡単にテストできないほどの状態数になります。
こういうの、次元の呪いとか、組み合わせ爆発とか言ったりしますね。

問題なのは、例えば「性別によって◯◯を変更する」みたいな仕様追加されたた時、元の状態数が10通りなら20通りになるだけですが、1000通りなら2000通りになるということです。プラスN通りで増やしていると思っていたら、いつの間にか状態数が爆発していることがあります。

 

3.2.例外で更に厄介になる

もちろん、すべての状態が例外なくルールベースでプログラムされていれば軽症で済むのですが、例外を入れるとかなり厄介になります
下の図で、左は2属性によって3x4=12状態に分割されたものとイメージしてください。
これはif else 文を2階層で書くことができます。
それに例外を2つ入れたのが右の図です。if elseの一部のブロックにもう一つ別の条件分岐が存在します。

f:id:otihateten3510:20190330165541p:plain

2属性の状態

 

こうなると、まず適切なテストのパターンを洗い出すのが難しくなってきます。今はまだ2状態ですが、右図は既に複雑の片鱗が見えています。これを3属性、4属性にしていったらどうなるか想像してみてください。

3.3.例外はどのように発生しがちか

  • 特殊仕様「この時に限ってこれをこうする」
  • ゴリ押し対応「この時だけバグるのでここだけ対応する」

これらはジェンガコードの温床です。

 

3.4.状態数が爆発したらどうなるか

  • 見通しが悪くなる
  • テストの漏れが多くなる
  • バグの条件を切り出すのが難しくなる
  • うっかり意図しない状態をいじってしまう

そしてジェンガコードが出来上がります。

 

3.5.状態数は仕様や要件に依存する

困ったことに、コードをどれだけ弄っても、状態数は結局仕様の複雑さに依存してしまうため、コードの中だけでは完結しません。
仕様の調整も必要です。

 

3.6.状態数が爆発させる火種

慎重に状態数を抑えようとしても、どうしても状態数を増やしてしまうものがあります。

 

時間・タイミング
例えば「ある時刻から1秒前か1秒後かで挙動が変わる」という仕様が、自分のコードではなくプラットフォーム状に組み込まれてしまったりします。
また、画面を遷移する直前、画面を遷移した直後のようなタイミングも似た要因です。
これによって勝手に状態数は爆発させられます。

 

割り込み処理・ユーザー
時間にも似ていますが、これらも同様です。
例えば「ユーザーが〜〜という処理をしたら」というのは際限がなかったりしますし、「端末のバッテリーが切れたら」「ネットワークが切れたら」のようなケースもあります。

 

外部のAPISDK
こういった自分のコード外に引きづられるケースもあります。

 

これらは組み込まないように注意するだけではなく、積極的に回避したり、どうにか工夫しなければなりません。
また、応用ですが、自分のコードの状態数が多いほどこういった外部要因の状態数爆発で更にひどいことになります。

3.7.依存関係も複雑さの原因の一つ

これもきちんと説明すると本一冊になりそうなのでざっくりと(誰か研究とかしてないんですかね?)

依存関係にはいろんな形があります。一番わかり易いのはcallerとcalleeの関係。他にもcall backの関係。継承の関係などがあります。

f:id:otihateten3510:20190330171834p:plain

上図はcaller calleeの話ですが、上図のようなものはシンプルです。

下図のようなものは悪いです。何となく察しが付くと思いますが、観点としては「それを変更した時の影響範囲が広い」あるいは「未知数」という点です。

f:id:otihateten3510:20190330172108p:plain

個人的に理想なのは下図のような状態だと思います。
ちょっと分かりづらいですが、要はブログと同じです。
タイトルがあり、章立てされていて、それが構造的である。内部で勝手に情報のやり取りはしない。こうするとコードを見たとおりの影響範囲で落ち着くようになりますし、可読性が上がります。

f:id:otihateten3510:20190330172917p:plain

caller,callee以外の関係はまた少し難しいのですが、安易な自作クラスの継承はやめようとか、安易な共通処理はやめようとか、そういうことが言われていたりします。

 

依存関係を複雑にしがちなものに神クラス、神オブジェクトというものがあったりします。

God object - Wikipedia

簡単に言えば、何でもかんでも詰め込んだ数千行を超えるモノリスです。

 

3.8.依存関係はコードである程度対応可能

こちらはどのように現実の振る舞いをきれいにコードに落とし込むかという話なので、頑張ればコードできれいにできます。
ただちょっと難しいのと、現実がそもそも複雑というケースもあるので、何ともならないケースもあります。

 

 4.技術的負債に立ち向かう

ここまでは「技術的負債って何?」をおおよそ適当に書きました。
ここからは現実的に技術的負債を解消する方法について考えます。

 

4.1.負債を解消できる人材が必要 

当たり前ですが、大量に時間をかければ何とか出来る人材が居ないと詰んでます。

(つまりファイアリングも話の俎上に上がってくるんですよね)

 

4.2.負債解消には時間がかかる

「仕様を満たす」だけでは負債は溜まったままです
負債解消には「仕様を満たす」以上に時間がかかります。

プロダクトの実際の振る舞いというのは、コードのほんの一部でしかなく、コードの品質が悪いと少し変更しただけでバグがいっぱい出てくるという状態になります(潜在バグ)。目には見えないタンスの裏をどれだけ掃除するか、みたいな話です。表層だけきれいにしていると身動きが取れなくなってきます。

 

https://2.bp.blogspot.com/-mlB3OCfVu5Q/V5Xczy66fDI/AAAAAAAA8uU/yeFcuAMgP7Yq0OWQ6yY0pA4yd1e2GUbAACLcB/s450/hyouzan_ikkaku.png

つまり時間がなければ解消は難しいです。
負債をそのままにするといずれ開発スピードが遅くなって心停止するので、これはトレードオフとなります。

技術的負債を解消する時間 vs 開発スピードが遅くなるリスク+バグが生まれるリスク

を何とか見積もり、ジャッジしなければなりません。
タイミングや方法が重要になってきます。

 

4.3.負債解消には個人差があり難しい

しかし実際、「技術的負債を解消しましょう」「でもなるはやでね!」とか言われても困るわけです。
「仕様を満たす」以上の、どの程度コードをきれいにするかは、非常に個人差があります
そんなの無視して納品したほうが、非エンジニアからは「仕事が速いやつ」と見られて印象が良いです。だからこれはその人の主義とか思想依存なわけです

プルリクエストやコードレビューで対応する事も考えられますが、個人的にはそれほど効果があるように思えません。
何故かと言うと、技術的負債を仕込みやすいのは設計レベルだからです。プルリクで「設計やり直し」と言うのは、言う方も言われる方もきついのではないでしょうか?

 

4.4.リファクタリング駆動開発で時間をとる(提案)

これは一つの提案 ですが、テスト駆動開発のような方法を取ると良いのかと思います。

  1. 仕様を満たすコードを書く
  2. ある程度のテスト
  3. リファクタリングをする
  4. きちんとしたテスト

リファクタリングの時間を設け、忙しいときにはその時間を減らし、余裕があるときにはその時間を増やす。そのような調整弁として存在していればよいのかと思います。

 

4.5.マージ後コードレビュー(提案)

すいません、名前忘れましたが、マージ前のコードレビューではなく、マージ後にコードレビューをしている人の話をどこかで見ました。

このメリットは

  • 修正がきつい設計レベルの指摘を言ってもどうせ今は修正できないから言いやすい(心理的安全性)
  • 実際に修正しないので、宗教論争になりにくい(答え先送り)
  • 修正できないので、次からそう書こうと心に刻める

もちろん、「これは修正が必要だろう」というものは新たにチケットを切れば良いわけです。

 

4.6.技術的負債の要件を洗い出す(提案)

ここまで「負債」だの「複雑」だの「依存が」だの、ふわふわしたことを書いてきましたが、そんな抽象的なものでは実際にリファクタリングができません。
技術的負債を解消しているつもりだけど、本当に解消できているか不明というプロジェクトは非常に多いと思います。 計測が非常に難しいのでそうなってしまいます。

でもせめて、「このプロジェクトにとって技術的負債とは何か」みたいなものは洗い出せるはずです。観点リストですね。

例を書いてみます。

 

コードのトレーサビリティは良いか
  ex:GOTOの排除、暗黙的入出力の排除
バグの箇所をすぐ検知できるか
影響範囲がすぐにわかるか
誤解の生まない表現であるか
  記述する場所は適切か
  名前は適切か
  仕様が読み取れるか

単一責任になっているか
可読性はよいか
平易な書き方であるか(※チーム依存)
構造が複雑すぎではないか
  理解するのに時間が掛かりすぎないか
  混乱しないか
神クラス、神メソッドではないか
  呼び出されすぎではないか
  呼び出しすぎではないか
  依存が複雑ではないか
機能毎に削除しやすいか
  疎結合であるか
ベースとなるコードの抽象度が適切か
  仕様追加で変更が生じないか
ベースとなるコードの具体度が高いか
  一意に決まるか
仕様変更になった場合、困らないか
  具体的すぎないか
  削除できるか
  密結合すぎないか
技術がバージョンアップしたときに困らないか
コメントは必要量存在しているか
不要なコードが放置されていないか
コードについて誰が知っているかが自明か

など

 

これの上流に位置する目標をもう一度貼っておきます。

f:id:otihateten3510:20190330115803p:plain

 

4.7.素晴らしく難しいコード

あまり技術的負債とみなされていないものに、素晴らしく難しいコードがあると思います。例えば歴15年のエンジニアAと、歴2年のエンジニアBのコミットしているソースがあるとします。エンジニアAは熟れているので、一般的に最適かつちょっと難しい書き方、設計でプログラムを書きますが、エンジニアBは未熟なのでその書き方を知りません。毎回調べて苦労しながら改修していきます。
この時どうするべきでしょうか?

  1. エンジニアBはもっと勉強するべき
  2. エンジニアAはもっと平易な表現を用いるべき

これはプロジェクトの状況次第ですし、そのコード次第です。
ですが世の中の風潮は非常に1寄りです。

しかし、もう一度これまでの話を振り返ってみます↓

f:id:otihateten3510:20190330115803p:plain

難しいコードによって、開発スピードが落ち、バグが生まれやすくなっています。
なのでその難しいコードは技術的負債です。

 

4.8.若手レビュー(提案)

つまり、技術的負債はチーム構成によってガラッと変わります。

これ理解できるでしょうか?
例えば、英語と日本語ができるAさんと、日本語しかできないBさんがいるとします。仕様書は当然日本語で書きますよね?
でも上の1の主張は「Bさんは英語を勉強するべき」という主張です。ちょっと意味がわかりません。

これは若手レビューでキャッチアップできると思います。
コードを見て「これは難しい」をジャッジしてもらうだけです。

アクションとしては3つ

  • 確かに難しすぎる、あまり使わないようにしよう
  • これは流石に覚えるべきだ(教える)
  • 知らなくても大丈夫

どの結論を取っても、技術的負債は解消されます。

 

4.9.外部知識・暗黙知を避ける(提案)

これもあまり理解されないですが。
頭の中に、あるいは別のドキュメントに依存関係が書かれている場合、その人が知らないと対処不可能なので、思いもよらぬ挙動になったりします。
思いもよらぬ挙動にならなくても、潜在バグとして負債が残ります

f:id:otihateten3510:20190330184850p:plain

何かしらヒントがあれば良いのですが、世の中の数多の言語は、ノーヒントで書けるようにいろんな機能が用意されています。そしてそれは素晴らしいものだと言われているので、積極的に活用されてしまいます。
これらはgoto文の系譜です。

goto文 - Wikipedia

 

もちろんそういう機能が完全にNGというわけではありません。改修者が知らなくても大丈夫なものに使用するとか、ヒントを与えられたら良いわけです。ですがそれ以外の方法で使うとトレーサビリティが落ちます。

 

外部知識のもう一つ大きなものがデザインパターンです。
そのデザインパターンに精通しているメンバーだけなら良いと思いますが、それ以外の人が入るとそれは技術的負債です。技術的負債を解消するつもりで、技術的負債になっています。でも本人は解消しているつもりです。
結局状況次第なのです。

 

4.10.トレーサビリティと開発環境依存

例えばXcodeはメソッドを呼び出している箇所が追跡できます。
Xcodeを使っていないメンバーが居ると話が変わるわけです。

また、追跡できない書き方もあります。この書き方を用いられると、例えば10箇所の呼び出し部分のうち、2箇所だけトレースできないみたいなことになります。そしてバグを仕込んでしまいます。そうでなくても、調査に時間がかかってしまいます。

ありがちなのが、「このメソッドもう使ってないと思うけど、ひょっとしたら使ってるかもしれないから消せない」というものです。こういうのが増えるとどんどん重くなっていきます。

 

4.11.トレーサビリティと新規メンバー

もちろん新規メンバーほどトレーサビリティがありません。
プロジェクトの構造を把握できていないからです。大きいものだと把握するのに数ヶ月以上かかったりします。なので、もはや新規メンバーは技術的負債の温床と言っても過言ではないと思います。

人の入れ替わりが激しいプロジェクトは・・・恐ろしいですね。

人月の神話に繋がる話です。

人月の神話 - Wikipedia

 

5.更に闘う者たち

技術的負債は、仕様・人材・体制、その他諸々に影響を受けます
つまりより頑張るためにはエンジニアチームの外をどうにかする必要があります。

 

5.1.エンジニアのコントロール

これわかりません。
要件としては、その人が技術的負債を解消できる人かどうかジャッジすることです。
わかりません・・・
監視するしか無いですかね?
監視したとして、ダメだったらどうすればいいんでしょうか。

 

5.2.体制のコントロール

できれば少人数でやりましょうとしか言えません。
これも難しいですね。

 

5.3.仕様のコントロール

仕様をシンプルに保つことが重要なので、仕様決定にはエンジニア(アーキ、リーダー)が参加しなければなりません。
仕様を決めるのは事業の都合なので、事業を理解していなければなりません。
事業の都合は業界やユーザー都合なので、業界やユーザーを理解しなければなりません。
よりコードがきれいになるように、代替案を出せなければなりません。

究極、プロダクトがシンプルなら、コードがカオスになることはありません。
最悪作り直せば良いわけです。

 

5.4.上長の理解

わかりません。
ここらへんは結構ググると出てきますが、「社長がエンジニアリングに理解がある人だった」以外の答えを見たことがない気がします。

あとは信頼関係ですかね・・・?
個人的には代替案でごり押したいですが

 

5.5.権限が必要

この話忘れてました。
技術的負債を解消するには権限がある程度必要です。
読めばわかると思いますが、どうしても作業フローの改善手続きが入ってしまうので、上司やチームリーダーやCTOなど、そういう人じゃないといけません。

もちろん権限がなくても練習はできますが。

 

5.6.議論より説得、質問

議論(ディスカッション)、つまり複数人で何かを決定するというのは、非常にコストが高く難しい作業です。
これを避けるには説得や質問の方が良いです。

開発でよくやりがちな過ちとして、抽象度の高い議論をしてしまうことです。

例えば「このプロジェクトにMVVMは適しているか」「ユーザー体験を向上させるにはどうすればいいか」「このコードは複雑だ」のような。
観点がありすぎて答えが出ないので、大体宗教論争でおわります。
「猫と犬どちらが素晴らしいか」みたいな話です。

より具体的な細かい話を、選択肢を出しつつ話すのがいいでしょう。

 

5.7.とは言え宗教論争は尽きない

宗教論争の一つの原因が、状況を絞れていないからです。

コードレビューなどの時に具体的に「ここに対して何が最適か」をリファクタリング要件と突き合わせて考えれば少しは話しが進むのではないでしょうか。
まあでも最後は多様性を受け入れろ、ってことになりそうですが。

 

6.おわりに

何か半分も書けていない気がしますが疲れたのでここまで。

 

忘れた気がする話

デザイン
やぐに
結合度、凝集度
改善をどのように指標化するか
時間がない時どうするかの話
どう勉強するか
他人を理解する(何を大事にしているか、何が負債と感じているか、何に興味があるか)
クソコード討論会

 

そのうちアップデートします