Com a desenvolupadors, hem passat molt de temps picant el codi amb l’única preocupació que funcioni, sense preocupar-nos gaire en la forma del codi, arquitectura, escalabilitat, mantenibilitat o llegibilitat.
Tots hem tingut terminis de lliurament estrets, però les presses, quan es donen repetitivament, deriven en un codi cada cop més difícil de mantenir.
No ens hem de conformar a fer les coses perquè funcionin, ens hem de preocupar per obtenir un codi que tots els desenvolupadors puguem entendre.
El que permetrà que diferents equips que no es coneixen puguin treballar amb un mateix producte, no és el llenguatge de programació, sinó les normes, nomenclatures, principis i patrons de disseny que s’apliquen al projecte.
Durant aquest article no parlarem de principis SOLID, ni patrons de disseny, sens dubte un nivell més avançat al món del Clean Code.
Ens centrarem en petites normes que, si tenim en compte durant el nostre desenvolupament, ens permetrà obtenir un codi més llegible que permeti a altres equips mantenir aquest codi sense perdre hores o fins i tot dies intentant entendre una funció. Fins i tot a nosaltres mateixos ens resultarà més fàcil reprendre el nostre propi codi setmanes després.
El mal codi té un cost i costa tant fer les coses bé com fer-les malament. Només cal saber com fer-les millor.
A continuació, us expliquem sis punts bàsics que us ajudaran a millorar el vostre codi i el resultat final del vostre projecte.
El ‘Switch’ no és un mal patró en si, és un condicional que normalment es fa servir per evitar llargs una cadena de ‘if-else-if-…’. Però, tot i així, tenen una sintaxi molt redundant i poden derivar també en mètodes molt llargs a causa de “case” massa grans. Dificultant la llegibilitat i el debugging en distribuir la lògica a cada ‘case’.
Hi ha diferents maneres d’evitar l’ús de ‘Switch’ que deriven el codi més llegible i escalable, i evita la redundància d’un ‘Switch’ amb la mateixa condició.
Personalment, la forma que jo prefereixo és fer servir poliformisme, de manera que la propietat que estem afegint al condicional es converteix en un objecte que implementa la lògica que hauria d’anar al ‘case’.
Vegem-ne un exemple a continuació:
for (var animal in home) {
switch (typeof(animal)) {
case "dog":
animal.bark();
break;
case "cat":
animal.meow();
break;
}
}
for(IAnimal animal in animals) {
animal.speak();
}
interface IAnimal{
Speak();
}
class Dog extends IAnimal
{
Speak(){
return 'Guau'
}
}
class Cat extends IAnimal
{
Speak(){
return 'Miau'
}
}
Que el codi sigui descriptiu és un ‘must’, i és fàcil pensar en anomenar les coses adequadament, encara que també és molt complicat trobar el nom adequat.
Tot i això, només tenir present que hem de descriure millor les coses farà el nostre codi més comprensible i fàcil de llegir.
En el cas dels condicionals, és molt més agraït i immediat llegir i entendre un condicional la condició del qual és un mètode del tipus:
/*Aproximación básica*/
if (isActiveStudent(user))
/*Usando una aproximación más propia de la orientación a objetos*/
if(user?.isActiveStudent())
Que llegir alguna cosa com:
if (user != null && user.RoleId==3 && (user.FinishDate !== null || user.FinishDate > Today))
Només de treure la condició a un mètode descriptiu, la farem entenedora al moment.
Al codi, tant les classes, com funcions i variables han de tenir un nom que reflecteixi la seva intenció.
Per tant, mai s’han d’usar acrònims o variables caràcter, com, per exemple:
f.i var u = User()
Ja que només la persona que les ha creat les entén, i al cap d’un temps, segurament us oblideu també dels acrònims utilitzats.
Les funcions que fan més duna cosa són de les pitjors coses que podem fer picant codi. Aquestes funcions acoblen codi de manera que només es pot anomenar aquest mètode si necessitem fer les “X accions” que fa aquest mètode.
D’aquesta manera, ens veiem obligats a duplicar aquest codi des d’un altre lloc, des del qual necessitem fer certa lògica.
Per això aquestes funcions dificulten entendre el que fa aquest codi i acaben sent mètodes amb excessives línies de codi, quan hem de mantenir els mètodes el més curts possibles. Sempre és millor partir un mètode, encara que només sigui per llegibilitat, que deixar un mètode.
Si, ho sé, si ens tornem extremistes amb aquest principi, mai no podríem definir un mètode que cridarà dos mètodes més en la seva implementació. Ja que aquest mètode pare estaria fent dues coses.
Tranquils/les, no cal tornar boig. L’important a tenir en compte és que el mètode pare, en si, no fa res, ho fan tots els dos mètodes fills, de manera que no estaríem contradient res.
De tota manera, la idea important aquí és intentar mantenir els mètodes tan curts com sigui possible, i encapsular degudament la lògica perquè cada mètode faci el que hagi i no més. Això millorarà increïblement la llegibilitat i reusabilitat del codi.
Repetir una línia de codi és molt greu, deriva en situacions en què toca reparar alguna cosa a tants llocs com aquesta lògica s’hagi duplicat i quan toca evolucionar el codi has de tenir present tots els punts on aquesta lògica s’ha duplicat per aplicar aquest canvi , derivant tot és una pitjor mantenibilitat el codi.
És lògic pensar que com més línies de codi té el projecte més complicada és de mantenir.
Però com evitem duplicar codi?
No hi ha resposta fàcil, però un primer pas és extreure la lògica a funcions degudament parametritzades, o extreure valors en variables.
Vegem-ne un exemple a continuació:
function getAquaticAnimals() {
const allAnimals = getAllAnimals()
const aquaticAnimals = []
for (var animal in allAnimals)
if (animal.type === "aquatic") {
aquaticAnimals.push(animal)
}
}
return aquaticAnimals
}
function getLandAnimal() {
const allAnimals = getAllAnimals()
const landAnimal = []
for (var animal in allAnimals)
if (animal.type === "land") {
landAnimal.push(animal)
}
}
return landAnimal
}
function getFlyingAnimals() {
const allAnimals = getAllAnimals()
const flyingAnimals = []
for (var animal in allAnimals)
if (animal.type === "flying") {
flyingAnimals.push(animal)
}
}
return flyingAnimals
}
Al codi anterior, podem veure que les dues funcions estan fent el mateix, amb l’única diferència del tipus de notícia. Així doncs, si extraiem aquesta lògica en un mètode, podem reutilitzar aquest codi a més de millorar-ne la llegibilitat:
function getAquaticAnimals() {
const allAnimals = getAllAnimals()
return getAnimalNames(allAnimals, 'aquatic')
}
function getRustNews() {
const allAnimals = getAllAnimals()
return getAnimalNames(allAnimals, 'land')
}
function getFlyingAnimals() {
const allAnimals = getAllAnimals()
return getAnimalNames(allAnimals, 'flying')
}
function getAnimalNames(allAnimals, animalType) {
const animalNames = []
for (var animal in allAnimals) {
if (animal.type === animalType) {
animalNames.push(animal.name)
}
}
return animalNames
}
Hauràs notat que l’anomenada “getAllAnimals()” es repeteix en cada mètode, això podria ser una mala pràctica o no, segons l’ús que vulguem donar a la funció reutilitzada getAnimalNames. Pot ser necessari parametritzar el conjunt danimals amb què treballa la funció.
function getAquaticAnimals() {
return getAllAnimalsNameBy('aquatic')
}
function getRustNews() {
return getAllAnimalsNameBy('land')
}
function getFlyingAnimals() {
return getAllAnimalsNameBy('flying')
}
function getAllAnimalsNameBy(animalType) {
const allAnimals = getAllAnimals()
const animalNames = []
for (var animal in allAnimals) {
if (animal.type === animalType) {
animalNames.push(animal.name)
}
}
return animalNames
}
Però, si d’altra banda, aquesta funció sempre treballarà sobre el conjunt total d’animals, és bona opció eliminar el paràmetre allAnimals i deixar els mètodes de la manera següent.
Derivant en un codi més llegible encara que lleugerament menys reusable, encara que sempre podem crear una segona funció getAnimalNames(allAnimals, type) que ens torna la reusabilitat.
function getAllAnimalsNameBy(animalType) {
const allAnimals = getAllAnimals()
return getAnimalNames(allAnimals,animalType)
}
function getAnimalNames(animalsArray, animalType) {
const animalNames = []
for (var animal in animalsArray) {
if (animal.type === animalType) {
animalNames.push(animal.name)
}
}
return animalNames
}
Com a antic i orgullós Boy Scout, sempre solíem dir alguna cosa com “Cal deixar el bosc més net del que ho hem trobat”. Així que, en marxar d’un lloc, recollíem allò que és nostre, i sempre ens vam endur una mica d’escombraries que trobàvem al nostre voltant.
Aquesta és una norma que aplica perfectament al bosc que representa el nostre codi.
Cada cop que anem de visita al codi, hem de fer les coses bé, i aplicar entre altres els principis que hem descrit anteriorment, per així no afegir-hi més escombraries.
Sempre cal deixar l’entorn una mica millor del que l’hem trobat, encara que nosaltres no en siguem responsables. Així, aconseguim que, a base de petites iteracions o visites, de mica en mica, anirem millorant el codi i literalment deixant-lo més net.
A més, tot el temps que hem dedicat a intentar entendre el propòsit d’un mètode o funció que ens hem trobat, el podem estalviar al desenvolupador següent si millorem d’alguna manera aquestes parts gens clares i ofuscades, en definitiva, brutes.
Nosaltres els programadors en som els responsables d’escriure codi de qualitat. El client ha de notar que la qualitat no està només en què es respecti la funcionalitat sinó que resoldre bugs, o fer evolutius resulta fàcil i sense sobrecost per codis mal implementats, ja que les males decisions tècniques les paga tant el client com el desenvolupador.
Hi ha moltes més normes que les que hem comentat, però amb aplicar les esmentades la llegibilitat i mantenibilitat del codi millorarà enormement, i per tant, la productivitat.
Més enllà de nous Frameworks, llenguatges de programació o serveis, és la nostra responsabilitat com a desenvolupadors aprendre tècniques de programació agnòstiques per tornar-nos millors programadors i poder oferir productes de qualitat, tant per fora, com per dins.