¿Por qué leer este artículo?
uno sobre cómo estructurar tus indicaciones para permitir que tu agente de IA realice magia. Ya existe un mar de artículos que detallan qué estructura usar y cuándo, por lo que no hay necesidad de otra.
En cambio, este artículo es uno de una serie de artículos que tratan sobre cómo mantenerse usted, el codificador, relevante en el ecosistema moderno de codificación de IA.
Se trata de aprender las técnicas que le permitirán sobresalir en el uso de agentes de codificación mejor que aquellos que presionan ciegamente el tabulador o copian y pegan.
Analizaremos los conceptos de las prácticas existentes de ingeniería de software que usted debe conocer y analizaremos por qué estos conceptos son relevantes, particularmente ahora.
Al leer esta serie, debería tener una buena idea de los errores comunes que debe buscar en el código generado automáticamente y saber cómo guiar a un asistente de codificación para crear código de grado de producción que sea mantenible y extensible. Este artículo es más relevante para programadores en ciernes, graduados y profesionales de otras industrias técnicas que desean mejorar su experiencia en codificación.
Lo que cubriremos no sólo le ayudará a utilizar mejor los asistentes de codificación, sino también a ser mejores codificadores en general.
Los conceptos centrales
Los conceptos de alto nivel que cubriremos son los siguientes:
El código huele a patrones de diseño de abstracción
En esencia, no hay nada nuevo en ellos. Para los desarrolladores experimentados, son una segunda naturaleza, inculcada en sus cerebros a través de años de revisiones y depuración de relaciones públicas. Con el tiempo, llegas a un punto en el que reaccionas instintivamente a un código que "siente" como un dolor futuro.
Y ahora, quizás sean más relevantes que nunca, ya que los asistentes de codificación se han convertido en una parte esencial de la experiencia de cualquier desarrollador, ya sea junior o senior.
¿Por qué?
Porque se ha descargado el trabajo manual de escribir código. La responsabilidad principal de cualquier desarrollador ahora ha pasado de escribir código a revisarlo. Todos se han convertido efectivamente en desarrolladores senior que guían a un junior (el asistente de codificación).
Por lo tanto, se ha vuelto esencial que incluso los profesionales de software novatos puedan "revisar" el código. Pero los que prosperarán en la industria actual son aquellos que tienen la visión de un desarrollador senior.
Es por eso que cubriremos los conceptos anteriores para que, como mínimo, puedas decirle a tu asistente de codificación que los tenga en cuenta, incluso si tú mismo no sabes exactamente lo que estás buscando.
Entonces, las presentaciones ya están hechas. Vayamos directamente a nuestro primer tema: el código huele.
El código huele
¿Qué es un olor a código?
Me parece un término muy acertado: es el equivalente a leche con olor agrio, que te indica que es una mala idea beberla.
Durante décadas, los desarrolladores han aprendido mediante prueba y error qué tipo de código funciona a largo plazo. El código "maloliente" es frágil, propenso a errores ocultos y difícil para un humano o un agente de inteligencia artificial entender exactamente lo que está sucediendo.
Por lo tanto, generalmente es muy útil para los desarrolladores conocer los olores del código y cómo detectarlos.
Enlaces útiles para leer más sobre los olores de código:
https://luzkan.github.io/smells
https://refactoring.guru/refactoring/smells
Ahora, después de haber usado agentes de codificación para crear de todo, desde canales de aprendizaje automático profesionales para mi trabajo de 9 a 5 hasta aplicaciones móviles completas en idiomas que nunca antes había tocado para mis proyectos paralelos, he identificado dos "olores" típicos que surgen cuando te vuelves demasiado dependiente de tu asistente de codificación:
Cambio divergente Generalidad especulativa
Repasemos cuáles son, los riesgos involucrados y un ejemplo de cómo solucionarlo.
Cambio divergente
El cambio divergente ocurre cuando un solo módulo o clase hace demasiadas cosas a la vez. El propósito del código se ha "divergido" en muchas direcciones diferentes y, por lo tanto, en lugar de centrarse en ser bueno en una tarea (principio de responsabilidad única), intenta hacerlo todo.
Esto da como resultado una situación dolorosa en la que este código siempre falla y, por lo tanto, requiere reparación por varias razones independientes.
¿Cuándo sucederá con la IA?
Cuando el desarrollador no está comprometido con el código base y acepta ciegamente el resultado del Agente, usted es doblemente susceptible a esto.
Sí, es posible que haya hecho todo lo correcto y haya creado un mensaje bien estructurado que se adhiera a las últimas novedades en ingeniería de mensajes.
Pero, en general, si le pide que "agregue funcionalidad para manejar X", el agente generalmente hará exactamente lo que se le indica e introducirá el código en su clase existente, especialmente cuando el código base existente ya es muy complicado.
En última instancia, depende de usted tener en cuenta la función, la responsabilidad y el uso previsto del código para llegar a un enfoque holístico. De lo contrario, es muy probable que termines con un código maloliente.
Ejemplo: ingeniería de aprendizaje automático
A continuación, tenemos una clase ModelPipeline de la que puede obtener indicios de futuros problemas de extensibilidad.
clase ModelPipeline: def __init__(self, data_path): self.data_path = data_path def load_from_s3(self): print(f"Conectando a S3 para obtener {self.data_path}") return "raw_data" def clean_txn_data(self, data): print("Limpieza del formato JSON de transacción específica") return "cleaned_data" def train_xgboost(self, data): print("Ejecutando XGBoost trainer") return "modelo" Una advertencia rápida:
No podemos hablar en términos absolutos y decir que este código es malo porque sí.
Siempre depende del contexto más amplio de cómo se usa el código. Para una base de código simple cuyo alcance no se espera que crezca, lo siguiente está perfectamente bien.
Tenga en cuenta también:
Es un ejemplo artificial y simple para ilustrar el concepto.
No se moleste en darle esto a un agente para demostrar que puede darse cuenta de que huele mal sin que se lo digan. El punto es que usted reconozca el olor antes de que el agente lo empeore.
Entonces, ¿qué cosas deberían pasar por tu cabeza cuando miras este código?
Recuperación de datos: ¿Qué sucede cuando empezamos a tener más de una fuente de datos, como tablas de Bigquery, bases de datos locales o blobs de Azure? ¿Qué posibilidades hay de que esto suceda? Ingeniería de datos: si los datos ascendentes cambian o el modelado descendente cambia, esto también deberá cambiar. Modelado: si utilizamos diferentes modelos, LightGBM o alguna Neural Net, el modelado ascendente debe cambiar.
Debe notar que al combinar las preocupaciones de plataforma, ingeniería de datos e ingeniería de aprendizaje automático en un solo lugar, hemos triplicado el motivo por el cual se modifica este código, es decir, código que comienza a oler a "cambio divergente".
¿Por qué es este un posible problema?
Riesgo operativo: cada edición corre el riesgo de introducir un error, ya sea humano o de IA. Al hacer que esta clase use tres sombreros diferentes, ha triplicado el riesgo de que esto se rompa, ya que hay tres veces más razones para que este código cambie. Contaminación del contexto del agente de IA: el agente ve el código de limpieza y entrenamiento como parte del mismo problema. Por ejemplo, es más probable cambiar la lógica de entrenamiento y carga de datos para adaptarse a un cambio en la ingeniería de datos, aunque sea innecesario. En última instancia, esto aumenta el olor del código a "cambio divergente". La IA magnifica el riesgo: un agente puede reescribir cientos de líneas de código en un segundo. Si esas líneas representan tres disciplinas diferentes, el agente acaba de triplicar la posibilidad de introducir un error que las pruebas unitarias podrían no detectar.
¿Cómo solucionarlo?
Los riesgos descritos anteriormente deberían brindarle algunas ideas sobre cómo refactorizar este código.
Un posible enfoque es el siguiente:
class S3DataLoader: """Solo maneja problemas de infraestructura.""" def __init__(self, data_path): self.data_path = data_path def load(self): print(f"Conectarse a S3 para obtener {self.data_path}") return "raw_data" class TransactionsCleaner: """Solo maneja problemas de dominio de datos/esquema.""" def clean(self, data): print("Limpieza del formato JSON de transacción específica") return "cleaned_data" class XGBoostTrainer: """Solo maneja inquietudes de aprendizaje automático/investigación.""" def train(self, data): print("Ejecutando XGBoost trainer") return "model" class ModelPipeline: """El orquestador: sabe 'qué' hacer, pero no 'cómo' hacerlo.""" def __init__(self, loader, Cleaner, trainer): self.loader = loader self.cleaner = cleaner self.trainer = entrenador def run(self): datos = self.loader.load() limpiado = self.cleaner.clean(datos) return self.trainer.train(limpiado)
Anteriormente, la responsabilidad del canal de modelos era manejar toda la pila de DS.
Ahora, su responsabilidad es orquestar las diferentes etapas de modelado, mientras que las complejidades de cada etapa se separan claramente en sus respectivas clases.
¿Qué se consigue con esto?
1. Riesgo operativo minimizado: ahora, las preocupaciones están disociadas y las responsabilidades están absolutamente claras. Puede refactorizar su lógica de carga de datos con la confianza de que el código de entrenamiento de ML permanece intacto. Mientras los insumos y los productos (los “contratos”) sigan siendo los mismos, se reduce el riesgo de afectar cualquier cosa posterior.
2. Código comprobable: es mucho más fácil escribir pruebas unitarias ya que el alcance de las pruebas es más pequeño y está bien definido.
3. Flexibilidad de los ladrillos Lego: la arquitectura ahora está abierta a la ampliación. ¿Necesita migrar de S3 a Azure? Simplemente coloque un AzureBlobLoader. ¿Quieres experimentar con LightGBM? Cambia el entrenador.
En última instancia, obtendrá un código que es más confiable, legible y fácil de mantener tanto para usted como para el agente de IA. Si no interviene, es probable que esta clase se vuelva más grande, más amplia y más inestable y acabe siendo una pesadilla operativa.
Generalidad especulativa
Mientras que el "cambio divergente" ocurre con mayor frecuencia en una base de código que ya es grande y complicada, la "generalidad especulativa" parece ocurrir cuando comienzas a crear un nuevo proyecto.
Este olor a código se produce cuando el desarrollador intenta preparar un proyecto para el futuro adivinando cómo se desarrollarán las cosas, lo que resulta en una funcionalidad innecesaria que solo aumenta la complejidad.
Todos hemos estado allí:
"Haré que este proceso de capacitación de modelos admita todo tipo de modelos, validación cruzada y métodos de ajuste de hiperparámetros, y me aseguraré de que haya retroalimentación humana en el circuito para la selección de modelos, de modo que podamos usarlo para toda nuestra capacitación en el futuro".
sólo para descubrir que…
Es un trabajo monstruoso, el código resulta inestable, dedicas demasiado tiempo a él mientras no has podido construir el modelo de clasificación LightGBM simple que necesitabas en primer lugar.
Cuando los agentes de IA son susceptibles a este olor
Descubrí que los agentes codificadores más recientes y de alto rendimiento son los más susceptibles a este olor. Combine un agente poderoso con un mensaje vago y rápidamente terminará con demasiados módulos y cientos de líneas de código nuevo.
Quizás cada línea sea oro puro y sea exactamente lo que necesitas. Cuando experimenté algo como esto recientemente, al principio el código ciertamente me pareció tener sentido.
Pero terminé rechazándolo todo. ¿Por qué?
Porque el agente estaba tomando decisiones de diseño para un futuro que yo ni siquiera había trazado aún. Sentí que estaba perdiendo el control de mi propio código base y que deshacerlo en el futuro sería un verdadero dolor de cabeza si surgiera la necesidad.
El principio clave: haga crecer su base de código de forma orgánica
El mantra que hay que recordar al revisar los resultados de la IA es “YAGNI” (No lo necesitarás). Es un principio en el desarrollo de software que sugiere que sólo se debe implementar el código que necesita, no el código que prevé.
Comience con lo más simple que funcione. Luego, repita sobre ello.
Esta es una forma más natural y orgánica de hacer crecer su base de código que hace las cosas, al mismo tiempo que es ágil, simple y menos susceptible a errores.
Revisando nuestros ejemplos
Anteriormente analizamos la refactorización del Ejemplo 1 (La clase "Hazlo todo") en el Ejemplo 2 (El Orquestador) para demostrar cómo el código original de ModelPipeline apestaba.
Necesitaba ser refactorizado porque estaba sujeto a demasiados cambios por demasiadas razones independientes y, en su estado actual, el código era demasiado frágil para mantenerlo de manera efectiva.
Ejemplo 1
clase ModelPipeline: def __init__(self, data_path): self.data_path = data_path def load_from_s3(self): print(f"Conectando a S3 para obtener {self.data_path}") return "raw_data" def clean_txn_data(self, data): print("Limpieza del formato JSON de transacción específica") return "cleaned_data" def train_xgboost(self, data): print("Ejecutando Entrenador XGBoost") devuelve "modelo"
Ejemplo 2
class S3DataLoader: """Solo maneja problemas de infraestructura.""" def __init__(self, data_path): self.data_path = data_path def load(self): print(f"Conectarse a S3 para obtener {self.data_path}") return "raw_data" class TransactionsCleaner: """Solo maneja problemas de dominio de datos/esquema.""" def clean(self, data): print("Limpieza del formato JSON de transacción específica") return "cleaned_data" class XGBoostTrainer: """Solo maneja inquietudes de aprendizaje automático/investigación.""" def train(self, data): print("Ejecutando XGBoost trainer") return "model" class ModelPipeline: """El orquestador: sabe 'qué' hacer, pero no 'cómo' hacerlo.""" def __init__(self, loader, Cleaner, trainer): self.loader = loader self.cleaner = cleaner self.trainer = entrenador def run(self): datos = self.loader.load() limpiado = self.cleaner.clean(datos) return self.trainer.train(limpiado)
Anteriormente, asumíamos implícitamente que se trataba de un código de grado de producción que estaba sujeto a diversos cambios de mantenimiento o adiciones de funciones que se realizan con frecuencia para dicho código. En tal contexto, el olor a código de 'Cambio Divergente' era relevante.
Pero, ¿qué pasaría si se tratara del código de un nuevo MVP o de I+D de un nuevo producto? ¿Se aplicaría el mismo olor a código de 'Cambio divergente' en este contexto?
En tal escenario, optar por el ejemplo 2 puede ser en realidad la opción más maloliente.
Si el alcance del proyecto es considerar una fuente de datos o un modelo, crear tres clases separadas y un orquestador puede contar como problemas de "resolución previa" que aún no tiene.
Por lo tanto, en situaciones de MVP/I+D en las que se desconocen las consideraciones detalladas de implementación y existen requisitos específicos de datos de entrada/modelo de salida, el ejemplo 1 podría ser más apropiado.
La lección general
Lo que revelan estos dos olores de código es que la ingeniería de software rara vez se trata de código "correcto". Se trata de contexto.
Un agente de codificación puede escribir Python perfecto tanto en función como en sintaxis, pero no conoce todo el contexto empresarial. No sabe si el guión que está escribiendo es un experimento descartable o la columna vertebral de una renovación de la producción multimillonaria.
Compensaciones de eficiencia
Se podría argumentar que simplemente podemos alimentar a la IA con cada pequeño detalle del contexto empresarial, desde las reuniones que ha tenido hasta las charlas a la hora del té que tuvo con un colega. Pero en la práctica, eso no es escalable.
Si tiene que pasar media hora escribiendo una “nota contextual” sólo para obtener una función limpia de 50 líneas, ¿realmente ha ganado eficiencia? ¿O simplemente ha transformado el trabajo manual de escribir código en el de escribir indicaciones?
¿Qué te diferencia del resto?
En la era de la IA, su valor como científico de datos ha cambiado fundamentalmente. Ahora se ha eliminado el trabajo manual de escribir código. Los agentes se encargarán de la repetición, el formateo y las pruebas unitarias.
Por lo tanto, para diferenciarse de los demás científicos de datos que copian y pegan código a ciegas, es necesario tener la intuición estructural para guiar a un agente de codificación en una dirección que sea relevante para su situación particular. Esto da como resultado una mejor confiabilidad, rendimiento y resultados que se reflejan en usted y lo hacen destacar.
Pero para lograr esto, es necesario desarrollar esta intuición que surge de años de experiencia al conocer los olores del código que hemos discutido y los otros dos conceptos (patrones de diseño, abstracción) en los que profundizaremos en artículos posteriores.
Y, en última instancia, poder hacer esto de manera efectiva le brinda más espacio para concentrarse en la resolución del problema y diseñar una solución para el problema, es decir, la verdadera "diversión" de la ciencia de datos.
Artículos relacionados
Si le gustó este artículo, consulte mi serie Conceptos de ingeniería de software para científicos de datos, donde ampliamos los conceptos más relevantes para los científicos de datos.