Clean Code: Digest (Capítol 3: Funcions)

Àngel Fernández
Calidae Blog
Published in
6 min readAug 9, 2016

Fa unes setmanes vaig escriure un post resumint el que diu el llibre Clean Code: A Handbook of Agile Software Craftsmanship de Robert C. Martin sobre Codi net i Noms significatius, els dos primers capítols.

Avui toca entrar en el meravellós món de les funcions, probablement el que podem considerar la unitat més petita dels nostres sistemes, els maons que ens serveixen per construir i crear. Més val que siguin de qualitat.

Petites!

La primera regla de les funcions és que han de ser petites. La segona regla de les funcions és que encara han de ser més petites.

Blocs i identació

La regla anterior implica que declaracions com if o while haurien de ser d’una sola línia (probablement una crida a una altre funció). També implica que les funcions no han de contenir estructures niades, i mai haurien de tenir més de un o dos nivells d’identació.

Les funcions han de fer una cosa. L’han de fer bé. I només han de fer allò.

function sendNewsletterToSubscribers(){
var subscribers = getSubscribers();
var template = getLastTemplate();
Mail.send(template, subscribers);
}

Qualsevol funció s’hauria de poder descriure amb un breu paràgraf “per a”: Per a sendNewsletterToSubscribers(), agafem la llista de subscriptors i els enviem a traves del servei de Mails l’última plantilla de newsletter.

Seccions en funcions

Quan una funció es divideix de forma lògica en seccions (per exemple marcant amb comentaris: 1. declarations, 2. inicializations, 3. algorithm) és un clar símptoma que la funció està fent més d’una cosa.

Un sol nivell d’abstracció per funció

Barrejar nivells d’abstracció és confós. Un lector no podrà saber si una sentencia és essencial o només un detall. Una funció mai hauria de tenir a la vegada coses com getHtml() i Something.append(‘\n’) al mateix temps.

Llegint codi de dalt cap a baix

Volem llegir el codi com una narrativa de funcions de més abstracta a menys. Llegir el programa com un conjunt de paràgrafs “per a”, cadascun descrivint l’actual nivell d’abstracció i referenciant als subsegüents paràgrafs “per a”.

Per a agafar la llista de subscriptors els demanem a la BBDD a través del ORM.
Per a demanar la llista de subscriptors a la BBDD executem la consulta “SELECT …”

Sentències Switch

És difícil crear un switch petit que faci una sola cosa. Per la seva naturalesa, un switch fa N coses. A vegades no els podem evitar, però sempre podem amagar-los a baix nivell gràcies al polimorfisme.

Fes servir noms descriptius

Recordant una mica el capítol 1, un nom llarg i descriptiu és millor que un nom curt i enigmàtic. Quan més petita i enfocada sigui una funció, més fàcil es trobar un son descriptiu.

Arguments de funcions

El número ideal d’arguments per a una funció es zero (nidalic). Després un argument (monadic), seguida de dos arguments (dyadic). Hauriem d’evitar sempre que puguem fer servir 3 arguments (triadic). I encara més les funcions amb més de 3 arguments (polyadic).

Els arguments tenen un gran poder conceptual, i poden complicar la llegibilitat del codi.

  • Monadic: Serveixen per preguntar sobre un argument, com a boolean fileExists(“file”). O per operar sobre l’argument, com a InputStream fileOpen(“file”).
  • Arguments flag: Son lletjos. Passar-li un booleà a una funció diu clarament que farà dues coses, una quan sigui cert i una altre quan sigui fals. Sempre és millor dividir la funció en dues.
  • Dyadic: Un clar exemple és p = new Point(0,0). Entenem perfectament què implica cadascun dels arguments, i en quin ordre han d’anar. En canvi, una funció com assertEquals(expected, actual) es problemàtica, ja que l’ordre dels arguments no es natural i podrien anar perfectament al reves, podent generar confusió al llegir alguna crida cap a aquesta funció.
  • Triads: Si l’ordre dels arguments d’una funció dyadic pot portar a confusió, 3 arguments augmenten considerablement el problema. Hem d’anar molt en compte si creen un triad.
  • Objectes com a argument: Quan una funció necessita més de 2 o 3 arguments, segurament podem agrupar aquests en una classe que els modeli.
    Per exemple, podem canviar aquest triad:
    Circle makeCircle(double x, double y, double radius)
    per un dyad:
    Circle makeCircle(Point center, double radius)
  • Llistes d’arguments: A la crida String.format(“%s worked %.2f hours”, name, hours) la podríem classificar com de tipus triad. Però realment es pot tractar com un dyad, perquè el segon i subsegüents arguments són equivalents i el seu ordre és lògic, i els podríem substituir per un objecte tipus llista.
  • Verbs i paraules clau: De nou, escollir un bon nom per una funció pot ajudar a entendre l’objectiu i l’ordre dels arguments. En cas d’un monad, la funció i l’argument poden formar un bon parell de verb/nom. Per exemple, write(name) és molt clar. O writeField(name) és encara més clar. També podríem solucionar el problema de la funció dyadic “assertEquals” amb assertExpectedEqualsActual(expected, actual).

