Signos de que eres un mal desarrollador

26 Oct 2011

Introducción

Siguiendo en la línea de los artículos que @ialcazar considera como ‘esos que despiertan conciencias‘, he querido traer ahora un escrito original de Lawrence Wenham donde desarrolló un listado de signos (con sus síntomas y remedios) que identificarían a los malos desarrolladores.

El objetivo de este artículo no es otro sino el de motivarnos a continuar aprendiendo ya que es perfectamente aplicable a cada uno de nosotros en el momento de enfrentarnos a una nueva tecnología. Me gusta aquí recordar como Rober Martin siempre aconseja que, para mejorar como desarrolladores, tenemos que salirnos frecuentemente de nuestra zona de confort, buscando nuevos retos en lenguajes que supongan un paradigma radicalmente opuesto al que manejamos en nuestro día a día.

Si decidimos dar el salto del paradigma imperativo al declarativo, podemos necesitar de algún tipo de rasero que nos indique si ya estaríamos preparados para aplicar los nuevos conocimientos a un entorno real de producción.

Veámos cuáles podrían ser los síntomas de si hemos o no comprendido lo que tenemos entre manos…

1. Incapacidad para razonar sobre un código dado

Incapacidad para razonar un código significa que no podemos seguir mentalmente el hilo de ejecución aún sabiendo su finalidad. Es decir, deberíamos contar con la habilidad de ejecutar un programa en nuestra cabeza sin necesidad de recurrir al compilador o intérprete.

Síntomas

  • Presencia de códigoue no tiene ningún efecto sobre el objetivo final de un programa pero que se continúa manteniendo. Por ejemplo, hablamos de variables que se inician pero nunca se utilizan, llamadas a funciones que resultan irrelevantes para el objetivo final, valores de salida que no se utilizan, etc…
  • Reiteradas llamadas a un mismo bloque de código (por ejemplo, llamar a una función save() varias veces únicamente para estar seguros).
  • Reparar errores escribiendo código que sobreescribe ese resultado erróneo. Por ejemplo, en lenguajes de tipado blando, si detectamos que una variable que debería contener un entero contiene una cadena, reescribimos su valor asignándole cero.
  • Código yo-yo que convierte un valor algo diferente para un cubrir un paso concreto de ejecución y luego retoma el valor original. Por ejemplo, convertir un valor decimal en una cadena, operar sobre él, y luego devolverlo a su forma decimal inicial.
  • Código bulldozer que da la impresión de refactorizar alguna estructura pero que, en realidad, resulta imposible de reutilizar fuera de su propio contexto.

Remedios

Para superar estas deficiencias, un programador debe practicar usando un buen IDE junto a su debugger favorito; esto le permitirá avanzar por un código paso a paso de modo que pueda seguir fácilmente su hilo de ejecución. Esto significa por ejemplo crear puntos de ruptura y monitorizar el valor de determinadas variables antes y después de cada cambio hasta entender qué es lo que está haciendo el código.

El objetivo es alcanzar ese punto donde podríamos fácilmente leer un código, identificar duplicados o estructuras innecesarias. Además de esto, mejoraremos la habilidad de detectar y corregir errores sin tener que reescribir todo desde cero por el simple hecho de que no sabemos leer lo existente.

2. Comprensión pobre del modelo de programación de un determinado lenguaje

La Programación Orientada a Objetos (POO u OOP) es un ejemplo de modelo de lenguaje, como lo es la programación funcional o declarativa. Éstos poseen a su vez diferencias significativas con otros paradigmas como el imperativo, del mismo modo en el que, por ejemplo, la programación estructural es muy diferente del ensamblador o de una programación basada en GOTO.

Existen lenguajes que se adscriben a un determinado modelo (como por ejemplo la POO) pero que presentan sus propias mejoras (features) sobre los mismos.

Síntomas

  • Usar la sintaxis que sea necesaria para romper el modelo de un lenguaje y poder así escribir código que nos resulte más familiar.
  • (POO) Tratar de llamar a funciones no estáticas o variables en clases no instanciadas y no ser capaces de comprender el porqué no funcionan como esperamos.
  • (POO) Escribir varias clases del tipo ‘xxxManager’ que contengan cada una todos los mismos métodos de manipulación (aún con ligeras variaciones entre ellos).
  • (Relacional) Tratar una base de datos relacional como un mero objeto de almacenaje sobre el que luego realizaremos todas las relaciones, uniones, concatenaciones o filtrados en el lado del cliente.
  • (Funcional) Crear varias versiones del mismo algoritmo para manejar diferentes tipos u operadores en lugar de pasar funciones de alto nuvel a una implementación genérica.
  • (Funcional) Cachear manualmente los resultados de una función determinista en una plataforma que puede hacerlo de forma automática (como por ejemplo SQL y Haskell).
  • Recurrir al copiar y pegar desde el código de otro programa para lidiar con I/O y Mónadas.
  • (Declarativo) Asignar valores individuales en código imperativo en lugar de usar un data-binding.

Remedios

Si la deficiencia de tus habilidades se debe a un aprendizaje o estudio inefectivo, entonces el remedio es tan sencillo como repasar los conceptos y profundizar en la especificación del lenguaje. No existe una forma más efectiva para aprender el modelo de un determinado lenguaje que iniciar un nuevo proyecto y obligarte a ti mismo a usar cada una de sus características, ya sean de forma inteligente o no. Incluso podemos crearnos un nuevo vocabulario con el que referirnos coloquialmente a cada una de esas nuevas funcionalidades para así asimilarlas de una forma más natural. Por ejemplo:

  • Fase 1: POO son solo registros con métodos.
  • Fase 2: los métodos POO son solo funciones que ejecutan un mini programa con sus propias variables globales.
  • Fase 3: Las variables globales se llaman campos, algunos de los cuales pueden ser privados e invisibles desde fuera del mini programa.
  • Fase 4: La idea de tener elementos privados y públicos es esconder la implementación de los detalles y mostrar una interfaz más limpia. Y esto es la encapsulación.
  • Fase 5: Encapsulación significa que la lógica de mi negocio no necesita de los detalles de la implementación para funcionar.

Otro ejemplo para programación funcional:

  • Fase 1: Programación funcional significa hacerlo todo encadenando funciones deterministas.
  • Fase 2: Cuando las funciones son deterministas, el compilador puede predecir cuándo puede cachear un resultado, saltarse una evaluación o cuando es seguro detener una evaluación de forma prematura.
  • Fase 3: Para poder soportar Evaluaciones parciales o perezosas, el compilador necesita que las funciones estén definidas en términos de cómo transformar un simple parámetro en otra función. A esto lo llamamos Currying.
  • Fase 4: Algunas veces, el compilador puede hacer Currying por mi.
  • Fase 5: Al permitir al compilador trabajar en los detalles, yo puedo escribir programas que describan qué es lo que quiero en lugar del cómo.

3. Habilidades deficiente para investigar / Conocimiento pobre de las características de la plataforma

Los lenguajes modernos y los frameworks suelen venir con una increible cantidad de recursos y funcionalidades ya preparados para su uso. Plataformas como Java, .NET o Cocoa poseen listados tan largos que incluso un desarrollador con experiencia puede tardar años en asimilarlos. Sin embargo, un buen programador sabrá buscar una función preconstruida para cubrir una necesidad antes de lanzarse a programar una desde cero.

Síntomas

Éstos son solo indicativos del problema si éste persiste en el tiempo más allá de una fase razonable de aprendizaje.

  • Reinventar or crear estructuras básicas que ya están presentes en el lenguaje de forma nativa.
  • Reinventar clases y funciones que ya están presenten en un framework de forma nativa.
  • Correos continuos del tipo ‘Enviame el código, por favor’ a los foros de ayuda.
  • Solucionar una tarea aplicando un mayor número de funciones del que sería necesario si conocemos el lenguaje.
  • Usar de forma persistente técnicas antiguas cuando éstas han sido reemplazadas por otras modernas y más efectivas. Por ejemplo, continuar utilizando funciones delegadas en lugar de usar expresiones lambda.
  • Permanecer constantemente en nuestra zona de confort y resolver así problemas complejos utilizando primitivas.

Remedios

Un programador no puede adquirir este tipo de conocimiento sin un lento aprendizaje. Sin embargo, se requiere de la habilidad para buscar información con un mínimo esfuerzo, lo cual puede significar que tenga que compartir escritorio con un grueso libro junto al teclado, o tener un segundo monitor dedicado a mostrar constantemente un navegador con resultados útiles sobre aquello en lo que se trabaja.

Para iniciarnos en el hábito de mejorar nuestras habilidades, una buena práctica es coger nuestro código antiguo y tratar de refactorizarlo hasta conseguir una reducción del orden de 10:1 en el número de instrucciones empleadas.

4. Incapacidad para entender punteros

Si no somos capaces de entender los punteros, el tipo de programas que somos capaces de escribir se reduce de forma considerable. El concepto de puntero es la llave que permite la creación de estructuras complejas de datos y APIs eficientes. Tendríamos aquí que tener en cuenta que existen lenguajes que manejan referencias en lugar de punteros los cuales, aunque actúan de una forma similar pero automatizando algunos aspectos, requieren de conocer el concepto en si mismo para elaborar estructuras eficientes.

Síntomas

  • Incapacidad para crear una lista enlazada, o escribir un código que inserte / elimine nodos de una lista o un árbol sin peder datos.
  • Asignar grandes arrays para colecciones de longitud variable y mantener un contador separado con dicha longitud en lugar de utilizar una estructura dinámica de datos.
  • Incapacidad para encontrar o corregir los errores tras realizar operaciones aritméticas en punteros.
  • Hacer una copia del puntero, cambiar su valor en la copia y asumir que el puntero original continúa apuntando a su antiguo valor.
  • Ordenar una matriz de punteros realizando una comparación sobre sus propios valores.

