7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【2024年版】JavaScript(React)におけるリーダブルコードのチートシート4~6章

Last updated at Posted at 2024-02-17

2024/2/27
6章コメントは的確で簡潔に
6.7「名前付き引数」コメント
6.8情報密度の高い言葉を使う
6.9まとめ

こちらの記事の続編になります。

4章 美しさ(41p~)

美しさの定義

  • 読み手が慣れているパターンと一貫性のあるレイアウトを使う
  • 似ているコードは似ているように見せる
  • 関連するコードをまとめてブロックにする

4.1なぜ美しさが大切なのか?

読みづらいパターン

// 全体的に関連するコードをまとめてブロックにまとまっていなくて読みづらい
export const Button = ({
  label,disabled,//改行に一貫性がない
  children,
  ...props
  
}: ButtonProps)=>{//一般的にはスペースを入れる
  return(
    <button
  className={styles.button}//インデントがバラバラ
      disabled={disabled}
      {...props}>
      {children}
    <button>
  )
}

修正版

export const Button = ({
  primary,
  label,
  disabled,
  children,
  ...props
}: ButtonProps) => {
  return(
    <button
      className={styles.button}
      disabled={disabled}
      {...props}>
      {children}
    </button>
  )
}

4.2一貫性のある簡潔な改行位置

4.3メソッドを使った整列

