Building Blocks for a Simplicity Driven Engineering Culture
After writing code for over 20 years and managing engineers for almost 10, I can definitely say that *good* software engineering is not about overcoming or solving complex stuff. It’s about the ability to avoid them.
During my time both as a developer and a tech leader, I tried to collect different methods to help me not only solve complex issues but to have the ability to simplify them. Years of dealing with the concept of simplicity have even led me to start my own company, called Appwrite, which solely focuses on solving complex (and repetitive) problems and abstracting them away from developers.
This post aims to summarize and share the things that worked best for my teams and me in the past years and which we use today as the building block of our simplicity-driven engineering culture. Although we’re constantly investigating new ways to improve, some of these tips have become fundamentals to our R&D philosophy. They have helped us move faster and build better products designed for developers.
Understand Your Problem(s)
The first step before solving any problem is to understand it. There are a lot of smart and sharp developers that can tackle very complex problems. Does that mean everyone in your team will be able to understand those solutions? Probably not. Stop and think, is your problem really that complex, or is it just a combination of smaller issues you better approach separately.
Complexity characterizes the behavior of a system or model whose components interact in multiple ways … The term is generally used to characterize something with many parts where those parts interact with each other in multiple ways, culminating in a higher order of emergence greater than the sum of its parts
- Wikipedia
From this definition, we can easily learn that complexity is “something with many parts where those parts interact with each other in multiple ways”. If complexity is characterized by multiple components and multiple interactions, is it possible that our problem is in fact a set of multiple smaller problems? In many cases, the answer is probably yes. It’s best to start with understanding the entire scope of the issue, its context, and priority.
Design, Design, Design, and Design Again
At Appwrite, we try to spend more time on design than code. It’s not always possible or true, but it’s what we always strive for. Developers are modern-day artists, and our focus should not be on technical actions like coding.
Coding is our tool to reach a result, that result might be our code library, component, API, or entire application. By spending enough time in plaining, we can make sure we understand how our code is going to look like, how to avoid unnecessary complexity and pitfalls. Write an RFC, design a spec, or draw a flow chart. Know how you want your code to look, behave, and interact with other components.
Try to visualize your entire solution before even attempting to implement it. This will also be an excellent chance to communicate with your teammates, validate your thoughts and eventually even code faster.
Think Small, Really Small
Ten simple solutions are 100 times better than a complex one. If you design an API, think about what libraries should help you compose it. If you’re creating a class, which methods do you need? Do you need an abstraction or interface?
At Appwrite, we divide our codebase into different layers. The first layer is our API, designed as an MVC. The second layer is our app-specific libraries. The third layer is the generic components that we can usually reuse across projects, and we mostly open-source them so others could also use and improve. In this layer, we also tend to leverage 3rd party dependencies that match our simplicity criteria and help us complete our products. Each component in our stack can only have one specific role and an intuitive API designed for humans.
By minimizing responsibility and leveraging the use of the smallest possible components, we not only reduce complexity, but also decouple responsibilities, make our code observability better, and testing easier. If one of these decoupled components fail, refactoring becomes extremely easy, and no project-wide rewrites are necessary.
Code for Humans
Choosing the right naming conventions is one of the biggest focuses in our day-to-day life at Appwrite. There have been times where we spent an hour naming a variable, and that’s OK. As with real life, once a name sticks, it’s hard to change it especially when it’s part of your API signature.
Names help you describe your components and suggest their expected behavior. Choose the appropriate words for the domain you’re operating in: be logical and consistent. Although machines are interpreting your code, it will be read by humans. Those humans may be your team mates, and in some cases, they might even be your customers. Name wisely.
Document Everything
Your code can be self-explanatory, and that’s great, but is this always the case for every developer? Are you writing perfect code 100% of the time? At least for me, the answer is no. Adding a few text lines on the top of your functions, a small readme, a few examples for your APIs, links to websites you referred to, or a tutorial for your future team member can only benefit.
Treat your docs as an essential part of solving your problem and completing your solution. When left to choice, inconsistency will follow, and what was today’s compromise will become tomorrow’s norm.
Eldad Fux
I’m the Creator, Founder, and CEO of Appwrite.io. We build an end-to-end platform that gives developers all the tools and APIs required to build any kind of modern software by leveraging their existing knowledge.
We are an open-source company built on top of the same awesome values that makes OSS so great. Our team is 100% remote, operating from multiple locations on our beautiful planet. You can follow me on Twitter or connect with me on LinkedIn.