The Effective Engineer

27 minute read

Effective time management helps to deliver projects on time. The key to effectiveness is to focus on high impact activities, and some of the highest impact activities include building a strong foundation and culture for your engineering team, which surprisingly do not relate to the actual work of delivering the projects.

Scenarios and examples provided in the book are very relevant and relatable to all professional engineers. I encourage everyone to grab a copy and start reading.

The Effective Engineer How to Leverage your Efforts in Software Engineering to Make a Disproportionate and Meaningful Impact - Edmund Lau, 2015

Effectiveness is determined by both time taken and value produced. An effective engineer produces high value in a short time.

Foreword

Bret Taylor, CEO of Quip

Silicon Valley would be a much better place for both managers and engineers if people embraced “working smart” rather than “working hard”.

Adopt the Right Mindset

1. Focus on High-Leverage Activities

Leverage = Impact Produced / Time Invested

High leverage activities meant that you will enjoy high ROI for your time and effort. This idea resembles the Pareto Principle (80-20 rule).

Use Leverage as Your Yardstick for Effectiveness

For engineers, focusing on onboarding and mentoring programs to ensure new hires start off on the right foundation greatly improves the code quality and productivity of the entire team. These are two of the highest leverage activities that any engineering teams can do.

Increase Your Leverage In Three Ways

  1. Reduce time taken to complete certain tasks.
  2. Increase output of certain tasks.
  3. Transition to higher leverage activities.

For every activity you perform, ask yourself if any or all of the three improvements can be applied.

Direct Energy towards Leverage Points, not just Easy Wins

Resources are never sufficient. Adopt habits that produce disproportionately high impact for any time spent.

2. Optimize for Learning

Optimizing for learning is a high-leverage activity for an effective engineer.

Adopt a Growth Mindset

Stanford psychologist Carol Dweck found that people adopt one of two mindsets. I find her idea similar to the nature vs nurture debate.

  • Fixed mindset individuals
    • believe that abilities are inherent
    • believe that failure indicates that they are not talented
    • tend to stick to activities they perform well to validate their intelligence
    • tend to give up early or easily, which enables them to point to a lack of effort rather than a lack of ability
  • Growth mindset individuals
    • believe that abilities can be cultivated through effort
    • believe that they lack aptitudes in certain areas
    • tend to view challenges and failures as opportunities to learn
    • less likely to give up on their path to success

Tamar Bercovici, engineering manager at Box:

It’s not about apologizing for where your resume doesn’t line up but rather telling your story - who you are, what skills you’ve built, what you’re excited about doing next and why

To be effective, take control of your own story, optimizing for experiences where you learn rather than experiences where you effortlessly succeed.

Invest in Your Rate of Learning

  1. Learning follows an exponential growth curve. Strong foundation enables you to gain more knowledge faster in future.
  2. The earlier you start, the more time your learning has to compound.
  3. Due to compounding, small deltas in your learning rate make a big difference in the long run.

Stephen Cohen, co-founder of Palantir:

When companies pay you for cushy and non-challenging 9-to-5 jobs, they are actually paying you to accept a much lower intellectual growth rate. When you recognize that intelligence is compounding, the cost of missing long-term compounding is enormous.

Seek Work Environment Conducive to Learning

Since you will be spending a lot of time at work, choose a work environment that promotes sustainable learning at both personal and professional level, using these criteria:

  1. Fast Growth - ample opportunities to make big impact and increase your responsibilities creates an environment that attracts strong talent, which generate a feedback to even faster growth. Environment that lacks growth leads to stagnation, politics and loss of talent.
  2. Training - effective teams recognize the importance of onboarding and mentorship. See the earlier section.
  3. Openness - a culture that share feedback and information proactively, reflecting on failures and internalizing lessons learnt.
  4. Pace - iterating quickly provides faster feedback cycle and enables you to learn faster. Bureaucracy usually kills speed. However do find a sustainable pace and avoid burn out.
  5. People - working with people who are better than you means you will have better mentors and teachers. Your teammates can matter more than your actual job scope.
  6. Autonomy - freedom to choose what and how to work drives learning. Smaller companies usually provide more diverse learning opportunities.

