Clean Code Javascript Pl Save

馃泚 Clean Code concepts adapted for JavaScript (Polish) 馃嚨馃嚤 Czysty kod JavaScript, polskie t艂umaczenie

Project README

Original Repository: ryanmcdermott/clean-code-javascript

Czysty kod JavaScript

Spis tre艣ci

  1. Wprowadzenie
  2. Zmienne
  3. Funkcje
  4. Obiekty i struktury danych
  5. Klasy
  6. SOLID
  7. Testowanie
  8. Wsp贸艂bie偶no艣膰
  9. Obs艂uga b艂臋d贸w
  10. Formatowanie
  11. Komentarze
  12. T艂umaczenie

Wprowadzenie

Humorystyczny obrazek przedstawiaj膮cy ocen臋 jako艣ci oprogramowania za pomoc膮 ilo艣ci przekle艅stw
wykrzyczanych podczas czytania kodu

Zasady in偶ynierii oprogramowania z ksi膮偶ki Roberta C. Martina Czysty kod, dostosowane do j臋zyka JavaScript. Nie s膮 to wytyczne dotycz膮ce stylu. To wytyczne do tworzenia czytelnego, prostego w refaktoryzacji i wielokrotnym u偶yciu oprogramowania w j臋zyku JavaScript.

Nie ka偶da z podanych tu zasad musi by膰 艣ci艣le przestrzegana i nie wszystkie z nich b臋d膮 powszechnie przyj臋te. To nic wi臋cej, ni偶 wskaz贸wki zebrane dzi臋ki wieloletniemu do艣wiadczeniu autor贸w Czystego kodu.

In偶ynieria oprogramowania ma troch臋 ponad 50 lat i nadal wiele si臋 uczymy. Gdy architektura oprogramowania b臋dzie tak stara, jak sama architektura, wtedy mo偶e b臋dziemy mieli trudniejsze zasady do przestrzegania. Na razie niech te wytyczne s艂u偶膮 jako podstawa do oceny jako艣ci kodu JavaScript, kt贸ry Ty i Tw贸j zesp贸艂 tworzycie.

Jeszcze jedno: poznanie zasad nie zrobi z Ciebie lepszego programisty w mgnieniu oka, a wieloletnia praca zgodnie z nimi nie sprawi, 偶e przestaniesz pope艂nia膰 b艂臋dy. Ka偶dy kawa艂ek kodu zaczyna si臋 od wst臋pnego szkicu i jest jak mokra glina formowana do ostatecznego kszta艂tu. Wreszcie niczym rze藕biarz d艂utem usuwamy wszelkie niedoskona艂o艣ci podczas przegl膮du kodu wsp贸lnie z kolegami. Nie zadr臋czaj si臋 wst臋pnymi szkicami wymagaj膮cymi poprawek. Zamiast tego m臋cz kod!

Zmienne

U偶ywaj znacz膮cych i wymawialnych nazw zmiennych

殴le:

const yyyymmdstr = moment().format('YYYY/MM/DD');

Dobrze:

const currentDate = moment().format('YYYY/MM/DD');

猬 powr贸t na pocz膮tek

U偶ywaj tego samego s艂ownictwa dla tego samego rodzaju danych

殴le:

getUserInfo();
getClientData();
getCustomerRecord();

Dobrze:

getUser();

猬 powr贸t na pocz膮tek

U偶ywaj odnajdywalnych nazw

B臋dziemy czyta膰 wi臋cej kodu, ni偶 kiedykolwiek napiszemy. Wa偶ne jest, aby nasz kod by艂 czytelny i przeszukiwalny. Nie nazywaj膮c zmiennych, maj膮cych znaczenie dla zrozumienia naszego programu, krzywdzimy czytaj膮cych. Spraw, by Twoje nazwy by艂y odnajdywalne. Narz臋dzia takie jak buddy.js i ESLint mog膮 pom贸c zidentyfikowa膰 nienazwane sta艂e.

殴le:

// Czym do diaska jest 86400000?
setTimeout(blastOff, 86400000);

Dobrze:

// Zadeklaruj j膮 jako nazwan膮 sta艂膮, u偶ywaj膮c wielkich liter.
const MILLISECONDS_IN_A_DAY = 86400000;

setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

猬 powr贸t na pocz膮tek

U偶ywaj zmiennych wyja艣niaj膮cych

殴le:

const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]);

Dobrze:

const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);

猬 powr贸t na pocz膮tek

Unikaj map mentalnych

Jasne jest lepsze ni偶 niejasne.

殴le:

const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l) => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // Czekaj, czym by艂o `l`?
  dispatch(l);
});

Dobrze:

const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location) => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch(location);
});

猬 powr贸t na pocz膮tek

Nie dodawaj niepotrzebnego kontekstu

Je艣li nazwa Twojej klasy/obiektu co艣 m贸wi, nie powtarzaj tego w nazwie zmiennej.

殴le:

const Car = {
  carMake: 'Honda',
  carModel: 'Accord',
  carColor: 'Blue'
};

function paintCar(car) {
  car.carColor = 'Red';
}

Dobrze:

const Car = {
  make: 'Honda',
  model: 'Accord',
  color: 'Blue'
};

function paintCar(car) {
  car.color = 'Red';
}

猬 powr贸t na pocz膮tek

U偶ywaj domy艣lnych warto艣ci argument贸w zamiast wykona艅 warunkowych lub warunk贸w

Domy艣lne warto艣ci argument贸w s膮 zwykle ja艣niejsze ni偶 wykonania warunkowe. Pami臋taj, 偶e je艣li ich u偶yjesz, Twoja funkcja dostarczy domy艣lne warto艣ci tylko dla argument贸w niezdefiniowanych (undefined). Inne "fa艂szywe" warto艣ci, takie jak '', "", false, null, 0 i NaN, nie b臋d膮 zast膮pione warto艣ci膮 domy艣ln膮.

殴le:

function createMicrobrewery(name) {
  const breweryName = name || 'Hipster Brew Co.';
  // ...
}

Dobrze:

function createMicrobrewery(breweryName = 'Hipster Brew Co.') {
  // ...
}

猬 powr贸t na pocz膮tek

Funkcje

Parametry funkcji (najlepiej 2 lub mniej)

Ograniczanie ilo艣ci parametr贸w funkcji jest niezwykle wa偶ne, gdy偶 czyni testowanie Twojej funkcji prostszym. Maj膮c wi臋cej ni偶 trzy, prowadzisz do eksplozji kombinatorycznej, w kt贸rej musisz przetestowa膰 mas臋 r贸偶nych przypadk贸w osobno z ka偶dym kolejnym parametrem.

Najlepiej, je艣li jest jeden lub dwa parametry, trzy powinny by膰 ju偶 w miar臋 mo偶liwo艣ci unikane. Wi臋ksza ilo艣膰 powinna by膰 skonsolidowana. Zwykle, gdy masz wi臋cej ni偶 dwa parametry, Twoja funkcja pr贸buje zrobi膰 za du偶o. W przypadkach, gdy tak nie jest, zazwyczaj obiekt wy偶szego rz臋du b臋dzie wystarczaj膮cym parametrem.

Jako 偶e JavaScript pozwala tworzy膰 obiekty w locie i bez konieczno艣ci u偶ycia kodu zwi膮zanego z klasami, zawsze mo偶esz u偶y膰 obiektu, gdy czujesz, 偶e potrzebujesz du偶o parametr贸w.

