Craftsmanship - Part 2
This series of posts was first published at the Fortnox developer blog during 2014. The articles where collected, refinished and put on display here after Jonas moved on from Fortnox in 2015.
The series consists of five blog posts:
Software craftsmanship - Part 1 - Covers the main points of Agile, the original version.
Software craftsmanship - Part 2 - Cover the rest of the Agile manifesto as well as talking about TDD and testing in general.
Software craftsmanship - Part 3 - Rundown of software craftsmanship.
Software craftsmanship - Part 4 - Spending some time on the SOLID principles.
Software craftsmanship - Part 5 - Refactoring, with actual code and stuff …
Intro
Today I thought we’d handle the two remaining points on the Agile manifesto. We’ll talk about the import of good people first and then turn our attention to the elephant in the Agile room, test driven development.
I will give some evidence as to why you should choose your job based on people and not tools or salary. And give a “quick” overview of TDD with a slightly evangelical finish. Apologies to those that are tired of evangelical TDD:ers. You might ask yourself why we are so many though :)
Colleagues
One of the more persistent points made by anyone having worked in a few places over the years is usually that the people matter more than anything else. If you have an awesome group of friends the process won’t seem so burdensome.
I bet most developers have their favourite tools; editor, version control system, language, framework, the list goes on. But imagine that you get every tool and toy you could ever want but your tasks feel meaningless, your boss ignores all your efforts, your colleagues scorn your work or ignore you. How long do you think it would take you to go look for another job? If you were smart, no longer than three months, just to avoid any permanent damage.
As humans we are fundamentally social creatures. We evolved in tight family groups working together literally for our survival. With the advent of fire we became slackers for real. Even today a camp fire has a magic lure to it and there is a very good reason why in the military you make camp first, then light the fire. Because once the fire is going all you want to do is sit down, stare into the flame and share hunting stories with the rest of the flock.
In modern life the water cooler, metaphorical or real, has taken the place of the fire. We have coffee breaks now, but the need to socialise and share our stories is still the same. It builds community and makes it easier to work together.
Without this social web tying us together we work less efficiently. Our insecurity about our work grows over time. We get defensive when discussing problems and solutions. No one wants to own issues or mistakes since that will open up for even more isolation. And ultimately our health suffers without it.
This is why you should select an employer on culture and personal fit over things like salary, tools, projects etc1. If you like your job you’ll do it better. If you work in a nurturing environment where people try to help each other out and share the problems you are far more likely to evolve as an individual and increase in the contributions you make to the group and project.
So when you start looking for your next job remember to pay close attention to these things, in this order: culture, your boss, peers, tools (especially the freedom of tools) and the salary and benefits.
Projects
The Agile manifesto says that we should value “working software over comprehensive documentation”2. While I’m fairly sure all of the signers would rather have both they still felt that programs that actually work are better than well documented stuff that doesn’t work. Why make this seemingly pointless observation?
When we build programs like in the anti examples of the last post we tend to get 90% there before the project is scrapped. Since the olden processes often required every module to be fully documented before delivery you would essentially end up with a bunch of modules, most of them finished, that did not work together, at least not to solve the problem in a reasonable way.
Looking at it from this perspective it easy to see that we should probably strive to deliver a working product first and documentation second. Although ideally both at the same time.
Test driven development
So what is documentation? Usually there are at least two kinds of documentation produced in a software project, developer documentation and end user documentation.
The end user documentation is essentially the manual to the program. Theoretically you should be able to evolve this in sprints in step with the rest of the development. In practice this is often done when the project is finished. But that doesn’t matter as much, it’s mostly a non technical task anyway and doesn’t require much developer time, which is what we are concerned with here.
The other kind of documentation is all the development stuff, diagrams, UML, call graphs, comments, project structure and on and on. These are all things produced by the development team as some kind of planning artefact or as a byproduct of development.
There are many processes for documentation, some good and some bad. But they all have the disadvantage of documenting something in retrospect, after the fact. What if we could write the documentation first? How would that look?
Imagine that we are going to write a new program. We start of by writing the user manual, it is essentially the spec of the program anyway right? From the manual we can derive UI screens, since they are in the screenshots in the manual, and from them we can start to derive some backend needs.
We want to be careful when constructing the backend though, it’s easy to make a mess of things so lets start by documenting the endpoints we need. And from that we can document their needs and so on and so on down through the stack through controllers, decorators, proxy objects, mappers, facades, over the network to remote services and databases.
What we would end up with is a complete documentation, down to the method level, of what every atom of the program would need to look like and what it would do. This kind of detailed planning would give us ultimate flexibility of design, making it possible to do sweeping changes if we had to, since we haven’t written any code yet.
But wait, I already dismissed this in the first post, it would take to long! The design would not work when we implemented it since we would forget or miss things. How can we have our cake and eat it too? I mean who doesn’t like cake?3
Tests for driving design
Tests are often seen as a way to verify code, and they do do that, if well written4. But what if we wrote the tests first, for some small part of our new, theoretical project?
Even if the process described above sounds awesome and even if we cant do it we can adapt it to work with our previous decision of small, incremental sprints. When we select a task from the backlog we start out just as above. We plan the end user experience first, the UI and flow of information needed to make that feature all it could be.
We can then take this one step at a time down our object stack and, crucially, we write the tests at each level before going down to the next. Why? Because the act of writing the test will force you to use the code you haven’t written yet. It will make you design interfaces that are inherently useful and minimal, since you are lazy and don’t want to write a lot of tests.
Only when you reach the lowest layer in your stack, the layer where something will have to happen, where the action won’t get delegated to a lower layer, then you write the code. So you have a test for that code, if you run it it will fail. You should run it, you should watch it fail. You should do that to make sure your test is working. Even if this is less important in this specific case, when laying the first brick of our new project, it’s increasingly more important as you go along and build on top of other parts of the system.
You have your failing test. You then implement some code to fulfil that test. You should in fact write the minimal amount of code you can to fulfil the test. When the test is passing you back up to the next level in your call stack. Here you have a failing test that depends, in some way, on the code you wrote in the lower layer. Again you fill in the blanks with as little code as possible. And on and on until you reach the UI layer and have the first part of your feature filled in.
A test suite by accident
When driving features by test like this, and the process is better described and in more detail elsewhere, you end up with a few things for free.5
Clean(er) code
You tend to get cleaner code. Since you are going top down when planning and bottom up when implementing you always have a very specific goal in mind when writing the code. You have to fulfil exactly X for the interface to work for the consumer and so you only fill in X, not Y and Z while you are at it because who knows, it-might-come-in-handy-right™?
tend to do that, if we have an attribute writer we should have an attribute reader right? Heck our IDE even puts them in in pairs! Delete the one you don’t need. It will probably just gather dust and when the next developer comes along he won’t know if it’s needed or not. He might implement some wrapper logic for that attribute and add his code to the reader as well.
Before long your code base has grown cancers all along and contains a lot of iffy logic to deal with edge cases that never occur.
Better interfaces
When we design object chains bottom up without the top down planning we tend to think in terms of the object we are designing, what makes sense to it, instead of what makes sense to it’s consumers.
If I need a string encrypted I don’t need to know about the salt, hash method and number of iterations. The encryptor object can deal with all of that. I just want to go: Encryptor.run( "my secret" )
and get something back that I can stick in the database or send to a client.
Internally the encryptor might allow for all sorts of fancy pants cutting edge stuff, which is fine as long as some part of the system needs it, but the external interface should be small and simple. Ideally just the one method as above.
Tests
Oh, yeah. We get tests too…
Why not excited about this, the most obvious reason to test?6 When you start testing the tests themselves are the goal. But as you go on and adopt a test driven approach you will start to see the other benefits of testing and the pure regression/correctness aspect will pale in comparison.
It’s really nice to have your integration tests, the high level act-as-a-user tests, running on your integration server at every deploy. It’s nice to know you didn’t break anything, or that you did if you did.
And it’s nice to have the unit tests running continually in the terminal to the right. Seeing the green flash every time you save. Or the occasional red when you add a test or break something. The extremely fast feedback cycle reduced debugging to a noop and gives you a feeling of peace.
But while those things are nice and help me sleep at night and speak with confidence to my customers they do not matter as much as the design benefits to your code. The difference between driving a design with test and trying to mock the system in your head and make decisions based on that is night and day, really.
You should try it.
Rounding off
If I could make developers in the word try one thing and one thing only, it would be to act with kindness towards all other beings. But if it had to be programming related it would be TDD.
It’s not something you pick up and love from the start, at least not for most. It’s something you slowly come to grips with. Something where the benefits starts out as one thing and slowly morphs into something completely different and so much better.
It’s something of a religion. Something that the converted speak of like I do and the sceptics don’t want to believe. But there is a way to know for sure, try it.
It’s legal. It’s good for your (mental) health. It’s free. It comes for any language. It’s in color and it runs on any platform. The only reason not to try it is that you are afraid I’m right and it will make you look silly when you come back in six months and have to admit that, yeah, maybe you’re starting to understand some of what I said. But that you wouldn’t go so far as …
That’s when I know it’s just a matter of time until you buy me a beer for introducing you to TDD :)
Next time
Enough of all this agile stuff already! Weren’t we supposed to learn about craftsmanship?
On why we have spent all this time on agile and what more there is to it is what I’ll talk about next time. Until then, be well and refactor mercilessly!
Image courtesy of Nick Normal at Flickr under a Creative Commons - Attribution, Non Commercial, No Derivatives license.
-
“Individuals and interactions over processes and tools” from “Manifesto for Agile Software Development” ↩
-
“Working software over comprehensive documentation” from “Manifesto for Agile Software Development” ↩
-
Some tips on what to avoid can be found in this Writing testable code guide. ↩
-
Kent Beck - Test driven development: By example ↩