3 Key Programming Best Practices for Beginners

3 Key Programming Best Practices for Beginners Featured Image

Today, I was inspired by Erica Vartanian to reflect on my knowledge of programming from the perspective of a beginner. As a result, I put together this article to share a few of the best practices that I think beginners should care about. Let’s get into it!

Table of Contents

Who’s a Beginner?

Assessing your own ability at something is a near impossible task. I think about this quite a lot as someone who has a variety of skill-based interests like gaming, music, and chess. For me, this is most apparent in chess because it’s so easy to feel like you get “it”—only to lose out of the opening to a trapOpens in a new tab..

Part of that feeling that you get “it” comes from the reality that you don’t know what you don’t know. In other words, so much of the knowledge required to master the skill is just not in your field-of-view yet, so you stumble into the Dunning-Krueger EffectOpens in a new tab. (i.e., you overestimate your ability).

But what about folks who are actually beginners? I’m talking about folks who have approached the skill maybe once or twice or perhaps not even at all. For instance, I’ve watched a lot of hockey in my life, but I’ve never played it beyond street hockey and knee hockey. As a result, I have an extremely rough idea of the types of skills needed to play the sport, but I’d have to learn the fundamentals (e.g., how to skate, how to hold a stick, etc.).

This brings me to programming, which is a skill with an almost unlimited set of interconnected subskills. Folks who spend a lot of time in this field often forget what they had to learn to do what they can do today, so guides on the fundamentals often skip essential beginner knowledge (e.g., how a pawn moves) as if to be almost too trivial to cover.

Ultimately, I was reminded of my own Curse of Knowledge as I was reading Erica Vartanian’s “6 coding best practices for beginner programmersOpens in a new tab..” Turns out that a lot of the skills I take for granted are actually really important to openly state and justify. As a result, I thought I would try to put together my own list of best practices targeted at beginners. Hopefully, it will serve two purposes: 1) to allow me to reflect on my knowledge as a programmer and 2) to provide a resource of best practices for newcomers.

Without further ado, let’s get into it!

Key Programming Best Practices for Beginners

For me, part of being an educator is always centering my teaching around beginners. It’s been a part of my education journey for as long as I can remember, but it’s become harder to do as I continue to build on my own knowledge. What seems intuitive to me at this point is incredibly convoluted to new folks, so I figured I’d take some time today to reflect on the basics. If you find the topics listed here to be overly trivial, this article isn’t for you. Otherwise, keep reading!

Group Duplicate Code Into Functions, Procedures, and/or Methods

In the world of programming, terms like functions, procedures, and methods technically have different definitions, but they’re often used interchangeably to describe the grouping of lines of code for reuse. For instance, I consistently use the word “function” throughout this article, despite its incorrect usage in a few places, to avoid overloading folks with jargon.

At any rate, without the habit of using functions, it might be tempting to write all of your code as a sequence of steps like a recipe:

1. Sift the dry ingredients together.
2. Make a well, then add the wet ingredients. Stir to combine.
3. Scoop the batter onto a hot griddle or pan.
4. Cook for two to three minutes, then flip.
5. Continue cooking until brown on both sides.

allrecipes.comOpens in a new tab.

Unfortunately, what you’ll find with programming is that the steps are never this clean. After all, sifting ingredients is a process that requires some explanation (e.g., what does it mean to sift?, how do you know when the ingredients are together?, etc.). In other words, what’s six lines of pseudocode above might actually be a few dozen (or thousand) lines of code.

To make matters worse, if you were to actually write all the lines out in a sequence, you’d find that you’d be repeating yourself a lot. As Erica puts it, this is where the Don’t Repeat Yourself (or DRY) principle comes into play. Rather than rewriting the same code over an over again, you should take advantage of functions, which allow you to group code into chunks that can be called by some name later. As an added bonus, once you have the functions you want, you can essentially replicate pseudocode:

dry = sift(flour, baking_powder, sugar, salt)
batter = combine(dry, milk, butter, egg)
pancake = scoop(batter, griddle)
cook_and_flip(pancake, 2)
brown(pancake)

Perhaps it would make more sense with a concrete example, so here’s a silly script that computes the area of a bunch of squares. Keep in mind that I’m deliberately going overboard with the amount of duplicate code.

