<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Jacobo Tarrío</title>
    <link>https://jacobo.tarrio.org/</link>
    <description></description>
    <item>
      <title>Project Spite</title>
      <link>https://jacobo.tarrio.org/2022/project-spite.html</link>
      <description>&lt;p&gt;I saw my first demo in 1993 or so. It was magical: I would run a program, and a&#xA;display of shapes, effects, and music would appear on my computer, all rendered&#xA;in real time. One day, after watching one of those demos, still humming its&#xA;music, I started my editor to try and code a simple 3D cube, when my little&#xA;brother said:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;“Oh, so &lt;em&gt;you&lt;/em&gt; think you&amp;rsquo;re going to be able to code &lt;em&gt;that&lt;/em&gt;&amp;hellip;”&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;He was 10 or 11 and probably just wanted me to get off the computer so he could&#xA;play a game, but it hurt me a little at the time.&lt;/p&gt;&#xA;&lt;p&gt;So, of course, nearly 30 years later I still remember it and I dedicate this&#xA;project to that memory.&lt;/p&gt;&#xA;&lt;p&gt;Last April I realized that, one year from now, it will be the 30th anniversary&#xA;of &lt;a href=&#34;https://en.wikipedia.org/wiki/Second_Reality&#34;&gt;Second Reality&lt;/a&gt;, and thought&#xA;that:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;It would be cool if someone would write an HTML5 remake for 2023.&lt;/li&gt;&#xA;&lt;li&gt;Hey, I&amp;rsquo;m someone.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;So I started working on it.&lt;/p&gt;&#xA;&lt;p&gt;Now, about a month later I realized that I had never written a demo before,&#xA;it was taking me a long time to get something going, that I have little to&#xA;no idea what the names of the different effects in the demo are, much less how&#xA;to code them, and that I don&amp;rsquo;t have enough artistic ability to redo the drawings&#xA;for a 1080p or 4k resolution world.&lt;/p&gt;&#xA;&lt;p&gt;Therefore, I decided to complete the intro, put a ribbon on it, release it&#xA;along with the &lt;a href=&#34;https://github.com/jtarrio/spite&#34;&gt;source code&lt;/a&gt;, and see if&#xA;anyone would like to build upon it.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;../assets/2022/spite/index.html&#34;&gt;You can see the demo here.&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;You can exit the demo at any time pressing Enter or “Q”. It works at a 1080p&#xA;resolution, fullscreen. It should work in a computer but also on a phone or&#xA;tablet.&lt;/p&gt;&#xA;&lt;p&gt;Many of the assets were reused or adapted from the original Second Reality demo,&#xA;whose source code was published 10 year ago, for the 20th anniversary. I&#xA;repainted the planet landscape so it doesn&amp;rsquo;t look all blocky and upscaled.&lt;/p&gt;&#xA;&lt;p&gt;The music is played in real time from the original S3M, of course; I wrote the&#xA;player myself, though it is pretty much an “only needs to be able to play&#xA;one song” affair.&lt;/p&gt;&#xA;&lt;p&gt;Let me know what you think!&lt;/p&gt;&#xA;</description>
      <pubDate>Mon, 04 Jul 2022 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>No abuséis de los tipos de datos primitivos</title>
      <link>https://jacobo.tarrio.org/2021/menos-tipos-de-datos-primitivos.html</link>
      <description>&lt;p&gt;Pedidle a un programador moderno que os diseñe un programa, y pronto os dará un diagrama con clases llamadas&#xA;&lt;code&gt;Cliente&lt;/code&gt; y &lt;code&gt;Empleado&lt;/code&gt;, y &lt;code&gt;Usuario&lt;/code&gt; y &lt;code&gt;UsuarioDAO&lt;/code&gt;, y &lt;code&gt;ConnectionProvider&lt;/code&gt; y &lt;code&gt;ConnectionProviderFactory&lt;/code&gt; y&#xA;&lt;code&gt;AbstractConnectionProviderFactoryImpl&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Bueno, tal vez sólo los programadores Java se metan en tales fregados, pero está claro que, hoy en día,&#xA;cualquier programador conoce y utiliza los conceptos básicos de orientación a objetos. Incluso en lenguajes&#xA;no orientados a objetos, los programadores crean y usan &lt;code&gt;struct&lt;/code&gt;s, o &lt;code&gt;record&lt;/code&gt;s, o &lt;code&gt;Type&lt;/code&gt;s o cualesquiera&#xA;mecanismos del lenguaje para crear nuevos tipos de datos.&lt;/p&gt;&#xA;&lt;p&gt;Y, aún así, por muy sabios que nos consideremos los programadores en la actualidad, en nuestros programas todavía&#xA;sobreviven vestigios de los viejos tiempos en los que Fortran y COBOL y BASIC sólo nos proporcionaban tipos de datos&#xA;primitivos. Cada vez que queremos almacenar una clave primaria de un registro de la base de datos, la ponemos&#xA;en una variable de tipo &lt;code&gt;long&lt;/code&gt; o &lt;code&gt;string&lt;/code&gt;. Si queremos pasarle un &lt;em&gt;timestamp&lt;/em&gt; a una función, usamos un&#xA;argumento de tipo &lt;code&gt;int&lt;/code&gt; o &lt;code&gt;long&lt;/code&gt;. ¿Una función que devuelve un UUID? Tipo &lt;code&gt;string&lt;/code&gt;. ¿Una cadena de texto&#xA;recibida del usuario? ¿Una cadena de texto lista para insertar en un documento HTML? Ambas, tipo &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Utilizamos los mismos tipos de datos, una y otra vez, para cosas tan diferentes como claves primarias de distintas&#xA;tablas y &lt;em&gt;timestamps&lt;/em&gt; en segundos y milisegundos, o URIs y UUIDs. Ésta es una práctica extremadamente habitual que,&#xA;también, es una causa de errores extremadamente habitual. ¿Quién no ha escrito nunca código que intenta buscar&#xA;un registro en la tabla equivocada, o que compara segundos y microsegundos, o que inserta una cadena de texto&#xA;sin escapar en un documento HTML?&lt;/p&gt;&#xA;&lt;p&gt;En este artículo os describo tres estrategias para tratar con esos objetos, evitar confusiones como las que&#xA;describo arriba, y evitar errores de programación.&lt;/p&gt;&#xA;&lt;h1&gt;Utilizad nuevos tipos para nuevas entidades&lt;/h1&gt;&#xA;&lt;p&gt;¿Conocéis el &lt;a href=&#34;http://web.archive.org&#34;&gt;Internet Archive&lt;/a&gt;? Es una organización sin ánimo de lucro que quiere&#xA;formar una &amp;ldquo;biblioteca de Internet&amp;rdquo; archivando todas las páginas web del mundo para conservarlas y para que&#xA;podamos verlas en el futuro.&lt;/p&gt;&#xA;&lt;p&gt;Podéis suponer que el código fuente del Internet Archive trabaja mucho con URIs. Cada página web tiene su propio&#xA;URI y contiene otros URIs de todas las páginas web a las que enlaza. En el archivo, las páginas web están indexadas&#xA;mediante sus URIs para que los usuarios puedan acceder a ellas. En definitiva, para el Internet Archive, los URIs&#xA;son el pan nuestro de cada día.&lt;/p&gt;&#xA;&lt;p&gt;Los programadores siempre estamos tentados de almacenar URIs en variables de tipo &lt;code&gt;string&lt;/code&gt;. Vemos una cadena&#xA;alfanumérica y nos decimos: &amp;ldquo;eso es fácil: es una cadena de texto, así que tipo &lt;code&gt;string&lt;/code&gt;&amp;rdquo;. Sin embargo, los URIs&#xA;no son sólo cadenas de texto. Los URIs tienen ciertas reglas que deben cumplir en todo momento: han de seguir un&#xA;cierto formato, ciertos caracteres no son válidos, hay secuencias de escape, etc.&lt;/p&gt;&#xA;&lt;p&gt;Cuando almacenamos un URI en una variable de tipo &lt;code&gt;string&lt;/code&gt;, el sistema no sabe nada de esas reglas. La variable&#xA;podría contener un URI mal formateado, o incluso algo que no es un URI en absoluto, y no habría ningún mensaje&#xA;de error ni nada parecido hasta que intentásemos usarla y se rompiera algo. Por lo tanto, necesitamos&#xA;escribir (y acordarnos de utilizar siempre) código que, cada vez que asignemos un nuevo valor a la variable,&#xA;lo verifique y normalice. Si no lo hacemos, nos exponemos a una multitud de posibles errores de programación.&#xA;Tal vez no seamos capaz de encontrar la página web que el usuario busca, tal vez tengamos un XSS en nuestra página,&#xA;o algún otro problema entre medias.&lt;/p&gt;&#xA;&lt;p&gt;Para solucionar el problema con más eficacia tenemos que reconocer que un URI no es una cadena de texto que podamos&#xA;manipular usando sólo variables de tipo &lt;code&gt;string&lt;/code&gt;, sino que tenemos que utilizar una clase específica que codifique&#xA;todas las reglas y el formato de un URI. Esta clase &lt;code&gt;URI&lt;/code&gt; ha de tener funciones para convertir una cadena de texto&#xA;en un objeto de tipo &lt;code&gt;URI&lt;/code&gt;, aplicando todas las reglas de validación y normalización, y también necesita tener&#xA;funciones para extraer las distintas partes del URI y obtener una representación del URI en forma de cadena de texto.&lt;/p&gt;&#xA;&lt;p&gt;La mayoría de los lenguajes modernos incluyen una clase &lt;code&gt;URI&lt;/code&gt; en su biblioteca estándar, así que sólo tendremos que&#xA;acordarnos de utilizarla en lugar del tipo &lt;code&gt;string&lt;/code&gt; en cualquier lugar en el que tratemos con URIs.&lt;/p&gt;&#xA;&lt;p&gt;El Internet Archive, en su página principal, tiene una caja de texto en la que un usuario puede escribir un URI y&#xA;ver qué había en esa página en algún momento del pasado. El servidor web recibe la petición web que contiene ese URI&#xA;en forma de cadena de caracteres; lo primero que hace el servidor es convertirlo en un objeto de tipo &lt;code&gt;URI&lt;/code&gt;. A partir&#xA;de ese momento, ese URI está validado y formateado, por lo que el servidor puede pasarlo al servicio de&#xA;almacenamiento, que podrá recuperar el contenido de la página web.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;A veces no podemos utilizar clases proporcionadas por nuestra biblioteca estándar y tenemos que escribirlas nosotros&#xA;mismos. Por ejemplo, hace años trabajé en un proyecto que tenía una base de datos que almacenaba objetos indexados&#xA;mediante un identificador que seguía el formato &lt;code&gt;HHHHHHHH-V&lt;/code&gt;, donde &lt;code&gt;H&lt;/code&gt; era un dígito hexadecimal y &lt;code&gt;V&lt;/code&gt; era un número&#xA;de versión de uno o más dígitos.&lt;/p&gt;&#xA;&lt;p&gt;Los programadores originales del sistema no se habían puesto de acuerdo en cómo tratar estos identificadores.&#xA;En algunas partes del programa, el identificador iba almacenado en una variable de tipo &lt;code&gt;string&lt;/code&gt;. En otras partes,&#xA;el identificador estaba dividido en dos variables: una de tipo &lt;code&gt;string&lt;/code&gt; que contenía los dígitos hexadecimales&#xA;y otra de tipo &lt;code&gt;int&lt;/code&gt; que contenía el número de versión. Nuestro código muchas veces se parecía a esto:&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; List&amp;lt;String&amp;gt; &lt;span style=&#34;color:#c34e00&#34;&gt;buscaIdsAsociados&lt;/span&gt;(String id)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#00f&#34;&gt;throws&lt;/span&gt; IllegalArgumentException, NotFoundException {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;if&lt;/span&gt; (!esValido(id)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#00f&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; IllegalArgumentException();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String hexa = extraeHexa(id);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;int&lt;/span&gt; version = extraeVersion(id);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Registro registro = buscaRegistro(hexa, version);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    List&amp;lt;String&amp;gt; idsAsociados = &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; ArrayList&amp;lt;&amp;gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;for&lt;/span&gt; (Registro asociado : registro.getRegistrosAsociados()) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        idsAsociados.add(asociado.getHexa() + &lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;-&amp;#34;&lt;/span&gt; + asociado.getVersion());&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; idsAsociados;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Registro &lt;span style=&#34;color:#c34e00&#34;&gt;buscaRegistro&lt;/span&gt;(String hexa, &lt;span style=&#34;color:#00f&#34;&gt;int&lt;/span&gt; version) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Query query = createQuery(&lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;SELECT * FROM Registros WHERE hexa=?, version=?&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                              hexa, version);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; convertir(query.execute());&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Como podéis ver, era un jaleo. Nos pasábamos el tiempo validando identificadores y dividiéndolos en dos partes y&#xA;reensamblándolos y buscando errores causados por sitios donde nos habíamos olvidado de validar o donde habíamos&#xA;pasado un identificador cuando necesitábamos la parte hexadecimal o viceversa.&lt;/p&gt;&#xA;&lt;p&gt;La solución a este problema consistió en reconocer que un identificador es un nuevo tipo de entidad y no sólo una&#xA;cadena de texto, y crear una clase dedicada a almacenar y manipular identificadores.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#007575&#34;&gt;Identificador&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;private&lt;/span&gt; String hexa;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;int&lt;/span&gt; version;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;Identificador&lt;/span&gt;(String hexa, &lt;span style=&#34;color:#00f&#34;&gt;int&lt;/span&gt; version) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#00f&#34;&gt;this&lt;/span&gt;.hexa = hexa;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#00f&#34;&gt;this&lt;/span&gt;.version = version;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; String &lt;span style=&#34;color:#c34e00&#34;&gt;getHexa&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; hexa; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;getVersion&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; version; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; String &lt;span style=&#34;color:#c34e00&#34;&gt;toString&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; hexa + &lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;-&amp;#34;&lt;/span&gt; + version; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;static&lt;/span&gt; Identificador &lt;span style=&#34;color:#c34e00&#34;&gt;crear&lt;/span&gt;(String id) &lt;span style=&#34;color:#00f&#34;&gt;throws&lt;/span&gt; IllegalArgumentException {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f00;font-style:italic&#34;&gt;// Validación y extracción de las partes del identificador&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        String hexa = ...;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#00f&#34;&gt;int&lt;/span&gt; version = ...;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; Identificador(hexa, version);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Y, con esto, pudimos simplificar nuestro código, evitando las constantes validaciones y tratando los identificadores&#xA;de la misma manera en todo el programa, eliminando cientos de líneas de código y oportunidades para introducir errores.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; List&amp;lt;Identificador&amp;gt; &lt;span style=&#34;color:#c34e00&#34;&gt;buscaIdsAsociados&lt;/span&gt;(Identificador id)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#00f&#34;&gt;throws&lt;/span&gt; NotFoundException {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Registro registro = buscaRegistro(id);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    List&amp;lt;Identificador&amp;gt; idsAsociados = &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; ArrayList&amp;lt;&amp;gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;for&lt;/span&gt; (Registro asociado : registro.getRegistrosAsociados()) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        idsAsociados.add(asociado.getIdentificador());&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; idsAsociados;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Registro &lt;span style=&#34;color:#c34e00&#34;&gt;buscaRegistro&lt;/span&gt;(Identificador id) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Query query = createQuery(&lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;SELECT * FROM Registros WHERE hexa=?, version=?&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                              identificador.getHexa(), identificador.getVersion());&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; convertir(query.execute());&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;Utilizad distintos tipos para distintas entidades&lt;/h1&gt;&#xA;&lt;p&gt;En el mundillo de las bases de datos hay dos escuelas: la escuela de los que utilizan &lt;code&gt;INT&lt;/code&gt;s para las claves primarias&#xA;y la escuela de los que utilizan UUIDs. Sea cual sea la escuela a la que pertenezca vuestro DBA, vuestro código va a&#xA;acabar lleno de variables, todas del mismo tipo, que contienen claves primarias pertenecientes a distintas tablas.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; idArticulo = getLongParam(&lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;idArticulo&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; idComentario = getLongParam(&lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;idComentario&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Comentario comentario = cargaComentario(idArticulo, idComentario);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; idUsuario = comentario.idAutor();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Usuario usuario = cargaUsuario(idArticulo);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;outputTemplate(&lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;comentario&amp;#34;&lt;/span&gt;, usuario, comentario);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Este podría ser parte del código fuente de un CMS: una función que gestiona la respuesta a una operación HTTP &lt;code&gt;GET&lt;/code&gt;&#xA;para mostrar un comentario de un artículo. Esta función recibe un identificador de artículo y de comentario,&#xA;carga el comentario y los datos del usuario que publicó el comentario, y los convierte a HTML para mostrarlos en el navegador.&lt;/p&gt;&#xA;&lt;p&gt;La base de datos utiliza números enteros para las claves primarias, y el lenguaje las almacena en variables de tipo&#xA;&lt;code&gt;long&lt;/code&gt;. Tenemos una clave para el artículo, otra para el comentario y otra para el usuario. Y como todas&#xA;estas claves van en variables del mismo tipo, es muy fácil confundirse y mezclarlas sin darse cuenta. De hecho,&#xA;el código de arriba contiene un error. ¿Cuánto tiempo os lleva descubrirlo?&lt;/p&gt;&#xA;&lt;p&gt;Aunque todas las claves primarias van en variables del mismo tipo, no son intercambiables. Si pasamos un identificador&#xA;de usuario a una función que espera un identificador de artículo, esta función nos dará un resultado incorrecto.&#xA;Lo malo es que no nos daremos cuenta de esta confusión hasta que probemos el programa y nos demos cuenta del&#xA;error; o, peor todavía, hasta que un usuario lo vea y nos avise. O, lo peor de todo, nunca nos daremos cuenta y&#xA;terminaremos corrompiendo datos.&lt;/p&gt;&#xA;&lt;p&gt;Si estas claves no son intercambiables en la práctica, tampoco deberían ser intercambiables en el código. Podemos&#xA;lograr esto muy fácilmente usando clases distintas para las claves primarias de cada tabla.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;IdArticulo idArticulo = &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; IdArticulo(getLongParam(&lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;idArticulo&amp;#34;&lt;/span&gt;));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;IdComentario idComentario = &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; IdComentario(getLongParam(&lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;idComentario&amp;#34;&lt;/span&gt;));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Comentario comentario = cargaComentario(idArticulo, idComentario);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;IdUsuario idUsuario = comentario.idAutor();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Usuario usuario = cargaUsuario(idArticulo); &lt;span style=&#34;color:#f00;font-style:italic&#34;&gt;// El compilador nos da un error aquí.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;outputTemplate(&lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;comentario&amp;#34;&lt;/span&gt;, usuario, comentario);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Crear estas clases es superfácil en la mayoría de los lenguajes modernos. Por ejemplo, en Java, podemos crear una clase&#xA;base que incorpore toda la funcionalidad, y después añadir una línea de código para cada tipo.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#007575&#34;&gt;Id&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; id;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;Id&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; id) { &lt;span style=&#34;color:#00f&#34;&gt;this&lt;/span&gt;.id = id; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;getId&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; id; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; String &lt;span style=&#34;color:#c34e00&#34;&gt;toString&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; getClass().getSimpleName() + &lt;span style=&#34;color:#009c00&#34;&gt;&amp;#34;=&amp;#34;&lt;/span&gt; + id; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; IdArticulo &lt;span style=&#34;color:#00f&#34;&gt;extends&lt;/span&gt; Id { &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;IdArticulo&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; id) { &lt;span style=&#34;color:#00f&#34;&gt;super&lt;/span&gt;(id); } }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; IdComentario &lt;span style=&#34;color:#00f&#34;&gt;extends&lt;/span&gt; Id { &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;IdComentario&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; id) { &lt;span style=&#34;color:#00f&#34;&gt;super&lt;/span&gt;(id); } }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; IdUsuario &lt;span style=&#34;color:#00f&#34;&gt;extends&lt;/span&gt; Id { &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;IdUsuario&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; id) { &lt;span style=&#34;color:#00f&#34;&gt;super&lt;/span&gt;(id); } }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;&#xA;&lt;p&gt;Esta técnica también os va a resultar útil en tareas de procesamiento de datos, en las que podríamos&#xA;estar manejando datos pertenecientes a distintas etapas de procesamiento al mismo tiempo sin querer mezclarlos.&#xA;Hoy en día, os la encontraréis más habitualmente a la hora de evitar XSS en aplicaciones web.&lt;/p&gt;&#xA;&lt;p&gt;Muchas aplicaciones web necesitan recibir una cadena de texto del usuario, procesarla de alguna manera,&#xA;y finalmente mostrarla en una página web. Si no tenéis cuidado y pegáis ese texto directamente en el HTML,&#xA;os expondréis a un ataque XSS; como mínimo, tenéis que &lt;em&gt;escapar&lt;/em&gt; esa cadena de texto antes de ponerla en&#xA;el HTML. A veces, los programadores de las aplicaciones web se confunden y escapan una cadena dos veces,&#xA;y después los usuarios ven cosas raras como &amp;ldquo;t&amp;amp;eacute;cnica&amp;rdquo; en lugar de &amp;ldquo;técnica&amp;rdquo;. O hacen mal el escape&#xA;de cadenas para SQL y después los usuarios ven &amp;ldquo;O\&#39;Connell&amp;rdquo; en lugar de &amp;ldquo;O&#39;Connell&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;Para evitar estos problemas, muchos &lt;em&gt;frameworks&lt;/em&gt; web modernos no nos permiten pegar &lt;code&gt;string&lt;/code&gt;s directamente,&#xA;sino que nos obligan a usar tipos especiales que contienen cadenas de texto ya escapadas. Podemos crear&#xA;una instancia de uno de esos tipos a partir de una cadena sin escapar, y a partir de entonces ya no&#xA;cabe confusión: dondequiera que utilicemos esa clase, es una cadena ya escapada y lista para insertar en&#xA;código HTML.&lt;/p&gt;&#xA;&lt;h1&gt;Utilizad un solo tipo para una sola entidad&lt;/h1&gt;&#xA;&lt;p&gt;Es muy habitual utilizar &lt;code&gt;int&lt;/code&gt;s y &lt;code&gt;long&lt;/code&gt;s para representar intervalos de tiempo. La cuestión es: ¿en qué&#xA;unidad? El sistema operativo Unix utiliza segundos pero los lenguajes Java y JavaScript utilizan&#xA;milisegundos. Yo he trabajado con sistemas que utilizaban microsegundos y nanosegundos. Muchas veces es&#xA;necesario utilizar distintas unidades en distintos lugares de un mismo programa, dependiendo de quién&#xA;haya escrito el código o qué función reciba un determinado número.&lt;/p&gt;&#xA;&lt;p&gt;El problema es que todos esos intervalos de tiempo se representan como un &lt;code&gt;long&lt;/code&gt;, así sin más, sin marcar&#xA;las unidades empleadas de ninguna manera, así que es muy fácil pasar a una función un número de milisegundos&#xA;cuando espera segundos, o restar microsegundos de nanosegundos, u otras operaciones sin sentido que acabamos&#xA;realizando sin querer.&lt;/p&gt;&#xA;&lt;p&gt;Podríamos caer en la tentación de intentar solucionar este problema creando un nuevo tipo para cada unidad.&#xA;Una clase &lt;code&gt;Segundos&lt;/code&gt; almacenaría un intervalo medido en segundos, una clase &lt;code&gt;Milisegundos&lt;/code&gt; almacenaría otro&#xA;intervalo medido en milisegundos, y así sucesivamente. De esta manera ya no podríamos mezclar distintas&#xA;unidades sin que el compilador se quejara.&lt;/p&gt;&#xA;&lt;p&gt;El problema es que, muchas veces, necesitamos convertir entre unas unidades y otras; a veces tenemos una&#xA;función que devuelve un intervalo medido en segundos que tenemos que pasar a otra función que espera&#xA;milisegundos, así que necesitamos hacer una conversión. Otras veces, tenemos que combinar dos intervalos&#xA;que pueden estar medidos en distintas unidades. Esto podríamos solucionando añadiendo funciones de conversión&#xA;y de suma y de resta para cada par de unidades, pero con cuatro unidades, esto acabarían siendo&#xA;&lt;em&gt;cuarenta y cuatro&lt;/em&gt; funciones en total. Eso es un montón de funciones.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#007575&#34;&gt;Segundos&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Milisegundos &lt;span style=&#34;color:#c34e00&#34;&gt;milisegundos&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Microsegundos &lt;span style=&#34;color:#c34e00&#34;&gt;microsegundos&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Nanosegundos &lt;span style=&#34;color:#c34e00&#34;&gt;nanosegundos&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Segundos &lt;span style=&#34;color:#c34e00&#34;&gt;suma&lt;/span&gt;(Segundos otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Segundos &lt;span style=&#34;color:#c34e00&#34;&gt;suma&lt;/span&gt;(Milisegundos otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Segundos &lt;span style=&#34;color:#c34e00&#34;&gt;suma&lt;/span&gt;(Microsegundos otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Segundos &lt;span style=&#34;color:#c34e00&#34;&gt;suma&lt;/span&gt;(Nanosegundos otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Segundos &lt;span style=&#34;color:#c34e00&#34;&gt;resta&lt;/span&gt;(Segundos otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Segundos &lt;span style=&#34;color:#c34e00&#34;&gt;resta&lt;/span&gt;(Milisegundos otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Segundos &lt;span style=&#34;color:#c34e00&#34;&gt;resta&lt;/span&gt;(Microsegundos otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Segundos &lt;span style=&#34;color:#c34e00&#34;&gt;resta&lt;/span&gt;(Nanosegundos otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f00;font-style:italic&#34;&gt;// Y otras tres veces igual para Milisegundos, Microsegundos y Nanosegundos&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Francamente, esta opción no es sostenible. A poco que necesitemos añadir una o dos unidades u operaciones,&#xA;tendremos que escribir una cantidad de código colosal y, para más inri, la mayor parte consistirá&#xA;en operaciones de conversión.&lt;/p&gt;&#xA;&lt;p&gt;La &lt;em&gt;verdadera&lt;/em&gt; solución consiste en darse cuenta de que la entidad para la que estamos creando tipos de datos&#xA;no es el número de segundos, milisegundos o microsegundos. &lt;em&gt;La entidad es el intervalo de tiempo&lt;/em&gt;, y todas esas&#xA;unidades no son más que maneras de medirlo. No necesitamos crear un tipo para cada unidad de tiempo; necesitamos&#xA;crear un tipo para los intervalos de tiempo, que tenga las operaciones necesarias para poder expresar esos&#xA;intervalos en las unidades adecuadas.&lt;/p&gt;&#xA;&lt;p&gt;Por ejemplo, podríamos crear un tipo &lt;code&gt;Intervalo&lt;/code&gt; que contenga una variable que expresa la longitud del intervalo&#xA;en una unidad cómoda, y que también contenga funciones para expresar ese intervalo en segundos, milisegundos,&#xA;microsegundos y nanosegundos, junto con funciones para hacer la conversión inversa.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#007575&#34;&gt;Intervalo&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;double&lt;/span&gt; valor;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;Intervalo&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;double&lt;/span&gt; valor) { &lt;span style=&#34;color:#00f&#34;&gt;this&lt;/span&gt;.valor = valor; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f00;font-style:italic&#34;&gt;// Constructores&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;static&lt;/span&gt; Intervalo &lt;span style=&#34;color:#c34e00&#34;&gt;deSegundos&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; segundos) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; Intervalo(segundos); }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;static&lt;/span&gt; Intervalo &lt;span style=&#34;color:#c34e00&#34;&gt;deMilisegundos&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; milisegundos) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; Intervalo(milisegundos / 1e3); }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;static&lt;/span&gt; Intervalo &lt;span style=&#34;color:#c34e00&#34;&gt;deMicrosegundos&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; microsegundos) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; Intervalo(microsegundos / 1e6); }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;static&lt;/span&gt; Intervalo &lt;span style=&#34;color:#c34e00&#34;&gt;deNanosegundos&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; nanosegundos) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; Intervalo(nanosegundos / 1e9); }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f00;font-style:italic&#34;&gt;// Conversiones&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;segundos&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt;) valor; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;milisegundos&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt;) (valor * 1e3); }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;microsegundos&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt;) (valor * 1e6); }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; &lt;span style=&#34;color:#c34e00&#34;&gt;nanosegundos&lt;/span&gt;() { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt;) (valor * 1e9); }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f00;font-style:italic&#34;&gt;// Combinaciones&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Intervalo &lt;span style=&#34;color:#c34e00&#34;&gt;suma&lt;/span&gt;(Intervalo otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; Intervalo(valor + otro.valor); }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#00f&#34;&gt;public&lt;/span&gt; Intervalo &lt;span style=&#34;color:#c34e00&#34;&gt;resta&lt;/span&gt;(Intervalo otro) { &lt;span style=&#34;color:#00f&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;new&lt;/span&gt; Intervalo(valor - otro.valor); }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Y esto es todo lo que necesitamos para poder representar intervalos de tiempo medidos en varias unidades, y&#xA;cada vez que añadimos una nueva unidad, sólo necesitamos añadir dos funciones.&lt;/p&gt;&#xA;&lt;p&gt;Ahora podemos utilizar esta clase para sustituir en nuestro código todos esos &lt;code&gt;long&lt;/code&gt;s expresados en unidades&#xA;indeterminadas, y así evitar todas las confusiones y oportunidades para errores que mencioné más arriba.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f00;font-style:italic&#34;&gt;// Viejuno.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f00;font-style:italic&#34;&gt;// ¿En qué unidad está el _timeout_? ¿Segundos? ¿Milisegundos? ¿Semanas?&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Registro &lt;span style=&#34;color:#c34e00&#34;&gt;buscaRegistro&lt;/span&gt;(Identificador id, &lt;span style=&#34;color:#00f&#34;&gt;long&lt;/span&gt; timeout) { ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f00;font-style:italic&#34;&gt;// La última moda.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Registro &lt;span style=&#34;color:#c34e00&#34;&gt;buscaRegistro&lt;/span&gt;(Identificador id, Intervalo timeout) { ... }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Muchos lenguajes de programación modernos incluyen una clase parecida a &lt;code&gt;Intervalo&lt;/code&gt; en su biblioteca estándar.&#xA;Por ejemplo, Java tiene el paquete &lt;code&gt;java.time&lt;/code&gt;, que proporciona la clase &lt;code&gt;Duration&lt;/code&gt; (junto con otra clase,&#xA;llamada &lt;code&gt;Instant&lt;/code&gt;, que representa un particular punto en el tiempo). El lenguaje C++, por su parte,&#xA;proporciona el &lt;em&gt;namespace&lt;/em&gt; &lt;code&gt;std::chrono&lt;/code&gt;, con sus clases &lt;code&gt;duration&lt;/code&gt; y &lt;code&gt;time_point&lt;/code&gt;. En otros lenguajes hay&#xA;bibliotecas de terceros que la proporcionan. Utilizadlas siempre que podáis; nunca uséis &lt;code&gt;long&lt;/code&gt;s para&#xA;representar el tiempo.&lt;/p&gt;&#xA;&lt;h1&gt;Conclusiones&lt;/h1&gt;&#xA;&lt;p&gt;A veces, utilizar un tipo primitivo del sistema es lo más sencillo pero puede acabar dándonos muchos&#xA;quebraderos de cabeza. Tenemos que pensar en si el número que almacenamos en ese &lt;code&gt;long&lt;/code&gt; es &lt;em&gt;realmente&lt;/em&gt;&#xA;un número o si lo que hay en ese &lt;code&gt;string&lt;/code&gt; es una cadena de texto &lt;em&gt;sin más&lt;/em&gt;, y crear y utilizar&#xA;nuevos tipos de datos cuando éstos tengan algún tipo de complicación o restricción o invariante.&lt;/p&gt;&#xA;&lt;p&gt;Los humanos sabemos que no podemos sumar dos cerezas a tres naranjas, pero los ordenadores, si sólo ven&#xA;dos &lt;code&gt;long&lt;/code&gt;s o dos &lt;code&gt;double&lt;/code&gt;s, los sumarán y dividirán sin pensárselo dos veces. Nosotros, los humanos&#xA;que sabemos que las entidades distintas pertenecen a tipos distintos, tenemos que comunicarle&#xA;esta distinción al ordenador, en forma de nuevos tipos de datos.&lt;/p&gt;&#xA;&lt;p&gt;Y finalmente, nosotros sabemos que 60 segundos es lo mismo que un minuto, o que 5000 metros equivalen&#xA;a 5 kilómetros; sin embargo, el ordenador sólo ve una variable que dice &amp;ldquo;60&amp;rdquo; y otra que dice &amp;ldquo;1&amp;rdquo;, o&#xA;un valor &amp;ldquo;5000&amp;rdquo; y otro &amp;ldquo;5&amp;rdquo;. Depende de nosotros decirle al ordenador que ambas cosas son la misma.&lt;/p&gt;&#xA;&lt;p&gt;La próxima vez que penséis en que estaría bien si pudiéseis &amp;ldquo;anotar&amp;rdquo; o &amp;ldquo;marcar&amp;rdquo; un número o una cadena&#xA;de texto para tratarla especialmente, probad a crear y utilizar un nuevo tipo de datos. Al hacerlo,&#xA;estoy seguro de que vuestros programas serán mucho más fiables y fáciles de leer y modificar.&lt;/p&gt;&#xA;</description>
      <pubDate>Sun, 17 Jan 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>The bandwidth of a Morse Code signal</title>
      <link>https://jacobo.tarrio.org/2020/bandwidth-of-a-cw-signal.html</link>
      <description>&lt;p&gt;In most countries, you need to pass an examination to get an amateur radio&#xA;license. In the US, the question pool is public, so a big part of studying&#xA;for the license consists of going through the whole question pool and making&#xA;sure you know the answers to every question. One of them tripped me for a bit:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;T8A11&#xA;What is the approximate maximum bandwidth required to transmit a CW signal?&#xA;    A. 2.4 kHz&#xA;    B. 150 Hz&#xA;    C. 1000 Hz&#xA;    D. 15 kHz&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;(To normal people, a CW signal is a &amp;ldquo;beeping Morse code signal&amp;rdquo;.)&lt;/p&gt;&#xA;&lt;p&gt;Now, a CW signal is pretty much a sinusoidal wave, and I knew that a pure&#xA;sinusoidal wave takes a tiny, tiny bandwidth, so I could eliminate answers&#xA;&amp;ldquo;A&amp;rdquo;, &amp;ldquo;C&amp;rdquo;, and &amp;ldquo;D&amp;rdquo; straight away. That left &amp;ldquo;B&amp;rdquo;, but I didn&amp;rsquo;t know why it&#xA;would be the right answer, so I had to think about it for a while.&lt;/p&gt;&#xA;&lt;p&gt;It is true that a sinusoidal signal takes very little bandwidth. If a CW&#xA;signal consisted just of a steady sinusoidal carrier that never turned off&#xA;and on, it would indeed have an extremely low bandwidth, only limited by the&#xA;transmitting oscillator&amp;rsquo;s stability.&lt;/p&gt;&#xA;&lt;p&gt;However, a CW signal is not a steady sinusoidal; it is modulated. In&#xA;particular, it is modulated by turning it off and on according to a message&#xA;that&amp;rsquo;s encoded using Morse code. This modulation causes the CW signal to&#xA;have a bigger bandwidth than a steady carrier.&lt;/p&gt;&#xA;&lt;p&gt;As an extreme, we can imagine that we want to transmit a series of Morse&#xA;dots at 30 words per minute. That would be equivalent to switching the&#xA;carrier on and off 25 times every second. That&amp;rsquo;s a 12.5-Hz signal that,&#xA;when modulated, requires a 25-Hz bandwidth at the &lt;em&gt;very&lt;/em&gt; minimum (12.5 Hz&#xA;on each sideband). In practice, the required bandwidth would be higher,&#xA;depending on how abruptly the carrier was switched on and off.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;To demonstrate it, I wrote a widget to simulate a CW signal being received by&#xA;a CW radio with a filter centered at 600 Hz. It can play the received signal&#xA;on your speakers and show its frequency spectrum on your screen. The top half&#xA;displays an instantaneous chart (with horizontal lines every 40 dB), while&#xA;the bottom displays a waterfall plot. The vertical lines indicate the&#xA;frequency of the received signal, with dashed lines every 500 Hz and&#xA;continuous lines every 1000 Hz.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s first look at (and listen to) a transmitter that produces a signal with&#xA;very abrupt off/on and on/off transitions. Go ahead and press &amp;ldquo;Play&amp;rdquo;:&lt;/p&gt;&#xA;&lt;iframe src=&#34;../assets/2020/morsewidget/widget.html&#34; width=&#34;200&#34; height=&#34;300&#34; frameborder=&#34;0&#34; style=&#34;display: block; margin: auto;&#34;&gt;&lt;/iframe&gt;&#xA;&lt;p&gt;As you can see, when the carrier is steady on, the signal does not use much&#xA;bandwidth; it is when the signal switches on or off that it uses a lot of&#xA;bandwidth. This bandwidth usage depends on how sudden the on/off transitions&#xA;are. Above, the switches were instantaneous, so the signal uses a lot of&#xA;bandwidth, which is not good.&lt;/p&gt;&#xA;&lt;p&gt;To avoid using so much bandwidth, many radio transmitters ramp the signal&#xA;up and down over 5 milliseconds instead of cutting it on and off. This&#xA;lowers the bandwidth usage without really affecting the sound. You can check&#xA;it out by pressing &amp;ldquo;Play&amp;rdquo; on the widget below:&lt;/p&gt;&#xA;&lt;iframe src=&#34;../assets/2020/morsewidget/widget.html#envelope=sine&#34; width=&#34;200&#34; height=&#34;300&#34; frameborder=&#34;0&#34; style=&#34;display: block; margin: auto;&#34;&gt;&lt;/iframe&gt;&#xA;&lt;p&gt;That&amp;rsquo;s not the whole story, however. The widgets above simulate a radio with&#xA;a receive filter, so they don&amp;rsquo;t show the whole bandwidth that&amp;rsquo;s used by&#xA;the signals. The widgets below had their filters removed, so they can&#xA;show how much bandwidth is really used in each case. The one on the left is&#xA;the original signal that switches on and off suddenly, while the one on the&#xA;right is the modified signal with 5-millisecond transitions:&lt;/p&gt;&#xA;&lt;div style=&#34;margin: auto; width: fit-content;&#34;&gt;&#xA;  &lt;iframe src=&#34;../assets/2020/morsewidget/widget.html#nofilter=true&#34; width=&#34;200&#34; height=&#34;300&#34; frameborder=&#34;0&#34;&gt;&lt;/iframe&gt;&#xA;  &lt;iframe src=&#34;../assets/2020/morsewidget/widget.html#envelope=sine&amp;nofilter=true&#34; width=&#34;200&#34; height=&#34;300&#34; frameborder=&#34;0&#34;&gt;&lt;/iframe&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;The difference is undeniable. On the left, the on/off transitions appear&#xA;as broadband energy spikes that are almost as powerful as the signal itself&#xA;across the whole band. On the right, the spikes still produce quite a bit of&#xA;power, but it occupies a smaller bandwidth and their power goes down faster&#xA;as they get further from the central frequency.&lt;/p&gt;&#xA;&lt;p&gt;The huge amount of power produced by sudden on/off switches causes an annoying&#xA;effect, called &amp;ldquo;key clicks&amp;rdquo;. Press &amp;ldquo;Play&amp;rdquo; on the widget below to hear it:&lt;/p&gt;&#xA;&lt;iframe src=&#34;../assets/2020/morsewidget/widget.html#frequency=3400&#34; width=&#34;200&#34; height=&#34;300&#34; frameborder=&#34;0&#34; style=&#34;display: block; margin: auto;&#34;&gt;&lt;/iframe&gt;&#xA;&lt;p&gt;In this widget, there is a signal being transmitted 2800 Hz above where we&#xA;are listening, so it&amp;rsquo;s outside of what the filter will let through and we&#xA;can&amp;rsquo;t hear it. However, the filter lets through some of the energy that comes&#xA;from the carrier being switched on and off, and it can be heard as clicks.&lt;/p&gt;&#xA;&lt;p&gt;These &amp;ldquo;key clicks&amp;rdquo; are very annoying since they could even drown out real&#xA;signals and they can often be heard quite far away from the signal&amp;rsquo;s&#xA;frequency, so if your transmitter produces clicks, other amateur radio&#xA;operators will be quick to find you to tell you what they think of your&#xA;radio transmitter.&lt;/p&gt;&#xA;&lt;p&gt;On a transmitter with a 5-millisecond ramp time there isn&amp;rsquo;t so much power&#xA;outside of the signal, so the filter doesn&amp;rsquo;t let so much energy through&#xA;and there are no clicks. Press &amp;ldquo;Play&amp;rdquo; on the widget below to hear the result:&lt;/p&gt;&#xA;&lt;iframe src=&#34;../assets/2020/morsewidget/widget.html#envelope=sine&amp;frequency=3400&#34; width=&#34;200&#34; height=&#34;300&#34; frameborder=&#34;0&#34; style=&#34;display: block; margin: auto;&#34;&gt;&lt;/iframe&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;This is the part where you can do your own experiments with key clicks.&#xA;Here are two widgets you can try: the left one is clicky, while the right&#xA;one is not clicky. Both have a slider you can use to modify the signal&amp;rsquo;s&#xA;frequency and see where the clicks are present, or where the signal is&#xA;louder than the clicks.&lt;/p&gt;&#xA;&lt;div style=&#34;margin: auto; width: fit-content;&#34;&gt;&#xA;  &lt;iframe src=&#34;../assets/2020/morsewidget/widget.html#slider=true&#34; width=&#34;200&#34; height=&#34;300&#34; frameborder=&#34;0&#34;&gt;&lt;/iframe&gt;&#xA;  &lt;iframe src=&#34;../assets/2020/morsewidget/widget.html#envelope=sine&amp;slider=true&#34; width=&#34;200&#34; height=&#34;300&#34; frameborder=&#34;0&#34;&gt;&lt;/iframe&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;Found anything interesting? Let me know what you think!&lt;/p&gt;&#xA;</description>
      <pubDate>Wed, 13 May 2020 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Mi bug más memorable</title>
      <link>https://jacobo.tarrio.org/2020/mi-bug-mas-memorable.html</link>
      <description>&lt;p&gt;Este mes se cumplen diez años de aquella vez en que mi equipo y yo pensamos&#xA;por un rato que nos íbamos a ver envueltos en un incidente diplomático&#xA;internacional.&lt;/p&gt;&#xA;&lt;p&gt;Era mayo de 2010, y hacía unos meses que me había incorporado al equipo&#xA;del servidor de gadgets de Google en Mountain View (California), procedente&#xA;de la oficina de Google en Dublín (Irlanda).&lt;/p&gt;&#xA;&lt;p&gt;Si os acordáis de iGoogle, seguro que también recordaréis los gadgets.&#xA;iGoogle era el portal personalizable de Google y tenía unas ventanitas,&#xA;llamadas &amp;ldquo;gadgets&amp;rdquo;, que los usuarios podían seleccionar y poner en su página&#xA;de iGoogle según su antojo. Había gadgets para ver el email, las noticias o&#xA;el pronóstico del tiempo, e incluso había uno que hacía conversiones de&#xA;unidades de medida (de metros a pies, de pintas a litros, etc.). Nuestro&#xA;equipo se encargaba del sistema que mostraba los gadgets a los usuarios.&lt;/p&gt;&#xA;&lt;p&gt;Un día nos llegó un aviso que decía que, en Taiwán, el gadget de conversión&#xA;de unidades no aparecía en caracteres chinos tradicionales, que son los&#xA;que se utilizan en Taiwán, sino en caracteres chinos simplificados, que&#xA;son los que se utilizan en la China continental.&lt;/p&gt;&#xA;&lt;p&gt;En aquellas fechas, Google y China no estaban atravesando el mejor momento&#xA;de su relación: unos meses antes, Google había descubierto una serie de&#xA;ataques por parte de hackers procedentes de China y anunció que cerraba&#xA;el buscador especial para China que censuraba ciertos resultados. A partir&#xA;de entonces, los usuarios chinos usarían el buscador normal sin censura.&#xA;A China no le gustó esta reacción, ni a Google le había gustado la acción&#xA;original.&lt;/p&gt;&#xA;&lt;p&gt;Con tanta tensión entre Google y China, la noticia de que los usuarios en&#xA;Taiwán veían un gadget como si estuviesen en China tampoco fue del agrado&#xA;del equipo del servidor de gadgets. ¿Tal vez había alguien en China&#xA;interceptando el tráfico de Google para Taiwán? Deseábamos de todo&#xA;corazón que no fuera cierto, y aunque no pensábamos &lt;em&gt;de verdad verdadera&lt;/em&gt;&#xA;que eso era lo que estaba pasando, necesitábamos llegar al fondo del asunto.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;El primer paso cuando se recibe un aviso de error es intentar reproducirlo:&#xA;no es posible investigar un error que no se puede ver. Sin embargo, por más&#xA;que lo intentaba, yo no era capaz de reproducir el error. Enviaba peticiones&#xA;para ver el gadget como si fuera un usuario de Taiwán, pero siempre lo veía&#xA;en chino tradicional, como si no hubiera ningún error. Lo intenté de una&#xA;y otra manera, pero sin ningún resultado.&lt;/p&gt;&#xA;&lt;p&gt;Después se me ocurrió enviar la misma petición a todos los servidores al&#xA;mismo tiempo y ver si había diferencias entre ellos. Nuestro servicio&#xA;recibía peticiones de todo el mundo, así que también teníamos servidores&#xA;repartidos por todo el planeta y las peticiones llegaban automáticamente&#xA;al servidor más cercano. Cuando yo hacía mis pruebas, mis peticiones iban&#xA;a un servidor situado en los EEUU, pero las peticiones procedentes de&#xA;Taiwán iban a un servidor ubicado en Asia. En teoría, todos los servidores&#xA;eran idénticos, pero ¿y si no lo fueran?&lt;/p&gt;&#xA;&lt;p&gt;Preparé una página web que enviaba peticiones directamente a todos los&#xA;servidores, la cargué en mi navegador, y entonces vi que algunos servidores&#xA;daban respuestas diferentes. La mayoría de los servidores en Europa y&#xA;América respondían con el gadget en chino tradicional, que era el resultado&#xA;correcto; sin embargo, la mayoría de los servidores en Asia  respondían&#xA;en chino simplificado.&lt;/p&gt;&#xA;&lt;p&gt;Para hacerlo más misterioso, no todos los servidores en cada lugar me daban&#xA;el mismo resultado: algunos daban la respuesta correcta y otros la respuesta&#xA;incorrecta, pero la proporción entre uno y otro era distinta dependiendo&#xA;de la ubicación.&lt;/p&gt;&#xA;&lt;p&gt;Después de hacer muchas pruebas, me di cuenta de que había una especie de&#xA;efecto memoria. Durante varios minutos después de enviar una petición para&#xA;ver el gadget en chino simplificado, los servidores respondían en chino&#xA;simplificado a todas las peticiones en chino tradicional. También ocurría&#xA;al contrario: tras una petición en chino tradicional, los servidores&#xA;respondían en chino tradicional a las peticiones en chino simplificado.&lt;/p&gt;&#xA;&lt;p&gt;Esto explicaba por qué la mayor parte de los servidores en Asia respondían&#xA;en chino simplificado: la mayoría de los usuarios hablantes de chino viven&#xA;en China, por lo que usan caracteres simplificados. La mayoría de las&#xA;peticiones procedentes de China iba a servidores en Asia, por lo que éstos&#xA;recibían peticiones en chino simplificado. Entonces estos servidores se&#xA;quedaban &amp;ldquo;atascados&amp;rdquo; en chino simplificado y, cuando recibían una petición&#xA;en chino tradicional, daban una respuesta en chino simplificado.&lt;/p&gt;&#xA;&lt;p&gt;Sentí un gran alivio cuando di con esta explicación, ya que significaba que&#xA;el problema no había sido causado por una acción de interceptación de&#xA;tráfico al nivel de una nación-estado, sino que era un error de programación&#xA;normal y corriente. No obstante, todavía necesitábamos arreglarlo, y los&#xA;síntomas sugerían que estaba causado por un problema con una caché.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Los gadgets estaban definidos en un fichero XML. Los gadgets también podían&#xA;estar traducidos a varios idiomas, así que el texto del gadget en cada&#xA;idioma estaba guardado en otro fichero XML, y el fichero de definición del&#xA;gadget contenía una lista que indicaba qué idioma aparecía en qué fichero&#xA;de traducción.&lt;/p&gt;&#xA;&lt;p&gt;Cada vez que alguien quería ver un gadget, el servidor tenía que descargar&#xA;el fichero XML de definición, interpretarlo, determinar qué fichero de&#xA;traducción tenía que usar, descargar el fichero XML de traducción e&#xA;interpretarlo también. Algunos gadgets tenían millones de usuarios, por&#xA;lo que el servidor tendría que descargar e interpretar los mismos ficheros&#xA;una y otra vez. Para evitarlo, el servidor de gadgets tenía una &amp;ldquo;caché&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;Una caché es una estructura de datos que almacena el resultado de una&#xA;operación para evitar tener que realizar esa operación repetidamente.&#xA;En el servidor de gadgets, la caché almacenaba ficheros XML que había&#xA;descargado e interpretado anteriormente. Cuando el servidor necesitaba&#xA;un fichero, primero miraba si ya estaba en la caché; si lo estaba, podía&#xA;utilizarlo directamente sin necesidad de descargarlo e interpretarlo.&#xA;Si no, el servidor descargaba el fichero, lo interpretaba y almacenaba&#xA;el resultado en la caché para poder utilizarlo en el futuro.&lt;/p&gt;&#xA;&lt;p&gt;Mi teoría inicial era que, de alguna manera, la caché podría estar&#xA;confundiendo los ficheros de traducción al chino simplificado y chino&#xA;tradicional. Me pasé varios días inspeccionando el código y el contenido&#xA;de la caché, pero no pude ver ningún problema. Hasta donde yo podía ver,&#xA;la caché de ficheros XML estaba implementada correctamente y funcionaba&#xA;perfectamente. Si no fuera porque podía verlo con mis propios ojos, habría&#xA;jurado que era imposible que el gadget mostrara el idioma incorrecto.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Durante el tiempo que invertí en inspeccionar el código, también estuve&#xA;intentando reproducir el problema en mi ordenador. Los servidores de&#xA;producción se quedaban &amp;ldquo;atascados&amp;rdquo; durante unos minutos en chino simplificado&#xA;o chino tradicional, pero esto no sucedía nunca cuando ejecutaba el servidor&#xA;en mi ordenador: si enviaba peticiones mezcladas, también recibía respuestas&#xA;mezcladas. Por lo tanto, una vez más, no podía reproducir el problema de&#xA;forma controlada.&lt;/p&gt;&#xA;&lt;p&gt;Por este motivo tomé una decisión drástica: iba a conectar un depurador a&#xA;un servidor en la red de producción y reproducir el error allí.&lt;/p&gt;&#xA;&lt;p&gt;A buen seguro, no iba a hacer eso con un servidor &lt;em&gt;de producción&lt;/em&gt;. En aquel&#xA;momento teníamos varios tipos de servidores: no sólo servidores de producción,&#xA;que recibían las peticiones procedentes de usuarios normales; también teníamos&#xA;servidores &amp;ldquo;sandbox&amp;rdquo;, que no tenían usuarios externos, sino que los usábamos&#xA;para que iGoogle y otros servicios que usaban gadgets pudiesen hacer pruebas&#xA;sin afectar a los usuarios. Yo no iba, ni de lejos, a conectar un depurador&#xA;a un servidor de producción y arriesgarme a afectar a usuarios externos;&#xA;lo haría todo en un servidor &amp;ldquo;sandbox&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;Así pues, elegí uno de los servidores &amp;ldquo;sandbox&amp;rdquo;, lo preparé, le conecté un&#xA;depurador, reproduje el error, lo investigué y, finalmente, dejé todo como&#xA;estaba antes. Tras mi investigación confirmé que, como yo pensaba, era un&#xA;problema de caché, pero no el problema de caché que yo me esperaba.&lt;/p&gt;&#xA;&lt;p&gt;Según mi teoría, el programa acudiría a la caché para obtener el fichero&#xA;con la traducción al chino tradicional pero la caché respondería con el&#xA;fichero incorrecto. Mi intención era interrumpir el programa justo antes&#xA;de que solicitara el fichero XML y ver qué ocurría. Para mi sorpresa, la&#xA;caché funcionó correctamente: el programa solicitó el fichero con la&#xA;traducción al chino tradicional y la caché proporcionó la traducción al&#xA;chino tradicional, tal como debía ser. Obviamente, el problema tenía que&#xA;estar en otro sitio.&lt;/p&gt;&#xA;&lt;p&gt;Después de obtener la traducción, el programa la aplicó al gadget. En los&#xA;gadgets con traducciones, el fichero de definición no incluía ningún texto&#xA;en ningún idioma; en su lugar, incluía unas marcas que el servidor sustituía&#xA;por el texto que aparecía en el fichero de traducción. Y, en efecto, eso es&#xA;lo que hizo el servidor: tomó el fichero XML de definición del gadget,&#xA;buscó las marcas y, dondequiera que hubiese una marca, la sustituyó por el&#xA;correspondiente texto en chino tradicional.&lt;/p&gt;&#xA;&lt;p&gt;El siguiente paso era interpretar el fichero XML resultante.&lt;/p&gt;&#xA;&lt;p&gt;Había muchísima gente que utilizaba el gadget de conversión de unidades&#xA;traducido a caracteres chinos tradicionales. Esto significa que, después&#xA;de sustituir las marcas por texto en chino, el servidor tendría que&#xA;interpretar el mismo código XML resultante una y otra vez. Ya que el servidor&#xA;tenía que interpretar el mismo código varias veces al día, éste utilizaba&#xA;una caché para ahorrarse todo ese trabajo redundante, y hasta ese momento&#xA;yo no tenía ni idea de que esa caché existía.&lt;/p&gt;&#xA;&lt;p&gt;Ésta era la caché que daba el resultado incorrecto: la caché recibía el&#xA;fichero XML con textos en chino tradicional y devolvía el resultado de&#xA;interpretar el mismo fichero XML con textos en chino simplificado.&lt;/p&gt;&#xA;&lt;p&gt;Lo que necesitaba determinar era por qué sucedía eso.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Las cachés funcionan asociando una clave a un valor. Por ejemplo, la primera&#xA;caché de la que hablé en este artículo, que evitaba tener que descargar e&#xA;interpretar ficheros XML repetidamente, usaba el URL del fichero como clave&#xA;y el fichero interpretado como valor.&lt;/p&gt;&#xA;&lt;p&gt;La nueva caché, que se utilizaba para evitar tener que interpretar&#xA;repetidamente ficheros de definición con una traducción aplicada, usaba&#xA;de clave el fichero XML representado como un array de bytes. Para obtener&#xA;la clave, el servidor llamaba a la función&#xA;&lt;a href=&#34;https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#getBytes()&#34;&gt;&lt;code&gt;String.getBytes()&lt;/code&gt;&lt;/a&gt;,&#xA;que convierte una cadena a un array de bytes utilizando la codificación&#xA;predeterminada.&lt;/p&gt;&#xA;&lt;p&gt;En mi ordenador, la codificación predeterminada era UTF-8. Esta codificación&#xA;representa cada carácter chino en dos o tres bytes. Por ejemplo, UTF-8&#xA;representa el texto &amp;ldquo;你好&amp;rdquo; como los bytes&#xA;&lt;code&gt;{0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd}&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;En los servidores, sin embargo, la codificación predeterminada era US-ASCII.&#xA;Esta codificación es muy antigua (1963) y sólo contempla el alfabeto latino&#xA;utilizado en el idioma inglés, por lo que no puede codificar los caracteres&#xA;chinos. Cuando &lt;code&gt;getBytes()&lt;/code&gt; encuentra un carácter que no puede codificar,&#xA;lo sustituye por un signo de interrogación. Por lo tanto, el texto &amp;ldquo;你好&amp;rdquo;&#xA;queda convertido en “??”.&lt;/p&gt;&#xA;&lt;p&gt;Y aquí estaba el problema: cuando el servidor, que utilizaba US-ASCII,&#xA;generaba una clave, ésta consistía en un fichero XML con cada carácter&#xA;chino sustituido por un signo de interrogación. Como las traducciones&#xA;al chino simplificado y chino tradicional usaban el mismo número de&#xA;caracteres, aunque éstos fuesen distintos, las claves resultaban ser&#xA;idénticas, por lo que el servidor respondía con el valor que había&#xA;en la caché aunque fuese en la variante de chino incorrecta.&lt;/p&gt;&#xA;&lt;p&gt;En cambio, este problema no era reproducible en mi ordenador, ya que&#xA;usaba UTF-8, que sí contempla los caracteres chinos. Por lo tanto,&#xA;las claves eran diferentes y la caché respondía con el valor correcto.&lt;/p&gt;&#xA;&lt;p&gt;Después de varias semanas probando esto y aquello, inspeccionando el&#xA;código, batiéndome en vano con la caché y, finalmente, tomando medidas&#xA;desesperadas, la solución a este error consistió en sustituir todas&#xA;las llamadas a &lt;code&gt;getBytes()&lt;/code&gt; para usar la codificación UTF-8 de forma&#xA;explícita.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Esta historia comenzó como una intriga de espionaje a nivel internacional&#xA;y terminó cambiando una función para especificar la codificación.&#xA;Supongo que es un final un poco anticlimático, pero al menos todos los&#xA;miembros del equipo estábamos contentos de no tener que acudir a&#xA;testificar al Congreso de los EEUU ni nada parecido.&lt;/p&gt;&#xA;&lt;p&gt;Aún así, este episodio grabó a fuego en mi mente la importancia de&#xA;especificar siempre todos los parámetros de los que depende el programa&#xA;y no dejar nada implícito o dependiente del entorno. Nuestro servidor&#xA;falló porque dependía de un parámetro de configuración que era diferente&#xA;en nuestros ordenadores y en producción; si hubiésemos especificado la&#xA;codificación UTF-8 de forma explícita desde el principio, esto nunca&#xA;nos habría pasado.&lt;/p&gt;&#xA;&lt;p&gt;Y yo no tendría una historia tan chula para contar.&lt;/p&gt;&#xA;&lt;p&gt;No se puede tener todo.&lt;/p&gt;&#xA;</description>
      <pubDate>Sat, 02 May 2020 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Una antena casera de onda corta para radioaficionados</title>
      <link>https://jacobo.tarrio.org/2020/antena-de-onda-corta.html</link>
      <description>&lt;p&gt;Un día, en el verano de 2002, estaba navegando por Internet y caí en una&#xA;página web en la que un radioaficionado describía sus experimentos haciendo&#xA;rebotar ondas de radio en la Luna. Esa página me tuvo cautivo durante varias&#xA;horas viendo fotos de sus &lt;a href=&#34;http://web.archive.org/web/20200429021909/https://hb9q.ch/2018/&#34;&gt;gigantescas antenas&lt;/a&gt;,&#xA;vídeos en los que enviaban código Morse y recibían el eco&#xA;&lt;a href=&#34;https://www.youtube.com/watch?v=JbruflE27_8&#34;&gt;dos segundos y medio después&lt;/a&gt;,&#xA;e informes sobre cómo dos radioaficionados en extremos opuestos del mundo&#xA;se comunicaban usando la Luna como reflector.&lt;/p&gt;&#xA;&lt;p&gt;Como soy un pedazo de friki, durante los siguientes días pensé en lo&#xA;interesante que sería sacarme la licencia de radioaficionado e instalar&#xA;una pequeña estación de radio en casa (no tan potente como para hacer rebotar&#xA;señales en la Luna, pero sí para comunicarme con otros países). Sin embargo,&#xA;aquél no era el mejor momento para esas ideas: estaba intentando terminar la&#xA;carrera y tenía varias asignaturas pendientes para setiembre, por lo que mis&#xA;padres no verían con buenos ojos esa nueva distracción. Además, entonces era&#xA;necesario aprender código Morse para obtener la licencia. Por no hablar de la&#xA;antena; seguro que necesitaría una antena muy grande. Quita, quita&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;../assets/2020/torre-antenas.jpg&#34; alt=&#34;Foto de una torre de antenas de radioaficionado&#34;&gt;&lt;span class=&#34;imageCaption&#34;&gt;Una torre de antenas de radioaficionado construida sobre una casa en Palo&#xA;Alto (California). En lo más alto tiene una antena omnidireccional para VHF&#xA;y UHF; debajo, un dipolo, posiblemente para 20 metros; debajo del dipolo,&#xA;una antena direccional tipo Yagi, probablemente para 10 metros. El dipolo y&#xA;la antena Yagi se pueden girar con un motor para orientarlas hacia otras&#xA;antenas distantes.&lt;/span&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Varios años después, en 2013 y 2014, se popularizó el uso de descodificadores&#xA;de TDT como receptores de radio definidos por software (SDR). Estos&#xA;descodificadores tienen un modo especial en el que pueden recibir ondas de&#xA;radio, digitalizarlas y pasarlas al ordenador para que éste las procese.&#xA;Con este sistema es muy fácil experimentar con radio y, en muy poco tiempo,&#xA;mucha gente hizo programas para escuchar la radio, recibir transmisiones&#xA;ADS-B procedentes de aviones, recibir fotos meteorológicas directamente del&#xA;satélite, etc.&lt;/p&gt;&#xA;&lt;p&gt;Pronto cayó en mis manos uno de estos descodificadores e hice una &lt;a href=&#34;https://github.com/google/radioreceiver&#34;&gt;app de&#xA;Chrome para escuchar la radio&lt;/a&gt;.&#xA;No tardé mucho tiempo en añadirle la capacidad de escuchar transmisiones de&#xA;radioaficionados y, poco tiempo después, se me ocurrió otra vez la idea de&#xA;sacarme la licencia. Me compré los libros, estudié (ya no era necesario&#xA;aprender Morse) y en 2015 obtuve licencias en los EEUU y en España.&#xA;Ahora realizo contactos por onda corta&#xA;&lt;a href=&#34;https://drive.google.com/open?id=1eq3c8n1HzD49JQFdfDwgEW1igC9HGmoi&amp;amp;usp=sharing&#34;&gt;con un montón de radioaficionados&lt;/a&gt;,&#xA;y, para mi alivio, no he tenido que instalar una antena gigantesca.&lt;/p&gt;&#xA;&lt;p&gt;La mayoría de los radioaficionados no nos podemos permitir tener una torre&#xA;como la de la foto de arriba (¡ojalá!). Muchos vivimos en pisos de alquiler,&#xA;o los códigos urbanísticos no permiten este tipo de estructuras, o no tenemos&#xA;espacio, o&amp;hellip; miles de razones. Por este motivo, la historia de la&#xA;radioafición es también la historia de la búsqueda de antenas compactas&#xA;que se puedan utilizar en espacios limitados.&lt;/p&gt;&#xA;&lt;p&gt;Durante estos años he probado a construir y utilizar varias antenas. Cuando&#xA;vivía en California vivía en un bajo con un balcón cubierto, así que construí&#xA;una &lt;a href=&#34;https://photos.app.goo.gl/kzt8jhXNR1NUp1vu7&#34;&gt;antena de bucle magnético&lt;/a&gt;.&#xA;Estas antenas son muy compactas pero también son engorrosas; tienen muy poco&#xA;ancho de banda, así que es necesario reajustarlas cada vez que uno cambia&#xA;de frecuencia.&lt;/p&gt;&#xA;&lt;p&gt;Después de mudarme a Nueva York tuve un patio de 6 por 6 metros con un árbol,&#xA;así que me construí una antena vertical hecha con cables eléctricos. Esta&#xA;antena es multibanda, portátil y fácil de utilizar, y es la que os voy a&#xA;describir en este artículo.&lt;/p&gt;&#xA;&lt;h1&gt;Descripción de la antena&lt;/h1&gt;&#xA;&lt;p&gt;Mi antena vertical consta de varios elementos: un &lt;em&gt;radiador&lt;/em&gt;, seis &lt;em&gt;radiales&lt;/em&gt;,&#xA;un &lt;em&gt;acoplador&lt;/em&gt;, un &lt;em&gt;unun&lt;/em&gt; y, por supuesto, un cable coaxial que la conecta al&#xA;transmisor de radio.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;../assets/2020/antena-vertical-casera.svg&#34; alt=&#34;Diagrama de mi antena vertical&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;El radiador es el componente que asciende verticalmente y da el nombre de&#xA;&amp;ldquo;vertical&amp;rdquo; a la antena. Está formado por un cable eléctrico de unos 15 metros&#xA;de largo. Cualquier cable eléctrico sirve, pero es conveniente elegir uno que&#xA;sea ligero y resistente, ya que va a estar colgado de un árbol y va a sufrir&#xA;la acción del viento y del sol.&lt;/p&gt;&#xA;&lt;p&gt;Los radiales son seis cables eléctricos de cinco metros de largo que forman&#xA;el &amp;ldquo;plano de tierra&amp;rdquo; de la antena. Todos ellos reposan directamente en el&#xA;suelo, están conectados en un solo punto y luego salen en línea recta en&#xA;todas las direcciones, como los radios de una rueda de bicicleta. Igual&#xA;que para el radiador, cualquier cable sirve.&lt;/p&gt;&#xA;&lt;p&gt;El acoplador hace ajustes para que la diferencia de impedancia entre la&#xA;antena y la línea de transmisión no afecte negativamente a la radio. El&#xA;acoplador es opcional, pero entonces tendréis que cortar el radiador a una&#xA;longitud específica para que funcione en una banda. El acoplador os&#xA;permite utilizar la antena en las bandas de 20, 40 y 60 metros, entre otras.&lt;/p&gt;&#xA;&lt;p&gt;Finalmente, el &lt;em&gt;unun&lt;/em&gt; se encarga de eliminar interferencias de la línea&#xA;de transmisión. Al transmitir, la antena induce corrientes en el cable&#xA;coaxial que viene de la radio y el acoplador detecta esas corrientes, piensa&#xA;que son debidas a un desajuste en la antena, e intenta arreglarlo, con lo&#xA;que se desajusta por completo. El &lt;em&gt;unun&lt;/em&gt; elimina esas corrientes antes de&#xA;que lleguen al acoplador, por lo que éste funciona correctamente.&lt;/p&gt;&#xA;&lt;p&gt;&lt;span class=&#34;multipleImgs&#34;&gt;&lt;img src=&#34;../assets/2020/cable-de-antena.jpg&#34; alt=&#34;El cable que uso para el radiador&#34;&gt;&lt;img src=&#34;../assets/2020/conexiones-acoplador.jpg&#34; alt=&#34;Conexiones del radiador y radiales al acoplador&#34;&gt;&lt;img src=&#34;../assets/2020/antena.jpg&#34; alt=&#34;Acoplador, unun, radiales&#34;&gt;&lt;/span&gt;&lt;span class=&#34;imageCaption&#34;&gt;&lt;em&gt;Primera foto&lt;/em&gt;: el cable que utilizo para el radiador. Es un cable trenzado&#xA;de acero revestido de cobre diseñado específicamente para antenas portátiles,&#xA;pero sirve cualquier tipo de cable que sea lo suficientemente ligero y&#xA;resistente.&#xA;&lt;br&gt;&#xA;&lt;em&gt;Segunda foto&lt;/em&gt;: detalle de la conexión del radiador y los radiales al&#xA;acoplador. Utilizo un adaptador de bornes para conectar la antena; el&#xA;radiador está conectado al borne rojo y los radiales al borne negro.&#xA;El adaptador de bornes es opcional: podría haber enchufado el radiador&#xA;directamente al centro del conector &amp;ldquo;Ant&amp;rdquo; y los radiales al tornillo &amp;ldquo;Gnd&amp;rdquo;.&#xA;&lt;br&gt;&#xA;&lt;em&gt;Tercera foto&lt;/em&gt;: el acoplador&#xA;(&lt;a href=&#34;https://ldgelectronics.com/index.php/products/zero-power/z-11pro/&#34;&gt;LDG Z11 Pro II&lt;/a&gt;,&#xA;caja negra con botones grises) y el &lt;em&gt;unun&lt;/em&gt;&#xA;(&lt;a href=&#34;https://ldgelectronics.com/index.php/products/accessories/baluns/&#34;&gt;LDG RU-1:1&lt;/a&gt;,&#xA;caja azul) después de haber hecho todas las conexiones. Los radiales están&#xA;extendidos en todas las direcciones. La caja negra con la etiqueta blanca&#xA;contiene 8 pilas tipo AA para proporcionar 12 voltios al acoplador.&lt;/span&gt;&#xA;&#xA;&#xA;&lt;/p&gt;&#xA;&lt;h1&gt;Cómo utilizar la antena&lt;/h1&gt;&#xA;&lt;p&gt;Para usar esta antena es necesario colgar el radiador de un árbol, un poste&#xA;o una estructura similar. Preferiblemente, esa estructura debería estar hecha&#xA;de un material no conductor. Por ejemplo, una caña de pescar hecha de&#xA;fibra de vidrio serviría, pero no una caña de pescar hecha de fibra de&#xA;carbono.&lt;/p&gt;&#xA;&lt;p&gt;Para colgar la antena yo utilizo un sedal y un peso de plomo. Primero ato&#xA;el plomo al sedal, lo voleo como si fuera una honda y lo arrojo por encima&#xA;de la copa del árbol. Con un poco de suerte, el plomo irá suficientemente&#xA;alto, no chocará con ninguna rama y aterrizará al otro lado del árbol.&lt;/p&gt;&#xA;&lt;p&gt;Hay algo de peligro de que el sedal se enrede en las ramas del árbol.&#xA;En mi experiencia, esto ocurre principalmente si interfiero con el vuelo&#xA;del plomo. Si lo lanzo y luego no toco el sedal hasta que el plomo llegue&#xA;al suelo, casi siempre irá todo bien. Si lo lanzo y luego agarro el sedal para&#xA;intentar controlar el vuelo del plomo, hay mucho peligro de que el plomo&#xA;haga un extraño alrededor de una rama y se quede atascado.&lt;/p&gt;&#xA;&lt;p&gt;Después de haber arrojado el sedal por encima del árbol puedo ir al plomo,&#xA;desatarlo del sedal, atar la punta del radiador al sedal y luego izar el&#xA;radiador. Después de esto sólo queda conectar el radiador y los radiales al&#xA;acoplador, éste al &lt;em&gt;unun&lt;/em&gt; y finalmente conectarlo al transmisor con un cable&#xA;coaxial.&lt;/p&gt;&#xA;&lt;p&gt;Los dibujos de debajo muestran distintas posibles configuraciones de la&#xA;antena que podéis usar dependiendo de cuánto espacio tengáis, dónde estén&#xA;vuestros árboles, etc. Fijaos en que un extremo del sedal está atado al&#xA;radiador pero el otro extremo no está atado a ningún punto fijo, sino a un&#xA;peso que lo mantiene tenso pero lo permite oscilar. Esto es importante&#xA;porque el árbol y el radiador van a sufrir los embates del viento y se&#xA;menearán en todas direcciones. Si atáis el sedal, es posible que, al&#xA;moverse, tire del radiador demasiado fuerte y lo rompa. Al usar un peso&#xA;que oscila libremente, no hay peligro de que esto ocurra.&lt;/p&gt;&#xA;&lt;p&gt;Si vuestro radiador es más pesado que el mío, es muy probable que un sedal no&#xA;sea lo suficientemente fuerte para izarlo. En ese caso deberíais usar un&#xA;cordel más resistente; por ejemplo, una línea de arborista con su&#xA;correspondiente peso.&lt;/p&gt;&#xA;&lt;p&gt;&lt;span class=&#34;multipleImgs&#34;&gt;&lt;img src=&#34;../assets/2020/esquema-antena-vertical-1.svg&#34; alt=&#34;Radiador colgado de un árbol alto&#34;&gt;&lt;img src=&#34;../assets/2020/esquema-antena-vertical-2.svg&#34; alt=&#34;Radiador colgado de un árbol distante&#34;&gt;&lt;img src=&#34;../assets/2020/esquema-antena-vertical-3.svg&#34; alt=&#34;Radiador con una pata colgante&#34;&gt;&lt;img src=&#34;../assets/2020/esquema-antena-vertical-4.svg&#34; alt=&#34;Radiador en zig-zag&#34;&gt;&lt;/span&gt;&lt;span class=&#34;imageCaption&#34;&gt;En condiciones ideales, con un árbol suficientemente alto y suficientemente&#xA;cerca, el radiador colgará verticalmente o casi verticalmente (primera figura).&#xA;No siempre tenemos condiciones ideales, así que a veces hay que hacer&#xA;adaptaciones. Por ejemplo, si el árbol está un poco lejos, podemos inclinar&#xA;el radiador (segunda figura). Si el árbol no es lo bastante alto, tal vez&#xA;tengamos que pasar parte del radiador por encima de la rama y dejarlo&#xA;colgando de ésta (tercera figura). En ciertos casos, tal vez tengamos que&#xA;dejar que el radiador zigzaguee por todo el sitio (cuarta figura).&lt;/span&gt;&#xA;&#xA;&#xA;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Los radiales deberían salir de debajo del radiador y extenderse en línea&#xA;recta y en todas direcciones. Si no tenéis espacio, tal vez necesitéis&#xA;apañároslas de una u otra manera. Al final, lo más importante es que&#xA;el plano de tierra sea lo más tupido, simétrico y uniforme posible,&#xA;añadiendo radiales y alargándolos donde podáis o acortándolos donde no os&#xA;quede más remedio.&lt;/p&gt;&#xA;&lt;p&gt;&lt;span class=&#34;multipleImgs&#34;&gt;&lt;img src=&#34;../assets/2020/radiales-1.svg&#34; alt=&#34;Seis radiales&#34;&gt;&lt;img src=&#34;../assets/2020/radiales-2.svg&#34; alt=&#34;Plegando los radiales en un espacio limitado&#34;&gt;&lt;img src=&#34;../assets/2020/radiales-3.svg&#34; alt=&#34;Añadiendo radiales para aprovechar el espacio&#34;&gt;&lt;/span&gt;&lt;span class=&#34;imageCaption&#34;&gt;Lo ideal sería distribuir los radiales de una forma simétrica y uniforme&#xA;(primera figura). Si el espacio es limitado, siempre podéis doblar y plegar&#xA;los radiales un poco para adaptarlos al sitio, aunque es conveniente que&#xA;vayan lo más rectos posible (segunda figura). Ante la duda, lo mejor es&#xA;poner muchos radiales para llenar el espacio disponible (tercera figura).&lt;/span&gt;&#xA;&#xA;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Dependiendo de la configuración del radiador y de los radiales, el patrón&#xA;de radiación de la antena tendrá una u otra forma, así que sería inútil&#xA;tratar de caracterizarlo en esta página web, pero en general, la máxima&#xA;ganancia tiende a ser perpendicular al radiador y, si los radiales no son&#xA;simétricos, habrá menos ganancia en la dirección en la que haya menos&#xA;radiales o éstos sean menos densos.&lt;/p&gt;&#xA;&lt;p&gt;Por lo tanto, si queréis una antena omnidireccional con buen DX, tratad&#xA;de colgar el radiador más vertical que podáis y colocad los radiales lo&#xA;más simétricos que podáis.&lt;/p&gt;&#xA;&lt;h1&gt;Conclusiones&lt;/h1&gt;&#xA;&lt;p&gt;He estado utilizando y mejorando poco a poco esta antena desde que me&#xA;mudé a Nueva York. Como está hecha con cables eléctricos, es muy portátil,&#xA;así que he podido utilizarla en mi patio en Brooklyn (6 por 6 metros&#xA;con un árbol de unos 8 metros) y también en la casa de mis suegros en&#xA;Connecticut (muchísimo espacio y un árbol de unos 12 metros). Gracias al&#xA;acoplador, puedo utilizarla en las bandas de 20, 40 y 60 metros, así&#xA;como otras bandas a las que no presto mucha atención.&lt;/p&gt;&#xA;&lt;p&gt;En Nueva York casi siempre hago un zig-zag con el radiador y tengo que&#xA;doblar los radiales un poco. Hay edificios justo al norte de la antena,&#xA;así que no puedo recibir en esa dirección. Además, siendo Nueva York, hay&#xA;un montón de ruido eléctrico. Aún así, he podido comunicarme con Brasil y&#xA;con Polonia usando 50 vatios.&lt;/p&gt;&#xA;&lt;p&gt;En Connecticut el radiador va completamente extendido pero inclinado hacia&#xA;el norte, y el suelo también hace una ligera pendiente ascendente hacia el&#xA;noroeste, por lo que también me es difícil hacer contactos en esas&#xA;direcciones. Sin embargo, consigo muchos contactos con las islas del Caribe&#xA;y con Europa. Mis contactos más lejanos son Serbia y Argentina, también con&#xA;50 vatios, aunque he podido, en ocasiones, oir estaciones australianas.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;../assets/2020/contactos.png&#34; alt=&#34;Contactos realizados con mi antena vertical a día de hoy&#34;&gt;&lt;span class=&#34;imageCaption&#34;&gt;Contactos realizados con mi antena vertical. Los puntos azules son contactos&#xA;realizados desde Brooklyn; los puntos marrones son contactos realizados desde&#xA;Connecticut. También podéis ver el&#xA;&lt;a href=&#34;https://drive.google.com/open?id=1eq3c8n1HzD49JQFdfDwgEW1igC9HGmoi&amp;amp;usp=sharing&#34;&gt;mapa actualizado con los últimos datos&lt;/a&gt;.&#xA;(Mapa proporcionado por Google).&lt;/span&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;En el futuro cercano tengo pensado duplicar el número de radiales, de seis a&#xA;doce. Esto debería mejorar el rendimiento de mi antena en 1 o 2 decibelios.&#xA;También tengo interés en probar un poste extensible de fibra de vidrio (una&#xA;&amp;ldquo;caña de pescar&amp;rdquo;) que me permita extender el radiador verticalmente sin&#xA;necesidad de un árbol.&lt;/p&gt;&#xA;&lt;p&gt;¡No dudéis en poneros en contacto conmigo si tenéis preguntas o sugerencias,&#xA;o si queréis planear un contacto por radio!&lt;/p&gt;&#xA;</description>
      <pubDate>Wed, 29 Apr 2020 00:00:00 +0000</pubDate>
    </item>
  </channel>
</rss>