Al programar orientado a eventos puede haber una condición de carrera entre eventos. Esto sucede cuando el orden de procesamiento de eventos importa. Una solución es reducir la generación de eventos por los manejadores de eventos.

Por ejemplo, al programar una interfaz gráfica con ventanas, se genera un evento como «on windows moved» cuando se mueve una ventana. Imaginemos que esto pasa a una ventana «principal» cuyo manejador de evento debe mover de la misma forma sus «subventanas», por ejemplo los menús o barras de herramientas. Este cambio de posición añade nuevos eventos al final de la cola para repintar la pantalla. Antes de procesarlos, el usuario puede mover una de estas subventanas a otra posición, que será entonces alterada cuando se procesan los eventos generados por la ventana principal.

Este problema no está limitado a interfaces gráficas, sino en cualquier sistema que permite generar el mismo tipo de evento a dos actores diferentes; por ejemplos al usuario y al programa mismo. La programación orientada a eventos permite, en teoría, de poder generar tantos eventos como uno quiere. Pero uno puede perder el control por las condiciones de carrera cuando se permiten generar nuevo eventos por los manejadores de eventos, ya que estos pueden desencadenar una avalancha de eventos.

Para evitar interferencias indeseadas entre eventos se pueden aplicar dos trucos

  1. Cambiar datos dependientes suprimiendo la generación de eventos.
  2. Desechar eventos mientras queden eventos del mismo tipo en la cola

Los dos trucos aprovechan que sólo hay una cola de eventos y que los eventos se tratan de forma secuencial.

Suprimir la generación de eventos

Si quedamos en el ejemplo inicial, entonces el manejador del evento «on move» de la ventana principal cambiaría las posiciones de las subventanas por la misma distancia. La forma más simple es cambiar las propiedades «x» e «y» de estas ventanas. No obstante, justamente esto provoca un evento «on move» en cada subventana que se mete al final de la cola de eventos. Entre el principio y final de esta cola ya puede haber otro evento «on move» de la misma ventana generado por el usuario.

Esto no queremos. Por esto debemos buscar la manera de cambiar la posición de la subventana sin generar estos eventos. Dependiendo de la biblioteca que se usa, puede haber una función en lugar de una propiedad que permite cambiar los valores de la posición de una ventana sin generar un evento. Después tenemos que llamar a una función de tipo actualizar, es decir «aplicar los valores actuales a la ventana», para que nuestros cambios tengan efecto. Como todo esto sucede dentro del mismo manejador de evento de la ventana principal, no se aplica ningún otro evento «on move» mientras tanto. Con esto hemos evitado que un desplazamiento posterior interfiere.

¡Nótese que este sistema sobresalta el diseño orientado a eventos! Esto puede indicar un mal diseño. Además, el programado debe llamar «manualmente» a los manejadores internos del framework utilizado, porque el sistema ya no se da cuenta de los cambios de otro modo.

Por cierto, un evento de tipo «aplicar los valores actuales» nunca es peligroso ya que aplica los más actual, no importa cuántos eventos de cambio se ejecutaron o quedan por ejecutar. Es menos aconsejable enviar eventos con cambios relativos, porque el resultado final puede depender del orden de procesamiento de los eventos.

Desechar eventos

Desechar eventos sirve cuando vienen más eventos del mismo tipo que podemos procesar. Imaginemos un protector de pantalla que muestra mil moscas. Cada vez, cuando el simulador cambia alguna propiedad de una mosca, se genera un evento de repintar la pantalla. Imaginemos que esto no demanda demasiado durante el curso normal del programa, pero al inicializar o reconfigurar el color de una gran parte de las moscas hay mil eventos a la vez – y para cada una repintamos la pantalla cuando realmente sólo necesitamos pintarla una vez para todas.

Se podría introducir un modo especial en que las moscas no generan eventos de cambio como descrito en el apartado anterior. No obstante, esto destruye la arquitectura, en que el modelo de mosca no tiene por qué saber de su representación. Como el programa es generador y consumidor de los eventos a la vez, también tenemos control sobre lo que hay en la cola de eventos.

Cada evento tiene un número de serie. Para cada evento enviado, el programa guarda el último número de serie en una memoria central. El manejador de evento puede comparar el número de serie del evento recibido con el número del último enviado. Si coinciden sabe, que no queden más eventos de este tipo en la cola y aplica los valores actuales, es decir, repinta la pantalla. En el caso contrario echa el evento sin hacer nada, porque sabe que pronto llegará otra actualización. De este forma se repinta la pantalla sólo una vez en lugar de muchas.

A veces conviene no esperar realmente al último evento, para que el usuario vea una reacción del programa. Por ejemplo, no se desecha cada n-ésimo evento o el primer evento tras un cierto tiempo.

Conclusión

El manejo de eventos que genera eventos a su vez puede causar las condiciones de carrera típicas para programas de multi-hilo, aunque sólo haya una cola de eventos que se procesa de forma secuencial. La consecuencia pueden ser errores esporádicos y difíciles de reproducir.

Aquí he presentado dos trucos de como tener más control de lo que entra en la cola de eventos. La primera idea trata de reducir los eventos generados, la segundo idea es desechar eventos en los manejadores de evento.

No obstante, ten en cuenta que estos trucos destruyen la arquitectura orientada a eventos. Por eso, antes de aplicarlos, considera si son trucos y no chapuzas.

Lectura adicional

Chiste de informáticos: viene el recién titulado de la universidad y pregunta por la documentación.

Si no has entendido el chiste, entonces ¡enhorabuena! Parece que nunca tuviste que entender un código kilométrico sin comentarios ni explicaciones ni documentación. Pues, aquí quiero proponer algunos trucos antes de ponerte a llorar.

Un escenario típico de analizar código puede empezar en una línea de código – si las trazas del programa eran tan amables de indicarla. En esta línea de código aparecen variables, que se definen con un valor de retorno de unas funciones que a su vez llaman más funciones y estas aún más. Sólo averiguar qué valor obtiene una variable y por qué puede llevarte a estudiar 20 subfunciones de cien líneas cada una. Al fin de tu estudio sabes que la variable tiene el valor 3.0, 5 o «mpf», pero todavía no sabes, qué esto significa y si es correcto. ¿Son tres litros de cerveza, cinco mesas libres o el comentario sobre el resultado del partido de fútbol? Lógicamente, esto se podría evitar documentando bien el código, pero en este artículo asumimos que ya no estamos en la universidad.