Aby by艂o oczywistym, jakich parametr贸w oczekuje funkcja, mo偶esz u偶y膰 destrukturyzacji wprowadzonej w wersji ES2015/ES6. Ma to kilka zalet:

  1. Gdy kto艣 popatrzy na sygnatur臋 funkcji, od razu b臋dzie mie膰 jasno艣膰, jakie w艂a艣ciwo艣ci b臋d膮 wykorzystywane.
  2. Destrukturyzacja klonuje okre艣lone prymitywne warto艣ci argument贸w obiektu przekazywanego do funkcji. Mo偶e to pom贸c w unikni臋ciu efekt贸w ubocznych. Uwaga: obiekty i tablice b臋d膮ce wynikiem destrukturyzacji argument贸w obiektu NIE b臋d膮 klonowane.
  3. Lintery mog膮 ostrzec Ci臋 przed nieu偶ywanymi zmiennymi, co b臋dzie niemo偶liwe bez destrukturyzacji.

殴le:

function createMenu(title, body, buttonText, cancellable) {
  // ...
}

Dobrze:

function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

createMenu({
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
});

猬 powr贸t na pocz膮tek

Funkcja powinna wykonywa膰 tylko jedno zadanie

Jest to zdecydowanie najwa偶niejsza zasada w in偶ynierii oprogramowania. Gdy funkcje wykonuj膮 wi臋cej ni偶 jedno zadanie, s膮 trudniejsze w kompozycji, testowaniu i zrozumieniu. Je艣li mo偶esz ograniczy膰 dzia艂anie funkcji do tylko jednego zadania, b臋dzie ona prostsza w refaktoryzacji, a Tw贸j kod czytelniejszy. Nawet je艣li nie wyniesiesz z tego przewodnika nic wi臋cej poza tym, to i tak b臋dziesz do przodu w stosunku do wielu deweloper贸w.

殴le:

function emailClients(clients) {
  clients.forEach((client) => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

Dobrze:

function emailActiveClients(clients) {
  clients
    .filter(isActiveClient)
    .forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

猬 powr贸t na pocz膮tek

Nazwy funkcji powinny m贸wi膰 co one robi膮

殴le:

function addToDate(date, month) {
  // ...
}

const date = new Date();

// Z nazwy funkcji trudno wywnioskowa膰, co jest dodawane
addToDate(date, 1);

Dobrze:

function addMonthToDate(month, date) {
  // ...
}

const date = new Date();
addMonthToDate(1, date);

猬 powr贸t na pocz膮tek

Funkcje powinny by膰 tylko jednym poziomem abstrakcji

Gdy masz wi臋cej ni偶 jeden poziom abstrakcji, Twoja funkcja zwykle robi za du偶o. Podzielenie funkcji umo偶liwi wielokrotne u偶ycie kodu i u艂atwi jego testowanie.

殴le:

function parseBetterJSAlternative(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(' ');
  const tokens = [];
  REGEXES.forEach((REGEX) => {
    statements.forEach((statement) => {
      // ...
    });
  });

  const ast = [];
  tokens.forEach((token) => {
    // lex...
  });

  ast.forEach((node) => {
    // parse...
  });
}

Dobrze:

function tokenize(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(' ');
  const tokens = [];
  REGEXES.forEach((REGEX) => {
    statements.forEach((statement) => {
      tokens.push( /* ... */ );
    });
  });

  return tokens;
}

function lexer(tokens) {
  const ast = [];
  tokens.forEach((token) => {
    ast.push( /* ... */ );
  });

  return ast;
}

function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const ast = lexer(tokens);
  ast.forEach((node) => {
    // parse...
  });
}

猬 powr贸t na pocz膮tek

Usu艅 powielony kod

R贸b wszystko, co tylko mo偶esz, aby unikn膮膰 powielania kodu. Powielony kod jest z艂y, gdy偶 oznacza, 偶e jest wi臋cej ni偶 jedno miejsce do zmodyfikowania, gdy potrzebujesz zmieni膰 troch臋 logiki.

Wyobra藕 sobie, 偶e prowadzisz restauracj臋 i sprawujesz nadz贸r nad swoimi zapasami: wszystkie pomidory, cebule, czosnek, przyprawy itd. Je艣li masz je spisane w wielu miejscach, to wszystkie z nich musz膮 zosta膰 uaktualnione, gdy serwujesz danie z pomidorami. Maj膮c jedn膮 list臋, do uaktualnienia jest tylko jedno miejsce!

Cz臋stokro膰 powielasz kod, gdy偶 musisz rozwi膮za膰 dwa lub wi臋cej nieznacznie r贸偶nych problem贸w, maj膮cych ze sob膮 wiele wsp贸lnego, a ich r贸偶nice wymuszaj膮 na Tobie posiadanie dw贸ch lub wi臋cej oddzielnych funkcji robi膮cych wiele tego samego. Usuni臋cie powielonego kodu oznacza utworzenie abstrakcji, mog膮cej obs艂u偶y膰 ten zestaw r贸偶nych problem贸w za pomoc膮 tylko jednej funkcji/modu艂u/klasy.

Poprawne u偶ycie abstrakcji jest istotne, dlatego te偶 powiniene艣 przestrzega膰 zasad SOLID, znajduj膮cych si臋 w rozdziale Klasy. Z艂a abstrakcja mo偶e by膰 gorsza, ni偶 powielony kod, b膮d藕 wi臋c ostro偶ny! Je艣li mo偶esz zastosowa膰 dobr膮 abstrakcj臋, zr贸b to! Nie powtarzaj si臋, w przeciwnym razie sko艅czysz uaktualniaj膮c wiele miejsc za ka偶dym razem, gdy chcesz zmieni膰 jedn膮 rzecz.

殴le:

function showDeveloperList(developers) {
  developers.forEach((developer) => {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink();
    const data = {
      expectedSalary,
      experience,
      githubLink
    };

    render(data);
  });
}

function showManagerList(managers) {
  managers.forEach((manager) => {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects();
    const data = {
      expectedSalary,
      experience,
      portfolio
    };

    render(data);
  });
}

Dobrze:

function showEmployeeList(employees) {
  employees.forEach((employee) => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();

    const data = {
      expectedSalary,
      experience
    };

    switch (employee.type) {
      case 'manager':
        data.portfolio = employee.getMBAProjects();
        break;
      case 'developer':
        data.githubLink = employee.getGithubLink();
        break;
    }

    render(data);
  });
}

猬 powr贸t na pocz膮tek

Ustawiaj domy艣lne obiekty u偶ywaj膮c Object.assign

殴le:

const menuConfig = {
  title: null,
  body: 'Bar',
  buttonText: null,
  cancellable: true
};

function createMenu(config) {
  config.title = config.title || 'Foo';
  config.body = config.body || 'Bar';
  config.buttonText = config.buttonText || 'Baz';
  config.cancellable = config.cancellable !== undefined ? config.cancellable : true;
}

createMenu(menuConfig);

Dobrze:

const menuConfig = {
  title: 'Order',
  // U偶ytkownik nie uwzgl臋dni艂 klucza 'body'
  buttonText: 'Send',
  cancellable: true
};

function createMenu(config) {
  config = Object.assign({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
  }, config);

  // config ma teraz warto艣膰: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

猬 powr贸t na pocz膮tek

Nie u偶ywaj flag jako argument贸w funkcji

Flagi m贸wi膮 u偶ytkownikowi, 偶e dana funkcja robi wi臋cej ni偶 jedn膮 rzecz. Funkcje powinny robi膰 jedn膮 rzecz. Rozdziel swoje funkcje, je艣li ich kod pod膮偶a innymi 艣cie偶kami zale偶nie od zmiennej boolowskiej.

殴le:

function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

Dobrze:

function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}

猬 powr贸t na pocz膮tek

Unikaj skutk贸w ubocznych (cz臋艣膰 1)

Funkcja daje skutki uboczne, gdy robi cokolwiek innego ni偶 pobranie jednej warto艣ci i zwr贸cenie innej lub innych. Skutkiem ubocznym mo偶e by膰 zapis do pliku, zmodyfikowanie jakiej艣 zmiennej globalnej, lub te偶 przypadkowe przelanie wszystkich Twoich pieni臋dzy nieznajomemu.

Czasem potrzebujesz skutk贸w ubocznych w programie. Jak w poprzednim przyk艂adzie mo偶esz potrzebowa膰 zapisu do pliku. Tym, co chcesz zrobi膰, jest znalezienie jednego miejsca, gdzie go umie艣cisz. Nie miej kilku funkcji i klas, kt贸re zapisuj膮 do poszczeg贸lnych plik贸w. Miej jedn膮 us艂ug臋, kt贸ra to robi. Jedn膮 i tylko jedn膮.

G艂贸wn膮 kwesti膮 jest unikanie powszechnych pu艂apek, takich jak dzielenie stanu mi臋dzy obiektami bez jakiejkolwiek struktury, u偶ycie mutowalnych typ贸w danych, kt贸re mog膮 by膰 nadpisane przez cokolwiek i nieokre艣lenie jednego miejsca daj膮cego skutki uboczne. Je艣li mo偶esz to zrobi膰, b臋dziesz szcz臋艣liwszy, ni偶 zdecydowana wi臋kszo艣膰 programist贸w.

殴le:

// Globalna zmienna po kt贸rej nast臋puja funkcja, kt贸ra si臋 do niej odnosi
// Je艣li b臋dziemy mie膰 kolejn膮 funckj臋 u偶ywaj膮c膮 tej nazwy, teraz b臋dzie ona tablic膮 i mo偶e spowodowa膰 b艂膮d.
let name = 'Ryan McDermott';

function splitIntoFirstAndLastName() {
  name = name.split(' ');
}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];

Dobrze:

function splitIntoFirstAndLastName(name) {
  return name.split(' ');
}

const name = 'Ryan McDermott';
const newName = splitIntoFirstAndLastName(name);

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

猬 powr贸t na pocz膮tek

Unikaj skutk贸w ubocznych (cz臋艣膰 2)

W j臋zyku JavaScript typy proste przekazywane s膮 przez warto艣膰, a obiekty/tablice przez referencj臋. W przypadku obiekt贸w i tablic, je艣li funkcja dokona zmiany w tablicy koszyka z zakupami, na przyk艂ad przez dodanie produktu, to inna funkcja u偶ywaj膮ca tej tablicy koszka cart b臋dzie t膮 zmian膮 dotkni臋ta. Mo偶e to by膰 dobre, jednak mo偶e te偶 by膰 i z艂e. Wyobra藕 sobie z艂膮 sytuacj臋:

U偶ytkownik klika przycisk "Kup" wywo艂uj膮cy funkcj臋 purchase, kt贸ra tworzy 偶膮danie i wysy艂a tablic臋 cart do serwera. Z powodu s艂abego po艂膮czenia sieciowego, funkcja purchase musi powtarza膰 偶膮danie. Co je艣li w mi臋dzyczasie u偶ytkownik przypadkowo kliknie przycisk "Dodaj do koszyka" na produkcie, kt贸ry nie by艂 dodany wcze艣niej? Je艣li tak si臋 wydarzy i 偶膮danie zostanie wys艂ane, wtedy funkcja kupuj膮ca wy艣le przypadkowo dodany produkt, gdy偶 posiada ona referencj臋 do tablicy koszyka zakup贸w zmodyfikowan膮 przez funkcj臋 addItemToCart poprzez dodanie niechcianego produktu.

艢wietnym rozwi膮zaniem by艂oby, aby addItemToCart zawsze klonowa艂a cart, edytowa艂a i zwraca艂a sklonowan膮 tablic臋. To zapewnia, 偶e 偶adna inna funkcja przechowuj膮ca referencj臋 do koszyka zakup贸w b臋dzie dotkni臋ta jak膮kolwiek zmian膮.

Dwa zastrze偶enia do tego podej艣cia:

  1. Mog膮 wyst臋powa膰 przypadki, w kt贸rych rzeczywi艣cie chcesz zmodyfikowa膰 wej艣ciowy obiekt, ale gdy zaczniesz stosowa膰 t臋 praktyk臋 w programowaniu, przekonasz si臋, 偶e s膮 one do艣膰 rzadkie. Wi臋kszo艣膰 mo偶e by膰 zrefaktoryzowana bez skutk贸w ubocznych!

  2. Klonowanie du偶ych obiekt贸w mo偶e by膰 kosztowne pod wzgl臋dem wydajno艣ci. Na szcz臋艣cie w praktyce nie jest to du偶y problem, gdy偶 mamy 艣wietne biblioteki pozwalaj膮ce takiemu podej艣ciu do programowania by膰 szybkim i nieobci膮偶aj膮cym pami臋ci a偶 tak, jak by to by艂o w przypadku r臋cznego klonowania obiekt贸w i tablic.

殴le:

const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

Dobrze:

const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};

猬 powr贸t na pocz膮tek

Nie pisz do funkcji globalnych

Zanieczyszczanie globalnej przestrzeni jest z艂膮 praktyk膮 w j臋zyku JavaScript, gdy偶 mo偶esz kolidowa膰 z inn膮 bibliotek膮 i u偶ytkownik Twojego API mo偶e by膰 niczego nie艣wiadomym, dop贸ki wyj膮tek nie pojawi si臋 na produkcji. Pomy艣lmy o takim przyk艂adzie: co je艣li chcia艂by艣 rozszerzy膰 natywny obiekt Array, aby mia艂 metod臋 diff, kt贸ra mo偶e pokaza膰 r贸偶nic臋 mi臋dzy dwiema tablicami? M贸g艂by艣 przypisa膰 swoj膮 now膮 funkcj臋 do Array.prototype, ale mo偶e to kolidowa膰 z inn膮 bibliotek膮, kt贸ra pr贸bowa艂a zrobi膰 to samo. Co je艣li inna biblioteka u偶ywa艂a diff do znalezienia r贸偶nicy mi臋dzy pierwszym i ostatnim elementem tablicy? W艂a艣nie dlatego by艂oby du偶o lepiej u偶ywa膰 po prostu klas wprowadzonych w wersji ES2015/ES6 i zwyczajnie rozszerzy膰 globaln膮 Array.

殴le:

Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};

Dobrze:

class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}

猬 powr贸t na pocz膮tek

Przedk艂adaj programowanie funkcyjne nad programowanie imperatywne

JavaScript nie jest j臋zykiem funkcyjnym w takim stopniu, jak Haskell, ale zawiera troch臋 funkcyjnego aromatu. J臋zyki funkcyjne mog膮 by膰 czystsze i prostsze w testowaniu. Preferuj ten styl programowania, kiedy tylko mo偶esz.

殴le:

