Los desarrolladores usan LLMs para código pero obtienen resultados inconsistentes.
Situación típica 1 — Código incompleto: pedís "crea una función para validar emails" y recibís una función básica sin manejo de errores, sin type hints, con regex obsoleta, sin considerar casos edge como emails con "+".
Situación típica 2 — Refactoring destructivo: pedís "mejora este código" y el LLM cambia la API pública, elimina funcionalidad que "parecía innecesaria", e introduce dependencias nuevas no solicitadas.
Situación típica 3 — Tests superficiales: pedís "genera tests para esta función" y recibís 2-3 tests del happy path, cero tests de casos edge, cero tests de errores.
Situación típica 4 — Debug por adivinanza: pegás 200 líneas de código con "esto no funciona, arreglalo" y el LLM hace cambios aleatorios sin entender el problema real.
Por qué es importante: sin prompts específicos para código pasás más tiempo corrigiendo que si lo hubieras escrito vos, introducís bugs sutiles que el LLM no menciona, el código generado no sigue los estándares de tu proyecto, y no aprendés del proceso porque no entendés los cambios.
Elementos de un prompt de generación efectivo: contexto técnico (lenguaje/versión, framework, dependencias permitidas, estándares del proyecto), requisito funcional (qué debe hacer, inputs, outputs), requisitos no funcionales (manejo de errores, seguridad, rendimiento), y formato de respuesta.
// Ejemplo real: función que valide y normalice números de teléfono españoles a formato E.164, con manejo de errores explícito (ValueError si no tiene 9 dígitos, si tiene caracteres no numéricos), restricción de no usar librerías como `phonenumbers`, y formato de respuesta con 3 casos de test inline.
El refactoring requiere ser MUY específico sobre qué mejorar y qué NO tocar.
| Objetivo | Cómo pedirlo |
|---|---|
| Legibilidad | "Mejora la legibilidad: nombres descriptivos, extrae funciones auxiliares, elimina código duplicado" |
| Rendimiento | "Optimiza rendimiento: identifica el cuello de botella y proponé mejora. Explica el trade-off." |
| Patrones | "Aplica el patrón [Strategy/Factory/etc.] para [objetivo específico]" |
| Modernización | "Actualiza a sintaxis moderna de [lenguaje versión]: usa [feature específica]" |
| Simplificación | "Reduce complejidad ciclomática: divide funciones >20 líneas, elimina anidación >3 niveles" |
// Ejemplo real: refactorizar una función `process_order` con anidación profunda de if/else y variables de una letra (o, t, i, d), manteniendo el nombre público, los tipos de parámetro/retorno y el comportamiento exacto — solo extrayendo lógica repetida, renombrando variables, y reduciendo anidación a 2 niveles.
| Tipo | Qué especificar |
|---|---|
| Unit tests | Función específica, casos edge, mocks necesarios |
| Integration tests | Componentes a integrar, estado inicial/final, cleanup |
| Property-based | Propiedades invariantes, rangos de inputs |
| Regression tests | Bug específico que debe quedar cubierto |
// Ejemplo real: tests pytest para `validate_email`, con edge cases explícitos (subdominio, "+", números, dominios largos como .museum), errores explícitos (sin @, sin dominio, espacios, múltiples @), y tipos inválidos (None, número, lista como input) — usando pytest.mark.parametrize.
El debugging efectivo requiere contexto estructurado: el error (mensaje exacto, cuándo ocurre, stack trace), el código (solo lo relevante, no 500 líneas), el contexto (qué debería hacer vs qué hace en realidad, qué cambió), y tus hipótesis (qué creés que puede estar mal, qué ya probaste).
// Ejemplo real: TypeError al hacer unpacking de `get_user_data(user_id)` cuando el usuario no existe — la función tiene un `return` implícito de `None` cuando el `if user:` es falso (falta el `else` con un manejo explícito). El contexto incluye qué ya se probó (verificar que el user_id es un UUID válido, print confirmando que retorna None).
| Tipo | Especificar |
|---|---|
| Docstrings | Estilo (Google, NumPy, Sphinx), incluir ejemplos o no |
| README | Secciones requeridas, audiencia, nivel de detalle |
| API docs | Formato (OpenAPI, markdown), ejemplos de request/response |
| Guías | Audiencia (junior/senior), paso a paso vs conceptual |
| Comentarios inline | Cuándo añadir (solo lógica compleja vs todo) |
La plantilla de generación pide "3 casos de test inline con assert al final" como parte del formato de respuesta. Eso es un smoke test rápido para confirmar que el código anda mientras lo generás — no reemplaza la suite de tests real de la técnica 3 (con edge cases, errores y tipos inválidos explícitos). Si tratás esos asserts inline como "ya está testeado", te vas a llevar sorpresas: son una primera señal, no cobertura.
| Tarea | Técnica clave |
|---|---|
| Función nueva desde cero | Generación: contexto + requisitos + restricciones |
| Mejorar código existente sin cambiar comportamiento | Refactoring: objetivo específico + lista de NO cambiar |
| Código funciona pero sin tests | Testing: cobertura explícita + casos edge + errores |
| Error que no entiendo | Debugging: error + contexto + hipótesis + qué ya probaste |
| Código funciona pero sin docs | Documentación: estilo + audiencia + qué incluir |
| Código legacy que debo entender | Combinación: primero pedí explicación, luego docs |
Objetivo: aplicar las 5 técnicas en un mini-proyecto coherente — vas a construir la misma función a lo largo de todo el ejercicio.
Contexto inicial: usá Claude.ai, ChatGPT, o cualquier LLM.
Pedí una función format_bytes(bytes_size: int) -> str en Python 3.11+, sin dependencias externas, que convierta cantidades de bytes a formato legible humano (ej: 1024 → "1.0 KB", 1048576 → "1.0 MB"), con manejo de errores (ValueError si es negativo, TypeError si no es int), usando divisiones de 1024 y máximo 1 decimal. Guardá el código generado — lo vas a reutilizar en los pasos siguientes.
Con el código del paso 1, pedí tests pytest cubriendo: happy path (bytes pequeños, KB, MB, GB, TB), edge cases (0 bytes, exactamente 1024, números muy grandes, límites entre unidades como 1023 vs 1024), y errores (negativo, string, None, float como input). Verificá que los tests cubren todos los casos que pediste.
Pedí refactorizar la función para que sea más extensible (fácil agregar nuevas unidades) y legible, sin modificar el nombre, los parámetros, el tipo de retorno, ni el comportamiento — extrayendo las unidades a una constante y usando patrones más pythónicos.
Copiá esta versión con un bug intencional (a la lista de unidades le falta 'PB'):
Armá el prompt de debugging con el síntoma real: para el input 1125899906842624 (1 PB), la función retorna "1024.0 TB" en lugar de "1.0 PB". Preguntá: ¿por qué ocurre?, ¿cuál es la solución?, ¿qué test debería agregarse para evitar regresiones?
Con el código corregido (unidades incluyendo 'PB'), pedí docstring estilo Google (descripción, args, returns, raises, 3 examples variados), comentarios inline solo donde la lógica no sea obvia, y un bloque final explicando cuándo usar la función, limitaciones conocidas, y alternativas — audiencia: desarrolladores junior recién incorporados al equipo.
| Paso | Verificación |
|---|---|
| Generación | ¿El código funciona con los ejemplos dados? |
| Testing | ¿Hay al menos 10 tests cubriendo happy path, edge y errores? |
| Refactoring | ¿El código es más limpio sin cambiar el comportamiento? |
| Debugging | ¿Identificó correctamente que falta 'PB' en la lista? |
| Documentación | ¿Un junior podría usar la función solo leyendo el docstring? |
| Si ves esto... | Causa | Solución |
|---|---|---|
| Código generado sin manejo de errores | No especificaste excepciones | Añadí sección "# Manejo de errores" con casos específicos |
| Refactoring cambia la API pública | No restringiste qué NO cambiar | Lista explícita: "NO modificar: nombre, parámetros, retorno" |
| Tests solo del happy path | No listaste casos edge | Incluí lista exhaustiva: "Edge cases: [caso 1], [caso 2]..." |
| Debug no encuentra el problema | Poco contexto del error | Incluí: mensaje exacto, inputs que fallan, qué ya probaste |
| Documentación demasiado genérica | No especificaste audiencia | Definí: "Audiencia: [tipo], asumir que [saben/no saben] [concepto]" |
| Código mezcla español e inglés | No definiste idioma | Especificá: "Variables y comentarios en [inglés/español]" |
Concepto central: cada tipo de tarea de código requiere información diferente. Generación necesita requisitos completos, refactoring necesita restricciones de qué NO cambiar, testing necesita cobertura explícita, debugging necesita contexto del error, y documentación necesita audiencia definida.
Para recordar: