8 Common Web Development Mistakes

And how to avoid them

Markus Hatvan
Oct 26 · 9 min read
Clean workspace of a web developer
Clean workspace of a web developer
Photo by the author.

I recently joined a small startup where I helped create and deliver a web application for an important clinical study that focuses on COVID-19. The only problem was the deadline for the project was in two weeks! That alone already sounded scary and stressful, but I decided to accept the challenge.

On top of the looming deadline, the junior developer who was mainly responsible for leading the project was understandably overwhelmed by the enormous workload. As a result, the code output was rushed and was a complete mess. The scope of the project was simply unrealistic for a team of two developers to manage in such a short time span.

In the end, the minimal viable product was deployed and working with minor hiccups, but it now requires a major overhaul and refactoring effort due to the messy way the code was written. It’s a daunting task that will require a lot of time but won’t lead to any additional revenue for the company.

This could have easily been avoided from the beginning if the project had been set up correctly and used some best practices.

After working on many diverse projects, I have created my own list of must-haves to ensure a successful project and great developer experience.

To save you valuable time on your web development projects in the future, make sure to avoid these eight common web development mistakes.

1. Not Enabling Code Quality Tools Right Away

This should always be one of the first tasks on your to-do list when you begin working on a new project. Make sure code quality tools are in place based on the project’s needs. You will be grateful for it later.

When I joined the aforementioned project, nothing was set up and the code was inconsistent, with mixed-up usage of quotes, missing .catch() blocks, and various formatting issues.

ESLint will save you from producing errors like these that could have been prevented in the first place. After running a lint script on the project for the first time with an opinionated configuration, there were 200+ warnings and errors waiting to be fixed. Fun.

I know, I know, it can be hard to make the configuration work exactly as you need it to. Additionally, the project owner wants to see actual results and doesn’t care about you spending precious time configuring development tools. But it is such a worthy time investment in the long run and shouldn’t be delayed. In the end, it will make you even more productive when you have a project that is clean and error-free, which is beneficial for everyone.

I recommend using all or some of these packages for your configuration depending on your needs:

  • eslint or @typescript-eslint for a base rule setup.
  • eslint-plugin-import for clean and ordered imports.
  • eslint-plugin-jest for writing better and stricter unit tests.
  • eslint-plugin-node for back-end development and supported node version feature checks.
  • eslint-plugin-promise to avoid missing .catch() blocks and other bad practices when working with asynchronous code.
  • eslint-plugin-jsx-a11y for writing accessible code in case you use React.
  • eslint-plugin-unicorn for miscellaneous helpful rules.

On top of the recommended configs that give you a base rule setup, I add additional rules like eqeqeq, prefer-template, prefer-const, and no-var, which are not included in the recommended config out of the box.

Apart from avoiding nasty bugs and writing bad code, you can also gain an incredible amount of knowledge by simply following lint suggestions and looking through the ESLint documentation on why a specific rule exists and why it is necessary.

On the other hand, Prettier will make sure that the whole team conforms to the same stylistic code formatting guidelines and the achieved readability will save you time as well. The default configuration settings provided by Prettier out of the box are great, so I usually only have to do minor tweaks. This is a minimal .prettierrc.json configuration file that I tend to start with:

After setting up ESLint and Prettier, you have a solid baseline of code quality tools in place that will greatly improve your developer experience.

2. Using Outdated Dependencies

Various packages of your project are multiple major versions behind. The package.json dependencies haven't been upgraded for more than a year. You could delay upgrading and hope you never have to. But believe me, you will as soon as support for that old Node.js version drops or when there are new code vulnerabilities discovered in the old dependencies that you are using. Additionally, you would love to use the latest features of a library, but you can't because you are stuck with an old dependency version. Sound familiar?

Whenever I create a new project or join an existing one, one of the first things I do is check the package.json for outdated dependencies. Make sure that the dependencies are at least somewhat up to date to have potential bugs and security vulnerabilities in your third-party libraries fixed. For existing projects, ask the main responsible developer if there is a good reason why the dependencies are not up to date.

I personally create a dedicated package.md file in projects that I work on that looks like this:

This way, every collaborator of the project will be informed about known dependency upgrade issues.

Always keep this file up to date when you run into dependency issues or resolve some. Ideally, the file stays empty and everything can be upgraded as expected.

3. Writing Variable Names and Comments in a Language Other Than English

Easy rule of thumb: If you or others reading your code need to spin up “Google Translate” to understand what is happening in the code, it is a waste of precious development time. Translating code should not be a part of being a web developer.

In the MVP project, the entities that were coming from MongoDB through a Node.js back end had some fields named in German and others in English, while the front end was mostly using English. This required a lot of unnecessary mapping from one naming convention to the other. It was not possible to use object shorthand and it was easy to forget which field was which. Additionally, any developer who might join the team and is not a German native speaker would have problems understanding the usage of each field.

Stick to keeping the whole code base in English. Apart from variable names looking weird in other languages like German, you exclude international developers from understanding what is going on in the code. Whenever you need to have words displayed in another language than English in your user interface, you can use libraries like Format.js to handle internationalization needs.

