22 ポイント 投稿者 GN⁺ 2024-09-04 | 5件のコメント | WhatsAppで共有
  • 見慣れないコードベースを保守するとき、文字列を検索することに多くの時間を費やす
  • 1人で書いたプロジェクトでも、関数名、エラーメッセージ、クラス名など多くのものを検索する必要がある
  • 検索しにくいと、コードベース内で参照を見つけられず、不必要なものだと見なしてしまう危険がある
  • このような状況で、コードベースのGreppability(grepしやすさ)を保つために適用できるいくつかのルールを導き出した

識別子を分割しない

  • 識別子を分割したり動的に組み立てたりするのはよい考えではない
  • shipping_addressesbilling_addresses という2つのデータベーステーブルがあると仮定すると、注文タイプに応じてテーブル名を動的に組み立てるのはよさそうに見える
const getTableName = (addressType: 'shipping' | 'billing') => {  
    return `${addressType}_addresses`  
}  
  • これはDRYに見えるが、保守には向いていない。誰かが shipping_addresses テーブル名をコードベースで検索したときに、この部分を見落とすかもしれない
  • 識別子をハードコードするほうがよい方法
  • 検索しやすさのためにリファクタリングしたコード:
const getTableName = (addressType: 'shipping' | 'billing') => {  
    if (addressType === 'shipping') {  
        return 'shipping_addresses'  
    }  
    if (addressType === 'billing') {  
        return 'billing_addresses'  
    }  
    throw new TypeError('addressType must be billing or shipping')  
}  
  • カラム名、オブジェクトフィールド、メソッド名/関数名(JavaScriptではメソッド名を動的に組み立てることが容易)にも同じことが当てはまる

スタック全体で同じ名前を使う

  • 命名規則に合わせるために、アプリケーション境界でフィールド名を変更しないこと
  • 代表的な例として、PostgreSQLスタイルの snake_case 識別子を JavaScript に持ち込み、camelCase に変換するのはよくない
  • これは検索を難しくする。すべてを見つけるには、1つではなく2つの文字列を検索しなければならない
const getAddress = async (id: string) => {  
    const address = await getAddressById(id)  
    return {  
        streetName: address.street_name,  
        zipCode: address.zip_code,  
    }  
}  
  • むしろオブジェクトをそのまま返すほうがよい
const getAddress = async (id: string) => {  
    return await getAddressById(id)  
}  

ネストよりフラットなほうがよい

  • PythonのZenに着想を得て、名前空間を扱うときはフォルダ/オブジェクト構造をネストさせるより、フラットにするほうがたいてい望ましい
  • 翻訳ファイル設定に次の2つの選択肢がある場合:
{  
    "auth": {  
        "login": {  
            "title": "Login",  
            "emailLabel": "Email",  
            "passwordLabel": "Password",  
        },  
        "register": {  
            "title": "Register",  
            "emailLabel": "Email",  
            "passwordLabel": "Password",  
        }  
    }  
}  
{  
    "auth.login.title": "Login",  
    "auth.login.emailLabel": "Email",   
    "auth.login.passwordLabel": "Password",  
    "auth.register.title": "Login",  
    "auth.register.emailLabel": "Email",  
    "auth.register.passwordLabel": "Password",  
}  
  • 2つ目の選択肢を選ぶのがよい。キーを簡単に見つけられ、t('auth.login.title') のように参照できる
  • Reactコンポーネント構造を考えると:
./components/AttributeFilterCombobox.tsx  
./components/AttributeFilterDialog.tsx  
./components/AttributeFilterRating.tsx  
./components/AttributeFilterSelect.tsx  
  • 次のような構造より好ましい
./components/attribute/filter/Combobox.tsx  
./components/attribute/filter/Dialog.tsx  
./components/attribute/filter/Rating.tsx  
./components/attribute/filter/Select.tsx  
  • 検索の観点では、Dialog のような一般的な名前ではなく、AttributeFilterCombobox のように名前空間を含んだ完全なコンポーネント名で検索できるからだ

GN⁺の見解

  • このブログ記事は、コードベースを保守する際に識別子検索の重要性をうまく説明している
  • 識別子を動的に組み立てたり、アプリケーション境界で名前を変更したりすると、コード保守が難しくなる。識別子は一貫して明確であるべきだ
  • その代わりに、識別子をハードコードし、名前空間をフラットに保つほうが検索の観点では優れている
  • コードの可読性と保守性を高めるために、こうした原則をプロジェクトに適用するとよい
  • 著者が提案したルール以外にも、Self-Documenting Code の作成や意味のあるコメントの活用など、コード品質を高めるさまざまな方法がある

5件のコメント

 
nowpark 2024-09-06

json のフルパスに変換して、grep しやすくしてくれるツールも 1 つ置いておきます!

https://ja.news.hada.io/topic?id=3159

 
botplaysdice 2024-09-05

いいですね……greppabilityですか……

 
ahwjdekf 2024-09-04

有用な情報はできるだけ1行に書くのも有用そうです

 
roxie 2024-09-09

いいですね

 
GN⁺ 2024-09-04
Hacker Newsの意見
  • 関数名やクラス名のようなシンボルを検索するのは、コードの構文を理解するツールを使うのに比べると弱い

    • 「定義へ移動」や「参照を検索」機能だけでも、テキスト検索の必要性を大きく減らせる
    • この10年間、主にユーザーに見える文字列だけを検索してきた
    • このような投稿は、筆者が自分の言語に合ったより良いツールを学ぶために時間を投資すべきだということを意味する
    • 良いIDEだけでも多くの時間を節約できる
  • grepツールに「スーパー大文字小文字区別なし」モードがあると便利そう

    • たとえば FooBar|first_name を検索するとき、あらゆるケースにマッチするよう展開すること
    • こうした機能がデフォルトでない状況は想像しにくい
  • greppability を支持する

    • スウェーデン語には grep-bargrep-barhet のような語が実際に存在する
    • greppbar は「理解できる」を意味し、greppbarhet は「理解可能性」を意味する
  • Hamilton を設計したとき、関数定義とその下流での使用箇所を簡単に grep できるようにすることが目標だった

    • Pythonのデータ変換の世界では、grep があまり役に立たないコードベースを簡単に作れてしまう
  • greppable はそれ自体では使われていない単語/概念である

    • 長いあいだ、これを組織化の原則として使ってきた
    • コードを構造化する最良の方法の一つだ
  • 条件付き文字列補間を使った複雑な例を見たことがある

    • プロジェクトに初めて参加したとき、UIで見た3つの単語を見つけるのに時間がかかりすぎた
    • 後になって、このコードをすべて簡単に grep できる文字列に置き換えた
  • 多くのコーディングスタイルやツールは、文字列定数を長さに関係なく1行のまま維持する

    • プログラムの出力で文字列を見て、コード内で同じ文字列を検索できるようにするためだ
  • Rust、Javascript、Lisp は関数定義の前にキーワードを置くため、検索しやすい

    • C にはそのようなキーワードがないため、関数名だけを検索できる
    • 一部のCのコーディング規約では、定義を2行に分けて検索しやすくしている
  • greppability には同意するが、境界をまたいで名前を同じに保つことには反対する

    • 1つのシンボルが1つのドメインにだけ存在するほうが認知負荷を減らせる
  • コードの検索しやすさは良いが、例は意図的にエラーの可能性を高めている

    • 文字列条件を追加すると、入力と出力の不一致が起きる可能性が生じる
    • 辞書をフラット化すると、タイプミスが起きる可能性が高くなる
    • タイプミスはよく起きるもので、複数のコードベースにコピーされていると解決が難しい