Github Copilot como compañero de programación

may. 1, 2024

En el artículo de hoy vamos a hablar sobre Github Copilot, un asistente de programación por Inteligencia Artificial entrenado específicamente para ello. Los productos Copilot son una colaboración entre OpenAI y Microsoft, existen dos productos con el mismo nombre actualmente, Microsoft Copilot que es el integrado en la ya más que conocida suite de ofimática de la grande tecnológica, Office, y Github Copilot, desarrollado y entrenado por la filial de Microsoft, Github, pensada específicamente como herramienta de apoyo durante el proceso de desarrollo de software.

Github Copilot, el que en esta ocasión nos atañe, se nos presenta en formato de extensión para los editores de código más populares, yo utilizo a diario la suite de Jetbrains pero también está disponible para Visual Studio Code, aunque de forma un poco más limitada. Por las pruebas que he podido hacer tanto el auto completado como las sugerencias proporcionadas en Code son más pobres que en IntelliJ, por lo que recomendaría este último para la mejor experiencia posible.

La herramienta hace uso del algoritmo de ChatGPT en su versión 4 y ha sido entrenado con el código fuente de Github por lo que su potencial para programar es único. Es capaz de autocompletar código, sugerir líneas de código, escribir funciones completas y explicarnos lo que hace un trozo de código entre otras muchas cosas. Además, es capaz de leer y entender el contexto del código en el que estamos trabajando, por lo que las sugerencias que nos haga serán más acertadas y útiles, sin tener que proporcionárselo en cada momento como tendríamos que hacer con ChatGPT si lo usamos directamente.

Introducción al uso de Github Copilot

El precio de Github Copilot es de 9.99$ al mes, pero si eres estudiante o profesor, puedes obtenerlo de forma gratuita. Personalmente podría decirte que merece la pena por el hecho de tener el chat integrado con el contexto del código y un auto completado mejorado respecto al del propio IDE. Hay que tener en cuenta que está en fase beta y es habitual que en determinadas ocasiones no funcione como esperamos y se comporte de forma extraña llegando a repetir la misma respuesta errónea una y otra vez, entre otras taras.

Utilizaremos la propia tienda de nuestro IDE para instalar la extensión Copilot, utilizaremos Android Studio para nuestro ejemplo que se encuentra disponible de forma gratuita y nos permite probar la extensión sin necesidad de licencia de Jetbrains. Simplemente tenemos que buscarla en el marketplace de extensiones e instalarla, una vez terminado, nos pedirá que iniciemos sesión con nuestra cuenta de Github, y listo, ya podemos empezar a utilizarlo.

Observaremos ahora nuevas pestañas en nuestro IDE, una con el texto Github Copilot Chat en la barra lateral derecha y el icono de Github en la barra inferior. Con el primero accederemos directamente al chat integrado y el icono de Github nos permitirá abrir la extensión de Github Copilot para gestionar nuestra sesión y configuración además de habilitar o deshabilitar ciertas características.

Chat integrado

Ahora estamos listos para empezar a crear nuestro código para ello vamos a crear un pequeño programa de ejemplo. Servirá para seleccionar un nombre a partir de una lista de nombres creada por el usuario a través de un formulario. Lo construiremos utilizando Flutter pero esta vez su versión para Linux, Mac OS y Windows. Para ello le escribiré la siguiente descripción del proyecto al Chat de Copilot:

