DEV Community

Lane Wagner
Lane Wagner

Posted on • Originally published at qvault.io on

Optimize For Simplicity First

KISS keep it simple stupid

The post Optimize For Simplicity First first appeared on Qvault.

We can’t optimize for everything in software engineering, so we need to start with something, and that something should be simplicity. For example, to over-optimize for speed in JavaScript, we might write our for-loops backwards to the detriment of readability. On other occasions, we may over-optimize architectural abstraction to the detriment of speed.

I assert that we should optimize for simplicity first, and only make complex memory, speed, and abstraction improvements as they become necessary.

But Muh Speed

If it’s slow but readable, I can make it fast. If it’s broken but readable, I can make it work. If it’s impossible to understand, then I have to ask around until I can find out what the abomination is supposed to do in the first place.

Working, readable software should be the “MVP” of your code. It’s trivial to find a bottleneck in code that is easy to understand. That one slow chunk of code can be optimized for speed when and if necessary.

A Small Caveat

pitfall

There are cases in which it makes sense to take speed seriously upfront. For example, choosing which language or framework to use for a project is a decision that cannot be undone or changed easily.

Memory Problems

Do you need Redis? But do you REALLY need Redis? Probably not. In the case of a web API, omit caching on your first iteration. Most servers don’t require in-memory caching to effectively service users. When speed starts to become a problem, implement in-memory caching on the server itself if possible. In terms of overall system complexity, the only thing worse than code dependencies are external dependencies.

Only add a new database, queuing system, API service, or NPM module if there is no simpler option.

Abstractions and DRY Code

There is nothing wrong with writing reusable functions, and most will written functions will be reusable without adding any needless complexity. However, too often I’ve seen developers over-generalize a problem to the detriment of readability.

If there is currently only one place in your application where a function is being called, don’t worry about making that function the most generalized version of itself. For example, let’s say I have some validation middleware in my Go API:

type apiParams struct {
    OrgID string
    UserID string
}

func validateParams(params apiParams) error {
    if params.OrgID == "" {
        return errors.New("OrgID is required")
    }
    if params.UserID == "" {
        return errors.New("UserID is required")
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

A useful function to be sure, but our naive DRY filter may kick in an tempt us to do the following:

type apiParams struct {
    OrgID string
    UserID string
}

func validateParams(params interface{}) error {
    dat, _ := json.Marshal(params)
    mapParams := map[string]string{}
    json.Unmarshal(dat, &mapParams)

    for k, v := range mapParams {
        if v == "" {
            return fmt.Errorf("%s not found", k)
        }
    }
    return nil
}

Enter fullscreen mode Exit fullscreen mode

We’ve succeeded in making the code more abstract, now any function can pass in any struct and check if the fields exist! The problem is that we have also added many edge cases that will certainly produce bugs under many conditions. For example, what if an integer is passed in? What if a struct that contains more than just string values is used?

The code was just fine as it was, we had no reason to generalize it. When we finally are forced to generalize it later we will know better how to build a good abstraction.

KISS > DRY. When used properly, DRY code will be more simple than it was before anyhow, these rules aren’t in direct competition.

Thanks For Reading!

Follow us on Twitter @q_vault if you have any questions or comments

Take game-like coding courses on Qvault Classroom

Subscribe to our Newsletter for more educational articles

Top comments (1)

Collapse
 
pinotattari profile image
Riccardo Bernardini

I totally agree with this. Correctness, not efficiency, should be the first goal in software production. Speaking generally, a code that is simpler it is also easier to read and to mantain (and bugs have less cracks to enter from).

On a side note, this morning I was just thinking about how the approach to "efficiency" in software has something of schizophrenic... If you talk about computational complexity (i.e., a O(N log N) complexity vs O(N^2) for sorting algorithms) you can get comments like

"Why do you care about this? Where do you live, stone age? Nowadays we have smartphones, cloud, edge compunting, AI..."

by the same persons that write

x << 3

instead of

x*8

because "it is more efficient" (although compilers have been transforming the latter into the former since the '80s)