Google y el texto oculto: no es una cuestión de optimización para el buscador sino una cuestión de anti-spam

Noviembre 14, 2008 at 8:00 pm (Evangelios de Dioses de Silicio) (, , )

Desde que Google implemento las actuales guias para webmasters ha habido una renovada confusión, en especial con respecto al tema del texto y links ocultos. Si algún desarrollador web dice no haberse encontrado recientemente con algún cliente o colega reticente a usar algo tan viejo y simple como display:none por temor a las “represalias de google”, seguro miente. U otro caso puntual: haberse topado con la “supuesta” necesidad de validar la página contra “validadores” que observan como problemas a textos que se encuentran explícitamente ocultos…

Vamos por partes.

Punto uno. Google no es un gran y único dios clarividente, por un lado esta el googlebot, por otro lado esta el equipo anti-spam de google. El primero es un algoritmo, un programa, un proceso, un automata. El segundo es un grupo de personas.

Punto dos. El googlebot NO INDEXA CSS, no tiene idea sobre el, no le importa a su tarea. Si no me creen, fijense que es lo que se guarda en la cache de google (al hacer una búsqueda existe la posibilidad de ver la página encontrada según como la guardo el googlebot). Luego, si el googlebot no sabe nada del CSS, ¿cómo es que este pueda afectar a la indexabilidad de un sitio?

¿Por qué al googlebot no le importa el CSS? Al algoritmo le interesa los contenidos para indexarlos, la visualización no importa. Es más, cuanto mejor este armado el HTML para favorecer el procesamiento del contenido, ¡MEJOR! (es por esto que toda la movida de la web semántica empezó, porque google paga las cuentas y google dice: “mi bot entiende mejor el HTML estructurado con una orientación al contenido”).

Punto 3. Evitar la visualización del texto para poner en su lugar un bitmap del mismo en una tipografía visualmente más pertinente es uno de las técnicas más viejas en el toolbox del desarrollador web. Vean las plantillas de CSS Zen Garden y van a ver de que hablo. ¿Tiene sentido limitar la capacidad creativa de los profesionales de la web, y la experiencia que se le puede ofrecer al usuario, solo por “evitarse un problema” con google? NO. Por el contrario, google no quiere que hagamos eso (ver estas discusiones: Using CSS to hide text, Google Breaking It’s Own Quality Guidelines? – CSS).

Punto 4. Los que evaluan si hay o no textos ocultos es el equipo anti-spam, y acá no hay algoritmo que valga (los programas pueden ayudar pero las maneras que los spammers encuentran para engañar suelen requerir de un ojo humano que de el visto bueno antes de proceder con una falsa acusación). Acá es donde entre en rigor el tema del texto oculto. No se trata de evitar a toda costa usar CSS que oculte texto, sino de no usarlo de manera desleal.

Ver estos casos para hacerse una idea de cómo se detectan los casos de texto oculto y por qué se dan de baja del índice: Communication in other languages, Notifying webmasters of penalties.

Punto 5. Por sobre todo la usabilidad va primero. La web es para que la usen los usuarios, no para que google la indexe mejor. Hay que ponerse bien las prioridades: Accesibilidad > Usabilidad > Indexabilidad. Además no es la indexabilidad (o la optimización para buscadores) la que hace que una web aparezca primero en los buscadores, como todo negocio, si no hay promoción nadie se entera… menos google.

Permalink 2 comentarios

A que estar atento en los namespaces de PHP 5.3

Agosto 5, 2008 at 9:21 pm (Evangelios de Dioses de Silicio, Libro de Hechizos PHP) (, , , , , )

Introducción

Se viene PHP 5.3 así como quedo, algo feo para mi gusto. Veamos a que atenerse en cuanto a namespaces.

Los namespaces de PHP son bastante distintos a similares construcciones de otros lenguajes. Consideremos este escenario: dos desarrolladores PHP están trabajando en componentes distintos, sin posibilidad de saber uno lo que hace el otro, ¿cómo evitar que al integrar los dos componentes falle la aplicación por colisiones de nombres? Una opción es hacer lo que venimos haciendo hasta ahora, prefijar todos los nombres con el nombre del componente. La nueva opción es indicar como namespace el nombre del componente, que básicamente es lo mismo que el prefijo (porque más que eso no hace).

Namespace ahora es un keyword

Y sí, rompieron la compatibilidad hacia atrás, y no es un cambio mayor de versión.

Después de varias idas y vueltas, cotejando el impacto de usar un keyword u otro, quedo namespace como keyword. No importa cual hubieran elegido entre los dos favoritos, package y namespace, cualquier de los dos son palabras de uso frecuente (por ejemplo, xml tiene namespaces). Cualquier clase que tuviera como nombre de variable a namespace, ahora va a dejar de funcionar. JODANSE.

Consejo: nada que hacer. Refactoring. Con suerte no usas namespace como nombre.

Lo que hubiera estado bueno: no hacia falta tomar un keyword nuevo. Había opciones que no requerían un statement para indicar el namespace. Y si no quedaba otra… hubiera sido para PHP6 el cambio.

El namespace implicito y especial de nombres internos

Segundo gotcha! del tema de los namespaces. Hay todo un conjunto de nombres internos, que corresponden a funciones y clases de la librería estándar y extensiones, que debería ser “visible” desde cualquier namespace, para que uno pueda fácilmente refactorizar código sin namespace a código con namespace. O sea, si yo indico que trabajo en un namespace igual quiero poder usar count(), strlen(), etc tal y como siempre lo hice.

El tema acá es que una función o clase interna se puede “sobrecargar” en un namespace, o sea, redefinirla. Esto es útil para los componentes que definen su propia clase de excepciones, por ejemplo. Pero hay que estar atento que la función count() adentro de un namespace puede que no devuelva la cantidad de elementos de un array sino lo que al desarrollador de ese componente le parecio que count() debía hacer.

Consejo: usar namespaces solo para clases, evitar declarar funciones dentro de namespaces a menos que uno sepa exactamente lo que está haciendo. También uno puede referirse inequívocamente a la función del espacio interno anteponiendo ::, por ejemplo, ::count().

Lo que hubiera estado bueno: que no se pueda sobrecargar los nombres del espacio interno, pero que exista un namespace inicial con ciertos nombres que resulta práctico sobrecargar, por ejemplo: Exception. De esta manera se evitan prácticas que pueden resultar perjudiciales y no tienen sentido (como sobrecargar las funciones básicas), y se le da la flexibilidad al desarrollador para sobrecargar los nombres que si tiene sentido sobrecargar.

Colisión de nombres no detectada, función pisa metodo estatico

Acá la pregunta que seguro se van a hacer todos es “¿cómo? ¿no era que con esto resolvíamos el tema de las colisiones de nombres?”, bueno, no del todo… hay un “pequeño” problema.

Supongamos que tengo una función llamada bar() en el namespace test::foo. Ahora supongamos que tengo una clase llamada foo, que tiene un método estático llamado bar(), y se encuentra en el namespace test. Históricamente en PHP los nombres de funciones y nombres de métodos no tenían chances de colisionar, pero… a ver quien me dice a que corno apunta test::foo::bar()!

Lo cierto es que esto que acabo de ejemplificar es un caso de colisión de nombres del que php5.3 no te da aviso alguno. Si intentas el ejemplo, PHP va a llamar a la función, no al método estático. De manera que ahora existe una manera de “pisar” métodos estáticos, o sea, desde otro namespace se vuelve inaccesible el método estático de la clase.

Consejo: no declarar funciones en namespaces (¿para qué?) o evitar en lo posible anidar namespaces.

Lo que hubiera estado bueno: realmente no hacia falta namespaces que contuvieran funciones ni tampoco varios niveles de anidación. Cuanto más simple, mejor.

Multiples namespaces en un mismo archivo

No se si esto esta documentado o explicado en detalle en la documentación, pero se puede declarar el namespace más de una vez en una archivo. Esto fue para resolver una situación muy ad-hoc y es un moco feo que no debería haber llegado a producción. Se puso para que se pudieran concatenar archivos php para generar un único archivo que contuviera todo, una optimización medio fea que ya no tiene sentido teniendo a disposición phar.

Consejo: por el amor de todo lo que es buen diseño, ¡NO DECLAREN EL NAMESPACE MÁS QUE UNA SOLA VEZ POR ARCHIVO! Y castiguen a todo el que lo haga.

Lo que hubiera estado bueno: que quitaran esto.

El “use” (import/alias de nombres) solo funciona para clases y namespaces.

Otra de las cosas que no se arreglaron. El use es la herramienta más útil que nos da esto de los namespaces, y en realidad ni siquiera nos hace falta usar el resto. El “use” es básicamente un generador de alias de nombres, con el podemos hacer de esos nombre realmente largo en nombre cortos, por ejemplo:

use Really_Long_Long_Long_Name as ShortName;

Pero hay un tema, solo se puede renombrar clases y namespaces, pero no así funciones ni constantes, que son elementos que también se encuentran dentro de un namespace. O sea, sirve para acortar nombres de clases, que es genial y que es lo que queríamos, pero no para acortar nombres de funciones o constantes. Con esto surge la pregunta: entonces, ¿para qué esta la opción de tener funciones y constantes dentro de un namespace?

Consejo: creo que lo dije ya, esto de los namespaces en PHP solo viene bien para usarlos con clases.

Lo que hubiera estado bueno: que se enfocaran más en esta herramienta que era lo que realmente importaba.

El “use” no permite importar todo un namespace

Esto es lo que todos los que usan namespaces en otros lenguajes van a pedir seguro, pero no hay caso… esto es PHP! (cualquier alusión a la novela gráfica 300 es pura coincidencia). Hay que entender que el use no es un import, en realidad es solo una manera de asignar nombres más cortos dentro del scope de un archivo php.

Impacto en performance

Infaltable este tema. La promesa era que la implementación de namespaces no iba a afectar la performance, que el mayor impacto iba a ocurrir en la compilación, de manera que con un opcode cache se podía salvar el overhead. Lo cierto es que esto no es así, algo hay que pagar por el uso de los namespaces. De todas maneras estamos hablando de mediciones en el rango de las millonesimas de segundo. No hay sistema PHP que tenga suficiente procesamiento como para que se note realmente el impacto del uso de namespaces. Igual vale la pena notar que nada es gratis.

En los análisis de tiempo que hice observe impactos de performance en llamadas a métodos estáticos y en la construcción de objetos, lugares donde la lógica de resolución de nombres tiene sus vueltas (en realida solo esperaba un impacto importante en las llamadas estáticas, no comprendo porque hay un overhead importante con la construcción de objetos).

De PHP5.2 a PHP5.3 sin declarar namespace, el impacto en las llamadas a métodos estáticos es mínimo. Tarda un 4% más (que es solo 1 millonésima de segundo medido en mi pc: de 25 millonésimas a 26).

Declarando un namespace, el impacto en las llamadas a métodos estáticos es de un 20% más de tiempo (5 millonésimas de segundo en mi PC: de 25 millonésimas a 30).

Declarando un namespace, el impacto en la construcción de objetos es de un 33% más de tiempo (3 millonésimas de segundo en mi PC: de 9 millonésimas a 12).

Consejo: no creo que haya que preocuparse por el overhead, pero este existe, cuando se empiece a probar en producción se vera si hay que considerar o no el impacto en performance. Si no es aceptable el overhead, no hace falta usar namespaces, con el prefijo en nombres nos veníamos arreglando bastante bien, y el “use” alivia el tema de los nombres largos aún si no usamos namespaces (notese que el “use” se resuelve siempre en compilación así que el overhead lo amortigua el opcode cache, por lo que no es una preocupación).

¿Y los features de encapsulación?

La otra pregunta que varios se van a hacer es qué sentido tienen los namespaces si no puedo ajustar los limitadores de visibilidad que me permiten manejar la encapsulación. Así como una clase tiene funciones privada, protegidas y publicas, un namespace debería poder indicar que nombres se exportan y que nombres son internos al paquete.

PD: Desactualización de las IDEs

La lógica de resolución de nombres no es nada parecido a cuanto se viene haciendo en PHP. Las IDEs van a tener que actualizarse, y esta actualización no es coser y cantar.

Permalink Dejar un comentario

PHP Lambda functions y Closures, lo que es bullshit y lo que realmente es de provecho

Agosto 3, 2008 at 8:25 pm (Evangelios de Dioses de Silicio, Libro de Hechizos PHP) (, , , , , , )

Bueno, hace unos días que lo ví en el twitter de phpsenior y tenía ganas de explicar realmente como viene la mano con este tema algo controversial.

Primero tienen que entender que hace años que el tema se propone incontables veces en la lista de internas de PHP y el argumento de rechazo a las lambda functions siempre fue el mismo: PHP no es lenguaje funcional, no hay nada de provecho en implementar lambda functions, y el eterno purismo que dice que las lambda functions son solo indicio de un mal diseñador de sistemas.

En general esto último tiene algo de cierto. Las lambda functions son en esencia funciones anónimas o con nombres aleatorios. Este tipo de construcciones son en general indicadores de un mal diseño. Si algo carece de una denominación apropiada es porque no debería estar ahí o no se encuentra en el lugar correcto del sistema.

Ahora, esto cambio cuando alguien trajo a la mesa la posibilidad de incluir closures y con esto una nueva utilidad a las funciones anonimas o lambda functions. Pero primero voy a tratar de explicar porque y para que es que ciertos usuarios piden que se agregen las lambda functions.

Como saben hay ciertas funciones en la librería de PHP que utilizan otra función indicada por parametros, por ejemplo array_map, array_filter, array_reduce, array_walk, usort y sus variantes. La queja de los que piden las lambda functions es que a veces no resulta práctico tener que crear una función adicional para poder pasarsela a este tipo de funciones, por ejemplo, si quisieramos filtrar todos los elementos de un array que sean mayores a 2:

function greaterThan2($number) {
	return $number > 2;
}
$array = array_filter($array, 'greaterThan2');

La función que creamos es muy ad-hoc, carente de un propósito general que justifique su presencia salvo para este caso particular. Usando lambda functions:

$array = array_filter($array, function ($number) {
	return $number > 2;
});

En general esto puede a simple vista resultar una manera práctica y elegante de resolver esta situación, a contraste de otras opciones que uno tiene a disposición en PHP, tal como se argumenta en el RFC. Esto no es cierto. Abusar de estas facilidades resulta en un embrollo en donde se pierde fácilmente la noción de cual es el scope donde actualmente estamos parados, sumado al anonimato de los componentes se vuelve una pasta inentendible y difícil de depurar. Si no me creen, dense una vuelta por el mundo Java a ver que tal les parece las clases anónimas, sumado al AOP y los stack traces quilómetros…

Lo que uno tiene que darse cuenta como programador es que lo que esta bueno de un lenguaje no es lo que te deja hacer, sino lo que no te deja hacer. En el momento en que empiecen a ver lambda functions anidadas vamos a ver que tan elegante les parece.

Y lo cierto es que sí hay una opción elegante, reusable y que no hace uso de funciones anónimas, algo no muy utilizado del OOP, functor objects. Un functor es basicamente un objeto que abstrae el comportamiento de una función. Mejor explicado con un ejemplo:

class GreaterThan {
	private $limit;
	public function __construct($limit) { $this->limit = $limit; }
	public function call($number) { return $number > $this->limit; }
}
$array = array_filter($array, array(new GreaterThan(2), 'call'));

En este caso el functor tiene un propósito general que puede reutilizarse en otras partes del sistema.

Ahora, volviendo al tema de los closures, esta adición hizo que el parche de las lambda functions resultara interesante. Para entender porque volvamos al ejemplo, suponiendo ahora que estamos construyendo una función genérica para seleccionar solo los elementos que superen cierto valor, usando array_filter. Esto NO lo podemos hacer en PHP:

function filterGreaterThan($array, $limit) {
	$array = array_filter($array, function ($number) {
		return $number > $limit;
	});
}

No podemos hacerlo porque $limit no existe dentro de la lambda function, no puede “ver” esa variable desde ahí dentro. Para eso se requiere implementar closures. En la propuesta que se implemento para PHP5.3 sería de esta manera:

function filterGreaterThan($array, $limit) {
	$array = array_filter($array, function ($number) use ($limit) {
		return $number > $limit;
	});
}

De esta manera la función anónima “copia” la variable a su scope.

Ahora vuelvan a mirar el ejemplo de functor que les mostre antes y vuelvan a mirar el ejemplo de lambda function con closure. Si no tuvieron un momento “AHA!!” cierren la página y vayan a estudiar más analisis y diseño orientado a objetos porque les está haciendo falta.

En efecto, los functor objects no solo son una muy buena opción para no tener que usar lambda functions, también son una muy buena opción para no tener que usar closures. Todo esto sin andar ensuciando el sistema con funciones y/o clases anónimas. Lo más interesante de todo es que cuando usas lambda functions con closures internamente PHP está creando un functor object. La clase especial “Closure” creada con este parche no es más que un Functor que contiene a la función anónima y en sus propiedades contiene a las variables pasadas mediante use(…)

Ahora, mi consejo es: un buen diseño orientado a objetos debería evitar las funciones anónimas. Estan los functor objects y varios patrones de diseño como opciones para no tener que usarlos. Pero en todo el meollo de lo que implementaron hay algo muy interesante y realmente útil para agregar a la “bolsa de trucos”. Ironicamente esto que considero lo más importante esta visto como un “extra”, un adicional, un condimento: la función __invoke(). Esto es lo que faltaba para que usar functors con PHP sea práctico y elegante.

Mediante __invoke podemos hacer que un objeto se comporte como una función, lo cual es justamente lo que queremos para un functor. Reescribiendo el primer ejemplo de functor:

class GreaterThan {
	private $limit;
	public function __construct($limit) { $this->limit = $limit; }
	public function __invoke($number) { return $number > $this->limit; }
}
function filterGreaterThan($array, $limit) {
	$array = array_filter($array, new GreaterThan($limit));
}

Hora de buscar mi vieja implementación de functors en PHP y ver si la publico como open source de una buena vez…

Permalink 5 comentarios

Siguiente Página »