Entonces, ¿dónde vas a empezar a entender todo este código sin significado? Pues, básicamente escribes comentarios de documentación como si fuera un wiki: siempre cuando sabes algo, lo escribes en el sitio más adecuado. Cuando entiendes más, refinas los comentarios o los mueves a un sitio mejor. Así sigues hasta que hayas entendido el código lo suficiente para resolver tu problema concreto. Por supuesto, puedes seguir hasta tenerlo todo documentado, pero hasta ahora no he tenido ningún jefe que te dejaría el tiempo. En cambio, nadie se queja de comentarios incompletos. Más bien invitan a corregirlos. Esto es justamente el poder de un wiki: uno empieza, otro lo mejora. Pero sin el uno, el otro no haría nada. Así, ¡atrévete! ¡Pon comentarios de documentación aunque no estés de todo seguro!

Donde empiezas depende un poco de la parte que ya entiendes, pero típicamente son líneas simples o bloques pequeños. A continuación vamos a dar ejemplos.

El significado de una variable es más que saber su valor, sino también su tipo y su semántica. A veces una variables contiene un valor inequívoco. Por ejemplo t = time() en en el lenguaje C asigna a t el segundo actual desde el inicio de 1970 en segundos. Es decir, tú añadirías este comentario.

// Tiempo de sistema en segundos desde el 1 de enero de 1970
t = time();

Puede ser que el tiempo absoluto es siempre el tiempo «desde época» para ti. Entonces puedes omitirlo. Pero debe quedar claro para ti (y los demás) e – importante – debes escribir lo que sabes. Recuerda que queremos documentar el código como un wiki. Y esto empezamos con algo fácil. Aunque la línea por si misma será obvia, cada comentario te indica donde ya has entendido algo. Y nadie te prohibe borrarlo si has elaborado un comentario mejor.

Por cierto, corregir los niveles de indentación o los nombres de los identificadores también es documentar el código. En el ejemplo anterior, podrías escribir la documentación en el nombre de la variable y omitir el comentario.

tiempo_de_sistema_en_segundos_desde_1970 = time();

Esto resulta en una refactoración que a veces ni es fácil ni está permitida por modificar el código ejecutable. Pero ¡atrévete si puedes!

Conociendo el significado de una variable nos ayuda a entender más variables. Si t es el tiempo absoluto, entonces la variable x debe ser una duración en la expresión t + x. Quizá todavía no sabemos la duración de qué, pero ya podemos documentar la variable x con «duración en segundos». Y esto nos permite entender al menos parcialmente expresiones con x.

Si entendemos que el valor de una constante enumerativa signífica «verde», entonces podemos asumir que las demás constantes del mismo tipo todas serán colores – más unas con el significado de «ningún color» o «todos los colores». Sabiendo esto, ponemos comentarios a las definiciones de estas constantes y el tipo enumerativo. Una variable a que asignamos una de estas constantes tendrá lógicamente el significado de color también. Quizá no sabemos el color de qué, pero ya sabemos mucho más que nada.

Por cierto, si ya sabes que debes revisar un comentario por no tenerlo completo, entonces márcalo con una cadena de texto única. Probablemente tu nombre ya vale. Identificadores en el código no suelen llamarse María o José.

Cuando más identificadores simples entendemos, más probablemente entenderemos expresiones. Una expresión general como a < b se convierte, sabiendo que a y b son tiempos absolutos en

// Si a es antes que b
if (a &lt; b)

Conseguir reemplazar una expresión (matemática) por una frase en lengua natural ayuda treméndamente a entenderla. Recuerda que anteriormente tenías que haber entendido los significados de a y b – que se pueden haber inicializadas en funciones muy distintas. Por esto es importante documentar también las variables simples: son los componentes que debes haber entendido antes de poder entender sus agrupaciones.

Como con las expresiones ayuda resumir bloques de código en una sola frase. ¿Qué entiendes viendo este código?

p.db.set(a);
p.update();
p.notify();

Ahora imagina que alguien le habría puesto el título «Repinta el dashboard con el color a«. Para llegar a esto debes haber entendido el significa de p, db y a – y al contrario de la intuición, db no era database. Una vez entendido puedes reunir un bloque completo en una sóla frase. Con varios sub-bloques bien comentados puedes comprender el significado de un super-bloque complejo. Finalmente entenderás el significado de una función entera.

Entender una función aumenta drásticamente tu comprensión del código porque ahora ya no necesitas consultar la implementación de la función y todas sus subfunciones. Basta con leerse la documentación de la función con sus parámetros, su valor de retorno y sus excepciones. Esta documentación ya te permite documentar al menos parcialmente funciones que llaman a la función documentada, por ejemplo, si simplemente pasan el valor de retorno o no captan excepciones ya que estas tendrán probablemente el mismo significado para el usuario de la función llamante.

Finalmente con un par de funciones entendidas puedes documentar la clase que contiene todas estas funciones. Esto, a su vez, te ayuda a entender el significado de instancias de esta clase. Con lo que acabamos al inicio: documentar el significado de una variable simple.

Durante todo este transcurso no olvides tu meta: el error que buscas o la funcionalidad concreta que quieres entender. Tus comentarios serán como un wiki: incompletos y con cambios frecuentes. Lógicamente pararse ahí no es obligatorio. Puede ser la meta de escribir un documento de diseño del código a alto nivel. Esto requiere documentar muchas clases, tipos y demás. No serías el primero en ingenería inversa del propio software por no tener un código bien documentado. Pero puedes ser el primero en tu empresa en usar tus nuevos conocimientos para hacer la documentación.

Recuerda: el primero en agradecer estos comentarios de documentación, aunque sean incompletos, serás probablemente . Porque ya te imaginarás a quién el jefe asignará la tarea de analizar el próximo error en el mismo código.

Nota también, que bloques y funciones bien descritas son la base para refactoraciones éxitosas. Puedes sustituir un bloque de varias líneas con una función cuyo nombre lleva el título que pusiste al bloque. Esto hace el código más legible y reduce el tamaño de una función. Además, te darás cuenta cuando puedes sustir varios bloques por la misma función.

Lectura adicional

Encriptación y estanografía

Encriptación es codificar datos de manera que un atacante no los pueda entender. Estanografía es esconder datos de manera que el atacante ni siquiera se dé cuenta de que los haya. En el mundo de la informática la estanografía se refiere a menudo a esconder información dentro de otra, por ejemplo esconder un texto modificando ligeramente una imagen, una grabación de audio u otro texto. En Internet se encuentran bastante programas que ofrecen este servicio. En un sentido amplio, la estanografía es ocultar un mensaje de cualquier manera, sea en el tacón o llevando una chuleta al examen.

Estanografía y encriptación son conceptos complementarios. Esconder un mensaje lo hace invisible pero no ilegible una vez descubierta. Lo más seguro es encriptar y esconder.

Trucos simples

