sábado, 29 de noviembre de 2014

Herramientas Imprescindibles: Control de versiones

Durante la semana un codificador me estaba exponiendo una excelente idea para poder resolver un problema, en mitad de la conversación que pregunta, "¿Crees que va a funcionar?" y le conteste: "No, nunca funciona a la primera" (Regla N°2 de la Ingeniera de Software: "Va a fallar").

Cualquier cosa o idea que tengamos nunca va a funcionar a la primera, y si funciona es que porque algo hemos hecho mal o estamos obviando algo. Las buenas ideas necesitan fallos para poder triunfar, montarle piezas y quitárselas hasta que se obtengan la forma ideal, y generalmente se necesitan más de una persona para hacer grandes proyectos realidad.

Ante tal panorama es importante el control, algo que nos responda rápidamente a las preguntas "¿Qué hice y donde lo hice?" y "¿Qué hicieron otros y donde?", a la vez que permitirnos regresarnos rápidamente sobre nuestro pasos, para poder emprender otra dirección más exitosa.

Este control nos lo permite las herramientas de "Control de versiones", que son medios automatizados para gestionar el código fuente, a través de su evolución. No solo eso, es como una máquina del tiempo (o mejor como una Tardis), que nos permite ver que ocurrió con nuestro código fuente (Porque todo cambia, Regla N°1 de la Ingeniera de Software: "Va a cambiar") , en una fecha determinada, quien hizo los cambios, y comprender el porqué. (por que todo tiene un motivo, Regla N° 4: Todo sistema tiene un propósito).

Pero, exactamente ¿Que debe permitir una herramienta de Control de versiones?

  • Gestionar los cambios que se hagan con nuestro código fuente, de una forma sencilla.
  • Integrar los cambios que hagan otros codificadores de forma automática.
  • permitirnos revisar dichos cambios a través del tiempo.
  • Permitirnos reversar cambios específicos de nuestro código ( es decir no se trata de reverso "todo o nada").
  • Debe funcionar de forma remota. Los programadores trabajan en sus maquinas (Cada uno en sus maquinas), y mediante el software de control de versiones, se integra todo en un repositorio que está en un servidor, de forma automáticamente. las cambios de los otros codificadores se integran a nuestro código (en nuestra maquina) de forma automática de igual modo.
  • Realizar ramificaciones de nuestro código.


El tema de las ramificaciones (branch) en un tema muy importante, que se explica con el siguiente ejemplo: Imaginemos que tenemos un determinado proyecto, del cual vamos a sacar la versión 2.0, con lo que usamos el control de versiones para gestionar nuestros cambios desde la versión 1.0 a la versión 2.0. La construcción dura unos 6 meses en cuestión de tiempo, pero a su vez tenemos la versión 1.0 la cual sigue siendo usada por nuestro clientes (porque seis meses es mucho tiempo), Esta versión tiene errores que necesitan ser corregidos o mejoras que no pueden esperar a la versiones 2.0. Aquí tenemos varias opciones:

  • No hacer nada para la versión 1.0, con lo que estaremos perdiendo seis meses de oportunidades de negocio.
  • Hacer los cambios en la versión 1.0 y cuando salga la versiones 2.0 incorporarlos manualmente, con la extremadamente posibilidad de perder código, o funcionalidad al hacer una mala "recodificación" de los cambios de la versión 1.0 a la 2.0.


La tercera opciones es hacer una rama de software con nuestra herramienta de control de versiones, esto seria así: Originalmente existe un solo proyecto llamado "Proyecto 1.0", que se ramificaría en dos. uno llamado "Proyecto 2.0" y otro "Proyecto 1.1 " (por poner un nombre), cada una con una serie de codificaciones independientes (y puede que un equipo de trabajo también). Llegado el momento la herramienta de control de versiones nos permitirá replicar (de forma automática), todos los cambios realizados desde la versión "Proyecto 1.0" hasta la versión "Proyecto 1.1", pero dentro del código fuente del "Proyecto 2.0". La integración del código será automática, y en el caso que existiera conflictos, se nos indicara para que los corrijamos manualmente.

Como comentamos, cada codificador trabaja de forma independiente en su máquina, y preferiblemente, hace una entrega de código (llamada frecuentemente commit) y una integración (automática) del código fuente de otros codificadores (llamada también frecuentemente update), todo esto pasando por un servidor remoto que contiene toda la historia del código fuente.

