Technical Debt, Tooling and Architecture

Complex software, technical debt, and excessive manual processes will prevent us from shipping high-quality software quickly and consistently. To maintain high velocity, we continuously improve our code by refining our architecture, building tools, and addressing technical debt.

Engineers are closest to the problem and are responsible for finding solutions and allocating time to improve code.

Improving our code is an ongoing cost of feature work. The amount of work varies greatly from week-to-week, but we should expect to spend roughly 20% of our time improving code.

Responsibilities of Engineers

Drive improvement

Fix straightforward issues immediately rather than incurring overhead by creating and tracking tickets, especially if that ticket will be assigned to someone else.

During technical scoping, identify code improvements that would simplify the solution and include that in the scope of work.

As you complete a feature, look for technical debt related to the feature and fix it, even if it expands the scope of work. Solicit feedback from other engineers about technical debt and provide feedback during retros about opportunities to improve code.

When code improvement doesn’t fit cleanly into an epic, work with your manager to schedule cooldown time (or Exploration Time) to address the issue.

In the uncommon case where more time is required, document the scope of work and advocate for its inclusion on our roadmap.

As always, prioritize pragmatism and simplicity.

Advocate for simpler solutions

When building a feature, we can often identify simpler models that will still solve the user’s problem. This includes implementation details but also user interactions.

Guidelines

Start with the problem statement

Agree on the problem to effectively evaluate the tradeoffs of solutions.

Solicit feedback on the problem and solutions from teammates via direct conversations, ADRs, or the iteration meeting.

Consider the “Rule of Three”

Consider waiting for a third occurrence of a problem before addressing it to avoid premature abstraction.

If you are concerned about the duplication, consider adding a DUPE comment e.g.

# in file1.rb
# DUPE: counting-numbers
evens, odds = some_arr.partition(&:even?)
evens.count

# in file2.rb
# DUPE: counting-numbers
divisible_by_three = some_other_arr.select { |x| (x % 3).zero? }
divisible_by_three.count

Beware of layering complexity

Often, the most convenient way to modify the behavior of a complex system is to add another layer of complexity on top, but this leads to even more complex systems.

Try to simplify the original system rather than adding more complexity to accommodate new use cases.