[React] 実務でのReact Clean Code

2026-02-14 hit count image

TossのSLASH 21カンファレンスで発表されたReact Clean Codeの核心概念である凝集度、単一責任、抽象化を実際のコード例と共に整理して共有します。

react

概要

Tossという韓国のFinTech会社でSLASHというカンファレンスで発表された動画の内容が良かったので、共有します。

Tossは韓国のFinTechスタートアップ会社で、現在は上場して韓国人開発者が行きたい会社の上位にある会社です。

Clean Codeというと、分かりやすい名前、重複を減らすことなどを考えますが、実務ではもっと読みやすいコードのためのスキルが必要です。

ここではフロントエンドで読みやすいコードを書くための概念とアクションアイテムを共有します。

例題コードはReactです。実際動くコードではないので、概念の把握に重点を置くと良いかと思います。

実務でのClean Codeの定義

開発者の自己満足以外に、実務でClean Codeをする意味は何がありますか?

そのコードは触らない方がいいと思います。

一旦私が修正します。

実務で、このようなセリフを聞いたことがあると思います。大体の会社ではこのような地雷コードを持っています。

このセリフには下記のような内容が含まれてます。

  1. コードの流れの意味の把握が難しい
  2. ドメイン文脈がうまく表現できてない
  3. 他のメンバーに聞かないと分からないコード

この地雷コードは開発する時、ボトルネックになるし、メインテナンスする時、コードの把握のため、時間が長くかかります。

最悪の場合は、機能追加ができなくなることもあります。

また、このようなコードは性能も悪いので、ユーザの立場でも良くない経験をさせる場合が多いです。

実務でのClean Codeの意味 = メインテナンス時間の短縮

実務でClean Codeはメインテナンスの時間の短縮を意味します。

他のメンバーまたは過去の自分が書いたコードを早く理解できると、メインテナンスをする時の開発時間が短くなります。

維持補修時間の短縮 = コードの把握、デバッグ、レビューの時間短縮

読みやすい綺麗なコードはコードレビューの時間も、バグが発生した時のデバッグの時間も短縮してくれます。

時間=リソース=金

時間はリソースで、リソースはお金と同じです。直すのに1日かかるコードと3日かかるコードがある場合、簡単に計算して3日かかるコードは開発者、または開発者の努力が3倍必要です。つまり、開発者である我々が3倍もっと仕事をしなきゃならないです。

コード追加の罠

私たちはプロの開発者ですので、最初コードを設計して作る時は、とても綺麗なコードを書けると思います。

しかし、既存のコードに機能を追加する状況では、ちょっと話が違います。ちょっとでも緊張を逃したら、コードが悪くなります。

私たちの仕事の90%が既存のコードに機能を追加することです。他のメンバーが書いたコード、先週自分が書いたコードに機能を入れます。

プレゼンターが実際受けた仕事は以下の通りです。

保険に関して質問を入力するページがありましたが、ユーザの保険担当者がある場合、その担当者の写真が入ったポップアップを表示して欲しいという機能追加です。

設計と実装

既存のコードは下記のようです。

function QuestionPage() {
  async function handleQuestionSubmit() {
    // 利用規約確認
    const agree = await getTargetElement();
    if (!agree) {
      // 利用規約が必要な場合、ポップアップを表示
      await openAgreePopup();
    }
    // 利用規約を同意したユーザの場合、質問を登録して、成功メッセージを表示
    await sendQuestion(questionValue);
    alert('質問が登録されました。');
  }

  return (
    <Main>
      <form>
        <textarea placeholder="質問内容を入力してください" />
        <button onClick={handleQuestionSubmit}>質問を登録</button>
      </form>
    </Main>
  );
}

このコードで新しい機能の追加はどうすれば良いでしょうか?簡単に下記のように追加すればいいと思います。

【設計】

function QuestionPage() {
  async function handleQuestionSubmit() {
    /*
     * 【保険担当者がある場合をチェックして、
     *   担当者がある場合、ポップアップを表示するロジックを追加】
     */
    // 利用規約確認
    const agree = await getTargetElement();
    if (!agree) {
      // 利用規約が必要な場合、ポップアップを表示
      await openAgreePopup();
    }
    // 利用規約を同意したユーザの場合、質問を登録して、成功メッセージを表示
    await sendQuestion(questionValue);
    alert('質問が登録されました。');
  }

  return (
    <Main>
      <form>
        <textarea placeholder="質問内容を入力してください" />
        <button onClick={handleQuestionSubmit}>質問を登録</button>
        /* * 【ポップアップのコンポーネント追加。最初は非表示で、必要な場合表示するようにする】 */
      </form>
    </Main>
  );
}

【開発】

function QuestionPage() {
  const [popupOpened, setPopupOpend] = useState(false); // ポップアップの状態

  async function handleQuestionSubmit() {
    // 担当者がいる場合、ポップアップを表示
    const myExpert = await getMyExpert();
    if (myExpert !== null) {
      setPopupOpened(true);
    } else {
      // 利用規約確認
      const agree = await getTargetElement();
      if (!agree) {
        // 利用規約が必要な場合、ポップアップを表示
        await openAgreePopup();
      }
      // 利用規約を同意したユーザの場合、質問を登録して、成功メッセージを表示
      await sendQuestion(questionValue);
      alert('質問が登録されました。');
    }
  }

  async function handleMyExpertQuestionSubmit() {
    await sendQuestionToMyExpert(questionValue, expert.id);
    alert(`${myExpert.name}に質問をしました。`);
  }

  return (
    <Main>
      <form>
        <textarea placeholder="質問内容を入力してください" />
        <button onClick={handleQuestionSubmit}>質問を登録</button>
        {popupOpened && (
          <MyExpertPopup onSubmit={handleMyExpertQuestionSubmit} />
        )}
      </form>
    </Main>
  );
}

設計と同じように開発をします。

ポップアップが表示されたかどうかを保存するStateを追加して、既存のクリック関数に保険担当者があるかどうかを確認するif文を追加しました。

保険担当者ポップアップで確認ボタンを押した時、呼ばれる関数を作って、ポップアップコンポーネントも追加しました。

問題点

これは正しい、当たり前なコード追加に見えますが、実は悪いコードになりました。

  1. 一つの目的のコードがあちこちあります。 下記のコードは保険担当者とポップアップと関連するコードですが、このコードが離れてあるので、後で機能追加する時、スクロールしながらあちこち確認する必要があります。

    1. popupOpenedのState
    2. 担当者チェック、ポップアップ表示ロジック
    3. 担当者にデータを送る関数
    4. 担当者ポップアップコンポーネント
  2. 1つの関数が複数の役割をしています。 既存の関数が3個の役割をしています。関数の中身を全部読まないと関数の役割を把握することができないです。なので、コードの追加や削除する時も、もっと時間がかかることになります。

  3. 関数の詳細実装の階層が違います。 handleQuestionSubmit関数とhandleMyExpertQuestionSubmit関数はイベントハンドリング関数です。名前はhandleQuestionSubmithandleMyExpertQuestionSubmitで似てますが、handleQuestionSubmit関数は質問を登録すること以外にも色んなことをやっていますので、コードを全般的に見ないと分かりづらいです。名前だけ見てコードの内容が予測できなくなったので、全部見なきゃならない、または名前だけ見て誤って理解する可能性が出ます。

最初のコードは良かったので、たった1つの機能追加で分かりづらいコードになりました。

ここでの罠はPRで見ると、これが読みづらいコードだと認識することが難しいことです。

その理由は変更点だけ見ると、悪いコードではないように見えるためです。しかし、全般的に見るとメチャクチャです。

Afterのコードはプレゼンターが実際最初PRを出したコードです。

解決(リファクタリング)

以下のようなリファクタリングを通じて問題を解決できます。

  1. 関数の詳細実装の階層を統一しました。 既存の関数名をhandleQuestionSubmitからhandleNewExpertQuestionSubmitに変更して、handleMyExpertQuestionSubmit関数を追加して、関数の階層を統一しました。
    1. handleNewExpertQuestionSubmitにはユーザが新しい担当者に送るロジックだけ入れました。
    2. handleMyExpertQuestionSubmitにはすでに連結された担当者に送るロジックだけ入れました。
function QuestionPage() {
  const myExpert = useFetchMyExpert();

  async function handleNewExpertQuestionSubmit() {
    await sendQuestion(questionValue);
    alert("質問が登録されました。");
  }

  async function handleMyExpertQuestionSubmit() {
    await sendQuestionToMyExpert(questionValue, expert.id);
    alert(`${myExpert.name}に質問をしました。`);
  }
  1. ポップアップ関連コードを1つにまとめました。

    • 前のコードはポップアップを開くボタンとポップアップコードが離れてましたが、これをまとめてPopupTriggerButtonというコンポーネントを作りました。
  2. 1つの関数が1つの役割をするように分けました。

    • 利用規約同意関数をopenPopupToNotAgreeUsersという名前で関数を作って、必要な時、呼ぶようにしました。
function QuestionPage() {
  const myExpert = useFetchMyExpert();

  async function handleNewExpertQuestionSubmit() {
    await sendQuestion(questionValue);
    alert('質問が登録されました。');
  }

  async function handleMyExpertQuestionSubmit() {
    await sendQuestionToMyExpert(questionValue, expert.id);
    alert(`${myExpert.name}に質問をしました。`);
  }

  async function openPopupToNotAgreeUsers() {
    // 利用規約確認
    const agree = await getTargetElement();
    if (!agree) {
      // 利用規約が必要な場合、ポップアップを表示
      await openAgreePopup();
    }
  }

  return (
    <Main>
      <form>
        <textarea placeholder="質問内容を入力してください" />
        <button onClick={handleQuestionSubmit}>質問を登録</button>
        {myExpert.connected ? (
          <PopupTriggerButton
            popup={<MyExpertPopup onSubmit={handleMyExpertQuestionSubmit} />}
          >
            質問する
          </PopupTriggerButton>
        ) : (
          <Button
            onClick={async () => {
              await openPopupToNotAgreeUsers();
              await handleNewExpertQuestionSubmit();
            }}
          >
            質問する
          </Button>
        )}
      </form>
    </Main>
  );
}

実務でのClean Codeとは?

コードが最初のバージョンより長くなりました。

Clean Code != 短いコード

実務でのClean Codeは短いコードではなく、欲しいロジックを早く探せるコードです。

Clean Code == 欲しいロジックを早く探せるコード

ロジックを早く探せるコード

欲しいロジックを早く探せるようにするため、1つの目的を持つコードがあちこちある場合は凝集度をあげてまとめる必要があり、関数が色んな役割をする場合は、単一責任原則で関数を分けなきゃならないです。

そして、関数の詳細実装の階層が違う時は抽象化段階を調節して、重要概念を必要なだけ外に見せるようにする必要があります。

ロジックを早く探せるコード

  • 1つの目的を持つコードがばら撒いてる → 凝集度(Cohesion)
  • 関数が複数の役割をしてる → 単一責任(Single Responsibility)
  • 関数の詳細実装の階層がバラバラ → 抽象化(Abstraction)

実際のコードを見ながら1つずつ説明します。

凝集度(Cohesion)

同じ目的のコードはまとめてあげます。

実際のコードです。ポップアップを操作するコードが3箇所に分かれています。

function QuestionPage() {
  const [popupOpened, setPopupOpened] = useState(false);

  async function handleClick() {
    setPopupOpened(true);
  }

  function handlePopupSubmit() {
    await sendQuestionToMyExpert(questionValue, expert.id);
    alert(`${myExpert.name}に質問をしました。`);
  }

  return (
    <>
      <button onClick={handleClick}>質問する</button>
      <Popup title="保険質問する" open={popupOpened}>
        <div>担当者が説明します</div>
        <button onClick={handlePopupSubmit}>確認</button>
      </Popup>
    </>
  )
}

コードの把握も一目でできないし、バグ発生危険性も高いコードです。

Refactor V1

Custom Hooksを使って1つのところにまとめました。

function QuestionPage() {
  const [openPopup] = useMyExpertPopup(myExpert.id);

  async function handleClick() {
    openPopup();
  }

  return <button onClick={handleClick}>質問する</button>;
}

これで、openPopup関数を呼ぶことでポップアップを開くことができます。

しかし、落ち着いてみるとこのコードは読みづらいコードになりました。

どういうポップアップが表示されるのか、ポップアップのボタンを押した時、どういうアクションをするのかがこのページで一番重要なポイントですが、これが全部hooksに隠れて分からないようになりました。

これはCustom Hooksの代表的なアンチパターンです。コードを見て汚いと思ったら、一旦hooksで全部まとめることです。

そしたら何をまとめるべきでしょうか?

今すぐ分からなくてもいい詳細です。これを隠しとくと、短いコードだけ見ても早くコードの目的を把握することができます。

逆にまとめると見づらくなるものは、コードを把握するため必須な情報です。これを分けておくと、色んなモジュールを行ったり来たりしながら、コードの流れを確認する状態になります。

Clean Code != 短いコード

コードをまとめて短いコードを作ることで、コードがClean Codeになることではないです。

Clean Codeは探したいロジックを早く探せるコードです。

どうすれば、読みやすいように凝集できるんでしょうか?

コード凝集Tip:コアデータと詳細実装を分ける

まず、残すべきコアデータと隠してもいい詳細実装を分けてみましょう。

このコードでコアデータはポップアップボタンをクリックした時、実行するアクションとポップアップのタイトルと内容です。

function QuestionPage() {
  /*
   * 詳細実装:ポップアップボタンクリックした時のアクション
   */
  const [popupOpened, setPopupOpened] = useState(false);

  async function handleClick() {
    setPopupOpened(true);
  }

  function handlePopupSubmit() {
    /*
     * コアデータ:ポップアップボタンクリックした時のアクション
     */
    await sendQuestionToMyExpert(questionValue, expert.id);
    alert(`${myExpert.name}に質問をしました。`);
  }

  return (
    <>
      <button onClick={handleClick}>質問する</button>
      /*
       * コアデータ:タイトル、内容
       * 詳細実装:コンポーネントのMarkupとボタンをクリックしたら関数をコールする
       */
      <Popup title="保険質問する" open={popupOpened}>
        <div>担当者が説明します</div>
        <button onClick={handlePopupSubmit}>確認</button>
      </Popup>
    </>
  )
}

詳細実装はポップアップを開いたり閉じたりするStateとコンポーネントの細かいMarkup、そしてポップアップのボタンをクリックした時、特定関数を呼ぶようにするバインディングです。

Refactor V2

ここでコアデータを残して詳細実装は隠すと、把握しやすいコードになります。

openPopupというCustom Hooksに全てのコードを隠すことではなく、詳細実装だけ隠してコアデータであるポップアップのタイトル、内容、アクションは外に残します。

function QuestionPage() {
  const [openPopup] = usePopup();

  async function handleClick() {
    const confirmed = await openPopup({
      /*
       * ポップアップのタイトル、内容
       */
      title: '保険質問する',
      contents: <div>担当者が説明します</div>,
    });

    if (confirmed) {
      /*
       * ポップアップのアクション
       */
      await submitQuestion();
    }
  }

  async function submitQuestion(myExpert) {
    await sendQuestionToMyExpert(questionValue, expert.id);
    alert(`${myExpert.name}に質問をしました。`);
  }

  return <button onClick={handleClick}>質問する</button>;
}

そしたら、詳細実装を読まなくても、どういうポップアップであるのか把握できます。

宣言型プログラミング

このような開発スタイル、つまり

ポップアップ、あなたに宣言する!

タイトルは"保険質問する"

内容は"専門家が説明します。"

"そして確認ボタンをクリックしたら、質問を送信して!"

と宣言すると、ポップアップが事前に実装してあった詳細実装で、その内容を表示するスタイル、これを宣言型プログラミングと言います。

宣言型プログラミングの特徴は、関数が何をするのかが早く理解できることです。

詳細実装は隠して気にする必要がないこと、この”何(What)“を変えて簡単に再利用ができるところです。

<Popup onSubmit={質問送信} onSuccess={ホームに移動} />

宣言型でまとめなくて、1つ1つ詳細実装を作成する方法は命令型プログラミングと言います。

<Popup>
  <button
    onClick={async () => {
      const res = await 会員登録();
      if (res.success) {
        プロフィール移動();
      }
    }}
  >
    送信
  </button>
</Popup>

宣言型プログラミングも中を見るとこのように命令型で作成されてます。詳細実装が全て公開されてるので、これをカスタマイズすることは簡単ですが、これを読むのは時間がかかるし、再利用することが難しいです。

Q:宣言型プログラミングが絶対いいですか? A:いいえ。二つの方法を活用して作成すればいいです。

宣言型プログラミングが命令型プログラミングよりトレンドだから、もっといいのか?と考える人もいるかもしれないですが、それぞれいいところがあります。

ReactのJSX文法ではHTMLでも宣言型プログラミングをすることができる利点がありますが、propsで”何(What)“を渡すこと、このような詳細内容を渡す場合は命令型の設計も必要です。

単一責任

1つのことをするのが分かるようなはっきりした名前を持つ関数を作る必要があります。

次のようなイベントハンドラ関数を作ってみましょう。フォームで質問送信ボタンをクリックした時の関数の名前です。

async function 〇〇〇〇() {
  const 利用規約同意 = await 利用規約同意取得();
  if (!利用規約同意) {
    await 利用規約同意ポップアップ();
  }
  await 質問送信(questionValue);
  alert('質問が登録されました');
}

利用規約を同意したかチェックして、質問を送信します。

質問を送信することがコア機能なので、handle質問送信ぐらいがいいかと思いませんか?

async function handle質問送信() {
  const 利用規約同意 = await 利用規約同意取得();
  if (!利用規約同意) {
    await 利用規約同意ポップアップ();
  }
  await 質問送信(questionValue);
  alert('質問が登録されました');
}

いいえ!良くないです!関数名は質問送信ですが、実装内容には利用規約チェック質問送信が混在してます。

async function handle質問送信() {
  /*
   * 利用規約チェック
   */
  const 利用規約同意 = await 利用規約同意取得();
  if (!利用規約同意) {
    await 利用規約同意ポップアップ();
  }
  /*
   * 質問送信
   */
  await 質問送信(questionValue);
  alert('質問が登録されました');
}

このように重要なポイントが全て含まれてない関数名は、読む人が予想した通りコードが動作しないので、コードに関する信頼低下に繋がります。

その後からは関数名を信用しなくなって、詳細内容を全て確認するようになります。

ここで機能追加が入ると、どうなるんでしょう。

async function handle質問送信() {
  const 利用規約同意 = await 利用規約同意取得();
  if (!利用規約同意) {
    await 利用規約同意ポップアップ();
  }
  await 質問送信(questionValue);
  alert('質問が登録されました');
  const 保険担当者 = await 保険担当者取得();
  if (保険担当者 !== null) {
    await 保険担当者に質問送信(questionValue);
    alert(`${保険担当者.name}に質問を送信しました。`);
  }
}

関数がもっと太って、handle質問送信という名前に質問送信以外にも2つの役割をやることになります。

このようにすでにある関数に機能だけ追加することを皆んなやったことあると思います。

このような機能追加が繰り返すと、未来の私たちが他の人に使う言葉があります。

そのコードは触らない方がいいと思います。

一旦私が修正します。

1つの役割をする明確な名前の関数になるようにリファクタリングしてみました。

async function handle保険担当者に質問送信() {
  await 保険担当者に質問送信(questionValue);
  alert(`${保険担当者.name}に質問を送信しました。`);
}

async function handle新しい質問送信() {
  await 質問送信(questionValue);
  alert('質問が登録されました');
}

async function handle利用規約同意確認() {
  const 利用規約同意 = await 利用規約同意取得();
  if (!利用規約同意) {
    await 利用規約同意ポップアップ();
  }
}

このように分けて、必要な時それぞれ読んで使えばいいと思います。

1つの役割をする機能性コンポーネント

このように関数を分けるように、Reactコンポーネントも機能別に分けることもできます。

<button
  onClick={async () => {
    log('送信ボタンクリック');
    await openConfirm();
  }}
/>

ボタンをクリックするとサーバにログを残すコードがあります。

ここで残念なことはボタンクリック関数にログを残す関数とAPIコールが混ざっていることです。

<LogClick message="送信ボタンクリック">
  <button onClick={openConfirm} />
</LogClick>

LogClickというコンポーネントを作って、ボタンをクリックしたら自動でクリックログが送信されるようにリファクタリングするといいと思います。

こうすると、ボタンクリック関数ではAPIコールだけ考えることができます。

そして、要素が被ってるかどうか判断するIntersectionObserverコードがあります。

const targetRef = useRef(null);

useEffect(() => {
  const observer = new IntersectionObserver(([{ isIntersecting }]) => {
    if (isIntersecting) {
      fetchCats(nextPage);
    }
  });
  return () => {
    observer.unobserve(targetRef.current);
  };
});

return <div ref={targetRef}>もっと見る</div>;

このObserverコードの詳細実装とAPIを呼ぶことが混ざっていることがちょっと残念です。

これはIntersectionAreaという上位コンポーネントを作って、Intersectionの詳細コードとAPIの呼ぶ部分を分離することができます。

<IntersectionArea onImpression={() => fetchCats(nextPage)}>
  <div ref={targetRef}>もっと見る</div>
</IntersectionArea>

抽象化(Abstraction)

抽象化を通じてロジックからコア概念を抜き出すことができます。

次のポップアップコンポーネントのコードは最初から細かく実装したものです。

/* ポップアップコードをゼロから実装 */
<div style={ポップアップスタイル}>
  <button
    onClick={async () => {
      const res = await 会員登録();
      if (res.success) {
        プロフィール移動();
      }
    }}
  >
    送信
  </button>
</div>

次はこのポップアップコードを送信アクションと成功アクションという重要な概念だけ残して、他のは抽象化したものです。

/* 重要概念を残して抽象化 */
<Popup onSubmit={会員登録} onSuccess={プロフィール移動} />

関数の抽象化の例も見てみましょう。

次は担当者情報を取得して、取得した情報によって違うラベルを表示するコードです。

/* 担当者のラベルを取得するためのコードの詳細実装 */
const planner = await fetchPlanner(plannerId);
const label = planner.new ? '新しい担当者' : '連携された担当者';

次はこの詳細実装をgetPlannerLabelという関数名の中に全て抽象化したコードです。

/* 重要概念を関数名に入れて抽象化 */
const label = await getPlannerLabel(plannerId);

このようにコンポーネント、関数などのコードは具体的なコードをちょっと抽象的に、または、もっと抽象的にリファクタリングすることができます。

もう一つ見てみましょう。

次のようにボタンをクリックする時、Confirmを表示させて、ここでConfirmボタンを押したら特定メッセージを表示する具体的なコードがあります。

/* Level 0 */
<Button onClick={showConfirm}>送信</Button>;
{
  isShowConfirm && (
    <Confirm
      onClick={() => {
        showMessage('成功');
      }}
    />
  );
}

ボタンが押された時、Confirmを表示させる機能をConfirmButtonというコンポーネントで抽象化してみました。

onConfirmを使って、押されたら発火するアクションを渡すことができます。

/* Level 1 */
<ConfirmButton
  onConfirm={() => {
    showMessage('成功');
  }}
>
  送信
</ConfirmButton>

このコードを使ってみたら、messageというpropsだけ渡して、Confirmに表示されて欲しいメッセージを表示するようにもっと簡単に抽象化することもできます。

/* Level 2 */
<ConfirmButton message="成功">送信</ConfirmButton>

ここで、もう一歩進んで、全ての機能をConfirmButtonという名前に抽象化することもできます。

/* Level 3 */
<ConfirmButton />

正解はありません。状況によって、必要なだけ抽象化して使えば良いです。

実際のレビューの例です。

  • 例1

【レビュアー】 この部分を全て抽象化することはできないですか? await moreAccurateLocation.request()のようにです。 親がモーダルを開く部分を知る必要がないと思いました。

【コード作成者】 いいですね。 moreAccurateLocation.check()という名前はどうでしょうか?

  • 例2

【レビュアー】 UltraCallProgressとUltraCallComparisonが同じように見えますが、共通部分を抽象化できないでしょうか?

【コード作成者】 この部分はまだコードの重複が酷くないので、今後の柔軟性を考えたら違う部分が生まれると思って、わざわざ抽象化をしませんでした。(早い段階で抽象化をして、メンテナンスが辛かった時が結構ありました。)もうちょっと考えてみます。

抽象化のレベルが混在するとコードの把握が難しいです。抽象化のレベルが混在すると全般的にコードがどのレベルで具体的に書いてるのか把握しづらいです。

次のように抽象化のレベルが混在してると、具体的に書いてるコードを見て、その次に来る高いレベルで抽象化されたコードも具体的に書いてると思い込みます。

<Title>評価してください。</Title>
<div>
  {STARS.map(() => <Star />)}
</div>
<Reviews />
{rating !== 0 && (
  <>
    <Agreement />
    <Button rating={rating} />
  </>
)}

そして高いレベルで抽象化されたコードが小さいコードと思って中身を見ると、かなり複雑なコードが出てくる時があります。

このように書くと、コードを読む時、思考が揃わないようになります。コードがどのレベルで具体的に書いてるのか把握ができないためです。

<Title>評価してください。</Title>
<Stars />
<Reviews />
<AgreementButton show={rating !== 0}/>

このように似てる抽象化のレベルで合わせて書くと、コードがもっと把握しやすくなります。

ここでは高いレベルの抽象化で整理しましたが、状況によって、低いレベルの抽象化で整理しても大丈夫です。

アクションアイテム

理論は勉強したので、明日から皆さんのフロントエンドのコードにもClean Codeを適用してみてください。

そのためのアクションアイテムを紹介します。

怖がらず既存のコードを修正する

コードを壊すことが怖いと、クリンな実務コードを作ることができないです。

Pull RequestにFile Changesが多くなる言い訳で、私たちは既存コードを壊さず、コードを追加します。

怖がらず、既存のコード、アーキテクチャを壊してコードを作成してみてください。

Pull RequestのFile Changeを減らしたい場合は、Mother Branchを作ってリファクタリングを追加する方法もあります。

全体図を見る練習

その時は合ってたものが、今は違うことがあります。

既存のコードが綺麗だったが、私が追加したコードでぐちゃぐちゃになる場合があります。

私が追加した機能自体は綺麗かもしれないですが、全体図で見ると綺麗ではないコードかもしれないです。

チームと共感帯を作る

コードには正解がないです。

なので、コードレビューする際に、Clean Codeに関するコメントを残してもいいか迷う時があります。

すぐには小さなイシューなのでコメントを残すことを迷いますが、このように小さな一貫性を壊すコードが積み重なると、メンテナンスしづらいコードになります。

共感帯は自動で作られないです。明確に話す時間が必要です。

一緒に作ったコードで直したいポイントをお互いに話をして、問題と思ってるポイントを共有して集団知性を集めてください。

そして、どう改善するか考えれば良いと思います。改善はすぐに答えを出す必要はないです、問題を共有した後、時間をとって改善すれば大丈夫です。

ドキュメントで書く

Clean Codeは曖昧な概念です。文字で落とすと明確になります。

このコードが将来、どういうポイントで危険になるのか、どう改善できるのか自分なりの原則を書いてみてください。

完了

この記事で整理したReact Clean Codeの核心概念をまとめると以下の通りです。

  • 凝集度:同じ目的のコードをまとめるが、コアデータと詳細実装を分ける
  • 単一責任:1つの役割をする明確な名前の関数を作る
  • 抽象化:核心概念を抜き出すが、抽象化レベルを統一する

この記事の内容はTossのSLASH 21カンファレンスで発表された内容を基にしています。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。



SHARE
Twitter Facebook RSS