Remedios

Mi amigo José estaba alojado en alguna habitación del hotel pero yo no sabía en cuál. Como si que sabía dónde estaba nuestro amigo Alberto, llamé a su puerta y le pregunté si el conocía dónde se alojaba José; me contestó que no, pero si que sabía el número de habitación de otro de nuestros colegas, Miguel. Fui entonces a su habitación y le pregunté. Me dijo que José se alojaba en la 414. Tras agradecérselo, me dirigí a la habitación 414 y, efectivamente, allí estaba mi amigo.

Los punteros pueden describirse siguiendo varias metáforas igual que puede hacerse con las estructuras de datos. El ejemplo de arriba es solo una forma de ejemplificar la idea que hay detrás. Historias como esa, pueden resultar útiles para convertir especificaciones en estructuras mentales más fáciles de asimilar.

5. Dificultad para ver a través de la recursión

La idea de la recursión es sencilla de entender pero los programadores a menudo tienen problemas para imaginar el resultado de una operación recursiva mentalmente, o cómo un resultado complejo puede ser calculado a través de una función muy simple. Esto hace difícil diseñar una función recursiva efectiva porque tenemos problemas en dibujarlas primero en nuestra cabeza. A todo este conjunto de operaciones las llamamos algoritmia.

Síntomas

  • Superposición de algoritmos iterativos para soluciones que pueden alcanzarse de una forma sencilla y elegante con recursividad.
  • Funciones recursivas que comprueban el estado de la base tanto antes como después de cada llamada.
  • Funciones recursivas que no comprueban en ningún momento el estado/valor de la base.
  • Subrutinas recursivas que concatenan / suman una variable global.
  • Confusión entre qué pasa a una llamada recursiva como parámetro o llamadas recursivas que pasan un parámetro sin modificar en cada iteración.
  • Pensar que el número de iteraciones va a pasar como parámetro.

Remedios

Ármate de paciencia y asume que vas a desbordar la pila varias veces hasta dar con el resultado correcto. Comienza escribiendo código con una única base-condición que pase como parámetro a una función recursiva. Codificamos y ejecutamos el algoritmo. ¿Desbordamos? Pues modificamos la lógica y volvemos a probar. El objetivo es finalmente alcanzar una visión completa del camino que recorren los datos dentro de un algoritmo hasta el punto de que podamos seguir varios hilos de forma simultánea. Es costoso, pero se puede practicar.

6. Desconfianza en el código

Síntomas

  • Escribir funciones del tipo isNull, isNotNull, isFalse
  • Comprobar si un valor tipado como booleano se corresponde con algo diferente de true o false.

Remedios

Estás tratando de aplicar tus conocimientos de un lenguaje (por ejemplo de tipado blando) a otro (de tipado duro)? Si no es así, volvemos al primer punto sobre la incapacidad de razonar sobre un código.

Muchos de estos posibles síntomas se relacionan más con una dependencia sobre un código dentro de nuestra zona de confort que con una incapacidad natural para razonar una estructura abstracta. El único remedio aquí puede ser el de concedernos más tiempo para familiarizarnos con el lenguaje y construir así un conocimiento sólido.

Conclusión

Todos los puntos anteriores deben tomarse como un mero recetario con el que medir nuestra mayor o menor habilidad con un lenguaje o nuestra mejor o peor comprensión sobre una determinada plataforma.

El análisis de Lawrence puede resultar muy útil cuando por ejemplo, estamos aprendiendo un nuevo sistema y queremos saber si estamos listos para aplicarlo sobre un entorno real de producción.

Sin embargo, como el autor resuelve más arriba, cuando nos enfrentamos a una tecnología nueva, el mejor aprendizaje es el que obtenemos iniciando un proyecto real y tratando de superar las dificultades reales que surgen.

Finalmente, un acceso fluido a la información, la constancia y el no tener miedo a los errores, constituyen la clave para no quedarnos en el camino.

El resto del artículo, lo tenéis aquí.

Más:

{4} Comentarios.

  1. Muchas gracias por la traducción!
    Muy buen artículo, lo que está claro es que el tema más importante es la experiencia y la práctica continua.

  2. Laura

    Gracias por el artículo. A mi me ha servido para mirarme al espejo y verme realmente 🙂

    Un saludo!

  3. Carlos

    Tus articulos son muy buenos

  4. Tore

    No se yo. Creo que estos artículos solo son leidos por programadores experimentados que solo buscan saber si solo son unos paquetes programando. No creo que alguien pueda avanzar en su conocimiento leyendo esos puntos. Basicamente obtienes ese conocimiento con la experiencia.

    Seria increible que uno los leyese y se dijese “Ayba!”, pero lo dudo.

    😛

    Un programador con dudas.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *