This article was adapted from a Google Tech on the Toilet (TotT) episode. You can download a printer-friendly version of this TotT episode and post it in your office.

By Sebastian Dörner

We often read code linearly, from one line to the next. To make code easier to understand and to reduce cognitive load for your readers, make sure that adjacent lines of code are coherent.  One way to achieve this is to order your lines of code to match the data flow inside your method:

fun getSandwich(

    bread: Bread, pasture: Pasture

): Sandwich {

  // This alternates between milk-

// bread-related code.

  val cow = pasture.getCow()

  val slicedBread = bread.slice()

  val milk = cow.getMilk()


  val toast = toastBread(slicedBread)

  val cheese = makeCheese(milk)


  return Sandwich(cheese, toast)

}

fun getSandwich(

    bread: Bread, pasture: Pasture

): Sandwich {

  // Linear flow from cow to milk

// to cheese.

  val cow = pasture.getCow()

  val milk = cow.getMilk()

  val cheese = makeCheese(milk)


  // Linear flow from bread to slicedBread

// to toast.

  val slicedBread = bread.slice()

  val toast = toastBread(slicedBread)


  return Sandwich(cheese, toast)

}

To visually emphasize the grouping of related lines, you can add a blank line between each code block.

Often you can further improve readability by extracting a method, e.g., by extracting the first 3 lines of the function on the above right into a getCheese method. However, in some scenarios, extracting a method isn’t possible or helpful, e.g., if data is used a second time for logging. If you order the lines to match the data flow, you can still increase code clarity:

fun getSandwich(bread: Bread, pasture: Pasture): Sandwich {

  // Both milk and cheese are used below, so this can’t easily be extracted into

// a method.

  val cow = pasture.getCow()

  val milk = cow.getMilk()

  reportFatContentToStreamz(cow.age, milk)

  val cheese = makeCheese(milk)


  val slicedBread = bread.slice()

  val toast = toastBread(slicedBread)


  logWarningIfAnyExpired(bread, toast, milk, cheese)

  return Sandwich(cheese, toast)

}


It isn’t always possible to group variables
perfectly if you have more complicated data flows, but even incremental changes in this direction improve the readability of your code. A good starting point is to declare your variables as close to the first use as possible.


By Kanu Tewary and Andrew Trenk


Tech on the Toilet (TotT) is a weekly one-page publication about software development that is posted in bathrooms in Google offices worldwide. At Google, TotT is a trusted source for high quality technical content and software engineering best practices. TotT episodes relevant outside Google are posted to this blog.


We have been posting TotT to this blog since 2007. We're excited to announce that Testing on the Toilet has been renamed Tech on the Toilet. TotT originally covered only software testing topics, but for many years has been covering any topics relevant to software development, such as coding practices, machine learning, web development, and more.


A Cultural Institution


TotT is a grassroots effort with a mission to deliver easily-digestable one-pagers on software development to engineers in the most unexpected of places: bathroom stalls! But TotT is more than just bathroom reading -- it's a movement. Driven by a team of 20-percent volunteers, TotT empowers Google employees to learn and grow, fostering a culture of excellence within the Google engineering community.



Photo of TotT posted in a bathroom stallPhoto of TotT posted in a bathroom stall

Photos of TotT posted in bathroom stalls at Google.


Anyone at Google can author a TotT episode (regardless of tenure or seniority). Each episode is carefully curated and edited to provide concise, actionable, authoritative information about software best practices and developer tools. After an episode is published, it is posted to Google bathrooms around the world, and is also available to read online internally at Google. TotT episodes often become a canonical source for helping far-flung teams standardize their software development tools and practices.  


Because Every Superhero Has An Origin Story 

TotT began as a bottom-up approach to drive a culture change. The year was 2006 and Google was experiencing rapid growth and huge challenges: there were many costly bugs and rolled-back releases. A small group of engineers, members of the so-called Testing Grouplet, passionate about testing, brainstormed about how to instill a culture of software testing at Google. In a moment of levity, someone suggested posting flyers in restrooms (since people have time to read there, clearly!). The Testing Grouplet named their new publication Testing on the Toilet. TotT’s red lightbub, green lightbulb logo–displayed at the top of the page of each printed flyer–was adapted from the Testing Grouplet’s logo.   

The TotT logo

The TotT logo.

The first TotT episode, a simple code example with a suggested improvement, was written by an engineer at Google headquarters in Mountain View, and posted by a volunteer in Google bathrooms in London. Soon other engineers wrote episodes, and an army of volunteers started posting those episodes at their sites. Hundreds of engineers started encountering TotT episodes.

The initial response was a mix of surprise and intrigue, with some engineers even expressing outrage at the "violation" of their bathroom sanctuary. However, the majority of feedback was positive, with many appreciating the readily accessible knowledge. Learn more about the history of TotT in this blog post by one of the original members of the Testing Grouplet.  

Trusted, Concise, Actionable

TotT has become an authoritative source for software development best practices at Google. Many episodes, like the following popular episodes at Google, are cited hundreds of times in code reviews and other internal documents: 

A 2019 research paper presented at the International Conference of Software Engineering even analyzed the impact of TotT episodes on the adoption of internal tools and infrastructure, demonstrating its effectiveness in driving positive change.

TotT has inspired various other publications at Google, like Learning on the Loo: non-technical articles to improve efficiency, reduce stress and improve work satisfaction. Other companies have been inspired to create their own bathroom publications, thanks to TotT. So the next time you find yourself reading a TotT episode, take a moment to appreciate its humble bathroom beginnings. After all, where better to ponder the mysteries of the code than in a place of quiet contemplation?

This article was adapted from a Google Testing on the Toilet (TotT) episode. You can download a printer-friendly version of this TotT episode and post it in your office.
This article was adapted from a Google Testing on the Toilet (TotT) episode. You can download a printer-friendly version of this TotT episode and post it in your office.

By Adam Bender

The test pyramid is the canonical heuristic for guiding test suite evolution. It conveys a simple message - prefer more unit tests than integration tests, and prefer more integration tests than end-to-end tests.

A diagram of the test pyramid

While useful, the test pyramid lacks the details you need as your test suite grows and you face challenging trade-offs. To scale your test suite, go beyond the test pyramid.

The SMURF mnemonic is an easy way to remember the tradeoffs to consider when balancing your test suite:

  • Speed: Unit tests are faster than other test types and can be run more often—you’ll catch problems sooner.

  • Maintainability: The aggregated cost of debugging and maintaining tests (of all types) adds up quickly. A larger system under test has more code, and thus greater exposure to dependency churn and requirement drift which, in turn, creates more maintenance work.  

  • Utilization: Tests that use fewer resources (memory, disk, CPU) cost less to run. A good test suite optimizes resource utilization so that it does not grow super-linearly with the number of tests. Unit tests usually have better utilization characteristics, often because they use test doubles or only involve limited parts of a system. 

  • Reliability: Reliable tests only fail when an actual problem has been discovered. Sorting through flaky tests for problems wastes developer time and costs resources in rerunning the tests. As the size of a system and its corresponding tests grow, non-determinism (and thus, flakiness) creeps in, and your test suite is more likely to become unreliable.

  • Fidelity: High-fidelity tests come closer to approximating real operating conditions (e.g., real databases or traffic loads) and better predict the behavior of our production systems. Integration and end-to-end tests can better reflect realistic conditions, while unit tests have to simulate the environment, which can lead to drift between test expectations and reality.

A radar chart depicting the relationship between SMURF attributes as applied to unit, integration, and end-to-end tests. Unit tests perform best on all attributes except fidelity, where they are the worst. Integration tests are mid-way performers on all aspects. End-to-end tests are worst on all aspects, except fidelity where they are the best.

A radar chart  of Test Type vs. Test Property (i.e. SMURF). Farther from center is better. 


In many cases, the relationships between the SMURF dimensions are in tension: improving one dimension can affect the others. However, if you can improve one or more dimensions of a test without harming the others, then you should do so. When thinking about the types of your tests (unit, integration, end-to-end), your choices have meaningful implications for your test suite’s cost and the value it provides.



This is another post in our Code Health series. A version of this post originally appeared in Google bathrooms worldwide as a Google Testing on the Toilet episode. You can download a printer-friendly version to display in your office.

By Amy Fu

Although a product's requirements can change often, its fundamental ideas usually change slowly. This leads to an interesting insight: if we write code that matches the fundamental ideas of the product, it will be more likely to survive future product changes.

Domain objects are building blocks (such as classes and interfaces) in our code that match the fundamental ideas of the product. Instead of writing code to match the desired behavior for the product's requirements ("configure text to be white"), we match the underlying idea ("text color settings").

For example, imagine you’re part of the gPizza team, which sells tasty, fresh pizzas to feed hungry Googlers. Due to popular demand, your team has decided to add a delivery service.

Without domain objects, the quickest path to pizza delivery is to simply create a deliverPizza method:

public class DeliveryService {

  public void deliverPizza(List<Pizza> pizzas) { ... }

}

Although this works well at first, what happens if gPizza expands its offerings to other foods?
You could add a new method:

  public void deliverWithDrinks(List<Pizza> pizzas, List<Drink> drinks) { ... }

But as your list of requirements grows (snacks, sweets, etc.), you’ll be stuck adding more and more methods. How can you change your initial implementation to avoid this continued maintenance burden?

You could add a domain object that models the product's ideas, instead of its requirements:

  • A use case is a specific behavior that helps the product satisfy its business requirements.
    (In this case, "Deliver pizzas so we make more money".)

  • A domain object represents a common idea that is shared by several similar use cases.

To identify the appropriate domain object, ask yourself:

  1. What related use cases does the product support, and what do we plan to support in future?

A: gPizza wants to deliver pizzas now, and eventually other products such as drinks and snacks.

  1. What common idea do these use cases share?

A: gPizza wants to send the customer the food they ordered.

  1. What is a domain object we can use to represent this common idea?

A: The domain object is a food order. We can encapsulate the use cases in a FoodOrder class.

Domain objects can be a useful generalization - but avoid choosing objects that are too generic, since there is a tradeoff between improved maintainability and more complex, ambiguous code. Generally, aim to support only planned use cases - not all possible use cases (see YAGNI principles).

// GOOD: It's clear what we're delivering.

public void deliver(FoodOrder order) {}

// BAD: Don't support furniture delivery.

public void deliver(DeliveryList items) {}

Learn more about domain objects and the more advanced topic of domain-driven design in the book Domain-Driven Design by Eric Evans.