5 More Mistakes to Avoid as a Software Engineer

Tim Estes
7 min readFeb 22, 2022

--

Traveling the software developer road isn’t an easy one. There are a lot of small decisions that can lead to unforeseen consequences later on. On this series, I talk about the mistakes I’ve made on my journey so you don’t have to.

It’s been almost a year since I published my last Medium article about mistakes to avoid. I thought it would be appropriate to curate another list of mistakes I worked through this year. Who knows, maybe it will become a yearly tradition!

For those farther down the developer path than I, some of the mistakes I will be talking about seem obvious. Perhaps they are. But to the uninitiated, some of these things are hard to see or anticipate.

Look forward to Part 2 where I talk about some less technical (but more personal) mistakes.

Let’s jump into it.

Mistake 1: Not Writing API specs

I’ve built a lot of Flask APIs for my job. I did an “ok” job of trying to document all the endpoints, but when it came down to having other developers actually use the API, I had to spend a lot of time in Slack answering questions.

A couple months ago, my friend introduced me to the OpenAPI specification. OpenAPI uses a yaml or json file to precisely describe how an API behaves. My favorite part about OpenAPI is the Swagger UI that allows you to visualize the specs in style. The coolest part is that this can double as documentation for your API!

The following is a screenshot of an API that should be familiar to anyone that has used Swagger: the Petstore API. You can interact with it here.

I’ve fallen in love with Swagger UI and use it whenever I’m developing a new API. My favorite combo is VSCode and the Open API extension. It allows you to code your specs on one side and see them update on the right!

Don’t be intimidated by the ugly looking json files. If you follow some quickstarts and understand the petstore example, you’ll be just fine.

It adds some time to development, but you’ll be saving it later on by not having to repeat yourself. Plus, your fellow devs will thank you and be impressed!

Mistake 2: Not Writing Unit Tests

I remember I used to think unit-tests were optional. I don’t believe that anymore. (Cue eerie music)

Once upon a time, a junior dev was tasked with building a pipeline to ingest CSV data. Over many months and man-hours, a pipeline was cobbled together that met the requirements and scope of the project. And everyone lived happily ever after.

Well, I wish that’s how the story went. I was the developer in this story. And unlike our fairy tales, some stories don’t wind up with satisfying and conclusive endings.

As our company matured, so did the requirements for the data pipeline. Things were added on, parts were re-hauled. I started spending a lot of time fixing and maintaining the pipeline.

I could have saved myself a lot of pain and suffering if I had taken the extra time up front to add unit-tests to my code. I had many moments where I pushed up a “fix” to the pipeline, only to watch the it break something else in production.

My brain is finite, and it can’t keep track of all the variables and moving pieces inherent to complex systems. Unit-tests can help cover your bases and force you to become proactive in identifying and fixing problems.

Don’t neglect or ignore unit-testing! Unless you like to live on the edge…

I used to live like this, until I pushed an extra comma to production and crashed everything

Mistake 3: Using Only One Deployment Environment

When developing software for industrial use, it’s common to have separate environments (i.e. one for development, one for end-users, etc.). In my current company, we have four environments, but I remember when this didn’t used to be the case.

In systems where you need to go fast, it’s easy to neglect the common practice of building and maintaining four development environments. I mean, who has time to set up four MongoDB databases. Who has time to set up four clusters in Kubernetes? We need this done on Friday. Can’t we just use one environment for now?

I like analogies. If I had to think of one for this, I’d say that using only one environment for deployment is like trying to build a house while someone is already living in it. One person is just trying to live in the house, but good luck. The carpet just got ripped out, the living room is being fumigated for bugs, and the power is out until the electrician comes back from his 4 day vacation. No wonder the house keeps falling down!

Without separate development branches, its nigh impossible to create a stable production environment that’s usable.

I’m happy to say that things are much better now. The environments are becoming more and more separated day by day.

Testers blissfully ravage the testing deployment.

Devs push new code to development every day.

In staging, internal users make sure things are stable before pushing the new release to production.

Such is the development life.

Mistake 4: Hardcoding Variables

In the attempt to create four different development branches on a project, I decided to have one variable called ENVIRONMENT that would let the service know which deployment environment it was in.

On the dev branch, I hardcoded it to be "dev".

On the prod branch, I hardcoded it to be "prod".

Iterate through the other branches:

Yay I’m coding! Aren’t I doing it well!?

What I was left with were some headaches when it came to merging. Every time development was merged into production, I had to make sure that the ENVIRONMENT variable wasn’t overwritten with the wrong value.

When it comes to dealing with dynamic or sensitive variables, instead of hardcoding them, it’s often better to figure out a strategy for smuggling them into your OS at runtime instead of trying to control them explicitly.

A teammate added some code to our GitLab CI/CD pipelines that would automatically set the ENVIRONMENT variable depending upon what the branch was. For example, any pushes to production automatically had ENVIRONMENT="prod" as an environment variable. Now I don’t even have to think about it!

In the case where you have a secretive variable (think auth-tokens and the like), its crucial to not hardcode. The damage done by a potential breach will be greatly lessened if the attackers are unable to obtain your keys just by looking at your source-code.

We still have to yell at devs who store auth keys in their variables.py files. This is a horrible habit and can compromise the integrity of your code, especially if you are sharing it with other people.

Here are some practical steps for moving away from hardcoding (via Python3):

  1. Install python dotenv.
  2. Create a .envfile and store your variables in there
  3. Use the dotenv.load_dotenv()method to load the variables stored in your .env into your current session.
  4. Access them via os.getenv("YOUR_VARNAME").

Mistake 5: Being Ignorant of Design Patterns

I didn’t take a bunch of computer classes in college. While others where learning about data structures, I was learning about Bayesian inference and Binomial distributions.

Not to say that what I learned in college is meaningless. Far from it! However when it came to software and programming, I had a lot of catching up to do.

When designing new systems or microservices, I would try to hack things together until it just worked. No real blueprint in my mind, just winging it. The solutions I designed would often work, but were too complicated.

My coworker saw this and introduced me to an excellent website that opened my eyes to the wonderful world of Design Patterns.

The refactoring.guru website contains a treasure trove of solutions to common problems encountered in software.

For example, say you are creating a Sims-like video game that needs to generate digital people. People are insanely diverse, and manually creating code for each person would be exhausting.

This is where the factory method can come to the rescue! It suggests to use a Factory class. By calling the factory class, you can create people while also having great control over the attributes of the different type of people.

The following is an implementation in Python:

from abc import ABCMeta, abstractmethod
from dataclasses import dataclass
@dataclass
class Person(metaclass=ABCMeta):
name: str
age: int
@abstractmethod
def personality_type(self) -> str:
pass
@abstractmethod
def talk(self) -> str:
pass
class Introvert(Person):
def personality_type(self) -> str:
return "introvert"
def talk(self) -> str:
return "mhhmm."
class Extrovert(Person):
def personality_type(self) -> str:
return "extrovert"
def talk(self) -> str:
return "lorem ipsum dolor sit amet. consectetur adipiscing elit! bla bla bla."
class God:
@staticmethod
def create_person(name: str, age: int, personality: str) -> Person:
if personality == "introvert":
return Introvert(name, age)
elif personality == "extrovert":
return Extrovert(name, age)
else:
return Person(name, age)
if __name__ == "__main__":
person_1 = God.create_person("John", 30, "introvert")
person_2 = God.create_person("Jane", 25, "extrovert")
print(person_1.talk())
# > mhhmm.
print(person_2.talk())
# > lorem ipsum dolor sit amet. consectetur adipiscing elit! bla bla bla.

In this example, God() was the factory class and it created People() with different attributes accessed by the same method.

Well that’s all I have for this article. Thanks for reading!

--

--

Tim Estes

If I were 5 Jeopardy categories, they would be: Microservices, Magic: the Gathering, Microwaved Meals, The Old Testament, Elasticsearch, and League of Legends