OJ’s rants What would OJ do?

20Jul/0710

Avoid Writing Unintuitive Code

This blog post was inspired by a brief chat I had recently with Kirupa of kirupa.com. I subscribe to his blog's RSS feed as he comes out with some really good stuff. His recent post which showed a way of shuffling a List of strings (using C#) inspired a bit of thought on the topic of code readability, how and when it's learned (if at all) and why there is so little of it around.

Code quality and readability is something that isn't necessarily learned at University, nor is it something that can be mastered in a short period of time. It is something that anyone can learn. The main ingredients that are required are a bit of self-scrutiny, and the removal of the assumption that working code is the same as finished code.

Let me start by applauding Kirupa for the effort he puts into his posts, and the level of quality he achieves. This is by no means a stab at his work. I'm grateful that he's happy to discuss how his code can be improved. It's rare to find someone who's open to constructive criticism! Like me, he appears to be keen to learn from other people - if only there were more of those people around!

To avoid confusion, I think I should clarify exactly what I mean by "Unintuitive Code". In a nutshell, what I mean is: Code which fails to imply its purpose/functionality through poor use of language features, semantics, structure, naming conventions and natural language. Phew!

I'd like to share my thoughts on the steps that you can take to aid in writing more readable and maintainable code, developing interfaces to objects that are intuitive, and imply the right kinds of things with regards to the way the functions operate.

Solve the problem first

This doesn't mean write code! This means get a pen and paper, or a white board and a marker! Write down what you think the problem really is and break it into smaller chunks which can be broken down into a set of logical steps. When you give yourself a clearer understanding of what the problem is you'll increase the chance of writing more meaningful code with a much clearer interface.

When you've broken the problem down into smaller chunks, it's highly likely you'll then find issues that you wouldn't have seen or considered if you'd just fired up your code editor first. This is good because you've now got a greater understanding of the problem space, and can now create a more meaningful and appropriate code architecture while keeping in mind the issues that you've already stumbled across. The amount of "coding in the dark" you have to do has already been minimised.

At this point you've already prevented the need to refactor your code before you've even finished the first iteration. Your interfaces will be cleaner, and the number of 'hacks' that you'll have to put in to fix things you hadn't initially thought of will be substantially reduced. Don't underestimate how valuable a bit of up-front analysis and design can help with keeping your code clean.

Once you're done with improving your understanding of the problem space, you're ready to open your editor and start writing code. Writing defensively from the start is a good idea, particularly if the code is going to end up being maintained by someone else.

Write your first iteration

Put your ideas down in code. Try your best to make an intuitive model, using concrete objects where applicable, trying your best to reduce coupling, and separating implementation from interface. While this is a key phase, it's not as key as the next one! Make sure that when you're done writing your first version, you prepare yourself to be happy to rip out a large portion of what you write - because you're going to be replacing it with something better.

Call a spade a spade

If you want the marks, state the bloody obvious! That is, your funtions should state exactly what they do. I'm not suggesting that you have function names like GetAReferenceToTheNormalVectorAndUseItToCalculateTheLightingForThePolygon() as that's a bit over the top. Try to avoid names like DoStuff(), ProcessData(), DoEvents() (yes, that's a stab at you, Microsoft) and GetDoohicky(). Names like CalculateLighting() and GetNormal() are descriptive enough and do a good job of implying their function. Don't mislead the user of your class by using names that quite clearly suck.

Review your interfaces and functions

There's a strong possibility that even though you've put a great deal of thought into your solution, you've still managed to imply certain things that you didn't necessarily mean.

The interface to an object and the signature of your functions can (and should) say more about your code than you think. So ask yourself the following:

  • Does your object name reflect the functionality that it encapsulates?
  • Does the function imply operations that don't happen?
  • Does the function not imply functions that do happen?
  • Is the function's name descriptive of the type of function that it actually is?
  • Does your function return a meaningful value based on the context?
  • Does your function require intuitive parameters for it to function?
  • Is the parameter list as minimal as possible without affecting the functionality?

I'm sure there are more, but I can't think of them off the top of my head.

When you've finished with your review, ask someone else to have a read of it. Ask them to give you a brief overview of your classes/functions as if you didn't know the code and they did. If they give a pretty good picture of what the object does, then you've done well. If they can't tell you what goes on at a high-level then you'll have to go through another review iteration.

Add comments that clarify, and remove comments that don't

I'm almost certain that every maintenance coder on the planet has been faced with the nasty combination of confusing code and invalid comments. It not only doesn't help, it makes things worse. Here is an example of a "WTF comment":

/* this class handles date formats */
class Profile
{
  // code goes here
};

If you didn't say "WTF!?" to the above code, then please go to the start of this post, and reread!

Iterate

Make sure that you don't stop working when the code starts working. Working code is not the same as finished code. Keep reviewing your work. Look for possible improvements. Be critical of your own work. Treat your own code as if it was someone else's and treat the task as though the result is going to be shown to a stack of other developers.

Be receptive to post-release feedback

When your code gets used by other people, you'll end up with more suggestions and possible tweaks to make it less unintuitive. Don't be ignorant, even if the suggestions are petty. Take them on board and see if you can utilise those bits of feedback to aid in production of even better quality code!

Good luck :)

  • OJ
    I agree with you. I've said on more than one occasion (possibly even on this blog in the past) that the first iteration of your solution really serves only as a way for you to get a better understanding of the problem.

    Only when you have attempted it once will you really be able to give it a good shot the second time (if not the third). The first time we try and tackle a problem we don't really understand it 100% nor know of all the possible points of failure. Iteration is the future!

    It's a shame that it's rare to stumble across such pieces of code :(
  • Pardon? What were we talking about? :)

    You're right. It is easier to describe why a piece of code is not intuitive (or maintainable) than why it is. In fact, sometimes when you look over a piece of code and marvel at how good it is, it is sometimes difficult to articulate just why it is so much better than say ... something produced by a code generator.

    Wouldn't you agree it's really presence of mind by the author? The code shows that the author has given it much thought. Really good code you look at first and think, oh, I would not have done it like that. Then you realize the author of the code has already worked through your mistakes and did it that way as a result of much contemplation.

    That's why the very best code is almost always the product of a second effort. Usually it's that rewrite and cleanup that really nails it, cuts half the lines out and avoids trying to do things the object really shouldn't be handling.
  • OJ

    Yes, the double negative is sufficiently obtuse.


    If you're talking about the title of the post then I don't agree :) There is a subtle but important difference between "Writing Intuitive Code" and "Not Writing Unintuitive Code". The point: it's easier to explain what's not intuitive rather than what is. Intuition varies a lot, but most people can agree on a lowest common denominator when it comes to being bad/unintuitive.
  • Yes, the double negative is sufficiently obtuse.

    If you want to write intuitive code, think about what you are doing. See the big picture.

    Imagine being so literal minded you think "object oriented programming" means using a tool like Codesmith to generate a thousand objects, each a type specific class representing an object in your application, each tied to another thousand lines of autogenerated code to select, insert and update your object in the DBS. Is it possible to move farther away from good code without losing the topical appearance of providing the requested solution.

    "Bossman, you said you wanted a financial application and you listed 100 types of data in your spec, so I generated 100 objects all with data access routines, then generated all the front end pages with Ironspeed. My mom thought I should go into dentistry, but I heard there was more money in this IT thingamajiggy. I don't seem to actually be able to force my brain to think about the problem, but that doesn't mean I can't have a rich and rewarding career in IT by just pushing this generate button until my index finger collapses from strain injury. By the time people figure out I can't actually code, I'll be up for retirement. That works for me."

    Code Generators / Intuitive Code Design =
    AntiMatter / Matter. They are diametrical opposites. That's why I mentioned them.
  • OJ
    Grasshopper? :) I'll make an effort to stay on-topic and avoid commenting on that!

    So in short, your point was:

    Another way of avoiding unintuitive code is to not use code generators.

    Is that about right?
  • But grasshopper, this is because the noisy static of your own mind interferes with the sound of one tree falling in the woods on top of you with nobody to hear it.

    My point, on-topic in a Zippy The Pinhead kind of way, is that the opposite end of the spectrum from writing intuitive code is using code generators instead to avoid all that painful and messy thinking.
  • OJ
    I have to agree with Rob, as I'm failing to see the link between your comment and the original post (despite the fact that I agree with you!).
  • Seriously though Vault-Co: How is that even close to being on topic?

    I don't see how code generators could ever be accused of writing "intuitive" code as described in this article...
  • Code generators are a tool of Satan.

    I have realized it working on my current contract.

    Sure, a tool writes all your SQL basic routines for you ... in rigid typefixed inputs and outputs. If you change your database or anything else, you need to regenerate it all again.

    Seems like it's not so bad. Actually it's the worst.

    Code should be produced by a human somewhere thinking, really hard about the problem.

    For example, I discovered a couple years back you can replace 90,000 lines of templated SQL with five or six routines using loose coupling and referencing the xml descriptor of any named table in the DBS to autoload a generic container with the fields and values as properly typed objects.

    If you had to maintain that code, would you rather the Codesmith colossal brain-neutral mega files of 200,000 lines that crashes intellisense, or six routines in one class and a peek at the database, which is the proper place to make assumptions about the table and data structure, not the code?

    In my experience, a little real thinking is better than an army of code monkeys and thousands of dollars of generator tools. One guy who lives in his momma's garage and has never kissed a girl, but is willing to think, can replace armies of IT manbots. Honest, he can. Armies. One good thinking man is better than a million drones just using whatever shortcuts they can to give the illusion of progress with generators and templates.

    Seriously. For once.
  • Good post!

    Something I'd like to say about the planning phase though. I couldn't agree more that when you're working with a group of developers, nothing beats the good old white board or pen-n-paper routes, but lately I've been finding (especially as I work more and more on solo projects at home), that tools are getting better and better to use as part of planning all the time.

    For example, one of the big planning tools I use are mocks and unit tests applied in a TDD fashion. I will say though that this, however, takes quite a bit more discipline and I can't stress enough that you need to be just as prepared to delete "planning" code as you are to tear up the paper or wipe the board clean. I am, so therefore I find that it helps tremendously in the planning process because I kinda get to see my efforts working in a prototype fashion, without falling into the trap of thinking that this code is anywhere near complete. If you can be disciplined like that, I highly recommend it, because of a number of reasons:

    <ol><li>I find that I'm getting better and better all the time at "expressing" my intentions or requirements in code, which is not the same as expressing yourself on paper. This leads to more clear code and tighter classes in the first iteration.</li><li>It ends up in you writing more code. Writing more code means more practice. More practice means you get better and better. Getting better means your first iteration (step 2 above) is more accurate over time, because you don't fight with the language as much. Never underestimate the power of "writing code" versus reading an example that you understand "by sight" alone.</li><li>It caters to my impulsive need to put fingers to keyboard which often drags developers away from the white board too early. Now I incorporate it as part of planning and that itch is scratched!</li></ol>Don't get me wrong - sometimes I just have to stand up and back away before I get sucked into the black hole of confusion and I find I invariably head back to first principles: pen-n-paper.

    Of course, this in NO WAY negates the steps that follow - first iteration and onwards. Those are crucial! But, when working alone, I have found quite a bit of success using a sort of prototyping approach.

    My £0.02...
blog comments powered by Disqus