Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
jueves, 12 de abril de 2007
Resulta que leyendo la especificación de C# 2.0 he encontrado, en el apartado correspondiente a los tipos anulables un nuevo operador que me ha parecido de lo más interesante, para el tratamiento rápido de valores nulos, el Null Coalescing Operator (NCO) u Operador de Fusión de Nulos (traducción libre).

El operador en cuestión, expresado como ?? (dos cierres de interrogación), permite devolver un valor si no es nulo, o devolver otro valor alternativo ante la nulidad del primero. En otras palabras, un código como:
if (s!=null)
return s;
else
return "por defecto";

O también escrito de la forma, utilizando el operador ternario:

return (s!=null?s:"por defecto"); 

Quedaría, utilizando el nuevo operador de fusión, como:

return s ?? "por defecto"; 

Qué limpio, ¿no?

A primera vista puede parecer que salvo mejorar la legibilidad, no aporta demasiadas ventajas frente al operador ternario ?, pero fijaos en el siguiente código, en un método que retorna el nombre del algo cuyo Id le llega como parámetro:

string nombre = obtenerNombre(id);
if (nombre==null)
return "Desconocido";
else
return nombre;

Utilizando el operador ternario ? podríamos dejarlo en una única línea:

return obtenerNombre(id)==null?"Desconocido":obtenerNombre(id); 

Esto tiene una pega aparte de la dificultad de lectura: si el nombre del llamamos dos veces a obtenerNombre(id), lo cual podría tener efectos secundarios no deseados, o simplemente causar un problema de rendimiento. Para mejorarlo podríamos hacer esto, que mejora la legibilidad y llama sólo una vez al método, aunque es más largo de codificar, siendo muy similar al if inicial:

string p = obtenerNombre(id);
return p==null?"Desconocido":p;

Con el nuevo operador el código quedaría así de simple:

return obtenerNombre(id) ?? "Desconocido"; 

Esto se ejecuta de la siguiente forma: se llama a obtenerNombre(id) y si no es nulo se retorna el valor obtenido. Sólo si el resultado de la expresión ha sido nulo se retornaría el literal "Desconocido".

No sé a vosotros, pero a mí me ha parecido interesantísimo, y de lo más útil.
domingo, 8 de abril de 2007
Vía una entrada en Barrapunto, llego a un artículo en el País titulado "Proveedores de Internet se unen contra el correo basura" donde se recoge la noticia de que los 26 principales proveedores españoles se han puesto de acuerdo en utilizar SPF para luchar contra el spam. Lógico, teniendo en cuenta que sólo un 15% de los mensajes que pululan por sus servidores y redes son reales, lo que indica que la mayor parte de sus recursos están siendo utilizados por el lado oscuro.

SPF (Sender Policy Framework) es una especificación experimental publicada por la IETF en la RFC 4408 (abril 2006), y describe un conjunto de técnicas para evitar la utilización de dircciones de email falsas en los mensajes que circulan por la red.

Aunque creo que esto ya lo he comentado en otras ocasiones, uno de los principales problemas que hacen florecer el spam, scam, phishing, y alguna que otra atrocidad más, es la inocencia con la que fue creado el protocolo SMTP, utilizado para los envíos de mensajes de correo electrónico entre servidores. En este estándar no se establece ninguna técnica para asegurar la veracidad del remitente, por lo que cualquiera puede escribir en nombre de otro, hacerse pasar por quien desee, o simplemente inventar sobre la marcha un emisor, existente o no.

Por eso los mensajes de spam que recibimos pueden provenir incluso de personas que conocemos, pertenecientes a nuestro propio dominio, compañeros que tenemos sentados justo a nuestro lado, etc.

El SPF pretende acabar con esto, pues define un sistema de comprobación en tiempo real de la veracidad del remitente, así como del servidor desde el que se envía un mensaje, consistente, por una parte, en incluir en los registros MX de cada dominio emisor de correos una línea donde se indique la dirección IP desde la que pueden ser enviados. De esta forma, cuando otro servidor SMTP recibe un mensaje con un remitente del dominio anterior, puede consultar esos registros y comprobar si la IP desde la que está recibiendo el email está autorizada para enviarlo.

Lo vemos con un ejemplo real, por suerte (?) tengo bastantes para elegir. Hoy he recibido un correo desde la dirección finkenhoferqov0@hotmail.com aconsejándome soluciones para mis terribles problemas de erección (¿cómo se habrán enterado? :-D). Por suerte, según me indican, la mitad de los varones los sufren, el que no se consuela es porque no quiere.

Si rastreo el mensaje, veo que ha llegado a mi servidor desde la dirección IP 71.186.100.38, cuyo nombre parece ser pool-71-186-100-38.chi01.dsl-w.verizon.net. Si seguimos escarbando, podemos obtener la siguiente información de esta IP:

OrgName: Verizon Internet Services Inc.
OrgID: VRIS
Address: 1880 Campus Commons Dr
City: Reston
StateProv: VA
PostalCode: 20191
Country: US
NetRange: 71.160.0.0 - 71.191.255.255
CIDR: 71.160.0.0/11
NetName: VIS-BLOCK

Y aunque las herramientas de geoposicionamiento de direcciones IP no son el colmo de la precisión, podríamos incluso atrevernos a conjeturar dónde se encuentra el spammer, en Washington.

A la vista de estos datos, se trata de un equipo que utiliza una dirección IP dinámica, conectado a través de una DSL que, de forma voluntaria o no, está enviando spam, cual poseso, al resto del mundo. Está claro que ningún spammer decente dejaría ver todos sus datos así, por lo que, o bien se trata de un PC secuestrado (zombie), o bien toda la información que deja ver es falsa.

En cualquier caso, está claro que no se trata de Hotmail, de donde era, como recordaréis, el remitente del mensaje.

Si tanto mi servidor como Hotmail utilizaran SPF, se habría rechazado el mensaje de forma directa. Al recibir la dirección del remitente, habría acudido a los registros MX de Hotmail para averiguar si la IP desde la que se ha producido la conexión es válida para envíos @hotmail.com, y, obviamente, de esta comprobación siempre se determinaría que el emisor es inválido.

Todo esto está muy bien, personalmente creo bastante en este tipo de soluciones colaborativas y de consenso para luchar contra el spam, más incluso que en las de análisis de contenido que, como ya he comentado en posts anteriores, tienen bastantes dificultades para garantizar el filtrado correcto dada la gran variedad de trucos utilizados.

El problema es precisamente su implementación en la práctica, y eso que en el caso del SPF no es especialmente compleja, todo lo contrario. Está claro que si todos los ISP del mundo utilizaran este método, el spam, fishing y en general cualquier historia basada en la utilización de remitentes falsos tendrían los días contados... al menos en la forma en que hoy los conocemos, claro.
miércoles, 4 de abril de 2007
Indiscutiblemente, los nulos son una de las peores pesadillas del desarrollador, y causa de una gran cantidad de errores en tiempo de ejecución de aplicaciones. La falta de herramientas para tratarlos apropiadamente hacen a veces compleja la convivencia con la oveja negra del rebaño de los valores posibles a aplicar a una variable.

Y es que con poco que hayáis desarrollado, seguro que alguna vez habéis encontrado la difícil tarea de asignar un valor nulo a una variable de tipo valor (int, float, char...) donde no encaja más que usando artimañas difíciles de realizar, trazar y documentar. Por ejemplo, dada una clase Persona con un propiedad de tipo entero llamada Edad, ¿qué ocurre si cargamos un objeto de dicha clase desde una base de datos si en ésta el campo no era obligatorio?

A priori, fácil: al leer de la base de datos comprobamos si es nulo, y en ese caso le asignamos a la propiedad de la clase el valor -1. Buen parche, sin duda.

Sin embargo, optar de forma general por esta idea presenta varios inconvenientes. En primer lugar, para ser fieles a la realidad, si quisiéramos almacenar de nuevo este objeto en la base de datos, habría que realizar el cambio inverso, es decir, comprobar si la edad es -1 y en ese caso guardar en el campo un nulo.

En segundo lugar, fijaos que estamos llevando a la clase artificios que no tienen sentido en el dominio del problema a resolver, en la entidad a la que representa. Mirándolo un poco desde lejos, ¿qué sentido tiene una edad negativa en una entidad Persona? Ninguno.

