Skip to content
aipatterns.dev

Code smellOther Smells

Incomplete Library Class

A library type lacks a method you need and you cannot edit it, so helper logic for it is scattered across your code.

Signs

  • The same few lines operating on a library type appear in several modules: computing the start of a week from a Date, or splitting a URL query.
  • A utils file grows functions whose first parameter is always one library type.
  • Code works around a missing library method with manual field access.
  • Upgrading the library would mean finding every copy of that workaround.

Why it happens

  • Library authors design for the common case and cannot predict every caller's need.
  • The library is third-party, generated or shared across teams, so you cannot add the method upstream.
  • Each developer who hit the gap solved it inline, without checking whether someone already had.

Example

Several reports need "next business day" from the built-in Date.

before.ts
// billing.ts
function dueDate(issued: Date): Date {
  const d = new Date(issued);
  d.setDate(d.getDate() + 1);
  while (d.getDay() === 0 || d.getDay() === 6) d.setDate(d.getDate() + 1);
  return d;
}

// shipping.ts
function dispatchDate(ordered: Date): Date {
  const d = new Date(ordered.getTime());
  do {
    d.setDate(d.getDate() + 1);
  } while ([0, 6].includes(d.getDay()));
  return d;
}

Two copies of the same logic, written slightly differently.

A foreign method puts the logic in one function that takes the library type as its first argument:

dates.ts
export function nextBusinessDay(from: Date): Date {
  const d = new Date(from.getTime());
  do {
    d.setDate(d.getDate() + 1);
  } while (d.getDay() === 0 || d.getDay() === 6);
  return d;
}

When many such helpers accumulate, a local extension groups them in one type:

business-date.ts
export class BusinessDate {
  constructor(private readonly value: Date) {}

  next(): BusinessDate {
    return new BusinessDate(nextBusinessDay(this.value));
  }

  isWeekend(): boolean {
    const day = this.value.getDay();
    return day === 0 || day === 6;
  }

  toDate(): Date {
    return new Date(this.value.getTime());
  }
}

Treatment

  • If you need one or two extra operations, use Introduce Foreign Method: a function in your code that takes the library object as its first argument.
  • If you need many operations, use Introduce Local Extension: a wrapper class or subclass that adds them.
  • Prefer a wrapper over a subclass when the library type is final, sealed, or created by the library itself.
  • If the gap is a bug or an obvious omission, also open an issue or a pull request upstream, and keep your local fix until it lands.

Payoff

  • One implementation of each missing operation, with one set of tests.
  • Library upgrades touch one file.
  • Call sites read as domain intent, such as nextBusinessDay(d), instead of loops over date fields.

When to ignore

  • The missing operation is a one-off call on the library's own type, where an extension method reads naturally. In languages that have them - C# extension methods, Kotlin extension functions, Swift extensions, Rust traits - write it there; it is the idiomatic form of a foreign method.
  • Monkey-patching the library prototype (JavaScript, Ruby) solves the same problem but leaks to every consumer and breaks on upgrade. Do not use it for this smell.
  • The workaround appears once. A single inline helper is fine until a second caller needs it.
  • A maintained library already provides the operation, such as a date library. Check the dependencies you already have before writing your own.

Agent checklist

  • Search the codebase for existing helpers on the same library type before adding one.
  • Check whether an installed dependency already offers the operation.
  • Choose a foreign function for one or two operations and a wrapper for many.
  • Never modify the library's prototype or vendored source.
  • Replace every duplicated inline workaround with the new helper and run the tests.

Treatments

See also

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