Weblogs Código

Picando Código

Nuevo diseño en Picando Código

noviembre 19, 2019 11:00

Si visitan el blog y no lo leen desde algún otro sitio o un lector RSS (no, ¡no murieron con Google Reader!), notarán que cambié radicalmente el diseño. Las últimas veces que he creado un tema de WordPress nuevo para el blog, siempre lo escribo desde cero. Esta vez no fue la excepción. Arranco con un archivo index.html  enlazado con un style.css y ahí voy armando. Después voy creando los archivos necesarios de un tema en una máquina virtual con WordPress (como Varying Vagrant Vagrants) y ahí va tomando forma todo.

El archivo html estático me da la oportunidad de probar cosas sin depender de todo un WordPress como para ir armando la idea de lo que quiero lograr con el diseño final. Muchas veces uso un cuaderno y lápices o lapiceras para hacer borradores, y después basarme en eso. Pero no fue el caso ésta vez, arranqué a escribir HTML y CSS y lo fui armando en la pantalla mismo. Decidí usar Bulma, un framework CSS que vengo usando en varios proyectos personales últimamente y me ha resultado bastante bueno. Es relativamente liviano y bastante simple de usar y trae de todo.

El último “rediseño” fue en 2014:
Nuevo diseño en Picando Código (2014)

Que a su vez fue más bien un reciclado del diseño nuevo del 2013:
Nuevo diseño en Picando Código (2013)

El diseño anterior desperdiciaba bastante espacio horizontal, pero lo estoy aprovechando más ahora. También mejoró bastante el uso en pantallas pequeñas. Si bien ya lo tenía en cuenta en diseños anteriores, creo que ésta es la mejor versión por el momento. De esa manera también hay más márgenes y paddings por todos lados. Ésto se me hizo relativamente sencillo al usar Bulma.

Notarán un cambio en los colores del sitio. Cuando arranqué el blog allá por 2007 y lo moví a un WordPress propio, solía usar paletas de colores bastante oscuras, como emulando una línea de comandos con fondos negros o grises. Gracias al feedback de varios lectores terminé optando por un color de fondo claro en el rediseño anterior. Pero esta vez decidí volver al fondo oscuro por una simple razón: a mí me gusta más. Me inspiré bastante en los temas que he venido usando en Emacs, y estoy bastante contento con el resultado. Me parece que tiene más un toque personal que antes. Si prefieren un fondo claro para leer el blog, recomiendo ampliamente la Vista de lectura de Firefox. Nos provee el contenido de un sitio o  blog sin formato y un lector de pantalla también.

Para tener en el recuerdo, cómo se veía el sitio hasta éste cambio de imagen:

Diseño Picando Código 2019

Diseño Picando Código 2019

Espero que les guste, yo quedé bastante conforme. Si notan algún defecto o error que se me haya pasado de la migración de tema, no duden en comentarlo, estaré agradecido.

» Leer más, comentarios, etc...

Fixed Buffer

Novedades de C# 8: Pattern Matching

noviembre 19, 2019 09:00

Imagen ornamental para la entrada de C#8 pattern matching

En la última entrada hablamos del nuevo IAsyncEnumerable, y hoy es el turno de ver entra novedad que nos trae c#8, el ‘pattern matching’. Esta es una característica muy interesante y muy útil que nos puede ahorrar complejidad a la hora de escribir el código, y un buen número de líneas dentro de él.

Por si aún no conoces cómo funciona el pattern matching en C# o no te suena de que estoy hablando, debes saber que es una característica que se introdujo en C# 7 y que nos permitía reconocer objetos tipados dentro de otro tipo y castear directamente al tipo si se cumple. Podríamos decir que es una especia de ‘is’ seguido de un ‘as’:

//C# 6
if (dato is string)
{
    var valor = dato as string;
    Console.WriteLine("Dato es un string y su valor es: " + valor);
}

//C#7
if (dato is string valor)
{
    Console.WriteLine("Dato es un string y su valor es: " + valor);
}

Pattern matching en C# 8

Con la llegada de C# 8, esto ha mejorado aun más, y ha añadido algunas opciones bastante interesantes:

  • Expresiones switch
  • Switch con propiedades
  • Switch con tuplas
  • Switch posicional

Vamos a verlos en profundidad:

Expresiones switch

Con los cambios de C# 7 podemos utilizar un pattern matching como el que hemos visto arriba en un ‘switch’, pero gracias a esta nueva característica de C# 8, vamos a poder crear expresiones para los switch. Las expresiones reemplazan el ‘case :’ y el ‘break’ por ‘=>’ y el ‘defaul :’ por ‘_’, además, el objeto sobre el que vamos a hacer el switch va delante de este (y no detrás como hasta ahora) para indicar que se trata de una expresión. Vamos a ver un ejemplo:

//Version en C# 7
public static char GetLetraInstruccion(int code)
{
    switch (code)
    {
        case 1:
            return 'a';
        case 2:
            return 'b';
        default:
            return '_';
    }
}
//Version en C# 8
public static char GetLetraExpresion(int code)
{
    return code switch
    {
        1 => 'a',
        2 => 'b',    
        _ => '_' // Default
    };
}

//Versión en C# 8 con arrow functions
public static char GetLetraExpresion2(int code) => code switch
{
    1 => 'a',
    2 => 'b',
    _ => '_' //Default
};

Como puedes comprobar, esta versión de C# y su pattern matching junto a las arrow fuctions nos deja un código mucho más limpio.

Switch con propiedades

Utilizando el pattern matching de C# 7, podíamos utilizar las propiedades del objeto para aplicar condiciones extra en los switch:

public class Direccion
{
    public string Pais { get; set; }
    public string Comunidad { get; set; }
}
//...
[Flags]
public enum Idiomas
{
    Castellano = 1,
    Gallego = 2,
    Euskera = 4,
    Catalan = 8,
}
//...
//Version en C# 7
public static Idiomas GetIdiomasC7(Direccion direccion)
{
    switch (direccion)
    {
        case Direccion dir when dir.Pais == "ES" && dir.Comunidad == "Galicia":
            return Idiomas.Castellano | Idiomas.Gallego;
        case Direccion dir when dir.Pais == "ES" && dir.Comunidad == "Pais Vasco":
            return Idiomas.Castellano | Idiomas.Euskera;
        case Direccion dir when dir.Pais == "ES" && dir.Comunidad == "Cataluña":
            return Idiomas.Castellano | Idiomas.Catalan;
        case Direccion dir when dir.Pais == "ES":
            return Idiomas.Castellano;
        default:
            return Idiomas.Ingles;
    }
}

Esto ha cambiado gracias a C# 8 y su pattern matching, gracias al reconocimiento de propiedades y a las expresiones switch, vamos a poder dejar un código más limpio accediendo directamente a sus propiedades:

public static Idiomas GetIdiomasC8(Direccion direccion)
{
    return direccion switch
    {
        { Pais: "ES", Comunidad: "Galicia" } => Idiomas.Castellano | Idiomas.Gallego,
        { Pais: "ES", Comunidad: "Pais Vasco" } => Idiomas.Castellano | Idiomas.Euskera,
        { Pais: "ES", Comunidad: "Cataluña" } => Idiomas.Castellano | Idiomas.Catalan,
        { Pais: "ES" } => Idiomas.Castellano,
        _ => Idiomas.Ingles //Default
    };
}

Con esto que hemos visto, el código queda mucho más limpio y claro ¿verdad? Hasta este momento solo hemos visto cambios que son azúcar sintáctico, son útiles, son bonitos, pero no aportan nada nuevo respecto a lo que ya teníamos disponible en la versión anterior del C#. Entonces, ¿qué trae nuevo el pattern matching de C# 8? ¿Hay algo que realmente sea nuevo? ¡Sí!, vamos con ello.

Switch con tuplas

Hasta ahora, un switch era un flujo de control que se operaba solo sobre una variable. Si necesitábamos conjugar dos o más variables solo nos quedaba utilizar un if, pero if else tiene un problema de rendimiento cuando aumenta mucho de tamaño ya que para llegar al último tiene que ir comprobando todos los anteriores, cosa que con un switch no pasa.

Como dato, aproximadamente a partir de 5 posibles caminos dentro de un if else, ya es más ‘barato’ en términos de CPU el utilizar un switch que siempre va a tardar lo mismo independientemente del número de posibles caminos y cual sea el elegido. Da igual que sea el primero o el último.

Precisamente esta nueva característica una de las dos que se han introducido en C# 8 y su pattern matching, permitiéndonos utilizar una tupla como objeto para el switch, y una tupla como condición, fíjate que limpito queda ahora un «Piedra papel o tijera, lagarto Spock«:

public static string PiedraPapelTijeraLagartoSpock(string playerA, string playerB)
{
    return (playerA, playerB) switch
    {
        ("piedra", "papel") => "Gana papel",
        ("piedra", "tijera") => "Gana piedra",
        ("piedra", "lagarto") => "Gana piedra",
        ("piedra", "spock") => "Gana Spock",
        ("papel", "piedra") => "Gana papel",
        ("papel", "tijera") => "Gana tijera",
        ("papel", "lagarto") => "Gana lagarto",
        ("papel", "spock") => "Gana papel",
        ("tijera", "piedra") => "Gana piedra.",
        ("tijera", "papel") => "Gana tijera",
        ("tijera", "lagarto") => "Gana tijera.",
        ("tijera", "spock") => "Gana Spock",
        ("lagarto", "piedra") => "Gana piedra",
        ("lagarto", "papel") => "Gana lagarto",
        ("lagarto", "tijera") => "Gana tijero",
        ("lagarto", "spock") => "Gana lagarto",
        ("spock", "piedra") => "Gana Spock.",
        ("spock", "papel") => "Gana papel",
        ("spock", "tijera") => "Gana Spock.",
        ("spock", "lagarto") => "Gana lagarto",
        (_, _) => "empate" //Default
    };
}

En el caso anterior, como en todos los que se usen expresiones switch, ‘_’ significa cualquier valor, por lo que puedes tener errores porque exista algún código que es inalcanzable si las pones en el orden erróneo (por ejemplo {_,_} el primero de todos).

Vale, ya hemos visto una utilidad que realmente es nueva, pero… ¿Quedan más? Por supuesto, vamos a por la última.

Switch posicional

Una característica de C# 7 era la deconstrucción de clases. Esta característica nos va a permitir si tenemos un método Deconstruct en la clase que sea accesible, obtener una tupla con los valores que generemos con la deconstrucción. Por ejemplo:

public class Posicion
{
    private readonly double _x;
    private readonly double _y;

    public Posicion(double x, double y)
    {
        _x = x;
        _y = y;
    }

    public void Deconstruct(out double x, out double y)
    {
        x= _x;
        y= _y;
    }
}

//
var posicion = new Posicion(10,20);
var (x, y) = posicion;

En el caso anterior veíamos que podemos trabajar con tuplas perfectamente, y esto es una tupla… ¿Dónde está lo nuevo?

Precisamente el caso anterior trabajábamos con diferentes casuísticas de tuplas, pero ahora vamos a poder trabajar con los valores de la tupla para hacer la condición de nuestro switch, por ejemplo:

public enum Cuadrantes
{
    SuperiorDerecho,
    SuperiorIzquierdo,
    InferiorIzquierdo,
    InferiorDerecho,
    Origen
}

public static Cuadrantes GetCuadrante(Posicion posicion)
{
    return posicion switch
    {
        var (x, y) when x > 0 && y > 0 => Cuadrantes.SuperiorDerecho,
        var (x, y) when x < 0 && y > 0 => Cuadrantes.SuperiorIzquierdo,
        var (x, y) when x < 0 && y < 0 => Cuadrantes.InferiorIzquierdo,
        var (x, y) when x > 0 && y < 0 => Cuadrantes.InferiorDerecho,
        _ => Cuadrantes.Origen
    };
}

Si te detienes en el código anterior, puedes comprobar que estamos haciendo un switch deconstruyendo el objeto, y después filtrándolo por sus diferentes propiedades deconstruidas.

En realidad, esta nueva característica del pattern matching de C# 8 es igual que al anterior ya que ambas trabajan con tuplas (de hecho, en el caso anterior también podemos utilizar variables y usar when como en ese), pero aportándonos algo de azúcar sintáctico para ahorrarnos tener que añadir un paso extra para obtener la tupla.

Puede que a simple vista parezca algo raro y poco útil, es la reacción habitual a los cambios. Una vez que te acostumbres a utilizar las expresiones switch y el resto de las características, no vas a querer dejar de usarlas ya que deja un código mucho más limpio y claro.

Si tienes cualquier duda o comentario, no te lo pienses y adelante!!

**La entrada Novedades de C# 8: Pattern Matching se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Variable not found

Novedades en la evaluación en cliente de EF Core 3.0

noviembre 19, 2019 07:05

Entity Framework CoreHace algunos meses hablábamos del peligro que suponía la la evaluación en cliente de Entity Framework Core, una característica que, por defecto, venía activada de serie. En el post explicábamos los problemas que podían dar, cómo podíamos detectar su uso e incluso impedirlo para evitar males mayores.

Pues bien, esto ha cambiado a partir de la versión 3.0, en mi opinión, hacia una dirección bastante más correcta. Veamos en qué consisten estos cambios.

El comportamiento antes de EF Core 3.0

Como sabemos, en versiones anteriores a la 3.0 de Entity Framework Core, una consulta como la siguiente funcionaba perfectamente:
// Consulta:
public List<Friend> GetAdults()
{
var adults = ctx.Friends.Where(f => IsAdult(f));
return adults.ToList();
}

private static bool IsAdult(Friend f) => f.Birthdate < DateTime.Now.AddYears(-18);
El problema que tenía ese código es que, dado que la expresión IsAdult() no tenía sentido para el proveedor de datos, ésta era evaluada en cliente para todas las filas obtenidas en la consulta, que en
este caso, dado que no hay otros criterios de filtrado, serían absolutamente todas.

El framework ejecutaría la consulta, recorrería todas las filas de la tabla, materializaría las entidades y descartaría las que no cumplieran las condiciones, pero el destrozo a nivel de rendimiento ya se habría hecho.

El comportamiento a partir de EF Core 3.0

A partir de Entity Framework Core 3.0, la ejecución de una consulta como la mostrada anteriormente resultará en una excepción InvalidOperationException como la mostrada en la siguiente captura de pantalla:

Excepción lanzada
System.InvalidOperationException: 'The LINQ expression 'Where<Friend>( source: DbSet<Friend>, predicate: (f) => Program.IsAdult(f))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
El error indica que la expresión IsAdult() no puede ser traducida al "idioma" del almacén de datos (por ejemplo, no existe algo como IsAdult() en SQL), y nos invita a replantear la consulta de forma que el motor pueda entenderla, o bien mover esa condición al lado cliente para que pueda ser evaluada con éxito.

Hay muchos escenarios en los que no será posible aplicar la primera solución, aunque en nuestro caso podría ser muy sencillo porque podemos convertirlo a operaciones soportadas por el almacén de datos:
// Sí, no es muy exacto, pero ya me entendéis... ;)
var adults = ctx.Friends.Where(f => DateTime.Today.Year - f.Birthdate.Year >= 18);
O también podríamos utilizar las extensiones DbFunctions como se muestra a continuación:
var adults = ctx.Friends.Where(f =>
EF.Functions.DateDiffYear(f.Birthdate, DateTime.Now) >= 18
);
La segunda sugerencia es más violenta, pues implica la lectura de todas las entidades y su posterior cribado. Vaya, algo similar a lo que se hacía con versiones anterior a EF Core 3.0, lo que ocurre es que seríamos nosotros los que explícitamente se lo estaríamos indicando:
var adults = ctx.Friends
.AsEnumerable() // La consulta es ejecutada aquí
.Where(f => IsAdult(f)); // El filtro se aplica sobre las entidades materializadas
Entonces, ¿esto significa que en las consultas no podemos incluir ningún tipo de expresiones evaluadas en cliente? Pues no. Hay un escenario en el que sí pueden ser incluidas, y puede ser bastante útil: las expresiones en proyecciones finales.

Por ejemplo, imaginad que queremos obtener el nombre de nuestros amigos que cumplan una condición determinada, junto con un booleano indicando si es mayor de edad. La consulta podría ser como la siguiente:
public class FriendServices
{
public void ShowSomeFriends()
{
using var ctx = new FriendsContext();
var query = ctx.Friends
.Where(f => f.Name.StartsWith("J"))
.Select(f=> new { Name=f.Name, IsAdult = IsAdult(f) });

foreach (var friend in query)
{
Console.WriteLine(friend.Name + " " + friend.IsAdult);
}
}

private static bool IsAdult(Friend f) => f.Birthdate < DateTime.Now.AddYears(-18);
}
Aquí son importantes dos detalles que conviene remarcar.

En primer lugar, la expresión en cliente sólo podrá ser utilizada si se trata de la proyección final. Tiene bastante sentido: esta proyección se realiza en el momento de la materialización a objetos del CLR, por lo que ya nos encontramos en el lado cliente, y ya podemos utilizar expresiones .NET.

Sin embargo, dado que las proyecciones intermedias son resueltas en el almacén de datos, no sería posible utilizar expresiones evaluables únicamente en cliente. Por esta razón, el siguiente ejemplo fallaría en tiempo de ejecución:
var query = ctx.Friends
.Select(f => new {f.Name, f.Birthdate, IsAdult = IsAdult(f)})
.Where(f=>f.IsAdult)
...
Otro aspecto importante a tener en cuenta es que el árbol de expresión introducirá referencias hacia los objetos o métodos incluidos en la expresión cliente, y esto puede conducir a sutiles memory leaks. Por ejemplo, si en el caso anterior el método IsAdult() fuera de instancia, el árbol de expresión contendría una referencia a dicho método y, por tanto, a la instancia de FriendServices que lo aloja.

De hecho, EF Core detectará esta circunstancia y hará que la consulta rompa en ejecución por este motivo:

Excepción InvalidOperationException por posible memory leak
System.InvalidOperationException: 'Client projection contains reference to constant expression of type: MyConsoleApp.FriendServices. This could potentially cause memory leak.'
Publicado en Variable not found.

» Leer más, comentarios, etc...

Picando Código

Respects Your Freedom: Nueva tienda de la FSF de hardware 100% Software Libre

noviembre 18, 2019 11:00

Desde hace un tiempo la Free Software Foundation tiene un programa de certificación: Respects Your Freedom (RYF). El programa certifica a fabricantes o vendedores que venden hardware que respeta los derechos de sus usuarios y cuenta exclusivamente con software libre. Para ser parte del programa, se debe pasar por un proceso riguroso donde la FSF analiza cada aspecto de la experiencia de usuario: desde la compra inicial a flashear versiones modificadas del firmware. El usuario no debería interactuar con software o documentación no libre en ningún momento.

El programa se lanzó en 2010 y ha tenido bastante crecimiento. En 2012, existía una página única en el sitio web de la Free Software Foundation, con un sólo comerciante vendiendo un dispositivo con la certificación. Pero creció al punto en que hoy hay 8 comerciantes con casi 50 certificaciones. Por eso, se creó un nuevo sitio web para hacer más fácil la búsqueda de dispositivos que cumplan con las condiciones para ser parte de RYF. De esta forma va a ser más fácil gestionar los productos a medida que se sigan agregando dispositivos libres. El sitio web se encuentra en: https://ryf.fsf.org

Respects Your Freedom

Podemos navegar la tienda para encontrar productos por tipo y vendedor, y ver las certificaciones más recientes. Cada dispositivo tiene su página, la cual dirige a el anuncio de la certificación, la fecha, y un enlace al sitio del comerciante para poder comprar el producto. Hay impresoras 3D, Laptops, routers y más. Con suerte eventualmente veremos teléfonos móviles además de más modelos de computadoras.

Mientras los dispositivos de uso masivo tienden cada vez más a la invasión de privacidad y respetan cada vez menos la libertad de sus usuarios, es importante que existan alternativas y activismo por parte de fundaciones como la FSF. Su simple existencia ayuda a combatir este presente distópico en el que vivimos.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 380

noviembre 18, 2019 07:27

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Picando Código

¿Estoy muy viejo para Debian?

noviembre 15, 2019 07:00

Tengo una Dell XPS 13 que obtuve en mi trabajo anterior al cambiar a una laptop más nueva. La laptop venía con Ubuntu de fábrica (18.04 si la memoria no me falla). Creo que lo único que le había llegado a actualizar era Ubuntu 18.10. Siendo mi computadora de uso diario en el trabajo, nunca consideré cambiarle la distribución GNU/Linux. Ubuntu es de las más populares, particularmente a la hora de herramientas de desarrollo. Es fácil encontrar software en los repositorios, o encontrar repositorios de terceros para instalar lo que necesitemos. Y el hardware de la laptop es 100% compatible con Ubuntu: esto me da a entender que probablemente sea igual con todas las distros mientras permita el uso de firmware privativo.

Gilbert Kerr y pingüino

Cuestión que al haber dejado mi trabajo anterior, llegó el momento de formatearla y darle una nueva vida como laptop personal. Si bien ya era mi laptop personal, tenía montón de software y código del trabajo que ya no necesitaba. Así que entre esas cosas me decidí a probar Debian testing, distribución que no he usado en un buen tiempo. Descargué el ISO booteable, probé la versión Live con KDE, y me gustó mucho. Pasé el ISO de instalación a USB y me dispuse a instalar. El instalador gráfico es un viejo conocido, así que con alegría y esperanza empecé a dar “Continuar” a cada paso. Hasta que finalmente llegó el paso ese que complica… Primero, no logré conectarme a internet porque no tenía el firmware privativo de la tarjeta de red. Conectar por cable no era una opción porque la laptop no tiene conexión ethernet. En otra laptop descargué el firmware a otro pendrive, lo inserté en la Dell XPS 13, puse para que lo buscara, y nada. Algún error con diferencias en los módulos de kernel, y hasta ahí llegué.

Seguramente con algo más de paciencia podría haber buscar una solución a este problema e instalar finalmente Debian. Pero he aquí el problema, ¡ya no tengo esa paciencia! Sé que con un ISO de Ubuntu tengo el sistema instalado de cero y funcionando en unos pocos minutos. Ya está, me convertí en una de esas personas que prefieren la comodidad de Ubuntu al poder que te otorga Debian. Ya tuve que “aprender” Linux durante mis primeros años ahora sólo lo quiero usar. Me convertí en eso que tanto odiaba y creía que nunca me iba a convertir…

Usé Debian y ArchLinux la mayor parte de mi tiempo con Linux. Esto de Ubuntu es un evento bastante reciente, pero es demasiado conveniente. Conclusión: tengo un hermoso Ubuntu 19.10 nuevo instalado en mi Dell XPS 13. Por ahora pienso usar eso, aunque no descarto probar otra distro en otro momento con más tiempo y paciencia. Tengo todo particionado como a mí me gusta, y la laptop está en un estado nuevo así que no perdería demasiado tiempo dejándola a gusto.

No creo que esté demasiado viejo realmente para usar Debian, ArchLinux, o cualquier otra distro que requiera más trabajo. De hecho lo que recuerdo de las últimas veces que instalé Debian es que era igual de fácil que instalar Ubuntu. El tema es siempre el hardware que complica con el software privativo y los firmwares y las compatibilidades. Pero me pareció interesante compartir por acá esta última experiencia. Eventualmente probaré alguna otra distro. Y ya comentaré en el blog cómo me fue. Pero por ahora, sigo con Ubuntu 😱

Foto: El escocés Gilbert Kerr, parte de la tripulación de la nave “Scotia” en una expedición a la Antártida, 1902-1904

» Leer más, comentarios, etc...

Blog Bitix

Rotar los archivos de trazas con log4j por fecha o tamaño

noviembre 15, 2019 04:00

La librería log4j 2 es configurable para que si se guardan las trazas en un archivo estos se roten en una fecha indicada en una expresión cron, cuando lleguen a un cierto tamaño o cuando se inicie la aplicación. El rotado además de para archivar las trazas de la aplicación y clasificarlas por fecha sirve para evitar que lleguen a consumir todo el espacio de almacenamiento disponible.

Java

Una tendencia en el uso de aplicaciones basadas en contenedores o microservicios es que estos sus mensajes de trazas o logs los emitan a la salida estándar del proceso, esto tiene la ventaja de que la aplicación no ha de conocer ni impone ninguna limitación si posteriormente se utiliza alguna herramienta para agregar esos logs. Una combinación es utilizar ELK (Elasticsearch para indexar el texto, Logstash para guardar los logs, Kibana como interfaz de consulta) pero en un futuro podría cambiarse por otra y la aplicación que emita sus logs en la salida estándar no requeriría ninguna modificación.

Otra posibilidad adicional o como sustituta es guardar los log en un archivo en el sistema de archivos. Sin embargo, hay que estimar la cantidad de información que puede llegar emitir la aplicación para aprovisionar en la máquina espacio suficiente para darles cabida. Para limitar el espacio que pueden llegar a ocupar los logs se pueden rotar los archivos cuando lleguen a cierto tamaño o por fecha. De no imponer un cierto límite a los archivos de log estos pueden llegar a consumir todo el espacio de almacenamiento disponible y ocasionar una caída del servicio de la aplicación.

En la librería log4j para realizar logging en Java la política y estrategia de rotación se define con el Appender de tipo RollingFileAppender. Las políticas de rotado definen cuando se realiza el rotado, por fecha, por tamaño o al inicio de la aplicación. La estrategia define cómo se realiza el rotado y que nombre se le da a los archivos rotados, cuantos rotados de archivos se conservan y si los archivos rotados se archivan comprimidos.

En la configuración de RollingFileAppender los parámetros de configuración fileName y filePattern indican en que archivo se generan los logs y que nombre se les da a los archivos rotados y si se comprimen. La política CronTriggeringPolicy permite definir con una expresión cron en que momento y fecha periódica se realiza el rotado, la política SizeBasedTriggeringPolicy rota los archivos cuando lleguen a cierto tamaño especificado por parámetro de configuración en KB, MB o GB. Con la estrategia DefaultRolloverStrategy por defecto se configura cuantos archivos de log se quieren conservar como máximo, una vez llegado al límite el más antiguo se elimina limitando de esta forma el espacio ocupado por los logs de la aplicación.

En el siguiente ejemplo se muestra el archivo de configuración de log4j que emite las trazas a la consola y a un archivo en los que cada día o cuando lleguen a 500 MB son rotados. Al especificar en el parámetro filePattern la extensión gz los archivos rotados se comprimen para que ocupen menos espacio. Como se define en DefaultRolloverStrategy se conservan como máximo 10 archivos rotados, por tanto ocupando un máximo de 5 GiB.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
configuration:
 status: warn

 appenders:
 console:
 name: STDOUT
 patternLayout:
 Pattern: "%d{DEFAULT} %X{uuid} %-5level %60.60logger %msg%n"

 rollingFile:
 name: RollingFile
 fileName: "logs/app.log"
 filePattern: "logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz"
 patternLayout:
 pattern: "%d{DEFAULT} %X{uuid} %-5level %60.60logger %msg%n"
 policies:
 cronTriggeringPolicy:
 schedule: "0 0 * * * ?"
 sizeBasedTriggeringPolicy:
 size: "500 MB"
 defaultRollOverStrategy:
 max: "10"

 loggers:
 root:
 level: info
 appenderRef:
 ref: STDOUT
 appenderRef:
 ref: RollingFile

Rotar los logs es una buena idea ya que en algunas aplicaciones Java si la aplicación por alguna circunstancia emite a los archivos de log un stacktrace de forma continuada generando una considerable cantidad de información en poco tiempo, si se guarda en el almacenamiento acaba por consumir todo el espacio disponible por muy previsor que se haya sido al aprovisionar el tamaño del espacio de almacenamiento, la aplicación terminará por dejar de prestar su servicio y alguien un sábado a las 3:00 de la noche es posible que deba levantarse de la cama porque ha llegado alguna alerta de monitorización si se es afortunado de disponer de uno para reaccionar cuanto antes y antes de que la aplicación deje de funcionar o peor recibe una llamada de teléfono cuando la aplicación ya se ha dejado de funcionar.

» Leer más, comentarios, etc...

Variable not found

¿Para qué sirve la llamada a SetCompatibilityVersion() incluida en los proyectos ASP.NET Core 2.x, y por qué no aparece en ASP.NET Core 3.0?

noviembre 12, 2019 07:05

ASP.NET Core MVCUn alumno del curso de ASP.NET Core 3 en CampusMVP, me preguntaba hace unos días qué había pasado con la llamada al método SetCompatibilityVersion() que veíamos en la plantilla de proyectos ASP.NET Core MVC y Razor Pages desde la versión 2.1:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Esta llamada era incluida de serie en los nuevos proyectos ASP.NET Core desde la versión 2.1, pero en la versión 3.0 ya no aparece. Y probablemente también os llame la atención a quienes ya habéis trabajado con ASP.NET Core 2.x, así que he pensado que sería interesante comentarlo por aquí.

¿Para qué sirve la llamada a SetCompatibilityVersion() ?

Como contamos hace algún tiempo, esta novedad de ASP.NET Core 2.1 era un mecanismo de defensa que nos protegía ante cambios de comportamiento inesperados que podían surgir conforme el framework MVC iba evolucionando en sus distintas revisiones menores, es decir, las numeradas como 2.x.

El objetivo era que los pequeños cambios introducidos de una a otra versión no bloquearan a los usuarios a la hora de hacer un upgrade del framework en proyectos existentes y, al mismo tiempo, permitir que el equipo de producto pudiera seguir haciéndolo evolucionar. Es decir, la idea era que si teníamos una aplicación MVC creada con ASP.NET Core 2.0 y queríamos actualizar el marco de trabajo a la versión 2.2, las funcionalidades o cambios introducidos no se aplicaran al proyecto a no ser que lo indicásemos expresamente mediante una llamada a SetCompatibilityVersion().

En la práctica, esto implicaba que en todos los proyectos ASP.NET Core 2.x, el comportamiento del framework era el de la versión 2.0, excepto cuando se indicara lo contrario con SetCompatibilityVersion(). De esta forma:
  • Proyectos inicialmente creados con ASP.NET Core 2.0 continuarían comportándose de la misma manera aunque actualizáramos la versión del framework a la 2.1 o 2.2. Pero si queríamos estar a la última y aprovechar las novedades de estas revisiones, podíamos hacer la llamada expresa a SetCompatibilityVersion() en su clase Startup.
     
  • En proyectos nuevos creados con la versión más reciente del framework, dado que la llamada a SetCompatibilityVersion() ya se incluía por defecto en la plantilla apuntando a la versión en curso, estaríamos aprovechando todas las últimas novedades desde el principio.
Pero la llamada a SetCompatibilityVersion() es sólo la parte visible de la estrategia utilizada para poder introducir cambios en revisiones sin "salpicar" a los usuarios de las versiones actuales o anteriores del marco de trabajo.

Y creo que vale la pena comentarla porque el procedimiento seguido puede ser una buena inspiración cuando programemos APIs, frameworks o componentes que evolucionan con cierta periodicidad y necesitamos mantener un cierto nivel de compatibilidad entre versiones.

¿Cómo se introducen novedades en el framework sin romper nada?

Cuando en la versión 2.2 de ASP.NET Core se iban a introducir en MVC nuevas características que podían afectar a usuarios de la versión 2.0 o 2.1, la forma de trabajar era más o menos la siguiente:
  • Se implementaban las nuevas features de forma que su funcionamiento pudiera ser activado o desactivado, normalmente modificando sendos switches del objeto MvcOptions que solemos utilizar para configurar el framework MVC. Por ejemplo, en ASP.NET Core 2.2 se introdujo la propiedad booleana EnableEndpointRouting para habilitar o deshabilitar la nueva infraestructura de rutado endpoint routing.

  • El valor inicial de los switches era establecido de forma que las novedades de la versión 2.2 no entraran en funcionamiento por defecto. Siguiendo con el ejemplo anterior, la propiedad EnableEndpointRouting valía inicialmente false, lo que conseguía que el endpoint routing no estuviera activo por defecto, asegurando que no se rompía ningún proyecto existente.

  • La llamada a SetCompatibilityVersion() pasándole un valor igual o superior a Version_2_2 modificaba los switches para habilitar las features introducidas en la versión 2.2 y anteriores. Continuando con el ejemplo, esto quiere decir que se establecía la propiedad EnableEndpointRouting a true, habilitándose así el endpoint routing en el proyecto MVC.

  • Si un desarrollador quería utilizar todas las características de la versión más reciente excepto alguna en particular, siempre podía establecer la compatibilidad a la 2.2, pero luego desactivar manualmente los switches en cuestión:
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .AddMvcOptions(options =>
    {
    options.EnableEnpointRouting = false;
    });
    }

Entonces, ¿por qué no aparece la llamada a SetCompatibilityVersion() en proyectos ASP.NET Core 3.0?

Pues básicamente, porque de momento no es necesario. Como podemos observar en la siguiente captura de pantalla, la llamada a SetCompatibilityVersion() sigue existiendo en IMvcBuilder, pero observad que la mayoría de sus posibles valores están marcados como obsoletos porque ya no se utilizan:

Intellisense mostrando el método SetCompatibilityVersion()

El uso de opciones como Version_2_1 o Version_2_2 no tienen sentido porque las funcionalidades introducidas opcionalmente en dichas versiones ya forman parte de ASP.NET Core 3.0 MVC, bien como features obligatorias o bien como switches que por defecto vienen habilitados de serie. Hay que tener en cuenta que un salto de versión entre 2.x y 3.0 permite introducir breaking changes.

A día de hoy sólo podríamos utilizar esta llamada con el valor Version_3_0 o Latest, lo cual, en la práctica, no hace absolutamente nada, pues esta versión framework ya establece por defecto el nivel de compatibilidad a la versión 3.0.

Por tanto, en este momento no tiene sentido que la plantilla de proyectos ASP.NET Core MVC y Razor Pages incluya esta llamada. Sin embargo, conforme el producto continúe evolucionando, es posible que aparezcan nuevas funcionalidades y switches, y entonces probablemente veremos de nuevo aparecer la llamada a SetCompatibilityVersion() en el contenido inicial de nuestros proyectos o se nos recomendará utilizarla en los proyectos existentes para hacer uso de estas novedades.

Publicado en Variable not found.

» Leer más, comentarios, etc...

proyectos Ágiles

Debate sobre métodos ágiles vs métodos tradicionales

noviembre 12, 2019 05:00

Debate sobre métodos ágiles vs métodos tradicionales en La Salle Barcelona con motivo del 20 aniversario del MUDP.

Minuto 0:17 al minuto 1:14

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 379

noviembre 11, 2019 07:05

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Picando Código

Nuevo trabajo

noviembre 09, 2019 11:00

Fernando DescendentsEn octubre dejé mi trabajo anterior. Hace poco más de 2 años me mudé a Escocia para unirme a Cultivate, una empresa de desarrrollo de software especializada en Ruby pero con experiencia en varios otros lenguajes (intentamos promover mucho Elixir como opción a los clientes, incluso se desarrolló al menos un proyecto con Elixir). Tenía historia previa con Cultivate, así que finalmente se alinearon las cosas para que me terminara viniendo.

En 2019 llegó la noticia de que el mayor cliente de Cultivate, Deliveroo, iba a adquirir la empresa. Por segunda vez en mi carrera pasé por la etapa de adquisición. Deliveroo era cliente de Cultivate hacía bastante tiempo, pero siempre existía la esperanza de otros clientes. No estaba muy contento trabajando para Deliveroo, así que esperé a que se terminara de cerrar la adquisición para salir a buscar nuevas oportunidades.

Estoy agradecido por la oportunidad que me dieron, fue un esfuerzo bastante grande venir sólo a Escocia (incluyendo dos meses remoto levantándome a las 5am para tener más tiempo con el equipo), pero el trabajo en Cultivate fue el empujón que me faltaba para terminar de convencerme de venir.

El lunes 11 de noviembre empiezo a trabajar en Elastic, los creadores de Elasticsearch, Kibana y más. Sigo en Escocia y voy a estar trabajando desde CodeBase, el mismo edificio donde estaba, pero ahora en la zona de coworking. Estoy muy contento y con los nervios normales de empezar un nuevo trabajo.

Es un trabajo remoto, con lo que ya tuve experiencia en mis años de freelance así que no va a ser algo nuevo. Pero en diciembre ya voy a tener la oportunidad de conocer a mucha gente de Elastic en persona, iré conociendo más con el tiempo y voy a estar en CodeBase que tiene una comunidad muy amigable.

La oportunidad en particular promete muchísimo. Voy a trabajar con Ruby (y otras tecnologías) en proyectos open source y herramientas por y para desarrolladores. Muchos desafíos interesantes a nivel técnico, muchísimo para aprender y muchas expectativas. Iré contando cómo me va.

Al publicar este tipo de entradas a lo largo de los años, el blog hace el trabajo de un diario de mi carrera profesional. Me sirve para recordar distintas etapas de mi vida, pero también compartirlas. A veces me cuesta creer que un fernandino sin título haya llegado tan lejos en su carrera, ¡y todavía queda camino por recorrer!

» Leer más, comentarios, etc...

Blog Bitix

Interfaz de monitorización e instrumentalización con JMX en aplicaciones Java

noviembre 08, 2019 04:00

JMX es una forma sencilla e integrada en la plataforma Java de monitorizar e instrumentalizar ciertas operaciones de funcionamiento interno de la aplicación que no tenga que ver con el ámbito de negocio que resuelve sino en el aspecto técnico. Unos casos de uso son activar una característica de la aplicación mientras la aplicación está funcionando o limpiar una cache de modo que los datos que almacena se actualicen de nuevo de la fuente origen en tiempo real y sin necesidad de reniciarla, cualquier otro realizable con código Java es posible.

Java

Las aplicaciones Java tienen a su disposición integradas en la propia plataforma Java una interfaz para monitorizar su estado y realizar acciones de instrumentalización para modificar algún comportamiento o cambiar alguna configuración en tiempo real mientras la aplicación está funcionando sin necesidad de reiniciarla. La especificación que proporciona esta interfaz es Java Management Extensions (JMX).

JMX define una serie de recursos a ser administrados, estos ha de instrumentalizarse con el lenguaje Java definiendo unos objetos MBeans que accedan a los recursos. Una vez el recurso ha sido instrumentalizado es gestionado por una agente JMX. El agente JMX controla los recursos y los hace disponibles a las aplicaciones de gestión, el objeto principal del agente es el MBean server donde los MBean son registrados. Los recursos puede ser accedidos a través de diferentes protocolos mediante adaptadores y conectores. Un adaptador HTML muestra un MBean en el navegador y un conector se encarga de la comunicación entre la la aplicación de gestión y el agente JMX.

La instrumentalización se implementa con los objetos MBean similares a los objetos JavaBean que siguen varios patrones de diseño establecidos por la especificación JMX. Un MBean puede representar un dispositivo, una aplicación o un recurso que necesite ser administrado. Los MBean exponen una interfaz de gestión que consiste en:

  • Un conjunto de propiedades de lectura, escritura o ambas.
  • Un conjunto de operaciones invocables.
  • Una autodescripción.

Además de propiedades y operaciones los MBean también pueden emitir notificaciones cuando ocurren ciertos eventos.

Ejemplo de JMX en una aplicación Java

Un MBean no es más que una interfaz que una clase Java implementa.

