技術的負債なんて放っておけ

仕事 (job) をしていてつらいことの一つが、人類の叡智を無下にした冒涜的な仕事 (work) を見なければならないことです。単に技術的負債ともいいます。高く積み上げられてしまった負債の山の前で、呆然と立ち尽くして、絶望的な気分になります。

どうしてこんなことになってしまったのだろうと怒りつつも、事情は想像がつくし、事実としてそこに問題が存在している状況で、問題の存在を批難しても仕方がありません。技術的負債は、これを負のサンクコストとでも呼んだらよいのか、もう過去に戻って取り返したりはできないものです。これからどうするのかだけ考えるしかありません。

どうにかしなければならないと考えつくのは、負債の山であっても、キャッシュフローを生み出す資産価値があるからです。事業自体にやる価値がないなら、何も考えずに負債ごと捨ててしまえばいい。きっとそれで解決する問題も多いでしょう。人が足りていない社会にあって、そんなものを維持することに貴重な人的リソースを浪費するのは悪行でしかありません。

気が滅入って前向きになれないような雑な仕事だと、事業が潰れることを願ってしまうこともあるでしょう。一人が思うようなことは他の人も思っているので、こういう状態になれば遠からず願い通りになりますが、良い結末ではないでしょう。個人としては逃げるのも十分に理にかなった選択でしょう。昔は別として、今は退くべきときに退けない人は他に行き場所がないか決断ができない人間だと思います。

でも、私は前向きにどうにかしたいと思ってます。しかし、前向きにどうにかしようと考えても、やっぱり呆然と立ち尽くすしかなくなるのが、困ったところです。解決できたら百億円オーダーの価値がある仕事ですが、壮大すぎて気が遠くなります。別に難しい仕事でもなくて、世にいう工数を突っ込めばどうにかなります。つまらない仕事が膨大にあるだけです。それがしたくないねえんだよという話なのに、それ以外の解決方法が見つからない。

もはや放っておく以外はないのかもしれません。負債というのはよくできた比喩で、別に金融的な負債は悪いことではありません。利率よりも儲かるならば、むしろ良いことです。リスクを勘定して、レバレッジを効かせるのが、効率的な経営というものでしょう?金融的な負債と同じく、技術的な負債が問題になるのは事業の収益性が下がったときで、そうなったら技術的負債なんて問題にならない。ただ、事業と一緒に死ぬだけです。

我慢すれば/させればどうにかなるならそれでいいじゃないかとすら思うのです。誰かが人柱になれば、数億円ぐらいで解決します。そして、そういう人は世の中にいっぱいいます。人柱を立てるのが一番安いからでしょう。当人からしても特殊な知識を持っているがゆえに、安定した仕事です。いや、それが耐え難いというのが問題であって、我慢するのは果たして解決なのだろうか。

他に思い至るのは、技術的負債を先送りにしすぎて身動きがとれなくなっている企業はそのまま死ぬべきというものです。金融的負債を繰り延べさせてもらって延命させてもらっているような企業はゾンビ企業で死ぬべきであるという理屈があって、それと同様です。いや、でも、それをどうにかしたいというのが目的なので、受け入れられない。

考えが堂々巡りになって、解決が見えない。解決が見えないなら、放っておく以外ないのだろうか。特に結論はありません。

結合の種類と結合度

先日に発見したモジュールの結合の定義を自身でも気に入ったので、よく知られている結合の種類をこの定義に当てはめてみようと思います。

モジュールの結合にはいくつかの種類がありまして、以下のものが知られています。

  • 内部結合 content coupling
  • 共通結合 common coupling
  • 外部結合 external coupling
  • 制御結合 control coupling
  • スタンプ結合 stamp coupling (data-structured coupling)
  • データ結合 data coupling

名前が変だし、それぞれの関係性も分かりづらいので、この分類はあまり好きではないのですが、他のもっと良い分類をしらないので、とりあえずこれを使います。

モジュール A の モジュール B に対する結合度 C_{AB} を以下の式で定義します。

C_{AB} =  - \sum_{h  \in H_{AB}} \log  \big( 1 - P_h \big)

ここで、 H_{AB}AB に対して持つ仮定の集合、P_h は仮定 h が成立しなくなる確率です。

