Implementation tips

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. --Martin Fowler [ source]

Overview

Whether you like coding or hate it, these tips will help you to successfully complete the implementation tasks assigned to you.

Tips

Write self-explanatory code, not excessive comments

Some students think writing excessive amounts of comments will increase the "code quality". This is not so.

Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’ Improve the code and then document it to make it even clearer.
--Steve McConnell
[ source]

Avoid writing comments to explain bad code. Try to refactor the code to make it self-explanatory.

Avoid unnecessary comments. Do not use comments to repeat what is already obvious from the code. If the parameter name already clearly indicates what it is, there is no need to repeat the same thing in a comment just for the sake of writing "well documented" code.

However, you might have to write comments occasionally to help someone who reads the code. Do not write such comments as if they are private notes to self. Instead, write them well enough to be understandable to another programmer.

Write simple code

Often simple code runs faster. Do not dismiss a brute force yet simple solution too soon and jump into a complicated solution for the sake of performance, unless you have proof that a more complex solution has a sufficient performance edge. As the old adage goes, KISS (keep it simple, stupid - don't try to write clever code)

Write code for human readers

Programs must be written for people to read, and only incidentally for machines to execute. --Abelson and Sussman [ source]

In particular, always assume anyone who reads the code you write is to be dumber than you (duh). This means you need to put in extra effort to make the code understandable to such a person. The smarter you think you are compared to average programmers (in this case, your teammates), the more effort you need to make the code understandable to an average programmer.

Write code that reads like a story

Just like we use section breaks, chapters, and paragraphs to organize a story, use classes, methods, indentation, and line spacing in your code to group related segments of the code.

Sometimes the correctness of your code does not depend on the order in which you perform certain intermediary steps. Nevertheless, this order may affect the clarity of the story you are trying to tell. Choose the order that makes the story most readable.

Maintain a similar level of abstraction

A sure way to ruin a good story is to tell details not relevant to the story. Do not vary the level of abstraction too much within a piece of code.

[Bad]
  readData();
  salary = basic*rise+1000;
  tax = (taxable?salary*0.07:0);
  displayResult();
[Better]
  readData();
  processData();
  displayResult();

Clarify. Don't assume.

When in doubt about what the spec or the design means, always clarify with the relevant party. Do not simply assume the most likely interpretation and implement that in code. Furthermore, document the clarification or the assumption (if there was no choice but to make one) in the most *visible* manner possible, for example, using an assertion rather than a comment.

Choose defensive or DbC and stick with it

Choose Design-by-Contract (DbC) approach or defensive programming approach and stick to it throughout the code:

Do not release temporary code

We all get in to situations where we need to write a piece of code "for the time being" that we hope to dispose of or replace with better code later. Mark all such code in some obvious way (e.g., using an embarrassing print message) so that you do not forget about their temporary nature later. It is irresponsible to release such code for others' use. Whatever you release to others should be worthy of the quality standards you live by. If the code is important enough to release, it is important enough to be of production quality.

Heed the compiler

Instead of switching compiler warnings off, turn it to the highest level. When the compiler gives you a warning, act on it rather than ignoring it. Compilers (and compiler writers) are smarter than most of us. It is wise to listen to it when it wants to help us.

Give some thought to error handling strategy

Error handling strategy is often too important to leave to individual developers. Rather, it deserves some preplanning and standardization at team level.

Response to an error should match the nature of the error. Handle local errors locally, rather than throwing every error back to the caller. Some systems can benefit from centralized error handling where all errors are passed to a single error handler. This technique is especially useful when errors are to be handled in a standard yet non-trivial manner (e.g., by writing an error message to a network socket).

Have sufficient verbosity/interactiveness

An application should be sufficiently verbose/interactive so as to avoid the doubt "is the application stuck or simply busy processing some data?" For example, if your application does an internal calculation that could take more than a couple of seconds, produce some output at short intervals during such a long process(e.g., show the number of records processed at each stage) - this shows that the application is simply busy, and had not crashed.

Force the system to keep notes

Some bugs crops up only occasionally and they are hard to reproduce under test conditions. If such a bug crashed your system, there should be an easy way to figure out what went wrong. If your application writes internal state information to a log file at different points of operation, you can simply look at that log file for clues.

greet the user

Printing a welcome message at startup (or a splash screen, if your application has a UI) - showing some useful data like the version number - is not just good manners; it also makes our application look more professional and helps us catch problems of "releasing an outdated version"

Don't forget the shutdown

The ability to do a clean shutdown is an important part of a system functionality most students seem to forget, especially, if there is some to be done during the shutdown such as saving user preferences or closing network connections.

Minimize global variables

Using global variables creates implicit links between code that uses the global variable. Avoid them as much as possible.

Avoid magic numbers

Avoid indiscriminate use of numbers with special meanings (e.g., 3.1415 to mean the value of mathematical ratio PI) all over the code. Define them as constants, preferably in a central location.

[Bad]     return 3.14236;
[Better] return PI;

Along the same vein, make calculation logic behind a number explicit rather than giving the final result.

[Bad]     return 9;
[Better] return MAX_SIZE-1;

Imagine going to the doctor's and saying "My nipple1 is swollen"! Minimize the use of numbers to distinguish between related entities such as variables, methods and components.

[Bad] value1, value 2
[Better] originalValue, finalValue

A similar logic applies to string literals with special meanings (e.g., "Error 1432").

Avoid duplication

The pragmatic programmer calls this the DRY (Don't Repeat Yourself) principle. Code duplication (especially, when you copy-paste-modify code) often indicates a poor quality implementation. While it is not possible to have zero duplication, always think twice before duplicating code; most often there is a better alternative.

Make the default path the prominent path

The default path of execution (i.e., the path taken if everything goes well) should be clear and prominent in your code. Someone reading the code should not get distracted by alternative paths taken when error conditions happen. One technique that could help in this regard is the use of guard clauses.

Stick to the given terminology

Most students invent their own set of terminology to refer to things in the problem domain. This terminology eventually makes it to the program, as class/function/variable names. Instead, stick to the terms used in the problem domain. For example, if the specification calls a component of the system "the projector", do not refer to the same component as "the visualizer" in your code. While the latter may be a better match for what that component actually does, it could cause unnecessary confusion (and much annoyance) for the evaluator/supervisor/customer.

Refactor often

Since you are rather inexperienced in programming, no one expects you to write quality code in the first attempt. However, if you decided to live with messy code you produced, it will get messier as time goes on (have you heard about the "broken windows theory"?).  The remedy is refactoring. Refactor often to improve your code structure.

Throw away the throw away prototype

A 'throw-away prototype' (not an 'evolutionary prototype), is not meant to be of production quality. It is a quick and dirty system you slapped together to verify certain things (e.g., to verify the architectural integrity). It is meant to be thrown away and you are supposed to keep only the knowledge you gained from it. Having said that, most of you will find it hard to throw the prototype away after spending so much time on it (because of your inexperience, you probably spent more time on the prototype than it really required). If you really insist on building the real system on top of the prototype (i.e., treating what was meant to be a throw-away prototype now as an evolutionary prototype), devote the next iteration to converting the prototype to a production quality system. This will require adding unit tests, error handling code, documentation,  logging code, and quite a bit of refactoring.

Fix before you add

Most bugs are like needles in a haystack. Do not think piling more hay will make them easier to find. It is always preferable to find and fix as many bugs as you can before you add more code.

Never release broken code

Before you release your own code, checkout the latest code from others, do a clean build, and run all the test cases. The biggest offense you can do while coding is releasing broken code to code base.

Even if it was a tiny change and it could not have possibly broken anything, do all of the above anyway, just to be sure.

Define, and follow, conventions

Appoint someone to oversee the code quality and consistency in coding style(i.e., a code quality guru). He/she can choose a coding standard to adopt (for most languages, there are several coding standards floating around). Prune it if it is too lengthy - circulating a 50 page coding standard will not help.

A coding standard does not specify everything. You will have to come up with some more conventions for your team e.g. naming conventions, how to organize the source code, how to do integration testing etc.  This also includes high-level style guidelines such as "all open resources must be closed explicitly inside the finally block" and "no empty catch blocks".

But remember, conventions only work only if the whole team follows them. You can also look for tools that help to enforce coding conventions.

Don't program by coincidence

You code a little, try it, and it seems to work. You code some more, try it, and it still seems to work. After many rounds of coding like this, the program suddenly stops working, and after hours of trying to fix it, you still cannot figure out why. That's programming by coincidence. You code worked up to now by coincidence, not because it was 100% correct.

Instead, code a little, test it thoroughly, before coding some more. See here for more advice on testing.

Do an early code-review

Hold at least one code-review session at an early stage of the project. Use this session to go over each others code. Discuss different styles each one has adopted, and choose one for the whole team to follow. Warning: things can turn ugly in this discussion!

Stop when the code is "good enough"

There is no such this as "perfect" code. While you should not release sloppy software, do not hold up progress by insisting on a yet another minor improvement to an already "good enough" code.

Sign your work

To quote from The pragmatic programmer: We want to see pride of ownership. "I wrote this, and I stand behind my work."

By making you put your name against the code your wrote (e.g., as a comment at the beginning of the code), we encourage you to write code that you are proud to call your work. This is also useful during grading the quality of work by individual team members.

Have fun

To most of us, coding is fun. If it is not fun for you, you are probably not doing it right.

Further resources

Grading tips

A bad job of coding is a sure path to a low grade (but a good job of coding does not necessarily mean a good grade).


Refactoring is the process of changing code in such a way that it does not alter the external behavior of the code, yet improves its internal structure. Therefore, adding functionality, or bug fixing, is not refactoring.