1
2
3
4
5
6
7
8
package io.github.picodotdev.blogbitix.jmx.mbean;
public interface HelloMBean {
void sayHello();
int add(int x, int y);
String getName();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.jmx.mbean;
public class Hello implements HelloMBean {
public void sayHello() {
System.out.println("hello, world");
}
public int add(int x, int y) {
return x + y;
}
public String getName() {
return "Reginald";
}
}

Creada la interfaz y la implementación del MBean ha de registrarse en el servidor de MBean. Los MBean se registra en un dominio junto con una serie de propiedades clave/valor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.picodotdev.blogbitix.jmx.java;
import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import io.github.picodotdev.blogbitix.jmx.mbean.Hello;
public class Main {
public static void main(String[] args) throws Exception {
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
Hello mbean = new Hello();
ObjectName mbeanName = new ObjectName("io.github.picodotdev.blogbitix:type=Hello");
mbeanServer.registerMBean(mbean, mbeanName);
System.out.println("Waiting for incoming requests...");
Thread.sleep(Long.MAX_VALUE);
}
}

Iniciando la aplicación que registra un MBean en el servidor de MBean la plataforma Java incluye la herramienta JConsole de monitorización y gestión que cumple con la especificación JMX. VisualVM es otra herramienta de monitorización para una máquina virtual de Java, el soporte para visualizar y realizar operaciones sobre MBans hay que añadirlo con un complemento o plugin. Se inician con el siguiente comando y hay que abrir un diálogo para conectarse a uno de los agentes locales iniciados por una máquina virtual.

1
2
$ jconsole
$ ./visualvm
Herramienta de monitorización e instrumentalización JConsole

Realizada la conexión al agente se muestran las propiedades y operaciones de los MBean registrados con la posibilidad de cambiar sus valores, invocar las operaciones y obtener sus resultados. La propia plataforma Java proporciona numerosos MBean como se muestra en el árbol lateral de la imagen.

Instrumentalización de un MBean en JConsole y VisualVM

En el caso de que la aplicación esté contenida dentro de una aplicación web y desplegada en un servidor de aplicaciones como Tomcat o WildFly registrar un MBean es similar al caso del ejemplo de la aplicación Java y posteriormente administrados con la herramienta JConsole.

Ejemplo de JMX con Spring Boot

El ejemplo anterior muestra como usar JMX en una aplicación Java, Spring ofrece soporte para implementar JMX en aplicaciones que usen este framework con las anotaciones @ManagedResource, @ManagedAttribute, @ManagedOperation, @ManagedOperationParameters, @ManagedOperationParameter y @EnableMBeanExport.

El mismo MBean de la aplicación Java implementado con spring es el siguiente, lo único que cambia son las anotaciones prporcionadas para que Spring descubra de forma automática los MBean disponibles y los registre sin necesidad de hacerlo de forma explícita.

1
2
3
4
5
6
7
8
package io.github.picodotdev.blogbitix.jmx.mbean;
public interface HelloMBean {
void sayHello();
int add(int x, int y);
String getName();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package io.github.picodotdev.blogbitix.jmx.mbean;
import org.springframework.stereotype.Component;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedAttribute;
@Component
@ManagedResource(objectName = "io.github.picodotdev.blogbitix:type=Hello")
public class Hello implements HelloMBean {
@ManagedOperation
public void sayHello() {
System.out.println("hello, world");
}
@ManagedOperation
public int add(int x, int y) {
return x + y;
}
@ManagedAttribute
public String getName() {
return "Reginald";
}
}

Por autoconfiguración y la anotación @EnableMBeanExport los MBean se autodescubren y registran en el servidor MBean.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package io.github.picodotdev.blogbitix.jmx.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableMBeanExport;
import io.github.picodotdev.blogbitix.jmx.mbean.Hello;
@SpringBootApplication(scanBasePackageClasses = {Hello.class})
@EnableMBeanExport
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}

Tanto en el ejemplo de MBean con Java como con Spring el puerto RMI para acceder a JMX se configura con varias propiedades de la máquina virtual o con un archivo properties de configuración.

1
2
com.sun.management.jmxremote=false
com.sun.management.jmxremote.ssl=false

Cómo añadir acceso remoto y añadir seguridad securizad a JMX

Por defecto JMX solo es accesible desde la maquina local, esto en producción no es muy útil pero activar el acceso remoto requiere añadir nuevas propiedades de configuración para proporcionar seguridad realizando autenticación y usando una comunicación segura con SSL. Para la comunicación segura se requiere crear un keystore.

1
2
3
$ keytool -genkey -keyalg RSA -keystore keystore.jks -keysize 8192
$ keytool -export -keystore keystore.jks -file certificate.cer -storepass password
$ keytool -import -file certificate.cer -keystore trustore.jks -storepass password -noprompt
1
2
3
4
5
6
7
com.sun.management.jmxremote=true
com.sun.management.jmxremote.port=1419
com.sun.management.jmxremote.rmi.port=31419
com.sun.management.jmxremote.ssl=true
com.sun.management.jmxremote.password.file=jmxremote.password
com.sun.management.jmxremote.access.file=jmxremote.access
com.sun.management.jmxremote.ssl.config.file=jmxremote-ssl.properties
1
2
3
4
5
javax.net.ssl.keyStore=keystore.jks
javax.net.ssl.keyStorePassword=password
javax.net.ssl.trustStore=truststore.jks
javax.net.ssl.trustStorePassword=password

Los archivos jmxremote.password y jmxremote.access configuran la autenticación mediante clave y contraseña además de la autorización a las operaciones que el usuario tiene permiso para realizar. Estos archivos han tener restringidos los permisos de lectura para el usuario que inicia la aplicación o se produce una excepción.

1
2
3
4
5
6
7
# The passwords in this file are hashed.
# In order to change the password for a role, replace the hashed password entry
# with a clear text password or a new hashed password. If the new password is in clear,
# it will be replaced with its hash when a new login attempt is made.
admin password
user password
1
2
admin readwrite
user readonly
1
2
3
4
5
6
7
8
Error: Se debe restringir el acceso de lectura al archivo de contraseñas: jmxremote.password
jdk.internal.agent.AgentConfigurationError
at jdk.management.agent/sun.management.jmxremote.ConnectorBootstrap.checkPasswordFile(ConnectorBootstrap.java:590)
at jdk.management.agent/sun.management.jmxremote.ConnectorBootstrap.startRemoteConnectorServer(ConnectorBootstrap.java:436)
at jdk.management.agent/jdk.internal.agent.Agent.startAgent(Agent.java:447)
at jdk.management.agent/jdk.internal.agent.Agent.startAgent(Agent.java:599)
$ chmod 600 jmxremote.password jmxremote.access jmxremote-ssl.properties
1
$ jconsole -J-Djavax.net.ssl.trustStore=truststore.jks -J-Djavax.net.ssl.trustStorePassword=password
Acceso remoto a la herramienta de monitorización e instrumentalización JConsole

El acceso remoto también es posible mediante una aplicación Java que actúe como cliente del servidor MBean.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew run.

» Leer más, comentarios, etc...

Picando Código

Derechos de Estudiantes 2.0

noviembre 06, 2019 02:05

D.A.T.A. Uruguay nos presenta Derechos de Estudiantes 2.0:

Derechos de Estudiantes

Con mucha alegría compartimos la versión 2.0 de nuestra plataforma Derechos de Estudiantes.

El proyecto busca difundir los derechos de los y las estudiantes en la educación media uruguaya, así como fomentar una cultura de defensa, acceso y demanda de estos derechos. En primer lugar, haciendo fácilmente accesible información sobre los mismos, pero también a través de los mecanismos de consulta y denuncia anónima, que permitirán abordar situaciones particulares.

Desde el lanzamiento de la primer versión en 2016 hasta el día de hoy, la herramienta sumó aproximadamente 85.000 usuarios/as únicos/as, más de 100.000 sesiones y aprox. 325.000 páginas vistas.

En esta nueva versión esperamos seguir ese camino de crecimiento gracias a un nuevo diseño y usabilidad de la plataforma, además de las siguientes mejoras:

  • La incorporación de UTU, que se suma al Consejo de Educación Secundaria que está presente dede el lanzamiento en 2016.
  • Mejoras en el diseño responsivo para el uso desde dispositivos móviles.
  • Mejoras en la redacción de la información sobre derechos, y los mecanismos para realizar consultas.
  • Mejoras en el buscador de derechos y preguntas, sugiriendo automáticamente preguntas ya respondidas conteniendo las palabras clave buscadas.

La herramienta evoluciona con la participación de los/as estudiantes, actualizando su contenido para reflejar las inquietudes planteadas, así como los nuevos derechos adquiridos.

Derechos de Estudiantes fue co-creada en por el Consejo de Educación Secundaria (CES), Consejo de Educación Técnico Profesional (CETP/UTU), Administración Nacional de Educación Pública (ANEP), UNICEF Uruguay y DATA Uruguay y permite a estudiantes, padres/madres y actores educativos consultar preguntas con información actualizada y completa sobre derechos de estudiantes.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 378

noviembre 05, 2019 09:44

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Fixed Buffer

Novedades de C# 8: IAsyncEnumerable

noviembre 05, 2019 09:05

La imagen muestra el logotipo de c# 8.0 para la entrada sobre IAsyncEnumerable<T>

¿Y qué es eso de IAsyncEnumerable<T>? Hace algo más de un mes que se libreró .Net Core 3.0 y ya hemos hablado sobre algunas de las novedades que nos trae (que no son pocas…). Pero sin duda, una de las principales novedades que se liberaron junto a .Net Core 3, fue C# 8.0.

C# 8 nos ofrece una interesante cantidad de novedades de las que vamos a ir hablando durante las próximas semanas:

  • Tipos nullables
  • Rangos e índices
  • Declaraciones using
  • Interfaces con implementaciones por defecto
  • Reconocimiento de patrones
  • Asignaciones de uso combinado

Pero para mí el más importante en cuanto al impacto que puede tener en el rendimiento de nuestra aplicación, sin duda alguna, es «IAsyncEnumerable«.

Hay otros cambios que afecta mucho más al rendimiento, como Span<T> por ejemplo, pero es más difícil que lo usemos en nuestro día a día que IAsyncEnumerable

Hasta ahora, no teníamos manera de iterar un objeto de manera asíncrona medida que se va generando. ¿Qué quiere decir esto? ¡Pues fácil! Imagina que tenemos un método que nos devuelve una colección de 100 objetos y que, por ejemplo, tarda en generar cada objeto 1 segundo. Una vez que tenemos la colección lista, la iteramos con un foreach que tarda otro segundo en realizar un ciclo. Algo así:

async Task<IEnumerable<int>> GetCountIEnumerableAsync()
{
    var list = new List<int>();
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(1000); 
        list.Add(i);
    }

    return list;
}

//....

foreach (var valor in await GetCountIEnumerableAsync())
{
    //Acción que tarda un segundo en ejecutarse
}

Con el código anterior, asumiendo 1 segundo para generar cada objeto de la colección y un segundo para procesarlo, vamos a tardar 200 segundos en ejecutar el foreach completo. Hasta ahora, no nos quedaba otra que gestionar nosotros la situación para obtener los datos por un lado y procesarlos por otro para poder darle salida nada más obtenerlos, obligándonos a implementar una lógica extra que hay que mantener y sincronizar.

IAsyncEnumerable<T> al rescate

Como decíamos antes, con C# 8 se han introducido los streams asíncronos a través de la interfaz IAsyncEnumerable<T>. Esto lo que nos va a permitir es no tener que esperar a que la colección se obtenga completa antes de empezar a iterarla. Volviendo al ejemplo anterior, si en obtener cada dato tardamos un segundo, pero mientras obtenemos un nuevo dato procesamos el anterior, en vez de 200 segundos, solo vamos a tardar 101 (aproximadamente). Vamos a verlo con un poco de código:

async IAsyncEnumerable<int> GetCountIAsyncEnumerableAsync()
{
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(1000); 
        yield return i;
    }
}

//....

await foreach (var valor in GetCountIAsyncEnumerableAsync())
{
    //Acción que tarda un segundo en ejecutarse
}

Este código va a ir despachando cada uno de los objetos a medida que se van generando nuestro método «GetCountIAsyncEnumerableAsync». Como puedes comprobar esto es infinitamente más fácil y más legible. Imagina si tuviésemos que gestionar dos hilos independientes, uno para adquirir datos y otro para procesarlos…

Si los datos provienen de algún servicio externo como una base de datos o un API Rest (o cualquier otro origen en general). Como resultado, vamos a obtener una mejora bastante importante gracias a esto. Ya sea por aumentar el rendimiento, o por facilitarnos el código. ¿Aun no tienes claro cómo funciona?

Vamos a ver un ejemplo claro

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace IAsyncEnumerable
{
    internal static class Program
    {
        private static async Task Main(string[] args)
        {
            Console.WriteLine("Ejecutando foreach con Task<IEnumerable<int>>");
            foreach (var valor in await GetCountIEnumerableAsync())
            {
                Console.WriteLine($"Procesado el valor {valor}");
            }

            Console.WriteLine("Ejecutando foreach con IAsyncEnumerable<int>");
            await foreach (var valor in GetCountIAsyncEnumerableAsync())
            {
                Console.WriteLine($"Procesado el valor {valor}");
            }

            Console.Read();
        }

        private static async IAsyncEnumerable<int> GetCountIAsyncEnumerableAsync()
        {
            for (var i = 0; i < 3; i++)
            {
                await Task.Delay(1000);
                Console.WriteLine($"Añadido valor {i}");
                yield return i;
            }
        }

        private static async Task<IEnumerable<int>> GetCountIEnumerableAsync()
        {
            var list = new List<int>();
            for (var i = 0; i < 3; i++)
            {
                await Task.Delay(1000);
                Console.WriteLine($"Añadido valor {i}");
                list.Add(i);
            }
            return list;
        }
    }
}

En el código anterior, simplemente hemos implementado ambos casos, uno con IAsyncEnumerable<int> y otro con Task<IEnumerable<int>>. Si ejecutamos el código la salida que obtenemos es algo como esto:

La imagen muestra la salida por consola del ejemplo anterior, con y sin IAsyncEnumerable

Con la imagen se ve más claro cuál es el cambio de funcionamiento que nos porta esta nueva característica, pero… ¡Si tienes alguna duda, puedes dejar un comentario!

Durante las próximas semanas vamos a seguir hablando de las interesantes novedades que nos aporta C# 8. No te lo pierdas, realmente es muy interesante

**La entrada Novedades de C# 8: IAsyncEnumerable<T> se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Variable not found

Implementación de servicios gRPC con ASP.NET Core

noviembre 05, 2019 07:15

gRPC logo Como seguro sabréis, gRPC lleva ya algún tiempo proponiéndose como sustituto de las APIs HTTP/REST en determinados escenarios, y ASP.NET Core 3.0 incluye de serie soporte para esta tecnología.

En este post echaremos un vistazo a gRPC y su uso en la nueva versión del framework.

¿Qué es gRPC?

Se trata de un marco de trabajo para la implementación de llamadas a procedimientos remotos (vaya, el RPC de toda la vida), actualizado y basado en estándares abiertos como HTTP/2 y Protobuf y que, a diferencia de otras opciones, proporciona soporte directo para autenticación, streaming, serialización binaria, cancelación o timeouts, entre otras características.

Aunque inicialmente fue desarrollado por Google para su uso interno, hoy en día es un proyecto perteneciente a la Cloud Native Computing Foundation, una organización que promueve estándares y tecnologías abiertas, interoperables e independientes de fabricantes o proveedores de servicios. Hoy en día dispone de implementaciones en un gran número de lenguajes y plataformas.