const programmerOutput = [
  {
    name: 'Uncle Bobby',
    linesOfCode: 500
  }, {
    name: 'Suzie Q',
    linesOfCode: 1500
  }, {
    name: 'Jimmy Gosling',
    linesOfCode: 150
  }, {
    name: 'Gracie Hopper',
    linesOfCode: 1000
  }
];

let totalOutput = 0;

for (let i = 0; i < programmerOutput.length; i++) {
  totalOutput += programmerOutput[i].linesOfCode;
}

Dobrze:

const programmerOutput = [
  {
    name: 'Uncle Bobby',
    linesOfCode: 500
  }, {
    name: 'Suzie Q',
    linesOfCode: 1500
  }, {
    name: 'Jimmy Gosling',
    linesOfCode: 150
  }, {
    name: 'Gracie Hopper',
    linesOfCode: 1000
  }
];

const totalOutput = programmerOutput
  .map(output => output.linesOfCode)
  .reduce((totalLines, lines) => totalLines + lines);

猬 powr贸t na pocz膮tek

Stosuj hermetyzacj臋 warunk贸w

殴le:

if (fsm.state === 'fetching' && isEmpty(listNode)) {
  // ...
}

Dobrze:

function shouldShowSpinner(fsm, listNode) {
  return fsm.state === 'fetching' && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

猬 powr贸t na pocz膮tek

Unikaj negowania warunk贸w

殴le:

function isDOMNodeNotPresent(node) {
  // ...
}

if (!isDOMNodeNotPresent(node)) {
  // ...
}

Dobrze:

function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}

猬 powr贸t na pocz膮tek

Unikaj warunk贸w

Wydaje si臋 to by膰 zadaniem niewykonalnym. Wi臋kszo艣膰 ludzi s艂ysz膮c to pierwszy raz, powie: "Jak mam zrobi膰 cokolwiek bez instrukcji if?" Odpowiedzi膮 jest, 偶e mo偶esz u偶y膰 polimorfizmu, aby osi膮gn膮膰 to samo w wielu przypadkach. Drugim pytaniem jest zwykle: "Dobrze, to 艣wietnie, ale dlaczego chcia艂bym to zrobi膰?" Odpowiedzi膮 jest poprzednio poznana koncepcja czystego kodu: funkcja powinna robi膰 tylko jedn膮 rzecz. Gdy masz klasy i funkcje zawieraj膮ce instrukcje if, m贸wisz u偶ytkownikowi, 偶e Twoja funkcja robi wi臋cej, ni偶 jedn膮 rzecz. Pami臋taj, r贸b po prostu jedn膮 rzecz.

殴le:

class Airplane {
  // ...
  getCruisingAltitude() {
    switch (this.type) {
      case '777':
        return this.getMaxAltitude() - this.getPassengerCount();
      case 'Air Force One':
        return this.getMaxAltitude();
      case 'Cessna':
        return this.getMaxAltitude() - this.getFuelExpenditure();
    }
  }
}

Dobrze:

class Airplane {
  // ...
}

class Boeing777 extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getPassengerCount();
  }
}

class AirForceOne extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude();
  }
}

class Cessna extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getFuelExpenditure();
  }
}

猬 powr贸t na pocz膮tek

Unikaj sprawdzania typ贸w (cz臋艣膰 1)

JavaScript jest typowany dynamicznie, co oznacza, 偶e Twoje funkcje mog膮 przyjmowa膰 argumenty dowolnego typu. Czasami ta wolno艣膰 jest uci膮偶liwa i sprawdzanie typ贸w w Twoich funkcjach okazuje si臋 kusz膮ce. Jest wiele sposob贸w, aby tego unikn膮膰. Pierwszym jest rozwa偶enie zwartych API.

殴le:

function travelToTexas(vehicle) {
  if (vehicle instanceof Bicycle) {
    vehicle.pedal(this.currentLocation, new Location('texas'));
  } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, new Location('texas'));
  }
}

Dobrze:

function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location('texas'));
}

猬 powr贸t na pocz膮tek

Unikaj sprawdzania typ贸w (cz臋艣膰 2)

Je艣li pracujesz z podstawowymi warto艣ciami jak ci膮gi znak贸w i tablice i nie mo偶esz u偶y膰 polimorfizmu, a nadal czujesz potrzeb臋 sprawdzania typ贸w, powiniene艣 rozwa偶y膰 u偶ycie j臋zyka TypeScript. Jest on 艣wietn膮 alternatyw膮 dla normalnego j臋zyka JavaScript, gdy偶 dostarcza statycznego typowania do jego standardowej sk艂adni. Problemem z r臋cznym sprawdzaniem typ贸w w normalnym JavaScript jest to, 偶e u偶ywanie go poprawnie wymaga na tyle du偶o dodatkowej rozwlek艂o艣ci, i偶 otrzymane sztuczne bezpiecze艅stwo typologiczne nie wynagradza utraty czytelno艣ci. Utrzymuj Tw贸j JavaScript czystym, pisz dobre testy i miej dobre przegl膮dy kodu. W przeciwnym razie r贸b wszystko to samo, ale u偶ywaj膮c TypeScript (kt贸ry, jak powiedzia艂em, jest 艣wietn膮 alternatyw膮!).

殴le:

function combine(val1, val2) {
  if (typeof val1 === 'number' && typeof val2 === 'number' ||
      typeof val1 === 'string' && typeof val2 === 'string') {
    return val1 + val2;
  }

  throw new Error('Must be of type String or Number');
}

Dobrze:

function combine(val1, val2) {
  return val1 + val2;
}

猬 powr贸t na pocz膮tek

Nie optymalizuj nadmiernie

Nowoczesne przegl膮darki w czasie wykonania dokonuj膮 wielu optymalizacji "pod mask膮". Cz臋sto gdy optymalizujesz, po prostu tracisz sw贸j czas. Tu s膮 dobre 藕r贸d艂a pokazuj膮ca niedostatki optymalizacji. Postaw je sobie za cel w mi臋dzyczasie, dop贸ki nie b臋d膮 poprawione, je艣li mog膮 by膰.

殴le:


// W starych przegl膮darkach ka偶da iteracja z niebuforowanym `list.length` by艂aby kosztowna
// w zwi膮zku z ponownym obliczeniem `list.length`. W nowoczesnych przegl膮darkach jest to zoptymalizowane.
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

Dobrze:

for (let i = 0; i < list.length; i++) {
  // ...
}

猬 powr贸t na pocz膮tek

Usuwaj martwy kod

Martwy kod jest po prostu tak samo z艂y, jak powielony kod. Nie ma powodu, aby go trzyma膰. Je艣li nie b臋dzie wywo艂ywany, pozb膮d藕 si臋 go! B臋dzie nadal bezpieczny w historii Twojego systemu wersjonowania, je艣li ci膮gle go potrzebujesz.

殴le:

function oldRequestModule(url) {
  // ...
}

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');

Dobrze:

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');

猬 powr贸t na pocz膮tek

Obiekty i struktury danych

U偶ywaj getter贸w i setter贸w

U偶ywanie getter贸w i setter贸w, aby uzyska膰 dost臋p do danych obiekt贸w, mo偶e by膰 lepsze, ni偶 zwyk艂e sprawdzanie w艂a艣ciwo艣ci obiektu. Mo偶esz spyta膰: "Dlaczego?". Hmmm... oto niekt贸re z powod贸w:

  • Je艣li chcesz robi膰 co艣 ponad pobieranie w艂a艣ciwo艣ci obiektu, nie musisz sprawdza膰 i zmienia膰 ka偶dego akcesora w swoim kodzie.
  • Dodanie walidacji jest proste podczas wykonywania set.
  • Hermetyzuje wewn臋trzn膮 reprezentacj臋.
  • 艁atwo doda膰 logowanie i obs艂ug臋 b艂臋d贸w podczas pobierania i ustawiania.
  • Mo偶esz zastosowa膰 leniwe 艂adowanie w艂a艣ciwo艣ci Twojego obiektu, przyk艂adowo, pobieraj膮c je z serwera.

