miércoles, 21 de septiembre de 2016

Automatic Software Repair

Automatic Software Repair es un área de investigación del desarrollo de software que comenzó a utilizarse alrededor del año 2000. Es una técnica que tiene como función arreglar bugs de forma automática; es decir sin intervención humana.

Para poder comprender de qué se trata, tenemos que partir de los conceptos fundamentales utilizados en esta rama de la investigación de software: 

En primer lugar, cabe destacar que todo programa en ejecución tiene comportamientos esperados y no esperados, pero también hay un sujeto implícito que es el que observa que estos comportamientos sean de esa manera. El mismo puede ser un QA (un humano) o también puede ser una especificación (conjunto de comportamientos esperados explícitos, ya sea los formulados en una batería de test, o los implícitos, como ser un programa que no puede “crashear” bajo ningún input). 

Otro concepto importante y muy ligado a las especificaciones, es el de oráculos. Cuando hablamos de ellos, nos referimos a “artefactos” que nos indican si una ejecución es correcta o no. Como vemos, los dos términos se asemejan entre sí, ya que hablan de comportamientos esperados; entonces, ¿dónde radica la diferencia? En que la especificación suele contener información no funcional (tipos de input, rangos de entrada) mientras que los oráculos tienen instancias concretas de validación. De esta manera, dentro de Automatic Software Repair tenemos dos oráculos: los de bug y los de regresión. Los de Bug nos indican que existe un error y que hay que empezar nuestro proceso de reparación, mientras que los de regresión nos indican que tras este proceso introdujimos algún error. 

Una de las claves de Automatic Software Repair, es que para realizar un proceso de reparación automática, debe realizarse en una clase de bugs. Este conjunto de errores puede ser de acuerdo a su causa raíz, a sus síntomas o a su reparación. 

Luego, se deberían preparar buenos oráculos, tanto de bug como de regresión, para continuar con la ejecución de algoritmos de reparación que completen el proceso. Estos algoritmos, nos indican adonde tenemos que probar nuestras reparaciones automáticas, y se pueden clasificar en dos grandes “familias”. 

La primera de ellas es la reparación en tiempo real, que se basa en el cambio de estado del programa para lograr un comportamiento adecuado. No se refiere a cambiar el código, sino a la configuración o el input del mismo. 

El oráculo de bug más frecuentemente utilizado en esta reparación es un “crash” o una invariante en el sistema. Para reparar estos estados hay algunos estudios basados en la redundancia, que indican que en un programa hay “varias maneras de hacer lo mismo”. 

Así es como hay estudios para escapar de loops infinitos, para modificar un input por uno esperado dentro del rango, o para darle al programa un objeto default para no “crashear” ante un Null Pointer Exception. 

La otra familia es la generación automática de parches, en donde, a diferencia de la reparación en tiempo real, se cambia el código del programa para repararlo automáticamente. 

En la mayoría de estos estudios, el oráculo de bug es un test case que falla, mientras que un oráculo de regresión es la batería de test asociada a la aplicación que hay que probar. 

En este tipo de reparaciones, llamadas behavior (comportamientos), uno de los estudios más conocidos se basa en “algoritmos de mutación”. Para estos, la solución está en el código: si quitás una línea de código, quizá solucionas un bug; si añadís una, podrías llegar a solucionar un error y, si modificas una línea, también podrías solucionar un bug. ¿Pero cómo se llega a esta conclusión? Con algoritmos que seleccionan y encuentran soluciones, denominados parches.

A pesar de que ya existen gran cantidad de respuestas, dentro de esta rama de investigación, aún quedan algunas preguntas por responder: ¿qué pasa cuando la solución que da el programa no es la correcta? o ¿qué ocurre cuando no se encuentran algoritmos que puedan solucionar el programa? Y esto plantea un nuevo desafío: que un programa pueda reparar un bug, subir un commit y que las personas puedan aprobarlo o no. 

Autor:
Gustavo Lamy Cobarrubia
QA & Testing Specialist

miércoles, 7 de septiembre de 2016

Hoy por mí, mañana por ti... ¿Por qué ser prolijos al escribir código?

¡Hola! Hoy quería hacer foco en un tema que refleja la calidad de las soluciones que desarrollamos como parte de nuestro trabajo. No, no voy a hablar de Testing como la última vez, sino de desarrollo de software.

Cuando hablamos de calidad, no sólo lo hacemos pensando en que un sistema funcione correctamente, sin bugs, sin problemas de performance, con un buen look & feel, cumpliendo con los requerimientos de nuestro cliente, etc. Cuando hablamos de calidad, también podemos referirnos al código fuente, es decir, a aquello que no se ve pero está allí presente, y que es lo que hace que los sistemas puedan funcionar bien, o no tanto…

Hoy por mí

En primer lugar, deberíamos generar código de calidad pensando en que luego es uno mismo quien debe leer, mantener y evolucionar constantemente el mismo a lo largo del proyecto. Difícilmente algo que desarrollemos pueda mantenerse aislado del resto de los componentes de la solución o, una vez listo, no tengamos que volver a modificarlo nunca más.

El primer y principal punto a considerar entonces, es generar el código lo suficientemente claro, ordenado y prolijo, como para que nosotros mismos podamos entenderlo pasados unos días, unas semanas, o unos requerimientos.

Dentro de los aspectos más importantes que debemos tener en cuenta a la hora de escribir buen código fuente, podemos mencionar:

Evitar la duplicación de código, ya sean líneas, bloques completos, métodos y/o clases.

Mantener el código simple, evitando la complejidad innecesaria.

Asignar nombres claros y precisos a las clases, variables, métodos y propiedades. Definir una forma de nombrarlos y luego mantenerla consistente a lo largo de todo el desarrollo.

Comentar nuestro código fuente responsablemente, evitando aquellos comentarios innecesarios que no añadan nada valioso y poniendo sólo aquellos que realmente aporten algo de valor. Además, hay que recordar que la primera documentación del código, debe ser el código mismo. Si logramos que el código sea autodocumentado, sólo deberemos utilizar comentarios en casos particulares o con alguna lógica compleja que deba detallarse.

Evitar construir métodos y/o clases muy largas, o como decimos en la jerga, kilométricas. Esto generalmente suele ser un indicio de un mal diseño técnico previo.

No construir funciones “por las dudas”. Si algo no va a ser usado, es mejor no construirlo. Por otro lado, siguiendo la misma línea, si estamos haciendo mantenimiento de código y vamos a pasar a desuso un bloque de código, es preferible eliminarlo en vez de comentarlo. Si en un futuro lo volvemos a necesitar, podemos obtenerlo del historial de cambios del repositorio de código fuente que estemos usando (hoy en día no hay excusas para trabajar sin uno de ellos).

No abusar de la herencia. Si bien es un mecanismo potente, un mal uso de la misma sólo hace que la solución sea más compleja y difícil de interpretar. Además, es importante no confundir “herencia” con “agregación” a la hora de compartir y dividir responsabilidades funcionales.

Seleccionar los patrones de diseño que realmente nos van a aportar valor a la solución y nos permitirán lograr un diseño más flexible y preciso. Es un error muy común creer que agregando más patrones de diseño la solución va a ser mejor; por el contrario, sólo la hacen más compleja y difícil de mantener.

Evitar las “God Classes”, es decir, esas estructuras que hacen de todo un poco, ya que termina siendo muy engorroso poder modificarlas debido a que su nivel de acoplamiento termina siendo muy alto.

No diseñar firmas de métodos con una gran cantidad de parámetros, ya que agregar o quitar uno de ellos terminará siendo un dolor de cabeza importante, más allá de que logremos volver a hacer compilar la solución. Si tenemos un método que recibe muchos parámetros, podemos preguntarnos si realmente son necesarios o si deberíamos dividir la funcionalidad en dos o más partes. En el caso de que realmente los necesitemos, vayamos por el enfoque de utilizar un DTO en vez de pasar los parámetros individualmente, ya que las modificaciones serán mucho más fáciles de esta forma.

Y la lista continúa… Lo ideal es, más allá de las reglas generales, consultar cuáles son las buenas prácticas particulares que existen para el lenguaje y/o plataforma que estemos utilizando, e implementarlas como parte de la solución.

Mañana por ti

De la forma en la que hoy trabajamos desarrollando software, es muy poco probable que lo hagamos solos, aislados, sin un equipo al lado nuestro participando del mismo proyecto. Por eso, el segundo punto a tener en cuenta es que, además de que nosotros podamos entender nuestro propio código, este tiene que poder ser comprendido por el resto de las personas que forman parte del grupo de trabajo.

Difícilmente podamos lograr una solución integral con un buen estándar de calidad, si no podemos interpretar lo que nuestro compañero quiso codificar. No sólo porque deberemos integrarnos con lo que otros han desarrollado, sino porque además seguramente debamos modificar, extender y/o corregir bugs en código que no es nuestro y, si no lo podemos entender correctamente, es de esperar que no podamos resolver de manera eficiente lo que se nos pida.

Lo bueno es que para poder lograr esto, existen unas series de reglas que, si todos los miembros que formamos parte del equipo del proyecto respetamos, nos permitirán generar un código más homogéneo y de mayor calidad:

Es necesario contar con estándares de codificación definidos, aprobados, aceptados y conocidos por todas las personas que trabajamos en el mismo equipo. Estos pueden ser distintos en cada equipo, o ser definidos de manera global por un área técnica de la empresa, como sucede en el caso de Baufest.

El proyecto debe estar coordinado técnicamente por un Arquitecto o Technical Leader, que deberá velar por la correcta aplicación de los estándares adoptados. Una forma de garantizar esto, es mediante los code reviews.

Además, el código del proyecto debe estar fundado sobre una arquitectura diseñada para satisfacer los requerimientos planteados por el cliente. Sin una arquitectura de base, será muy difícil mantener el código ordenado y prolijo.

Cuando ingresa una nueva persona al equipo, lo ideal es que antes de ponerse a leer los requerimientos funcionales que luego deberá construir, primero se centre en conocer y adoptar los estándares de codificación y trabajo que se están usando en el proyecto.

Se deberá contar con una herramienta de análisis de código que permita analizar el nivel de adopción de los estándares. Es un complemento a los code reviews, pero mucho más potente, ya que permite realizar análisis completos en toda la solución y obtener métricas y reportes precisos sobre los puntos a mejorar. 

En Baufest, por ejemplo, para los proyectos de .Net contamos con SonarQube, integrado a Jenkins. Por lo que si el proyecto está correctamente configurado con integración continua, podremos acceder a estas métricas fácilmente, las cuales se actualizarán con cada checkin que hagamos.

Y... mañana por mí también

Y finalmente… ¿a quién no le ha pasado de volver a un determinado proyecto en el que estuvo hace un par de años atrás? ¿A quién no lo han llamado las personas que luego tomaron ese proyecto preguntándole acerca de algo desarrollado, pidiéndole una mano para implementar una nueva funcionalidad?

Es más común de lo que parece y, por ello, hay que estar siempre preparado. Más allá de pensar en que el código pueda ser entendido por uno mismo unos días después de haberlo escrito, o por el resto del equipo durante el proyecto, hay que pensar qué pasaría si dentro de unos años es necesario retomarlo, ya sea que estemos en el nuevo equipo o no.
Por ello, cuando generemos código, no sólo debe verse bonito, sino además, tiene que ser fácilmente entendible, tiene que estar documentado, organizado, seguir un patrón de diseño y estar asociado a estándares de programación más globales, que permitan al nuevo equipo poder entender rápidamente qué se quiso hacer y cómo.

Además, generar código de calidad hace que las aplicaciones se comporten de forma más predecible, tengan menos tasa de errores, sean más fáciles de mantener y evolucionar y, por otro lado, nos potencian como profesionales, ya que no sólo estaremos generando código, sino que lo estaremos generando bien.

Recuerden, ser prolijos al generar código no sólo nos ayudará a nosotros mismos, sino que además, ayudará a otras personas a entender qué quisimos hacer cuando escribimos esas líneas.

Bueno, es todo por hoy. Les dejo un cierre con un poco de humor referido al tema… 

Fuente: http://www.codecomics.com/

¡Hasta la próxima!

Ing. Ariel Martín Bensussán
.Net Practice Manager