それぞれの結合の種類が、この結合度の値にどう影響するのか考察していきましょう。

内部結合 content coupling

あるモジュールと別のモジュールは、普通はインターフェースによって接しています。Java の interface だけでなくて、関数やクラス/メソッドといった、インターフェースを定義して外部に公開できる機能がプログラミング言語には用意されています。普通の人は、この機能を使って他のモジュールと自身のモジュールを結合させます。

しかし、インターフェースという概念すら理解していない人は、それを無視して、リフレクションで private な変数を取ってきて、使ったりします。こういう他のモジュールの内部に結合するような種類を内部結合と呼びます。

インターフェースは変更をすれば、呼び出し側も変更しなければならないので、あまり頻繁に変更しようとは思いません。しかし、内部は仕事をしていれば変更されます。モジュールの内部が変更されないという仮定は高い確率で成立しなくなるので、モジュール間の結合度 C は非常に大きくなります。

プログラミング言語の機能で普通すればできないようになっているので、意図して汚く脆弱に作ろうとでもしないかぎり、内部結合をつくってしまうことはないと思いますが、決してそんなことはしてはいけません。

共通結合 common coupling

いわゆるグローバル変数を共有している状態です。グローバル変数を使うときは、他のモジュールがこちらの意図どおりにグローバル変数を書き換えている/いないことを仮定しています。この仮定は壊れやすいので、グローバル変数を共有しているモジュールの結合度は高いです。

もし、グローバル変数が immutable であれば、意図しない書き換えが起こる可能性は低いので、疎結合は保たれます。例えば、設定変数はグローバルに持っていてもよいでしょう。

外部結合 external coupling

2つのモジュールが、ファイルやプロトコルといったもので結合している状態です。モジュールというかシステム/サービスの結合の話のようです。これだけ毛色が異なります。

なお、出典によって、例えば 『ずっと受けたかったソフトウェア設計の授業 』と wikipedia 、意味が違うのですが、wikipedia の方を採用しました。原典に当たるほどでもないし、正しいとかないので。

制御結合 control coupling

あるモジュールが他のモジュールの動きを制御している状態です。例えば、クラスに○○モードみたいな名前の変数があって、それを変えると動きが変わるような場合です。

制御する方は制御される方がどう動くのか知りすぎた状態になるので、この2つのモジュールの結合度はやや高くなります。抽象化が十分でなくて、中身が漏れているイメージですね。制御用の変数が増えて、変な if 文が追加されていきそうなのが、想像できます。この場合は、制御する方もされる方も不安定な I/F に依存していることになります。

インターフェースを抽象化して、ポリモーフィズムなどを使って、知り過ぎない状態にするのが解決方法です。

スタンプ結合 stamp coupling (data-structured coupling)

なぜスタンプなのかはよく分かりません。データ構造で結合している状態とは、引数や返り値に独自に定義したクラスなどを使って、それで2つのモジュールが通信している状態などです。必ずしも悪いわけではないが、不必要な型で結合するのは、要らない仮定を持ち込むことになります。仮定の数が多ければ結合度が高まります。

1つのフィールドしか要らないのに、データ構造を丸々渡してしまうのは、良くないでしょう。例えば、 this を引数に渡すのは乱暴さを感じませんか?

また、引数には複数の値が渡せますし、返り値としても Python などは多値を返せます。意味もなく型を共有せずに、基本的な型だけのインターフェースの方が、使いやすくなることもあるということは知っておいた方がよいでしょう。

データ結合 data coupling

必要なデータだけを交換して結合している状態です。要らない仮定がなくて、仕様が安定している状態なので、結合度が低いです。

モジュールの分割点

アーキテクチャの問題とは、システムをどこで分割 (decomposition) するかという問題と言い換えてよいでしょう。分割するのは、魚を捌くのと同じようなもので、包丁を入れるべき場所というのがあります。

分割するというからには、切り分けるサイズがありますが、今回は分割の単位の議論はしません。なぜかといいますと、ちゃんとつくれば、システムの部品は入れ子構造になっているはずです。再帰的に作られているという言い方もできます。つまり、システムを切り分けて出てきたものは、やはり同じやり方で切り分けることができます。マトリョーシカというか、フラクタル的といいますか、問題を切り分ければ同じものが出てくるはずです。明らかに正しいことが分かるサイズが最小単位で、そこに至るまで分割を繰り返されたものが、ちゃんとした仕事でしょう。

問題をバラすときにタテに切るかヨコに切るか、そしてその切り目はどこかというのは、分割のサイズに依らず決まっているように思います。まだ全てを発見したわけではないし、重複もあるかもしれないということを断った上で、以下を挙げます。

データ構造

特にクラスベースのオブジェクト指向プログラミングではデータ構造でモジュールを分割するのが基本でしょう。データを統べることが機能であり責務であるというところから、データ構造とデータの操作を切り出してまとめたのがクラスです。人間が計算機にさせたいことは、データの変換と記録だけなので、データから考える方が簡単です。人間の興味とシステムのアーキテクチャが一致していたら、扱いやすいものができるそうなことは想像がつくでしょう。

トランザクション

システムのアーキテクチャは人の興味に一致していた方が便利である――というのは経験則であります。データ構造からたどる方が一般にやりやすいと思いますが、システムにやらせたいこともシステムの切り目です。WEB システムに API が複数生えていたら、当然 API ごとにモジュールを切り分けますよね?やらせたいことは、トランザクション/手続きといった方が分かりやすいかもしれませんが、それらごとにモジュールは分割されます。クラスの中もメソッドが分かれております。これもデータ構造に対してやらせたいことごとに分割されているはずです。

共通機能

同じシステムは一つとないとはいっても、だいたいどんなシステムも似たようなものでして、全く同じ機能がいることが多々あります。そういうものは共有できるように別モジュールに切り出した方が便利です。ライブラリとかフレームワークとか呼ばれたりします。ただし、意味もなく再利用を志向するのは避けるべきです。そういうのが再利用されることはまずありません。

レイヤードアーキテクチャは共通機能で分割する手法の一種です。分解して抽象度を落としていくと、同じものが必要になります。自然界だって要素に分解していくと、原子・素粒子と全てが同じになります。同じものはみんなで共有して使いまわした方が便利です。

組織

コンウェイの法則で知られるものです。組織が異なったらモジュールも分かれます。人が異なってもモジュールは分かれます。組織が巨大化しているのに、モノリシックなアーキテクチャを維持するのは無理です。組織を編成するひとは、アーキテクチャを知って仕事をしないと、組織は上手く回らないでしょう。

開発の時系列

後から機能を加えるときは、取ってつけたように追加せざるをえません。組織でモジュールが分かれるように、たとえ同じ人がやっていたとしてもプロジェクトの単位でモジュールが分かれるでしょう。開発プロジェクトは何かしらの論理的な単位で切られるものなので、そこでモジュールが分かれるのは不自然ではないでしょう。むしろ、既存のモジュールに変な条件文を加えるほうが、不自然な改修だと思います。

メソッドは関数ではないのだが

Clean Architecture を読んでいて、クラスおよびメソッドのことを、関数あるいはサブルーチンの一種としているような記述があって、やや気に障りました。

具体的には、単一責任原則の説明のところでして、

f:id:fjkz:20181013154747p:plain

上の絵のように Employee というクラスにいろんな機能を集めたら、太っちょになります。

f:id:fjkz:20181013155919p:plain

そのため、このように機能を切り出したらよいでしょうとあります。

最初に巨大化するようなクラスを定義してしまったら、こういう関数っぽいクラスに切り分けるのも仕方ないことと思います。しかし、この設計がきれいとは思えないです。手続きを頑張ってクラスの中に書いたら、余計に汚くなるように思います。世の中そんな仕事ばかりです。

普通のオブジェクトは手続きよりも、むしろデータ構造を記述するものです。普通に作ったら、欲しいデータごとにクラスがあって、それらに対する操作するがメソッドになるはずです。多分以下みたいになるのではなかろうか。

f:id:fjkz:20181013161306p:plain

なお、本には Clean Architecture といって、ドメインモデルが定義された設計についても、ちゃんと書いていることは補足しておきます。

疎結合の正体見たり