殴le:

function makeBankAccount() {
  // ...

  return {
    balance: 0,
    // ...
  };
}

const account = makeBankAccount();
account.balance = 100;

Dobrze:

function makeBankAccount() {
  // ta jest prywatna
  let balance = 0;

  // "getter" udost臋pniony publicznie przez zwr贸cenie obiektu poni偶ej
  function getBalance() {
    return balance;
  }

  // "setter" udost臋pniony publicznie przez zwr贸cenie obiektu poni偶ej
  function setBalance(amount) {
    // ... walidacja przed uaktualnieniem zmiennej "balance"
    balance = amount;
  }

  return {
    // ...
    getBalance,
    setBalance,
  };
}

const account = makeBankAccount();
account.setBalance(100);

猬 powr贸t na pocz膮tek

U偶ywaj prywatnych w艂a艣ciwo艣ci i metod obiekt贸w

Mo偶na to osi膮gn膮膰 dzi臋ki domkni臋ciom (dla wersji ES5 i ni偶szych).

殴le:


const Employee = function(name) {
  this.name = name;
};

Employee.prototype.getName = function getName() {
  return this.name;
};

const employee = new Employee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined

Dobrze:

function makeEmployee(name) {
  return {
    getName() {
      return name;
    },
  };
}

const employee = makeEmployee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe

猬 powr贸t na pocz膮tek

Klasy

Przedk艂adaj klasy wprowadzone w ES2015/ES6 ponad proste funkcje jak w ES5

Trudno uzyska膰 czytelne dziedziczenie, konstrukcj臋 i definicje metod klasycznymi technikami dost臋pnymi w ES5. Gdy potrzebujesz dziedziczenia (a b膮d藕 艣wiadom, 偶e mo偶e nie musisz), wykorzystuj klasy wprowadzone w ES2015/ES6. Niemniej jednak przedk艂adaj ma艂e funkcje ponad klasy, dop贸ki nie b臋dziesz potrzebowa艂 wi臋kszych i bardziej z艂o偶onych obiekt贸w.

殴le:

const Animal = function(age) {
  if (!(this instanceof Animal)) {
    throw new Error('Instantiate Animal with `new`');
  }

  this.age = age;
};

Animal.prototype.move = function move() {};

const Mammal = function(age, furColor) {
  if (!(this instanceof Mammal)) {
    throw new Error('Instantiate Mammal with `new`');
  }

  Animal.call(this, age);
  this.furColor = furColor;
};

Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};

const Human = function(age, furColor, languageSpoken) {
  if (!(this instanceof Human)) {
    throw new Error('Instantiate Human with `new`');
  }

  Mammal.call(this, age, furColor);
  this.languageSpoken = languageSpoken;
};

Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};

Dobrze:

class Animal {
  constructor(age) {
    this.age = age;
  }

  move() { /* ... */ }
}

class Mammal extends Animal {
  constructor(age, furColor) {
    super(age);
    this.furColor = furColor;
  }

  liveBirth() { /* ... */ }
}

class Human extends Mammal {
  constructor(age, furColor, languageSpoken) {
    super(age, furColor);
    this.languageSpoken = languageSpoken;
  }

  speak() { /* ... */ }
}

猬 powr贸t na pocz膮tek

Wykorzystuj 艂a艅cuchowanie metod

Wzorzec ten jest bardzo przydatny w j臋zyku JavaScript i wida膰 to w wielu bibliotekach takich jak jQuery i Lodash. Pozwala on uzyska膰 kod ekspresywny i mniej rozwlek艂y. W zwi膮zku z tym m贸wi臋 - u偶yj 艂a艅cuchowania metod i popatrz, jak czysty b臋dzie Tw贸j kod. W funkcjach Twoich klas zwyczajnie zwracaj this na ko艅cu ka偶dej funkcji i b臋dziesz m贸g艂 doczepi膰 do nich (jak ogniwa 艂a艅cucha) inne metody klasy.

殴le:

class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
  }

  setModel(model) {
    this.model = model;
  }

  setColor(color) {
    this.color = color;
  }

  save() {
    console.log(this.make, this.model, this.color);
  }
}

const car = new Car('Ford','F-150','red');
car.setColor('pink');
car.save();

Dobrze:

class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
    // ZAUWA呕: Zwracamy this dla 艂a艅cuchowania
    return this;
  }

  setModel(model) {
    this.model = model;
    // ZAUWA呕: Zwracamy this dla 艂a艅cuchowania
    return this;
  }

  setColor(color) {
    this.color = color;
    // ZAUWA呕: Zwracamy this dla 艂a艅cuchowania
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    // ZAUWA呕: Zwracamy this dla 艂a艅cuchowania
    return this;
  }
}

const car = new Car('Ford','F-150','red')
  .setColor('pink')
  .save();

猬 powr贸t na pocz膮tek

Przedk艂adaj kompozycj臋 ponad dziedziczenie

Jak stwierdzono g艂o艣no we Wzorcach projektowych Bandy Czterech, powiniene艣 przedk艂ada膰 kompozycj臋 ponad dziedziczenie tam, gdzie tylko mo偶esz. Jest wiele dobrych powod贸w, aby u偶ywa膰 dziedziczenia i wiele dobrych powod贸w, aby u偶ywa膰 kompozycji. G艂贸wnym punktem tej maksymy jest, aby gdy w my艣lach instynktownie sk艂aniasz si臋 ku dziedziczeniu, pr贸bowa膰 zastanowi膰 si臋, czy kompozycja mo偶e odwzorowa膰 problem lepiej. W niekt贸rych przypadkach mo偶e.

Mo偶esz si臋 zastanawia膰: "kiedy powinienem u偶y膰 dziedziczenia?". To zale偶y od Twojego problemu, ale tu jest skromna lista przypadk贸w, gdy dziedziczenie ma wi臋cej sensu ni偶 kompozycja:

  1. Twoje dziedziczenie reprezentuje relacj臋 "x-jest-y" a nie "x-posiada-y" (Cz艂owiek->Zwierz臋 kontra U偶ytkownik->Szczeg贸艂yU偶ytkownika).
  2. Mo偶esz wykorzysta膰 ponownie kod ze zbioru swoich klas (ludzie mog膮 porusza膰 si臋 jak wszystkie zwierz臋ta).
  3. Chcesz dokona膰 globalnych zmian w klasach pochodnych zmieniaj膮c klas臋 podstawow膮 (zmieni膰 zu偶ycie kalorii podczas poruszania dla wszystkich zwierz膮t).

殴le:

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  // ...
}

