Programming Best Practices

This is a short compilation of a few programming best practices I try to follow to build clean and easily maintainable software programs. This is not the ultimate complete list of all best practices but should be a good step towards perfection.

1/ Use meaningful names

Always use meaningful names when creating variables, functions, classes, modules etc. Reading a variable or a function name should give a quiet clear idea about its purpose. This makes it easy to understand for someone doing review of your code or taking over your code or even yourself when reading your own code in the future or when doing refactoring. Avoid names like tmp1, tmp2, a, b ,c, i ... The exception is when using variables in iteration statements like for loops.

2/ Use constants for enumerations

When you have an enumeration like a status which can be a string like “OK”, “ABORTED”, “CANCELLED”, or numeric like 0, 1, 2… Define constants holding these values, and use these constants across your program. So instead of writing if(status==”OK”) you will be writing something like if(status==STATUS_OK). This makes it easy for you when you want to find references to one enumeration value or when you want to refactor like rename “CANCELLED” to “CANCELED”. It also makes it easier to understand the code since all possible values for an enumeration will be visible in one place where you defined the constants.

3/ Avoid useless else blocks

Sometimes it is useless to have an else block. Variable initialization should be used in such cases. For example :

can be simplified with

4/ Check inputs and fail early

If your program accepts inputs like arguments in the command line or query string in http requests, make sure to check all of them before starting the actual processing. Ensure that all required inputs have been provided, inputs that need to have a specific format are well formatted, inputs that need to be existing paths really exist … If something is wrong abort the program execution. No need to go further. Code that comes after this initial check step does not need to worry anymore about input correctness.

5/ Use { } in flow control blocks

In many programming languages like java and javascript, you can omit the { } in if/else/while/for blocks if the block consists of one line. I always use { } even when the concerned block is one line. This makes the different flow control blocks consistent across all the code. This consistency makes it easier for my mind to walk through the code without being interrupted, especially when reading large blocks of code for refactoring or review purposes. This also makes code quicker to update since if you omit the { } and want to add lines to the one line block, now you need to add the { } and maybe re-indent your code.

6/ Handle errors at higher level

Errors that occur in low level and utilility components must be reported back to the caller because these components could be used by several functions and called in several locations and contexts, so they have no clue what to do with the error. For example, a route handler in an express app calling a function in the DB utility to query the database should know if the query was successful or not and it should know what to do with the error, like return a 500 response to user. the DB utility on the other end does not have a clue what to do with the error, it should just report it back to its caller. Of course, the low level component should abstract the underlying layer errors when needed and wrap them in a simpler form. This will make it more efficient when writing the caller code. Java comes to my mind with regards to this, when a low level component needs to call a stream or file API and catch a whole lot of exceptions, it will be a bad idea to just catch them and do nothing or just print the stack trace to logs : it must report back errors to the caller. It is also a bad idea to not catch anything and leave that to the caller because every caller will need to catch all of them and if there are a handful of callers it will probably result in duplicate or bloated code.

7/ Use environment variables for configuration management

A typical program uses services like database, cache, urls, paths … These configuration parameters will typically change between development, integration and production environments. Using environment variables is a convenient way to store them. Your program will just need to source or be handed the appropriate target environment variables. I used to run an express app as a debian system service using this method, then when i decided to migrate the app to AWS Lambda, I just feeded my lambda function the same environment variables, no other changes were required in respect to how the program was handling configuration parameters.

8/ Exit status matter

This is especially true for programs that are meant to be run in the command line like unix shell scripts, perl, python, tcl and so on. You should stick to the standards when possible, that is, exit with zero status when the program execution was successful, a non-zero code in case of errors with errors written to stderr.

9/ Don’t re-invent the wheel

This is a commonly know and used principle in software development and other disciplines. If you need to achieve something look at what is already available and achieving the same thing. There are chances that what you are looking for is not only already available in your programming language’s modules registry, but also full-featured, well tested and supported by the community or a third party entity. So why not use it.

10/ Test thoroughly

A program should have a unit test suite that checks the correctness of its functions and components on their own independently from the external services it depends on. Sometimes it might be required to have integration test suite that checks if the program interacts with external services as expected. Well tested programs inspire confidence and make refactoring and code change easier to do.