Since almost everything we do is a stepping stone to enable something higher-level, something with more significance than what we’re working on at any given moment, its elegance should be appreciated and then it should almost disappear. If you’re having to pay attention to the tool – the screen, the verbiage, the lower-level API routine, even a door handle – then that’s attention that you (or your target audience) will be unable to bring to the problem you’re actually trying to solve.
When developing, don’t strive for optimization, strive for simple understandability. Follow standards. Use existing systems wherever possible. Its very tempting to look ahead and see, “Aha, this will become more complicated in the future so I’ll start fixing that problem now.” The trouble is that there are any number of things that can happen between now and then – the feature may be removed, usage patterns may change, computers may become fast enough that the simple, understandable metaphor (which may be wildly inefficient) is still thoroughly fast enough… whereas once something is more complicated, it will add burdens every time you touch it.
This should go beyond buzzwords like lean or agile – if you continually strive to simplify you’ll be able to reduce code complexity (and with it cost, risk, and future maintenance burdens). Here’s one counter-intuitive example:
Let’s say that you’re working on a new module. 98% of the time the path through the system will be very clean. One time out of fifty, however, you’ve got to take care of a range of exceptional conditions – either conditional processing, more checking, or something else.
Your first reaction should be to see if the 1/50 path can be eliminated. If that’s a reasonable trade-off, even if it means losing one or two customers, it may be worth it. Let’s assume that this is not an option. The goal of many developers will be to create two clean paths through the system – one “easy path” and one “complex path”. This will allow most of the traffic to go down a very well understood (and easily tested) path, with the exceptional conditions being handled in a more complicated one.
Unfortunately, the real-world result here will be that while the simple path is likely to be well understood and maintainable, the complex path will lag behind. Inexperienced developers may add features of fix issues in the simple path only, and these may not be discovered until you’ve released product. Even though these issues will only impact a small number of users, we’ve already stated that ignoring them isn’t a viable option.
Here’s a phrase to remember: no special cases. If you need more complex checks for a few users, figure out a clean way to make them for all users. The “common” code path may be more complex, but by removing a branch you’ll actually have a much better chance of making the overall system clean and consistent. Anything you add to the system will need to work with and be coded into the complex path anyway, so adding a new “simple” path actually increases your overall complexity – the very thing you were hoping to avoid.
When you are adding those extra checks (or whatever), also try to describe them in terms of specific business need rather than, for example, account type. If your commercial customers need a check that your personal ones don’t, is it truly because they’re commercial or is it because they might have over 10 users? By using the true business-logic checks rather than artificial ones, you’ll tend to produce code that’s more efficient and future-proof. You also won’t find yourself with “unpopular” not-very-well-supported code branches.
True simplicity, not surface simplicity: that’s the goal.