// 殴le, gdy偶 Pracownicy "posiadaj膮" dane podatkowe. DanePodatkowePracownika nie s膮 typem Pracownika
class EmployeeTaxData extends Employee {
  constructor(ssn, salary) {
    super();
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

Dobrze:

class EmployeeTaxData {
  constructor(ssn, salary) {
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  setTaxData(ssn, salary) {
    this.taxData = new EmployeeTaxData(ssn, salary);
  }
  // ...
}

猬 powr贸t na pocz膮tek

SOLID

Zasada jednej odpowiedzialno艣ci (SRP)

Jak stwierdzono w Czystym Kodzie, "Nigdy nie powinno by膰 wi臋cej ni偶 jednego powodu do modyfikacji klasy". Kusz膮cym jest zapakowanie w klas臋 wielu funkcjonalno艣ci, tak jak wtedy, gdy mo偶esz zabra膰 tylko jedn膮 walizk臋 podczas lotu. Problemem jest tutaj to, 偶e Twoja klasa nie b臋dzie koncepcyjnie sp贸jna i da jej to wiele powod贸w do zmian. Minimalizacja ilo艣ci sytuacji, w kt贸rych musisz zmieni膰 klas臋, jest istotna. Jest istotna, gdy偶 je艣li jedna klasa zawiera zbyt du偶o funkcjonalno艣ci i modyfikujesz cz臋艣膰 z nich, mo偶e sta膰 si臋 trudnym do zrozumienia, jak wp艂ynie to na inne zale偶ne modu艂y w Twoim kodzie.

殴le:

class UserSettings {
  constructor(user) {
    this.user = user;
  }

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
}

Dobrze:

class UserAuth {
  constructor(user) {
    this.user = user;
  }

  verifyCredentials() {
    // ...
  }
}


class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }

  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
}

猬 powr贸t na pocz膮tek

Zasada otwarte-zamkni臋te (OCP)

Jak stwierdzi艂 Bertrand Meyer, "Encje (klasy, modu艂y, funkcje itd.) powinny by膰 otwarte na rozszerzenie, ale zamkni臋te na modyfikacje". Co wi臋c to oznacza? Ta zasada po prostu stwierdza, 偶e powiniene艣 umo偶liwi膰 u偶ytkownikom dodanie nowych funkcjonalno艣ci bez zmiany istniej膮cego kodu.

殴le:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'ajaxAdapter';
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'nodeAdapter';
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    if (this.adapter.name === 'ajaxAdapter') {
      return makeAjaxCall(url).then((response) => {
        // przekszta艂膰 odpowied藕 i zwr贸膰
      });
    } else if (this.adapter.name === 'httpNodeAdapter') {
      return makeHttpCall(url).then((response) => {
        // przekszta艂膰 odpowied藕 i zwr贸膰
      });
    }
  }
}

function makeAjaxCall(url) {
  // 偶膮danie i zwr贸cenie obietnicy
}

function makeHttpCall(url) {
  // 偶膮danie i zwr贸cenie obietnicy
}

Dobrze:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'ajaxAdapter';
  }

  request(url) {
    // 偶膮danie i zwr贸cenie obietnicy
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'nodeAdapter';
  }

  request(url) {
    // 偶膮danie i zwr贸cenie obietnicy
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then((response) => {
      // przekszta艂膰 odpowied藕 i zwr贸膰
    });
  }
}

猬 powr贸t na pocz膮tek

Zasada podstawienia Liskov (LSP)

Jest to przera偶aj膮ca nazwa dla bardzo prostego poj臋cia. Jest formalnie zdefiniowana jako "Je艣li S jest podtypem T, wtedy obiekty typu T mog膮 by膰 wymienione z obiektami typu S (np. obiekty typu S mog膮 zast膮pi膰 obiekty typu T) bez zmieniania 偶adnych po偶膮danych w艂a艣ciwo艣ci tego programu (poprawno艣膰, zadanie wykonane itd.)". To jeszcze bardziej przera偶aj膮ca definicja.

Najlepszym wyja艣nieniem tego b臋dzie, je艣li masz klas臋 bazow膮 i klas臋 potomn膮, wtedy klasy bazowa i potomna mog膮 zosta膰 u偶yte wymiennie bez otrzymania niepoprawnych wynik贸w. Mo偶e to by膰 nadal pogmatwane, sp贸jrzmy wi臋c na klasyczny przyk艂ad: Kwadrat-Prostok膮t. Matematycznie kwadrat jest prostok膮tem, ale je艣li modelujesz to u偶ywaj膮c relacji "x-jest-y" przez dziedziczenie, szybko wpadniesz w k艂opoty.

殴le:

class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width) {
    this.width = width;
    this.height = width;
  }

  setHeight(height) {
    this.width = height;
    this.height = height;
  }
}

function renderLargeRectangles(rectangles) {
  rectangles.forEach((rectangle) => {
    rectangle.setWidth(4);
    rectangle.setHeight(5);
    const area = rectangle.getArea(); // 殴LE: Zwraca 25 dla Kwadratu. Powinno by膰 20
    rectangle.render(area);
  });
}

const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);

Dobrze:

class Shape {
  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }

  getArea() {
    return this.length * this.length;
  }
}

function renderLargeShapes(shapes) {
  shapes.forEach((shape) => {
    const area = shape.getArea();
    shape.render(area);
  });
}

const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);

猬 powr贸t na pocz膮tek

Zasada segregacji interfejs贸w (ISP)

JavaScript nie posiada interfejs贸w, wi臋c ta zasada nie ma tu tak restrykcyjnego zastosowania, jak pozosta艂e. Mimo tego jest wa偶na i istotna nawet przy braku typowania w JavaScript.

ISP stwierdza, 偶e "Na klientach nie powinna by膰 wymuszana zale偶no艣膰 od interfejs贸w, kt贸rych oni nie u偶ywaj膮". Interfejsy w JavaScript s膮 niejawnymi kontraktami przez kacze typowanie.

Dobrym przyk艂adem demonstruj膮cym t臋 zasad臋 w JavaScript s膮 klasy, kt贸re wymagaj膮 du偶ych obiekt贸w z opcjami. Nie wymaganie od klient贸w instalowania ogromnych ilo艣ci ustawie艅 jest korzystne, gdy偶 przez wi臋kszo艣膰 czasu nie b臋d膮 oni potrzebowa膰 wszystkich tych ustawie艅. Uczynienie ich opcjonalnymi pomo偶e zapobiec otrzymaniu "grubego interfejsu".

殴le:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.animationModule.setup();
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName('body'),
  animationModule() {} // W wi臋kszo艣ci przypadk贸w nie b臋dziemy musieli animowa膰 podczas trawersowania.
  // ...
});

Dobrze:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.options = settings.options;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.setupOptions();
  }

  setupOptions() {
    if (this.options.animationModule) {
      // ...
    }
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName('body'),
  options: {
    animationModule() {}
  }
});

猬 powr贸t na pocz膮tek

Zasada odwr贸cenia zale偶no艣ci (DIP)

Ta zasada okre艣la dwie istotne rzeczy:

  1. Wysokopoziomowe modu艂y nie powinny zale偶e膰 od modu艂贸w niskopoziomowych. Jedne i drugie powinny zale偶e膰 od abstrakcji.
  2. Abstrakcje nie powinny zale偶e膰 od szczeg贸艂贸w. Szczeg贸艂y powinny zale偶e膰 od abstrakcji.

