miércoles, 8 de agosto de 2018

Clean Code

Lectura recomendada:



Hay veces que repasando código ajeno uno se puede encontras cosas muy farragosas. Este método simplemente pretende buscar entre una lista de rangos cuál corresponde a un valor en concreto.
Por ejemplo: "A", para valores entre 100 y 51; "B" para valores entre 50 y 31; "C" para valores por debajo de 30.

Partíamos de este código, que prácticamente da miedo verlo:

  • El nombre del método es excesivamente largo y para un lector angloparlante, está mal escrito.
  • Los nombres de las variables también son muy largos y con elementos redundantes: "nameNosequé" es innecesario: toda variable se usa precisamente para nombrar algo.
  • La iteración es poco natural (con una condición negada como primer control del bucle) y usa construcciones del lenguaje (en este caso, la función "each" de PHP) que están "deprecated".
    Con nombres tan largos y esta asignación metida en el control del bucle, queda realmente ilegible:

    while (!isset($nameOfMatchedTransparencyRanking) && (list($transparencyRankingName, $arrayTransparencyRankingInterval) = each($arrayTransparencyRankings))) {
    // Cuerpo del bucle
    }
Para recorrer una estructura de datos de este tipo (array asociativo) es más natural y legible -y posiblemente, más eficiente- usar un bucle foreach.


No es la mejor implementación del mundo, pero al menos ya es más legible y se entiende un poco mejor qué hace este método. Si quisiéramos que fuese más genérico, y no estuviese atado a una aplicación en concreto, los nombres de variables podrían ser aún más genéricos (p.ej, $value en vez de $transparency).

Hacer un return entre medias del bucle no es muy académico ni formal, pero permite un código compacto y sencillo. Esta forma de escribir la utilizaban los mismísimos creadores de C, y en funciones cortas como esta, es perfectamente aceptable. En un método más largo, más allá de 10 líneas, personalmente, ya no lo utilizaría porque tendríamos un punto de salida descontrolado que no se ve claramente.



(Capturas del libro "The C Programming Language" de D. Ritchie y B. Kernighan. Si no sabes quiénes son estos señores, no sé que haces leyendo este blog ;-)

miércoles, 25 de julio de 2018

Sobre la "obligatoriedad" de usar HTTPS

Ayer (24/07/2018) Google publicó la versión 68 de su navegador, Chrome.

Una de las novedades más comentada es que empieza a marcar de forma explícita en la barra de direcciones cuando una web no se sirve bajo https:



Hasta ahora sólo se mostraba una advertencia, pero no de forma explícita. Si la página carga bajo https pero intenta referenciar recursos que no están bajo https, muestra un icono en la barra de estado que permite cargarlos.



Si optamos por cargar los "scripts no seguros", nos lo advierte claramente:


Algunas reflexiones tras muchas jornadas inventariando plantillas, ficheros de configuración, código...

  • Es una buena idea que las páginas se sirvan bajo https, así como los recursos que éstas emplean (css, js, imágenes,...)
    No vamos a repetir aquí todo el argumentario de seguridad, está claro que el hecho de que la conexión esté cifrada la hace más segura.
  • Menos mal que tenemos Let's Encrypt, si no, no hubiese sido posible para muchos sitios pequeños o medianos poder servir su web bajo https.
  • Hay un aspecto muy preocupante: la falsa sensación de seguridad que podemos crear, y que nos podemos llegar a creer.
    Servir nuestra web bajo https no la hace segura, esto es una obviedad. Si nuestras web son vulnerables (SQLi, XSS,...), lo seguirán siendo las sirvamos bajo http o https.
    Es nuestra obligación como técnicos recordárselo a las gerencias: tener el candadito verde no garantiza nada, es una protección más, simplemente.
  • También es algo preocupante el hecho de que Google y su calendario de productos marque el calendario de los proyectos IT, cada vez más.
    Debería hacernos reflexionar el hecho de que no se acometen las mejoras en general hasta que no viene uno de los grandes a apretarnos las clavijas. Pasó con la eliminación de Flash (lo que Apple no consiguió con su esnobismo lo consiguió Google recurriendo a motivos más terrenales: "anunciantes, ya no admitimos anuncios en flash"), ahora ha pasado con la implantación de https por defecto en las webs.
    Hay cosas que desde IT sabemos que deben hacerse y por calendarios de lanzamiento de producto, prioridades de la compañía, etc, se van posponiendo hasta que nos tiene que venir el hermano mayor (Google, Facebook, Microsoft, Apple,...) a decirnos "o implementáis esto o ...".
  • Cabrea un poco el encontrarse con tanto gurú y listillo que se creen que por saber migrar un blog en Wordpress o una web en Drupal tienen la suficiente autoridad como para pontificar y criticar a los que les está costando hacer esta migración.
    Si se tiene un site que tenga una infraestructura que implique varias zonas (una red interna, una red externa,...), servicios y servidores que interactúan entre sí, capas de caché, tareas programadas, varios CMS diferentes, componentes "legacy",.. el trabajo es bastante arduo, nada trivial.




martes, 6 de marzo de 2018

Euler #35

https://projecteuler.net/problem=35

La (pequeña) dificultad de este problema consiste en calcular todas las posibles rotaciones de un número. La sintaxis de Python hace muy cómoda esta tarea.

import sympy

def check_rotations(number):
 all_primes = True
 number_str = str(number)
 number_str = number_str[1:] + number_str[:1]
 while int(number_str) <> number:
  if not sympy.isprime(int(number_str)):
   all_primes = False
   break
  number_str = number_str[1:] + number_str[:1]
 return all_primes

circular_primes = []
for n in range(1, 1000000):
 if sympy.isprime(n) and check_rotations(n):
  circular_primes.append(n)

print circular_primes
print len(circular_primes)

Puntos a destacar:
  • sympy es una librería de Python especializada en matemáticas con símbolos (polinomios, resolución de ecuaciones, matrices,...) En nuestro script simplemente utilizamos la función isprime() para comprobar si el número es primo. Podríamos haber reutilizado alguna de las funciones que ya escribimos en problemas anteriores.
  • La expresión "cadena"[1:] devuelve todos los caracteres exceptuando el primero: "adena"
  • La expresión "cadena"[:1] devuelve el primer caracter, podríamos haber escrito también "cadena"[0]
  • La sintaxis de subíndices de Python garantiza siempre que s == s[:i] + s[i:] 
Una posible optimización es recorrer solo los números impares, fijando 2 como único par a incluir:

circular_primes = [2]
for n in range(3, 1000000, 2): # El tercer parámetro de range es "step"

martes, 20 de febrero de 2018

Euler #34

Tras un largo parón, otro problema del proyecto Euler, de nuevo lo solucionamos con Python. Tratando los números como cadenas de texto y convirtiendo los dígitos de nuevo a enteros el problema es muy sencillo de resolver.

https://projecteuler.net/problem=34


def fact(n):
 if n <= 1:
  return 1
 else:
  return n * fact(n - 1)

candidates = []
n = 3
while True:
# print "Analizyng", n
 n_str = str(n)
 partial_sum = 0
 for digit in n_str:
  partial_sum += fact(int(digit))
 if partial_sum == n:
  candidates.append(n)
 if n > 1000000:
  break
 n += 1

#print candidates
print sum(candidates)

lunes, 5 de junio de 2017

Euler #33

https://projecteuler.net/problem=33

De nuevo, la facilidad que ofrece Python para pasar de unos tipos a otros y los módulos estándar que trae hacen que este problema sea trivial.

Se puede compactar más la solución, pero así se ve claro cómo vamos filtrando poco a poco los números candidatos hasta llegar a la solución. También ayuda mucho el enunciado del problema, estableciendo cuántos resultados son esperables.

#!/usr/bin/env python
from fractions import Fraction
from operator import mul

fracts = []
for d in range(10, 100):
 for n in range(10, 100):
  if n / d < 1 and n % 10 != 0 and d % 10 != 0:
   num = str(n)
   den = str(d)
   for x in range(1, 10):
    xstr = str(x)
    if (num[0] == xstr and den[1] == xstr) or (num[1] == xstr and den[0] == xstr):
     new_n = int(num.replace(xstr, '', 1))
     new_d = int(den.replace(xstr, '', 1))
     if (new_n * d == new_d * n):
      fracts.append(Fraction(new_n, new_d))

print reduce(mul, fracts, 1)

La función "reduce" es muy útil para operar con listas en bloque. Es un atajo para no tener que escribir bucles:

tot = 1
for f in fracts:
    tot *= f
print tot

Así mismo, la clase Fraction es lo suficientemente inteligente para lidiar con enteros, operar con fracciones, simplificarlas,...