再読『プリンシプル オブ プログラミング』

はいどーもこんにちは。
本日は『プリンシプル オブ プログラミング』の感想記事を書いていきたいと思いますよ。
ちなみにこの本を読むのは二回目です。
前回読んだのは約半年前で、難しくてよくわからなかったという旨の感想記事を書いています。
今回は果たしてどうだったのでしょーか。

同意できる部分が格段に増えていた

前回読んだときは実務経験が皆無だったので、言っていることがいまいち理解できない部分が多々ありました。
ですが今回はある程度プログラミングの技術が向上したことと、チーム開発の経験も積んだことによって、書かれていることの大部分を理解できるようになっていました。
もちろんまだ書かれた通りに実践できるとは限りませんが、意識はできるようになりましたよ。
というわけで、特に気になった部分をそれぞれ見ていきます。
わりと長めの記事になっていますよ。

勝手に要件を加えない

KISSの項目で「コードに余計なことをしない」という節があります。
この中で「勝手に要件を判断し、余計なコードを加えてしまう場合があります」と書かれていました。
つまり依頼者が「Aという機能を作って欲しい」と言った時、判断不可能な箇所をプログラマが勝手に想像し、作ってしまうことですね。
これは僕も徹底して避けるようにしています。
なぜなら、作った機能が依頼者の求めていたものと違った場合、依頼者はもちろん、僕も機能を作り直すことになり、全てにおいて不幸しかないからです。
また、Aという機能を作ろうとしていたとき、よくよく聞いてみたら実はAという機能が不要だった、という場合も往々にしてあります。
勝手に用件を判断せず、きちんとコミュニケーションを取ろうとすることはプログラマの義務ではないかなと思いますよ。

コードの柔軟性

一度書いたコードを完成させても、そのコードは変更される可能性が非常に高いです。
であるならば、コードは変更に強くなければなりません。
例えばAという機能を付け加えたいだけなのに、その機能を付け加えるだけで既存のコードが動かなくなったり、壊れたりするのではお話になりません。
また、拡張性が低く、Aという機能を付け足すために、余計なコードを色々と付け足さなければならないのも不便です(かと言って使われもしない拡張ポイントを大量に用意してもコードの複雑性が上がるのですが……)。
あとこれは個人的な意見ですが、コード自体が柔軟性を持つことは当然として、機能を追加しても既存の機能が壊れていないことを保証するためのテストも必須かなと思いました。
コード自体に柔軟性があるだけでは、変更後のチェックが大変すぎて結局コードの変更をおこなわないか、不十分なテストのままコードをリリースすることになるからです。

変更の影響範囲を狭くする

クラスAの変更がクラスBとクラスCに影響する場合と、クラスAの変更がクラスAとクラスBにしか影響しない場合では、当然後者の方がコードの変更が容易でしょう。
これはクラス単位に限らず、本書で言うモジュール全般に言えることです。
変更の影響範囲はなるべく狭くしたいですね。僕も気をつけたいと思います。
ちなみにですが、特定のクラスに依存しているのはそもそもよろしくないので、インターフェイスに依存するように書きたいところですね。

カプセル化と情報隠蔽の違い

この項目はメモがてら引用しておきます。
・カプセル化

 関係ある要素を集めてモジュール化することです。
 関連の深いデータと関数を1箇所にまとめます。

・情報隠蔽

 モジュールの内部状態や内部関数を隠蔽することです。
 内部に対する外部からの直接アクセスを遮断します。

おそらくクラスという概念が初学者に難しいのは、この二つの機能が一緒くたにされている場合が多いからではないでしょうか。
僕の見たところですと、クラスという概念に触れたばかりの方は、カプセル化はできても情報隠蔽についてはあまり意識していないことが多い気がしました。
それはすなわち「クラス外に向かって無駄な情報を公開している」ことに他ならず、理解しにくく、使い勝手の悪いコードを書いていることを意味します。
最悪なのは、何の考えもなく「何となく便利だし、教本でもpublicなものがよく使われているから、publicなセッターを書いちゃおう」という場合じゃないでしょうか。
publicなセッターを持つプロパティは外部から容易に値を変更できるため、そのクラスが色々なクラスから利用されていたら、不具合が発生したとき原因の特定が困難になります。
というか、publicなセッターのせいで不具合が発生している可能性が高いと思いますw
必要がない限り、プロパティはなるべくゲッターのみ公開したいですね。

ポリシーと実装の分離

実装モジュールというのは、「特定のソフトウェアに依存しない「純粋」なモジュール」のことらしいです。
ポリシーという用語は他であまり見たことがないのですが、実装モジュールとは違って「そのソフトウェアに特化」していて、「そのソフトウェアに変更が生じ」た場合に変更を強いられるモジュールのことらしいです。
例えばUnityでよくObsoleteになっているメソッドがありますが、このObsoleteなメソッドを使用していたモジュールは「Unityに依存しており、Unityに変更があれば一緒に変更しなくてはならない」ポリシーモジュールと言えるでしょう。
逆にUnityに依存しておらず、Unityがどのように変化したとしても使い回せるモジュールは実装モジュールということになりますね。
本来は実装モジュールであるにもかかわらず、実装モジュールがポリシーモジュールの中に混ぜ込まれていた場合、当然のことながら実装モジュールはポリシーモジュールに引っぱられます。
それはよろしくないのでポリシーと実装を分離しましょうねーというお話でした。

インターフェイスと実装の分離

これは各所で口を酸っぱくして言われていることですね。
ですが、今までインターフェイスを使わずにプログラミングしてきた人だと、何が利点なのかいまいちピンと来ないと思います(実際僕がそうでした)。
なのでここではインターフェイスと実装の分離の具体例を挙げてみます。

現在僕が作成中のプロジェクトでは、広告の表示にAdMobというものを使っています。
詳細は省略しますが、「広告を表示できる機能がまとまったもの」の一つと思ってください。
そしてこの「広告を表示できる機能がまとまったもの」はAdMobだけではなく、例えば「Unity Ads」があります。
この二つに共通する動作に、例えば「広告を表示する」ということがあるとします(実際にあり得そうですよね)。
そこでインターフェイスの登場です。
たとえばIAdDisplayerというインターフェイスがShowAdという広告を表示するためのメソッドを持っているとします。
このIAdDsiplayerインターフェイスをAdMobの広告を表示させるクラスに実装させ、Unity Adsの広告を表示させるクラスにも実装させます。
こうすると、内部的にAdMobを使っていても、Unity Adsを使っていても、IAdDisplayerインターフェイスを介してShowAdメソッドを使用する限り、その内部の詳しい処理の違いを気にせず、全く同じようにShowAdメソッドを使用することができるのです。
これは「最初はAdMobの広告を使っていたけど、収益がかんばしくないのでUnity Adsに変えたい」と思ったときなんかに力を発揮します。
依存先はインターフェイスなので、ただ依存性を注入している箇所を変更すれば万事上手くいくわけですからね。
これが個別のクラスに依存していると、変更箇所が多岐に渡る場合が多いでしょう。
また、そのような設計だとメソッドの名前もぐちゃぐちゃで、ひと目では理解できないコードになっている可能性が高いんじゃないかなと思います(実際僕がそうでしたw)。

90パーセント解

完璧主義者が大変な人生を歩んでいることは、皆さんもよく耳にすることがあるんじゃないでしょうか。
プログラミングに限らず、何事においても完璧を目指すとろくなことがありません。
というわけで、100パーセントの解答を目指すのではなく、90%の解答を目指そう、残りの10パーセントはユーザーになんとかしてもらおう、というのがこの「90パーセント解」の考えらしいです。
たしかに完璧を目指していつまで経っても完成しないよりは、多少粗があってもひとまず完成させた方が価値がありそうですよね。
まあ細部にこだわることこそ職人だ! みたいな考えもあるので一概には言えないかもしれませんが……。

仲間に協力を求める

「自分の書いたコードを、積極的に仲間に見せて、改善点を指摘」してもらうことが本書では勧められています。
恐らくこれはプロの現場の同僚を想定して書かれているため、一人でプログラミングをやっている人はちょっと真似が難しいんじゃないかなと思います。
実際、僕もGitHubにコードをあげていますが、コメントは全く来ませんw
お互いに忌憚なくコードを批評できる関係の友人が欲しくなりますねえ。

関数側では、引数の調整を行わない

関数の引数に何らかの値を渡すことは普通です。
値を渡された関数は、その値を使って何らかの処理をおこないます。
このとき、渡された値がその関数にとって不正であった場合に「引数の調整を行わない」ことが本書では勧められています。
すなわち、例えば関数Aは正の数を受け取ることを想定しているが、実際に渡された値が負の数の場合に、それを勝手に正の数に調整してはいけない、ということです。
本書のこの節では「引数の調整を行わない」理由までは説明されていないのですが、おそらくは「想定外の値が渡されているのに、エラーを発生させないよう値を勝手に改変しているために、より重大なエラーが発生するか、より重大なエラーを見逃してしまう可能性がある」からといったところではないでしょうか。
実際、節全体を通して、想定していない値が渡されていないかどうかをアサーションでチェックしましょう、という旨のことが書かれています。
僕も今まであんまり意識できておらず、例えば0から100の値を引数に渡して欲しいとき、関数の側でClampメソッドで0から100に値を変えていたりしてました。
これから気をつけたいですね。

アサーションに渡す条件式には副作用を持たせない

これって当たり前と言えば当たり前なんですが、大事なのでメモしておきます。
理由は「アサーションは、コンパイルの方法によって、実行されたり、実行されなかったり」するからです。
つまり、よくわからん挙動の原因となる可能性があるためです。

エラー処理における「正当性」と「堅牢性」

エラーが起きたとき、さっさと落としてしまうか、それとも何とかして動作を続けさせるか、その方針を決める時の指標が「正当性」と「堅牢性」です。
正当性を重視する場合は、エラーが出たらさっさとアプリを落としてしまいます。
間違った操作を実行してしまうよりは、むしろ何もできないほうがいい、というアプリの場合はこちらの指針が合っていますね。
堅牢性を重視する場合は、エラーが出ても可能な限りアプリを実行し続けようとします。
これはゲームが当てはまるんじゃないでしょうか。
急にゲームが落ちるよりは、何かしら実行できたほうが救われる気がします。
というか、頻繁にエラーが出てかつ、エラーが出るたびに落ちるゲームとかやりたくないですw
ただエラーが出てもゲームを実行するとき、正常にゲームを続行できるかどうかがが大事ですよねえ……。
この辺は皆さんどうしてるんでしょうね。

人と人は交換不能

これは別にソフトウェア開発の領域に限定されないことでしょうが、プログラマが一人抜けたから、別のプログラマを補充すればそれでよし、というわけにはいきません。
人と人と言っても、個々人の能力には大きな差があるからです。
例えば本屋のバイトを考えてみてください。
一年そこで働いているAさんは、どこの棚に何の本が置いてあるかはだいたい把握できているでしょうが、Aさんが辞めたから別のBさんを雇ったからと言って、すぐにはAさんの代わりにならないことは容易に想像がつくと思います。
プログラマもこれと同じで、Aさんが辞めたから、Bさんを雇って埋め合わせる、というのは基本的には難しいはずです(と言うより専門技術に対する知識が必要な分、プログラマの方が程度が甚だしいかも)。
もっと具体的に言えば、僕じゃ書けないコードを書ける人が辞めたとき、代わりに僕を雇っても、全く役に立たないわけですw

ここからは余談ですが、会社としては「個々人に依存するのではなく、サウンドプログラマなどのインターフェイスに依存するようにする」のがいいんでしょうね(Adaptive Codeの感想記事か何かで似たこと書きましたが)。
個々人に依存しすぎるプロジェクトも社会には必要でしょうが、そうでないもの、そうするべきではないものの方が圧倒的に多いような気がします。

アーキテクチャは組織に従う

読んでてなるほどと思ったのがこの項目です。
確かにアーキテクチャは組織に従う傾向があると実感します。
ですが本書によれば、正解はアーキテクチャに組織を従わせることだそうです。
発展の項目にあった「組織とプロセス」の説明は、「じゃあどうすればいいの?」ということが抜けていて画竜点睛を欠く感が凄まじいのですが、まあこの辺はチーム開発の話なので、やはりすでにプロとしてコーディングしている人に向けて書かれているんでしょうね。
僕じゃあちょっとよくわかりませんでした。

セカンドシステム症候群

セカンドシステム症候群とは、要は「2番目のバージョンに機能を盛り込みすぎること」を指すようです。
これは僕にも思い当たる節があります。
仕事で作った某クラスはやっていることが多岐に渡っていて、何をするクラスなのか一言で説明するのが難しかったです。
実際、何度か「これ使い方がよくわからないんだけど……。これはどうすればできるの?」みたいな質問を受けていました。
これは明らかに機能を盛り込みすぎていた弊害です。盛り込みすぎた末に、全く使われない機能までありましたw
今ならもう少し分割して理解しやすいように作るかなあと思います。
そっちの方が何かと便利ですし、バグも少なくなります。また、仮にバグが出たとしても、影響範囲が狭ければ修正するのも容易です。

というわけで、機能を盛り込みすぎてよかったと感じたことが僕自身もほぼないため、今はなるべく機能を削るか、洗練するかの方針を取るようにしています。
もちろん必要な機能を追加する分にはかまわないと思いますけどね。

====ここから理解していない項目===
ここからはあんまり理解できなかった項目について書いています。

階層原理

この項目の趣旨としては「抽象レベルによって階層をわけましょうねー」という話なのでしょうが、後半にある「上位から見た時に、下位レベルは、「それを外部から見ている」ような視点で記述しましょう」の意味が判然としません。
ちょっとここは具体的なコードが欲しかったですね(本書はあえてコードを使用せず説明しているのですが、全ての項目において無理に排除する必要はあったのかなーとは思います)。
ピンと来ないということは、まだまだ勉強が足りないということなんでしょうねえ。

情報はデータに寄せて表現

