miércoles, 6 de junio de 2007

Precauciones con los Callbacks

¿Callbacks? ¿Qué es eso?

Una posible definición de "callback" podría ser que se trata de una función "A" que es llamada desde una función "B", con la particularidad de que esta última es una "caja negra", es decir, que no podemos tocar su código o sobreescribirla. De este modo, se nos permite modificar su comportamiento sin alterar su funcionalidad básica.

En CakePHP los modelos y controladores tienen varios CallBacks correspondientes a diversos "eventos", como beforeSave, afterFind, etc. Esto nos permite añadir lógica específica para esos momentos, sin tener que reescribir cosas como el método Save para un modelo concreto. Es un buen lugar para cambiar el formato de datos, recolectar estadísticas, borrar archivos asociados a un modelo cuando borramos éste, y muchas cosas más.

No sólo eso, sino que los behaviors, components y helpers también tienen sus CallBack que son llamados por sus modelos, controladores y vistas respectivos en el momento adecuado.

Esto nos permite tener una organización del código bastante buena, así como seguir un proceso de trabajo relativamente seguro.

Por lo que respecta a la organización, podemos separar la lógica principal "de negocio" de la lógica, digamos, "burocrática" de preproceso y postproceso.

Por ejemplo, acabo de escribir un behavior "upload" que se encarga de guardar en el lugar adecuado un archivo recién subido desde un formulario de modelo y rellenar el campo con la ruta al archivo. De este modo, las acciones add y edit del controlador no hacen más que lo que tienen que hacer (obtener los datos del POST y crear o actualizar el modelo), mientras que el behavior se encarga del trabajo "sucio" de mover el archivo en el callback beforeSave, dejándolo todo listo para guardar.

En cuanto al proceso de desarrollo, una de las ventajas de trabajar con los CallBacks es que puedes ir creando la lógica básica de la aplicación y dejando para una segunda fase los detalles relativos a filtros de entrada y salida de datos. De este modo se aislan esas partes de código y es más fácil depurar los posibles problemas, que quedan bien localizados.

¿Y las precauciones?

Del mismo modo que los CallBacks resultan muy prácticos a la hora de organizar el código, también es cierto que hay que tomar algunas medidas para asegurarnos de que las cosas van a funcionar correctamente.

Por ejemplo, model::beforeSave se va a ejecutar cada vez que hagas un model::Save (normalmente en acciones del controlador omo add o edit, pero también en otras partes) o un model::saveField. En cualquier caso tienes que asegurarte de que tu CallBack sabe lidiar con los datos que va a recibir. He aquí un caso práctico:

Caso práctico moderadamente vergonzante para el autor

Acabo de pasar unas horas intentado averiguar por qué un simple model::saveField no guardaba los datos de un campo. Pues todo "por culpa" de un CallBack en un Behavior (el upload que citaba antes). Lo que yo quería hacer era borrar el campo que contenía la ruta a un archivo mediante una llamada model::saveField ('archivo', null). Por supuesto, ese campo en concreto era procesado por el behavior. El modelo llama al beforeSave del behavior desde model::saveField, como acabo de decir más arriba, y justamente trata de procesar ese mismo campo. Al no haber programado un control para esa situación (que el campo 'archivo' no tuviese valores o éstos no tuviesen la estructura de datos para subir archivos) el resultado no tiene sentido y el intento de guardar falla.

Al principio resultaba muy extraño porque la lógica básica era muy sencilla: para borrar el archivo, lee su ruta con el modelo, localiza y elimina el archivo en el sistema de archivos y borra el campo del modelo.

Sólo después de varias horas he acabado dándome cuenta de que ese "error" en el behavior no era consecuencia del problema de no poder guardar el nuevo valor en el modelo, sino justamente al revés: era la causa.

Moraleja(s)

A la hora de depurar, lleva un registro de los Callbacks que puedan afectar a tus datos (tanto en los modelos y controladores, como en los Behaviors, Components y Helpers). Así podrás ir descartando ubicaciones del problema.

Asegúrate de que identifican correctamente el tipo de datos que pueden/deben manejar y que saben cómo actuar si los datos vienen con otra estructura, aunque sea válida.

Modulariza todo lo que puedas. Nunca se tiene bastante RAM ni se modulariza el código lo suficiente.

No hay comentarios: