Cómo comentar el código

En la lista de libros frecuentemente citados y raramente leídos, probablemente encontraríamos desde “El fin de la historia y el último hombre”, de Francis Fukuyama, hasta “Code Complete”, de Steve McConnell. Este último es el que voy a citar aquí, sin haberlo leído para seguir la tradición, concretamente su muy extendida afirmación de que los comentarios no tan solo no son necesarios sino que pueden conseguir el efecto contrario: un código complejo que el programador no trabaja para simplificarlo por tener a su disposición el camino, inicialmente más breve, de los comentarios.

En mi humilde opinión, aunque estoy de acuerdo en gran parte con la opinión de McConnell y otros, los comentarios sí son necesarios. Estos deben describir el porqué o describir qué se está haciendo, no el cómo, pues es el cómo lo único que puede delegarse a un código claro y descriptivo. Cuando el proyecto carece de documentación o no se mantiene actualizada, los comentarios que explican el porqué son aun más necesarios.

A continuación, veamos algunos ejemplos prácticos para ilustrar mi opinión:

class Model_Session
{

   /**
    * Sigue el patrón singleton para encapsular la sesión.
    * No extiende Model_Signia_Abstract porque debe definir construct privado.  
    */
   private $calledClass;

   protected function __construct($options)
   {
      $this->calledClass = explode('_', get_called_class())[1];
   }

   final static function getInstance($options = null)
   {
      static $instances = array();

      $calledClass = get_called_class();

      if (!isset($instances[$calledClass])) {
         $instances[$calledClass] = new $calledClass($options);
      }

      return $instances[$calledClass];
   }

   public function __clone()
   {
      trigger_error("An instance of the class " . __CLASS__ . " already exists.", E_USER_ERROR);
   }

   protected function set($value)
   {
      $_SESSION[$this->calledClass] = $value;
   }

   public function get($value)
   {
      if (isset($_SESSION[$this->calledClass][$value])) {
         return $_SESSION[$this->calledClass][$value];
      } else {
         return false;
      }
   }

   public function isLogged()
   {
      return isset($_SESSION[$this->calledClass]);
   }
   
   public function delete($key)
   {
      unset($_SESSION[$this->calledClass][$key]);
   }
   
   public function logout()
   {
      unset($_SESSION[$this->calledClass]);  
   }
   .
   .
   .
}

Algunos consideran que singleton es un antipatrón de diseño, pero este código está ahora mismo solucionando necesidades reales de empresas y soy de la opinión de que la teoría existe para ayudarnos, no para interferir en aquello por lo que nos pagan. En la misma línea, aunque trigger_error debería evitarse, en los métodos mágicos prefiero lanzar un error a lanzar una excepción. Aclarado esto, regresemos al tema que ocupa este artículo. Con la primera línea del comentario explico por qué y para qué uso el patrón:

Sigue el patrón singleton para encapsular la sesión.

La segunda línea está pensada para responder tanto el porqué que me pueda plantear yo cuando revise el código tiempo después y no recuerde por qué lo programé así, como el que se pueda plantear otro programador que vea esta clase por primera vez:

No extiende Model_Signia_Abstract porque debe definir construct privado.

El resto de la clase no tiene más comentarios y tampoco le hacen falta, pero, ¿qué sucede cuando los nombres de las variables y métodos no son descriptivos? Que nos vemos obligados a crear comentarios que clarifiquen cómo funciona la rutina. Comparemos estos dos métodos:

function permsM($m)
{
      $perms = [];
      
      foreach (['add', 'view', 'edit', 'delete'] as $a) {
         if (isset($this->get('permissions')[$a]) && in_array($m, $this->get('permissions')[$a])) {
            $perms[] = $a;
         }
      }

      return $perms;
}

Probablemente el ejemplo esté un poco forzado, pero sin duda demuestra que, o bien añadimos comentarios por doquier, o bien usamos nombres descriptivos:

public function getPermissionsForModule($sModule)
{
      $aPermissions = [];
      
      foreach (['add', 'view', 'edit', 'delete'] as $action) {
         if (isset($this->get('permissions')[$action]) && in_array($sModule, $this->get('permissions')[$action])) {
            $aPermissions[] = $action;
         }
      }

      return $aPermissions;
}

Cuando tenía unos 9 años hice mi primer “programa”, lo sufrió un Zx Spectrum 128, su cantidad de memoria estaba contenida en su nombre, en Kilobytes. Con semejante restricción, entiendo que en la época tenía lógica optimizar el consumo de memoria incluso recortando el nombre de las funciones y variables, pero a día de hoy eso no tiene ningún sentido. Además, tanto los IDE como sencillos editores (por ejemplo Notepad++), disponen de la funcionalidad de autocompletar texto, que nos ayuda a recordar y escribir correctamente los nombres de variables, funciones, métodos y clases sin importar cuán largos sean. Por lo tanto, a día de hoy no hay excusas para no usar nombres descriptivos.

En este sentido, las constantes ayudan enormemente:

if ($file['error'] === 0 { //¿Mande? ¿Fue bien o mal?

if ($file['error'] === UPLOAD_ERR_OK {

Podemos llegar a ser más papistas que el Papa con el código que se explica a si mismo:

if ($file['size'] > 5242880) { // 5 MB

if ($file['size'] > 5*1024*1024) {

Otros comentarios necesarios

– Cuando, por falta de tiempo, se ha tenido que programar de forma poco óptima, indicarlo ayudará más adelante, cuando el proyecto haya sido entregado, a reconocer las partes que necesitan refactoring más urgentemente.

– Existen expresiones que son complejas y no hay azúcar sintáctico que las simplifique, como por ejemplo las expresiones regulares; en estos casos, comentar qué se está buscando aumentará la productividad de todo el equipo.

– Finalmente, cuando hemos copiado un código con licencia que lo permita, es de caballeros citar el autor y la fuente (si es que no estamos obligados directamente por la misma).

En definitiva, si cada vez que escribimos un comentario nos preguntamos si es realmente necesario o, si por el contrario, está encubriendo un código poco legible, conseguiremos elaborar software más fácil de mantener, tanto para nosotros como para quienes vengan después. Si describe cómo procesa, lo más probable es que nos estemos haciendo trampas.

MySQL Cannot Add Foreign Key Constraint

Cuando estamos alterando la estructura de un tabla en MySQL, nos podemos encontrar con el siguiente error al añadir una llave foránea:

MySQL Cannot Add Foreign Key Constraint

Estos errores con poca información son más molestos de lo habitual. Como las posibles razones por las que el motor de la base de datos no ha podido crearla son diversas, sería ventajoso evitar tener que estudiarlas una por una. Esto lo podemos lograr con la siguiente orden:

SHOW ENGINE INNODB STATUS;

En la sección “LATEST FOREIGN KEY ERROR” encontraremos una descripción del error más completa.

| InnoDB | |
=====================================
2018-02-20 11:22:34 0x7f075c175700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 25 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 12622 srv_active, 0 srv_shutdown, 5333759 srv_idle
srv_master_thread log flush and writes: 5344820
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 19660
OS WAIT ARRAY INFO: signal count 17755
RW-shared spins 0, rounds 11780, OS waits 6140
RW-excl spins 0, rounds 17899, OS waits 382
RW-sx spins 738, rounds 11751, OS waits 121
Spin rounds per wait: 11780.00 RW-shared, 17899.00 RW-excl, 15.92 RW-sx
------------------------
LATEST FOREIGN KEY ERROR

------------------------

2018-02-19 09:36:53 0x7f075c208700 Cannot drop table `serca`.`promo_landing`
because it is referenced by `serca`.`promo_landing_i18n`
------------
TRANSACTIONS
------------
Trx id counter 1864352
Purge done for trx's n:o < 1864352 undo n:o < 0 state: running but idle
History list length 808
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421144897607520, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
--------
FILE I/O
--------
I/O thread 0 state: waiting for completed aio requests (insert buffer thread)
I/O thread 1 state: waiting for completed aio requests (log thread)
I/O thread 2 state: waiting for completed aio requests (read thread)
I/O thread 3 state: waiting for completed aio requests (read thread)
I/O thread 4 state: waiting for completed aio requests (read thread)
I/O thread 5 state: waiting for completed aio requests (read thread)
I/O thread 6 state: waiting for completed aio requests (write thread)
I/O thread 7 state: waiting for completed aio requests (write thread)
I/O thread 8 state: waiting for completed aio requests (write thread)
I/O thread 9 state: waiting for completed aio requests (write thread)
Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
ibuf aio reads:, log i/o's:, sync i/o's:
Pending flushes (fsync) log: 0; buffer pool: 0
310347 OS file reads, 83964 OS file writes, 23539 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.92 writes/s, 0.52 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 7 merges
merged operations:
insert 4, delete mark 3, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
Hash table size 34673, node heap has 2 buffer(s)
Hash table size 34673, node heap has 14 buffer(s)
Hash table size 34673, node heap has 3 buffer(s)
Hash table size 34673, node heap has 35 buffer(s)
Hash table size 34673, node heap has 8 buffer(s)
Hash table size 34673, node heap has 9 buffer(s)
Hash table size 34673, node heap has 4 buffer(s)
Hash table size 34673, node heap has 2 buffer(s)
11.56 hash searches/s, 1.68 non-hash searches/s
---
LOG
---
Log sequence number 567281250
Log flushed up to 567281250
Pages flushed up to 567281250
Last checkpoint at 567281241
0 pending log flushes, 0 pending chkp writes
15014 log i/o's done, 0.28 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 42107376
Buffer pool size 8191
Free buffers 1024
Database pages 7090
Old database pages 2597
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 150106, not young 4595614
0.16 youngs/s, 0.00 non-youngs/s
Pages read 308306, created 2548, written 65550
0.00 reads/s, 0.00 creates/s, 0.60 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 7090, unzip_LRU len: 0
I/O sum[18]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Process ID=1022, Main thread ID=139669620905728, state: sleeping
Number of rows inserted 12470971, updated 1849, deleted 367, read 104405344
0.24 inserts/s, 0.00 updates/s, 0.00 deletes/s, 2852.41 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

Como excusa para probar Simple Screen Recorder en mi portátil, he hecho el siguiente vídeo en el que explico lo mismo: