Good programming practices

Reading time10 min

In brief

Article summary

In this article, we discuss some good programming practices that you should keep in mind when writing a program. They help you keep your codes clean, easy to update, and help avoiding programming errors.

Main takeaways

  • A code should be readable, with proper indentation, names and comments.

  • Avoid using values, favor variables and constants.

Article contents

A program that does what it is supposed to do is great, but a program that you can share, extend and reuse is even better. This course introduces language independent recommendations for writing valuable programs, before focusing on Python good programming practices.

1 — Universal recommendations

Whatever the programming language used to implement your algorithms, some universal good practices are worth to be followed to ensure high code quality. This quality can especially be measured based on three criteria.

1.1 — Readability

Many very simple rules make reading source code easier. Acquiring these good habits from the early stages of a programmer’s career is very important. Here are some key stylistic notions that influence the code readability.

1.1.1 — Indentation

“Indentation” refers to the use of spaces or tabs to materialise blocks of statements.Despite the debate between 4 spaces or a tab, a coherent and consistent use of indentation is very important to make your code easier to read.

Example

Here are two examples of Java codes that do exactly the same thing, but with a clear difference in terms of readability.

/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */
public class Main {

/**
 * This is the entry point of your program.
 * It contains the first codes that are going to be executed.
 * 
 * @param args Command line arguments received.
 */
public static void main (String[] args) {
// Work on a string
String input = "aabbccddeeffgghh";
StringBuilder result = new StringBuilder(input);
// Iterate over each character in the string
for (int i = 0; i < input.length(); i++) {
// Check all subsequent characters for repetition
char currentChar = input.charAt(i);
boolean isRepeated = false;
for (int j = i + 1; j < input.length(); j++) {
if (currentChar == input.charAt(j)) {
// Convert repeated characters to uppercase in the result string
isRepeated = true;
for (int k = j; k < input.length(); k++) {
if (result.charAt(k) == currentChar) {
result.setCharAt(k, Character.toUpperCase(currentChar));
}
}
}
}
// Convert the current character to uppercase if it is repeated
if (isRepeated) {
result.setCharAt(i, Character.toUpperCase(currentChar));
}
}
// Print the result
System.out.println("Original string: " + input);
System.out.println("Transformed string: " + result);
}
}
/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */
public class Main {

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     *
     * @param args Command line arguments received.
     */
    public static void main(String[] args) {
        // Work on a string
        String input = "aabbccddeeffgghh";
        StringBuilder result = new StringBuilder(input);
        // Iterate over each character in the string
        for (int i = 0; i < input.length(); i++) {
            // Check all subsequent characters for repetition
            char currentChar = input.charAt(i);
            boolean isRepeated = false;
            for (int j = i + 1; j < input.length(); j++) {
                if (currentChar == input.charAt(j)) {
                    // Convert repeated characters to uppercase in the result string
                    isRepeated = true;
                    for (int k = j; k < input.length(); k++) {
                        if (result.charAt(k) == currentChar) {
                            result.setCharAt(k, Character.toUpperCase(currentChar));
                        }
                    }
                }
            }
            // Convert the current character to uppercase if it is repeated
            if (isRepeated) {
                result.setCharAt(i, Character.toUpperCase(currentChar));
            }
        }
        // Print the result
        System.out.println("Original string: " + input);
        System.out.println("Transformed string: " + result);
    }
}
Information

We do not provide Python code here, as indentation is part of Python’s syntax. We will discuss this point later.

1.1.2 — Naming conventions

“Naming convention” refers to the set of rules used to determine the name and case of files, variables, constants, modules, classes, functions, methods. Whatever the language, names given to these different code elements have to be explicit so that the meaning of its content can be deduced from the name directly. The name of a constant is generally in upper case.

Example

Use employee_management.py, result, weight, getPasswordAge() instead of prog1.py, a, x1, pwd(), etc.

Information

This may seem obvious to some of you, but, as teachers, we have encoutered variables in students’ programs named as (actual examples):

  • li, lili, lilili lililili, etc.
  • papa, maman, etc.
  • x1, x2, x3, x4, x5, etc.

Try to imagine one of your friend asking you for help to debug their program, and the mental effort it would take you to understand that papa is the squared total of last month’s client expenses.

1.1.3 — Generally avoid direct (re)use of values

Avoid direct use of values in your statements, and rather use variables and constants instead.

Example

Consider the following program. Now imagine that you want to change the size of the substring on which we work from 5 to 8. Which one is the easiest to update?

# Work on a string
s = "Hello, World!"

# Capitalize the first characters
s = s[:5].upper() + s[5:]

# Change the vowels among the first characters to 'X'
for c in "AEIOUY":
    s = s[:5].replace(c, 'X') + s[5:]

# Reverse the first characters
s = s[:5][::-1] + s[5:]
# Define a variable for the constant
SUBSTRING_SIZE = 5

# Work on a string
s = "Hello, World!"

# Capitalize the first characters
s = s[:SUBSTRING_SIZE].upper() + s[SUBSTRING_SIZE:]

# Change the vowels among the first characters to 'X'
for c in "AEIOUY":
    s = s[:SUBSTRING_SIZE].replace(c, 'X') + s[SUBSTRING_SIZE:]

# Reverse the first characters
s = s[:SUBSTRING_SIZE][::-1] + s[SUBSTRING_SIZE:]
/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */
public class Main {

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     *
     * @param args Command line arguments received.
     */
    public static void main(String[] args) {
        // Work on a string
        String s = "Hello, World!";

        // Capitalize the first characters
        s = s.substring(0, 5).toUpperCase() + s.substring(5);

        // Change the vowels among the first characters to 'X'
        String firstPart = s.substring(0, 5);
        for (char c : "AEIOUY".toCharArray()) {
            firstPart = firstPart.replace(c, 'X');
        }
        s = firstPart + s.substring(5);

        // Reverse the first characters
        StringBuilder reversedFirstPart = new StringBuilder(firstPart).reverse();
        s = reversedFirstPart + s.substring(5);
    }

}
/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */
public class Main {

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     *
     * @param args Command line arguments received.
     */
    public static void main(String[] args) {
        // Define a variable for the constant
        int SUBSTRING_SIZE = 5;

        // Work on a string
        String s = "Hello, World!";

        // Capitalize the first characters
        s = s.substring(0, SUBSTRING_SIZE).toUpperCase() + s.substring(SUBSTRING_SIZE);

        // Change the vowels among the first characters to 'X'
        String firstPart = s.substring(0, SUBSTRING_SIZE);
        for (char c : "AEIOUY".toCharArray()) {
            firstPart = firstPart.replace(c, 'X');
        }
        s = firstPart + s.substring(SUBSTRING_SIZE);

        // Reverse the first characters
        StringBuilder reversedFirstPart = new StringBuilder(firstPart).reverse();
        s = reversedFirstPart + s.substring(SUBSTRING_SIZE);
    }

}
1.1.4 — Use local variables and avoid global ones

Where to define variables is one of the topics addressed during session 2, but in a nutshell, it concerns the fact that variables should be defined as close as possible to the statements that use them and should not be shared between different parts of your program.

1.2 — Maintainability

In general, software, and therefore its code, evolves over time. It is therefore vital to ensure that you and your team will be able to understand the code in a week, a month or even a year’s time. There are at least two crucial levers to guarantee the code maintainability:

  • Comment your code – Using natural language, provide your collaborators with an up-to-date description of your code’s functionalities and implementation strategies. Comments should appear at different places in your code, to describe the whole file content, each function and method, statements with non-trivial logic. In this course, we comment most codes that we give you, so check our corrections for examples.

  • Structure your code – To facilitate its reusability. This is also a crucial topic seen during session 2 about the way your code should be factorized and structured to allow for a reuse of individual functionalities.

1.3 — Reliability

Reliability is related to the confidence you can have in the functionality you provide to other developers or to the rest of your programming projects. The only way to provide reliable code is to wisely structure it into atomic and independant functionalities (session 2) and to test each of them (session 3). Another way to improve the reliability of your code is to avoid reinventing the wheel and to reuse as much of the functionality embedded in the language as possible. Built-in functions have been extensively improved and tested to make them safe to reuse.

2 — Python good practices

2.1 — Coding norms

Each language has its own set of good practices in addition to the universal ones. These are described by some norms, such as Python Enhancement Proposals (PEP) 8 for Python. Such norms describe how your program should be written, to provide global recommendations to all developers of that language.

Typically, you may have noticed some differences between the Python and Java codes we propose you. In Python, a variable should be named my_variable (this is called “snake case”), while in Java it should be myVariable (this is called “camelCase”).

Information

Norms are made to try to ease code sharing between users, and collaborations. While this is a good idea to try to follow them as close as possible, they mostly act as a guide.

The most important part is to be “consistent” across your codes. Thus, it’s ok to deviate a bit from the norm as long as you respect that!

2.2 — Comment and document your code

Comments are short sentences placed at various points in your code to describe, explain or clarify a step in your algorithm or a particular implementation strategy. Comments start with # and should be concise and clear to help developers understand your code more quickly.

Below are two versions of the code that do the same thing. Take 5 minutes to understand the algorithm by looking at the raw version, which is what is produced when the user types “AAZERTYYYYYY”. Then, compare the readability of this raw version with the second, which respects stylistic conventions and includes comments.

m = input("Enter the message: ")
em = ""
i = 0

while (i <= len(m) - 1):
    c = 1
    ch = m[i]
    j = i
    while (j < len(m) - 1):
        if (m[j] == m[j+1]):
            c = c + 1
            j = j + 1
        else:
            break
    em = em + str(c) + ch
    i = j + 1

print(em)
"""
  This simple script encodes a message using run-length encoding.
  The principle of this simple encoding is to count the number of times a character appears in a row and then append the count to the character.
  Example: AAAZEEERRRR -> 3A1Z3E4R
"""

# Ask user for a string to work on
message = input("Enter the message to encode: ")

# Initialize variables
encoded_message  = ""
i = 0

# Treat each character in the message
while i <= len(message) - 1:
    
    # Count the number of times the character appears in a row
    count = 1
    ch = message[i]
    j = i
    while j < len(message) - 1 and message[j] == message[j+1]:
        count = count + 1
        j = j + 1

    # Append the count and the character to the encoded message
    encoded_message += str(count) + ch
    i = j + 1

# Display the encoded message
print(encoded_message)
import java.util.Scanner;

public class Main
{

    public static void main (String[] args)
    {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter the message: ");
        String m = scanner.nextLine();

        StringBuilder em = new StringBuilder();
        int i = 0;

        while (i <= m.length() - 1)
        {
            int c = 1;
            char ch = m.charAt(i);
            int j = i;

            while (j < m.length() - 1)
            {
                if (m.charAt(j) == m.charAt(j + 1))
                {
                    c++;
                    j++;
                }
                else
                {
                    break;
                }
            }

            em.append(c).append(ch);
            i = j + 1;
        }

        System.out.println(em.toString());
    }

}
// Useful imports
import java.util.Scanner;

/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */
public class Main {

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     * This simple script encodes a message using run-length encoding.
     * The principle of this simple encoding is to count the number of times a character appears in a row and then append the count to the character.
     * Example: AAAZEEERRRR -> 3A1Z3E4R
     *
     * @param args Command line arguments received.
     */
    public static void main(String[] args) {
        // Ask user for a string to work on
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter the message to encode: ");
        String message = scanner.nextLine();

        // Initialize variables
        StringBuilder encodedMessage = new StringBuilder();
        int i = 0;

        // Treat each character in the message
        while (i <= message.length() - 1) {
            // Count the number of times the character appears in a row
            int count = 1;
            char ch = message.charAt(i);
            int j = i;
            while (j < message.length() - 1 && message.charAt(j) == message.charAt(j + 1)) {
                count++;
                j++;
            }

            // Append the count and the character to the encoded message
            encodedMessage.append(count).append(ch);
            i = j + 1;
        }

        // Display the encoded message
        System.out.println(encodedMessage);
    }

}

Whereas comments aim at helping you and collaborators extend the code, documentation acts, in addition, as a more detailed guide to use it. Docstrings are used to integrate documentation within your code. A docstring section is defined as follows:

"""
Your docstring documentation
"""
/**
 * Your docstring documentation
 */

In session 3 you will discover how to document key constituents of your code, namely functions and then classes, as well as tools to automatically leverage the docstrings to produce sharable documentations.

To go further

Important

The content of this section is optional. It contains additional material for you to consolidate your understanding of the current topic.

Looks like this section is empty!

Anything you would have liked to see here? Let us know on the Discord server! Maybe we can add it quickly. Otherwise, it will help us improve the course for next year!

To go beyond

Important

The content of this section is very optional. We suggest you directions to explore if you wish to go deeper in the current topic.