悪い例

  //全体的にグルーピングができていない
  const openCustomerPortal = async () => {setLoading(true)
    try {
      // 改行がなく読みづらい
      if (!loginUser.customer_id) {alert("お支払い情報がありません")return
      }
      //1行が長すぎる
      const res = await axios.post('/api/customer-portal', {customer_id: loginUser.customer_id,}
      const url = res.data.response
      //グループごとに改行を入れた方がわかりやすい
      if (url) {
        router.push(url)
      }
    } catch (error) {
      //リターンなど重要な項目は単独の行の方が良い
      setMessage('エラーが発生しました。' + error)return
    } finally {
      setLoading(false)
    }
  }

改善後

  • グルーピングすることでテストの追加がしやすくなる
  • 見た目を改善すれば表面上の改善でなく構造も改善できて後々の保守性(コードの理解する時間)も短縮できる
const openCustomerPortal = async () => {
    
    setLoading(true)

    try {
      if (!loginUser.customer_id) {
        alert("お支払い情報がありません")
        return
      }

      const res = await axios.post('/api/customer-portal', {
        customer_id: loginUser.customer_id,
      })

      const url = res.data.response

      if (url) {
        router.push(url)
      }

    } catch (error) {
      setMessage('エラーが発生しました。' + error)
      return

    } finally {
      setLoading(false)
    }
  }

4.4縦の線を真っ直ぐにする

なぜ整列するべきなのか
・コード理解の時間が短縮できる
・階層構造がわかりやすい
・タイプミスに気付きやすい

「似ているコードは似ているように見せる」

またPrettierなどのフォーマッターを使用してコードを自動で整列させることが多い
※わからない人は「vscode Prettier」とかで調べると出てくるので調べてみてください

{
  "contents": [
    {
      "id": "affdgsgds",
      "createdAt": "2023-12-20T09:23:16.569Z",
      "updatedAt": "2023-12-20T09:53:54.766Z",
      "publishedAt": "2023-12-20T09:23:16.569Z",
      "revisedAt": "2023-12-20T09:53:54.766Z",
      "subject_name": "テスト01",
      "slag": 1,
      "type_of_subject": 1,
      "icon": {
        "url": "https://iadfaddd",
        "height": 82,
        "width": 82
      }
    },
    {
      "id": "affdgsgds",
      "createdAt": "2023-12-20T09:23:16.570Z",
      "updatedAt": "2023-12-20T09:23:16.570Z",
      "publishedAt": "2023-12-20T09:23:16.570Z",
      "revisedAt": "2023-12-20T09:23:16.570Z",
      "subject_name": "テスト02",
      "slag": 2,
      "type_of_subject": 1
    },
    
  ],
  "totalCount": 12,
  "offset": 0,
  "limit": 10
}

4.5一貫性と意味のある並び

下記はNext.jsで使用したimport一覧

  import styles from '@/components/Organisms/Signup/Signup.module.scss'
  import { useState } from 'react'
  import { useRouter } from 'next/navigation'
  import Link from 'next/link'
  import { useForm, SubmitHandler } from 'react-hook-form'
  import { zodResolver } from '@hookform/resolvers/zod'
  import clsx from 'clsx'
  import { Button } from '@/components/Atoms/Button/Button'
  import type { Database } from '@/lib/database.types'
  • JSX(html)で使用する順番
  • 重要度順
  • グループに分ける(ライブラリ、コンポーネント、型定義)など
  • アルファベット順
    などなど

4.6宣言ブロックにまとめる

人間の脳はグループや階層を1つの単位として考える
下記は1つのformの中でグルーピングされている箇所とされていない箇所がある。
1行空けるだけでだいぶ読みやすくなる。

<form onSubmit={handleSubmit(onSubmit)}>
    {/* グルーピングされていない */}
    <div>
      <div>
        <div>
          <Image src={avatarUrl} />
        </div>
        <input type='file' id='avatar' onChange={onUploadImage} />
        <div>{fileMessage}</div>
      </div>
    </div>
    <div>
      <div>名前</div>
      <input
        type='text'
        placeholder='名前'
        id='name'
      />
      <div>{errors.name?.message}</div>
    </div>

    {/* グルーピングされていてコメンされている */}
    <div>
      <div>自己紹介</div>
      <textarea
        placeholder='自己紹介'
        id='introduce'
        rows={5}
      />
    </div>
    
    <div>
      {loading ? (
        <Loading />
      ) : (
        <Button type='submit'>送信</Button>
      )}
    </div>
  </form>

4.7コードを「段落」に分割する

下記コードは会員登録(supabaseを使用)をNext.jsで実装した例だ
onSubmit関数はいくつかの段落で分けることができる

  • サインアップの関数を叩く
  • すでに登録されているかチェック→登録されてたらreturn
  • 登録されていなければプロフィールを更新
  • プロフィールの更新がちゃんとできているか確認
  • フォームをクリアしてstateにメッセージを格納

※本当はエラー処理とかも書いてあるけど省略

段落で分割されていなかったら流れが分からずコードを読み解くのに時間がかかる

const onSubmit: SubmitHandler<Schema> = async (data) => {
  // 会員登録をするための関数
  const { data: signUpData, error: errorSignup } = await supabase.auth.signUp({
    email: data.email,
    password: data.password,
    options: {
      emailRedirectTo: `${location.origin}/auth/callback`,
    },
  })

  // 登録されているメールアドレスの場合、空の配列が返ってくる。
  const identities = signUpData.user?.identities
  if (identities?.length === 0) {
    console.log(identities)
    setErrorMessage('既に登録済みです。')
    return
  }

  // プロフィールの名前を更新
  const { error: updateError } = await supabase
    .from('profiles')
    .update({ name: data.name })
    .eq('email', data.email)

  // エラーチェック
  if (updateError) {
    setErrorMessage('エラーが発生しました。')
    return
  }

  // 入力フォームクリア
  reset()
  setSendMailMessage(
    '本登録用のURLを記載したメールを送信しました。',
  )
}

4.8個人的な好みと一貫性

最終的には個人の好み

//CSS Modulesにおけるclassの定義とか
<dev className={styles['kv']}></div>
<dev className={styles.kv}></div>

/*
functionとアロー関数の定義
*/
const logger = () => {}
function logger() {}
厳密には違うけど現場ではこっちで統一で!」と言われることが多い気がする

一貫性のあるスタイルは「正しい」スタイルよりも大切

4.9まとめ

  • 【一貫】複数のコードブロックで同じようなことをしてたらシルエットも揃える
  • 【階層】列を揃えて階層構造を可視化
  • 【順序】A・B・C と定義していたら他の場所使用する時もA・B・Cとならべる
  • 【段落】空行を使ってブロックを理論的な「段落」に分ける

5章 コメントすべきことを知る(55p~)

5.1コメントするべきではないこと

  • 新しいもの情報を提供していない
  • 理解しやすくならない
//POST関数の定義
function POST(req: NextRequest) {}

point

  • コードからすぐわかることをコメントに書かない
  • コメントのためのコメントをしない
  • 酷い名前はコメントをつけず名前を変える

名前で伝わるものに無理してコメントはつけなくてよし

5.2自分の考えを記録する

「監督のコメンタリー」を入れる

//このデータだとハッシュテーブルよりもバイナリーツリーの方が40%早かった

コードの欠陥にコメントをつける

//TODO:もっと高速なアルゴリズムを使う
記法 典型的な意味
TODO あとで手をつける
FIXME 既知の不具合があるコード
HACK あまり綺麗じゃない解決策
XXX 危険! 大きな問題がある

余談
VSCodeでは「TODO」をわかりやすく表示するプラグインがあるのでぜひ使ってみてください!

5.3読み手の立場になって考える

質問されそうなことを想像する
質問されそうなこと→なぜエンコードするのか

// タイトルに#などが含まれているケースがあるためエンコードする。
const url = `/api/breadcrumbs?pathname=${pathname}&title=${encodeURIComponent(
  pageTitle,
)}`;

「要約」のコメント
関数であれば、その中身の流れがわかるように書くと良い

export async function POST(req: NextRequest) {
  try {
    // Stripeからのリクエストかどうかを確認
    ・・・   
    
    // イベントタイプごとに処理を分ける
    ・・・
    
    // 最初の支払いが成功したとき
    ・・・
    
    // サブスクリプション詳細取得
    ・・・ 
    
    // ユーザーのプロフィールにStripeの顧客IDを追加
    ・・・

WHAT・WHY・HOWはコメントに書くべきか?
コメントの内容はコードを理解するのに役立つならなんでいい

5.4ライターズブロックを乗り越える

コメントを書く作業

  • 頭の中にあるコメントを書き出す
  • コメントを読んで改善が必要なものを見つける
  • 改善

5.5まとめ

コメントすべきでは「ないことリスト

  • コードからわかる情報
  • 酷いコードの捕捉文→コードを修正

コメントするべきリスト

  • なぜこのロジックになっているのかの意図を説明
  • コードの欠陥(TODOとか)
  • 定数の値にまつわる背景

コメントする際の意識するポイント

  • あとでコードを誰かに読まれた時に質問されそうなところを予想してコメント
  • 読み手が細部に囚われないようブロックごとにコメントして流れがわかるようにする

##6章コメントは的確で簡潔に

6.1コメントを簡潔にしておく

- /* Supabaseクライアントの初期化
- *supabaseにアクセスするためのURL
- *supabaseのアクセス用のキー
*/

+ // Supabaseクライアントを初期化
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

6.2曖昧な代名詞を避ける

「その」が指すのはデータかキャッシュか曖昧

- //データをキャッシュに入れる。ただし、先にそのサイズをチェックする
+ //データをキャッシュに入れる。ただし、先にデータサイズをチェックする
or
+ //データが小さければ。それをキャッシュに入れる 

6.3歯切れの悪い文章を磨く

- //これまでにクロールしたURLかどうかによって優先度を変える
+ //これまでにクロールしていないURLの優先度を上げる

歯切れ悪いpoint
URLかどうかによって
どのように変えるのか

6.4関数の動作を正確に記述する

//このファイルに含まれる行数を返す
const countLines = (fileName: string) => {
    
}

あまり正確なコメントではない。
行の意味はいろいろある

  • 空ファイルは0行なのか1行なのか
  • "hello"は0行なのか1行なのか
  • "hello\n"は1行なのか2行なのか

改行文字の場合下記の方が的確

//このファイルに含まれる改行文字("\n")を数える
const countLines = (fileName: string) => {
    
}

6.5入出力のコーナーケースに実例を使う

- //srcの先頭や末尾にあるcharsを除去する
+ strip("abba/a/ba", "ab")"/a/"を返す
const strip = (src, chars) => {}

一見は百聞にしかず

6.6コードの意図を書く

//次の講義のIDを出力
  const getPreviousObjectId = (objects: Lesson[], targetId: string): string | null => {
    const index = objects.findIndex((obj) => obj.id === targetId)
    if (index > 0) {
      return objects[index - 1].id
    }
    return null // 指定されたIDが最初の要素である場合、または見つからない場合
  }

コード上をただ説明するのではなくどのような関数なのかの意図を説明する

6.7「名前付き引数」コメント

const connect = (timeout: number, isEncryption: boolean) => {
    //処理
    return
  }
connect(/** timeout_ms*/ 10, /** isEncryption_ms*/ false)

6.8情報密度の高い言葉を使う

- //所在地から余分な空白を排除する。それから「Avenue」を「Ave.」にするなどの整形を施す。
- //こうすれば、表記がわずかに違う所在地でも同じであると判断できる

+ //所在地を正規化する(例: "avenue" → "Ave.")

6.9まとめ

  • 対象が複数に取れる「それ」「これ」は使わない
  • 関数の動作は正確に説明
  • コメントに含める入出力の実例を慎重に選ぶ
  • コードの意図は詳細レベルではなく、高レベルに記述
  • よくわからない引数にはインラインコメントを使用
  • 意味の詰め込まれた表現を使って簡潔に

続きはこちら

7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?