Kaique Mitsuo Silva Yamamoto
Arquitetura softwareFrontend webTypeScriptClean Code

DRY, KISS e YAGNI

Três lembretes que evitam os maiores crimes contra a legibilidade — Don't Repeat Yourself, Keep It Simple, You Aren't Gonna Need It.

DRY, KISS e YAGNI

São três princípios simples que servem como lembretes diários. Não são regras absolutas — são balanças. Quando em dúvida, eles te puxam para o lado da simplicidade.


DRY — Don't Repeat Yourself

"Cada pedaço de conhecimento deve ter uma representação única, autoritária e não ambígua no sistema." — The Pragmatic Programmer (1999)

O que é

Se você copiou e colou código mais de uma vez, provavelmente deveria extrair uma função, componente ou utilitário. Duplicação significa que uma mudança precisa ser feita em vários lugares — e esquecer um deles gera bugs.

Frontend — Componentes duplicados

// ❌ DRY violado — mesmo JSX copiado em 3 páginas
// pages/products.tsx
<div className="card">
  <img src={product.image} alt={product.name} />
  <h3>{product.name}</h3>
  <p>{formatCurrency(product.price)}</p>
  <button onClick={() => addToCart(product)}>Comprar</button>
</div>

// pages/favorites.tsx
<div className="card">
  <img src={product.image} alt={product.name} />
  <h3>{product.name}</h3>
  <p>{formatCurrency(product.price)}</p>
  <button onClick={() => addToCart(product)}>Comprar</button>
</div>

// pages/search.tsx — MESMO CÓDIGO de novo
<div className="card">
  <img src={product.image} alt={product.name} />
  <h3>{product.name}</h3>
  <p>{formatCurrency(product.price)}</p>
  <button onClick={() => addToCart(product)}>Comprar</button>
</div>
// ✅ Componente extraído — uma única fonte de verdade
function ProductCard({ product, onAddToCart }: ProductCardProps) {
  return (
    <div className="card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>{formatCurrency(product.price)}</p>
      <button onClick={() => onAddToCart(product)}>Comprar</button>
    </div>
  );
}

// pages/products.tsx — usa o componente
<ProductCard product={product} onAddToCart={addToCart} />

Backend — Validação duplicada

// ❌ DRY violado — mesma validação em 3 rotas
// routes/users.ts
if (!req.body.email || !req.body.email.includes('@')) {
  return res.status(400).json({ error: 'Email inválido' });
}

// routes/orders.ts
if (!req.body.email || !req.body.email.includes('@')) {
  return res.status(400).json({ error: 'Email inválido' });
}

// routes/subscriptions.ts
if (!req.body.email || !req.body.email.includes('@')) {
  return res.status(400).json({ error: 'Email inválido' });
}
// ✅ Validação extraída — uma única fonte de verdade
// validators/email.validator.ts
function validateEmail(email: unknown): Result<string, ValidationError> {
  if (typeof email !== 'string' || !email.includes('@')) {
    return { ok: false, error: new ValidationError('Email inválido', 'email') };
  }
  return { ok: true, value: email };
}

// routes/users.ts
const emailResult = validateEmail(req.body.email);
if (!emailResult.ok) return res.status(400).json({ error: emailResult.error.message });

Quando DRY não se aplica

DRY tem um limite: não confunda duplicação com coincidência. Se dois trechos parecem iguais agora mas têm razões diferentes para mudar, não una-os.

// ❌ Falso DRY — "formatar nome de usuário" e "formatar nome de produto"
// Parecem a mesma coisa, mas regras de negócio são diferentes
function formatName(name: string) {
  return name.trim().toLowerCase().replace(/\b\w/g, c => c.toUpperCase());
}

// Usuário: "da Silva" → "Da Silva" (preposição minúscula)
// Produto: "da Silva" → "Da Silva" (tudo capitalizado)

// ✅ Separado — cada um tem sua regra
function formatUserName(name: string) { /* regras para nomes */ }
function formatProductName(name: string) { /* regras para produtos */ }

KISS — Keep It Simple, Stupid

"A simplicidade deve ser um objetivo de design, não um subproduto." — Kelly Johnson (Engenheiro da Lockheed Martin, 1960)

O que é

Se a solução parece complexa, provavelmente existe uma mais simples. Código simples é mais fácil de ler, testar e manter.

Frontend — Hook complexo vs simples

// ❌ Over-engineered — genérico demais para o caso de uso
function useGenericDataFetcher<T, E, F>(
  url: string,
  options: FetchOptions<T, E, F>,
  transformers: TransformerChain<T, E, F>,
  validators: ValidatorSet<T>,
  errorHandler: ErrorHandler<E>,
  cacheStrategy: CacheStrategy<T>,
) {
  // 200 linhas de lógica genérica que ninguém entende
}

// Chamada — precisa de 30 linhas de configuração
useGenericDataFetcher<User, ApiError, UserDTO>(
  '/api/users/1',
  { method: 'GET', headers: { /* ... */ } },
  { toDTO: transformUserToDTO, fromDTO: transformDTOToUser },
  { schema: userSchema, strict: true },
  { fallback: defaultErrorHandler, retry: 3 },
  { ttl: 3600, staleWhileRevalidate: true },
);
// ✅ Simples — faz o que precisa, nada mais
function useUser(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
  });
}

// Chamada — 1 linha
const { data: user, isLoading } = useUser('1');

Backend — Controller complexo vs simples

