Think more, design more, write less, better code
If you want to be a better product designer/developer and work with others on the same codebases, then learn this skill faster than everyone else does. Thinking more, designing more, writing less, better code for your team mates.
BTW: I’m just talking about coding practices here in teams. If you want to be a better product engineer, then you are going to have to do a lot more than just writing code, you are not a product engineer unless you do discovery work as well as delivery work.
Code that “just works” is not the goal of product development. In code reviews, I keep hearing “well it works! doesn’t it? what’s wrong with it?” like it is some kind of self-defence for the horrific code’s existence.
Just doing what it takes to make the code do what you want it to do, is simply NOT good enough; you have to go much further when you are product engineering.
It is obvious to anyone when you see bad code (especially what you did not just write yourself), but it is NOT that obvious even to the author if all you choose to see is that the code works. That’s is not the point we are making. We assume your damn code works, of course it should work, duh! we are talking about something else entirely.
Bad code often manifests itself as: lengthy, procedural, hard to read, hard to comprehend, scary to change, littered with optimistic redundant comments, poorly named items, convoluted compound ‘if statements’, nested ‘for loops ‘— you name it, far too much going on in one giant function of hundreds of lines of code.
“Well, it does work!” I hear you say, “at least, I think it works”.
Well, it might even work (well done! you) and perhaps it was a real struggle for you to achieve that masterpiece of code. Perhaps it was a piece of code where you had to do a lot more than the average amount of research to learn how to achieve it, and that consumed a bunch more of your time, and it was late at night, and all you wanted to do was go to bed, and you finished it far later than you wanted to in a final push. Good stuff, well done you. You pushed yourself. Now it is time to delete that mess, and start coding it with some better design, now that you know how to solve the problem.
You might be safe if your code were to be judged on a binary: “it worked” or if “did not work” scale, by a language compiler (that cares nothing about the code it happens to compiles every time it awakens). Your code may even do what you claim it does, maybe it does that beautifully efficiently.
But the point is that: it is still just not good enough for us other humans, who have to come in behind you and try to work with that code, and attempt to refactor that code, after you’ve heroically stormed through here, and left us a wake of code cleanup and refactoring to do, so that we understand that damn code!
Your code creations not only have to “work”, but they also have to be “changed” and adapted. You are not creating a polished life sized sculpture in bronze that is set in concrete in some metropolis, or that has some historical significance that can’t be forgotten to millions of people. You are creating a temporary piece of research and development that needs to evolve rapidly and continuously as the real world around it changes. You may not even recognise that code in its current form, weeks to years from now! If it even exists at all by then!
Funnily enough, all of what you do in product development amounts to “research and development” work R&D. A bunch of attempts at getting something working that solves a problem. It is funny because almost know one admits to doing R&D work.Instead, they want to be seen as knowing precisely what they are doing. But that’s a complete myth.
To be easily changed, your code has to be “designed” to change, and that change has to be cost effective to do by the other mere mortals around you, sustainably and continuously, with or without you o the team.
To create code that will inevitably change by another human programmer (read: your teammates, or even yourself in six weeks from now) requires writing far better designed code, far better than the code you would write to ‘just to make it work’. and this takes more time than you would expect. Nonetheless, whether you have the time or not, you need to make the time to do it.
Think about it
After all, what do you all the fuss is all about with these things: well-structured-and-named implementation patterns, refactoring tools, clean code practices, structural architecture, software design, and all those kinds of O.P. things that you may not have mastered right now? They are a BIG deal.
But the secret to getting better skilled at doing this (presumably you want me to save you decades of pain learning the hard way working on shitty codebases for the next 10 years of your programming life — then ultimately giving up through frustration— then going into management roles of others like you continuing to work on shitty codebases), is to spend more time thinking about the design of your code. Less time coding the obvious solution in a procedural manner, in a spasm of internet searching for general solutions, ferocious typing, and manual testing in the debugger late at night.
And the only proven way to get better at that, I can tell you, is to insist on doing more of the better things yourself. Stop waiting to be told to!
What does this mean, in practice? What should you do about it? How should you start to improve yourself? (You know: how do you improve the human being that outputs the code you are working on today?)
Well, let’s set an expectation first. Do you really think there is a download that I or anyone else can give you to insert into your head: like Neo does in The Matrix? and you suddenly have divine power as a better engineer?
Eat the marshmallow? Not bloody likely. Still not in 2021, not 20 years ago, and not likely 10 years from now.
But are there some things you can do right now, that won’t take 10 years, but that will cost you a ton more of your time and your effort to get to that place. Yes, just like every other skilled engineer that has gone before you.
Where should you start?
Better design requires a different mindset, better practices, and different habits. Practices that actually force you to design your code better are best. Without which, your code will simply have less design. Or perhaps just continue to get lucky in ways you won’t understand repeating. Without intentional design practices, your code is simply going to remain procedural, unstructured and hard to change for everyone around you. Where your teammates can’t wait until you leave the team. That’s because that’s the lowest common denominator form of code, and it is the easiest thing to achieve, requiring the least amount of effort from you (or so it may seem that way to you at least).
There are a few effective techniques you can use to achieve better design, for example: try failing a build, if static analysis finds a function of yours over 20 lines long! or finds a function with more than 5 parameters, forcing you to refactor your code. These practices work, but they don’t teach you why, and they don’t teach you how to avoid? You just start to game the system.
There is simply no better practice for driving better design, in my personal opinion, than mastering test-driven-development, a dependency injection framework and applying (aggressively) principles like: domain driven design, yagni, solid, dry and kiss every line of code you create.
It took me at least ~10–15 years of my career to discover this fact, and to get the point that I learned how to do this stuff far better. Frankly, I needed some time in the saddle, just to get to the point where I had already experienced a bunch of crappy ways to create software from multiple places that I had worked, and realized that the outcome was always going to be the same. Frustratingly, I had topped out my learning about writing well designed, durable code. By then I had already gorged myself on all the design patterns books and best practices guidance, even worked in the engineering excellence halls at Microsoft, but “better design” just seemed tantalizingly out of my reach, in the hands of the programming Gods (which I definitely was not one) and cracking this nut was the only way forward if I was not to check-out of programming altogether. Thank the Gods that I didn’t - like so many around me did, and who still never attained it.
You’ve heard all about things like: domain driven design, yagni, solid, dry and kiss before, and maybe you can even point to times that you’ve successfully used them. But what I am talking about is using them aggressively all the time in a disciplined way, until you’ve mastered them, and then of course, mastered implicitly when it is appropriate to use them, and when not to use them.
The use of the word aggressive and disciplined here is my way of expressing emphasis and agency to force yourself to care about these things instead of dismissing them when things get hard. It does not mean dogmatically applying these things verbatim to the point they make no sense. Engineering a good design is still a game in constant tradeoffs, and sometimes you have to bend the rules to get the best outcome. But, sometimes, most of the time, you need to sweat quite a bit more to get there too.
Why is test driven development still so damn controversial? It think it is because it is hard to master, can’t be attained quickly, and the benefits are not easily realized straight away. It takes a lot of effort, a lot of practice, and there are many pitfalls that you must learn while mastering it. Frankly, I think (behind learning to actually do discovery work), learning test-driven-development is perhaps the hardest skill to master that a developer will encounter, and that’s exactly why so many have failed to attain it. Those who have tried and failed know that, and then to cover themselves, they argue against it, or excuse themselves from having to try so hard, because they’ve convinced themselves that is not as effective as what they already know. So, they figure, they don’t have to try anymore.
I understand them, I hear them, but I no longer want to work any of them. In my mind, they are a walking liability to the success of a software product long term, because they are inadvertently slowing it all down and introducing far too much accidental complexity for the rest of us to manage on their behalf. I’m more than happy to replace them with less-experienced, so be it, but better team players who give a shit about the product long term.
Then, there are also the other things in software that are also hard to master, like: domain driven design, yagni, solid, dry and kiss. Those are hard to apply well also, because to do them well, you have to change your perspective and habits, some of which you have relied upon for years to get to where you are. Writing the simplest code to achieve the task (KISS), writing it very well, (using DRY and SOLID principles) and using DDD principles to decouple your code, encapsulating the important stuff (with TellDontAsk) are all super important practice and disciplines to attain good design.
On the other hand, writing code as fast as you can and leaving no design in your wake (as tech debt) is not good design, nor is it fast in the long run. Think about it.
How do you help yourself?
I spend most of time in my day job mentoring and leading software product teams. Not from afar, but at the coalface alongside inside those teams. So, I figure that I’ve learned a few things about learning how to be a better product engineer.
So, here is some general advice on how to start learning, the most effective ways to develop better software design habits.
I would start by insisting that: the way you are doing things now, are possibly not good enough to get to where you want to be, and that you need to change you, to open the door to where you want to be. That first step will be immediately rewarding, because you just became aware of what you need to learn and improve, and that’s the start of your journey forward.
I would start seeking guidance and opportunities around you to start to learn more from the people who have more of what you want, and that are within your reach (rather than remote). So that you can have a proven direction to start going in first, one guaranteed to have you on the right track, straight away. You might have to invest the effort in your own time to take these steps and start the journey for yourself. But it will be rewarding, because you made it happen in a way that works for you, and works for those around you.
I would avoid asking Google for general direction or opinion, because you are unlikely (at this stage) to be capable of sifting through the haystack of noise and find the signal of what you need right here and right now. Instead, seek those answers in the place you spend most of your days working and learning. Perhaps, even exposing yourself to being vulnerable to judgement and openly asking those around you to guide you to discovering things yourself.
But, if you do this, please, please, please don’t expect those people, that you seek advice from, not to judge you, or invest time in you, if you won’t invest time in what they are sharing with you. That’s the way it works, I’m afraid, we all only have so much energy to give things, and no one likes working with askholes.
I would find good reasons to spend your free time with the people around you helping them do their thing, rather than asking them to invest in doing your specific thing. You might also will have to learn how to make your own free time to do that. You will find that rewarding because you get to see a different thing, and different way of working on that different thing, that you can add to your toolbox of things. Pairing with others is a very powerful way to get exposed to things you didn’t know that didn’t know. (pairing — another practice that seems so controversial to most)
I would start to pay attention to, and invest more time in the details of the things you seek, because it’s the details and their complex relationships that have been refined over the years that make the biggest differences long term, rather than immediate big silver bullet impacts, that excuse you from trying harder.
You may not be aware of it, but you live and work within systems, and within systems its about improving the relationships, not the individual parts, that make systems better.
Next, I would start using test driven development practices the next time you want to create some new code. Just get started on it right there and then: you don’t need permission, be damned your estimates — you are learning to be better.
You just need to start. You know the technique already, god you’ve read enough about it: red, green, refactor. Give it a try. Learn to write some unit tests, learn to create your own mocks (by hand), and start challenging yourself on the separation of concerns, for every detail your code is expected to do. This process alone will support your procedural thinking, but codify it into a better design of software, and even better, it will defer implementation of the curly bits of your solution that you may not yet know how to resolve, to a point further out where you can resolve them in isolation.
Stop looking up StackOverflow for easy generalized answers. Instead think about what your code is doing, what components should be doing it, and which components should care about those details. Start refactoring towards those abstractions and make your code more readable and self-documenting. Naming is so damn important to conveying your intent, and functions are a useful tool for capturing that intent with a domain specific name. When you finally get to the bottom of the abstraction and you need to now do the actual thing (usually I.O), and you don’t know how, then you can look that up on StackOverflow.
Progress will be slow at the start. Painfully slow, but keep at it — you are learning a new skill and a new set of habits.
In your code reviews, park your ego, and listen for the details. Well designed and testable code looks different, feels different, and it has different patterns and characteristics. You may not be familiar with them yet, so you can’t judge them well yet. It will come with time. So, try to go with it, and observe until you have learned what is being taught.
The bottom line is this:
You need to start spending more time thinking about the design of your code, how it reads, how it is structured, how it works; than spending time debugging it and just making it work, and moving on to the next thing.