- 見慣れないコードベースを保守するとき、文字列を検索することに多くの時間を費やす
- 1人で書いたプロジェクトでも、関数名、エラーメッセージ、クラス名など多くのものを検索する必要がある
- 検索しにくいと、コードベース内で参照を見つけられず、不必要なものだと見なしてしまう危険がある
- このような状況で、コードベースのGreppability(grepしやすさ)を保つために適用できるいくつかのルールを導き出した
識別子を分割しない
- 識別子を分割したり動的に組み立てたりするのはよい考えではない
shipping_addresses と billing_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件のコメント
json のフルパスに変換して、grep しやすくしてくれるツールも 1 つ置いておきます!
https://ja.news.hada.io/topic?id=3159
いいですね……greppabilityですか……
有用な情報はできるだけ1行に書くのも有用そうです
いいですね
Hacker Newsの意見
関数名やクラス名のようなシンボルを検索するのは、コードの構文を理解するツールを使うのに比べると弱い
grepツールに「スーパー大文字小文字区別なし」モードがあると便利そう
FooBar|first_nameを検索するとき、あらゆるケースにマッチするよう展開することgreppability を支持する
grep-barやgrep-barhetのような語が実際に存在するgreppbarは「理解できる」を意味し、greppbarhetは「理解可能性」を意味するHamilton を設計したとき、関数定義とその下流での使用箇所を簡単に grep できるようにすることが目標だった
greppableはそれ自体では使われていない単語/概念である条件付き文字列補間を使った複雑な例を見たことがある
多くのコーディングスタイルやツールは、文字列定数を長さに関係なく1行のまま維持する
Rust、Javascript、Lisp は関数定義の前にキーワードを置くため、検索しやすい
greppability には同意するが、境界をまたいで名前を同じに保つことには反対する
コードの検索しやすさは良いが、例は意図的にエラーの可能性を高めている