In Part 2 of the “DNA of Solu”, we look at Solu’s software development principles. Click here for Part 1.
Many software products and frameworks we use daily were created by small teams. Why do some teams achieve more than others? How can engineers stay happy and productive as the project matures? The answer is to build a compounding codebase, and this post explains what you need to do to make your code compound.
“Compound interest is the eighth wonder of the world. He who understands it, earns it … he who doesn’t … pays it.”
― Albert Einstein
The definition of a compounding codebase is that development should become easier, not harder, as your project matures. How does this happen? The short answer is through constant iteration and obsessing over clarity. In practice, it boils down to these principles:
What great software looks like
Data structures should align with the problem you are solving
Poorly aligned data structures make every feature unnecessarily complex and your codebase unreadable.
If you get the “it shouldn’t be this hard” frustration, you are probably right. Take a step back and revisit your data structures.
A relational database is not an object. Do not try to mimic it as such.
Naming things is your most important documentation
Use consistent vocabulary from the domain language. Overloaded terms confuse people.
Replace generic verbs (like get) with specific ones. In our codebase, we load from database, read from file, fetch over http, pick from collection, compute from given input, and transform objects toSomething.
If a function has side effects, it should be clearly stated in its name (e.g., function saveUser(…)).
Reduce the next developer’s cognitive load
Orthogonality: One function should do one thing. If you can’t easily name your function, you have probably done something wrong.
Document rationale: Good code is self-explanatory about what it does. Comments should explain why you chose to do it that way.
Fail loudly: If your function assumes that an input number is positive, it should throw an error if it isn’t. Silent bugs are the worst.
The process of building great software
Lazy development, eager prototyping
Don’t code anything into production that can be mocked, until you really know it is useful.
Test new ideas with throw-away prototypes. This gives you a sense of how long it will take to make the real thing.
Separate design from implementation
Quality of design comes from the number of iterations, and iterating is 10x faster in Figma.
Hasty implementations of unused features are the highway to a rotten codebase.
Plan your implementations in Notion, not VS Code
Write a game plan before opening the code editor. Clean code requires clear thought, and writing clarifies your thoughts. Starting to do this was the most important step on my learning path.
Get in the habit of including several implementation alternatives and their pros and cons. This also makes receiving feedback from colleagues much easier.
Sometimes your initial plan crumbles the moment you try to implement it. This is OK – you can just discard it. Writing it still clarified your thinking.
Start with a walking skeleton and add flesh iteratively
Start with a walking skeleton, i.e., the simplest possible solution that works end-to-end. Then, add flesh iteratively by solving the current most pressing issue.
Work in iterations of 1-2 weeks with a clear definition of done. This duration keeps an engineer focused and helps you avoid outrunning your headlights.
For example, Solu’s walking skeleton was made in a week (after several throw-away prototypes). It was a website where you could upload a genomic sample and receive a FastQC quality control report in a few minutes. It was unstyled HTML with a ton of duct tape under the hood, but it was deployed online and worked end-to-end. Since then, every development week has been an iteration to solve the next most impactful need.
Create structure in a lagging manner
Good structure is better than no structure, no structure is better than bad structure.
Solid abstraction layers are the key to a compounding codebase. To get them right, you need to understand what is shared / configured between their use cases. And you will probably guess this wrong unless you’ve written something very similar several times before.
This is a chicken-and-egg problem, and you should solve it like chicken and eggs do – iteratively. When you keep adding flesh to your walking skeleton, you will notice that some chunks of your codebase annoys you disproportionally. This is your cue to refactor it.
How to build a great software team
Balance between productivity and team alignment
Constant interruptions are detrimental to developer productivity and happiness.
At the same time, alignment between team members is more important than the productivity of individual team members.
This is a tradeoff that you will never perfectly solve. The best you can do is frequent and open communication.
Respect your headspace
Regular team alignment meetings increase your focus time. Without them, you would have the same conversations with sporadic interruptions.
It is the product manager's job to shield the developer’s focus from half-hearted interruptions. It is your job to communicate your focus needs. Don’t expect your colleagues to read your mind; let them know when you need a deep dive.
Respect your colleagues’ headspace
Clarify your thoughts before pouring them on your colleagues. Again, writing is excellent for clarification and enables async communication.
Always google before bugging your teammates.
Divide project ownership without building silos
Developers should have clear project ownership and frequent cross-pollination.
Code reviews are primarily for knowledge sharing, not quality control.
Take ownership of your projects
A professional developer should give reliable estimates for implementation time. Learn to overcome your natural optimism bias. Occasionally running over deadlines is inevitable, but constantly doing so is not OK.
Lack of knowledge is not an excuse – it’s your job to figure it out. Don’t know what a mecA gene is? Ask Google and your colleagues. Don’t know how to render a pdf? Start googling and prototyping.
Keep your ego in check
Be informative, not impressive. Trying to impress colleagues results in overengineering. Trying to make their lives easier results in clean code.
Ask for help if you are stuck. Spending hours trying to understand old code instead of having a 15-minute conversation is a terrible waste of time.
Be direct and kind
Have high standards. Tolerance for crappy code is contagious, and new team members pick it up quickly. Don’t let it creep in. Give and ask for candid feedback.
Make an effort to celebrate the successes of your colleagues. “Of course they know how awesome they are” is true less often than you think.
Be kind and supportive in your everyday communication.
Psst! We're hiring.
If you liked this post, you will likely find Solu a great place to work.