表現性の原則によれば「コードにおける情報の表現方法は、ロジックでなくデータに寄せて書くようにします」とのことです。
いまいち理解できていませんが、例えばある情報Aを表現する場合、入り組んだif文をコード上で書くよりは、それに相当するデータAを作成しろ、ということなのでしょうか。
確かにif文のネストが深くなると視認性がそもそも悪くなりますが、データならば複雑性はそれなりに抑えられそうな気がします。
これも具体的なコードとデータ構造の例が欲しかったですねえ……。

ソフトウェア入出力の箴言

「受け入れるものについては「自由主義(リベラル)に、送るものについては「保守主義」に」という考え方があるらしいです。
この考え方を説明するのにHTMLの例が挙げられていたのですが、そのとき「動作の仕様」は寛容にしてもよいが、「仕様の解釈」はいけない、という旨のことが書かれていました。
ですがHTMLの歴史をよく知らない僕ではいまいちピンときませんでした。
色んな記述方法があったんですかね?
例えばリンクを表すアンカータグが<a>じゃなくて<an>もOKだったとか……?
HTMLに詳しくない読者のための説明がもう少し欲しいなあと思いました。

自己記述的なデータ形式

自己記述的なデータ形式だと「読み込むコードを混乱させずに新しい節を追加したり、古い節を取り除いたりできる」らしいのですが、この項目についてはまるっとわかりませんでしたw
一応ググってもみたのですが、まとまって綺麗に説明してくれているサイトはなさそうでした。
この辺は要勉強ですかねえ……。

情報的強度

凝集度についての説明で、情報的強度というものが紹介されていました。
これは「特定のデータ構造を扱う複数の機能を、1つのモジュールにまとめたものです」とのことです。
これに似た凝集度に論理的強度というものがあります。
2つの違いは、ズバリ入り口の数です。
例えば情報的強度は「広告」という情報を扱う際に

・ShowAd()
・HideAd()
・DestroyAd()

などのメソッドがあるかもしれません。
それぞれのメソッドは「広告」という情報に対する操作を行います。
これに対して論理的強度の入り口は一つのため、

・ManipulateAd(enum type)

などとして、引数typeで操作方法を示します。
どちらがよいかと言えば、それは「パット見ただけで何をするかわかりやすい」情報的強度の方でしょう。
ManipulateAdは操作したい種類が増えれば増えるほど、使用が難しくなります。
それに対して、情報的強度のメソッドは、数こそ増えれど、すでに存在しているメソッドの使用方法は変わらず、従って使用は平易なままに保たれます。
しかもこのtypeは、呼び出される側の内部を少し変更しただけで、呼び出す側にも影響を与える場合があります。
例えばtypeShowで「広告を表示する」という挙動をさせていたが、何らかの事情でtypeShowからtypeDisplayにenumの判定を変えた場合、呼び出す側のコードも全てtypeDisplayに変えなければなりません。
これは不便ですよね。だから止めた方がいい……という認識で読み進めたのですが、正直この項目はあっているかどうか自信がありませんw
恐らく考え方自体は間違っていないと思っているのですが、もう少し詳しい解説がほしいですね~。

リレーション

データベースの話がちょこっとだけ出てくるのですが、そこでリレーションについての説明があります。
僕はデータベースについて詳しくないので、ぶっちゃけこの項目はほとんど意味がわかいりませんでしたw
「なんかデータベースにも色々な形式があるのかな? それらの形式の中で、リレーショナルモデルを採用するならデータベースの直行性を保つ必要があるのかな?」
くらいの認識です。
そもそも例として挙げられていた「年度別にテーブルを分けてしまう設計だと、同じ値がないことを保証できない」すら意味がわかってませんw
データーベースもそのうちしっかり勉強しないとダメですかねえ。

クラス不変表明

説明が簡素すぎて正確には理解できなかったのですが、クラスAにHogeプロパティがあったとき、Bという条件を常に保証する。ただしメソッドの内部処理中は保証されない、的な感じですかね。
まあ前提となる条件が保証されなかったらそのクラスを安心して使えませんから、当然といえば当然ですかね……?
うーん、よくわかりません。

終わりに

久しぶりの記事ですし、書きたいこともたくさんあったので、今回の記事はだいぶん長くなりました。
偉そうに書いている部分もありますが、僕も本書の全ての項目を完全にパスしているどころか、むしろ理解すらできていない項目もまだあります。
しかしそれでも、前回読んだときのように「抽象的な話が多く、自分の浅いプログラミング経験では具体例がいまいちピンと思い浮かばないことが多々あった」というような感じではないです。
理解できない部分や具体例が思い浮かばない部分は1割か、それ以下でしょう。
今回はかなり成長を実感できたので、一年後あたり(つまり二年目)にまた読み直して、自分の成長具合をもう一度確かめたいですね。

あ、お仕事をくれる人も引き続き募集中です。
ほなそんな感じでまた。

フォローする