El tercer capítulo del libro toma el nombre de una de las unidades de código más importantes, las funciones (aunque en POO se les denomine métodos, también hace referencia a estos).
La primera regla del club de las funciones es que no se habla del club de las funciones es que tienen que ser pequeñas, la segunda es que tienen que ser más pequeñas. Quien no se ha enfrentado (o hecho) funciones de 100, 200, 300 lineas? Y quien ha entendido lo que hacían en menos de 3 minutos? El autor dice que su máximo es de 20-30 líneas por función (no vale meter varias instrucciones en una misma linea, que nos conocemos), y como se consigue pasar de 200 a 30? Agrupando código en funciones más pequeñas con un buen nombre (eso que aprendimos en el capítulo anterior) sin estructuras anidadas y con no más de 2 niveles de sangría.

Haz una cosa

FUNCTIONS SHOULD DO ONE THING. THEY SHOULD DO IT WELL. THEY SHOULD DO IT ONLY

Para mi está es la regla de oro: que una función haga solo una cosa. Lo difícil es determinar que es «una cosa». Si te ves tentado a poner un and en el nombre, vamos mal. Una manera de saber si una función hace más de una cosa es intentar extraer otra función cuyo nombre no sea simplemente una reexpresión de su implementación.

Otra clara señal es si la función puede dividirse en bloques de código (declaración, inicialización y ejecución por ejemplo), a veces dejamos una línea en blanco para entender mejor unas líneas, o incluso ponemos un pequeño comentario explicativo (truco personal: si se entiende mejor con un comentario, mételo en una función y usa el comentario de nombre).

Un nivel de abstracción por función

Esto es claro y simple, si en una función hay más de un nivel de abstracción: caca. La idea es que cada llamada a una función tenga un nivel de abstracción más profundo.

Switch e If/else

Por su naturaleza tanto los Switch como los If/else sirven para hacer varias cosas, por lo que al usarlos rompemos la premisa de una única funcionalidad y es muy fácil hacer que choquen con la de un nivel de abstracción: son el demonio.
Que alternativas hay? Una es usar un patrón Factory o Abstract Factory (no me voy a extender en explicarlo de momento, queda pendiente para un artículo futuro, pero he puesto enlaces a la wikipedia).
Pero como va a ser malo usar un switch o un if/else, si lo uso a diario?
Cada herramienta tiene su momento, si quieres programar rápido sigue usándolos, pero si quieres hacerlo de una manera Clean, con todas las ventajas que conlleva, que no sean tu primera opción.
El autor recomienda evitarlos excepto si solo aparecen una única vez, se usan para crear objetos polimórficos, o están escondidos bajo una herencia. Por supuesto, también reconoce que en ocasiones no cumple esta regla (ni yo tampoco 🙂 ).

Usar nombres descriptivos

Nunca me cansaré de repetir esto, y nunca me cansaré de oír «luego le cambio el nombre». Tienen la misma importancia que nombrar variables y se aplican las mismas reglas. Se debe hacer especial énfasis en que los nombres tengan un estilo similar: usar las mismas palabras, estructura, sintaxis,… hace que cuenten una historia. Si en una serie de funciones: addCar, addTruck y addBus hay una que rompe el esquema como trainAdd, insertTrain, addTrains o addOneTrain hace pensar que no se comporta de la misma manera.
Es muy recomendable usar verbos en el nombre: write(name) deja muy claro que escribe un name, sea lo que sea. Y lo es aún más usar palabras clave: writeField(name) ademas nos indica que name es un field.

Argumentos

El número ideal de argumentos para una función es cero. Luego viene uno, seguido de cerca por dos. Deben evitarse tres argumentos cuando sea posible. Más de tres requiere una justificación muy especial y no debería usarse.
A menor número de argumentos, es más fácil entender que hace una función leyendo solo su nombre. También facilita el testing: cuantos más argumentos, mayor es el número combinaciones que hay que probar. Eso si, que esto no se convierta en una fiesta de variables globales para no usar argumentos, la finalidad de reducir los argumentos es reducir la complejidad de las funciones.
Los argumentos de salida son aún más complejos de entender, evitar en la medida de lo posible devolver información modificando los inputs, únicamente debería hacerse mediante un return.