// ❌ Over-engineered — 50 linhas para retornar um JSON
class UserController {
  async getUser(req: Request, res: Response) {
    const { id } = req.params;
    const validator = new UserValidator();
    const validation = await validator.validateGetRequest(req);
    if (!validation.isValid) {
      const errorResponse = ResponseFactory.createError(400, validation.errors);
      Logger.logError('UserController.getUser', validation.errors);
      return res.status(400).json(errorResponse);
    }
    const sanitizedId = Sanitizer.sanitizeString(id);
    const service = ServiceFactory.createUserService();
    const result = await service.getUserById(sanitizedId);
    if (result.isLeft()) {
      const mappedError = ErrorMapper.map(result.value);
      Logger.logError('UserController.getUser', mappedError);
      return res.status(mappedError.statusCode).json(mappedError);
    }
    const dto = DTOFactory.createUserDTO(result.value);
    const response = ResponseFactory.createSuccess(dto);
    Logger.logInfo('UserController.getUser', { id: sanitizedId });
    return res.status(200).json(response);
  }
}
// ✅ Simples — claro, testável, curto
class UserController {
  async getUser(req: Request, res: Response) {
    const user = await this.userService.findById(req.params.id);

    if (!user) {
      return res.status(404).json({ error: 'Usuário não encontrado' });
    }

    return res.json(user);
  }
}

YAGNI — You Aren't Gonna Need It

"Sempre implemente as coisas quando você realmente precisa delas, nunca quando apenas prevê que vai precisar." — Extreme Programming

O que é

Não adicione funcionalidade "por via das dúvidas". Se o requisito não existe hoje, não implemente. Código que não é usado é código que precisa ser mantido, testado e compreendido — sem nenhum benefício.

Frontend — Sistema de temas sem necessidade

// ❌ YAGNI violado — ninguém pediu dark mode, tema customizável ou i18n
interface ThemeConfig {
  mode: 'light' | 'dark' | 'auto' | 'custom';
  colors: {
    primary: string;
    secondary: string;
    accent: string;
    background: string;
    surface: string;
    error: string;
    warning: string;
    info: string;
    success: string;
    text: {
      primary: string;
      secondary: string;
      disabled: string;
    };
  };
  typography: { /* 20 propriedades */ };
  spacing: { /* 10 propriedades */ };
  breakpoints: { /* 5 propriedades */ };
  shadows: { /* 6 níveis */ };
  borders: { /* ... */ };
}

// 500 linhas de config de tema que ninguém usa
// O cliente quer apenas: fundo branco, texto preto, botão azul
// ✅ YAGNI respeito — faz o que o cliente pediu
// styles.css — simples, funciona
// Se dark mode for necessário no futuro, aí sim implementa

Backend — Sistema de plugins sem necessidade

// ❌ YAGNI violado — ninguém pediu sistema de plugins
interface Plugin {
  name: string;
  version: string;
  hooks: {
    beforeRequest: (req: Request) => Request;
    afterResponse: (res: Response) => Response;
    onError: (error: Error) => void;
  };
  config: Record<string, unknown>;
}

class PluginManager {
  private plugins: Map<string, Plugin> = new Map();

  register(plugin: Plugin): void { /* ... */ }
  unregister(name: string): void { /* ... */ }
  getPlugin(name: string): Plugin | undefined { /* ... */ }
  runHook(hook: string, data: any): any { /* ... */ }
  getMiddleware(): Middleware { /* ... */ }
  // 200 linhas de sistema de plugins...
}

// O sistema tem 3 meses e 0 plugins instalados
// ✅ YAGNI respeito — quando precisar de middleware, adiciona diretamente
app.use(cors());
app.use(helmet());
app.use(rateLimit());
// Pronto. Se um dia precisar de plugins, aí implementa.

Os três juntos na prática

// ❌ Viola os 3 princípios
// DRY: validação duplicada em cada rota
// KISS: lógica desnecessariamente complexa
// YAGNI: sistema de cache genérico que ninguém usa
class DataProcessor {
  private cache = new LRUCache<string, any>({ max: 1000, ttl: 3600000 });
  private validator = new ComplexValidator();
  private transformer = new GenericTransformer();

  async process(data: any) {
    const cacheKey = this.generateCacheKey(data);
    const cached = await this.cache.get(cacheKey);
    if (cached) return cached;

    const validated = await this.validator.validate(data, { deep: true, strict: true });
    const transformed = await this.transformer.transform(validated, {
      normalize: true,
      deduplicate: true,
      enrich: true,
    });

    await this.cache.set(cacheKey, transformed);
    return transformed;
  }
}
// ✅ Respeita os 3
// DRY: validação extraída em função reutilizável
// KISS: faz o que precisa, sem genéricos
// YAGNI: sem cache que ninguém pediu
function validateOrder(data: unknown): Result<Order, ValidationError> {
  if (!data || typeof data !== 'object') {
    return { ok: false, error: new ValidationError('Dados inválidos') };
  }
  if (!('productId' in data) || typeof data.productId !== 'string') {
    return { ok: false, error: new ValidationError('productId é obrigatório') };
  }
  if (!('quantity' in data) || typeof data.quantity !== 'number' || data.quantity < 1) {
    return { ok: false, error: new ValidationError('quantity deve ser >= 1') };
  }
  return { ok: true, value: data as Order };
}

async function createOrder(data: unknown) {
  const result = validateOrder(data);
  if (!result.ok) return result;

  return ordersRepository.create(result.value);
}

Referências

On this page