Z pocz膮tku mo偶e to by膰 trudne do zrozumienia, ale je艣li pracowa艂e艣 z AngularJS, widzia艂e艣 implementacj臋 tej zasady w formie Wstrzykiwania zale偶no艣ci (DI). Podczas gdy nie s膮 one identycznymi poj臋ciami, Zasada odwr贸cenia zale偶no艣ci trzyma wysokopoziomowe modu艂y z dala od wiedzy na temat szczeg贸艂贸w ich niskopoziomowych modu艂贸w i ich ustawiania. Mo偶e to by膰 osi膮gni臋te dzi臋ki Wstrzykiwaniu zale偶no艣ci. Ogromn膮 korzy艣ci膮 jest, i偶 redukuje to zale偶no艣ci mi臋dzy modu艂ami. Zale偶no艣膰 jest bardzo z艂ym wzorcem, gdy偶 czyni Tw贸j kod trudniejszym do zrefaktoryzowania.

Jak wcze艣niej zaznaczono, JavaScript nie posiada interfejs贸w, wi臋c abstrakcje b臋d膮ce zale偶nymi, s膮 niejawnymi kontraktami. Oznacza to metody i w艂a艣ciwo艣ci, kt贸re obiekt/klasa wystawia dla innego obiektu/klasy. W przyk艂adzie poni偶ej niejawnym kontraktem jest to, 偶e dowolny modu艂 Request dla InventoryTracker b臋dzie posiada艂 metod臋 requestItems.

殴le:

class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryTracker {
  constructor(items) {
    this.items = items;

    // 殴LE: Utworzyli艣my zale偶no艣膰 od specyficznej implementacji 偶膮dania.
    // Powinni艣my po prostu uczyni膰 requestItems zale偶nym od metody: `request`
    this.requester = new InventoryRequester();
  }

  requestItems() {
    this.items.forEach((item) => {
      this.requester.requestItem(item);
    });
  }
}

const inventoryTracker = new InventoryTracker(['apples', 'bananas']);
inventoryTracker.requestItems();

Dobrze:

class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestItems() {
    this.items.forEach((item) => {
      this.requester.requestItem(item);
    });
  }
}

class InventoryRequesterV1 {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryRequesterV2 {
  constructor() {
    this.REQ_METHODS = ['WS'];
  }

  requestItem(item) {
    // ...
  }
}

// Przez skonstruowanie naszych zale偶no艣ci na zewn膮trz i wstrzykni臋cie ich, mo偶emy 艂atwo
// zast膮pi膰 nasz modu艂 偶膮dania nowym, fantazyjnym, u偶ywaj膮cym WebSockets.
const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
inventoryTracker.requestItems();

猬 powr贸t na pocz膮tek

Testowanie

Testowanie jest wa偶niejsze ni偶 dostarczanie. Je艣li nie masz test贸w albo jest ich nieodpowiednia ilo艣膰, to za ka偶dym razem dostarczaj膮c sw贸j kod, nie b臋dziesz pewnym, 偶e czego艣 nie popsu艂e艣. Decyzja o tym, jaka ilo艣膰 test贸w jest odpowiednia, nale偶y do Twojego zespo艂u, ale pokrycie w 100% (wszystkie instrukcje i ga艂臋zie) jest tym, co pozwoli osi膮gn膮膰 wysok膮 pewno艣膰 i 艣wi臋ty spok贸j dewelopera. Oznacza to, 偶e jako dodatek do posiadanego 艣wietnego frameworka do testowania, musisz jeszcze u偶y膰 dobrego narz臋dzia pokrycia.

Nie ma usprawiedliwienia dla niepisania test贸w. Jest [mn贸stwo dobrych framework贸w testowych] (http://jstherightway.org/#testing-tools), znajd藕 wi臋c ten, kt贸ry Tw贸j zesp贸艂 preferuje. Kiedy go znajdziesz, wtedy postaw sobie za cel, aby zawsze pisa膰 testy dla ka偶dej nowej funkcjonalno艣ci/modu艂u, kt贸ry wprowadzasz. Je艣li preferowan膮 przez Ciebie metod膮 jest Test Driven Development (TDD), to 艣wietnie, ale istot膮 jest po prostu upewnienie si臋, 偶e osi膮gasz swoje cele dotycz膮ce pokrycia przed wypuszczeniem jakiejkolwiek funkcjonalno艣ci albo refaktoryzacji ju偶 istniej膮cej.

Pojedynczy pomys艂 na test

殴le:

import assert from 'assert';

describe('MakeMomentJSGreatAgain', () => {
  it('handles date boundaries', () => {
    let date;

    date = new MakeMomentJSGreatAgain('1/1/2015');
    date.addDays(30);
    assert.equal('1/31/2015', date);

    date = new MakeMomentJSGreatAgain('2/1/2016');
    date.addDays(28);
    assert.equal('02/29/2016', date);

    date = new MakeMomentJSGreatAgain('2/1/2015');
    date.addDays(28);
    assert.equal('03/01/2015', date);
  });
});

Dobrze:

import assert from 'assert';

describe('MakeMomentJSGreatAgain', () => {
  it('handles 30-day months', () => {
    const date = new MakeMomentJSGreatAgain('1/1/2015');
    date.addDays(30);
    assert.equal('1/31/2015', date);
  });

  it('handles leap year', () => {
    const date = new MakeMomentJSGreatAgain('2/1/2016');
    date.addDays(28);
    assert.equal('02/29/2016', date);
  });

  it('handles non-leap year', () => {
    const date = new MakeMomentJSGreatAgain('2/1/2015');
    date.addDays(28);
    assert.equal('03/01/2015', date);
  });
});

猬 powr贸t na pocz膮tek

Wsp贸艂bie偶no艣膰

U偶ywaj Obietnic, a nie wywo艂a艅 zwrotnych

Wywo艂ania zwrotne nie s膮 czyste i powoduj膮 nadmierne ilo艣ci zagnie偶d偶e艅. W ES2015/ES6 Obietnice s膮 wbudowanym, globalnym typem. U偶ywaj ich!

殴le:

import { get } from 'request';
import { writeFile } from 'fs';

get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => {
  if (requestErr) {
    console.error(requestErr);
  } else {
    writeFile('article.html', response.body, (writeErr) => {
      if (writeErr) {
        console.error(writeErr);
      } else {
        console.log('File written');
      }
    });
  }
});

Dobrze:

import { get } from 'request';
import { writeFile } from 'fs';

get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then((response) => {
    return writeFile('article.html', response);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });

猬 powr贸t na pocz膮tek

Async/Await s膮 jeszcze bardziej czyste ni偶 Obietnice

Obietnice s膮 bardzo czyst膮 alternatyw膮 dla wywo艂a艅 zwrotnych, ale ES2017/ES8 wprowadza async i await, kt贸re oferuj膮 jeszcze czystsze rozwi膮zanie. Wszystkim, czego potrzebujesz, jest funkcja poprzedzona s艂owem kluczowym async i wtedy mo偶esz pisa膰 swoj膮 logik臋 imperatywnie bez 艂a艅cucha funkcji z then. U偶ywaj tego, je艣li mo偶esz skorzysta膰 z funkcjonalno艣ci ES2017/ES8 ju偶 dzi艣!

殴le:

import { get } from 'request-promise';
import { writeFile } from 'fs-promise';

get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then((response) => {
    return writeFile('article.html', response);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });

Dobrze:

import { get } from 'request-promise';
import { writeFile } from 'fs-promise';

async function getCleanCodeArticle() {
  try {
    const response = await get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
    await writeFile('article.html', response);
    console.log('File written');
  } catch(err) {
    console.error(err);
  }
}

猬 powr贸t na pocz膮tek

Obs艂uga b艂臋d贸w

Wyrzucane b艂臋dy s膮 czym艣 dobrym! Oznaczaj膮, 偶e w czasie wykonania zosta艂o poprawnie zidentyfikowane co艣, co posz艂o 藕le w Twoim programie i daje Ci zna膰, aby zatrzyma膰 wykonywanie funkcji na obecnym stosie, zamkn膮膰 proces (w Node) i poinformowa膰 Ci臋 w konsoli ze 艣ladem stosu.

Nie ignoruj przechwyconych b艂臋d贸w

Nie zrobienie niczego z przechwyconym b艂臋dem nie daje Ci mo偶liwo艣ci naprawienia albo zareagowania na ten b艂膮d. Logowanie b艂臋du do konsoli (console.log) nie jest du偶o lepsze, gdy偶 mo偶e on zagin膮膰 w morzu rzeczy informacji do konsoli. Je艣li zawrzesz jakikolwiek kawa艂ek kodu w bloku try/catch, oznacza to, 偶e my艣lisz o b艂臋dzie mog膮cym tam wyst膮pi膰 i w zwi膮zku z tym powiniene艣 mie膰 plan albo utworzy膰 艣cie偶k臋 kodu do miejsca, w kt贸rym wyst膮pi.

殴le:

try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}

