論理的凝集のサンプルを考えてみた

凝集度の論理的凝集の例

以下の getProductDetail は商品の名前と価格を返す関数です。

apple: 100

const getProductDetail = (product) => {
  const name = product.name
  const price = product.price
  return `${name}: ${price}`
}

const product = {id:1, name: 'apple', price: 100}
const detailEl = document.getElementById('detail')
detailEl.innerText = getProductDetail(product)

人気商品かどうかを表示する追加機能が必要になった

商品の詳細を出す時に、購買履歴が5回以上あれば以下のように「(人気)」と表示する機能が必要になりました。

apple: 100 (人気)

ただし、表示したいケースと表示したくないケースがあるので制御する必要があります。

これまでの getProductDetail 関数に新たに isPopular というフラグを追加して、true だったら「(人気)」と表示するかの処理が実行されるようにしました。

const getProductDetail = (product, isPopular = false) => {
  const name = product.name
  const price = product.price
  
  if(isPopular){
  	const history = getHistory(product) // getHistory はすでに定義されているとします。
    return history.length > 5 
    	? `${name}: ${price}(人気)` 
      : `${name}: ${price}`
  }
  
  return `${name}: ${price}`
}

const product = {id:1, name: 'apple', price: 100}
const detailEl = document.getElementById('detail')
detailEl.innerText = getProductDetail(product, true)

一見問題なさそうな気がしますが、これが論理的凝集になります。

getProductDetail は「商品の詳細を表示」と「人気かどうか表示」の二つの責務をもってしまい、フラグによって制御されています。

「商品の詳細を表示」と「人気かどうか表示」が密に関連してしまっているため、例えば「人気かどうかだけ取得したい」という場合に対応できません。(if文のところが重複したコードになってしまいます。)

また、「人気」という文字列ではなく、ある条件では別の文字列を表示したいなどの変更があった場合、getProductDetail にはさらなる引数が必要になり、ますます複雑化します。

「いつかどこかでリファクタリングしよう」と思っていても、ほとんどのプロジェクトではそのような余裕はありません。

そして、割れ窓理論によって「getProductDetail にはいろいろ機能が詰め込まれているから、新たな機能が必要になったときもこの関数に追加すればいいのだな」という感覚で、どんどん無法地帯になり、最終的には保守性が低いシステムが完成します。

改善する

人気かどうかの表示の部分を getPopular という別の関数にして、getProductDetail と分けます。

const getPopular = (product) => {
  const history = getHistory(product)
  return history.length > 5 ? '(人気)' : ''
}

const getProductDetail = (product) => {
  const name = product.name
  const price = product.price 
  return `${name}: ${price}`
}

const product = {id:1, name: 'apple', price: 100}
const detailEl = document.getElementById('detail')
detailEl.innerText = getProductDetail(product) + getPopular(product)

このようにすることで、getPopular だけを呼び出すことが可能になり、先ほど挙げたような「ある条件では(人気)以外を表示したい」変更の場合も getPopular だけを変更すれば良いため、影響範囲を把握しやすく変更に強くなります。

これがベストの形というわけではなく、システムの変更に合わせて常にリファクタリングしていくことが重要です。