Estamos desarrollando una aplicación de sorteo de nombres basada en escritorio utilizando Flutter para aprovechar sus capacidades multiplataforma en Windows, macOS y Linux. La aplicación contará con una interfaz amigable para el usuario donde los participantes podrán ingresar nombres manualmente a través de una entrada de texto. Esta configuración facilitará la entrada y gestión de datos de manera sencilla. La funcionalidad principal se centrará en un botón de ' Iniciar Sorteo’, que, al activarse, iniciará una animación visualmente atractiva que recorrerá los nombres de manera aleatoria antes de seleccionar un ganador. Esta animación será construida utilizando las poderosas capacidades de la interfaz de usuario de Flutter para garantizar una experiencia de usuario dinámica y fluida. La aplicación estará diseñada para manejar un volumen significativo de entradas de manera eficiente, manteniendo un alto rendimiento en todas las plataformas de escritorio. Nuestro objetivo es crear una aplicación intuitiva y atractiva que pueda ser fácilmente accesible y utilizada en diferentes sistemas operativos, mejorando el alcance y la usabilidad de la herramienta.

Un prompt que no tiene nada de especial, simplemente le estoy describiendo con palabras lo que quiero hacer y cómo para que Copilot me proporcione una estructura de proyecto básica para luego continuar iterando sobre ella e ir así dando forma a la aplicación final. Mencionar que comunicarse en Inglés con Copilot (al igual que con ChatGPT) es más efectivo, aunque no es estrictamente necesario, nos entiende y ayuda perfectamente en español.

Copilot nos guiará en el proceso de creación del proyecto y configuración inicial, tan fácil como ejecutar el comando flutter create name_raffle en la terminal, siguiendo los pasos indicados por la respuesta de Copilot. Tras su creación, accedemos a él desde el propio IDE y dentro de la carpeta lib encontraremos el fichero main.dart, que es el punto de entrada de nuestra aplicación.

Podemos sustituir el contenido del fichero main.dart por el siguiente código sugerido por Copilot:


    import 'package:flutter/material.dart';
    
    void main() {
      runApp(NameRaffleApp());
    }
    
    class NameRaffleApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Name Raffle',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: NameRaffleHomePage(title: 'Name Raffle Home Page'),
        );
      }
    }
    
    class NameRaffleHomePage extends StatefulWidget {
      NameRaffleHomePage({Key? key, required this.title}) : super(key: key);
    
      final String title;
    
      @override
      _NameRaffleHomePageState createState() => _NameRaffleHomePageState();
    }
    
    class _NameRaffleHomePageState extends State<NameRaffleHomePage> {
      // TODO: Implement state and methods for loading names and starting the raffle
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                TextField(
                  // Implement text field for name input
                  
    
                ),
                ElevatedButton(
                  child: Text('Start Raffle'),
                  onPressed: () {
                    // TODO: Implement starting the raffle
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    

Ahora, para ejecutarlo y verlo, le preguntamos de nuevo a Copilot cómo podemos hacer para ejecutar en nuestro sistema operativo el proyecto que acabamos de crear. Éste nos dice que utilizando el comando flutter config --enable-linux-desktop podemos habilitar la compilación para Linux, estando ubicados en la carpeta raíz de nuestro proyecto. Las respuestas no siempre son las mismas, puede que a ti te guíe por otro camino. En la documentación oficial puedes consultar los requisitos tanto de hardware como de librerías en el sistema para poder desarrollar aplicaciones de Flutter para Linux si lo necesitas.

Una vez habilitada la compilación para Linux, podremos ejecutar el comando flutter run -d linux en la terminal y veremos cómo se abre la aplicación en una nueva ventana. Si todo ha ido bien, deberíamos ver algo como esto:

Como decíamos antes, las respuestas no son siempre las mismas por tanto el proceso puede variar, pero lo que aquí, nos interesa analizar el proceso de desarrollo utilizando la herramienta más que el resultado en sí. Para empezar hemos utilizado el Chat integrado en el IDE, funciona similar a ChatGPT sólo que tiene contexto del código que con el que estamos trabajando, mientras que si utilizásemos ChatGPT tendríamos que proporcionárselo, qué estamos haciendo, qué queremos hacer, en qué punto estamos, el código fuente relevante, etc. Ahora vamos a decirle que reorganice y añada algunos componentes que nos harían falta.

Vamos a reorganizar los elementos de esta página principal del sorteo y añadir los que nos faltan. Debemos mantener un espacio vacío para mostrar el nombre seleccionado cuando el sorteo se haya completado; nos falta un botón para añadir el nombre introducido por el usuario a la derecha del campo de texto. También necesitamos añadir la función de almacenar los nombres en una lista.

En mi caso me sugiere la siguiente modificación:


    class NameRaffleHomePage extends StatefulWidget {
      NameRaffleHomePage({Key? key, required this.title}) : super(key: key);
    
      final String title;
    
      @override
      _NameRaffleHomePageState createState() => _NameRaffleHomePageState();
    }
    
    class _NameRaffleHomePageState extends State<NameRaffleHomePage> {
      List<String> names = [];
      String winner = '';
      final nameController = TextEditingController();
    
      void addName() {
        setState(() {
          names.add(nameController.text);
          nameController.clear();
        });
      }
    
      void startRaffle() {
        if (names.isEmpty) {
          return;
        }
    
        final random = Random();
        final index = random.nextInt(names.length);
    
        setState(() {
          winner = names[index];
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  winner,
                  style: Theme.of(context).textTheme.headlineMedium,
                ),              
                Row(
                  children: <Widget>[
                    Expanded(
                      child: TextField(
                        controller: nameController,
                        decoration: InputDecoration(
                          border: OutlineInputBorder(),
                          labelText: 'Enter a name',
                        ),
                      ),
                    ),
                    ElevatedButton(
                      child: Text('Add'),
                      onPressed: addName,
                    ),
                  ],
                ),
                ElevatedButton(
                  child: Text('Start Raffle'),
                  onPressed: startRaffle,
                ),
              ],
            ),
          ),
        );
      }
    }
    

