Integrando la IA en los IDEs

Integrating AI in IDEs

La IA ha demostrado su inmenso potencial en tareas de programación, como responder preguntas, generar fragmentos de código, explicar código y detectar problemas. Sin embargo, el enfoque actual de utilizar interfaces de usuario web como ChatGPT y depender en gran medida de métodos de copiar/pegar puede ser engorroso y menos eficiente. Para maximizar los beneficios de la IA en la programación, es esencial una integración más estrecha de las capacidades de IA dentro de los entornos de desarrollo integrados (IDE) modernos. Se pueden explorar varias ideas para lograr esta integración perfecta, algunas de las cuales ya se han implementado en IDE como IntelliJ IDEA, mientras que otras son oportunidades sin explotar.

Existen dos conceptos principales de esta integración: Acciones de IA y Asistente de IA (interfaz de usuario de chat).

Acciones de IA

Las acciones de IA son tareas que aprovechan la IA para lograr resultados deseados. Cada acción de IA debe:

  1. Preparar una solicitud y un contexto, involucrar a la IA para procesar los datos, recopilar los resultados y presentarlos al usuario de la forma más conveniente o realizar cambios en el código.
  2. Preparar una solicitud y un contexto, llamar al Asistente de IA y permitir que el usuario complete el trabajo.
  3. Realizar una combinación complicada de los escenarios 1 y 2.

Las acciones de IA se pueden llevar a cabo de diferentes formas:

  • Desencadenadas por solicitudes del usuario (por ejemplo, como refactorizaciones)
  • Iniciadas por servicios del sistema según un horario, en segundo plano o cuando sea apropiado (por ejemplo, acciones de análisis de código)

Intenciones de código de IA

En el mundo de los IDE modernos, una característica poderosa que se ha vuelto cada vez más común es el concepto de “Intenciones”. Estas Intenciones ofrecen a los usuarios la capacidad de ejecutar acciones predefinidas en su código en función del contexto específico. Imagina, por ejemplo, trabajar con una declaración “for” y tener varias Intenciones disponibles dentro de ese contexto, como “Convertir a while”, “Invertir el orden” o “Usar Streams”, entre otras. El uso de Intenciones resulta increíblemente ventajoso, ya que permite refactorizaciones rápidas y manipulación de código, agilizando el proceso de desarrollo.

Sin embargo, desarrollar Intenciones no es una tarea fácil, por lo que los proveedores de IDE las codifican y las incluyen en sus respectivos IDE. Si bien algunos IDE permiten a los desarrolladores crear sus propias Intenciones, este esfuerzo es bastante desafiante y requiere un conocimiento profundo de la plataforma del IDE, una codificación extensa y la creación de complementos para implementar las nuevas Intenciones desarrolladas.

Afortunadamente, con la llegada de modelos de lenguaje basados en IA como ChatGPT, solicitar acciones relacionadas con el código se ha vuelto significativamente más sencillo. Simplemente proporcionando un fragmento de código y una solicitud, el modelo de lenguaje basado en IA puede ejecutar fácilmente la acción deseada. Por ejemplo, si proporcionas un bucle al modelo con la solicitud “Invertir dirección de un bucle”, realizará fácilmente la transformación especificada en el fragmento de código dado.

Y obtendrás el mismo resultado que con la Intención codificada:

Por lo tanto, es bastante natural que podamos introducir Intenciones de IA, que básicamente son solicitudes de IA con nombre vinculadas a algún contexto. El contexto puede ser un archivo, una clase, una declaración, un método en particular, etc. Por lo tanto, una Intención de IA debe proporcionar una solicitud, solicitar al IDE un contexto, involucrar a la IA para procesar los datos, recopilar los resultados y presentarlos al usuario de la forma más conveniente o realizar cambios en el código.

Ventajas de las Intenciones de IA:

  1. Casi cualquier refactorización se puede hacer utilizando lenguaje natural. Las solicitudes pueden ser tan simples como “Reemplazar usando formato” para API conocidas (como PrintStream.println(String) de Java) o pueden ser más complejas con más instrucciones para API menos conocidas.
  2. No es necesario codificar estas Intenciones
  3. No es necesario escribir complementos para distribuir estas Intenciones
  4. Estas Intenciones se pueden configurar fácilmente por el usuario.
  5. No es necesario utilizar una interfaz de chat, especialmente para acciones repetitivas
  6. Menor tráfico de tokens del modelo de lenguaje basado en IA para ahorrar costos

Definir Intenciones de Código de IA con Anotaciones

Una característica emocionante e innovadora a considerar es la integración de intenciones de IA directamente en la API mediante anotaciones. Al introducir intenciones de IA declarativas, los desarrolladores pueden instruir al IDE sobre las intenciones disponibles para cada clase, campo o método y especificar las solicitudes apropiadas para ejecutar intenciones específicas a través de la asistencia de un modelo de lenguaje como ChatGPT. Estas intenciones declarativas de IA pueden ser proporcionadas por los autores de frameworks o bibliotecas y estar disponibles de manera transparente para todos los desarrolladores que utilicen IDE que las admitan.

Como ejemplo ilustrativo, echemos un vistazo a la Intención de IA “Reemplazar usando formato”, que permite a los desarrolladores reemplazar las llamadas println(String) por llamadas más eficientes printls() que toman como entrada un formato y una lista de argumentos:

Por lo tanto, aplicando estas Intenciones a una llamada:

System.out.println("i = " + i);

Resultados:

System.out.printf("i = %d%n", i);

Si el IDE puede proporcionar vistas renderizadas para algunos elementos de texto en el editor, una Intención de IA Anotada puede ser renderizada en el código solo con el título, ocultando la extensa indicación. Además, esa renderización puede contener el botón Play integrado que permite realizar esa acción con un solo clic.

Acción para Solucionar Deprecaciones

Otro uso muy bueno para las Intenciones de IA declarativas es tratar con API obsoletas. Por lo tanto, cada método obsoleto puede incluir anotaciones que definen Intenciones de IA especiales para permitir refactorizar ese método a una versión moderna. Estas intenciones de IA pueden ser invocadas explícitamente mientras se edita o se navega por el código. O puede haber otra acción de nivel superior que recopile todos los métodos obsoletos y debidamente anotados y solicite al desarrollador corregir algunos o todos ellos.

Los beneficios de utilizar Intenciones de IA declarativas para manejar API obsoletas son numerosos. Reduce significativamente el tiempo y el esfuerzo requeridos para mantener y actualizar el código heredado, fomentando una transición más fluida a las últimas tecnologías y mejores prácticas. Además, mejora la colaboración entre los desarrolladores al proporcionar un enfoque unificado para administrar los métodos obsoletos en todo el código base.

Acción TODO

En muchos casos, los comentarios TODO (como // TODO:) proporcionan suficientes instrucciones para que LLM genere correctamente el código necesario para completar esos TODO. Por ejemplo, el siguiente código:

será refactorizado correctamente por LLM usando la indicación “Completar TODO en el siguiente código” de la siguiente manera:

Por lo tanto, es bastante natural recopilar los comentarios TODO y ofrecerlos en la lista de Acciones de IA a realizar. Los TODO a nivel de clase deben ofrecerse en cualquier lugar de la clase, los TODO a nivel de método deben ofrecerse en el ámbito del método, etc. Ciertamente, algunos/muchos TODO no se pueden completar solo con LLM y indicaciones genéricas, pero dependerá de cada desarrollador decidir si invocarlos o no.

Si el IDE puede proporcionar vistas renderizadas para algunos elementos de texto en el editor, un TODO puede ser renderizado en el código con un botón integrado (como Play) que permita completar ese TODO con un solo clic.

Acción Crear Método

Sería bueno permitir que la IA genere la firma del método utilizando el nombre del método escrito y adivinando el tipo de retorno y los parámetros. Por ejemplo, una indicación “Proponer firma para un método (con cuerpo vacío): splitStringUsingRegex” produce bastante correctamente el siguiente método:

Opcionalmente, también se incluiría la generación del cuerpo del método.

La acción “Crear Método” debería ser invocada en el editor de código escribiendo el nombre del método y presionando la tecla Tab (u otro atajo adecuado) o seleccionando explícitamente la acción “Crear Método”.

Acción Sugerir Dependencias

LLM es experto en generar código basado en los requisitos de los usuarios. Sin embargo, el código generado a menudo depende de bibliotecas o frameworks de terceros. Esto puede causar errores de compilación si las dependencias necesarias aún no se han agregado al proyecto. En consecuencia, encontrar y agregar manualmente las dependencias necesarias puede llevar más tiempo que obtener el propio código.

Para abordar este problema, una adición útil sería una acción “Sugerir Dependencias”. Esta acción permitiría a los usuarios solicitar a LLM (o a un modelo específicamente entrenado) información sobre ciertos tipos de dependencias, como bibliotecas, Maven, Gradle, etc. Si se encuentran las dependencias, se pueden aplicar automáticamente al proyecto, agilizando el proceso y ahorrando tiempo.

Por ejemplo, al solicitar a LLM que encuentre la dependencia Maven “org.json.JSONObject”, podríamos obtener la siguiente sugerencia:

Esto se puede usar para modificar las dependencias de un proyecto.

Acción Sugerir Nombre

Existen muchas refactorizaciones de código existentes que introducen nuevos métodos de clase, parámetros o variables como “Extraer Método”, “Introducir Parámetro”, etc. Todas las cosas nuevas creadas durante estas refactorizaciones deben tener un nombre adecuado. Por lo tanto, es bastante natural utilizar LLM para encontrar posibles nombres sugeridos basados en el código que se está refactorizando y mostrar esos nombres en un cuadro emergente. La recuperación de nombres lleva tiempo, por lo que no debería ser intrusiva y, por lo tanto, se debe realizar en segundo plano.

Sugerir Acción de Implementación

Esta acción permitiría generar la implementación para los métodos que tienen un cuerpo vacío. LLM puede ser bastante preciso si el nombre del método y sus argumentos definen claramente (como deberían) el propósito del método. Por ejemplo, para el siguiente método vacío:

LLM sugiere correctamente su cuerpo como:

Esta acción debe realizarse en segundo plano y el resultado debe aplicarse en el editor de código sin mostrar ninguna interfaz adicional y sin detener al desarrollador para que siga trabajando con el IDE.

Probablemente, debería haber una opción para lanzar la acción “Sugerir Implementación” en la interfaz de chat para tener más control si es necesario.

Sugerir Acción Regex

La acción “Sugerir Regex” debería estar disponible para todas las literales de cadena y debería tomar una literal de cadena y usarla para consultar a la IA por una expresión regular. Luego, la literal de cadena debería ser reemplazada por una expresión regular generada.

Tal vez esta acción debería estar disponible solo para literales de cadena que comienzan con “Regex”.

Por ejemplo, después de invocar la acción “Sugerir regex” para una literal de cadena en el siguiente código:

El código se cambiaría para incluir una expresión regular generada:

Explicar Acción de Código

La acción “Explicar Código” debería utilizar LLM con una pregunta adecuada de “explicar código” para obtener una descripción de lo que hace el código. De forma predeterminada, esta explicación se mostrará en un cuadro emergente con la capacidad de cambiar entre las versiones corta y completa de la explicación. Además, debería haber una opción para lanzar la acción “Explicar Código” en la interfaz de chat para tener más control si es necesario.

Comentar Acción de Código

La acción “Comentar Código” debería utilizar LLM con una especie de pregunta de “Explicar código” e insertar una breve explicación de código como un comentario justo al lado del código. Esta acción debe realizarse en segundo plano y el resultado debe aplicarse en el editor de código sin mostrar ninguna interfaz adicional y sin detener al desarrollador para que siga trabajando con el código.

Explicar Acción de Cadena

Esta acción debería estar disponible para literales de cadena y debería mostrar una explicación en un cuadro emergente. Debería haber una opción para abrir esta explicación en la interfaz de chat si se necesitan explicaciones o pruebas adicionales.

LLMs son bastante buenos para reconocer lo que está incluido en la cadena, por lo que esta acción funcionará con una pregunta bastante básica. Por ejemplo, LLMs reconocen fácilmente:

  • Expresiones regulares más o menos complejas
  • Sentencias SQL
  • Cadenas de formato de printf
  • Cadenas de formato de MessageFormat
  • Etc.

Cuando LLM no puede detectar el formato de la cadena, debería haber una pregunta para describir el formato.

Buscar Acción de Problemas Potenciales

La mayoría de los IDE modernos incluyen herramientas que pueden detectar y resaltar todos los errores y advertencias de código encontrados por un compilador y por herramientas de análisis de código específicas del IDE.

Sin embargo, LLMs pueden proporcionar alguna detección adicional de problemas potenciales, especialmente en algunas situaciones muy complicadas. Con una pregunta adecuada, LLM puede producir informes de problemas estructurados en forma de texto o en formato JSON. Por lo tanto, analizar y utilizar ese informe sería bastante fácil.

Por lo tanto, la acción “Buscar Problemas Potenciales” puede utilizar LLM con una pregunta adecuada de “Encontrar problemas en el código” y contexto, analizar los resultados y presentarlos en la vista habitual de lista de mensajes, proporcionando una experiencia de usuario común con navegación de código, etc.

Sugerir Acción de Refactorización

La acción “Sugerir Refactorización” debería utilizar LLM para obtener una versión optimizada, corregida o mejorada de tu código.

De forma predeterminada, esta acción debe realizarse en segundo plano y el resultado debe aplicarse en el editor de código sin mostrar ninguna interfaz adicional intrusiva. Debería haber una opción para abrir esta refactorización en la interfaz de chat si se necesitan pasos adicionales, explicaciones o pruebas.

Si el IDE puede proporcionar vistas renderizadas para algunos elementos de texto en el editor, un método refactorizado se puede renderizar temporalmente con botones Prev/Next incrustados a la derecha que permiten cambiar rápidamente las variantes de código refactorizado (si las hay).

Sugerir Acción de Mensaje de Confirmación

La acción “Suggest Commit Message” debería utilizar LLM para componer un mensaje de confirmación de VCS basado en una lista curada de cambios realizados en el proyecto. Debería haber una opción para cambiar entre diferentes estilos de mensajes como listas monolíticas o itemizadas.

Acción Generar Documentación

La acción “Generate Documentation” debería utilizar LLM para componer documentación para desarrolladores de métodos y clases en un formato específico del lenguaje, como JavaDoc.

Asistente de IA

El Asistente de IA proporciona una interfaz de chat universal similar a la ofrecida por ChatGPT, para que los desarrolladores puedan interactuar con LLM, obtener respuestas y código, y enviar los resultados si así lo desean.

El Asistente de IA debería realizar un seguimiento del contexto del chat y ser consciente de dónde se encuentra el punto de edición. Esto nos permite:

  • Generar código basado en el código existente (si el tamaño de la ventana de contexto lo permite).
  • Verificar y modificar el código a insertar para producir resultados compilables válidos. Por ejemplo:
    • Si el Asistente de IA produce un método, debería insertarse correctamente a nivel de la clase, ignorando la posición del cursor o colocándolo en el lugar válido más cercano.
    • Si el Asistente de IA produce un fragmento de código y el punto de inserción no está dentro del método, se puede envolver en un método con el nombre sugerido por la IA.
    • Etc.
  • Avoidar la generación de una clase y un método main() hasta que se solicite explícitamente.
  • Verificar los nombres para evitar la producción de elementos con nombres duplicados.
  • Reutilizar métodos existentes sin necesidad de generar duplicados (quizás con una confirmación).

Debería haber un área de juegos incorporada directamente en el Asistente de IA. De esta manera, el código generado se puede probar y depurar en el lugar sin necesidad de pegarlo de nuevo, agregar el método main(), construir un proyecto, etc. Se puede hacer tanto en el propio Asistente de IA como en una ventana/vista relacionada. Cualquier dependencia faltante pero requerida debería resolverse automáticamente (consulte la acción “Suggest Dependencies” para obtener más detalles).

Nota: Sería bueno evitar el uso del Asistente de IA en la mayoría de los casos en los que se puedan obtener resultados suficientemente buenos mediante una única transacción de LLM utilizando una indicación adecuadamente diseñada. El Asistente de IA debería utilizarse solo cuando el usuario lo invoque explícitamente o para acciones que REQUIERAN varias transacciones con la participación directa del usuario para obtener los resultados deseados y sea difícil diseñar una interfaz de usuario dedicada.