可読性・テスタビリティ・拡張性を高くif文を書く書き方


先日、ペアプロしている際に、if文を書く位置をどうするかに、そこにif文を書くのではなく、別の書き方をしたほうが可読性が高いよという話をしていたので、if文を書く位置について普段考えていることをまとめます。
まず、自分が考えているのは、基本的に以下の2点です。reactで具体例を書いていきます。

  • 可読性・テスタビリティ高くため、if文によって処理が分岐する箇所を最低限の場所になるようにする
  • if文の中に処理は基本的に書かずに、分岐後の処理の詳細はメソッドに切り出す

if文の場所が複数箇所にあり、可読性が低いbad例

export const Bad: React.FC<{ isAdmin: boolean }> = ({ isAdmin }) => {
  return (
    <div>
      {isAdmin && <p>adminにはこれを見せるよ</p>}
      {!isAdmin && <p>userにはこっち見せるよ</p>}
      <p>両方に見せるよ</p>
      {isAdmin && <p>adminだけこの情報も見せるよ</p>}
    </div>
  )
}
  • adminなのか、userなのかロールによる条件分岐をずっと気にしなければ、ならないため可読性が低い

if文がまとまっており、可読性が高いbetter例

こちらの例ではadminなのか、userなのかロールによる条件分岐を気にするタイミングが、最初だけであるため、気にするタイミングが少なく可読性が高いです。

export const Better: React.FC<{ isAdmin: boolean }> = ({ isAdmin }) => {
  if (isAdmin) {
    return (
      <>
        <p>adminにはこれを見せるよ</p>
        <p>両方に見せるよ</p>
        <p>adminだけこの情報も見せるよ</p>
      </>
    )
  }
  return (
    <>
      <p>userにはこっち見せるよ</p>
      <p>両方に見せるよ</p>
    </>
  )
}

出し分けロジック、表示内容の詳細で責務が別れており、拡張性が高いgood例

better例と基本的には同じですが、別コンポネに表示内容の詳細は切り出すことで、拡張性が高くなります。
また、AdminComponent単体でテストをしたり、storybookを作ったりしやすくもなります。

const AdminComponent = () => {
  return (
    <>
      <p>adminにはこれを見せるよ</p>
      <p>両方に見せるよ</p>
      <p>adminだけこの情報も見せるよ</p>
    </>
  )
}

const UserComponent = () => {
  return (
    <>
      <p>userにはこっち見せるよ</p>
      <p>両方に見せるよ</p>
    </>
  )
}

export const Good: React.FC<{ isAdmin: boolean }> = ({ isAdmin }) => {
  if (isAdmin) {
    return <AdminComponent />
  }
  return <UserComponent />
}
  • Goodコンポーネントが持つ責務が、出し分けだけになるため、表示の複雑度が上がった際に対応しやすく、各種表示のロジックの詳細を別コンポーネントに分離することができる

if文が一箇所にまとまるため、テストがしやすい

bad例でのtestを書くと例えばこのようになり、「adminにはこれを見せるよ」と「adminだけこの情報も見せるよ」の2つが表示されており、「userにはこっち見せるよ」が表示されてないことをテストする必要があります。

  it('Bad admin', () => {
    const { container, asFragment } = render(<Bad isAdmin />)
    expect(container.textContent).toContain('adminにはこれを見せるよ')
    expect(container.textContent).toContain('adminだけこの情報も見せるよ')
    expect(container.textContent).not.toContain('userにはこっち見せるよ')
    expect(asFragment()).toMatchSnapshot()
  })

betterでのtestでは、「adminにはこれを見せるよ」が表示されており、「userにはこっち見せるよ」が表示されていないことをテストできれば、表示の切り分けが十分にテストできてるので、テストが簡単になります。
goodのコンポーネントも分けた状態も同様です。
また、場合によっては、good例の場合は、個別のコンポネはshallow mount(この例では、react testing libraryを使用してるためshallow mountはありませんが)したり、mockしたほうがテストは簡単にかけるかもしれません。

  it('Better admin', () => {
    const { container, asFragment } = render(<Better isAdmin />)
    expect(container.textContent).toContain('adminにはこれを見せるよ')
    expect(container.textContent).not.toContain('userにはこっち見せるよ')
    expect(asFragment()).toMatchSnapshot()
  })

今回は、reactを例に書いていますが、vueはもちろん、サーバーサイドの処理でも同様の考え方でif文の処理が分岐していくようにすると可読性が高く・テスタブルな処理がかけます。
条件分岐が複雑になりそうな処理を書く際は、この2つを意識してみてください。
- 可読性・テスタビリティ高くため、if文によって処理が分岐する箇所を最低限の場所になるようにする
- if文の中に処理は基本的に書かずに、分岐後の処理の詳細はメソッドに切り出す

また、例に使用しているコードの全体はこちらにも上げています。
https://github.com/YasushiKobayashi/samples/blob/master/src/react-if-sample/src/Main.spec.tsx
https://github.com/YasushiKobayashi/samples/blob/master/src/react-if-sample/src/Main.tsx