モジュールが疎結合になっているとか密結合になっているとか、業界にいますとよく聞きます。モジュール間の結合度の定義を発見したのでメモしておきます。

モジュール A の モジュール B に対する結合度 C_{AB} は以下の式で定義できます。

C_{AB} =  - \sum_{h  \in H_{AB}} \log  \big( 1 - P_h \big)

ここで、 H_{AB}AB に対して持つ仮定の集合、P_h は仮定 h が成立しなくなる確率です。

要するに、これは情報エントロピーを用いて結合度を定義しようとしていまして、C_{AB} は0以上の値を取り、結合度の値が大きいほどモジュール間の結合が密となります。

そもそも、モジュール間の結合度というものが定義されていなかったので、その定義を発見したことに意味があります。

さらに、この定義が便利なのは有名な設計原則を説明できてしまうことです。以下のようなものを聞いたことがあると思います。

  • デルメル原則
  • リスコフの置換原則
  • ハリウッド原則
  • 驚き最小の原則

これらはだいたい同じことを言っています。依存する側が持つべき仮定のことを仕様といいまして、依存する側はできるだけ少なく、覆ることがなさそうな仮定の上でシステムを作れと言っています。結合度 C は、仮定の数が少なく、仮定が成立し続ける蓋然性が高いほど、小さくなります。

簡単のために原則でない例から挙げていきますと、REST API でシステム間を疎結合にしたマイクロサービスとか良くいいますよね。API 仕様が安定で、HTTP プロトコルが廃れる可能性が低ければ、結合度 C は小さくなります。逆に、システム間で、データベースやソースコードを共有していたりすると、それらが変わる可能性は高いので、C が大きく密結合になります。

他にも、Java の標準ライブラリに密結合しているとは誰も言わないのは、Java はサービスの寿命が続く限り使い続けることができて、Java の標準ライブラリは安定していて変わる心配をする必要がないからです。一方で、 Kotlin が廃れる可能性は高いので、Kotlin で作ったアプリは Kotlin と密結合だと言ってよいでしょう。

原則に戻りますと、デルメル原則は別名で最小知識の原則といいまして、結合度 C の定義でいうところの、仮定の数を少なくしましょうという法則です。仮定の数が少なければそれだけ疎結合になります。

リスコフの置換原則・ハリウッド原則についても、これらはインターフェース仕様にのみ依存して、実装が変わっても動くように作れといっています。インターフェース仕様は安定していて、変わる可能性は低いので、 P が小さくなり、疎結合が実現できます。一方で、不安定なインターフェース仕様だと、余計に密結合になってしまうでしょう。

驚き最小の原則も、要するにびっくりしない仕様は普通の仕様なので、安定しているから、それに依存すれば疎結合だよねという意味です。

他の例を上げれば、バグがあって、バグを回避するように奇妙な細工をしたら、バグを改修された動かなくなります。バグが直らないという不確かな仮定の上に作られているので、結合度は大きいです。あるいは、他のライブラリの private な変数をリフレクションで覗いて、それにロジックに組み込むという正気を疑うコードを昔見たことがあります。当然のように相手のバージョンが上がったら動かなくなってましたが、これも不確かな仮定の上に作られた密結合な作りです。

設計を評価するのに、今回発見した結合度の定義は大変有用性が高いように思います。

ビジネス向けの製品は嫌い

ビジネス向けやエンタープライズ向けといって売られている製品に、会社員をしていますとよく出会います。特に IT ベンダーはこれらの言葉を好んで使います。しかし、私はそういったビジネス向けの製品を好ましく思っていません。

ビジネス向けと特別に言わなければならないほど、ビジネスとは偉いものなのだろうかと疑問に思います。BI -- Business Intelligence という単語は変な言葉です。ビジネスには元々知性がなかったのでしょうか。悲しいかな、あえて BI と言わなければならないように、大方の会社では当て推量で意志決定が行われているのが実態でしょう。ビジネスというものの程度の低さにもどかしさを、私はむしろ覚えているのに、これはビジネス向けの製品で特別なものだと言われますと戸惑うのです。

