Familiarity seems like a good thing, you know the thing, how to deal with it, what to expect and where problems might be hiding. But more and more I see its dark side.
Some time ago I was working on a legacy system. It was very complex and fragile, most developers didn’t want to have anything to do with it. We knew the time would come when we would need to touch the core of this monster, so we wrote some tests and started slowly cleaning it up. But still, test and release process was quite complex, even if partially scripted. Long story short, each release was taking a lot of manual work. As in the end, I became the sole maintainer of the app, I was following the process quite often and luckily avoided any major mistakes. It took some time, but I got used to it. Only when someone joining the team asked why I was doing all this manually despite having Jenkins around, I realized that this “getting used to” went too far. My familiarity with the process was so big, that I didn’t see it as a problem anymore, it disappeared from before my very own eyes. The process was working, so I stopped thinking about it.
Recently, I saw another situation where familiarity had led to problems. I was part of a team, that started developing a product, but some time later I was assigned to another project. However, from time to time I got some pull requests to review. One time I got another one: I looked at the code but honestly couldn’t understand it. Fun fact: I was still on that project when this particularly troublesome class had begun to look like a mess. I even went as far as to roughly compare code from my last commit to the project with what I got in this pull request. There was certainly more code, but the quality and style were comparable. The author of this pull request even told me, that he had already cleaned up things a bit. Still, for me, the code was very hard to read and this review took me something between 2 and 3 hours.
In both cases, someone being familiar with the code or build system was, unwillingly, preventing others from easily joining the project and starting to work on it. In those examples, I was both a victim and an offender. In the first case, I created a problem for others by becoming blind to its existence but also fell victim to it by having to explain the process to others. In the second case I was partially responsible for the code quality of the project by having written parts of it but then fell victim to it as it was very hard to understand the code.
Of course, it’s not all bad. Familiar code or tooling means faster development. It’s easier for us to do this next thing we need to do because we’ve probably done it before a few times. If not, then at least we roughly know how to move around and know basic principles, so we can imagine how this problem should be approached. Imagine yourself trying to write an application in a language you don’t know. Now take others that have been using this language day in, day out for years. They’ll be much faster than you, at least for some time.
One other aspect of it is that we can be tempted to go for already known solutions even if they do not fit.
I’m a Java developer and almost every project I’m part of is using Spring or Hibernate.
By themselves those projects are not bad, they do help solve some serious problems.
But that doesn’t mean we should be using them all the time.
I fell into this trap at least once.
I had a small application to write that had to do a REST call to another system.
It was simple enough that I didn’t require any complex, full-blown REST client, so I decided to use Spring’s RestTemplate.
I noticed that I was annoyed by how long my integration tests were taking to execute.
The reason was that the whole Spring
ApplicationContext had to be started.
I did my homework, analysed the application and found out, that this RestTemplate was the only thing I needed from Spring.
And the only thing I really needed was an HttpClient, so I ended up using HttpComponents from Apache and my tests became much faster.
At the same time, I didn’t have to sacrifice anything, the application itself was still usable, the code might have become clearer and easier to use.
The fact that something is familiar to us and it’s easier for us to work with it doesn’t mean it applies to everyone. The fact that for us something is easy is very subjective. We might have been using this particular technology for a very long time, but for a newcomer, it might look more like magic than technology. By constantly using the same tools or ways, we might be preventing others from joining us, or making the process very hard. We might also be losing opportunities to learn something new.
We need to start questioning our choices from time to time. In my last example, I was given a hint: long-running tests. If we keep our eyes and minds open, we might be able to spot issues coming from the tensions between the problem we’re trying to solve and what our technology choices are good for. For example, relational databases might not be good for storing the data your application is using. If you’re working mostly with big binary data with a bit of metadata attached, you might want to look more closely at things like CouchDB or MongoDB. Even if you have never worked with any of them before it might be worth the effort to learn one of them.
On the other hand, for your other system, a relational database might be a perfect fit, but you might find out, that skipping a Hibernate/JPA solution might lead to a simpler and faster application. SQL is a very good language for data processing, its implementations in various databases are highly optimized and efficient. Is it really always a mistake to not abstract SQL away and use JDBCTemplate? And if you ask yourself how high the chances are that you’d need to replace your database engine, you might find out that abstracting the database is not worth your time.
I’d suggest that quite regularly we need to stop doing whatever it is that we’re doing, take a step back and look around, maybe look at things from a different perspective. Think if there are places in your system that are particularly troublesome. It can be anything: fragility, complex code that is difficult to understand, tests requiring a lot of setup, a lot of boilerplate code, etc. Those might be signals that the current implementation somehow doesn’t fit the reality. This tension or friction is a hint that the system is giving you, suggesting that there is room for improvement. Once you put a bit of effort into trying to find out what makes this piece of code so problematic, you might discover better ways of solving this particular problem that the code is responsible for.
This approach is certainly not limited to code. The whole agile movement is about constantly adapting and improving, never settling for “the perfect process”. Even if something was working in your last project doesn’t mean that it will fit your next one. Sure, if you’ve been doing something for quite some time already and it’s (still) working, it would be a good assumption, that it will also be OK in the future. But keep your eyes open, because just this time the situation might so different that you’d need to find another way. For example, so far your team was mostly developing new software and scrum was working OK for you. But now you’re taking over a project that includes a lot of ad-hoc support tasks that take precedence over regular development. Because you cannot predict how much firefighting you’d need to do on any given day/week, scrum with fixed-length sprints might not be the best idea, maybe kanban would be a better choice, better suiting the problem of organizing your work.
If all you have is a hammer, everything looks like a nail. What we already know is not always the best, though it might seem the easiest. We should be questioning our choices constantly and look for solutions to the problems we’re supposed to solve instead of bending the problem so that it can be solved with the toolset we’re familiar with.
I’d like to thank Ben Wolf for all the feedback and fixes he provided.