0% прочитано

When One Change Touches Dozens of Files, Check the Component Boundary

When one behavior requires changes across many similar components, the problem may not be the amount of work but the responsibility boundary. I show how I distinguish a legitimate broad change from a refactoring signal.

4 жовтня 2026 р. 8 хв читанняLaravel

Sometimes a change looks small: add one behavior rule for a link, button, or another UI action.

Then the first component is opened, then the second, the third, the tenth, and it becomes clear that the same decision is repeated in many places.

Technically, I can update every file carefully.

But a large number of identical edits is a signal for me: the behavior may be owned at the wrong level of the system.

Not every broad change means bad architecture. But when one reason for change produces dozens of edit points, I recheck the responsibility boundary.

The number of changed files is not the problem by itself. The important question is how many different reasons make those files change together.

I Look at the Reason for Change, Not Just the File Count

There are legitimate cases where a change naturally touches many files.

For example, renaming a public type, changing a shared contract, or updating application-wide design.

A different situation is more suspicious: dozens of components change because of one shared behavior rule.

At that point I stop asking "how do I update all these files faster?" and ask "why does every one of these files know this rule?".

A Repeated Decision Matters More Than Repeated Syntax

Vue - The same behavior repeated across many components
<a    :href="item.url"    :target="isExternal(item.url) ? '_blank' : undefined"    :rel="isExternal(item.url) ? 'noopener noreferrer' : undefined">    {{ item.label }}</a>

Duplication does not always look like a large copied method.

Sometimes only a small decision repeats: open external links one way, internal links another way, add an attribute for one action type but not another.

A few lines may initially look too small to justify an abstraction.

But if those same lines must be changed consistently across many components, the repeated thing is no longer syntax. It is the rule.

A Leaf Component Should Not Always Own the Rule

A component that only renders a link can gradually accumulate too much responsibility.

At first it knows only the href and label.

Then external URL detection appears. Then an exception for one action type. Then a security rule. Then another URL format.

Eventually, every renderer begins making the same decisions independently.

In that situation, I check whether the behavior can be resolved earlier and the component can return to simply rendering the result.

A Shared Rule Is Better Resolved in One Place

PHP - Generic example of a centralized behavior rule
final readonly class ActionLink{    public function __construct(        public string $url,        public ?string $target,        public ?string $rel,    ) {}} final class ActionLinkResolver{    public function resolve(string $url): ActionLink    {        $external = $this->isExternal($url);         return new ActionLink(

This does not mean every project needs exactly this service or value object.

The important part is the boundary: one place owns the decision and the renderers receive an already resolved result.

Changing the rule no longer requires remembering every individual renderer.

A Good Boundary Reduces What the Component Needs to Know

Before reconsidering the boundary
Component knows: - what an external URL is- when target is required- which rel is safe- which action types are exceptions- how to normalize the URL- how to render the link
After moving the shared decision
Component knows: - URL- target- rel- how to render the link  Shared resolver knows: - behavior rules

I Do Not Abstract After the First Duplication

The opposite extreme is dangerous too: see two identical lines and immediately build a universal helper.

Two similar components may look identical today and evolve in different directions later.

I therefore look not only for repeated syntax but for a shared reason to change.

If several components repeatedly change together because of the same rule, the abstraction boundary becomes much clearer.

Signals That Make Me Stop Before a Mass Edit

Before editing many similar files, I check several signals:

  • the same condition is being added everywhere;
  • the rule is unrelated to the visual responsibility of the individual component;
  • one missed renderer would behave differently from the others;
  • a new component type would need to copy the same rule again;
  • the next rule change would require another global search;
  • components are learning details they do not need for rendering.

The more of these are true, the stronger the signal that the decision belongs at a higher level.

Centralization Changes What I Need to Test

When a rule lives in many components, it must either be tested repeatedly or there is a risk that one renderer behaves differently.

After centralization, the main behavior variants can be tested close to the rule itself.

Component tests remain simpler: they verify that already resolved properties are rendered correctly.

This is not only about reducing code. It also separates the decision from the rendering.

The Refactor Does Not Always Need to Happen All at Once

If many components already exist, a full refactor may be riskier than the feature itself.

In that case I prefer an incremental approach:

  1. define the new shared boundary;
  2. cover the behavior with focused tests;
  3. move one component type first;
  4. verify the result;
  5. gradually remove the old local decisions.

Centralization should not itself become an unnecessarily risky rewrite.

This Signal Is Not Limited to UI Code

I use the same principle in backend code.

If one rule change requires edits across many controllers, jobs, listeners, or repositories, that may also signal that the decision is spread too low in the architecture.

URL formatting, access rules, data normalization, integration parameters, or status decisions often benefit from one clear owner.

Not because everything must be DRY, but because one business or technical reason to change should have a clear home.

What I Check When One Feature Spreads Across the Codebase

In that situation, I ask several questions:

  • Are all these files changing for the same reason?
  • Is the repeated thing a rule rather than just a few lines of syntax?
  • Which component or service should actually own this decision?
  • Can lower layers receive a resolved result instead of raw data that forces them to decide again?
  • Will the next component be easier to add after this refactor?
  • Can the rule be tested in one place?
  • Am I creating a generic abstraction before the real boundary is clear?

A Mass Edit Can Reveal Architecture Better Than a Diagram

I do not treat a large number of changed files as an automatic problem.

But it can reveal where the system has a shared reason to change.

If one behavior forces me to visit dozens of similar components, I may still complete the feature first, but I then check whether the rule itself belongs at a higher level.

A good abstraction is not simply one that reduces the number of lines.

It should reduce the number of places I need to remember the next time the same rule changes.

When one reason to change is scattered across dozens of files, the problem may not be the file count but the place where the decision lives.