dely Tech Blog

クラシル・TRILLを運営するdely株式会社の開発ブログです

不確実性とうまくやっていくためのプログラミング設計論

こんにちは。delyのTech Leadのうめもりです。

これはdely Advent Calendarの25日目の記事です。ほかの記事についてはこちら

qiita.com adventar.org

をご覧ください。

昨日はプロダクトマネージャー兼開発部ジェネラルマネージャーをしている奥原 (@okutaku0507)が

tech.dely.jp

という記事を書いてくれました。ご興味あればそちらも是非ご覧ください。

25日目の記事は、みんな大好き技術的負債の話をしたいと思います。

はじめに

「技術的負債」

我々プログラマーからすればうまく付き合っていく必要のある厄介な存在であり、「何故技術的負債を解消していかないといけないのか?」というトピックは定期的にプログラマー界隈でも話題になりやすいものです。

時にはビジネスサイドに技術的負債の存在やその厄介さについて説明する必要が生じることもあり、その説明の難儀さに苦労した方も多いのではないかと思います。

今日はプログラミング、プロダクト開発の不確実性というテーマから、技術的負債についての説明をしてみたいと思います。

そのためにまずは、我々は何故プロダクト開発をするのか?というところに立ち返ってみましょう。

我々は何故プロダクト開発をするのか?

BtoC、CtoC、様々なビジネス領域においてプロダクト開発という業務は存在していますが、仮にあなたがどんなプロダクトを作っていたとしても 「我々は何故プロダクト開発をするのか?」という問いに対する答えは基本的には同じはずです。

我々は、未来のマーケット、未来のユーザーに価値を提供するためにプロダクト開発をしている。 これが我々が何故プロダクト開発をするのか?ということに対する答えになると思います。

注意しなければならないのは、現在のマーケット、現在のユーザーに対してのものではないということです。何故ならばそのプロダクトが出来上がるまでには多かれ少なかれ時間がかかるはずであり、その頃にはマーケット、ユーザーは多少なりとも変化しているはずですから。

未来を確実に予測することはできないという前提

未来のマーケット、未来のユーザーに価値を提供するということを考える際に、最も重要な原則があります。それは 未来を確実に予測することはできない ということです。

どんなに注意深くマーケット、ユーザーの情報を集めたとしても、100%未来がこうなると予測することは現在の技術ではできません。つまり、未来のマーケット、未来のユーザーに対して開発する予定のプロダクトが、本当に価値を提供できるかどうかを確実に予測するすべはないということです。プロダクト開発とは本質的に不確実性を持っているものであり、我々はプロダクト開発がもたらす不確実性とどう付き合っていくか、ということを考える必要があります。

もしそのプロダクトを使うユーザーはあなたがよく知っている人間であるとしても、その不確実性を排除することはできません。プロダクトを欲しがっているユーザーと、プロダクトを前にしたユーザーはもはや他人であると考えるべきです。(多かれ少なかれ皆さんも経験があることではないでしょうか?)

そして、一部の例外を除くほとんどのプロダクトについては、今プロダクトを使っていない未知のユーザーに届ける必要性があるものだといえます。勝手知ったるユーザーにプロダクトを提供することに不確実性があるなら、今あなたが全く知らないユーザーに対してプロダクトを提供することについては言うまでもないでしょう。

不確実性を味方にするたった一つの方法

では、我々はプロダクト開発がもたらす不確実性とどう付き合っていけばいいのでしょうか?

未来を予測する最も確実な方法は、それを発明することだ - アラン・ケイ

我々は未来を確実に予測できなくても、過去のプロダクトがマーケット、ユーザーにとって価値があったかどうかを検証することはできます。 あらゆるプロダクト開発は、マーケット、ユーザーにとってそのプロダクトが価値があるかどうかを検証するために行われるものだと言っても過言ではありません。

プロダクト開発が成功した場合においても、失敗した場合においても、我々はそのプロダクトのもたらす価値という情報を得ることになります。

プロダクト開発が成功した場合に得られる情報は、それはそのプロダクトが価値がある、という情報です。
プロダクト開発が失敗した場合に得られる情報は、それはそのプロダクトが価値が無い、という情報です。

大抵の場合、そのプロダクトに価値があるかどうかは100%か0%ではなく、ある部分は価値があり、ある部分は価値がないという情報が得られるでしょう。

プロダクト開発がもたらす不確実性を味方にするたった一つの方法、それはプロダクト開発が失敗した場合に被る損失を最小にし、プロダクト開発が成功した場合に得られる利益を最大化するという方法です。

プロダクト開発の失敗とどう向き合うのか?

プロダクト開発の成功から得る利益を最大にする方法、それはプロダクトの価値のある部分を破棄せずに使い続けることです。
プロダクト開発の失敗から被る損失を最小にする方法、それはさっさとプロダクトの価値のない部分を破棄して、作り直すことです。

ここで一つ気を付けなければならないのは、コードの改修は大抵の場合はこの破棄して作り直す、ということを意味するということです。破棄する範囲が広いか狭いか、という違いだけがそこには存在します。

価値のある部分を残し、価値のない部分を捨てるためには、それらを区別することができることが必須条件です。

もし価値のある部分と価値のない部分の区別がつかなければ、価値のない部分だけを捨てるという判断が出来ません。そうやってプロダクト自体の価値のない部分が時間とともに増えていけば、いずれプロダクト全体を捨てなければならないという破綻を招くことになります。

疎結合であり、単純明快で意味のある構造を実現し続けること

プロダクトの価値のある部分を残し、価値のない部分を捨てられるようにするための基本的なアイディアとして、コードを疎結合な状態に保つということがあります。疎結合とは単純にコードが分割されている、というだけではなく、コードの意味として分割されていることで、分割されたお互いのモジュールの実装の詳細を知らなくてもそれぞれの機能が提供できる、という状態のことです。

例えばプロダクトがAという機能とBという機能を提供していたとして、Aという機能を提供している部分、Bという機能を提供している部分がコード上での構造としても明確であり、Bという機能だけを検証の結果として破棄することになったとしてもすんなり破棄できる状態であるのが疎結合になっているという状態です。これが、Aという機能がBという機能の実装を深く理解していないと提供できない、あるいはそもそも不可分である、という状態になっていると、そう簡単にBという機能だけを破棄することが出来ないということになります。Bという機能を外から不可視にすることは簡単にできるかもしれませんが、システムとしては本来必要のないBという機能の実装を理解した上で、今後の機能実装を進める羽目になるでしょう。そういった破棄できない部分が増えていけば、前述したようにいずれコードを丸ごと破棄するしかなくなるという結末が待っているでしょう。

コードを疎結合に保つ、ということはそうでない場合に比べて実装コストがかかりやすいものですし、そもそも困難なことです。大抵の場合は密結合に作る方が簡単ですし、実装コストも安く済むでしょう。ただし、それは実装した部分全てが有用であり、変更する必要が無いということが前提になっているか、そもそも一回きりの実装でコードを丸ごと破棄することに問題がない場合だけでしょう。(例えばプロトタイプ開発とか)

我々はプロダクトを実装した結果から学習し、不要な部分を破棄した上で先に進み続ける必要があります。古くからそのための様々な考え方がありますが、今回はその文脈でよく出てくるDRY原則と、SOLID原則についてもう一度振り返ってみましょう。

DRY原則をコードを破棄するという観点から振り返る

DRY(Don’t Repeat Yourself)原則とは、情報の重複を避けるという考え方です。特定の機能の実装がバラバラにコードの中に入っていると、その機能を破棄する際にコードの実装を細かく調べる必要があり、破棄することが困難になります。例えばそれらが同一の関数あるいは同一のメソッドあるいは同一のクラスで表現されていれば、破棄は容易になるはずです。一つにまとめたうえで、分かりやすい名前がついていればより簡単にコードを捨てられるようになるでしょう。

ここで気を付けなければならないのは、コードの重複を排除して一つにまとめることが、コードを破棄しにくくすることにつながってしまうことも往々にしてあるということです。一見それは重複であっても、機能上の意味として異なる場合にはそれらの機能をまとめてはいけません。

DRY原則は有用な考え方ですが、プロダクトから学習してプロダクトを改善し続ける際には、注意深く適用しなければならない考え方です。

SOLID原則をコードを破棄するという観点から振り返る

SOLID原則とは、オブジェクト指向言語(今日の実用的なプログラミング言語は多かれ少なかれオブジェクト指向的な要素を持っていますね)における、5つのプログラミング上の原則をまとめて頭文字をとったものです。

  • Single Responsibility Principle(単一責務の原則)
  • Open/closed principle(開放/閉鎖の原則)
  • Liskov substitution principle(リスコフの置換原則)
  • Interface segregation principle(インターフェース分離の原則)
  • Dependency inversion principle(依存性逆転の原則)

今回はそのすべてを振り返ることはしませんが、コードを破棄するという観点から考えるとどれも有用な考え方です。そのうちの二つ、単一責務の原則と開放/閉鎖の原則について振り返ってみます。

単一責務の原則

Single Responsibility Principle(単一責務の原則)とは、「クラスはただ一つの理由で変更すべきであり、一つの機能だけを持っているようにするべきである」という原則のことです。コードを破棄するということから考えると、とても自明なことであると言えます。機能がクラスごとに明確に分割されており一対一でマッピングされる状態であるなら、それらを破棄することはとても容易なはずです。

開放/閉鎖の原則

Open/closed principle(開放/閉鎖の原則)とは、「クラスは拡張に対して開いていなければならず、修正に対して閉じていなければならない」という原則のことです。機能を追加する際にクラス自体を修正する必要が無いようになっているということも重要ですし、その他クラス外の何か変更によって、クラスの挙動が破壊されないということが保証されているならば、安心してコードの破棄を行うことができるようになるはずです。

まとめ:「技術的負債」とは何だったのか?

さて、プロダクトから得る価値を最大化するためにコードを破棄するという観点からDRY原則、SOLID原則の一部を振り返ってみました。

「技術的負債」という言葉をここまで使いませんでしたが、この言葉をプロダクトから得る価値を最大化するためにコードを破棄するという観点から整理し直すと、

  • マーケット、ユーザーにとって価値のないコードそのもの(いずれ破棄しなければならないという債務を抱えている)
  • コードを破棄することを妨げるコード(負債への対応を先送りし、プロダクト全体の破綻を招くコード)
  • プロダクトの不確実性に耐えられない、脆い部分

が技術的負債である、ということが言えるのではないでしょうか。

常にそれは時間とのトレードオフになりますが、我々はプロダクトから学習しプロダクトを成長させ続けるために、技術的負債にいかに対応していくかということを考えていく必要があります。

参考文献

最後に

dely Advent Calendarは今年初めて行った試みですが、今日で25日分全て公開できました。(本来のアドベントカレンダーは24日分であることが多いと思うのですが、何故ブログは25日分書くことになっているのかは割と謎ですよね)

今までdelyの開発部はあまり情報を外に出していなかったのですが、今回は技術的な情報を外に出していくといういいきっかけになったと思います。今後も定期的に色々な情報を発信していきますので、dely engineering blog, dely design blogを引き続きよろしくお願いいたします。

最後に、師走で業務も忙しい中しっかり記事を書いてくれた開発部の皆さんに感謝を。どうもありがとうございました。