One Thing at a Time
Yesterday I told you about my three step virtuous cycle of software development.
Today, I'm going to talk about how to approach step 1 (writing code) in a way that keeps your cycle quick and sets up step 2 to succeed.
Learn to think about boundaries. Consistent, well-articulated boundaries between the components of your application make it possible to break large problems down into smaller problems.
Let's look at an example.
Imagine a typical single page app with a RESTful back end. It consists of:
- UI layer (eg. React)
- State management (eg. Redux)
- REST API (eg. Express, Rails, or Django)
- A database (eg. Postgres, MySQL, or MongoDB)
If I told you to implement a CRUD workflow on this stack, where you render a form and allow updates to your database from that form, you might start by coding up the UI. There's nothing wrong with starting at the UI, but it often leads to bushwhacking your way from the client to the server and back.
What I mean by that is that you'll code up the form, then you'll only start thinking about the state management layer as a means of getting info from the UI to the API endpoints. From there the API endpoints only emerge as a consequence of what you've built so far, and so on.
Instead, the experienced engineer starts thinking at the boundaries where each layer meets. When I'm working across the stack like this, I almost always think about my database models and my endpoints first. How am I representing data, and what interface am I presenting for manipulating that data? From there I'll think about what actions and events are necessary in my client side state management. Then I'll sketch out how I'll get whatever data and events I need from the UI to kick off those actions.
At this point I haven't written any code yet. As I'm sketching out these different ideas, I'm most concerned with the shape of the data as it passes from phase to phase. What's the shape of the data that I dispatch to my Redux store? What's the shape of the payload that I send in my POST request? And so on.
Once I have these boundaries figured out, I will pick one component (often an API endpoint) and commit that specification to a test. That test says that when I provide input X (eg, a request payload) I should see output Y (eg, the response payload). Once I've staked out those clear boundaries, I start writing the implementation of the endpoint.
When I'm confident that the endpoint is working as designed, I'll move on to the state management. Again, I'll write some tests for how I want the state management layer to behave, and only once I'm satisfied that it's working correctly will I move on to building the UI.
Even if it's a toy project where I'm not writing tests, I'll still focus on a single component at a time, making sure that it is conforming to the boundaries that I've determined.
By keeping my focus on a single component at a time, I can usually execute my write/test/debug loop multiple times per minute. Compare that to trying to code up an entire feature and then spelunking through the code to figure out why it doesn't work.
This is a skill that comes with practice. Looking at a complex problem and being able to decompose it into its constituent parts is not easy, but it's what you have to learn to succeed as an engineer.
Tomorrow and Thursday I'll talk about testing and debugging, and Friday I'll do a live coding demo putting it all together.