Chapter 2: A Pragmatic Approach

I found this chapter to be insightful and can relate it to my experience as an engineer, both good and bad. The chapter discusses good design, DRY, tracer code, prototyping, and estimating. I’ll share what I learned along with my thoughts.

Easy To Change (ETC)

The chapter begins with a discussion on what makes good design. They emphasize the benefits of good design, including that it must be changeable without changing any adjacent, upstream, or downstream code. In essence, use ETC or make it easier to change by using techniques such as modularization and interfaces to keep responsibilities separate.

ETC is more than just applying the Single Responsibility Principle. *Thomas* and *Hunt* advocate that it’s a value for decision-making. It won’t tell you how to design and implement systems, but it will help you make better decisions between design and implementation options. 

When dealing with unknown paths or novel projects, their sage advice is to make it replaceable. That way, if a better design option is presented later, that chunk of code can be quickly replaced. In their words, your initial design or implementation decision “won’t be a roadblock” to implementing the better option. 

Finally, be sure to note the situation and reasoning in a journal and code to be able to understand and reflect on your decision later. This is wise advice for me. I didn’t keep meta notes, and I feel this bit of wisdom is helpful to grow as an engineer.

Don’t Repeat Yourself (DRY)

They emphasize that DRY, or Don’t Repeat Yourself, applies to both code and documentation. If a change has to be made in multiple locations, then the code isn’t DRY. For example, having a function where multiple lines use the same steps to perform a calculation. In this case, remove the repetition by moving those steps to a separate function that can be used by each operation. Even low-level operations such as number and text formatting should be converted to a reusable function call, rather than repeating the operation multiple times in the code. 

This also means that documentation in the code shouldn’t be a copy of what’s in the code. This will lead to the documentation and the code becoming misaligned in the future because, as the code changes, it’s more likely that the documentation won’t be updated. Instead, use documentation to highlight an exception, a known issue to fix later, or to explain an engineering decision. 

Duplication should also be avoided between APIs (internal and external) and data sources. The goal is to find a neutral standard to specify APIs or data.

Orthagonality

They introduce the concept of *orthogonality* in programming with an example of the systems used to control a helicopter in flight. The controls on a helicopter aren’t independent; moving one lever will affect how the other controls should be manipulated. You cannot move the cyclic to get it to move forward without having to adjust the pitch lever, throttle, and foot pedals. 

The interplay of a helicopter’s systems is an example of a non-orthogonal system where each is intricately intertwined such that you can’t adjust one without adjusting the others. This is not how code should perform.

Each system should operate independently of the other. This makes code changes easier, saves time, and reduces risk because an engineer will understand that they only need to create and test the change in one system (or subsystem). It also promotes component reuse, where new and creative combinations can be made in the future.

During the design process, maintain orthogonality by using implementation layers. That is, the user interface is developed separately from the data access layer, which is separate from authorization and any business logic. A quick check they advise is to ask yourself, ‘If I dramatically change the requirements behind a particular function, how many modules are affected?

Also, be mindful of how your design is decoupled from the real world. For example, using randomly generated IDs for user account IDs instead of real-world information such as phone numbers, because they can change, and you will not have control over them.

And of course, this also applies to documentation where content and presentation are separated. It is best to have a platform that handles the presentation layer, such as Markdown, so that you can focus on the content.

Tracer Code

An interesting tool they mention is to use tracer code. Tracer code is where a system is developed just enough to get each layer working together end-to-end to show that the system as a whole can be integrated. This is especially important for novel projects where the possibilities are unknown. The tracer code is developed in the same environment it needs to run in, within the same constraints. It gets from the requirement to an operational but simplified version of the system running quickly. From there, developers can add to each subsystem until all requirements are implemented.

It avoids the burden of developing all the requirements at once without anything to demo until much later. Tracer code helps to demonstrate to all stakeholders that the project is viable and will encourage buy-in and support for the rest of the implementation. Tracer code is skeleton system code that can be used to implement the rest of the functionality.

Prototypes

Prototypes, on the other hand, are meant to be disposable. It is a way to demo or work out a specific problem without producing production code. It can be done on sticky notes, a computer, or a small-scale model. The ultimate difference between tracer code and a prototype is that a prototype can be discarded.

Estimating

Finally, they discuss estimating and how to get a better handle on setting time frames for project task completion. They suggest referencing prior projects or talking to engineers who have experience with this type of project, which is important wisdom to have. 

They mention how to talk about and reference estimation to set expectations. For example, if a project is estimated to take 25 weeks to complete, set the duration to six months. That way, the expectation is to have it done in 5-7 months. This will provide 1-3 months of wiggle-room versus 1-3 weeks, which is a significant difference. 

They also suggest building a model of the steps needed to understand what needs to be done. The goal is to make sure you have a good understanding of how your team or organization develops projects.

They advise keeping a journal of your estimations to reference and reuse later. As an engineer, this is an area that I struggled with. It is challenging to set a task estimate of work you haven’t done before on a team that provides little guidance and shared estimating knowledge. It can feel daunting. 

They advocate for my preferred method of project task planning and estimation. This method broadly defines all but the initial tasks. Then, as project implementation progresses, progressively refine the remaining tasks in parallel. In my role at Amazon, I was expected to create a firm, detailed task plan as a new engineer, regardless of my experience and uncertainty, and then not deviate from that plan. This method meant that revisions to my timeline had a high negative impact without much guidance.

Conclusion

Overall, my previous experience resonated with what Thomas and Hunt advise. I honestly feel that this book should be a required reading for new engineers because it gives them access to wisdom gained from years of experience, whether their team can provide it or not. It is a way to build a strong foundation as an engineer that can be referred to and refined over time.

I am looking forward to reading each chapter of this book. Coming up next, chapter three, entitled The Basic Tools.