Liberado PHP Litipk BigNumbers 0.3

NumerosHoy he liberado la versión 0.3 de una pequeña biblioteca de código PHP para manejar números grandes que llevo desarrollando desde hace unas semanas en algunos de mis ratos libres.

Por el momento la biblioteca incorpora tres clases relevantes: Decimal, que sirve para tratar con números en base decimal con precisión arbitraria; Infinite, que sirve para manipular cantidades infinitas (para los más teóricos, lo siento, no distingue entre cardinalidades infinitas); y NaN, que representa el típico "Not a Number" resultado de operaciones prohibidas.

La biblioteca tiene como requerimientos que la versión de PHP sea mayor o igual a 5.4.0, pero también soporta HHVM :) .

Respecto a por qué la he creado si ya existen las extensiones BCMath y GMP... bien: a BCMath le faltan métodos importantes y su API no es demasiado cómoda. En cuanto a GMP, al menos hasta la versión 5.5 de PHP, solo soporta aritmética de enteros, y además no trata con objetos sino con recursos no serializables.

¿Y por qué no he aprovechado una biblioteca preexistente en Packagist o PEAR? Pues porque las que ya existían antes no se tomaban demasiado en serio los casos poco frecuentes. Y porque quería experimentar con Composer y Packagist ^^U .

Bueno, ya está bien de cháchara. Aquí os dejo el enlace al proyecto en Github: https://github.com/Litipk/php-bignumbers .

Si estáis interesados en contribuir código estaré encantado :) . Todavía no considero el desarrollo acabado, y tengo bastantes ideas para mejorar esta biblioteca y hacerla más útil y potente.

Combatiendo malas prácticas en PHP (II)

ElephpantEn este segundo artículo de la serie sobre malas prácticas de programación en PHP nos centraremos en el abuso de los métodos estáticos y sus consecuencias.

Hoy en día es habitual que la mayoría de proyectos de programación web (PHP) realicen un uso extensivo de clases estáticas. Esto no pasa  solo en proyectos cerrados, esta práctica también es común en grandes frameworks y bibliotecas de código abierto.

Como no puede ser de otra manera, esta práctica tiene muchos defensores acérrimos. Los argumentos que esgrimen para defenderla se basan principalmente en dos supuestos:

  1. Las llamadas a métodos estáticos son ligeramente más rápidas que las llamadas a métodos de objetos.
  2. Una gran parte de los métodos son utilidades vagamente relacionadas y que no justifican la creación de objetos para poder usarlos.

Aunque algo de cierto hay en esos puntos, no justifican ni de lejos que esta forma de programar sea tan habitual. En primer lugar se exagera la importancia de sus ventajas, y además se omiten deliberadamente las consecuencias negativas que puede tener esta manera de proceder.

¿Estás seguro de querer usar métodos estáticos?

Abusar de los métodos estáticos nos llevará a un diseño pobre que muy probablemente tendrá repercusiones negativas en el rendimiento del software, hasta el punto de contrarrestar con creces casi cualquier beneficio que estos nos pudieran aportar.

Las globales y los métodos estáticos son amigos

La experiencia me ha demostrado que cuantos más métodos estáticos se usan en un proyecto de software, más proclive es este mismo proyecto a usar variables globales sin ningún tipo de control.

Sin más datos que estos se podría pensar que no existe relación directa entre un hecho y otro y que puede haber una causa común a ambos. Sin embargo, si analizamos el código no solo en un momento dado, sino a medida que evoluciona, podremos advertir un patrón que (al menos a mi) me lleva a pensar que la primera práctica ayuda a que se produzca la segunda.

  1. Al ser estáticos los métodos, estos no pueden tratar directamente con el estado encapsulado propio de un objeto.
  2. Una "solución" recurrente para el punto anterior suele ser el aumento del número medio de parámetros de los métodos.
  3. Algunos programadores, que tienen la noción más o menos acertada de que ciertos parámetros deberían estar agrupados, tenderán a usar arrays asociativos u objetos dummy¹ como parámetros para sus métodos con el objetivo de simplificar sus declaraciones.
  4. La gran versatilidad de los arrays asociativos y de los objetos de PHP² anima a algunos desarrolladores a considerarlos como un mero saco de datos en el que se puede meter prácticamente cualquier cosa. Esto desemboca en una alta cohesión coincidente (es decir, una cohesión prácticamente nula).
  5. Llegados a este punto, esos "sacos de datos" acaban dando al traste con los principios de encapsulación y de independencia entre módulos. Los efectos colaterales pasan a ser el orden del día. Solo hace falta un becario inexperto para hacer explotar el polvorín. Usar variables globales o pasar esas estructuras de datos como parámetros pasa a ser equivalente en términos de "peligrosidad". La puerta al abuso de variables globales se acaba de abrir de par en par.

