Rating: 7.6/10.
Software Engineering at Google: Lessons Learned from Programming Over Time, Curated by Titus Winters, Tom Manshreck, and Hyrum Wright
Book about software engineering practices and processes relevant for large tech companies like Google. As an organization increases in scale (size of codebase and amount of time it needs to function), its priorities become different. Google must deal with code that lives for years, not days or weeks, and certain problems that only arise at that scale, like upgrading the compiler version, can be ignored in short lived projects. This scale adds a lot of challenges not faced by smaller organizations.
The first part of the book is about managing teams: for example, delegating work to subordinates, giving feedback to underperformers, giving enough direction for engineers to be productive while leaving them autonomy, and shielding them from organizational chaos. A higher-level leader takes a large problem, divides it into subproblems, and assign different teams to each one, while at the same time, not becoming a single point of failure and a bottleneck for the team. This section overlaps somewhat with a previous book I read about software engineering management.
Next up are three chapters on style guides, code reviews, and documentation. Style guides are useful for disallowing language features that are prone to misuse, and improving readability since code in a language look the same across teams; it should be automatically enforced by tools whenever possible, while also allowing for natural evolution of the language. Code review is useful for correctness and knowledge transfer, they should be fairly short so that they can be reviewed quickly; only one reviewer provides the most benefit, although Google has a second reviewer for readability. Documentation can come in many types, each optimized for a particular type of reader and their goals: API references, tutorials, design docs, etc, and each type needs to make a tradeoff between clarity and accuracy, since the most technically correct documentation would be confusing to beginners.
The next few chapters cover testing on different scales, from simple unit tests all the way up to multi-server tests of production-like setups. The majority of tests should be small unit tests: small tests are less likely to become slow or flaky, which causes developers to lose confidence in them. Unit tests should aim not to be brittle (needing to be updated when only internal implementation details change) – test for publicly observable behavior and not internal state; prefer to have some repetition in the testing code, rather than complex logic that is difficult to read. For code that requires external dependencies, prefer using test fakes (toy implementations with correct behavior) rather than mocking return values, mocking is brittle because it is easy for the mocked function to diverge from the actual API. Larger tests (spanning multiple services) are harder to run automatically, and many testing strategies at this level requires some degree of human involvement to determine if the test behavior is correct.
The final 2/3 of the book are about Google-specific processes, many of which are different from most other companies and are of limited relevance for companies operating on a smaller scale. For example, Google uses its own version control system and enforces a monorepo to make it easier to avoid multiple versions of dependencies. The internal code search tool searches across the entire codebase (too large to fit into an IDE) leveraging their famous search technologies such as PageRank. The chapter about large scale changes is interesting: when the company needs to change a basic data structure across the codebase, often thousands of files are affected, and it is more efficient for a small team to handle all of these changes than for each team to do it themselves. Dependencies are also a challenging area: Hyram’s law says that when an API is released, all of its observable behavior is quickly depended on, so it cannot be easily changed without breaking stuff. With a large enough web of dependencies, you will get the diamond dependency problem where multiple libraries depend on different versions of the base dependency. Semantic versioning is an attractive proposal, but still, its effectiveness is limited because whether or not a change is major is largely a subjective decision.
Overall, this book provides a good picture of day-to-day software engineering problems faced by large companies; it’s a bit long and I became tired after reading it cover to cover, although many of the chapters can be read independently (especially the later ones). Many of the concepts are familiar to anybody who has worked as a professional software engineer (eg: code review, version control, CI/CD, static analysis), but there are always some interesting discussions of design decisions and trade-offs that need to be made.