Lo que ha hecho ha sido crear la lista que almacenará los nombres introducidos por el usuario, un botón para añadir a la lista el nombre introducido y poco más. Sólo nos faltaría pedirle que añada un campo para mostrar los nombres almacenados en la lista.

Ahora necesitamos añadir un nuevo campo para mostrar la lista de nombres añadidos en tiempo real. Debemos tener cuidado con listas muy grandes, por lo que deberíamos usar un componente desplazable.

A lo que Copilot nos sugiere añadir un Widget Expanded para mostrar la lista de nombres debajo del botón para iniciar el sorteo.


  Expanded(
    child: ListView.builder(
      itemCount: names.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(names[index]),
        );
      },
    ),
  ),
        

De esta manera, podemos ver las palabras que vamos añadiendo antes de realizar el sorteo de nombres definitivo. Lo realizado hasta ahora está en crudo, sin diseño alguno ni validaciones, para este ejemplo es suficiente. Mencionar que en temas de diseño tanto Copilot como el propio ChatGPT están muy limitados y no suelen ofrecer buenas soluciones a no ser que tú le indiques exactamente lo que quieres y específiques profundamente cómo.

Más allá del Chat integrado en el IDE, Copilot integra otras funcionalidades como la posibilidad de pedirle tareas específicas y auto completado con ciertas mejoras con respecto al del propio IDE, aunque a veces las soluciones ofrecidas puedan no ser las más adecuadas. Veamos un ejemplo de cada una de ellas.

Tarea específica

Ahora vamos a ver cómo podemos pedirle a Copilot que nos ayude con una tarea específica, en este caso solicitaremos ayuda para implementar la funcionalidad encargada de gestionar la lista de nombres introducidos por el usuario. Basta con crear un comentario explicando la tarea que queremos que nos ayude a completar para que Copilot haga su trabajo. Por ejemplo le podemos pedir que nos cree un método para leer nombres de un fichero de texto.

Una función para obtener los nombres a partir de un fichero de texto

De esta manera Copilot sabrá qué queremos hacer exactamente en esa posición y nos sugerirá una aproximación que podremos dar por válida y aplicar a nuestro código utlizando la tecla Tab, o descartarlo si no nos convence y buscar otra forma de pedírselo. Este fue el código sugerido:


  // A function to load names from a file
  void loadNames() {
    final file = File('names.txt');
    if (!file.existsSync()) {
      return;
    }

    final lines = file.readAsLinesSync();
    setState(() {
      names = lines;
    });
  }
    

Recordemos que no siempre crea el código correcto ni el que mejor se ajuste a nuestra casuística por lo que será necesario revisarlo y adaptarlo a nuestras necesidades, ya sea reiterando con Copilot o haciéndolo manualmente. Otra de las funcionalidades, probablemente de las más conocidas, es el auto completado, pero en este caso con ciertas mejoras.

Auto completado de código mejorado

Esta funcionalidad la suelen tener integrada por defecto la gran mayoría de IDE más utilizados, pero gracias a la “inteligencia” de Copilot recibiremos sugerencias avanzadas. Vamos a crear un método para limpiar la lista de nombres pero esta vez empezaremos a escribir el nombre método y dejaremos que sea Copilot quién lo complete.


  void clearNames() {
    setState(() {
      names.clear();
    });
  }
  

Podemos aprovechar para crear también el botón correspondiente encargado de limpiar la lista de nombres, simplemente empezando a escribir parte de la palabra “ElevatedButton”, Copilot detectará que hemos creado un nuevo método para limpiar los nombres y automáticamente nos sugerirá un botón para hacerlo. Hay casos en los que ni siquiera es necesario escribir nada, basta con poner el cursor en la posición deseada, al lado de los botones en nuestro caso, para que Copilot ya sepa que vamos a añadir el botón correspondiente al método que acabamos de crear, la ilustración de abajo lo demuestra.

Sí, hay veces que incluso parece cómo si te leyera la mente y es que el simple hecho de tener el contexto de la aplicación integrado en el propio Copilot hace que las sugerencias sean mucho más acertadas y útiles, muchas veces incluso sugiriendo lo que teníamos pensado hacer y en la misma forma, con la única diferencia es que de esta manera basta con pulsar la tecla Tab en vez de tener que escribir el código completo.

Reenfoque de planteamiento

No sólo para crear código sino que muchas veces nos puede ayudar también a reestructurar nuestras ideas o atajar enfoques de formas diferentes si estamos atascados o no nos convence la aproximación actual; por ejemplo, podemos pedirle, un poco a lo bruto, que nos ayude a reenfocar el planteamiento de la aplicación que estamos desarrollando.

Me gustaría reenfocar la aproximación de este ejemplo para hacerlo de otra forma diferente

Tras realizar la solicitud en el chat nos sugiere crear una clase Raffle encargada de gestionar tanto los nombres como el estado del sorteo y el ganador.


    import 'package:flutter/material.dart';
    import 'dart:io';
    import 'dart:math';

    class Raffle {
      List<String> names = [];
      String winner = '';
    
      void loadNames(String filePath) {
        final file = File(filePath);
        if (!file.existsSync()) {
          return;
        }
    
        final lines = file.readAsLinesSync();
        names = lines;
      }
    
      void addName(String name) {
        names.add(name);
      }
    
      void startRaffle() {
        if (names.isEmpty) {
          return;
        }
    
        final random = Random();
        final index = random.nextInt(names.length);
        winner = names[index];
      }
    }
    
    void main() {
      runApp(NameRaffleApp());
    }
    
    class NameRaffleApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Name Raffle',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: NameRaffleHomePage(title: 'Name Raffle Home Page'),
        );
      }
    }
    
    class NameRaffleHomePage extends StatefulWidget {
      NameRaffleHomePage({Key? key, required this.title}) : super(key: key);
    
      final String title;
    
      @override
      _NameRaffleHomePageState createState() => _NameRaffleHomePageState();
    }
    
    class _NameRaffleHomePageState extends State<NameRaffleHomePage> {
      final raffle = Raffle();
      final nameController = TextEditingController();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  raffle.winner,
                  style: Theme.of(context).textTheme.headlineMedium,
                ),
                Row(
                  children: <Widget>[
                    Expanded(
                      child: TextField(
                        controller: nameController,
                        decoration: InputDecoration(
                          border: OutlineInputBorder(),
                          labelText: 'Enter a name',
                        ),
                      ),
                    ),
                    ElevatedButton(
                      child: Text('Add'),
                      onPressed: () {
                        setState(() {
                          raffle.addName(nameController.text);
                          nameController.clear();
                        });
                      },
                    ),
                  ],
                ),
                ElevatedButton(
                  child: Text('Load'),
                  onPressed: () {
                    setState(() {
                      raffle.loadNames('names.txt');
                    });
                  },
                ),
                ElevatedButton(
                  child: Text('Start Raffle'),
                  onPressed: () {
                    setState(() {
                      raffle.startRaffle();
                    });
                  },
                ),
                Expanded(
                  child: ListView.builder(
                    itemCount: raffle.names.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text(raffle.names[index]),
                      );
                    },
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

Por temas de espacio y tamaño en las respuestas, Copilot suele sugerir las clases en el mismo fichero, pero en un entorno de desarrollo real, lo ideal sería separarlas en ficheros diferentes para mantener una estructura más limpia y ordenada. He ahí parte de la importancia del papel del programador experimentado para mantener una estructura organizada en el proyecto.

Más allá de la utilidad y la calidad de los cambios realizados en este punto concreto, lo que queremos destacar es la capacidad de Copilot para ayudarnos a replantear ideas y buscar nuevas aproximaciones, algo que puede ser muy útil en determinadas situaciones. Normalmente no le pedirás un cambio de enfoque literal sino que entre ambos podréis ir haciendo un brainstorming de ideas y soluciones para encontrar la mejor aproximación a un problema. Siempre teniendo en cuenta que Copilot no es un Product Manager sino una herramienta que nos ayuda a programar, por lo que no siempre acertará en sus sugerencias, ni las sugeridas serán la mejor opción.

Conclusiones

Este ejemplo nos demuestra la capacidad de Github Copilot para ayudarnos en el proceso de desarrollo de software, en la práctica se siente como tener un compañero trabajando mano a mano contigo, muchas veces se equivocará, como nosotros mismos, pero en gran parte de ellas te aportará soluciones y en otras, incluso te sorprenderá. No sólo para escribir código sino para replantear ideas y buscar nuevas aproximaciones, la agilidad que estas herramientas proporcionan no deberían ser subestimadas para programadores que quieran competir de tú a tú con el estilo de programación actual. La tendencia parece ser hacia cada vez escribir menos código literal y centrarnos en la lógica y la estructura del mismo, la parte maś “creativa” y menos tediosa por lo general. Sin ir más lejos, mientras escribía este artículo, el pasado día 29 de abril Github anunciaba Copilot Workspace, un entorno de desarrollo que integra agentes impulsados por IA para asistir a los desarrolladores desde la concepción de ideas hasta la implementación de código en lenguaje natural, reduciendo la cantidad de código escrita y buscando mejorar la productividad.

También puede utilizarse para testing y muchas otras cosas, el tenerlo integrado simplemente nos proporcionará mayor agilidad y eficiencia en nuestro día a día. Te recomiendo probarlo por ti mismo, puedes utilizarlo para completar la parte de carga de nombres a partir de un fichero de texto de nuestro ejemplo y ¡Happy Coding! (Mientas dure…)

¿Te ha servido esta información?

Tu apoyo nos permite seguir compartiendo conocimiento de calidad. Si alguno de nuestros artículos te han aportado valor, considera hacer una donación para mantener este espacio en marcha y actualizado. ¡Gracias por tu contribución! 😊

Artículos relacionados

Quizá te puedan interesar