Good Programming Practice!

Save yourself time and effort.

Introduction

Anyone can learn to write code. Once you start getting into writing programs with a decent level of size and complexity there are a few things you can do to make your code easier to read, easier to maintain and more robust. Some of them will take a small amount of extra time and effort to implement as you go but will pay off greatly in the long run as your code will be more reusable, easier to test, less prone to errors in the first place and easier to modify/ extend as your needs change (which they pretty much always will).

The code examples on this page are in a fictional language called RyanScript. I have done this deliberately as there are many programming languages out there, each different in their syntax. The concepts discussed on this page are generic and apply no matter which language you are developing your software in.

Good practice for readability

The readability of your code is vitally important. Increasingly so as your programs increase in size. The following basic practices will improve the readability of your code.

Naming of variables, constants, functions, classes etc

Naming things in a descriptive way makes it much easier to understand what is going on. Avoid the temptation to try and look like a 1337 Hax0r by only using single letters or cryptic names. It doesn't make you look cool, it just makes your code harder to read, debug, maintain etc and makes you look immature and childish.

It's also a good idea to have slighly different formatting conventions for different types of things so that when you see names within your code it is immediately obvious what type of thing it is (variable, constant, function, class etc). Here is a format I like to use but as long as you are consistent you can use whatever format works for you.

  • Variables are named using camelCase. eg. userName, classAverageMark
  • Constants are named using all UPPERCASE. eg. GST, CLASSMAX
  • Functions are named using underscores. eg. add_new_user, render_table
  • Classes are named similar to variables but the first letter is capitalised. eg. OffRoadVehicle, DatabaseManager

Functions should be named using verb_noun format. State the action, what is being done, followed by what it is being done to. eg. get_prices or mark_responses

Typical verbs include:

  • set
  • get
  • update
  • delete
  • render
  • print
  • save
  • load
  • validate

But you will probably have others as well specific to what your program is doing.

Ensure that you are consistent with your naming scheme and style.

Name booleans positively

A boolean is a value which can be either True or False. They are quite useful and are used all the time in programming. A variable can be boolean. eg.

assignmentSubmitted = True;

A function can also return a boolean value. eg.

assignmentSubmitted = has_assignment_been_submitted(studentID);

In general you should try to name your booleans (variables and functions) positively. It makes it harder to misunderstand their meaning and get it wrong.

Good names:

  • playerAlive
  • invoicePaid
  • user_exists()

Bad names:

  • playerDead
  • invoiceUnPaid
  • user_not_exists()

Naming your booleans positively avoids code like the following which is easy to misread:

Poorly_named.rs

  1. if (!user_not_exists()) {
  2. create_user(userEmail);
  3. }

! means not

Formatting / Indenting

Now that you've got your naming down, the next thing to look at is formatting and indenting. This is essentially the spacing between elements of your code and has a big impact on readability. Elegantly formatted code makes it easier to see the structure of your logic and also makes it easier to spot and correct syntax errors. Here is an example of some poorly formatted code:

Poorly_formatted.rs

  1. while(counter<5){
  2. score=counter*2;
  3. playerLocation=playerLocation+playerVelocity;
  4. update_enemies(playerLocation, counter);
  5. if(counter==3){
  6. print("half way");
  7. }

There is actually a syntax error here. Let's format the code better and the syntax error should become obvious.

Elegantly_formatted.rs

  1. while (counter < 5) {
  2. score = counter * 2;
  3. playerLocation = playerLocation + playerVelocity;
  4.  
  5. update_enemies(playerLocation, counter);
  6.  
  7. if(counter == 3) {
  8. print("half way");
  9. }

Now the structure of the code is clearer and the syntax error is easier to see. (It is a missing closing curly bracket) You'll also notice that newlines have been added to separate out logical elements of the code. This again helps to illustrate the structure of your program. There are various ways you can format your code. All are perfectly valid as long as they make your code easy to read and also that you are consistent. Here is another popular way of formatting (with the missing curly bracket added in):

Elegantly_formatted_2.rs

  1. while (counter < 5) {
  2. {
  3. score          = counter * 2;
  4. playerLocation = playerLocation + playerVelocity;
  5.  
  6. update_enemies(playerLocation, counter);
  7.  
  8. if(counter == 3)
  9. }
  10. print("half way");
  11. }
  12. }

Formatting and indenting your code may seem like a tedius waste of time but once you get the hang of it it becomes habbit and you do it without consciously having to think about it. The amount of extra time it takes is also a fraction of the time you will save trying to find syntax and logic errors.

Good practice for maintainability

When undertaking software design and development, one of the things you soon come to realise is that you really can't predict how your project is going to evolve and grow. No matter how much you plan and think you know what you want, the reality is that you will have new (and hopefully better) ideas as the project comes together. Once you start using your product your views on things will also change. You will also identify bugs which need to be fixed.

Designing for maintainability means organising your code to make it easy to fix and improve.

Some of the ideas discussed in this section also apply to the next section (Extendability).

Clean and uncluttered higher level functions

If you've organised the structure of your program well then you should end up with higher level functions which direct and manage lower level functions which do the work. In particular you should have a main function which coordinates in a broad, easy to understand way. Here is an example for playing a game:

game.rs

  1. // Starts and manages the game
  2.  
  3. function main () {
  4. // Initialise variables
  5. score = 0;
  6. numberOfEnemies = 10;
  7. playerLocation = 20; // start in middle of screen
  8. enemies = create_enemies(numberOfEnemies); // an array of enemies
  9. playerHealth = 100;
  10.  
  11. // Game loop
  12. while (playerHealth > 0) {
  13. inputs = recordKeystrokes();
  14. update_game_elements(inputs, playerLocation, enemies, playerHealth);
  15. render_screen(playerLocation, enemies);
  16. }
  17.  
  18. render_game_over_screen();
  19. }

Looking at this function it is easy to see how the game code is structured. It also helps to lead to a logical breakdown of the code which will make developing easier to manage.

Order functions logically

There are a few ways you can order your functions and classes but there should be some logical order to them in your source files. You may have a series of helper functions. It often makes sense to group these together at the bottom of your file. You should then try and order the rest of your functions in a roughly chronological order. eg. if you have a main function which is called first then this should be the first function at the top of the page. If it calls a function to displya a Start screen then that function should be next, etc.

Doing this makes it easier to read through and follow your code. When trying to follow through and understand some processing you shouldn't need to constantly be scrolling up and down in the file trying to find the next function which is referred to.

You won't be able to get a perfect chronological order as no doubt there will be functions called from multiple places but you should try and achieve this as much as possible.

Avoid deep nesting

Nesting is where you have a decision or a loop within another decision or loop. eg.:

Elegantly_formatted_2.rs

  1. while (counter < 5) {
  2. {
  3. score          = counter * 2;
  4. playerLocation = playerLocation + playerVelocity;
  5.  
  6. update_enemies(playerLocation, counter);
  7.  
  8. if(counter == 3)
  9. }
  10. print("half way");
  11. }
  12. }

In this code the if statement is nested within the while loop. The if statement could contain another loop or decision within it, which could also have another loop or decision within it and so on.

Deep nesting is where your code nests down to more than a few levels. The result is code which is hard to read and easy to make mistakes with. The logic alse ends up being messy and makes maintainability more difficult. If you end up with code which has deep nesting your really want to take a look at it and see if it can be rewritten in a cleaner way (nearly all the time it can be). Here is a simple example:

number_guess.rs

  1. targetNumber = random_number(0, 100);
  2. guess = read_user_guess()
  3.  
  4. if (guess != targetNumber) {
  5. if (guess < targetNumber) {
  6. print ("You guessed too low.");
  7. } else {
  8. print ("You guessed too low.");
  9. }
  10. } else {
  11. print ("You guessed the number.");
  12. }

This could be better rewritten as:

number_guess.rs

  1. targetNumber = random_number(0, 100);
  2. guess = read_user_guess()
  3.  
  4. if (guess < targetNumber) {
  5. print ("You guessed too low.");
  6. } else if (guess < targetNumber) {
  7. print ("You guessed too low.");
  8. } else {
  9. print ("You guessed the number.");
  10. }