Pero existe otra forma de trabajar, mediante repositorios distribuidos, esto es un escenario en que cada codificador tiene una copia del repositorio en su máquina, y puede trabajar libremente con el sin aceptar a otros codificadores (puede hacer commit, revertir, crear rama e integrarlas, etc.). cuando el codificador lo considere oportuno puede integrar el código al repositorio común, para que este a disponibilidad de otros codificadores, al mismo tiempo que descarga el código de estos.

Aquí una pequeña guía para saber cuándo usar control de versiones en nuestros proyectos

  • Regla numero 1:¿Cuándo debo usar control de versiones?
Siempre.

  • Regla numero 2:Aun así, no sé si por el tamaño de mi proyecto debo usar control de versiones.
Ok, a veces es confuso, por favor en caso de duda revise la regla numero uno.

  • Regla numero 3:Pero no estoy compartiendo código fuente con otros codificadores, ¿Realmente es necesario usar control de versiones?
En este caso igualmente podemos aplicar la regla numero uno.


Por último Algunas herramientas de control de versiones:

  • Subversion:frecuentemente llamado SVN, uno de los más populares y más fácil de aprender, puede consultarse un excelente manual aquí, y descargase de aquí.
  • TorotiseSVN:cliente de SVN integrado con el explorador de Windows, una maravilla, puede descargase de aquí.
  • AnkhSVN:cliente SVN integrado con Visual Studio, se descarga de aquí.
  • VisualSVN Server:Un servidor de SVN, extremadamente sencillo de configurar, vale mucho la pena, incluso su licencia gratuita, de descargar de aquí.
  • Git:Control de versiones distribuido muy útil, quizás el mejor, algo difícil al principio, pero sin duda vale la pena el esfuerzo, se puede consultar el siguiente manual aquí, y descargarse el Gitde aquí.
  • TortoiseGIT:El homologo de TortoiseSVN para GIT, se descarga de aquí.


Aunque SVN es una excelente herramienta, mi recomendación es comenzar con GIT, aunque sea un poco más difícil, porque es sistema posterior que se creó con la intención de mejorar la debilidades de SVN. No obstante los creadores de Git, sabiendo de la gran difusión de SVN, crearon una capa de compatibilidad entre ambos sistemas, pudiendo trabajar con un cliente GIT en nuestra maquina (con toda sus ventajas) y un servidor SVN del lado remoto.

Por último una serie de reglas a aplicar en el uso de control de versiones:

  • Nunca subas al control de versión algo que no compila, solo te causaras el desprecio del resto de codificadores
  • Revisa línea por liana lo que modificaste antes de subirlo, si no reconoces un cambio reviértelo, debes reconoce todo lo que estas subiendo. Para realizar esta tarea el control de versiones tiene herramientas que te muestran que se modifico exactamente, de forma rápida y sencilla.Nunca te saltes este paso.
  • Haz update y commit tantas veces como sea posible (por lo menos una vez al día), si no tu versión del código de alejara de la versión de otros codificadores, y cuando quieres integrarla, te encontraras con un monto de conflictos.

domingo, 2 de noviembre de 2014

Estándares de programación: Preferiblemente usa bucles "foreach"

En cualquier lenguaje de programación hay una serie de estructuras básicas para realizar un bucle, en esta publicación analizaremos el bucle for y el bucle foreach, y la conveniencia de usar el segundo sobre el primero, el código mostrado sera en C# (aunque debiera ser semejante en cualquier otro lenguaje).

Comencemos viendo los tipos de bucle mas comunes:
  • Esta el bucle while:
1
2
3
4
while (SeCumplaCondicion())
{
}
while (SeCumplaCondicion())
{

}
Es el más básico, y se ejecutaría hasta que algo hiciera que se cumpla la condición.
  • bucle while tipo dos:

1
2
3
do {
} while (SeCumplaCondicion())
do {

} while (SeCumplaCondicion())
Garantiza que por lo menos se realiza una iteración del bucle antes de comprobar si se cumple la sentencia, por lo demás es igual que el anterior
  • El bucle for:


1
2
3
4
for (int i = 0; i < 10; i++)
{
}
for (int i = 0; i < 10; i++)
{

}
Este bucle se usa para recorrer algún tipo de array, o una colección, teniendo esta estructura