En tercer lugar, existe un problema de coherencia en las consultas. Si tengo en memoria una colección de personas (realizada, por ejemplo usando generics ;-)) y quiero conocer las que tienen edad definida, debería comprobar por cada elemento si su propiedad Edad vale -1; sin embargo, al realizar la misma consulta en la base de datos debería preguntar por el valor NULL sobre el campo correspondiente.

Cierto es que podríamos llevar también a la base de datos el concepto "-1 significa nulo" en el campo Edad, pero... ¿no estaríamos salpicando a la estructura de datos con una particularidad (y limitación) del lenguaje de programación utilizado? Otra idea bizarra podría ser introducir la edad en un string y problema solucionado: las cadenas, al ser un tipo referencia pueden contener nulos sin problemas, lo que pasa es que las ordenaciones saldrían regular ;-)

Por último, ¿y si en vez de la edad, donde claramente no pueden existir negativos se tratase de una clase CuentaCorriente y su propiedad SaldoActual? Aquí sí que se ve claramente que esto no es una solución válida.

Soluciones, aparte de las comentada, hay para todos los gustos. Se podría, por ejemplo, añadir una propiedad booleana paralela que indicara si el campo Edad es nulo (un trabajazo extra, sobre todo sin son varias las propiedades que pueden tener estos valores), o encapsular la edad dentro de una clase que incorporara la lógica de tratamiento de este nulo.

En cualquier caso, las soluciones posibles son trabajosas, a veces complejas, y sobre todo, demasiado artificiales para tratarse de algo tan cotidiano como es un simple campo nulo.

Conscientes de ello, los diseñadores de C# han tenido en cuenta en su versión 2.0 una interesante característica: los nullables types, o tipos anulables (traducción libre), un mecanismo que permite introducir el nulo en nuestras vidas de forma no traumática.

La siguiente línea generaba un error en compilación, que decía, y no le faltaba razón, que "no se puede convertir null en 'int' porque es un tipo de valor":
int s = null; 

Ahora, en C# 2.0, es posible hacer lo siguiente:

int? s;
s = null;
s = 1;

Obsérvese la interrogación junto al tipo, que es el indicativo de que la variable s es de tipo entero, pero que admite también un valor nulo.

Por dentro, esto funciona de la siguiente forma: int? es un alias del tipo genérico System.Nullable<int>. De hecho, podríamos usar indistintamente cualquiera de las dos formas de expresarlo. Internamente se crea una estructura con dos propiedades de sólo lectura: HasValue, que retorna si la variable en cuestión tiene valor, y Value, que contiene el valor en sí.

Se entiende que una variable con HasValue igual a false contiene el valor nulo, y si intentamos acceder al mismo a través de Value, se lanzará una excepción.

Sin embargo, la principal ventaja que tienen es que se utilizan igual que si fuera un tipo valor tradicional. Los tipos nullables se comportan prácticamente como ellos y ofrecen los mismos operadores, aunque hay que tener en cuenta sus particularidades, como se aprecia en el siguiente código:

int? a = 1;
int? b = 2;
int? intNulo = null;
bool? si = true;
bool? no = false;
bool? niSiNiNo = null;
Console.WriteLine(a + b); // 3
Console.WriteLine(a + intNulo); // Nada, es nulo
Console.WriteLine(a * intNulo); // Nada, es nulo
Console.WriteLine(si & no); // false
Console.WriteLine(si & no); // true
Console.WriteLine(si & niSiNiNo); // Nada, es nulo
Console.WriteLine(no & niSiNiNo); // false
Console.WriteLine(si | niSiNiNo); // true
Console.WriteLine(no | niSiNiNo); // Nada, es nulo

Curioso en los booleanos, en los que el valor nulo se puede interpretar como un quizás. De esta forma es fácil prever el resultado de una operación lógica: "Verdad ó Quizás" resuelve como verdadero, ó "Falso y Quizás" es falso, por ejemplo.
sábado, 31 de marzo de 2007
Una particularidad interesante en la declaración de los generics en C# es la posibilidad de establecer limitaciones en los tipos utilizados como parámetros de las plantillas. Paradójico pero cierto, el propio lenguaje facilita la creación de clase genéricas que no lo sean tanto.

Para verle el sentido a esto, utilicemos el siguiente ejemplo, aparentemente correcto:

public class Seleccionador<Tipo>
{
public Tipo Mayor(Tipo x, Tipo y)
{
int result = ((IComparable)x).CompareTo(y);
if (result > 0)
return x;
else
return y;
}
}

Se trata de una clase genérica abierta, cuya única operación (Mayor(...)) permite obtener el objeto mayor de los dos que le pasemos como parámetros. El criterio comparativo lo pondrá la propia clase sobre la que se instancie la plantilla: los enteros serán según su valor, las cadenas según su orden alfabético, etc.

A continuación creamos dos instancias partiendo de la plantilla anterior, y concretando el generic a los tipos que nos hacen falta:

Seleccionador<int> selInt = new Seleccionador<int>();
Seleccionador<string> selStr = new Seleccionador<string>();

Estas dos instanciaciones son totalmente correctas, ¿verdad? Si después de ellas usamos el siguiente código:

Console.WriteLine(selInt.Mayor(3, 5));
Console.WriteLine(selStr.Mayor("X", "M"));

Obtendremos por consola un 5 y una X. Todo correcto, aparece, para cada llamada, la conversión a cadena del objeto mayor de los dos que le hayamos enviado como parámetros.

El problema aparece cuando instanciamos la clase genérica para un tipo que no implementa IComparable, que se utiliza en el método Mayor para determinar el objeto mayor de ambos. En este caso, se lanza una excepción en ejecución indicando que el cast hacia IComparable no puede ser realizado, abortando el proceso. Por ejemplo:

public class MiClase // No es comparable
{
}
[...]
Seleccionador<MiClase> sel = new Seleccionador<MiClase>();
MiClase x1 = new MiClase();
MiClase x2 = new MiClase();
Console.WriteLine(selString.Mayor(x1, x2)); // Excepción,
// no son
// comparables!


Una posible solución sería, antes del cast a IComparable en el método Mayor(), hacer una comprobación de tipos y realizar el cast sólo si es posible, pero, ¿y qué hacemos en caso contrario? ¿devolver un nulo? La pregunta no creo que tenga una respuesta sencilla, puesto que en cualquier caso, se estarían intentado comparar dos objetos que no pueden ser comparados.

La solución óptima, como casi siempre, es controlar en tiempo de compilación lo que podría ser una fuente de problemas en tiempo de ejecución. La especificación de C# 2.0 incluye la posibilidad de definir constraints o restricciones en la declaración de la clase genérica, limitando los tipos con los que el generic podrá ser instanciado. Y, además, de forma bastante simple, nada más que añadir en la declaración de la clase Seleccionador la siguiente cláusula where:


public class Seleccionador
where Tipo: IComparable // !Sólo comparables!
{
public Tipo Mayor(Tipo x, Tipo y)
[...]

Justo a partir de ese momento, el intento de instanciar el Seleccionador utilizando MiClase o cualquier otro tipo que no implemente IComparable, generará un error en tiempo de compilación.
martes, 27 de marzo de 2007
En un post anterior comentaba la idoneidad de un GUID para identificar de forma única e inequívoca elementos como podían ser los registros de una tabla de una base de datos.

Tradicionalmente utilizo claves artificiales, más concretamente autonuméricos, para casi todo. Incluso a veces más de la cuenta, en esas ocasiones en las que es antinatural no usar claves naturales, valga la rebuscada frase. Sin embargo, la facilidad con la que se manejan estos identificadores, la maximización del rendimiento y espacio ocupado hacen que olvide cualquier otro criterio y me incline hacia esos números consecutivos que automáticamente el sistema genera para nosotros.

Sin embargo, cualquiera que haya usado autonuméricos para diseñar una aplicación medianamente compleja se habrá topado con una serie de inconvenientes como:

  • la imposibilidad para predecir su valor. En otras palabras, hay veces que debemos dividir las sentencias o interfaces de introducción de datos en tablas enlazadas en varios pasos, con objeto de obtener los autonuméricos asignados en algunas de ellas y poder establecerlos en las tablas que vinculan a éstas.
  • no son consecutivos, determinados acontecimientos como la cancelación de una transacción pueden hacer que aparezcan huecos en las asignaciones, lo que puede provocar el efecto pánico de borrado accidental.
  • son problemáticos a la hora de volcar información entre tablas o bases de datos distintas. Por ejemplo, para exportar una serie de tablas relacionadas mediante IDs de una base de datos a otra, suele ser necesario la creación de scripts o incluso aplicaciones relativamente complejas que traduzcan los identificadores de la base de datos origen a los asignados en la de destino, manteniendo las relaciones intactas. Labor de monos, vaya.
  • en el mismo escenario anterior, puede ocurrir que a la hora de realizar fusiones entre tablas un identificador concreto esté ocupado en ambas, lo que hace necesario de nuevo la creación de aplicaciones de volcado.

A estos casos, seguro que habituales, hay que añadir otros escenarios más complejos y menos cotidianos, como los relativos a bases de datos distribuidas, sincronizaciones o replicaciones.

Los GUID pueden solucionar en parte estos problemas, puesto que ofrecen las ventajas de la unicidad, ampliada más allá del alcance de la simple tabla, a la vez que permiten una manipulación más directa por parte del usuario, es decir:

  • los GUID son únicos de verdad, y de forma universal. Vamos, que no se van a repetir (recordemos que existen más de 1038 valores distintos) ni siquiera en tablas distintas, ni en bases de datos diferentes. Ideal para entornos distribuidos, mezclas de datos, volcados, etc.
  • pueden ser generados por aplicaciones, no es necesario esperar a crear un registro para obtener el GUID que será asignado a un registro; podemos generarlo de forma anticipada desde nuestra aplicación y utilizarlo a nuestro antojo.

Pero como casi todo en la vida, esto tiene su precio. Los principales inconvenientes son el mayor consumo de espacio, con la consiguiente merma del rendimiento en consultas y actualizaciones, dispersión de los valores creados (problemático en el uso de clústers o agrupaciones por valores) y, para mi gusto, casi lo peor de todo: la dificultad para depurar (¿quién ve práctico buscar en una tabla diciendo select * from clientes where id={BAE7DF4-DDF-3RG-5TY3E3RF456AS10} en vez de select * from clientes where id=17?).

domingo, 25 de marzo de 2007

El tipo de datos GUID existe, y es una opción a la hora de crear un campo, en sistemas gestores de bases de datos como SQL Server, pero, cosas de la ignorancia, nunca había pensado que pudieran ser una opción razonable para identificar una fila en una tabla.

Sin embargo, recientemente me he topado con una aplicación en la que, observando su base de datos, se utilizaban como identificadores únicos de registro valores de tipo GUID, lo cual me ha llamado la atención y me ha llevado a profundizar un poco en el tema.

Pero, ¿qué es un GUID? Según su definición formal, se trata de una implementación del estándar UUID (Universally Unique Identifier, Identificador Unico Universal), promovido por la OSF (Open Software Foundation), que propone un método de identificación única ideal para entornos distribuidos en los que no existe un coordinador central. En otras palabras, permite que nodos dispersos puedan establecer identificadores de forma única a entidades o unidades de información de forma que se pueda asegurar razonablemente que no van a existir duplicidades en los mismos, y todo ello sin necesidad de crear un punto común de comprobaciones.

A efectos prácticos, un GUID es un número de 16 bytes, o 128 bits, escrito habitualmente en forma de chorizo hexadecimal del tipo "550e8400-e29b-41d4-a716-446655440000", generado utilizando algoritmos pseudoaleatorios.

Pero diréis que un algoritmo pseudoaleatorio no garantiza su unicidad, y es cierto. Sin embargo, 16 bytes ofrecen un universo de 2128 valores posibles (del orden de 3·1038), lo que hace prácticamente imposible que dos GUID se repitan.

Esto los hace especialmente atractivos como identificadores únicos: registros en bases de datos, identificación de componentes (como COM o DCOM), registro de documentos, artículos, posts y un largo etcétera.