Escrever código está mudando, mas a qualidade deveria cair?
Entenda, não estou aqui para criticar o código gerado pelas LLMs, afinal, ela provavelmente escreve muito melhor que eu, desde que receba as instruções corretas.
E é justamente aí que mora o problema. Nessa nova era do desenvolvimento, onde todo mundo está fascinado com o que a IA consegue fazer, pouca gente está prestando atenção no que está pedindo pra ela. Quando você pede algo genérico, a IA entrega algo genérico. Funciona? Funciona. Mas sem querer, você pode estar gerando um backlog de débito técnico silencioso na sua base de código.
Design patterns
Lembram quando os maias precisavam estudar design patterns para resolver problemas de arquitetura e falar a mesma língua que o resto do time? Pois é, estudar patterns continua importante, mas o motivo principal mudou. Não é mais só sobre comunicação entre devs, embora isso ainda importe. Agora, patterns funcionam como instruções de alta precisão para a IA. São a diferença entre receber código que "funciona" e código que é extensível, testável e que não vai te assombrar em seis meses.
Exemplo
Imagine que você vai criar um sistema de processamento de pagamentos. Você tem cartão, boleto e PIX. Cada tipo de pagamento tem regras diferentes, embora no final todos tenham a mesma finalidade, cobrar o cliente. Para isso, a gente poderia optar pelo padrão Strategy.
Agora vamos pensar em como um dev sem esse conhecimento pediria isso pra IA.
Ele descreve o que precisa, e só:
"Crie um serviço em NestJS que processa pagamentos via cartão de crédito,
boleto e PIX."
A IA vai entregar algo funcional, provavelmente um PaymentService com um método cheio de condicionais. Vai funcionar? Vai. Mas pensando em manutenção futura é horrível, difícil de testar.
Código da service gerado pela IA (Sonnet 4.6 no Claude Code):
import { BadRequestException, Injectable } from "@nestjs/common";
import { ProcessPaymentDto } from "./dto/process-payment.dto";
@Injectable()
export class PaymentsService {
process(dto: ProcessPaymentDto) {
switch (dto.method) {
case "credit_card":
return this.processCreditCard(dto.amount, dto.description);
case "boleto":
return this.processBoleto(dto.amount, dto.description);
case "pix":
return this.processPix(dto.amount, dto.description);
default:
throw new BadRequestException(
`Método de pagamento inválido: ${(dto as any).method}`,
);
}
}
private processCreditCard(amount: number, description?: string) {
return {
method: "credit_card",
status: "approved",
amount,
description,
authorizationCode: this.generateCode(),
processedAt: new Date().toISOString(),
};
}
private processBoleto(amount: number, description?: string) {
const dueDate = new Date();
dueDate.setDate(dueDate.getDate() + 3);
return {
method: "boleto",
status: "pending",
amount,
description,
barcode: this.generateBarcode(),
dueDate: dueDate.toISOString(),
processedAt: new Date().toISOString(),
};
}
private processPix(amount: number, description?: string) {
return {
method: "pix",
status: "approved",
amount,
description,
txid: this.generateCode(),
pixKey: "pagamentos@exemplo.com",
processedAt: new Date().toISOString(),
};
}
private generateCode(): string {
return Math.random().toString(36).substring(2, 14).toUpperCase();
}
private generateBarcode(): string {
return Array.from({ length: 44 }, () =>
Math.floor(Math.random() * 10),
).join("");
}
}
Agora pensa comigo num prompt feito por alguém que sabe o que quer, e sabe dizer como quer:
"Crie um serviço de processamento de pagamento em NestJS, usando o pattern Strategy.
Defina uma interface PaymentStrategy com um método process(). Implemente uma classe
concreta para cada meio de pagamento (cartão, boleto, PIX).
Use o módulo de injeção de dependência do NestJS para resolver a estratégia correta
em tempo de execução."
A diferença aqui fica clara. Não estamos falando apenas de ter citado o nome do padrão. Estamos falando de passar pra IA uma planta arquitetural. Ela sabe exatamente o que você espera.
O resultado? O output gerado vai ser extensível: precisa adicionar outra forma de pagamento como Apple Pay ou Stripe? É só criar uma nova classe. Nenhum outro método é afetado, o que reduz drasticamente a chance de efeito colateral. E cada meio de pagamento pode ser testado de forma isolada, com seu próprio teste unitário.
import { BadRequestException, Inject, Injectable } from "@nestjs/common";
import { PaymentStrategy } from "./interfaces/payment-strategy.interface";
import { CreatePaymentDto } from "./dto/create-payment.dto";
export const PAYMENT_STRATEGIES = "PAYMENT_STRATEGIES";
@Injectable()
export class PaymentsService {
constructor(
@Inject(PAYMENT_STRATEGIES)
private readonly strategies: PaymentStrategy[],
) {}
process(dto: CreatePaymentDto) {
const strategy = this.strategies.find((s) => s.method === dto.method);
if (!strategy) {
throw new BadRequestException(
`Método de pagamento inválido: ${dto.method}`,
);
}
return strategy.process(dto.amount);
}
}
Interface da nossa Strategy:
export interface PaymentResult {
transactionId: string;
method: string;
amount: number;
status: string;
}
export interface PaymentStrategy {
readonly method: string;
process(amount: number): PaymentResult;
}
Estratégias concretas:
BoletoStrategy:
import { Injectable } from "@nestjs/common";
import {
PaymentResult,
PaymentStrategy,
} from "../interfaces/payment-strategy.interface";
@Injectable()
export class BoletoStrategy implements PaymentStrategy {
readonly method = "boleto";
process(amount: number): PaymentResult {
return {
transactionId: `BOL-${Math.floor(Math.random() * 10000)}`,
method: this.method,
amount,
status: "pending",
};
}
}
CreditCardStrategy:
import { Injectable } from "@nestjs/common";
import {
PaymentResult,
PaymentStrategy,
} from "../interfaces/payment-strategy.interface";
@Injectable()
export class CreditCardStrategy implements PaymentStrategy {
readonly method = "credit_card";
process(amount: number): PaymentResult {
return {
transactionId: `CC-${Math.floor(Math.random() * 10000)}`,
method: this.method,
amount,
status: "approved",
};
}
}
PixStrategy:
import { Injectable } from "@nestjs/common";
import {
PaymentResult,
PaymentStrategy,
} from "../interfaces/payment-strategy.interface";
@Injectable()
export class PixStrategy implements PaymentStrategy {
readonly method = "pix";
process(amount: number): PaymentResult {
return {
transactionId: `PIX-${Math.floor(Math.random() * 10000)}`,
method: this.method,
amount,
status: "approved",
};
}
}
Lembrando que isso aqui é um código de exemplo, não vai gerar pix, fiquem tranquilos.
Cenas dos próximos capítulos
Esse é apenas o começo. Nos próximos posts dessa série, vou explorar outros design patterns e repetir o mesmo exercício: prompt genérico vs. prompt com pattern, lado a lado. O objetivo é sempre o mesmo, te provocar a escrever prompts melhores, que geram código que não vai virar uma bola de neve nos seus projetos.
A ideia é simples: mostrar que na era do desenvolvimento assistido por IA, saber o que pedir é tão importante quanto saber codar.
Mais em: https://ojeremias.dev/
Top comments (0)