A class with six static factory methods and a private constructor, where five of the factories did the same thing with different argument names.
// what it was
final class DateRange
{
private function __construct(private DateTimeImmutable $from, ...) {}
public static function between($a, $b): self { /* ... */ }
public static function from($a, $b): self { /* ... */ }
public static function of($a, $b): self { /* ... */ }
public static function create($a, $b): self { /* ... */ }
// and two that genuinely differ
public static function month(int $y, int $m): self { /* ... */ }
public static function today(): self { /* ... */ }
}
Named constructors earn their place when they encode a distinct way of building the thing — month() and today() do, and the four synonyms do not. The rule I settled on is that a factory must either take different arguments or apply a different rule; if it only renames new, it is an alias, and an alias in a public API is a thing every future reader has to check is not subtly different.