なぜフロントエンド開発にStorybookを導入するのか


0. はじめに

Storybookはiframeによる閉じた環境でComponentを実装できるツールです。主な用途として次の3つがあります。

  1. エンジニアとデザイナーが協働する環境として使う
  2. エンジニアがサンドボックス環境として使う
  3. 再利用できるComponentのカタログとして使う

いずれも強力な利点ですが、エンジニアリングの観点で最も重要なのは3番目の用途です。フロントエンド開発は自由度が高く、不用意に複数のエンジニアで開発を進めると、コードベースは簡単に秩序を失い技術的負債が蓄積されていきます。

Storybookを導入する主たる理由は、Storybookでカタログ化することにより、Componentの凝集度を高める動機づけになるからです。その結果、再利用できるComponentが量産されていきます。

この記事では、フロントエンド開発において容易に凝集度が低下する理由と、なぜStorybookの導入により凝集度と再利用性が向上するかについて述べます。

1. Componentを再利用しない場合

複数人で開発を進めている場合、よほど気をつけていない限り、類似の役割のComponentが乱立していきます(以後、類似Component)。例えば複数のページで同じようなモーダルを表示する要件がある場合、個々のエンジニアが各ページで独自にモーダル機能を実装してしまうようなケースです。

本来であればモーダルのような汎用的なComponentは、抽象化した高凝集なComponent(以後、共通Component)として実装し、各ページはこの共通Componentを利用するべきです。

類似Componentに対しテストケースを書いて堅牢性を担保する場合でも、複数の類似Componentに同じようなテストケースを書くより、一つの共通Componentに対し厚いテストケースを書いた方が、トータルでの開発効率は高く、結果的に共通Componentの堅牢性が向上します。

2. 再利用のための必要条件

再利用しやすい共通Componentを実装し管理するためには、次の2つの条件を満たす必要があります。

  1. 高凝集であること
  2. 一覧性があること

いずれもStorybookでアプローチ可能な条件です。順番に見ていきます。

2-1. 高凝集であること

またモーダルの例を用います。モーダルでメッセージを表示する要件があったとき、例えば次のような低凝集なコードが書かれてしまいます。

// 低凝集の実装例
const Modal = () => {
  return (
    <div className="modal">
      <div className="modal__header">Hello</div>
      <div className="modal__content">
        You've clicked button.
      </div>
      <div className="modal__footer">
        <button onClick={() => setOpened(false)}>Close</button>
      </div>
    </div>
  )
}

const [opened, setOpened] = useState(false)
const showModal = () => setOpened(true)

<button onClick={showModal}>Click!</button>
{ opened ? <Modal/> : null }

Modal ComponentはsetOpened関数の存在を仮定しているため、上位スコープに依存しており、凝集度は低い状態です。このように文脈依存の低凝集なComponentは再利用できず、テストケースの作成も容易ではありません。

またモーダル内に表示するタイトルやメッセージが固定のリテラルで書かれてしまっているのも、再利用できない要因です。

Modal Componentの凝集度を上げ、再利用性を考慮すると、次のような書き方ができます。

// 高凝集の実装例
const Modal = ({onClose, title, message}) => {
  return (
    <div className="modal">
      <div className="modal__header">{title}</div>
      <div className="modal__content">
        {message}
      </div>
      <div className="modal__footer">
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  )
}

const [opened, setOpened] = useState(false)
const showModal = () => setOpened(true)
const onClose = () => setOpened(false)

<button onClick={showModal}>Click!</button>
{ opened ?
  <Modal
    title="Hello"
    message="You've clicked button."
    onClose={onClose}
  /> : null
}

表示するタイトル、メッセージ、イベントハンドラを引数化し、上位スコープに依存しない構造に書き直しました。これにより再利用性が高まり、テストケースの作成も容易になります。

このModal Component(Modal定数)は独立したファイルとして括り出し、以後はあらゆる場所からimportし利用できます。

Storybookでカタログ化するためには、このように独立したファイルとして括り出しておくことが必要条件です。したがって、開発者はStorybookへのカタログ化を前提としたとき 、文脈に依存しない高凝集なComponent実装を行う動機が発生します

Component設計の巧拙は、通常は開発メンバーのスキルに強く依存します。そのため、コーディングガイドラインの制定やコードレビュー、ペアプロといった方法により、個人スキルの向上を図ってもらう以外に有効な方法がありませんでした。

しかしStorybookのカタログ化といったプロセスを通じて、一定の品質のComponent設計を促す良い意味での強制力が発生します。これは上のような属人スキルとは違った観点からのアプローチになります。

2-2. 一覧性があること

高凝集なComponent実装を心がけ、あらゆる場所から再利用できる状態に維持している場合でも、Storybookは重要な役割を果たします。

いくら再利用できるComponentを実装していても、Componentがコードベースのあちこちに点在し、他のエンジニアメンバーが存在を認知してくれなければ、類似Componentが作られてしまいます。

Storybookは散逸しがちなComponentを集約&カタログ化し、GUIで一覧できるツールです。これによりメンバーはComponentの存在を周知し合い、類似Componentの作成を抑制する効果が期待できます。

3. おわりに

Storybookは導入に一手間かかる他、有用性をメンバーに啓蒙できなければ形骸化してしまうツールです。メンバー全員が利用してくれなければ開発効率を最大化できないため、誰かが明確な意思を持って導入し推進していく必要があります。

しかし一定規模以上のチーム開発である場合、その努力に見合った価値のあるツールかと思います。

Twitter: @aki202