- Notifications
You must be signed in to change notification settings - Fork321
🛁 Clean Code concepts adapted for JavaScript
License
devictoribero/clean-code-javascript
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Si quieresmejorar como ingeniero de software de seguro que te interesami nuevo canal de youtube!
Este contenido no es original. está traducido deaquí.Tampoco significa que todo lo que esté en este repositorio lo comparta. De hecho,hay unas cosas en las que no estoy de acuerdo.
- Introducción
- Variables
- Funciones
- Objetos y estructuras de datos
- Clases
- SOLID
- Testing
- Concurrencia
- Manejo de errores
- Formato
- Comentarios
- Traducciones
Principios de Ingeniería de Software por Robert C. Martin en el libroCódigo Limpio,adaptado al Javascript. Esto no es una guía de estilos. Esto es una guíapara crear códigolegible, reutilizable y de fácil modificaciónen Javascript.
No se deben seguir estrictamente todos los principios e incluso aún menos,como tampoco, éstos van a ser dogmas internacionales ni verdades absolutas.Los conceptos explicados no son más una compilación de buenas prácticas quehan sido agrupadas a lo largo de muchos años de experiencia colectiva porlos autores deCódigo Limpio.
Nuestro oficio de ingeniería de software tiene poco más de 50 años y todavíaestamos aprendiendo mucho. Quizás cuando la arquitectura del software sea tanantigua como la arquitectura tradicional en sí misma, tengamos reglas másdefinidas que seguir. Por ahora, dejemos que estas pautas sirvan de faro paraevaluar la calidad del código JavaScript que tu equipo y tu producís.
Una cosa más: Debes saber que estos principios, consejos o como quieras llamarlo,no te hará instantáneamente un mejor desarrollador de software y que trabajar conellos durante muchos años tampoco significa que no vayas a hacer más errores.Cada trozo de código comienza como un primer borrador, igual que un jarrón preciosoempieza con un trozo de arcilla feo y húmedo el cual vamos moldeando hasta conseguir elresultado final. Finalmente, limamos las imperfecciones cuando lo revisamos connuestros compañeros a base de iteraciones. No te castigues por la posible mejorade los primeros borradores. En vez de eso, ¡Vence al código!
🙅 Mal:
constyyyymmdstr=moment().format("YYYY/MM/DD");
👨🏫 Bien:
constfechaActual=moment().format("YYYY/MM/DD");
🙅 Mal:
conseguirInformacionUsuario();conseguirDatosCliente();conseguirRegistroCliente();
👨🏫 Bien:
conseguirUsuario();
Leeremos más código del que jamás escribiremos. Es importante que el código queescribamos sea legible y se puede buscar en él. Al no crear variables que seansignificativas para entender nuestro código... Estamos entorpeciendo a sus lectores.Haz tus variables sean fáciles de entender y buscar. Herramientas comobuddy.js yESLintpueden ayudan a identificar constantes no nombradas.
🙅 Mal:
// Para que cojones sirve 86400000?setTimeout(blastOff,86400000);
👨🏫 Bien:
// Declaralas como constantes nombradasconstMILISEGUNDOS_POR_DIA=86400000;setTimeout(blastOff,MILISEGUNDOS_POR_DIA);
🙅 Mal:
constdireccion="Calle Mallorca, Barcelona 95014";constexpresionRegularCodigoPostalCiudad=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;guardarCP(direccion.match(expresionRegularCodigoPostalCiudad)[1],direccion.match(expresionRegularCodigoPostalCiudad)[2]);
👨🏫 Bien:
constdireccion="One Infinite Loop, Cupertino 95014";constexpresionRegularCodigoPostalCiudad=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;const[,ciudad,codigoPostal]=direccion.match(expresionRegularCodigoPostalCiudad)||[];guardarCP(ciudad,codigoPostal);
Explícito es mejor que implícito.
🙅 Mal:
constciudades=["Barcelona","Madrid","Sitges"];ciudades.forEach(l=>{hacerAlgo();hacerAlgoMas();// ...// ...// ...// Espera, para que era `l`?dispatch(l);});
👨🏫 Bien:
constciudades=["Barcelona","Madrid","Sitges"];ciudades.forEach(direccion=>{hacerAlgo();hacerAlgoMas();// ...// ...// ...dispatch(direccion);});
Si tu nombre de clase/objeto ya dice algo, no lo repitas en tu nombre de variable
🙅 Mal:
constCoche={marcaCoche:"Honda",modeloCoche:"Accord",colorCoche:"Azul"};functionpintarCoche(coche){coche.colorCoche="Rojo";}
👨🏫 Bien:
constCoche={marca:"Honda",modelo:"Accord",color:"Azul"};functionpintarCoche(coche){coche.color="Rojo";}
Los argumentos por defecto suelen ser más limpios que los cortocircuitos. Tenen cuenta que si los usas, solo se asignara ese valor por defectos cuando elvalor del parámetro seaundefined
. Otros valores "falsos" como''
," "
,false
,null
,0
yNaN
, no serán reemplazado por un valor predeterminadopues se consideran valores como tal.
🙅 Mal:
functioncrearMicroCerveceria(nombre){constnombreMicroCerveceria=nombre||"Hipster Brew Co.";// ...}
👨🏫 Bien:
functioncrearMicroCerveceria(nombre="Hipster Brew Co."){// ...}
Limitar la cantidad de parámetros de una función es increíblemente importanteporque hacen quelas pruebas de tu función sean más sencillas. Tener más de treslleva a una locura combinatoria donde tienes que probar toneladas de casosdiferentes con cada argumento por separado.
El caso ideal es usar uno o dos argumentos, tres... deben evitarse si es posible.Cualquier número superior a eso, debería ser agrupado. Por lo general, si tienesmás de dos argumentos, tu función debe de estar haciendo demasiadas cosas. En loscasos donde no es así, la mayoría de las veces un objeto de nivel superior serásuficiente como un argumentoparámetro objeto.
Ya que Javascript te permite crear objetos al vuelo sin tener que hacer muchocódigo repetitivo en una clase, puedes usar un objeto en caso de estar necesitandomuchos parámetros.
Para indicar que propiedades espera la función, puedes usar las funcionalidadesde desestructuración que nos ofrece ES2015/ES6. Éstas tienen algunas ventajas:
- Cuando alguien mira la firma de la función, sabe inmediatamente que propiedadesestán siendo usadas
- La desetructuración también clona los valores primitivos especificados del
objeto argumento
pasado a la función. Esto puede servir de ayuda para prevenir efectos adversos.Nota: Los objetos y los arrays que son desestructurados del objeto parámetro NO son clonados. - Las herramientas lintera olinterns pueden avisarte de qué propiedades delobjeto parámetro no están en uso.Cosa que es imposile sin desestructuración.
🙅 Mal:
functioncrearMenu(titulo,cuerpo,textoDelBoton,cancelable){// ...}
👨🏫 Bien:
functioncrearMenu({ titulo, cuerpo, textoDelBoton, cancelable}){// ...}crearMenu({titulo:"Foo",cuerpo:"Bar",textoDelBoton:"Baz",cancelable:true});
De lejos, es la regla más importante en la ingeniería del software. Cuandolas funciones hacen más de una cosa, son difíciles de componer ytestearentre otras cosas. Si isolamos las funciones por acciones, éstas pueden sermodificadas y mantenidas con mayor facilidad y tu código será mucho más limpio.De toda esta guía... si has de aprender algo, que sea esto. Ya estarás mmuypor delante de muchos desarrolladores de software.
🙅 Mal:
functionenviarCorreoAClientes(clientes){clientes.forEach(cliente=>{consthistoricoDelCliente=baseDatos.buscar(cliente);if(historicoDelCliente.estaActivo()){enviarEmail(cliente);}});}
👨🏫 Bien:
functionenviarCorreoClientesActivos(clientes){clientes.filter(esClienteActive).forEach(enviarEmail);}functionesClienteActivo(cliente){consthistoricoDelCliente=baseDatos.buscar(cliente);returnhistoricoDelCliente.estaActivo();}
🙅 Mal:
functionañadirAFecha(fecha,mes){// ...}constfecha=newDate();// Es difícil saber que se le está añadiendo a la fecha en este casoañadirAFecha(fecha,1);
👨🏫 Bien:
functionañadirMesAFecha(mes,fecha){// ...}constfecha=newDate();añadirMesAFecha(1,fecha);
Cuando tienes más de un nivel de abstracción, tu función normalmente estáhacicendo demasiado. Separarla en funciones más pequeñas te ayudará a poderreutilizar código y te facilitará eltestear éstas.
🙅 Mal:
functionanalizarMejorAlternativaJavascript(codigo){constEXPRESIONES_REGULARES=[// ...];constdeclaraciones=codigo.split(" ");consttokens=[];EXPRESIONES_REGULARES.forEach(EXPRESION_REGULAR=>{declaraciones.forEach(declaracion=>{// ...});});constast=[];tokens.forEach(token=>{// lex...});ast.forEach(nodo=>{// parse...});}
👨🏫 Bien:
functionanalizarMejorAlternativaJavascript(codigo){consttokens=tokenize(codigo);constast=lexer(tokens);ast.forEach(nodo=>{// parse...});}functiontokenize(codigo){constEXPRESIONES_REGULARES=[// ...];constdeclaraciones=codigo.split(" ");consttokens=[];EXPRESIONES_REGULARES.forEach(EXPRESION_REGULAR=>{declaraciones.forEach(declaracion=>{tokens.push(/* ... */);});});returntokens;}functionlexer(tokens){constast=[];tokens.forEach(token=>{ast.push(/* ... */);});returnast;}
Haz todo lo posible para evitar duplicación de código. Duplicar código esmalo porque significa que para editar un comportamiento... tendrás que modificarlkoen más de un sitio. ¿Y no queremos trabajar de más, verdad?
Como caso práctico: Imagina que tienes un restaurante. Llevas el registro delinventario: Todos tus tomates, cebollas, ajos, especies, etc... Si tuvieras másde una lista que tuvieras que actualizar cada vez que sirves un tomate o usasuna especie, sería más fácil de cometer errores, además de todo el tiempo perdido.Si solo tienes una, la posibilidad de cometer una error se reduce a ésta!
A menudo tienes código duplicado porque tienes dos o más cosas ligeramentediferentes, que tienen mucho en común, pero sus diferencias te obligan a tenerese código de más. Borrar la duplicación de código significa crear una abstracciónque pueda manejar este conjunto de cosas diferentes con una sola función/módulo/clase.
Hacer que la abstracción sea correcta es fundamental y a veces bastante complejo.Es por eso que debes seguir los PrincipiosSOLID
establecidos en la secciónClases.Las malas abstracciones pueden ser peores que el código duplicado. ¡Así que ten cuidado!Dicho esto, si se puede hacer una buena abstracción, ¡Házla! Evita repetirteporque de lo contrario, como hemos comentado anteriormente, te verás editandoen más de un lugar para modificar un comportamiento.
🙅 Mal:
functionmostrarListaDesarrolladores(desarrolladores){desarrolladores.forEach(desarrollador=>{constsalarioEsperado=desarrollador.calcularSalarioEsperado();constexperiencia=desarrollador.conseguirExperiencia();constenlaceGithub=desarrollador.conseguirEnlaceGithub();constdatos={ salarioEsperado, experiencia, enlaceGithub};render(datos);});}functionmostrarListaJefes(jefes){jefes.forEach(jefe=>{constsalarioEsperado=desarrollador.calcularSalarioEsperado();constexperiencia=desarrollador.conseguirExperiencia();constexperienciaLaboral=jefe.conseguirProyectosMBA();constdata={ salarioEsperado, experiencia, experienciaLaboral};render(data);});}
👨🏫 Bien:
functionmostrarListaEmpleados(empleados){empleados.forEach(empleado=>{constsalarioEsperado=empleado.calcularSalarioEsperado();constexperiencia=empleado.conseguirExperiencia();constdatos={ salarioEsperado, experiencia};switch(empleado.tipo){case"jefe":datos.portafolio=empleado.conseguirProyectosMBA();break;case"desarrollador":datos.enlaceGithub=empleado.conseguirEnlaceGithub();break;}render(datos);});}
🙅 Mal:
constconfiguracionMenu={titulo:null,contenido:"Bar",textoBoton:null,cancelable:true};functioncrearMenu(config){config.titulo=config.titulo||"Foo";config.contenido=config.contenido||"Bar";config.textoBoton=config.textoBoton||"Baz";config.cancelable=config.cancelable!==undefined ?config.cancelable :true;}crearMenu(configuracionMenu);
👨🏫 Bien:
constconfiguracionMenu={titulo:"Order",// El usuario no incluyó la clave 'contenido'textoBoton:"Send",cancelable:true};functioncrearMenu(configuracion){configuracion=Object.assign({titulo:"Foo",contenido:"Bar",textoBoton:"Baz",cancelable:true},configuracion);// configuracion ahora es igual a: {titulo: "Order", contenido: "Bar", textoBoton: "Send", cancelable: true}// ...}crearMenu(configuracionMenu);
Las banderas oflags te indican de que esa función hace más de una cosa. Yaque como vamos repitiendo, nuestras funciones solo deberían hacer una cosa, separaesa lógica que es diferenciada por la bandera oflag en una nueva función.
🙅 Mal:
functioncrearFichero(nombre,temporal){if(temporal){fs.create(`./temporal/${nombre}`);}else{fs.create(nombre);}}
👨🏫 Bien:
functioncrearFichero(nombre){fs.create(nombre);}functioncrearFicheroTemporal(nombre){crearFichero(`./temporal/${nombre}`);}
Una función produce un efecto adverso/colateral si hace otra cosa que recibirun parámetro de entrada y retornar otro valor o valores. Un efecto adverso puedeser escribir un fichero, modificar una variable global o accidentalmente enviartodo tu dinero a un desconocido.
Ahora bien, a veces necesitamos efectos adversos en nuestros programas. Comoen el ejemplo anterior, quizás necesitas escribir en un fichero. Así pues, lo quequeremos es centralizar donde se hace esta acción. No queremos que esta lógicala tengamos que escribir en cada una de las funciones o clases que van a utilizarla.Para eso, la encapsularemos en un servicio que haga eso. Sólo eso.
El objetivo principal es evitar errores comunes como compartir el estado entre objetossin ninguna estructura, usando tipos de datos mutables que pueden ser escritos por cualquier cosay no centralizar donde se producen sus efectos secundarios. Si puedes hacer esto, serásmás feliz que la gran mayoría de otros programadores.
🙅 Mal:
// Variable Global referenciada por la siguiente función// Si tuvieramos otra función que usara ese nombre, podría ser un array y lo estaríamos rompiendo// If we had another function that used this name, now it'd be an array and it could break it.letnombre='Ryan McDermott';functionsepararEnNombreYApellido(){nombre=nombre.split(' ');}separarEnNombreYApellido();console.log(nombre);// ['Ryan', 'McDermott'];
👨🏫 Bien:
functionsepararEnNombreYApellido(nombre){returnnombre.split(' ');}constnombre='Ryan McDermott';constnuevoNombre=separarEnNombreYApellido(nombre);console.log(nombre);// 'Ryan McDermott';console.log(nuevoNombre);// ['Ryan', 'McDermott'];
En JavaScript, los primitivos se pasan por valor y los objetos / arrays se pasan porreferencia. En el caso de objetos y arrays, si su función hace un cambio como por ejemplo,añadiendo un elemento al array que representa el carrito de la compra, entonces cualquierotra función que use ese arraycarrito
se verá afectada por esta modificación.Eso puede ser genial, sin embargo, también puede ser malo. Imaginemos una mala situación:
El usuario hace clic en el botón "Comprar", que llama a una función de "compra" quegenera una petición de red y envía el arraycarrito
al servidor. Dada una malaconexión de red, la funcióncomprar
tiene que seguir reintentando la solicitud.Ahora, ¿Qué pasa si mientras tanto el usuario hace clic accidentalmente en el botón"Agregar al carrito" en un elemento que realmente no quiere, antes de que comiencela solicitud de red? Si esto sucede y la solicitud de red comienza, entonces esafunción de compra enviará el artículo agregado accidentalmente porque tiene unareferencia al objeto dado que la funciónañadirObjetoAlCarrito
modificó elcarrito
agregando un elemento que no deseado.
Una buena solución paraañadirObjetoAlCarrito
podría ser clonar elcarrito
, editarlo,y retornar la copia. Esto nos asegura que ninguna otra función tiene referencia alobjeto con los campos modificados. Así pues, ninguna otra función se verá afectadapor nuestros cambios.
Dos advertencias que mencionar para este enfoque:
Puede haber casos en los que realmente desee modificar el objeto de entrada,pero cuando adopte esta práctica de programación encontrará que esos casos sonbastante raros ¡La mayoría de las cosas se pueden refactorizar para que no tenganefectos secundarios!
Clonar objetos grandes puede ser muy costosa en términos de rendimiento.Por suerte, en la práctica, esto no es un gran problema dado que haybuenas librerías que permiten estetipo de enfoque de programación. Es rápido y no requiere tanta memoria como tecostaría a ti clonar manualmente los arrays y los objetos.
🙅 Mal:
constañadirObjetoAlCarrito=(carrito,objeto)=>{carrito.push({ objeto,fecha:Date.now()});};
👨🏫 Bien:
constañadirObjetoAlCarrito=(carrito,objeto)=>{return[...carrito,{ objeto,fecha:Date.now()}];};
La contaminación global es una mala práctica en JavaScript porque podría chocarcon otra librería y usuarios usuarios de tu API no serían conscientes de ello hastaque tuviesen un error en producción. Pensemos en un ejemplo: ¿Qué pasaría si quisierasextender los arrays de Javascript para tener un métododiff
que pudiera enseñar ladiferencia entre dos arrays? Podrías escribir tu nueva función en elArray.prototype
,pero podría chocar con otra librería que intentó hacer lo mismo. ¿Qué pasa si esa otralibrería estaba usandodiff
para encontrar la diferencia entre los elementos primeroy último de una matriz? Tendríamos problemas... Por eso, sería mucho mejor usar lasclases ES2015 / ES6 y simplemente extender elArray
global.
🙅 Mal:
Array.prototype.diff=functiondiff(matrizDeComparación){consthash=newSet(matrizDeComparación);returnthis.filter(elemento=>!hash.has(elemento));};
👨🏫 Bien:
classSuperArrayextendsArray{diff(matrizDeComparación){consthash=newSet(matrizDeComparación);returnthis.filter(elemento=>!hash.has(elemento));}}
Javascript no es un lenguage funcional en la misma medida que lo es Haskell, perotiene aspectos que lo favorecen. Los lenguages funcionales pueden ser más fácilesy limpios detestear. Favorece este estilo de programación siempre que puedas.
🙅 Mal:
constdatosSalidaProgramadores=[{nombre:"Uncle Bobby",liniasDeCodigo:500},{nombre:"Suzie Q",liniasDeCodigo:1500},{nombre:"Jimmy Gosling",liniasDeCodigo:150},{nombre:"Gracie Hopper",liniasDeCodigo:1000}];letsalidaFinal=0;for(leti=0;i<datosSalidaProgramadores.length;i++){salidaFinal+=datosSalidaProgramadores[i].liniasDeCodigo;}
👨🏫 Bien:
constdatosSalidaProgramadores=[{nombre:"Uncle Bobby",liniasDeCodigo:500},{nombre:"Suzie Q",liniasDeCodigo:1500},{nombre:"Jimmy Gosling",liniasDeCodigo:150},{nombre:"Gracie Hopper",liniasDeCodigo:1000}];constsalidaFinal=datosSalidaProgramadores.map(salida=>salida.linesOfCode).reduce((totalLinias,linias)=>totalLinias+linias);
🙅 Mal:
if(fsm.state==="cogiendoDatos"&&estaVacio(listaNodos)){// ...}
👨🏫 Bien:
functiondeberiaMostrarSpinner(fsm,listaNodos){returnfsm.state==="cogiendoDatos"&&estaVacio(listaNodos);}if(deberiaMostrarSpinner(fsmInstance,listNodeInstance)){// ...}
🙅 Mal:
functionnoEstaElNodoPresente(node){// ...}if(!noEstaElNodoPresente(node)){// ...}
👨🏫 Bien:
functionestaElNodoPresente(node){// ...}if(estaElNodoPresente(node)){// ...}
Esto parece una tarea imposible. Al escuchar esto por primera vez, la mayoría dela gente dice"¿como voy a ser capaz de hacer cosas sin unif
"? La respuesta a eso,es que deberías usar polimorfismo para conserguir lo mismo en la gran mayoría de loscasos. La segunda pregunta que normalmente la gente hace es,¿Bueno está bien peropara que voy a querer hacerlo? La respuesta es uno de los conceptos previos quehemos visto deCódigo limpio: Una función debería hacer únicamente una cosa.Cuando tienes una función o clase que posee unif
, le estás diciendo al usuarioque tu función está haciendo más de una cosa. Recuerda, tan sólo una cosa.
🙅 Mal:
classAvion{// ...obtenerAlturaDeVuelo(){switch(this.tipo){case"777":returnthis.cogerAlturaMaxima()-this.conseguirNumeroPasajeros();case"Air Force One":returnthis.cogerAlturaMaxima();case"Cessna":returnthis.cogerAlturaMaxima()-this.getFuelExpenditure();}}}
👨🏫 Bien:
classAvion{// ...}classBoeing777extendsAvion{// ...obtenerAlturaDeVuelo(){returnthis.cogerAlturaMaxima()-this.conseguirNumeroPasajeros();}}classAirForceOneextendsAvion{// ...obtenerAlturaDeVuelo(){returnthis.cogerAlturaMaxima();}}classCessnaextendsAvion{// ...obtenerAlturaDeVuelo(){returnthis.cogerAlturaMaxima()-this.getFuelExpenditure();}}
Javascript es un lenguaje no tipado. Esto significa que las funciones pueden recibircualquier tipo como argumento. A veces, nos aprovechamos de eso... y es por eso, quese vuelve muy tentador el controlar los tipos de los argumentos de la función. Hayalgunas soluciones para evitar esto. La primera, son APIs consistentes. Por API seentiende de que manera nos comunicamos con ese módulo/función.
🙅 Mal:
functionviajarATexas(vehiculo){if(vehiculoinstanceofBicicleta){vehiculo.pedalear(this.ubicacionActual,newLocalizacion("texas"));}elseif(vehiculoinstanceofCar){vehiculo.conducir(this.ubicacionActual,newLocalizacion("texas"));}}
👨🏫 Bien:
functionviajarATexas(vehiculo){vehiculo.mover(this.ubicacionActual,newLocalizacion("texas"));}
Si estás trabajando con los tipos primitivos como son lascadenas
oenteros
,y no puedes usar polimorfismo pero aún ves la necesidad del control de tipos,deberías considerarTypescript
. Es una excelente alternativa alJavascript
convencional que nos aporta control de tipos de manera estática entre otrasmuchas cosas. El problema de controlar manualmente el tipado enJavascript
esque para hacerlo bien, necesitamos añadir mucho código a bajo nivel que afecta ala legibilidad del código. Mantén tu códigoJavascript
limpio, escribetestsy intenta tener revisiones de código. Si no, intenta cubrir el máximo de cosas conTypescript
que como ya hemos dicho, es una muy buena alternativa.
🙅 Mal:
functioncombina(valor1,valor2){if((typeofvalor1==="number"&&typeofvalor2==="number")||(typeofvalor1==="string"&&typeofvalor2==="string")){returnvalor1+valor2;}thrownewError("Debería ser una cadena o número");}
👨🏫 Bien:
functioncombina(valor1,valor2){returnvalor1+valor2;}
Los navegadores modernos hacen mucha optimización por detrás en tiempo de ejecución.Muchas veces, al interntar optimizar tu código... estás perdiendo el tiempo.Esta es una buena documentaciónpara ver donde falta optimización. Pon el foco en éstas hasta que estén arregladas/hechassi es que se pueden.
🙅 Mal:
// En los navegadores antiguos, cada iteración en la que `list.length` no esté cacheada// podría ser costosa por el recálculo de este valor. En los modernos, ya está optimizadofor(leti=0,tamaño=lista.length;i<tamaño;i++){// ...}
👨🏫 Bien:
for(leti=0;i<lista.length;i++){// ...}
El código inútil es tan malo como la duplicación. No hay razón alguna paramantenerlo en tu código. Si no está siendo usado por nadie, ¡Bórralo! Siempreestará disponible en sistema de versiones para el caso que lo necesites.
🙅 Mal:
functionantiguoModuloDePeticiones(url){// ...}functionnuevoModuloDePeticiones(url){// ...}constpeticion=nuevoModuloDePeticiones;calculadorDeInventario("manzanas",peticion,"www.inventory-awesome.io");
👨🏫 Bien:
functionnuevoModuloDePeticiones(url){// ...}constpeticion=nuevoModuloDePeticiones;calculadorDeInventario("manzanas",peticion,"www.inventory-awesome.io");
Usargetters
ysetters
para acceder a la información del objeto está mejorque simplemente accediendo a esa propiedad del objeto. ¿Por qué?
- Si quieres modificar una propiedad de un objeto, no tienes que ir mirandosi existe o no existe para seguir mirando a niveles más profundos del objeto.
- Encapsula la representación interna (en caso de tener que comprobar cosas, mirar en varios sitios...)
- Es sencillo añadir mensajes y manejos de error cuando hacemos
get
yset
- Te permite poder hacer lazy load en caso de que los datos se recojan de una Base de Datos (bbdd)
🙅 Mal:
functioncrearCuentaBancaria(){// ...return{balance:0// ...};}constcuenta=crearCuentaBancaria();cuenta.balance=100;
👨🏫 Bien:
functioncrearCuentaBancaria(){// Esta es privadaletbalance=0;// Un "getter", hecho público a través del objeto que retornamos abajofunctioncogerBalance(){returnbalance;}// Un "setter", hecho público a través del objeto que retornamos abajofunctionintroducirBalance(cantidad){// ... validamos antes de hcaer un balancebalance=cantidad;}return{// ... cogerBalance, introducirBalance};}constcuenta=crearCuentaBancaria();cuenta.introducirBalance(100);
Esto se puede hacer medianteclojures
(de ES5 en adelante).
🙅 Mal:
constEmpleado=function(nombre){this.nombre=nombre;};Empleado.prototype.cogerNombre=functioncogerNombre(){returnthis.nombre;};constempleado=newEmpleado("John Doe");console.log(`Nombre del empleado:${empleado.cogerNombre()}`);// Nombre del empleado: John Doedeleteempleado.nombre;console.log(`Nombre del empleado:${empleado.cogerNombre()}`);// Nombre del empleado: undefined
👨🏫 Bien:
functioncrearEmpleado(name){return{cogerNombre(){returnname;}};}constempleado=crearEmpleado("John Doe");console.log(`Nombre del empleado:${empleado.cogerNombre()}`);// Nombre del empleado: John Doedeleteempleado.name;console.log(`Nombre del empleado:${empleado.cogerNombre()}`);// Nombre del empleado: John Doe
Es muy complicado de conseguir que un código sea entendible y fácil de leer conherencia de clases, construcción y metodos típicos de clases con las clases de ES5.Si necesitas herencia (y de seguro, que no la necesitas) entonces, dale prioridad alas clases ES2015/ES6. De todas las maneras, deberías preferir pequeñas funcionesantes que ponerte a hacer clases. Solo cuando tengas un código largo o cuando veasnecesaria la implementación de clases, añádelas.
🙅 Mal:
constAnimal=function(edad){if(!(thisinstanceofAnimal)){thrownewError("Inicializa Animal con `new`");}this.edad=edad;};Animal.prototype.mover=functionmover(){};constMamifero=function(edad,furColor){if(!(thisinstanceofMamifero)){thrownewError("Inicializa Mamifero con `new`");}Animal.call(this,edad);this.furColor=furColor;};Mamifero.prototype=Object.create(Animal.prototype);Mamifero.prototype.constructor=Mamifero;Mamifero.prototype.aniversario=functionaniversario(){};constHumano=function(edad,furColor,idioma){if(!(thisinstanceofHumano)){thrownewError("Inicializa Humano con `new`");}Mamifero.call(this,edad,furColor);this.idioma=idioma;};Humano.prototype=Object.create(Mamifero.prototype);Humano.prototype.constructor=Humano;Humano.prototype.hablar=functionhablar(){};
👨🏫 Bien:
classAnimal{constructor(edad){this.edad=edad;}mover(){/* ... */}}classMamiferoextendsAnimal{constructor(edad,furColor){super(edad);this.furColor=furColor;}aniversario(){/* ... */}}classHumanextendsMamifero{constructor(edad,furColor,idioma){super(edad,furColor);this.idioma=idioma;}hablar(){/* ... */}}
Este es un patrón útil en Javascript y verás que muchas librerías como jQueryo Lodash lo usan. Permite que tu código sea expresivo y menos verboso.Por esa razón, utiliza las funciones anidadas y date cuenta de que tan limpio estarátu código. En las funciones de tu clase, sencillamente retornathis
al final decada una y con eso, tienes todo lo necesario pra poder anidar las llamadas a lasfunciones.
🙅 Mal:
classCoche{constructor(marca,modelo,color){this.marca=marca;this.modelo=modelo;this.color=color;}introducirMarca(marca){this.marca=marca;}introducirModelo(modelo){this.modelo=modelo;}introducirColor(color){this.color=color;}guardar(){console.log(this.marca,this.modelo,this.color);}}constcoche=newCoche("Ford","F-150","rojo");coche.introducirColor("rosa");coche.guardar();
👨🏫 Bien:
classCoche{constructor(marca,modelo,color){this.marca=marca;this.modelo=modelo;this.color=color;}introducirMarca(marca){this.marca=marca;// NOTE: Retornamos this para poder anidas funcionesreturnthis;}introducirModelo(modelo){this.modelo=modelo;// NOTE: Retornamos this para poder anidas funcionesreturnthis;}introducirColor(color){this.color=color;// NOTE: Retornamos this para poder anidas funcionesreturnthis;}guardar(){console.log(this.marca,this.modelo,this.color);// NOTE: Retornamos this para poder anidas funcionesreturnthis;}}constcoche=newCoche("Ford","F-150","rojo").introducirColor("rosa").guardar();
Como se citó enPatrones de Diseñopor "the Gang of Four", deberías priorizar la composición en vez de la hereciasiempre que puedas. Hay muy buenas razones para usar tanto la herecia como lacomposición. El problema principal es que nuestra mente siempre tiende a la herenciacomo primera opción, pero deberíamos de pensar qué tan bien nos encaja la composiciónen ese caso particular porque en muchas ocasiones es lo más acertado.
Te estarás preguntando entonces,¿Cuando debería yo usar la herencia? Todo depende.Depende del problema que tengas entre mano, pero ya hay ocasiones particulares dondela herencia tiene más sentido que la composición:
- Tu herencia representa una relación "es un/a" en vez de "tiene un/a"(Humano->Animal vs. Usuario->DetallesUsuario)
- Puedes reutilizar código desde las clases base (Los humanos pueden moverse como animales)
- Quieres hacer cambios generales a clases derivadas cambiando la clase base.(Cambiar el consumo de calorías a todos los animales mientras se mueven)
🙅 Mal:
classEmpleado{constructor(nombre,correoElectronico){this.nombre=nombre;this.correoElectronico=correoElectronico;}// ...}// Bad because Employees "have" tax data. EmployeeTaxData is not a type of EmpleadoclassInformacionImpuestosEmpleadoextendsEmpleado{constructor(ssn,salario){super();this.ssn=ssn;this.salario=salario;}// ...}
👨🏫 Bien:
classInformacionImpuestosEmpleado{constructor(ssn,salario){this.ssn=ssn;this.salario=salario;}// ...}classEmpleado{constructor(nombre,correoElectronico){this.nombre=nombre;this.correoElectronico=correoElectronico;}introducirInformacionImpuestos(ssn,salario){this.informacionImpuestos=newInformacionImpuestosEmpleado(ssn,salario);}// ...}
Como se cita enCódigo Limpio, "No debería haber nunca más de un motivo paraque una clase cambie". Es muy tentador acribillar a una clase con un montón defuncionalidad. El problema que tiene esto, es que tu clase no tendrá cohesióny tendrá bastantes motivos por los que cambiar. Es por eso que es importantereducir el número de veces que tendrás que modificar una clase. Y lo es, porqueen caso de que tengamos una clase que haga más de una cosa y modifiquemos unade ellas, no podemos saber que efectos colaterales puede tener esta acción en las demás.
🙅 Mal:
classOpcionesUsuario{constructor(usuario){this.usuario=usuario;}changeSettings(opciones){if(this.verificarCredenciales()){// ...}}verificarCredenciales(){// ...}}
👨🏫 Bien:
classAutenticationUsuario{constructor(usuario){this.usuario=usuario;}verificarCredenciales(){// ...}}classUserSettings{constructor(usuario){this.usuario=usuario;this.autenticacion=newAutenticationUsuario(usuario);}changeSettings(settings){if(this.autenticacion.verificarCredenciales()){// ...}}}
Citado por Bertrand Meyer:"Las entidades de software (clases, módulos, funciones, ...)deberían estar abiertas a extensión pere cerradas a modificación." ¿Qué significa esto?Básicamente significa que los usuarios deberían de ser capaces de añadir funcionalidada la aplicación sin tener que tocar el código creado hasta ahora.
🙅 Mal:
classAdaptadorAjaxextendsAdaptador{constructor(){super();this.name="adaptadorAjax";}}classAdaptadorNodosextendsAdaptador{constructor(){super();this.nombre="adaptadorNodos";}}class{constructor(adapter){this.adapter=adapter;}fetch(url){if(this.adapter.nombre==="adaptadorAjax"){returnhacerLlamadaAjax(url).then(respuesta=>{// transformar la respuesta y devolverla});}elseif(this.adapter.nombre==="adaptadorHttpNodos"){returnhacerLlamadaHttp(url).then(respuesta=>{// transformar la respuesta y devolverla});}}}functionhacerLlamadaAjax(url){// request and return promise}functionhacerLlamadaHttp(url){// request and return promise}
👨🏫 Bien:
classAdaptadorAjaxextendsAdapter{constructor(){super();this.nombre="adaptadorAjax";}pedir(url){// Pedir y devolver la promesa}}classAdaptadorNodosextendsAdapter{constructor(){super();this.nombre="adaptadorNodos";}pedir(url){// Pedir y devolver la promesa}}classEjecutadorPeticionesHttp{constructor(adaptador){this.adaptador=adaptador;}fetch(url){returnthis.adaptador.pedir(url).then(respuesta=>{// Transformar y devolver la respuesta});}}
Este es un término que asusta para lo sencillo que es. Estrictamente se define como"Si S es un subtipo de T, entonces los objetos del tipo T deberían poderse substituirpor objetos del tipo S".
Un ejemplo práctico vien a ser si tenemos unaclase padre y unaclase hija,entonces ambas han de poderse substituir la una por la otra y viceversa sin recibirningún tipo de error o datos erróneos. Un caso práctico es el del cuadrado y elrectángulo. Geométricamente, un cuadrado es un rectángulo, pero si lo creamoscon una relación "es un" a través de herencia, empezamos a tener problemas...
🙅 Mal:
classRectangulo{constructor(){this.anchura=0;this.altura=0;}introducirColor(color){// ...}render(area){// ...}introducirAnchura(anchura){this.anchura=anchura;}introducirAltura(altura){this.altura=altura;}conseguirArea(){returnthis.anchura*this.altura;}}classCuadradoextendsRectangulo{introducirAnchura(anchura){this.anchura=anchura;this.altura=anchura;}introducirAltura(altura){this.width=altura;this.altura=altura;}}functionrenderizaRectangulosLargos(rectangulos){rectangulos.forEach(rectangulo=>{rectangulo.introducirAnchura(4);rectangulo.introducirAltura(5);constarea=rectangulo.conseguirArea();// MAL: Para el cuadrado devuelve 25 y devería ser 20rectangulo.render(area);});}constrectangulos=[newRectangulo(),newRectangulo(),newCuadrado()];renderizaRectangulosLargos(rectangulos);
👨🏫 Bien:
classForma{introducirColor(color){// ...}render(area){// ...}}classRectanguloextendsForma{constructor(width,height){super();this.anchura=anchura;this.altura=altura;}conseguirArea(){returnthis.anchura*this.altura;}}classCuadradoextendsForma{constructor(distancia){super();this.distancia=distancia;}conseguirArea(){returnthis.distancia*this.distancia;}}functionrenderizaRectangulosLargos(shapes){shapes.forEach(shape=>{constarea=shape.conseguirArea();shape.render(area);});}constshapes=[newRectangulo(4,5),newRectangulo(4,5),newCuadrado(5)];renderizaRectangulosLargos(shapes);
Javascript no dispone de interfaces así que no podemos aplicar el principio comotal. De todas maneras, es importante conceptualmente hablando aunque no tengamostipados como tal, pues eso resulta haciendo un código mantenible igualmente.
ISP dice que "los servicios no deberían estar forzados a depender de interfacesque realmente no usan".
Un buen ejemplo en javascript sería las típicas clases que requieren de unenormes objetos de configuración. No hacer que los servicios requieran degrandes cantidades de opciones es beneficioso, porque la gran mayoría del tiempo,no necesitarán esa configuración. Hacerlos opcionales ayuda a no tener el problemade "Interaz gorda", en inglés conocido como "fat interface".
🙅 Mal:
classDOMTraverser{constructor(configuraciones){this.configuraciones=configuraciones;this.setup();}preparar(){this.nodoRaiz=this.configuraciones.nodoRaiz;this.ModuloAnimacion.preparar();}atravesar(){// ...}}const$=newDOMTraverser({nodoRaiz:document.getElementsByTagName("body"),moduloAnimacion(){}// Most of the time, we won't need to animate when traversing.// ...});
👨🏫 Bien:
classDOMTraverser{constructor(configuraciones){this.configuraciones=configuraciones;this.opciones=configuraciones.opciones;this.preparar();}preparar(){this.nodoRaiz=this.configuraciones.nodoRaiz;this.prepararOpciones();}prepararOpciones(){if(this.opciones.moduloAnimacion){// ...}}atravesar(){// ...}}const$=newDOMTraverser({nodoRaiz:document.getElementsByTagName("body"),opciones:{moduloAnimacion(){}}});
Por favor, no confundir con Inyección de Dependencias. Mucha gente se piensaque la "D" deSOLID es de Inyección de Dependencias(Dependency Inection, DI).
Este principio nos dice dos cosas básicamente:
- Módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambosdeberían depender de abstracciones.
- Las abstracciones no deberían depender de detalles si no que, los detallesdeberían depender de abstracciones.
Esto puede ser algo complejo al principio, pero si has trabajado con AngularJS,has visto de manera indirecta esto con la Inyección de Dependencias. Comocomentaba anteriormente, aunque no son lo mismo, van de la mano. La Inversión deDependencías es posible gracias a la Inyección de Dependencias.DI hace posibleque los módulos de alto nivel dependan de abstracciones y no de detalles.
El mayor de los beneficioses la reducción del acoplamiento entre módulos. Cuántomayor acoplamiento, mayor dificultad en refactorización.
Como hemos comentado antes,Javascript
no tiene interfaces así que los contratosson un poco... así asá. Están en nuestro cabeza y eso debemos tenerlo en cuenta.Mucha gente usa javascript docs, anotaciones en comentarios justo encima de losmódulos y algunas cosas más. Vamos a ver un ejemplo conRastreadorDeInventario
.
🙅 Mal:
classSolicitadorDeInventario{constructor(){this.REQ_METHODS=["HTTP"];}pedirArticulo(articulo){// ...}}classRastreadorDeInventario{constructor(articulos){this.articulos=articulos;// MAL: Hemos creado una dependencia de una concreción que va atada a una implementación// Deberíamos tener pedirArticulos dependiendo únicamente de un método: 'solicitud'this.solicitador=newSolicitadorDeInventario();}pedirArticulos(){this.articulos.forEach(articulo=>{this.solicitador.pedirArticulo(articulo);});}}constrastreadorDeInventario=newRastreadorDeInventario(["manzanas","platanos"]);rastreadorDeInventario.pedirArticulos();
👨🏫 Bien:
classRastreadorDeInventario{constructor(articulos,solicitador){this.articulos=articulos;this.solicitador=solicitador;}pedirArticulos(){this.articulos.forEach(articulo=>{this.solicitador.pedirArticulo(articulo);});}}classSolicitadorDeInventarioV1{constructor(){this.REQ_METHODS=["HTTP"];}pedirArticulo(articulo){// ...}}classSolicitadorDeInventarioV2{constructor(){this.REQ_METHODS=["WS"];}pedirArticulo(articulo){// ...}}// By constructing our dependencies externally and injecting them, we can easily// substitute our request module for a fancy new one that uses WebSockets.// Construyendo nuestras dependencias desde fuera e inyectandolas, podríamos// substituir nuestro Módulo solicitador por uno con websockets o lo que seaconstrastreadorDeInventario=newRastreadorDeInventario(["manzanas","platanos"],newSolicitadorDeInventarioV2());rastreadorDeInventario.pedirArticulos();
El testing es más importante que la entrega. Si no tienes test o tienes muchosque no soy de gran ayuda, cada vez que quieras entregar valor no estarás segurode ue eso funciona debidamente y que nada falla. Puedes decidir con el equipocuál es el porcentaje al que queréis ceñiros pero, la única manera de tenerconfianza total de que nada falla, es teniendo 100% de covertura de test. Paraesto, ncesitarás tener una gran herramienta para poder testear pero tambiénuna que te calcule adecuadamenteel porcentaje cubierto.
No hay excusas para no escribir tests. Hayun montón de frameworks de JS entrelos que podréis tu y tu equipo decidir. Una vez hayáis elegido el framework,para cada nueva funcionalidad que se quiera añadir a la plataforma, escribir tests.Si prefieres hacerTest-Driven Development me parece bien, pero la ide principal delos test es dar confianza suficiente al programador para que pueda seguir entregando valor.
🙅 Mal:
importassertfrom"assert";describe("MakeMomentJSGreatAgain",()=>{it("maneja límites de las fechas",()=>{letfecha;fecha=newMakeMomentJSGreatAgain("1/1/2015");fecha.addDays(30);assert.equal("1/31/2015",fecha);fecha=newMakeMomentJSGreatAgain("2/1/2016");fecha.addDays(28);assert.equal("02/29/2016",fecha);fecha=newMakeMomentJSGreatAgain("2/1/2015");fecha.addDays(28);assert.equal("03/01/2015",fecha);});});
👨🏫 Bien:
importassertfrom"assert";describe("MakeMomentJSGreatAgain",()=>{it("Maneja los meses con 30 días",()=>{constfecha=newMakeMomentJSGreatAgain("1/1/2015");fecha.addDays(30);assert.equal("1/31/2015",fecha);});it("Maneja los años bisiestos",()=>{constfecha=newMakeMomentJSGreatAgain("2/1/2016");fecha.addDays(28);assert.equal("02/29/2016",fecha);});it("Maneja los años NO bisiestos",()=>{constfecha=newMakeMomentJSGreatAgain("2/1/2015");fecha.addDays(28);assert.equal("03/01/2015",fecha);});});
Los callbacks son funciones que se pasan como parámetros a otras funcionespara ser ejecutaras una vez esta función termina. Por ejemplo: Dada las funcionesA y B, se dice que B es el callback de A si la función B es pasada como parámetroa la función A y esta, se ejecuta este callback una vez ha terminado
Loscallbacks
no son limpios ni en cuanto a legibilidad ni en cuanto a formatode texto(dado que provocan niveles de identación). Con ES2015/ES6 las promesasson un tipo global. ¡Úsalas!
🙅 Mal:
import{get}from"request";import{writeFile}from"fs";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin",(requestErr,respuesta)=>{if(requestErr){console.error(requestErr);}else{writeFile("article.html",respuesta.body,writeErr=>{if(writeErr){console.error(writeErr);}else{console.log("File written");}});}});
👨🏫 Bien:
import{get}from"request";import{writeFile}from"fs";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin").then(respuesta=>{returnwriteFile("article.html",respuesta);}).then(()=>{console.log("File written");}).catch(err=>{console.error(err);});
Las promesas son una elección más limpia que los callbacks pero ES2017/ES8trae la funcionalidad deasync/await
que es incluos más limpio que las promesas.Todo lo que tienes que hacer es añadir el prefijoasync
a una función y entoncesya podemos usar esa función de manera imperative sin ningún.then()
. Lapalabraawait
la usarás para hacer que ese código asincrono se comporte de"manera síncrona".
🙅 Mal:
import{get}from"request-promise";import{writeFile}from"fs-promise";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin").then(respuesrta=>{returnwriteFile("article.html",respuesrta);}).then(()=>{console.log("File written");}).catch(err=>{console.error(err);});
👨🏫 Bien:
import{get}from"request-promise";import{writeFile}from"fs-promise";asyncfunctionconseguirArticulosDeCodigoLimpio(){try{constrespuesrta=awaitget("https://en.wikipedia.org/wiki/Robert_Cecil_Martin");awaitwriteFile("article.html",respuesrta);console.log("File written");}catch(err){console.error(err);}}
¡Lanzar errores está bien! Significa que en tiempo de ejecución se ha detectadoque algo no estaba funcionando como debía y se ha parado la ejecución del trozode código. Además se notifica siempre en la consola del navegador.
No hacer nada con los errores capturados no te da la opción de anticiparteo arreglar dicho error. El printar el error por la consola del navegadorno es una solución, pues la gran mayoría de veces nadie es consciente de esoy el error pasas desapercibido. Envuelve tu código contry/catch
y es ahídonde tendrás que elaborar tu plan de reacción a posibles errores
🙅 Mal:
try{functionQueDeberiaLanzarError();}catch(error){console.log(error);}
👨🏫 Bien:
try{functionQueDeberiaLanzarError();}catch(error){// Una option (algo más molesta que el convencional console.log)console.error(error);// Otra opción:notificarAlUsuarioDelError(error);// Otra opción:reportarElArrorAUnServicio(error);// O hazlas todas!}
No ignores las promesas que han sido rechadas por la misma razón que no deberíasignorar errores capturados en eltry/catch
.
🙅 Mal:
cogerDatos().then(datos=>{functionQueDeberiaLanzarError(datos);}).catch(error=>{console.log(error);});
👨🏫 Bien:
cogerDatos().then(datos=>{functionQueDeberiaLanzarError(datos);}).catch(error=>{// Una option (algo más molesta que el convencional console.log)console.error(error);// Otra opción:notificarAlUsuarioDelError(error);// Otra opción:reportarElArrorAUnServicio(error);// O hazlas todas!});
El formato del código es algo subjetivo. Como otras reglas aquí, no hay una reglaque deberías seguir o una fórmula secreta. Lo que si que está claro es que nodeberíamos discutir ni crear conflictos con nuestros compañeros de trabajo acercade estas reglas. Hay unas cuantasherrmientasque automatizan todas las reglas de formato de texto. ¡Ahorrarse tiempo en estasformateando el texto es un pasada!
Como ya hemos dicho,javascript
es un lenguage no tipado así pues, lacapitalización de las variables importa, y mucho. Estas son reglas totalmentesubjetivas así que como equipo, podéis elegir lo que más os guste/convenga.La cuestión es que independientemente de lo que decidáis, seáis consistentes.
🙅 Mal:
constDIAS_POR_SEMANA=7;constdiasPorMes=30;constcanciones=["Back In Black","Stairway to Heaven","Hey Jude"];constArtistas=["ACDC","Led Zeppelin","The Beatles"];functionborrarBaseDeDatos(){}functionrestablecer_baseDeDatos(){}classanimal{}classAlpaca{}
👨🏫 Bien:
constDIAS_POR_SEMANA=7;constDIAS_POR_MES=30;constCANCIONES=["Back In Black","Stairway to Heaven","Hey Jude"];constARTISTAS=["ACDC","Led Zeppelin","The Beatles"];functionborrarBaseDeDatos(){}functionrestablecerBaseDeDatos(){}classAnimal{}classAlpaca{}
Si una función llama a otra, haz que esta función que va a ser llamada estélo más cerca posible de la función que la llama. Idealmente, situa siemprela función que va a ser llamada justo después de la función que la ejecuta.¿El motivo? Pues normalmente acostumbramos a leer de arriba abajo y tampocoqueremos tener que hacerscroll hasta abajo del todo del ficheor para volvera subir.
🙅 Mal:
classRevisionDeRendimiento{constructor(empleado){this.empleado=empleado;}conseguirCompañeros(){returndb.buscar(this.empleado,"compañeros");}conseguirJefe(){returndb.buscar(this.empleado,"jefe");}conseguirOpinionDeLosCompañeros(){constcompañeros=this.conseguirCompañeros();// ...}executarRevision(){this.conseguirOpinionDeLosCompañeros();this.conseguirOpinionDelJefe();this.conseguirAutoRevision();}conseguirOpinionDelJefe(){constjefe=this.conseguirJefe();}conseguirAutoRevision(){// ...}}constreview=newRevisionDeRendimiento(empleado);review.executarRevision();
👨🏫 Bien:
classRevisionDeRendimiento{constructor(empleado){this.empleado=empleado;}executarRevision(){this.conseguirOpinionDeLosCompañeros();this.conseguirOpinionDelJefe();this.conseguirAutoRevision();}conseguirOpinionDeLosCompañeros(){constcompañeros=this.conseguirCompañeros();// ...}conseguirCompañeros(){returndb.buscar(this.empleado,"compañeros");}conseguirOpinionDelJefe(){constjefe=this.conseguirJefe();}conseguirJefe(){returndb.buscar(this.empleado,"jefe");}conseguirAutoRevision(){// ...}}constreview=newRevisionDeRendimiento(empleado);review.executarRevision();
Los comentarios son una disculpa, no un requerimiento. Supuesatmente se diceque un buen código debería comentarse por si mismo. Un código perfecto noestá optimizado para la máquina sinó que lo está para la manteniblidad de éstepor un compañero o futuro compañero. Para esto, ha de ser lo más semántico posible.El código ha de estar escrito para que niños pequeños lo entiendan.
🙅 Mal:
functionhashIt(datos){// El hashlethash=0;// Tamaño del stringconsttamaño=datos.length;// Iteramos a través de cada carácter de los datosfor(leti=0;i<tamaño;i++){// Coger código del caracterconstchar=datos.charCodeAt(i);// Crear el hashhash=(hash<<5)-hash+char;// Convertir a un entero de 32 bitshash&=hash;}}
👨🏫 Bien:
functionhashIt(datos){lethash=0;consttamaño=datos.length;for(leti=0;i<tamaño;i++){constcaracter=datos.charCodeAt(i);hash=(hash<<5)-hash+caracter;// Convertir a un entero de 32 bitshash&=hash;}}
El control de versiones existe para algo. Si tu motivo o excusa por el que comentarun código es porque en breves o algun día lo vas a necesitas, eso no me sirve. Esecódigo que acabas de borrar consta en alguna de tus versiones de tu código fuente.Lo que deberías hacer entonces quizás, es usargit tags
, poner el código de latarea en el nombre del commit, etc... Hay muchos truquitos para hacer eso!
🙅 Mal:
hacerCosas();// hacerOtrasCosas();// hacerCosasAunMasRaras();// estoHaceMaravillas();
👨🏫 Bien:
hacerCosas();
Recuerda ¡Usa el control de versioens! No hay motivo alguno para tener códigomuerto, código comentado y aún menos, un diadrio o resumen de modificaciones entus comentarios. Si quieres ver las modificaciones, usagit log
, la herramientoblame
o incluso elhistory
.
🙅 Mal:
/** * 2016-12-20: `monads` borrados, no hay quien los entienda * 2016-10-01: Código mejorado con 'monads' * 2016-02-03: Borrado tipado * 2015-03-14: Añadido tipado */functioncombinar(a,b){returna+b;}
👨🏫 Bien:
functioncombinar(a,b){returna+b;}
Normalmente acostumbran a ser molestos. Deja que las variables y las funcioneshagan su función con sus identaciones naturales y de esta manera, formateen elcódigo correctamente.
🙅 Mal:
////////////////////////////////////////////////////////////////////////////////// Instanciación del Modelo Scope////////////////////////////////////////////////////////////////////////////////$scope.modelo={menu:"foo",nav:"bar"};////////////////////////////////////////////////////////////////////////////////// Preparación de la acción////////////////////////////////////////////////////////////////////////////////constacciones=function(){// ...};
👨🏫 Bien:
$scope.modelo={menu:"foo",nav:"bar"};constacciones=function(){// ...};
También esta disponible en otros idiomas
Brazilian Portuguese:fesnt/clean-code-javascript
Spanish (Uruguay):andersontr15/clean-code-javascript
Spanish (Spain):tureey/clean-code-javascript
Chinese:
German:marcbruederlin/clean-code-javascript
Korean:qkraudghgh/clean-code-javascript-ko
Polish:greg-dev/clean-code-javascript-pl
Russian:
Vietnamese:hienvd/clean-code-javascript/
Japanese:mitsuruog/clean-code-javascript/
Indonesia:andirkh/clean-code-javascript/
Italian:frappacchio/clean-code-javascript/
About
🛁 Clean Code concepts adapted for JavaScript
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Languages
- JavaScript100.0%