Good Code

We probably all like to think we write good code; so therefore, it follows that we like to think we know what good code is. But what is good code? Are there rules for determining how good some code is, and if there are rules for good and bad code then why doesn't everyone write good code? Why aren't these rules taught in colleges and universities? The problem is it is a difficult question to find an answer for which is both succinct and complete. For a start the question is vague[1], you may as well ask what is art or love.


A better place to start might be if ask "What is bad code?". Now this is a much easier question to answer ;) If I don't know what good code is certainly know what bad code is. We have all worked with 'bad code', and probably been guilty of writing some :o I have worked on more than my fair share of projects with bad code[2] and equally I'm sure I've written more than my fair share myself :/

So using my vast knowledge and experience of bad code as a reference, I have compiled a list of common faux pas :-



  • Style - bad code is hard to read

    Bad style is one of the most immediate coding horrors. Whilst what people consider 'good style' is largely subjective, there are definitely bad styles and bad habits as aptly demonstrated by the many obfuscated code contests.

    Conversely, good style is easy to read and understand. The intent of the code should be obvious and immediate and apply the Principle of Least Surprises. Maybe worse than bad style though is inconsistent style as it can potentially cause problems for 'grepping'[3].


  • Design - bad code is badly designed

    Bad design complicates changes, bug fixes or enhancements. Bad design may not be intentional; as projects grow there is a tendency for them to turn into a big ball of mud[4]. Whether intentional or not, some common 'design' related difficulties are ...


    • logic duplication or 'de-coupling' - meaning you need to change many files in many different places to make simple changes

    • intricate dependencies amongst files and objects which results in classes and code being difficult to re-use or refactor.

    • over complication - interfaces and objects that have an unnecessarily high overhead in implementing.


    Good code should be well designed. It should implement things in the most straight forward and accessible way. Interfaces and 'ownership' should be intuitive. Code and 'logic' should be modular and coupling avoided where possible.

    "A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.",
    Antoine de Saint-Exupery, Aviator.


  • Execution - bad code does not work

    No matter how good the design or the style, the code needs to work. Confidence is undermined if the code is riddled with bugs. Buggy code is harder to fix, as whilst you might think you are fixing one bug you could just be fixing the symptom of another. Additionally programs that behave erratically due to bugs (such as memory corruption caused by a buffer overflow or dangling pointer for example) are more difficult to debug than a simple logical error.

    Code should work. All known bugs should be fixed.


  • Specification - bad code is not suitable for its intended use

    'Good' of course, is a relative word. What makes something good in one context does not necessarily follow in another. Different projects and platforms have different requirements. It is no good writing a master crafted opus of software engineering if it won't fit into memory, executes too slowly or fails on stability, safety or any other criteria.



  • Stability - bad code is fragile

    Code should be robust. Slight changes to data or the code base should not cause the program to crash. Errors should be handled gracefully.


  • Debugging - bad code is difficult to debug

    Here's an example ...

    mpLayer = mpScreen->AddLayer(
    FECK2::CLayer::Create()
    .Name( "NonmodalControlLayer" )
    .Depth( 10000 )
    .EventDeclaration( new CLEFunctor( this, &FECK_LAYER_EVENT_FUNCTION_NAME( CFENonmodal, EE_DECLARE_HANDLERS ) ) ) );


    Not only is this an unusual and quirky way of initialising an object but makes it difficult to step into any one of the individual functions. At first glance it is not obvious where the execution will go. Generally, function calls in chains ( id est x()->y()->z(); ) should be avoided.

    Another debugging nightmare is objects or data that are obscured by some abstraction. For example, void pointers, CRC's instead of strings and opaque interfaces (COM?) can make identifying objects in the debugger difficult or impossible.

    Debugging is an integral part of programming and good code should not make it difficult.



As a list of 'bad things' you can do with code I think that covers all the important bases. Certainly many of the particular horrors that I have encountered. So maybe the answer to 'what is good code' is just 'not bad code'.

Corollary, from our list we could define good code as :-

  • well presented
  • having a good design which is as only as complex as it needs to be
  • easy to enhance or repurpose
  • fulfilling its intent within the given constraints
  • stable, robust and with no known bugs
  • friendly to all the usual tools we use to work with code (debuggers, grepping, etc)


This is probably an acceptable (and maybe even workable answer[5]). Following these guidelines we can be pretty sure of not writing any bad code. But as an answer to the question of 'what is good code' it is not very satisfying. It feels there should be more to the art of programming than just following a set of do's and dont's. Not writing bad code should be the norm - and therefore 'average' code. There must be something else that separates good code and lets it shine above the rest.

In mathematics we have the notion of 'elegance', which in my view can be equally applied to code. In fact, if we search the wikipedia for elegance we find the following quote ...

"The proof of a mathematical theorem is considered elegant if it is surprisingly simple yet effective and constructive; similarly, a computer program or algorithm is elegant if it uses a small amount of intuitive code to great effect."

http://en.wikipedia.org/wiki/Elegance

And this to me captures the essence of what separates good code from the reset. Good code is elegant, beautiful and a joy to work with. It solves difficult and intricate problems in simple, clever and intuitive ways.

But as a practical answer it is probably not very useful. It is not always obvious why a piece of code is good or elegant without an appreciation of the problem and the many bad solutions that have been applied. Elegance isn't something that can easily be defined or something you can achieve by just blindly following a set of rules. But then again, it's probably fitting that the answer is just as vague and nebulous as the question ;)




[1] Naturally I can not possibly be at fault, blame for my inability to come up with a suitable response must lie squarely with the question ;)

[2] In fact some I would go as far as saying had 'horrendous code'. I remember one project which was using goto's to jump in between cases in different switch statements *shudder* ... I still have nightmares ;)

[3] The humble 'grep' (or "Find in Files" for VC users), despite the many advances in IDE's is imo still one of the most useful tools available to programmers. So therefore good code should always be 'grep' friendly imo.

[4] Big Ball of Mud, Brian Foote and Joseph Yoder, Department of Computer Science University of Illinois, http://www.laputan.org/mud/mud.html

[5] Workable in the sense of gleaning some kind of metric or as a basis for objective code critique.

Comments

Jamie said…
Agree with 80% here, only thing I'm not sure about is function chains.
x()->y()->z() is only syntactically different from x( y( z())), which is functional programming in a nutshell.
I used to agree that I wanted to be able to step through in a debugger - I even used to make my programmers break
if( x && y && z && q ) into
if(x) {
if(y) {
if(z) { etc...
But since then - debuggers abilities to evaluate expressions have gotten better, and I've become a functional nut.

Popular posts from this blog

Game Development in a Post-Agile World

Comments

Polyphasic Sleep - Dymaxion Day 1