A full set of questions can be found in the same section of the book, for you to ask your hiring manager at the next job interview. At different stage of your career, some criteria may weigh more than others.

Dedicate Time on the Job to Develop New Skills

Spend around 20% of your working time to develop new skills. Either gain a deeper understanding in an area you already know, or an adjacent discipline. Here are some suggestions:

  1. Study code for core abstractions written by the best engineers - open source projects would work too.
  2. Write more code - hands-on practice reinforces learning.
  3. Learn from technical or educational materials - codelabs, tech talks, MOOCs etc.
  4. Master programming languages that you use.
  5. Send your code reviews to the harshest critics.
  6. Voluntarily participate in design discussions of projects that interests you.
  7. Work on a diversity of projects - interleaved practice is more effective and helps you appreciate problems common in all projects.
  8. Work with senior engineers and learn from them.
  9. Dive into codes you don’t know.

Always Be Learning

Here are some other points to cultivate a habit of learning outside of work:

  1. Learn new programming languages and frameworks.
  2. Learn skills that are in high market demand.
  3. Read books - the author can read 1-2 books a week. That is insanely fast.
  4. Join a discussion group - a club of mutual improvement.
  5. Attend talks, conferences, and meetups.
  6. Build and maintain a strong network of relationships - meeting large number of people in your daily lives dramatically increases your chance of getting lucky.
  7. Follow bloggers who teach.
  8. Write to teach.
  9. Tinker on side projects.
  10. Pursue what you love.

3. Prioritize Regularly

Prioritizing regularly is a most highly leveraging activity as it determines the amount of leverage for the rest of your time.

Track To-Dos in a Single, Easily Accessible List

Convenience will encourage yourself to use the to-do list. The list helps to prevent tasks from falling through the cracks.

Focus on What Directly Produces Value

  • Make pairwise comparison between items on the to-do list and prioritize those that directly produces value.
  • After which prioritize those that creates the most impact.
  • Showing that you have delivered high impact helps you get away with dodging unnecessary meetings, emails, non-urgent bugs etc.
  • Learn to say “No” to people who are not respecting the opportunity cost of your time.

Focus on the Important and Non-Urgent

  • All tasks can be mapped on a graph with 2 axis, Urgency and Importance.
  • We often neglect non-urgent tasks even though they may be important.
  • Prioritizing important but non-urgent tasks (which are usually planning and strategic in nature) can prevent important and urgent tasks, which are usually crisis, from happening.
  • Label all to-dos in the list with Importance and Urgency indicator.

Protect Your Maker’s Schedule

Reduce context switching. Preserve larger blocks of time in your schedule to focus deeply on your task so that you can be more productive when you are free from interruption. To produce good quality engineering work requires this.

Limit the Amount of Work In Progress

Competing priorities divide your time and effort, which slows down the momentum of all concurrent activities. Try to get your tasks done by focusing on a subset of activities first instead of hanging everything at work-in-progress.

Fight Procrastination with If-Then Plans

if (certain condition is met) 
then (do something)

Adopting this plan helps you overcome the inertia of putting off tasks, helps you expend the activation energy required to start a task (which is usually all in the mind, to plan the task), and capture small gaps of free time in your schedule to channel them to more productive tasks.

Make Routine Prioritization

A myriad of workflow systems are available. Find that suits your needs. The author is personally using Asana, with a workflow that performs a weekly review of tasks and plans out the following week. Daily milestones and minor adjustments are made every morning. The actual to-do list incorporates all the tips mentioned in this section.

Execution

4. Invest in Iteration Speed

Move Fast to Learn Fast

  • iterate faster allows you to identify what works and what doesn’t earlier.
  • iterating fast actually reduces risk as teams can focus on smaller batches of changes and pinpoint problems faster.

Invest in Time-Saving

The book talks about improving DevOps process in general using tools.

Raffi Krikorian, VP of Platform Engineering:

If you have to do something manually more than twice, then write a tool for the third time

Faster tools get used more often, further increasing leverage. Faster tools also unlock new development workflows that were previously not possible. Some ideas mentioned for you to consider:

  • Shorten compile time. This encourages shorter iteration.
  • Interactive programming environment. Read-eval-print loop (REPL) is faster than Edit-compile-run-debug loop, which is why Scala and Clojure is faster to develop than Java even though all of them uses the JVM.
  • Hot code reload. Application runs updated code without full restart.
  • Continuous integration. Every commit triggers building of codebase and running the entire test suite, speeding up debugging.
  • Promote adoption of productivity tool in the team to multiply the leverage gained by every team member.
  • Lower the effort of switching tool to increase adoption.
  • Start small. Small success empowers you to build bigger tools in future.

Shorten Your Debugging and Validation Loops

Instead of running through the normal steps to trigger a state in the software for debugging, shorten it by creating workflow to immediately run the software in the desired state. This saves debugging time in the long run, and will provide further leverage if you integrate the workflow into a test suite.

Master Your Programming Environment

Programming environment do not change drastically. Mastering the standard tools used for all developments is a worthwhile investment.

  • Get proficient with your preferred text editor/IDE.
  • Learn at least one productive, high-level programming language.
  • Get familiar with UNIX/Windows shell commands.
  • Prefer keyboard over mouse.
  • Automate manual workflow.
  • Make it fast and simple to run unit tests with the current changes.

Don’t Ignore Your Non-Engineering Bottlenecks

  1. People bottleneck - you are waiting for dependencies to be delivered by another team or person. Constant and over communication helps. In the most extreme case, undertake the development of dependencies to take full control.
  2. Decision making bottleneck - get buy-in from key decision maker early, before heavily investing your development. Constantly communicate your ideas to them, and understand their priorities to align your project.
  3. Review process bottleneck - plan ahead, communicate and coordinate with teams involved to smoothen the process.

Some bottlenecks may be outside your sphere of influence. Just do your best to optimize the biggest bottleneck within your control.

5. Measure What You Want to Improve

Having a good metric to quantify user happiness is important. Example given in the book for Google Search’s success is using long click (duration user spent on the linked page before returning to the search result page) as a measure for user satisfaction with the search results.

Use Metrics to Drive Progress

  1. Good metrics help you focus on the important features that meet your objectives, and those with highest leverage.
  2. Tracking metrics over time guard against future regressions.
  3. Good metric drives forward progress. Setting a ratchet on the metric ensure that updates that worsen the metric cannot be deployed until another improvement counterbalances the change.

Pick the Right Metric to Incentivize the Behavior You Want

A good metric guides our decisions and focus to high leverage activities. A poor metric is counter productive and incentivize bad behavior. Choose a metric that:

  • maximize impact. something focused on the most important aspect of your product, unified across all teams.
  • is actionable. can be related directly back to engineering changes made by the team.
  • is responsive yet robust. responds to changes quickly yet does not pick up much noise.

Instrument Everything to Understand What’s Going On

  • The previous section talks about high level goals and metrics the entire team should be focused on.
  • This section talks about measuring operational metrics for the teams to understand how they are running.

Engineering team at Etsy:

Measure anything. Measure Everything.

A lot of insights are gathered through exploratory data analysis. Having dashboards, tools, and instruments to measure operations is essential to help achieve the main over arching goals mentioned in the previous sections. If not, everyone is just flying blind.

Internalize Useful Numbers

Jeff Dean, Googler, recommends the following latency numbers to keep in mind:

Access Type Latency
L1 Cache Ref 0.5 ns
Branch Mispredict 5 ns
L2 Cache Ref 7 ns
Mutex lock/unlock 100 ns
Main Memory Ref 100 ns
Compress 1KB w Snappy 10 micro-sec
Send 2KB over 1Gbps network 20 micro-sec
Read 1MB sequentially from memory 250 micro-sec
RTT within same data center 500 micro-sec
Disk seek 10 ms
Read 1MB sequentially from network 10 ms
Read 1MB sequentially from disk 30 ms
Send packet from CA to Netherlands to CA 150 ms

On top of these, which may not be relevant to you, there are other quick numbers more specific to your products/business that you should know, such as industry benchmarks.

  • number of users, weekly and monthly active, sign up etc.
  • number of requests per second.
  • amount and total capacity of data stored.
  • amount of data written and accessed daily.
  • number of servers needed per certain number of services.
  • throughput of services/endpoints.
  • growth rate of traffic.
  • average page load time.
  • distribution of traffic across different parts of product.
  • distribution of traffic across devices, browsers, OS, regions etc.

In conclusion:

  • Knowing such key figures enables you to do preliminary evaluation of designs without actually building it.
  • You can also quickly spot anomalies in related data measurements.
  • Helps to clarify areas and scopes of improvements.
  • if necessary, build simple tools to profile performance and gather these numbers.

Be Skeptical about Data Integrity

Untrustworthy and wrong data that gets incorporated into decision making processes creates negative leverage. Engineers tend to neglect metrics data integrity because:

  1. in the face of deadlines, measuring metrics has a lower priority.
  2. it is easier to validate features than to verify metrics data.
  3. of an assumption that since the feature code that generate the metrics are well tested, the data integrity should be guaranteed.

Strategies you can consider:

  • log data liberally in case it is needed in future.
  • build tools to iterate on data accuracy sooner.
  • write end-to-end integration tests to validate your entire analytics pipeline.
  • examine collected data sooner.
  • cross validate data accuracy by computing the same metric in multiple ways.
  • when a number looks off, investigate early.

6. Validate Your Ideas Early and Often

Faster iteration helps us get more things done. Frequent validation helps us get the right things done.

Find Low-Effort Ways to Validate Your Work

Invest a small amount of work to gather data to validate your project assumptions and goals in order to save a lot more wasted effort in the long run.

  • Dropbox used a video to showcase the target app behavior to measure user interest, without building an actual prototype.
  • 42Floors used mock up pages in their site to test out 8 different designs with users and only select the winning design for production.
  • Asana created a sign up form button to gather user clicks response before deciding to implement sign up feature.

Continuously Validate Product Changes with A/B Testing

A/B test feeds a random subset of users a change or a new feature while the rest of the users act as the control group.

  • perform on a number of features to find out the best variation.
  • quantify how much better is one variation compared to another.
  • encourages iterative approach to development.
  • if building an A/B testing framework is not feasible, consider open source or commercial tools.

Beware the One-Person Team

Working alone runs the risk of getting blindsided by design flaws that surface too late, jeopardizing entire project. Working in a team also improves morale and sustain momentum. Try the following strategies for a start:

  • be open and receptive to feedbacks.
  • commit code early and often.
  • request code review from thorough critics.
  • ask to bounce ideas off your teammates.
  • design the interface or API of a new system first.
  • send out design document first before you start coding.
  • structure ongoing projects so that there is some shared context with your teammates. this promotes collaboration and sharing.
  • solicit buy-in for controversial features before investing too much effort.

These also applies if you truly are working as a one-man team. There are always avenue to incorporate the feedback of others, and they do not even need to be engineers.

Build Feedback Loops for Your Decision

Validation should be built into decisions made at all levels of management, not just technical decisions. Even if you cannot test a decision rigorously like an A/B test, you can still put validation to practice.

  • formulate a hypothesis about what might work.
  • design an experiment to test it.
  • understand what good and bad outcomes look like.
  • run the experiment.
  • learn from the result.
  • make adjustments.

For more radical changes, perhaps a thought experiment amd soliciting feedback from others would suffice.

7. Improve Your Project Estimation Skills

Based on a study done in 2009, out of 50,000 software projects survey, 44% are delivered late, over-budgeted or missing requirements.

Use Accurate Estimate to Drive Project Planning

In general, use effort estimate to plan project timeline, instead of setting deadline first then cramp your development effort. When target date is impossible, the only way is to either scale back on deliverables or push back the deadline, to avoid a disaster.

To produce better estimates:

  • Decompose the project into granular tasks. this prevents hidden tasks from creeping up.
  • Estimate how long a task will take objectively, casting all agendas aside.
  • Estimates are probability distributions, not best-case scenarios. provide most probable outcome, not most ideal outcome.
  • The person executing the task should provide the estimates.
  • Beware of anchoring bias. avoid giving initial guesstimate and committing to the number without outlining the tasks.
  • Use multiple approaches to estimate the same task for more confidence. based on tasks, based on empirical data, based on number of systems etc.
  • Beware the mythical man-month. number of persons assigned to the task and time spent are not interchangeable, hence be very careful using man-day metrics.
  • Use time-boxing to constraint tasks that can grow in scope. examples are research and evaluation, set a hard deadline and force a decision within a time box.
  • Allow others to challenge the estimates.
  • iteratively improve the estimate. measure the actual effort and time taken, estimates can be refined to be more accurate as the project progresses, and reflect on making better estimates in future.

Budget for the Unknown

Changes may occur during the project that affects timeline:

  • creating new framework to follow best practices but this was not anticipated before project begins.
  • interrupted by high priority customers ad-hoc requests/incidents.
  • debugging for difficult-to-reproduce bugs.
  • tackle scalability problems.
  • manpower movements.
  • creating new subsystems for optimal performance when the original plan was to use third party libraries.
  • improving DevOps tools and processes to go faster, but required migration effort.

The longer the project timeline, the higher the probability of disruption occurring. Build buffer time into the effort estimate. Simple exercise to keep track of time spent on tasks can bring awareness to all the other competing priorities distracting you from the work and provide a sanity check for the projected completion time.

Define Specific Project Goals and Measurable Milestones

Setting a concrete project goal helps to:

  • distinguish between the must-haves and the nice-to-have in the list of tasks, and prioritize work accordingly.
  • build clarity and alignment across key stakeholders.

In addition, setting measurable milestones that generate value will keep the project on track and keep everyone focused on delivering the essentials. This also helps with communicating progress to management.

Reduce Risk Early

  • Prioritize the hardest tasks in the project first, so that we may address any likely error in the effort estimation and adjust accordingly while there are still ahead of time.
  • Integration usually has a higher risk due to numerous and complex interactions between systems and the difficulty in splitting the work into smaller tasks. Try building end-to-end scaffolding and do system testing as soon as possible, even if it is partly functional and stubs need to be used.

Approach Rewrite Projects with Extreme Caution

  • due to familiarity with original software, we tend to underestimate the effort of rewrites.
  • it is easy to introduce too many additional improvements as part of the rewrite.
  • any new features requested during rewriting must be added to both software, which increases risks and complexity the longer the project runs.

In general, try to rewrite a large piece of code in smaller chunks and refactor incrementally while preserving system behavior. This also provides flexibility to make changes during the rewrite.

If this approach is not feasible as traffic cannot be sliced between new and old versions running simultaneously, then perform the rewrite in different targeted phases. For example, first migrate the language of the code base as phase 1, then fix problems in phase 2.

Don’t Sprint in the Middle of a Marathon

Adding more hours to catch deadlines usually doesn’t work:

  • hourly productivity decreases with additional hours worked, just like the Law of Diminishing Marginal Return.
  • additional hours can burn out teammates.
  • you are probably way behind schedule that the extra hours don’t matter.
  • extra hours can hurt team dynamics.
  • communication overhead increases as deadline draws near.
  • sprint towards deadline incentivize technical debt.

In general, it is more realistic to either extend the deadline or deliver what is possible. If overtime has to happen, try these approach:

  • ensure the team understands why they are behind schedule.
  • develop a realistic and revised plan and timeline.
  • be ready to abandon the sprint if you slip further from deadline.

Build Long-Term Value

8. Balance Quality with Pragmatism

High software quality enables you to scale and also speed of delivery, however it is also possible to create too much overhead with code reviews, standardization, and test coverage that it produces diminishing returns and improvement in quality reduces effectiveness instead. Striking a balance is essential.

Establish a Sustainable Code Review Process

Code reviews helps to:

  • catch bugs or design issues early.
  • increases accountability.
  • set a positive model of good code.
  • shares working knowledge of codebase.
  • increases long-term agility to make changes in future.

In order to gain these benefits and balance effectiveness, code review need not be absolute. Review can be conducted in many flavours:

  • over-the-shoulder code walk through.
  • pair programming in place of review.
  • review the trickiest part of code only.
  • post-commit review.
  • review only business logic codes in models and controllers.
  • using code review tools to speed up and simplify the process.

Manage Complexity through Abstraction

