decadence

個人のメモ帳

事前条件の難しさ

オブジェクト指向入門 第2版 原則・コンセプト (IT Architect’Archive クラシックモダン・コンピューティング)
読み終えた
下巻も大学図書館使える内に借りて読む
オブジェクト指向入門 第2版 方法論・実践 (IT Architects’Archive CLASSIC MODER)
読み終えた

上巻について軽く触れる

前半のモジュールは開いていて閉じているべきとか、後半の継承についてとか面白かった
最近はScalaコンパイル遅いのに悩まされてるので、きちんとモジュール化頑張りたい
水平に分割したい所と垂直に分割したい所が交差する所の判断が難しい
色々な技術がどんなメリットがあるのかってのを明示しながら書いてくれるのが面白い
基本的にはScalaの機能を意識しながら読んでたんだけど、アンカーの話とか知らなかったしなるほど、ってなって良かった

下巻について軽く触れる

どういう時に継承をすれば良いのか、について逐一名前付けをしながら説明しているのが良い
さらに実践ともタイトルにあるように、どのようにしてクラスを見つけるのかといった事についても書かれていた
後は平行処理を行う上での設計についてなども記述があったが、他の点についてはありふれた知識も多くそこまで楽しくなかった
それにしても通してScalaに実装されてるものの説明が多くあったように思う*1
最後に本書を通して使っていた言語の名前を述べるのかっこいい

本題: 事前条件について

契約に必要な事前条件について悩ましく、結局は責務云々や状況によりけりといった結果になるであろうとか考えてる*2
該当するのは、11.7.4節「事前条件の設計:保護型か要求型か」について
要求型が事前条件を呼び出す顧客側に責任を割り当てるもので、保護型が供給者のルーチン内に事前条件に対する制御構造を書くもの
本の中でも、どちらが良いかは個人的な好みの問題だが、再利用性が重要視される場合には要求型がとられる、といったように書かれてる

適当にコードを書くとこんな感じ

  • 要求型
object HogeService {
  def exec(user: User, heavyInfo: HeavyInfo) = {
    user.use(heavyInfo)
    ...
  }
}
val heavyInfo: HeavyInfo = ...
users.filter(_.isFree).foreach(user => HogeService.exec(user, heavyInfo))

他の言語でもforの内側にifを書いて何かを実行することは良くあると思う

  • 保護型
object HogeService {
  def exec(user: User, heavyInfo: HeavyInfo) = {
    if (!user.isFree) {
      return
    }
    user.use(heavyInfo)
    ...
  }
}
val heavyInfo: HeavyInfo = ...
users.foreach(user => HogeService.exec(user, heavyInfo))

今回考えたいのは、事前条件が比較的明らかで、ある程度共通化出来る場合には保護型を使いたい
一方で、単一責務の原則に反するのではないかといった悩ましさと、余計な処理は行いたくないといった事について

条件の共通性に基づく保護型の利用

HogeService.execが様々な場所から呼ばれている場合、毎度呼び出す度に要求型の条件処理を書くのは非常に億劫であるし、保守性にも欠ける事が多い
事前条件がルーチンの中にある場合、その1点のみを変更する事で変更が終わる
要求型の場合、呼び出す箇所全ての条件を考える必要はあるし、それに対応するテストも全て変更しなくてはならないのが辛い所

関数名と責務について

問題の1つとして、sendとかnotifyといった関数名で、そのルーチン内で保護型の事前条件が行われているのは非常に分かりにくい事が挙げられる
どうにか明示する方法としてsendByUserStatusみたいな関数名にする事もありえる
こんな事を連ねていくと、やはり要求型を用いて綺麗な名前のままにする方が良いのではといった事も考える
ここには、1メソッドに単一責務を与えるべきといった事に反しているのでは、といった理由も存在する

heavyInfoを取得するのに重い処理が必要な場合

保護型な処理を書くと、見ての通り実際には使われない変数を束縛してしまう事が多い
Scalaの場合、call-by-nameを使うと上記保護型のコードを以下のように書き換える事が出来、結果として余計な処理が実行されなくて済む

object HogeService {
  def exec(user: User, heavyInfo: => HeavyInfo) = {
    if (!user.isFree) {
      return
    }
    user.use(heavyInfo)
    ...
  }
}
lazy val heavyInfo: HeavyInfo = ...
users.foreach(user => HogeService.exec(user, heavyInfo))

これは言語が機能として名前渡しや遅延評価を実装しているから書ける内容であり、このような機構がサポートされていない場合には、保護型を使うのを躊躇してしまう

事前条件

今まで何も考えずに出来ていたように思えていた事前条件が、考えだすと何も出来てなかったのではと思うようになった
保護型で書きたい場面もある一方、単一責務の事を考えると要求型の方が綺麗に思える
世の中そんな綺麗事ばかりじゃないんだとも考えつつ、結論が出ないまま今に至る

オブジェクト指向入門 第2版 原則・コンセプト (IT Architect’Archive クラシックモダン・コンピューティング)

オブジェクト指向入門 第2版 原則・コンセプト (IT Architect’Archive クラシックモダン・コンピューティング)

オブジェクト指向入門 第2版 方法論・実践 (IT Architects’Archive CLASSIC MODER)

オブジェクト指向入門 第2版 方法論・実践 (IT Architects’Archive CLASSIC MODER)

*1:逆にモダンなOOの知見を多いにScala側が取り込んだだけとも考えられるが

*2:何も分かってない