├── .gitattributes ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-documentation=false 2 | *.md linguist-language=JavaScript 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ryan McDermott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ✨ Mejora como ingeniero de software 2 | Si quieres **mejorar como ingeniero de software** de seguro que te interesa **[mi nuevo canal de youtube](https://youtube.com/@devictoribero)!** 3 | 4 | # clean-code-javascript 5 | 6 | Este contenido no es original. está traducido de [aquí](https://github.com/ryanmcdermott/clean-code-javascript). 7 | Tampoco significa que todo lo que esté en este repositorio lo comparta. De hecho, 8 | hay unas cosas en las que no estoy de acuerdo. 9 | 10 | ## Contenido 11 | 12 | 1. [Introducción](#introduction) 13 | 2. [Variables](#variables) 14 | 3. [Funciones](#functions) 15 | 4. [Objetos y estructuras de datos](#objects-and-data-structures) 16 | 5. [Clases](#classes) 17 | 6. [SOLID](#solid) 18 | 7. [Testing](#testing) 19 | 8. [Concurrencia](#concurrency) 20 | 9. [Manejo de errores](#error-handling) 21 | 10. [Formato](#formatting) 22 | 11. [Comentarios](#comments) 23 | 12. [Traducciones](#translation) 24 | 25 | ## Introducción 26 | 27 | ![Humorous image of software quality estimation as a count of how many expletives 28 | you shout when reading code](http://www.osnews.com/images/comics/wtfm.jpg) 29 | 30 | Principios de Ingeniería de Software por Robert C. Martin en el libro 31 | [_Código Limpio_](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882), 32 | adaptado al Javascript. Esto no es una guía de estilos. Esto es una guía 33 | para crear código [legible, reutilizable y de fácil modificación](https://github.com/ryanmcdermott/3rs-of-software-architecture) 34 | en Javascript. 35 | 36 | No se deben seguir estrictamente todos los principios e incluso aún menos, 37 | como tampoco, éstos van a ser dogmas internacionales ni verdades absolutas. 38 | Los conceptos explicados no son más una compilación de buenas prácticas que 39 | han sido agrupadas a lo largo de muchos años de experiencia colectiva por 40 | los autores de _Código Limpio_. 41 | 42 | Nuestro oficio de ingeniería de software tiene poco más de 50 años y todavía 43 | estamos aprendiendo mucho. Quizás cuando la arquitectura del software sea tan 44 | antigua como la arquitectura tradicional en sí misma, tengamos reglas más 45 | definidas que seguir. Por ahora, dejemos que estas pautas sirvan de faro para 46 | evaluar la calidad del código JavaScript que tu equipo y tu producís. 47 | 48 | Una cosa más: Debes saber que estos principios, consejos o como quieras llamarlo, 49 | no te hará instantáneamente un mejor desarrollador de software y que trabajar con 50 | ellos durante muchos años tampoco significa que no vayas a hacer más errores. 51 | Cada trozo de código comienza como un primer borrador, igual que un jarrón precioso 52 | empieza con un trozo de arcilla feo y húmedo el cual vamos moldeando hasta conseguir el 53 | resultado final. Finalmente, limamos las imperfecciones cuando lo revisamos con 54 | nuestros compañeros a base de iteraciones. No te castigues por la posible mejora 55 | de los primeros borradores. En vez de eso, ¡Vence al código! 56 | 57 | ## Variables 58 | 59 | ### Utiliza nombres con sentido y de fácil pronunciación para las variables 60 | 61 | **🙅‍ Mal:** 62 | 63 | ```javascript 64 | const yyyymmdstr = moment().format("YYYY/MM/DD"); 65 | ``` 66 | 67 | **👨‍🏫 Bien:** 68 | 69 | ```javascript 70 | const fechaActual = moment().format("YYYY/MM/DD"); 71 | ``` 72 | 73 | **[⬆ Volver arriba](#contenido)** 74 | 75 | ### Utiliza el mismo tipo de vocabulario para el mismo tipo de variables 76 | 77 | **🙅‍ Mal:** 78 | 79 | ```javascript 80 | conseguirInformacionUsuario(); 81 | conseguirDatosCliente(); 82 | conseguirRegistroCliente(); 83 | ``` 84 | 85 | **👨‍🏫 Bien:** 86 | 87 | ```javascript 88 | conseguirUsuario(); 89 | ``` 90 | 91 | **[⬆ Volver arriba](#contenido)** 92 | 93 | ### Utiliza nombres que puedan ser buscados 94 | 95 | Leeremos más código del que jamás escribiremos. Es importante que el código que 96 | escribamos sea legible y se puede buscar en él. Al no crear variables que sean 97 | significativas para entender nuestro código... Estamos entorpeciendo a sus lectores. 98 | Haz tus variables sean fáciles de entender y buscar. Herramientas como 99 | [buddy.js](https://github.com/danielstjules/buddy.js) y 100 | [ESLint](https://github.com/eslint/eslint/blob/660e0918933e6e7fede26bc675a0763a6b357c94/docs/rules/no-magic-numbers.md) 101 | pueden ayudan a identificar constantes no nombradas. 102 | 103 | **🙅‍ Mal:** 104 | 105 | ```javascript 106 | // Para que cojones sirve 86400000? 107 | setTimeout(blastOff, 86400000); 108 | ``` 109 | 110 | **👨‍🏫 Bien:** 111 | 112 | ```javascript 113 | // Declaralas como constantes nombradas 114 | const MILISEGUNDOS_POR_DIA = 86400000; 115 | 116 | setTimeout(blastOff, MILISEGUNDOS_POR_DIA); 117 | ``` 118 | 119 | **[⬆ Volver arriba](#contenido)** 120 | 121 | ### Utiliza variables explicativas 122 | 123 | **🙅‍ Mal:** 124 | 125 | ```javascript 126 | const direccion = "Calle Mallorca, Barcelona 95014"; 127 | const expresionRegularCodigoPostalCiudad = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 128 | guardarCP( 129 | direccion.match(expresionRegularCodigoPostalCiudad)[1], 130 | direccion.match(expresionRegularCodigoPostalCiudad)[2] 131 | ); 132 | ``` 133 | 134 | **👨‍🏫 Bien:** 135 | 136 | ```javascript 137 | const direccion = "One Infinite Loop, Cupertino 95014"; 138 | const expresionRegularCodigoPostalCiudad = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 139 | const [, ciudad, codigoPostal] = 140 | direccion.match(expresionRegularCodigoPostalCiudad) || []; 141 | guardarCP(ciudad, codigoPostal); 142 | ``` 143 | 144 | **[⬆ Volver arriba](#contenido)** 145 | 146 | ### Evita relaciones mentales 147 | 148 | Explícito es mejor que implícito. 149 | 150 | **🙅‍ Mal:** 151 | 152 | ```javascript 153 | const ciudades = ["Barcelona", "Madrid", "Sitges"]; 154 | ciudades.forEach(l => { 155 | hacerAlgo(); 156 | hacerAlgoMas(); 157 | // ... 158 | // ... 159 | // ... 160 | // Espera, para que era `l`? 161 | dispatch(l); 162 | }); 163 | ``` 164 | 165 | **👨‍🏫 Bien:** 166 | 167 | ```javascript 168 | const ciudades = ["Barcelona", "Madrid", "Sitges"]; 169 | ciudades.forEach(direccion => { 170 | hacerAlgo(); 171 | hacerAlgoMas(); 172 | // ... 173 | // ... 174 | // ... 175 | dispatch(direccion); 176 | }); 177 | ``` 178 | 179 | **[⬆ Volver arriba](#contenido)** 180 | 181 | ### No añadas contexto innecesario 182 | 183 | Si tu nombre de clase/objeto ya dice algo, no lo repitas en tu nombre de variable 184 | 185 | **🙅‍ Mal:** 186 | 187 | ```javascript 188 | const Coche = { 189 | marcaCoche: "Honda", 190 | modeloCoche: "Accord", 191 | colorCoche: "Azul" 192 | }; 193 | 194 | function pintarCoche(coche) { 195 | coche.colorCoche = "Rojo"; 196 | } 197 | ``` 198 | 199 | **👨‍🏫 Bien:** 200 | 201 | ```javascript 202 | const Coche = { 203 | marca: "Honda", 204 | modelo: "Accord", 205 | color: "Azul" 206 | }; 207 | 208 | function pintarCoche(coche) { 209 | coche.color = "Rojo"; 210 | } 211 | ``` 212 | 213 | **[⬆ Volver arriba](#contenido)** 214 | 215 | ### Utiliza argumentos por defecto en vez de circuitos cortos o condicionales 216 | 217 | Los argumentos por defecto suelen ser más limpios que los cortocircuitos. Ten 218 | en cuenta que si los usas, solo se asignara ese valor por defectos cuando el 219 | valor del parámetro sea `undefined`. Otros valores "falsos" como `''`, `" "`, 220 | `false`,`null`, `0` y `NaN`, no serán reemplazado por un valor predeterminado 221 | pues se consideran valores como tal. 222 | 223 | **🙅‍ Mal:** 224 | 225 | ```javascript 226 | function crearMicroCerveceria(nombre) { 227 | const nombreMicroCerveceria = nombre || "Hipster Brew Co."; 228 | // ... 229 | } 230 | ``` 231 | 232 | **👨‍🏫 Bien:** 233 | 234 | ```javascript 235 | function crearMicroCerveceria(nombre = "Hipster Brew Co.") { 236 | // ... 237 | } 238 | ``` 239 | 240 | **[⬆ Volver arriba](#contenido)** 241 | 242 | ## Funciones 243 | 244 | ### Argumentos de una función (idealmente 2 o menos) 245 | 246 | Limitar la cantidad de parámetros de una función es increíblemente importante 247 | porque hacen que _las pruebas_ de tu función sean más sencillas. Tener más de tres 248 | lleva a una locura combinatoria donde tienes que probar toneladas de casos 249 | diferentes con cada argumento por separado. 250 | 251 | El caso ideal es usar uno o dos argumentos, tres... deben evitarse si es posible. 252 | Cualquier número superior a eso, debería ser agrupado. Por lo general, si tienes 253 | más de dos argumentos, tu función debe de estar haciendo demasiadas cosas. En los 254 | casos donde no es así, la mayoría de las veces un objeto de nivel superior será 255 | suficiente como un argumento _parámetro objeto_. 256 | 257 | Ya que Javascript te permite crear objetos al vuelo sin tener que hacer mucho 258 | código repetitivo en una clase, puedes usar un objeto en caso de estar necesitando 259 | muchos parámetros. 260 | 261 | Para indicar que propiedades espera la función, puedes usar las funcionalidades 262 | de desestructuración que nos ofrece ES2015/ES6. Éstas tienen algunas ventajas: 263 | 264 | 1. Cuando alguien mira la firma de la función, sabe inmediatamente que propiedades 265 | están siendo usadas 266 | 2. La desetructuración también clona los valores primitivos especificados del `objeto argumento` 267 | pasado a la función. Esto puede servir de ayuda para prevenir efectos adversos. 268 | _Nota: Los objetos y los arrays que son desestructurados del objeto parámetro NO son clonados._ 269 | 3. Las herramientas lintera o _linterns_ pueden avisarte de qué propiedades del 270 | objeto parámetro no están en uso. _Cosa que es imposile sin desestructuración._ 271 | 272 | **🙅‍ Mal:** 273 | 274 | ```javascript 275 | function crearMenu(titulo, cuerpo, textoDelBoton, cancelable) { 276 | // ... 277 | } 278 | ``` 279 | 280 | **👨‍🏫 Bien:** 281 | 282 | ```javascript 283 | function crearMenu({ titulo, cuerpo, textoDelBoton, cancelable }) { 284 | // ... 285 | } 286 | 287 | crearMenu({ 288 | titulo: "Foo", 289 | cuerpo: "Bar", 290 | textoDelBoton: "Baz", 291 | cancelable: true 292 | }); 293 | ``` 294 | 295 | **[⬆ Volver arriba](#contenido)** 296 | 297 | ### Las funciones deberían hacer una cosa 298 | 299 | De lejos, es la regla más importante en la ingeniería del software. Cuando 300 | las funciones hacen más de una cosa, son difíciles de componer y _testear_ 301 | entre otras cosas. Si isolamos las funciones por acciones, éstas pueden ser 302 | modificadas y mantenidas con mayor facilidad y tu código será mucho más limpio. 303 | De toda esta guía... si has de aprender algo, que sea esto. Ya estarás mmuy 304 | por delante de muchos desarrolladores de software. 305 | 306 | **🙅‍ Mal:** 307 | 308 | ```javascript 309 | function enviarCorreoAClientes(clientes) { 310 | clientes.forEach(cliente => { 311 | const historicoDelCliente = baseDatos.buscar(cliente); 312 | if (historicoDelCliente.estaActivo()) { 313 | enviarEmail(cliente); 314 | } 315 | }); 316 | } 317 | ``` 318 | 319 | **👨‍🏫 Bien:** 320 | 321 | ```javascript 322 | function enviarCorreoClientesActivos(clientes) { 323 | clientes.filter(esClienteActive).forEach(enviarEmail); 324 | } 325 | 326 | function esClienteActivo(cliente) { 327 | const historicoDelCliente = baseDatos.buscar(cliente); 328 | return historicoDelCliente.estaActivo(); 329 | } 330 | ``` 331 | 332 | **[⬆ Volver arriba](#contenido)** 333 | 334 | ### Los nombres de las funciones deberían decir lo que hacen 335 | 336 | **🙅‍ Mal:** 337 | 338 | ```javascript 339 | function añadirAFecha(fecha, mes) { 340 | // ... 341 | } 342 | 343 | const fecha = new Date(); 344 | 345 | // Es difícil saber que se le está añadiendo a la fecha en este caso 346 | añadirAFecha(fecha, 1); 347 | ``` 348 | 349 | **👨‍🏫 Bien:** 350 | 351 | ```javascript 352 | function añadirMesAFecha(mes, fecha) { 353 | // ... 354 | } 355 | 356 | const fecha = new Date(); 357 | añadirMesAFecha(1, fecha); 358 | ``` 359 | 360 | **[⬆ Volver arriba](#contenido)** 361 | 362 | ### Las funciones deberían ser únicamente de un nivel de abstracción 363 | 364 | Cuando tienes más de un nivel de abstracción, tu función normalmente está 365 | hacicendo demasiado. Separarla en funciones más pequeñas te ayudará a poder 366 | reutilizar código y te facilitará el _testear_ éstas. 367 | 368 | **🙅‍ Mal:** 369 | 370 | ```javascript 371 | function analizarMejorAlternativaJavascript(codigo) { 372 | const EXPRESIONES_REGULARES = [ 373 | // ... 374 | ]; 375 | 376 | const declaraciones = codigo.split(" "); 377 | const tokens = []; 378 | EXPRESIONES_REGULARES.forEach(EXPRESION_REGULAR => { 379 | declaraciones.forEach(declaracion => { 380 | // ... 381 | }); 382 | }); 383 | 384 | const ast = []; 385 | tokens.forEach(token => { 386 | // lex... 387 | }); 388 | 389 | ast.forEach(nodo => { 390 | // parse... 391 | }); 392 | } 393 | ``` 394 | 395 | **👨‍🏫 Bien:** 396 | 397 | ```javascript 398 | function analizarMejorAlternativaJavascript(codigo) { 399 | const tokens = tokenize(codigo); 400 | const ast = lexer(tokens); 401 | ast.forEach(nodo => { 402 | // parse... 403 | }); 404 | } 405 | 406 | function tokenize(codigo) { 407 | const EXPRESIONES_REGULARES = [ 408 | // ... 409 | ]; 410 | 411 | const declaraciones = codigo.split(" "); 412 | const tokens = []; 413 | EXPRESIONES_REGULARES.forEach(EXPRESION_REGULAR => { 414 | declaraciones.forEach(declaracion => { 415 | tokens.push(/* ... */); 416 | }); 417 | }); 418 | 419 | return tokens; 420 | } 421 | 422 | function lexer(tokens) { 423 | const ast = []; 424 | tokens.forEach(token => { 425 | ast.push(/* ... */); 426 | }); 427 | 428 | return ast; 429 | } 430 | ``` 431 | 432 | **[⬆ Volver arriba](#contenido)** 433 | 434 | ### Elimina código duplicado 435 | 436 | Haz todo lo posible para evitar duplicación de código. Duplicar código es 437 | malo porque significa que para editar un comportamiento... tendrás que modificarlko 438 | en más de un sitio. ¿Y no queremos trabajar de más, verdad? 439 | 440 | Como caso práctico: Imagina que tienes un restaurante. Llevas el registro del 441 | inventario: Todos tus tomates, cebollas, ajos, especies, etc... Si tuvieras más 442 | de una lista que tuvieras que actualizar cada vez que sirves un tomate o usas 443 | una especie, sería más fácil de cometer errores, además de todo el tiempo perdido. 444 | Si solo tienes una, la posibilidad de cometer una error se reduce a ésta! 445 | 446 | A menudo tienes código duplicado porque tienes dos o más cosas ligeramente 447 | diferentes, que tienen mucho en común, pero sus diferencias te obligan a tener 448 | ese código de más. Borrar la duplicación de código significa crear una abstracción 449 | que pueda manejar este conjunto de cosas diferentes con una sola función/módulo/clase. 450 | 451 | Hacer que la abstracción sea correcta es fundamental y a veces bastante complejo. 452 | Es por eso que debes seguir los Principios `SOLID` establecidos en la sección _Clases_. 453 | Las malas abstracciones pueden ser peores que el código duplicado. ¡Así que ten cuidado! 454 | Dicho esto, si se puede hacer una buena abstracción, ¡Házla! Evita repetirte 455 | porque de lo contrario, como hemos comentado anteriormente, te verás editando 456 | en más de un lugar para modificar un comportamiento. 457 | 458 | **🙅‍ Mal:** 459 | 460 | ```javascript 461 | function mostrarListaDesarrolladores(desarrolladores) { 462 | desarrolladores.forEach(desarrollador => { 463 | const salarioEsperado = desarrollador.calcularSalarioEsperado(); 464 | const experiencia = desarrollador.conseguirExperiencia(); 465 | const enlaceGithub = desarrollador.conseguirEnlaceGithub(); 466 | const datos = { 467 | salarioEsperado, 468 | experiencia, 469 | enlaceGithub 470 | }; 471 | 472 | render(datos); 473 | }); 474 | } 475 | 476 | function mostrarListaJefes(jefes) { 477 | jefes.forEach(jefe => { 478 | const salarioEsperado = desarrollador.calcularSalarioEsperado(); 479 | const experiencia = desarrollador.conseguirExperiencia(); 480 | const experienciaLaboral = jefe.conseguirProyectosMBA(); 481 | const data = { 482 | salarioEsperado, 483 | experiencia, 484 | experienciaLaboral 485 | }; 486 | 487 | render(data); 488 | }); 489 | } 490 | ``` 491 | 492 | **👨‍🏫 Bien:** 493 | 494 | ```javascript 495 | function mostrarListaEmpleados(empleados) { 496 | empleados.forEach(empleado => { 497 | const salarioEsperado = empleado.calcularSalarioEsperado(); 498 | const experiencia = empleado.conseguirExperiencia(); 499 | 500 | const datos = { 501 | salarioEsperado, 502 | experiencia 503 | }; 504 | 505 | switch (empleado.tipo) { 506 | case "jefe": 507 | datos.portafolio = empleado.conseguirProyectosMBA(); 508 | break; 509 | case "desarrollador": 510 | datos.enlaceGithub = empleado.conseguirEnlaceGithub(); 511 | break; 512 | } 513 | 514 | render(datos); 515 | }); 516 | } 517 | ``` 518 | 519 | **[⬆ Volver arriba](#contenido)** 520 | 521 | ### Asigna objetos por defecto con Object.assign 522 | 523 | **🙅‍ Mal:** 524 | 525 | ```javascript 526 | const configuracionMenu = { 527 | titulo: null, 528 | contenido: "Bar", 529 | textoBoton: null, 530 | cancelable: true 531 | }; 532 | 533 | function crearMenu(config) { 534 | config.titulo = config.titulo || "Foo"; 535 | config.contenido = config.contenido || "Bar"; 536 | config.textoBoton = config.textoBoton || "Baz"; 537 | config.cancelable = 538 | config.cancelable !== undefined ? config.cancelable : true; 539 | } 540 | 541 | crearMenu(configuracionMenu); 542 | ``` 543 | 544 | **👨‍🏫 Bien:** 545 | 546 | ```javascript 547 | const configuracionMenu = { 548 | titulo: "Order", 549 | // El usuario no incluyó la clave 'contenido' 550 | textoBoton: "Send", 551 | cancelable: true 552 | }; 553 | 554 | function crearMenu(configuracion) { 555 | configuracion = Object.assign( 556 | { 557 | titulo: "Foo", 558 | contenido: "Bar", 559 | textoBoton: "Baz", 560 | cancelable: true 561 | }, 562 | configuracion 563 | ); 564 | 565 | // configuracion ahora es igual a: {titulo: "Order", contenido: "Bar", textoBoton: "Send", cancelable: true} 566 | // ... 567 | } 568 | 569 | crearMenu(configuracionMenu); 570 | ``` 571 | 572 | **[⬆ Volver arriba](#contenido)** 573 | 574 | ### No utilices banderas o flags 575 | 576 | Las banderas o _flags_ te indican de que esa función hace más de una cosa. Ya 577 | que como vamos repitiendo, nuestras funciones solo deberían hacer una cosa, separa 578 | esa lógica que es diferenciada por la bandera o _flag_ en una nueva función. 579 | 580 | **🙅‍ Mal:** 581 | 582 | ```javascript 583 | function crearFichero(nombre, temporal) { 584 | if (temporal) { 585 | fs.create(`./temporal/${nombre}`); 586 | } else { 587 | fs.create(nombre); 588 | } 589 | } 590 | ``` 591 | 592 | **👨‍🏫 Bien:** 593 | 594 | ```javascript 595 | function crearFichero(nombre) { 596 | fs.create(nombre); 597 | } 598 | 599 | function crearFicheroTemporal(nombre) { 600 | crearFichero(`./temporal/${nombre}`); 601 | } 602 | ``` 603 | 604 | **[⬆ Volver arriba](#contenido)** 605 | 606 | ### Evita los efectos secundarios (parte 1) 607 | 608 | Una función produce un efecto adverso/colateral si hace otra cosa que recibir 609 | un parámetro de entrada y retornar otro valor o valores. Un efecto adverso puede 610 | ser escribir un fichero, modificar una variable global o accidentalmente enviar 611 | todo tu dinero a un desconocido. 612 | 613 | Ahora bien, a veces necesitamos efectos adversos en nuestros programas. Como 614 | en el ejemplo anterior, quizás necesitas escribir en un fichero. Así pues, lo que 615 | queremos es centralizar donde se hace esta acción. No queremos que esta lógica 616 | la tengamos que escribir en cada una de las funciones o clases que van a utilizarla. 617 | Para eso, la encapsularemos en un servicio que haga eso. Sólo eso. 618 | 619 | El objetivo principal es evitar errores comunes como compartir el estado entre objetos 620 | sin ninguna estructura, usando tipos de datos mutables que pueden ser escritos por cualquier cosa 621 | y no centralizar donde se producen sus efectos secundarios. Si puedes hacer esto, serás 622 | más feliz que la gran mayoría de otros programadores. 623 | 624 | **🙅‍ Mal:** 625 | 626 | ```javascript 627 | // Variable Global referenciada por la siguiente función 628 | // Si tuvieramos otra función que usara ese nombre, podría ser un array y lo estaríamos rompiendo 629 | // If we had another function that used this name, now it'd be an array and it could break it. 630 | let nombre = 'Ryan McDermott'; 631 | 632 | function separarEnNombreYApellido() { 633 | nombre = nombre.split(' '); 634 | } 635 | 636 | separarEnNombreYApellido(); 637 | 638 | console.log(nombre); // ['Ryan', 'McDermott']; 639 | ``` 640 | 641 | **👨‍🏫 Bien:** 642 | 643 | ```javascript 644 | function separarEnNombreYApellido(nombre) { 645 | return nombre.split(' '); 646 | } 647 | 648 | const nombre = 'Ryan McDermott'; 649 | const nuevoNombre = separarEnNombreYApellido(nombre); 650 | 651 | console.log(nombre); // 'Ryan McDermott'; 652 | console.log(nuevoNombre); // ['Ryan', 'McDermott']; 653 | ``` 654 | 655 | **[⬆ Volver arriba](#contenido)** 656 | 657 | ### Evita los efectos secundarios (parte 2) 658 | 659 | En JavaScript, los primitivos se pasan por valor y los objetos / arrays se pasan por 660 | referencia. En el caso de objetos y arrays, si su función hace un cambio como por ejemplo, 661 | añadiendo un elemento al array que representa el carrito de la compra, entonces cualquier 662 | otra función que use ese array `carrito` se verá afectada por esta modificación. 663 | Eso puede ser genial, sin embargo, también puede ser malo. Imaginemos una mala situación: 664 | 665 | El usuario hace clic en el botón "Comprar", que llama a una función de "compra" que 666 | genera una petición de red y envía el array `carrito` al servidor. Dada una mala 667 | conexión de red, la función `comprar` tiene que seguir reintentando la solicitud. 668 | Ahora, ¿Qué pasa si mientras tanto el usuario hace clic accidentalmente en el botón 669 | _"Agregar al carrito"_ en un elemento que realmente no quiere, antes de que comience 670 | la solicitud de red? Si esto sucede y la solicitud de red comienza, entonces esa 671 | función de compra enviará el artículo agregado accidentalmente porque tiene una 672 | referencia al objeto dado que la función `añadirObjetoAlCarrito` modificó el `carrito` 673 | agregando un elemento que no deseado. 674 | 675 | Una buena solución para `añadirObjetoAlCarrito` podría ser clonar el `carrito`, editarlo, 676 | y retornar la copia. Esto nos asegura que ninguna otra función tiene referencia al 677 | objeto con los campos modificados. Así pues, ninguna otra función se verá afectada 678 | por nuestros cambios. 679 | 680 | Dos advertencias que mencionar para este enfoque: 681 | 682 | 1. Puede haber casos en los que realmente desee modificar el objeto de entrada, 683 | pero cuando adopte esta práctica de programación encontrará que esos casos son 684 | bastante raros ¡La mayoría de las cosas se pueden refactorizar para que no tengan 685 | efectos secundarios! 686 | 687 | 2. Clonar objetos grandes puede ser muy costosa en términos de rendimiento. 688 | Por suerte, en la práctica, esto no es un gran problema dado que hay 689 | [buenas librerías](https://facebook.github.io/immutable-js/) que permiten este 690 | tipo de enfoque de programación. Es rápido y no requiere tanta memoria como te 691 | costaría a ti clonar manualmente los arrays y los objetos. 692 | 693 | **🙅‍ Mal:** 694 | 695 | ```javascript 696 | const añadirObjetoAlCarrito = (carrito, objeto) => { 697 | carrito.push({ objeto, fecha: Date.now() }); 698 | }; 699 | ``` 700 | 701 | **👨‍🏫 Bien:** 702 | 703 | ```javascript 704 | const añadirObjetoAlCarrito = (carrito, objeto) => { 705 | return [...carrito, { objeto, fecha: Date.now() }]; 706 | }; 707 | ``` 708 | 709 | **[⬆ Volver arriba](#contenido)** 710 | 711 | ### No escribas en variables globales 712 | 713 | La contaminación global es una mala práctica en JavaScript porque podría chocar 714 | con otra librería y usuarios usuarios de tu API no serían conscientes de ello hasta 715 | que tuviesen un error en producción. Pensemos en un ejemplo: ¿Qué pasaría si quisieras 716 | extender los arrays de Javascript para tener un método `diff` que pudiera enseñar la 717 | diferencia entre dos arrays? Podrías escribir tu nueva función en el `Array.prototype`, 718 | pero podría chocar con otra librería que intentó hacer lo mismo. ¿Qué pasa si esa otra 719 | librería estaba usando `diff` para encontrar la diferencia entre los elementos primero 720 | y último de una matriz? Tendríamos problemas... Por eso, sería mucho mejor usar las 721 | clases ES2015 / ES6 y simplemente extender el `Array` global. 722 | 723 | **🙅‍ Mal:** 724 | 725 | ```javascript 726 | Array.prototype.diff = function diff(matrizDeComparación) { 727 | const hash = new Set(matrizDeComparación); 728 | return this.filter(elemento => !hash.has(elemento)); 729 | }; 730 | ``` 731 | 732 | **👨‍🏫 Bien:** 733 | 734 | ```javascript 735 | class SuperArray extends Array { 736 | diff(matrizDeComparación) { 737 | const hash = new Set(matrizDeComparación); 738 | return this.filter(elemento => !hash.has(elemento)); 739 | } 740 | } 741 | ``` 742 | 743 | **[⬆ Volver arriba](#contenido)** 744 | 745 | ### Da prioridad a la programación funcional en vez de la programación imperativa 746 | 747 | Javascript no es un lenguage funcional en la misma medida que lo es Haskell, pero 748 | tiene aspectos que lo favorecen. Los lenguages funcionales pueden ser más fáciles 749 | y limpios de _testear_. Favorece este estilo de programación siempre que puedas. 750 | 751 | **🙅‍ Mal:** 752 | 753 | ```javascript 754 | const datosSalidaProgramadores = [ 755 | { 756 | nombre: "Uncle Bobby", 757 | liniasDeCodigo: 500 758 | }, 759 | { 760 | nombre: "Suzie Q", 761 | liniasDeCodigo: 1500 762 | }, 763 | { 764 | nombre: "Jimmy Gosling", 765 | liniasDeCodigo: 150 766 | }, 767 | { 768 | nombre: "Gracie Hopper", 769 | liniasDeCodigo: 1000 770 | } 771 | ]; 772 | 773 | let salidaFinal = 0; 774 | 775 | for (let i = 0; i < datosSalidaProgramadores.length; i++) { 776 | salidaFinal += datosSalidaProgramadores[i].liniasDeCodigo; 777 | } 778 | ``` 779 | 780 | **👨‍🏫 Bien:** 781 | 782 | ```javascript 783 | const datosSalidaProgramadores = [ 784 | { 785 | nombre: "Uncle Bobby", 786 | liniasDeCodigo: 500 787 | }, 788 | { 789 | nombre: "Suzie Q", 790 | liniasDeCodigo: 1500 791 | }, 792 | { 793 | nombre: "Jimmy Gosling", 794 | liniasDeCodigo: 150 795 | }, 796 | { 797 | nombre: "Gracie Hopper", 798 | liniasDeCodigo: 1000 799 | } 800 | ]; 801 | 802 | const salidaFinal = datosSalidaProgramadores 803 | .map(salida => salida.linesOfCode) 804 | .reduce((totalLinias, linias) => totalLinias + linias); 805 | ``` 806 | 807 | **[⬆ Volver arriba](#contenido)** 808 | 809 | ### Encapsula los condicionales 810 | 811 | **🙅‍ Mal:** 812 | 813 | ```javascript 814 | if (fsm.state === "cogiendoDatos" && estaVacio(listaNodos)) { 815 | // ... 816 | } 817 | ``` 818 | 819 | **👨‍🏫 Bien:** 820 | 821 | ```javascript 822 | function deberiaMostrarSpinner(fsm, listaNodos) { 823 | return fsm.state === "cogiendoDatos" && estaVacio(listaNodos); 824 | } 825 | 826 | if (deberiaMostrarSpinner(fsmInstance, listNodeInstance)) { 827 | // ... 828 | } 829 | ``` 830 | 831 | **[⬆ Volver arriba](#contenido)** 832 | 833 | ### Evita condicionales negativos 834 | 835 | **🙅‍ Mal:** 836 | 837 | ```javascript 838 | function noEstaElNodoPresente(node) { 839 | // ... 840 | } 841 | 842 | if (!noEstaElNodoPresente(node)) { 843 | // ... 844 | } 845 | ``` 846 | 847 | **👨‍🏫 Bien:** 848 | 849 | ```javascript 850 | function estaElNodoPresente(node) { 851 | // ... 852 | } 853 | 854 | if (estaElNodoPresente(node)) { 855 | // ... 856 | } 857 | ``` 858 | 859 | **[⬆ Volver arriba](#contenido)** 860 | 861 | ### Evita condicionales 862 | 863 | Esto parece una tarea imposible. Al escuchar esto por primera vez, la mayoría de 864 | la gente dice _"¿como voy a ser capaz de hacer cosas sin un `if`"?_ La respuesta a eso, 865 | es que deberías usar polimorfismo para conserguir lo mismo en la gran mayoría de los 866 | casos. La segunda pregunta que normalmente la gente hace es, _¿Bueno está bien pero 867 | para que voy a querer hacerlo?_ La respuesta es uno de los conceptos previos que 868 | hemos visto de _Código limpio_: Una función debería hacer únicamente una cosa. 869 | Cuando tienes una función o clase que posee un `if`, le estás diciendo al usuario 870 | que tu función está haciendo más de una cosa. Recuerda, tan sólo una cosa. 871 | 872 | **🙅‍ Mal:** 873 | 874 | ```javascript 875 | class Avion { 876 | // ... 877 | obtenerAlturaDeVuelo() { 878 | switch (this.tipo) { 879 | case "777": 880 | return this.cogerAlturaMaxima() - this.conseguirNumeroPasajeros(); 881 | case "Air Force One": 882 | return this.cogerAlturaMaxima(); 883 | case "Cessna": 884 | return this.cogerAlturaMaxima() - this.getFuelExpenditure(); 885 | } 886 | } 887 | } 888 | ``` 889 | 890 | **👨‍🏫 Bien:** 891 | 892 | ```javascript 893 | class Avion { 894 | // ... 895 | } 896 | 897 | class Boeing777 extends Avion { 898 | // ... 899 | obtenerAlturaDeVuelo() { 900 | return this.cogerAlturaMaxima() - this.conseguirNumeroPasajeros(); 901 | } 902 | } 903 | 904 | class AirForceOne extends Avion { 905 | // ... 906 | obtenerAlturaDeVuelo() { 907 | return this.cogerAlturaMaxima(); 908 | } 909 | } 910 | 911 | class Cessna extends Avion { 912 | // ... 913 | obtenerAlturaDeVuelo() { 914 | return this.cogerAlturaMaxima() - this.getFuelExpenditure(); 915 | } 916 | } 917 | ``` 918 | 919 | **[⬆ Volver arriba](#contenido)** 920 | 921 | ### Evita el control de tipos (parte 1) 922 | 923 | Javascript es un lenguaje no tipado. Esto significa que las funciones pueden recibir 924 | cualquier tipo como argumento. A veces, nos aprovechamos de eso... y es por eso, que 925 | se vuelve muy tentador el controlar los tipos de los argumentos de la función. Hay 926 | algunas soluciones para evitar esto. La primera, son APIs consistentes. Por API se 927 | entiende de que manera nos comunicamos con ese módulo/función. 928 | 929 | **🙅‍ Mal:** 930 | 931 | ```javascript 932 | function viajarATexas(vehiculo) { 933 | if (vehiculo instanceof Bicicleta) { 934 | vehiculo.pedalear(this.ubicacionActual, new Localizacion("texas")); 935 | } else if (vehiculo instanceof Car) { 936 | vehiculo.conducir(this.ubicacionActual, new Localizacion("texas")); 937 | } 938 | } 939 | ``` 940 | 941 | **👨‍🏫 Bien:** 942 | 943 | ```javascript 944 | function viajarATexas(vehiculo) { 945 | vehiculo.mover(this.ubicacionActual, new Localizacion("texas")); 946 | } 947 | ``` 948 | 949 | **[⬆ Volver arriba](#contenido)** 950 | 951 | ### Evita control de tipos (parte 2) 952 | 953 | Si estás trabajando con los tipos primitivos como son las `cadenas` o `enteros`, 954 | y no puedes usar polimorfismo pero aún ves la necesidad del control de tipos, 955 | deberías considerar `Typescript`. Es una excelente alternativa al `Javascript` 956 | convencional que nos aporta control de tipos de manera estática entre otras 957 | muchas cosas. El problema de controlar manualmente el tipado en `Javascript` es 958 | que para hacerlo bien, necesitamos añadir mucho código a bajo nivel que afecta a 959 | la legibilidad del código. Mantén tu código `Javascript` limpio, escribe _tests_ 960 | y intenta tener revisiones de código. Si no, intenta cubrir el máximo de cosas con 961 | `Typescript` que como ya hemos dicho, es una muy buena alternativa. 962 | 963 | **🙅‍ Mal:** 964 | 965 | ```javascript 966 | function combina(valor1, valor2) { 967 | if ( 968 | (typeof valor1 === "number" && typeof valor2 === "number") || 969 | (typeof valor1 === "string" && typeof valor2 === "string") 970 | ) { 971 | return valor1 + valor2; 972 | } 973 | 974 | throw new Error("Debería ser una cadena o número"); 975 | } 976 | ``` 977 | 978 | **👨‍🏫 Bien:** 979 | 980 | ```javascript 981 | function combina(valor1, valor2) { 982 | return valor1 + valor2; 983 | } 984 | ``` 985 | 986 | **[⬆ Volver arriba](#contenido)** 987 | 988 | ### No optimizes al máximo 989 | 990 | Los navegadores modernos hacen mucha optimización por detrás en tiempo de ejecución. 991 | Muchas veces, al interntar optimizar tu código... estás perdiendo el tiempo. 992 | [Esta es una buena documentación](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) 993 | para ver donde falta optimización. Pon el foco en éstas hasta que estén arregladas/hechas 994 | si es que se pueden. 995 | 996 | **🙅‍ Mal:** 997 | 998 | ```javascript 999 | // En los navegadores antiguos, cada iteración en la que `list.length` no esté cacheada 1000 | // podría ser costosa por el recálculo de este valor. En los modernos, ya está optimizado 1001 | for (let i = 0, tamaño = lista.length; i < tamaño; i++) { 1002 | // ... 1003 | } 1004 | ``` 1005 | 1006 | **👨‍🏫 Bien:** 1007 | 1008 | ```javascript 1009 | for (let i = 0; i < lista.length; i++) { 1010 | // ... 1011 | } 1012 | ``` 1013 | 1014 | **[⬆ Volver arriba](#contenido)** 1015 | 1016 | ### Borra código inútil 1017 | 1018 | El código inútil es tan malo como la duplicación. No hay razón alguna para 1019 | mantenerlo en tu código. Si no está siendo usado por nadie, ¡Bórralo! Siempre 1020 | estará disponible en sistema de versiones para el caso que lo necesites. 1021 | 1022 | **🙅‍ Mal:** 1023 | 1024 | ```javascript 1025 | function antiguoModuloDePeticiones(url) { 1026 | // ... 1027 | } 1028 | 1029 | function nuevoModuloDePeticiones(url) { 1030 | // ... 1031 | } 1032 | 1033 | const peticion = nuevoModuloDePeticiones; 1034 | calculadorDeInventario("manzanas", peticion, "www.inventory-awesome.io"); 1035 | ``` 1036 | 1037 | **👨‍🏫 Bien:** 1038 | 1039 | ```javascript 1040 | function nuevoModuloDePeticiones(url) { 1041 | // ... 1042 | } 1043 | 1044 | const peticion = nuevoModuloDePeticiones; 1045 | calculadorDeInventario("manzanas", peticion, "www.inventory-awesome.io"); 1046 | ``` 1047 | 1048 | **[⬆ Volver arriba](#contenido)** 1049 | 1050 | ## Objectos y estructuras de datos 1051 | 1052 | ### Utiliza setters y getters 1053 | 1054 | Usar `getters` y `setters` para acceder a la información del objeto está mejor 1055 | que simplemente accediendo a esa propiedad del objeto. ¿Por qué? 1056 | 1057 | - Si quieres modificar una propiedad de un objeto, no tienes que ir mirando 1058 | si existe o no existe para seguir mirando a niveles más profundos del objeto. 1059 | - Encapsula la representación interna (en caso de tener que comprobar cosas, mirar en varios sitios...) 1060 | - Es sencillo añadir mensajes y manejos de error cuando hacemos `get` y `set` 1061 | - Te permite poder hacer lazy load en caso de que los datos se recojan de una Base de Datos (bbdd) 1062 | 1063 | **🙅‍ Mal:** 1064 | 1065 | ```javascript 1066 | function crearCuentaBancaria() { 1067 | // ... 1068 | 1069 | return { 1070 | balance: 0 1071 | // ... 1072 | }; 1073 | } 1074 | 1075 | const cuenta = crearCuentaBancaria(); 1076 | cuenta.balance = 100; 1077 | ``` 1078 | 1079 | **👨‍🏫 Bien:** 1080 | 1081 | ```javascript 1082 | function crearCuentaBancaria() { 1083 | // Esta es privada 1084 | let balance = 0; 1085 | 1086 | // Un "getter", hecho público a través del objeto que retornamos abajo 1087 | function cogerBalance() { 1088 | return balance; 1089 | } 1090 | 1091 | // Un "setter", hecho público a través del objeto que retornamos abajo 1092 | function introducirBalance(cantidad) { 1093 | // ... validamos antes de hcaer un balance 1094 | balance = cantidad; 1095 | } 1096 | 1097 | return { 1098 | // ... 1099 | cogerBalance, 1100 | introducirBalance 1101 | }; 1102 | } 1103 | 1104 | const cuenta = crearCuentaBancaria(); 1105 | cuenta.introducirBalance(100); 1106 | ``` 1107 | 1108 | **[⬆ Volver arriba](#contenido)** 1109 | 1110 | ### Hacer que los objetos tengan atributos/métodos privados 1111 | 1112 | Esto se puede hacer mediante `clojures` _(de ES5 en adelante)_. 1113 | 1114 | **🙅‍ Mal:** 1115 | 1116 | ```javascript 1117 | const Empleado = function(nombre) { 1118 | this.nombre = nombre; 1119 | }; 1120 | 1121 | Empleado.prototype.cogerNombre = function cogerNombre() { 1122 | return this.nombre; 1123 | }; 1124 | 1125 | const empleado = new Empleado("John Doe"); 1126 | console.log(`Nombre del empleado: ${empleado.cogerNombre()}`); // Nombre del empleado: John Doe 1127 | delete empleado.nombre; 1128 | console.log(`Nombre del empleado: ${empleado.cogerNombre()}`); // Nombre del empleado: undefined 1129 | ``` 1130 | 1131 | **👨‍🏫 Bien:** 1132 | 1133 | ```javascript 1134 | function crearEmpleado(name) { 1135 | return { 1136 | cogerNombre() { 1137 | return name; 1138 | } 1139 | }; 1140 | } 1141 | 1142 | const empleado = crearEmpleado("John Doe"); 1143 | console.log(`Nombre del empleado: ${empleado.cogerNombre()}`); // Nombre del empleado: John Doe 1144 | delete empleado.name; 1145 | console.log(`Nombre del empleado: ${empleado.cogerNombre()}`); // Nombre del empleado: John Doe 1146 | ``` 1147 | 1148 | **[⬆ Volver arriba](#contenido)** 1149 | 1150 | ## Clases 1151 | 1152 | ### Prioriza las classes de ES2015/ES6 antes que las funciones planas de ES50 1153 | 1154 | Es muy complicado de conseguir que un código sea entendible y fácil de leer con 1155 | herencia de clases, construcción y metodos típicos de clases con las clases de ES5. 1156 | Si necesitas herencia (y de seguro, que no la necesitas) entonces, dale prioridad a 1157 | las clases ES2015/ES6. De todas las maneras, deberías preferir pequeñas funciones 1158 | antes que ponerte a hacer clases. Solo cuando tengas un código largo o cuando veas 1159 | necesaria la implementación de clases, añádelas. 1160 | 1161 | **🙅‍ Mal:** 1162 | 1163 | ```javascript 1164 | const Animal = function(edad) { 1165 | if (!(this instanceof Animal)) { 1166 | throw new Error("Inicializa Animal con `new`"); 1167 | } 1168 | 1169 | this.edad = edad; 1170 | }; 1171 | 1172 | Animal.prototype.mover = function mover() {}; 1173 | 1174 | const Mamifero = function(edad, furColor) { 1175 | if (!(this instanceof Mamifero)) { 1176 | throw new Error("Inicializa Mamifero con `new`"); 1177 | } 1178 | 1179 | Animal.call(this, edad); 1180 | this.furColor = furColor; 1181 | }; 1182 | 1183 | Mamifero.prototype = Object.create(Animal.prototype); 1184 | Mamifero.prototype.constructor = Mamifero; 1185 | Mamifero.prototype.aniversario = function aniversario() {}; 1186 | 1187 | const Humano = function(edad, furColor, idioma) { 1188 | if (!(this instanceof Humano)) { 1189 | throw new Error("Inicializa Humano con `new`"); 1190 | } 1191 | 1192 | Mamifero.call(this, edad, furColor); 1193 | this.idioma = idioma; 1194 | }; 1195 | 1196 | Humano.prototype = Object.create(Mamifero.prototype); 1197 | Humano.prototype.constructor = Humano; 1198 | Humano.prototype.hablar = function hablar() {}; 1199 | ``` 1200 | 1201 | **👨‍🏫 Bien:** 1202 | 1203 | ```javascript 1204 | class Animal { 1205 | constructor(edad) { 1206 | this.edad = edad; 1207 | } 1208 | 1209 | mover() { 1210 | /* ... */ 1211 | } 1212 | } 1213 | 1214 | class Mamifero extends Animal { 1215 | constructor(edad, furColor) { 1216 | super(edad); 1217 | this.furColor = furColor; 1218 | } 1219 | 1220 | aniversario() { 1221 | /* ... */ 1222 | } 1223 | } 1224 | 1225 | class Human extends Mamifero { 1226 | constructor(edad, furColor, idioma) { 1227 | super(edad, furColor); 1228 | this.idioma = idioma; 1229 | } 1230 | 1231 | hablar() { 1232 | /* ... */ 1233 | } 1234 | } 1235 | ``` 1236 | 1237 | **[⬆ Volver arriba](#contenido)** 1238 | 1239 | ### Utiliza el anidación de funciones 1240 | 1241 | Este es un patrón útil en Javascript y verás que muchas librerías como jQuery 1242 | o Lodash lo usan. Permite que tu código sea expresivo y menos verboso. 1243 | Por esa razón, utiliza las funciones anidadas y date cuenta de que tan limpio estará 1244 | tu código. En las funciones de tu clase, sencillamente retorna `this` al final de 1245 | cada una y con eso, tienes todo lo necesario pra poder anidar las llamadas a las 1246 | funciones. 1247 | 1248 | **🙅‍ Mal:** 1249 | 1250 | ```javascript 1251 | class Coche { 1252 | constructor(marca, modelo, color) { 1253 | this.marca = marca; 1254 | this.modelo = modelo; 1255 | this.color = color; 1256 | } 1257 | 1258 | introducirMarca(marca) { 1259 | this.marca = marca; 1260 | } 1261 | 1262 | introducirModelo(modelo) { 1263 | this.modelo = modelo; 1264 | } 1265 | 1266 | introducirColor(color) { 1267 | this.color = color; 1268 | } 1269 | 1270 | guardar() { 1271 | console.log(this.marca, this.modelo, this.color); 1272 | } 1273 | } 1274 | 1275 | const coche = new Coche("Ford", "F-150", "rojo"); 1276 | coche.introducirColor("rosa"); 1277 | coche.guardar(); 1278 | ``` 1279 | 1280 | **👨‍🏫 Bien:** 1281 | 1282 | ```javascript 1283 | class Coche { 1284 | constructor(marca, modelo, color) { 1285 | this.marca = marca; 1286 | this.modelo = modelo; 1287 | this.color = color; 1288 | } 1289 | 1290 | introducirMarca(marca) { 1291 | this.marca = marca; 1292 | // NOTE: Retornamos this para poder anidas funciones 1293 | return this; 1294 | } 1295 | 1296 | introducirModelo(modelo) { 1297 | this.modelo = modelo; 1298 | // NOTE: Retornamos this para poder anidas funciones 1299 | return this; 1300 | } 1301 | 1302 | introducirColor(color) { 1303 | this.color = color; 1304 | // NOTE: Retornamos this para poder anidas funciones 1305 | return this; 1306 | } 1307 | 1308 | guardar() { 1309 | console.log(this.marca, this.modelo, this.color); 1310 | // NOTE: Retornamos this para poder anidas funciones 1311 | return this; 1312 | } 1313 | } 1314 | 1315 | const coche = new Coche("Ford", "F-150", "rojo") 1316 | .introducirColor("rosa") 1317 | .guardar(); 1318 | ``` 1319 | 1320 | **[⬆ Volver arriba](#contenido)** 1321 | 1322 | ### Prioriza la composición en vez de la herecia 1323 | 1324 | Como se citó en [_Patrones de Diseño_](https://en.wikipedia.org/wiki/Design_Patterns) 1325 | por "the Gang of Four", deberías priorizar la composición en vez de la herecia 1326 | siempre que puedas. Hay muy buenas razones para usar tanto la herecia como la 1327 | composición. El problema principal es que nuestra mente siempre tiende a la herencia 1328 | como primera opción, pero deberíamos de pensar qué tan bien nos encaja la composición 1329 | en ese caso particular porque en muchas ocasiones es lo más acertado. 1330 | 1331 | Te estarás preguntando entonces, _¿Cuando debería yo usar la herencia?_ Todo depende. 1332 | Depende del problema que tengas entre mano, pero ya hay ocasiones particulares donde 1333 | la herencia tiene más sentido que la composición: 1334 | 1335 | 1. Tu herencia representa una relación "es un/a" en vez de "tiene un/a" 1336 | (Humano->Animal vs. Usuario->DetallesUsuario) 1337 | 2. Puedes reutilizar código desde las clases base (Los humanos pueden moverse como animales) 1338 | 3. Quieres hacer cambios generales a clases derivadas cambiando la clase base. 1339 | (Cambiar el consumo de calorías a todos los animales mientras se mueven) 1340 | 1341 | **🙅‍ Mal:** 1342 | 1343 | ```javascript 1344 | class Empleado { 1345 | constructor(nombre, correoElectronico) { 1346 | this.nombre = nombre; 1347 | this.correoElectronico = correoElectronico; 1348 | } 1349 | 1350 | // ... 1351 | } 1352 | 1353 | // Bad because Employees "have" tax data. EmployeeTaxData is not a type of Empleado 1354 | class InformacionImpuestosEmpleado extends Empleado { 1355 | constructor(ssn, salario) { 1356 | super(); 1357 | this.ssn = ssn; 1358 | this.salario = salario; 1359 | } 1360 | 1361 | // ... 1362 | } 1363 | ``` 1364 | 1365 | **👨‍🏫 Bien:** 1366 | 1367 | ```javascript 1368 | class InformacionImpuestosEmpleado { 1369 | constructor(ssn, salario) { 1370 | this.ssn = ssn; 1371 | this.salario = salario; 1372 | } 1373 | 1374 | // ... 1375 | } 1376 | 1377 | class Empleado { 1378 | constructor(nombre, correoElectronico) { 1379 | this.nombre = nombre; 1380 | this.correoElectronico = correoElectronico; 1381 | } 1382 | 1383 | introducirInformacionImpuestos(ssn, salario) { 1384 | this.informacionImpuestos = new InformacionImpuestosEmpleado(ssn, salario); 1385 | } 1386 | // ... 1387 | } 1388 | ``` 1389 | 1390 | **[⬆ Volver arriba](#contenido)** 1391 | 1392 | ## **SOLID** 1393 | 1394 | ### Principio de Responsabilidad Única (SRP) 1395 | 1396 | Como se cita en _Código Limpio_, "No debería haber nunca más de un motivo para 1397 | que una clase cambie". Es muy tentador acribillar a una clase con un montón de 1398 | funcionalidad. El problema que tiene esto, es que tu clase no tendrá cohesión 1399 | y tendrá bastantes motivos por los que cambiar. Es por eso que es importante 1400 | reducir el número de veces que tendrás que modificar una clase. Y lo es, porque 1401 | en caso de que tengamos una clase que haga más de una cosa y modifiquemos una 1402 | de ellas, no podemos saber que efectos colaterales puede tener esta acción en las demás. 1403 | 1404 | **🙅‍ Mal:** 1405 | 1406 | ```javascript 1407 | class OpcionesUsuario { 1408 | constructor(usuario) { 1409 | this.usuario = usuario; 1410 | } 1411 | 1412 | changeSettings(opciones) { 1413 | if (this.verificarCredenciales()) { 1414 | // ... 1415 | } 1416 | } 1417 | 1418 | verificarCredenciales() { 1419 | // ... 1420 | } 1421 | } 1422 | ``` 1423 | 1424 | **👨‍🏫 Bien:** 1425 | 1426 | ```javascript 1427 | class AutenticationUsuario { 1428 | constructor(usuario) { 1429 | this.usuario = usuario; 1430 | } 1431 | 1432 | verificarCredenciales() { 1433 | // ... 1434 | } 1435 | } 1436 | 1437 | class UserSettings { 1438 | constructor(usuario) { 1439 | this.usuario = usuario; 1440 | this.autenticacion = new AutenticationUsuario(usuario); 1441 | } 1442 | 1443 | changeSettings(settings) { 1444 | if (this.autenticacion.verificarCredenciales()) { 1445 | // ... 1446 | } 1447 | } 1448 | } 1449 | ``` 1450 | 1451 | **[⬆ Volver arriba](#contenido)** 1452 | 1453 | ### Principio de abierto/cerrado (OCP) 1454 | 1455 | Citado por Bertrand Meyer: _"Las entidades de software (clases, módulos, funciones, ...) 1456 | deberían estar abiertas a extensión pere cerradas a modificación."_ ¿Qué significa esto? 1457 | Básicamente significa que los usuarios deberían de ser capaces de añadir funcionalidad 1458 | a la aplicación sin tener que tocar el código creado hasta ahora. 1459 | 1460 | **🙅‍ Mal:** 1461 | 1462 | ```javascript 1463 | class AdaptadorAjax extends Adaptador { 1464 | constructor() { 1465 | super(); 1466 | this.name = "adaptadorAjax"; 1467 | } 1468 | } 1469 | 1470 | class AdaptadorNodos extends Adaptador { 1471 | constructor() { 1472 | super(); 1473 | this.nombre = "adaptadorNodos"; 1474 | } 1475 | } 1476 | 1477 | class { 1478 | constructor(adapter) { 1479 | this.adapter = adapter; 1480 | } 1481 | 1482 | fetch(url) { 1483 | if (this.adapter.nombre === "adaptadorAjax") { 1484 | return hacerLlamadaAjax(url).then(respuesta => { 1485 | // transformar la respuesta y devolverla 1486 | }); 1487 | } else if (this.adapter.nombre === "adaptadorHttpNodos") { 1488 | return hacerLlamadaHttp(url).then(respuesta => { 1489 | // transformar la respuesta y devolverla 1490 | }); 1491 | } 1492 | } 1493 | } 1494 | 1495 | function hacerLlamadaAjax(url) { 1496 | // request and return promise 1497 | } 1498 | 1499 | function hacerLlamadaHttp(url) { 1500 | // request and return promise 1501 | } 1502 | ``` 1503 | 1504 | **👨‍🏫 Bien:** 1505 | 1506 | ```javascript 1507 | class AdaptadorAjax extends Adapter { 1508 | constructor() { 1509 | super(); 1510 | this.nombre = "adaptadorAjax"; 1511 | } 1512 | 1513 | pedir(url) { 1514 | // Pedir y devolver la promesa 1515 | } 1516 | } 1517 | 1518 | class AdaptadorNodos extends Adapter { 1519 | constructor() { 1520 | super(); 1521 | this.nombre = "adaptadorNodos"; 1522 | } 1523 | 1524 | pedir(url) { 1525 | // Pedir y devolver la promesa 1526 | } 1527 | } 1528 | 1529 | class EjecutadorPeticionesHttp { 1530 | constructor(adaptador) { 1531 | this.adaptador = adaptador; 1532 | } 1533 | 1534 | fetch(url) { 1535 | return this.adaptador.pedir(url).then(respuesta => { 1536 | // Transformar y devolver la respuesta 1537 | }); 1538 | } 1539 | } 1540 | ``` 1541 | 1542 | **[⬆ Volver arriba](#contenido)** 1543 | 1544 | ### Principio de sustitución de Liskov (LSP) 1545 | 1546 | Este es un término que asusta para lo sencillo que es. Estrictamente se define como 1547 | "Si S es un subtipo de T, entonces los objetos del tipo T deberían poderse substituir 1548 | por objetos del tipo S". 1549 | 1550 | Un ejemplo práctico vien a ser si tenemos una _clase padre_ y una _clase hija_, 1551 | entonces ambas han de poderse substituir la una por la otra y viceversa sin recibir 1552 | ningún tipo de error o datos erróneos. Un caso práctico es el del cuadrado y el 1553 | rectángulo. Geométricamente, un cuadrado es un rectángulo, pero si lo creamos 1554 | con una relación "es un" a través de herencia, empezamos a tener problemas... 1555 | 1556 | **🙅‍ Mal:** 1557 | 1558 | ```javascript 1559 | class Rectangulo { 1560 | constructor() { 1561 | this.anchura = 0; 1562 | this.altura = 0; 1563 | } 1564 | 1565 | introducirColor(color) { 1566 | // ... 1567 | } 1568 | 1569 | render(area) { 1570 | // ... 1571 | } 1572 | 1573 | introducirAnchura(anchura) { 1574 | this.anchura = anchura; 1575 | } 1576 | 1577 | introducirAltura(altura) { 1578 | this.altura = altura; 1579 | } 1580 | 1581 | conseguirArea() { 1582 | return this.anchura * this.altura; 1583 | } 1584 | } 1585 | 1586 | class Cuadrado extends Rectangulo { 1587 | introducirAnchura(anchura) { 1588 | this.anchura = anchura; 1589 | this.altura = anchura; 1590 | } 1591 | 1592 | introducirAltura(altura) { 1593 | this.width = altura; 1594 | this.altura = altura; 1595 | } 1596 | } 1597 | 1598 | function renderizaRectangulosLargos(rectangulos) { 1599 | rectangulos.forEach(rectangulo => { 1600 | rectangulo.introducirAnchura(4); 1601 | rectangulo.introducirAltura(5); 1602 | const area = rectangulo.conseguirArea(); // MAL: Para el cuadrado devuelve 25 y devería ser 20 1603 | rectangulo.render(area); 1604 | }); 1605 | } 1606 | 1607 | const rectangulos = [new Rectangulo(), new Rectangulo(), new Cuadrado()]; 1608 | renderizaRectangulosLargos(rectangulos); 1609 | ``` 1610 | 1611 | **👨‍🏫 Bien:** 1612 | 1613 | ```javascript 1614 | class Forma { 1615 | introducirColor(color) { 1616 | // ... 1617 | } 1618 | 1619 | render(area) { 1620 | // ... 1621 | } 1622 | } 1623 | 1624 | class Rectangulo extends Forma { 1625 | constructor(width, height) { 1626 | super(); 1627 | this.anchura = anchura; 1628 | this.altura = altura; 1629 | } 1630 | 1631 | conseguirArea() { 1632 | return this.anchura * this.altura; 1633 | } 1634 | } 1635 | 1636 | class Cuadrado extends Forma { 1637 | constructor(distancia) { 1638 | super(); 1639 | this.distancia = distancia; 1640 | } 1641 | 1642 | conseguirArea() { 1643 | return this.distancia * this.distancia; 1644 | } 1645 | } 1646 | 1647 | function renderizaRectangulosLargos(shapes) { 1648 | shapes.forEach(shape => { 1649 | const area = shape.conseguirArea(); 1650 | shape.render(area); 1651 | }); 1652 | } 1653 | 1654 | const shapes = [new Rectangulo(4, 5), new Rectangulo(4, 5), new Cuadrado(5)]; 1655 | renderizaRectangulosLargos(shapes); 1656 | ``` 1657 | 1658 | **[⬆ Volver arriba](#contenido)** 1659 | 1660 | ### Principio de Segregacion de Interfaces (ISP) 1661 | 1662 | Javascript no dispone de interfaces así que no podemos aplicar el principio como 1663 | tal. De todas maneras, es importante conceptualmente hablando aunque no tengamos 1664 | tipados como tal, pues eso resulta haciendo un código mantenible igualmente. 1665 | 1666 | _ISP_ dice que "los servicios no deberían estar forzados a depender de interfaces 1667 | que realmente no usan". 1668 | 1669 | Un buen ejemplo en javascript sería las típicas clases que requieren de un 1670 | enormes objetos de configuración. No hacer que los servicios requieran de 1671 | grandes cantidades de opciones es beneficioso, porque la gran mayoría del tiempo, 1672 | no necesitarán esa configuración. Hacerlos opcionales ayuda a no tener el problema 1673 | de "Interaz gorda", en inglés conocido como "fat interface". 1674 | 1675 | **🙅‍ Mal:** 1676 | 1677 | ```javascript 1678 | class DOMTraverser { 1679 | constructor(configuraciones) { 1680 | this.configuraciones = configuraciones; 1681 | this.setup(); 1682 | } 1683 | 1684 | preparar() { 1685 | this.nodoRaiz = this.configuraciones.nodoRaiz; 1686 | this.ModuloAnimacion.preparar(); 1687 | } 1688 | 1689 | atravesar() { 1690 | // ... 1691 | } 1692 | } 1693 | 1694 | const $ = new DOMTraverser({ 1695 | nodoRaiz: document.getElementsByTagName("body"), 1696 | moduloAnimacion() {} // Most of the time, we won't need to animate when traversing. 1697 | // ... 1698 | }); 1699 | ``` 1700 | 1701 | **👨‍🏫 Bien:** 1702 | 1703 | ```javascript 1704 | class DOMTraverser { 1705 | constructor(configuraciones) { 1706 | this.configuraciones = configuraciones; 1707 | this.opciones = configuraciones.opciones; 1708 | this.preparar(); 1709 | } 1710 | 1711 | preparar() { 1712 | this.nodoRaiz = this.configuraciones.nodoRaiz; 1713 | this.prepararOpciones(); 1714 | } 1715 | 1716 | prepararOpciones() { 1717 | if (this.opciones.moduloAnimacion) { 1718 | // ... 1719 | } 1720 | } 1721 | 1722 | atravesar() { 1723 | // ... 1724 | } 1725 | } 1726 | 1727 | const $ = new DOMTraverser({ 1728 | nodoRaiz: document.getElementsByTagName("body"), 1729 | opciones: { 1730 | moduloAnimacion() {} 1731 | } 1732 | }); 1733 | ``` 1734 | 1735 | **[⬆ Volver arriba](#contenido)** 1736 | 1737 | ### Principio de Inversión de Dependencias (DIP) 1738 | 1739 | _Por favor, no confundir con Inyección de Dependencias._ Mucha gente se piensa 1740 | que la "D" de _SOLID_ es de Inyección de Dependencias _(Dependency Inection, DI)._ 1741 | 1742 | Este principio nos dice dos cosas básicamente: 1743 | 1744 | 1. Módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos 1745 | deberían depender de abstracciones. 1746 | 2. Las abstracciones no deberían depender de detalles si no que, los detalles 1747 | deberían depender de abstracciones. 1748 | 1749 | Esto puede ser algo complejo al principio, pero si has trabajado con AngularJS, 1750 | has visto de manera indirecta esto con la Inyección de Dependencias. Como 1751 | comentaba anteriormente, aunque no son lo mismo, van de la mano. La Inversión de 1752 | Dependencías es posible gracias a la Inyección de Dependencias. _DI_ hace posible 1753 | que los módulos de alto nivel dependan de abstracciones y no de detalles. 1754 | 1755 | El mayor de los beneficioses la reducción del acoplamiento entre módulos. Cuánto 1756 | mayor acoplamiento, mayor dificultad en refactorización. 1757 | 1758 | Como hemos comentado antes, `Javascript` no tiene interfaces así que los contratos 1759 | son un poco... así asá. Están en nuestro cabeza y eso debemos tenerlo en cuenta. 1760 | Mucha gente usa javascript docs, anotaciones en comentarios justo encima de los 1761 | módulos y algunas cosas más. Vamos a ver un ejemplo con `RastreadorDeInventario`. 1762 | 1763 | **🙅‍ Mal:** 1764 | 1765 | ```javascript 1766 | class SolicitadorDeInventario { 1767 | constructor() { 1768 | this.REQ_METHODS = ["HTTP"]; 1769 | } 1770 | 1771 | pedirArticulo(articulo) { 1772 | // ... 1773 | } 1774 | } 1775 | 1776 | class RastreadorDeInventario { 1777 | constructor(articulos) { 1778 | this.articulos = articulos; 1779 | 1780 | // MAL: Hemos creado una dependencia de una concreción que va atada a una implementación 1781 | // Deberíamos tener pedirArticulos dependiendo únicamente de un método: 'solicitud' 1782 | this.solicitador = new SolicitadorDeInventario(); 1783 | } 1784 | 1785 | pedirArticulos() { 1786 | this.articulos.forEach(articulo => { 1787 | this.solicitador.pedirArticulo(articulo); 1788 | }); 1789 | } 1790 | } 1791 | 1792 | const rastreadorDeInventario = new RastreadorDeInventario([ 1793 | "manzanas", 1794 | "platanos" 1795 | ]); 1796 | rastreadorDeInventario.pedirArticulos(); 1797 | ``` 1798 | 1799 | **👨‍🏫 Bien:** 1800 | 1801 | ```javascript 1802 | class RastreadorDeInventario { 1803 | constructor(articulos, solicitador) { 1804 | this.articulos = articulos; 1805 | this.solicitador = solicitador; 1806 | } 1807 | 1808 | pedirArticulos() { 1809 | this.articulos.forEach(articulo => { 1810 | this.solicitador.pedirArticulo(articulo); 1811 | }); 1812 | } 1813 | } 1814 | 1815 | class SolicitadorDeInventarioV1 { 1816 | constructor() { 1817 | this.REQ_METHODS = ["HTTP"]; 1818 | } 1819 | 1820 | pedirArticulo(articulo) { 1821 | // ... 1822 | } 1823 | } 1824 | 1825 | class SolicitadorDeInventarioV2 { 1826 | constructor() { 1827 | this.REQ_METHODS = ["WS"]; 1828 | } 1829 | 1830 | pedirArticulo(articulo) { 1831 | // ... 1832 | } 1833 | } 1834 | 1835 | // By constructing our dependencies externally and injecting them, we can easily 1836 | // substitute our request module for a fancy new one that uses WebSockets. 1837 | 1838 | // Construyendo nuestras dependencias desde fuera e inyectandolas, podríamos 1839 | // substituir nuestro Módulo solicitador por uno con websockets o lo que sea 1840 | const rastreadorDeInventario = new RastreadorDeInventario( 1841 | ["manzanas", "platanos"], 1842 | new SolicitadorDeInventarioV2() 1843 | ); 1844 | rastreadorDeInventario.pedirArticulos(); 1845 | ``` 1846 | 1847 | **[⬆ Volver arriba](#contenido)** 1848 | 1849 | ## **Testing** 1850 | 1851 | El testing es más importante que la entrega. Si no tienes test o tienes muchos 1852 | que no soy de gran ayuda, cada vez que quieras entregar valor no estarás seguro 1853 | de ue eso funciona debidamente y que nada falla. Puedes decidir con el equipo 1854 | cuál es el porcentaje al que queréis ceñiros pero, la única manera de tener 1855 | confianza total de que nada falla, es teniendo 100% de covertura de test. Para 1856 | esto, ncesitarás tener una gran herramienta para poder testear pero también 1857 | una que te calcule adecuadamente [el porcentaje cubierto](http://gotwarlost.github.io/istanbul/). 1858 | 1859 | No hay excusas para no escribir tests. Hay 1860 | [un montón de frameworks de JS](http://jstherightway.org/#testing-tools) entre 1861 | los que podréis tu y tu equipo decidir. Una vez hayáis elegido el framework, 1862 | para cada nueva funcionalidad que se quiera añadir a la plataforma, escribir tests. 1863 | Si prefieres hacer _Test-Driven Development_ me parece bien, pero la ide principal de 1864 | los test es dar confianza suficiente al programador para que pueda seguir entregando valor. 1865 | 1866 | ### Sólo un concepto por test 1867 | 1868 | **🙅‍ Mal:** 1869 | 1870 | ```javascript 1871 | import assert from "assert"; 1872 | 1873 | describe("MakeMomentJSGreatAgain", () => { 1874 | it("maneja límites de las fechas", () => { 1875 | let fecha; 1876 | 1877 | fecha = new MakeMomentJSGreatAgain("1/1/2015"); 1878 | fecha.addDays(30); 1879 | assert.equal("1/31/2015", fecha); 1880 | 1881 | fecha = new MakeMomentJSGreatAgain("2/1/2016"); 1882 | fecha.addDays(28); 1883 | assert.equal("02/29/2016", fecha); 1884 | 1885 | fecha = new MakeMomentJSGreatAgain("2/1/2015"); 1886 | fecha.addDays(28); 1887 | assert.equal("03/01/2015", fecha); 1888 | }); 1889 | }); 1890 | ``` 1891 | 1892 | **👨‍🏫 Bien:** 1893 | 1894 | ```javascript 1895 | import assert from "assert"; 1896 | 1897 | describe("MakeMomentJSGreatAgain", () => { 1898 | it("Maneja los meses con 30 días", () => { 1899 | const fecha = new MakeMomentJSGreatAgain("1/1/2015"); 1900 | fecha.addDays(30); 1901 | assert.equal("1/31/2015", fecha); 1902 | }); 1903 | 1904 | it("Maneja los años bisiestos", () => { 1905 | const fecha = new MakeMomentJSGreatAgain("2/1/2016"); 1906 | fecha.addDays(28); 1907 | assert.equal("02/29/2016", fecha); 1908 | }); 1909 | 1910 | it("Maneja los años NO bisiestos", () => { 1911 | const fecha = new MakeMomentJSGreatAgain("2/1/2015"); 1912 | fecha.addDays(28); 1913 | assert.equal("03/01/2015", fecha); 1914 | }); 1915 | }); 1916 | ``` 1917 | 1918 | **[⬆ Volver arriba](#contenido)** 1919 | 1920 | ## Concurrencia 1921 | 1922 | ### Usa Promesas, no callbacks 1923 | 1924 | _Los callbacks son funciones que se pasan como parámetros a otras funciones 1925 | para ser ejecutaras una vez esta función termina. Por ejemplo: Dada las funciones 1926 | A y B, se dice que B es el callback de A si la función B es pasada como parámetro 1927 | a la función A y esta, se ejecuta este callback una vez ha terminado_ 1928 | 1929 | Los `callbacks` no son limpios ni en cuanto a legibilidad ni en cuanto a formato 1930 | de texto _(dado que provocan niveles de identación)_. Con ES2015/ES6 las promesas 1931 | son un tipo global. ¡Úsalas! 1932 | 1933 | **🙅‍ Mal:** 1934 | 1935 | ```javascript 1936 | import { get } from "request"; 1937 | import { writeFile } from "fs"; 1938 | 1939 | get( 1940 | "https://en.wikipedia.org/wiki/Robert_Cecil_Martin", 1941 | (requestErr, respuesta) => { 1942 | if (requestErr) { 1943 | console.error(requestErr); 1944 | } else { 1945 | writeFile("article.html", respuesta.body, writeErr => { 1946 | if (writeErr) { 1947 | console.error(writeErr); 1948 | } else { 1949 | console.log("File written"); 1950 | } 1951 | }); 1952 | } 1953 | } 1954 | ); 1955 | ``` 1956 | 1957 | **👨‍🏫 Bien:** 1958 | 1959 | ```javascript 1960 | import { get } from "request"; 1961 | import { writeFile } from "fs"; 1962 | 1963 | get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin") 1964 | .then(respuesta => { 1965 | return writeFile("article.html", respuesta); 1966 | }) 1967 | .then(() => { 1968 | console.log("File written"); 1969 | }) 1970 | .catch(err => { 1971 | console.error(err); 1972 | }); 1973 | ``` 1974 | 1975 | **[⬆ Volver arriba](#contenido)** 1976 | 1977 | ### Async/Await is incluso más limpio que las Promesas 1978 | 1979 | Las promesas son una elección más limpia que los callbacks pero ES2017/ES8 1980 | trae la funcionalidad de `async/await` que es incluos más limpio que las promesas. 1981 | Todo lo que tienes que hacer es añadir el prefijo `async` a una función y entonces 1982 | ya podemos usar esa función de manera imperative sin ningún `.then()`. La 1983 | palabra `await` la usarás para hacer que ese código asincrono se comporte de 1984 | "manera síncrona". 1985 | 1986 | **🙅‍ Mal:** 1987 | 1988 | ```javascript 1989 | import { get } from "request-promise"; 1990 | import { writeFile } from "fs-promise"; 1991 | 1992 | get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin") 1993 | .then(respuesrta => { 1994 | return writeFile("article.html", respuesrta); 1995 | }) 1996 | .then(() => { 1997 | console.log("File written"); 1998 | }) 1999 | .catch(err => { 2000 | console.error(err); 2001 | }); 2002 | ``` 2003 | 2004 | **👨‍🏫 Bien:** 2005 | 2006 | ```javascript 2007 | import { get } from "request-promise"; 2008 | import { writeFile } from "fs-promise"; 2009 | 2010 | async function conseguirArticulosDeCodigoLimpio() { 2011 | try { 2012 | const respuesrta = await get( 2013 | "https://en.wikipedia.org/wiki/Robert_Cecil_Martin" 2014 | ); 2015 | await writeFile("article.html", respuesrta); 2016 | console.log("File written"); 2017 | } catch (err) { 2018 | console.error(err); 2019 | } 2020 | } 2021 | ``` 2022 | 2023 | **[⬆ Volver arriba](#contenido)** 2024 | 2025 | ## Manejo de errores 2026 | 2027 | ¡Lanzar errores está bien! Significa que en tiempo de ejecución se ha detectado 2028 | que algo no estaba funcionando como debía y se ha parado la ejecución del trozo 2029 | de código. Además se notifica siempre en la consola del navegador. 2030 | 2031 | ### No ignores los errores capturados 2032 | 2033 | No hacer nada con los errores capturados no te da la opción de anticiparte 2034 | o arreglar dicho error. El printar el error por la consola del navegador 2035 | no es una solución, pues la gran mayoría de veces nadie es consciente de eso 2036 | y el error pasas desapercibido. Envuelve tu código con `try/catch` y es ahí 2037 | donde tendrás que elaborar tu plan de reacción a posibles errores 2038 | 2039 | **🙅‍ Mal:** 2040 | 2041 | ```javascript 2042 | try { 2043 | functionQueDeberiaLanzarError(); 2044 | } catch (error) { 2045 | console.log(error); 2046 | } 2047 | ``` 2048 | 2049 | **👨‍🏫 Bien:** 2050 | 2051 | ```javascript 2052 | try { 2053 | functionQueDeberiaLanzarError(); 2054 | } catch (error) { 2055 | // Una option (algo más molesta que el convencional console.log) 2056 | console.error(error); 2057 | // Otra opción: 2058 | notificarAlUsuarioDelError(error); 2059 | // Otra opción: 2060 | reportarElArrorAUnServicio(error); 2061 | // O hazlas todas! 2062 | } 2063 | ``` 2064 | 2065 | ### No ignores las promesas rechazadas 2066 | 2067 | No ignores las promesas que han sido rechadas por la misma razón que no deberías 2068 | ignorar errores capturados en el `try/catch`. 2069 | 2070 | **🙅‍ Mal:** 2071 | 2072 | ```javascript 2073 | cogerDatos() 2074 | .then(datos => { 2075 | functionQueDeberiaLanzarError(datos); 2076 | }) 2077 | .catch(error => { 2078 | console.log(error); 2079 | }); 2080 | ``` 2081 | 2082 | **👨‍🏫 Bien:** 2083 | 2084 | ```javascript 2085 | cogerDatos() 2086 | .then(datos => { 2087 | functionQueDeberiaLanzarError(datos); 2088 | }) 2089 | .catch(error => { 2090 | // Una option (algo más molesta que el convencional console.log) 2091 | console.error(error); 2092 | // Otra opción: 2093 | notificarAlUsuarioDelError(error); 2094 | // Otra opción: 2095 | reportarElArrorAUnServicio(error); 2096 | // O hazlas todas! 2097 | }); 2098 | ``` 2099 | 2100 | **[⬆ Volver arriba](#contenido)** 2101 | 2102 | ## Formato 2103 | 2104 | El formato del código es algo subjetivo. Como otras reglas aquí, no hay una regla 2105 | que deberías seguir o una fórmula secreta. Lo que si que está claro es que no 2106 | deberíamos discutir ni crear conflictos con nuestros compañeros de trabajo acerca 2107 | de estas reglas. Hay unas cuantas [herrmientas](http://standardjs.com/rules.html) 2108 | que automatizan todas las reglas de formato de texto. ¡Ahorrarse tiempo en estas 2109 | formateando el texto es un pasada! 2110 | 2111 | ### Usa consistenemente la capitalización 2112 | 2113 | Como ya hemos dicho, `javascript` es un lenguage no tipado así pues, la 2114 | capitalización de las variables importa, y mucho. Estas son reglas totalmente 2115 | subjetivas así que como equipo, podéis elegir lo que más os guste/convenga. 2116 | La cuestión es que independientemente de lo que decidáis, seáis consistentes. 2117 | 2118 | **🙅‍ Mal:** 2119 | 2120 | ```javascript 2121 | const DIAS_POR_SEMANA = 7; 2122 | const diasPorMes = 30; 2123 | 2124 | const canciones = ["Back In Black", "Stairway to Heaven", "Hey Jude"]; 2125 | const Artistas = ["ACDC", "Led Zeppelin", "The Beatles"]; 2126 | 2127 | function borrarBaseDeDatos() {} 2128 | function restablecer_baseDeDatos() {} 2129 | 2130 | class animal {} 2131 | class Alpaca {} 2132 | ``` 2133 | 2134 | **👨‍🏫 Bien:** 2135 | 2136 | ```javascript 2137 | const DIAS_POR_SEMANA = 7; 2138 | const DIAS_POR_MES = 30; 2139 | 2140 | const CANCIONES = ["Back In Black", "Stairway to Heaven", "Hey Jude"]; 2141 | const ARTISTAS = ["ACDC", "Led Zeppelin", "The Beatles"]; 2142 | 2143 | function borrarBaseDeDatos() {} 2144 | function restablecerBaseDeDatos() {} 2145 | 2146 | class Animal {} 2147 | class Alpaca {} 2148 | ``` 2149 | 2150 | **[⬆ Volver arriba](#contenido)** 2151 | 2152 | ### Funciones que llaman y funciones que son llamadas, deberían estar cerca 2153 | 2154 | Si una función llama a otra, haz que esta función que va a ser llamada esté 2155 | lo más cerca posible de la función que la llama. Idealmente, situa siempre 2156 | la función que va a ser llamada justo después de la función que la ejecuta. 2157 | ¿El motivo? Pues normalmente acostumbramos a leer de arriba abajo y tampoco 2158 | queremos tener que hacer _scroll_ hasta abajo del todo del ficheor para volver 2159 | a subir. 2160 | 2161 | **🙅‍ Mal:** 2162 | 2163 | ```javascript 2164 | class RevisionDeRendimiento { 2165 | constructor(empleado) { 2166 | this.empleado = empleado; 2167 | } 2168 | 2169 | conseguirCompañeros() { 2170 | return db.buscar(this.empleado, "compañeros"); 2171 | } 2172 | 2173 | conseguirJefe() { 2174 | return db.buscar(this.empleado, "jefe"); 2175 | } 2176 | 2177 | conseguirOpinionDeLosCompañeros() { 2178 | const compañeros = this.conseguirCompañeros(); 2179 | // ... 2180 | } 2181 | 2182 | executarRevision() { 2183 | this.conseguirOpinionDeLosCompañeros(); 2184 | this.conseguirOpinionDelJefe(); 2185 | this.conseguirAutoRevision(); 2186 | } 2187 | 2188 | conseguirOpinionDelJefe() { 2189 | const jefe = this.conseguirJefe(); 2190 | } 2191 | 2192 | conseguirAutoRevision() { 2193 | // ... 2194 | } 2195 | } 2196 | 2197 | const review = new RevisionDeRendimiento(empleado); 2198 | review.executarRevision(); 2199 | ``` 2200 | 2201 | **👨‍🏫 Bien:** 2202 | 2203 | ```javascript 2204 | class RevisionDeRendimiento { 2205 | constructor(empleado) { 2206 | this.empleado = empleado; 2207 | } 2208 | 2209 | executarRevision() { 2210 | this.conseguirOpinionDeLosCompañeros(); 2211 | this.conseguirOpinionDelJefe(); 2212 | this.conseguirAutoRevision(); 2213 | } 2214 | 2215 | conseguirOpinionDeLosCompañeros() { 2216 | const compañeros = this.conseguirCompañeros(); 2217 | // ... 2218 | } 2219 | 2220 | conseguirCompañeros() { 2221 | return db.buscar(this.empleado, "compañeros"); 2222 | } 2223 | 2224 | conseguirOpinionDelJefe() { 2225 | const jefe = this.conseguirJefe(); 2226 | } 2227 | 2228 | conseguirJefe() { 2229 | return db.buscar(this.empleado, "jefe"); 2230 | } 2231 | 2232 | conseguirAutoRevision() { 2233 | // ... 2234 | } 2235 | } 2236 | 2237 | const review = new RevisionDeRendimiento(empleado); 2238 | review.executarRevision(); 2239 | ``` 2240 | 2241 | **[⬆ Volver arriba](#contenido)** 2242 | 2243 | ## Comentarios 2244 | 2245 | ### Comenta únicamente la lógica de negocio que es compleja 2246 | 2247 | Los comentarios son una disculpa, no un requerimiento. Supuesatmente se dice 2248 | que un buen código debería comentarse por si mismo. Un código perfecto no 2249 | está optimizado para la máquina sinó que lo está para la manteniblidad de éste 2250 | por un compañero o futuro compañero. Para esto, ha de ser lo más semántico posible. 2251 | El código ha de estar escrito para que niños pequeños lo entiendan. 2252 | 2253 | **🙅‍ Mal:** 2254 | 2255 | ```javascript 2256 | function hashIt(datos) { 2257 | // El hash 2258 | let hash = 0; 2259 | 2260 | // Tamaño del string 2261 | const tamaño = datos.length; 2262 | 2263 | // Iteramos a través de cada carácter de los datos 2264 | for (let i = 0; i < tamaño; i++) { 2265 | // Coger código del caracter 2266 | const char = datos.charCodeAt(i); 2267 | // Crear el hash 2268 | hash = (hash << 5) - hash + char; 2269 | // Convertir a un entero de 32 bits 2270 | hash &= hash; 2271 | } 2272 | } 2273 | ``` 2274 | 2275 | **👨‍🏫 Bien:** 2276 | 2277 | ```javascript 2278 | function hashIt(datos) { 2279 | let hash = 0; 2280 | const tamaño = datos.length; 2281 | 2282 | for (let i = 0; i < tamaño; i++) { 2283 | const caracter = datos.charCodeAt(i); 2284 | hash = (hash << 5) - hash + caracter; 2285 | 2286 | // Convertir a un entero de 32 bits 2287 | hash &= hash; 2288 | } 2289 | } 2290 | ``` 2291 | 2292 | **[⬆ Volver arriba](#contenido)** 2293 | 2294 | ### No dejes código comentado en tu repositorio 2295 | 2296 | El control de versiones existe para algo. Si tu motivo o excusa por el que comentar 2297 | un código es porque en breves o algun día lo vas a necesitas, eso no me sirve. Ese 2298 | código que acabas de borrar consta en alguna de tus versiones de tu código fuente. 2299 | Lo que deberías hacer entonces quizás, es usar `git tags`, poner el código de la 2300 | tarea en el nombre del commit, etc... Hay muchos truquitos para hacer eso! 2301 | 2302 | **🙅‍ Mal:** 2303 | 2304 | ```javascript 2305 | hacerCosas(); 2306 | // hacerOtrasCosas(); 2307 | // hacerCosasAunMasRaras(); 2308 | // estoHaceMaravillas(); 2309 | ``` 2310 | 2311 | **👨‍🏫 Bien:** 2312 | 2313 | ```javascript 2314 | hacerCosas(); 2315 | ``` 2316 | 2317 | **[⬆ Volver arriba](#contenido)** 2318 | 2319 | ### No hagas un diario de comentarios 2320 | 2321 | Recuerda ¡Usa el control de versioens! No hay motivo alguno para tener código 2322 | muerto, código comentado y aún menos, un diadrio o resumen de modificaciones en 2323 | tus comentarios. Si quieres ver las modificaciones, usa `git log`, la herramiento 2324 | `blame` o incluso el `history`. 2325 | 2326 | **🙅‍ Mal:** 2327 | 2328 | ```javascript 2329 | /** 2330 | * 2016-12-20: `monads` borrados, no hay quien los entienda 2331 | * 2016-10-01: Código mejorado con 'monads' 2332 | * 2016-02-03: Borrado tipado 2333 | * 2015-03-14: Añadido tipado 2334 | */ 2335 | function combinar(a, b) { 2336 | return a + b; 2337 | } 2338 | ``` 2339 | 2340 | **👨‍🏫 Bien:** 2341 | 2342 | ```javascript 2343 | function combinar(a, b) { 2344 | return a + b; 2345 | } 2346 | ``` 2347 | 2348 | **[⬆ Volver arriba](#contenido)** 2349 | 2350 | ### Evita los marcadores de secciones 2351 | 2352 | Normalmente acostumbran a ser molestos. Deja que las variables y las funciones 2353 | hagan su función con sus identaciones naturales y de esta manera, formateen el 2354 | código correctamente 2355 | . 2356 | 2357 | **🙅‍ Mal:** 2358 | 2359 | ```javascript 2360 | //////////////////////////////////////////////////////////////////////////////// 2361 | // Instanciación del Modelo Scope 2362 | //////////////////////////////////////////////////////////////////////////////// 2363 | $scope.modelo = { 2364 | menu: "foo", 2365 | nav: "bar" 2366 | }; 2367 | 2368 | //////////////////////////////////////////////////////////////////////////////// 2369 | // Preparación de la acción 2370 | //////////////////////////////////////////////////////////////////////////////// 2371 | const acciones = function() { 2372 | // ... 2373 | }; 2374 | ``` 2375 | 2376 | **👨‍🏫 Bien:** 2377 | 2378 | ```javascript 2379 | $scope.modelo = { 2380 | menu: "foo", 2381 | nav: "bar" 2382 | }; 2383 | 2384 | const acciones = function() { 2385 | // ... 2386 | }; 2387 | ``` 2388 | 2389 | **[⬆ Volver arriba](#contenido)** 2390 | 2391 | ## Traducciones 2392 | 2393 | También esta disponible en otros idiomas 2394 | 2395 | - ![br](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Brazil.png) **Brazilian Portuguese**: [fesnt/clean-code-javascript](https://github.com/fesnt/clean-code-javascript) 2396 | - ![es](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Uruguay.png) **Spanish (Uruguay)**: [andersontr15/clean-code-javascript](https://github.com/andersontr15/clean-code-javascript-es) 2397 | - ![es](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Spain.png) **Spanish (Spain)**: [tureey/clean-code-javascript](https://github.com/tureey/clean-code-javascript) 2398 | - ![cn](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/China.png) **Chinese**: 2399 | - [alivebao/clean-code-js](https://github.com/alivebao/clean-code-js) 2400 | - [beginor/clean-code-javascript](https://github.com/beginor/clean-code-javascript) 2401 | - ![de](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Germany.png) **German**: [marcbruederlin/clean-code-javascript](https://github.com/marcbruederlin/clean-code-javascript) 2402 | - ![kr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/South-Korea.png) **Korean**: [qkraudghgh/clean-code-javascript-ko](https://github.com/qkraudghgh/clean-code-javascript-ko) 2403 | - ![pl](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Poland.png) **Polish**: [greg-dev/clean-code-javascript-pl](https://github.com/greg-dev/clean-code-javascript-pl) 2404 | - ![ru](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Russia.png) **Russian**: 2405 | - [BoryaMogila/clean-code-javascript-ru/](https://github.com/BoryaMogila/clean-code-javascript-ru/) 2406 | - [maksugr/clean-code-javascript](https://github.com/maksugr/clean-code-javascript) 2407 | - ![vi](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Vietnam.png) **Vietnamese**: [hienvd/clean-code-javascript/](https://github.com/hienvd/clean-code-javascript/) 2408 | - ![ja](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Japan.png) **Japanese**: [mitsuruog/clean-code-javascript/](https://github.com/mitsuruog/clean-code-javascript/) 2409 | - ![id](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Indonesia.png) **Indonesia**: 2410 | [andirkh/clean-code-javascript/](https://github.com/andirkh/clean-code-javascript/) 2411 | - ![it](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Italy.png) **Italian**: 2412 | [frappacchio/clean-code-javascript/](https://github.com/frappacchio/clean-code-javascript/) 2413 | 2414 | **[⬆ Volver arriba](#contenido)** 2415 | --------------------------------------------------------------------------------