Providing abstraction, in the form of libraries/SDK/APIs, allows engineers to avoid writing cumbersome codes that deals with lower level logic. It helps to:

  • reduce complexity of the original problem into easy-to-understand primitives.
  • reduce future application maintenance and makes it easier to make improvements.
  • implement foundation code once and use them repeatedly for many times.

However, it is possible to overinvest in creating abstraction that detract you from your main project goals. It is also possible to create bad abstraction, that not only waste development time of anyone using them, but also becomes a liability in future.

Features of a good abstraction:

  • easy to learn
  • easy to use without documentation
  • hard to misuse
  • sufficiently powerful to satisfy requirements
  • easy to extend
  • appropriate to the audience

To aid in your learning, start with:

  • studying popular abstraction in your work codebase/GitHub repository, and try extending them.
  • studying key abstractions released by large tech companies (Protocol Buffer, Thrift, Hive, MapReduce etc.) to understand what makes them essential to other developers.
  • studying other popular APIs to learn what makes them easy to use.

Automate Testing

  • Having a library of tests in place builds confidence and overcomes the fear of making changes, especially when these changes are not implemented by the original code authors.
  • It may be hard to introduce testing because of the investment required or because of organizational inertia. Take baby steps and start from the test with highest impact to exhibit value.
  • Automated testing allows you to quickly zoom in to bugs and find the party accountable to make the fixes.

In conclusion, automated testing reduces overall error rates of each iteration, compounding the benefit, making it a high leverage activity despite its initial cost.

Repay Technical Debt

Technical Debt = deferred work that is necessary to improve the health and quality of the codebase, and would be a liability if left unaddressed.

Incurring technical debt allows you to deliver faster in the short run, only if you promptly repay the debt. Left unattended in the long run, debt-ridden code will severely impede progress.

Technical debt can happen when we take shortcuts using quick and dirty workarounds, or when we did not fully understood the problem space and implemented a primitive solution.

Try having a regular schedule to fix technical debt. Also, try to prioritize fixing the highest leverage code first.

9. Minimize Operational Burden

If we create software that compounds operational efforts (scale features, maintain uptime, fix bug, new recruit onboarding) over time in order to perform, then eventually the entire development team will be engulfed by operations work.

Embrace Operational Simplicity

Steve Jobs:

the first solutions you come up with are very complex … but if you keep going … and peel more layers of the onion off, you can often times arrive at some very elegant and simple solutions.

Having a complex architecture imposes the following costs:

  • engineering expertise gets splintered across multiple systems.
  • increased complexity introduces more potential single points of failure.
  • onboarding new engineers face a steeper learning curve.
  • dilute effort towards improving abstractions and tools.

In general, try to always use the simplest solution that can get the job done while also reducing operational burden.

Build Systems to Fail Fast

The more directly we can link a bug to the source, the more quickly we can reproduce the problem and resolve the issue. An approach to achieve this is to Fail Fast = failing immediately and visibly.

  • crashing at startup when there is config errors.
  • input validation.
  • throw error from external service instead of suppressing it.
  • throw exception when changes in data structure causes dependents to be unusable.
  • throw exception when data structure is corrupted instead of propagating the corruption.
  • assert key invariants before and after complex logic.

This does not mean crashing the system, but to highlight problems as close to the source as possible. A typical flow could be to handle issues by a global exception handler that reports to engineers while failing gracefully for the end users. The handler can even log the exception, and aggregate it to show engineers the problem in a visualized manner to facilitate debugging.

Relentlessly Automate Mechanical Tasks

The decision point is usually: will you save more time overall by manually doing a task or by paying the upfront investment of automating it?

Reasons why engineers automate less frequently than they should:

  • lack of time.
  • tragedy of the commons.
  • not familiar with automation tools.
  • underestimate frequency of recurrence.
  • did not evaluate total time saved.

Things that you can consider automating:

  • validation.
  • extract-transform-aggregation of data.
  • detect spikes in error rate.
  • build and deploy software.
  • capture and restore database snapshot.
  • run batch computations.
  • restarting services.
  • code style conformity checks.
  • training ML model.
  • user account/data management.
  • add/remove server from groups.

Note that automating mechanics is straight forward, but automating decision making carries a much higher risk and you may still want to rely on manual process. For a start, automate mechanics first.

Make Batch Processes Idempotent

idempotency = produces the same results regardless of whether a process is run once or multiple times.

If idempotence is not possible, at least make your process retry-able. This can help you manage script crashes. Also, processes that cannot be retry typically leaves side effect that affect certain global state.

Idempotence allows you to run processes more frequently to expose problems earlier. This can also help to reduce the chances of logging false positives as incidents.

Hone Your Ability to Respond and Recover Quickly

The Chaos Monkey system in Netflix kills services in its own infrastructure randomly to see how resilient their systems are, and how well they recover from failure.

No matter how carefully we try to prevent them, unexpected failures will still occur. Therefore, being able to recover quickly is more important than prevention.

It is helpful to map out failure scenarios, put contingencies in place, and simulate the scenario to be prepared to recover from failure.

This applies to non-technical aspects, such as changes in management direction, manpower movements, user revolt etc.

10. Invest in Your Team’s Growth

Your effectiveness is often measured by the impact of the entire team. Therefore, if everyone else is successful, you will be swept along by the tide even if you did nothing.

Make Hiring Everyone’s Responsibility

Hiring often provides more leverage than the actual development work.

A good interview process:

  • screen for people likely to do well on the team.
  • gets candidate excited about the team, mission, and culture.
  • ask questions with high signal-to-noise ratio, usually pertaining to qualities most correlated with success of the team.
  • controlled pace to maintain high signal-to-noise ratio and prevent going off track.
  • firing short-answer questions to probe a wide surface area can surface red flags.

To iteratively improve the interview process:

  • discuss with the team to identify qualities they care about in their potential new teammates, and make sure these are covered by the questions.
  • periodically revise the process by looking at how well the new hires are performing.
  • design questions with multiple layers of difficulty that can be added/removed to tailor to the current candidate’s abilities.
  • shadow different team members during interviews to calibrate ratings across interviewers and promote feedback.
  • dare to try unconventional interview approaches to help you identify signals.

Design a Good Onboarding Process

Good onboarding allows new engineers to be productive faster, making this a high leverage activity.

To create a good onboarding process:

  • identify onboarding goals the team wants to achieve.
  • construct a set of mechanisms to accomplish these goals.

As with all other processes mentioned in this book, this is again an iterative process and a good onboarding experience can be incrementally reviewed and improved upon.

Share Ownership of Code

Sharing ownership helps everyone in the team. No one is held solely responsible for any tasks, and they will be able to go for vacations in peace. The management is also better able to react to manpower movements.

  • avoid one-man team.
  • review each other’s code and software designs.
  • rotate tasks and responsibilities in the team.
  • keep code readable and high quality.
  • give talks on software decisions and architecture.
  • document, at high-level and in code-level comment.
  • document complex workflow and non-obvious workarounds.
  • invest time to teach and mentor others.

Build Collective Wisdom through Post-Mortem

After a high priority incident, the goal of post-mortem is not to assign blame, which is counterproductive, but to work together to identify better solutions.

If situation is not preventable, then focus on making recovery easier.

Post-mortem help to measure success, provided the metric is well-defined.

Document and distribute post-mortem information will help other teams avoid the same problems, and prevent loss of knowledge from manpower movements.

Ultimately, compiling lessons learned requires honest conversation, in a culture comfortable and open to failure and receptive to feedback.

Build a Great Engineering Culture

From the author’s past experience, a great engineering culture has the following characteristics, many are covered in earlier chapters:

  1. Optimized for iteration speed.
  2. Push relentlessly for automation.
  3. Build the right software abstraction.
  4. Focus on high code quality through code reviews.
  5. Maintain a respectful work environment.
  6. Build shared ownership of code.
  7. Invest in automated testing.
  8. Have time to experiment, through 20% time or hackathon.
  9. Foster culture of learning and continuous improvement.
  10. Hire the best.

Great culture isn’t built in one day. It improves iteratively, and as a great culture attracts stronger talent, the positive feedback cycle propagates.

Epilogue

Edmund Lau, the awesome author:

Time is our most finite asset, and leverage - the value we produce per unit time - allows us to direct our time toward what matters most.