Quantcast
Viewing all 2707 articles
Browse latest View live

Fixed Buffer: ClosedXML, una manera fácil de dar formato a nuestros .xlsx

Image may be NSFW.
Clik here to view.
closedxml

Hace ya unos meses que empezamos en este blog, con una entrada sobre ClosedXML, pero si que es cierto, que se puedo quedar un poco corta, aunque daba las ideas más básicas. Hoy, vamos a ampliar esa pequeña píldora sobre ClosedXML aprendiendo a dar formato a nuestras hojas.

Nuestro objetivo

Vamos a intentar hacer una tabla de colores como la que tenemos a continuación:

Image may be NSFW.
Clik here to view.
Resultado ClosedXML

Creación del proyecto

Por cambiar un poco desde de la primera parte, vamos a crear un proyecto en .NetCore (ahora que también sabemos instalar el framework en linux o como depurar sobre SSH), para ello, creamos un proyecto de consola:

Image may be NSFW.
Clik here to view.
consola .net core

O desde el CLI de NetCore:

dotnet new console

Lo siguiente que tenemos que hacer es añadir el paquete ClosedXML a través de nuget. Esto se puede hacer a través de la “Consola de Administrador de Paquetes” con el comando:

PM->Install-Package ClosedXML

O también desde el CLI de NetCore:

dotnet add package ClosedXML

Como siempre, utilizando el administrador que integra VS:

Image may be NSFW.
Clik here to view.

Generando y formateando el fichero con ClosedXML

Teniendo claro el objetivo, vamos a ponernos con en faena y analizar el código que hemos utilizado:

using ClosedXML.Excel;
using System.Collections.Generic;

namespace PostClosedXML2
{
  class Program
  {
    /// <summary>
    /// Lista de colores para el ejemplo
    /// </summary>
    /// <returns></returns>
    static IEnumerable<XLColor> GetColors()
    {
      yield return XLColor.Red;
      yield return XLColor.Amber;
      yield return XLColor.AppleGreen;
      yield return XLColor.AtomicTangerine;
      yield return XLColor.BallBlue;
      yield return XLColor.Bittersweet;
      yield return XLColor.CalPolyPomonaGreen;
      yield return XLColor.CosmicLatte;
      yield return XLColor.DimGray;
      yield return XLColor.ZinnwalditeBrown;
    }

    static void Main(string[] args)
    {
      using (var workbook = new XLWorkbook())
      {
        //Generamos la hoja
        var worksheet = workbook.Worksheets.Add("FixedBuffer");
        //Generamos la cabecera
        worksheet.Cell("A1").Value = "Nombre";
        worksheet.Cell("B1").Value = "Color";

        //-----------Le damos el formato a la cabecera----------------
        var rango = worksheet.Range("A1:B1"); //Seleccionamos un rango
        rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
        rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
        rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; //Alineamos horizontalmente
        rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente
        rango.Style.Font.FontSize = 14; //Indicamos el tamaño de la fuente
        rango.Style.Fill.BackgroundColor = XLColor.AliceBlue; //Indicamos el color de background
        

        //-----------Genero la tabla de colores-----------
        int nRow = 2;
        foreach (var color in GetColors())
        {
          worksheet.Cell(nRow, 1).Value = color.ToString(); //Indicamos el valor en la celda nRow, 1
          worksheet.Cell(nRow, 2).Style.Fill.BackgroundColor = color; //Cambiamos el color de background de la celda nRow,2
          nRow++;
        }

        //Aplico los formatos
        rango = worksheet.Range(2, 1, nRow-1, 2); //Seleccionamos un rango
        rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
        rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
        rango.Style.Font.SetFontName("Courier New"); //Utilizo una fuente monoespacio
        rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right; //Alineamos horizontalmente
        rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente


        worksheet.Columns(1, 2).AdjustToContents(); //Ajustamos el ancho de las columnas para que se muestren todos los contenidos

        workbook.SaveAs("CellFormating.xlsx");  //Guardamos el fichero
      }
    }
  }
}

Lo primero de todo, hemos creado una lista con los colores que queremos utilizar para nuestra tabla. Para ello, utilizamos los colores propios del paquete, que trae una lista enorme de colores:

static IEnumerable<XLColor> GetColors()
{
  yield return XLColor.Red;
  yield return XLColor.Amber;
  yield return XLColor.AppleGreen;
  yield return XLColor.AtomicTangerine;
  yield return XLColor.BallBlue;
  yield return XLColor.Bittersweet;
  yield return XLColor.CalPolyPomonaGreen;
  yield return XLColor.CosmicLatte;
  yield return XLColor.DimGray;
  yield return XLColor.ZinnwalditeBrown;
}

Una vez que tenemos eso controlado, vamos a entrar al jugo del código. En primero lugar, podemos ver que todo el código esta dentro de un using:

using (var workbook = new XLWorkbook())
{
   //Código
}

Con esto conseguimos que los recursos utilizados para generar el fichero se liberen correctamente al acabar de usarlos. Lo siguiente que vemos, es como generamos la hoja dentro del libro:

//Generamos la hoja
var worksheet = workbook.Worksheets.Add("FixedBuffer");

Una vez que tenemos la hoja creada, añadimos las dos primeras celdas que nos servirán de cabecera para la tabla:

//Generamos la cabecera
worksheet.Cell("A1").Value = "Nombre";
worksheet.Cell("B1").Value = "Color";

Vamos a darle formato a la cabecera de la tabla:

//-----------Le damos el formato a la cabecera----------------
var rango = worksheet.Range("A1:B1"); //Seleccionamos un rango
rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; //Alineamos horizontalmente
rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente
rango.Style.Font.FontSize = 14; //Indicamos el tamaño de la fuente
rango.Style.Fill.BackgroundColor = XLColor.AliceBlue; //Indicamos el color de background

Para trabajar con mayor comodidad, seleccionamos un rango (A1:B1), de modo que todo lo que hagamos sobre el rango, se va a aplicar a todas las celdas del rango, en este caso a A1 y a B1. Lo que hacemos es asignarle los bordes extreriores e interiores, alinear el contenido horizontal y verticalemnte, le modificamos el tamaño de la fuente, y por ultimo, pintamos el color de fondo. Sin ningun problema, generamos la tabla de colores:

//-----------Genero la tabla de colores-----------
int nRow = 2;
foreach (var color in GetColors())
{
    worksheet.Cell(nRow, 1).Value = color.ToString(); //Indicamos el valor en la celda nRow, 1
    worksheet.Cell(nRow, 2).Style.Fill.BackgroundColor = color; //Cambiamos el color de background de la celda nRow,2
    nRow++;
}

Como en casos anteriores, asignamos un valor a una celda, y un backcolor a la otra. Con esto, solo nos queda aplicar los formatos y guardar. Vamos a aplicar los formatos:

//Aplico los formatos
    rango = worksheet.Range(2, 1, nRow-1, 2); //Seleccionamos un rango
    rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
    rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
    rango.Style.Font.SetFontName("Courier New"); //Utilizo una fuente monoespacio
    rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right; //Alineamos horizontalmente
    rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente

Pero ojo, esta vez, hemos asignado también la fuente:

rango.Style.Font.SetFontName("Courier New");

Esto lo hacemos para que la tabla nos quede perfectamente cuadrada al utilizar letras monoespacio. Por ultimo, queremos que las celdas se queden perfectamente a la vista todas, aunque el contenido sea mayor que el ancho de la columna, por lo tanto, vamos a ajustar el ancho de columnas. Eso lo hacemos con:

worksheet.Columns(1, 2).AdjustToContents(); //Ajustamos el ancho de las columnas para que se muestren todos los contenidos

Lo que conseguimos así, es que el ancho de las columnas 1 y 2, se ajuste de modo que se vea el contenido de todas las celdas. Con todo hecho, simplemente guardamos:

workbook.SaveAs("CellFormating.xlsx");  //Guardamos el fichero

Y al abrir el excel, veremos que se nos ha quedado una tabla como la que presentábamos al principio. Como siempre, si queréis probar el código fuente, os dejo el enlace al repositorio de GitHub. Esto solo es una pequeña pincelada de todo los que se puede conseguir con ClosedXML y con OpenXML en general. En futuras entradas, seguiremos ampliando su uso.

**La entrada ClosedXML, una manera fácil de dar formato a nuestros .xlsx se publicó primero en Fixed Buffer.**


Blog Bitix: Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix

Image may be NSFW.
Clik here to view.
Spring
Image may be NSFW.
Clik here to view.
Java

Los microservicios son independientes unos de otros y se comunican mediante operaciones de red. Dado que las operaciones se realizan a través de un medio no confiable como la red, dada su naturaleza efímera y a que pueden fallar en los microservicios es importante que los clientes estén preparados ante posibles fallos.

Un patrón o técnica que se suele emplear es el de Circuit Breaker, en Java y con Spring se ofrece en el proyecto Spring Cloud Netflix mediante Hystrix y Javanica. Este patrón soluciona dos problemas cuando un microservicio del que se depende falla y hace al microservicio que lo usa tolerante a fallos.

  • Uno es que cuando un microservicio empieza a fallar es necesario dejar de hacerle peticiones para permitirle recuperarse si está saturado que provoca esos fallos. Cuando ocurre un fallo es posible realizar una acción en sustitución de la llamada al microservicio y devolver un valor alternativo como medida paliativa y hacer que el microsevicio afectado tenga la posibilidad de seguir ofreciendo su servicio aunque sea de forma degradada.
  • Otro problema es que el microservicio aunque no falle tarde demasiado en responder, se puede establecer un timeout que si se supera se deja de esperar e igualmente se realiza la acción de sustitución lo que evita que los microservicios que usan uno que tarda demasiado en responder agoten sus recursos y empiecen a fallar o tardar demasiado también.

En ambos casos se evita que la cadena de microservicios empiece a fallar y con ello sistema completo.

Image may be NSFW.
Clik here to view.
Hystrix

El patrón circuit breaker se denomina así ya que implementa una lógica similar a un circuito eléctrico. El circuito en su estado normal está cerrado y se realizan las llamadas al microservicio servidor. Si el microservicio servidor empieza a fallar se llama a la acción alternativa con su valor, si se supera un umbral de errores el circuito pasa a estado abierto y se dejan de hacer llamadas al microservicio servidor. Cada cierto tiempo definido se realiza una llamada al servicio servidor para comprobar su estado de forma que si responde correctamente el circuito pasa a estado cerrado nuevamente y las siguientes llamadas se realizan al microservicio servidor dejándose de utilizar la acción alternativa.

Para utilizar Hystrix como implementación del patrón circuit breaker en una aplicación Java con Spring Boot el método que realiza la llamada al microservicio servidor ha de encapsularse en un método anotado con la anotación @HystrixCommand, como parámetro se indica un método con la acción alternativa o fallback que obtiene un valor en los fallos. También se puede indicar el tiemout de espera antes de considerar que la llamada ha fallado con la propiedad execution.isolation.thread.timeoutInMilliseconds. Igualmente se pueden indicar los valores para abrir el circuito con circuitBreaker.requestVolumeThreshold y circuitBreaker.errorThresholdPercentage. Esos son los básicos para utilizar este patrón de tolerancia a fallos. Tiene algunos valores adicionales más que se pueden configurar para adaptar el patrón a los valores óptimos de la aplicación.

En el ejemplo el cliente en un bucle realiza las llamadas al servicio con un método get() anotado con @HystrixCommand. En este método se encapsula la petición HTTP que puede fallar, utilizando la librería Jersey y obtenida la ubicación de una instancia del servicio a su vez del servicio de registro y descubrimiento Eureka.

El circuito se abre cuando el número de llamadas supera un umbral y un porcentaje de fallos, se han de cumplir las dos condiciones. Si el número de llamadas que se realizan no superan el umbral aunque todas fallen el circuito permanece cerrado. Ambos valores son personalizables con las propiedades circuitBreaker.requestVolumeThreshold y circuitBreaker.errorThresholdPercentage. El circuito permanece abierto al menos durante el tiempo indicado por metrics.rollingStats.timeInMilliseconds.

En la aplicación ejemplo hay un microservicio servidor y un microservicio cliente, iniciada una instancia de microservicio servidor y una instancia del microservicio cliente que implementa el patrón circuit breaker inicialmente las peticiones se realizarán correctamente si no ocurre un timeout. Si se finaliza el proceso del microservicio servidor las peticiones del cliente al servidor empezarán a fallar y este obtiene el valor alternativo del método fallback, si se supera el umbral de llamadas y de fallos el circuito pasa a estado abierto. Mientras el circuito permanezca abierto el cliente sondea con una petición cada cierto tiempo el estado del servidor, si el servicio servidor se inicia unos instantes después de que esté disponible el cliente con la petición de sondeo comprobará que el servidor funciona, se cerrará el circuito y el cliente empezará a obtener los valores devueltos por el microservicio servidor.

Para monitorizar en tiempo real el estado del sistema y de los circuitos se ofrece un dashboard en el que visualizan el número de peticiones que se están realizando, las fallidas, el estado de los circuitos, las que fallan por timeout o las que fallan con error. Para tener acceso a esta página hay que incluir la dependencia org.springframework.cloud:spring-cloud-starter-netflix-hystrix-dashboard. La página dashboard está disponible en la dirección http://localhost:8085/hystrix.

El proyecto Hystrix ha dejado de desarrollarse de forma activa tal como aparece en el propio README.md y como alternativa se recomienda usar Resilience4j que además está diseñado para Java 8 y la programacion funcional.

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 ./gradle-run.sh.

Blog Bitix: Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot

Image may be NSFW.
Clik here to view.
Java
Image may be NSFW.
Clik here to view.
Spring

Hystrix es una implementación del patrón circuit breaker para hacer que un servicio sea tolerante fallos cuando aquellos que utiliza fallan. Es conveniente tener una herramienta de monitorización para conocer el estado del sistema y actuar pronto o conocer si el comportamiento del sistema es diferente al hacer algún cambio. Hystrix proporciona varios datos como el número de peticiones realizadas, cuantas han fallado o cual es el estado del patrón circuit breaker. Prometheus es una herramienta de monitorización que recoge las métricas de los servicios de forma periódica y las almacena para una consulta posterior, Grafana es otra herramienta de monitorización que permite visualizar en gráficas las métricas almacenadas en Prometheus y observar los valores a lo largo del tiempo.

En el artículo Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix explicaba como crear un servicio de Spring que implementa el patrón circuit breaker con Hystrix y en el artículo Monitorizar una aplicación Java con Spring Boot, Micrometer, Prometheus y Grafana explicaba como exportar las métricas de Spring Boot Actuator a Prometheus y como crear gráficas en Grafana.

Hystrix ofrece un dashboard algo espartano con los datos de Hystrix de la propia aplicación. Los datos de las métricas de Hystrix por defecto no se exponen en Spring Boot Actuator pero se pueden añadir creando un beanHystrixMetricsBinder en la configuración de Spring.

Una vez hecho esto Spring en el endpoint/actuator/metrics se exponen las métricas de Hystrix, si además se configura Spring añadiendo la dependencia io.micrometer:micrometer-registry-prometheus para exponer las métricas en el formato para que Prometheus las recolecta también se añaden en el endpoint/actuator/prometheus.

Con estas métricas recolectadas por Prometheus se pueden visualizar en gráficas por Grafana. Hay algunos paneles de Grafana para Hystrix como el 7145 pero que necesitan ser adaptados según la nomenclatura de las propiedades expuestas por Spring Boot. En este caso se monitoriza el número de peticiones realizadas, el tiempo de latencia, si los circuitos están abiertos, los fallos, éxitos y tiemouts así como el estado de los thread pools que utiliza Hystrix para realizar las peticiones de un cliente a un servicio.

Image may be NSFW.
Clik here to view.
Panel de Grafana para métricas de Hystrix

Exponer las métricas en una aplicación de Spring Boot para Prometheus es muy sencillo y con Grafana se puede observar el estado del sistema de forma tan detallada como lo sean las métricas expuestas por la aplicación. Por defecto Spring Boot ya expone una buena cantidad de métricas del estado del servicio como uso de CPU, memoria, hilos o recolector de basura.

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 ./gradle-run.sh.

Fixed Buffer: ¡Feliz navidad!

Image may be NSFW.
Clik here to view.

Bueno, han pasado ya 3 meses desde que empece con este proyecto, y la verdad, cuando empece no pensaba que las cosas iban a ir tan bien como están yendo! Casi mil personas habéis entrado y leído el contenido, mis ideas (locas y no tan locas). Muchas gracias a todos por la gran acogida que me habéis dado, ¡la verdad es que es un gran apoyo para seguir!

Ahora, toca parar y coger fuerzas, disfrutar de la familia, el turrón y los villancicos. Aprovecho esta entrada para desearos unas felices fiestas, y un prospero año 2019.

¡Nos vemos en enero con las pilas cargadas y muchas ideas nuevas, muchas cosas que contar, y alguna colaboración que se esta gestando!

**La entrada ¡Feliz navidad! se publicó primero en Fixed Buffer.**

Arragonán: El juego de la vida en la Commit Conf

En Junio se acababa la fecha límite para enviar propuestas a la Commit Conf, el viñarock del software (evento organizado por las mismas personas que Codemotion hasta 2018). No me apetecía proponer una charla, más a tanto tiempo vista celebrándose en Noviembre; pero finalmente me animé a enviar una propuesta de coding dojo para practicar TDD y pair programming a través del juego de la vida de Conway.

Tuve bastantes dudas en hacerlo: Lo propuse en una ocasión en un evento de agile y no cuadró, así que no tenía claro como encajaría en un evento más técnico pero un tanto generalista. Sólo había participado/facilitado coding dojos en eventos pequeños. La kata da más juego con más tiempo, a partir de 4 iteraciones o así suele haber más mezcla y se va observando mucha evolución en las soluciones. Y que podía ser un fail tanto por soledad como por superpoblación ;).

Así que procuré poner una descripción para todos los públicos y finalmente la aceptaron.

Aunque es una kata que había practicado bastante y la había facilitado en un par de code retreats la prefería rodar. Tuve la oportunidad de hacerlo en un coding dojo interno con compañeros de Zara.com, y me tomé como cierto rodaje las 2 primeras iteraciones en el Global day of Code Retrat de A Coruña.

De modo que pude acortar y refinar un poco la pequeña presentación para ayudarme a contextualizar, ya que suponía que habría gente que nunca habría participado en un coding dojo o hubiera utilizado katas como ejercicios de práctica deliberada.

Y mientras que en los code retreats prefiero ir improvisando constraints dependiendo de lo que se va viendo en cada iteración. En este caso como no había mucho tiempo ni margen de maniobra, preferí llevarlo preparado en la presentación.

Siendo el segundo día del evento y a primera hora de la mañana había alrededor de una treintena de personas. Y que en general vi a la gente muy concentrada en el ejercicio, mucha comunicación entre pares y tríos, personas desconocidas relacionándose entre ellas, gente descubriendo el testing automático o haciendo sus pinitos con TDD, programando en distintos lenguajes…

Como anécdotas os diré que yo tenía que gritar para dar instrucciones y a veces me sentía ignorado (eso que no tengo mal volumen de voz), y que uno de los fotógrafos de la organización me preguntó si era algún tipo de concurso al ver a todo el mundo tan engorilado.

Así que, aún más acordándome de mis dudas en Junio, al acabar me quedé con un buen sabor de boca :).

Variable not found: Enlaces interesantes 343 y... ¡Felices fiestas!

Image may be NSFW.
Clik here to view.
Enlaces interesantes, edición feliz navidad!
Ahí van los enlaces recopilados durante la semana pasada y, como siempre, espero que os resulten interesantes :-)

Y de paso, aprovecho este momento  para desearos a todos unas felices fiestas y un grandioso 2019 en todos los ámbitos.

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en: www.variablenotfound.com.Image may be NSFW.
Clik here to view.

Blog Bitix: Permitir o denegar tráfico de red con el firewall UFW en GNU/Linux

Image may be NSFW.
Clik here to view.
GNU
Image may be NSFW.
Clik here to view.
Linux

Un cortafuegos o firewall hace que un sistema sea más seguro analizando todo el tráfico de red que se recibe y envía impidiéndolo o permitiéndolo según las reglas que se hayan definido. Aunque los routers con los que nos conectamos a internet también hacen la función de firewall es aconsejable instalar un cortafuegos en cada sistema, sobre todo en sistemas portátiles que podemos conectar a redes WIFI ajenas a las que pueden conectarse al mismo tiempo otros muchos usuarios que desconocemos.

En GNU/Linux un firewall con el que las reglas de tráfico de red se puede definir de forma sencilla es Uncomplicated Firewall o UFW. En la distribución Arch Linux hay que instalar su paquete y habilitarlo para que se inicie con el sistema.

En la wiki de Arch Linux hay una buena página explicativa de su uso.

Instalado y activado hay que definir las reglas de tráfico permitidas. Por defecto, se deniega el tŕafico proveniente de un sistema que no sea el local. Por ejemplo, con el cortafuegos activado para que un servidor web sea accesible desde otro equipo en la misma red o desde internet hay crear una regla que permita todo el tráfico entrante los puertos por defecto 80 para http y 443 para https.

Para permitir acceso al servidor web únicamente desde la red local a la que está conectado el equipo hay que indicar la dirección IP de la red, los casos habituales son 192.168.0.0/24 o 192.168.1.0/24. En vez de usar la directiva allow se puede emplear la directiva deny para denegar el tráfico.

Definidas algunas reglas se pueden listar y de forma numerada en el caso de querer eliminar alguna.

Para eliminar reglas hay que hacerlo según su número de la lista anterior.

Ciertas aplicaciones usan un puerto conocido como el ejemplo de servidor web en el puerto 443 o 80, SSH en el 22 o descarga y compartición de archivos mediante P2P por Bittorrent que dependiendo de la aplicación se usa uno u otro, en el caso de Transmission es 51413. Sabiendo la aplicación para la que se quiere permitir el tráfico no es necesario conocer su puerto, ufw ya incorpora varias aplicaciones conocidas que se listan y activan con un comando.

En el caso de este ejemplo se permite el tráfico para la aplicación Transmission desde cualquier ordenador y a las aplicaciones de servidor web tanto en el puerto seguro como inseguro y en el puerto 8080 que usan algunos servidores de aplicaciones limitando su acceso únicamente desde la red local.

Los comandos anteriores aunque cambiando los valores de los puertos o dirección IP son suficientes para hacer un sistema más seguro no permitiendo tráfico de red y evitando que se puedan establecer conexiones desde otro sistema en la misma red de área local a ciertos puertos de red abiertos sin ser conscientes de que los están.

En algunos casos como usando una Raspberry Pi sin interfaz gráfica para minimizar el consumo de recursos es necesario conocer o consultar los comandos de ufw.

Referencia:

Koalite: Resumen 2018

Da un poco de vergüenza que después de no escribir nada de julio la forma de reactivar el blog sea este post, pero mi intención es retomar el blog durante el 2019 y para ello nada mejor que respetar las costumbres y preparar el resumen anual.

Estadísticas

Me había marcado como objetivo escribir 24 posts y está bastante claro que no lo he conseguido. Han sido en total 14 posts, todos (excepto éste) escritos antes del verano. Para el año que viene no me atrevo a marcar un objetivo en cuanto a número de posts porque no tengo ni idea de si podré cumplirlo o no; mi objetivo real es mantener el blog vivo, aunque sea para escribir algo de vez en cuando sin periodicidad fija.

Según Google Analytics, a lo largo del año han visitado este blog unos 140.000 usuarios, en 180.000 sesiones que les han llevado a ver 225.000 páginas. Es una disminución de entre el 20% y el 25% con respecto al año pasado dependiendo de la métrica que tomes.

Si no me preocupaban mucho estas cosas cuando el blog era una parte más importante en mi vida, obviamente tampoco voy a empezra a preocuparme por ello ahora. Teniendo en cuenta los pocos posts que he escrito este año y la temática de muchos de ellos, es más que razonable que el interés, y sobre todo el posicionamiento en Google, vaya cayendo.

Quizá afecten también temas puros de SEO (como no tener configurado el acceso https al blog), pero si dedico tiempo al blog será más para escribir, que es lo que me gusta, que para andar jugando con cuestiones puramente técnicas.

Lo más visitado del año

El top 5 del año es:

  1. Programar no es desarrollar
  2. Ofuscar el código para proteger… ¿qué?
  3. Lo mínimo a entender sobre transacciones
  4. Ingenieros o artesanos
  5. APIs con Node, TypeScript, Koa y TypeORM

El post sobre lo que realmente implica desarrollar software es también mi post preferido del año, y además creo es uno de los más aprovechables de este blog si quieres dedicarte a desarrollar software. Entre los que se quedan fuera, me gustó repasar las diferencias entre tipado nominal y estructural y diseñar builders basados en funciones para TypeScript.

Lo más visitado de siempre

Echando la vista atrás y buscando los posts más visitados desde el origen del blog, se nota mucho lo que pesa el tráfico de motores de búsqueda y se cuelan ahí posts que no son especialmente interesantes pero que están bien posicionados (y que a día de hoy ni siquiera sé si siguen valiendo para algo con los cambios de versiones de librerías):

  1. 5 cosas que deberías aprender para dedicarte al desarrollo de software
  2. Lectura de Códigos de Barras con HTML5 y Javascript
  3. ¿Qué es la Programación Lógica?
  4. Cómo Generar Códigos QR en C#
  5. Paso de parámetros entre controladores en AngularJS

De todos estos posts el único que merece la pena releer es el primero y, si no sabes lo que es la programación lógica, el tercero. Me sorprende mucho que ese post tenga tantas visitas; estoy casi seguro de que debe de haber otra cosa que la gente llama programación lógica que les lleva hacia él, porque dudo que prolog tenga tantos seguidores en el mundo.

Hasta el año que viene

Y hasta aquí este irregular 2018 en el blog. Espero que el 2019 pueda tener más tiempo para disfrutar escribiendo.

¡Feliz 2019 a todos!

Posts relacionados:

  1. Resumen 2016
  2. Resumen 2013
  3. Resumen 2017
Image may be NSFW.
Clik here to view.

Variable not found: El operador virgulilla "~" en C#

Image may be NSFW.
Clik here to view.
.NET Core
Desde que apareció Roslyn, C# ha ido evolucionando a pasos agigantados. Tanto es así que es frecuente encontrar desarrolladores que, aunque lo usan a diario, desconocen todo su potencial porque la velocidad de introducción de cambios en el lenguaje es mayor que la de asimilación de novedades por parte de los profesionales que nos dedicamos a esto.

Por ejemplo, en las consultorías técnicas que realizo en empresas es frecuente encontrar equipos de trabajo en los que aún no está generalizado el uso de construcciones tan útiles como el null coalescing operator (fullName ?? "Anonymous"), safe navigation operator (person?.Address?.Street), el at object operator (Address@person), o características tan potentes como las funciones locales, interpolación de cadenas, tuplas o muchas otras.

Sin embargo, creo que el rey de los desconocidos es el operador virgulilla "~" de C#. Introducido con C#7 es probablemente uno de los operadores menos utilizados y, sin embargo, de los más potentes ofrecidos por el lenguaje.
Nota de traducción: el nombre original del operador es "tilde operator", y como he encontrado poca literatura al respecto en nuestro idioma, me he tomado la libertad de traducirlo como operador virgulilla (¡sí, esa palabra existe!). También, en entornos más informales lo encontraréis con el nombre "wormy operator" (operador gusanillo) o como "soft similarity operator" (que podríamos traducir como operador de similitud relajada).

El operador virgulilla

Originalmente el carácter "~" ya era utilizado desde los comienzos de C# para realizar la operación de complemento bit a bit, pero, dado su poco uso, en C#7 reescribieron el operador para alinear el lenguaje con las nuevas corrientes de popularización de los sistemas basados en inteligencia artificial.

Básicamente, el operador virgulilla retorna un valor booleano resultado de evaluar una comparación entre dos objetos o valores, pero, a diferencia de las comparaciones "tradicionales", utilizando criterios basados en lógica borrosa y aprendizaje automático o machine learning.

Creo que la mejor forma de entender rápidamente en qué consiste es viendo código, así que vamos con el primer ejemplo:
var n1 = 987654320;
var n2 = 987654321;

Console.WriteLine(n1 == n2); // n1 equals n2? --> false
Console.WriteLine(n1 ~ n2); // n1 is similar to n2? --> true!
Efectivamente, como podéis suponer, el operador ~ comprueba si los dos operandos son similares, retornando true en caso afirmativo.

Para ajustar en cada escenario el nivel de precisión que deseamos aplicar a las operaciones de comparación, C# permite encadenar hasta tres virgulillas para indicar que queremos aplicar un grado mayor de precisión en la evaluación de similitud:
var n1 = 987654320;
var n2 = 987654321;

Console.WriteLine(n1 == n2); // n1 is equals to n2? --> false
Console.WriteLine(n1 ~ n2); // n1 is similar to n2? --> true
Console.WriteLine(n1 ~~ n2); // n1 is very similar to n2? --> true
Console.WriteLine(n1 ~~~ n2); // n1 is very very similar to n2? --> false!
Observad que en muchos escenarios puede librarnos del típico off by one, uno de los errores más habituales en el mundo de la programación.
Los ejemplos anteriores pueden verse más o menos claros porque estamos comparando números. Pero la cosa va más allá; veamos otro ejemplo, usando la sobrecarga del operador para tipos string:
string s1 = "Hello, what is your name?";
string s2 = "Hello, wat is your name?";

Console.WriteLine(s1 == s2); // s1 equals s2? --> false
Console.WriteLine(s1 ~ s2); // s1 is similar to s2? --> true
Empleando bien esta característica podemos simplificar bastante las comparaciones de cadenas y, sobre todo, el tratamiento de valores nulos, vacíos o blancos, como en el siguiente ejemplo:
string strNull = null;
string strEmpty = "";
string strSpaces = "";

Console.WriteLine(strNull == strEmpty); // false
Console.WriteLine(strNull ~ strEmpty); // true
Console.WriteLine(strNull ~ strSpaces); // true
Console.WriteLine(strSpaces ~ null); // true: Bye bye, string.IsNullOrEmpty()!
Y como operador completo que es, también podemos utilizar composición de gusanillos para expresar comprobaciones complejas de forma más concisa:
Console.WriteLine(strNull ~ strEmpty ~ strSpaces);   // true

Comparaciones AI-Based

Este operador también tiene la capacidad de comparar valores de distinto tipo, y es donde encontramos probablemente su utilidad más espectacular porque con ello se ponen en marcha los mecanismos de inteligencia artificial integrados en el compilador. Fijaos en esto:
string strRed = "red";
string strRgbRed = "ff0000";
Color colorRed = Color.Red;
int intRgbRed = 0xff0000;

Console.WriteLine(strRed ~ strRgbRed ~ colorRed ~ intRgbRed); // true!!
Internamente, el sistema es capaz de detectar la similitud gracias a la siguiente cadena de decisiones:
  • Sabe que Color.Red es un miembro de un enum con un valor entero equivalente a 0xff0000.
  • Por esta razón, sabe que colorRed ~ intRgbRed.
  • También sabe que "ff0000" es la representación textual de 0xff0000, y teniendo en cuenta los pasos anteriores, deduce que strRgbRed ~ colorRed ~ intRgbRed.
  • El texto "red" se encuentra en el nombre de la constante Color.Red, por lo que ya puede concluir que strRed ~ strRgbRed ~ colorRed ~ intRgbRed.
Brutal, ¿eh?

Pues subamos la apuesta ;) La comparación de similitud ofrecida por el operador también está integrada con los sistemas NLP (procesamiento de lenguaje natural) incluidos en el compilador. Observad en el siguiente ejemplo, donde evaluamos la similitud de cadenas de caracteres y enteros utilizando comparación semántica:
using Microsoft.Extensions.Operators.Tilde.Nlp;
...
int numberTen = 10;
string strDiez = "diez";
string strDecena = "una decena";
string strDecimoDeCien = "la décima parte de cien";

Console.WriteLine(strDiez ~ numberTen ~ strDecena ~ strDecimoDeCien); // true!
Nota: para que esto funcione correctamente es necesario incluir el espacio de nombres Microsoft.Extensions.Operators.Tilde.Nlp.
Incluso podemos sacar partido fácilmente a la traducción en tiempo de compilación a distintos locales. Fijaos que en este caso son dos los namespaces los que debemos utilizar:
using Microsoft.Extensions.Operators.Tilde.Nlp;
using Microsoft.Extensions.Operators.Tilde.Nlp.I18N;
...
string strTen = "ten";
string strDiez = "diez";

Console.WriteLine(strTen ~ strDiez); // true; usando auto-locale
Console.WriteLine(strDiez ~en-us~ strTen); // true; forzando el locale en-us
Console.WriteLine(strDiez ~fr-fr~ strTen); // false (el locale forzado es incorrecto)

Activación en Visual Studio 2017 u otros IDE

Sin duda, este operador es uno de los grandes desconocidos por la controvertida decisión de Microsoft de hacer que éste sólo esté disponible cuando es habilitado explícitamente en el entorno de desarrollo. Es decir, no podemos utilizarlo en nuestro código hasta que lo activemos expresamente en el proyecto.

Para conseguirlo en Visual Studio 2017, basta con acudir a las propiedades del proyecto, pestaña "Build", clicar el botón "Avanzado" y asegurarse de que está marcado Enable similarity operator, como se muestra en la siguiente captura de pantalla:


Image may be NSFW.
Clik here to view.
Activación del operador virgulilla en Visual Studio 2017

Si no utilizamos Visual Studio, también podemos acudir al archivo .csproj del proyecto e insertar manualmente las siguientes líneas (es posible que requiera reiniciar VS tras hacerlo):
<PropertyGroup>
<LanguageVersion>Latest</AssemblyName>
<EnableSimilarityOperator>true</EnableSimilarityOperator>
</PropertyGroup>

¿Y está disponible este operador en Visual Basic .NET?

Nota: Este punto no estaba inicialmente en el post, pero lo he actualizado para responder a la pregunta que me hacía @visualbutnotbasic vía Twitter nada más publicarlo.
Pues aún no, pero lo estará pronto. Según Victor Bellino, principal program manager en Redmond, se está trabajando duro para llevar este operador a Visual Basic, aunque probablemente bajo otra denominación para alinearlo sintácticamente con las construcciones habituales de este lenguaje:
If x Is Similar To y And Similar To z Then
...
End If

En conclusión

Desconozco las razones que han llevado a Microsoft a mantener el operador virgulilla semioculto tras opciones de compilación, y a no haberlo publicitado más. Probablemente existan reticencias por que, como todo gran poder, su uso exige una gran responsabilidad y es posible que piensen que los desarrolladores no estamos aún preparados para asumirla.

Pero en cualquier caso, sin duda, el operador virgulilla es una de las incorporaciones al lenguaje C# más interesantes de los últimos años, pues nos abre la puerta a la construcción de sistemas más fiables, inteligentes y flexibles.

El operador virgulilla es a la inteligencia artificial lo que LINQ fue en su momento a las bases de datos: una integración dentro del lenguaje de funcionalidades que tradicionalmente pertenecían a un mundo aparte. De ahí que podamos considerarlo como una herramienta totalmente disruptiva para los desarrolladores.

Publicado en ~Varible not fund.Image may be NSFW.
Clik here to view.

Blog Bitix: Hemeroteca #14

Casi 9 años publicando de forma constante un artículo a la semana, dos artículos algunas semanas. Durante casi está década creo que ha habido pocas semanas en las que no he publicado. Este último años no ha sido distinto, el siguiente espero… espero que sea parecido o mejor ahora que tengo mi propio equipo personal que he estado mucho tiempo esperando hasta que fuese comercializado, un Intel NUC Bean Canyon del que en este semestre he publicado un artículo con de su desempaquetado junto con otros artículos del monitor Benq PD2700Q y teclado ratón pero también comentando las opciones que he barajado.

Casi 9 años publicando, para llegar a esta cifra hay que tener constancia para llegar a escribir tanto tiempo, dedicación no empezar una cosa y dejarlo al cabo de poco e incluso de publicar aún cuando el ánimo no es el de los mejores momentos, planificación y organización para saber que publicar, tiempo ya que cuesta escribir los artículos y motivación ya sea tener más visitas, comentarios de algunos usuarios, más ingresos de AdSense, compartir, … No es fácil, en mi caso sabiendo que mucho de lo que escribo no lo aplico en el trabajo, donde actualmente tengo un Mac por obligación y no trabajo con Java y mis temas principales son GNU/Linux y Java, con lo que a veces tengo la sensación de no serme útil. Al finalizar este 2018 ya van 371 artículos publicados desde finales del 2013 que empecé a hacerlo en Blog Bitix.

Ahora que tengo mi propio equipo y de nuevo estoy usando Arch Linux podré publicar más artículos sobre GNU/Linux, he empezado por publicar como jugar a Diablo 3 u otros juegos de Blizzard en Linux con Wine.

Durante este semestre he escrito varios relacionados con el ordenador que necesitaba y finalmente he comprado al final de año pero del que había noticias desde muchos meses antes, la espera de varios meses hasta que se comercializase se me hizo larga.

He completado con algún artículo más la serie sobre GraphQL.

También he escrito varios artículos de la serie sobre Spring Cloud para escribir microservicios en Java.

Algunos pocos artículos sobre GNU/Linux.

Sobre Java o programación algunos más.

Y varios de opinión o noticias relevantes como la caída de Sun hace ya años o la compra de Red Hat por parte de IBM.

Revisando los artículos que he publicado en otras hemerotecas hay algunos que parece no los he escrito hace 2 y 3 años sino mucho menos. El crecimiento tanto en visitas como en ingresos no ha sido tanto como años anteriores, supongo que ya llegado un cierto umbral es difícil conservar los mismos ratios de crecimiento. Todos estos nuevos artículos es nuevo contenido que hace que reciba algunas visitas más y que los ingresos sean algo mayores. Dependiendo de si miro en Analytics o AdSense unas 520K páginas vistas este 2018 no muchas más que las casi 500K del 2017. En cualquier caso en las siguientes imágenes están las estadísticas de este blog por si a alguien le sirven para comparar con el suyo o como previsión de una web similar, también el tiempo necesario para conseguirlo o el número de artículos requeridos.

Incluyo además algunas métricas que proporciona AdSense de RPM, clics y CPC. Este final de año ha sido especialmente bueno con la semana del black friday como punto álgido llegando casi a 70 € en un mes, en todo el año han sido casi 500 € contra los 450 € del 2017.

Además, de AdSense incluyo enlaces de Amazon de algunos productos que compro y de los que publico algún artículo. Este año a través de los enlaces de afiliado de Amazon le he hecho facturar casi 900 € y he recibido a cambio 50 € en comisiones.

Como siempre mis deseos de que empecéis bien el nuevo año, en este caso el 2019, ¡buen 2019!.

Image may be NSFW.
Clik here to view.
¡Buen 2019!. Fuente: www.klowner.com

Poesía Binaria: ¡El 2018 se escapa! Posts, estadísticas, cambios, retos, programas, amigos, y más

Image may be NSFW.
Clik here to view.
2018 con bombilla.

¡Ya casi estamos en 2019! Y, como siempre, no he escrito ni la mitad de posts que quería escribir, ni he empezado ni la mitad de los proyectos que quería empezar. Aunque, estoy con ganas, fuerzas, e ilusión para llevarlo todo a cabo en 2019. Aunque, a primeros de año empecé con muchas ganas, las circunstancias han hecho que me tire unos tres meses sin escribir nada. Incluso aunque tengo un par de series de posts empezadas, no he podido continuarlas. Espero que este año sí que pueda hacerlo.

Y, como todos los años, quiero compartir algunas de las estadísticas que me han llamado la atención. Aunque, este año, quiero copiarme de @ochobitsunbyte y hacer mención a algunos de los proyectos que he seguido este año. Que, aunque no he publicado mucho, ni comentado mucho, entro, leo y curioseo Image may be NSFW.
Clik here to view.
🙂

Redes sociales

¡Tengo 14 seguidores menos en Facebook! Es el primer año que termino con menos seguidores de los que tenía cuando empezó. Se nota que no le he dedicado mucho tiempo a Facebook este año. Le puedo echar la culpa a los escándalos que ha tenido Facebook, es verdad que tengo varios contactos que ya no están, y como debían, por ser amigos, tenían el Me Gusta dado a la página, podría echarle la culpa a las restricciones que tiene la API de Facebook y que me incomoda un poco a la hora de promocionar posts y eso… pero bueno, tendré que dedicar algo de tiempo a esta red.

También me está dando últimamente por publicar píldoras de Bash en redes sociales, aprovechando que me he hecho un Instagram para el blog. Iré comentando poco a poco todas las píldoras. La idea de montar estas imágenes la saqué de @hacemoswebserie, por lo que con un pequeño (bueno, no tan pequeño) script con ImageMagick voy montando las imágenes.

Los posts más populares en Facebook

  1. Foto de Debian en mi móvil Android. Por cierto, publiqué un vídeo de la instalación.
  2. Rescatando capturas de pantalla perdidas en el disco duro.
  3. Mi configuración personal de Emacs
  4. Trabajar en equipo a través de Internet utilizando herramientas libres
  5. Script para hacer capturas de pantalla

Twitter

En lo que respecta a Twitter, sigo con la tarea de automatización pendiente, aunque algo he hecho y he probado en Twitter y LinkedIn. Pero no la he utilizado activamente. Tengo que agradecer a @juldelagbeta, @almu_hs, @m4r14h, @HighLordIron, @currolinux, @hacemoswebserie, @daboblog, @PGNalda, @CalafontNatural, @otixunil y a muchísimos más por hacer mi Twitter mucho más ameno. En serio, el día que me ponga a monitorizar Twitter os pondré a todos y no se me olvidará nadie Image may be NSFW.
Clik here to view.
🙂

También quiero agradecer a @guilleamodeo, que aunque no mira mucho Twitter. Me enseñó muchísimas cosas cuando nos conocimos y me hizo un logo muy chulo que tengo en Facebook y he puesto por aquí alguna vez. Y a Romualdo, que tampoco pisa mucho Twitter, agradezco los comentarios que me deja en la página de Facebook.

Blogs y más

Yo soy de ese tipo de personas que tienen más de 500 pestañas abiertas a la vez, en concreto, 1186 pestañas abiertas en cuatro perfiles (entre Firefox y Chrome). Lo sé, es una barbaridad, porque suelo dejar las cosas abiertas para volver luego. Lo malo, es que cuando vuelvo, al cabo de los dos o tres meses, algunas páginas han desaparecido. Me ha pasado también con muchos enlaces que he hecho desde esta página… problemas del cibermundo.

Este año, quiero aprovechar para mencionar algunos de los blogs que he seguido este 2018. Es verdad que cada año va variando, pero siempre encuentras alguno recurrente, y me parece perfecto, al menos hacer mención a su gran labor. Y, por favor, si estos blogs tienen publicidad, desactivemos los bloqueadores, y miremos sin nos interesa, porque una pequeña gratificación económica a los autores no viene mal, sobre todo si no nos cuesta nada. Hombre, ya donar dinero al autor, contratar posts patrocinados o un espacio de publicidad mucho mejor. Ahí va mi lista de este año:

  • ochobitshacenunbyte: Una gran referencia para el mundo sysadmin. Tanto para mis andanzas laborales como frikeos personales. En más de una ocasión, me he encontrado geniales posts de @ochobitsunbyte. De esos
  • Linuxito: Otro blog donde encontramos muchísima información para los que pasamos el día entre servidores de Internet.
  • El Array de Jota: Otra más de sysadmin. Muchos consejos, tutoriales y “marrones” de trabajo que pueden ser divertidos y llevaderos.
  • System Inside: Instalaciones, videotutoriales, reviews y mucho más, en formato vídeo.
  • ¿Hacemos una webserie? Cine, teatro, literatura, cultura y la importancia de la automatización con el software en formato podcast. También tiene muchos vídeos de ImageMagick y si buscamos, muchos tutoriales sobre Bash e ImageMagick.

También quiero hacer mención a LinuxCenter por la labor que están haciendo con el software libre y su divulgación (tengo que subir una foto con la camiseta que me regalaron); Podcast Linux, lo escucho algo menos a menudo que el año pasado, pero suelo escuchar todos los audios que han subido desde la última vez; atareao con contenidos para parar un tren… Me dejo muchos blogs y personas sin mencionar. Poco a poco quiero ir haciendo un blogroll decente.

Programas que me han salvado la vida

Otros años le he dedicado un post completo a esto. Aunque, tengo algo especial pensado. Me he estado dedicando un poco más a la administración de sistemas. Así que docker, ansible, puppet y netdata son tres de los proyectos que más he utilizado. Y tengo bastante contenido en el tintero de cada uno de ellos.
Pero otra de mis grandes pasiones, el vídeo, no puedo dejarla atrás. Y veo que Blender, cada vez es más estable. Y ahora, por fin, tienen un nuevo encargado del desarrollo del secuenciador, por lo que esperemos que tengan novedades al respecto. Revisaré y enviaré de nuevo algunas modificaciones que propuse y daré un poco de guerra.

Lo más visitado

Aunque @oscaragugav insiste en que ponga Google Analytics, yo utilizo Piwik. Dentro de lo que cabe, intento no utilizar demasiados servicios externos, aunque sean gratis, quiero que la información me pertenezca. Es cierto que también tengo Adsense, y que Google puede monitorizar del mismo modo las visualizaciones y quedarse con la información que le dé la gana de mi blog, pero bueno, con Piwik tengo algo más de control de las analíticas web. Aunque me gustaría montar mi propio sistema de publicidad, eso sería un trabajazo que me llevaría demasiado tiempo y no me da para vivir.

Por tanto, lo más visitado este año en mi blog ha sido:

  1. Algoritmos: Formas de transformar un entero a cadena en C y C++
  2. Cómo procesar múltiples argmentos de entrada en scripts para Bash.
  3. Cómo extraer ruta, nombre de fichero y extensión en Bash
  4. Bucles y cursores en MySQL con ejemplos.
  5. 9 trucos para manejar cadenas en Bash y no morir en el intento.

Parece que Bash se lleva la mayoría de las visitas.

Navegadores más usados

  • Google Chrome (65%) sube 5% con respecto al año pasado.
  • Mozilla Firefox (22%) se mantiene con respecto al año pasado.
  • Opera (3%) sube 1% con respecto al año pasado.
  • Chrome Mobile (2%) Baja 4%.
  • Safari (2%) se mantiene con respecto al año pasado.

Sistemas Operativos más usados

  • Windows (68%) sube 6% con respecto al año pasado.
  • GNU/Linux (cualquier distribución) (21%) sube 3% con respecto al año pasado
  • Mac OSX (5%) se mantiene
  • Android (3%) baja 6%

Creo que dado a que mucha gente entra por móvil gracias a redes sociales. Al no haber hecho mucho énfasis este año en ellas, más usuarios han entrado desde ordenador.

Enlaces entrantes

En este listado, dejando fuera las entradas desde redes sociales como Facebook, Twitter, Google+, Youtube y LinkedIn:

Países

  • España (61.5%) baja 1.5%
  • EEUU (14.9%) baja 2.1%
  • México (6.7%) sube 0.7%
  • Argentina (2.9%) baja 0.1%
  • Reino Unido (2.2%) sube 1%

Lo más comentado

Empezamos con las consultas a mi WordPress. Aunque no las pondré todas, porque en este post están todas. Los posts más comentados del año son los siguientes:

Otras curiosidades

Este año se han publicado 30 posts y 2 páginas (0.6 por semana) de los cuales 24 de ellos contienen código y 20 de ellos ejemplos de uso de terminal (con el plugin SimTerm). Además, he modificado 8 posts de años anteriores. Además, he invertido unas 183 horas (algo más de semana y media) escribiendo para el blog.

En total 110 fragmentos de código (71 menos que el año pasado). De los cuales:

  • 2 son de C
  • 24 son de PHP
  • 30 son de BASH
  • 10 son de Lisp
  • 3 son de Python

Los demás, son JSON, Javascript, XML, archivos de configuración, SQL y algunos más.

Cada post tiene una media de 2391.82 (472 palabras más que el año pasado) o unas 17402.7586 letras (2681 letras más que el año pasado), ¡enrollándome aún más que otros años!. También he publicado 212 enlaces (174 menos que en 2017) de los cuales 109 son internos (103 externos).

Los posts más largos

Los posts más cortos

Este año ha habido algunos posts cortos, sobre todo para comentar fotos de mi Instagram, y algunas pequeñas píldoras que quería publicar sin enrollarme demasiado.

Posts en otros lugares

Este año escribí tres posts en LinuxCenter:

El segundo es un post original para LinuxCenter, mientras que los otros dos están extraídos de antiguos posts de este blog, aunque reescritos.

Retos del 2019

El año pasado quería llegar a los 150 posts, pero creo que a unos 100 posts, dado que de media son bastante extensos puedo llegar. Tengo muchos proyectos que quiero ir ordenando. Me gustaría empezar un podcast, ahora que está de moda, y darle un poco de movimiento al canal de Youtube, puesto que me parece un buen lugar para compartir ciertos contenidos. Ya tengo un micrófono listo para eso, no es de lo mejor en micrófonos, pero se escucha mejor que el micrófono de la cámara, y no tengo que gritar tanto.

The post ¡El 2018 se escapa! Posts, estadísticas, cambios, retos, programas, amigos, y más appeared first on Poesía Binaria.

Arragonán: Un repaso a mi 2018

Este año tuve un cambio vital importante que vino dado por una combinación de circunstancias y tras un par de meses de conversaciones, terminé mudándome en Junio a A Coruña para trabajar en Zara.com. Así que cuando estuve repasando hace unos días los objetivos del año anterior, pasó lo que era de esperar, que fue bastante fiasco:

En Coding Stones como muchas otras empresas (aunque jamás nos constituyéramos como una), no conseguimos llegar y superar el tercer año de vida. Con las reglas de juego que nos marcamos sabíamos que era un modelo difícil, exige a los socios estar muy alineados y cuesta horrores vender a los clientes. Para mí haber formado parte de esta banda-cooperativa fue una experiencia que me hizo crecer en muchos aspectos, resulta imposible no tener morriña de mis exsocios de vez en cuando.

Respecto a Outreach Tool una de cal y otra de arena. El software tras la reescritura es mucho más extensible y pude ir implementando cambios y mejoras más fácilmente, aunque con mi cambio profesional se quedaron bastantes cosas en el tintero, principalmente relacionadas con temas del rediseño de UI. Ahora mismo la evolución (y explotación) del software está un poco en el limbo, quizás puedan seguir haciéndolo otras personas.

A pesar del parón en febrero por la lesión de una costilla flotante durante una sesión de guanteo, los primeros meses del año tuve una muy buena rutina de entrenamiento y empecé a sentir que (además de mantenerme bien físicamente) técnicamente mejoré bastante. Pena que con el lío del cambio de ciudad no reservé apenas tiempo para buscar un gimnasio en el que seguir aprendiendo, finalmente en noviembre encontré uno que pintaba muy bien, aunque aún he ido muy poco.

En 2018 no hice charlas como tal, sólo la que preparé con la excusa del lanzamiento del curso de Codely de BDD con Cucumber. Facilité coding dojos en el trabajo y en la CommitConf, además del Global day of Code Retreat en Coruña. Experimenté con el formato screencast y me moló bastante. Pero apenas escribí 2 posts en el medium de Coding Stones, este me gustaría escribir más. De nuevo formé parte de la organización del Startup Open Space Zaragoza.

Lo de sacar un DNDzgz como PWA quedó en un fail total. Tengo una versión funcional desplegada que les pasé a un puñado de amigos pero le faltan afinar muchas cosas, así que por el momento se queda como otro petproject que iré tocando cuando me de el puntazo de mejorar algo.

Y bueno, ni mucho menos fue un objetivo, pero algo tendré que contar de Zara.com. Llegué con la idea de dedicar la mayor parte de mi tiempo a hacer producto e involucrarme hasta la cocina en todo lo que me dejaran y fuera capaz respecto a ello. Esto es que no quiero sólo desarrollarlo, sino estar desde las fases de conceptualización y aterrizaje hasta el aprendizaje de tenerlo en producción, y acompañar en hacerlo crecer y evolucionarlo. En el camino estoy haciendo variedad de trabajo, yendo desde cuestiones puramente técnicas hasta temas más relacionados con producto o gestión, así que no me aburro ;)

Como de costumbre, más cosas del año y sin ningún orden en particular:

  • Leí mucho este año, pero casi todo fueron libros técnicos.
  • Estuve unos días en Roma.
  • Repetí participación en Movember.
  • No fui a tantos conciertos como en los últimos años, pero fui a un buen puñado.
  • Fui a algún partido a la Romareda y a Riazor (aunque no pudo ser al Depor-Zaragoza).
  • Pude asistir a BilboStack, From The Trenches en Donosti, NOS Day y XantarJ en Santiago, Software Crafters Madrid y Monitoring Day en Zaragoza.
  • Vi un par de veladas de boxeo y me quedé con ganas de ir a alguna grande.
  • Por avisado que fuera, me sorprendió lo masificada de la noche de San Juan en Coruña.
  • Volví a tener vértigos.
  • Colaboré (poco) con la fundación canem.
  • En verano hice unas vacaciones típicas aragonesas: costa daurada y fiestas del pueblo.
  • Al fin vi el Guernica de Picasso.
  • Montamos un eventito con Cachirulo Valley.
  • Hice un curso de product owning con las buenas gentes de Makoto.
  • Sobreviví a 3 bodas.
  • Vanessa también se vino a vivir a Coruña.
  • Me hicieron una fiesta de despedida sorpresa que lo moló todo.

Objetivos 2019

  • Patear la zona noroeste de la península. Tanto zonas costeras atlántica y cantábrica como visitar ciudades y pueblos más hacia el interior; son zonas que apenas he tenido oportunidad de conocer.
  • Colaborar con grupos locales a través de talleres, coding dojos… Ya tengo algunas cosillas medio apalabradas con varias personas que organizan iniciativas por aquí.
  • Conseguir tener una rutina de entrenamiento similar a la que tuve los primeros meses de 2018 para al menos recuperar técnica y aprender trucos nuevos.
  • Escribir una media de un post al mes. Llevo un tiempo en el que me apetece escribir sobre varios temas sobre los que voy trabajando, pero aunque no sean temas muy sesudos sé que me cuesta horrores y termino no dedicándole tiempo.

Respecto a mi actual trabajo, más que marcarme objetivos lo que tengo son retos. Retos que me resultan muy interesantes para este 2019, junto a varios que se avecinan en el horizonte y otros que seguro que irán surgiendo.

¡Feliz an nou!

Blog Bitix: Las contraseñas e información sensible en el código fuente o bytecode de Java no son seguras

Image may be NSFW.
Clik here to view.
Java

En Java el código fuente se compila a una representación en bytecode independiente de la arquitectura del procesador y sistema operativo donde posteriormente se ejecuta. Este bytecode es un formato binario pero que puede ser decompilado fácilmente con la herramienta javap incluida en el propio JDK o examinado su contenido simplemente con un editor de texto hexadecimal. Con estas herramientas es fácil ver las instrucciones del programa para la máquina virtual y los caracteres de las cadenas que fueron incluidas en el código fuente.

Lógicamente, de este modo hardcodear una contraseña en el código fuente hace que el código fuente sea inseguro pero es que incluso distribuir el binario compilado no es seguro ya que cualquier usuario que tenga acceso al binario de la aplicación es potencialmente capaz de recuperar la contraseña. Quien dice contraseña dice igualmente una clave privada de cifrado simétrico usada para cifrar o descifrar datos o un bearer token de OAuth. En definitiva es un problema de seguridad.

Compilado el programa y utilizando la herramienta javap se puede obtener el valor de la contraseña. ¿Adivinas cual es la contraseña en este archivo binario de bytecode examinado el contenido?

Image may be NSFW.
Clik here to view.
Contenido hexadecimal de un archivo binario de bytecode Java

El siguiente ejemplo sencillo de un programa Java incluye una cadena con una supuesta contraseña. Se observa que en el archivo visualizado en formato hexadecimal o decompilado los caracteres de la cadena son fácilmente reconocibles.

Para compilar este pequeño programa se utiliza el comando javac que genera el archivo de bytecodeMain.class.

Para decompilar este pequeño programa se utiliza el comando javap, con él se ven las instrucciones interpretadas por la máquina virtual de Java y la cadena con la contraseña.

Una solución para evitar este problema de seguridad es ubicar la contraseña a un archivo de configuración incluso con los valores sensibles cifrados y que sean descifrados únicamente por la aplicación en el momento de iniciarse. En el caso de ubicar este archivo de configuración en un servidor se puede proteger mediante permisos para que solo los administradores o algunos desarrolladores tenga acceso a él y no cualquier usuario que consiga acceso al sistema.

Koalite: Mis tecnologías de 2018

Entre los post tradicionales de este blog se encuentra el repaso de las tecnologías que he usado profesionalmente a lo largo del año. Si alguien tiene curiosidad por ver la evolución, puede revisar lo que usé en el 2015, 2016 y 2017.

Como el contexto es importante, vamos a centrar un poco desde qué perspectiva está escrito este post.

Sólo voy a mencionar tecnologías que uso profesionalmente, no aquellas con la que haya podido jugar un rato pero no haya tenido en producción. Para eso ya están otros posts de este blog.

También es fundamental recordar que me dedico a productos, no proyectos. Además son productos (software para puntos de venta) que se despliegan localmente en las máquinas y en los cuales no controlamos el proceso de despliegue, el cual se realiza de forma manual no por una cuestión técnica sino de negocio: nuestros clientes son distribuidores que se ganan la vida vendiendo nuestro producto y cobrando por cosas como ésta. Si a esto le unes la importancia de mantener la compatibilidad con versiones anteriores del software, y sistemas operativos entre antiguos y obsoletos, el margen de maniobra que nos queda para jugar con nuevas tecnologías es menor y preferimos garantizar la estabilidad de todo el sistema.

.NET, servidores y escritorio

La base de nuestras aplicaciones está desarrollada usando .NET Framework 3.5 SP1. Sí, es antiguo, pero tenemos mucho parque instalado cuya actualización no es trivial (o ni siquiera está soportada). También tenemos algunos desarrollos de servidor “puro” en los que controlamos el despliegue y usamos versiones más modernas de .NET Framework (tradicional, no .NET Core), pero representa una parte mucho menor de nuestro código.

Aquí miro con envidia algunas funcionalidades nuevas que ofrecen las últimas versiones de C#, pero para usarlas tendríamos que actualizar el tooling (seguimos con Visual Studio 2013 y el msbuild y Resharper de aquella época) y, hasta ahora, no nos ha compensado.

Mantenemos nuestro stack clásico basado en NHibernate, Castle.Windsor, log4net y NUnit. Son piezas que nos funcionan muy bien, las conocemos perfectamente para explotar sus ventajas y minimizar sus inconvenientes, y la migración a otras librerías no creemos que nos aporte lo suficiente como para compensar el coste de hacerlo.

Como servidor de base de datos seguimos usando Microsoft SQL Server, fundamentalmente las versiones 2005 y 2014. La realidad es que tampoco usamos nada especial del 2014 (más allá de que en la versión Express es algo más generoso en tamaño de base de datos) y nos ceñimos a lo soportado por 2005 para mantener compatibilidad. Tampoco es que tengamos unos requisitos muy elevados en este aspecto.

Clientes web

Cada vez más hemos ido migrando las aplicacones escritas en .NET (usando Windows Forms) a tecnologías web.
Ahí se nota más la evolución a lo largo de los años (se puede ver también en los posts que he ido escribiendo sobre tecnologías front) y tenemos variedad de librerías, frameworks y lenguajes. Eso complica el mantenimiento, pero de momento no lo suficiente como para justificar la reescritura de aplicaciones, que están funcionando bien y generando negocio, en aras de usar The Best Platform™.

Tenemos cosas con AngularJS 1.x (estamos deseando quitárnoslo de encima, pero es difícil de justificar económicamente) y con varias versiones de ReactJS (todas relativamente añejas). Tenemos cosas con ES5, otras con ES2015 y otras con TypeScript.

Para hacerlo más entretenido, cada una usa el sistema de compilación que mejor nos pareció en cada momento, incluyendo Grunt, Webpack o simples scripts npm.

En la parte de testing en frontend usamos estrategias variadas, pero al final utilizamos las librerías típicas: jasmine, mocha, chai, protractor y karma (para la parte de angular), nightwatch para tests de extremo a extremo con React. Con Enzyme ya el año anterior vimos que acabábamos teniendo tests que nos resultaban poco útiles y creo que este año no hemos escrito ningún test nuevo con él (incluso hemos borrado algunos existentes para no tener que mantenerlos).

La nube

Este año hemos empezado a prestar más atención a la nube. Nos hemos encontrado con la necesidad de ofrecer determinados servicios mantenidos por nosotros y eso nos ha llevado a aprovechar la parte de IaaS para levantar servidores dedicados para algunos clientes.

De momento estamos usando como proveedor Azure (paso lógico dado nuestro bagaje), y por el camino hemos comenzado también a utilizar algunos servicios concretos de Azure, como Azure Blobs. Imagino que poco a poco iremos usando más servicios de Azure y atándonos más a él. No es algo que me deje demasiado tranquilo, pero bueno.

En nuestra (limitadísima) experiencia con la nube y, especialmente, con Azure, ha habido cosas que nos han gustado más y cosas que nos han gustado menos, como era de esperar.

Por una parte, la flexibilidad para levantar máquinas, aumentar o disminuir su potencia en función de las necesidades, gestionar backups, infraestructura de red, etc., es muy práctica. Como todas las cosas para pobres, lo barato acaba saliendo caro a la larga y los costes cuando empiezas a mirar a medio plazo pueden ser más elevados que con sistemas propios, pero la comodidad y la disminución de la barrera de entrada compensa en muchos casos.

En la parte mala, a veces nos quedamos con la idea de que avanza todo un poco demasiado rápido. Y eso no es necesariamente malo, sino fuese porque te quedas con la impresión de producto a medio hacer. Que estén cambiando continuamente el UI del portal, que tengas unas cuantas cosas en preview, otras marcadas como classic (que suena a que las vas a descontinuar pasado mañana), otras con v1, v2, v3…

Genera cierta sensación de inseguridad y no saber realmente qué puedes usar y hasta cuándo. Nos ocurrió por ejemplo con la parte de RBAC que nos gustó mucho pero cuando fuimos a implementarla nos encontramos con algunos problemas por no estar completamente integrada con otros servicios.

(D)VCS e integración continua

En esta parte es donde más cambios hemos introducido este año. Después de diez años usando Subversion para gestionar el código y CruiseControl.NET para lanzar nuestros scripts de compilación, hemos dado el salto a GitLab.

No es que hubiera dejado de funcionar lo que teníamos, pero empezaba a suponer demasiada fricción para algunas cosas que queríamos hacer.

El rendimiento de SVN ya se hacía difícil de soportar con el volumen de código que teníamos. Por su parte, CCNET nos complicaba la parte de compilar varias ramas de una misma aplicación. Lo hacíamos, pero de forma muy manual. Eso, unido a una suite de tests grande que tarda mucho en ejecutarse hacía que la rama principal se rompiese con frecuencia porque para evitar lanzar los tests en nuestros equipos de desarrollo subíamos directamente y dejábamos que se pasaran arriba.

Con GitLab conseguimos un sistema de control de versiones más rápido y potente, que nos deja hacer más cosas, y de paso la gestión de pipelines de compilación.

La migración de la parte de código no ha existido (adiós a la historia, que sigue viviendo en SVN) y ha consistido más en empezar a usar git “en serio”. Todos conocíamos git, pero no lo habíamos explotado realmente. De momento está siendo una buena experiencia.

Adaptar nuestros scripts de compilación ha sido sencillo porque siempre hemos apostado por independizarlos de la herramienta que los lanza. Eso nos ha permitido aprovechar para automatizar un par de procesos manuales que nos quedaban y montar algo parecido a un sistema de entrega continua hacia la gente de QA que nos ayuda a detectar bugs antes.

Después de probar unas cuantas (bastantes) herramientas para usar git, todos hemos acabado adoptando Git Bash como interfaz principal, apoyándonos en alguna otra cosa para tareas puntuales. Aquí me quedo con la espinita clavada de magit, que me encantó por filosofía y usabilidad, pero que es inmanejable con repositorios grandes en Windows.

Resumen

Viendo la evolución tecnológica que hemos tenido en los últimos años está claro que no formamos un equipo que esté trabajando siempre con lo último de lo último. Como decía al principio, el contexto en que nos movemos hace que eso no sea práctico y necesitemos ser más conservadores.

Sin embargo, que tengamos que ser conservadores al adoptar nuevas tecnologías no nos exime de tener que estar pendientes de lo que va surgiendo y conocer qué nos ofrece esa nueva moda superinteresante de la que todo habla (spoiler: muchas veces, más de lo mismo). Puede que no lo pongamos en producción en la versión 1, y ni siquiera en la versión 2, pero si realmente nos soluciona un problema, es probable que acabemos usándolo.

En lo que sí tenemos mucho cuidado es en evitar la tecnología por la tecnología. Jugar con cosas nuevas y cambiar de plataformas cada cierto tiempo es muy divertido y tentador, pero desde un punto de vista puramente económico (y no olvidemos que aquí estamos hablando de tecnologías que usamos profesionalmente), casi nunca es rentable.

Posts relacionados:

  1. Resumen 2018
  2. Mis tecnologías del 2017
  3. Mis tecnologías del 2016
Image may be NSFW.
Clik here to view.

Variable not found: Top Posts 2018 en Variable Not Found

Image may be NSFW.
Clik here to view.
Top Posts 2018 en Variable Not Found
Desde hace una década, el primer post de enero siempre lo reservo para revisar qué publicaciones del blog han sido las más visitadas del año recién finalizado, así que vamos a continuar con esta arraigada tradición una vez más ;)

También, como no podía ser de otra forma, aprovecho también para desearos a todos un año 2019 lleno de alegrías tanto en el terreno personal como en el profesional, deseo que podéis considerar extensible para a todos los que os rodean y os importan :)

Y dicho esto, vamos al tema...

Top ten 2018 en Variable not found

Comenzando por el final, en décima posición encontramos el post donde echábamos un vistazo al "filtro [ApiController] en ASP.NET Core MVC, una novedad presentada con ASP.NET Core 2.1, que promete acompañarnos durante mucho tiempo gracias a la introducción de convenciones bastante razonables para los desarrolladores de APIs web.

La novena página más visitada es un artículo de reflexión y autoayuda titulado "Mi controlador tiene muchos parámetros en el constructor, ¿estoy haciendo algo mal?" ;) Es una pregunta que seguro muchos nos hemos hecho al ver que nuestro código desprende un cierto tufillo, y en este post vemos cómo detectarlo y eliminar estos síntomas aromáticos que pueden indicar que algo no va bien.

Image may be NSFW.
Clik here to view.
¿Qué podría salir mal?
¿Cómo? ¿Que a estas alturas no comparo con null de forma totalmente correcta? Pues eso habéis debido pensar muchos al leer este post, el octavo más leído, donde se muestra un escenario en el que la comparación con doble igualdad del tipo x==null no se comportaría como esperábamos. Aunque se trate de un caso extremo, creo que vale la pena conocer cómo es posible que esto ocurra.

En séptima posición como contenido más visitado, encontramos cómo incluir scripts en la página desde vistas parciales ASP.NET Core MVC con DynamicSections, la presentación de una mis pequeñas contribuciones a la humanidad en forma de paquete NuGet, que permite registrar bloques de script desde cualquier punto del proceso de una petición (renderizado de parciales, filtros, controladores...) e incluirlos finalmente en la página generada. Algo parecido a los bloques @section de Razor, pero resuelto de tiempo de ejecución, de forma dinámica.

Ah, las redirecciones HTTP, esas grandes desconocidas ;) Aunque todos hemos utilizado cientos de veces los códigos de estado HTTP 301 y 302 para redirigir al cliente a nuevas URL, parece que no nos sonaban tanto los estados HTTP 303, 307 y 308, códigos definidos en el estándar HTTP que aportan mayor riqueza al resultado. ¿Los conocíais?

A continuación, otro post que ha despertado bastante interés es el dedicado al estándar Problem details (RFC7807), que describe cómo retornar errores desde APIs HTTP de forma normalizada, y veíamos su uso en ASP.NET Core. Creo que, aunque sólo sea para pillar algunas ideas, vale la pena echarle el vistazo.

En el tercer y cuarto puesto encontramos posts que resumían, respectivamente, las novedades llegadas con las últimas actualizaciones del framework, ASP.NET Core 2.1 y ASP.NET Core 2.2. Es bueno saber que a los desarrolladores nos gusta estar al tanto de estas cosas :)

En segunda posición, un post donde trataba la extensión de claims de usuarios en ASP.NET Core, un interesante mecanismo que permite aligerar el peso de los tokens JWT o cookies permitiendo la modificación manual del Principal asociado a las peticiones. Sin duda, algo que vale la pena conocer si utilizáis este tipo de autenticación en vuestras aplicaciones.

And the winner is... sorprendentemente, el post más visitado de este pasado año es "¿Se pueden desarrollar aplicaciones ASP.NET Core con Visual Basic .NET?", una prueba de que este lenguaje sigue manteniendo su tirón y hay muchos usuarios interesados en utilizarlo en aplicaciones ASP.NET Core, o al menos en conocer su futuro. Amigos de Microsoft: si estáis leyendo esto, tomad nota y haced algo al respecto, anda ;)

Como mención especial, creo que vale la pena citar al ya famoso operador virgulilla de C#, que aunque no ha llegado a situarse dentro del top ten, se ha quedado bastante cerca y sobre todo teniendo en cuenta que se publicó un par de días antes de terminar el año. Su propósito era sólo sorprender y divertir a esa gente rarilla como nosotros, los que disfrutamos cada vez que hay novedades en los lenguajes o herramientas con los que trabajamos a diario.

Y ahora, ¡a por 2019!

Publicado en Variable not found
(Imagen de portada original de Pixabay)
Image may be NSFW.
Clik here to view.

Variable not found: Enlaces interesantes 344

Image may be NSFW.
Clik here to view.
Enlaces interesantes
Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

    Otros

    Publicado en Variable not found.Image may be NSFW.
    Clik here to view.

    Fixed Buffer: Reconociendo a personas mediante Azure FaceAPI

    Image may be NSFW.
    Clik here to view.
    Azure

    Han pasado ya las navidades, y toca la vuelta al trabajo, la vuelta a los viajes… y gracias a mi último viaje estuve cenando con un amigo, y me contó un proyecto que se trae entre manos, el cual requería del reconocimiento facial como parte de la identificación inequívoca de usuarios. En su momento, leí sobre los Cognitive Services de Azure, pero nunca me había aventurado a probarlos, y bueno, ya sabéis que soy curioso, así que en cuanto he tenido un momento, me he puesto a probarlo, ¡y la verdad es que ha sido realmente fácil! Vamos con ello:

    Crear el servicio Azure FaceAPI

    Lo primero que necesitamos, es tener una cuenta en Azure, no es necesario que sea de pago (aunque para confirmar la cuenta nos piden una tarjeta de crédito, no se efectúa ningún cargo).

    Una vez que la tenemos, vamos a crear el servicio:

    Image may be NSFW.
    Clik here to view.
    CrearServicio

    Para ello, con eso, nos muestra una nueva ventana donde poner nuestros datos:

    Image may be NSFW.
    Clik here to view.
    NombreServicio

    En ella, basta con que le indiquemos el nombre, que suscripción queremos utilizar, donde queremos que este el servidor, el plan de precios (OJO!!, yo utilizo “S0” porque estoy haciendo otra prueba más en profundidad y tengo el “F0” ocupado, pero el gratuito es “F0”) y por último nos pide el grupo de recursos al que queremos que pertenezca (si no tenemos ningún grupo, lo podemos crear fácilmente con el botón “Crear nuevo”).

    Con este paso, pulsamos sobre “Crear”, y como podemos ver en el dashboard, ya tenemos nuestro servicio creado:

    Image may be NSFW.
    Clik here to view.
    dashboard

    Solo nos queda conocer la url que debemos utilizar, y crear las claves para poder usarla, para eso, basta con pulsar sobre el servicio para ver su configuración:

    Image may be NSFW.
    Clik here to view.
    overview

    He indicado en amarillo la url de endpoint que tendremos que indicarle a la API en nuestro programa, en mi caso es:
    https://westeurope.api.cognitive.microsoft.com/face/v1.0

    Justo debajo, tenemos el botón para gestionar nuestras claves de acceso, lo que lanza una ventana donde nos las muestra:

    Image may be NSFW.
    Clik here to view.
    keys

    Debemos apuntarlas (o volver luego y consultarlas) al igual que la url del endpoint, ya que son datos que le vamos a tener que pasar a la API desde nuestro programa.

    Creando nuestro programa

    Para poder consumir nuestro servicio Azure FaceAPI, vamos a utilizar una aplicación de formularios en .Net Framework, pero por supuesto, podemos utilizarlo en nuestro proyecto NetCore (pero esta vez, la librería cambia de nombre). Para ello, creamos nuestro proyecto de formularios, y añadimos el paquete:

    PM->Install-Package Microsoft.ProjectOxford.Face -v 1.4.0

    Si estuviésemos en NetCore, por ejemplo en nuestra web ASP, el paquete seria:

    PM->Install-Package Microsoft.ProjectOxford.Face.DotNetCore -Version 1.1.0

    Al ser un código extenso, pongo el enlace para descargarlo desde GitHub, y aquí solo vamos a ver las partes claves de la API:

     
    var faceServiceClient = new FaceServiceClient("Tu Key", "Url de la API");
    

    Crearemos una instancia del cliente de la API, que luego utilizaremos.

     
    var groups = await faceServiceClient.ListPersonGroupsAsync();
    

    Listamos los grupos que ya existan creador previamente.

     
    await faceServiceClient.DeletePersonGroupAsync(group.PersonGroupId);

    Borramos un grupo.

     
    await faceServiceClient.CreatePersonGroupAsync(GroupGUID, "FixedBuffer");

    Creamos un grupo.

     
    var personResult = await faceServiceClient.CreatePersonAsync(GroupGUID, personName);
    

    Creamos una persona dentro del grupo.

     
    var persistFace = await faceServiceClient.AddPersonFaceInPersonGroupAsync(GroupGUID, personResult.PersonId, fStream, file.FullName);
    

    Añadimos una foto de training a la persona.

     
    await faceServiceClient.TrainPersonGroupAsync(GroupGUID);
    

    Hacemos el entrenamiento del grupo.

     
    var faces = await faceServiceClient.DetectAsync(fStream);
    

    Detectamos las caras de la imagen.

     
    var results = await faceServiceClient.IdentifyAsync(GroupGUID, faces.Select(ff => ff.FaceId).ToArray());
    

    Identificamos las caras dentro de la gente del grupo.

     
    var person = await faceServiceClient.GetPersonAsync(GroupGUID, result.Candidates[0].PersonId);
    

    Obtenemos a la persona por Guid.

    Como se puede ver, la API nos provee de una manera de funcionar bastante lógica.
    En primer lugar, tenemos que crear un grupo de personas, después añadir personas a ese grupo, añadir caras a esas personas, y después entrenar el grupo. Esto solo es necesario hacerlo una vez para entrenar el grupo, con eso, podemos pasar a identificar a personas.

    Para identificar, los pasos son también sencillos, en primer lugar, detectar las caras en la imagen, en segundo lugar, detectar a quien puede pertenecer esa cara, y en último lugar (en caso de no haberlo almacenado locamente), obtener a la persona a quien pertenece esa cara.

    Código completo:

     
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Configuration;
    using System.Windows.Forms;
    using Microsoft.ProjectOxford.Face;
    using System.IO;
    
    namespace PostAzureFaceAPI
    {
        public partial class MainForm : Form
        {
            string FaceAPIKey = ConfigurationManager.AppSettings["FaceAPIKey"];
            string FaceAPIEndPoint = ConfigurationManager.AppSettings["FaceAPIEndPoint"];
            string GroupGUID = Guid.NewGuid().ToString();
    
            public MainForm()
            {
                InitializeComponent();
            }
    
            private async void btn_Train_Click(object sender, EventArgs e)
            {
                //Abrimos un dialogo de seleccion de carpetas
                FolderBrowserDialog dialog = new FolderBrowserDialog();
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    //Si se ha seleccionado un directorio, hacemos su info
                    DirectoryInfo directory = new DirectoryInfo(dialog.SelectedPath);
    
                    //Comprobamos que el directorio tiene carpetas de personas
                    if (directory.GetDirectories().Count() == 0)
                        return;
    
                    //=====Empezamos a crear el grupo de trabajo
                    //Creamos el cliente
                    var faceServiceClient = new FaceServiceClient(FaceAPIKey, FaceAPIEndPoint);
    
                    //Vamos a trabajar desde 0 siempre, asi que comprobamos si hay grupos, y si los hay los borramos
                    var groups = await faceServiceClient.ListPersonGroupsAsync();
                    foreach (var group in groups)
                    {
                        await faceServiceClient.DeletePersonGroupAsync(group.PersonGroupId);
                    }
                    //Creamos un grupo
                    await faceServiceClient.CreatePersonGroupAsync(GroupGUID, "FixedBuffer");
    
                    foreach (var person in directory.GetDirectories())
                    {
                        //Comprobamos que tenga imagenes
                        if (person.GetFiles().Count() == 0)
                            return;
    
                        //Obtenemos el nombre que le vamos a dar a la persona
                        var personName = person.Name;
    
                        lbl_Status.Text = $"Entrenando a {personName}";
    
                        //Añadimos a una persona al grupo
                        var personResult = await faceServiceClient.CreatePersonAsync(GroupGUID, personName);
    
                        //Añadimos todas las fotos a la persona
                        foreach (var file in person.GetFiles())
                        {
                            using (var fStream = File.OpenRead(file.FullName))
                            {
                                try
                                {
                                    //Cargamos la imagen en el pictureBox
                                    pct_Imagen.Image = new Bitmap(fStream);
                                    //Reiniciamos el Stream
                                    fStream.Seek(0, SeekOrigin.Begin);
                                    // Actualizamos las caras en el servidor
                                    var persistFace = await faceServiceClient.AddPersonFaceInPersonGroupAsync(GroupGUID, personResult.PersonId, fStream, file.FullName);
                                }
                                catch (FaceAPIException ex)
                                {
                                    lbl_Status.Text = "";
                                    MessageBox.Show($"Imposible seguir, razón:{ex.ErrorMessage}");
                                    return;
                                }
                            }
                        }
                    }
    
                    try
                    {
                        //Entrenamos el grupo con todas las personas que hemos metido
                        await faceServiceClient.TrainPersonGroupAsync(GroupGUID);
    
                        // Esperamos a que el entrenamiento acabe
                        while (true)
                        {
                            await Task.Delay(1000);
                            var status = await faceServiceClient.GetPersonGroupTrainingStatusAsync(GroupGUID);
                            if (status.Status != Microsoft.ProjectOxford.Face.Contract.Status.Running)
                            {
                                break;
                            }
                        }
    
                        //Si hemos llegado hasta aqui, el entrenamiento se ha completado
                        btn_Find.Enabled = true;
                        lbl_Status.Text = $"Entrenamiento completado";
                    }
                    catch (FaceAPIException ex)
                    {
                        lbl_Status.Text = "";
                        MessageBox.Show($"Response: {ex.ErrorCode}. {ex.ErrorMessage}");
                    }
                    GC.Collect();
                }
            }
    
            private async void btn_Find_Click(object sender, EventArgs e)
            {
                lbl_Status.Text = "";
                OpenFileDialog dialog = new OpenFileDialog();
                dialog.DefaultExt = ".jpg";
                dialog.Filter = "Image files(*.jpg, *.png, *.bmp, *.gif) | *.jpg; *.png; *.bmp; *.gif";
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    var imagePath = dialog.FileName;
    
                    //Creamos el cliente
                    var faceServiceClient = new FaceServiceClient(FaceAPIKey, FaceAPIEndPoint);
    
                    using (var fStream = File.OpenRead(imagePath))
                    {
                        //Cargamos la imagen en el pictureBox
                        pct_Imagen.Image = new Bitmap(fStream);
                        //Reiniciamos el Stream
                        fStream.Seek(0, SeekOrigin.Begin);
    
                        try
                        {
                            //Detectamos las caras
                            var faces = await faceServiceClient.DetectAsync(fStream);
    
                            //Detectamos a las personas
                            var results = await faceServiceClient.IdentifyAsync(GroupGUID, faces.Select(ff => ff.FaceId).ToArray());
    
                            //Creamos una lista de caras y nombres asociados
                            List<(Guid, string)> detections = new List<(Guid, string)>();
                            foreach (var result in results)
                            {
                                //En caso de no haber encontrado un candidato, nos lo saltamos
                                if (result.Candidates.Length == 0)
                                    continue;
                                var faceId = faces.FirstOrDefault(f => f.FaceId == result.FaceId).FaceId;
                                //Consultamos los datos de la persona detectada
                                var person = await faceServiceClient.GetPersonAsync(GroupGUID, result.Candidates[0].PersonId);
    
                                //Añadimos a la lista la relacion
                                detections.Add((faceId,person.Name));            
                            }
    
                            var faceBitmap = new Bitmap(pct_Imagen.Image);
    
                            using (var g = Graphics.FromImage(faceBitmap))
                            {                           
    
                                var br = new SolidBrush(Color.FromArgb(200, Color.LightGreen));
    
                                // Por cada cara reconocida
                                foreach (var face in faces)
                                {
                                    var fr = face.FaceRectangle;
                                    var fa = face.FaceAttributes;
    
                                    var faceRect = new Rectangle(fr.Left, fr.Top, fr.Width, fr.Height);
                                    Pen p = new Pen(br);
                                    p.Width = 50;
                                    g.DrawRectangle(p, faceRect);                                
    
                                    // Calculamos la posicon del rectangulo
                                    int rectTop = fr.Top + fr.Height + 10;
                                    if (rectTop + 45 > faceBitmap.Height) rectTop = fr.Top - 30;
    
                                    // Calculamos las dimensiones del rectangulo                     
                                    g.FillRectangle(br, fr.Left - 10, rectTop, fr.Width < 120 ? 120 : fr.Width + 20, 125);
    
                                    //Buscamos en la lista de relaciones cara persona
                                    var person = detections.Where(x => x.Item1 == face.FaceId).FirstOrDefault();
                                    var personName = person.Item2;
                                    Font font = new Font(Font.FontFamily,90);
                                    //Pintamos el nombre en la imagen
                                    g.DrawString($"{personName}",
                                                 font, Brushes.Black,
                                                 fr.Left - 8,
                                                 rectTop + 4);
                                }
                            }
                            pct_Imagen.Image = faceBitmap;
                        }
                        catch (FaceAPIException ex)
                        {
    
                        }
                    }
                }
            }
        }
    }
    

    Para poder utilizar el código, debemos modificar el App.config cambiando las claves:

    <appSettings> 
        <add key="FaceAPIKey" value=""/> 
        <add key="FaceAPIEndPoint" value=""/> 
      </appSettings> 
    

    Por las que hemos obtenido al crear el servicio. Como se puede ver en el funcionamiento, la carga se hace desde seleccionando una carpeta, que a su vez tenga dentro una carpeta por cada persona que queramos entrenar y que se llame como la persona, será dentro de cada una de estas carpetas donde metamos las fotos de cada persona:

    Image may be NSFW.
    Clik here to view.
    train

    Una vez que hayamos completado el entrenamiento, podremos seleccionar una imagen en la que identificar a la persona:

    Image may be NSFW.
    Clik here to view.
    resultado

    Como hemos podido ver, es muy sencillo empezar a utilizar la API de reconocimiento e identificación facial. En próximas entradas, seguiremos profundizando en los Cognitive Services de Azure, tanto conociendo el resto de los servicios, como profundizando en este, que ofrece muchas más opciones aparte de la identificación de personas.

    **La entrada Reconociendo a personas mediante Azure FaceAPI se publicó primero en Fixed Buffer.**

    Blog Bitix: Acceder a hojas de cálculo de Google Docs mediante API desde una aplicación Java

    Image may be NSFW.
    Clik here to view.
    Java
    Image may be NSFW.
    Clik here to view.
    Google

    Google ofrece numerosos productos de desarrollo que permiten automatizar tareas e realizar integración con servicios, aplicaciones y documentos de Google creando un programa con un lenguaje de programacion. Las API que ofrece Google desde Drive, Sheets, Sides, GMail, Calendar, Contacts, Street View, AdSense, Analytics, Youtube, Speech y muchos más.

    Para acceder a los servicios mediante APIs hay que obtener unas credenciales. Un ejemplo es el siguiente usando un API key para acceder a una hoja de cálculo compartida para cualquier usuario que tenga el enlace o identificativo del documento en Google Drive. Cada servicio de Google ofrece una API distinta según su contexto y datos que maneja.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    packageio.github.picodotdev.javagoogleapi;...publicclassMain{...privatestaticCredentialgetCredentialsApiKey(){returnnewGoogleCredential().createScoped(SCOPES);}...publicstaticvoidmain(String...args)throwsIOException,GeneralSecurityException{NetHttpTransporthttpTransport=GoogleNetHttpTransport.newTrustedTransport();JsonFactoryjsonFactory=JacksonFactory.getDefaultInstance();Sheetsservice=newSheets.Builder(httpTransport,jsonFactory,getCredentialsApiKey()).setApplicationName(APPLICATION_NAME).build();...}}

    Las hojas de cálculo se utilizan para contener información, son fácilmente editables por los usuarios y se convierten casi en una forma de base de datos. Con las APIs que ofrece Google para Spreadsheets esta información es utilizable en una aplicación, un buen caso de uso es aquel en el que ciertos datos o parámetros potencialmente cambian cada cierto tiempo o según reglas de negocio. Por ejemplo, se puede crear una hoja de cálculo con los precios, descripciones, existencias, disponibilidad o gastos de envío de los productos e importar esta información en la base de datos de una aplicación usando una API de Google, en vez de crear una aplicación backoffice de edición a medida para editar esa información, la aplicación consistiría en procesar el documento e insertar su información en la base de datos.

    Como contrapartida de estas integraciones hay que tener en cuenta que una aplicación se hace dependiente del servicio los servicios de Google que utilice, hay que evaluar si esta dependencia es deseable.

    Otro posible aplicación es utilizar documentos de texto en Google Drive como plantillas de correos electrónicos, se permite una edición sencilla y posteriormente se importan en la aplicación para que los utilice. A un documento de Google Drive se accede mediante esta petición HTTP GET. Las hojas de cálculo tamibén están disponibles mediante una interfaz REST sin embargo usando las APIs que ofrece Google para cada lenguaje es más cómodo que tratar con los datos en crudo en formato JSON.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    $ curl -X GET "https://sheets.googleapis.com/v4/spreadsheets/1JhBPGW4F.../values/Hoja1?key=AIzaSyDGwW..."{"range": "Hoja1!A1:AA1000",
    "majorDimension": "ROWS",
    "values": [["a1",
    "a3"],
    ["a2",
    "a4"]]}
    1
    2
    3
    4
    
    $ curl -X GET "https://www.googleapis.com/drive/v3/files/1YCnD37w6p.../export?key=AIzaSyDGwW...&mimeType=text/plain"
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
    ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
    occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    Para las hojas de cálculo hay dos formas de autorización para una aplicación. Mediante una API key con permisos de utilización de la API permite acceder a cualquier documento público, compartido de forma pública o para los usuarios que tengan el enlace o identificativo del documento. La otra más segura es creando una cuenta de servicio de forma que el documento se comparta únicamente con esa cuenta de servicio como si de cualquier otro usuario se tratase en vez de hacerlo público o para cualquiera que tenga el enlace.

     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
    
    packageio.github.picodotdev.javagoogleapi;...publicclassMain{...privatestaticfinalStringCREDENTIALS_FILE_PATH="/blogbitix-119471bc8ebf.json";...privatestaticCredentialgetCredentialsServiceAccount(NetHttpTransporthttpTransport,JsonFactoryjsonFactory)throwsIOException{InputStreamin=Main.class.getResourceAsStream(CREDENTIALS_FILE_PATH);returnGoogleCredential.fromStream(in,httpTransport,jsonFactory).createScoped(SCOPES);}publicstaticvoidmain(String...args)throwsIOException,GeneralSecurityException{NetHttpTransporthttpTransport=GoogleNetHttpTransport.newTrustedTransport();JsonFactoryjsonFactory=JacksonFactory.getDefaultInstance();Sheetsservice=newSheets.Builder(httpTransport,jsonFactory,getCredentialsServiceAccount(httpTransport,jsonFactory)).setApplicationName(APPLICATION_NAME).build();...}}

    Laa API key se crean en la página de Credenciales para lo que previamente hay que crer un proyecto. Para leer el documento hay que compartirlo al menos para cualquiera que tenga acceso al enlace, al compartirlo se especifica si se hace en modo solo lectura o con permisos de ecritura.

    El enlace al compartir el documento o al editarlo contiene el identificativo de documento. Con la API key o cuenta de servicio, el identificativo del documento y el documento compartido al menos para cualquiera que tenga el enlace la información del documento está accesible para una aplicación mediante una API REST o de forma programática con una implementación de la API con Java, este programa Java imprime el contenido de las celdas de la hoja de cálculo en la terminal. Se necesita una expresión que identifique la hoja y el contenido de las celdas de las que se quieren datos con un rango en notación A1.

     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
    
    packageio.github.picodotdev.javagoogleapi;...publicclassMain{...publicstaticvoidmain(String...args)throwsIOException,GeneralSecurityException{...Sheetsservice=newSheets.Builder(httpTransport,jsonFactory,getCredentialsApiKey()).setApplicationName(APPLICATION_NAME).build();ValueRangeresponse=service.spreadsheets().values().get(SPREADSHEET_ID,RANGE).setKey(API_KEY).execute();List<List<Object>>values=response.getValues();if(values==null||values.isEmpty()){System.out.println("No data found.");return;}for(List<Object>row:values){System.out.println(row.stream().map(Object::toString).collect(java.util.stream.Collectors.joining(", ")));}}}
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    $ ./gradlew run
    > Task :compileJava
    > Task :processResources UP-TO-DATE
    > Task :classes
    > Task :run
    a1, a3
    a2, a4
    BUILD SUCCESSFUL in 1s
    3 actionable tasks: 2 executed, 1 up-to-date

    El método de API key obliga a hacer público el documento lo que no es deseable desde el punto de vista de seguridad aunque es un poco más simple que crear una cuenta de servicio. Para no hacer público el documento pero permitir acceder a una aplicación hay que crear una cuenta de servicio en la página Cuentas de servicio seleccionando o creando un proyecto.

    Image may be NSFW.
    Clik here to view.
    Cuenta de servicio de Google

    Al crear una cuenta de servicio y una clave se genera un archivo en formato JSON con las credenciales que hay que guardar y utilizar en una aplicación para acceder a los documentos compartidos con esta cuenta de servicio.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    {
    "type": "service_account",
    "project_id": "blogbitix",
    "private_key_id": "119471bc8...",
    "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFQpNVknb3bRp9\n...\n-----END PRIVATE KEY-----\n",
    "client_email": "blogbitix@blogbitix.iam.gserviceaccount.com",
    "client_id": "110222042...",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/blogbitix%40blogbitix.iam.gserviceaccount.com"
    }
    

    En vez de compartir el documento con cualquiera que tenga en enlace, con una cuenta de servicio el documento se puede compartir únicamente con esa cuenta de servicio, la cuenta de servicio posee un correo electrónico que la identifica, el documento se puede compartir únicamente con esta cuenta de servicio como si de cualquier otro usuario se tratase.

    Image may be NSFW.
    Clik here to view.
    Documento compartido con cuenta de servicio

    El siguiente código Java accede a un documento utilizando las credenciales de una cuenta de servicio.

     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
    
    packageio.github.picodotdev.javagoogleapi;....publicclassMain{privatestaticfinalStringAPPLICATION_NAME="JavaGoogleApi";privatestaticfinalStringAPI_KEY="AIzaSyDBZ...";privatestaticfinalStringCREDENTIALS_FILE_PATH="/blogbitix-119471bc8ebf.json";privatestaticfinalStringSPREADSHEET_ID="1JhBPGW4F...";privatestaticfinalStringRANGE="Hoja1";privatestaticfinalList<String>SCOPES=Collections.singletonList(SheetsScopes.SPREADSHEETS_READONLY);privatestaticCredentialgetCredentialsApiKey(){returnnewGoogleCredential().createScoped(SCOPES);}privatestaticCredentialgetCredentialsServiceAccount(NetHttpTransporthttpTransport,JsonFactoryjsonFactory)throwsIOException{InputStreamin=Main.class.getResourceAsStream(CREDENTIALS_FILE_PATH);returnGoogleCredential.fromStream(in,httpTransport,jsonFactory).createScoped(SCOPES);}publicstaticvoidmain(String...args)throwsIOException,GeneralSecurityException{NetHttpTransporthttpTransport=GoogleNetHttpTransport.newTrustedTransport();JsonFactoryjsonFactory=JacksonFactory.getDefaultInstance();Sheetsservice=newSheets.Builder(httpTransport,jsonFactory,getCredentialsServiceAccount(httpTransport,jsonFactory)).setApplicationName(APPLICATION_NAME).build();ValueRangeresponse=service.spreadsheets().values().get(SPREADSHEET_ID,RANGE).setKey(API_KEY).execute();List<List<Object>>values=response.getValues();if(values==null||values.isEmpty()){System.out.println("No data found.");return;}for(List<Object>row:values){System.out.println(row.stream().map(Object::toString).collect(java.util.stream.Collectors.joining(", ")));}}}

    En el caso de Java hay que incluir la dependencia que proporciona la implementación de la API de Google Spreadsheets para Java como se muestra usando Gradle.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    applyplugin:'java'applyplugin:'application'mainClassName='io.github.picodotdev.javagoogleapi.Main'version='1.0'repositories{mavenCentral()}dependencies{compile'com.google.apis:google-api-services-sheets:v4-rev553-1.25.0'}

    Google ofrece un explorador para probar las peticiones y permisos de los documentos o explorar cualquier otra API de Google por ejemplo esta de Drive para exportar un documento en un formato determinado directamente desde una web sin tener que crear una aplicación, también se pueden hacer peticiones desde las páginas de documentación.

    El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

    Blog Bitix: Compilar el código fuente y ejecutar con los comandos javac, java y jar en Java 8 o anteriores

    Image may be NSFW.
    Clik here to view.
    Java

    Aún recuerdo cuando empecé a programar con el lenguaje Java sobre el año 1997 que la compilación y ejecución del código la hacía manualmente con los comandos javac, java y jar en un máquina Intel Pentium a 120 Mhz con tan solo 8 MiB, más tarde 32 MiB, con Windows 95 y Java 1.2, momento en el que ni siquiera había un IDE ni las herramientas de construcción modernas como Gradle, había que descargar manualmente las librerías de dependencias en forma de archivos jar que se requiriesen. Luego con JBuilder como IDE este se encargaba de realizar la compilación y ejecución y no hacía falta utilizar estos comandos directamente.

    Ahora con herramientas como Gradle además de compilar y ejecutar el programa incluso las dependencias son descargadas de forma automática de repositorios donde se ubican versionadas incluso de forma transitiva, descargando las dependencias de las dependencias.

    Usar estos dos comandos directamente ya no es necesario pero como curiosidad comentaré como es su uso. El comando javac sirve para compilar los archivos de código fuente, dado que los paquetes del código fuente de Java se corresponden con directorios en el sistema de archivos el código fuente se ha de ubicar de forma consistente entre la estructura de directorio y el código fuente. Suponiendo que que hay las siguientes clases que hacen uso de la librería log4j2 y están ubicadas en el directorio src/main/java con la misma convención que utiliza Gradle el comando para realizar la compilación y copiar los recursos es el siguiente.

     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
    
    $ tree
    .
    ├── jar.sh
    ├── javac.sh
    ├── java-jar.sh
    ├── java.sh
    ├── libraries
    │   ├── log4j-api-2.11.1.jar
    │   └── log4j-core-2.11.1.jar
    ├── src
    │   └── main
    │   ├── java
    │   │   └── io
    │   │   └── github
    │   │   └── picodotdev
    │   │   └── blogbitix
    │   │   └── java8
    │   │   └── helloworld
    │   │   └── Main.java
    │   ├── misc
    │   │   └── MANIFEST.MF
    │   └── resources
    │   └── log4j2.xml
    └── target
    └── classes
    14 directories, 9 files
    1
    2
    3
    
    #!/usr/bin/env bash
    javac -classpath "libraries/*" -sourcepath src/main/java -source 1.8 -target 1.8 -d target/classes src/main/java/io/github/picodotdev/blogbitix/java8/helloworld/Main.java
    cp -r src/main/resources/* target/classes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    packageio.github.picodotdev.blogbitix.java8.helloworld;importjava.util.Arrays;importjava.util.stream.Collectors;importorg.apache.logging.log4j.Logger;importorg.apache.logging.log4j.LogManager;publicclassMain{privatestaticLoggerlogger=LogManager.getLogger(Main.class);publicstaticvoidmain(String[]args){logger.info("Arguments: {}",Arrays.asList(args).stream().collect(Collectors.joining(", ")));}}

    Con el parámetro -classpath se indica la ubicación de las librerías o dependencias que requiriere el código fuente, con el parámetro -sourcepath el directorio raíz de los archivos de código fuente, el parámetro -source indica la versión del lenguaje del código fuente, -target la versión de la máquina virtual del bytecode que generará el compilador y con el parámetro -d el directorio donde generan los archivos class con el bytecode.

    Una vez generados los archivos de bytecode a partir de la compilación del código fuente su ejecución se realiza con el comando java donde hay que indicar las ubicaciones del los archivos class y las librerías jar necesarias que necesiten, la clase principal con el punto de entrada del programa que contenga un método public static void main(String[] args) y los parámetros del programa que se reciben en el parámetro args del método main.

    1
    2
    
    #!/usr/bin/env bash
    java -classpath "target/classes:libraries/*" io.github.picodotdev.blogbitix.java8.helloworld.Main "$@"
    1
    2
    
    $ ./java.sh arg1 arg2 arg3
    16:13:45.386 [main] INFO io.github.picodotdev.blogbitix.java8.helloworld.Main Arguments: arg1, arg2, arg3

    La distribución de los archivos class se suele realizar usando librerías jar y estas se construyen usando el comando jar. El archivo de manifiesto es un descriptor en el que se puede indicar la clase de entrada sin tener que especificarla en el comando java haciendo los archivo jar similar a un ejecutable.

    1
    2
    
    #!/usr/bin/env bash
    jar cvfm holamundojava8.jar src/main/misc/MANIFEST.MF -C target/classes .
    1
    2
    3
    
    Main-Class: io.github.picodotdev.blogbitix.java8.helloworld.Main
    Class-Path: libraries/log4j-api-2.11.1.jar libraries/log4j-core-2.11.1.jar
    

    Y la ejecución de del programa contenido en el archivo jar.

    1
    2
    
    #!/usr/bin/env bash
    java -jar holamundojava8.jar "$@"
    1
    2
    
    $ ./java-jar.sh arg1 arg2 arg3
    16:20:33.848 [main] INFO io.github.picodotdev.blogbitix.java8.helloworld.Main Arguments: arg1, arg2, arg3

    Así es la compilación y ejecución de código Java en Java 8 y anteriores, con la introducción de la modularidad a partir de Java 9 esto cambia ya que el classpath queda obsoleto y es reemplazado por el equivalente con módulos module-path.

    El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

    Una sinfonía en C#: Delegate, predicate, Action, Func, métodos anónimos, explicados para mortales.

    No es un tema nuevo ni mucho menos,  sin embargo no siempre es del todo bien comprendido y su correcta comprensión es muy importante ya que muchas otras características del framework se apoyan en los delegados, así que vamos a hablar un poco sobre ellos.

    Pero, ¿Qué es un delegado?

    La respuesta corta es “un delegado es una referencia a un método”, pero no confundir, no es una referencia como un punto sino que en realidad un delegado nos permite definir una firma para poder luego pasar un método. Vemos un ejemplo.

    var testDelegates = new TestDelegates();
    testDelegates.DoSomething();
    

    En este simple código tenemos un objeto testDelegates que tiene un método DoSomething y que hace algo, claro, pero imaginemos que ese método, por ejemplo, hace una llamada a un servicio que ocurre de manera asíncrona y termina después de un tiempo indeterminado. La llamada se realiza pero queremos que de alguna manera este método nos informe que la ejecución ha finalizado. Bien, una buena forma de hacerlo sería indicarle de alguna manera este método DoSomething luego de finalizar su ejecución invoque a otro método y nosotros hagamos algo en consecuencia. Más o menos cómo cuando hacemos una llamada AJAX desde Javascript donde tenemos una forma de ser informados de la finalización de la ejecución de la llamada.

    Delegado simple

    Para hacer esto necesitamos indicar al método DoSomething cuál es el método que queremos que sea invoca al final su ejecución (exitosa o no, en este caso eso no nos preocupa) .NET nos permite hacer esto pero nos obliga de definir siempre la firma de este método, es decir, tenemos de alguna manera que decir en DoSomething que podemos informar el final de la ejecución a un método pero son ciertas firma (que reciba ciertos parámetros y devuelva cierto tipo de datos) acá es donde entran los delegados en acción, justamente nos permiten definir la firma del método.

    public class TestDelegates
    {
        // declaración del delegador, sin valor de retorno (void) y sin parámetros
        public delegate void DelegadoSimple();
        public void DoSomething(DelegadoSimple delegado)
        {
            // does something
            // llamar al la función
            delegado();
        }
    }
    

    Los primero que hacemos es definir en delegado de modo de ser visible, como dijimos antes esta será la firma que deberá tener el método que pasaremos como parámetro, entonces lo ponemos en la clase que lo utilizará con visibilidad pública.

    Luego ponemos como parámetro el delegado que funciona como un tipo y nos permite tener la referencia a la función.

    Luego dentro del código podemos invocar la función, en este caso solo agregando los paréntesis porque no recibe parámetros.

    private static void Main(string[] args)
    {
        var testDelegates = new TestDelegates();
        testDelegates.DoSomething(FuncionSimple);
    }
    
    private static void FuncionSimple()
    {
        // hacer algo
    }
    

    En cuando a la llamada a DoSomething simplemente agregamos el parámetro que es una función igual que el delegado, que no retorna valor (es void) y no recibe parámetros, notemos que no tiene parámetros porque no la invocamos solo pasamos una referencia.

    Métodos anónimos

    Qué pasaría si no quisiéramos crear una función con nombre y todo para que sea invocada. En este caso podemos crearla inline, de este modo.

    private static void Main(string[] args)
    {
        var testDelegates = new TestDelegates();
        // en este caso pasamos un método anónimo
        testDelegates.DoSomething(()=>
        {
            //hacer algo
        });
    }
    
    

    Lo que hicimos es declarar nuestro método en el mismo sitio que invocamos a DoSomething, vamos a analizar la sintaxis.

    testDelegates.DoSomething(()=>
    {
        //hacer algo
    });
    

    Primero tenemos que tenemos en cuenta que es necesario cumplir con el delegado, con lo cual el método anónimo no tiene valor de retorno y no recibe parámetros. entonces lo primero que vemos es que hay dos paréntesis vacíos, justamente porque no recibe parámetros y luego la “flecha” que indica que a continuación viene el cuerpo el método ()=>

    Después simplemente tenemos las llaves que limitan el cuerpo del método anónimo y si tuviéramos código estaría por ahí, como el método no retorna valor no hay un return.

    Es importante destacar que al ser anónimo (el método no tiene nombre) no puede ser invocado desde otra parte.

    Imaginemos que lo que queremos hacer es escribir en la consola, el código quedaría así:

    testDelegates.DoSomething(() =>
    {
        Console.WriteLine("Ejecución finalizada");
    });
    

    Y qué ganamos creando un método anónimos? bien, lo principal es que es posible que no usemos ese método para nada más que para ser invocado por DoSomething, por otro lado tenemos visibilidad de las variables dentro del método donde declaramos el método anónimo, por ejemplo podemos hacer esto:

    var testDelegates = new TestDelegates();
    var variablePrivada = "valor original";
                // en este caso pasamos un método anónimo
    testDelegates.DoSomething(() =>
    {
        Console.WriteLine("Ejecución finalizada");
        Console.WriteLine($"El valor de la variable 'variablePrivada' es {variablePrivada}");
        variablePrivada = "valor nuevo";
        Console.WriteLine($"El nuevo valor de la variable 'variablePrivada' es {variablePrivada}");
    });
    

    No solamente podemos leer una variable privada del método donde declaramos el método anónimo sino que también podemos modificarla, algo similar a un closer.

    Métodos anónimos con parámetros

    public delegate void DelegadoSimple(int milisegundos);
    public void DoSomething(DelegadoSimple delegado)
    {
        // does something
        // llamar al la función
        delegado(20);
    }
    
    

    Primero cambiamos la definición del método anónimo para que reciba parámetros, en este caso el tiempo que demoró la ejecución de nuestro método DoSomething, esta vez cuando lo invocamos le pasamos el valor.

    private static void Main(string[] args)
    {
        var testDelegates = new TestDelegates();
        testDelegates.DoSomething(FuncionSimple);
    }
    
    private static void FuncionSimple(int milisegundos)
    {
        Console.WriteLine("Ejecución finalizada");
        Console.WriteLine($"La ejecución tomó {milisegundos} milisegundos");
    }
    

    Modificamos el método para que reciba el parámetro y al ser ejecutada lo mostramos en pantalla, si lo hiciéramos anónimos sería así:

    private static void Main(string[] args)
    {
        var testDelegates = new TestDelegates();
        testDelegates.DoSomething((int milisegundos) =>
        {
            Console.WriteLine("Ejecución finalizada");
            Console.WriteLine($"La ejecución tomó {milisegundos} milisegundos");
        });
    }
    

    en este caso agregamos en parámetro dentro de los paréntesis.

    ¿Y qué pasa si tenemos que devolver valores en nuestros delegados?

    Por supuesto que podemos tener un delegado que retorne un valor de cualquier tipo, un ejemplo sería el siguiente:

    public delegate string DelegadoSimple(int milisegundos);
    public void DoSomething(DelegadoSimple delegado)
    {
        // does something
        // llamar al la función
        Console.Write(delegado(20));
    }
    
    

    Ahora cambiamos la declaración del delegado para que retorne un string y al invocar el delegado lo imprimimos, en código de la otra parte sería así:

    testDelegates.DoSomething((int milisegundos) =>
    {
        Console.WriteLine("Ejecución finalizada");
        return $"La ejecución tomó {milisegundos} milisegundos";
    });
    

    Muy sencillo.

    Delegados predefinidos.

    Existen algunos delegados predefinidos que nos permiten evitar declarar todo el tiempo los propios, lo más comunes son:

    • Predicate
    • Action
    • Func

    El predicate se usa en muchos métodos de filtrado de colecciones, por ejemplo en la lista genérica:

    public T Find(Predicate match)
    

    Y si vemos la definición de predicate podemos comprender bien de qué se trata

    public delegate bool Predicate(T obj)
    

    Un predicate sirve para proveer al método Find de una filtro personalizado, es decir, definir nosotros cómo queremos filtrar los datos, vamos a ver el código interno del método Find

    public T Find(Predicate match) {
    	if( match == null) {
    		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    	}
    	Contract.EndContractBlock();
    
    	for(int i = 0 ; i < _size; i++) {
    		if(match(_items[i])) {
    			return _items[i];
    		}
    	}
    	return default(T);
    }
    
    

    El predicate es la variable match y vemos que lo que hace este método Find es recorrer todos los elementos de la lista y si match es true, retorna el elemento que coincide. Esto nos demuestra la utilidad de los delegados, podemos nosotros definir el criterio de búsqueda.

    Existen muchos otros métodos en la clase List<T> que aceptan un predicate y en otras clases también.

    Action y Func

    Action y Func son más simple, si miramos su definición nos damos cuenta para qué sirven

    public delegate void Action();
    public delegate void Action(T obj);
    
    public delegate TResult Func();
    public delegate TResult Func(T arg);
    

    En el caso de Action es una delegado pre-definido que nos permite definir “acciones” es decir, métodos que no devuelven un valor, el ejemplo del principio lo podríamos haber hecho con Action.

    public void DoSomething(Action delegado)
    {
        delegado();
    }
    

    Lo mismo que si quisiéramos pasar uno o varios parámetros

    public void DoSomething(Action delegado)
    {
        delegado(20);
    }
    

    Existen muchas sobre cargas genéricas para poder pasar hasta 16 parámetros.

    En el caso de Func es como un Action pero que devuelve un valor, entonces nuestro código final usando un Func quedaría así:

    public void DoSomething(Func delegado)
    {
        Console.WriteLine(delegado(20));
    }
    

    Simplemente Action y Func no son más que delegados pre-definidos que nos ahorran crear los nuestros en la mayor parte de los casos.

    Es importante comprender bien los delegados porque están por todas partes, sobre todo en Linq, todo lo que sea asíncrono como HttpClient o lo relacionado con Threads.

    Nos leemos.

    Viewing all 2707 articles
    Browse latest View live