Un único argumento

Hay 2 razones principales para pasar un único argumento en una función: preguntar algo sobre ese argumento u operar con él. La tercera razón más común es utilizar la función como un evento (hay un input, pero no un output) y utilizar el argumento para alterar el estado del sistema (hay que ser cuidadoso con el nombre que se le da, para no inducir a error).

Argumentos Flag

Pasar un boolean a una función es una práctica realmente terrible (lo se, todos lo hemos hecho). Complica la firma de la función y está proclamando que hace más de una cosa (una si es true y otra si es false). Deberíamos dividirla en 2: una para cada opción.

Dos o tres argumentos

Cuando se pasan 2 o 3 argumentos, se aumenta ligeramente la complejidad. Cuantas veces no estamos seguros de en que orden hay que pasar los argumentos?

Argumentos Objeto

Cuando necesitamos pasar más de 2 o 3 argumentos, es una clara señal de que varios pueden agruparse en un objeto. Viendo estos 2 ejemplos:
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);

De buenas a primeras no sabría decir que significan X e Y, pero en el segundo ejemplo queda bastante claro (y es un claro indicador de que X e Y forman parte de un concepto que merece un nombre propio).

Sin efectos secundarios

Si una función tiene un nombre que indica que hace algo, hay que evitar que haga otra cosa adicional. No solo rompe la regla de «solo una cosa», sino que da pié a confusión.

Responder a una pregunta o hacer algo

Una función debería devolver una respuesta a una pregunta o realizar una acción, pero no ambas cosas a la vez. Puede parecer útil que ademas de realizar un proceso se devuelva un estado, así nos evitamos hacer otra llamada solo para obtener el estado (el típico si se cumple X, haz Y y devuelve true, sino devuelve false). El autor plantea que si lo vemos usado como if(metodo(arg1, arg2))… como sabemos si la idea era llamarlo para realizar la acción, o solo para obtener el estado

Mejor Excepciones que devolver un error

Cuando devuelves un código de error, haces que se tenga que tratar inmediatamente, ademas de que viola la regla «Haz una cosa».
Manejar la excepción es también hacer una cosa, por lo que el contenido del try debería extraerse a una función aparte.
Generalmente, los códigos de error se manejan en una clase aparte, por lo que, añadir un nuevo error supone modificar esa clase y todos los sitios donde se pueda capturar el nuevo error… Que levante la mano quien le está apeteciendo mogollón revisar el código para encontrar donde se puede capturar ese error! Y que levante la mano quien reutilizaría un error que ya hay para evitarlo, pues eso es lo que pasa y se acaban haciendo chapuzas por una mala decisión.

No te repitas (DRY: Don’t Repeat Yourself)

Repetir algo supone multiplicar el tiempo de mantenimiento y aumenta la posibilidad de errores. Es muy fácil olvidarse de replicar un cambio en alguno de los sitios donde se repite.

Este punto es bastante fácil de entender, pero no solo afecta al código: documentación, gestión de horas,… Lo más grave que he visto ha sido clonar un proyecto entero para aplicarlo a otro cliente y que cada uno evolucione a su ritmo, una locura.

Programación estructurada

Cada función y cada bloque de código debe tener una única entrada y salida. Solo debería haber un return, no debería haber breaks o continues en los loops y nunca un goto (por suerte han desaparecido en los lenguajes modernos). Usarlos es útil en funciones grandes, al crear funciones pequeñas dejan de serlo.
Personalmente no concibo loopear un array entero en busca de un elemento si ya lo he encontrado, es una regla a seguir, pero en ocasiones me la salto por motivos muy concretos.

Como se escriben funciones así?

Es imposible muy difícil cumplir todos estos preceptos a la primera, el truco está en conseguir un código que funcione y una vez conseguido refactorizar poco a poco hasta que quede limpio. Con la experiencia se van haciendo necesarios menos pasos, pero únicamente con una buena batería de tests puedes asegurar que la refactorización no ha roto nada por el camino (sin tener que estar probando continuamente todo, claro).

 

Y eso es todo, este resumen ha quedado un poco largo, pero había muchas cosas que contar. Espero que os haya sido útil y os ayude a escribir código más legible.

Categorías: libros

0 comentarios

Deja una respuesta

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