1
2
3
4
5
6
List<string> nombres = new List<string>() { "JUAN", "PEDRO", "ANA", "INES" };
for (int i = 0; i < nombres.Count; i++)
{
    Console.WriteLine(" Nombre :" + nombres[i]);
}
List<string> nombres = new List<string>() { "JUAN", "PEDRO", "ANA", "INES" };

for (int i = 0; i < nombres.Count; i++)
{
    Console.WriteLine(" Nombre :" + nombres[i]);
}
El resultado será algo parecido a:


  • Nombre :JUAN
  • Nombre :PEDRO
  • Nombre :ANA
  • Nombre :INES

Este bucle tiene varios problemas asociados:

lo primero es que usamos un numero para acceder a la colección, si nos fijamos nuestra intención no es trabajar con la posición de los nombres dentro del objeto, es decir nos da igual si es la primera o la ultima, nosotros queremos recórrelos todos en secuencia uno a uno. Su posición para nosotros es irrelevante. Esto es importante, por que se pierde el verdadero objetivo del código, hacer algo con todos los elementos del array, al margen de su posición. Requiera además que ya se sepan todos los nombres (los elementos de la colección) antes de procesar el bucle, en algunos casos no requeríamos conocer todo el escenario antes de querer procesarlo, por ejemplo, si estamos buscando un archivo en todo el disco duro, no sería recomendable que primero listemos todos los archivos y luego lo recorriéramos buscando el archivo idóneo.

  • El último bucle, es el bucle foreach, tiene la siguiente estructura


List<string> nombres = GenerarListaNombres();

1
2
3
4
5
6
List<string> nombres = GenerarListaNombres();
foreach (string nombre in nombres)
{
    Console.WriteLine(" Nombre :" + nombre);
}
List<string> nombres = GenerarListaNombres();

foreach (string nombre in nombres)
{
    Console.WriteLine(" Nombre :" + nombre);
}
En esta forma de bucle, se elimina el uso de los índices (int i), lo que simplifica su código y nos permite centrarnos en el objetivo real, que es recorrer los elementos de la lista.

El hecho de que el código sea más sencillo, lo hace a su vez mas mantenibles y escalable en un futuro.

Por otro lado el único requisito es que el elemento a recorrer implemente la interfaz IEnumerable, lo cual nos permite realizar ajustes a como se procesan los elementos, en este ejemplo se recorren los nombres hasta que se encuentra la palabra "PEDRO"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void Ejemplo2()
{
    foreach (string nombre in GenerarListaNombresEnumerable())
    {
        Console.WriteLine(" Nombre :" + nombre);
        if (nombre == "PEDRO") break;
    }
}
public static IEnumerable<string> GenerarListaNombresEnumerable()
{
    yield return "JUAN";
    yield return "PEDRO";
    yield return "ANA";
    yield return "INES";
}
public static void Ejemplo2()
{
    foreach (string nombre in GenerarListaNombresEnumerable())
    {
        Console.WriteLine(" Nombre :" + nombre);
        if (nombre == "PEDRO") break;
    }
}

public static IEnumerable<string> GenerarListaNombresEnumerable()
{
    yield return "JUAN";
    yield return "PEDRO";
    yield return "ANA";
    yield return "INES";
}
Esta función especial que devuelve los campos con la palabra yield, nos permite genera la lista de nombres al mismo tiempo que se está procesando, la ventaja es que no se generan nombres que no se van a usar (por ejemplo no se genera "ANA", ni "INES", en caso que poníamos anteriormente, si estuviéramos buscando un archivo por todo el disco duro, podríamos detener la búsqueda cuando encontráramos el archivos requerido, y no tendríamos primero que localizar todo los archivos y ver si cumplen las características que deseamos uno a uno.

Adicionalmente el listado de nombre no se puede modificar dentro de un bucle foreach, con lo que garantiza seguridad de no alteración del listado (los objetos dentro del listado si se puede mejorar).

Resumiendo las ventajas del bucle foreach, quedarían las siguiente:

  • Código más limpio y sencillo, que expresa mejor las intenciones del codificador.
  • No es necesario generar completamente los elementos a recorrer, antes de comenzar a recorrerlos.
  • No es posible modificar el listado original.


¿Cuándo usar un bucle for o una foreach?, en principio la mejor opción es siempre el bucle foreach, por los motivos establecidos, (sobre todo el de claridad en el código), salvo en el caso que realmente sea relevante la posición del elemento en la lista.