Maker.io main logo

How to Use Best Practices to Write Better Code

2024-06-24 | By Maker.io Staff

How to Use Best Practices to Write Better Code Best practices in programming can help us write maintainable, clear, and overall, better code.

Programming has become an essential practical skill over the last decade, and you won’t get around writing some code for projects anymore. It’s often easier to spot bad code than to define what constitutes a good program. Beginners, in particular, frequently struggle with writing clear and maintainable code.

By following only four simple guidelines — or best practices — you can drastically improve your coding game without much effort.

Don’t Repeat Yourself!

Let’s start with something that most of you should know repeating the same piece of code in a program is usually considered bad practice, as doing so means that programmers have to modify the code in multiple spots should one of the repeated segments contain a bug. Additionally, unnecessarily repeating code leads to clutter in the program, making the code more difficult to read, understand, test, and maintain. Consider the following example:

Copy Code
# Initialize Data
nums_1 = [10, 5, 8, 15, 20]
nums_2 = [91, 3, 8, 22, -7]
sum_1 = 0
sum_2 = 0

# Do calculations
for i in nums_1:
    sum_1 += i
for i in nums_2:
    sum_2 += i
avg_1 = sum_1 / len(nums_1)
avg_2 = sum_2 / len(nums_2)

# Print results
print("Total:", sum_1)
print("Average:", avg_1)
print("Total:", sum_2)
print("Average:", avg_2)

The two for-loops perform the same task on different lists. Similarly, the setup and print calls at the end also look very similar, only differentiated by the list you use. Thus, the repeated code segments should be extracted into a function that takes a list as a parameter. The program can then call the function with two different lists. The following listing contains an expanded version of the new function, and the two method calls underneath:

Copy Code
# Function to process a list of numbers
def process_list(lst):
    s = 0
    c = len(lst)
    mx = float('-inf')
    mn = float('inf')
    for i in lst:
        s += i
        if i > mx:
            mx = i
        if i < mn:
            mn = i
    a = s / c
    print("Total:", s)
    print("Count:", c)
    print("Average:", a)
    print("Max Value:", mx)
    print("Min Value:", mn)

# Main program
nums_1 = [10, 5, 8, 15, 20]
nums_2 = [91, 3, 8, 22, -7]
process_list(nums_1)
process_list(nums_2)

Extracting repeated sections with the same functionality into methods is a significant first step in slimming down code and enhancing its readability.

Establish Meaningful and Consistent Names

Good names can also drastically influence how fast you can understand source code, whether in other people’s projects or your own projects from some time back. While, initially, it may seem tempting to use short variable and function names to keep the code concise and reduce the effort needed to type out a program, this decision often comes back to bite programmers later.

Take, for example, the snippet from above. Granted, it’s an exaggerated example, but understanding the purpose of even a short snippet is difficult at first glance. And despite containing some documentation, the comments are not helpful.

The following revised version utilizes more descriptive variable and function names:

Copy Code
def calculate_statistics(numbers):
    average = 0
    total = 0
    count = 0
    max_value = float('-inf')
    min_value = float('inf')
    for current_value in numbers:
        total += current_value
        count += 1
        if current_value > max_value:
            max_value = current_value
        if current_value < min_value:
            min_value = current_value
    average = total / count
    print("Total:", total)
    print("Count:", count)
    print("Average:", average)
    print("Max Value:", max_value)
    print("Min Value:", min_value)

# Main program
test_numbers = [10, 5, 8, 15, 20]
calculate_statistics(test_numbers)

While using more descriptive names makes the program seem longer, doing so also results in much easier-to-understand code. The names now convey the meaning of the values stored within the variables. The revised version clarifies from the beginning what values are stored in each variable, a particularly important aspect in languages that don’t support strict typing. Further, the method name accurately conveys what the method does, allowing the comment above the function to be omitted.

However, there are still a few things that could be improved upon.

Modularize Code into Meaningful Units

Splitting code into manageable units is a fantastic way to improve readability. The method from above secretly performs two tasks, even though its name suggests otherwise — the function first calculates some statistical values and then prints them to the console. Therefore, this function should be split up into two separate ones:

Copy Code
def calculate_statistics(numbers):
    average = 0
    total = 0
    count = 0
    max_value = float('-inf')
    min_value = float('inf')
    for current_value in numbers:
        total += current_value
        count += 1
        if current_value > max_value:
            max_value = current_value
        if current_value < min_value:
            min_value = current_value
    average = total / count
    return (average, total, count, min_value, max_value)

def print_statistics(value_tuple):
    print("Total:", value_tuple[0])
    print("Count:", value_tuple[1])
    print("Average:", value_tuple[2])
    print("Max Value:", value_tuple[3])
    print("Min Value:", value_tuple[4])

# Main program
test_numbers = [10, 5, 8, 15, 20]
statistics = calculate_statistics(test_numbers)
print_statistics(statistics)

Splitting the code also helps track bugs and errors, as they appear more isolated. Unfortunately, it’s next to impossible to define a fixed rule of how large methods may become before they should be broken down. A good rule of thumb is that if a method performs more than one well-defined task, it can probably be broken down, especially if some parts are used in multiple places throughout a program.

Handle Unforeseen Events and Errors

Checking some critical input values is also considered good practice. A few simple steps help improve the initial code snippet to make it more manageable and easier to understand, but there’s no guarantee that whoever calls the function will always provide it with the correct values it expects. What if someone passes an empty list to the calculation method or a tuple that’s not long enough for the print function? Running the code would result in an error that would crash the program.

The programmer should define how the program behaves in case of erroneous inputs. Two common ways to handle them are to print an error message and gracefully exit the program or continue execution with some pre-defined default values instead of the incorrect ones.

The revised version of the calculate_statistics method could use a default value like this:

Copy Code
def calculate_statistics(numbers):
    if len(numbers) == 0:
        numbers = [0]
    average = 0
    total = 0
    count = 0
    max_value = float('-inf')
    min_value = float('inf')
    for current_value in numbers:
        total += current_value
        count += 1
        if current_value > max_value:
            max_value = current_value
        if current_value < min_value:
            min_value = current_value
    average = total / count
    return (average, total, count, min_value, max_value)

The added if-statements check whether the list is empty and replace empty lists with a new one containing only the number zero. For the print_statistics method, continuing with default values doesn’t make much sense, so an error message is a better option:

Copy Code
def print_statistics(value_tuple):
    if len(value_tuple) < 5:
        print("Error: Invalid number of values!")
    else:
        print("Total:", value_tuple[0])
        print("Count:", value_tuple[1])
        print("Average:", value_tuple[2])
        print("Max Value:", value_tuple[3])
        print("Min Value:", value_tuple[4])

Finally, wrapping critical method calls in a try-except block is another viable option that you must not forget, as demonstrated by the following short pseudo-code snippet:

Copy Code
try:
    do_something(x, y, z)
except InvalidParameterError:
    handle_error()

When calling the do_something() method results in an InvalidParameterError, the code doesn’t just crash. Instead, the program calls the handle_error() method, which can execute further commands to deal with the problem.

Good Code is All About Readability

All the measures in this article aim at one common goal: Improving code readability. While there are many more aspects to consider, using meaningful, consistent names, modularizing repeated parts into functions or classes, and handling errors and invalid inputs are generally good techniques to start.

Writing readable code offers many benefits, as understandable programs are easier to develop, maintain, debug, and adapt in the future. You can thus drastically cut down development time and potential frustration by following just a few simple guidelines.

TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum