Como usar Eclipse en todo su esplendor
No más tantos dolores de cabeza
--Joaquín Azcárate y Francisco Bravo
Índice
Entendiendo la perspectiva de Debug
La razón por la que confeccionamos este compilado de tres guías, todas sobre Eclipse, es porque creemos que la mayor parte del tiempo que van a pasar desarrollando el TP de Sistemas Operativos, lo van a pasar frente a Eclipse; por lo que intentamos hacerles aprender esta herramienta de una forma lo más feliz posible.
Si detectan algún error, o sienten que algo se explicó mal, poco o no está, pueden contactarme a: os@florius.com.ar, u cualquier otro ayudante.
Ahora sí, una vez sacados de encima esa introducción molesta; abrimos el Eclipse.
Eclipse nos otorga la posibilidad de generar nuestros propios atajos (snippets/templates), cuyo fin es acelerar el proceso de programación. Los Snippets funcionan como esquemas de una porción de código; la cual, cada vez que utilicemos el atajo, se auto “escribirá” sola. Por ejemplo, tener un atajo para sentencias específicas como pueden ser los if’s, for’s, while’s, entre otros.
Otro elemento que nos proporciona Eclipse son las Task Tags, no son la gran cosa, pero ayudan. Nos sirven para colocar un indicador en nuestro código, ya sea como recordatorio o como forma de acceder rápido a una sección del código sin tener que andar revisándolo todo.
Meh, no es tan interesante, pero quiero ver cómo se hacen
Al igual que con los templates, debemos ir a Window, Preferences, C/C++; pero ahora queremos ir a Task Tags. Donde veremos una lista con todos los tags, el Eclipse ya trae algunos por default.
Para agregar nuestros propios tags, solo debemos ingresar en “New…” y crearlo. Luego solo queda usarlos. Los Task Tags deben ser utilizados en comentarios, de otra forma, Eclipse no los reconocerá como tales.
En la imagen podemos ver que los Tags aparecen en letra negrita y con color azul, además en la barra scrolleable del código nos aparecen indicadores de la posición de dichos Tags.
La idea es que terminen de leer esta guia y sean capaces de entender y afrontar cualquier error de programación que tengan. No se cubrirán errores de sintaxis o de semántica, simplemente es una descripción del entorno de Eclipse CDT y su interfaz gráfica del debugger de C (GDB[1]).
Todo lo que se ve en este documento es simplemente una forma linda de correr GDB, por lo que los conocimientos son independientes del Eclipse CDT. Para comprender los comandos de GDB que Eclipse nos brinda de forma visual, los redirijo a la documentación de GDB.
Todas las imagenes estan sacadas usando Eclipse IDE for C/C++ Developers, versión: Juno Service Release 2; build id: 20130225-0426[2].
Posiblemente su Eclipse se vea un poco diferente, como saque las imagenes en una resolución pequeña, voy a estar moviendo y escalando los diferentes marcos.
Esta guía plantea un flujo de trabajo que pretende ser interactivo, por lo que les recomiendo seguir, localmente, a la par estos conceptos, detenerse, pensar, probar, codificar, o lo que sientas en tu corazón al ver esto:
Vale hacer uso del índice para consulta.
Posiblemente, si quieren debugear ya tengamos un proyecto creado, pero a modo de ejemplo, creen un nuevo proyecto haciendo File > New > C Project > [Ponen un nombre al proyecto] > Linux GCC > Finish. Ahora con un proyecto vacío, podemos hacerle Click derecho a este > New > Source file > [Le dan un nombre al nuevo archivo]; en mi caso, mi proyecto se llama “Ejemplo” con un único archivo “principal.c”
Supongamos que quieren un programa que cree una variable entera, le asigne a esta nueva variable el número 4, y devuelve este valor. Los aliento a que piensen y programen el problema planteado antes de avanzar, así pueden seguir la guía en sus propias computadoras. Suele ser mejor aprender haciendo que solo leyendo; pero ¿que voy a saber yo? Soy un documento en internet.
Una vez que tengan programado algo, pueden apretar el boton de Run Debug, esto ejecutará su programa en modo de debug, donde pueden seguir paso a paso el flujo y los datos de su programa.
Si es la primera vez que apretan el boton de Run Debug les preguntara si quieren cambiar de perspectiva; una perspectiva es una forma diferente de ver el Eclipse donde cambian las vistas. Lo más cómodo es pedirle que recuerda que “Si, quiero cambiar la perspectiva”. De apretar no, quedaran en la perspectiva de programacion, para ir a la perspectiva de Debug pueden ir a Window > Open Perspective > Other... > Debug.
De ahora en adelante les aparecera la perspectiva de Debug a la izquierda, junto con la perspectiva de programación, y pueden ir intercambiandolas a medida que necesiten programar / debuggear.
En esta perspectiva tienen 5 marcos diferenciables:
Recordemos a donde quedamos, estamos con nuestro programa corriendo en modo Debug, por lo que fren antes de ejecutar cualquier cosa. Tenemos marcada la primera línea, por lo que todavía no se ejecutó. Si ponemos el mouse arriba de la letra ‘a’ veremos que es de tipo int con el valor 0. Esto mismo podemos verlo en la vista de Variables. Junto con las variables locales, nos aparecen los parámetros de la función; en este caso argc y argv.
Intentemos recordar cómo podemos ejecutar esta linea. Pausa dramática y ruido de “pensamiento”.
Si adivinaron Step Into o Step Over estan en lo correcto
Como vemos, ahora esta pintada la línea siguiente; el valor de a se alteró y se pintó de amarillo
Supongamos que nos encarga la NASA de armar un programa que sea capaz de almacenar patentes (3 letras y 3 números); y queremos almacenar las patentes: “ABC 123”, “SQL 035”, “UTN 999”.
Devuelta, los invito a pensar la solucion en C.
Posiblemente tengan una solución como esta[3]
Intentemos correrlo como debug y frenar justo antes de insertar la palabra “UTN”.
Entonces correriamos en debug este programa y apretaríamos 6 veces Step Over. Tiene que existir una mejor forma de hacer esto, y no tener que contar los Steps...
Tan simple como hacer doble click en la barra a la izquierda de la línea de código que queremos interrumpir. Esto se puede hacer tanto en la perspectiva de programación C/C++, como en la de Debug. Incluso se pueden hacer mientras el programa esté ejecutando (o frenado) en modo Debug.
Adicionalmente en la perspectiva de Debug, esta la vista de “Breakpoints” donde pueden ver los breakpoints que tengan, y desactivarlos temporalmente haciendole click al tick.
Ahora podemos correr en Debug nuestro programa y darle Resume, la ejecución se va a trabar donde pusimos el breakpoint, sin ejecutar la línea que fue trabada.
Cuando logres hacer esto, intenta mirar la variable local “patentes”.
Como esta variable es un puntero, GDB no puede suponer si es un puntero, o si es un vector, por lo que cuando lo ves con la vista solo van a ver el primero ¿Como podemos arreglar esto?
Si uno apreta Click derecho en la variable que queremos, nos presenta la opción de ver la memoria tal cual la grabo o castear a un tipo particular o, como queremos ahora, mostrar cómo una array.
Recordemos que esto ya esta en ejecución, por lo que si quisiéramos mostrar como un array de 100 elementos, la patente 4 en adelante estarían fuera del segmento alocado y tendría que dar un fallo de segmento. Pueden probarlo, pero esto no pasa porque solo nos muestra la memoria. Si casualmente hay algo nos va a mostrar algo corrupto, o si no esta asignado nada después de la posicion asignada, nos dice que no puede accederla
(Si uno hace doble click en una pestaña, particularmente la vista de Variables se agranda. Volver a hacer doble click vuelve todo a la normalidad)
Y vemos exactamente el valor de cada variable dentro de un vector. Podríamos tener un vector dentro de otro, por lo que repetimos este proceso de Click derecho > Display as Array...
Supongamos que queremos refactorizar nuestra solución y generar un TAD de patentes; algo que se vea asi:
Y queremos un resultado en la consola como:
Patente 0: ABC 123
Patente 1: SQL 035
Patente 2: UTN 999
Antes de empezar a pensar la solución, esto es lo que pasaría de correr como Debug ese código:
¿Notas como en la vista de Variables no esta la variable “patentes”?
Esto no es un “bug”, sino que como patentes fue definida de forma global, no aparece junto con las variables locales ¿Entonces como podemos ver el contenido?
Una solución válida es poner el mouse arriba, pero aprovecho esta situación para introducirles la vista de Expresiones.
Muy posiblemente no tengan esta vista por defecto; para accederla uno puede ir a Window > Show View > Expressions.
Ahora tenemos en el mismo marco que Variables, una nueva pestaña de expresiones con un +, donde podemos hacer click y escribir la expresión que queramos. Recordemos que el programa ya está ejecutando, por lo que no podemos pedir que ejecute una funcion, solo podemos ver variables.
Si escribimos “patentes” podemos acceder a la variable global, tal como haríamos en la vista de Variables.
Ahora si, a programar una posible solución
Supongamos que generamos esta función que inicializa nuestro TAD, e intentamos ejecutar esta instrucción:
agregarPatenteAVector(crearPatente("ABC", 123), patentes);
Tenemos una funcion cuyo argumento es otra funcion.
Supongamos un código de crearPatente de este estilo:
t_patente* crearPatente(char* letras, unsigned short numeros){
t_patente* ret = malloc( sizeof(t_patente) );
ret->letras = strdup(letras);
ret->numero = numeros;
return ret;
}
Al hacer Step Into podemos ver que en el stack (dentro de la vista de Debug) aparece que estamos dentro de la función crearPatente(), llamada por main().
Antes de seguir adelante, es una buena oportunidad de comentarles que dentro de la vista de Variables (y de Expresiones) uno puede alterar el valor de una variable. Haciendole doble click a el Value, uno puede ingresar lo que quiera y de ese punto en adelante, el programa tendrá ese nuevo número. Usar con cuidado.
Ahora estamos en una línea con una llamada a una funcion que no programamos nosotros: malloc[4]. Si apretamos Step Into, ¿qué tendría que pasar?
Si, se merece todo un título para esto nomas.
Al igual que el Step Into anterior, el stack crece y se llama a malloc, pero como esta programado por otras personas, y no tenemos acceso al código, Eclipse nos informa que no puede encontrar el código. Si puede encontrar la biblioteca compartida y ejecutar el código; el problema es que ese código tiene a su vez varias llamadas a otras funciones y varias líneas, por lo que entrar en pánico y apretar Step Into y Step Over nos lleva más adentro de la madriguera del conejo.
Lo que nos lleva a la salvación con el Step Out, que nos devuelve a la función de donde se llamó, en este caso crearPatente().
Una vez que tenemos breakpoints, uno puede hacer click derecho en la pelotita e ir a sus propiedades.
Por ejemplo podríamos querer un breakpoint que solo interrumpe la ejecución en el codigo de imprimir las patentes, si las letras del vector a mirar son “UTN”
Notar que en esta condición tenemos una llamada a una funcion y evaluamos el retorno. Esto se puede hacer.
Una vez creado este nuevo breakpoint, podríamos resumir nuestra solucion y ver si efectivamente frena en algún lugar.
Abajo lo viejo y arriba el refactor
Los ejemplos de código que se mostraran en esta sección de la guía solo tienen como objetivo demostrar el funcionamiento de los refactors y no están pensados como ejemplos de buen código.
Un refactor se lo llama a realizar un cambio en el código fuente, pero que a la vez, su funcionamiento es el mismo. Es decir, cambiar la estructura interna de nuestro código y mantener el comportamiento externo.
El motivo por el que queremos hacer refactors es incrementar distintas cualidades de nuestro código; como puede ser, la expresividad, la consistencia, la flexibilidad, entre otros.
Si vamos al Eclipse, podemos notar que una de la opciones en la barra superior tiene el nombre “Refactor” (¿Quizás nos puede servir, no?). Al hacer click en esta opción, se despliega un listado de posibles refactors bastante comunes, para los que el Eclipse ya viene automatizado.
Analicemos cada refactor:
Veamos el siguiente código:
Como se puede deducir, no es sencillo saber qué es lo que hace este código, principalmente por el nombre de las variables. En este caso, podemos utilizar el Rename, para renombrar cada una de estas variables de una forma automatizada, en lugar de andar renombrando cada aparición de las mismas.
Como se ve en la imagen, el rename recuadra todas las apariciones de la variable y nos pide ingresar el nuevo nombre. Una vez escrito el nombre, hay que apretar Enter. En la imagen ya podemos ver renombradas las variables bar a maximo y foobar a vector_numeros.
Con este refactor logramos que nuestro código sea más expresivo y fácil de entender.
También, si miramos la función not_main() vemos que el IDE reconoce el scope de las variables, ya que solo toma aquellas variables foo del contexto en el que aplicamos el refactor.
Cambiemos nuestro código a lo siguiente:
Se puede notar que tanto la cadena “password” como el número entero 4 son constantes del programa, las cuales, quizás sería bueno abstraer y definir como constantes. ¿Para qué? Imaginemos que la cadena “password” se emplea en 40 líneas de nuestro código y que por algún motivo, es necesario cambiarla por “contraseña”; habría que ir a las 40 líneas y cambiar la cadena. En estos casos, es beneficioso definir constantes. El refactor Extract Constant se encarga precisamente de esto, basta con seleccionar nuestra constante (en este caso “password” y el número 4), ir a la solapa “Refactors” y elegir “Extract Constant”. Nos aparecerá un cartel pidiéndonos el nombre de la constante a definir y una vez hecho ya tenemos el código refactorizado.
Al hacer este refactor pudimos deshacernos de la variable palabra_clave que se utilizaba en el if y en el segundo printf. Más allá de esto, logramos que nuestro código sea más flexible a cambios.
Si bien este extraer en constantes tiene sus beneficios, es muy importante saber identificar cuándo se está tratando con una constante y cuándo no.
Ahora se nos plantea la siguiente situación:
Podemos notar que el cálculo subrayado en rojo es nuestro inconveniente. Supongamos que dicho cálculo es incluso más complejo aún. ¿Qué tan expresivo resultaría el cálculo?¿Podrían saber qué es lo que resuelve solo viéndolo? Lo más probable es que no. Acá es cuando entra el refactor de Extract Local Variable. Con este refactor podemos asociar este cálculo a una variable en la que podemos definir un nombre expresivo de lo que realiza.
De esta forma es más expresivo nuestro código; y volviendo a refactors anteriores, supongamos que el cálculo pasa a ser una operación completamente distinta, en esta nueva situación podríamos modificar lo que se asigna a nuestra variable y luego renombrar la variable, lo que nos facilitaría mucho trabajo (considerando que dicho cálculo modificado se emplea en muchas secciones del código).
Continuando con el código luego de Extract Local Variable:
Podemos ver que ambos recuadros son claramente distintas funcionalidades de nuestro programa. Una buena práctica es modularizar nuestro código, de tal manera que cada función realice una funcionalidad específica (como en este caso, una función calcula el valor de la operación y la otra función valida si el resultado es par o impar y muestra por pantalla). Al seleccionar la opción Extract Function nos abre la siguiente ventana:
Nos pide un nombre de función (Function name), una de las 3 opciones para Access modifier (En C cualquiera de las 3 es lo mismo, ya que no tiene definidos ninguno de ellos), y ya nos infiere la lista de parámetros que va a requerir nuestra función (con la opción Edit… podemos cambiar el nombre de los parámetros y con las opciones Up/Down podemos cambiar el orden en que recibe los parámetros).
En nuestro caso particular, yo extraje los 2 recuadros rojos en 2 funciones, funcion_calculo() y es_par(); quedando así:
De esta forma, subdividimos las funcionalidades. Esto nos permite abstraer cómo una función realiza una operación y simplemente mostrar qué es lo que hace.
Si bien ya vimos los refactors que nos ofrece Eclipse para la programación en C, no hay que olvidar que se debe analizar cada situación y poder ver cuándo resulta conveniente aplicar un refactor y cuál de todos (o qué combinación de refactors) es la mejor opción. Como es una herramienta que se puede hacer siempre, hay que saber cuándo aplicarla.
The End
Ahora, a debugear!
[2] Es la que está en la máquina virtual entregada por la cátedra en el 2013 y 2014
[3] El código este pierde memoria. Documentación sobre el correcto uso de la memoria.