Google

Abhishek Baranwal7 min read

## Testing Blog

Code Review Responses: Add Context When It Counts

Tuesday, May 12, 202679

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 Saicharan Nimmala

When responding to code review comments, responses like “Done,” “Updated,” or “Fixed” are commonly used to indicate addressing a suggestion. However, sometimes, a little extra context adds a lot of clarity.

Next time you resolve a code review comment, ask yourself: "Is how I addressed the comment completely obvious from the code change and comment thread?" If not, supplement your response with a brief note to clarify the “why” or “how.” Your reviewers will thank you.

When is it helpful to add context to a code review comment response? Here are a few examples:

  • Your code change doesn't fully explain how you addressed the comment. Providing a brief summary helps the reviewer verify the changes without re-examining every line of the delta, and creates a clearer historical record.

Reviewer:

This approach seems risky. It might not handle all the edge cases properly.

Less helpful response:

More helpful response:

Author:

Updated.

Good catch. I've added checks for null, empty, and negative inputs, each with a new test case. Thanks!

  • You made a design choice or trade-off that isn't self-evident. Capturing the reasoning behind a choice provides valuable context. Note that non-obvious design choices within the code should ideally be explained in code comments or the commit description as well.

Reviewer:

Consider using a more performant library for this data transformation.

Less helpful response:

More helpful response:

Author:

I’ll go with Y.

Done. I considered Library X, but stuck with Library Y because our datasets here are typically small, so the performance difference is negligible, and Library Y has a much simpler API.

  • An offline discussion influenced the solution. Briefly summarizing the outcome or key reasoning from an offline sync ensures that other reviewers, who only see the final code change, can grasp the “why”.

Reviewer:

This logic seems a bit complex. Consider a simpler way to handle these.

Less helpful response:

More helpful response:

Author:

Fixed.

As we discussed offline, this complexity is required to maintain backward compatibility with legacy data formats. I’ve added a comment in the code to clarify this. Thanks!

  • There are multiple ways to address the comment. Clearly stating which option you selected and the reasoning behind that choice over other alternatives helps reviewers.

Learn more code review practices in Google’s code review guide: google.github.io/eng-practices/review.

Share on TwitterShare on Facebook

No comments

Labels: Saicharan Nimmala , TotT

Construct with Collaborators, Call with Work

Tuesday, May 05, 2026

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 Shahar Roth

Classes require various objects and parameters to function. The "Construct with Collaborators, Call with Work" guideline can help you construct effective inputs:

  • Use the constructor for collaborators—the dependencies that establish the object’s identity. Collaborators stay with the object for its lifetime to enable it to fulfill its ongoing duties.

  • Pass work—the parameters that change with each interaction—to methods. Unique to each call, these inputs provide the specific data needed for an operation such as a file path or database query.

Consider a ReportGenerator that needs a database, a formatter, and a date range to generate a report. The database and formatter, as collaborators, are injected via the constructor, while dateRange, which varies per report generation, is passed as a method parameter to the generate method:

class ReportGenerator {

private final Database database;

private final Formatter formatter;

// database and formatter are passed as collaborators.

ReportGenerator(Database database, Formatter formatter) {

this.database = database;

this.formatter = formatter;

}

// dateRange is passed as a parameter.

Report generate(Range<Instant> dateRange) {

return formatter.format(database.getRecords(dateRange));

}

}

A single ReportGenerator object can generate multiple reports with different date ranges:

ReportGenerator generator = new ReportGenerator(database, formatter);

Report report1 = generator.generate(dateRange1);

Report report2 = generator.generate(dateRange2);

Following the "Construct with Collaborators, Call with Work" guideline promotes:

  • Reusability: Enables instances to be used for multiple, distinct operations.

  • Testability: Separates dependency setup from business logic.

  • Cleaner code: Hides implementation dependencies from the object’s users.

  • Predictable behavior: Locks in dependencies at creation time.

Note that the definition of "collaborator" versus "work" depends on the object's identity. For example, a RequestMessage could be a collaborator for a RequestHandler if the handler operates on a single request, or work if the handler processes different requests with each method call.

Share on TwitterShare on Facebook

No comments

Labels: Shahar Roth , TotT

One Map Key, One Lookup

Wednesday, April 29, 2026

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 Roman Govsheev

Can you spot the wasted CPU cycles in the map usage?

if employee_id in employees:

mail_to(employees[employee_id].email_address)


The redundant lookup caused the waste by performing a check (in) and a fetch ([]) as two separate operations when one is sufficient.

Every lookup involves a cost—whether it's computing a hash and scanning buckets or performing an O(log n) traversal. These costs add up quickly. But avoiding them isn’t just “premature optimization”—it’s about writing cleaner, more robust code that stays efficient at scale and prevents potential race conditions.

Instead of paying this cost twice, perform the lookup once and reuse the result:

if (employee := employees.get(employee_id)) is not None:

mail_to(employee.email_address)


Assigning the search result to a variable avoids a second lookup. This efficiency is native to Go via the “comma ok” idiom (val, ok := map[key]) and C++ using map.find(key), both handling retrieval and existence in a single pass.

The same inefficiency applies when counting or initializing default. Stop checking for presence; instead, use idioms that handle missing keys automatically at the container level:

The redundant way

The efficient way

If key not in counts:

counts[key] = 1

else:

counts[key] += 1

counts = defaultdict(int)  # Initializes 0 automatically

# ... other logic ...

counts[key] += 1

Here are some details depending on which language you use:

  • C++: operator[] returns a reference to the value—automatically inserting a default (like 0) if the key is missing—allowing the increment to happen in place.

  • Java: Use map.computeIfAbsent() to perform retrieval and updates in a single call. This is more concise and, on concurrent collections, has the potential to be thread-safe—preventing the “check-then-act” race conditions common with separate contains and put calls.

  • Python: Use collections.defaultdict to handle defaults at the container level, which pushes the logic into optimized C code for better performance and robustness. Note that the += operation (shown later in the above code sample) still involves both a read and a write operation.

  • Go: Use val, ok := map[key] to handle retrieval and existence in one memory access.

Share on TwitterShare on Facebook

No comments

Labels: Roman Govsheev , TotT

The Way of TDD

Tuesday, March 10, 2026

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 Bartosz Papis

Test-Driven Development (TDD) is the practice of working in a structured cycle where writing tests comes before writing production code. The process involves three steps, sometimes called the red-green-refactor cycle:

  1. Write a failing test

  2. Make the test pass by writing just enough production code

  3. Refactor the production code to meet your quality standards

The three steps in TDD

Research shows TDD has several benefits: it improves test coverage, reduces the number of bugs, increases confidence, and facilitates code reuse. This practice also helps reduce distractions and keep you in the flow. TDD also has its limitations and is not a silver bullet! See the Wikipedia article about TDD for a detailed explanation and references.

Here is a short practical example. Assume you need to modify the following voting algorithm to support the option for voters to abstain:

def outcome(ballots):

if ballots.count(Vote.FOR) > len(ballots) / 2:

return "Approved"

return "Rejected"

1. We start by writing a failing test - as expected, the test doesn't even compile:

def test_abstain_doesnt_count(self):

self.assertEqual(outcome([Vote.FOR, Vote.FOR, Vote.AGAINST, Vote.ABSTAIN]), "Approved")

2. We fix the compilation error by including the missing enum option:

class Vote(Enum):

FOR = 1

AGAINST = 2

ABSTAIN = 3

Now that the test compiles, we fix the production code to get all tests passing:

def outcome(ballots):

if ballots.count(Vote.FOR) > (len(ballots) - ballots.count(Vote.ABSTAIN)) / 2:

return "Approved"

return "Rejected"

3. We now refactor the code to improve clarity, and complete an iteration of the TDD cycle:

def outcome(ballots):

counts = collections.Counter(ballots)

return "Approved" if counts[Vote.FOR] > counts[Vote.AGAINST] else "Rejected"

Learn more about TDD in the book Test Driven Development: By Example, by Kent Beck.

Share on TwitterShare on Facebook

No comments

Labels: Bartosz Papis , TotT

Labels

Archive

Feed