Dobrze:

try {
  functionThatMightThrow();
} catch (error) {
  // Jedna z opcji (g艂o艣niejsza ni偶 console.log):
  console.error(error);
  // Inna opcja:
  notifyUserOfError(error);
  // Inna opcja:
  reportErrorToService(error);
  // ALBO zastosuj wszystkie trzy!
}

Nie ignoruj odrzuconych obietnic

Z tych samych powod贸w, dla kt贸rych nie powiniene艣 ignorowa膰 przechwyconych b艂臋d贸w w try/catch.

殴le:

getdata()
  .then((data) => {
    functionThatMightThrow(data);
  })
  .catch((error) => {
    console.log(error);
  });

Dobrze:

getdata()
  .then((data) => {
    functionThatMightThrow(data);
  })
  .catch((error) => {
    // Jedna z opcji (g艂o艣niejsza ni偶 console.log):
    console.error(error);
    // Inna opcja:
    notifyUserOfError(error);
    // Inna opcja:
    reportErrorToService(error);
    // ALBO zastosuj wszystkie trzy!
  });

猬 powr贸t na pocz膮tek

Formatowanie

Formatowanie jest subiektywne. Jak w wielu innych tu przypadkach, nie ma sztywnej i szybkiej zasady, kt贸r膮 musisz przyj膮膰. Najwa偶niejsze, aby艣 NIE SPIERA艁 SI臉 o formatowanie. S膮 tony narz臋dzi, by to zautomatyzowa膰. U偶yj jednego! Spieranie si臋 o formatowanie jest strat膮 czasu i pieni臋dzy dla in偶ynier贸w.

W sprawach, kt贸re nie s膮 obj臋te automatycznym formatowaniem (wci臋cia, tabulacje kontra spacje, podw贸jne kontra pojedyncze cudzys艂owy itd.) zagl膮dnij tu po troch臋 wskaz贸wek.

U偶ywaj wielkich liter konsekwentnie

JavaScript jest typowany dynamicznie, wi臋c wielkie litery powiedz膮 Ci du偶o o Twoich zmiennych, funkcjach itd. Te zasady s膮 subiektywne, wi臋c Tw贸j zesp贸艂 mo偶e wybra膰 cokolwiek chce. Chodzi o to, 偶eby艣 bez wzgl臋du na to, co wybierzecie, by艂 konsekwentnym.

殴le:

const DAYS_IN_WEEK = 7;
const daysInMonth = 30;

const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restore_database() {}

class animal {}
class Alpaca {}

Dobrze:

const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;

const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restoreDatabase() {}

class Animal {}
class Alpaca {}

猬 powr贸t na pocz膮tek

Wywo艂anie funkcji i wywo艂ana funkcja powinny by膰 blisko siebie

Je艣li funkcja wywo艂uje inn膮, trzymaj te funkcje wertykalnie blisko w pliku 藕r贸d艂owym. Najlepiej umie艣膰 wywo艂anie zaraz powy偶ej wywo艂anej. Mamy tendencj臋 do czytania kodu z g贸ry na d贸艂 jak gazet臋. Dlatego te偶 spraw, aby Tw贸j kod by艂 czytany w ten spos贸b.

殴le:

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();

Dobrze:

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();

猬 powr贸t na pocz膮tek

Komentarze

Komentuj tylko rzeczy maj膮ce z艂o偶on膮 logik臋 biznesow膮.

Komentarze s膮 przeprosinami, nie wymogiem. Dobry kod przewa偶nie dokumentuje si臋 sam.

殴le:

function hashIt(data) {
  // Hash
  let hash = 0;

  // D艂ugo艣膰 艂a艅cucha znak贸w
  const length = data.length;

  // P臋tla po literach w zmiennej data
  for (let i = 0; i < length; i++) {
    // Pobierz kod litery.
    const char = data.charCodeAt(i);
    // Utw贸rz hash
    hash = ((hash << 5) - hash) + char;
    // Przekonwertuj na liczb臋 32-bitow膮
    hash &= hash;
  }
}

Dobrze:


function hashIt(data) {
  let hash = 0;
  const length = data.length;

  for (let i = 0; i < length; i++) {
    const char = data.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;

    // Przekonwertuj na liczb臋 32-bitow膮
    hash &= hash;
  }
}

猬 powr贸t na pocz膮tek

Nie pozostawiaj zakomentowanego kodu

Kontrola wersji istnieje nie bez powodu. Pozostaw stary kod w Twojej historii.

殴le:

doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();

Dobrze:

doStuff();

猬 powr贸t na pocz膮tek

Nie tw贸rz komentarzy-dziennika.

Pami臋taj, u偶ywaj kontroli wersji! Nie jest potrzebny martwy kod, zakomentowany kod i przede wszystkim komentarze b臋d膮ce dziennikiem. U偶ywaj git log, by sprawdzi膰 histori臋!

殴le:

/**
 * 2016-12-20: Usun膮艂em monady, nie rozumia艂em ich (RM)
 * 2016-10-01: Ulepszy艂em u偶ycie specjalnych monad (JP)
 * 2016-02-03: Usun膮艂em sprawdzanie typ贸w (LI)
 * 2015-03-14: Doda艂em funkcj臋 combine ze sprawdzaniem typ贸w (JR)
 */
function combine(a, b) {
  return a + b;
}

Dobrze:

function combine(a, b) {
  return a + b;
}

猬 powr贸t na pocz膮tek

Unikaj marker贸w pozycyjnych

Zwykle dodaj膮 one tylko szum. Pozw贸l funkcjom i nazwom zmiennych wraz z poprawnymi wci臋ciami i formatowaniem da膰 wizualn膮 struktur臋 Twojemu kodowi.

殴le:

////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
  menu: 'foo',
  nav: 'bar'
};

////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
  // ...
};

Dobrze:

$scope.model = {
  menu: 'foo',
  nav: 'bar'
};

const actions = function() {
  // ...
};

猬 powr贸t na pocz膮tek

T艂umaczenie

Ten dokument dost臋pny jest r贸wnie偶 w innych j臋zykach:

猬 powr贸t na pocz膮tek

Open Source Agenda is not affiliated with "Clean Code Javascript Pl" Project. README Source: greg-dev/clean-code-javascript-pl
Stars
113
Open Issues
0
Last Commit
6 years ago
License
MIT

Open Source Agenda Badge

Open Source Agenda Rating