Claude Code en TypeScript: 3 smells frecuentes
Claude Code genera código TypeScript que compila, se ve limpio y pasa el lint. También esconde tres smells que un revisor humano habría marcado en la primera pasada. Los veo repetirse en PRs que paso por el agente, y los veo también en mi propio código cuando acepto una sugerencia sin leer con cuidado. No son errores exclusivos de la IA; la IA solo los produce con más consistencia porque el patrón visual es correcto aunque el patrón de tipos sea mentira.
El estudio de SniffTSX sobre code smells en proyectos React+TypeScript reales encontró que los más frecuentes son el uso de Any Type y de Non-Null Assertion. Agrego un tercero (Type Assertion con as) porque Claude Code lo usa cada vez que una respuesta de API necesita un tipo “rápido”. Vamos uno por uno con el reemplazo que uso en producción.
Smell 1: el any que apaga el chequeo
function processData(data: any) {
return data.items.map((item: any) => item.name.toUpperCase());
}
Esto compila. También explota si data.items llega como undefined o si item.name es null. Claude Code tiende a producir any cuando el prompt no especifica la forma del dato, porque es la salida mínima que hace feliz al compilador.
El reemplazo es unknown más un type guard. unknown obliga a validar antes de usar; any deja pasar todo.
interface DataResponse {
items: Array<{ name: string }>;
}
function isDataResponse(data: unknown): data is DataResponse {
return (
typeof data === 'object' && data !== null &&
'items' in data && Array.isArray((data as { items: unknown }).items)
);
}
function processData(data: unknown): string[] {
if (!isDataResponse(data)) {
throw new Error('Invalid data format');
}
return data.items.map(item => item.name.toUpperCase());
}
Vale notar que el type guard sigue usando un as interno, pero contenido en una función que afirma la forma explícitamente. Esa es la diferencia entre un as responsable y un as que miente a todo el resto del archivo.
Smell 2: el ! que promete lo que no puede

const user = users.find(u => u.id === id)!;
console.log(user.name);
const element = document.getElementById('app')!;
element.innerHTML = 'Hello';
Non-Null Assertion (!) le dice al compilador “créeme, esto no es null”. En runtime, si lo es, la aplicación se cae con Cannot read property 'name' of undefined. Claude Code pone ! cuando el ejemplo que vio en su entrenamiento también tenía ! ahí; es un patrón visual replicado sin la garantía que lo justifica.
El reemplazo es un chequeo explícito. TypeScript hace el estrechamiento de tipo solo.
const user = users.find(u => u.id === id);
if (!user) {
throw new Error(`User ${id} not found`);
}
console.log(user.name);
const element = document.getElementById('app');
if (!element) {
throw new Error('App element not found');
}
element.innerHTML = 'Hello';
El cambio son tres líneas por caso. A cambio, obtienes un mensaje de error con contexto en vez de un stack trace críptico. Vale la pena en cualquier código que llegue a producción.
Smell 3: el as que inventa un contrato
const response = await fetch('/api/users');
const users = (await response.json()) as User[];
Esto es el smell más peligroso porque Claude Code lo escribe mucho y porque falla silencioso. Si el backend cambia su respuesta y manda { error: 'unauthorized' }, el as User[] convence a TypeScript de que recibiste un array. La iteración posterior falla con un mensaje incomprensible, lejos del lugar del contrato roto.
El reemplazo es validar en runtime. Yo uso Zod porque el schema sirve también como fuente de verdad del tipo.
import { z } from 'zod';
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
});
const UsersResponseSchema = z.array(UserSchema);
const response = await fetch('/api/users');
const data = await response.json();
const users = UsersResponseSchema.parse(data);
Si la respuesta no cumple el schema, parse lanza una excepción con el camino exacto que falló (users[0].email: Invalid email). Zod 4 además agrega z.prettifyError para formatear el error en un string legible para el registro, y safeParse para el flujo donde no quieres throw. El schema vale 13 líneas y protege el límite entre tu código y una API que no controlas.
Lo que recomiendo en el CLAUDE.md
Después de encontrar los tres smells en varios PRs, recomiendo agregar al CLAUDE.md del proyecto estas cuatro líneas para bajar la frecuencia de estos patrones:
## TypeScript rules
- Never use `any`. Use `unknown` + type guard.
- Never use Non-Null Assertion `!`. Use explicit null check with early return.
- Never use Type Assertion `as` on API responses. Validate at runtime (Zod).
- Keep `strict: true` and `noUncheckedIndexedAccess: true` in tsconfig.
El cambio no es mágico; Claude Code seguirá proponiendo any de vez en cuando. Pero debería bajar la frecuencia lo suficiente para que la revisión manual deje de sentirse repetitiva. También recomiendo activar estas reglas en tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true
}
}
noUncheckedIndexedAccess solo ya elimina una categoría entera de bugs: te obliga a verificar que array[0] no sea undefined antes de usarlo. En proyectos existentes rompe mucho código al activarlo; en proyectos nuevos vale la primera semana de ajuste.
El punto que falta en los tres
Los tres smells comparten la misma causa raíz: el compilador quedó convencido de algo que el runtime no garantiza. any quita la pregunta, ! la responde sin evidencia, as inventa la respuesta. En los tres casos el fix es el mismo: no mentirle al tipo. Validar en el límite donde entra el dato, confiar adentro.
Claude Code es rápido escribiendo TypeScript. Si lo revisas con estas tres categorías en mente, la mayoría de las correcciones son mecánicas y toman menos tiempo que escribir el código desde cero. La parte que no automatizo es el momento de leer el diff antes de aceptarlo. Esa es la revisión que separa un agente útil de uno que llena el codebase de bombas que explotan en producción.
ken imoto · WebRTC & Voice AI engineer · kenimoto.dev
Libro relacionado Practical Claude Code Claude Code desde cero hasta producción — CLAUDE.md, Plan Mode y workflows de equipo, desde un año de uso real Ver la página del libro → ¿Te resultó útil este artículo?