Como curiosidad, en la versión 1.0, el término gRPC procedía del acrónimo recursivo "gRPC Remote Procedure Call", pero el significado de la "g" inicial va cambiando el cada release: "Good Remote Procedure Call", "Glamorous Remote Procedure Call", etc. Podéis ver la lista completa aquí

Respecto a las APIs REST tradicionales, basadas principalmente en el uso de HTTP/1.x y JSON, el uso de gRPC permite prestar y consumir servicios de forma mucho más potente y eficiente gracias a la estructura y menor tamaño de sus mensajes y al uso de protocolos modernos, que ofrecen menor latencia y posibilidades, antes impensables, como el streaming bidireccional o el multiplexado de conexiones.

Pero claro, también hay algunos aspectos negativos. El principal en este momento sería la falta de soporte universal por parte de algunos tipos de servidor y de los navegadores, lo que limita su uso sólo a algunos escenarios (comunicación entre servidores, microservicios, custom client/server, o similares) o hace necesario el uso de gateways que intermedien entre los navegadores y los endpoints gRPC. También, dado el uso de serialización binaria y empaquetado de mensajes, no será sencillo leerlos o crearlos de forma directa, como hacíamos con APIs HTTP/JSON usando herramientas como Fiddler o Postman.

.NET Core 3 ofrece herramientas tanto para implementar servicios gRPC (lado servidor) como para consumirlos (lado cliente).
Ojo: en este momento, Azure App Service no soporta todavía el alojamiento de servicios gRPC. Si esto es un must, deberíais seguir de cerca el issue donde se está comentando el asunto.

Implementación de servicios gRPC en ASP.NET Core (lado servidor)

Podemos crear muy fácilmente un proyecto gRPC desde Visual Studio, seleccionando este tipo de proyecto en el primer paso del asistente:
Creación de proyecto gRPC
Como es habitual, también podríamos conseguir lo mismo utilizando la línea de comandos .NET Core, ejecutando dotnet new grpc.
Este tipo de proyecto es una aplicación ASP.NET Core estándar a la que se ha añadido el paquete NuGet Grpc.AspNetCore, que es el que proporciona el soporte para gRPC. También encontraremos en la clase Startup código para registrar los servicios y mapear un endpoint gRPC hacia un servicio incluido en la plantilla por defecto, llamado GreeterService:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
}

public void Configure(IApplicationBuilder app)
{
...
app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
...
});
}
}
Obviamente, también podríamos haber partido de una aplicación ASP.NET Core, añadirle el correspondiente paquete NuGet e introducir el código de inicialización. El resultado sería el mismo, pero si creamos el proyecto tendremos ya implementado un servicio de ejemplo.

Definición de servicios

A diferencia de servicios API HTTP convencionales, los servicios gRPC parten de la definición de un contrato, o interfaz, especificado utilizando sintaxis protobuf en archivos con extensión .proto.

La verdad es que incluso sin haberlo visto antes, la estructura del servicio definida en el .proto se puede leer y comprender muy fácilmente. Como muestra, en el siguiente código es el contenido del archivo Protos/Greet.proto incluida por defecto en los proyectos gRPC:
syntax = "proto3";

option csharp_namespace = "GrpcServiceDemo";

package Greet;

// Definición del servicio
service Greeter {
// Método para el envío de un saludo
rpc SayHello (HelloRequest) returns (HelloReply);
}

// Estructura del mensaje de entrada (el nombre del solicitante):
message HelloRequest {
string name = 1;
}

// Estructura del mensaje de respuesta (el texto del saludo):
message HelloReply {
string message = 1;
}
Como se puede intuir, las primeras líneas establecen parámetros básicos, como la versión de Protocol Buffer en uso o el namespace de las clases que serán generadas a partir de esta definición. Más abajo, la definición del servicio service permite especificar los métodos que serán invocables de forma remota, a la vez que define el tipo de dato que recibe y retorna cada uno de ellos.

Dichos tipos de datos son detallados más abajo en estructuras message que incluyen información sobre los campos que contienen, sus identificadores únicos (el número que aparece junto a cada campo), y su tipología (string, bool, int32, etc.) Podéis consultar la guía completa del lenguaje en la documentación oficial de protobuf.

Por cada archivo .proto, el tooling incluido en el paquete NuGet que hemos instalado en el proyecto generará automáticamente una clase que usaremos como base para implementar nuestros métodos remotos.

Ojo: si vuestro nombre de usuario en Windows contiene caracteres especiales o espacios, la compilación fallará. Echad un vistazo a este post.

En el ejemplo anterior, se generará una clase llamada Greeter.GreeterBase que contendrá implementaciones vacías de los métodos especificados en los contratos; lo único que tendremos que hacer es heredar de esta clase y sobrescribir sus métodos, introduciendo en ellos nuestra lógica de proceso de invocaciones:
public class GreeterService : Greeter.GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
Fijaos que en la implementación estamos utilizando clases tipadas como HelloRequest y HelloReply, que también han sido generadas de forma automática, para representar los parámetros de entrada y los resultados a retornar, respectivamente.
Nota: Estas clases son generadas en la carpeta /obj en cada compilación y no son incluidas en el proyecto o en los repositorios de código. Pero no os preocupéis, porque tampoco vais a necesitar acceder a ellas directamente.
Finalmente, será la clase que implementa el servicio, en nuestro ejemplo GreeterService, la que se registrará como endpoint en la clase de inicialización de la aplicación Startup.
public void Configure(IApplicationBuilder app)
{
...
app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
...
});
}

Ejecución del servidor

Podemos ejecutar nuestro proyecto gRPC como cualquier otro, ya que no hay diferencia con una aplicación ASP.NET Core de las que estamos acostumbrados a utilizar. De hecho, como seguro podréis intuir, es totalmente posible que la misma aplicación ASP.NET Core albergue servicios gRPC al mismo tiempo que otros tipos de endpoint como MVC o Razor Pages.

Pero eso sí, un aspecto importante a tener en cuenta es que gRPC utiliza necesariamente HTTP/2 y TLS. En el entorno de desarrollo estos dos aspectos están configurados por defecto y no tendremos que hacer nada, pero en la documentación oficial se detallan algunos pasos extra necesarios para la puesta en marcha de los servicios gRPC en entornos de producción.

En cualquier caso, una vez puesto en marcha necesitaremos un cliente gRPC para poder invocar a nuestros procedimientos remotos.

Consumo de servicios gRPC

Podemos consumir servicios gRPC desde cualquier tipo de aplicación .NET Standard 2.1.

Añadir servicio conectado

Para ello, sin duda lo más sencillo es utilizar las herramientas de Visual Studio para generar un proxy que nos permita acceder a los servicios de forma tipada. Esto lo conseguimos abriendo el menú contextual sobre la carpeta References del proyecto, y seleccionando la opción Add connected service.

Tras ello, en la pestaña Service References pulsamos la opción correspondiente para añadir una referencia a un servicio gRPC, introduciendo en el cuadro de diálogo la ruta hacia el archivo .proto, que especifica el contrato del servicio, e indicando en este caso que deseamos generar el código para acceder al mismo, es decir, el código para un cliente del servicio.

Añadir referencia a servicio gRPC

A partir de este momento tendremos en nuestro proyecto la referencia añadida. Desde ese mismo punto podemos examinar la clase C# generada, editar la referencia, o eliminarla cuando ya no la necesitemos.

Referencia a servicio añadida

Además de generar la clase, este proceso incluirá en el proyecto un vínculo hacia el .proto original (es decir, no copia físicamente el archivo sino un "atajo" hacia él), así como los paquetes NuGet necesarios para que todo funcione, en caso de que no existieran ya:
  • Google.Protobuf
  • Grpc.Net.ClientFactory
  • Grpc.Tools
Si en lugar de Visual Studio preferimos utilizar la línea de comandos de .NET Core (CLI), lo único que tendremos que hacer es incluir de forma manual los paquetes NuGet anteriores, y traer de alguna forma el archivo .proto al proyecto, bien sea copiándolo o bien añadiendo manualmente un vínculo al archivo original, como se muestra en la siguiente porción de .csproj:
<ItemGroup>
<Protobuf Include="..\GrpcServiceDemo\Protos\greet.proto" GrpcServices="Client">
<Link>Protos\greet.proto</Link>
</Protobuf>
</ItemGroup>
En cualquier caso, el tooling generará una clase estática con el nombre del servicio especificado en el archivo .proto, en la que existirá una clase instanciable con el nombre del mismo seguido de la palabra Client. Por ejemplo, en el caso del servicio Greeter que hemos visto anteriormente, podemos instanciar nuestro cliente haciendo un new Greeter.GreeterClient(...). Esta clase actuará como proxy para realizar las invocaciones al procedimiento remoto.

Sin embargo, para instanciar un cliente necesitamos disponer previamente de un canal gRPC, que básicamente es la tubería a través de la cual realizaremos las llamadas. Al crearlo, especificaremos la dirección del servidor que aloja los servicios y, opcionalmente, settings para modificar el comportamiento por defecto del mismo.

La creación de un canal es tan sencilla como se muestra a continuación:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
Como indican en la documentación oficial, es importante tener en cuenta que la creación de estos objetos GrpcChannel puede ser costosa, y conviene que sean reutilizados entre distintos servicios o clientes. De hecho, el framework proporciona una factoría de clientes gRPC muy parecida a la que utilizamos para reutilizar instancias de HttpClient.
Con el canal creado ya podemos instanciar nuestra clase cliente e invocar el método remoto, enviándole los parámetros que espera recibir, que en este caso serán de tipo HelloRequest. Observad que la respuesta a la llamada es también tipada, y en nuestro ejemplo recibiremos objetos HelloReply, según lo especificado por el contrato protobuf:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);

var request = new HelloRequest() { Name = "John" };
HelloReply result = await client.SayHelloAsync(request);

Console.WriteLine(result.Message); // Hello John

En resumen

Como hemos visto, el tooling y componentes proporcionados por .NET Core 3 ocultan gran parte de la complejidad real de la implementación de gRPC tanto en cliente como en servidor, convirtiéndolo en lago casi trivial para los desarrolladores.

Muy resumidamente, la cosa consiste en:
  • En el lado servidor:
    • Definir el contrato de los servicios, procedimientos y tipos de mensaje en un archivo .proto.
    • Heredar de las clases autogeneradas y sobrescribir sus métodos con el código personalizado de los servicios.
  • En el lado cliente:
    • Añadir el contrato del servicio (archivo .proto) al proyecto.
    • Utilizar las clases generadas para invocar los procedimientos remotos.
Aunque aún faltan algunos aspectos por afinar, como el soporte desde algunos clientes y servidores, la verdad es que gRPC se está posicionando como una alternativa bastante interesante para el desarrollo de APIs en las que sea fundamental el rendimiento u otras de las interesantes características proporcionadas por esta tecnología.

Y dicho esto... mmmmm.... cómo recuerda esta forma de trabajar a los tiempos en los que los Web Services XML eran la panacea, ¿verdad? ;)

Publicado en Variable not found.

» Leer más, comentarios, etc...

proyectos Ágiles

Aprender a colaborar lleva su tiempo, incluso para la Inteligencia Artificial

noviembre 05, 2019 05:00

Conseguir un equipo de alto rendimiento que colabore muy bien lleva su tiempo. Requiere de aprendizaje, esfuerzo y finalmente vale la pena 🙂

Recientemente se realizó un experimento con inteligencia artificial (AI) de donde se puede sacar un interesante paralelismo con esto. A continuación, el análisis:

Google DeepMind creó “agentes artificiales” que tenían que aprender a jugar en Quake (donde los jugadores tienen que robar la bandera del enemigo y llevarla a su base, mientras protegen la suya propia) a partir de prueba y error, sin conocer las reglas del juego. De manera muy resumida, lo que sucedió fue lo siguiente:

Tras las primeras 50.000 partidas, los agentes descubrieron que, siguiéndose unos a otros (como si fuesen hormigas) ganaban más batallas. En este punto, no se genera más que una linealidad por poner más agentes en el sistema, una suma de las partes, van juntos pero todavía no colaboran.

Necesitaron 4 veces más partidas para que cada agente desarrollase su propia manera de jugar (especialización) y empezasen a colaborar de manera efectiva, es decir, entre todos los agentes crearon un equipo multidisciplinar, a partir de lo cual empezaron a ganar muchas más partidas. Para ello, como se comenta en el siguiente artículo de Javier Salas en El País:

DeepMind los había programado para que generaran sus propias señales de recompensa: algunos se sentían más motivados a matar al enemigo (al dispararles y así devolverlos a su base), otros a capturar banderas, etc., lo que produjo un abanico amplio de jugadores con distintas habilidades y técnicas. Además, el software propicia que los agentes actúen en dos velocidades, por lo que pueden disparar con la adrenalina del enfrentamiento inmediato, pero también planificar movimientos en el largo plazo para una mejor estrategia.

Conseguir un beneficio por ir juntos es relativamente inmediato, pero conseguir resultados espectaculares necesita del desarrollo de estrategias de colaboración, y eso lleva su tiempo.

Conseguir efectos “no-lineales” (mucho más del de ser un “grupo”, aprovechando diversidad, la complementariedad y la inteligencia colectiva, pensando juntos de manera “consciente”) cuesta mucho más esfuerzo de prueba y error hasta conseguir una buena colaboración. Parece que hay aquí un paralelismo interesante con seres complejos como los humanos 🙂

Este paralelismo curiosamente se rompe en el momento en que comparamos el rendimiento de los agentes artificiales con el de seres humanos. Aquí nos encontramos con otro aspecto interesante: las máquinas son muy superiores a las personas, y sólo pierden cuando en su equipo se cambia algún agente por un humano. Y no sólo eso, los jugadores humanos puntuaron a los agentes como más cooperativos que ellos mismos. Da un poco de miedo, ¿no creéis? 🙂

Como futuro esperanzador, el artículo de El País acaba mencionando el gran aspecto en positivo detrás de todo esto:

