Skip to content
aipatterns.dev

Refactoring techniqueSimplifying Method Calls

Hide Method

Reduce a method's visibility to private or protected when nothing outside the class needs it, shrinking the public surface.

Problem

A method is public, but only the class itself (or its subclasses) calls it. Other code may start depending on it, which ties the class's internals to outside callers and makes later changes risky.

Solution

Make the method private, or protected if subclasses need it. Use the narrowest visibility the language offers that still compiles.

Before

TypeScript
export class PasswordResetService {
  constructor(private tokens: TokenStore, private mail: Mailer) {}

  async requestReset(email: string): Promise<void> {
    const token = this.generateToken();
    await this.tokens.save(email, this.hashToken(token), Date.now() + 15 * 60_000);
    await this.mail.send(email, "reset", { link: `https://app.example/reset?t=${token}` });
  }

  generateToken(): string {
    return crypto.randomBytes(32).toString("base64url");
  }

  hashToken(token: string): string {
    return crypto.createHash("sha256").update(token).digest("hex");
  }
}

After

TypeScript
export class PasswordResetService {
  constructor(private tokens: TokenStore, private mail: Mailer) {}

  async requestReset(email: string): Promise<void> {
    const token = this.generateToken();
    await this.tokens.save(email, this.hashToken(token), Date.now() + 15 * 60_000);
    await this.mail.send(email, "reset", { link: `https://app.example/reset?t=${token}` });
  }

  private generateToken(): string {
    return crypto.randomBytes(32).toString("base64url");
  }

  private hashToken(token: string): string {
    return crypto.createHash("sha256").update(token).digest("hex");
  }
}

Why

  • A smaller public interface is easier to learn and harder to misuse.
  • You can change or delete private methods without checking other modules.
  • For security-related helpers, hiding prevents callers from building tokens outside the reset flow.

Steps

  1. Search for every call to the method outside its class, including tests, other packages and string-based calls.
  2. If there are none, change the visibility to private. Run the compiler and tests.
  3. If only subclasses call it, use protected.
  4. If tests call it directly, test through the public method instead, or move the logic into its own small class that is public and tested.
  5. Repeat periodically. Methods often become hideable after other refactorings remove their external callers.

Watch out

  • TypeScript private is compile-time only; JavaScript callers and as any still reach it. Use #private fields when runtime privacy matters.
  • Do not hide methods that are part of a published library API without a deprecation cycle.
  • Do not hide a method just to stop a test from calling it, if that leaves important logic untested. Extract it to a separate unit instead.
  • Interface implementations and framework hooks (lifecycle methods, decorators) must stay public.
  • The inverse is making the method public, which has no named technique; do it only for a real external caller.

Agent checklist

  • No caller outside the class (or its subclasses) exists, including in other packages and string lookups.
  • The method is not part of an interface, framework hook or published API.
  • Tests exercise the logic through public behavior.
  • Runtime privacy (#) was used where it matters for security.

Treats these smells

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.