En este artículo quiero proponer algunos trucos de estanografía simples, que no requieren un programa especial. Por lo tanto, tampoco son especialmente sofisticados. No obstante, el valor de la estanografía viene de esconder datos de una forma que el atacante no espera. Enviar una carta de papel no es especialmente sofisticado pero desafía los mejores algoritmos de análisis de datos de Internet. Pues, la ventaja de los trucos simples es: no necesitar programas especiales.

Esconder datos en basura

El truco más obvio es hacer un fichero “escondido” con el sistema operativo. En Linux, esto son fichero cuyos nombres empiezan con punto; en Microsoft Windows existe incluso un atributo “escondido”. No obstante, falta poco sabiduría para mostrar también los ficheros escondidos.

Cualquier persona desordenada tiene el talento de confundir a los demás en no dejarles claro, qué fichero es realmente el importante. Ficheros con nombres no adecuados y varias versiones de ficheros que no dejan claro, cuál es la buena ya forman parte de la ofuscación involuntaria dentro de cualquier proyecto desde que es posible dejar más que un fichero en un disquete. Al contrario de las personas desordenadas, que tampoco saben cuál era la versión buena, la gente organizada puede aprovechar este viejo truco para confundir al enemigo.

Tener varias versiones de la hoja de cálculo sobre el soborno de políticos con pequeñas variaciones en cantidades y nombres pone más difícil al fiscal de probar los hechos – quizá con la excepción de los países donde todos los políticos reciben sobornos. Este truco funciona sobre todo cuando las variaciones son plausibles y, además, detalles importantes. (¿Entrega a las 7 o a las 8? ¿A José María o María José?)

Cambiando el reloj del ordenador por un momento modifica la fecha de la última modificación, que es ideal para no dejar claro cuál fue la última versión.

Cambiando nombres de fichero ayuda también. El manual de sabotaje parece menos sospechoso con el nombre “cumpleaños 2018”. Lo mismo vale para sufijos. El fichero “virus.exe” parece más interesante que “corona.xt5”. Mejor aún si este fichero está dentro de una carpeta con muchos ficheros, quizá del mismo tipo: Un fichero tipo DLL más en la carpeta C:\Windows\System32 no llama atención.

Utilizar programas estándar

Al entrar en territorio chino, las autoridades pueden instalarte un programa que busca por determinados ficheros en tu teléfono móvil utilizando los hashes de los ficheros (como MD5 o SHA256). Esto puedes desafiar por mínimos cambios en tus ficheros: los más fáciles son comprimirlos en un archivo o simplemente añadir un carácter. Muchos formatos binarios tampoco se quejan, si añades algo más. En Linux puedes usar programas como split o cat para separar y recomponer contenido.

También se puede esconder datos con el control de versiones git.

Conclusión

Las ideas propuestas ayudan esconder datos de una forma simple. Son aún más eficaces en combinación. No obstante, son ayudas rápidas y no deberían sustituir un sistema de protección de datos potente. Datos deberían estar siempre encriptados si posible.

El programa VeraCrypt permite crear contenedores de ficheros encriptados escondidos dentro de otro contenedor encriptado. Por encriptar y esconder, es seguramente una de las soluciones más potentes – y en combinación con las propuestas simple presentadas aquí aún más.

Lectura adicional

 

Funciones variádicas permiten un número arbitrario de parámetros de tipo arbitrario. La más conocida en C y C++ (y otros lenguajes) es printf. Aquí tratamos de cómo declarar e implementar una función variádica. También comparamos estas funciones con otros conceptos relacionados como sobrecarga de una función y patrones variádicos (variadic templates).

Cómo declarar una función variádica

Cómo ejemplo de declaración usamos la signatura de printf.

int printf(const char* formato, ...);

La signatura de una función variádica tiene como parámetro una elipsis .... Esta elipsis quiere decir: y unos cuántos parámetros más de tipo desconocido. A continuación llamaremos los valores que el elipsis representa «parámetros anónimos». Son parámetros que existen, pero para cuales no existe un nombre con que los podemos referenciar.

Una elipsis siempre tiene que ser el último parámetro con excepción de parámetros con valor de defecto.

int mi_función_variádica_1(int a, ..., int b);  // Error de compilación
int mi_función_variádica_2(int a, ..., int b = 0);  // Ok

Los parámetros con nombre y sin valor por defecto son parámetros obligatorios. Obligan al usuario de especificar al menos uno, dos o más valores. Vamos a ver en la implementación que un parámetro obligatorio como mínimo es útil.

Cómo implementar una función variádica

Cuando se llama a una función variádica, el compilador guarda todos los parámetros en el stack. El problema es como accedemos a estos parámetros ya que no tienen nombre. La solución viene por unas macros cuya implementación depende tanto de cada compilador que recomiendo no intentar entenderla a menos que sea este tu destino.

Explicamos las macros en un ejemplo de uso. Nota que las macros vienen sin el namespace. Macros nunca tienen namespace.

// Incluye la definición de las macros
#include 

// Una función variádica
void trata_valores(const std::list lista_de_tipos, ...)
{
    // Una referencia para aceder a los parámetros anónimos
    va_list parámetros_desconocidos;
    
    // Asigna el puntero de lectura al primer parámetro anónimo.
    va_start(parámetros_desconocidos, lista_de_tipos);
    // El segundo parámetro es el último parámetro con nombre.
    // Es una de las dos buenas razones de tener al menos un
    // parámetro con nombre, porque sin él no se puede inicializar
    // la referencia de parámetros anónimos.
    
    while (/* ver explicación abajo */)
    {
        // Obtiene un parámetro. ¿Cómo sabemos su tipo?
        un_tipo i = va_arg(parámetros_desconocidos, un_tipo);
    }
    
    // Limpia cualquier recurso reservado por va_start y va_arg
    va_end(parámetros_desconocidos);
}

En corto:

  1. Define un nombre con va_list.
  2. Inicializa el acceso a los parámetros anónimos con va_start.
  3. Obtén un nuevo parámetro con cada llamada a va_arg.
  4. Recoge la basura con va_end.

La dificultad es ¿cómo saber los tipos de los parámetros? Pues, no se puede por las macros de acceso. La signatura de una función variádica no asegura ningún tipo específico para los parámetros anónimos. De hecho, una implementación puede obtener diez valores de tipo int y tres de tipo float con éxito aunque el usuario no haya llamado la función variádica con estos parámetros. La macro va_arg simplemente devuelve un valor del tipo especificado en el segundo parámetro y mueve el puntero de lectura tantos bytes como el tamaño de este tipo. Es genial para leer memoria basura, pero ¿cómo podemos obtener los parámetros reales?

Pues, ahí viene la segunda razón para el primer parámetro obligatorio: el primer parámetro contiene una información sobre los tipos de los parámetros anónimos. Por esto lo hemos definido como una lista de identificadores de tipos en el ejemplo arriba. Podemos imaginarnos que la lista contiene tantos elementos como parámetros anónimos y que cada valor de id_de_tipo corresponde a un tipo posible.

En la función printf, esta lista de tipos de parámetros es el parámetro de formato. El bucle while dentro de la implementación de printf imprime cada letra a menos que viene el símbolo %. Entonces sabe que viene la especificación de un tipo que entonces puede usar para obtener uno de los parámetros anónimos. Obviamente debe haber tantos símbolos % en el texto de formato como hay parámetros anónimos – y los tipos especificados deben corresponder a los reales. Sino printf imprime una basura que cualquier programador seguramente ya ha visto alguna vez.

Limitaciones de funciones variádicas

Ya hemos visto que funciones variádicas no aseguran el tipo de los parámetros anónimos y que esto puede llegar a no obtener los valores de los parámetros correctamente. Esto, desde luego, no es aceptable para un buen estilo de programación y, por eso, mucho se ha ideado en C++ para evitar tener que usar funciones variádicas. No obstante la función printf es junto con las macros de C una de las chapuzes más populares que se siguen usando porque simplemente permiten hacer algo complicado de una forma simple.

Otra limitación es el número de tipos que una función variádica puede manejar. Cada implementación contiene un bucle while, y dentro de esto habrá un switch que llama para cada caso a va_arg con un tipo diferente. Lógicamente no se puede manejar un tipo si no está programado en este switch. Esta es la razón porque printf sólo acepta tipos simples, nativos del lenguaje. Los autores de printf simplemente no podían saber qué estructuras a imprimir inventarás.

Alternativas a funciones variádicas

Con tanto peligro de leer mal los parámetros, C++ tenía que ofrecer nuevas maneras de pasar un número de parámetros arbitrario. No obstante, todos tienen limitaciones.

Sobrecarga y valores por defecto

Si el número de posibles permutaciones de parámetros no es demasiado alto, se pueden definir sobrecargas de una función para cada una. Esto se hace, por ejemplo, en el

operator<<(std::ostream& out, const un_tipo& valor)

Hay una definición para cada tipo nativo del compilador y cada clase de biblioteca estándar cuyo contenido tiene sentido de imprimir como para std::string.

Aunque estés depuesto a escribir mil sobrecargas de una función, no llegarás a sustituir printf con ellas. No obstante, puedes usar algunos trucos.

  • Si lo que varía es más el número de los parámetros y no su tipo, entonces te basta una función con muchos parámetros del mismo tipo y todos estos parámetros tienen valores por defecto. El valor por defecto entonces dirá algo como «parámetro no especificado». Sin embargo, esta versión puede ser más lenta que una función variádica, porque el programa guarda todos los parámetros en el stack – también aquellos que no se usan.
  • El truco del operator<< consiste en sólo permitir un parámetro de tipo variado y luego encadenar un número arbitrario de operadores. De esta forma sólo hay que definir tantas funciones como hay tipos a manejar.
  • Si lo que varía son los tipos y no el número de parámetros, entonces se pueden usar templates de funciones.
Templates

Un template permite definir una función con tipos variados. Esto permite en principio crear una implementación que vale para cualquier tipo, porque se puede usar el nombre del tipo variable en la macro va_arg.

Un template convencional tiene un número de parámetros fijo. Desde el estándar C++11, templates pueden también tener un número variado de tipos. Esto se llama variadic template o parameter pack. Cuando se tiene un número arbitrario de tipos, entonces la función puede aceptar también un número arbitrario de parámetros. De esta manera, sí, se puede escribir un sustituto de printf que asegura los tipos de los parámetros. (Para facilitar el manejo de una lista de parámetros de tipo arbitrario, la biblioteca estándar provee el tipo std::tuple.)

En una función de tipos y parámetros arbitrarios habrá, igual como en una implementación de una función variádica convencional, un bucle, que hace algo para cada parámetro: típicamente llama a una función con sólo un parámetro y luego vuelve a llamar a si misma con los parámetros restantes. Es decir, funciones de variadic template suelen ser a menudo recursivos. Esto les hace lento, ya que tienen copiar los parámetros varias veces. Además, se genera una instancia de la función para cada conjunto de tipos. Por eso, una función con templates variádicos provee la misma forma de uso, pero no ejecutará tan rápida como una función variádica sin templates.

Conclusión

Funciones variádicas como printf permiten implementar funciones con un número de parámetros arbitrario de forma eficaz. Desgraciadamente dependen de que el usuario provee una lista con los tipos de los parámetros suministrados. Al hacerlo mal, una función variádica puede leer o incluso escribir en la memoria de forma no controlada.

C++ provee los variadic templates como alternativa que asegura los tipos de los parámetros, pero sus implementaciones suelen traer desventajas a la hora de ejecución. Además, su implementación tampoco suele ser más fácil. Por eso, las funciones variádicas tienen su derecho de existencia, aunque un buen programador siempre pensará en alternativas antes de implementarlas.

Lectura adicional

Ya desde tiempo, los «métodos ágiles» de desarrollo de software como Scrum, eXtreme Programming o Scaled Agile son de moda; tanto que incluso tu empresa ya estará disfrutando de ellas. O de algo parecido al menos de nombre. Porque los métodos ágiles se parecen a menudo al comunismo. Son ideas maravillosas mientras no tienes que vivir con ellas.

Lo que también tienen en común con el comunismo, es que ciertos elementos, sí, son útiles. Igual como un capitalismo social avanza el bienestar general (capitalismo = hay medicamentos, socialismo = los enfermos pueden pagarlo), elementos de los métodos ágiles pueden mejorar mucho el desarrollo de software. La pregunta es: ¿qué elementos?

Para sacarles el mejor rendimiento, conviene entender el contexto cultural de donde los métodos ágiles salieron. Como muchas ideas religiosas recientes, los métodos ágiles también han salido de los Estados Unidos de América. Y como los norte-americanos tienen un don de marketing, les gusta dar nombre llamativos como «Scrum», «eXtreme Programming» o «Chrystal Clear» que, para el tu bien mental, mejor no traduzques a tu lengua materna. El libro gordo de esta religión se llama «Agile Manifesto» y expresa, entre otras cosas, y en palabras más positivas, los siguiüente puntos en negrita.

  1. La profesionalización del concepto «el producto madura en el cliente». Funciona muy bien, mientras el cliente piensa que las frecuente actualizaciones corresponden a «mantener el producto» y no a «no hacerlo bien desdel principio». Madurar el producto en el cliente está aceptable para el software que tienes en tu teléfono, pero no suele ser buena idea para aparatos medicinales.
  2. Nadie tiene dominios. Todos programan de todo. En todas las empresas (menos en una en que era el único programador) se ha tenido la idea de un proxy que sería capaz de hacer tu trabajo si no estás por enfermedad, vacaciones o despido. Y en ninguna empresa ha durado mucho hasta dejarlo. Los programadores no son intercambiables. Todos no saben igual de todo. En el momento que escribes una pieza de código, eres automáticamente el máximo experto para ello. Es inevitable. Así, o bien aceptas que los demás programadores constantemente deben aprender código nuevo que les hace bastante lento, o dejas mentirte y aceptas que cada uno tiene un dominio. No quiero decir que vale la pena mandar a todos a hacer de todo. Puede funcionar si se hace con consecuencia desde el principio. Pero no pondrás un novato a estudiar una nueva parte de código por varios días, si mañana tienes la release final.
  3. No hagas documentación que nadie se lea. Porque cualquier documentación tiene el peligro de hacerse obsoleta con los cambios en el códio. Por eso es mejor no tenerla. En cambio el códio es auto-explicativo y el resto se pregunta. No olvides que comunicación es importante en los métodos ágiles. Ya sabes, todos deben saber de todo, y mejor que no haya nadie a quién ocurre proponer un documento que explica el programa al menos a alto nivel. Saber leer y dejar trabar a los demás es para introvertidos. Los novatos tienen que integrarse y esto es más fácil si deben preguntar de todo. Mejor aún si deben pasarse una semana entera (o para siempre) con una pareja veterana en la mesa. Así y comunicación.
  4. Usa el sentido común. Recordar que comunicación es importante parece poco novedoso si vienes de un país con un clima en que a la gente gusta encontrarse en la calle, pero esto quizá no es tan obvio para otros países donde tienes el permiso de sacar un arma si alguien pisa tu territorio. Tampoco falta un título en bla-bla avanzado para descubrir, que es más eficiente si un programador termina un trabajo antes de empezar uno nuevo en lugar de interumpirle constantemente. Si quieres promover métodos ágiles en tu empresa, considera que, fuera de Estados Unidos, la gente viene equipado con sentido común desde fábrica y probablemente ya lo sabe.

De hecho, sentido común ayuda considerablemente en sacar el máximo provecho de los métodos ágiles. Desgraciadamente, estos «consultores» de métodos ágiles demasiadas veces no son lo suficientemente ágiles ellos mismos para reconocer, que los prerequisitos de su método son díficiles de conseguir. Programadores no son fáciles de intercambiar, el product owner no tiene tiempo aunque debería y el cliente no tiene ganas de pasar su tiempo en la empresa que paga para quitarle el trabajo.

Un buen ejemplo del carácter religioso de métodos ágiles es la creencia de la mayor eficiencia de la programación en pareja. Una minoría de los programadores se llevará bien, y pasa el día charlando en lugar de trabajando. La mayoría acaba estando tan estresando con tener constantemente otro tipo al lado, que se marchará de la empresa. De hecho, la programación en pareja es una manera eficaz de descubrir los programadores competentes: son aquellos que se han ido tras un año. Pero también puedes verlo positivo: siempre tendrás a un equipo joven. (Si no te dejan introducir la programación en pareja para descubrir a los talentos, obligar a llevar corbata funciona también. Si ya llevas corbata pero no entiendes el chiste, entonces, enhorabuena, ya trabajas para tu empresa perfecta.)

No sigas a un método ágil porque es la moda. Busca los elementos que mejoran tu desarrollo y aplícalos al dominio adecuado.

Como ejemplo de limitar el dominio adecuado, considera una empresa que vende equipamento médico. No puede vender su producto mediamente hecha y esperar mejorarlo cada dos semanas con las opiniones de sus clientes. Un laboratorio médico puede tardar un año entero probando que tu producto funciona como deseado. Te puedes imaginar como se alegran de poder repetirlo cada mes. Pero lo que puedes hacer, es producir versiones tempranas durante el desarrollo del producto para poder probarlo con todos los componentes existentes juntos. En este caso el método ágil no funciona con el cliente externo, pero, sí, con clientes internos.

No obstante, son muchos los elementos de los métodos ágiles que valen la pena tener en consideración.

  1. Empieza con un producto minimal, pero útil y con calidad. Lo «minimal» puede a veces ser todo, pero muchas veces no lo es. Y si lo poco funciona bien, los clientes estarán interesados en la próxima versión.
  2. A nivel de equipo de desarrollo, esto se traduce a generar una versión temprana para que otros equipos puedan integrarla. Puede ser incompleta, pero debería funcionar.
  3. Genera versiones con frecuencia, para que los demás equipos pueden tener la mejor versión posible en cada momento.
  4. Intenta dividir tareas grandes en tareas pequeñas pero completas en sí, para que puedas publicar algo en cada iteración.
  5. Usa ramas de desarrollo para añadir nuevas funcionalidades y siempre manten una versión que funciona. Puede ser tu rescate si un desarrollo no funcionaba.
  6. Sólo añade cambios completos. Completo quiere decir que haya casos de pruebas (que se ejecutan automatizados) y la documentación adecuada. (Actualizar la documentación es parte del cambio.)
  7. Limitarse a lo que es necesario. Lo necesario es a veces no tan fácil de reconocer. En primer lugar no es crear un vector de n elementos especificado para el caso n = 3, si sólo necesitas un vector de tamaño 3. En el mismo primer lugar es no crear documentación porque sí, sino por qué ya sabes denominar alguien, que la quiera leer. (Miembros nuevos del equipo, por ejemplo.) De otro lado hay decisiones en que hay que pensar hasta el final: La arquitectura del programa, los requisitos mínimos, si los programadores serán intercambiales (y por lo tanto se les permite más tiempo para aprender código nuevo), la estructura de crear y testear versiones etc. Una buena estrategia para limitarse a lo necesario es pensar: ¿ya tenemos un escenario en que esto va a importar? ¿Sí? ¡Entonces hazlo! ¿No? ¡Entonces espera con ello! No hagas cosas por si acaso sino porque alguien quiere pagar por ellas.
  8. Fomentar la comunicación. Las pequeñas reuniones diarias y un tablón con las tareas pendientes fomenta que todos ven como va el equipo en completo.
  9. Ten una estructura de comunicarse con el cliente. Los métodos ágiles demandan que los clientes están presentes en el equipo de desarrollo. Esto es muchas veces imposible. Pero debería haber alguien que pueda presentar la vista del cliente – y esto será preferiblemente uno que entiende realmente a clientes aunque no entienda de todo a los programadores.
  10. Dejar a los programadores estimar cuánto tiempo necesitan para una tarea y dejarles decidir, a cuantas tareas se quieren cometer. Tendrás una mejor estimación de tiempo y los planes se basan en las palabras de aquellos que deben cumplirlas. Es decir, en lugar de «esto necesito hasta viernes» preguntar «qué necesitas pra terminarlo hasta viernes».
  11. No cambies planes durante una iteración. Deja suficiente tiempo libre para atender a emergencias típicas.
  12. Intenta terminar el plan de una iteración. Si un programador ya hizo todo su trabajo, entonces quizá puede ayudar a otro en crear casos de pruebas o actualizar la documentación.

En fin, esto no es un pamfleto contra los método ágiles, pero uno contra su aplicación religiosa. Y un recordadorio que todas las culturas tienen puntos fuertes. Hazte conciente de ellas antes de adoptar algo sólo porque aparenta tener éxito en EEUU.

Lectura adicional

En C++ existen varios modificadores de tipos de variables. El más conocido es const. Aquí queremos hablar de un modificador más oscuro que const: volatile.

El modificador volatile quiere decir: Esta variable no sólo modifica tu programa sino alguien más.

La pregunta es: ¿quién más cambia las variables de tu programa? Pues, un componente de hardware, por ejemplo. Cuando escribes un controlador de hardware, entonces usas volatile bastante a menudo.

Los dispositivos de hardware se «mapean» (asignan) a ciertas direcciones de memoria. Por ejemplo, en la dirección 100 no escribes en RAM, pero en un registro de la tarjeta gráfica. Si escribes ahí, digamos, un cinco, entonces la tarjeta gráfica lo entiende como una orden de poner la pantalla en negro. El hardware real es ligermanete más sofisticado, pero esto es el principio: ciertas direcciones de memoria corresponden a registros de dispositivos de hardware.

Lógicamente debes asegurar que tu programa realmente escribe en esta dirección. Y que también realmente lee desde ahí. Porque si una variable corresponde a una dirección donde la tarjeta gráfica devuele un valor de error, entonces no te puedes fiar del último valor que esta variable tenía en tu programa. Debes realmente leerlo desde ahí. Y esto es la tarea del modificador volatile. Dice al compilador que, por favor, no optimice el uso de esta variable.

Y ahora: ¿Por qué tu programa no siempre lee y escribe en la posición de memoria a que la variable corresponde?

Esto tiene que ver con la optimización del código. Considera el siguiente trozo de código.

const int a = 5;
int b = a + 3;
b = 4;

Sin optimización, la constante a corresponde, digamos, a la dirección 8. La constante b a la dirección 12.

  1. Si el compilador no optimiza, entonces traduce la primera línea en un comando de escribir un 5 en la dirección 8.
  2. La segunda línea traduce en leer lo que pone en la dirección 8, sumarle 3 y escribirlo a la dirección 12.
  3. Luego escribe un 4 en la dirección 12.

No obstante, esto se puede optimizar para ganar velocidad. La constante a corresponde a un 5. Así podría modificar la segunda línea en.

int b = 5 + 3;

o directamente en

int b = 8;

Como se escribe el valor 4 en la variable b directamente después, entonces el compilador podría borrar las dos primeras líneas. El resultado es el mismo.

A menos si las variables a y b corresponden a direcciones de dispositivos de hardware. Para ellos, sí, importa que se lee desde la dirección que corresponde a la constante a y se escribe en la dirección de b. El mismo programa con volatile puede tener un significativo bastante distinto.

const volatile int a = 5;
volatile int b = a + 3;
b = 4;
  1. Declara un campo de lectura del despositivo y asígnale el valor por de defecto 5.
  2. Lee el nuevo valor de a, súmale 3 y escríbelo en la dirección de b.
  3. Escribe un nuevo valor en la dirección de b.

Bajo este punto de vista tiene también sentido de escribir dos valores distintos en b sin utilizar el valor en el programa. Lo puedes entender como un comando que ejecuta, por ejemplo, la tarjeta gráfica y no tu programa.

Pero no olvides, todo esto sólo tiene efecto si el compilador optimiza el programa como lo suele hacer para una versión de release.

Por cierto, si tienes la paranoia que cualquier variable se podría cambiar desde fuera, entonces no necesitas declarar todas volatile. Basta compilar tu programa sin optimizaciones – es decir, en modo de depuración.

Lectura adicional

En C++ existen varios modificadores de tipos de variables. El más conocido es const. Aquí queremos hablar de un modificador que de alguna forma es el contrario de const: El modificador mutable.

El modificador mutable quiere decir: Este miembro sigue siendo variable aunque la instancia de su clase sea declarada const. Fuera de una clase, mutable no tiene sentido.

La pregunta es: ¿por qué alguien quiere tener un miembro de clase variable cuando ha declarado el objeto const? Vemos un ejemplo clásico de la programación multi-hilo.

class Lista
{
public:
    // Una función que modifica la instancia
    void añadeElemento(const int nuevo_valor)
    {
        while (mutex)
        {
            // Bloquea el objeto para acceso exclusivo
            mutex = true;
            // Modifica el objeto
            una_lista.push_back(nuevo_valor);  
            // Libera el objeto
            mutex = false;
        }
    }
    
    // Una función que lee la instancia
    std::list<int> devuelveLista() const
    {
        std::list<int> copia;
        while (mutex)
        {
            // Bloquea el objeto para acceso exclusivo
            mutex = true;  
            // Crea una copia temporal
            copia = una_lista;
            // Libera el objeto
            mutex = false;
        }
        // Con la copia el hilo puede leer cuanto quiera
        return copia;  
    }
    
private:
    std::list<int> una_lista;
    mutable bool mutex;
};

La clase Lista contiene un objeto complejo una_lista y un mutex. En un entorno multi-hilo, todos los hilos podrían querer acceder a la lista potencialmente al mismo tiempo. No hay problema mientras todas los hilos quieren leer, pero esto se cambia si uno quiere escribir. Añadir un nuevo elemento requiere cambiar varias variables dentro de la lista. Con un poco de mala suerte (no mucha), un hilo intenta leer de la lista cuando el cambio todavía no está completo y obtiene así datos corruptos. Por esta razón, se bloquea la lista con un mutex. (Una tipo de mutex no muy sofisticado, pero para nuestro ejemplo nos sirve.) Mientras un hilo tiene el mutex (asignando true, los otros se deben esperar en el bucle while.

Si un hilo quiere leer la lista con la función devuelveLista, entonces, conceptualmente, lo puede hacer sobre un objeto constante. Leer no modifica el contenido del objeto y, en consequencia, la función devuelveLista está declarada const. No obstante, el miemro mutex también sería constante en este caso. Y ahora, ¿cómo se puede bloquear la lista contra el acceso de escritura en una función const?

La solución es declarar el mutex mutable. Así el mutex sigue siendo variable mientras el resto de la clase es constante.

Es obvio que sobrescribir el carácter constante de un objeto con mutable invita a crear un mal diseño. Por eso úsalo con consideración. Un miembro mutable suele contener meta-información como un guardian de acceso (como un mutex), o un contador que cuenta cuántas veces se ha leído un dato. Los datos mismos nunca tienen razón de ser mutable.

Lectura adicional

En C++ existen cuatro formas nativas de convertir tipos: static_cast, const_cast, reinterpret_cast y dynamic_cast. Además, se puede convertir un tipo con un constructor de clase o sobrecargar el operador de cast. Finalmente existe el cast tradicional de C. Aquí queremos hablar de las diferencias que tienen y cuando conviene usar uno u otro.

Lista de diferentes cast

Para aclarar un poco más las diferentes formas, empezamos con una enumeración de las diferentes posibilidades.

Formas de conversión de tipos
Nombre Ejemplo Uso típico
Cast tradicional de C (int) float_var Combina static_cast, const_cast y reinterpret_cast
static_cast static_cast(float_var) Convertir tipos nativos o clases con constructor o operador de cast
const_cast const_cast(const_int_var) Eliminar attributos de tipo como const
reinterpret_cast reinterpret_cast( void_pointer) Pretender que un tipo (de puntero) es realmente otro
dynamic_cast dynamic_cast( referencia_a_clase_base) Reinterpretar una clase base como una clase derivada
Constructor con un parámetro Clase(const OtroTipo& fuente) Inicialiazar una instancia con otro tipo
Operador de cast operator int(void) const; Convertir una instancia a otro tipo

Cast tradicional de C

El cast tradicional consiste en escribir el tipo entre paréntesis delante la variable que quieres convertir.

int a = 5;
float b = (float) a;

Esta forma de cast puede actuar como muchas de las otras formas de cast. El problema es que no siempre está claro cuál. Por eso es posible que tu programa acaba haciendo otra cosa que el esperabas y, por eso, suele haber la recomendación de ya no utilizarlo. Con las demás formas de cast habrá menos malentendidos entre tú y tu compilador.

Cast entre tipos nativos de C++

static_cast

El mayor uso de static_cast es convertir tipos nativos. Por ejemplo

int a = 5;
unsigned short int b = static_cast(a);
float c = static_cast(a);

Aunque para el programador los dos static_cast parecen lo mismo, el compilador debe hacer dos cosas distintas. A convertir de un tipo entero a otro, le basta jugar con los bits. (Básicamente debe llevar el bit de signo a una nueva posición.) Al convertir entre números enteros y números de coma flotante el compilador debe generar código más complejo, ya que la representación de bits es bastante distinta en tipos flotantes.

static_cast sirve también para convertir clases con el operador de cast como veremos más abajo. Si no sabes con qué cast a probar primero, entonces static_cast es una buena opción.

reinterpret_cast

Como indica el nombre, reinterpret_cast reintepreta un tipo como si fuera otro. Esto funciona especialmente bien entre punteros, ya que tienen el mismo formato físico aunque apunten a tipos diferentes. No obstante, reinterpret_cast reintepreta todo. Convertir un int a un puntero de función te traerá una cuantas advertencias del compilador, pero con reinterpret_cast se puede hacer.

Esta claro que algo tan poderoso invita al abuso. Por eso, la mayoría de las guías de programación te recomiendan con toda corazón de estudiar alternativas. Porque suele haber alternativas como static_cast – a menos que realmente quieres convertir números enteros a punteros de funciones.

const_cast

const_cast sirve para añadir y quitar atributos de tipos. Atributos son const, volatile y mutable. En la mayoría de los casos, const_cast sirve para quitar el atributo const de una variable – de ahí el nombre del cast.

Obviamente quitar el atributo const destruye la buena intención de no modificar una constante que, por lo tanto, las guías de programación recomiendan a no hacer. Sin embargo, suele ser justificado de usarlo para no romper la const-correctness de tu código por tener que usar una función sin const-correctness.

Cast entre clases

dynamic_cast

dynamic_cast se usa cuando sé que una instancia de una clase base es realmente una instancia de una clase derivada. Se usa típicamente en funciones virtuales.

class Base
{
public:
    void hazAlgo(const Base& instancia)
    {
        // Usar una función puramente virtual
        hazAlgoMás(instancia);
    }
private:
    virtual void hazAlgoMás(const Base& instancia) = 0;
};

class Derivada : public Base
{
private:
    virtual void hazAlgoMás(const Base& instancia)
    {
        // Aquí sé que instancia es realmente del
        // tipo Derivada.
        const Derivada& instancia_de_clase_derivada =
            dynamic_cast(instancia);
        // Aquí haz algo con instancia_de_clase_derivada
    }
};

void main()
{
    Derivada uno, dos;
    uno.hazAlgo(dos);
}

En el ejemplo arriba, llamamos para el objeto uno al método hazAlgo. Como la clase derivada no sobrescribe este método, llamamos realmente al método de la clase base. hazAlgo llama a una función puramente virtual hazAlgoMás que se resuelve al método Derivada::hazAlgoMás. La clase Base no sabe nada de la clase Derivada y por eso pasa como parámetro una referencia a un objeto de tipo Base, pero que realmente es un objeto de tipo Derivada. En la implementación del método virtual sabemos que el parámetros instancia debe ser de tipo Derivada y lo obtenemos mediante dynamic_cast.

No nececitamos hacer un cast para el puntero this. El sistema de métodos virtuales ya lo hace por nosotros.

Si el dynamic_cast no succede, por ejemplo, porque el tipo a que se convierte no está dentro del árbol de herencia del objeto a convertir, entonces dynamic_cast lanza un excepción std::bad_cast. Por este sistema de manejo de error, se considera dynamic_cast un método seguro para convertir entre clases del mismo árbol de herencia.

Por cierto, no es necesario convertir una instancia de una clase derivada a una instance de la clase base. Un objeto de una clase derivada siempre es también una instancia de la clase base.

Constructor con un parámetro

Considera un constructor del estilo

class MiClase
{
    explicit MiClase(const OtroTipo& fuente);
};

Este constructor permite inicializar una instancia de MiClase con un objeto de OtroTipo. Pues, esto no es otra cosa que una conversión de tipos.

No hay muchos situaciones en que un constructor con otro tipo es útil, pero cuando es útil, entonces este constructor es la manera recomendada de conversión. Como conversiones implícitas no están recomendadas, hemos escrito la palabra clave explicit delante. Esta nos obliga llamar al constructor explícitamente.

OtroTipo fuente;
MiClase a;
a = fuente; // Error, conversión implícita
a = MiClase(fuente);  // Bien, conversión explícita

Operador de cast

Se puede definir un operador que convierte una instancia de una clase a otro tipo.

class MiClase
{
    explicit operator bool() const;
};

void main()
{
    MiClase c;
    bool b1 = c;  // Error conversión implícita
    bool b1 = (bool) c;  // Bien, pero estilo C
    bool b2 = static_cast(c);  // Bien. Forma recomendada
}

Como vemos, el operator bool permite utilizar la instancia de la clase como si fuera otro tipo. En lugar de bool podríamos poner cualquier otro tipo nativo o clase.

Desde el estándar C++11 podemos escribir explicit delante la declaración para evitar conversiones implícitas. Aunque hemos mencionado arriba que conversiones implícitas no suelen ser deseadas, se usan con abundancia en la biblioteca estándar para las clases traits como std::is_constructible. No obstante, detrás de estas clases suele ser una implementación con templates que deben poder utilizarse como una función. Es decir, un asunto con que no queremos ruinarnos el – hasta ahora – buen entendimiento del tema.

Cambios en los nuevos estándares de C++

Bjarne Stroustrup, el inventor de C++, que ha introducido static_cast para evitar los cast al estilo C, ahora recomienda no utilizar static_cast tampoco. (Ver en los CPP Core Guidelines aquí y aquí.) Es por la misma razón: porque a veces no hace lo que el programador espera. O porque uno se hace la vida demasiado fácil.

De todas formas podemos esperar la introducción de nuevos y más seguros formas de cast como el todavía no estándar narrow_cast. Y ya tienes perlas como std::move y std::forward para resolver problemas que, posiblemente, todavía no has tenido.

En todo caso, si desarrollas código para sistemas que pueden causar daño a personas (que son básicamente aquellas donde las licencias de Microsoft y Apple te prohiben utilizar sus sistemas operativos), entonces vale la pena a echar un ojo al buen uso de los cast y controlarlos con una herramienta de análsis de código estática.

Referencias

Lectura adicional

Si quieren ver los ficheros en tu ordenador en un control policial u otro organismo de seguridad o inseguridad, git te puede ayudar a esconder datos. Crea un repositorio git y crea dos ramas. En la rama master guardas datos no sospechosos y en la otra con nombre no sospechoso guardas los datos que quieres proteger. Entonces, con git checkout master puedes rápidamente esconder lo que no quieras que vean. La carpeta «.git» está escondida y no contiene ficheros con nombres claros.

No olvides que esta forma de esconder datos sólo funciona si el atacante no piensa en este truco y no conoce git. Esta propuesta camufla el hecho que hay datos de interés, pero no los encripta. También ten en cuenta que un cambio de la rama puede durar un tiempo si son muchos ficheros.

Lectura adicional

Cada página debería tener una declaración de privacidad – aunque sólo sea para declarar que el usuario realmente no tiene privacidad. Esta declaración a continuación puedes usar como ejemplo para una página tuya albergada en algún proveedor de tipo aspiradora de datos. Seguramente no prometes demasiado (poco) aquí.

Sus datos privados son importantes. Por eso intento obtener un máximo de ellos en esta página. Como no tengo ni conocimientos técnicos ni derechos de acceso suficientes a la base de datos de esta aplicación web para poder espiar su uso de navegador, subcontraté el tratamiento de sus datos al proveedor de estas páginas.

El proveedor, sus representaciones nacionales y sus partners, llamados simplemente “proveedor” a continuación, usan la declaración de privacidad que puede encontrar en cualquiera de sus páginas. En corto dice que el proveedor puede – o no – tratar sus datos para saber más sobre usted y poder ofrecerle la mejor publicidad y “servicios”. Como el proveedor no tiene capacidad de hacer todo esto solo, puede pasar o no – sus datos a empresas subcontratadas en su país u otro lado que, a su vez, pueden pasar o no – sus datos a empresas subsubcontratadas que posiblemente no están localizadas en su país, su continente o su planeta. Por supuesto, para los partners de su proveedor valen las misma reglas estrictas como para el proveedor.

Además, el proveedor está obligado a dar acceso a sus datos a las “autoridades” si alguna ley en algún país le obliga. Sobre esto no hay más explicaciones en la declaración de privacidad de su proveedor, porque, en EEUU y otros países, está prohibido por ley de admitir la colaboración con las “autoridades”. Las “autoridades” de EEUU y de otros países tienen un derecho general de copiar todos sus datos y pasarlos a base de alguna ley real o deseada a “autoridades” o “instituciones” en otros países amigos o no tan amigos. Estos países se han obligados a proteger sus datos con la misma diligencia. En otros países más allá no es necesario advertir de las posibilidades de espionaje, porque el servicio secreto está omnipresente y sus poblaciones acostumbrada a que toda la comunicación por Internet o teléfono será supervisada por “su seguridad” por los señores en el poder.

Para que el proveedor pueda mostrarle la publicidad que más eficazmente le hace comprar, usted debería usar este y los demás servicios del proveedor al máximo – en particular la búsqueda por Internet para que el proveedor sepa que le mueve y la aplicación de red social para saber lo que le ha movido. Por conveniencia no necesita introducir todos los datos en todas las aplicaciones de nuevo. Gracias a una cookie con su identificación de usuario, que cordialmente se ha ofrecido a su ordenador, el proveedor puede conectar todos sus datos introducidos en cualquier aplicación. Usted sabe que hizo todo bien, cuando encuentra en diversas páginas web publicidad para los medicamentos contra la enfermedad sexual que recientemente ha buscado en la máquina de búsqueda del proveedor.

Está invitado a hacer click en todos los botones de “compartir” que suelen aparecer al final de la página para distribuir sus datos igualmente a todas las empresas y prevenir así un monopolio de datos potencialmente malo para la economía. Por supuesto, sus amigos en las demás redes sociales también están curiosos que usted divulga en Internet. En el caso de sus amigos, usted podría incluso llegar a tener la sensación que es usted quien controla el acceso a sus datos.

Si usted no está conforme con distribuir sus intereses de esta forma, entonces usted debería volver a escribir cartas en papel o comprar los periódicos en un quiosco en lugar de perder su tiempo en Internet.

Lectura adicional

Escribe tu dirección de correo electrónico para suscribirte a este blog, y recibir notificaciones de nuevos mensajes por correo.

Únete a otros 61 suscriptores

Archivos

May 2024
L M X J V S D
 12345
6789101112
13141516171819
20212223242526
2728293031