├── .gitattributes ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-documentation=false 2 | *.md linguist-language=JavaScript 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 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 | ## Sadržaj 4 | 5 | 1. [Uvod](#uvod) 6 | 2. [Promenljive](#promenljive) 7 | 3. [Funkcije](#funkcije) 8 | 4. [Objekti i strukture podataka](#objekti-i-strukture-podataka) 9 | 5. [Klase](#klase) 10 | 6. [SOLID](#solid) 11 | 7. [Testiranje](#testiranje) 12 | 8. [Asihronost](#asihronost) 13 | 9. [Rad sa greškama](#rad-sa-greškama) 14 | 10. [Formatiranje](#formatiranje) 15 | 11. [Komentari](#komentari) 16 | 12. [Prevod](#prevod) 17 | 18 | ## Uvod 19 | 20 | ![Humorous image of software quality estimation as a count of how many expletives 21 | you shout when reading code](https://www.osnews.com/images/comics/wtfm.jpg) 22 | 23 | Principi programiranja, iz knjige Roberta C. Martina [_Clean Code_](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882), prilagođeni za JavaScript. Ovo nije style guide. Ovo je vodič za pisanje [čitljivog, ponovno upotrebljivog, i održivog](https://github.com/ryanmcdermott/3rs-of-software-architecture) programa u JavaScript-u. 24 | 25 | Ne mora se svaki pricip koji je ovde opisan poštovati. Ovo su smernice i ništa više, ali smernice koje su autori _Clean Code_ napisali tokom dugogodišnjeg iskustva. 26 | 27 | Zanat programiranja star je više od 50 godina, a mi još uvek mnogo učimo. Kada softverska arhitektura bude stara kao i sama arhitektura, možda ćemo tada imati strožija pravila koja će biti neophodna. Za sada neka ove smernice služe kao kamen temeljac uz pomoć koga ćete oceniti kvalitet JavaScript koda koji pišete. 28 | 29 | Još nešto: poznavanje ovih principa neće vas odmah učiniti boljim programerom, a pridržavanje ovih principa dugi niz godina ne znači da nećete pogrešiti. Svaki komad koda započinje kao grubi nacrt, kao i komad mokre gline koji poprima svoj konačan oblik. Nedostatke ćemo ispraviti kada sa kolegama pregledamo kod. Ne udarajte po sebi zbog prvih nacrta kojima su potrebna poboljšanja. Umesto toga udrite po kodu! :) 30 | 31 | ## **Promenljive** 32 | 33 | ### Koristite imena koja otkrivaju namenu 34 | 35 | **Loše:** 36 | 37 | ```javascript 38 | const yyyymmdstr = moment().format("YYYY/MM/DD"); 39 | ``` 40 | 41 | **Dobro:** 42 | 43 | ```javascript 44 | const currentDate = moment().format("YYYY/MM/DD"); 45 | ``` 46 | 47 | **[⬆ vrati se na početak](#sadržaj)** 48 | 49 | ### Koristite istu metodu za isti tip promenljive 50 | 51 | **Loše:** 52 | 53 | ```javascript 54 | getUserInfo(); 55 | getClientData(); 56 | getCustomerRecord(); 57 | ``` 58 | 59 | **Dobro:** 60 | 61 | ```javascript 62 | getUser(); 63 | ``` 64 | 65 | **[⬆ vrati se na početak](#sadržaj)** 66 | 67 | ### Koristite imena koja se mogu lako pretraživati 68 | 69 | Pročitaćemo više koda nego što ćemo ga ikada napisati. Važno je da kod koji mi pišemo bude čitljiv i da ga je lako pretražiti. Alati kao što su [buddy.js](https://github.com/danielstjules/buddy.js) i [ESLint](https://github.com/eslint/eslint/blob/660e0918933e6e7fede26bc675a0763a6b357c94/docs/rules/no-magic-numbers.md) nam mogu pomoći u identifikovanju neimenovanih konstanti. 70 | 71 | **Loše:** 72 | 73 | ```javascript 74 | // What the heck is 86400000 for? 75 | setTimeout(blastOff, 86400000); 76 | ``` 77 | 78 | **Dobro:** 79 | 80 | ```javascript 81 | // Declare them as capitalized named constants. 82 | const MILLISECONDS_IN_A_DAY = 86_400_000; 83 | 84 | setTimeout(blastOff, MILLISECONDS_IN_A_DAY); 85 | ``` 86 | 87 | **[⬆ vrati se na početak](#sadržaj)** 88 | 89 | ### Koristite imena koja imaju smisleno značenje 90 | 91 | **Loše:** 92 | 93 | ```javascript 94 | const address = "One Infinite Loop, Cupertino 95014"; 95 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 96 | saveCityZipCode( 97 | address.match(cityZipCodeRegex)[1], 98 | address.match(cityZipCodeRegex)[2] 99 | ); 100 | ``` 101 | 102 | **Dobro:** 103 | 104 | ```javascript 105 | const address = "One Infinite Loop, Cupertino 95014"; 106 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 107 | const [_, city, zipCode] = address.match(cityZipCodeRegex) || []; 108 | saveCityZipCode(city, zipCode); 109 | ``` 110 | 111 | **[⬆ vrati se na početak](#sadržaj)** 112 | 113 | ### Izbegavajte mentalno mapiranje 114 | 115 | Eksplicitno je bolje od implicitnog. Čitaoci koda ne bi trebalo mentalno da prevode imena u druga imena koja već znaju. To je problem sa imenima promeljivih sa jednim slovom. 116 | 117 | **Loše:** 118 | 119 | ```javascript 120 | const locations = ["Austin", "New York", "San Francisco"]; 121 | locations.forEach(l => { 122 | doStuff(); 123 | doSomeOtherStuff(); 124 | // ... 125 | // ... 126 | // ... 127 | // Wait, what is `l` for again? 128 | dispatch(l); 129 | }); 130 | ``` 131 | 132 | **Dobro:** 133 | 134 | ```javascript 135 | const locations = ["Austin", "New York", "San Francisco"]; 136 | locations.forEach(location => { 137 | doStuff(); 138 | doSomeOtherStuff(); 139 | // ... 140 | // ... 141 | // ... 142 | dispatch(location); 143 | }); 144 | ``` 145 | 146 | **[⬆ vrati se na početak](#sadržaj)** 147 | 148 | ### Nemojte dodavati nepotrebno značenje 149 | 150 | Ako vam ime klase / objekta nešto govori, nemote to ponavljati u imenama promenljivih. 151 | 152 | **Loše:** 153 | 154 | ```javascript 155 | const Car = { 156 | carMake: "Honda", 157 | carModel: "Accord", 158 | carColor: "Blue" 159 | }; 160 | 161 | function paintCar(car) { 162 | car.carColor = "Red"; 163 | } 164 | ``` 165 | 166 | **Dobro:** 167 | 168 | ```javascript 169 | const Car = { 170 | make: "Honda", 171 | model: "Accord", 172 | color: "Blue" 173 | }; 174 | 175 | function paintCar(car) { 176 | car.color = "Red"; 177 | } 178 | ``` 179 | 180 | **[⬆ vrati se na početak](#sadržaj)** 181 | 182 | ### Koristite podrazumevane vrednosti 183 | 184 | **Loše:** 185 | 186 | ```javascript 187 | function createMicrobrewery(name) { 188 | const breweryName = name || "Hipster Brew Co."; 189 | // ... 190 | } 191 | ``` 192 | 193 | **Dobro:** 194 | 195 | ```javascript 196 | function createMicrobrewery(name = "Hipster Brew Co.") { 197 | // ... 198 | } 199 | ``` 200 | 201 | **[⬆ vrati se na početak](#sadržaj)** 202 | 203 | ## **Funkcije** 204 | 205 | ### Argumenti funkcije 206 | 207 | Ograničavanje broja argumenata za funkciju je od velike važnosti jer olakšava testiranje funkcije. Imati više od tri argumenta dovodi do velikog broja kombinacija pri testiranju u kojem moramo da ponovimo veliki broj različitih slučajeva za svaki pojedinačni argument. 208 | 209 | Idealan broj argumenata za funkciju je nula. Zatim jedan i dva. Tri argumenta treba izbegavati gde god je moguće. Kada se čini da je za funkciju potrebno više od dva ili tri argumenata, onda bi neki od njih trebalo da budu zamotani u objekat. 210 | 211 | Da biste učinili očiglednim koje argumente funkcija očekuje, možete koristiti sintaksu destruktuiranja ES2015/ES6. 212 | 213 | **Loše:** 214 | 215 | ```javascript 216 | function createMenu(title, body, buttonText, cancellable) { 217 | // ... 218 | } 219 | 220 | createMenu("Foo", "Bar", "Baz", true); 221 | 222 | ``` 223 | 224 | **Dobro:** 225 | 226 | ```javascript 227 | function createMenu({ title, body, buttonText, cancellable }) { 228 | // ... 229 | } 230 | 231 | createMenu({ 232 | title: "Foo", 233 | body: "Bar", 234 | buttonText: "Baz", 235 | cancellable: true 236 | }); 237 | ``` 238 | 239 | **[⬆ vrati se na početak](#sadržaj)** 240 | 241 | ### Funkcije treba da rade jednu stvar 242 | 243 | Ovo je ubedljivo najvažnije pravilo u razvoju softvera. Kada funkcije rade više stvari, teže ih je kombinovati, testirati i razumeti. Kada funkciju svedemo samo na jednu radnju, mnogo je lakše refaktorizati je i kod postaje mnogo čitljiviji. 244 | 245 | **Loše:** 246 | 247 | ```javascript 248 | function emailClients(clients) { 249 | clients.forEach(client => { 250 | const clientRecord = database.lookup(client); 251 | if (clientRecord.isActive()) { 252 | email(client); 253 | } 254 | }); 255 | } 256 | ``` 257 | 258 | **Dobro:** 259 | 260 | ```javascript 261 | function emailActiveClients(clients) { 262 | clients.filter(isActiveClient).forEach(email); 263 | } 264 | 265 | function isActiveClient(client) { 266 | const clientRecord = database.lookup(client); 267 | return clientRecord.isActive(); 268 | } 269 | ``` 270 | 271 | **[⬆ vrati se na početak](#sadržaj)** 272 | 273 | ### Koristite opisna imena funkcija 274 | 275 | **Loše:** 276 | 277 | ```javascript 278 | function addToDate(date, month) { 279 | // ... 280 | } 281 | 282 | const date = new Date(); 283 | 284 | // It's hard to tell from the function name what is added 285 | addToDate(date, 1); 286 | ``` 287 | 288 | **Dobro:** 289 | 290 | ```javascript 291 | function addMonthToDate(month, date) { 292 | // ... 293 | } 294 | 295 | const date = new Date(); 296 | addMonthToDate(1, date); 297 | ``` 298 | 299 | **[⬆ vrati se na početak](#sadržaj)** 300 | 301 | ### Jedan nivo apstrakcije po funkciji 302 | 303 | Ako funkcija ima više od jednog nivoa apstrakcije, ima tendenciju da radi previše. Odvajanjem takvih funkcija dovodi do ponovne upotrebe i lakšeg testiranja. 304 | 305 | **Loše:** 306 | 307 | ```javascript 308 | function parseBetterJSAlternative(code) { 309 | const REGEXES = [ 310 | // ... 311 | ]; 312 | 313 | const statements = code.split(" "); 314 | const tokens = []; 315 | REGEXES.forEach(REGEX => { 316 | statements.forEach(statement => { 317 | // ... 318 | }); 319 | }); 320 | 321 | const ast = []; 322 | tokens.forEach(token => { 323 | // lex... 324 | }); 325 | 326 | ast.forEach(node => { 327 | // parse... 328 | }); 329 | } 330 | ``` 331 | 332 | **Dobro:** 333 | 334 | ```javascript 335 | function parseBetterJSAlternative(code) { 336 | const tokens = tokenize(code); 337 | const syntaxTree = parse(tokens); 338 | syntaxTree.forEach(node => { 339 | // parse... 340 | }); 341 | } 342 | 343 | function tokenize(code) { 344 | const REGEXES = [ 345 | // ... 346 | ]; 347 | 348 | const statements = code.split(" "); 349 | const tokens = []; 350 | REGEXES.forEach(REGEX => { 351 | statements.forEach(statement => { 352 | tokens.push(/* ... */); 353 | }); 354 | }); 355 | 356 | return tokens; 357 | } 358 | 359 | function parse(tokens) { 360 | const syntaxTree = []; 361 | tokens.forEach(token => { 362 | syntaxTree.push(/* ... */); 363 | }); 364 | 365 | return syntaxTree; 366 | } 367 | ``` 368 | 369 | **[⬆ vrati se na početak](#sadržaj)** 370 | 371 | ### Rešite se dupliranog koda 372 | 373 | Potrudite se da izbegnete duplirani kod. Duplirani kod je štetan zato što potrazumeva više mesta koja treba izmetiti ako se algoritam promeni. 374 | 375 | Zamislite da vodite restoran i pratite potrošnju namernica: paradajiz, luk, začine itd. Ako imate više spiskova na kojima ovo pratite, za servisiranje bilo kog jela sa paradajizom biće potrebne promene na svakom spisku. Ako postoji samo jedan spisak, biće potrebno samo jedno ažuriranje! 376 | 377 | **Loše:** 378 | 379 | ```javascript 380 | function showDeveloperList(developers) { 381 | developers.forEach(developer => { 382 | const expectedSalary = developer.calculateExpectedSalary(); 383 | const experience = developer.getExperience(); 384 | const githubLink = developer.getGithubLink(); 385 | const data = { 386 | expectedSalary, 387 | experience, 388 | githubLink 389 | }; 390 | 391 | render(data); 392 | }); 393 | } 394 | 395 | function showManagerList(managers) { 396 | managers.forEach(manager => { 397 | const expectedSalary = manager.calculateExpectedSalary(); 398 | const experience = manager.getExperience(); 399 | const portfolio = manager.getMBAProjects(); 400 | const data = { 401 | expectedSalary, 402 | experience, 403 | portfolio 404 | }; 405 | 406 | render(data); 407 | }); 408 | } 409 | ``` 410 | 411 | **Dobro:** 412 | 413 | ```javascript 414 | function showEmployeeList(employees) { 415 | employees.forEach(employee => { 416 | const expectedSalary = employee.calculateExpectedSalary(); 417 | const experience = employee.getExperience(); 418 | 419 | const data = { 420 | expectedSalary, 421 | experience 422 | }; 423 | 424 | switch (employee.type) { 425 | case "manager": 426 | data.portfolio = employee.getMBAProjects(); 427 | break; 428 | case "developer": 429 | data.githubLink = employee.getGithubLink(); 430 | break; 431 | } 432 | 433 | render(data); 434 | }); 435 | } 436 | ``` 437 | 438 | **[⬆ vrati se na početak](#sadržaj)** 439 | 440 | ### Podesite podrazumevane vrednosti objekta pomoću Object.assign metode 441 | 442 | **Loše:** 443 | 444 | ```javascript 445 | const menuConfig = { 446 | title: null, 447 | body: "Bar", 448 | buttonText: null, 449 | cancellable: true 450 | }; 451 | 452 | function createMenu(config) { 453 | config.title = config.title || "Foo"; 454 | config.body = config.body || "Bar"; 455 | config.buttonText = config.buttonText || "Baz"; 456 | config.cancellable = 457 | config.cancellable !== undefined ? config.cancellable : true; 458 | } 459 | 460 | createMenu(menuConfig); 461 | ``` 462 | 463 | **Dobro:** 464 | 465 | ```javascript 466 | const menuConfig = { 467 | title: "Order", 468 | // User did not include 'body' key 469 | buttonText: "Send", 470 | cancellable: true 471 | }; 472 | 473 | function createMenu(config) { 474 | let finalConfig = Object.assign( 475 | { 476 | title: "Foo", 477 | body: "Bar", 478 | buttonText: "Baz", 479 | cancellable: true 480 | }, 481 | config 482 | ); 483 | return finalConfig 484 | // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} 485 | // ... 486 | } 487 | 488 | createMenu(menuConfig); 489 | ``` 490 | 491 | **[⬆ vrati se na početak](#sadržaj)** 492 | 493 | ### Ne koristite argumente pokazivače 494 | 495 | Argumenti pokazivači, zastavice (engl. *flags*) su ružni. 496 | Prosleđivanje logičke vrednosti u funkciju je zaista loša praksa. To odmah otežava razumevanje metode, naglašavajući da funkcija obavlja više stvari. Jedna stvar je ako je vrednost pokazivača istinita (engl. *true*), a druga ako je lažna (engl. *false*). 497 | 498 | **Loše:** 499 | 500 | ```javascript 501 | function createFile(name, temp) { 502 | if (temp) { 503 | fs.create(`./temp/${name}`); 504 | } else { 505 | fs.create(name); 506 | } 507 | } 508 | ``` 509 | 510 | **Dobro:** 511 | 512 | ```javascript 513 | function createFile(name) { 514 | fs.create(name); 515 | } 516 | 517 | function createTempFile(name) { 518 | createFile(`./temp/${name}`); 519 | } 520 | ``` 521 | 522 | **[⬆ vrati se na početak](#sadržaj)** 523 | 524 | ### Izbegavajte neželjene efekte (deo 1) 525 | 526 | Funkcija će imati sporedan efekat ako radi bilo šta drugo osim što uzima neku vrednost i vraća drugu vrednost ili vrednosti. Neželjeni efekti se mogu javiti u vidu izmena globalne varijable i slično. 527 | 528 | **Loše:** 529 | 530 | ```javascript 531 | // Global variable referenced by following function. 532 | // If we had another function that used this name, now it'd be an array and it could break it. 533 | let name = "Ryan McDermott"; 534 | 535 | function splitIntoFirstAndLastName() { 536 | name = name.split(" "); 537 | } 538 | 539 | splitIntoFirstAndLastName(); 540 | 541 | console.log(name); // ['Ryan', 'McDermott']; 542 | ``` 543 | 544 | **Dobro:** 545 | 546 | ```javascript 547 | function splitIntoFirstAndLastName(name) { 548 | return name.split(" "); 549 | } 550 | 551 | const name = "Ryan McDermott"; 552 | const newName = splitIntoFirstAndLastName(name); 553 | 554 | console.log(name); // 'Ryan McDermott'; 555 | console.log(newName); // ['Ryan', 'McDermott']; 556 | ``` 557 | 558 | **[⬆ vrati se na početak](#sadržaj)** 559 | 560 | ### Izbegavajte neželjene efekte (deo 2) 561 | 562 | U JavaScript-u primitivni tipovi se čuvaju po vrednosti, a objketi i nizovi po referenci. 563 | Referentani tip vrednosti se čuva u sporijem delu memorije i za razliku od primitivnog tipa vrednosti referentni tip vrednost može da se menja tokom vremena. 564 | 565 | Ukoliko neka funkcija izvrši promenu nad ulaznim nizom ili objketom, to će uticati na sve ostale funkcije koje koriste isti taj niz ili objekat kao ulazni parametar. Odlično rešenje bi bilo da nizove i objekte kao ulazne parametre, unutar funkcije uvek kloniramo. 566 | 567 | Treba istaći i dva upozorenja ovom pristupu: 568 | 1. Možda postoje slučajevi kada želimo da izmenimo ulazni objekat, ali kada usvojite ovu programsku praksu, otkrićete da su ti slučajevi prilično retki. 569 | 570 | 2. Kloniranje velikih objekata može biti veoma skupo u pogledu performansi. Srećom ovo nije veliko pitanje u praksi, obzirom da postoje [sjajne biblioteke](https://facebook.github.io/immutable-js/) koje rešavaju probleme ovakvog pristupa. 571 | 572 | **Loše:** 573 | 574 | ```javascript 575 | const addItemToCart = (cart, item) => { 576 | cart.push({ item, date: Date.now() }); 577 | }; 578 | ``` 579 | 580 | **Dobro:** 581 | 582 | ```javascript 583 | const addItemToCart = (cart, item) => { 584 | return [...cart, { item, date: Date.now() }]; 585 | }; 586 | ``` 587 | 588 | **[⬆ vrati se na početak](#sadržaj)** 589 | 590 | ### Nemojte dodavati globalne funkcije 591 | 592 | Dodavanje funkcija globalnom objketu je loša praksa u Javascript-u jer može doći do sukoba sa drugim bibliotekama. Šta ako želimo da proširimo globalni objekat `Array` tako da ima `diff` metodu koja će prikazivati razliku između dva niza? Mogli bismo da napišemo novu metodu na `Array.prototype`, ali možda će početi da se sukobljava sa drugom bibliotekom koja pokušava da učini isto. Šta ako druga biblioteka koristi `diff` da pokaže razliku između prvog i poslednjeg elementa u nizu? Zbog toga je mnogo bolje koristiti klase ES2015/ES6 i proširiti globalni objekat `Array`. 593 | 594 | **Loše:** 595 | 596 | ```javascript 597 | Array.prototype.diff = function diff(comparisonArray) { 598 | const hash = new Set(comparisonArray); 599 | return this.filter(elem => !hash.has(elem)); 600 | }; 601 | ``` 602 | 603 | **Dobro:** 604 | 605 | ```javascript 606 | class SuperArray extends Array { 607 | diff(comparisonArray) { 608 | const hash = new Set(comparisonArray); 609 | return this.filter(elem => !hash.has(elem)); 610 | } 611 | } 612 | ``` 613 | 614 | **[⬆ vrati se na početak](#sadržaj)** 615 | 616 | ### Dajte prednost funkcionalnom programiranju nad imperativnim programiranjem 617 | 618 | JavaScript nije toliko funkcionalan kao Haskell, ali ima predispoziciju za to. Funkcionalni jezici su čistiji i lakši za testiranje. Preferirajte ovaj stil programiranja kad god možete. 619 | 620 | **Loše:** 621 | 622 | ```javascript 623 | const programmerOutput = [ 624 | { 625 | name: "Uncle Bobby", 626 | linesOfCode: 500 627 | }, 628 | { 629 | name: "Suzie Q", 630 | linesOfCode: 1500 631 | }, 632 | { 633 | name: "Jimmy Gosling", 634 | linesOfCode: 150 635 | }, 636 | { 637 | name: "Gracie Hopper", 638 | linesOfCode: 1000 639 | } 640 | ]; 641 | 642 | let totalOutput = 0; 643 | 644 | for (let i = 0; i < programmerOutput.length; i++) { 645 | totalOutput += programmerOutput[i].linesOfCode; 646 | } 647 | ``` 648 | 649 | **Dobro:** 650 | 651 | ```javascript 652 | const programmerOutput = [ 653 | { 654 | name: "Uncle Bobby", 655 | linesOfCode: 500 656 | }, 657 | { 658 | name: "Suzie Q", 659 | linesOfCode: 1500 660 | }, 661 | { 662 | name: "Jimmy Gosling", 663 | linesOfCode: 150 664 | }, 665 | { 666 | name: "Gracie Hopper", 667 | linesOfCode: 1000 668 | } 669 | ]; 670 | 671 | const totalOutput = programmerOutput.reduce( 672 | (totalLines, output) => totalLines + output.linesOfCode, 673 | 0 674 | ); 675 | ``` 676 | 677 | **[⬆ vrati se na početak](#sadržaj)** 678 | 679 | ### Enkapsulacija uslova 680 | 681 | **Loše:** 682 | 683 | ```javascript 684 | if (fsm.state === "fetching" && isEmpty(listNode)) { 685 | // ... 686 | } 687 | ``` 688 | 689 | **Dobro:** 690 | 691 | ```javascript 692 | function shouldShowSpinner(fsm, listNode) { 693 | return fsm.state === "fetching" && isEmpty(listNode); 694 | } 695 | 696 | if (shouldShowSpinner(fsmInstance, listNodeInstance)) { 697 | // ... 698 | } 699 | ``` 700 | 701 | **[⬆ vrati se na početak](#sadržaj)** 702 | 703 | ### Izbegavajte negativne uslove 704 | 705 | **Loše:** 706 | 707 | ```javascript 708 | function isDOMNodeNotPresent(node) { 709 | // ... 710 | } 711 | 712 | if (!isDOMNodeNotPresent(node)) { 713 | // ... 714 | } 715 | ``` 716 | 717 | **Dobro:** 718 | 719 | ```javascript 720 | function isDOMNodePresent(node) { 721 | // ... 722 | } 723 | 724 | if (isDOMNodePresent(node)) { 725 | // ... 726 | } 727 | ``` 728 | 729 | **[⬆ vrati se na početak](#sadržaj)** 730 | 731 | ### Izbegavajte uslove 732 | 733 | Čini se kao nemoguć zadatak. Većina ljudi, kada ovo prvi put čuje, kaže: "Kako da radim nešto bez `if`?" Odgovor je da u mnogim slučajevima možemo koristiti polimorfizam da bi postigli isti cilj. Zašto bi ovo radili leži u jednom od prethodnih principa: funkcija treba da radi samo jednu stvar. Čim funkcija ima uslov `if` to znači da ta funkcija radi više od jedne stvari. 734 | 735 | **Loše:** 736 | 737 | ```javascript 738 | class Airplane { 739 | // ... 740 | getCruisingAltitude() { 741 | switch (this.type) { 742 | case "777": 743 | return this.getMaxAltitude() - this.getPassengerCount(); 744 | case "Air Force One": 745 | return this.getMaxAltitude(); 746 | case "Cessna": 747 | return this.getMaxAltitude() - this.getFuelExpenditure(); 748 | } 749 | } 750 | } 751 | ``` 752 | 753 | **Dobro:** 754 | 755 | ```javascript 756 | class Airplane { 757 | // ... 758 | } 759 | 760 | class Boeing777 extends Airplane { 761 | // ... 762 | getCruisingAltitude() { 763 | return this.getMaxAltitude() - this.getPassengerCount(); 764 | } 765 | } 766 | 767 | class AirForceOne extends Airplane { 768 | // ... 769 | getCruisingAltitude() { 770 | return this.getMaxAltitude(); 771 | } 772 | } 773 | 774 | class Cessna extends Airplane { 775 | // ... 776 | getCruisingAltitude() { 777 | return this.getMaxAltitude() - this.getFuelExpenditure(); 778 | } 779 | } 780 | ``` 781 | 782 | **[⬆ vrati se na početak](#sadržaj)** 783 | 784 | ### Izbegavajte proveru tipa (deo 1) 785 | 786 | **Loše:** 787 | 788 | ```javascript 789 | function travelToTexas(vehicle) { 790 | if (vehicle instanceof Bicycle) { 791 | vehicle.pedal(this.currentLocation, new Location("texas")); 792 | } else if (vehicle instanceof Car) { 793 | vehicle.drive(this.currentLocation, new Location("texas")); 794 | } 795 | } 796 | ``` 797 | 798 | **Dobro:** 799 | 800 | ```javascript 801 | function travelToTexas(vehicle) { 802 | vehicle.move(this.currentLocation, new Location("texas")); 803 | } 804 | ``` 805 | 806 | **[⬆ vrati se na početak](#sadržaj)** 807 | 808 | ### Izbegavajte proveru tipa (deo 2) 809 | 810 | Ukoliko osećate da imate potrebu za proverom tipa, trebalo bi da razmislite o korišćenju TypeScript-a. TypeScript je programski jezik koji je baziran na JavaScript jeziku, ali je postavljen kao nad-jezik, tj. jezik koji proširuje funkcionalnosti JavaScript-a. Posebna osobina TypeScript jezika, u odnosu na JavaScript je ta što koristi mehanizam statički izričito definisanih tipova za promenljive, parametre funkcija, povratne tipove funkcija itd. 811 | 812 | **Loše:** 813 | 814 | ```javascript 815 | function combine(val1, val2) { 816 | if ( 817 | (typeof val1 === "number" && typeof val2 === "number") || 818 | (typeof val1 === "string" && typeof val2 === "string") 819 | ) { 820 | return val1 + val2; 821 | } 822 | 823 | throw new Error("Must be of type String or Number"); 824 | } 825 | ``` 826 | 827 | **Dobro:** 828 | 829 | ```javascript 830 | function combine(val1, val2) { 831 | return val1 + val2; 832 | } 833 | ``` 834 | 835 | **[⬆ vrati se na početak](#sadržaj)** 836 | 837 | ### Ne optimizujte preterano 838 | 839 | Moderni pretraživači rade puno optimizacije tokom izvršavanja koda. [Postoje dobri resuri](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) za otkrivanje nedostataka optimizacije, koristite ih. 840 | 841 | **Loše:** 842 | 843 | ```javascript 844 | // On old browsers, each iteration with uncached `list.length` would be costly 845 | // because of `list.length` recomputation. In modern browsers, this is optimized. 846 | for (let i = 0, len = list.length; i < len; i++) { 847 | // ... 848 | } 849 | ``` 850 | 851 | **Dobro:** 852 | 853 | ```javascript 854 | for (let i = 0; i < list.length; i++) { 855 | // ... 856 | } 857 | ``` 858 | 859 | **[⬆ vrati se na početak](#sadržaj)** 860 | 861 | ### Uklonite kod koji se ne koristi 862 | 863 | Kod koji se ne koristi je jednako loš kao i duplirani kod. Nema razloga da čuvamo kod koji više ne koristimo. 864 | 865 | **Loše:** 866 | 867 | ```javascript 868 | function oldRequestModule(url) { 869 | // ... 870 | } 871 | 872 | function newRequestModule(url) { 873 | // ... 874 | } 875 | 876 | const req = newRequestModule; 877 | inventoryTracker("apples", req, "www.inventory-awesome.io"); 878 | ``` 879 | 880 | **Dobro:** 881 | 882 | ```javascript 883 | function newRequestModule(url) { 884 | // ... 885 | } 886 | 887 | const req = newRequestModule; 888 | inventoryTracker("apples", req, "www.inventory-awesome.io"); 889 | ``` 890 | 891 | **[⬆ vrati se na početak](#sadržaj)** 892 | 893 | ## **Objekti i strukture podataka** 894 | 895 | ### Koristite getters i setters 896 | 897 | Bolje je koristiti `get` i `set` metode kada pristupamo svojstvima objekta nego direktno pristupiti njima. Ako se pitate "Zašto?" Evo nekoliko razloga: 898 | 899 | - Validacija je lako primeljiva na nivou `set` metode 900 | - Enkapsulaciju promenljivih unutar objekta 901 | - Jednostavnije je rukovanje greškama na nivou `get` i `set` metoda 902 | 903 | **Loše:** 904 | 905 | ```javascript 906 | function makeBankAccount() { 907 | // ... 908 | 909 | return { 910 | balance: 0 911 | // ... 912 | }; 913 | } 914 | 915 | const account = makeBankAccount(); 916 | account.balance = 100; 917 | ``` 918 | 919 | **Dobro:** 920 | 921 | ```javascript 922 | function makeBankAccount() { 923 | // this one is private 924 | let balance = 0; 925 | 926 | // a "getter", made public via the returned object below 927 | function getBalance() { 928 | return balance; 929 | } 930 | 931 | // a "setter", made public via the returned object below 932 | function setBalance(amount) { 933 | // ... validate before updating the balance 934 | balance = amount; 935 | } 936 | 937 | return { 938 | // ... 939 | getBalance, 940 | setBalance 941 | }; 942 | } 943 | 944 | const account = makeBankAccount(); 945 | account.setBalance(100); 946 | ``` 947 | 948 | **[⬆ vrati se na početak](#sadržaj)** 949 | 950 | ### Koristite privatne članove 951 | 952 | To se može postići pomoću *closure* 953 | 954 | **Loše:** 955 | 956 | ```javascript 957 | const Employee = function(name) { 958 | this.name = name; 959 | }; 960 | 961 | Employee.prototype.getName = function getName() { 962 | return this.name; 963 | }; 964 | 965 | const employee = new Employee("John Doe"); 966 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 967 | delete employee.name; 968 | console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined 969 | ``` 970 | 971 | **Dobro:** 972 | 973 | ```javascript 974 | function makeEmployee(name) { 975 | return { 976 | getName() { 977 | return name; 978 | } 979 | }; 980 | } 981 | 982 | const employee = makeEmployee("John Doe"); 983 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 984 | delete employee.name; 985 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 986 | ``` 987 | 988 | **[⬆ vrati se na početak](#sadržaj)** 989 | 990 | ## **Classes** 991 | 992 | ### Preferirajte klase ES2015/ES6 u odnosu na konstruktor funkcije ES5 993 | 994 | Pomoću ES5, veoma je teško napisati čitljivu konstruktor funkciju koja nasleđuje neke metode od druge konstruktor funkcije. Ukoliko je potrebno da koristite nasleđivanje, onda koristite klase iz ES2015/ES6. Najbolje je raditi sa malim funkcijama sve dok ne vidite potrebu za većim i složenijim objektom. 995 | 996 | **Loše:** 997 | 998 | ```javascript 999 | const Animal = function(age) { 1000 | if (!(this instanceof Animal)) { 1001 | throw new Error("Instantiate Animal with `new`"); 1002 | } 1003 | 1004 | this.age = age; 1005 | }; 1006 | 1007 | Animal.prototype.move = function move() {}; 1008 | 1009 | const Mammal = function(age, furColor) { 1010 | if (!(this instanceof Mammal)) { 1011 | throw new Error("Instantiate Mammal with `new`"); 1012 | } 1013 | 1014 | Animal.call(this, age); 1015 | this.furColor = furColor; 1016 | }; 1017 | 1018 | Mammal.prototype = Object.create(Animal.prototype); 1019 | Mammal.prototype.constructor = Mammal; 1020 | Mammal.prototype.liveBirth = function liveBirth() {}; 1021 | 1022 | const Human = function(age, furColor, languageSpoken) { 1023 | if (!(this instanceof Human)) { 1024 | throw new Error("Instantiate Human with `new`"); 1025 | } 1026 | 1027 | Mammal.call(this, age, furColor); 1028 | this.languageSpoken = languageSpoken; 1029 | }; 1030 | 1031 | Human.prototype = Object.create(Mammal.prototype); 1032 | Human.prototype.constructor = Human; 1033 | Human.prototype.speak = function speak() {}; 1034 | ``` 1035 | 1036 | **Dobro:** 1037 | 1038 | ```javascript 1039 | class Animal { 1040 | constructor(age) { 1041 | this.age = age; 1042 | } 1043 | 1044 | move() { 1045 | /* ... */ 1046 | } 1047 | } 1048 | 1049 | class Mammal extends Animal { 1050 | constructor(age, furColor) { 1051 | super(age); 1052 | this.furColor = furColor; 1053 | } 1054 | 1055 | liveBirth() { 1056 | /* ... */ 1057 | } 1058 | } 1059 | 1060 | class Human extends Mammal { 1061 | constructor(age, furColor, languageSpoken) { 1062 | super(age, furColor); 1063 | this.languageSpoken = languageSpoken; 1064 | } 1065 | 1066 | speak() { 1067 | /* ... */ 1068 | } 1069 | } 1070 | ``` 1071 | 1072 | **[⬆ vrati se na početak](#sadržaj)** 1073 | 1074 | ### Koristite *method chaining* 1075 | 1076 | Ovaj pattern je veoma koristan u JavaScript-u i možete ga videti u mnogim bibliotekama kao što su jQuery i Lodash. 1077 | Jednostavno u metodama vratite `this` na kraju svake funkcije i zatim kod poziva metoda, možete nadovezivati ostale metode. 1078 | 1079 | **Loše:** 1080 | 1081 | ```javascript 1082 | class Car { 1083 | constructor(make, model, color) { 1084 | this.make = make; 1085 | this.model = model; 1086 | this.color = color; 1087 | } 1088 | 1089 | setMake(make) { 1090 | this.make = make; 1091 | } 1092 | 1093 | setModel(model) { 1094 | this.model = model; 1095 | } 1096 | 1097 | setColor(color) { 1098 | this.color = color; 1099 | } 1100 | 1101 | save() { 1102 | console.log(this.make, this.model, this.color); 1103 | } 1104 | } 1105 | 1106 | const car = new Car("Ford", "F-150", "red"); 1107 | car.setColor("pink"); 1108 | car.save(); 1109 | ``` 1110 | 1111 | **Dobro:** 1112 | 1113 | ```javascript 1114 | class Car { 1115 | constructor(make, model, color) { 1116 | this.make = make; 1117 | this.model = model; 1118 | this.color = color; 1119 | } 1120 | 1121 | setMake(make) { 1122 | this.make = make; 1123 | // NOTE: Returning this for chaining 1124 | return this; 1125 | } 1126 | 1127 | setModel(model) { 1128 | this.model = model; 1129 | // NOTE: Returning this for chaining 1130 | return this; 1131 | } 1132 | 1133 | setColor(color) { 1134 | this.color = color; 1135 | // NOTE: Returning this for chaining 1136 | return this; 1137 | } 1138 | 1139 | save() { 1140 | console.log(this.make, this.model, this.color); 1141 | // NOTE: Returning this for chaining 1142 | return this; 1143 | } 1144 | } 1145 | 1146 | const car = new Car("Ford", "F-150", "red").setColor("pink").save(); 1147 | ``` 1148 | 1149 | **[⬆ vrati se na početak](#sadržaj)** 1150 | 1151 | ### Dajte prednost kompoziciji (composition) nad nasleđivanjem (inheritance) 1152 | 1153 | Postoji mnogo razloga kada treba upotrebiti nasleđivanje i mnogo razloga kada treba upotrebiti kompoziciju. 1154 | Ukoliko mislite da treba da implementirate nasleđivanje, pokušajte da razmislite da li bi kompozicija bila bolje rešenje. U nekim slučajevima će sigurno biti. 1155 | 1156 | Ukoliko za primer uzmemo automobil: točkovi, motor, menjač itd. mogu biti posmatrani kao posebne klase. Klasa automobil bi predstavljala kompoziciju ovih pojedinačnih klasa. 1157 | 1158 | **Loše:** 1159 | 1160 | ```javascript 1161 | class Employee { 1162 | constructor(name, email) { 1163 | this.name = name; 1164 | this.email = email; 1165 | } 1166 | 1167 | // ... 1168 | } 1169 | 1170 | // Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee 1171 | class EmployeeTaxData extends Employee { 1172 | constructor(ssn, salary) { 1173 | super(); 1174 | this.ssn = ssn; 1175 | this.salary = salary; 1176 | } 1177 | 1178 | // ... 1179 | } 1180 | ``` 1181 | 1182 | **Dobro:** 1183 | 1184 | ```javascript 1185 | class EmployeeTaxData { 1186 | constructor(ssn, salary) { 1187 | this.ssn = ssn; 1188 | this.salary = salary; 1189 | } 1190 | 1191 | // ... 1192 | } 1193 | 1194 | class Employee { 1195 | constructor(name, email) { 1196 | this.name = name; 1197 | this.email = email; 1198 | } 1199 | 1200 | setTaxData(ssn, salary) { 1201 | this.taxData = new EmployeeTaxData(ssn, salary); 1202 | } 1203 | // ... 1204 | } 1205 | ``` 1206 | 1207 | **[⬆ vrati se na početak](#sadržaj)** 1208 | 1209 | ## **SOLID** 1210 | SOLID je akronim koji se sastoji iz 5 principa: Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation i Dependency Inversion. Ovih 5 principa se najčešće koriste i imaju veoma velike pozitivne efekte na softver i kod u kome se primene. 1211 | 1212 | ### Single Responsibility Principle (SRP) 1213 | 1214 | Princip jednostruke odgovornosti govori o tome da svaka klasa treba da ima samo jednu odgovornost. To ne znači da treba da ima samo jednu funkciju. Ona može imati i više funkcija ako one zajedno izvršavaju jedan zadatak i zajedno čine samo jedan razlog za promenu te klase. 1215 | 1216 | **Loše:** 1217 | 1218 | ```javascript 1219 | class UserSettings { 1220 | constructor(user) { 1221 | this.user = user; 1222 | } 1223 | 1224 | changeSettings(settings) { 1225 | if (this.verifyCredentials()) { 1226 | // ... 1227 | } 1228 | } 1229 | 1230 | verifyCredentials() { 1231 | // ... 1232 | } 1233 | } 1234 | ``` 1235 | 1236 | **Dobro:** 1237 | 1238 | ```javascript 1239 | class UserAuth { 1240 | constructor(user) { 1241 | this.user = user; 1242 | } 1243 | 1244 | verifyCredentials() { 1245 | // ... 1246 | } 1247 | } 1248 | 1249 | class UserSettings { 1250 | constructor(user) { 1251 | this.user = user; 1252 | this.auth = new UserAuth(user); 1253 | } 1254 | 1255 | changeSettings(settings) { 1256 | if (this.auth.verifyCredentials()) { 1257 | // ... 1258 | } 1259 | } 1260 | } 1261 | ``` 1262 | 1263 | **[⬆ vrati se na početak](#sadržaj)** 1264 | 1265 | ### Open/Closed Principle (OCP) 1266 | 1267 | Otvoren-zatvoren princip govori o tome da klasa treba biti otvorena za proširenja, a zatvorena za izmene. To znači da ako je potrebno uneti neke nove funkcionalnosti, onda ne treba menjati klasu i postojeće metode, već je proširiti. 1268 | 1269 | **Loše:** 1270 | 1271 | ```javascript 1272 | class AjaxAdapter extends Adapter { 1273 | constructor() { 1274 | super(); 1275 | this.name = "ajaxAdapter"; 1276 | } 1277 | } 1278 | 1279 | class NodeAdapter extends Adapter { 1280 | constructor() { 1281 | super(); 1282 | this.name = "nodeAdapter"; 1283 | } 1284 | } 1285 | 1286 | class HttpRequester { 1287 | constructor(adapter) { 1288 | this.adapter = adapter; 1289 | } 1290 | 1291 | fetch(url) { 1292 | if (this.adapter.name === "ajaxAdapter") { 1293 | return makeAjaxCall(url).then(response => { 1294 | // transform response and return 1295 | }); 1296 | } else if (this.adapter.name === "nodeAdapter") { 1297 | return makeHttpCall(url).then(response => { 1298 | // transform response and return 1299 | }); 1300 | } 1301 | } 1302 | } 1303 | 1304 | function makeAjaxCall(url) { 1305 | // request and return promise 1306 | } 1307 | 1308 | function makeHttpCall(url) { 1309 | // request and return promise 1310 | } 1311 | ``` 1312 | 1313 | **Dobro:** 1314 | 1315 | ```javascript 1316 | class AjaxAdapter extends Adapter { 1317 | constructor() { 1318 | super(); 1319 | this.name = "ajaxAdapter"; 1320 | } 1321 | 1322 | request(url) { 1323 | // request and return promise 1324 | } 1325 | } 1326 | 1327 | class NodeAdapter extends Adapter { 1328 | constructor() { 1329 | super(); 1330 | this.name = "nodeAdapter"; 1331 | } 1332 | 1333 | request(url) { 1334 | // request and return promise 1335 | } 1336 | } 1337 | 1338 | class HttpRequester { 1339 | constructor(adapter) { 1340 | this.adapter = adapter; 1341 | } 1342 | 1343 | fetch(url) { 1344 | return this.adapter.request(url).then(response => { 1345 | // transform response and return 1346 | }); 1347 | } 1348 | } 1349 | ``` 1350 | 1351 | **[⬆ vrati se na početak](#sadržaj)** 1352 | 1353 | ### Liskov Substitution Principle (LSP) 1354 | 1355 | Princip Liskove zamene kaže da sve podklase neke nadklase mogu da se zamene svojom nadklasom, a da se pri tome ponašanje programa ne promeni. 1356 | 1357 | **Loše:** 1358 | 1359 | ```javascript 1360 | class Rectangle { 1361 | constructor() { 1362 | this.width = 0; 1363 | this.height = 0; 1364 | } 1365 | 1366 | setColor(color) { 1367 | // ... 1368 | } 1369 | 1370 | render(area) { 1371 | // ... 1372 | } 1373 | 1374 | setWidth(width) { 1375 | this.width = width; 1376 | } 1377 | 1378 | setHeight(height) { 1379 | this.height = height; 1380 | } 1381 | 1382 | getArea() { 1383 | return this.width * this.height; 1384 | } 1385 | } 1386 | 1387 | class Square extends Rectangle { 1388 | setWidth(width) { 1389 | this.width = width; 1390 | this.height = width; 1391 | } 1392 | 1393 | setHeight(height) { 1394 | this.width = height; 1395 | this.height = height; 1396 | } 1397 | } 1398 | 1399 | function renderLargeRectangles(rectangles) { 1400 | rectangles.forEach(rectangle => { 1401 | rectangle.setWidth(4); 1402 | rectangle.setHeight(5); 1403 | const area = rectangle.getArea(); // Bad: Returns 25 for Square. Should be 20. 1404 | rectangle.render(area); 1405 | }); 1406 | } 1407 | 1408 | const rectangles = [new Rectangle(), new Rectangle(), new Square()]; 1409 | renderLargeRectangles(rectangles); 1410 | ``` 1411 | 1412 | **Dobro:** 1413 | 1414 | ```javascript 1415 | class Shape { 1416 | setColor(color) { 1417 | // ... 1418 | } 1419 | 1420 | render(area) { 1421 | // ... 1422 | } 1423 | } 1424 | 1425 | class Rectangle extends Shape { 1426 | constructor(width, height) { 1427 | super(); 1428 | this.width = width; 1429 | this.height = height; 1430 | } 1431 | 1432 | getArea() { 1433 | return this.width * this.height; 1434 | } 1435 | } 1436 | 1437 | class Square extends Shape { 1438 | constructor(length) { 1439 | super(); 1440 | this.length = length; 1441 | } 1442 | 1443 | getArea() { 1444 | return this.length * this.length; 1445 | } 1446 | } 1447 | 1448 | function renderLargeShapes(shapes) { 1449 | shapes.forEach(shape => { 1450 | const area = shape.getArea(); 1451 | shape.render(area); 1452 | }); 1453 | } 1454 | 1455 | const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)]; 1456 | renderLargeShapes(shapes); 1457 | ``` 1458 | 1459 | **[⬆ vrati se na početak](#sadržaj)** 1460 | 1461 | ### Interface Segregation Principle (ISP) 1462 | 1463 | JavaScrip nema intefejse, pa se ovaj princip ne može u potpunosti iskoristiti. U ISP se naglašava da "Klijente ne treba prisiljavati da zavise od interfejsa koje ne koriste". Dobar primer koji se može primeniti u JavaScript-u jeste da prilikom kreiranja neke klase ne zahtevamo veliki broj ulaznih parametra koji se neće puno koristiti, već da ih ostavimo kao opcione. 1464 | 1465 | **Loše:** 1466 | 1467 | ```javascript 1468 | class DOMTraverser { 1469 | constructor(settings) { 1470 | this.settings = settings; 1471 | this.setup(); 1472 | } 1473 | 1474 | setup() { 1475 | this.rootNode = this.settings.rootNode; 1476 | this.settings.animationModule.setup(); 1477 | } 1478 | 1479 | traverse() { 1480 | // ... 1481 | } 1482 | } 1483 | 1484 | const $ = new DOMTraverser({ 1485 | rootNode: document.getElementsByTagName("body"), 1486 | animationModule() {} // Most of the time, we won't need to animate when traversing. 1487 | // ... 1488 | }); 1489 | ``` 1490 | 1491 | **Dobro:** 1492 | 1493 | ```javascript 1494 | class DOMTraverser { 1495 | constructor(settings) { 1496 | this.settings = settings; 1497 | this.options = settings.options; 1498 | this.setup(); 1499 | } 1500 | 1501 | setup() { 1502 | this.rootNode = this.settings.rootNode; 1503 | this.setupOptions(); 1504 | } 1505 | 1506 | setupOptions() { 1507 | if (this.options.animationModule) { 1508 | // ... 1509 | } 1510 | } 1511 | 1512 | traverse() { 1513 | // ... 1514 | } 1515 | } 1516 | 1517 | const $ = new DOMTraverser({ 1518 | rootNode: document.getElementsByTagName("body"), 1519 | options: { 1520 | animationModule() {} 1521 | } 1522 | }); 1523 | ``` 1524 | 1525 | **[⬆ vrati se na početak](#sadržaj)** 1526 | 1527 | ### Dependency Inversion Principle (DIP) 1528 | 1529 | Princip inverzije zavisnosti naglašava dve bitne stvari: 1530 | 1531 | 1. Moduli višeg nivoa ne treba da zavise od modula nižeg nivoa. 1532 | 2. Apstrakcije ne treba da zavise od detalja, već detalji od apstrakcija. 1533 | 1534 | Kao što smo ranije napomenuli, JavaScript nema iterfejse, to znači da treba da se kreiraju apstrakcije koje predstavljaju ugovor. 1535 | Apstrakcija samo govori šta neka klasa treba da radi, ne i kako. Klase koje će naslediti tu apstraktu klasu treba da znaju šta sve ta apstraktna klasa ima od funkcija i na taj način zapravo detalji zavise od apstrakcija, a apstrakcije ne zavise od detalja. 1536 | 1537 | **Loše:** 1538 | 1539 | ```javascript 1540 | class InventoryRequester { 1541 | constructor() { 1542 | this.REQ_METHODS = ["HTTP"]; 1543 | } 1544 | 1545 | requestItem(item) { 1546 | // ... 1547 | } 1548 | } 1549 | 1550 | class InventoryTracker { 1551 | constructor(items) { 1552 | this.items = items; 1553 | 1554 | // Bad: We have created a dependency on a specific request implementation. 1555 | // We should just have requestItems depend on a request method: `request` 1556 | this.requester = new InventoryRequester(); 1557 | } 1558 | 1559 | requestItems() { 1560 | this.items.forEach(item => { 1561 | this.requester.requestItem(item); 1562 | }); 1563 | } 1564 | } 1565 | 1566 | const inventoryTracker = new InventoryTracker(["apples", "bananas"]); 1567 | inventoryTracker.requestItems(); 1568 | ``` 1569 | 1570 | **Dobro:** 1571 | 1572 | ```javascript 1573 | class InventoryTracker { 1574 | constructor(items, requester) { 1575 | this.items = items; 1576 | this.requester = requester; 1577 | } 1578 | 1579 | requestItems() { 1580 | this.items.forEach(item => { 1581 | this.requester.requestItem(item); 1582 | }); 1583 | } 1584 | } 1585 | 1586 | class InventoryRequesterV1 { 1587 | constructor() { 1588 | this.REQ_METHODS = ["HTTP"]; 1589 | } 1590 | 1591 | requestItem(item) { 1592 | // ... 1593 | } 1594 | } 1595 | 1596 | class InventoryRequesterV2 { 1597 | constructor() { 1598 | this.REQ_METHODS = ["WS"]; 1599 | } 1600 | 1601 | requestItem(item) { 1602 | // ... 1603 | } 1604 | } 1605 | 1606 | // By constructing our dependencies externally and injecting them, we can easily 1607 | // substitute our request module for a fancy new one that uses WebSockets. 1608 | const inventoryTracker = new InventoryTracker( 1609 | ["apples", "bananas"], 1610 | new InventoryRequesterV2() 1611 | ); 1612 | inventoryTracker.requestItems(); 1613 | ``` 1614 | 1615 | **[⬆ vrati se na početak](#sadržaj)** 1616 | 1617 | ## **Testiranje** 1618 | 1619 | Testovi su jednako važni za zdravlje projekta kao i sam kod. Možda su još i važniji, jer testovi čuvaju i poboljšavaju felksibilnost, održavanje i naknadnu upotrebu proizvodnog koda. Ako imamo testove ne plašimo se promene koda! Bez testova svaka promena predstavlja moguću grešku. 1620 | 1621 | ### Jedna tvrdnja po testu (single concept per test) 1622 | 1623 | **Loše:** 1624 | 1625 | ```javascript 1626 | import assert from "assert"; 1627 | 1628 | describe("MomentJS", () => { 1629 | it("handles date boundaries", () => { 1630 | let date; 1631 | 1632 | date = new MomentJS("1/1/2015"); 1633 | date.addDays(30); 1634 | assert.equal("1/31/2015", date); 1635 | 1636 | date = new MomentJS("2/1/2016"); 1637 | date.addDays(28); 1638 | assert.equal("02/29/2016", date); 1639 | 1640 | date = new MomentJS("2/1/2015"); 1641 | date.addDays(28); 1642 | assert.equal("03/01/2015", date); 1643 | }); 1644 | }); 1645 | ``` 1646 | 1647 | **Dobro:** 1648 | 1649 | ```javascript 1650 | import assert from "assert"; 1651 | 1652 | describe("MomentJS", () => { 1653 | it("handles 30-day months", () => { 1654 | const date = new MomentJS("1/1/2015"); 1655 | date.addDays(30); 1656 | assert.equal("1/31/2015", date); 1657 | }); 1658 | 1659 | it("handles leap year", () => { 1660 | const date = new MomentJS("2/1/2016"); 1661 | date.addDays(28); 1662 | assert.equal("02/29/2016", date); 1663 | }); 1664 | 1665 | it("handles non-leap year", () => { 1666 | const date = new MomentJS("2/1/2015"); 1667 | date.addDays(28); 1668 | assert.equal("03/01/2015", date); 1669 | }); 1670 | }); 1671 | ``` 1672 | 1673 | **[⬆ vrati se na početak](#sadržaj)** 1674 | 1675 | ## **Asihronost** 1676 | 1677 | ### Koristite Obećanja (Promises), umesto povratnih poziva (callbacks) 1678 | 1679 | Povratni pozivi (callbacks) dovode do prekomernog gnježđenja i loše čitljivosti koda. 1680 | U ES2015/ES6 Promisi su ugrađeni kao globalni tipovi. Koristite ih! 1681 | 1682 | **Loše:** 1683 | 1684 | ```javascript 1685 | import { get } from "request"; 1686 | import { writeFile } from "fs"; 1687 | 1688 | get( 1689 | "https://en.wikipedia.org/wiki/Robert_Cecil_Martin", 1690 | (requestErr, response, body) => { 1691 | if (requestErr) { 1692 | console.error(requestErr); 1693 | } else { 1694 | writeFile("article.html", body, writeErr => { 1695 | if (writeErr) { 1696 | console.error(writeErr); 1697 | } else { 1698 | console.log("File written"); 1699 | } 1700 | }); 1701 | } 1702 | } 1703 | ); 1704 | ``` 1705 | 1706 | **Dobro:** 1707 | 1708 | ```javascript 1709 | import { get } from "request-promise"; 1710 | import { writeFile } from "fs-extra"; 1711 | 1712 | get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin") 1713 | .then(body => { 1714 | return writeFile("article.html", body); 1715 | }) 1716 | .then(() => { 1717 | console.log("File written"); 1718 | }) 1719 | .catch(err => { 1720 | console.error(err); 1721 | }); 1722 | ``` 1723 | 1724 | **[⬆ vrati se na početak](#sadržaj)** 1725 | 1726 | ### Async/Await čini kod čistijim više od Promisa 1727 | 1728 | Promisi su vrlo dobra alternativa za callback, ali ES2017/ES8 uvode async/await koje predstavlja još bolje rešenje. Sve što treba da uradite jeste da napišete funkciju sa `async` prefiksom u kojoj možete da koristite svoju asihronu logiku. 1729 | 1730 | **Loše:** 1731 | 1732 | ```javascript 1733 | import { get } from "request-promise"; 1734 | import { writeFile } from "fs-extra"; 1735 | 1736 | get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin") 1737 | .then(body => { 1738 | return writeFile("article.html", body); 1739 | }) 1740 | .then(() => { 1741 | console.log("File written"); 1742 | }) 1743 | .catch(err => { 1744 | console.error(err); 1745 | }); 1746 | ``` 1747 | 1748 | **Dobro:** 1749 | 1750 | ```javascript 1751 | import { get } from "request-promise"; 1752 | import { writeFile } from "fs-extra"; 1753 | 1754 | async function getCleanCodeArticle() { 1755 | try { 1756 | const body = await get( 1757 | "https://en.wikipedia.org/wiki/Robert_Cecil_Martin" 1758 | ); 1759 | await writeFile("article.html", body); 1760 | console.log("File written"); 1761 | } catch (err) { 1762 | console.error(err); 1763 | } 1764 | } 1765 | 1766 | getCleanCodeArticle() 1767 | ``` 1768 | 1769 | **[⬆ vrati se na početak](#sadržaj)** 1770 | 1771 | ## **Rad sa greškama** 1772 | 1773 | ### Ne ignorišite uhvaćene greške 1774 | 1775 | Ne radeći ništa sa uhvaćenom greškom, gubimo priliku da je ispravimo ili da na nju ikada reagujemo. Ako obmotovamo deo koda u `try/catch` onda sumnjamo da se tu može javiti greška, tada trebamo imati plan šta ćemo uraditi sa njom. 1776 | 1777 | **Loše:** 1778 | 1779 | ```javascript 1780 | try { 1781 | functionThatMightThrow(); 1782 | } catch (error) { 1783 | console.log(error); 1784 | } 1785 | ``` 1786 | 1787 | **Dobro:** 1788 | 1789 | ```javascript 1790 | try { 1791 | functionThatMightThrow(); 1792 | } catch (error) { 1793 | // One option (more noisy than console.log): 1794 | console.error(error); 1795 | // Another option: 1796 | notifyUserOfError(error); 1797 | // Another option: 1798 | reportErrorToService(error); 1799 | // OR do all three! 1800 | } 1801 | ``` 1802 | 1803 | ### Nemojte zanemariti ni odbijene (rejected) promise 1804 | 1805 | **Loše:** 1806 | 1807 | ```javascript 1808 | getdata() 1809 | .then(data => { 1810 | functionThatMightThrow(data); 1811 | }) 1812 | .catch(error => { 1813 | console.log(error); 1814 | }); 1815 | ``` 1816 | 1817 | **Dobro:** 1818 | 1819 | ```javascript 1820 | getdata() 1821 | .then(data => { 1822 | functionThatMightThrow(data); 1823 | }) 1824 | .catch(error => { 1825 | // One option (more noisy than console.log): 1826 | console.error(error); 1827 | // Another option: 1828 | notifyUserOfError(error); 1829 | // Another option: 1830 | reportErrorToService(error); 1831 | // OR do all three! 1832 | }); 1833 | ``` 1834 | 1835 | **[⬆ vrati se na početak](#sadržaj)** 1836 | 1837 | ## **Formatiranje** 1838 | 1839 | Formatiranje je subjektivno. Kao i mnogo pravila u ovom dokumentu, ne postoji čvrsto i brzo pravilo koje morate poštovati. Glavna poenta je da se ne svađate oko formatiranja! :) 1840 | Postoji [mnogo alata](https://standardjs.com/rules.html) za automatizaciju formatiranja. 1841 | 1842 | ### Budite dosledni prilikom imenovanja 1843 | 1844 | JavaScript je netipiziran, tako da vaš tim može izabrati imenovanje koje želi. Poenta je da bez obzira šta se odabere, budete dosledni. 1845 | 1846 | **Loše:** 1847 | 1848 | ```javascript 1849 | const DAYS_IN_WEEK = 7; 1850 | const daysInMonth = 30; 1851 | 1852 | const songs = ["Back In Black", "Stairway to Heaven", "Hey Jude"]; 1853 | const Artists = ["ACDC", "Led Zeppelin", "The Beatles"]; 1854 | 1855 | function eraseDatabase() {} 1856 | function restore_database() {} 1857 | 1858 | class animal {} 1859 | class Alpaca {} 1860 | ``` 1861 | 1862 | **Dobro:** 1863 | 1864 | ```javascript 1865 | const DAYS_IN_WEEK = 7; 1866 | const DAYS_IN_MONTH = 30; 1867 | 1868 | const SONGS = ["Back In Black", "Stairway to Heaven", "Hey Jude"]; 1869 | const ARTISTS = ["ACDC", "Led Zeppelin", "The Beatles"]; 1870 | 1871 | function eraseDatabase() {} 1872 | function restoreDatabase() {} 1873 | 1874 | class Animal {} 1875 | class Alpaca {} 1876 | ``` 1877 | 1878 | **[⬆ vrati se na početak](#sadržaj)** 1879 | 1880 | ### Srodne funkcije bi trebale biti u blizini 1881 | 1882 | Ako funkcija poziva drugu funkciju, držite te funkcije vertikalno blizu izvornoj funkciji. Idealno bi bilo da funkcija koja koristi drugu funkciju bude tačno iznad nje. Zato što obično čitamo od vrha do dna. 1883 | 1884 | **Loše:** 1885 | 1886 | ```javascript 1887 | class PerformanceReview { 1888 | constructor(employee) { 1889 | this.employee = employee; 1890 | } 1891 | 1892 | lookupPeers() { 1893 | return db.lookup(this.employee, "peers"); 1894 | } 1895 | 1896 | lookupManager() { 1897 | return db.lookup(this.employee, "manager"); 1898 | } 1899 | 1900 | getPeerReviews() { 1901 | const peers = this.lookupPeers(); 1902 | // ... 1903 | } 1904 | 1905 | perfReview() { 1906 | this.getPeerReviews(); 1907 | this.getManagerReview(); 1908 | this.getSelfReview(); 1909 | } 1910 | 1911 | getManagerReview() { 1912 | const manager = this.lookupManager(); 1913 | } 1914 | 1915 | getSelfReview() { 1916 | // ... 1917 | } 1918 | } 1919 | 1920 | const review = new PerformanceReview(employee); 1921 | review.perfReview(); 1922 | ``` 1923 | 1924 | **Dobro:** 1925 | 1926 | ```javascript 1927 | class PerformanceReview { 1928 | constructor(employee) { 1929 | this.employee = employee; 1930 | } 1931 | 1932 | perfReview() { 1933 | this.getPeerReviews(); 1934 | this.getManagerReview(); 1935 | this.getSelfReview(); 1936 | } 1937 | 1938 | getPeerReviews() { 1939 | const peers = this.lookupPeers(); 1940 | // ... 1941 | } 1942 | 1943 | lookupPeers() { 1944 | return db.lookup(this.employee, "peers"); 1945 | } 1946 | 1947 | getManagerReview() { 1948 | const manager = this.lookupManager(); 1949 | } 1950 | 1951 | lookupManager() { 1952 | return db.lookup(this.employee, "manager"); 1953 | } 1954 | 1955 | getSelfReview() { 1956 | // ... 1957 | } 1958 | } 1959 | 1960 | const review = new PerformanceReview(employee); 1961 | review.perfReview(); 1962 | ``` 1963 | 1964 | **[⬆ vrati se na početak](#sadržaj)** 1965 | 1966 | ## **Komentari** 1967 | 1968 | ### Kometarišite samo stvari koje imaju složenu poslovnu logiku 1969 | 1970 | Komentari nisu obavezni. Dobar kod se sam opisuje. 1971 | 1972 | **Loše:** 1973 | 1974 | ```javascript 1975 | function hashIt(data) { 1976 | // The hash 1977 | let hash = 0; 1978 | 1979 | // Length of string 1980 | const length = data.length; 1981 | 1982 | // Loop through every character in data 1983 | for (let i = 0; i < length; i++) { 1984 | // Get character code. 1985 | const char = data.charCodeAt(i); 1986 | // Make the hash 1987 | hash = (hash << 5) - hash + char; 1988 | // Convert to 32-bit integer 1989 | hash &= hash; 1990 | } 1991 | } 1992 | ``` 1993 | 1994 | **Dobro:** 1995 | 1996 | ```javascript 1997 | function hashIt(data) { 1998 | let hash = 0; 1999 | const length = data.length; 2000 | 2001 | for (let i = 0; i < length; i++) { 2002 | const char = data.charCodeAt(i); 2003 | hash = (hash << 5) - hash + char; 2004 | 2005 | // Convert to 32-bit integer 2006 | hash &= hash; 2007 | } 2008 | } 2009 | ``` 2010 | 2011 | **[⬆ vrati se na početak](#sadržaj)** 2012 | 2013 | ### Ne ostavljate zakomentarisan kod 2014 | 2015 | Ostavite stari kod u istoriji kontroli verzije *(version control)* 2016 | 2017 | **Loše:** 2018 | 2019 | ```javascript 2020 | doStuff(); 2021 | // doOtherStuff(); 2022 | // doSomeMoreStuff(); 2023 | // doSoMuchStuff(); 2024 | ``` 2025 | 2026 | **Dobro:** 2027 | 2028 | ```javascript 2029 | doStuff(); 2030 | ``` 2031 | 2032 | **[⬆ vrati se na početak](#sadržaj)** 2033 | 2034 | ### Ne vodite dnevnik komentara 2035 | 2036 | Zapamtite, koristite *(version control)*! Koristite `git log` da biste videli istoriju. 2037 | 2038 | **Loše:** 2039 | 2040 | ```javascript 2041 | /** 2042 | * 2016-12-20: Removed monads, didn't understand them (RM) 2043 | * 2016-10-01: Improved using special monads (JP) 2044 | * 2016-02-03: Removed type-checking (LI) 2045 | * 2015-03-14: Added combine with type-checking (JR) 2046 | */ 2047 | function combine(a, b) { 2048 | return a + b; 2049 | } 2050 | ``` 2051 | 2052 | **Dobro:** 2053 | 2054 | ```javascript 2055 | function combine(a, b) { 2056 | return a + b; 2057 | } 2058 | ``` 2059 | 2060 | **[⬆ vrati se na početak](#sadržaj)** 2061 | 2062 | ### Izbegavajte pozicione markere 2063 | 2064 | Neka funckije i imena promenljivih zajedno sa odgovarajućim formatiranjem daju vizalnu strukturu vašem kodu. 2065 | 2066 | **Loše:** 2067 | 2068 | ```javascript 2069 | //////////////////////////////////////////////////////////////////////////////// 2070 | // Scope Model Instantiation 2071 | //////////////////////////////////////////////////////////////////////////////// 2072 | $scope.model = { 2073 | menu: "foo", 2074 | nav: "bar" 2075 | }; 2076 | 2077 | //////////////////////////////////////////////////////////////////////////////// 2078 | // Action setup 2079 | //////////////////////////////////////////////////////////////////////////////// 2080 | const actions = function() { 2081 | // ... 2082 | }; 2083 | ``` 2084 | 2085 | **Dobro:** 2086 | 2087 | ```javascript 2088 | $scope.model = { 2089 | menu: "foo", 2090 | nav: "bar" 2091 | }; 2092 | 2093 | const actions = function() { 2094 | // ... 2095 | }; 2096 | ``` 2097 | 2098 | **[⬆ vrati se na početak](#sadržaj)** 2099 | 2100 | ## Prevod 2101 | 2102 | Dostupno i na drugim jezicima 2103 | 2104 | - ![am](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Armenia.png) **Armenian**: [hanumanum/clean-code-javascript/](https://github.com/hanumanum/clean-code-javascript) 2105 | - ![bd](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Bangladesh.png) **Bangla(বাংলা)**: [InsomniacSabbir/clean-code-javascript/](https://github.com/InsomniacSabbir/clean-code-javascript/) 2106 | - ![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) 2107 | - ![cn](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/China.png) **Simplified Chinese**: 2108 | - [alivebao/clean-code-js](https://github.com/alivebao/clean-code-js) 2109 | - [beginor/clean-code-javascript](https://github.com/beginor/clean-code-javascript) 2110 | - ![tw](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Taiwan.png) **Traditional Chinese**: [AllJointTW/clean-code-javascript](https://github.com/AllJointTW/clean-code-javascript) 2111 | - ![fr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/France.png) **French**: [GavBaros/clean-code-javascript-fr](https://github.com/GavBaros/clean-code-javascript-fr) 2112 | - ![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) 2113 | - ![id](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Indonesia.png) **Indonesia**: [andirkh/clean-code-javascript/](https://github.com/andirkh/clean-code-javascript/) 2114 | - ![it](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Italy.png) **Italian**: [frappacchio/clean-code-javascript/](https://github.com/frappacchio/clean-code-javascript/) 2115 | - ![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/) 2116 | - ![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) 2117 | - ![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) 2118 | - ![ru](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Russia.png) **Russian**: 2119 | - [BoryaMogila/clean-code-javascript-ru/](https://github.com/BoryaMogila/clean-code-javascript-ru/) 2120 | - [maksugr/clean-code-javascript](https://github.com/maksugr/clean-code-javascript) 2121 | - ![es](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Spain.png) **Spanish**: [tureey/clean-code-javascript](https://github.com/tureey/clean-code-javascript) 2122 | - ![es](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Uruguay.png) **Spanish**: [andersontr15/clean-code-javascript](https://github.com/andersontr15/clean-code-javascript-es) 2123 | - ![tr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Turkey.png) **Turkish**: [bsonmez/clean-code-javascript](https://github.com/bsonmez/clean-code-javascript/tree/turkish-translation) 2124 | - ![ua](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Ukraine.png) **Ukrainian**: [mindfr1k/clean-code-javascript-ua](https://github.com/mindfr1k/clean-code-javascript-ua) 2125 | - ![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/) 2126 | 2127 | **[⬆ vrati se na početak](#sadržaj)** 2128 | --------------------------------------------------------------------------------