Breaking your processing down into smaller, more targeted functions can also help in this regard.

Favor improvement of existing code over new features

When designing and developing software it is easy to get excited by what can be done. You're making progress, things are coming together, I could just add this, and just add that. Your code is structured well so doing so is easy. Before you know it your elegant and beautiful project is messy and cluttered. That intuitive interface has become a mess of buttons and data, your code is bloated and slowing down. This is an easy situation to get yourself into.

I'm not saying don't add new features but make sure it is done sparingly and that they are planned and integrated cleanly. More importantly, make sure you don't add new features unless they will really add value. Spending time improving existing code (making it run more efficiently and robustly) is generally way more valuable than just throwing new features in.

Comments

Writing good comments is a balancing act and an art. Comments should explain what the code achieves, not what it does. If you have done the things mentioned above in this section and the previous section then you will have something called intrinsic documentation. That is, the code itself makes it obvious what it is that it does. Comments are used sparingly to fill in the gaps. Comments in your code should be included:

  • At the top of your code with details about the file.
  • Preceeding each function with details about the function.
  • As headings to separate out logical groupings of functions / sections of your code.
  • To explain the functioning of code which is not obvious.

At the top of your file is it common to include some comments with details of the file. These details can include the date it was written / last updated, the author and a brief outline of what the code is that is within the file. For example:

form_validation.rs

  1. // This file contains a library of functions useful for
  2. // validating input from forms.
  3. // 18th April 2024
  4. // Author: Ryan Chadwick
  5.  
  6. ...

Preceeding each of your functions it is also advisable to include a comment with details of the function. Useful details are purpose of the function, what parameters it accepts and what it's return value is. This makes maintaining and extending your code easier as you can quickly skim these comments to see if there is existing code that does what you want that you can call rather than having to write.

