汎用な設計思考でプログラミングの世界から飛び出てみる
普段プログラムやソフトウェアプロダクトを作っていて、作るときの考え方が、単にプログラムだけでなく一般的設計思考、デザインシンキング的なものに通じるところが多いなと感じることが多いので、随筆または備忘録的にだらだらと書いておく。ほぼ自分のために、そしてプログラマ以外のために。
あ、これはPLAID Advent Calendarだった!こんにちは、@makinoyです。プレイドではCTOをやっています。PLAID Advent Calenderも今日が最終日となります、これまでお付き合いいただきありがとうございます、メリークリスマス!
では、はじめ。
とにかく動かしてみる、そして考えろ
頭の中で、もしくは机の上で、考えていること、と、それを実現してできたもの、実際に動かしてみたものには、乖離がある。それは、思考の限界と現実や環境の複雑さに、ギャップがあるからだ。物事を多面的に捉えるために、実際に動かしてみて考えよう。
そして一方矛盾しているようだが、あらかじめわかることはよく考えておくことが大事だ。動かしたからといって考えられるわけではない。結果がわかるだけだ。どういう考えで作ったかが大事だ。
限界まで考え、動かしてみる。動かしてみて、限界まで考えること。どちらが先でもよいが、繰り返すこと。
とすると、小さくはじめること、速く失敗して学習することが大事だ。やってみるまでの時間とコストを見積もることが重要だ。コストを見積もれないときには、やってみるのは控えた方がいいかもしれない。場合によっては大ダメージを受けるかもしれない。
実は、プログラマの仕事の大半が自分が作ったもののデバッグだ。考えて、作って、動かして、結果から考えを修正する速度が肝なのだ。
大きく考えて、捉われるな
なにか一つの物を作るときに、大きく、抽象的に解こうとしている課題を捉えよう。個別の問題だけを考えると前提や制約が多すぎるので、問題はより難しいし、問題を解いたときの効果は小さい。小さい課題から、そもそも何を実現しようとしているかを考えて、なるべく大きく問題を捉えよう。
そして、ときには暗黙の前提に捉われていることがある。例えば、覚醒が足りないとき、コーヒーを飲まなければいけないわけではない。コーヒーを飲むことがソリューションではないかもしれない。他でカフェインをとる方法はたくさんあるし、そもそも今寝るべきかもしれない。そういう風に考えると、選択肢の幅は広がるし、問題は大きく改善する。
小さい問題は溜められるだけ溜めておいて、重要なものから一気に解決しよう。一気に解決するときのために、問題はなるべく分割しておき、大きいレベルで解きに行こうとしたときに捉えられるようにしておこう。問題はなるべく分割し、解決策はなるべく一気にまとめて統合的に解く。
だだ、あまりに抽象化すると問題は難しくなるし、作るものは大掛かりになる。問題設定の抽象化レベルが大事だ。
“広く浅く”つくることを繰り返そう
いま考えていること、作ろうとしていることの全体感を捉えて、まず全体を広く浅く一通り作ろう。いま乗り物を作ろうとしているとしたら、まず、乗れて動くものを作る。そして、より大事なポイントを改善していこう。速さが大事なのか、乗り心地が大事なのか、を抑え、法律など他の満たすべき要件を徐々に考えていく。
Ref (Making sense of MVP Henrik Kniberg) : https://blog.crisp.se/2016/01/25/henrikkniberg/making-sense-of-mvp
なぜ”広さ”が必要なんだろう?人は、問題に気づくことは簡単で、小さいスコープのものを考えるのは得意だ。なので自然に思考すると、小さいところに捉われていきがちだからだ。おそらく。
ただし、一方で、実際に何かを細かく作る時は、今度は反対により小さいモジュールを堅く作ることが大事だ。特によく出てくるパターンを再利用可能なモジュールで補うことが重要だ。そしてできるなら、安定した外のモノを利用することが大事だ。
全体と部分を行ったり来たりして繰り返し考えて、全体の中で部分を考えることが大事だ。
不必要なものは外に出さない、なるべく閉じること
最適化の一歩目は、最適化しようとする物事が、他に依存してないことを確認し、変更の自由度をあげておくのだ。他の人が依存しているのに勝手に変わると困る。
自分の考えるもの、作るもの、外部依存性を最小にしておくことが運命の分かれ目だ。自分がコントロールできない世界を変えることは時間がかかる。また、外部に対して制約を多く設けられていると、選択肢の幅は狭まり、そもそも多くのことを考えるのは問題を難しくする。なるべく、インターフェースは小さく閉じておくことが大事だ。そうすることによって、変更の自由度と幅を確保しておこう。
これは、いろんなレベルで重要だ。小さいモジュールでもそうだし、最後、他人にものを提供する時もそうだ。
プログラミングの世界の話だが、テストしたいからといって、インターフェースを開けたくなったりすることがある。が、ぐっと我慢しよう。開けたインターフェースをテストも含めて、メンテし続けなければいけない。そして、そもそものモジュールの組み合わせ方を考えよう。不安を感じるなら、モジュールが大きすぎるかもしれない。もっと小さいモジュールが必要だ。おそらく。
動かしながら改善(リファクタリング)しよう
最小のインターフェースを保ちつつ、中身はダイナミックに変えることを繰り返そう。プログラミングの世界ではリファクタリングと言ったりする。そうすることによって、外の世界の動きを止めることなく改善を続けることができる。
稼働を止めないで変える方法を工夫することで、改善を自由にできるようになる。モジュールの中で、何かを導入して問題が起こったときに、前のやり方に戻すのも、止めないで改善しようとすることによって実現しやすくなる。
リファクタリングを可能にしておくことによって、使われ方を知った上での改善や、裏側のシステムを新しいものにアップデートして、時代の流れについていき続けることができる。
分離と統合のバランスをとる、またその繰り返し
なるべく閉じることが大事だ。簡単に実現する方法が、全体から分離して、隔離してしまうことだ。可能な限りモジュール化されていることで閉じることができる。
エンジニアリングの世界では、マイクロサービスと呼ばれる、小さくそれ自体で成立するサービスで全体を構成することが流行り、流行だ、ブームだ。基本的には、コンテナと呼ばれる、OSよりも上位で抽象化された軽量な基盤の成長によるものだ。
ただ、このとき分離の仕方が大事だ。一度、分離してしまうと、関心ごとが、分離されたモジュールにまたがってしまうと、解決策の選択肢の幅が狭まってしまう。大きく考えたとしても、複数の分離モジュールの面倒を見ないといけない。分離の仕方をどうするかを考えることは非常に重要であるし、一度分離したものを場合によっては統合することも考えることが大事だ。
少し引いてみると、システムは分離と統合を繰り返して進化している。おそらく。
考えてみれば、分けることは、まとめることと、表裏一体の関係だ。
レイヤーを重ね、レイヤーの厚みは薄くする
プログラムの世界では、ファイルは開いた人が閉じる、閉じられる前に、例外がおきたら捕捉する、ことが当たり前だ。開いたまま、誰かに渡してしまったら、正しく閉じられるかわからなくなってしまう。
レイヤーを構成して、下のレイヤーに処理を任せたら、上のレイヤーでその結果を受け取りその責任を持つと、責任がどこかにいってしまわないようにする。上から下のレイヤーに責任を伝搬させ、伝搬させた責任の状況を確認しよう。
仕事は、丸投げするな。丸投げすると、その仕事がどこでどうなったかわからなくなるし、例外が起こったときにどうなるか知るよしもない。
小さいモジュールの上に、レイヤーを重ねるように構成していくと、責任の所在が明らかになる設計ができる。
そして、レイヤーは可能な限り薄くする。分厚くて不必要に高機能で複雑なレイヤーは、理解しにくく、問題が発生しがちだ。必要最小限の厚みを持たせた方が良い。
ちなみに、そもそも高機能なこと自体はいいことでもなんでもない。
同期的処理は必要最小限にする、非同期で
同期的な処理とは、あるサブモジュールや他のシステムに任せた処理が、処理が終わることをまって、次の処理に進むような処理だ。同期的な処理の問題は、その処理が終わらないと次に進めないことだ。
他に渡した処理は、処理結果を待たずに次に進める。その結果に基づくタスクは処理が返ってきてからすすめるような、やり方や体制をとる。
会話は、同期的になりがちだ。小さいチーム内の会話は同期的に頻繁に行い、それ以外はあまり同期的に会話しないようにするのが良かったりすることもある。
ただ、とはいえ、あまりにも非同期で進めると、タスクが同時に大量に発生して、スケジューリングが難しくなったりすることがある。まさにNode.jsでよく起こる問題だ。
頻繁に発生すること、非同期にしても待ち時間がそれ以上有効活用されない場合は、同期的に処理するようにする。
怠惰であれ
必要なものは必要になるまで処理しないことが、プログラミング上有効なテクニックだ。あらかじめ必要なことを予測するのは難しく、大きく全体で見た場合に、必要でない処理は多いからだ。必要なときに必要な処理をするようになると、処理にまとまりができ最適化を測るチャンスが生まれるメリットがある。
必要になるまで、ぐっと我慢することが大事だ。意外と、実は必要がないケースが多い。
冬用のタイヤが要りそうだからといって、空いてる時期に替えても、全然使わなかったりする。本当に必要になってから替えよう。
ただ、あまりにも怠惰になってはいけない。必要になったときに、ものすごく時間がかかることがあるからだ。会議の時間になるまで、寝てたらいけない。必ず必要なことがわかっていることは、前もって準備しよう。
パフォーマンスチューニング とことん最適化するな
プログラミングの世界では、とにかく最適化しないことが非常に重要だとされている。
なぜか?基本的に、最適化は制約条件をうまく使うことで行うことが多い。最適化してしまうと、最適化時に使った制約条件によって自由度を削ってしまうことになる。下手すると、考えや、作っているものの、可能性や選択肢の幅を狭めてしまう。最適化は必ずしも善ではない。つまり、ある程度の無駄はそこそこ善だ。ただし、”ある程度”の。無駄が善とは言ってない。
チューニングを行う際には、可能な限り、明らかに無駄があるところを削るなどにとどめておいた方がよい。もしくは最適化したものを戻す、de-optimize可能な余地を残しておこう。
それでも、最適化が必要なときは、正しく計測てから、一番重要なものから取り掛かろう。大抵の場合、想定もしなかったところにボトルネックがあることが多く、的外れな最適化は害しかない。
わかりやすく作ること
コードを書くときに、実質的に、同じ処理をやってたとしてもリーダブルでアンダースタンダブルなコードであることが重要だ。ちなみに、同じセマンティクス(意味)である、と言ったりする。で、なぜ、わかりやすさが必要なのか?
一つは、他の人に対して、問題や解決策を共有することで、より良い解決策を出しやすくし、同じ視点でメンテナンスできるようにするため。もう一つは、自分で書いたコードを忘れてしまうためだ。
また、思考を自分から切り離し、外部化しておくことでゼロベースで問題を見直すことができる。
プログラマーは、機械に対して命令を書くことをイメージとしてもたれることがあるかもしれないが、実はそうではなく、人がわかるように手続きや関数を書いていることが多く、多くのプログラミング言語はそのためにあるのだ。
他人にわかりやすいことをする/モノを作ることで、他の人の創造性、ひいては自分の創造性を引き出すことができる。
プログラマが、わかりやすさのためにやっていることは、なんだろう?
ひとつは、わかりやすい名前を使い、一般的な知識にたとえることが大事だ。
そして、長々と書かないことが大事だ。この文章はまあまあ長くなっていないか。多分わかりやすくないんだろう…
またひとつ、パターンや法則にしたがって書くことが大事だ。途中で書き方のパターンや法則を変えないことが大事だ。変えたいときは一気に変えてしまおう。
ちなみに、わかりやすいとはどういうことか興味がある方は、「わかる」とは何かという難しい問題を脳/認知的な視点から書かれている本が、面白かったのでこちらを読んでみてほしい。https://www.amazon.co.jp/%E3%80%8C%E3%82%8F%E3%81%8B%E3%82%8B%E3%80%8D%E3%81%A8%E3%81%AF%E3%81%A9%E3%81%86%E3%81%84%E3%81%86%E3%81%93%E3%81%A8%E3%81%8B%E2%80%95%E8%AA%8D%E8%AD%98%E3%81%AE%E8%84%B3%E7%A7%91%E5%AD%A6-%E3%81%A1%E3%81%8F%E3%81%BE%E6%96%B0%E6%9B%B8-%E5%B1%B1%E9%B3%A5-%E9%87%8D/dp/4480059393
「神は細部に宿る」完全性 < きたなさ < 丁寧さ
とにかく、完全性を求めることは時間がかかるし、難しい。ある意味、汚さを許容し、スピードを優先させる。しかし、汚いことが良いわけではない。”神は細部に宿る”で、細かいところに対する丁寧さが品質にとって重要だ。必要以上に、完全さを求めず、汚さを許容した上で、集中力をもって丁寧に考える、ものを作ることが大事だ。
これも、もちろん問題による。正しさが求められる場所では、完全性を大事にしよう。ときには、それが正しいかどうか証明しよう。もしくは、二つの別の方法を使ってそれが正しいことをチェックすることが大事だ。
なにを割り振るか?時間ではなく、集中力と深い思考だ
どうやら、よくプログラマは、機械的にシステマチックに仕事しているように見えるようだ。実際にたまに機械のような人を見かける。が、実際は、プログラマーはプログラムをかけるので、プログラムでプログラムできない部分を、頭を働かせてプログラミングしているのだ。これまで述べてきたような設計思考的な考えや、色々な物事バランス、ライブラリやシステムの細かい知識、そしてユーザーのことを考えてプログラミングするのだ。
つまり、いいプログラムを書くためには深い思考が必要だ。そのためには、食事、睡眠、休息、ときにはバリに行くことが大事だ。
深い思考のためには、時間を管理するだけでは不十分だ。どのように集中力を発揮させるか集中力や注意をリソースとして考えることが大事だ。
これは、プログラマー以外の世界でもそのうち当たり前になる可能性があるかもしれない。デジタル化、データ化、自動化は、思ったよりも遅い速度で、しかしながら、着々と進んでいる。人が人として価値を発揮するには、人しかできないような領域で、深い思考を行うことが避けられなくなるかもしれない。
まとめ
せっかくなので、好きなことを書こうと自分にとっての文章をかいてみました。プログラマー以外にとってもそこそこ使える考え方ではないでしょうか?
これらの、設計思考を実際実行しようとする場合、周りにそれを理解してくれる人たちがいることが大事だと思います。もしそうでない場合、この設計思考を浸透させるだけでも骨が折れるし、なかなか理解されないことでしょう。仕事上では、こういう考え方を、理解してくれる同僚、リーダー、経営者がいるかどうかが重要です。あ、そういえば、こういう考え方で仕事ができるPLAIDという会社がありました。プログラマーだけでなく、興味を持たれた方は、ぜひご連絡ください。それでは!
参考文献
後ほどリストします