Antipatterns in Software development

Cosmin Vladutu
8 min readJul 20, 2022

--

At the beginning of my career as a software developer, everything looked hard: algorithms, frameworks, working in a team and splitting things and so on. After some time everything started to get easier and after 4–5 years I thought there wasn’t a problem that I couldn’t solve. I thought I was invincible. I was like: Give me the problem and a few hours on google and I will give you the solution. After some more years, things got again a lot harder, because I understood that I wasn’t really invincible and even searching on Google, it’s an art and even when I had the solution it might have been not the best one, or if it was, for sure in a few months it became just another legacy code, that could be improved; so as a conclusion as a junior everything was hard, as mid everything was simple, as senior everything got hard again.
After this long introduction, I had to share with you some antipatterns that I saw in my professional life (until now), antipatterns which if I knew them at the start of my career, everything could have been much simpler.

Antipatterns from my point of view have 3 causes: lack of knowledge, no standards or laziness.

In the day-to-day work as a regular developer

  1. “Do that, because everyone does it” or “Do that because this is how it’s done”. This is usually the most annoying thing for me. People write pieces of code, without knowing why or what’s the reason behind that logic, or without understanding what that code is doing, or its purpose. This is the biggest problem in a developer’s life. In my mind, NO ONE should even type one word without knowing why it’s there. This can be called copy-paste programming and it’s bad on so many levels.
  2. You need to add an operator for example, in 5 places, because even if in all 5 the same operators are used, the code is not extracted in only one place — poor quality code / bad maintenance of code. If you don’t take time to refactor, you’ll end up needing exponentially more time for each new operator you’ll need to add. Even removing one will have unforeseeable consequences and you’ll end up telling your PO that the cost is too expansive than the value that the change will bring to the app. This is all on your shoulders because you didn’t refactor on time!
  3. Boat anchor — Similar to the second antipattern, this is more related to keeping and maintaining code that it’s not necessary anymore, only because you that are afraid of deleting it and the consequences of doing that.
  4. Spaghetti code — This is the most known antipattern. In short words, it’s a code that is so coupled and hard to maintain, exactly like a plate of spaghetti.
  5. Accidental complexity — Only the person who wrote the code understands it. This is usually because he wants to write “clean code” (which is a thing that nowadays is so abstract, so, everyone understands something different from it), but ends up with a code so complex, that the only person who understands it, it’s only him, and the unconscious reason is that, he wants to show everyone, how smart he is.
  6. Hardcoding stuff — Every configuration, magic number or magic string, should be in its own place, not in the methods or wherever it’s used. The configurations should be real configurations that can be changed from a settings file or something, without needing a redeploy of the entire app, the magic strings or numbers should be constants or enums, depending on the language that you’re using and the needs of your app and so on. You can imagine a magic number if you need to change it, and it’s in 30 places…
  7. Poltergeists — Also known as Gypsy Wagons pattern, is the idea of adding unnecessary abstractions or creation of objects with the only purpose of moving data around. You can think of creating an abstract class or an interface that has only one implementation, (without really needing a contract, if we’re talking about interfaces), or creating DTOs without really needing them. Disclaimer: You can pass data between layers using tuples for example and you don’t create any unnecessary classes or structs.
  8. Infinite memory — Even in our days (yes in 2022) I still see the “Out of memory” exception. Keeping objects forever in the memory, or including every relation an object has, when you bring it from the database, will eventually cause you trouble.
  9. Low details on errors. Tell the user as little information as you can, when you have a problem. This was maybe a good idea 10 years ago. Yes, you should give him a clear message, but you can also add an error code, based on which you can debug easier. In production for sure, you won’t have “verbose” logging, to see exactly what happened, and even if you have that, for sure, there will be too much information for you to find the exact moment and info you need to find out the user’s problem.
  10. Reinventing the wheel — Another classic; This usually happens when you want to have your own implementation of everything, and you are afraid of using any provider or package. Are you sure you can do a better job than the guys who made that package? Are you sure you have enough budget to create your own package? Usually, the answer is no.
  11. Premature optimization — People tend to refactor and refactor to make optimizations over optimizations before going live. Most of the time this is a waste of time and resources.
  12. Bad usage of branching:
    The fear of doing it —
    People fear merging because of consequences. The same with commits. People fear making commits because they think someone might look over their code and see “bad things” or “not clean code”, even if it’s just work in progress
    Big bang merging — merging a very big branch with a lot of changes that no one knows what’s about. In the end, you’ll get a lot of conflicts and it will be very hard not to miss something (or auto-merge will kill you slowly)
    Branching all the way — Create branches for absolutely every change. Create branches for every small task, all targeting the main branches, not a feature one.
    Branches based on team member names — I know, it’s easier to find them, but you just create a wall in the team. Branches should be for features, bugs, and business things, not split by team members.
  13. Over-engineering — and it’s counter, KISS (keep it simple stupid). The problem is that what for me is simple stupid, for you might be pretty complicated (or the other way around). You need to keep it in balance
  14. Copy, paste — Even if ut should be safe, because in the end you are copying from a place in which you know that code works, it creates an unreliable code, and readability goes lower ( because most of the time what you copied must be changed only a little bit, and you get into the thing that actually is the same but not 100%). Things about a code that has 19 lines identical, and one different. What are the chances that a new guy will actually see the difference?

