Book Summary: The Effective Engineer by Edmond Lau (II)
May 17, 2023
Welcome back to the second part of our two-installment series on "The Effective Engineer" by Edmond Lau. In the first installment, we explored the first five chapters of this influential book, gaining valuable insights on optimizing productivity as a software engineer. But our journey is far from over.
In this continuation, we'll delve into the remaining chapters, uncovering even more pearls of wisdom that can supercharge your engineering prowess.
6. Validate Your Ideas Early and Often
Validating ideas early and often is a crucial aspect of effective engineering. It involves finding low-effort ways to validate your work and continually asking yourself if you can collect data and validate your approach with a small fraction of the total effort.
One powerful technique discussed in the book is the Minimum Viable Product (MVP) approach. It involves creating a minimal version of your idea, such as a short video or a prototype, and testing it with users or stakeholders. This early validation helps you gauge interest, gather feedback, and make informed decisions about the direction of your project.
Several real-world examples illustrate the effectiveness of this approach. Dropbox, for instance, gained significant traction by creating a simple 4-minute video that showcased their product's capabilities.
Similarly, a team seeking to redesign a web app tested multiple design options by creating 8 static HTML pages combined with an AdWords campaign and then measuring conversion rates to choose the version that would be integrated into the dynamic app.
The importance of iterative development and collaboration is also highlighted. Working alone on a project can be demoralizing and risky. By involving others and seeking feedback early on, you can reduce risk, maintain momentum, and benefit from diverse perspectives.
To facilitate effective collaboration, we can implement practical strategies such as:
- committing code iteratively,
- requesting thorough code reviews,
- bouncing ideas off teammates,
- creating shared context through design documents and shared responsibilities, and
- soliciting buy-in for controversial features
In summary, validating ideas early and often, involving others, and building feedback loops are essential practices for effective engineering. By implementing these strategies, engineers can save time, mitigate risks, and increase the likelihood of delivering successful and impactful solutions.
7. Improve Your Project Estimation Skills
Accurate estimation plays a crucial role in project planning and decision-making. A good estimate provides a clear view of project reality, enabling effective project control and target achievement.
To improve project estimation skills, the chapter presents several concrete strategies. One approach is to decompose the project into granular tasks and estimate the time required for each task based on realistic assessments, rather than personal desires or external demands. On the other hand, thinking of estimates as probability distributions helps engineers understand the range of possible outcomes and set more realistic expectations.
Avoiding anchoring bias is crucial. It's important not to commit to initial estimates before thoroughly outlining the tasks involved. Using multiple estimation approaches, such as historical data analysis and task decomposition, provides a more comprehensive understanding of the project's complexity and time requirements.
Tracking time spent on tasks not initially part of the project plan builds awareness of potential scope creep. Defining specific project goals and measurable milestones serves as checkpoints to measure progress and guard against feature creep.
It is also important to make efforts for risk reduction from the get-go. Tackling the riskiest areas of a project first allows for early identification of estimation errors and timely adjustments. Caution is advised when undertaking rewrite projects, recommending breaking them down into smaller, targeted phases to mitigate risks and improve success rates.
Lastly, the chapter cautions against excessive overtime and emphasizes the negative impact on productivity, team dynamics, and project success. While acknowledging that overtime may be necessary in some cases, securing buy-in from the team and developing realistic revised project plans is essential.
Improving project estimation skills is an ongoing process that requires practice and experience, so make sure you don't skip the estimation stage in any of your projects for you to be able to hone the skill.
8. Balance Quality with Pragmatism
In the world of software engineering, achieving the right balance between quality and pragmatism is crucial. What works for tech giants like Google may not be suitable for startups or small companies. Software quality, at its core, involves making tradeoffs rather than following a universal rule.
One essential aspect is establishing a sustainable code review process. While code reviews enhance quality, there's a tradeoff between their benefits and the short-term productivity gains.
To minimize overhead, code reviews can be structured in different ways along a continuum. This allows for reducing friction while still retaining their advantages. For riskier changes, frequent reviews before committing them are recommended. Additionally, code reviews play a valuable role in aligning new employees with the team's standards and improving their code quality and style.
To manage complexity effectively, leveraging abstractions is key. Tools like lint checkers and abstraction frameworks contribute to faster feature development by reducing the time required from weeks or months to mere hours or days. However, building abstractions involves tradeoffs as well. It's essential to focus on making the core abstractions robust, as overinvesting in an abstraction or building a poor one can be costly.
When creating abstractions, certain characteristics should be prioritized. They should be:
- easy to learn,
- easy to use without extensive documentation,
- difficult to misuse,
- sufficiently powerful to meet requirements,
- easy to extend, and
- appropriate for the intended audience
By following principles such as avoiding mutable state, favoring functional programming over imperative approaches, emphasizing composition over inheritance, and expressing data manipulations declaratively, developers can reduce incidental complexity.
In the pursuit of high-quality software, automated testing plays a significant role. While beneficial, it's important to note that building automated tests for every component may not always be the most efficient use of time. Achieving 100% code coverage can be challenging and may not be necessary unless working on mission-critical or safety-critical systems. Starting with the most valuable tests and gradually expanding test coverage is a pragmatic approach.
Technical debt is an unavoidable part of software development. Every minute spent on suboptimal code incurs interest on that debt. Similar to financial debt, failure to repay the principal of technical debt leads to dedicating more time and energy to paying off accumulated interest rather than building value.
Effective engineers acknowledge the importance of periodic debt repayment, especially when code is written without a deep understanding of the problem space. Companies employ various strategies, such as:
- dedicating specific weeks or days to polish and clean up code,
- scheduling rewrite projects for excessively high technical debt, or
- organizing cleanup efforts after hackathon
However, not all technical debt is worth repaying. Instead of blindly addressing all instances, effective engineers focus on repaying debt with the highest leverage. This means prioritizing code in highly-trafficked areas of the codebase that can be fixed with minimal effort.
By balancing quality and pragmatism, engineers can navigate the complex landscape of software development, making informed decisions about code reviews, abstractions, automated testing, and technical debt management. Ultimately, the goal is to deliver high-quality software while being mindful of the unique context and tradeoffs involved in each situation.
9. Minimize Operational Burden
Simplicity is a key principle in minimizing operational burden. Simple solutions are easier to understand, maintain, and modify. Rather than opting for complex architectures or systems, engineers should strive for simplicity.
Complex architectures come with several maintenance costs. They disperse engineering expertise across multiple systems, introduce potential single points of failure, and increase the learning curve for new engineers. Moreover, maintaining numerous systems diverts engineering resources that could be better utilized on a focused set of building blocks. When system complexity surpasses the team's ability to manage it effectively, productivity and progress suffer.
Instead of continually adding new services or introducing unnecessary complexity, it's crucial to evaluate existing abstractions and tools. Often, repurposing an existing solution or abstraction can be simpler than developing a custom one. It's also important to assess whether a distributed cluster is truly necessary for processing large amounts of data or if a single powerful machine can suffice. The mantra should be to do the simple thing first, considering the long-term benefits of operational simplicity.
Build Systems to Fail Fast
Failing fast is an effective approach to identifying and resolving problems promptly. When a system fails fast, problems are immediately and visibly evident. This allows for quicker detection and resolution of bugs before they make their way into production.
Examples of failing fast include crashing at startup time upon encountering configuration errors, validating software inputs, and throwing exceptions when critical data structures are corrupted.
By implementing fail-fast techniques, engineers can surface issues early, enabling timely fixes and preventing further complications within the system.
Relentlessly Automate Mechanical Tasks
Automation is a powerful tool for reducing operational burden. While a quick manual fix may seem expedient at first, automating repetitive tasks and building sustainable solutions proves more beneficial in the long run.
Assessing the time saved by automating a particular task versus performing it manually is essential. Even seemingly insignificant time savings per task can accumulate into substantial gains over time.
Automation can be applied to various activities, such as code validation, data processing, error rate detection, software deployment, and more. However, it's important to recognize that automating decision-making can be more challenging and should be approached after automating the mechanics of tasks.
Make Batch Processes Idempotent
Idempotence plays a crucial role in minimizing the impact of failures in batch processes. By making a batch process idempotent, it can be retried or reentered if necessary. This allows for easier troubleshooting and faster resolution of issues.
One effective technique is to schedule regular dry runs to convert infrequent workflows into more common ones. By conducting these dry runs, any failures can be quickly addressed, and potential causes can be pinpointed within a narrower window of time.
Hone Your Ability to Respond and Recover Quickly
Focusing on the ability to respond and recover quickly is a higher leverage strategy than solely aiming to prevent failures. By configuring systems to identify architectural weaknesses and conducting regular exercises simulating disasters or increased load, engineering teams can proactively identify and address potential issues.
Additionally, working through contingency plans, asking "what if" questions, and practicing failure scenarios enables teams to be better prepared for unexpected situations. The ability to react swiftly and effectively to problems is vital for minimizing the operational burden.
In summary, minimizing operational burden requires embracing operational simplicity, building systems that fail fast, automating mechanical tasks, making batch processes idempotent, and honing the ability to respond and recover quickly. By implementing these practices, engineering teams can streamline their operations and enhance overall efficiency.
10. Invest in Your Team’s Growth
To foster a thriving engineering team, investing in growth is paramount. As you climb the ladder, your success is measured by how well you elevate your team, company, and the industry. Start early by helping your co-workers succeed, instilling the right habits for your own success.
Make hiring a collective responsibility, as it's a high-leverage activity. Designing an effective interview process that screens for the right candidates and ignites excitement for the team's mission should be the goal when tackling hiring.
Craft a robust onboarding program to quickly ramp up new engineers, impart culture, and expose them to essential skills. The faster new hires can start contributing to the team, the more effective your team be as a whole in spite of the overhead incurred in creating the onboarding program.
Another useful investment that contributes to a team's growth is conducting post-mortems after critical incidents, learning from failures without assigning blame. Use post-mortems to share insights, build operational guidelines, and foster a culture of growth.
A great engineering culture entails values like iteration speed, automation, code quality, shared ownership, and a respectful work environment. Invest in your team's growth, and you'll build a strong engineering culture that drives success.
By identifying and prioritizing activities that yield the greatest impact, we can optimize our productivity and fulfillment, and this is something that not only applies to work but also to other aspects of our lives.
However, it is important to note that spending all the time pursuing high-leverage activities can be exhausting, so striking a balance is important for living a more fulfilling life.
This wraps up our series on The Effective Engineer, I hope you gained a lot of useful insights in the process so that you can truly become a more effective engineer.
If you're interested in books like this or in web development topics in general, make sure to follow the blog, new content is coming out every week!