avoid-repeated-params

📁 marius-townhouse/effective-typescript-skills 📅 10 days ago
1
总安装量
1
周安装量
#51711
全站排名
安装命令
npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill avoid-repeated-params

Agent 安装分布

mcpjam 1
openhands 1
replit 1
windsurf 1
zencoder 1

Skill 文档

Avoid Repeated Parameters of the Same Type

Overview

Multiple parameters of the same type invite mix-ups.

When a function takes (string, string, string), it’s easy to pass arguments in the wrong order. Use named parameters (objects) to make calls self-documenting.

When to Use This Skill

  • Functions with 2+ parameters of the same type
  • Parameters that are easy to confuse
  • APIs that users will call frequently
  • Refactoring confusing function signatures

The Iron Rule

If parameter order is confusing, use an object.
Names prevent mix-ups better than positions.

Remember:

  • TypeScript can’t detect swapped same-type arguments
  • Position-based APIs require documentation study
  • Object parameters are self-documenting
  • IDE autocomplete works better with named params

Detection: Easy to Confuse

function sendEmail(
  to: string,
  subject: string,
  body: string
) { /* ... */ }

// Which is which?
sendEmail(
  'Hello!',
  'bob@example.com',
  'Welcome to our service'
);
// Whoops! Subject and recipient are swapped

TypeScript sees (string, string, string) – all valid, no error.

Solution: Object Parameter

function sendEmail(params: {
  to: string;
  subject: string;
  body: string;
}) { /* ... */ }

// Now it's clear:
sendEmail({
  to: 'bob@example.com',
  subject: 'Hello!',
  body: 'Welcome to our service'
});

// Wrong order is obvious:
sendEmail({
  to: 'Hello!',  // Clearly wrong
  subject: 'bob@example.com',  // Obviously an email
  body: 'Welcome'
});

When Position-Based is OK

Different Types

// Clear: types are different
function setProperty(obj: object, key: string, value: unknown) { }

// Clear: number vs string
function pad(str: string, length: number) { }

Single Parameter

// Nothing to confuse
function greet(name: string) { }

Well-Known Conventions

// Math functions are universally position-based
function max(a: number, b: number) { }

// Array methods follow established patterns
function slice(arr: any[], start: number, end: number) { }

Destructuring for Clean Implementation

// Clean function signature and implementation
function sendEmail({
  to,
  subject,
  body
}: {
  to: string;
  subject: string;
  body: string;
}) {
  console.log(`Sending to ${to}: ${subject}`);
  // Use to, subject, body directly
}

Optional Parameters with Defaults

interface SendEmailOptions {
  to: string;
  subject: string;
  body: string;
  cc?: string[];
  priority?: 'high' | 'normal' | 'low';
}

function sendEmail({
  to,
  subject,
  body,
  cc = [],
  priority = 'normal'
}: SendEmailOptions) {
  // ...
}

// Call with just required params
sendEmail({ to: 'bob@example.com', subject: 'Hi', body: 'Hello!' });

// Or with optional params
sendEmail({
  to: 'bob@example.com',
  subject: 'Urgent',
  body: 'Please respond',
  priority: 'high'
});

Mixing Required and Optional

// Required first parameter, options object second
function createElement(
  tagName: string,
  options?: {
    className?: string;
    id?: string;
    children?: Element[];
  }
) { /* ... */ }

createElement('div');
createElement('div', { className: 'container', id: 'main' });

Real-World Example: Date Formatting

// Bad: which is format, which is locale?
function formatDate(date: Date, format: string, locale: string): string { }

formatDate(new Date(), 'en-US', 'YYYY-MM-DD');  // Wrong order!

// Good: named parameters
function formatDate(
  date: Date,
  options: { format: string; locale: string }
): string { }

formatDate(new Date(), { format: 'YYYY-MM-DD', locale: 'en-US' });

Rectangle Example

// Bad: easy to confuse width/height, x/y
function drawRect(x: number, y: number, width: number, height: number) { }

// Better: grouped semantically
function drawRect(
  position: { x: number; y: number },
  size: { width: number; height: number }
) { }

// Best: named everything
interface DrawRectOptions {
  x: number;
  y: number;
  width: number;
  height: number;
}
function drawRect(options: DrawRectOptions) { }

Pressure Resistance Protocol

1. “Too Verbose”

Pressure: “Object syntax is longer”

Response: One-time verbosity prevents ongoing confusion.

Action: Use named parameters for clarity; brevity isn’t worth bugs.

2. “It’s a Standard Pattern”

Pressure: “Other libraries use positional params”

Response: You’re not other libraries. Make your API clear.

Action: Design for your users, not convention.

Red Flags – STOP and Reconsider

  • Functions with 2+ parameters of the same type
  • Documentation needed to explain parameter order
  • Tests that verify parameter order
  • Bugs from swapped arguments

Common Rationalizations (All Invalid)

Excuse Reality
“It’s only two parameters” Two strings are still confusable
“IDE shows parameter names” Names shown, not enforced
“We have good documentation” People don’t read docs

Quick Reference

// DON'T: Same-type positional parameters
function fn(a: string, b: string, c: string) { }
fn('x', 'y', 'z');  // What's what?

// DO: Named parameters
function fn(params: { a: string; b: string; c: string }) { }
fn({ a: 'x', b: 'y', c: 'z' });  // Clear!

// OK: Different types
function fn(name: string, count: number) { }

// OK: Single parameter
function fn(message: string) { }

The Bottom Line

Named parameters prevent argument mix-ups.

When functions take multiple parameters of the same type, use an object parameter. The small verbosity cost is repaid many times over in prevented bugs and improved readability.

Reference

Based on “Effective TypeScript” by Dan Vanderkam, Item 38: Avoid Repeated Parameters of the Same Type.