Project smells: or loose thoughts on what to strive for in the code development process

Mateusz Górski Mateusz Górski • Dec 20
Post Img

Why this article/intro

Once, I was involved in a project that I mostly enjoyed. We were trying to be consistent and follow good practices, we had our workflow. Hence, it’s pretty obvious that when I had to change the team, I had to get used to the new flow –  and that’s always a struggle. The very first time I looked into a code base, some of the things I took for granted in recent times turned out not to be granted. Of course, I’ve been there, done that but during these two years of “good practices”, I somehow managed to forget that most of the projects/teams/code looks quite different.

Surely, code and process refactoring takes a lot of resources, mostly time. So the purpose of this article is not to point out the things that are entirely wrong and you should immediately change them; rather to show things that might smell a bit or present ideas to consider when writing code. I just took the point of view of a (potentially junior) programmer who joins the project at the beginning of their career and assumes (oftentimes wrongly) some practices are good.

Some of the following points will refer to things that you can consider smells. Some of them are rather tips or things to pay attention to – in this case, lack of them might be a red flag.

Disclaimers

1. I’m not referring to any particular project I am or was involved in. This change of project just gave me the motivation to look back and consider all the practices that were followed in various projects throughout my career. 

2. There’s no silver bullet. The one thing to keep in mind is that all the practices can be traded (and that can be positive).

3. This article is supposed to be high-level. I’m trying to mention (or link to) some key ideas that you can investigate further.

Tests

This is probably the crucial one, that’s why it’s at the very beginning (and is the longest one). You’ve probably heard it so many times that untested code should be treated as the code that doesn’t work, and that you should write tests. I’ve heard it too and still, it’s not that obvious. A couple of ideas to consider (related to each other):

  • Good tests = good documentation. For a new joiner, nothing is telling more about the code base than good-written tests.
  • You should focus on good naming of tests (certainly, that refers to the previous point). Don’t be afraid of long names. Consider being in the context of a command handler (e.g. UpdateAdressCommandHandler) and testing an error scenario. You could use a name like test_fails but also test_raises_validation_error_when_incorrect_country_code_provided and the latter would tell something about the logic.
  • Keep in mind that testability is a really important factor to think of when writing the code. Be sure to know what dependency injection is.
  • Tests are a part of the code base. When you think of writing clean code and following good practices, apply it also to your tests. 
  • 100% code coverage is not unreal. I’m not saying it must be good but I was involved in a project in which we had 100% coverage and it was very good. So, at least, consider it, and don’t prematurely assume it would be a pain in the neck. 
  • You can test the stuff from the client’s point of view (usually it’s API). If the process involves multiple layers (e.g. API, command handlers, services, databases), you may check both the action and the result through API. Considering updating the user’s address, it would correspond to PATCH /addresses/{id} and GET /addresses/{id}. Of course, it’s just a happy path scenario but the test already covers multiple layers. If, alternatively, you decide to test every small function/class separately, that might make your code rigid. By the way: one shouldn’t test results by querying the database directly. That’s smelly. 

Dependencies between modules/components

You don’t have to always use 100% clean architecture but sometimes it’s helpful to think about The Dependency Rule that specifies the relationship between architecture layers (the inner should never rely on anything from the outer). We could simplify it and consider: does module A depend on module B? If not, module A shouldn’t import anything from module B. If the relationship between two modules is specified (one-direction imports) it’s easier to test and maintain. The popular problem of circular imports is a typical smell here. There are multiple ways to solve this trouble but it may indicate an issue with the design.

Also, don’t forget about the encapsulation and that it also applies to the level of modules (not only classes). 

Programming frameworks

Once, I participated in a survey regarding programmers’ satisfaction with the tools we use. My colleagues were asked about the pros and cons of frameworks we use for various projects. One of them is Django, and only one person mentioned the disadvantage of active record pattern. The rest was really satisfied. I’m far from saying that Django is bad. But the conclusion from this survey is: we should be aware of the design patterns that frameworks we use impose on us and what consequences it brings to our code and tests. Some of the approaches that one framework takes for granted, others perceive as anti-pattern. 

Meetings/SCRUM

I think, recently people are very much aware of these but still, it’s always good to remind us how the process can be improved. 

  • Too many meetings is a bad idea. They are very expensive for your team. Keep the essential ones.
  • Don’t be imprisoned by SCRUM. It’s created to help, so take whatever you need from that. 
  • Standard estimations of Jira tickets are not always helpful/necessary. Keep in mind, there’s another side of #noEstimates.

It’s just a code

All these things are really important and may improve the maintainability of the code and, in general, the whole process of software development. But keep in mind it’s just a code. It serves some business purpose and it’s designed to perform very specific tasks. It’s really important to consider, especially during code review. It’s easy to fall into a pitfall of thinking my way of thinking is the only right one. There are other ones too.


Drive tactical delivery
without inflating the top line

Your Swiss Army Knife in AI, Cloud and Digital

Get in touch Button Arrow