Las máquinas pueden coordinarse con un humano para llevarlo a la victoria. Y ese es precisamente el más noble objetivo de los defensores del futuro de la inteligencia artificial: su capacidad de impulsar a los humanos más allá.

Enlace al artículo original en DeepMind.

» Leer más, comentarios, etc...

Una sinfonía en C#

Predicados y array en Javascript: find, filter, some.

noviembre 04, 2019 09:48

En esta tercera parte vamos a ver varios métodos juntos, find, filter y some porque...bueno, porque sí.
En realidad porque tiene algo en común, todos reciben como parámetro un predicado

¿Y qué es un predicado?

En este contexto es una función que establece una condición contra la cual se evaluarán elementos.
Dicho en palabras un poco menos complicadas, pasamos una función que será la encargada de decirnos si un elemento de un array cumple con una condición.
Veamos un ejemplo:

Array.filter

Empecemos con filter, al final los tres métodos son similares pero hacen cosas distintas.
Filter permite tomar solo aquellos elementos de un array que cumplen con una condición, en este caso, determinada por el predicado.
Entonces, si tenemos el siguiente array:

var a = [1,2,3,4,5,6,7,8,9];

y quisiera quedarme solo con aquellos elementos que son pares podría hacer esto:

var aa = [];
for(var item in a){
     if(item % 2 == 0)
         aa.push(item);
}
console.log(aa);

Con filter la cosa sería así:

aa = a.filter(function(item){
     return item % 2 == 0; });

El resultado es exactamente el mismo.

Y es igual que:

aa = a.filter((item)=> item % 2 == 0);

Aún más elegante.

Algunas características de filter:

  • Solo se invoca el callback (el predicado) para elementos que tiene valores, es decir, los elementos del array sin valores se ignoran.
  • Los elementos borrados durante la ejecución no son visitados.
  • El rango de elementos a visitar se establece antes del inicio de la iteración, es decir, si se agregan elementos los mismo no son visitados.
  • Si un elemento cambia de valor se leerá el nuevo valor.

Además filter recibe dos parámentros, el callback que recibe tres: el elemento actual, el índice actual y el array original.
El segundo parámetro es el contexto de this, vamos, igual que las funciones que vimos en los posts anteriores.

Todo es es igual para el caso de find y some, veamos cómo funcionan.

Find

Simplemente retorna el primer elemento que coincida con el criterio del predicado, en el ejemplo anterior retornaría el número 2.

a.find((item)=> item % 2 == 0);

Evidentemente si hay más elementos que cumplen la condición establecida en el predicado son ignorados, en caso de no haber ninguno retorna undefined.

a.find((item) => item > 10);

Las mismas reglas de comportamiento anteriores se aplican a find.

Some

En este caso devuelve true si al menos un elemento cumple con el criterio de búsqueda y false si no hay ninguno.

a.some((item) => item > 2);

Devolverá true.

a.some((item)=> item > 10);

Devolverá false, sería casi como hacer algo así con find:

a.find((item)=>item > 10) != undefined;

Lo dejamos acá por hoy, nos leemos.

» Leer más, comentarios, etc...

Blog Bitix

Cambiar la ubicación por defecto de los directorios de usuario en GNU/Linux

noviembre 04, 2019 06:30

GNU
Linux

Los directorios de Descargas, Documentos, Música, Imágenes, Vídeos, Escritorio, Plantillas y Público «bien conocidos» por defecto se encuentran en el directorio de inicio o home del usuario, en mi caso sería /home/picodotdev/ o abreviadamente ~/.

Por preferencias o necesidad según el usuario, la ubicación de cada uno de estos directorios se puede cambiar individualmente. Por ejemplo, si en la carpeta Documentos, Música e Imágenes se tiene un montón de archivos que ocupan varias decenas de gigabytes que no se usan de forma habitual ni se desea tener en el SSD ocupando espacio la ubicación de estos directorios se puede cambiar por uno que se encuentra en un disco mecánico USB o tarjeta microSD externa.

Los discos duros SSD se han abaratado muchísimo en los últimos años pero aún no han alcanzado a los discos duros mecánicos en el precio de € por GB. Un disco duro mecánico de 4 TB tiene un precio bastante asequible. A lo largo del tiempo he ido reaprovechando los discos duros de portátiles para los que compré una caja USB para utilizarlos como discos duros externos USB y ahora tengo varios, uno de 500 GB, otro de 320 GB, y dos de 120 GB.

Cuando compré el Intel NUC compré la versión slim sin bahía para disco SATA 2.5” y solo le puse un SSD de 500 GB con conexión M.2. Tenía intención de comprar una tarjeta SDXC o una memoria USB pequeña para tenerla siempre conectada al ordenador y como una forma de ampliar la capacidad de almacenamiento. Sin embargo, ni la tarjeta microSD ni la memoria USB es barata comparada con el precio de un SSD SATA. Al final he optado por utilizar uno de esos discos duros externos que tengo para el mismo propósito. Lo que necesitaba era cambiar la ubicación de esos directorios por defecto para que en vez de estar en la carpeta de inicio de mi usuario y en el SSD estuviesen en el disco duro externo USB.

La ubicación de los directorios de usuario se puede cambiar modificando las rutas en el archivo de configuración ~/.config/user-dirs.dirs. En este caso poniendo la ubicación del punto de montaje del disco duro externo USB.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# This file is written by xdg-user-dirs-update
# If you want to change or add directories, just edit the line you're
# interested in. All local changes will be retained on the next run.
# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
# absolute path. No other format is supported.
#
XDG_DESKTOP_DIR="/run/media/picodotdev/bmovenegro/Escritorio/"
XDG_DOWNLOAD_DIR="/run/media/picodotdev/bmovenegro/Descargas/"
XDG_TEMPLATES_DIR="/run/media/picodotdev/bmovenegro/Plantillas/"
XDG_PUBLICSHARE_DIR="/run/media/picodotdev/bmovenegro/Público/"
XDG_DOCUMENTS_DIR="$HOME/Documentos"
XDG_MUSIC_DIR="/run/media/picodotdev/bmovenegro/Música/"
XDG_PICTURES_DIR="/run/media/picodotdev/bmovenegro/Imágenes/"
XDG_VIDEOS_DIR="/run/media/picodotdev/bmovenegro/Vídeos/"

Cambiar los valores de los directorios de usuario a otro directorio requiere que esos directorios estén disponibles a iniciar sesión de usuario en el entorno de escritorio. Para que el disco duro externo USB se monte al iniciar el sistema he definido un servicio de tipo mount para systemd en la ubicación /etc/systemd/system/run-media-picodotdev-bmovenegro.mount con el siguiente contenido. El disco duro se monta en el directorio /run/media/bmovenegro/.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Unit]
Description=Disk bmovenegro mount
[Mount]
What=/dev/disk/by-uuid/f0fe2765-00aa-4c2a-adef-83a3264b8f4f
Where=/run/media/picodotdev/bmovenegro/
Type=ext4
Options=defaults
[Install]
WantedBy=multi-user.target

El identificador UUID de un dispositivo se obtiene con el comando lsblk.

1
2
3
4
5
6
7
8
9
$ lsblk -o +uuid,name
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT UUID NAME
sda 8:0 0 465,8G 0 disk sda
└─sda1 8:1 0 465,8G 0 part /run/media/picodotdev/bmovenegro f0fe2765-00aa-4c2a-adef-83a3264b8f4f sda1
nvme0n1 259:0 0 465,8G 0 disk nvme0n1
├─nvme0n1p1 259:1 0 511M 0 part /boot 7869-6341 nvme0n1p1
└─nvme0n1p2 259:2 0 465,3G 0 part 3067348f-58f4-48fd-be4b-7cb3aae80dd1 nvme0n1p2
└─lvm 254:0 0 465,3G 0 crypt c0fVvN-1QTv-HccD-tyEt-cl49-GM7M-xDNGJQ lvm
└─vg-root 254:1 0 465,3G 0 lvm / 4b561dc9-bbd6-4433-bc53-c879bde68042 vg-root

Con esta configuración para el explorador de archivos en este caso Nautilus de GNOME la ubicación de estos archivos de usuario es transparente, los directorios aparecen en el panel lateral. Aunque en el directorio home siguen existiendo las carpetas originales de los directorios de usuario realmente cuando se hace clic en el panel lateral del directorio Documentos se muestra el contenido /run/media/bmovenegro/Documentos y no de ~/Documentos.

Directorios de usuario en el directorio home y en disco USB externo

En el caso de GNU/Linux los directorios de usuario además están localizados de forma automática, esto es al listar los directorios los nombres están en el idioma del usuario de forma consistente, al contrario de como ocurre en otros sistemas operativos que sus nombres se mantienen en inglés aún en su explorador de archivos aparecer en el idioma del usuario creando una inconsistencia entre los nombres que se listan desde la terminal y los que aparecen en su explorador de archivos.

Directorios de usuario en el directorio home y en disco USB externo

» Leer más, comentarios, etc...

Blog Bitix

Cambiar la ubicación por defecto de los directorios de usuario en GNU/Linux

noviembre 04, 2019 06:30

GNU
Linux

Los directorios de Descargas, Documentos, Música, Imágenes, Vídeos, Escritorio, Plantillas y Público «bien conocidos» por defecto se encuentran en el directorio de inicio o home del usuario, en mi caso sería /home/picodotdev/ o abreviadamente ~/.

Por preferencias o necesidad según el usuario, la ubicación de cada uno de estos directorios se puede cambiar individualmente. Por ejemplo, si en la carpeta Documentos, Música e Imágenes se tiene un montón de archivos que ocupan varias decenas de gigabytes que no se usan de forma habitual ni se desea tener en el SSD ocupando espacio la ubicación de estos directorios se puede cambiar por uno que se encuentra en un disco mecánico USB o tarjeta microSD externa.

Los discos duros SSD se han abaratado muchísimo en los últimos años pero aún no han alcanzado a los discos duros mecánicos en el precio de € por GB. Un disco duro mecánico de 4 TB tiene un precio bastante asequible. A lo largo del tiempo he ido reaprovechando los discos duros de portátiles para los que compré una caja USB para utilizarlos como discos duros externos USB y ahora tengo varios, uno de 500 GB, otro de 320 GB, y dos de 120 GB.

Cuando compré el Intel NUC compré la versión slim sin bahía para disco SATA 2.5” y solo le puse un SSD de 500 GB con conexión M.2. Tenía intención de comprar una tarjeta SDXC o una memoria USB pequeña para tenerla siempre conectada al ordenador y como una forma de ampliar la capacidad de almacenamiento. Sin embargo, ni la tarjeta microSD ni la memoria USB es barata comparada con el precio de un SSD SATA. Al final he optado por utilizar uno de esos discos duros externos que tengo para el mismo propósito. Lo que necesitaba era cambiar la ubicación de esos directorios por defecto para que en vez de estar en la carpeta de inicio de mi usuario y en el SSD estuviesen en el disco duro externo USB.

La ubicación de los directorios de usuario se puede cambiar modificando las rutas en el archivo de configuración ~/.config/user-dirs.dirs. En este caso poniendo la ubicación del punto de montaje del disco duro externo USB.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# This file is written by xdg-user-dirs-update
# If you want to change or add directories, just edit the line you're
# interested in. All local changes will be retained on the next run.
# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
# absolute path. No other format is supported.
#
XDG_DESKTOP_DIR="/run/media/picodotdev/bmovenegro/Escritorio/"
XDG_DOWNLOAD_DIR="/run/media/picodotdev/bmovenegro/Descargas/"
XDG_TEMPLATES_DIR="/run/media/picodotdev/bmovenegro/Plantillas/"
XDG_PUBLICSHARE_DIR="/run/media/picodotdev/bmovenegro/Público/"
XDG_DOCUMENTS_DIR="$HOME/Documentos"
XDG_MUSIC_DIR="/run/media/picodotdev/bmovenegro/Música/"
XDG_PICTURES_DIR="/run/media/picodotdev/bmovenegro/Imágenes/"
XDG_VIDEOS_DIR="/run/media/picodotdev/bmovenegro/Vídeos/"

Cambiar los valores de los directorios de usuario a otro directorio requiere que esos directorios estén disponibles a iniciar sesión de usuario en el entorno de escritorio. Para que el disco duro externo USB se monte al iniciar el sistema he definido un servicio de tipo mount para systemd en la ubicación /etc/systemd/system/run-media-picodotdev-bmovenegro.mount con el siguiente contenido. El disco duro se monta en el directorio /run/media/bmovenegro/.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Unit]
Description=Disk bmovenegro mount
[Mount]
What=/dev/disk/by-uuid/f0fe2765-00aa-4c2a-adef-83a3264b8f4f
Where=/run/media/picodotdev/bmovenegro/
Type=ext4
Options=defaults
[Install]
WantedBy=multi-user.target

El identificador UUID de un dispositivo se obtiene con el comando lsblk.

1
2
3
4
5
6
7
8
9
$ lsblk -o +uuid,name
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT UUID NAME
sda 8:0 0 465,8G 0 disk sda
└─sda1 8:1 0 465,8G 0 part /run/media/picodotdev/bmovenegro f0fe2765-00aa-4c2a-adef-83a3264b8f4f sda1
nvme0n1 259:0 0 465,8G 0 disk nvme0n1
├─nvme0n1p1 259:1 0 511M 0 part /boot 7869-6341 nvme0n1p1
└─nvme0n1p2 259:2 0 465,3G 0 part 3067348f-58f4-48fd-be4b-7cb3aae80dd1 nvme0n1p2
└─lvm 254:0 0 465,3G 0 crypt c0fVvN-1QTv-HccD-tyEt-cl49-GM7M-xDNGJQ lvm
└─vg-root 254:1 0 465,3G 0 lvm / 4b561dc9-bbd6-4433-bc53-c879bde68042 vg-root

Con esta configuración para el explorador de archivos en este caso Nautilus de GNOME la ubicación de estos archivos de usuario es transparente, los directorios aparecen en el panel lateral. Aunque en el directorio home siguen existiendo las carpetas originales de los directorios de usuario realmente cuando se hace clic en el panel lateral del directorio Documentos se muestra el contenido /run/media/bmovenegro/Documentos y no de ~/Documentos.

Directorios de usuario en el directorio home y en disco USB externo

En el caso de GNU/Linux los directorios de usuario además están localizados de forma automática, esto es al listar los directorios los nombres están en el idioma del usuario de forma consistente, al contrario de como ocurre en otros sistemas operativos que sus nombres se mantienen en inglés aún en su explorador de archivos aparecer en el idioma del usuario creando una inconsistencia entre los nombres que se listan desde la terminal y los que aparecen en su explorador de archivos.

Directorios de usuario en el directorio home y en disco USB externo

» Leer más, comentarios, etc...

Picando Código

Comunidad GNU/Linux en Español

noviembre 04, 2019 01:30

Spartan Tux

Me encontré en Reddit con una comunidad de usuarios GNU/Linux en Español: /r/GNULinuxEsp. La descripción dice:

Cualquier lector podrá enviar contenido sobre el tema que nos interesa, GNU/Linux y cultura libre, este pasara por una moderación previa antes de ser aprobado, recordar que el contenido es de preferencia en idioma español. La Ñ rocks!!!

Reddit es uno de los sitios que visito regularmente, así que me alegró empezar a seguir esta comunidad en español. Algunos artículos son noticias del mundo del software libre y cultura libre traducidos. Pero hay muchas guías, tutoriales y artículos de opinión que me resulta muy interesante leer en mi idioma nativo.

También me resultó un buen recurso para leer más blogs. No es novedad que los blogs están en peligro de extinción, así que descubrir blogs vigentes de temas interesantes está bueno. No sé qué otra comunidad virtual haya hoy en día compartiendo información útil y que no sea parte de alguna red social o estafa intentando hacer dinero con anuncios y títulos llamativos. Pero si saben de alguna, agradezco lo compartan en los comentarios. Y si quieren compartir su blog, ¡bienvenido sea!

» Leer más, comentarios, etc...

Coding Potions

VueJS - Métodos y propiedades computadas

noviembre 02, 2019 12:00

Introducción

En el capítulo de hoy vamos a ver una de las partes fundamentales de Vue, los métodos y las propiedades computadas.

Ahora veremos que los métodos los podemos llamar desde dentro de las vistas, por lo que serán esenciales para dotar de lógica a los componentes web que hemos creado hasta ahora.

Este artículo es bastante importante si estás aprendiendo Vue y te recomiendo que lo leas varias veces si es necesario para entenderlo.

Métodos

Vue también tiene métodos, y funcionan igual que en los otros lenguajes de programación, es decir, se pueden crear para poder llamarlos desde otra parte del código y así evitar la repetición de código, veamos un ejemplo:

<script>
  export default {
    methods: {
      print() {
        console.log("Esto es un método, por el momento no se puede ejecutar");
      }
    }
  };
</script>

Como ves, para crear métodos lo que tienes que hacer es poner dentro del componente un objeto llamado methods. Siempre se tiene que llamar así y siempre tiene que ser un objeto. Dentro, creamos las funciones, es decir, tantos métodos como queramos separados por comas. Por ejemplo si quieres crear dos métodos:

<script>
  export default {
    methods: {
      print() {
        console.log("Esto es un método, por el momento no se puede ejecutar");
      },
      anotherPrint() {
        console.log("Esto es otro método, por el momento no se puede ejecutar");
      }
    }
  };
</script>

Si desde un método quieres llamar a otro, o quieres acceder a una variable del data, tenemos que usar siempre el this. Si no sabes lo que es el data te recomiendo que le eches un ojo a este tutorial que escribí: Sistema de vistas de Vue

Veamos un ejemplo accediendo a una variable:

<script>
  export default {
    data: () => ({
      value: 20
    }),
    methods: {
      calculate() {
        console.log(this.value * 5);
      }
    }
  };
</script>

Ahora un ejemplo llamando a otro método:

<script>
  export default {
    data: () => ({
      value: 20
    }),
    methods: {
      double() {
        this.value = this.value * 2;
        this.print();
      },
      print() {
        console.log(this.value);
      }
    }
  };
</script>

¿Fácil no? Pues ahora vamos a conectar los métodos en las vistas para que los puedas llamar desde ahí, para ello vamos a hacer que la función devuelva el valor para que se pinte en la vista:

<template>
  <div class="content">
    {{calculate()}}
  </div>
</template>

<script>
  export default {
    data: () => ({
      value: 20
    }),
    methods: {
      calculate() {
        return this.value * 2;
      }
    }
  };
</script>

Como pasaba al pintar variables, para poder llamar a los métodos necesitas poner dobles llaves {{ }}. Otro detalle es que no necesitas hacer uso del this. Es decir, como conclusión, siempre que accedas a algo de la lógica del componente (lo que va dentro de la etiqueta script), tienes que usar dobles llaves y no tienes que usar el this. En cambio, si quieres llamar desde dentro de la lógica a algo necesitas usar siempre el this.

Otra cosa que se puede hacer dentro de los métodos es crear variables auxiliares locales a ese método, es decir, a esas variables no se puede acceder desde otros métodos.

<script>
  export default {
    data: () => ({
      value: 20
    }),
    methods: {
      calculate() {
        let aux = this.value * 2;
        console.log(aux);
      }
    }
  };
</script>

Por mucho que queramos, desde otro método no podremos acceder a la variable aux, de no ser que hagamos un return de esa variable dentro de la función o que la declaremos dentro del data.

Estas variables locales (puedes crearlas con let o const) viene muy bien cuando necesitas hacer cálculos o operaciones intermedias con valores y no necesitas almacenar su valor. Como se declaran dentro de la función, cuando vuelvas a llamar a la función volveran a resetearse. Si quieres guardar el valor de las variables locales entre ejecuciones del método tienes que declarar la variable arriba:

<template>
  <div class="content">
    {{calculate()}} - {{calculate()}} - {{calculate()}}
  </div>
</template>

<script>
  let total = 0;
  export default {
    methods: {
      calculate() {
        total++
        return total;
      }
    }
  };
</script>

Si te fijas, como ahora el valor que accedemos está fuera del componente (arriba del export default) no tienes que poner el this. He llamado desde la vista 3 veces al valor para que se vea que la variable total se mantiene para cada ejecución.

Si declaras total como variable local dentro de la función en la vista se pintaría 0 - 0 - 0.

Eventos de click

Dentro de vue existe una cosa llamada eventos que veremos con más detalle más adelante. Por el momento tienes que saber que existe un evento click que sirve para saber cuándo el usuario pulsa sobre un elemento del HTML.

En la vista, dentro del evento de click podemos ejecutar un método del componente:

<template>
  <div class="content">
    <button @click="calculate()">Calculate</button>
  </div>
</template>

<script>
  export default {
    data: () => ({
      value: 20
    }),
    methods: {
      calculate() {
        let aux = this.value;
        console.log(this.double(aux));
      },
      double(value) {
        return value * 2;
      }
    }
  };
</script>

<style scoped></style>

Como te habrás dado cuenta, si quieres crear un evento de click, en este caso en un botón, lo puedes hacer poniendo el @ y el nombre del evento, en este caso el de click. Dentro de las comillas del evento de clic, en la vista, se llama al método asociado a ese método, es decir, el método que se va a ejecutar en cada clic. Además, dentro de las comillas no necesitas poner llaves para llamar al método como hasta ahora.

Listo, si abres la página y haces click en el botón, en la consola del navegador verás que se pinta el número.

Lo bueno de los eventos de click es que los podemos asociar con cualquier etiqueta del HTML, no tiene por qué ser botones (aunque por temas de accesibilidad lo mejor es botones), por ejemplo:

<template>
  <div class="content">
    <span @click="calculate()">Calculate</span>
  </div>
</template>

<script>
  export default {
    data: () => ({
      value: 20
    }),
    methods: {
      calculate() {
        let aux = this.value;
        console.log(this.double(aux));
      },
      double(value) {
        return value * 2;
      }
    }
  };
</script>

<style scoped></style>

Este ejemplo funciona exactamente igual que el ejemplo anterior.

Como las propiedades del data son reactivas, cuando actualicemos su valor dentro de un método se actualizará cambiando automáticamente la vista:

<template>
  <div class="content">
    <button @click="increment()">Increment</button>
    <span> Current value: {{value}}</span>
  </div>
</template>

<script>
export default {
  data: () => ({
    value: 20
  }),
  methods: {
    increment() {
      this.value++;
    }
  }
};
</script>

Eventos de click de vue

Propiedades computadas

Las propiedades computadas o computed properties son una característica muy interesante de Vue. Como su nombre indica, son propiedades asociadas a componentes que son computadas, lo que es lo mismo, son variables a las que antes se le pueden aplicar una serie de cálculos o transformaciones.

Como hemos hablado con anterioridad, las variables que creas en el data no pueden depender de otras variables del data, por ejemplo no puedes crear una variable en el data cuyo valor sea el doble de otra variable. Las propiedades computadas vienen a resolver este problema.

Las computadas son funciones que SIEMPRE tienen que devolver un valor. Actúan como un getter de otros lenguajes de programación. Veamos un ejemplo sencillo.

<template>
  <div class="content">
    {{double}}
  </div>
</template>

<script>
  export default {
    data: () => ({
      value: 20
    }),
    computed: {
      double() {
        return this.value * 2;
      }
    }
  };
</script>

<style scoped></style>

Como en los métodos, dentro del objeto computed creamos todas funciones separadas por comas. Es importante saber que las computadas nunca pueden recibir un parámetro desde fuera de la función. Si necesitas pasar un valor a una computada tienes que crear un método.

Dentro de cada computada con el this accedemos a los valores del data o a los métodos que necesitemos.

Las computadas se comportan igual que las variables. Si por ejemplo llamas dos veces a una computadas, si el resultado es el mismo, Vue es lo suficientemente listo como para no tener que recalcular cada vez su valor.

Además, las variables computadas son reactivas también y actualizarán su valor en la vista cuando cambie su valor.

Otra cosa muy interesante de las computadas es que Vue las trata como una variable más y no como métodos. Esto quiere decir que dentro de los métodos o de la vista podemos llamar a las propiedades computadas como si fueran variables:

<template>
  <div class="content">
    {{double}} - {{calculate()}}
  </div>
</template>

<script>
  export default {
    data: () => ({
      value: 20
    }),
    computed: {
      double() {
        return this.value * 2;
      }
    },
    methods: {
      calculate() {
        return this.double + 1;
      }
    }
  };
</script>

<style scoped></style>

Cuando el valor de la variable value cambie, como se usa dentro de la computada, la computada se actualizará automáticamente y su valor se mostrará en la vista

Diferencia entre method y computed

A simple vista los métodos y las computadas se pueden confundir, porque un método puede usarse como una especie de computada si devolvemos valores también, pero hay diferencias.

En primer lugar, como hemos dicho las computadas son reactivas y su valor se actualiza solo. Con un método que devuelva algo del data solo se ejecutaría la primera vez y no reaccionaria a un cambio en la variable del data.

Otra diferencia es que las propiedades computadas tienen caché, es decir, utilizar propiedades computadas es más óptimo porque si Vue detecta que la computada va a devolver el mismo valor, no ejecutará la computada ahorrando cálculos .Los métodos se ejecutan siempre cada vez aunque el resultado sea el mismo.

Aunque se puede hacer, dentro de las computadas se recomienda no llamar a los métodos del componente. Como hemos dicho el propósito de las computadas es devolver valores del definidos en el data pero con alguna transformación. Además se pueden usar las computadas para devolver un valor que dependa de varias variables del data.

Conclusiones

Los métodos y las computadas en Vue son de lo que más va a usar en todas tus aplicaciones web, es muy importante que las entiendas bien, lo mejor es que practiques por tu cuenta porque así es como mejor se aprende.

En posteriores capítulos trataremos el ciclo de vida de los componentes de Vue para que puedas controlar lo que ocurre cuando se crea, se actualiza y se destruyen los componentes.

» Leer más, comentarios, etc...

Blog Bitix

Información básica del sistema y entorno de escritorio desde la terminal de GNU/Linux

noviembre 01, 2019 04:00

GNU
Linux

Los comandos neofetch y screenfetch permiten obtener una información básica del sistema desde la terminal. Esta información incluye la distribución GNU/Linux que se esté usando, la versión del kernel de Linux, el número de paquetes instalados, que intérprete de comandos se usa y su versión, la resolución de la pantalla, el gestor de ventanas y su tema, la colección de iconos, terminal y fuente de la terminal, CPU, GPU y cantidad de memoria usada y total del sistema.

Al realizar una captura de pantalla del escritorio es muy interesante mostrar una terminal con esta información. Es habitual hacerlo al mostrar la personalización del escritorio con su tema de iconos, que entorno de escritorio es el usado, la configuración de colores de la terminal, etc. que permite a otros usuarios que vean esa captura obtener mucha información para obtener la misma personalización. O conocer la CPU, GPU y kernel del sistema en que se tomó la captura.

Los comandos neofetch y screenfetch son muy similares en la salida que producen, quizá prefiero neofetch porque tiene menos dependencias sobre otros paquetes y ocupa menos. Con la opción –help de cada uno de ellos muestran las opciones de personalización que poseen.

1
2
3
4
5
$ neofetch
$ screenfetch
$ neofetch -help
$ screenfetch -help

En las imágenes se aprecia que mi distribución es Arch Linux, mi equipo es un Intel NUC8i5BEK, en el momento de escribir el artículo tengo la versión 5.3.7 del núcleo de Linux, un millar de paquetes instalados y unos pocos de paquetes de Flatpak, mi interprete de comandos es Bash, mi entorno de escritorio es GNOME sin apenas personalización, la CPU del NUC es una Intel i5-8259 de 4 núcleos y 8 hilos que tiene una GPU integrada Intel Iris Plus Graphics 655, con algunos programas abiertos que está consumiendo unos 3 GiB de los 32 GiB disponibles.

Información del sistema proporcionada por neofetch

Ambos comandos están disponibles en los repositorios de paquetes de la distribución que se esté usando.

» Leer más, comentarios, etc...

Una sinfonía en C#

Array map en Javascript, se usa mal?

octubre 27, 2019 07:24

En el post anterior hablamos sobre Array.forEach y su funcionamiento, ahora vamos con el que hoy en día (creo yo) es el segundo método de Array más usado, map.

Map recorre un array y crea un nuevo array como resultado de la invocación a una función dada.
En palabras simples, map recorre un array y aplica una función (que es parámetro) a cada elemento del mismo, y crea un nuevo array con los elementos resultantes.

Un ejemplo simple (y no muy útil)

var a = [1, 2, 3, 4];
const m = a.map(function(item){
     return item;
});
console.log(m);

O lo que sería lo mismo:

m = array1.map(x => x);

Lo que ocurre es que la función que se pasa como parámetro es quien genera el resultado, en este caso devuelve el mismo elemento que recibe (en item se pasa cada valor del array, uno a la vez) entonces el resultado es igual, pero es un nuevo array, no es el mismo.

Evidentemente este ejemplo no tiene mucha utilidad, en genera uno realiza operaciones dentro de la función, por ejemplo:

var a = [1, 2, 3, 4];
var m = a.map(function(item){
     return item * 2;
});
console.log(m);

En este caso la función recibe en item cada elemento y retorna su valor * 2.
Por supuesto que dentro de esta función podemos hacer cualquier cosa, incluso ignorando los valores del array original.

var a = [1, 2, 3, 4];
var m = a.map(function(item){
     console.log(+new Date());
});

Alguna características de map:

  • La función recibe tres parámetros, el item actual, el índice del elemento y el array orginal, con lo cual podemos operar sobre el array orginal.
  • Se invoca a la función una vez por cada elemento del array que contiene valores.
  • Si no se usa el array resultante o no se retorna un valor en el callback se considera incorrecto el uso de map, en tal caso se puede usar for-of o forEach.
  • Los elementos recorridos por map son determinados antes de iniciar el recorrido.
  • Los elementos que hayan cambiando luego de iniciada la iteración se considerará su valor actual.
  • Los elementos removidos no son recorridos (ya que sus valores serían inválidos).
  • Es posible pasar el contexto como segundo parámetro (que tomaría el valor de this).

Como vemos muchas de estas características son iguales a forEach.

Entonces, cuándo usamos map y cuándo forEach?

Como dice más arriba, si no vamos a utilizar el array resultante se considera un antipatrón usar map y debemos echar manos de forEach o for-of.

El uso tal vez más común al consultar una API sería "reformatear" los objetos obtenidos.

var a = [{"nombre":"Marcelo", "nombre":"Leonardo"}];
var m = a.map(function(item){
     return {"Nombre":item.nombre}
});

console.log(m);

Nos leemos.

» Leer más, comentarios, etc...

Blog Bitix

Cobertura de código y mutation testing en pruebas unitarias con JaCoCo y PIT en Java

octubre 25, 2019 04:30

En el caso extremo una cobertura de código del cien por cien pero que no tenga ningún assert pasa los teses pero que en realidad no comprueba nada así que por si sola no es garantía de tener teses efectivos. Mutation testing da una medida adicional a la cobertura de los teses más completa y efectiva que simplemente la cobertura de código ejecutado por los teses unitarios.

Java

Una medida que se suele emplear para medir la calidad o efectividad de los teses unitarios es su cobertura de código que consiste en la cantidad de código ejercitado del total por las pruebas unitarias con los casos de prueba y fixtures empleados. Sin embargo, la cobertura de código no es una medida fiable para conocer si los casos de prueba empleados son precisos y completos. La cobertura de código puede seguir siendo del cien por cien si se sustituye un un mayor que por un mayor que e igual o faltan casos de prueba que ejerciten los límites de las condiciones, los teses seguirán siendo correctos.

Para complementar la cobertura de código y obtener una medida de la precisión y completitud de los teses se emplea mutation testing. Esta forma de pruebas analiza el código, realiza operaciones de mutación en el código y posteriormente ejecuta las pruebas contra el código mutado y genera un resultado en el que indica cuáles de las mutaciones realizadas ha sobrevivido pasando todos los teses o cuantas mutaciones han muerto porque los teses han fallado, si alguna mutación sobrevive los teses no son precisos y completos al cien por cien.

PIT es una librería que permite realizar mutation testing en Java. Las operaciones de mutación que realiza en el código pueden ser en los condicionales, incrementos, invertir negativos, matemáticas, negar condicionales, cambiar los valores de retorno o eliminar llamadas a métodos sin retorno. Un ejemplo de mutaciones son realizar mutaciones en los límites de comparaciones, cambiando un < por un <= y comprobar si con operador mutado la mutación sobrevive pasando todos los teses.

Original Mutación
< <=
<= <
> >=
>= >

En siguiente ejemplo, la clase TicketPriceCalculator calcula el precio de los billetes de un grupo de viajeros. La lógica del calculador de precios determina el precio en función de la edad de los pasajeros y de si cumplen la condición de familia se les aplica un descuento.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package io.github.picodotdev.blogbitix.mutationtesting;
public class Passenger {
private int age;
public Passenger(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package io.github.picodotdev.blogbitix.mutationtesting;
import java.util.List;
public class TicketPriceCalculator {
public static double FAMILY_DISCOUNT = 0.05;
private static int ADULT_AGE = 18;
private static int FREE_TICKET_AGE_BELOW = 3;
public double calculatePrice(List<Passenger> passengers, int adultTicketPrice, int childTicketPrice) {
double price = countAdults(passengers) * adultTicketPrice + countChildrens(passengers) * childTicketPrice;
double discount = (isFamily(passengers)) ? FAMILY_DISCOUNT : 0d;
return price * (1 - discount);
}
private long countAdults(List<Passenger> passengers) {
return passengers.stream().filter(this::isAdult).count();
}
private long countChildrens(List<Passenger> passengers) {
return passengers.stream().filter(this::isChildren).count();
}
private boolean isAdult(Passenger passenger) {
return passenger.getAge() > ADULT_AGE;
}
private boolean isChildren(Passenger passenger) {
return passenger.getAge() > FREE_TICKET_AGE_BELOW && passenger.getAge() <= ADULT_AGE;
}
private boolean isFamily(List<Passenger> passengers) {
return countAdults(passengers) >= 2 && countChildrens(passengers) >= 2;
}
}

La siguiente batería de teses proporciona una cobertura de teses del cien por cien tanto para la cobertura del código como para las mutaciones como se muestran en los informes de JaCoCo para la cobertura de código y de PIT para la cobertura de mutaciones, después de haber realizado cambios tanto en el código como en los teses para obtener estos resultados.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package io.github.picodotdev.blogbitix.mutationtesting;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Assertions;
public class TicketPriceCalculatorTest {
private static int ADULT_TICKET_PRICE = 40;
private static int CHILD_TICKER_PRICE = 20;
private TicketPriceCalculator calculator;
@BeforeEach
public void before() {
calculator = new TicketPriceCalculator();
}
@Test
public void calculatePriceForOneAdult() {
List<Passenger> passengers = new ArrayList<>();
Passenger passenger = new Passenger(20);
passengers.add(passenger);
double price = calculator.calculatePrice(passengers, ADULT_TICKET_PRICE, CHILD_TICKER_PRICE);
Assertions.assertEquals(ADULT_TICKET_PRICE, price, 0);
}
@Test
public void calculatePriceForChild() {
List<Passenger> passengers = new ArrayList<>();
Passenger childPassenger = new Passenger(15);
passengers.add(childPassenger);
double price = calculator.calculatePrice(passengers, ADULT_TICKET_PRICE, CHILD_TICKER_PRICE);
Assertions.assertEquals(CHILD_TICKER_PRICE, price, 0);
}
@Test
public void calculatePriceForFamily() {
List<Passenger> passengers = new ArrayList<>();
Passenger adultPassenger1 = new Passenger(20);
Passenger adultPassenger2 = new Passenger(20);
Passenger childPassenger3 = new Passenger(12);
Passenger childPassenger4 = new Passenger(4);
passengers.add(adultPassenger1);
passengers.add(adultPassenger2);
passengers.add(childPassenger3);
passengers.add(childPassenger4);
double price = calculator.calculatePrice(passengers, ADULT_TICKET_PRICE, CHILD_TICKER_PRICE);
Assertions.assertEquals((2 * ADULT_TICKET_PRICE + 2 * CHILD_TICKER_PRICE) * (1 - TicketPriceCalculator.FAMILY_DISCOUNT), price, 0);
}
@Test
public void calculatePriceForNoFamilyByNoAdults() {
List<Passenger> passengers = new ArrayList<>();
Passenger adultPassenger1 = new Passenger(20);
Passenger childPassenger2 = new Passenger(12);
Passenger childPassenger3 = new Passenger(4);
passengers.add(adultPassenger1);
passengers.add(childPassenger2);
passengers.add(childPassenger3);
double price = calculator.calculatePrice(passengers, ADULT_TICKET_PRICE, CHILD_TICKER_PRICE);
Assertions.assertEquals(1 * ADULT_TICKET_PRICE + 2 * CHILD_TICKER_PRICE, price, 0);
}
@Test
public void calculatePriceForNoFamilyByNoChildren() {
List<Passenger> passengers = new ArrayList<>();
Passenger adultPassenger1 = new Passenger(20);
Passenger adultPassenger2 = new Passenger(20);
Passenger childPassenger3 = new Passenger(12);
passengers.add(adultPassenger1);
passengers.add(adultPassenger2);
passengers.add(childPassenger3);
double price = calculator.calculatePrice(passengers, ADULT_TICKET_PRICE, CHILD_TICKER_PRICE);
Assertions.assertEquals(2 * ADULT_TICKET_PRICE + 1 * CHILD_TICKER_PRICE, price, 0);
}
@Test
public void calculatePriceForChildNarrowCase() {
List<Passenger> passengers = new ArrayList<>();
Passenger childPassenger = new Passenger(18);
passengers.add(childPassenger);
double price = calculator.calculatePrice(passengers, ADULT_TICKET_PRICE, CHILD_TICKER_PRICE);
Assertions.assertEquals(CHILD_TICKER_PRICE, price, 0);
}
@Test
public void calculatePriceForFreeTicketNarrowCase() {
List<Passenger> passengers = new ArrayList<>();
Passenger childPassenger = new Passenger(3);
passengers.add(childPassenger);
double price = calculator.calculatePrice(passengers, ADULT_TICKET_PRICE, CHILD_TICKER_PRICE);
Assertions.assertEquals(0, price, 0);
}
}

Sin los casos de prueba calculatePriceForChildNarrowCase y calculatePriceForFreeTicketNarrowCase los teses son correctos, pero si PIT con una edad de 16 realiza una operación de mutación cambiando los límites de la condición de _passenger.getAge() > FREE_TICKET_AGE_BELOW && passenger.getAge() <= ADULTAGE, la mutación de <= a <_ sobrevive, esto inidica que los teses y casos de prueba no son totalmente precisos. Para que esta mutación no sobreviva hay que añadir estos dos teses que se encargan de comprobar los límites de las condiciones. El valor del caso de prueba que se debe utilizar es el valor del límite a partir del cual una persona se considera adulta, es un niño si su edad está comprendida a partir de 3 y menor e igual que 18.

Informe de teses correcto y de PIT incorrecto

El caso de prueba calculatePriceForFamily prueba que una familia esté formada por 2 adultos y 2 menores, PIT realiza las mutaciones para considerar una familia en el caso de ser de 3 adultos o 3 menores, la prueba de calculatePriceForFamily mata estas mutaciones haciendo que los teses sean precisos y completos. La cobertura de teses de mutación llega al cien por cien. En el informe de PIT se observa una descripción y número de mutaciones que ha realizado entre ellas divisiones en vez de multiplicaciones, substracciones en vez de sumas, reemplazo de valores de retorno o cambios y negaciones en condicionales. Los teses calculatePriceForNoFamilyByNoAdults y calculatePriceForNoFamilyByNoChildren completan la cobertura de todas las ramas del método isFamily.

Informe de pruebas de JUnit, de cobertura de JaCoCo y de mutación de PIT

Para generar los informes de cobertura de código y de mutación en Java y usando Gradle como herramienta de construcción las herramientas JaCoCo y PIT proporcionan un complemento o plugin que hay que añadir al archivo de construcción además de proporcionar algunas opciones de configuración en la sección pitest, entre estas propiedades está mutators en la que se puede indicar los mutators que PIT emplea para lanzar los teses con mutaciones. Los informes se generan en el directorio build/reports/. Realizar mutation testing solo requiere cierta configuración en el archivo de construcción del proyecto.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
buildscript {
repositories {
jcenter()
}
configurations.maybeCreate('pitest')
dependencies {
classpath 'info.solidsoft.gradle.pitest:gradle-pitest-plugin:1.4.5'
pitest 'org.pitest:pitest-junit5-plugin:0.8'
}
}
plugins {
id 'java'
id 'jacoco'
id 'info.solidsoft.pitest' version '1.4.5'
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
}
repositories {
jcenter()
}
compileJava {
sourceCompatibility = 11
targetCompatibility = 11
}
test {
useJUnitPlatform()
}
pitest {
testPlugin = 'junit5'
targetClasses = ['io.github.picodotdev.blogbitix.*']
threads = 4
outputFormats = ['HTML']
timestampedReports = false
mutators = ['DEFAULTS']
}
1
$ ./gradlew test jacocoTestReport pitest

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew test jacocoTestReport pitest.

» Leer más, comentarios, etc...

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin