They don't tweak the design of an off-the-shelf component to fit the situation. It's the other way around. So it fits the solution you already have, and that way you can build much faster, and you're saving your innovation tokens for the things that are actually new and differentiating in your company.
Which brings us to just solve the problem, not a different, more difficult problem. The hardest thing to do in software engineering is to build a generic solution, especially when all you have is a specific problem. It is generally a good idea, or rather, I'll say it this way. The older I get, the more my code looks like a very simple step-by-step process. It doesn't impress anyone. It's super boring to look at, and personally, I think the biggest compliment you can get on your code is when somebody looks at it and goes, wait, that's it? Right, that's it. It is simple, it's easy to understand, and it's easy to maintain.
Because you always have to push back on complexity. Engineering is an endless fight against complexity, and sometimes that means saying no to an architecture astronaut who's trying to build a generic framework to solve a problem forever when you just need, you know, we need to ship this code tomorrow, we don't even know yet what we're building in ten days, and you're trying to build a framework. Just fix the problem, solve it, and move on.
This also means that, again, because you understand your domain, because you have long-term ownership of the stuff you're building, you can push back on your product as well, on your product managers. Very often, you have one requirement that blows up the entire story and makes it way harder than it needs to be. You can usually push back on your PM and be like, hey, this one thing is making our work a lot harder. And they'll go, oh, yeah, shit, no, we don't actually need that, we don't care. Just drop that requirement and now you can use a very simple solution.
And where most complexity comes from in a software engineering project is not actually the code you're writing, it's the architecture you're creating. If you think about your code as a series of interconnected boxes, the boxes are like your code, those are classes, modules, services, whatever you want to call them, and the lines are how they talk to each other. The more connected these lines are, the more intertwined everything gets, the more complex it is.
One lesson that I've learned is that you can always change how a self-contained box works. You can rip out all of the code and write new code that's cleaner or better or whatever, but it's very hard to change those connections. And one way to limit that complexity is to use to organize your code vertically instead of horizontally. So instead of splitting components, hooks, types, utils or whatever you have in your stack, I don't know what stack you use, but if you instead split your code by everything that goes into a dashboard, anything that goes into a user profile, into a to-do list, you then have modules or boxes that aren't as intertwined. And it's those clear APIs that make everything easier.
One trick I like to use is to use type-driven or contract-driven development where we ahead of time, we talk about what are the different moving pieces of a story we're building, and then we talk about how they're going to talk to each other, and then everyone who's writing the code is empowered to build the internal implementation whatever way they want. Unfortunately, or fortunately, using vertical modules may mean that you will have to copy-paste some code. And this is probably one of my most controversial takes is that drying code, do not repeat yourself, is actually bad. And whenever possible, you should lean on separation of concerns more than you do on drying up your code.
Comments