If you want to write maintainable and robust code, following the set of rules I lay out here will help you a lot. They might seem like common sense to more senior people, but remember that your junior colleagues might not even be aware of them. If your colleagues know them but don’t follow them, you should consider having them copy down this article 10 times on a whiteboard. It may help.
Write tests. Please.
“What for? I write perfect code every time!” or “I’ll just test it manually…” are the most common comments I hear, but this is a short-sighted approach. Sure, writing tests takes time, but it’s time well spent — every time.
The immediate benefit is that you don’t have to test your code manually with each change (which is often much more time consuming than writing tests in the first place). Another undeniable plus is writing test cases forces you to think about what could go sideways in your code. You’ll be surprised at how often you find use cases and roadblocks that did not come up during initial implementation. Third, you don’t have to wait for full implementation to start writing tests.
Tests also help document your code’s behavior by choosing suitable test cases and how they are named. For example, if you are testing the parsing of API response and there is some initially unexpected response for which you have to branch your code to handle it properly, add a test case that covers this case — and name it properly.

Do code reviews properly.
If your code reviews are something like, “meh, looks good to me, approved,” you are almost certain to run into trouble down the road. During code review, you may gain new insights into the problem(s) you were trying to solve, or even learn some cool new feature you were not aware of. Whatever the case, it’s always better to have at least one person go through your code — a different point of view is almost universally helpful.
If you are the one doing the code review, please take your time and do it properly. Don’t just check that the reviewed code does what it is intended to. Try to find a slightly different solution, challenge some patterns, or just imagine what could go wrong. There is always something.
Always run your code before submitting it for code review or deploying it.
Deploying code to production with a function name changed in only half of its use cases would make me rethink it. We are all human, prone to errors, and shouldn’t be so sure of a code we’ve just written. Always run it locally first. That extra ten minutes you spend testing your code is much better than blind belief. In the end, you will waste much more time than you thought you had saved.

Use database transactions.
If you like inconsistent or duplicated data feel free to ignore this point. If you don’t, learn from my mistakes and use database transactions from the beginning of app development. Having to add transactions to an app that is already two years into its evolution is a pain in the ass.
I can’t stress enough how crucial transactions are when you’re working with databases. Without them, you can encounter a situation where you have an error halfway through your code and some data may have already been written to the database. Start the transaction from the highest level and pass it down to all modules. This approach ensures transaction will rollback on error, so you will either have consistent data, or no data at all, in your database.
Update dependencies.
One of my worst fears is jumping on a project where nobody has updated dependencies since…the beginning of time. Not only are you lacking those sweet features you’ve grown accustomed to, you also have to face the massive backlash when you decide to update them. Rewriting half of your codebase due to technical debt is a very enjoyable activity that I highly recommend to everyone.

Structure your code.
We can all agree (I hope) that a file containing 10,000 lines is a particular kind of hell. It gets even worse when you realize that 2/3 of the code could be in separate files. You can avoid endless scrolling by separating code to files that group by similar logic or responsibility.
I confess that I always thought having small modules containing only one function is overkill, but they are actually perfect for keeping your code clean and readable. Another advantage of small modules is easy testability. Proper naming goes hand-in-hand with structuralization — if you don’t name files or folders according to their purpose, finding something in a large codebase will quickly descend into a nightmare.
Use linters.
Nothing is worse than a codebase that doesn’t follow a unified code style. Code that follows a unified style is much easier to read, and helps orientation in your codebase. It saves time too — for example, if you use Typescript and prefer readonly properties in objects, you will save at least one iteration of code review having this rule enforced in ESLint.

Don’t use Any type
There is a special place in hell for people that do this. Why do you even use typed language if you are too lazy to use proper types? Always invest the time to type everything properly. If you have to do any refactoring in the future, having strict and proper types makes this job much easier.
Don’t get mad at your code reviewer.
I am guilty of this, but it’s essential to get rid of it. It’s really easy to think you have written perfect code…until someone else looks at it. If you are like me, you will eventually get mad if your code gets returned to you a few times. But in most cases, it’s for a good reason. Whatever it is, keep in mind that, in the end, you both want the same thing: robust and readable code.

Conclusion
I know it’s not always easy to follow best practices due to…whatever reasons. But, please try — and try hard. Investing more time in initial development almost always pays off, and it’s always better to try to do get things right the first time.