Intercambiando dos variables

Digamos que tenemos dos variables (a, b) y queremos intercambiarlas, es decir que una tome el valor de la otra:

int temp = a;
a = b;
b = temp;

Nada más fácil, ¿verdad? Creamos una variable temporal para poder resguardar el valor de a mientras asignamos a = b, y luego asignamos a b el valor previo de a.

Es necesario crear esta variable temporal para no perder el valor de la primera variable que intercambiamos… ¿o no? Qué tal esto:

a = a ^ b;
b = a ^ b;
a = a ^ b;

Si ejecutamos el fragmento de código en C podemos comprobar que los valores de a y b han sido intercambiados sin haber usado nunca una variable intermedia. ¿Qué está pasando?

La operación OR exclusivo (^) tiene un par de propiedades interesantes que están actuando en este caso. Un número combinado con sí mismo es igual a cero (a ^ a = 0), y un número combinado con cero es igual a sí mismo (a ^ 0 = a).

En la primera línea estamos asignando a = a ^ b. Luego en la segunda línea estamos asignando b = a ^ b, pero recordemos que ahora a = a ^ b, por lo tanto queda b = a ^ b ^ b.

b ^ b se cancela, por lo que efectivamente en la segunda línea estamos asignando b = a.

Igualmente en la tercera línea asignamos a = a ^ b, pero recordemos que antes de la asignación a contiene el valor a ^ b, y b contiene el valor de a, por lo tanto estamos asignando a = a ^ b ^ a, efectivamente a = b.

Este algoritmo se llama XOR swap. Y no es que tenga mucha utilidad práctica, pero es una linda curiosidad del mundo de la computación.

Administrando servicios de Windows

Recientemente tuve que programar una pequeña utilidad para administrar un servicio de Windows, así que pensé recoger aquí algunas informaciones que se encuentran dispersas en varias fuentes para facilitar la tarea.

La manera más fácil de administrar un servicio en .NET es mediante la clase ServiceController del assembly System.ServiceProcess.dll. El siguiente ejemplo muestra cómo reiniciar el servicio de impresión (Spooler):

using System;
using System.ServiceProcess;

namespace ServiceControllerExample
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Reiniciando servicio Spooler");

                ServiceController serviceController = new ServiceController("Spooler");

                serviceController.Stop();
                serviceController.WaitForStatus(ServiceControllerStatus.Stopped);

                serviceController.Start();
                serviceController.WaitForStatus(ServiceControllerStatus.Running);

                Console.WriteLine("Servicio reiniciado, presione Enter para finalizar.");
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadLine();
        }
    }
}

Para instanciar ServiceController simplemente debemos usar el nombre del servicio que lo podemos obtener de la consola de administración (services.msc). Si nos equivocamos con el nombre el objeto será creado igual, y los métodos para cambiar el estado pueden fallar por varias razones, así que es una buena idea poner un try…catch alrededor de todo.

Para reiniciar hay que llamar a Stop() y luego a Start(). El servicio debe estar en un estado previo que tenga sentido para la operación. Por ejemplo si llamamos a Start() en un servicio que ya está iniciado se producirá una excepción.

Otra cosa que hay que notar son las llamadas a WaitForStatus(). Los métodos Start() y Stop() son no bloqueantes, así que debemos hacer esto para sincronizar nuestro código.

Obteniendo el directorio de un servicio

Si desde tu servicio accedes a archivos, la primera vez puede llegarte a sorprender que todas las rutas relativas apuntan a c:\windows\system32. Así que para obtener el working directory del servicio hay que usar AppDomain.CurrentDomain.BaseDirectory.

Para obtener el working directory desde otro proceso hay un par de maneras. Mediante Windows Management Instrumentation (WMI), o directamente desde la clave HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\NombreDelServicio del registro.

RegistryKey key = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Services\Spooler");
string servicePath = (string)key.GetValue("ImagePath");

Refactoring

El Refactoring es una práctica fundamental en el desarrollo del software, que sin embargo, se deja de lado demasiado a menudo debido a las presiones para cumplir con las fechas de entrega.

No es algo “opcional” que se puede dejar “para cuando haya tiempo”. Al contrario. Es igual a la limpieza del taller mecánico, debe realizarse constantemente, sino se empezarán a perder piezas, herramientas, y la productividad se verá afectada gravemente. Nuestro proyecto comenzará a caer víctima de la ley de la entropía.

¿Y qué es el refactoring? Muy fácil. Consiste en realizar mejoras al código sin modificar su funcionalidad externa. Es decir, sin que el usuario de nuestro código tenga que enterarse de que hubo un cambio. No consiste en introducir nuevas funciones ni arreglar errores, sólo estamos optimizando lo que ya existe.

Quizás sea mejor ilustrar con un ejemplo muy simplificado, uno de mis refactorings favoritos, proveniente del catálogo de Martin Fowler.

Reemplazar Condicionales Anidados con Guards

Es bastante frecuente encontrar código como este:

public bool AvanzarSemaforo()
{
    if(_semaforo.Estado == EstadoSemaforo.OK)
    {
        if(_semaforo.Color != ColorSemaforo.Rojo)
        {
            if(_semaforo.Color != ColorSemaforo.Amarillo)
            {
                return true;
            }
        }
    }

    return false;
}

El problema con este antipatrón que ha sido llamado “punta de flecha” [1] por la forma que toma el código, es que aumenta innecesariamente la complejidad del código, dificultando su comprensión a la hora del mantenimiento.

La solución consiste en invertir las comparaciones booleanas, colocando los casos excepcionales al principio del método en forma de guardas y a continuación el camino normal de la ejecución:

public bool AvanzarSemaforo()
{
    if(_semaforo.Estado != EstadoSemaforo.OK
       || _semaforo.Color == ColorSemaforo.Rojo
       || _semaforo.Color == ColorSemaforo.Amarillo)
    {
        return false
    }

    return true;
}

Como podemos ver, el método es ahora mucho más breve y comprensible.

Programas como Resharper automatizan este tipo de refactorings e incluso los sugieren automáticamente. Sin embargo no es difícil hacerlo de forma manual, siempre teniendo cuidado de no alterar la funcionalidad original, que es el objetivo fundamental de todo refactoring.


[1] Sean Chambers, Simone Chiaretta: 31 Days of Refactoring. pág 38. Enlace: http://lostechies.com/e-books