🎃

Chapter6 / A Philosophy of Software Design

2022/05/30に公開

Chapter6. General-Purpose Modules are Deeper

6.1 Make classes somewhat general-purpose

  • 新しい機能を作るとき、general-purpose(多目的) or special-purpose(特化)のどちらのアプローチでいくかという議論がある
  • general-purposeアプローチの場合、今の時点では重要ではない問題の解決までをカバーする必要がある
    • このアプローチは将来への投資の側面を含んでいる
    • しかし、ソフトウェア開発において将来のニーズを予見するのは難しいため、場合によっては不要なものを作ってしまう可能性がある
  • それに反して、special-purposeアプローチは現状の問題を解決することを重視する。
    • 新しい問題が出てきた場合はリファクタリングすることでgeneral-purposeに近づけていく
    • このアプローチはインクリメンタルなソフトウェア開発である
  • 著者が驚いたのは、general-purposeなインターフェースの方が、special-purposeよりシンプルで深いものになるということ
  • モジュールの機能は現在のニーズを満たすものにするべきだが、インターフェースはgeneral-purposeとして設計されるべき

6.2 Example: storing text for an editor

  • GUIのテキストエディタを作る例について考えてみる
    • そのテキストエディタではファイルの中身を表示していて、ユーザーがその中でカーソルを移動したり、クリックしたり、文字をタイプしたりして編集することができる
    • そのエディタでは複数のビューで同時にそのファイルの中身を表示でき、アンドゥ・リドゥ機能をサポートする
  • 多くの生徒たちはこのエディタのText classをspecial-purpose APIで実装する
    • 例えばbackspaceキーがタイプされたらカーソルの左側の文字を削除する
    • deleteキーがタイプされたらカーソルの右側の文字を削除する
class Text {
  void backspace(Cursor cursor);
  void delete(Cursor cursor);
}
  • さらにそのエディタはコピーや削除に使われるselectionもサポートする必要がある
void deleteSelection(Selection selection);
  • このようなspecial-purposeな実装は、ユーザーから見た機能と1対1になっているので、一見するとわかりやすく簡単なように見える。しかし実際は開発者に大きな認知負荷を与えている

6.3 A more general-purpose API

class Text {
  void insert(Position position, String newText);
  void delete(Position start, Position end);
}
  • general-purposeなアプローチで考えると、6.2で紹介したような実装よりシンプルな、上記のような2つのメソッドだけを実装するやり方が生まれる
    • backspace, delete, deleteSelectionのような「テキストを削除する」という機能が全て1つのメソッドにまとめられている
    • Cursor, Selectionという2つの概念がPositionという1つの概念にまとめられている
  • さらにカーソルの移動も以下のようなメソッドで表現できる
/*
 * 指定された位置に移動してPositionを返す。
 * @position 現在の位置
 * @numChars 現在の位置から何文字だけ移動するか。正の場合は右方向、負の場合は左方向に移動する
 */
Position changePosition(Position position, int numChars);
  • これらのメソッドを使うと、deleteキーがタイプされた時の挙動は以下のように実装することができる
text.delete(cursor, text.changePosition(cursor, 1));
  • backspaceキーの場合はこう
text.delete(cursor, text.changePosition(cursor, -1));
  • special-purposeに比べてgeneral-purposeでAPIを実装すると、それを使う側のコードは少し長くなってしまうが、新しいコードの方がより明瞭になっている。
    • どの文字が削除されたかを意識する必要があり、general-purposeの実装の方がそれがわかりやすい(と著者は言っている)
    • special-purposeな実装だとどの文字が削除されるのかは実装コードをみないとわからない

6.4 Generality leads to better information hiding

  • general-purpose(多目的)のアプローチはtextとUIのクラスを綺麗に分離することができて、結果的により良い情報の隠蔽を提供する。
    • 例えば「backspaceキーがどうハンドリングされるか?」というようなUIをTextクラスは気にしなくてよい
    • Textクラスに新しい関数を追加することなく、新しいUI関連の機能を追加することができる
  • general-purposeのアプローチは認知負荷も減らせる
    • UIを作る開発者はいくつかのシンプルなメソッドを知っているだけで良い

6.5  Questions to ask yourself

  • general-purposeなインターフェースを作るより、認識する方が簡単
  • general-purposeなインターフェースを設計するためには、自分自身に以下の質問をするといい
    • 現在の全てのニーズをカバーするシンプルなインターフェースとは?
    • 機能性・できることを減らさずにメソッドの数を減らすことができれば、よりgeneral-purposeなインタフェースを作っていることになる
  • special-purposeなText classは、テキストを削除するための3つのメソッド(backspace, delete, deleteSelection)を持っていた
    • どのぐらいのシーンでそのメソッドが使われるか?を考えてみる
      • backspaceメソッドはバックスペースを押した時しか呼ばれないのでtoo special-purposeである
  • ニーズに対してこのAPI(メソッド)は使いやすいか?
    • シンプルなAPIになっていても、それを呼び出すのに追加でたくさんコードを書く必要がある場合は危険信号
    • 例えば insertdeleteメソッドが1文字しか操作対象にならない場合、テキストエディタを開発したい場合は使いやすいとは言えないだろう
      • 選択された範囲を削除する場合は呼び出し側でループしないといけないため
      • また、この1文字単位のアプローチは大規模の挿入・削除には向いていない

6.6 Push specialization upwards and downwards!

  • たいていのソフトウェアは、ユーザーにとって特別な機能を提供するため必然的にspecialized(特化した)コードがある。
    • なので、特化したコードを全て削除することは大抵不可能
  • しかし、特化されたコードはgeneral-purposeなコードからは綺麗に分離されているべき
    • これは特化されたコードをそのソフトウェアスタックに押し込めることで実現される
    • 特定の機能を提供するアプリケーションのトップレベルのクラスでは、その機能のために必然的に特化することになる。
    • しかし、この特化することは下層のクラスに浸透する必要はない
  • Text editorの例
    • 学生の元のコードだとtext classにキーボードのbackspaceやdeleteキーを考慮させるインターフェースになっているが、改良されたtext classではbackspaceやdeleteキーを気にするようなインターフェースにはなっていない
  • デバイスドライバーの例
    • OSは数千個のデバイスをサポートしなくてはならない
    • それぞれのデバイスは大抵そのデバイス向けのコマンドセットを持っている
    • しかし、OSはそのようなデバイスに特化したコマンドセットの情報を隠すために、general-purposeなインターフェースを定義して、それぞれのデバイスドライバーがそれを実装するようになっている
      • ブロックの読み込み、ブロックの書き込み, etc...
    • このアプローチはデバイスドライバーの特化された部分を下層に押し込んでいる。
    • これを行うことでOSのコアな部分を開発するときに、デバイスドライバーに特化したことを考慮しなくてよくなる
    • さらにこのアプローチだと、OS側のコードを変更することなく、新しいデバイスを追加しやすくなる

Discussion