Compatibility is an important code metric

A surveyor searching for metal in a field

When working on maintaining a codebase I'm unfamiliar with, I spend a lot of time grepping for strings. Even in projects I've written entirely myself, I have to grep a lot: function names, error messages, class names, and the like. If I can't find what I need, I'm at best frustrated, and at worst I can create a dangerous situation if I assume that some element is no longer needed because I can't find references to it in the codebase. Based on these situations, I've developed rules that help make code more grepable.

Don't separate IDs

It turns out that splitting or dynamically creating identifiers is a bad idea.

Let's say we have two database tables shipping_addresses, billing_addresses,. It may seem like a perfectly normal solution to create a table name dynamically based on the ordinal type.

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

While this seems nice and DRY, it's not very maintainable: someone will inevitably search the codebase for the name shipping_addresses and will skip this entry.

Let's refactor the code to improve comparability:

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 (in Javascript you can create class names dynamically).

Use the same names for elements throughout the stack.

Don't rename fields across application boundaries to match naming schemes. An obvious example: we import Postgres-style snake_case identifiers into our Javascript code, then convert them to camelCase. This makes searching more difficult — now you have to grep two lines instead of one to find all occurrences!

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

It's better to take a more complicated route and return the object directly:

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

Flat structure is better than nested structure

I took this advice from Zena Python: When working with namespaces, flattening folder/object structures is usually better than nesting.

For example, if you have two options for configuring 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",
}

then choose the second one! Then you can easily find keys, links to which will presumably look like this: t('auth.login.title').

Or consider the structure of React components. Component structure

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

preferable than

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

in terms of grepability, because you will be able to grep the entire component AttributeFilterCombobox in a namespace simply by its usage, not by string Dialogwhich can appear in many places in the application.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *