Medallia Blog

Building Cathedrals

By Kristian Eide on October 27, 2006

Three people are working on laying down bricks. You ask the first what he is doing, and he answers 'I am laying down bricks'. You ask the second, which says 'I am building a wall'. Finally you ask the third who tells you 'I am building a cathedral'.

Great programs are created the same way as any other program, one brick at a time, but each brick has to be laid down in the knowledge that it will be part of something greater than the small piece currently being worked on. If it is not known what the final structure will look like it is even more important to make sure the individual pieces can be built upon and reused later, because most code will find itself used in situations where requirements change. Knowing how much to abstract a given piece of code is what separates a good from a great programmer.

All great programmers were at some point merely good, however, and there are many rules that can be used to get better (first you have to learn the rules, and then you learn when to break them). Here are a few I personally try to live by:

  • Duplicate code is the root of all evil. Duplicating any non-trivial piece of code (where trivial is e.g. calling the same function from different places) is a maintenance nightmare in addition to being a time bomb that blows up in the face of one of your customer when you forget to update one of the copies of the code.
  • Do defensive programming. Anticipate what can go wrong and try to detect it as early as possible. Use assertions liberally, but only for things which are the result of a programming error. If someone is using your API in the wrong way it is much better that the system explodes as compared to silently ignoring the error and then doing the wrong thing.
  • Write test cases and use them while developing the code. If you are working on a team this has the added benefit that if someone else makes a changes that breaks your code a test will fail and the burden of dealing with it falls on them instead of you having to spend time to track it down when it crashes in the field.
  • Validate all user input and give resonable error messages. Recognize what is good and throw away the rest; trying to list all that is bad is usually futile (imagine trying to list all the foods you do not like instead of the ones you know you like when telling someone what you want for dinner).
  • Avoid accessing list elements by index if there is no reason to; use an iterator (or the enhanced for-loop in Java 1.5) instead.
  • If you have two (or more!) lists/arrays where the elements at equal indices belong together, put then in the same object and use one list.
Much has been said both in favor and against strong typing, but, if you find yourself working in a language that has it, use it to your advantage as much as possible.
  • Avoid using strings as identifiers (TypeTags are your friends). If you have to use string identifiers (e.g. http request parameters) convert them into proper types as soon as possible. String identifiers have no type safety (e.g. you cannot use a refactoring tool to rename one) and are much slower.
  • Use a static type checking tool (many IDEs, like Eclipse, has one built in) as this can catch many errors as you are typing. Unused variables and using a reference which can be null are typical bugs that should be caught this way.
Most programmers often find themselves in situations where they are tempted to make a quick hack to get something done (a typical example is that a reference to a specific object is needed, but which is far away in the call graph; the quick hack is to add a direct reference, which adds an unnecessary dependency and brings the code one step closer to the infamous "spaghetti code" case where dependencies are going all over the place). It is that exact moment, when the decision is made, which determines what kind of programmer you are.
Show Trackbacks

Comments

1404

That 'quick hack now or think of the future' thing applies to languages too. I often get the feeling C# and Perl are top of their class in quick hacks. Anything that seems like a good idea gets implemented straight away without thinking of the consequences.

All very valid points, though one issue on the unit tests: I think they are sorely overrated, especially in a static language like java.

A lot of bugs (memory leaks, code/comment mismatch, race conditions) are impossible or difficult to find with unit tests. A lot of code is hard to test in general (All GUI stuff for one). There are certain types of code that really trive with a unit test around, but stating as generic rule that everything needs a unit test is over-reaching.

Latent typed languages can go all over the place - they basically have no compile-time (read: write-time) error checking, so a unit test is made to serve as the alternative - so for e.g. Python I'm more inclined to agree to make it a hard and fast rule.

Then again, if you're going to go through all that trouble, python loses most of its point anyway.

1405

Actually, I really do think unit tests, even in a strongly typed language like Java, are very useful. Let us say you are a developer on a team creating a complex web application in Java, and you need to add a new feature; how do you test that the new code you write works? Starting the whole application server locally takes about 30 seconds even in the best case due to initialization and database loading, and you have to do this every time you want to test even the simplest code change. Productivity suffers, and you quickly get tried of switching between your IDE and web browser.

If you instead write a unit test that does a minimum of initialization and does not load anything from the database (instead creating mock objects) it is very easy to test your code while you are writing it; in fact, you can even re-run the test automatically ever time you save a source file. Filling out a stub function and seeing the test turn from red to green makes programming much more fun (which is what the Ruby people are talking about, but why can not we Java people share some of the joy as well?).

I do agree that it can be hard to create good tests for some things, e.g. UIs (two buttons being misaligned is not worth the effort to try to test for in most cases). However, if the design is good the UI should just be a thin layer on top of an API which actually implements the logic, and the unit test will simply call some of those APIs and check the result. When the QA people find “bugs” they will usually be the responsibility of the web designers, and you can keep writing new code instead of staring at what you did yesterday.

Finally, Eclipse actually supports injecting code into a running JVM (though even Java 1.6 does not support adding methods on the fly, so it is somewhat limited), which can be very useful if you do need to debug a running application.

1406

"If you have two (or more!) lists/arrays where the elements at equal indices belong together, put then in the same object and use one list."

"I often get the feeling C# and Perl are top of their class in quick hacks."

On that note, I find it suggestive that C# has an Array.Sort(array, array) method to sort exactly such pairs of arrays, and Java does not :)

As for unit testing, the main benefit I get is that they force me to design in such a way that it is actually possible to do unit tests (you can't unit test spaghetti) Also writing some "tests" for a subsystem that doesn't exist yet is a nice way to figure out if the interface I had in mind will actually be usable.

I wish there were a better word for "test", though. Unit tests are a completely different beast than 'watching the code run', but both can be called testing. Any suggestions? :)

1411

"All very valid points, though one issue on the unit tests: I think they are sorely overrated, especially in a static language like java."

I think it's in the other way around: unit tests are consistently underestimated. Check out any of the unit test tutorials out there and, more often than not, you will find unit testing explained on the class Operations. In particular, the really difficult and magical int add( int a, int b) method. Then you think: what's the purpose of this? And to help you answer the question, you stumble across the typical getter/setter unit test example. :(

Unit testing helps me think about my problem, at least, in two different ways: I solve it at the interface level, then at the implementation level. It makes my solutions more thorough; catching more corner cases than if I were just focusing on the implementation details without writing any test.

As a friend told me when I stated that making your code unit-testable biased your design: "You're right. It biases you to write code that's not only testable, but is also modular, and reusable." He was right.

3374

Good points all around. I would add that building test cases first then writing the code that implements the functionality is preferable. Think about it: at no time will you better understand the problem that you're trying to solve than at the time you are actually solving it and that makes you uniquely qualified to determine the pass/fail conditions. It takes a little discipline to get into this habit, but it will pay off.

The other strength of unit-testing is in preventing bugs from creeping into a project via the spaghetti code you mentioned. If, each time the application is launched, each unit is evaluated. Now, a junior programmer adds a hack which causes some far-away section of the system to break due to a side-effect. Normally, nobody discovers this new problem until system testing, or (worse) after the product ships. However, with unit testing you instantly know two very critical things 1) there is a bug and 2) the code that was just changed is the source of that bug, either directly or indirectly. I know it sounds simplistic, but this is HUGE. Knowing either is terrific, knowing both is awesome.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)