This is part II of the Best practices of Software Development series. If you want to know how to write quality code to become a better developer read on ... Today: Good Coding Methodologies.
- I) Programming Techniques and Strategies
- II) Good Coding Methodologies
- III) Maintaining Quality Code
Good Coding Methodologies
This post will talk about writing better code, common issues with program structures and how to improve them, building good methods and a short note on exceptions.
Writing better code
One of the most important criteria is human-readable code. Code should be self documenting. Don't rely on comments, use coding standards instead. It makes the code more concise and predictable and thus easier to test, debug and modify. The required code-indenting is one of the reasons I like Python for example.
Control structures should be wrapped in blocks even if not strictly required. I also try to make a habit of keeping lines under 70 or 80 characters wide. Whether you use camelCase or underscore-based style for your variables, use a consistent scheme and stick to it. .
Comments are better than external documentation because they stick with the code (modification). However don't overuse them: don't repeat the code, don't provide unnecessary explanations, watch out for code changes (forgetting to edit the comments as well!). Good comments summarize the code, describe its intent and provide info that the code cannot express.
Structure of programs
Examples of common issues:
- Improper use of pointers (wild or dangling pointers).
- The use of global variables making the program harder to maintain.
- Using literals instead of constants (e.g. 86400 instead of SECONDS_IN_DAY).
- Confusing comparison "==" with assignment "=" operators.
- Wrong use of loop initial and end-condition values.
- Short-circuit operators ("&&" and "||") - understanding that they might not execute in some circumstances.
- Comparing objects ("equals" and not "==").
- Fall-through: forget to "break" each "case" in a switch statement.
- Lack or improper use of error handling (see further down).
To generally improve your writing of proper expressions you should use parentheses in complex expressions. It is clearer to express Boolean expressions positively unless the outcome is likely to be negative - so use: "if(bookInStore)" instead of "if(!bookInStore)". Also avoid "(a>b) == TRUE", just say "(a>b)". You should work with TRUE and FALSE booleans instead of 1 and 0, if your language does not support them you can define them as constants.
It is important to sequence code properly so that dependencies are visible reading the method names. This implies that one method executes only one task (more on Methods further down).
Variables and types
The advantage of strong typing is that a variable can hold only one type of value. This encourages input validation and errors that are detected early on at compilation time. You should limit the lifetime of a variable, so declare it as close as where you are going to use it. Named constants and enumerations make code more readable and thus maintainable.
As mentioned: use the most likely outcome in the if statement (not in the "else"). Enforce blocks for better readability. Other best practices for loops: separate loop control code from loop work code, keep nesting down to two or three levels at most, be careful (document) break and continue statements.
Building good methods
The purpose of a method should be clear from its name. I stress it again: one method equals one task. This alone makes code much more readable and reliable. It makes bugs easier to solve or prevents some of them at all. It also favors code re-use.
Variables should be localized to the method to prevent unexpected modifications from outside the method. In OOP you'd hide the data used by a method within a object only making it accessible through the object's public interfaces.
Bottom line is to think well what the goal of the method is, what should it accomplish? Then write it.
Handling arguments and return values
A method should not have more than 7 or 8 parameters. Optional arguments should be avoided because they lure you in 1 method > 1 task (use other methods to handle them). Distinguishing between input/working/output variables (an example: input (passed to the method) = grossSalary; working = incomeTax ; output = netSalary (to be calculated and returned) ). This prevents the wrong variables to be modified with undesired results.
It is important to validate arguments, otherwise bad data will creep in leading to failures further into the program (which are harder to debug). Return empty arrays rather than null values.
Unexpected events / errors should be handled by exceptions. If not, the user is presented with errors that usually don't make any sense. So make sure that the catch/except block actually handles the event. Depending the problem you'd control what happens as a result, for example: disable a module, only show a message, show debug info under the hood, etc. etc. See Wikipedia for more info.