├── .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 | # clean-code-javascript 2 | 3 | ## Tabla de Contenidos 4 | 1. [Introducción](#introduccion) 5 | 2. [Variables](#variables) 6 | 3. [Funciones](#funciones) 7 | 4. [Objetos y Estructura de Datos](#objetos-y-estructura-de-datos) 8 | 5. [Clases](#clases) 9 | 6. [Pruebas](#pruebas) 10 | 7. [Concurrencia](#concurrencia) 11 | 8. [Error Handling](#manejo-de-errores) 12 | 9. [Formateo](#formateo) 13 | 10. [Comentarios](#comentarios) 14 | 15 | ## Introducción 16 | ![Imagen humorística de la estimación de la calidad del software como un recuento de cuantos WTFs al leer el código](http://www.osnews.com/images/comics/wtfm.jpg) 17 | 18 | Principios de ingeniería, del libro de Robert C. Martin [*Clean Code*](https://www.amazon.com.mx/C%C3%B3digo-limpio-Clean-code-Craftsmanship/dp/8441532109), 19 | adaptado para JavaScript . Esto no es una guía de estilo. Es una guía para producir software legible, reutilizable y facil de refactorizar en JavaScript. 20 | 21 | No todos los principios deben seguirse estrictamente, y aún menos tomarlos como principios universalmente acordados. Estas son directrices y nada más, pero han sido 22 | Codificados durante muchos años de experiencia colectiva por los autores de * Código Limpio *. 23 | 24 | Nuestro oficio de ingeniería de software tien un poco más de 50 años de edad, y todavía estamos aprendiendo mucho. Cuando la arquitectura de software sea tan antigua como la propia arquitectura, tal vez entonces tendremos reglas más difíciles de seguir. Por ahora, deje que estas directrices sirvan como piedra angular para evaluar la calidad del código JavaScript que usted y su equipo producen. 25 | 26 | Una cosa más: saber esto no lo hará de inmediato un mejor desarrollador de software, y trabajar con ellos durante muchos años no significa que no cometa errores. Cada pedazo de código comienza como un primer bosquejo, como la arcilla mojada formandose en su forma final. Finalmente, tallamos las imperfecciones cuando las revisamos con nuestros compañeros. No te hagas daño por los primeros borradores que necesitan mejoras. ¡Dale una paliza al código en su lugar! 27 | 28 | ## **Variables** 29 | ### Utilizar nombres de variables significativos y pronunciables 30 | 31 | **Mal:** 32 | ```javascript 33 | const yyyymmdstr = moment().format('YYYY/MM/DD'); 34 | ``` 35 | 36 | **Bien**: 37 | ```javascript 38 | const yearMonthDay = moment().format('YYYY/MM/DD'); 39 | ``` 40 | **[⬆ volver arriba](#tabla-de-contenidos)** 41 | 42 | ### Usa el mismo vocabulario para el mismo tipo de variable 43 | 44 | **Mal:** 45 | ```javascript 46 | getUserInfo(); 47 | getClientData(); 48 | getCustomerRecord(); 49 | ``` 50 | 51 | **Bien**: 52 | ```javascript 53 | getUser(); 54 | ``` 55 | **[⬆ volver arriba](#tabla-de-contenidos)** 56 | 57 | ### Utiliza nombres se puedan buscar 58 | Leeremos más código de lo que escribiremos. Es importante que el código que escribimos sea legible y pueda ser consultado. Al no nombrar variables que terminen siendo significativas para entender nuestro programa, lastimamos a nuestros lectores. 59 | Haga que sus nombres se puedan buscar. Herramientas como [buddy.js](https://github.com/danielstjules/buddy.js) y [ESLint](https://github.com/eslint/eslint/blob/660e0918933e6e7fede26bc675a0763a6b357c94/docs/rules/no-magic-numbers.md) 60 | Pueden ayudar a identificar constantes sin nombre. 61 | 62 | **Mal:** 63 | ```javascript 64 | // Para que diablos es 86400000 ? 65 | setTimeout(() => { 66 | this.blastOff(); 67 | }, 86400000); 68 | 69 | ``` 70 | 71 | **Bien**: 72 | ```javascript 73 | // Declare them as capitalized `const` globals. 74 | const MILLISECONDS_IN_A_DAY = 86400000; 75 | 76 | setTimeout(() => { 77 | this.blastOff(); 78 | }, MILLISECONDS_IN_A_DAY); 79 | 80 | ``` 81 | **[⬆ volver arriba](#tabla-de-contenidos)** 82 | 83 | ### Utilice variables explicativas 84 | **Mal:** 85 | ```javascript 86 | const address = 'One Infinite Loop, Cupertino 95014'; 87 | const cityStateRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 88 | saveCityState(address.match(cityStateRegex)[1], address.match(cityStateRegex)[2]); 89 | ``` 90 | 91 | **Bien**: 92 | ```javascript 93 | const address = 'One Infinite Loop, Cupertino 95014'; 94 | const cityStateRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 95 | const [, city, state] = address.match(cityStateRegex); 96 | saveCityState(city, state); 97 | ``` 98 | **[⬆ volver arriba](#tabla-de-contenidos)** 99 | 100 | ### Evitar Mapeo Mental 101 | Explícito es mejor que implícito. 102 | 103 | **Mal:** 104 | ```javascript 105 | const locations = ['Austin', 'New York', 'San Francisco']; 106 | locations.forEach((l) => { 107 | doStuff(); 108 | doSomeOtherStuff(); 109 | // ... 110 | // ... 111 | // ... 112 | // Wait, what is `l` for again? 113 | dispatch(l); 114 | }); 115 | ``` 116 | 117 | **Bien**: 118 | ```javascript 119 | const locations = ['Austin', 'New York', 'San Francisco']; 120 | locations.forEach((location) => { 121 | doStuff(); 122 | doSomeOtherStuff(); 123 | // ... 124 | // ... 125 | // ... 126 | dispatch(location); 127 | }); 128 | ``` 129 | **[⬆ volver arriba](#tabla-de-contenidos)** 130 | 131 | ### No agregue contexto innecesario 132 | Si su nombre de clase / objeto le dice algo, no lo repita en su nombre de variable. 133 | 134 | **Mal:** 135 | ```javascript 136 | const Car = { 137 | carMake: 'Honda', 138 | carModel: 'Accord', 139 | carColor: 'Blue' 140 | }; 141 | 142 | function paintCar(car) { 143 | car.carColor = 'Red'; 144 | } 145 | ``` 146 | 147 | **Bien**: 148 | ```javascript 149 | const Car = { 150 | make: 'Honda', 151 | model: 'Accord', 152 | color: 'Blue' 153 | }; 154 | 155 | function paintCar(car) { 156 | car.color = 'Red'; 157 | } 158 | ``` 159 | **[⬆ volver arriba](#tabla-de-contenidos)** 160 | 161 | ### Usar argumentos predeterminados en lugar de enredaderes o condicionales 162 | 163 | **Mal:** 164 | ```javascript 165 | function createMicrobrewery(name) { 166 | const breweryName = name || 'Hipster Brew Co.'; 167 | // ... 168 | } 169 | 170 | ``` 171 | 172 | **Bien**: 173 | ```javascript 174 | function createMicrobrewery(breweryName = 'Hipster Brew Co.') { 175 | // ... 176 | } 177 | 178 | ``` 179 | **[⬆ volver arriba](#tabla-de-contenidos)** 180 | 181 | ## **Funciones** 182 | ### Argumentos de la función (2 o menos idealmente) 183 | Limitar la cantidad de parámetros de la función es increíblemente importante porque facilita la comprobación de su función. Tener más de tres argumentos conduce a una explosión combinatoria donde hay que probar toneladas de casos diferentes con cada argumento separado. 184 | 185 | Cero Argumentos es el caso ideal. uno o dos argumentos esta bien, Y tres deben ser evitados. Cualquier cosa más que eso debería ser consolidado. Por lo general, si tiene más de dos argumentos entonces tu función está tratando de hacer demasiado. En los casos en que no lo es, la mayoría de las veces un objeto de nivel superior será suficiente como argumento. 186 | 187 | Dado que JavaScript nos permite hacer objetos sobre la marcha, sin muchas clases 188 | boilerplate, puedes usar un objeto si te encuentras necesitando muchos argumentos. 189 | 190 | **Mal:** 191 | ```javascript 192 | function createMenu(title, body, buttonText, cancellable) { 193 | // ... 194 | } 195 | ``` 196 | 197 | **Bien**: 198 | ```javascript 199 | const menuConfig = { 200 | title: 'Foo', 201 | body: 'Bar', 202 | buttonText: 'Baz', 203 | cancellable: true 204 | }; 205 | 206 | function createMenu(config) { 207 | // ... 208 | } 209 | 210 | ``` 211 | **[⬆ volver arriba](#tabla-de-contenidos)** 212 | 213 | 214 | ### Las funciones deben hacer solo una cosa 215 | Esta es con mucho la regla más importante en la ingeniería de software. Cuando las funciones hacen mas de una cosa, son dificiles de hacer, testear, y entender. 216 | Cuando se puede aislar una función con una sola acción, Pueden ser refactorizados fácilmente y su código leerá mucho más limpio. Si no toma nada más de esta guía mas que esto, usted estará delante de muchos desarrolladores. 217 | 218 | **Mal:** 219 | ```javascript 220 | function emailClients(clients) { 221 | clients.forEach((client) => { 222 | const clientRecord = database.lookup(client); 223 | if (clientRecord.isActive()) { 224 | email(client); 225 | } 226 | }); 227 | } 228 | ``` 229 | 230 | **Bien**: 231 | ```javascript 232 | function emailClients(clients) { 233 | clients 234 | .filter(isClientActive) 235 | .forEach(email); 236 | } 237 | 238 | function isClientActive(client) { 239 | const clientRecord = database.lookup(client); 240 | return clientRecord.isActive(); 241 | } 242 | ``` 243 | **[⬆ volver arriba](#tabla-de-contenidos)** 244 | 245 | ### Los nombres de las funciones deben decir lo que hacen 246 | 247 | **Mal:** 248 | ```javascript 249 | function addToDate(date, month) { 250 | // ... 251 | } 252 | 253 | const date = new Date(); 254 | 255 | // It's hard to to tell from the function name what is added 256 | addToDate(date, 1); 257 | ``` 258 | 259 | **Bien**: 260 | ```javascript 261 | function addMonthToDate(month, date) { 262 | // ... 263 | } 264 | 265 | const date = new Date(); 266 | addMonthToDate(1, date); 267 | ``` 268 | **[⬆ volver arriba](#tabla-de-contenidos)** 269 | 270 | ### Las funciones deben ser sólo un nivel de abstracción 271 | Cuando tienes mas de un nivel de abstración tu funcion esta haciendo demasido 272 | Cuando tienes más de un nivel de abstracción, tu función normalmente está haciendo demasiado. La división de funciones conduce a la reutilización y la prueba más fácil. 273 | 274 | **Mal:** 275 | ```javascript 276 | function parseBetterJSAlternative(code) { 277 | const REGEXES = [ 278 | // ... 279 | ]; 280 | 281 | const statements = code.split(' '); 282 | const tokens = []; 283 | REGEXES.forEach((REGEX) => { 284 | statements.forEach((statement) => { 285 | // ... 286 | }); 287 | }); 288 | 289 | const ast = []; 290 | tokens.forEach((token) => { 291 | // lex... 292 | }); 293 | 294 | ast.forEach((node) => { 295 | // parse... 296 | }); 297 | } 298 | ``` 299 | 300 | **Bien**: 301 | ```javascript 302 | function tokenize(code) { 303 | const REGEXES = [ 304 | // ... 305 | ]; 306 | 307 | const statements = code.split(' '); 308 | const tokens = []; 309 | REGEXES.forEach((REGEX) => { 310 | statements.forEach((statement) => { 311 | tokens.push( /* ... */ ); 312 | }); 313 | }); 314 | 315 | return tokens; 316 | } 317 | 318 | function lexer(tokens) { 319 | const ast = []; 320 | tokens.forEach((token) => { 321 | ast.push( /* ... */ ); 322 | }); 323 | 324 | return ast; 325 | } 326 | 327 | function parseBetterJSAlternative(code) { 328 | const tokens = tokenize(code); 329 | const ast = lexer(tokens); 330 | ast.forEach((node) => { 331 | // parse... 332 | }); 333 | } 334 | ``` 335 | **[⬆ volver arriba](#tabla-de-contenidos)** 336 | 337 | ### Elimina código duplicado 338 | Nunca, nunca, bajo ninguna circunstancia, tenga código duplicado. No hay razón para ello y es muy posiblemente el peor pecado que puede cometer como un desarrollador profesional. El código duplicado significa que hay más de un lugar para alterar algo si necesita cambiar alguna lógica. JavaScript no fuertemente tipado, por lo que hace tener funciones genéricas bastante fácil. ¡Aprovéchate de eso! Herramientas como [jsinspect](https://github.com/danielstjules/jsinspect) Puede ayudarle a encontrar código duplicado elegible para la refactorización. 339 | 340 | **Mal:** 341 | ```javascript 342 | function showDeveloperList(developers) { 343 | developers.forEach((developer) => { 344 | const expectedSalary = developer.calculateExpectedSalary(); 345 | const experience = developer.getExperience(); 346 | const githubLink = developer.getGithubLink(); 347 | const data = { 348 | expectedSalary, 349 | experience, 350 | githubLink 351 | }; 352 | 353 | render(data); 354 | }); 355 | } 356 | 357 | function showManagerList(managers) { 358 | managers.forEach((manager) => { 359 | const expectedSalary = manager.calculateExpectedSalary(); 360 | const experience = manager.getExperience(); 361 | const portfolio = manager.getMBAProjects(); 362 | const data = { 363 | expectedSalary, 364 | experience, 365 | portfolio 366 | }; 367 | 368 | render(data); 369 | }); 370 | } 371 | ``` 372 | 373 | **Bien**: 374 | ```javascript 375 | function showList(employees) { 376 | employees.forEach((employee) => { 377 | const expectedSalary = employee.calculateExpectedSalary(); 378 | const experience = employee.getExperience(); 379 | 380 | let portfolio = employee.getGithubLink(); 381 | 382 | if (employee.type === 'manager') { 383 | portfolio = employee.getMBAProjects(); 384 | } 385 | 386 | const data = { 387 | expectedSalary, 388 | experience, 389 | portfolio 390 | }; 391 | 392 | render(data); 393 | }); 394 | } 395 | ``` 396 | **[⬆ volver arriba](#tabla-de-contenidos)** 397 | 398 | ### Establecer objetos predeterminados con Object.assign 399 | 400 | **Mal:** 401 | ```javascript 402 | const menuConfig = { 403 | title: null, 404 | body: 'Bar', 405 | buttonText: null, 406 | cancellable: true 407 | }; 408 | 409 | function createMenu(config) { 410 | config.title = config.title || 'Foo'; 411 | config.body = config.body || 'Bar'; 412 | config.buttonText = config.buttonText || 'Baz'; 413 | config.cancellable = config.cancellable === undefined ? config.cancellable : true; 414 | } 415 | 416 | createMenu(menuConfig); 417 | ``` 418 | 419 | **Bien**: 420 | ```javascript 421 | const menuConfig = { 422 | title: 'Order', 423 | // User did not include 'body' key 424 | buttonText: 'Send', 425 | cancellable: true 426 | }; 427 | 428 | function createMenu(config) { 429 | config = Object.assign({ 430 | title: 'Foo', 431 | body: 'Bar', 432 | buttonText: 'Baz', 433 | cancellable: true 434 | }, config); 435 | 436 | // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} 437 | // ... 438 | } 439 | 440 | createMenu(menuConfig); 441 | ``` 442 | **[⬆ volver arriba](#tabla-de-contenidos)** 443 | 444 | 445 | ### No utilice banderas como parámetros de función 446 | Las banderas indican a su usuario que esta función hace más de una cosa.Las funciones deben hacer una cosa. Divida sus funciones si están siguiendo diferentes rutas de código basadas en un booleano. 447 | 448 | **Mal:** 449 | ```javascript 450 | function createFile(name, temp) { 451 | if (temp) { 452 | fs.create(`./temp/${name}`); 453 | } else { 454 | fs.create(name); 455 | } 456 | } 457 | ``` 458 | 459 | **Bien**: 460 | ```javascript 461 | function createFile(name) { 462 | fs.create(name); 463 | } 464 | 465 | function createTempFile(name) { 466 | createFile(`./temp/${name}`); 467 | } 468 | ``` 469 | **[⬆ volver arriba](#tabla-de-contenidos)** 470 | 471 | ### Evite los efectos secundarios 472 | A function produces a side effect if it does anything other than take a value in 473 | and return another value or values. A side effect could be writing to a file, 474 | modifying some global variable, or accidentally wiring all your money to a 475 | stranger. 476 | 477 | Now, you do need to have side effects in a program on occasion. Like the previous 478 | example, you might need to write to a file. What you want to do is to 479 | centralize where you are doing this. Don't have several functions and classes 480 | that write to a particular file. Have one service that does it. One and only one. 481 | 482 | The main point is to avoid common pitfalls like sharing state between objects 483 | without any structure, using mutable data types that can be written to by anything, 484 | and not centralizing where your side effects occur. If you can do this, you will 485 | be happier than the vast majority of other programmers. 486 | 487 | **Mal:** 488 | ```javascript 489 | // Global variable referenced by following function. 490 | // If we had another function that used this name, now it'd be an array and it could break it. 491 | let name = 'Ryan McDermott'; 492 | 493 | function splitIntoFirstAndLastName() { 494 | name = name.split(' '); 495 | } 496 | 497 | splitIntoFirstAndLastName(); 498 | 499 | console.log(name); // ['Ryan', 'McDermott']; 500 | ``` 501 | 502 | **Bien**: 503 | ```javascript 504 | function splitIntoFirstAndLastName(name) { 505 | return name.split(' '); 506 | } 507 | 508 | const name = 'Ryan McDermott'; 509 | const newName = splitIntoFirstAndLastName(name); 510 | 511 | console.log(name); // 'Ryan McDermott'; 512 | console.log(newName); // ['Ryan', 'McDermott']; 513 | ``` 514 | **[⬆ volver arriba](#tabla-de-contenidos)** 515 | 516 | ### No escribir en funciones globales 517 | Contaminacion global es una mala practica en JavaScript porque podría chocar con otra biblioteca y el usuario de tu API no se enteraría hasta obtener un excepción en produción. Pensemos en un ejemplo: y si deseas extender los metodos de los Arreglos de JavaScript para tener un metodo `diff` Que podría mostrar la diferencia entre dos arreglos? Podrías escribir tu nueva función en el `Array.prototype`, pero podría chocar con otra libreria que trata de hacer lo mismo. Qué pasa si esa otra biblioteca sólo estaba usando `diff` para encontrar la diferencia entre el primer y el último elemento de un array? Esta es la razón por la cual sería mucho mejor usar las clases de ES2015/ES6 y simplemente ampliar el 'Array` global. 518 | 519 | **Mal:** 520 | ```javascript 521 | Array.prototype.diff = function diff(comparisonArray) { 522 | const values = []; 523 | const hash = {}; 524 | 525 | for (const i of comparisonArray) { 526 | hash[i] = true; 527 | } 528 | 529 | for (const i of this) { 530 | if (!hash[i]) { 531 | values.push(i); 532 | } 533 | } 534 | 535 | return values; 536 | }; 537 | ``` 538 | 539 | **Bien:** 540 | ```javascript 541 | class SuperArray extends Array { 542 | diff(comparisonArray) { 543 | return this.filter(elem => !comparisonArray.includes(elem)); 544 | } 545 | } 546 | ``` 547 | **[⬆ volver arriba](#tabla-de-contenidos)** 548 | 549 | ### Favorecer la programación funcional sobre la programación imperativa 550 | JavaScript no es un lenguaje funcional en la forma en que Haskell lo es, pero tiene un sabor funcional similar. Los lenguajes funcionales son más limpios y fáciles de probar. 551 | Favorece este estilo de programación cuando puedas. 552 | 553 | **Mal:** 554 | ```javascript 555 | const programmerOutput = [ 556 | { 557 | name: 'Uncle Bobby', 558 | linesOfCode: 500 559 | }, { 560 | name: 'Suzie Q', 561 | linesOfCode: 1500 562 | }, { 563 | name: 'Jimmy Gosling', 564 | linesOfCode: 150 565 | }, { 566 | name: 'Gracie Hopper', 567 | linesOfCode: 1000 568 | } 569 | ]; 570 | 571 | let totalOutput = 0; 572 | 573 | for (let i = 0; i < programmerOutput.length; i++) { 574 | totalOutput += programmerOutput[i].linesOfCode; 575 | } 576 | ``` 577 | 578 | **Bien**: 579 | ```javascript 580 | const programmerOutput = [ 581 | { 582 | name: 'Uncle Bobby', 583 | linesOfCode: 500 584 | }, { 585 | name: 'Suzie Q', 586 | linesOfCode: 1500 587 | }, { 588 | name: 'Jimmy Gosling', 589 | linesOfCode: 150 590 | }, { 591 | name: 'Gracie Hopper', 592 | linesOfCode: 1000 593 | } 594 | ]; 595 | 596 | const totalOutput = programmerOutput 597 | .map((programmer) => programmer.linesOfCode) 598 | .reduce((acc, linesOfCode) => acc + linesOfCode, 0); 599 | ``` 600 | **[⬆ volver arriba](#tabla-de-contenidos)** 601 | 602 | ### Encapsulate conditionals 603 | 604 | **Mal:** 605 | ```javascript 606 | if (fsm.state === 'fetching' && isEmpty(listNode)) { 607 | // ... 608 | } 609 | ``` 610 | 611 | **Bien**: 612 | ```javascript 613 | function shouldShowSpinner(fsm, listNode) { 614 | return fsm.state === 'fetching' && isEmpty(listNode); 615 | } 616 | 617 | if (shouldShowSpinner(fsmInstance, listNodeInstance)) { 618 | // ... 619 | } 620 | ``` 621 | **[⬆ volver arriba](#tabla-de-contenidos)** 622 | 623 | ### Avoid negative conditionals 624 | 625 | **Mal:** 626 | ```javascript 627 | function isDOMNodeNotPresent(node) { 628 | // ... 629 | } 630 | 631 | if (!isDOMNodeNotPresent(node)) { 632 | // ... 633 | } 634 | ``` 635 | 636 | **Bien**: 637 | ```javascript 638 | function isDOMNodePresent(node) { 639 | // ... 640 | } 641 | 642 | if (isDOMNodePresent(node)) { 643 | // ... 644 | } 645 | ``` 646 | **[⬆ volver arriba](#tabla-de-contenidos)** 647 | 648 | ### Evitar condicionales 649 | Esto parece una tarea imposible. Al escuchar esto, la mayoría de la gente dice, 650 | "Cómo se supone que debo hacer algo sin declarar un `if`?" La respuesta es que se puede utilizar el polimorfismo para lograr la misma tarea en muchos casos. La segunda pregunta usualmente es, "Bueno, eso es genial, pero ¿por qué querría hacer eso?" La respuesta es un concepto de código limpio que aprendimos anterirmente : una función sólo debe hacer una cosa. Cuando tienes clases y funciones que tienen declaraciones `if`, le está diciendo a su usuario que su función hace más de una cosa. Recuerda, Solo haz una cosa. 651 | 652 | **Mal:** 653 | ```javascript 654 | class Airplane { 655 | // ... 656 | getCruisingAltitude() { 657 | switch (this.type) { 658 | case '777': 659 | return this.getMaxAltitude() - this.getPassengerCount(); 660 | case 'Air Force One': 661 | return this.getMaxAltitude(); 662 | case 'Cessna': 663 | return this.getMaxAltitude() - this.getFuelExpenditure(); 664 | } 665 | } 666 | } 667 | ``` 668 | 669 | **Bien**: 670 | ```javascript 671 | class Airplane { 672 | // ... 673 | } 674 | 675 | class Boeing777 extends Airplane { 676 | // ... 677 | getCruisingAltitude() { 678 | return this.getMaxAltitude() - this.getPassengerCount(); 679 | } 680 | } 681 | 682 | class AirForceOne extends Airplane { 683 | // ... 684 | getCruisingAltitude() { 685 | return this.getMaxAltitude(); 686 | } 687 | } 688 | 689 | class Cessna extends Airplane { 690 | // ... 691 | getCruisingAltitude() { 692 | return this.getMaxAltitude() - this.getFuelExpenditure(); 693 | } 694 | } 695 | ``` 696 | **[⬆ volver arriba](#tabla-de-contenidos)** 697 | 698 | ### Evitar la comprobación de tipos (parte 1) 699 | JavaScript no es tipado, lo que significa que sus funciones pueden tomar cualquier tipo de argumento. 700 | A veces eres mordido por esta libertad y se vuelve tentador hacer comprobaciones de tipo en las funciones. Hay muchas maneras de evitar tener que hacer esto. 701 | Lo primero a considerar son las APIs consistentes. 702 | 703 | **Mal:** 704 | ```javascript 705 | function travelToTexas(vehicle) { 706 | if (vehicle instanceof Bicycle) { 707 | vehicle.peddle(this.currentLocation, new Location('texas')); 708 | } else if (vehicle instanceof Car) { 709 | vehicle.drive(this.currentLocation, new Location('texas')); 710 | } 711 | } 712 | ``` 713 | 714 | **Bien**: 715 | ```javascript 716 | function travelToTexas(vehicle) { 717 | vehicle.move(this.currentLocation, new Location('texas')); 718 | } 719 | ``` 720 | **[⬆ volver arriba](#tabla-de-contenidos)** 721 | 722 | ### Evitar la comprobación de tipos (parte 2) 723 | Si está trabajando con valores primitivos básicos como cadenas, enteros, y arreglos, 724 | y no puedes usar polimorfismo pero todavía siente la necesidad de comprobar el tipo, 725 | debe considerar el uso de TypeScript. Es una excelente alternativa al JavaScript normal, ya que proporciona la escritura estática por encima de la sintaxis JavaScript estándar. El problema con la comprobación de tipo manual de JavaScript normal es que hacerlo bien requiere tanta verborrea extra que el falso "tipado-seguro" que se obtiene no compensa la legibilidad perdida. Mantenga su JavaScript limpio, escribe buenos tests, Y ten buenas revisiones de código. De lo contrario, hacer todo eso, pero con TypeScript (Que, como he dicho, es una gran alternativa!). 726 | 727 | **Mal:** 728 | ```javascript 729 | function combine(val1, val2) { 730 | if (typeof val1 === 'number' && typeof val2 === 'number' || 731 | typeof val1 === 'string' && typeof val2 === 'string') { 732 | return val1 + val2; 733 | } 734 | 735 | throw new Error('Must be of type String or Number'); 736 | } 737 | ``` 738 | 739 | **Bien**: 740 | ```javascript 741 | function combine(val1, val2) { 742 | return val1 + val2; 743 | } 744 | ``` 745 | **[⬆ volver arriba](#tabla-de-contenidos)** 746 | 747 | ### No sobre-optimizar 748 | Los navegadores modernos hacen un montón de optimización en tiempo de ejecución. Muchas veces, si usted está optimizando entonces usted está perdiendo su tiempo. [Hay buenos recursos](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) 749 | para ver donde falta optimización. Dirigete a esto mientras tanto, hasta que sean reparados si es que pueden ser reparados. 750 | 751 | **Mal:** 752 | ```javascript 753 | 754 | // On old browsers, each iteration with uncached `list.length` would be costly 755 | // because of `list.length` recomputation. In modern browsers, this is optimized. 756 | for (let i = 0, len = list.length; i < len; i++) { 757 | // ... 758 | } 759 | ``` 760 | 761 | **Bien**: 762 | ```javascript 763 | for (let i = 0; i < list.length; i++) { 764 | // ... 765 | } 766 | ``` 767 | **[⬆ volver arriba](#tabla-de-contenidos)** 768 | 769 | ### Eliminar código muerto 770 | El código muerto es tan malo como el código duplicado. No hay razón para mantenerlo en su código base. Si no se está llamando, desaste de eso! Seguirá estando seguro en tu historial de versiones si todavía lo necesitas. 771 | 772 | **Mal:** 773 | ```javascript 774 | function oldRequestModule(url) { 775 | // ... 776 | } 777 | 778 | function newRequestModule(url) { 779 | // ... 780 | } 781 | 782 | const req = newRequestModule; 783 | inventoryTracker('apples', req, 'www.inventory-awesome.io'); 784 | 785 | ``` 786 | 787 | **Bien**: 788 | ```javascript 789 | function newRequestModule(url) { 790 | // ... 791 | } 792 | 793 | const req = newRequestModule; 794 | inventoryTracker('apples', req, 'www.inventory-awesome.io'); 795 | ``` 796 | **[⬆ volver arriba](#tabla-de-contenidos)** 797 | 798 | ## **Objetos y Estructura de Datos** 799 | ### Utiliza getters y setters 800 | JavaScript no tiene interfaces o tipos por lo que es muy difícil hacer cumplir este patrón, porque no tenemos palabras claves como `public` y` private`. El uso de getters y setters para acceder a datos sobre objetos es mucho mejor que simplemente buscar una propiedad en un objeto. "Por que?" podrías preguntar. Bueno, he aquí una lista desorganizada de razones del por que: 801 | 802 | * Cuando desea hacer más cosas que obtener una propiedad de objeto, no tiene que buscar y cambiar todos los complementos en tu código base. 803 | * Hace la adición de validación simple cuando se hace un `set`. 804 | * Encapsula la representación interna. 805 | * Fácil de agregar registro y manejo de errores al obtener y configurar. 806 | * Al heredar esta clase, puede anular la funcionalidad predeterminada. 807 | * Puedes cargar lentamente las propiedades de tu objeto, digamos conseguirlo desde un servidor. 808 | 809 | 810 | **Mal:** 811 | ```javascript 812 | class BankAccount { 813 | constructor() { 814 | this.balance = 1000; 815 | } 816 | } 817 | 818 | const bankAccount = new BankAccount(); 819 | 820 | // Buy shoes... 821 | bankAccount.balance -= 100; 822 | ``` 823 | 824 | **Bien**: 825 | ```javascript 826 | class BankAccount { 827 | constructor(balance = 1000) { 828 | this._balance = balance; 829 | } 830 | 831 | // It doesn't have to be prefixed with `get` or `set` to be a getter/setter 832 | set balance(amount) { 833 | if (verifyIfAmountCanBeSetted(amount)) { 834 | this._balance = amount; 835 | } 836 | } 837 | 838 | get balance() { 839 | return this._balance; 840 | } 841 | 842 | verifyIfAmountCanBeSetted(val) { 843 | // ... 844 | } 845 | } 846 | 847 | const bankAccount = new BankAccount(); 848 | 849 | // Buy shoes... 850 | bankAccount.balance -= shoesPrice; 851 | 852 | // Get balance 853 | let balance = bankAccount.balance; 854 | 855 | ``` 856 | **[⬆ volver arriba](#tabla-de-contenidos)** 857 | 858 | 859 | ### Make objects have private members 860 | This can be accomplished through closures (for ES5 and below). 861 | 862 | **Mal:** 863 | ```javascript 864 | 865 | const Employee = function(name) { 866 | this.name = name; 867 | }; 868 | 869 | Employee.prototype.getName = function getName() { 870 | return this.name; 871 | }; 872 | 873 | const employee = new Employee('John Doe'); 874 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 875 | delete employee.name; 876 | console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined 877 | ``` 878 | 879 | **Bien**: 880 | ```javascript 881 | const Employee = function (name) { 882 | this.getName = function getName() { 883 | return name; 884 | }; 885 | }; 886 | 887 | const employee = new Employee('John Doe'); 888 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 889 | delete employee.name; 890 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 891 | ``` 892 | **[⬆ volver arriba](#tabla-de-contenidos)** 893 | 894 | 895 | ## **Clases** 896 | ### Principio de Responsabilidad Única (SRP) 897 | Como se indica en Codigo Limpio, "Nunca debe haber más de una razón para que una clase cambie". Es tentador empaquetar una clase con mucha funcionalidad, Como cuando sólo se puede llevar una maleta en su vuelo. El problema con esto es que su clase no será conceptualmente cohesiva y le dará muchas razones para cambiarla. Minimizar la cantidad de veces que necesita cambiar una clase es importante. 898 | Es importante porque si hay demasiada funcionalidad en una clase y se modifica una parte de ella, Puede ser difícil de entender cómo afectará a otros módulos dependientes en tu codigo base. 899 | 900 | **Mal:** 901 | ```javascript 902 | class UserSettings { 903 | constructor(user) { 904 | this.user = user; 905 | } 906 | 907 | changeSettings(settings) { 908 | if (this.verifyCredentials()) { 909 | // ... 910 | } 911 | } 912 | 913 | verifyCredentials() { 914 | // ... 915 | } 916 | } 917 | ``` 918 | 919 | **Bien**: 920 | ```javascript 921 | class UserAuth { 922 | constructor(user) { 923 | this.user = user; 924 | } 925 | 926 | verifyCredentials() { 927 | // ... 928 | } 929 | } 930 | 931 | 932 | class UserSettings { 933 | constructor(user) { 934 | this.user = user; 935 | this.auth = new UserAuth(user); 936 | } 937 | 938 | changeSettings(settings) { 939 | if (this.auth.verifyCredentials()) { 940 | // ... 941 | } 942 | } 943 | } 944 | ``` 945 | **[⬆ volver arriba](#tabla-de-contenidos)** 946 | 947 | ### Principio abierto/cerrado (OCP) 948 | Según Bertrand Meyer, "las entidades de software (classes, modules, functions, 949 | etc.) deben estar abiertos para su extensión, pero cerrados para su modificación." ¿Qué significa eso? Este principio básicamente establece que debe permitir a los usuarios ampliar la funcionalidad de su módulo sin tener que abrir el archivo `.js` de código fuente y manipularlo manualmente. 950 | 951 | **Mal:** 952 | ```javascript 953 | class AjaxRequester { 954 | constructor() { 955 | // What if we wanted another HTTP Method, like DELETE? We would have to 956 | // open this file up and modify this and put it in manually. 957 | this.HTTP_METHODS = ['POST', 'PUT', 'GET']; 958 | } 959 | 960 | get(url) { 961 | // ... 962 | } 963 | 964 | } 965 | ``` 966 | 967 | **Bien**: 968 | ```javascript 969 | class AjaxRequester { 970 | constructor() { 971 | this.HTTP_METHODS = ['POST', 'PUT', 'GET']; 972 | } 973 | 974 | get(url) { 975 | // ... 976 | } 977 | 978 | addHTTPMethod(method) { 979 | this.HTTP_METHODS.push(method); 980 | } 981 | } 982 | ``` 983 | **[⬆ volver arriba](#tabla-de-contenidos)** 984 | 985 | 986 | ### Principio de sustitución de Liskov (LSP) 987 | Este es un término de miedo para un concepto muy simple. Se define formalmente como "Si S es un subtipo de T, entonces los objetos de tipo T pueden ser reemplazados por objetos de tipo S (i.e., Objetos de tipo S pueden sustituir objetos de tipo T) sin alterar ninguna de las propiedades deseables de ese programa (Corrección, tarea realizada, etc.)." Esa es una definición aún más aterradora. 988 | 989 | La mejor explicación para esto es si tienes una clase padre y una clase hijo, entonces la clase base y la clase hijo se pueden usar indistintamente sin obtener resultados incorrectos. Esto todavía puede ser confuso, así que echemos un vistazo al ejemplo clásico de Square-Rectangle. Matemáticamente, un cuadrado es un rectángulo, pero si lo modelas utilizando la relación "es-un" a través de la herencia, rápidamente te metes en problemas. 990 | 991 | **Mal:** 992 | ```javascript 993 | class Rectangle { 994 | constructor() { 995 | this.width = 0; 996 | this.height = 0; 997 | } 998 | 999 | setColor(color) { 1000 | // ... 1001 | } 1002 | 1003 | render(area) { 1004 | // ... 1005 | } 1006 | 1007 | setWidth(width) { 1008 | this.width = width; 1009 | } 1010 | 1011 | setHeight(height) { 1012 | this.height = height; 1013 | } 1014 | 1015 | getArea() { 1016 | return this.width * this.height; 1017 | } 1018 | } 1019 | 1020 | class Square extends Rectangle { 1021 | setWidth(width) { 1022 | this.width = width; 1023 | this.height = width; 1024 | } 1025 | 1026 | setHeight(height) { 1027 | this.width = height; 1028 | this.height = height; 1029 | } 1030 | } 1031 | 1032 | function renderLargeRectangles(rectangles) { 1033 | rectangles.forEach((rectangle) => { 1034 | rectangle.setWidth(4); 1035 | rectangle.setHeight(5); 1036 | const area = rectangle.getArea(); // Mal: Will return 25 for Square. Should be 20. 1037 | rectangle.render(area); 1038 | }); 1039 | } 1040 | 1041 | const rectangles = [new Rectangle(), new Rectangle(), new Square()]; 1042 | renderLargeRectangles(rectangles); 1043 | ``` 1044 | 1045 | **Bien**: 1046 | ```javascript 1047 | class Shape { 1048 | setColor(color) { 1049 | // ... 1050 | } 1051 | 1052 | render(area) { 1053 | // ... 1054 | } 1055 | } 1056 | 1057 | class Rectangle extends Shape { 1058 | constructor() { 1059 | super(); 1060 | this.width = 0; 1061 | this.height = 0; 1062 | } 1063 | 1064 | setWidth(width) { 1065 | this.width = width; 1066 | } 1067 | 1068 | setHeight(height) { 1069 | this.height = height; 1070 | } 1071 | 1072 | getArea() { 1073 | return this.width * this.height; 1074 | } 1075 | } 1076 | 1077 | class Square extends Shape { 1078 | constructor() { 1079 | super(); 1080 | this.length = 0; 1081 | } 1082 | 1083 | setLength(length) { 1084 | this.length = length; 1085 | } 1086 | 1087 | getArea() { 1088 | return this.length * this.length; 1089 | } 1090 | } 1091 | 1092 | function renderLargeShapes(shapes) { 1093 | shapes.forEach((shape) => { 1094 | switch (shape.constructor.name) { 1095 | case 'Square': 1096 | shape.setLength(5); 1097 | break; 1098 | case 'Rectangle': 1099 | shape.setWidth(4); 1100 | shape.setHeight(5); 1101 | } 1102 | 1103 | const area = shape.getArea(); 1104 | shape.render(area); 1105 | }); 1106 | } 1107 | 1108 | const shapes = [new Rectangle(), new Rectangle(), new Square()]; 1109 | renderLargeShapes(shapes); 1110 | ``` 1111 | **[⬆ volver arriba](#tabla-de-contenidos)** 1112 | 1113 | ### Principio de Segregación de Interfaz (ISP) 1114 | JavaScript no tiene interfaces por lo que este principio no se aplica tan estrictamente como otros. Sin embargo, es importante y relevante, incluso con JavaScript que no es tipado. 1115 | 1116 | ISP establece que "los clientes no deben verse obligados a depender de interfaces que no usan." Interfaces son contratos implícitos en JavaScript debido a la tipificación falsa. 1117 | 1118 | Un buen ejemplo que demuestra este principio en JavaScript son las clases que requieren grandes objetos de configuración. No es necesario que los clientes configuren enormes cantidades de opciones, porque la mayoría de las veces no necesitarán todos los ajustes. Hacerlos opcionales ayuda a evitar tener una "interfaz pesada". 1119 | 1120 | **Mal:** 1121 | ```javascript 1122 | class DOMTraverser { 1123 | constructor(settings) { 1124 | this.settings = settings; 1125 | this.setup(); 1126 | } 1127 | 1128 | setup() { 1129 | this.rootNode = this.settings.rootNode; 1130 | this.animationModule.setup(); 1131 | } 1132 | 1133 | traverse() { 1134 | // ... 1135 | } 1136 | } 1137 | 1138 | const $ = new DOMTraverser({ 1139 | rootNode: document.getElementsByTagName('body'), 1140 | animationModule() {} // Most of the time, we won't need to animate when traversing. 1141 | // ... 1142 | }); 1143 | 1144 | ``` 1145 | 1146 | **Bien**: 1147 | ```javascript 1148 | class DOMTraverser { 1149 | constructor(settings) { 1150 | this.settings = settings; 1151 | this.options = settings.options; 1152 | this.setup(); 1153 | } 1154 | 1155 | setup() { 1156 | this.rootNode = this.settings.rootNode; 1157 | this.setupOptions(); 1158 | } 1159 | 1160 | setupOptions() { 1161 | if (this.options.animationModule) { 1162 | // ... 1163 | } 1164 | } 1165 | 1166 | traverse() { 1167 | // ... 1168 | } 1169 | } 1170 | 1171 | const $ = new DOMTraverser({ 1172 | rootNode: document.getElementsByTagName('body'), 1173 | options: { 1174 | animationModule() {} 1175 | } 1176 | }); 1177 | ``` 1178 | **[⬆ volver arriba](#tabla-de-contenidos)** 1179 | 1180 | ### Principio de inversión de dependencias (DIP) 1181 | Este principio establece dos cosas esenciales: 1182 | 1. Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones. 1183 | 2. Las abstracciones no deben depender de detalles. Los detalles deben depender de las abstracciones. 1184 | 1185 | Esto puede ser difícil de entender al principio, pero si has trabajado con Angular.js, 1186 | usted ha visto una puesta en práctica de este principio en la forma de inyección de dependencia (DI). Aunque no son conceptos idénticos, DIP mantiene los módulos de alto nivel de conocer los detalles de sus módulos de bajo nivel y configurarlos. 1187 | Puede lograrse a través de DI. Una gran ventaja de esto es que reduce el acoplamiento entre módulos. El acoplamiento es un patrón de desarrollo muy malo porque hace que su código sea difícil de refactorizar. 1188 | 1189 | Como se ha indicado anteriormente, JavaScript no tiene interfaces por lo que las abstracciones dependientes son contratos implícitos.Es decir, Los métodos y propiedades que un objeto/clase expone a otro objeto/clase.En el ejemplo siguiente, El contrato implícito es que cualquier módulo Request para un `InventoryTracker` tendrá un método` requestItems`. 1190 | 1191 | **Mal:** 1192 | ```javascript 1193 | class InventoryRequester { 1194 | constructor() { 1195 | this.REQ_METHODS = ['HTTP']; 1196 | } 1197 | 1198 | requestItem(item) { 1199 | // ... 1200 | } 1201 | } 1202 | 1203 | class InventoryTracker { 1204 | constructor(items) { 1205 | this.items = items; 1206 | 1207 | // Mal: We have created a dependency on a specific request implementation. 1208 | // We should just have requestItems depend on a request method: `request` 1209 | this.requester = new InventoryRequester(); 1210 | } 1211 | 1212 | requestItems() { 1213 | this.items.forEach((item) => { 1214 | this.requester.requestItem(item); 1215 | }); 1216 | } 1217 | } 1218 | 1219 | const inventoryTracker = new InventoryTracker(['apples', 'bananas']); 1220 | inventoryTracker.requestItems(); 1221 | ``` 1222 | 1223 | **Bien**: 1224 | ```javascript 1225 | class InventoryTracker { 1226 | constructor(items, requester) { 1227 | this.items = items; 1228 | this.requester = requester; 1229 | } 1230 | 1231 | requestItems() { 1232 | this.items.forEach((item) => { 1233 | this.requester.requestItem(item); 1234 | }); 1235 | } 1236 | } 1237 | 1238 | class InventoryRequesterV1 { 1239 | constructor() { 1240 | this.REQ_METHODS = ['HTTP']; 1241 | } 1242 | 1243 | requestItem(item) { 1244 | // ... 1245 | } 1246 | } 1247 | 1248 | class InventoryRequesterV2 { 1249 | constructor() { 1250 | this.REQ_METHODS = ['WS']; 1251 | } 1252 | 1253 | requestItem(item) { 1254 | // ... 1255 | } 1256 | } 1257 | 1258 | // Construyendo externamente nuestras dependencias e inyectándolas, podemos fácilmente 1259 | // Sustituir nuestro módulo de Request por uno nuevo que utilice WebSockets. 1260 | const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2()); 1261 | inventoryTracker.requestItems(); 1262 | ``` 1263 | **[⬆ volver arriba](#tabla-de-contenidos)** 1264 | 1265 | ### Prefeir clases ES2015/ES6 sobre funciones planas de ES5 1266 | Es muy difícil obtener herencia de clases legible, construcción y definicion de metodos para las clasicas clases de ES5. Si necesita herencia (Y tenga en cuenta que tal vez no), entonces prefiera las clases. Sin embargo, prefiera las funciones pequeñas sobre las clases hasta que se encuentre necesitando objetos más grandes y más complejos. 1267 | 1268 | **Mal:** 1269 | ```javascript 1270 | const Animal = function(age) { 1271 | if (!(this instanceof Animal)) { 1272 | throw new Error('Instantiate Animal with `new`'); 1273 | } 1274 | 1275 | this.age = age; 1276 | }; 1277 | 1278 | Animal.prototype.move = function move() {}; 1279 | 1280 | const Mammal = function(age, furColor) { 1281 | if (!(this instanceof Mammal)) { 1282 | throw new Error('Instantiate Mammal with `new`'); 1283 | } 1284 | 1285 | Animal.call(this, age); 1286 | this.furColor = furColor; 1287 | }; 1288 | 1289 | Mammal.prototype = Object.create(Animal.prototype); 1290 | Mammal.prototype.constructor = Mammal; 1291 | Mammal.prototype.liveBirth = function liveBirth() {}; 1292 | 1293 | const Human = function(age, furColor, languageSpoken) { 1294 | if (!(this instanceof Human)) { 1295 | throw new Error('Instantiate Human with `new`'); 1296 | } 1297 | 1298 | Mammal.call(this, age, furColor); 1299 | this.languageSpoken = languageSpoken; 1300 | }; 1301 | 1302 | Human.prototype = Object.create(Mammal.prototype); 1303 | Human.prototype.constructor = Human; 1304 | Human.prototype.speak = function speak() {}; 1305 | ``` 1306 | 1307 | **Bien:** 1308 | ```javascript 1309 | class Animal { 1310 | constructor(age) { 1311 | this.age = age; 1312 | } 1313 | 1314 | move() { /* ... */ } 1315 | } 1316 | 1317 | class Mammal extends Animal { 1318 | constructor(age, furColor) { 1319 | super(age); 1320 | this.furColor = furColor; 1321 | } 1322 | 1323 | liveBirth() { /* ... */ } 1324 | } 1325 | 1326 | class Human extends Mammal { 1327 | constructor(age, furColor, languageSpoken) { 1328 | super(age, furColor); 1329 | this.languageSpoken = languageSpoken; 1330 | } 1331 | 1332 | speak() { /* ... */ } 1333 | } 1334 | ``` 1335 | **[⬆ volver arriba](#tabla-de-contenidos)** 1336 | 1337 | 1338 | ### Utiliza encadenamiento de métodos 1339 | Contra el consejo de Codigo Limpio, este es un lugar donde tendremos que diferir. 1340 | Se ha argumentado que el encadenamiento de métodos es impuro y viola la [Ley de Demeter](https://en.wikipedia.org/wiki/Law_of_Demeter). 1341 | Tal vez es cierto, pero este patrón es muy útil en JavaScript y lo ves en muchas bibliotecas como jQuery y Lodash. It allows your code to be expressive, and less verbose. Por eso, digo, use el encadenamiento de métodos y eche un vistazo a lo limpio que es su código. En las funciones de tu clase, simplemente devuelve `this` al final de cada función, y puedes encadenar métodos de clase adicionales en él. 1342 | 1343 | **Mal:** 1344 | ```javascript 1345 | class Car { 1346 | constructor() { 1347 | this.make = 'Honda'; 1348 | this.model = 'Accord'; 1349 | this.color = 'white'; 1350 | } 1351 | 1352 | setMake(make) { 1353 | this.make = make; 1354 | } 1355 | 1356 | setModel(model) { 1357 | this.model = model; 1358 | } 1359 | 1360 | setColor(color) { 1361 | this.color = color; 1362 | } 1363 | 1364 | save() { 1365 | console.log(this.make, this.model, this.color); 1366 | } 1367 | } 1368 | 1369 | const car = new Car(); 1370 | car.setColor('pink'); 1371 | car.setMake('Ford'); 1372 | car.setModel('F-150'); 1373 | car.save(); 1374 | ``` 1375 | 1376 | **Bien**: 1377 | ```javascript 1378 | class Car { 1379 | constructor() { 1380 | this.make = 'Honda'; 1381 | this.model = 'Accord'; 1382 | this.color = 'white'; 1383 | } 1384 | 1385 | setMake(make) { 1386 | this.make = make; 1387 | // NOTE: Returning this for chaining 1388 | return this; 1389 | } 1390 | 1391 | setModel(model) { 1392 | this.model = model; 1393 | // NOTE: Returning this for chaining 1394 | return this; 1395 | } 1396 | 1397 | setColor(color) { 1398 | this.color = color; 1399 | // NOTE: Returning this for chaining 1400 | return this; 1401 | } 1402 | 1403 | save() { 1404 | console.log(this.make, this.model, this.color); 1405 | // NOTE: Returning this for chaining 1406 | return this; 1407 | } 1408 | } 1409 | 1410 | const car = new Car() 1411 | .setColor('pink') 1412 | .setMake('Ford') 1413 | .setModel('F-150') 1414 | .save(); 1415 | ``` 1416 | **[⬆ volver arriba](#tabla-de-contenidos)** 1417 | 1418 | ### Preferir la composición sobre la herencia 1419 | Como se ha afirmado en [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) Por la pandilla de cuatro, debe preferir la composición sobre la herencia siempre que puedas. Hay muchas buenas razones para usar la herencia y tambien muchas buenas razones para usar la composición. 1420 | El punto principal de esta máxima es que si su mente instintivamente va para la herencia, trate de pensar si la composición podría modelar mejor su problema. En algunos casos puede. 1421 | 1422 | Usted puede ser que se pregunte entonces, "cuándo debo usar la herencia?" Depende de su problema a mano, pero esta es una lista decente de cuando la herencia tiene más sentido que la composición: 1423 | 1424 | 1. Su herencia representa una relación "es-un" y no una relación "tiene-un" 1425 | (Animal->Human vs. User->UserDetails). 1426 | 2. Puede reutilizar código de las clases base (Los humanos pueden moverse como todos los animales). 1427 | 3. Desea realizar cambios globales en clases derivadas cambiando una clase base. (Cambiar el gasto calórico de todos los animales cuando se mueven). 1428 | 1429 | **Mal:** 1430 | ```javascript 1431 | class Employee { 1432 | constructor(name, email) { 1433 | this.name = name; 1434 | this.email = email; 1435 | } 1436 | 1437 | // ... 1438 | } 1439 | 1440 | // Mal because Employees "have" tax data. EmployeeTaxData is not a type of Employee 1441 | class EmployeeTaxData extends Employee { 1442 | constructor(ssn, salary) { 1443 | super(); 1444 | this.ssn = ssn; 1445 | this.salary = salary; 1446 | } 1447 | 1448 | // ... 1449 | } 1450 | ``` 1451 | 1452 | **Bien**: 1453 | ```javascript 1454 | class EmployeeTaxData { 1455 | constructor(ssn, salary) { 1456 | this.ssn = ssn; 1457 | this.salary = salary; 1458 | } 1459 | 1460 | // ... 1461 | } 1462 | 1463 | class Employee { 1464 | constructor(name, email) { 1465 | this.name = name; 1466 | this.email = email; 1467 | } 1468 | 1469 | setTaxData(ssn, salary) { 1470 | this.taxData = new EmployeeTaxData(ssn, salary); 1471 | } 1472 | // ... 1473 | } 1474 | ``` 1475 | **[⬆ volver arriba](#tabla-de-contenidos)** 1476 | 1477 | ## **Pruebas** 1478 | La prueba es más importante que la entrega. Si no tiene pruebas o una cantidad inadecuada, entonces cada vez que entregue código no estará seguro de que no rompió nada. Decidir cual es una cantidad adecuada depende de su equipo, pero con un 100% de cobertura (Todas las declaraciones y ramas) es cómo logra una confianza muy alta y la tranquilidad del desarrollador. Esto significa que además de tener un gran framework de pruebas, Usted también necesita usar una [Buena herramienta de cobertura](http://gotwarlost.github.io/istanbul/). 1479 | 1480 | There's no excuse to not write tests. There's [plenty of Bien JS test frameworks](http://jstherightway.org/#testing-tools), así que encuentre uno que a su equipo le guste. 1481 | Cuando encuentres uno que funcione para tu equipo, A continuación, intente escribir siempre pruebas para cada nueva función/módulo que introduzca. Si su método preferido es Test Driven Development (TDD), eso es genial, Pero el punto principal es asegurarse de que está alcanzando sus objetivos de cobertura antes de lanzar cualquier característica, o refactorizar una existente. 1482 | 1483 | ### Un único concepto por prueba 1484 | 1485 | **Mal:** 1486 | ```javascript 1487 | const assert = require('assert'); 1488 | 1489 | describe('MakeMomentJSGreatAgain', () => { 1490 | it('handles date boundaries', () => { 1491 | let date; 1492 | 1493 | date = new MakeMomentJSGreatAgain('1/1/2015'); 1494 | date.addDays(30); 1495 | date.shouldEqual('1/31/2015'); 1496 | 1497 | date = new MakeMomentJSGreatAgain('2/1/2016'); 1498 | date.addDays(28); 1499 | assert.equal('02/29/2016', date); 1500 | 1501 | date = new MakeMomentJSGreatAgain('2/1/2015'); 1502 | date.addDays(28); 1503 | assert.equal('03/01/2015', date); 1504 | }); 1505 | }); 1506 | ``` 1507 | 1508 | **Bien**: 1509 | ```javascript 1510 | const assert = require('assert'); 1511 | 1512 | describe('MakeMomentJSGreatAgain', () => { 1513 | it('handles 30-day months', () => { 1514 | const date = new MakeMomentJSGreatAgain('1/1/2015'); 1515 | date.addDays(30); 1516 | date.shouldEqual('1/31/2015'); 1517 | }); 1518 | 1519 | it('handles leap year', () => { 1520 | const date = new MakeMomentJSGreatAgain('2/1/2016'); 1521 | date.addDays(28); 1522 | assert.equal('02/29/2016', date); 1523 | }); 1524 | 1525 | it('handles non-leap year', () => { 1526 | const date = new MakeMomentJSGreatAgain('2/1/2015'); 1527 | date.addDays(28); 1528 | assert.equal('03/01/2015', date); 1529 | }); 1530 | }); 1531 | ``` 1532 | **[⬆ volver arriba](#tabla-de-contenidos)** 1533 | 1534 | ## **Concurrencia** 1535 | ### Use promesas, no callbacks 1536 | Los callbacks no son limpios, y causan cantidades excesivas de anidamiento. Con ES2015/ES6, las promesas son un tipo global incorporado. Usalos, usalos! 1537 | 1538 | **Mal:** 1539 | ```javascript 1540 | require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => { 1541 | if (requestErr) { 1542 | console.error(requestErr); 1543 | } else { 1544 | require('fs').writeFile('article.html', response.body, (writeErr) => { 1545 | if (writeErr) { 1546 | console.error(writeErr); 1547 | } else { 1548 | console.log('File written'); 1549 | } 1550 | }); 1551 | } 1552 | }); 1553 | 1554 | ``` 1555 | 1556 | **Bien**: 1557 | ```javascript 1558 | require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') 1559 | .then((response) => { 1560 | return require('fs-promise').writeFile('article.html', response); 1561 | }) 1562 | .then(() => { 1563 | console.log('File written'); 1564 | }) 1565 | .catch((err) => { 1566 | console.error(err); 1567 | }); 1568 | 1569 | ``` 1570 | **[⬆ volver arriba](#tabla-de-contenidos)** 1571 | 1572 | ### Async/Await son incluso más limpias que las Promesas 1573 | Las promesas son una alternativa muy limpia a los callbacks, pero ES2017/ES8 trae async y wait que ofrecen una solución aún más limpia. Todo lo que necesitas es una función que está prefijada en una palabra clave `async`, y entonces puedes escribir tu lógica imperativamente sin una cadena` then` de funciones. Utilice esto si puedes aprovechar las características de ES2017/ES8 hoy! 1574 | 1575 | **Mal:** 1576 | ```javascript 1577 | require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') 1578 | .then((response) => { 1579 | return require('fs-promise').writeFile('article.html', response); 1580 | }) 1581 | .then(() => { 1582 | console.log('File written'); 1583 | }) 1584 | .catch((err) => { 1585 | console.error(err); 1586 | }); 1587 | 1588 | ``` 1589 | 1590 | **Bien**: 1591 | ```javascript 1592 | async function getCleanCodeArticle() { 1593 | try { 1594 | const request = await require('request-promise'); 1595 | const response = await request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin'); 1596 | const fileHandle = await require('fs-promise'); 1597 | 1598 | await fileHandle.writeFile('article.html', response); 1599 | console.log('File written'); 1600 | } catch(err) { 1601 | console.error(err); 1602 | } 1603 | } 1604 | ``` 1605 | **[⬆ volver arriba](#tabla-de-contenidos)** 1606 | 1607 | 1608 | ## **Manejo de errores** 1609 | Los errores arrojados son algo bueno! significan que en tiempo de ejecucion se ha eidentificado que algo salio mal, y no los hace saber deteniendo la ejecucion de la funcion, matando el proceso (en Node), y notificandole en la consola con un error. 1610 | 1611 | ### No ignore los errores capturados 1612 | No hacer nada con un error detectado no le da la capacidad de reparar o reaccionar a dicho error. Registrar el error en la consola (`console.log`) no es mucho mejor ya que muchas veces se puede perder en un mar de cosas impresas a la consola. Si envuelve algún código en una `try/catch` Significa que usted piensa que un error puede ocurrir allí y por lo tanto usted debe tener un plan, o crear un path del código, para cuando ocurra. 1613 | 1614 | **Mal:** 1615 | ```javascript 1616 | try { 1617 | functionThatMightThrow(); 1618 | } catch (error) { 1619 | console.log(error); 1620 | } 1621 | ``` 1622 | 1623 | **Bien:** 1624 | ```javascript 1625 | try { 1626 | functionThatMightThrow(); 1627 | } catch (error) { 1628 | // One option (more noisy than console.log): 1629 | console.error(error); 1630 | // Another option: 1631 | notifyUserOfError(error); 1632 | // Another option: 1633 | reportErrorToService(error); 1634 | // OR do all three! 1635 | } 1636 | ``` 1637 | 1638 | ### Don't ignore rejected promises 1639 | For the same reason you shouldn't ignore caught errors 1640 | from `try/catch`. 1641 | 1642 | **Mal:** 1643 | ```javascript 1644 | getdata() 1645 | .then((data) => { 1646 | functionThatMightThrow(data); 1647 | }) 1648 | .catch((error) => { 1649 | console.log(error); 1650 | }); 1651 | ``` 1652 | 1653 | **Bien:** 1654 | ```javascript 1655 | getdata() 1656 | .then((data) => { 1657 | functionThatMightThrow(data); 1658 | }) 1659 | .catch((error) => { 1660 | // One option (more noisy than console.log): 1661 | console.error(error); 1662 | // Another option: 1663 | notifyUserOfError(error); 1664 | // Another option: 1665 | reportErrorToService(error); 1666 | // OR do all three! 1667 | }); 1668 | ``` 1669 | 1670 | **[⬆ volver arriba](#tabla-de-contenidos)** 1671 | 1672 | 1673 | ## **Formateo** 1674 | El formato es subjetivo. Como muchas reglas aquí, no hay una regla rigida y rápida que debas seguir. El punto principal es no discutir sobre el formato. 1675 | Hay [toneladas de herramientas](http://standardjs.com/rules.html) para automatizar esto. 1676 | ¡Usa uno! Es una pérdida de tiempo y dinero para los ingenieros discutir sobre el formato. 1677 | 1678 | Para las cosas que no caen en el ámbito del formato automático (identación, tabs vs. spaces, dobles vs. comillas simples, etc.) busque aquí alguna orientación. 1679 | 1680 | ### Utilice mayúsculas consistentes 1681 | JavaScript no es tipado, por lo que las mayusculas dice mucho acerca de sus variables, funciones, etc. Estas reglas son subjetivas, Para que su equipo pueda elegir lo que quiera.El punto es, no importa lo que todos elijan, sólo sean consistentes. 1682 | 1683 | **Mal:** 1684 | ```javascript 1685 | const DAYS_IN_WEEK = 7; 1686 | const daysInMonth = 30; 1687 | 1688 | const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; 1689 | const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; 1690 | 1691 | function eraseDatabase() {} 1692 | function restore_database() {} 1693 | 1694 | class animal {} 1695 | class Alpaca {} 1696 | ``` 1697 | 1698 | **Bien**: 1699 | ```javascript 1700 | const DAYS_IN_WEEK = 7; 1701 | const DAYS_IN_MONTH = 30; 1702 | 1703 | const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; 1704 | const artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; 1705 | 1706 | function eraseDatabase() {} 1707 | function restoreDatabase() {} 1708 | 1709 | class Animal {} 1710 | class Alpaca {} 1711 | ``` 1712 | **[⬆ volver arriba](#tabla-de-contenidos)** 1713 | 1714 | 1715 | ### Las declaraciones y las llamadas de la función deben estar cerca 1716 | Si una función llama a otrar, manten esas funciones muy cerca en tu codigo fuente. Idealmente, mantenga la llamada de la funcion justo encima de la declaracion. Tendemos a leer el código de arriba a abajo, como un periódico . Debido a esto, haga que su código se lea de esa manera. 1717 | 1718 | **Mal:** 1719 | ```javascript 1720 | class PerformanceReview { 1721 | constructor(employee) { 1722 | this.employee = employee; 1723 | } 1724 | 1725 | lookupPeers() { 1726 | return db.lookup(this.employee, 'peers'); 1727 | } 1728 | 1729 | lookupMananger() { 1730 | return db.lookup(this.employee, 'manager'); 1731 | } 1732 | 1733 | getPeerReviews() { 1734 | const peers = this.lookupPeers(); 1735 | // ... 1736 | } 1737 | 1738 | perfReview() { 1739 | this.getPeerReviews(); 1740 | this.getManagerReview(); 1741 | this.getSelfReview(); 1742 | } 1743 | 1744 | getManagerReview() { 1745 | const manager = this.lookupManager(); 1746 | } 1747 | 1748 | getSelfReview() { 1749 | // ... 1750 | } 1751 | } 1752 | 1753 | const review = new PerformanceReview(user); 1754 | review.perfReview(); 1755 | ``` 1756 | 1757 | **Bien**: 1758 | ```javascript 1759 | class PerformanceReview { 1760 | constructor(employee) { 1761 | this.employee = employee; 1762 | } 1763 | 1764 | perfReview() { 1765 | this.getPeerReviews(); 1766 | this.getManagerReview(); 1767 | this.getSelfReview(); 1768 | } 1769 | 1770 | getPeerReviews() { 1771 | const peers = this.lookupPeers(); 1772 | // ... 1773 | } 1774 | 1775 | lookupPeers() { 1776 | return db.lookup(this.employee, 'peers'); 1777 | } 1778 | 1779 | getManagerReview() { 1780 | const manager = this.lookupManager(); 1781 | } 1782 | 1783 | lookupMananger() { 1784 | return db.lookup(this.employee, 'manager'); 1785 | } 1786 | 1787 | getSelfReview() { 1788 | // ... 1789 | } 1790 | } 1791 | 1792 | const review = new PerformanceReview(employee); 1793 | review.perfReview(); 1794 | ``` 1795 | 1796 | **[⬆ volver arriba](#tabla-de-contenidos)** 1797 | 1798 | ## **Comentarios** 1799 | ### Solo comenta cosas que tengan logica de negocio muy compleja. 1800 | Los comentarios son una disculpa, no un requerimiento. El buen codigo *en su mayoria* se documenta asi mismo. 1801 | 1802 | **Mal:** 1803 | ```javascript 1804 | function hashIt(data) { 1805 | // The hash 1806 | let hash = 0; 1807 | 1808 | // Length of string 1809 | const length = data.length; 1810 | 1811 | // Loop through every character in data 1812 | for (let i = 0; i < length; i++) { 1813 | // Get character code. 1814 | const char = data.charCodeAt(i); 1815 | // Make the hash 1816 | hash = ((hash << 5) - hash) + char; 1817 | // Convert to 32-bit integer 1818 | hash &= hash; 1819 | } 1820 | } 1821 | ``` 1822 | 1823 | **Bien**: 1824 | ```javascript 1825 | 1826 | function hashIt(data) { 1827 | let hash = 0; 1828 | const length = data.length; 1829 | 1830 | for (let i = 0; i < length; i++) { 1831 | const char = data.charCodeAt(i); 1832 | hash = ((hash << 5) - hash) + char; 1833 | 1834 | // Convert to 32-bit integer 1835 | hash &= hash; 1836 | } 1837 | } 1838 | 1839 | ``` 1840 | **[⬆ volver arriba](#tabla-de-contenidos)** 1841 | 1842 | ### No deje comentado código en su código base 1843 | El control de versiones existe por una razón. Deja el código antiguo en tu historial. 1844 | 1845 | **Mal:** 1846 | ```javascript 1847 | doStuff(); 1848 | // doOtherStuff(); 1849 | // doSomeMoreStuff(); 1850 | // doSoMuchStuff(); 1851 | ``` 1852 | 1853 | **Bien**: 1854 | ```javascript 1855 | doStuff(); 1856 | ``` 1857 | **[⬆ volver arriba](#tabla-de-contenidos)** 1858 | 1859 | ### Elimina comentarios desactualizados 1860 | Recuerda, usa control de versiones! No hay necesidad de código muerto, codigo comentado, 1861 | y especialmente cometarios antiguos. Usa `git log` para obtener el historial! 1862 | 1863 | **Mal:** 1864 | ```javascript 1865 | /** 1866 | * 2016-12-20: Removed monads, didn't understand them (RM) 1867 | * 2016-10-01: Improved using special monads (JP) 1868 | * 2016-02-03: Removed type-checking (LI) 1869 | * 2015-03-14: Added combine with type-checking (JR) 1870 | */ 1871 | function combine(a, b) { 1872 | return a + b; 1873 | } 1874 | ``` 1875 | 1876 | **Bien**: 1877 | ```javascript 1878 | function combine(a, b) { 1879 | return a + b; 1880 | } 1881 | ``` 1882 | **[⬆ volver arriba](#tabla-de-contenidos)** 1883 | 1884 | ### Evitar marcadores posicionales 1885 | Por lo general sólo añaden ruido. Deje que las funciones y los nombres de las variables junto con la indentación y el formato adecuados den la estructura visual a su código. 1886 | 1887 | **Mal:** 1888 | ```javascript 1889 | //////////////////////////////////////////////////////////////////////////////// 1890 | // Scope Model Instantiation 1891 | //////////////////////////////////////////////////////////////////////////////// 1892 | $scope.model = { 1893 | menu: 'foo', 1894 | nav: 'bar' 1895 | }; 1896 | 1897 | //////////////////////////////////////////////////////////////////////////////// 1898 | // Action setup 1899 | //////////////////////////////////////////////////////////////////////////////// 1900 | const actions = function() { 1901 | // ... 1902 | }; 1903 | ``` 1904 | 1905 | **Bien**: 1906 | ```javascript 1907 | $scope.model = { 1908 | menu: 'foo', 1909 | nav: 'bar' 1910 | }; 1911 | 1912 | const actions = function() { 1913 | // ... 1914 | }; 1915 | ``` 1916 | **[⬆ volver arriba](#tabla-de-contenidos)** 1917 | --------------------------------------------------------------------------------