Keep It Short, Stupid!

Dec 10, 2009 12:31

A 5 year old article from my old website, salvaged from the Internet Archive.

We all know that one must
KISS. But how does
one KISS? This article is all about a hypothesis that if you try to
KISS, you can also
KISS.

Alright, so your Tech Lead has handed you down a nice interface
design and done whatever was necessary to explain the behaviour of
the system through those interfaces. Now what are you waiting for,
stupid? Code it up and deliver it yesterday... er... whatever! In any
case, it is your responsibility now to put together something that
works. You are the developer who writes the first cut of fresh
software.

You are the target of a zillion curses doled out by
maintenance engineers who have to clean up the mess after you're
done. You're at the recieving end of one bug report after another that
comes out of the QA department while you're working on the next
iteration of development. Life sure sounds like it's hell.

Everyone has his own opinions about how to handle this situation
and so have I developed a set of guidelines to keep myself out of such
situations. Do these work? Apparently they do, which is why I took
time out to put this whole thing together, but YMMV.

Be honest, be shameless

We all make errors in life and more so while programming a
computer, which involves giving very precise instructions because
computers haven't yet reached the stage where one can expect them to
DWIM. One of the often
made mistakes I have come across is trying to keep everything under
wraps when it comes to handling errors.

Do not handle errors that you can not.

Trying to cover up error conditions when you don't have any idea of
what to do can often be dangerous. Once I was trying to access a web
application that required a login. I gave my username and password and
it failed to authenticate. I spent about half an hour trying to figure
out what my password was and then going to the database itself and
resetting the password to something new. Still no luck. On scanning
the authentication code, I found that the implementer caught any
exception and returned authentication failure. The actual reason for
failure was that there was no database connection!

In another project, we started seeing errors in the logs that said
there was no column called "Infinity". That looked like a division by
zero error and it is apparently quite dumb to let a division happen
without checking for division by zero. In reality the divisor was
never supposed to be zero so the check was unnecessary. The best that
could done was to check and report an error but that machinery was
already in place and it worked. Had the case been hushed up, say, by
setting the result to zero, we would have had bad data and

not have noticed it.

The whole idea is for a program to fail noisily when it has
to. This makes it easy to detect the failure at the point of occurence
where you have most context available to figure out its cause. You can
then choose to eliminate the cause or plan out a strategy to handle
that failure. When you are writing lower layers of a system, you
should dutifully report errors and not hush them up. It is for the
higher layers to decide what to do. It may be a simple, “Sorry,
there seems to be some problem doing foo. Please try again
later.” or something more sophisticated like giving suggestions
on how or when to get things done right.

Note that you'd be lucky if you follow the above style and the
libraries you use report errors by throwing exceptions. With return
codes it is your responsibility to check the error code and report
it, otherwise you'll be simply swallowing it (and throwing an
unrelated error somewhere later).

Cut the crap

The goal of an implementation engineer is to write code that:

  • is largely free of defects

  • has defects that can be detected easily and quite early
  • has a simple logic for the benefit of maintenance engineers
  • is well structured to ease evolution and perfective
    maintenance

I will try to show how aiming to cut the crap and keeping the
number of lines in your source code at a minimum can help you in
reaching these goals.

Keep It Short, Stupid

Software Engineering statistics show that the number of defects per
line of code is roughly constant across all programming
languages. That is the reason why usage of higher level languages is
advocated. So, if you try to reduce lines of code within the decided
language of implementation, you reduce the incidence of bugs as
well. Once you have written a unit, you rescan it to see if there is
some flab you can shed. Rescanning for re-implementation is inherently
more rigorous than scanning for correctnes, because at each step you
would be double-checking to ensure you don't break the
behaviour. To keep it largely free of defects, KISS.

If we again recall our Software Engineering lessons we'll know
that the earlier a defect is found, the easier it is to rectify
it. When less amount of code does more work, more things break if it
is buggy, making things easier to detect. With more bug reports, you
also get more context to examine and pin-point the defect. For
code whose defects an be easily and quickly detected, KISS.

Trying to keep things short requires elimination of redundant
code. This results in simplification of the underlying logic because
someone reading your code has to deal with lesser number of confusing
constructs likes loops and tests. Lesser amount of context needs to be
kept in the head while going through it. It is also beneficial to do
data transformation in less number of steps, each doing something
significant, to keep things clear. To simplify your logic,
KISS.