No tenir efectes secundaris

Els efectes secundaris són mentides. Si la teva funció promet fer una sola cosa i en fa una altra també, està mentint. Per exemple, si una funció boolean checkPassword(user, pass) quan comprova que el password és correcte fa un Session.initialize() està creant un efecte secundari no esperat pel programador que utilitza a la funció.

Arguments de sortida

Hem d’evitar sempre que sigui possible tractar un argument per referència.
Si veus una crida appendFooter(s), què fa? Afegeix “s” com el peu d’alguna cosa? O afegeix algun peu a “s”? Hem d’anar a mirar la declaració de la funció i veure void appendFooter(StringBuffer report) per entendre el que fa.
Amb la programació orientada a objectes, es fàcil evitar aquests errors reescribint la crida dins d’una classe: report.appendFooter();

pd: “s” és un mal nom de variable com vam veure a la Part 1.

Command Query Separation (CQS):

Les funcions han de fer quelcom o contestar quelcom, però no ambdues coses, ja que sol portar a confusions.
Per exemple, public boolean set(String attribute, String value) és una funció que estableix el valor d’un atribut i retorna cert si tot ha anat bé o fals si l’atribut no existeix.

Això porta a coses com if(set(“username”, “angel”))… Què vol dir? Podria estar preguntant si a username hi ha el valor angel. És difícil de saber perquè set podria ser un verb o un adjectiu. L’autor l’ha definit com a verb, però al estar dins d’un if fa pensar que és un adjectiu.

La solució és separar l’ordre de la pregunta:

if (attributeExists("username")){
setAttribute("username", "angel");
...
}

No retornar codis d’error

Els llenguatges d’avui en dia porten gestió d’exepcions. No te cap sentit veure coses com if (deletePage(page) == E_OK), ja que trenca el CQS i no té cap sentit existint el concepte d’excepció.

La gestió d’errors ja és UNA cosa (i les funcions només n’han de fer una, recordes?)

Si una funció té un bloc try/catch, el seu objectiu es gestionar un error. No ha de fer res més, dins del bloc try ha d’existir una crida a una funció que fa també una sola cosa oblidant-se de la gestió d’errors.

No et repeteixis (Don’t Repeat Yourself)

La duplicació perfectament podria ser l’arrel de tot el mal al software. Si fas dues coses molt similars, pensa si es pot modelar separant la part comuna d’ambdues.

Programació estructurada

Segons Dijkstra, cada funció ha de tenir una sola entrada i una sola sortida. Per tant, un sol return per funció, i mai fer servir coses com break o continue. I no parlem de sentències goto.

Aquestes regles tenen sentit en funcions grans, però si programem funcions petites pot ser que trencar amb múltiples return, un break o continue ajudi a fer més expressiva la intenció de la funció. Però un goto NO. MAI.

I com escric funcions així?

Com quan escrius qualsevol altre cosa.

Quan escrius un correu electrònic important, primer escrius totes les idees d’una forma una mica caòtica. Un cop tens el primer esborrany, el vas repassant i donant-li forma fins que es llegeix com tu volies expressar-ho.

Escriure una funció és un procés similar. Serà llarga i caòtica, i poc a poc, gràcies a anar refactoritzant, aconseguiras convertirla en funcions petites amb noms entenedors i completament llegibles. Aquí el procés red-green-refactor és el teu amic.

Tenia intenció de plasmar també un resum dels comentaris, però aquí ja hi ha molta chicha així que seguiré el pròxim dia.

Si vols seguir llegint, pots tornar a:

--

--