response = input("What is the length of the first square?")
square = int(response)
area = square * square
print(area)

response = input("What is the length of the second square?")
square = int(response)
area = square * square
print(area)

response = input("What is the length of the third square?")
square = int(response)
area = square * square
print(area)

It’s a simple enough script that it doesn’t need to be changed, but if we wanted to, we could get rid of some of the duplicate code. For example, we might pull the area calculation out into a function:

def compute_area(square):
    return square * square

response = input("What is the length of the first square?")
square = int(response)
area = compute_area(square)
print(area)

response = input("What is the length of the second square?")
square = int(response)
area = compute_area(square)
print(area)

response = input("What is the length of the third square?")
square = int(response)
area = compute_area(square)
print(area)

And we could keep going! For instance, we can remove a lot of the duplicate text:

def compute_area(square):
    return square * square

def prompt_user(index):
    return input(f"What is the length of the {index} square?")

response = prompt_user("first")
square = int(response)
area = compute_area(square)
print(area)

response = prompt_user("second")
square = int(response)
area = compute_area(square)
print(area)

response = prompt_user("third")
square = int(response)
area = compute_area(square)
print(area)

Of course, we might notice that we’re actually making the code longer. Don’t worry! The four lines that are repeated can be placed in a function:

def compute_area(square):
    return square * square

def prompt_user(index):
    return input(f"What is the length of the {index} square?")

def get_area_of_square(index):
    response = prompt_user(index)
    square = int(response)
    area = compute_area(square)
    print(area)

get_area_of_square("first")
get_area_of_square("second")
get_area_of_square("third")

And if the number of lines still bother us, we can remove the two extra functions we created initially:

def get_area_of_square(index):
    response = input(f"What is the length of the {index} square?")
    square = int(response)
    area = square * square
    print(area)

get_area_of_square("first")
get_area_of_square("second")
get_area_of_square("third")

All of this is to say that there is no correct way to group lines of code into a function (despite what you might read online), but using functions is a best practice that you should definitely pick up early. There are just so many benefits to using functions that go beyond removing duplicate code (and removing duplicate code isn’t always recommended; see the WET principleOpens in a new tab.). For instance, you get better error messages thanks to being able to take advantage of the call stack. You also give yourself units of code that are now testable. Hell, functions save you from having to change the duplicate code in multiple spots if you make any mistakes like I did while writing up these examples (note: I may have forgotten to cast the user input to a int). Many of these advantages might not be appreciated by a beginner, but you’ll thank yourself later for building up the habit.

Space Code Out Into Logical Units

In Erica’s article, there’s a section on indentation. As a Python enjoyer, I tend not to have to worry about indentation as my programs literally won’t run without it. However, along similar lines, there’s a lot of value in writing programs with reasonable spacing.

For example, it’s totally okay to write a function like this (don’t worry about what the function does):

def _process_table(header, body):
    processed_header = []
    processed_body = []
    for item in header:
        if isinstance(item, (str, Inline)):
            processed_header.append(Paragraph([item]))
        else:
            processed_header.append(item)
    for row in body:
        processed_row = []
        for item in row:
            if isinstance(item, (str, Inline)):
                processed_row.append(Paragraph([item]))
            else:
                processed_row.append(item)
        processed_body.append(processed_row)
    return processed_header, processed_body

However, you may find that your program is a little easier to read with some key line breaks. In the example below, I add an empty line and a comment above each of my loops. As a result, it’s clear to see where some of the key operations are happening.

def _process_table(header, body):
    processed_header = []
    processed_body = []

    # Process header
    for item in header:
        if isinstance(item, (str, Inline)):
            processed_header.append(Paragraph([item]))
        else:
            processed_header.append(item)

    # Process body
    for row in body:
        processed_row = []
        for item in row:
            if isinstance(item, (str, Inline)):
                processed_row.append(Paragraph([item]))
            else:
                processed_row.append(item)
        processed_body.append(processed_row)

    return processed_header, processed_body

I personally like using spacing like this because there are distinct blocks of code that are logically separated out. As seen previously, we already do this with functions, so why not break down functions as well. And perhaps these blocks themselves could be refactored as functions if needed.

Communicate Your Intent Through Comments and Naming Conventions

The last best practice I’ll share with you today revolves around communication, which is an often undervalued skill in our field. Effective communication is a skill that can be employed in many different ways in software, but the two most applicable ways for beginners is probably through naming conventions and comments.

To start, naming conventions describe a set of techniques for giving things names. In the world of software, there are a lot of things that can be given names, such as variables, functions, and classes. As a beginner, you should be considerate of how you go about naming things. Again, there’s no correct way to create names, but there are certainly better ways than others. For instance, here’s a function with all of the important naming details removed:

def x(self):
    if self.y > 1:
        self.y -= 1
    return self

Any idea what this method might be used for? I would be amazed if you could guess. There is just no contextual information to help you out.

Now, here’s that same function with the names added back:

def promote(self) -> Heading:
    if self._level > 1:
        self._level -= 1
    return self

Do these names help? At the very least, you know the function is performing some “promotion” action, the return value is a “heading”, and the variable being modified is a “level”. Using context clues, you can probably imagine that this method promotes the level of a heading. In other words, notice how names can be used to communicate intent with someone, whether that be another person or your future self.

Interestingly, we can take our communication to the next level by incorporating comments in our code. Comments are a way of including notes in our code for folks who have the pleasure of reading it. Once again, there are so many techniques for writing comments, but as a beginner, I would recommend just getting in the habit of creating what are called “doc comments”. These are comments that can be found at the top of every function, and they’re used to document the intended behavior of that function in plain English. Here’s what that looks like for the function above:

def promote(self) -> Heading:
    """
    Promotes a heading up a level. Fails silently
    if the heading is already at the highest level (i.e., :code:`<h1>`).

    :return:
        self
    """
    if self._level > 1:
        self._level -= 1
    return self 

Seeing this description of the intended behavior of the method, our suspicions are confirmed: promote is a method that is supposed to promote a heading up a level. And even without all the context of the rest of the code, we can reasonably guess that the heading is related to an HTML heading based on the heading tag in the parentheses. With that context, we can make sense of why that if statement exists, but if we wanted, we could be even more explicit with our intent with some inline comments:

def promote(self) -> Heading:
    """
    Promotes a heading up a level. Fails silently
    if the heading is already at the highest level (i.e., :code:`<h1>`).

    :return:
        self
    """
    # HTML headings cannot be promoted above H1
    if self._level > 1:
        self._level -= 1
    return self 

That way, there’s no confusion around why the condition checks that the level is greater than one before attempting to decrement it.

Ultimately, programming is a craft that requires quite a bit of empathy for other folks, even if the other folks in question are just future versions of yourself. It’s a good idea to spend time working on your communication skills. Again, you’ll thank yourself later.

More Best Practices?

I’ve been interested in best practices for some time because there are so few of them that are actually universal, if any. Most of the time, best practices are touted as absolutesOpens in a new tab., but you’ll find that most of what we do is more contextual. That’s not to say there aren’t good rules out there, but rather that you should take any rule with a grain of salt.

Also, if you’re curious about the code snippets I used for a few of my examples, they came from my SnakeMD APIOpens in a new tab.. Feel free to browse the code to see how I try to practice what I preach.

With all that said, I’ve written a bit about software best practices for folks in the beginner to intermediate range that you might be interested in:

As you can see, even I jokingly use absolutes to attract folks to my articles. As always, if you liked this article and want to help me make more articles like it, check out my list of ways to grow the site. Otherwise, take care!

Jeremy Grifski

Jeremy grew up in a small town where he enjoyed playing soccer and video games, practicing taekwondo, and trading Pokémon cards. Once out of the nest, he pursued a Bachelors in Computer Engineering with a minor in Game Design. After college, he spent about two years writing software for a major engineering company. Then, he earned a master's in Computer Science and Engineering. Today, he pursues a PhD in Engineering Education in order to ultimately land a teaching gig. In his spare time, Jeremy enjoys spending time with his wife and kid, playing Overwatch 2, Lethal Company, and Baldur's Gate 3, reading manga, watching Penguins hockey, and traveling the world.

Recent Posts