When you need to make architectural decisions

  1. Dependency hell — Every module depends on a lot of other modules that depend on a lot of others and so on. When you need to upgrade one, everything goes to hell, because no module will build, incompatibilities between modules will appear because of versions and so on. Agree on a clear architecture and stick to it.
  2. Architecture By Implication — Designing an API for example without specifications and documentation. This usually appears when devs do not see any benefit in documentation, they don’t see how that API integrates itself in the “bigger picture”, or they are just too confident in their “power” of writing quality code. As a result, in 2 years tops, you’ll hear a lot of “WTF”s per minute from the team who will maintain that API.
  3. Forced requirements — Tools or frameworks that you need to use, because they were agreed on a higher/corporate/enterprise level, and you are actually forced to implement or make workarounds to use them, without any real reason, a benefit for the project or any other positive thing for the end-users. This happens usually because some decisions were taken long ago and no one cared much to try and change/challenge them, or because some architect/tech lead does not want to get out of his/hers comfort zone.
  4. The silver bullet is also known as the Golden hammer — The best example: Microservices everywhere, all the way, for everything. You know an architecture/package/provider and you implement it everywhere for every problem. This is a big no-no!

When you need to take management decisions also:

  1. Smoke and mirrors — Try to impress the customers by promising features without talking to the team first; always say it’s 90% done. Create false expectations for all the stakeholders and after that just put pressure on the team to deliver (and if they don’t try to find excuses and try to make it look like a minimal impact).
  2. Keep a hidden agenda — Usually, this happens with deadlines. When you are in a management position you tend not to say the real deadline to the team, or tell them a date not so far in the future, to put pressure on them. This is really bad since, in the end, they will lose confidence in you.
  3. Absent manager — The person in charge is not there or doesn’t take any decision or action. He does not commit to anything and he doesn’t even try to find excuses for it. Usually, this happens because of his fear of doing a mistake or because he doesn’t really care about the product. He is convinced that everyone else should be fine and work without him taking the decisions. Lately, this kind of manager found another concept that he understood wrong and he displays it everywhere: He is managing a self-development and managed team.
  4. The chosen one — This happens when all the rewards and examples are towards a specific team member. This in the end will get to a low motivation in the team.
  5. 9 women can’t make a baby in a month — It’s a classic. If a deadline is too close and the development is not done, usually adding more members to the team should make the deliverable be done in time. This is nonsense. People need to adjust, understand the project learn the standards and get on the same line with all the other team members. This takes time, and usually, when you add more people the productivity will go down before it goes up.
  6. Cart before the horse — Too many resources are allocated somewhere with a low priority, or to a project part that is dependent on something else. To mitigate this is pretty simple: do proper research, planning and consulting. Do not rush to make your teams “write some code”.
  7. Death march — The person / people in charge just deny the obvious or do not accept that a project is a total failure, and still push resources and money into it. If you see the project is not going into the right direction or you know it will fail, you need to pivot to something else, not go on the same path.

More might be out there, but those are the most common antipatterns that I saw during my career. My advice to you is to try to avoid them, because, for me, they never ended in a good way.

--

--

Cosmin Vladutu
Cosmin Vladutu

Written by Cosmin Vladutu

Software Engineer | Azure & .NET Full Stack Developer | Leader

Responses (2)