Every developer aspires to write good code. Nowadays everyone is talking about clean code. If you ask any developer most likely they will tell you that their code is good and/or clean code.
What makes a piece of code good? Is it code that passes all the tests? If it passes the tests, must it be correct? Is code that meets the end user requirements good? The story is done, so we can do the next one – surely that is good? Is code that is layered good? If all the methods are less than 10 lines and all classes less than 100 lines, is it good?
The biggest problem with “good code” is that all code is subjective. And you only learn how crap it was after you’ve lived with it for a year or two… then it becomes a pain if you didn’t write “good” code… and most likely the person who wrote it isn’t on the project any more. And maybe they aren’t in the company any more. But they probably were very opinionated as to what good code was when it was being written.
Scrum taught me to value small pieces. Small pieces lead you to composition of objects – breaking the system down into smaller objects that combine to become a greater whole. Each part is very easily understandable. Each part is very easily testable. Each part is really very small.
This realisation can come from forcing yourself to focus on testability – which forces composition – which forces you to notice that you have lots of smaller things which are much simpler and far easier to understand. And hence they are easier to maintain. And the complexity of any single thing goes down. Cohesion and coupling become clear. Separation of concerns becomes a primary concern and it becomes easier to see.
Small things are good. Small things optimise for simplicity and understandably.
This does come with the problem that you now have lots of little bits. Now you need to understand how they fit together. This could be conceptually intimidating if you try to hold it all in your head at once. Concerns arise about what if it doesn’t all integrate correctly.
But you no longer need to hold it all in your head. Smaller units of code combine into a single cohesive thing that has a well-defined interface and now you worry less about all the little things – you just worry about how you use the outside interface. This is very similar to a backlog which can at times represent lots of potential stories but the ones further away are stored as epics. The detail is only needed when you need to look more closely – and then you unpack them – just in time.
You land up with small things doing very clearly defined things that are easy to understand. And you trust that they work as the tests specify how they should work and they still pass. The more your system holds together like this along with keeping to small object graphs – the less the concern of integration becomes as your tests tell you how the code should work. And you gain more trust in your system.
You do need to ensure that the implementations of the same interfaces behave similarly. If you’re mocking an interface for testing things there could be a misrepresentation between the mock and reality. But that is a code implementation / design problem that we already have. Failing at that and having things unexpectedly coupled in odd ways that aren’t represented by tests / mocks is possible. But you should understand both sides of the interface that you’re using in order to change the code around it. That isn’t a new idea.
Knowing what you intend to do
Which leads me to knowing what you intend to do! Doing TDD challenges you to really decide what you’re doing before you start. It challenges you to be really in control of your code base – even the stuff you didn’t write where it influences you. And that is a great thing. Being in control of your code base means that the integration problem won’t happen as you really understand how it fits together on both sides of the interface before you modify it.
Small graphs = small messes
Developing using TDD and breaking things small also leads you to small graphs. Small graphs help you to potentially make lots of small messes instead of a few large graphs and one very large, interconnected ball of mud. Small messes can be individually fixed in a contained way. Large messes are far more difficult to fix.
You will never know it all
Scrum and agile ideas embrace the fact that you won’t know enough to start with. TDD enables safely not knowing enough. It allows you to learn and refactor the system in safe small steps as your knowledge grows. Refactoring saves us from having to have that perfect good code up front. Refactoring allows that code to change and become completely different good code – more closely fitting to the actual current purpose – over the years. Refactoring is what keeps the code base fluid and alive and closer to the reality of what the requirements are actually right now – not how they have been hacked on from the design created from the little that was known two years ago. But refactoring can’t be safely achieved without tests around the code to be refactored.
Value Driven Development
When doing Scrum you move from Shu to Ha when you really understand the values and principles of Scrum. When you understand them and really get them you can experiment with things based on those values and principles instead of blindly following what the Scrum guide says. But if you still don’t actually understand you will probably get burnt (and blame it on Scrum).
This is similarly useful when writing code. Understand the values you are trying to live by and optimise for them. Reflect when you fail to achieve them in order to get better. Write code with your eyes wide open so that you always know why you are doing what you are doing.
Optimise for something
I suspect many OO developers start out optimising for encapsulation and hiding behaviour. Only optimising for that can lead to tightly coupled, large graphs and a ball of mud.
Scrum suggests deploying working software every 2 weeks. How can you do that? Perhaps automation is required to give us the feedback as to the state of the code so that it can go live. So optimise for testability that can be automated.
If I optimise for testability or reducing the risk of change it may lead to a less tightly coupled system and smaller messes. I might discover things about composing objects and the SOLID principles.
If I optimise for knowing my software is working 100% or a fast feedback loop on changes that I make to my code then I might want to use TDD to enable that.
If I optimise for maintainability it might lead to more readable code and smaller functions and classes. That might lead again to a less tightly coupled system and smaller messes. I also will probably want to optimise for testability so I can get faster feedback about the impact of changes that I’m making in code that I’m unfamiliar with.
If I optimise for changeable or fluid software I may want to make things smaller and less coupled, I may be want to do TDD so I can get feedback on the effect of my changes as I make them.
If I optimise for the simplest thing that works, I may learn a lot about how code could be designed.
If I challenge myself as a developer as to what I’m optimising on – and know that this is a good thing – then I can consciously experiment and learn how to write the best code I can for the value that I am trying to optimise for.
Where does this come from?
This all might be obvious to many of you. But to me, thinking hard about how to do better code, I’ve had interesting conversations and thoughts driven by a more agile mind-set over the last couple of years. Several of those ideas were more clearly crystallised when attending an agile developer course with Aslam Khan and KRS late last year and experiments before and since.
I think “good” for me is becoming more defined – in a different way – from say 5 years ago. I wrote fine code. I could solve anything given enough time and relevant hacks if needed. But eventually projects would get messy. And frustrating. And change would be unsafe. And it would become harder to change.
A couple of years ago I stopped coding for my job. I led an agile transition implementing Scrum. I learnt a lot about Scrum. And so I’ve relearnt how I should code with an agile mind-set first and foremost. Now I’m practicing to actually understand how to implement those ideas. And it is awesome.
I feel far safer now. I feel more in control of the code base. I feel safer to change.
Scrum taught me the wisdom to break things up small and to understand what you’re really doing. XP translates that to the code level and gets you in control of your code base.
All of this leads to a far clearer understanding of why any line of code is there – where it is and why it should or should not be somewhere else. The code is cleaner. Hopefully it is good code. Hopefully it is clean code. But most of all – hopefully it will be easy to change to be cleaner or better as the design changes. And I’ll reflect and learn why if I fail – so I can actively do better code next time.
Hopefully I’ll also learn a lot from all the great coders out there on how they too are doing better code.