Weblogs Código

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

Cómo usar métodos y propiedades computadas en Vue

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...

Picando Código

Darksiders: Warmastered Edition

octubre 31, 2019 12:00

En un futuro post-apocalíptico donde los humanos por fín se extinguieron, ángeles y demonios se pelean por el control de la Tierra y entre medio de todo este lío están los Cuatro Jinetes del Apocalipsis que deben traer equilibrio al orden. Darksiders es un juego de acción y aventura que usa varios de estos mitos de distintas religiones que algunas personas se toman en serio. Lo jugué y terminé hace un par de meses en Nintendo Switch. El título fue publicado originalmente en 2010 para PlayStation 3 y Xbox 360, pero la versión “Warmastered Edition” (gráficos de alta definición y mejoras de rendimiento) se publicó en varias plataformas más a partir de 2016 y en 2019 para Nintendo Switch.

Darksiders: Warmastered Edition

Darksiders: Warmastered Edition

Se trata de un juego en tercera persona donde controlamos a War, uno de los Jinetes del Apocalipsis. Mezcla bastante bien los dos géneros “acción” y “aventura”. Es inevitable la comparación con los juegos de Zelda de la época del Nintendo 64. Se premia la exploración, y el tipo de puzzles y niveles que hay que superar me hace acordar a los calabozos clásicos (más que a los Shrines de Breath Of The Wild). Incluso para atacar tenemos un mecanismo de apuntar a los enemigos muy similar al que se creó en la transición a 3D de Link en Ocarina Of Time (y siguientes títulos) conocido como Z-Targeting. Nos vamos a encontrar cofres, paredes para escalar, cosas para empujar y demás.

Darksiders - puzzles

Darksiders – puzzles

Darksiders - Cofres

Darksiders – Cofres

Entrando en detalle con la acción, los enemigos tienen un tono bastante oscuro y las peleas son bien violentas y sanginarias. La mecánica de combate entra en el género hack & slash, hay partes con multitud de enemigos y batallas con jefes “finales” o gigantes. Algunos de estos jefes parecen difíciles al principio, pero tiene la característica clásica ya sea de tener que usar una habilidad aprendida recientemente o una estrategia en particular. Una vez que entendemos cómo hay que ganarles, se vuelve nada más que una tarea. Los diseños de los monstruos y personajes están bastante buenos.

Darksiders: Enemigos

Darksiders: Enemigos

Darksiders: Enemigos

Darksiders: Enemigos

Darksiders: Enemigos

Darksiders: partiendo al medio un murciélago diabólico

Durante la introducción del juego tenemos todo el arsenal, pero recién empezado el relato perdemos todas las habilidades. A lo largo de la aventura iremos adquiriendo armas, mejorándolas, aprendiendo nuevas técnicas y distintos tipos de ataques. Tiene un poco de RPG en cuanto a que podemos ir subiendo el nivel de las armas. También vamos a poder elegir los tipos de ataque y/o armas que más nos gusten.

La exploración es un elemento importante y es generalmente premiada con secretos o ítemos escondidos. Los puzzles pueden llegar a ser frustrantes pero el juego no nos exige más de lo que podemos hacer con lo que tenemos en el momento. Pero nos obliga a pensar para tratar de resolver algunas situaciones, algo que me sigue obligando a compararlo con Zelda porque es la sensación que me daba al jugarlo.

Es interesante cómo se va desarrollando la historia. Cuando los mitos y leyendas de la religión se usan como lo que son: ficción, se obtienen buenos resultados. Entre los personajes que nos vamos a encontrar está Ulthane, también conocido como The Black Hammer, un creador de armas con acento escocés (aunque el actor que le da la voz es Jean-Benoit Blanc, nacido en Francia pero criado en Inglaterra). Me cayó bastante simpático. También conoceremos demonios, ángeles, arcángeles y demás. La variedad de enemigos es bastante amplia, y por cada uno tenemos uno o dos poderes finales estilo “fatality” para liquidarlos cuando les queda poca energía.

Darksiders - Ulthane

Darksiders – Ulthane

También podemos interactuar con algunos objetos del mundo, y usarlos como armas o mandar volar autos de la policía:

Disculpen que siga las comparaciones, pero no en vano algunos llamaron a Darksiders lo que pasaría si Zelda y God Of War tuvieran un hijo. Pero tiene hasta una pelea con “Dark Link”… Un aspecto que me gustó del juego es la onda vieja escuela dadas sus limitaciones. No todos los juegos tienen que ser open world donde podemos interactuar con cada elemento que veamos y el mapa es tan grande como una ciudad en la vida real. Las limitaciones le dan un toque interesante, como más simplificado: si no puedo pasar por esta puerta es porque me falta hacer algo, etc. Es como un toque retro al estilo los primeros juegos 3D, y no está mal.

Darksiders

La mayor crítica que tengo es la cámara. Esto también tiene un toque vieja escuela, pero en vez de hacerlo mejor me recordaba a pesadilas como Gex 64: Enter the Gecko. A veces simplemente te demora al hacerte caer o no poder hacer bien algo porque la cámara se posiciona en el lugar más incómodo posible o salta de un lugar a otro. Pero lo peor es durante las peleas, particularmente cuando son varios enemigos. Hay escenarios que no están bien diseñados (o no lo testearon lo suficiente), y se vuelve sumamente frustrante. El sistema de apuntar a los enemigos tampoco está tan pulido como en la saga de Zelda.

Más allá del detalle de la cámara, en líneas generales me resultó un juego sumamente entretenido y recomendable. Hace poco salió también Darksiders II Deathinitive Edition para Nintendo Switch. Supuestamente mejora bastante y un seguidor de la saga me dijo que era su preferido. Así que seguramente lo termine comprando más adelante, después de terminar unos cuantos juegos que tengo en la lista…

Si les gustan los juegos clásicos de Zelda (Breath Of The Wild es un tema completamente distinto), los juegos hack & slash y este tipo de mitología, les sugiero probar Darksiders. Si llego a jugar Darksiders II comentaré por acá mis impresiones. Algo conveniente es que Darksiders: Warmastered Edition se encuentra a un precio bastante barato:
Amazon ES
Amazon US

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

Picando Código

Randall Munroe – 10.000

octubre 29, 2019 12:00

Si no saben quién es Randall Munroe y están leyendo este post, están de suerte. Es un científico con un título en física que abandonó su trabajo en la NASA al hacerse famoso dibujando figuras de palitos en xkcd: un webcómic sobre romance, ciencia, sarcasmo, matemáticas y más. Pueden leer el webcómic en su sitio web (y su traducción al español). El pasado 10 de octubre, Randall Munroe estuvo en Edimburgo presentando su último libro: HOW TO:

Para cada tarea que puedas querer realizar, hay una forma correcta, una forma incorrecta, y una forma tan monumentalmente compleja, excesiva y desaconsejable que nadie siquiera la intentaría. How To es una guía para el tercer enfoque. Está lleno de consejos muy poco prácticos para de todo desde aterrizar un avión a cavar un agujero.

El libro es genial, y la presentación estuvo excelente. Randall Munroe es un tipo sumamente inteligente y divertido, como se refleja en su cómic y sus libros. Define “How To” como el libro de auto-ayuda más inútil que se haya escrito. Si bien las “guías” son sumamente complejas y rebuscadas es muy gracioso y podemos aprender mucho en serio más allá del enfoque humorístico. En el prólogo comenta la idea de éste cómic de xkcd:

xkcd - Ten thousandEl cómic cuenta cómo el autor intenta no burlarse de las personas cuando admiten no saber algo. La cuenta que hace es que por cada cosa que “alguien sabe” cuando son adultos, por día hay un promedio de 10.000 personas (en Estados Unidos) que lo van a escuchar por primera vez. Un ejemplo que dio durante la charla fue el Titanic. Cómo a medida que la película se va haciendo más vieja, va a haber personas que se enteren de la existencia de la película primero, y después que está basada en un barco que existió y se hundió a principios del siglo 20.

Cuando nos burlamos de alguien porque no sabe algo, le estamos haciendo sentir inseguridad, vergüenza, y otros tantos sentimientos negativos. Además le enseñamos a no admitir que no conoce algo. La propuesta contraria es aprovechar la oportunidad para enseñarle a la otra persona qué tiene de especial eso que no sabe. Esto termina siendo una experiencia positiva para ambas, quien aprende y quien lo enseña. Ni que hablar del hecho de que cada uno de nosotros está del otro lado todos los días. Me parece una idea excelente y digna de difundir, alentar y practicar.

La última vez que lo hice concientemente fue durante una charla en la que una persona dijo “Nunca vi una película de Star Wars”. Teniendo presente lo de “una de las 10.000 suertudas de hoy”, le dije “tenés la suerte de poder vivir la experiencia de ver Star Wars por primera vez”. Esto hizo que se armara una charla sobre las películas donde cada uno compartía sus experiencias de cómo llegó a la saga, cuándo miró las películas, el orden que había que mirarlas y más. No sé si la persona que hizo el comentario original terminará mirando las películas, pero tuvo la oportunidad de escuchar la pasión con la que cada uno las comenta. Seguramente una experiencia mucho más positiva y alentadora que burlarse porque no ha visto Star Wars.

El evento con Randall Munroe y el libro me dejaron esto de “los 10.000 suertudos de hoy” bastante grabado. Burlarse de la otra persona porque no sabe algo que nosotros asumimos obvio o damos por sentado es una costumbre que podemos tener muy asumida casi como un reflejo. Pero podemos mejorar. Me parece una idea que hay que difundir, alentar y tener muy presente. Así que lo comparto por acá. La próxima vez que escuchen a alguien que no sabe algo que ya saben y dan por sentado, aprovechen la oportunidad para compartir lo que saben y generar una experiencia súper positiva en vez de burlarse. Tratar de ser mejor persona es una lucha diaria, pero no nos queda otra que intentarlo.

El día de la presentación había pre ordenado el libro y tuve la oportunidad de saludar a Munroe y obtener una firma en mi copia del libro. Había mucha gente así que no tenía mucho tiempo para dedicarle a cada uno, pero se tomó el tiempo de saludar y firmar copias de todos los presentes. Además de inteligente y gracioso, un tipo bastante humilde y muy buena onda. A partir de entonces tengo “mi propio xkcd”:

xkcd for Fernando

Pueden conseguir el libro HOW TO de Randall Munroe en:
Amazon ES
Amazon US

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

Variable not found

Cómo hacer que Fiddler inicie sin capturar conexiones

octubre 29, 2019 07:15

FiddlerSi soléis utilizar Fiddler para probar APIs, probablemente os resultará algo molesto el hecho de que al arrancarlo directamente comience a capturar todas las peticiones salientes desde vuestro equipo.

Pues bien, tras años sufriendo esto en silencio, he decidido invertir unos minutos a ver si existía una forma de ahorrarme los dichosos segundos que tardaba en desactivar la captura de tráfico cada vez que abría la herramienta.
Fiddler capturando peticiones

Y como efectivamente es posible, os dejo la forma de conseguirlo por si hay por ahí algún perezoso más al que pueda interesarle ;)

La solución es bien sencilla. Basta con acceder a las opciones de la herramienta en el menú Tools>Options y seleccionar la pestaña Connections del cuadro de diálogo. Una vez ahí, hay que desmarcar el check "Act as a system proxy on startup", como se muestra en la siguiente captura de pantalla:

Checkbox to act as a system proxy on startup

Tras ello, ya podemos iniciar Fiddler sin que se ponga a escuchar todo lo que circula por el equipo cual vieja'l visillo. Bueno, al arrancar simplemente veremos la petición que hace el propio Fiddler para determinar si existen nuevas versiones de la aplicación:

Arrancando Fiddler sin capturar peticiones

¿Y si queremos volver capturar peticiones? Pues como podemos suponer, para activar de nuevo esta funcionalidad sólo tendríamos que marcar el menú File>Capture traffic o bien pulsar su tecla aceleradora F12. Vaya, como siempre ;)

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 377

octubre 28, 2019 07:25

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

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» 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...

Fixed Buffer

Entity Framework Core 3.0: ¿qué novedades nos trae?

octubre 22, 2019 08:00

La imagen muestra el logo de Entity Framework Core

Hace unas semanas desde que se hizo pública la versión 3.0 de .Net Core y nos ha traído muchas novedades: C# 8, soporte ampliado en Windows, mejoras de rendimiento, Worker Services… Y como no, también nos trae una nueva versión de Entity Framework Core.

Son muchas las novedades que nos trae esta nueva release, que nos aportan mejoras de rendimiento, pero también cambios en el diseño. En esta entrada vamos a ver algunas de las características más importantes de esta nueva versión, aunque te recomiendo que le eches un ojo a la lista completa de cambios que trae Entity Framework Core 3.0 (y también a la lista de breaking changes).

Instrucción SQL única por consulta LINQ