Respecto a los puntos 2 y 3, en breve veremos como se dan no solo gracias al primer hecho de la lista, las limitaciones arquitecturales a que nos lleva el abuso de métodos estáticos también contribuyen.

En cuanto a la quinta afirmación, se debe matizar que no solo se usan variables globales, sino también propiedades estáticas de clases, que vienen a ser como las variables globales solo un poco más encapsuladas.

Adiós polimorfismo, adiós patrones, adiós reusabilidad

Usar las clases como meros espacios de nombres para los métodos estáticos nos impide aprovechar mucha de la "sabiduría" acumulada en forma de patrones de diseño dentro de la comunidad programadora.

El motivo esencial es que PHP es un lenguaje orientado a objetos, no a clases. Y evidentemente muchas estrategias que se han desarrollado a lo largo de los años tienen en cuenta las capacidades del lenguaje para trabajar con objetos.

¿Y qué? Pues que tenemos como efecto neto que se  duplicará código y probablemente se aplicarán antipatrones en nuestro proyecto.

No tiene mucho sentido escribir más sobre esto, ya que Kore Nordmann se explaya profusamente sobre los problemas de este tipo asociados a los métodos estáticos en un artículo interesantísimo (para el que quiera ampliar).

Imitaciones baratas: sucedáneo de polimorfismo con static late bindings e introspección

Los mecanismos de reflexión o introspección son una herramienta realmente poderosa e interesante, pero también peligrosa. Facilitan enormemente la introducción de bugs prácticamente indetectables, así como potenciales problemas de seguridad graves.

A cambio facilitan tareas como la creación de enrutadores para urls o uris en patrones MVC, inyección de dependencias bajo demanda y seguramente algunos pocos casos más que realmente donde realmente valga la pena aplicarlos.

Desgraciadamente a más de uno se le ocurrió que uno de esos casos "que valen la pena" es imitar el polimorfismo combinando los mecanismos de introspección con los static late bindings de PHP y sus métodos mágicos.

No explicaré como se hace porque es relativamente sencillo si se lee la documentación de los enlaces que he dejado aquí. Lo relevante del caso es que se puede conseguir un sucedáneo que nos permita implementar algunos de los patrones que anteriormente indiqué que serían inalcanzables por abuso de métodos estáticos. Y habrá quien cante victoria por ello, pobres desgraciados.

No os quepa duda, el precio a pagar por ello es muy alto. Se hace casi imposible depurar el funcionamiento de nuestra aplicación o detectar código muerto, y además hay una fuerte penalización en rendimiento por el uso de introspección y el enlazado estático en tiempo de ejecución. ¡Espera! ¿Pero no habíamos empezado a usar métodos estáticos porque eran más rápidos que los de los objetos instanciados?

Notas

  1. Objetos sin lógica asociada, o bien con todas sus propiedades públicas, o con getters y setters como sus únicos métodos.
  2. A través de sus "métodos mágicos" PHP permite crear, escribir y leer propiedades no declaradas en la definición de la clase del objeto ni en ninguna de sus clases o interfaces base.

Otros artículos de la serie

  1. Combatiendo malas prácticas en PHP (I)

Combatiendo malas prácticas en PHP (I)

ElephpantA estas alturas muchos ya hemos asumido que en PHP (por lo general) se programa mal. No se trata de una sensación ni de una opinión, es un hecho constatado.

A esto han contribuido muchos factores: entre ellos están la alta demanda de desarrolladores web, la baja cantidad de personas con conocimientos avanzados y el gran número de defectos del lenguaje.

Respecto al último punto, es cierto que PHP ha mejorado mucho en los últimos años (las versiones 5.3, 5.4 y 5.5, sobretodo la primera, han supuesto verdaderos saltos cualitativos), pero una proporción importante de empresas y programadores no han empezado todavía a sacarles partido.

El primer factor no es realmente un problema (aunque tenga sus consecuencias), y el segundo es aquel en el que me quiero centrar con esta serie de artículos. Mi objetivo es analizar una lista relativamente exhaustiva de malas prácticas, explicar por qué motivo las considero poco recomendables, y algunas técnicas para mejorar código con los problemas mencionados.

Antes de empezar con el material más denso, considero que es preferible hacer una pequeña pausa y dedicar un poco de nuestro tiempo al estilo de nuestro código.

Sin tener que entrar en técnicas de programación, arquitectura de software o patrones de diseño, hay una base enorme de código PHP que podría ser mejorada sustancialmente solamente cambiando su estilo.

"Estándares" PSR-0, PSR-1, PSR-2 y PSR-3

Hace relativamente poco se creó el PHP Framework Interop Group (http://www.php-fig.org/), con el objetivo de establecer una serie de convenios que permitiesen una mayor interoperabilidad entre frameworks PHP.

De su iniciativa surgieron 4 especificaciones (PSR-0, PSR-1, PSR-2 y PSR-3), que aunque no son un estándar propiamente dicho, sí lo están empezando a ser de facto (y pretendo ayudar a ello con este artículo).

Seguirlas a pies juntillas no le hará a nadie ser mejor programador (para eso hace falta mucho esfuerzo y dedicación, no seguir ciegamente el dictado de otros), pero hará más difícil que incurra en malas prácticas y facilitará la colaboración con otros programadores.

  • La especificación PSR-0 está centrada en la autocarga de clases. Esto afecta a nomenclatura de clases, ficheros y directorios, obligando a una cierta normalización y mejor organización del código del proyecto (a nivel de ficheros y directorios).
  • La especificación PSR-1 dicta que los ficheros deben estar codificados en UTF-8 (sin el bloque BOM de principio de fichero), que los ficheros con clases no deben contener código con efectos colaterales y viceversa y algunas puntualizaciones menores sobre nomenclatura de propiedades y métodos de clases e interfaces.
  • PSR-2 está enfocada en espaciados, identaciones y saltos de línea, además de establecer que true, false y null deben escribirse en minúsculas, y que debe especificarse de forma explícita la visibilidad de todos los métodos y propiedades de las clases e interfaces. También propone un orden concreto para las palabras claves (como static, abstract, final, private, protected o public) en las declaraciones de métodos y propiedades.
  • PSR-3 define una serie de interfaces comunes para bibliotecas con código para generar logs.

Mientras que PSR-1 y PSR-2 están enfocadas a mantener un estilo cómodo, coherente y consistente a lo largo y ancho del código, PSR-0 y PSR-3 están claramente orientadas a posibilitar y aumentar la interoperabilidad entre distintos frameworks y bibliotecas de código.

De PSR-1 destacaría especialmente su recomendación de codificar todo el código fuente de nuestros proyectos con UTF-8, y de PSR-2 su indicación de usar saltos de linea de Unix (LF), y no los de Windows (CR+LF). Los problemas de codificación y salto de línea por desgracia  siguen todavía hoy a la orden del día.

Mantener un estilo unificado que conozcan todos los programadores puede resultar de gran ayuda para que el código sea más legible para todo el mundo, pero presenta algunos beneficios más.

La teoría de los cristales rotos

¿Alguien conoce la teoría de los cristales rotos? Esa teoría dice (mas o menos), que un entorno descuidado incitará a la población a descuidarlo (y/o destrozarlo) más todavía, mientras que un sitio limpio, ordenado y bien mantenido promoverá que quienes están allí cuiden con más ahínco del espacio que les rodea.

Mi experiencia me dice que lo mismo sucede con el código. El código sucio genera más código sucio y en mayor medida que el pulcro. Por eso mismo he querido empezar por este punto, aunque también lo he hecho porque creo que se trata de un comienzo más accesible para los que tengan algo menos de experiencia.

Espero que la referencia a las especificaciones PSR os haya servido :) , nos vemos en el próximo artículo.

Dell me la ha jugado

Dell LogoHace poco empecé a trabajar en un espacio de coworking (Uikú), por lo que volví a usar mi portátil, que ya tiene unos 5 o 6 años. Es un Toshiba Satellite U200 que, la verdad, salió bueno. En todo este tiempo solo he tenido que cambiarle la batería (22€), y el teclado se ha hecho un poco menos sensible, por lo demás todo perfecto.

El problema con este portátil está principalmente en el teclado y en que me he "malacostumbrado" a ordenadores muy pero que muy rápidos.

Así pues empecé a buscar una nueva máquina que fuera a satisfacer mis necesidades, y apareció el Dell XPS13 Developer Edition, una pequeña joya: ultrafino, batería de larga duración, el tamaño de pantalla perfecto para un portátil, y muy potente (además, con Ubuntu preinstalado).

Hice el pedido a través de la tienda online de Dell. En la cesta añadí el portátil, una funda de nylon para éste y una pantalla de ordenador con posibilidad de rotarla 90º (para momentos en que tuviera que ver mucho código a la vez). Me iba a costar 1688.90€ (IVA incluído). Tiempo estimado de entrega: entre semana y media y 2 semanas.

A falta de 2 o 3 días para que el pedido llegara (según mis inocentes estimaciones), miro en mi panel de usuario en la página de Dell y me empiezo a cabrear: mi pedido ha sido cancelado. Y lo que es peor: no se me ha avisado, ni por email ni por teléfono.

Ese mismo día, cabreado como es esperable, hago otro pedido (lo que puede que ya no sea tan esperable). ¿Qué me motivó para repetir el pedido? Pues que el portátil en cuestión sigue siendo muy atractivo, y que... lo habían rebajado 100€. Además, hice un pedido más interesante que el anterior: añadí un expansor de puertos por solo 14 céntimos + IVA (sí, una oferta extraña de Dell), unos altavoces (para usar con mi torre fija, que no tiene), y escogí una pantalla distinta que estaba de oferta. El coste del segundo pedido iba a ser de 1529.60€ (IVA incluído). Eso son unos 159.30€ menos (~ 131.65 + IVA). Parece que ha valido la pena que Dell me haya cancelado el anterior pedido.

Pasado un tiempo bastante largo (como media hora) recibo un "Acuse de recibo de pedido". Ese fue el último dato que recibí por parte de Dell sobre ese pedido. NADA MÁS. Ni tan siquiera añadieron el pedido a mi panel de usuario/cliente de la web de Dell.

A los dos días llamo a Dell preocupado. Cancelaron mi primer pedido sin avisar, y de este segundo pedido he recibido un solo email, y no aparece en mi panel de usuario en su web. Se lo comento a un chico, me dice que mi pedido se canceló por problemas en el cobro. Me parece extraño porque tengo dinero de sobras en esa cuenta, a lo que me explica que puede deberse al límite de la tarjeta. El límite estaba situado en 2000€/semana para comercios, y 600€/semana para cajeros, debido a que ellos no son un cajero, no me cuadra su explicación: a ellos se les debería haber aplicado el primer límite, que cubría holgadamente ese pedido.

Le comento al chico que no puede ser que no se avise al cliente, porque tal vez esa situación podría haberse remediado en el momento, y que por culpa de eso había tenido que hacer otro pedido y todo se retrasaría bastantes semanas. Me dice que su protocolo es avisar al cliente... pues bien, no lo hicieron. Ni tan siquiera contempló la posibilidad de que alguien en su empresa hubiera hecho las cosas mal, hizo oídos sordos (o mintió, y ese supuesto protocolo no existe). Eso sí, fue muy amable. Me comentó que seguiría personalmente el pedido y que se ocuparía de mantenerme informado...

Dos horas después recibo una llamada. No llego a cogerla, demasiado tarde. Devuelvo la llamada, resulta que son las oficinas de atención al cliente de Dell situadas en Madrid, ¡viva! Tengo uno de sus números fijos escondidos tras el prefijo 902. Aun así, lo que viene es peor. Mala suerte, quien me llamó sabía de qué iba la cosa, ahora me toca una chica que no parece escuchar nada de lo que le digo, me comenta que me pasará con la chica que me ha llamado antes.

Paso a hablar con esa supuesta chica, y a los ... no sé, 20 o 30 segundos, dice algo raro que no llego a entender, oigo un "te la paso"... y empieza a sonar un hilo telefónico. 30 segundos más tarde me encuentro con un robot... pero en francés. Cuelgo. ¿Pero qué mierda es esta? En ningún momento grité o falté el respeto a nadie, fui educado, amable y comprensible, acepté que un error lo puede tener cualquiera... ¿Por qué si yo trato bien a la gente me tengo que encontrar con esto?

Durante ese día desisto, estoy cansado. Así que decido esperar al día siguiente para ver qué pasa. Y... ¡sorpresa! Parece que todo se ha arreglado (más adelante veremos que no): recibo un email diciéndome que ya puedo seguir el estado de mi pedido. Voy a mi panel y aparece un nuevo pedido. Supongo erróneamente que se trata del segundo pedido que estaba en el limbo. Y yo, tonto de mí, continúo ilusionado.

Al día siguiente recibo un email indicándome que el número de pedido XXXX pasa a ser el número ZZZZ, aunque el pedido sigue siendo el mismo, que solo pasa por motivos de "calidad". Contrasto el número ZZZZ y coincide con el que aparece en el panel, parece que todo encaja... ¡pero tonto de mi! Me he olvidado de ver a qué correspondía el número XXXX! En realidad tenía la mosca tras la oreja, pero estaba hasta las cejas de trabajo y pospuse esa comprobación.

Esta vez el pedido pasa de fase en fase mucho más rápido que antes... y en 2 días pasa al estado "Enviado". Un día después me llega un email con la factura (hoy). Empiezo a comprobar la factura y... ¡tachan! No hay altavoces, y pagaré 1688.90€. Tócate los huevos. ¿Qué ha pasado con el segundo pedido? Ni puta idea. Les interesaba cobrarme el primero, que es más pasta. Y además me llegará casi un mes después de lo inicialmente estimado.

Pues no lo pienso coger. Y no lo pienso pagar. Son un puñetero desastre y tienen demasiada poca vergüenza.

Liberado Platano Indexer

Gibbon Indexer

La semana pasada liberé Platano Indexer (bajo licencia MIT), un indexador y buscador (de palabras y frases) programado en Java 7 desarrollado en el seno de Bananity.com. Empecé a desarrollarlo como empleado de Bananity junto a Alberto Rubio Muñoz, aunque después continué como desarrollador principal siendo ya freelance.

En estos momentos su documentación escasea y la que existe dista de ser perfecta, así que iré introduciendo la plataforma poco a poco en sucesivos artículos a la vez que extiendo la documentación existente en el repositorio¹ y su wiki correspondiente.

Platano Indexer usa como base JBoss Application Server (probado fundamentalmente sobre la versión 7.11 comunitaria). Su arquitectura está pensada para soportar múltiples sistemas de almacenamiento, aunque actualmente solo ha sido desarrollado un backend para MongoDB.

Ideas interesantes detrás de Platano Indexer

Tengo que reconocer que no hemos estudiado los mecanismos usados por otros indexadores, como Lucene o ElasticSearch, así que puede que hayamos reinventado la rueda... o estemos usando técnicas más rudimentarias que las utilizadas estos dos productos mucho más maduros (no creo que hayamos dado con ninguna técnica más eficaz por casualidad).

Dicho esto, el indexador (a grandes rasgos) tiene un funcionamiento muy sencillo:

  1. Se coge la frase a indexar, se normaliza y se tokeniza (se divide en palabras). Aquí aplicamos un truco: también creamos tokens con pares de palabras consecutivas (esto tiene efectos importantes e interesantes en la importancia del orden en las búsquedas, así como en la búsqueda de frases largas con palabras cortas).
  2. De los tokens obtenidos obtenemos a su vez una lista de todas sus subcadenas (con repeticiones). En el caso de los tokens formados por dos palabras, se excluyen algunas subcadenas por motivos de rendimiento y calidad de resultados (fundamentalmente las subcadenas cortas centradas en el espacio que separa las 2 palabras).
  3. Cada uno de los subtokens es usado como clave en el sistema de almacenamiento (que será de clave-valor). El valor será una lista con un tamaño máximo definido que contendrá las palabras que contengan esa subcadena. Dado que la lista de palabras tiene una longitud máxima fijada, el orden de las palabras en dicha lista es importante (hablaremos de él más adelante).
  4. La búsqueda funciona de forma similar. De la cadena de búsqueda se obtienen los subtokens, que serán usados para pedir las listas de palabras al almacenamiento. Estas listas de palabras deberán mezclarse y reordenarse ligeramente para ofrecer los resultados en el orden más apropiado posible.

En cuanto a los mecanismos de ordenación, para la búsqueda usamos fundamentalmente la distancia de Jaccard (adaptada a multiconjuntos), pero para la indexación usamos criterios más sencillos (como longitud de la palabra y número de apariciones de la subcadena dentro de la palabra).

Lista de cosas por hacer

Soy consciente de que Platano Indexer no es todavía un producto maduro, queda mucho por hacer todavía. Aquí os presento una lista de cosas por hacer (por si alguien quisiera animarse):

  • Mejorar el sistema de configuración para aumentar la flexibilidad del sistema, y de paso refactorizar para conseguir un código más limpio y mantenible.
  • Añadir más backends de almacenamiento (como por ejemplo Redis).
  • Mejorar el proceso de instalación y deploy (puede que un buen paso sea empezar sustituyendo el actual Makefile).
  • Aumentar el número de opciones de cara a configurar la indexación y la búsqueda.
  • ¿Eliminar la dependencia de JBoss? Esto me gustaría mucho, el proceso de deploy e instalación se podría simplificar (hasta el punto de facilitar la creación de paquetes .deb y .rpm) y tengo la intuición de que hasta podríamos mejorar su rendimiento.

Artículos relacionados

Os enlazo algunos artículos que escribí en su momento gracias a lo aprendido durante el desarrollo de Platano Indexer.

  1. Unit Testing más efectivo con PIT Mutation Testing
  2. Manejando UNICODE y UTF8 desde Java

Referencias

  1. https://github.com/castarco/platano-indexer