Greppability is an underrated code metric

A surveyor searching for metal in a field
Photo by Scott blake on Unsplash

When I’m working on maintaining an unfamiliar codebase, I will spend a lot of time grepping the code base for strings. Even in projects exclusively written by myself, I have to search a lot: function names, error messages, class names, that kind of thing. If I can’t find what I’m looking for, it’ll be frustrating in the best case, or in the worst case lead to dangerous situations where I’ll assume a thing is not needed anymore, since I can’t find any references to it in the code base. From these situations, I’ve derived some rules you can apply to keep your code base greppable:

Don’t split up identifiers

It turns out that splitting up, or dynamically constructing identifiers is a bad idea.

Suppose you have two database tables shipping_addresses, billing_addresses, it might seem like a perfectly good solution to construct the table name dynamically from the order type.

const getTableName = (addressType: 'shipping' | 'billing') => {
    return `${addressType}_addresses`
}

Though it looks nice and DRY, it’s not great for maintainenance: someone will inevitably search the code base for the table name shipping_addresses and miss this occurence.

Refactored for greppability:

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')
}

The same goes for column names, object fields, and, god forbid, method/function names (it’s easily possible to dynamically construct method names with javascript).

Use the same names for things across the stack

Don’t rename fields at application boundaries to match naming schemes. An obvious example is then importing postgres-style snake_case identifiers into javascript, then converting them to camelCase. This makes it harder to find—you now have to grep for two strings instead of one in order to find all occurences!

const getAddress = async (id: string) => {
    const address = await getAddressById(id)
    return {
        streetName: address.street_name,
        zipCode: address.zip_code,
    }
}

You’re better off biting the bullet and returning the object directly:

const getAddress = async (id: string) => {
    return await getAddressById(id)
}

Flat is better than nested

Taking inspiration from the Zen of Python, when dealing with namespaces, flattening your folders/object structures is mostly better than nesting.

For example if you have two choices to set up your translation files:

{
    "auth": {
        "login": {
            "title": "Login",
            "emailLabel": "Email",
            "passwordLabel": "Password",
        },
        "register":
            "title": "Register",
            "emailLabel": "Email",
            "passwordLabel": "Password",
        }
    }
}

and

{
    "auth.login.title": "Login",
    "auth.login.emailLabel": "Email",
    "auth.login.passwordLabel": "Password",
    "auth.register.title": "Login",
    "auth.register.emailLabel": "Email",
    "auth.register.passwordLabel": "Password",
}

take the second option! You will be able to easily find your keys now, which you are probably referring to as something like t('auth.login.title').

Or consider React component structure: a component stucture like

./components/AttributeFilterCombobox.tsx
./components/AttributeFilterDialog.tsx
./components/AttributeFilterRating.tsx
./components/AttributeFilterSelect.tsx

is preferable to

./components/attribute/filter/Combobox.tsx
./components/attribute/filter/Dialog.tsx
./components/attribute/filter/Rating.tsx
./components/attribute/filter/Select.tsx

from a greppability perspective, since you’ll be able to grep for the whole namespaced component AttributeFilterCombobox just from the usage, as opposed to just Dialog, which you might have multiple of accross your application.

Want to read more like this?