また、どんな道具であっても用事を済ませるために、つまり仕事をするために作られています。ビジネスだけが仕事ではないです。ビジネス向けとついていない製品であっても仕事で使えるように作られています。JavaEE -- Java Platform, Enterprise Edition というものがありますけれども、Spring だって Struts だって Seasar だって当然のことながら仕事で使うために開発されました。使うべきかどうかは、道具としての完成度の問題で、別の話です。

むしろ、ビジネス向けと言われたら、ビジネスにしか使えないのかと低く見てしまいます。ビジネス向けは、逆にそれ以外ものと比べて、要件が緩いように思っています。ビジネス向けとして売られている製品は、品質が低くないですか。会社で使わされる製品は、我々が私生活で使用している製品より、高価格なのにも関わらず、品質の水準が低いように思います。例えば、社内システムは特注品はもちろんのこと、パッケージや SaaS あっても、私生活で使うウェブサービスと比べて、ダサくて遅くて使いくくないでしょうか。欠陥や障害も多い。もちろんイケているのもありますが、イケてなくても許されます。なぜなら、ビジネスなら使う人に我慢させることができるからです。コンシューマ向けであれば、消費者は厳しいので、良くなければすぐに使ってくれなくなります。ビジネス向けとコンシューマ向け要求される品質のレベルに大きな差があります。もちろん、ビジネスに特有の要件というものはありますので、それを解決してくれる製品は必要です。仕事なので我慢して使います。

ビジネス/エンタープライズは、マーケティング用語です。特に意味もない、なんとなく分かった気にさせる言葉です。それで売れるならマーケティング用語であっても積極的に使うべきだと思っています。しかし、ビジネスを卑しいとまでは言いませんが、特に偉いものでもないと思っている人には、ビジネスという言葉でマーケティングされても通じません。

やりたいことをするのにどうして許可がいるのだろう

あなたは何がしたいのですか?

よく聞かれます。私はこの問いが理解できない。シェンロンなのでしょうか?

お前の望みを言え。

これなら分かります。俺を不老不死にしてくれとでも答えましょうか。でも、何がしたいの、と聞く人は私の願いを叶えてくれるわけではない。そんな能力もない。では、いったい彼らは何を尋ねているのでしょうか?

「私が」やりたいことをあなたが訊いてどうするのでしょう?だって、あなたにやってほしいことではなくて、私がやりたいことですよ? どうして「私が」やりたいことをするのに、誰かの許しがいるのでしょうか?

やらせてあげる。

という言葉も聞いたことがありますが、これもよく分からないのです。セックスの話ではありません。セックスは相手の許しが要ります。ここで言っているのは、私の行動の話です。やらせてあげるという人は、本当にただやらせてくれるだけで、やるのは全部私であり、別に協力する気はない。でも、恩着せがましく、成果は奪う。多分彼らは封建的な考えを持っていて、やらせてあげるだけで、何かをして与えているつもりなのだと思います。個人の自由を認めていないならば、やりたいことを尋ねる理由は分かります。

しかし、現代では個人の自由は保証されています。自由なんて与えて貰わなくても、元よりあるはずです。人が何かをするのに、政府からの免許が必要なこと以外は、許可なんて要らない。実際、個人的にやりたいことがあったら、行動力さえあれば何でもできる。やりたいことがあったら、訊かれなくてもやっているはずだし、やるべきだと思うのです。例えば、海外で働きたいという人がよくいますが、やればいいじゃんとしか思いません。パスポートもって行きたい国に行けよと。やっていないとしたら、そんなにやりたいと思っていない。あるいは、違うことをさせられていて時間が取れないだけです。やりたいことを訊くなら、帰らせてくれたらいいのに。

私はやりたいことはやっている。できないことは元よりやりたいなんて思わないし、林の中の象のように静かに生活していたいので、それで思いつくようなことは大抵やっている。やりたいことはやっているので、何がしたいと訊かれても困るのです。

やりたいことはやっているので、それに介入される方が煩わしい。やりたいことやっていたら楽しいけれども、その過程や結果にあーだこーだ言われたら楽しくなくなるものです。

手の届く範囲は自由にしているのに、何がしたいのかという問いに答えてしまったら、自由は与えられるものだというのを認めることになりませんか。それは精神の自由を手放すことで、私はそれが嫌なのでしょう。