user_management.rs

  1. // Check if a user exists in the system already
  2. // firstName: (string) users firstname, case insensitive
  3. // emailAddress: (string) users email address, case insensitive
  4. // Returns: (boolean) True if user exists, False otherwise
  5.  
  6. user_exists (firstName, emailAddress) {
  7. ...

It is also a good idea to use comments to separate out logical sections of your code. You should have done this already for small sections of your code with newlines but higher levels of groupings should be commented. You may for instance have a collection of functions which are related for formatting of data prior to rendering on the screen. You would choose to group them all together under a comment like such:

library.rs

  1. // Functions for formatting data
  2.  
  3. format_date (timestamp) {
  4. ...

You may be developing a game and have a relatively large section of code related to working out the movements of enemy characters. That section may be identified with a comment like such:

enemies.rs

  1. // Update actions of enemies
  2.  
  3. foreach (enemies as enemy) {
  4. ...

Sections of code, or particular instructions which serve a purpose which is not immediately obvious, or things which are done a particular way for a specific reason, should also be commented.

pedometer.rs

  1. // Increment by 2 as pedometer records movement of one leg but we account for
  2. // similar movement of other leg.
  3. steps = steps + 2

Comments like the following should be avoided. They are redundant and only serve to reduce the readability of your code.

poor_commenting.rs

  1. score = 0 // Set the score to zero
  2.  
  3. while (playerAlive == True) { // loop until the player is dead
  4. ...

Good practice for extendability

If the development of your software is successful you should have a good product. Over time you will want to add new features to it. You want to ensure that adding these feautes is as easy as possible. You also want to ensure that adding new features doesn't inadvertantly break existing functionality. Designing and developing your software with the following ideas in mind will make extending your product a less stressful experience.

Avoid global variables where possible

Global variables are available everywhere within your code. It can be tempting to use global variables so you don't need to think about passing the data around as parameters. Doing so might seem convenient in the short term but over time leads to a higher chance of changing variables when you don't intend to.

There are some insances where globals are valid to use and useful. If a variable is set once at the beginning of execution of the software and then doesn't change, and is required frequently, then it is a good candidate for being set as a global. An example of this might be a variable which holds the path to the directory which holds configuration files for the software.

It is also good to name globals in a way which makes them stand out, eg all uppercase or with a certain prefix.

Don't Repeat Yourself

This is an idea which originated in the area of databases and the management of data. It is just as applicable to developing code however. DRY (Don't Repeat Yourself) If you find yourself writing code that is very similar to previous sections of code (or even worse copying and pasting code from ealier on) then you have probably found a good candidate for creating a generic function that can be repeatedly called instead.

Doing so will reduce the amount of code you have to write and also reduce the possibilities of making mistakes.

Functions are generic in nature

There is a strong chance that you will want to perform similar actions in various sections of your code. If you move that functionality into a function and write it in a generic way then you can just call that function instead of having to write a series of similar but slightly different functions. This has the benefit that you only have to test one section of code rather than several. If you need to change the way the action is performed you only need to change one section rather than several. Extending your software (where there is a high chance you will be doing things you didn't originally anticipate) should also be easier if the functions are generic in nature.

As an example of this, lets say we want to check the existence of a user. We could write a function which accepts a single argument which is a userID which returns True if a user with that userID exists in the database. But then when a new user signs up we may wish to check if an existing user has already used that email address. When logging into the system we may wish to check the existence of a user by their userName. We could write a slightly different function for each of these scenarios.

not_very_generic.rs

  1. check_user_exists_by_ID(userID) {
  2. ...
  3. }
  4.  
  5. check_user_exists_by_email(emailAddress) {
  6. ...
  7. }
  8.  
  9. check_user_exists_by_user_name(userName) {
  10. ...
  11. }

Or we could write one function which will accept a userID, emailAddress or userName.

more_generic.rs

  1. // Token may be either an email address, userID or userName
  2. // The function looks at the format of the value to work out
  3. // which type it is.
  4. check_user_exists(token) {
  5. ...
  6. }

For something like this you may consider also including a second parameter which states what type the token is. Although it would be very easy to work it out (eg, if the value is all numbers it must be a userID, if it contains an @ symbol it must be an email address, otherwise it is a username there may be the possibility for users to do malicious things. You should sanitise all data on forms before using it but it's always safer to not assume things.

Another good candidate is sorting. If you ever write a function which returns a sorted array of items, make sure it has a parameter which allows you to return the array sorted either ascending or descending. You never know when later on you may want the same list but in the opposite order.

Functions return values rather than print

It can be tempting to combine obtaining data, formatting it and printing (or rendering) it in one function. Doing so, however, makes your code less generic and less reusable. Instead you should have separate functions for processing and rendering.

Let's say you are developing a game and have a function which reads the previous scores from a file, works out the top 10 scores and prints them to the screen. It's very efficient and works fine but then you decide to extend the game and during play you want to print an encouraging message if the users score gets close to being in the Top 10. You can't just call the current function as it will just print the data. We will have to write another function that contains much of the functionality of the first one. If we decide later on to change the game to showing Top 20 instead of Top 10 we have to remember to udpate both of them.

If instead we have two functions, one which returns an array of the Top scores and one which accepts that array and prints the data then all we would have to do is call the first function and we have our data ready to go.

Mastering the art of programming

So you're doing all the ideas listed above and your programming is looking pretty good. To take things to the next level you need to start thinking about the bigger picture structure of your programs. Here are a few ideas to get you started. I've only briefly introduced each of the ideas below. If one of them appeals to you you should do further research on them. There is a lot of great information about all of them easily available through a search in your favourite search engine. Each of them is quite involved but quite effective and powerful when implemented properly. Studying them is a great way to improve your software design and development ability.

Design patterns

If you want to bake a chocolate cake you don't sit there and think about how you might put together what ingredients to achieve it. Instead you grab a recipe and follow that. The recipe was put together by people with a lot more experience than you (most likely) and you are confident it works as it will have been tested and refined and proven over time. Design patterns are similar but for code. A lot of the problems you want to solve to develop your software will have been encountered by many other developers many times before. They have developed tested and proven ways of structuring code in order to produce efficient and robust code. Rather than reinvent the wheel you can copy and learn from their experience.

Most design patterns are described in a generic, language agnostic way (that is, they can be implemented in any language) though there are some which are language specific and utilise specific features of that language.

Here are three general categories that design patterns tend to fall into:

  • Creational - Effective ways to utilise data structures to manage the data which your program uses.
  • Structural - How your program can be structured at a higher level to more effectively implement the functionality you want.
  • Behavioural - How your code can be structured in order to allow elements of your program to better assist other elements of your program to do their tasks.

Understanding and utilising the ideas presented in these patterns will help to improve your coding technique. It will also provide valuable approaches to making your code more generic and reusable.

REST

REST stands for REpresentational State Transfer. It is a design methodology that looks at splitting your software into two logical parts. The first part manages data and access to that data. The second part manages processing of that data. It is very popular in areas such as website development where you would typically have data stored on a server (within a database) and that data is accessed and processed by many clients which could be via a website or mobile app or desktop program etc. The idea is to separate the two sides in such a way that they are independent. Neither side needs to know anything about the other side in terms of what they do or how they do it. They just need to know that there is an API (application programming interface). Think of an API as a means to ask the other side for what you want.

CRUD

There is a reasonable chance that the software you are developing saves and reads data from another source in some way (eg, database, file, requests to another system). If so then your program will benefit greatly from a solid layer for managing this activity. If this aspect of your program is done reliably, efficiently and with an intuitive interface then the rest of your code becomes much easier to write and manage.

CRUD stands for Create, Read, Update, Delete. These are the four basic actions that you will perform on data and your module for interfacing with the storage of your data should provide an easy and reliable way to do each of these actions for each main bit of data for your system.

Game development model

A lot of people like to develop games. Games are also quite interesting in the way they work and require a bit of thought put into how you structure your software in order for it to work effectively. Unlike many programs which are sequential in their processing, games have an intricate interweaving of elements (player, enemies, environment etc) which are constantly interacting with and affecting each other. Here is a simple model which can help you to implement this in an easy to manage way. It is called the Game Loop.

Think of the Game Loop as a simple design pattern. It breaks your software down into 3 distinct stages.

  • Get inputs
  • Update game elements (player, enemies, environment etc)
  • Render to the screen

And loop continuously until the game ends.

At the end of each cycle of the loop, wait a tiny amount of time before starting again. This tiny amount of waiting is so that you know exactly how fast the loop runs and can adjust elements at the right amount to get the speed you want (otherwise things would move faster or slower depending on how fast the CPU on your device was). A typical way to set this up is to match it to the frame rate of your monitor (eg. 60 times per second).

Here is an example of what your initial section of code might look like:

game_loop_template.rs

  1. // Simple game loop template
  2. // 18th April 2024
  3. // Author: Ryan Chadwick
  4.  
  5. // Import necessary libraries
  6. ...
  7.  
  8. // Initialise game elements (player, enemies etc)
  9. ...
  10.  
  11. // Game Loop
  12. while (playerAlive) {
  13. inputs = get_user_inputs()
  14. elements = update_game_elements(inputs)
  15. render_screen(elements)
  16. sleep(cycleTime)
  17. }

For more complex games the game loop may not be tied to the aliveness of the player but just until the player quits. This way the game loop can handle the displaying of various game screens such as initial welcome screen, settings page, actual game, game over screen etc.

The above template is intended as a starting point. You will no doubt need to build upon it to suit the characterisics of your game. It should maintain this general overall structure however.

The big picture

People often think that writing your code is the last step of designing and developing your software. As long as it works it doesn't matter too much if it's done poorly. Testing and bug fixing your code to actually get it working can easily take up a lot more time than you actually spend writing the code if your writing is done poorly. If your software is structured logically working off a good plan from your design documentation then writing the code will be easier, the number of bugs you have to fix will be less and fixing those bugs will be less stressful. Keeping the ideas discussed on this page in mind when both writing your code and also designing the overall structure of your software will make for a much more enjoyable experience with a better quality final product as a result.

Summary

Readability
Make sure your code is easy to read. Don't underestimate the value of this simple aspect of your code.
Maintainability
If you're planning is done right the structure of your logic will stand out and be easier to work with.
Extendability
Design your code so that it is easy to add new features resusing as much existing code as possible.