☹️

「カプセル化ってのがあって、すべてのプロパティにはprivateをつけるべきだよ」という盲目的な教えに対する違和感

2022/09/26に公開

「カプセル化っていうのがあって、データを外から見えなくすることが大事だから、すべてのプロパティにはprivateをつけるべきだよ。カプセル化はロジックを流出させないために大事だよ。」 という盲目的な教えが世間的な常識になりつつあり、違和感を感じる。だから個人的にカプセル化という言葉は使わないようにしている。

違和感の原因は2点。

  • カプセル化の文脈が複数あり、それをごったに取り扱っていること。ロジックの流出を防ぐことは同意だが、それをアクセス修飾子が必要な理由にはならない。さまざまな論点をごちゃ混ぜにしてしまっている。カプセル化という市民権のある言葉を、(自分の理解が浅いままに)相手を納得させる道具にしてしまっていないか?それはカプセル化という言葉を使わずに自分でメリットを説明できるのか?
  • アクセス修飾子をprivateにしてsetter/getterを大量に生やしてしまっている。意味のないコード。

カプセル化という言葉を使う文脈

メソッドの中身を見なくてもよいように、外から振る舞いが予想できるように作るべき。

これは、使い手側のメリット。もしくは将来の自分に対するメリット。

データに関する振る舞いをいろんな場所に散らばせてはいけない

これは、データの変更やテストの容易性を上げるためのプラクティス。作成者側が今後のメンテやコード変更する際の助けになる。

bad
function hogeService () {
  const someModel = new SomeModel();
  
  // このようにサービスレイヤーにsomePropertyAに関する振る舞いやロジックを直書きしてしまう良くない習慣。
  if(someModel.somePropertyA > 5) {
    someModel.doSomething();
  }
}
good
function hogeService () {
  const someModel = new SomeModel();
  
  // someModelのdoSomethingメソッドにsomePropertyAに関する振る舞いやロジックを閉じ込める。
  someModel.doSomething();
}

想定外の使われ方を防止するため、不必要な情報を外部から取得できないようにする

これは外部に公開するSDKやライブラリのようなものであれば確かにそうだと思う。しかし、内部のライブラリで、チーム内の者しか使わない場合、想定外の使われ方をされるのは、プログラマのレベルの問題だと思う。コピペプログラミングでバグが混入するように、意図を理解せずに、もしくは新しく作るのが面倒だから既にあるやつを使ってしまうことで、バグの原因になったり、メンテがしにくいコードを作ってしまう。

相対的にレベルの低いプログラマのために、なるべく「データに関する振る舞いをいろんな場所に散らばせてはいけない」プラクティスに沿わせるために、デフォルトでprivateにしておく。

このようなコードを書いてもらわないために、そもそもsomePropertyAにアクセスできないようにする対策。

bad
function hogeService () {
  const someModel = new SomeModel();
  
  // このようにサービスレイヤーにsomePropertyAに関する振る舞いやロジックを直書きしてしまう良くない習慣。
  if(someModel.somePropertyA > 5) {
    someModel.doSomething();
  }
}

しかし、この対策も結局、そのプログラマがsomePropertyAを取得できるようにgetterを書いてしまえば意味がない。

class SomeModel {
  private _somePropertyA;
  
  // TSのgetter
  get somePropertyA(){
    return this._somePropertyA;
  }
}

また、他にも、コーディング規約とかで、getterを生やすのを禁止にしたとしても、モデルのデータをDBに保存するときや、APIのリクエストでレスポンスとして返すときに、プロパティに直接アクセスしたくなる。

DBに保存するとき
function hogeService () {
  const someModel = new SomeModel();
  
  // このようにサービスレイヤーにsomePropertyAに関する振る舞いやロジックを直書きしてしまう良くない習慣。
  if(someModel.somePropertyA > 5) {
    someModel.doSomething();
  }
  
  save(someModel);
}

function save(model){
  const dbClient = new DbClient();
  
  dbClient.save({
    somePropertyA: model.somePropertyA, // .somePropertyAでアクセスしていることに注目
  })
}

class SomeModel {
  private _somePropertyA;
  
  // 結局getterを定義している。privateにしている意味とは?
  getter somePropertyA(){
    return this._somePropertyA;
  }
}

ここで必要に駆られてgetterを定義したら、目的である「相対的にレベルの低いプログラマのために、なるべく「データに関する振る舞いをいろんな場所に散らばせてはいけない」プラクティスに沿わせるために、デフォルトでprivateにしておく。」が達成できなくなる。

これを避けるためには、SomeModelにDBに渡す用のメソッドを定義する必要がある。

SomeModel.ts
class SomeModel {
  private _somePropertyA;
  
  passToDb(){
    return {
      somePropertyA: this.somePropertyA
    }
  }
}
DBに保存するとき
function hogeService () {
  const someModel = new SomeModel();
  
  // このようにサービスレイヤーにsomePropertyAに関する振る舞いやロジックを直書きしてしまう良くない習慣。
  if(someModel.somePropertyA > 5) {
    someModel.doSomething();
  }
  
  save(someModel);
}

function save(model){
  const dbClient = new DbClient();
  
  dbClient.save(model.passToDb())
}

デフォルトでプロパティをすべて非公開(すべてのプロパティをprivateにする)にしても、データを外部に渡すときにわざわざそれようのメソッドを定義しないといけない。

それなら、データを取得しやすいように、デフォルトでプロパティをすべて公開(すべてのプロパティをpublicにする)してしまってほうがいいのではないか?相対的にレベルの低いプログラマがロジックを流出しないようなコードを書くように教育すればよい。

結局はプロパティをデフォルトで公開すべきか、非公開にすべきかという問題

カプセル化の、ロジックを流出させないという論点は同意だが、アクセス修飾子をprivateにすべきというのは盲目的に同意はできない。アクセス修飾子をpublicにしてもロジックのカプセル化は実現できる。

  • プロパティはアクセス修飾子をすべてprivateにする(デフォルトで非公開にする)
  • プロパティはアクセス修飾子をすべてpublicにする(デフォルトで公開にする)

それに付随するメリットデメリットで判断すべきではないか?

Discussion