Chapter 11: Implementation

      1. 11.1 Refactor often
      2. 11.2 Name well
      3. 11.3 Be obvious
      4. 11.4 No misuse of syntax
      5. 11.5 Avoid error-prone practices
      6. 11.6 Use conventions
      7. 11.7 Minimise global variables
      8. 11.8 Avoid magic numbers
      9. 11.9 Throw out garbage
      10. 11.10 Minimise duplication
      11. 11.11 Make comments unnecessary
      12. 11.12 Be simple
      13. 11.13 Code for humans
      14. 11.14 Tell a good story
      15. 11.15 Do not assume. Clarify. Assert.
      16. 11.16 Choose an approach and stick with it
      17. 11.17 Do not release temporary code
      18. 11.18 Heed the compiler
      19. 11.19 Strategise error handling
      20. 11.20 Have sufficient verbosity
      21. 11.21 Fly with a black box
      22. 11.22 Greet the user, but briefly
      23. 11.23 Shutdown properly
      24. 11.24 Stick to the domain terms
      25. 11.25 Throw away the prototype
      26. 11.26 Fix before you add
      27. 11.27 Never check-in broken code
      28. 11.28 Do not program by coincidence
      29. 11.29 Read good code
      30. 11.30 Do code reviews
      31. 11.31 Stop when 'good enough'
      32. 11.32 Create installers if you must
      33. 11.33 Sign your work
      34. 11.34 Respect copyrights
      35. 11.35 Have fun
    1. 'HOW TO' SIDEBAR 11A: How to achieve non-functional qualities
      1. 11.36 Reliability
      2. 11.37 Robustness
      3. 11.38 Maintainability
      4. 11.39 Efficiency
      5. 11.40 Security
      6. 11.41 Reusability
      7. 11.42 Testability
      8. 11.43 Scalability
      9. 11.44 Flexibility/Extensibility
      10. 11.45 Usability

 
Any fool can write code that a computer can understand. Good programmers write code that humans can understand. --Martin Fowler [in the book Refactoring: Improving the Design of Existing Code]

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. Here are some tips to ease your implementation pains.

11.1 Refactor often

Since you are rather inexperienced in programming, no one expects you to write quality code in the first attempt. However, if you decide to live with messy code you produce, it will get messier as time goes on (have you heard about the 'broken windows theory' [http://tinyurl.com/refactoringstory] ?).  The remedy is refactoring [http://tinyurl.com/code-refactoring]. Refactor often to improve your code structure.

Refactoring code regularly is as important as washing regularly to keep your privates clean. Neglecting either will result in a big stink. Analogies aside, any time spent on refactoring will not only reduce your debugging time, the quality improvement it adds to your code could even earn you extra credit.

11.2 Name well

Proper naming improves the code quality immensely. It also reduces bugs caused by misunderstandings about what a variable/method does. Here are some things to consider:

11.3 Be obvious

We can improve understandability of the code by explicitly specifying certain things even if the language syntax allows them to be implicit. Here are some examples:

11.4 No misuse of syntax

It is safer to use language constructs in the way they are meant to be used, even if the language allows shortcuts. Here are some examples:

11.5 Avoid error-prone practices

Some coding practices are notorious as sources of bugs, but nevertheless common. Know them and avoid them like the plague. Here are some examples:

11.6 Use conventions

When working in a team, it is very important for everyone to follow a consistent coding style. Appoint someone to oversee the code quality and consistency in coding style (i.e. a code quality guru). He can choose a coding standard to adopt. You can simply choose one from many coding standards floating around for any given language. Prune it if it is too lengthy - circulating a 50-page coding standard will not help. But note that a coding standard does not specify everything. You still have to come up with some more conventions for your team e.g. naming conventions, how to organise the source code, which 'error-prone' coding practices to avoid, how to do integration testing, etc.

Conventions work only if the whole team follows them. You can also look for tools that help you enforce coding conventions.

11.7 Minimise global variables

Global variables may be the most convenient way to pass information around, but they do create implicit links between code segments that use the global variable. Avoid them as much as possible.

11.8 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"! Minimise the use of numbers to distinguish between related entities such as variables, methods and components.

[Bad] value1, value2
[Better] originalValue, finalValue

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

11.9 Throw out garbage

We all feel reluctant to delete code we wrote ourselves, even if we do not use that code any more ("I spent a lot of time writing that code; what if we need it again?"). Consider all code as baggage you have to carry; get rid of unused code the moment it becomes redundant. Should you eventually need that code again, you can simply recover it from the code versioning tool you are using (you are using one, are you not?). Deleting code you wrote previously is a sign that you are improving.

11.10 Minimise duplication

The Pragmatic Programmer book 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.

11.11 Make comments unnecessary

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 

Some students think commenting heavily increases the 'code quality'. This is not so. Avoid writing comments to explain bad code. Try to refactor the code to make it self-explanatory.

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 to help understand the code more easily. Do not write such comments as if they are private notes to self. Instead, write them well enough to be understandable to another programmer. One type of code that is almost always useful is the header comment that you write for a file, class, or an operation to explain its purpose.

When you write comments, use them to explain 'why' aspect of the code rather than the 'how' aspect. The former is not apparent in the code and writing it down adds value to the code. The latter should already be apparent from the code (if not, refactor the code until it is).

The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague. --Edsger Dijkstra

11.12 Be simple

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

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.
--Brian W. Kernighan

11.13 Code for humans

In particular, always assume anyone who reads the code you write is dumber than you (duh). This means you need to make the code understandable to such a person. The smarter you think you are compared to your teammates, the more effort you need to make your code understandable to them. You might ask why we need to care about others reading our code because, in the short span of a class project, you can always be there to handle your own code. Here are some good reasons:

If you think the above reasons are good enough to try and write human-readable code, here are some tips that can help you there:

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

11.14 Tell a good story

Lay out the code to follow the logical structure of the code. The code should read like a story. Just like we use section breaks, chapters and paragraphs to organise a story, use classes, methods, indentation and line spacing in your code to group related segments of the code.  For example, you can use blank lines to group related statements together.

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.

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();

11.15 Do not assume. Clarify. Assert.

When in doubt about what the specification 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 [http://tinyurl.com/wikipedia-assertion] rather than a comment. You can use assertions to document parameter preconditions, input/output ranges, invariants, resource open/close states, or any other assumptions.

11.16 Choose an approach and stick with it

Choose the Design-by-Contract (DbC) [http://tinyurl.com/wikipedia-dbc] approach or the defensive programming [http://tinyurl.com/wikipedia-dp] approach and stick to it throughout the code:

11.17 Do not release temporary code

We all get into situations where we need to write some 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.

11.18 Heed the compiler

Instead of switching off compiler warnings, turn it to the highest level. When the compiler gives you a warning, act on it rather than ignore it. A compiler is better at detecting code anomalies than the most of us. It is wise to listen to it when it wants to help us.

11.19 Strategise error handling

The error handling strategy is often too important to leave to individual developers' discretion. Rather, it deserves some pre-planning and standardisation 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 centralised 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).

11.20 Have sufficient verbosity

A sufficiently verbose/interactive application avoids 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).

11.21 Fly with a black box

Some bugs crops up only occasionally and they are hard to reproduce under test conditions. If such a bug crashes your system, there should be a 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. This is the same reason why an aircraft carries a black box (aka Flight Data Recorder).

11.22 Greet the user, but briefly

Printing a welcome message at startup (or a splash screen, if your application has a GUI) - 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'. However, such preliminaries should not take too long and should not annoy the user. 

11.23 Shutdown properly

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

11.24 Stick to the domain terms

Most students invent their own set of terminology to refer to things in the problem domain. This terminology eventually makes it to the program code, documentation, and the UI. Instead, stick to the terms used in the problem domain. For example, if the specification calls a component the 'projector', do not refer to the same component as 'the visualiser' 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.


Programs should be written and polished until they acquire publication quality. --Niklaus Wirth 

11.25 Throw away the prototype

A 'throw-away prototype' (not to be confused with an 'evolutionary prototype') [http://tinyurl.com/wikipedia-prototyping] is not meant to be of production quality. It is a quick-and-dirty system you slapped together to verify certain things such as the architectural viability of a proposed product. 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. starting to treat a throw-away prototype 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 [see tip 11.21], and quite a bit of refactoring [see tip 11.1].

11.26 Fix before you add

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

11.27 Never check-in broken code

Before you release your own code, check-out the latest code from others, do a clean build, and run all the test cases (having automated test cases will be a great help here). The biggest offense you can do while coding is releasing broken code to the code base.

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

11.28 Do not 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. This approach is what some call 'programming by coincidence' [http://tinyurl.com/prog-by-coin]; your code worked up to now by coincidence, not because it was 100% correct.

Instead, code a little, test it thoroughly, only then you code some more. See chapter 13 for more tips on testing.


... nearly everybody is convinced that every style but their own is ugly and unreadable. Leave out the "but their own" and they're probably right... --Jerry Coffin (on indentation) 

11.29 Read good code

How can you write a great novel, if you have not even read any great novels? How can you write great code if you have not even seen any great code? Take a look at 'good code' written by someone else. This easiest way to find a good code sample is to dig into a well-established open source project. Even if you cannot understand what that code does, at least you will know what well-written production code looks like.

11.30 Do code reviews

Code reviews promote consistent coding styles within the team, give better programmers a chance to mentor weaker ones, and motivate everyone to write better code.

Hold at least one code-review session at an early stage of the project. Use this session to go over each other's code. Discuss the different styles each one has adopted, and choose one for the whole team to follow.

11.31 Stop when 'good enough'

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

11.32 Create installers if you must

Having an installer gives you several advantages, one of which is creating a more 'professional' impression of your software. There are several tools available today that can create an installer for your software very easily. But note that one major disadvantage of installers is some potential users might resist installing 'untested' software for the fear of 'slowing down the computer' or 'corrupting the registry'. If your software can do without a installer, then it is best not to have one.

11.33 Sign your work

To quote from book 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 own work. This is also useful during grading the quality of work by individual team members.

11.34 Respect copyrights

This is especially important if you release your product to the public. Whenever you reuse anything in your product, make sure the owner has explicitly granted others the right to reuse it. Just because you found it floating on the Internet does not mean you have the right to reuse it. This applies to, but not limited to, images, videos, sound tracks, code samples, libraries, and even terminology. 

11.35 Have fun

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

'HOW TO' SIDEBAR 11A: How to achieve non-functional qualities

The bitterness of poor quality remains long after the sweetness of meeting the schedule has been forgotten. --Anonymous

Functional correctness is usually the most important quality of a system. To achieve functional correctness, we should understand the specifications well (we need to know what is the 'correct' behaviour expected in the first place).

In addition, a system is required to have many non-functional qualities (i.e. qualities that are not directly related to its functionality), some explicitly mentioned, some only implied. The nature of the system decides which qualities are more important. Find out the relative importance of each quality, as you might have to sacrifice one to gain another. There are many to choose from such as Analysability, Adaptability, Changeability, Compatibility, Configurability, Conformance, Efficiency, Fault tolerance, Installability/uninstallability, Interoperability Learnability, Localizability, Maintainability, Portability, Performance, Replaceablity, Reliability, Reusability, Security, Scalability, Stability, Supportability, Testability, Understandability, Usability.

Correctness is clearly the prime quality. If a system does not do what it is supposed to do, then everything else about it matters little. --Bertrand Meyer

Most grading schemes have some marks allocated for certain NF qualities. Note that some NF qualities are not easily visible during a product demo. Be sure to talk about them during the presentation, and in the report. Given next are some common NF qualities and some tips on achieving each.

11.36 Reliability

The unavoidable price of reliability is simplicity.  -- C.A.R. Hoare
You need to show that your system can perform without failure for the specified operating environment. If the system is expected to run for a long period, you should ensure that it indeed can. For example, monitor the memory usage while the system is in operation; if it keeps increasing, your system might have a memory leak, which will crash it eventually.

Simplicity is prerequisite for reliability --Edsger W.Dijkstra

11.37 Robustness

Can your system withstand incorrect input? Will it gracefully terminate when pushed beyond the operating conditions for which it was designed? Does it warn the user when approaching the breaking point or simply crash without warning? If you can crash your system by providing wrong input values, then it is not a very robust system.

Software and cathedrals are much the same - first we build them, then we pray. --Anonymous

11.38 Maintainability

Do not think maintainability is unimportant because this is just a class project or because the instructor did not mention it specifically. The truth is that you will 'maintain' your code from the moment you write it. You will really regret writing unmaintainable code especially during bug-fixing and modifying existing features towards the end of the project.

Keeping the design and eventually, its implementation, simple and easy-to-understand can really boost the maintainability of your system. A comprehensive suite of automated test cases can detect undesirable ripple effects of a change on the rest of the system. Having such tests indirectly helps maintainability.

Always code as if the person who ends up maintaining your code will be a violent psychopath who knows where you live. -- Anonymous

Maintainability is of utmost importance if you plan to release the product to the public and keep it alive after the course. 

11.39 Efficiency

As software engineers, we should always give due consideration to the efficiency of what we are building. It becomes especially important if multiple teams are building similar products and more efficient products stand to gain extra marks. Here are some guidelines you can consider adopting.

11.40 Security

Security is a prime concern for most systems, especially for multi-user systems with open access (such as Internet-based applications). If security is a top priority for your system, make sure you have it built into the system from the very beginning.  It is much harder to secure a system built based on an unsecure architecture.

Before software can be reusable it first has to be usable. --Ralph Johnson 

11.41 Reusability

Under this aspect, you can address the following questions:

If you have a lot of duplicate code in your system (some code analysers can easily find this out for you), you have not reused code well.

If you can't test it, don't build it. If you don't test it, rip it out. --Boris Beizer, in the book 'Software testing techniques'

11.42 Testability

Testability does not come by default; some designs are more testable than others. To give a simple example, methods that return a value are easier to test than those that do not. If you give up testing some parts of your system because it is too difficult to test, you have a testability problem. If you have good testability, it is possible to test each part of your system in isolation and with reasonable ease. During integration, a system with good testability can be tested at each stage of the integration, integrating one component at a time.

11.43 Scalability

How well can your system handle bigger loads (bigger input/output, more concurrent users, etc.)? Does the drop in performance (if any) reasonably reflect the increase in workload?

Every program has (at least) two purposes: the one for which it was written, and another for which it wasn't. --Alan J. Perlis

11.44 Flexibility/Extensibility

How easily can you change a certain aspect of your system? For example, can you change the database vendor or the storage medium (say, from a relational database to a file-based system) without affecting the rest of the system? Can you replace a certain algorithm in your system by replacing a component?

How easily can you extend your system (to handle other types of input, do other types of processing, etc.)? For example, can you add functionality by simply plugging in more code, or do you need extensive changes to existing code to add functionality?

We have to stop optimizing for programmers and start optimizing for users. --Jeff Atwood

11.45 Usability

Does the usability of your system match the target user base? Is the operation intuitive to the target user base? Have you produced good user manuals? Does your system let users accomplish things with a minimal number of steps? Tip 10.19 can help you further in this aspect.

 

Giving feedback

Any suggestions to improve this book? Any tips you would like to add? Any aspect of your project not covered by the book? Anything in the book that you don't agree with? Noticed any errors/omissions? Please use the link below to provide feedback, or send an email to damith[at]comp.nus.edu.sg

Sharing this book helps too!

 

---| This page is from the free online book Practical Tips for Software-Intensive Student Projects V3.0, Jul 2010, Author: Damith C. Rajapakse |---

free statistics free hit counter javascript