If done correctly, code elimination also leads to improvements in
the internal design of code and makes things modular. For example, if
you face several blocks of code, each with only minor variations, you
factor out the common parts into one unit that the others
depend on. This makes the unit that you have factored out more
"responsible" and allows other blocks of code to off-load their
responsibility to it. We all know the benefits of modular code, so I
will just say that, to have well structured code, KISS.

Write disposable code

Now we move on to slightly more subjective issues. My suggestion is
that when you write disposable code, you implicitly write maintainable
and extensible code. What I mean by disposable code is your program is
composed of a number of small, self-contained units such that any unit
may be replaced with another unit that does the same job and it
shouldn't required changes in other parts of the program. This is the
holy grail of Component Based Software Engineering, and the general
principle can be applied to micro level coding too.

When you try to make your code disposable, you end up writing
modular, cohesive code that has a high degree of
invariance. Modularity implies that your codebase is divided into neat
sections, each having its own set of responsibilities. Cohesivity
implies that related things appear together in your
codebase. Invariance implies that changes in one part of your codebase
do not require changes in other parts. Perfective maintenance and
behavioural enhancements then become easier because you don't have to
worry about introducing regression errors, unless the behavioural
change itself is incompatible with the previous behaviour.

Now that I have harped about all the virtues of writing disposable
code, what does it take to figure out that a maintainer would be too
hesitant to dispose off pagefuls of code because he can't be sure of
its repercussions? Joel Spolsky thinks that rewriting
from scratch is a bad idea
. Writing disposable code allows you to
get a lot done without resorting to a rewrite from scratch.

Don't mind obfuscating in favour of saving loc numbers

Okay, that was just to wake you up. Writing obfuscated code is a
crime against humanity. Often people get the wrong ideas about
obfuscation, however. Being terse is not obfuscation. Even in
activities other than programming, like public speech or daily
conversation, verbosity is not appreciated. Why then do you think (if
you do think that way) that being verbose in code is going to make
things clear? To me, it is more clear if you write something like:

return !call_a_function();

than something like:

int retval = call_a_function();
// If retval was zero we should return success (true)
if (retval == 0)
{
return true;
}
// Otherwise we should return failure (false)
else
{
return false;
}

Another thing to remember is that once you are done coding the
logic, you should do a second pass over what you have written. Usually
when you are not implementing a well defined algorithm, you tend to
evolve some logic as you code. Evolutionary coding of logic looks
clear and simple to the implementer because it is a reification of his
thought process. However, it isn't necessarily simple for someone who
is reading through it for the first time and trying to decipher
it. More often than not, such evolutionary code has redundant loops,
conditionals and unnecessary explicit temporaries, that get in a
reader's way of understanding the logic. Like Eric Raymond says in his
book, “The Art of UNIX Programming”, Chapter
1, Section 9
:

“You'll carelessly complicate when you should be
relentlessly simplifying - and then you'll wonder why your code
bloats and debugging is so hard.... You need to care. You need to
play. You need to be willing to explore.”

After you are done with cutting the flab off your logic, you can
get some more mileage if you know the style conventions of your
programming language. Every language has a bunch of idiomatic way of
doing things. Those idioms are very well recognised and their usage
makes your code no less clear. The most illustrative example of
idiomatic usage, perhaps, is the short-circuit behaviour of
“logical and” and “logical or”
operators. Use these style conventions and idioms as often as you
can. Of course, a lot also depends on the syntax of your programming
language .Some languages are more easy to obfuscate (Perl?) than
others (Python!). Though business logic is usually so simple that you
have to try hard to obfuscate its implementation, my personal
observation is that the above, seemingly counter-intuitive tips work
for algorithmic code as well.

When worse comes to worst, and you manage to obfuscate your code,
be good and make it disposable. The maintenance engineer who has to
deal with it (and that may very well be you), he can bug the local
language expert for deciphering it out. He might also offer advice on
how to rewrite it better, and teach you some other tricks. In the
extreme worst case, your code might have to be thrown away, so it
should be easily replaceable.

Tahir Hashmi 2005-01-07

programming, simple, geekdom, kiss

Previous post Next post
Up