4. Different Naming Conventions Throughout the Whole Project

Try to avoid mixing up different naming conventions for HTML, CSS, and JavaScript code. Don’t use kebab-case, snake_case, and camelCase all over the code base or you will get confused quickly and lose productivity.

Learn about the different naming conventions and why they exist. I recommend you stick to the coding conventions of the language that you are using. Native JavaScript methods like .toLowerCase() are written in camelCase, so why would you write your variables with any different casing? While JavaScript uses camelCase, remember to use kebab-case for your HTML markup and CSS styles.

5. Using Meaningless Variable Names

I am sure you have seen code similar to this before:

What values are stored here? Is Gabriel the first name or last name of a person? What is x that gets mapped over? Is it even an array? What does the variable stuff hold?

You should not have to waste valuable cognitive energy on deciphering what you and others wrote. You should be focusing on bug fixes and new feature implementations instead.

You might think that it is cool to write short and cryptic variable names and expressions, but it’s not. Coding is not about writing the least amount of characters but about producing business logic that is easy to understand, valuable, and doesn’t need a refactoring right after writing it.

Let’s take a look at a good example:

Here, we can assume a lot more based on good variable naming and clarity, which means lesser cognitive overhead for the developer.

Your future self and your coworkers will be thankful when they still understand what each line of code is doing — even a year later.

6. Leaving console.logs and To-Dos Scattered All Over the Code

This is bad for the developer and user experience at the same time:

It can be embarrassing and unprofessional to leave console.log() messages in the dev tools for every user to read. On the developer side, it can be distracting and discouraging to find to-dos of what needs to be changed without detailed information and console.log(), which might be needed or not.

I recommend using the no-console ESLint rule and configuring it as needed. I tend to mark console.log() as an error and use it in conjunction with lint-staged pre-commit hooks to disallow commits that fail on lint mistakes. When you want to keep logging information, you could use console.info() to show the specific intent of requiring outputting information at that point.

If you cannot give up your console.logs in the code, you could opt in for a plug-in like babel-plugin-transform-remove-console or terser-webpack-plugin to strip out console messages for you based on the environment.

To-dos are preferably put into separate issues on your repository management tool. Make sure to provide enough information for another developer to start working on it without having to sync up with you first. Additionally, when putting to-dos into issues, every developer will be aware of them rather than stumbling over random to-do comments in the code base.

7. Mixing Up async/await, Promises, and Callback Syntax

Producing mistakes in asynchronous code can lead to bugs that are very hard to detect, so make sure to stick to one paradigm at a time.

Let’s take a look at a real-world example from the MVP project:

Even for me with more than four years of professional experience, I have a hard time figuring out how the code would flow here based on different outcomes.

The code example above shows missing knowledge about how async/await works. The code starts with usages of async/await, which is great for writing readable and concise code, but then it becomes unclear:

  • When does the function return true?
  • What does it return when we run into the catch block of the logoutToken.save() method?

With a few simple changes, we can drastically improve the code flow:

  • The code should be wrapped in a try/catch block to avoid the well-known UnhandledPromiseRejectionWarning message in Node.js.
  • Remove the .catch() block on logoutToken.save() since errors are caught in the catch statement of try/catch.
  • Stick to either using async/await or the Promises syntax. It could also be a good idea to consider not only returning false when jwt.verify() fails but to explicitly throw an error instead.

These code design mistakes can be deadly — especially when there are no tests written for this piece of code.

8. No Unit or End-to-End Tests for Complex Business Logic

This is so common that it is a running joke amongst the web development community. I still remember working at my first job and there were zero unit tests written for the project. When I asked about it, everyone said, “It would be great to have test coverage, but there is not enough time.”

Since unit or end-to-end tests don’t bring added value to the customer, they are often skipped and neglected. A common phrase is “We will do it in the future when we have time,” and then guess what? It never happens.

At the second company that I worked for, there were also almost no unit tests written for the front-end part of the project. By then, I was more experienced as a developer and motivated to do my team and myself a favor, so I started to implement unit tests here and there whenever a task was finished earlier than expected.

For one task that I had to work on, there were so many potential edge cases, so I started using “Test-driven development” (TDD) and wrote the tests before the actual code. Although I had to write the unit tests in addition to the business logic, I ended up finishing the issue around 30% faster due to having the “safety belt” of the unit tests catching all potential errors and edge cases. The test coverage will prevent you from looking for a bug in the wrong place as well.

Bottom line: Write unit tests when possible — especially for complex snippets of code — and use end-to-end tests, at least for the critical parts of an application.

Wrapping Up

Although I understand that time pressure alone can be the reason for some developers to throw code quality standards overboard, I highly advise pushing through while still doing your best at delivering clean code.

The minimal amount of time that you save just by duplicating code snippets and hacking away until it works will then start to pile up as technical debt, and it will take you even longer to fix afterward.

It will not always be possible to write the cleanest code, but don’t let that discourage you. After all, we are just human beings and make mistakes.

Have you made any of these or similar mistakes before? Leave a comment below!

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store