Se ha mejorado el motor de generación de sentencias SQL, anteriormente, consultas que hiciésemos con varias tablas, podían llevar a ejecutar varias consultas a la base de datos en vez de una sola (que es lo que esperamos al utilizarlo). Entity Framework Core 3.0 garantiza que solo se va a generar una única consulta que lanzar a la base de datos, y en caso de no poder traducirse a una única consulta, nos lanzara una excepción que nos lo va a indicar para que podamos refactorizar el código.

Restricción de la evaluación en cliente

En versiones anteriores, si una consulta no se podía traducir a SQL, se realizaba lo que se conoce como evaluación en cliente. Esto significa que se iba a lanzar a la base de datos una consulta con los máximos filtros posibles aplicados, y los que no se pueden traducir, se aplicaban en el propio cliente, a costa de traerse los datos en bruto y procesarlos en nuestra aplicación. Imagina este código (obtenido de la documentación oficial):

public static string StandardizeUrl(string url)
{
    url = url.ToLower();
    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }
    return url;
}

var blogs = context.Blogs
    .Where(blog => blog.Date > DateTime.Now.AddYears(-1) StandardizeUrl(blog.Url).Contains("dotnet"))
    .ToList();

Estamos utilizando una condición que la base de datos no puede ejecutar, por lo que el comportamiento va a ser:

  1. Hacemos una consulta que nos devuelve toda la tabla filtrada por fecha.
  2. Filtramos en memoria toda la tabla para aplicar nuestra condición personalizada.
  3. Generamos una lista con los resultados filtrados

Aunque esto a veces puede ser útil, si la tabla se hace grande va a ser una gran perdida de rendimiento. Por defecto, en versiones anteriores se permitía la consulta y se indicaba un warning. Este comportamiento por defecto a cambiado en Entity Framework Core 3.0, ahora va a lanzar una excepción que nos lo va a indicar. Si queremos hacer la evaluación en cliente, tenemos que hacerlo de forma explicita:

var blogs = context.Blogs
    .Where(blog => blog.Date > DateTime.Now.AddYears(-1))
    .AsEnumerable() //Cambiamos a Linq de objetos
    .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
    .ToList();

Compatibilidad con C# 8

La nueva versión de C# nos trae muchas mejoras y nuevas APIs, y entre ellas IAsyncEnumerable, lo que junto a «await foreach», nos va a permitir mejorar la velocidad a la que se ejecutan las consultas, ya que vamos a ir procesando los datos a medida que están disponibles. Gracias a C# en Entity Framework Core 3.0 podemos hacer algo como esto:

var orders = 
  from o in context.Orders
  where o.Status == OrderStatus.Pending
  select o;

//Cada nuevo nuevo dato obtenido los vamos a procesar inmediatamente
await foreach(var o in orders.AsAsyncEnumerable())
{
    Process(o);
} 

El tooling de Entity Framework Core 3.0 ya no forma parte de .Net Core 3.0

Un cambio de diseño importante es que .Net Core ya no trae integrado el set de herramientas para trabajar con Entity Framework Core. Es decir, vamos a necesitar instalar a parte el set de herramientas si realmente nos interesa usarlo. Esto lo podemos hacer de manera local con el archivo de manifiesto de herramientas o de manera global con el comando:

dotnet tool install --global dotnet-ef

Esta desarrollado para .Net Standard 2.1

Esta nueva versión de Entity Framework Core, está desarrollado cumpliendo con .Net Standard 2.1, esto es lo que le permite tener grandes mejoras de rendimiento, pero limita las plataformas donde se puede utilizar, .Net Core 3.0 es compatible con .Net Standard 2.1, pero .Net Framework no es compatible, por lo qué si necesitas usarlo en .Net Framework, no vas a poder utilizar Entity Framework Core 3.0.

Y un largo etcétera…

La verdad es que no se limitan a esto los cambios que nos ofrece esta nueva versión. Yo he querido resaltar los que a mi parecer son los más importantes o necesarios tener en cuenta si vienes de trabajar con versiones anteriores, pero te recomiendo encarecidamente que le eches un ojo a las nuevas características y sobre todo, a la lista de cambios que no son retrocompatibles. Verás que esta es una nueva versión muy interesante y con muchas mejoras.

**La entrada Entity Framework Core 3.0: ¿qué novedades nos trae? se publicó primero en Fixed Buffer.**

» 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