はじめに
このテクストはプログラマが知るべき97のことを読んでの感想文です。
本書は計97本のプログラミングに関する技術的な内容から精神的な内容まで、バラエティに富んだ数ページのテクストをまとめたエッセー集です。
今回は、その中から5つについて書いていこうと思います。なお、タイトルに付与した連番は書籍における掲載順とは関わりがなく、あくまで、本テクスト上で形式的に付与したものです。
1.技術的例外とビジネス的例外を明確に区別する
筆者の主張
-
技術的例外
通信エラーやデッドロック、存在していない変数へのアクセス、契約に反したメソッドの呼び出しなど、技術的な問題で発生した例外 -
ビジネス的例外
過去の日時で予約を取ろうとした、入力文字数が既定値を越えたなど、所謂仕様の問題で発生した例外
前者は技術的に復旧が困難であり、また、状況によっては復旧させるべきではないのに対して、後者は、技術的に解決可能であったとしても人為的に例外を発生させて処理を止めるべき内容である
筆者は、技術的例外については、その例外を捕捉して対応するべきではなく、トップレベルの例外処理機構に処理を委ねるべきであり、ビジネス的例外については、そもそも、その責務はクライアント側にあるとしたうえで、それでも発生した場合は例外を発生させ、その状況をクライアント側に通知して、クライアント側に処理を依頼するべきであるとしている。
技術的例外ついて、例外の処理をトップレベルの、言語やフレームワークの例外処理機構に任せるのは、トランザクションのロールバックやログの作成、システム管理者への警告や利用者への通知など、例外発生時に行われるべき、システムを安全な状態に戻す処理を行うためであるとしている。
以下、個人的な感想
一連の処理が外部に立っているDBとのやり取りを挟んでいるのなら、例外を捕捉して、その場で必要な処理を加えたうえで、再度、例外を投げる必要もあるだろうと思った。ただ、この時も、その場で基本的な処理、通知やログの作成など、を行うべきではなく、こういったある種のボイラープレートはトップレベルにお一度だけ定義されているほうが好ましいだろうとも思った。
2. シングルトンパターンの誘惑に負けない
筆者の主張
シングルトンパターンとは、
あるクラスに対してインスタンスが一つしか存在しないことを保証し、それにアクセスするためのグローバルな方法を提供する。
シングルトンパターンの主な問題点
- 「必要なインスタンスは一つだけ」という要望は、多くの場合推測に過ぎない
初期の設計時からの要件が変化しない、という想定には無理がある。要件は変化するもので、良い設計とはそれらに柔軟に対応できるものをいう。設計時から、一つしかインスタンスは生成されないとしていると、アプリケーションが潜在的な変更への弱さを持つことになってしまう。
- 理論的に独立しているはずのコード間に暗黙の依存関係を生んでしまう
これの大きな問題点は一見しただけでは、その依存関係に気付きにくい、というところ。問題がはっきりするのは、ユニットテストの作成が困難であるとわかったとき。
-
シングルトンパターンは永続化された状態を暗黙のうちに伴う
それぞれの処理が副作用を持ってしまうので、実行順序によって結果が変わってしまい、コードから状態の推測が困難になる。 -
シングルトンを明示的に殺すための機能はない
必要な時にプログラマがインスタンスのクリーンアップをしなければならず、複数のシングルトンがある場合には、クリーンアップの順序にも気を配らなければならなくなる
以下、個人的な感想
やはり、変数のスコープは適切な大きさでできるだけ小さいほうが好ましいわけで、グローバルに状態を共有するということは、状況をいたずらに複雑化しているように思う。
Fluxのように、変数へのアクセスを制限することもできるけど、制限するぐらいなら最初からアクセスできなくしておいた方が、長い目で見たときに安全だろう。
例えば、VuexやReduxのような状態管理をしてしまうと、コンポーネントの状態が、その前に行われた操作に依存してしまう。同じ入力ダイアログを開いているのに、一度目と二度目で挙動が変わるのは、それが仕様ではない限りただのバグだし、しかも、人間的に拾いにくい類のバグで、それを誘発してしまうのは良くない。もちろん、ライフサイクルに接続するフック関数で明示的にシングルトンをクリーンアップすることもできるが、ボイラープレートを増やすことになるし、そもそも、コンポーネント自体が状態を持てば、考えなくてもいい内容。
ただ、webアプリのフロントエンドにおけるユーザーのログイン情報のような、設計上、インスタンスが一つしか生成されないことを十分に想定できる内容なら、利用する意味があるだろうと思った。
3. 無駄な警告を排除する
筆者の主張
主張は簡単だ。警告は一つも残してはいけない
- あまりに多くの警告が出ていると、それらすべてを読むだけで大きなコストになる
- 一つ一つの警告について、それが意味のある警告か、無視してもいい警告かと考えるコストが無駄
- 多すぎる警告を前に、全てを無視するという行動を取り始め、警告が増えてゆく
そのため、警告は一つも残してはいけただ
ただ、コード自体を書き換えるだけが警告解消の方法ではない。ポリシーは公理でもなんでもなく、自分たちに見合ったものを作成するべきものであって、それは状況によっても変化する。
以下、個人的な感想
確かに、{}の前後にスペースが無いよ、程度のコーディング規約違反なら無視しても大きく影響はないと思う。ただ、割れ窓理論のことを考えると、これらを残しておくことも良くない。守らなくてもいいコーディング規約なら外してしまったほうがいい。
また、lintの使い方にはスタイルガイド以外にも、安全性の低い、バグりやすいコードの検出という側面もある。例えば、typescript-eslintのprefer-nullish-coalescingは論理和ではなくNull合体演算子の使用を促すことができ、予期せぬ挙動を回避することができる。
警告を無視できない環境を作ることで、バグりにくい、安全性の高い開発環境を作ることができる。
4. プリミティブ型よりドメイン固有の型を
筆者の主張
利点は以下の通り
- コードが読みやすくなる。ドメインの重要な概念がそのまま型名に使われるので、意味を理解しやすくなる
- 機能一つ一つがカプセル化され、独立するため、テストがしやすくなる
- 同じコードを複数のアプリケーションで再利用することが容易になる
以下、個人的な感想
二番目と三番目の利点はドメイン固有の型を定義するだけでは難しいのではないかと思ったが、ドメイン固有の型を定義することで、カプセル化については促進される可能性はあるのかな、と思った。
外部のアプリケーションが提供するデータに依存して動作する場合、例えば、TwitterのAPIを叩いて収集した投稿に対して何らかの操作をする場合、インターフェースをユーザー定義型で固めてしまえば、Twitter側の変更があったとしても、それによる自身のアプリケーションの影響範囲の推定が楽になる。静的型チェックのできる言語を使用しているなら、なおのこと楽だ。型を、変更に見合ったように書き換えて、エラーが出る箇所を見ていけばいい。
5. 誰にとっての「利便性」か
筆者の主張
これは、システム間だけではなく、クラスのメソッド等を含んだ、広義のAPI設計についての話。
「すでに似たようなAPIがあるのだから、別のものを作るべきではなく、既存のものを拡張するべきだ」
こういった意見はAPI設計時に善意のものとして出やすく、「そのほうが利便性が高い」という理由で採用されやすい。
しかし、こういった意見に従ってAPIを設計すると、困ったことが起こることがある。それはAPI自体が読みにくく、利用しにくいものになってしまう、ということだ。
例えば、Humanというクラスにwalkというメソッドが元々あったとして、Humanクラスに走るという動作をさせるという改修が入ったとする。
走るという動作と歩くという動作は、基本的には似たようなもので、その主だった違いは速さなのだから、walkというメソッドのオプショナルな引数にfastというbooleanを増設する。
class Human:
def walk(fast=false):
if (fast):
走る処理
else:
歩く処理
こうすれば、簡単に走るという動作をさせることはできるが、利用者が求めているのはrunという、端的なメソッドだ。(この例は少し手を加えているが、原文での例を参考にしている。早歩き、という動作をさせたくなったらどうするつもりなのだろうか)
そもそも、APIとは、複雑で面倒な処理を利用者に代わって行い、それをするための新たなボキャブラリを提供するものだ。
そのため、利用者に煩雑さを肩代わりしてもらうAPIの設計は良いものとは言えない。
「ほとんど同じ動作なのに、2種類の呼び出しを使うのは不便ではないか」という意見が出るかもしれない。
ただ、本当に不便なのは、利用者ではなく実装者だ。
- 利用者
誂えの、それに見合ったAPIが欲しい - 実装者
同じようなことを何度も書きたくない
確かに、冗長で、不整合な、美しくないものを作りたくない、という欲求は正しいが、それらの対偶にあるのは、効率や整合性、美しさであり、利便性ではない
では、どうしたらより利用しやすいAPI設計ができるか。
それには「意味のある問いを立てて、それに対する複数の回答を動詞で用意する」のが良い。
「部屋を片付けて静かに宿題をやりなさい」という意味を表すたった一つの動詞が存在しないように、複数の要素を一つのAPIに詰め込むべきではない。
以下、個人的な感想
加減の難しい話だなと思ったし、あくまでAPIに限定し、内部実装の共通化を図らないとすぐに収集がつかなくなるとも思った。
また、誂えの呼び出しが複数用意されている場合も、十分な抽象化がなされていないだけ、という可能性があって、むしろ良くない設計かもしれない。
例えば、赤く塗るというメソッドと青く塗るというメソッドが別々に用意されているより、色を塗るというメソッドに色を引数として渡せた方がいいかもしれない。
ただ、色の選択肢がとても限定的で、数多ある色から好きなものを選ぶというより、限られた選択肢から一つを採用するといった動きだった場合、それぞれの色を塗る専用のメソッドがあるほうがいいかもしれない。
ただし、透明に塗る、もしくは、白色に塗る、というメソッドの意味が今まで塗った色をなかったことにする、なのだとすると、これは良くない設計だろう。利用者は白く塗りたいわけではなく、やり直したいはずだ。
さらに、複数の処理を一度に行うAPIの呼び出しも、それが定期的に実行されるバッチ処理であったり、ある処理に付随して必ず行う処理なら、むしろ一括でまとめて呼び出せた方がいい。
例えば、目玉焼きを作る、という動作について、フライパンを出して油を引いて火をつける、なんて毎回書きたくない。
帰宅した息子に、「部屋を片付けて静かに宿題をやりなさい」と毎日言っているのなら、もはや、それは定期実行されるバッチ処理だ。
なので、あくまで骨子はAPI設計において、「そうしたほうが利用者にとって使いやすいかどうか」を考えることの優先度が、APIの美しさに勝るということなのだろう。
最後に
個々のエッセーはCC-by-3.0-USによりライセンスされており、プログラマが知るべき97のことで読むことができる。
そういった意図があってのことなのかは知らないが、知るべきことが割り切れない、というところがいいなと思った。