? Our Development Philosophy (2): Collaboration & Testing (archive)

Source originale du contenu

We learned in countless situations within our own team how important these skills really are: very often when things went wrong, it had been due to a lack of testing and/or collaboration. Here are some things we've learned while growing Tower to serve almost 100,000 users.


All Code Belongs to the Team

Code is teamwork, not the work of any individual developer. This also means that none of the code you've written belongs to you – it belongs to the team! Looking at code in this way is helpful and liberating for a couple of reasons:

"Code is teamwork, not the work of any individual developer."

Sharing Knowledge with Code Reviews

Code reviews have a huge potential to lift your development team to new heights.

Most obviously, discussing code in a review session can lead to a better solution than the original developer had had on her own. Two minds working together on the same problem will always have the chance to produce higher quality code.

But other potential benefits of code reviews might be even more interesting. Shared ownership, for example, is crucial to reduce knowledge silos: whenever you find that only a single developer in your team knows a certain piece of code, you are at risk. Whenever this developer goes on holidays, gets sick, or maybe even leaves the company you'll be in trouble.
But even if none of these situations occur: the fact that your team doesn't seem to have a truly shared code base is already telling. Have a look at the previous section and you'll understand why.

Additionally, code reviews can solve the hairy problem of knowledge exchange. If, like most teams, you have a mixture of junior and senior developers, you will certainly want those junior developers to benefit from your experts' experience and knowledge. Code reviews are an excellent way to pass on knowledge in situations like these.

And to make the process worthwhile for both sides (juniors and seniors), make sure your senior developers understand that both parties – the student and the teacher – will learn something on the way!

"Senior developers should understand that they, too, might learn something when mentoring a junior colleague!"

Not Everything Needs Improvement

Since there is no such thing as a perfect solution, you could argue that everything can be improved. And you're probably right. But the interesting question is not if something can be improved – but rather if it's worthwhile to improve a certain part in your application.

We know where our own product, Tower, has its weak spots. And if time was infinite, we would probably polish and improve every single bit. But with time being a scarce resource for any team you will have to set priorities. If you don't, your product will not take the direction you want it to take - or it might very well take no direction at all.

Being able to responsibly ponder if or if not to improve something really is an art. It takes years of experience and, in the best case, even conscious training. Saying no to a lot of things is a prerequisite for achieving anything of worth.

"Saying no to a lot of things is a prerequisite for achieving anything of worth."

True in life, and true in software development.

Ask Questions

When reading other people's code it's all too easy to assume that you would have done it better. On first encounter, this or that line might seem unnecessarily complex or even superfluous. And you might of course be right. But it's very hard to say because of one simple fact: you didn't actually write the code.

From our own experience, we know that even the simplest problem can really contain a surprising and infinite amount of complexity. Only when actually working on the problem – when picking it apart, thinking about possible solutions, side effects, etc. – will one be able to understand its scope.

Therefore, when coming across a piece of code that makes you suspicious, it's a great ground rule to assume a good amount of hidden complexity. From this point of view you can either dive deep into the code and try to understand it and the underlying problem – or you simply get into a conversation with your teammate(s) and ask some humble questions.

Software is Only Complete with Tests

Unit tests are sometimes regarded as the icing on the cake. More appropriately, however, they would be better described as the cake's base.

In smaller projects, you might get away without writing unit tests. In any project with just a slight amount of complexity, however, you will not. And, just as a side note, it's remarkable to see how many projects started simple and very soon became complex. So please don't act surprised.

There are indeed countless reasons why unit testing is essential for us:

"If you have paying customers, not having unit tests for your app should make you shiver every time you ship it."

Good test quality and good test coverage of important code parts are mandatory for larger projects. It made us much more confident that we would always be able to deliver Tower with a high quality and that we would always be able to make changes (big and small) safely and effectively.

Tests Make Development Faster

At first glance, using a test-driven approach would seem to be time-consuming and slow down your progress. And indeed, writing good unit tests takes time.

But over time, you will find that unit testing will in fact make you faster! For one thing, after thinking about your code design while writing a test, you will need less time to write the actual implementation code. You will have done a lot of the heavy lifting already by writing the tests.

The other benefit that will make you a lot more effective in the long run is stability: the likelihood of breaking existing code when making changes is significantly reduced through tests. You will save countless hours of bug hunting that you can now invest in something more useful.

Fixing by Testing

Bug fixing should be inseparably connected with writing tests. Of course you could just track that bloody animal down and simply fix the code. However, I guess that a lot of you are painfully familiar with the term "regression": the annoying re-emergence of a bug that you were so confident to have fixed.

If you want to make sure (and you should!) that a bug is fixed for good, there is only on proven way - by writing a test to safeguard this piece of code.

Test Low-Level Components

While producing a software product that is in daily use by almost 100,000 people, we learned a lot about errors. For one thing, we learned that they are an inevitable part of the business; especially with the diverse and complex system setups that Tower has to deal with, you cannot rule out and safeguard everything. But for another thing, we also learned where errors mostly occured.

In Tower (and most likely in a lot of other larger applications), most errors tend to occur in low level components. The good thing about this is that such components lend themselves very well to unit testing. In this area, a test-driven approach offers a huge chance to improve your application's overall stability and quality with a reasonable investment – by thoroughly testing low-level components.

Efficient Testing

In general, having a large amount of your code covered by unit tests is a good thing. However, if you ever hear a software developer brag about his "100% test coverage", you should be wary. Testing every single line of your application code might sound like something to strive for - but it really isn't!

On the one hand, this would of course afford an infinite amount of time. Much more importantly, however, there is in fact something like writing too many tests! At the latest, you will learn this when making a bigger refectoring: not only your code, but also your tests have to be maintained. When your code changes, your tests need to change, too.

The crucial criteria is value. Does the test bring you enough value to justify writing it? Typically, tests are of low value if they test only very simple logic. In these cases, the burden of having to maintain the test outweighs the little value that it brings.

Writing tests efficiently, then, means preferring fewer high-value tests over many low value tests.

Test, Don't Run

Manually testing a certain scenario in your application can really take a huge amount of time: you have to start the app, navigate your way to a certain view, and perform various additional actions to reproduce a certain scenario.

Writing a unit test for a scenario will indeed afford some time initially. But very quickly, probably already after having to test this only a couple of times, running the test will be much faster than starting the whole application and testing your way through it manually.

Besides being faster, however, the test will also provide better results in terms of quality and stability. In most cases, the initial effort is worth the long time benefits.