Dark Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

BoryaMogila/clean-code-javascript-ru

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

40 Commits

Repository files navigation

Perevod knigi Raiana Makdermota clean-code-javascript.

Oglavlenie

  1. Vvedenie
  2. Peremennye
  3. Funktsii
  4. Ob'ekty i struktury dannykh
  5. Klassy
  6. Testirovanie
  7. Asinkhronnost'
  8. Obrabotka oshibok
  9. Formatirovanie
  10. Kommentarii
  11. Perevod

Vvedenie

Inzhenernye printsipy programmnogo obespecheniia, iz knigi Roberta S. Martina Clean Code, prisposoblennye dlia JavaScript.

Eto ne stail gaid. Eto rukovodstvo po napisaniiu chitaemogo, pereispol'zuemogo i podderzhivaemogo koda na javascript. Ne kazhdyi printsip zdes', dolzhen strogo sobliudat'sia. Eto printsipy i nichego bol'she, no oni sformirovany v techenie mnogikh let kollektivnogo opyta avtorami clean code.

Nashemu remeslu inzhenerii programmnogo obespecheniia chut' bol'she 50 let, i my vse eshchio mnogo chemu uchimsia. Kogda programmnaia arkhitektura stanet stara kak sama arkhitektura, mozhet byt', togda my budem imet' bolee zhestkie pravila, kotorym neobkhodimo budet sledovat'. Na dannyi moment, pust' eti printsipy sluzhat kamnem pretknoveniia, s pomoshch'iu kotorogo vy budete otsenivat' kachestvo koda JavaScript, kotoryi pishete vy i vasha komanda.

Eshche odna veshch': znanie etikh printsipov ne sdelaet vas srazu luchshim razrabotchikom programmnogo obespecheniia, i esli vy budete priderzhivat'sia etikh printsipov mnogo let ne oznachaet, chto vy ne budete delat' oshibki. Kazhdyi fragment koda nachinaetsia kak chernovik, kak i kusok mokroi gliny kotoryi priobretaet svoiu okonchatil'nuiu formu. Nakonets, my ispravliaem nedostatki, kogda rassmatrivaem kod so svoimi kollegami. Ne korite sebia pri pervykh nabroskakh koda, kotorye nuzhdaiutsia v uluchshenii. Uluchshaite svoi kod vmesto etogo.

Peremennye

Ispol'zuite znachimye i proiznosimye imena peremennykh

Plokho:

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

Khorosho:

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

vernut'sia

Ispol'zuite odin i tot zhe metod dlia togo zhe tipa peremennoi

Plokho:

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

Khorosho:

getUser();

vernut'sia

Ispol'zuite imenovannye znacheniia

My budem chitat' kod chashche, chem my kogda-nibud' napishem. Vazhno pisat' chitaemyi kod, kotoryi legko iskat'. Delaite vashi imena dlia poiska. Takie instrumenty, kak buddy.js i ESLint mogut pomoch' identifitsirovat' nenazvannye konstanty.

Plokho:

// What the heck is 86400000 for?
setTimeout(blastOff, 86400000);

Khorosho:

// Ob'iavliaite ikh kak global'nye peremennye.
const MILLISECONDS_IN_A_DAY = 86400000;

setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

vernut'sia

Ispol'zuite ob'iasniaiushchie peremennye

Plokho:

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

Khorosho:

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

vernut'sia

Ispol'zuite ochelovechennye nazvaniia

Iavnoe luchshe, chem neiavnoe.

Plokho:

{ doStuff(); doSomeOtherStuff(); // ... // ... // ... // Chto znachit `l`? dispatch(l); });">const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l) => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// Chto znachit `l`?
dispatch(l);
});

Khorosho:

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

vernut'sia

Ne dobavliaite nenuzhnyi kontekst

Esli vashe imia klassa / ob'ekta govorit vam chto eto, ne povtoriaite tozhe pri imenovanii ego svoistv i metodov.

Plokho:

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

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

Khorosho:

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

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

vernut'sia

Ispol'zuite usloviia po umolchaniiu vmesto korotkikh zamykanii ili uslovnykh vyrazhenii

Plokho:

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

Khorosho:

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

vernut'sia

Funktsii

Argumenty funktsii (ideal'no 2 ili menee)

Ogranichenie kolichestva parametrov funktsii neveroiatno vazhno, poskol'ku ono uproshchaet testirovanie funktsii. Nalichie bolee chem triokh argumentov privodit k kombinatornomu vzryvu, kogda vam prikhoditsia perebirat' massu razlichnykh sluchaev s kazhdym otdel'nym argumentom.

Ideal'naia situatsiia -- otsutstvie argumentov. Odin ili dva argumenta -- khorosho, a trekh uzhe sleduet izbegat'.

Bol'shee kolichestvo argumentov neobkhodimo konsolidirovat'. Kak pravilo, esli peredaetsia bolee dvukh argumentov, vasha funktsiia pytaetsia sdelat' slishkom mnogoe. V tekh sluchaiakh, kogda eto vse zhe ne tak, luchshe ispol'zovat' ob'ekt v kachestve argumenta. Poskol'ku JavaScript pozvoliaet sozdavat' ob'ekty na letu, bez spetsial'nogo opisaniia klassov, ikh vpolne mozhno primeniat', kogda trebuetsia peredat' mnozhestvo argumentov.

Dlia togo, chtoby sdelat' svoistva funktsii ochevidnymi ispol'zuite sintaksis ES6 destruktsii. Eto imeet neskol'ko preimushchestv:

  1. Kogda prosmatrivaiut ob'iavlenie funktsii, to srazu poniatno, kakie svoistva ispol'zuiutsia.
  2. Destruktsiia takzhe kloniruet ukazannye prostye znacheniia iz argumenta funktsii. Eto mozhet pomoch' predotvratit' said effekty. Zametka: Ob'ekty i massivy NE KLONIRUIuTSIa.
  3. Lintery mogut predupredit' vas o neispol'zuemykh svoistvakh, chto bylo by nevozmozhno bez destrukturizatsii.

Plokho:

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

Khorosho:

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

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

vernut'sia

Funktsiia dolzhna reshat' odnu zadachu

Eto, bezuslovno, samoe vazhnoe pravilo v razrabotke programmnogo obespecheniia. Kogda funktsii reshaiut bolee odnoi zadachi, ikh trudnee sochetat', testirovat' i ponimat'. Kak tol'ko vy smozhete svesti kazhduiu funktsiiu k vypolneniiu tol'ko odnogo deistviia, ikh stanet znachitel'no proshche refaktorit', a vash kod stanet gorazdo bolee chitaemym. Dazhe esli privedennoe pravilo budet edinstvennym vynesennym vami iz etogo rukovodstva, vy vse ravno budete kruche mnogikh razrabotchikov.

Plokho:

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

Khorosho:

function emailClients(clients) {
clients
.filter(isClientActive)
.forEach(email);
}

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

vernut'sia

Nazvaniia funktsii dolzhny opisyvat' ikh naznachenie

Plokho:

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

const date = new Date();

// It's hard to to tell from the function name what is added
addToDate(date, 1);

Khorosho:

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

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

vernut'sia

Funktsii dolzhny predstavliat' tol'ko odin uroven' abstraktsii

Esli v funktsii predstavleno bolee odnogo urovnia abstraktsii, to ona, kak pravilo, delaet slishkom mnogoe. Razdelenie takikh funktsii privedet k vozmozhnosti povtornogo ispol'zovaniia i oblegcheniiu testirovaniia.

Plokho:

{ statements.forEach((statement) => { // ... }); }); const ast = []; tokens.forEach((token) => { // pravilo... }); ast.forEach((node) => { // parsing... }); }">function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];

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

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

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

Khorosho:

{ 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) => { // parsing... }); }">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) => {
// parsing...
});
}

vernut'sia

Izbavliaites' ot dublirovannogo koda

Izo vsekh sil staraites' izbegat' dublirovannogo koda. Dublirovannyi kod vreden tem, chto podrazumevaet nalichie bolee chem odnogo mesta, v kotoroe pridetsia vnosit' pravki, esli logika deistvii izmenitsia.

Predstav'te, chto upravliaete restoranom i vedete uchet vsekh produktov -- pomidorov, luka, chesnoka, spetsii i t.d. Esli ikh uchet vedetsia v raznykh spiskakh, to podacha liubogo bliuda s pomidorami potrebuet vneseniia izmenenii v kazhdyi spisok. Esli zhe spisok tol'ko odin, to i pravka budet vsego odna!

Zachastuiu dublirovannyi kod voznikaet v tekh sluchaiakh, kogda trebuetsia realizovat' dva ili bolee neznachitel'no razlichaiushchikhsia deistviia, kotorye v tselom ochen' skhozhi, no ikh razlichiia vynuzhdaiut vas zavesti dve ili bolee funktsii, delaiushchikh prakticheski odno i to zhe. V etom sluchae izbavlenie ot dublirovannogo koda budet oznachat' sozdanie abstraktsii, kotoraia smozhet predstavit' vse razlichiia v vide odnoi funktsii, klassa ili modulia.

Sozdanie pravil'noi abstraktsii -- vopros neveroiatnoi vazhnosti, i imenno poetomu vy dolzhny sledovat' printsipam SOLID. Plokhie abstraktsii mogut okazat'sia khuzhe dublirovannogo koda, tak chto bud'te ostorozhny!

Podvodia itog: esli mozhete obernut' kod khoroshei abstraktsiei -- tak i sdelaite! Ne dubliruite kod, inache vam pridetsia vnosit' mnozhestvo pravok na kazhdoe nebol'shoe izmenenie.

Plokho:

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);
});
}

Khorosho:

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

let portfolio = employee.getGithubLink();

if (employee.type === 'manager') {
portfolio = employee.getMBAProjects();
}

const data = {
expectedSalary,
experience,
portfolio
};

render(data);
});
}

vernut'sia

Ustanavlivaite ob'ekty po umolchaniiu s pomoshch'iu Object.assign

Plokho:

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);

Khorosho:

const menuConfig = {
title: 'Order',
// User did not include 'body' key
buttonText: 'Send',
cancellable: true
};

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

// teper' config = {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}

createMenu(menuConfig);

vernut'sia

Ne ispol'zuite flagi v kachestve parametrov funktsii

Flagi govoriat pol'zovateliu, chto funktsiia sovershaet bolee odnogo deistviia. Funktsiia dolzhna reshat' odnu zadachu. Razdeliaite funktsii, esli oni ispolniaiut razlichnye varianty koda na osnove logicheskogo znacheniia.

Plokho:

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

Khorosho:

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

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

vernut'sia

Izbegaite pobochnykh effektov (Chast' 1)

Funktsiia proizvodit pobochnyi effekt, esli ona sovershaet kakoe-libo deistvie pomimo polucheniia znacheniia i vozvrata drugogo znacheniia ili znachenii. Pobochnyi effekt mozhet byt' zapis'iu v fail, izmeneniem kakikh-to global'nykh peremennykh ili sluchainym perevodom vsekh vashikh deneg neizvestnym litsam.

Vprochem, pobochnye effekty v programme neobkhodimy. Pust', kak i v predydushchem primere, vam trebuetsia zapis' v fail. Opishite to, chto vy khotite sdelat', strogo v odnom meste.

Ne sozdavaite neskol'ko funktsii i klassov, kotorye pishut chto-to v konkretnyi fail. Sozdaite odin servis, kotoryi vsem etim zanimaetsia. Odin i tol'ko odin.

Sut' v tom, chtoby izbegat' rasprostranennykh oshibok, takikh kak, naprimer, peredacha sostoianiia mezhdu ob'ektami bez kakoi-libo struktury, s pomoshch'iu izmeniaemykh dannykh, kotorye mozhet perezapisyvat' kto ugodno, v obkhod tsentralizovannogo mesta primeneniia pobochnykh effektov.

Esli nauchites' tak delat', vy stanete schastlivee, chem podavliaiushchee bol'shinstvo drugikh programmistov.

Plokho:

// Global'naia peremennaia, na kotoruiu ssylaetsia posleduiushchaia funktsiia.
// Esli by u nas byla eshche odna funktsiia, kotoraia by rabotala s imenem name kak so strokoi,
// to obnaruzhiv massiv, on nepremenno by polomalas'
let name = 'Ryan McDermott';

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

splitIntoFirstAndLastName();

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

Khorosho:

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

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

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

vernut'sia

Izbegaite pobochnykh effektov (Chast' 2)

V JavaScript primitivy peredaiutsia po znacheniiu, a ob'ekty i massivy peredaiutsia po ssylke. V sluchae ob'ektov i massivov, esli nasha funktsiia vnosit izmeneniia v korzinu (massiv), naprimer, putem dobavleniia elementa v massiv, to liubaia drugaia funktsiia, kotoraia ispol'zuet etu korzinu (massiv) budet zaviset' ot etogo dobavleniia. Eto mozhet byt' i khorosho i plokho v raznykh sluchaiakh. Davaite predstavim sebe plokhuiu situatsiia:

Pol'zovatel' nazhimaet na knopku "Pokupka", kotoraia vyzyvaet funktsiiu purchase, chto otpravliaet dannye iz korziny (massiv) na server. V sluchae plokhogo podkliucheniia k seti funktsiia purchase dolzhna otpravit' povtornyi zapros. Teper', chto, esli v to zhe vremia pol'zovatel' sluchaino nazhimaet knopku "Dobavit' v korzinu", no poka ne khochet pokupat' tovar? Esli eto proizoidet, i nachinaetsia zapros seti, to funktsiia purchase poshlet sluchaino dobavlennyi element, poskol'ku on imeet ssylku na predydushchuiu korzinu (massiv), modifitsirovannuiu funktsiei addItemToCart. Otlichnoe reshenie bylo by dlia addItemToCart vsegda klonirovat' korzinu, otredaktirovat' i vernut' klon. Eto garantiruet, chto nikakie drugie funktsii, kotorye zavisiat ot korziny ne budut zaviset' ot kakikh-libo izmenenii.

Dva predosterezheniia po-povodu takogo podkhoda:

  1. Vozmozhny sluchai, kogda vy na samom dele khotite izmenit' ob'ekt po ssylke, no takie sluchai kraine redki. Bol'shinstvo funktsii mogut byt' ob'iavleny bez said effektov!
  2. Klonirovanie bol'shikh ob'ektov mozhet byt' ochen' nagruzochnym i vliiat' na proizvoditel'nost'. K schast'iu, eto ne iavliaetsia bol'shoi problemoi na praktike, potomu chto est' otlichnye biblioteki, kotorye pozvoliaiut klonirovat' ob'ekty s men'shei nagruzkoi na pamiat' v otlichii ot klonirovaniia vruchnuiu.

Plokho:

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

Khoroshe:

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

vernut'sia

Ne pereopredeliaite global'nye funktsii

Zagriaznenie global'nykh peremennykh -- plokhaia praktika v JavaScript, tak kak mozhet porodit' konflikty s drugoi bibliotekoi, i pol'zovatel' vashego API ne uvidit oshibok, poka ne poluchit iskliuchenie v prodakshene. Davaite rassmotrim primer: chto delat', esli vy khotite rasshirit' standartnyi funktsional Array iz JavaScript, dobaviv metod diff, kotoryi by vychislial razlichie mezhdu dvumia massivami? Vy dolzhny byli by zapisat' novuiu funktsiiu v Array.prototype, no togda ona mozhet voiti v konflikt s drugoi bibliotekoi, kotoraia pytalas' sdelat' to zhe samoe. A esli drugaia biblioteka ispol'zovala metod diff, chtoby naiti raznitsu mezhdu pervym i poslednim elementami massiva? Imenno poetomu gorazdo luchshe ispol'zovat' klassy ES2015/ES6 i prosto unasledovat' nashu realizatsiiu ot klassa Array.

Plokho:

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

Khorosho:

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

vernut'sia

Otdavaite predpochtenie funktsional'nomu programmirovaniiu nad imperativnym

JavaScript ne nastol'ko funktsional'nyi iazyk, kak Haskell, no opredelennoi doli funktsional'nosti on ne lishen. Funktsional'nye iazyki chishche i ikh proshche testirovat'. Primeniaite funktsional'nyi stil' programmirovaniia pri vozmozhnosti.

Plokho:

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;
}

Khorosho:

programmer.linesOfCode) .reduce((acc, linesOfCode) => acc + linesOfCode, INITIAL_VALUE);">const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];

const INITIAL_VALUE = 0;

const totalOutput = programmerOutput
.map((programmer) => programmer.linesOfCode)
.reduce((acc, linesOfCode) => acc + linesOfCode, INITIAL_VALUE);

vernut'sia

Inkapsuliruite usloviia

Plokho:

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

Khorosho:

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

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

vernut'sia

Izbegaite negativnykh uslovii

Plokho:

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

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

Khorosho:

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

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

vernut'sia

Izbegaite uslovnykh konstruktsii

Takaia zadacha kazhetsia nevozmozhnoi. Uslyshav podobnoe, bol'shinstvo liudei govoriat: "Kak ia dolzhen delat' chto-libo bez vyrazheniia if?". Otvet zakliuchaetsia v tom, chto vo mnogikh sluchaiakh dlia dostizheniia tekh zhe tselei mozhno ispol'zovat' polimorfizm. Vtoroi vopros, kak pravilo, zvuchit tak: "Khorosho, zamechatel'no, no pochemu ia dolzhen ikh izbegat'?". Otvet -- predydushchaia kontseptsiia chistogo koda, kotoruiu my uznali: funktsiia dolzhna vypolniat' tol'ko odnu zadachu. Esli u vas est' klassy i funktsii, soderzhashchie konstruktsiiu if, vy slovno govorite svoemu pol'zovateliu, chto vasha funktsiia vypolniaet bol'she odnoi zadachi. Pomnite: odna funktsiia -- odna zadacha.

Plokho:

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();
}
}
}

Khorosho:

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();
}
}

vernut'sia

Izbegaite proverki tipov (chast' 1)

JavaScript iavliaetsia netipizirovannym iazykom, a eto znachit, chto vashi funktsii mogut prinimat' argumenty liubogo tipa. Poroi vy obzhigalis' etoi svobodoi, chto pobuzhdalo vas proizvodit' proverku tipov v vashikh funktsiiakh. Est' mnozhestvo sposobov ee izbezhat'. V pervuiu ochered' stoit podumat' nad soglasovannym API.

Plokho:

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

Khorosho:

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

vernut'sia

Izbegaite proverki tipov (chast' 2)

Esli vy rabotaete s bazovymi primitivami, takimi kak stroki, tselye chisla i massivy, i ne mozhete ispol'zovat' polimorfizm, khotia vse eshche chuvstvuete neobkhodimost' v proverkakh tipa, vam stoit rassmotret' vozmozhnost' primeneniia TypeScript. Eto otlichnaia al'ternativa obychnomu JavaScript, predostavliaiushchaia vozmozhnost' staticheskoi tipizatsii poverkh standartnogo sintaksisa JavaScript. Problema s ruchnoi proverkoi tipov v obychnom JavaScript v tom, chto illiuziia bezopasnosti, kotoruiu ona sozdaet, nikak ne kompensiruetsia poterei chitabel'nosti iz-za mnogoslovnosti koda. Derzhite vash kod v chistote, pishite khoroshie testy i delaite effektivnye revizii koda. Ili delaite vse to zhe samoe, no s pomoshch'iu TypeScript (kotoryi, kak ia uzhe skazal, iavliaetsia prekrasnoi al'ternativoi!).

Plokho:

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');
}

Khorosho:

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

vernut'sia

Ne optimiziruite sverkh mery

Sovremennye brauzery proizvodiat mnozhestvo optimizatsii pod kapotom vo vremia ispolneniia koda. Optimiziruia kod vruchnuiu, vy, zachastuiu, prosto tratite svoe vremia. Est' prekrasnye resursy s opisaniem situatsii, kogda optimizatsiia deistvitel'no khromaet. Pogliadyvaite na nikh v svobodnoe vremia, poka eti problemy ne budut ispravleny, esli voobshche budut, konechno.

Plokho:

// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
// ...
}

Khorosho:

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

vernut'sia

Udaliaite mertvyi kod

Mertvyi kod tak zhe plokh, kak povtoriaiushchiisia kod. Net nikakikh prichin, chtoby derzhat' ego v repozitorii. Esli kod ne vyzyvaetsia, izbav'tes' ot nego!

On po-prezhnemu budet v sisteme kontrolia versii, esli kogda-nibud' on vse-taki vam ponadobitsia.

Plokho:

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

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

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

Khorosho:

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

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

vernut'sia

Ob'ekty i struktury dannykh

Ispol'zuite gettery i settery

V javascript otsutstvuiut kliuchevye slova private i public, chto uslozhniaet realizatsiiu klassov. Luchshe ispol'zovat' gettery i settery dlia dostupa k svoistvam ob'ekta, chem napriamuiu k nim obrashchat'sia. Vy sprosite <>. Vot neskol'ko prichin:

  • Esli vy khotite realizovat' bol'she, chem prosto dostup k svoistvu, vam nuzhno pomeniat' realizatsiiu v odnom meste, a ne po vsemu kodu.
  • Validatsiiu legko realizovat' na urovne realizatsii settera
  • Inkapsuliatsiia vnutrennego sostoianiia ob'ekta
  • Legko dobavit' logirovanie i obrabotku oshibok na urovne getterov i setterov
  • Nasleduia etot klass, vy mozhete pereopredelit' funktsional'nost' po umolchaniiu
  • Vy mozhete lenivo podgruzhat' svoistva vashego ob'ekta, naprimer, s servera.

Plokho:

class BankAccount {
constructor() {
this.balance = 1000;
}
}

const bankAccount = new BankAccount();

// Pokupaem, naprimer, obuv'...
bankAccount.balance -= 100;

Khorosho:

class BankAccount {
constructor(balance = 1000) {
this._balance = balance;
}

set balance(amount) {
if (this.verifyIfAmountCanBeSetted(amount)) {
this._balance = amount;
}
}

get balance() {
return this._balance;
}

verifyIfAmountCanBeSetted(val) {
// ...
}
}

const bankAccount = new BankAccount();

// Pokupaem, naprimer, obuv'...
bankAccount.balance -= shoesPrice;

// poluchaem balans
let balance = bankAccount.balance;

vernut'sia

Realizuite privatnye svoistva vashikh ob'ektov

Eto vozmozhno s pomoshch'iu zamykanii.

Plokho:

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

Khorosho:

const Employee = function (name) {
this.getName = function getName() {
return 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: John Doe

vernut'sia

Klassy

Printsip edinstvennoi otvetstvennosti (SRP)

Kak napisano v clean code, <> (There should never be more than one reason for a class to change). Zamanchivo vsio zasunut' v odin klass, kak v dorozhnyi chemodan. Problema v tom, chto vash klass ne budet kontseptual'no sviazan, i vy budete chasto izmeniat' ego na kazhdyi chikh. Ochen' vazhno minimizirovat' izmeneniia v klasse. Kogda vy vnosite izmeneniia v klass s ogromnym funktsionalom, tiazhelo otsledit' posledstviia vashikh izmenenii.

Plokho:

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

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

verifyCredentials() {
// ...
}
}

Khorosho:

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()) {
// ...
}
}
}

vernut'sia

Printsip otkrytosti/zakrytosti (OCP)

Kak zaiavil Bertran Meier, <> (software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification). Chto eto znachit? Eto znachit, chto vy dolzhny davat' vozmozhnost' rasshirit' funktsional'nost' sushchnosti ne izmeniaia sushchestvuiushchii kod.

Plokho:

{ // transform response and return }); } else if (this.adapter.name === 'httpNodeAdapter') { return makeHttpCall(url).then((response) => { // transform response and return }); } } } function makeAjaxCall(url) { // request and return promise } function makeHttpCall(url) { // request and return promise }">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) => {
// transform response and return
});
} else if (this.adapter.name === 'httpNodeAdapter') {
return makeHttpCall(url).then((response) => {
// transform response and return
});
}
}
}

function makeAjaxCall(url) {
// request and return promise
}

function makeHttpCall(url) {
// request and return promise
}

Khorosho:

{ // transform response and return }); } }">class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = 'ajaxAdapter';
}

request(url) {
// request and return promise
}
}

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

request(url) {
// request and return promise
}
}

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

fetch(url) {
return this.adapter.request(url).then((response) => {
// transform response and return
});
}
}

vernut'sia

Printsip podstanovki Barbary Liskov

Eto strashnyi termin dlia ochen' prostoi kontseptsii.

Opredelenie:

<> Wikipedia Opredelenie eshchio khuzhe, chem nazvanie.

Sut' zakliuchaetsia v tom, chto esli u vas est' roditel'skii i dochernii klassy, to oni mogut vzaimozameniat'sia bez oshibok. Eto po-prezhnemu mozhet sbivat' s tolku, tak chto davaite posmotrim na klassicheskii primer ploshchadi priamougol'nika. Matematicheski kvadrat eto priamougol'nik, no esli vy reshite etu zadachu s pomoshch'iu nasledovaniia, to u vas budut problemy. Bolee detal'no pro printsip mozhno pochitat' zdes'.

Plokho:

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(); // BAD: Will return 25 for Square. Should be 20.
rectangle.render(area);
});
}

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

Khorosho:

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

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

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

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

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

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

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

setLength(length) {
this.length = length;
}

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

function renderLargeShapes(shapes) {
shapes.forEach((shape) => {
switch (shape.constructor.name) {
case 'Square':
shape.setLength(5);
break;
case 'Rectangle':
shape.setWidth(4);
shape.setHeight(5);
}

const area = shape.getArea();
shape.render(area);
});
}

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

vernut'sia

Printsip razdeleniia interfeisa (ISP)

V javascript otsutstvuiut interfeisy, tak chto etot printsip ne poluchitsia ispol'zovat' v polnoi mere. Tem ne menee vazhno ego ispol'zovat', dazhe pri otsutstvii sistemy tipov javascript.

ISP utverzhdaet, chto <> (Clients should not be forced to depend upon interfaces that they do not use). Interfeisy eto uslovnye soglasheniia v JavaScript iz-za neiavnoi tipizatsii. Khoroshim primerom v javascript mogut byt' klassy s bol'shimi konfigami. Ne zastavliaite pol'zovatelei vashego klassa vvodit' kuchu konfigov. Oni, kak pravilo, ne budut ispol'zovat' ikh vse. U vas ne budet "zhirnogo interfeisa", esli vy ikh sdelaete optsional'nymi.

Plokho:

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() {} // Chashche vam ne nuzhna animatsiia pri dvizhenii.
// ...
});

Khorosho:

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() {}
}
});

vernut'sia

Printsip inversii zavisimosti (DIP)

Etot printsip glasit dve vazhnye veshchi:

  1. Moduli vysshego urovnia ne dolzhny zaviset' ot modulei nizshego urovnia. Oba dolzhny zaviset' ot abstraktsii.
  2. V abstraktsiiakh ne dolzhno byt' detalei. Detali dolzhny byt' v dochernikh klassakh.

Snachala trudno poniat' etot printsip. No esli vy rabotali s AngularJS, vy videli realizatsiiu etogo printsipa v vide Dependency Injection (DI). Nesmotria na to, chto oni ne iavliaiutsia identichnymi poniatiiami, DIP daiot vozmozhnost' otgranichit' moduli vysokogo urovnia ot detalei modulei nizkogo urovnia i ustanovki ikh. On mozhet sdelat' eto cherez DI. Etot printsip umen'shaet sviaz' mezhdu moduliami. Esli vashi moduli tesno sviazany, ikh tiazhelo refaktorit'.

Abstraktsii i est' neiavnymi soglasheniiami, kotorye predstavliaiut interfeisy v JavaScript. To est' metody i svoistva, chto ob'ekt/klass predostavliaet drugomu ob'ektu/klassu. V privedennom nizhe primere kazhdyi ekzempliar klassa InventoryTracker budet imet' metod requestItems.

Plokho:

{ this.requester.requestItem(item); }); } } const inventoryTracker = new InventoryTracker(['apples', 'bananas']); inventoryTracker.requestItems();">class InventoryRequester {
constructor() {
this.REQ_METHODS = ['HTTP'];
}

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

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

// Plokho to, chto my sozdali zavisimost' ot konkretnoi realizatsii zaprosa.
// teper' nash metod requestItems ne abstraktnyi i zavisit ot etoi realizatsii
this.requester = new InventoryRequester();
}

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

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

Khorosho:

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) {
// ...
}
}

// Sformirovav zavisimosti izvne, my mozhem legko
// zamenit' nash modul' zaprosov na drugoi, kotoryi ispol'zuet vebsokety
const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
inventoryTracker.requestItems();

vernut'sia

Otdavaite predpochtenie klassam (ES2015 / ES6) nad prostymi funktsiiami (ES5)

C pomoshch'iu klassicheskikh (ES5) klassov tiazhelo realizovat' chitaemye nasledovanie, konstruktsiiu i opredelenie metodov. Esli vam nuzhno nasledovanie, ne zadumyvaias' ispol'zuite (ES2015 / ES6) klassy. Tem ne menee, otdavaite predpochtenie malen'kim funktsiiam, a ne klassam, poka ne budet neobkhodimosti v bolee krupnykh i slozhnykh ob'ektakh.

Plokho:

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() {};

Khorosho:

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() { /* ... */ }
}

vernut'sia

Ispol'zuite metod tsepochki

Etot pattern ochen' polezen v JavaScript. Ego ispol'zuiut mnogie biblioteki, takie kak jQuery i Lodash. Eto delaet vash kod vyrazitel'nym i nemnogoslovnym. Ispol'zuia etot pattern, vy uvidite naskol'ko vash kod stanet chishche. Prosto vozvrashchaite this, v kontse vashikh metodov i vy smozhete vyzyvat' ikh po tsepochke.

Plokho:

class Car {
constructor() {
this.make = 'Honda';
this.model = 'Accord';
this.color = 'white';
}

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();
car.setColor('pink');
car.setMake('Ford');
car.setModel('F-150');
car.save();

Khorosho:

class Car {
constructor() {
this.make = 'Honda';
this.model = 'Accord';
this.color = 'white';
}

setMake(make) {
this.make = make;
// vozvrashchaem this dlia vyzova po tsepochke
return this;
}

setModel(model) {
this.model = model;
// vozvrashchaem this dlia vyzova po tsepochke
return this;
}

setColor(color) {
this.color = color;
// vozvrashchaem this dlia vyzova po tsepochke
return this;
}

save() {
console.log(this.make, this.model, this.color);
// vozvrashchaem this dlia vyzova po tsepochke
return this;
}
}

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

vernut'sia

Otdavaite predpochtenie kompozitsii nad nasledovaniem

Kak bylo skazano v knige Design Patterns ot Bandy chetyrekh, sleduet otdavat' predpochtenie kompozitsii nad nasledovaniem, gde vy tol'ko mozhete. Est' mnogo prichin, chtoby ispol'zovat' nasledovanie i mnogo prichin ispol'zovat' kompozitsiiu. Esli vash mozg instinktivno vidit nasledovanie, poprobuite predstavit' reshenie vashei problemy s pomoshch'iu kompozitsii.

Kogda zhe ispol'zovat' nasledovanie? Eto zavisit ot konkretnoi problemy. Vot spisok sluchaev, kogda nasledovanie imeet bol'she smysla, chem kompozitsiia:

  1. Kogda nasledovanie predstavliaet soboi zavisimost' <>, a ne <> (Human->Animal vs. User->UserDetails)
  2. Vy mozhete povtorno ispol'zovat' klass (Liudi mogut dvigat'sia kak i vse zhivotnye).
  3. Vy khotite, sdelav izmeneniia roditel'skogo klassa, izmenit' dochernie klassy (Izmenenie raskhoda kalorii vsekh zhivotnykh, kogda oni dvigaiutsia).

Plokho:

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

// ...
}

// U sotrudnikov est' nalogovye dannye. Nalogovye dannye ne mogut byt' sotrudnikom.
class EmployeeTaxData extends Employee {
constructor(ssn, salary) {
super();
this.ssn = ssn;
this.salary = salary;
}

// ...
}

Khorosho:

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);
}
// ...
}

vernut'sia

Testirovanie

Testirovanie ochen' vazhnaia chast' razrabotki. Esli u vas net testov ili ikh nedostatochno, kak vy mozhete byt' uvereny, chto vy nichego ne slomaete? Vasha komanda dolzhna sama prinimat' reshenie po ob'iomu koda pokrytogo testami, no chem bol'she pokryto testami, tem spokoinee spit razrabotchik. Eto oznachaet, chto v dopolnenie k nalichiiu khoroshego instrumenta dlia testirovaniia, neobkhodimo takzhe ispol'zovat' khoroshii instrument dlia proverki pokrytiia koda testami. Net prichin ne pisat' testy. Vot podborka khoroshikh instrumentov dlia testirovaniia. Podobrav udobnyi dlia vashei komandy, pishite testy dlia kazhdogo novogo modulia ili fichi. Esli vy vybrali razrabotku cherez testirovanie (Test Driven Development TDD) - eto prekrasno, no glavnoe zakliuchaetsia v tom, chtoby ubedit'sia, chto testy pokryvaiut vse vashi tseli pered razrabotkoi novogo koda, ili refaktoringom sushchestvuiushchego koda.

Odin test - odno opisanie.

Plokho:

{ it('handles date boundaries', () => { let date; date = new MakeMomentJSGreatAgain('1/1/2015'); date.addDays(30); date.shouldEqual('1/31/2015'); 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); }); });">const assert = require('assert');

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

date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
date.shouldEqual('1/31/2015');

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);
});
});

Khorosho:

{ it('handles 30-day months', () => { const date = new MakeMomentJSGreatAgain('1/1/2015'); date.addDays(30); date.shouldEqual('1/31/2015'); }); 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); }); });">const assert = require('assert');

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

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);
});
});

vernut'sia

Asinkhronnost'

Ispol'zuite promisy vmesto kolbekov

Kolbeki privodiat k chrezmernoi vlozhennosti i plokhoi chitaemosti koda.

Plokho

{ if (requestErr) { console.error(requestErr); } else { fs.writeFile('article.html', response.body, (writeErr) => { if (writeErr) { console.error(writeErr); } else { console.log('File written'); } }); } });">const request = require('request');
const fs = require('fs');

const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';

request.get(url, (requestErr, response) => {
if (requestErr) {
console.error(requestErr);
} else {
fs.writeFile('article.html', response.body, (writeErr) => {
if (writeErr) {
console.error(writeErr);
} else {
console.log('File written');
}
});
}
});

Khorosho

{ return fsPromise.writeFile('article.html', response); }) .then(() => { console.log('File written'); }) .catch((err) => { console.error(err); });">const requestPromise = require('request-promise');
const fsPromise = require('fs-promise');

const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';

requestPromise.get(url)
.then((response) => {
return fsPromise.writeFile('article.html', response);
})
.then(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});

vernut'sia

Async/Await delaet kod chishche, chem promisy

Promisy ochen' khoroshaia al'ternativa kolbekam, no v ES2017 / ES8 spetsifikatsii poiavilsia async/await, kotoryi predlagaet eshchio luchshee reshenie. Vse, chto vam nuzhno, eto napisat' funktsiiu s prefiksom async, vnutri kotoroi vy mozhete pisat' vashu asinkhronnuiu logiku imperativno. async/await mozhno ispol'zovat' priamo seichas pri pomoshchi babel.

Plokho

{ return fsPromise.writeFile('article.html', response); }) .then(() => { console.log('File written'); }) .catch((err) => { console.error(err); });">const requestPromise = require('request-promise');
const fsPromise = require('fs-promise');

const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';

requestPromise.get(url)
.then((response) => {
return fsPromise.writeFile('article.html', response);
})
.then(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});

Khorosho

const requestPromise = require('request-promise');
const fsPromise = require('fs-promise');

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

vernut'sia

Obrabotka oshibok

Brosat' oshibki -- khoroshee reshenie! Eto oznachaet, chto vo vremia vypolneniia vy budete znat', esli chto-to poshlo ne tak. Vy smozhete ostanovit' vypolnenie vashego prilozheniia v nuzhnyi moment i videt' mesto oshibki s pomoshch'iu stek treisa v konsoli.

Ne ignoriruite otlovlennye oshibki

Nichego ne delaia s poimannoi oshibkoi, vy teriaete vozmozhnost' ispravit' oshibku ili otreagirovat' na neio kogda-libo. Vyvod oshibki v konsol'(console.log(error)) ne daet luchshego rezul'tata, potomu chto oshibka mozhet poteriat'sia sredi vyvodimykh zapisei v konsol'. Esli vy zavorachivaete kusok koda v try / catch, znachit vy predpolagaete vozniknovenie oshibki. V takom sluchae vy dolzhny imet' zapasnoi plan.

Plokho

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

Khorosho

try {
functionThatMightThrow();
} catch (error) {
// Odin iz variantov (bolee zametnyi, chem console.log):
console.error(error);
// Drugoi variant - izvestit' pol'zovatelia pro oshibku:
notifyUserOfError(error);
// I eshche variant - otpravit' oshibku na server :
reportErrorToService(error);
// Ili ispol'zuite vse tri varianta!
}

vernut'sia

Ne ignoriruite oshibki, voznikshie v promisakh

Vy ne dolzhny ignorirovat' oshibki, voznikshie v promise, po toi zhe prichine, chto otlovlennye oshibki v try / catch.

Plokho

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

Khorosho

getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
// Odin iz variantov (bolee zametnyi, chem console.log):
console.error(error);
// Drugoi variant - izvestit' pol'zovatelia pro oshibku:
notifyUserOfError(error);
// I eshche variant - otpravit' oshibku na server :
reportErrorToService(error);
// Ili ispol'zuite vse tri varianta!
});

vernut'sia

Formatirovanie

Formatirovanie nosit sub'ektivnyi kharakter. Net zhestkikh pravil, kotorye vy dolzhny sobliudat'. Glavnoe pravilo -- ne sporit' po povodu formatirovaniia. Est' kucha instrumentov dlia avtomatizatsii etogo. Ispol'zuite tol'ko odnu! Spor po povodu formatirovaniia -- eto pustaia trata vremeni i deneg dlia razrabotchikov.

Dlia veshchei, kotorye ne podpadaiut pod sferu deistviia avtomaticheskogo formatirovaniia (otstupy, tabuliatsiia ili probely, dvoinye ili odinarnye kavychki i t.d.) smotrite kakoe-to rukovodstvo.

Ispol'zuite odin variant imenovaniia

JavaScript iavliaetsia netipizirovannym, poetomu imenovanie vashikh peremennykh, funktsii i t.d govorit vam mnogo chego o nikh. Eti pravila nosiat sub'ektivnyi kharakter, tak chto vasha komanda mozhet vybrat' tot variant, kotoryi khochet. Nevazhno kakoi variant vy vyberite, glavnoe priderzhivaites' vashego vybora.

Plokho

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 {}

Khorosho

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 {}

vernut'sia

Sviazannye funktsii dolzhny nakhoditsia riadom

Esli funktsiia vyzyvaet druguiu, sokhranite eti funktsii vertikal'no blizko v iskhodnom faile. V ideale, funktsiia, kotoraia ispol'zuet druguiu funktsiiu, dolzhna byt' priamo nad nei. My sklonny chitat' kod sverkhu-vniz, kak gazetu. Iz-za etogo udobno razmeshchat' kod takim obrazom.

Plokho

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(user);
review.perfReview();

Khorosho

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();

vernut'sia

Kommentarii

Kommentiruite tol'ko tot kod, kotoryi opisyvaet slozhnuiu biznes-logiku

Kommentarii ne obiazatel'ny. Khoroshii kod opisyvaet sebia sam.

Plokho

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

// dlina stroki
const length = data.length;

// Prokhod po kazhdomu simvolu dannykh
for (let i = 0; i < length; i++) {
// Beriom simvol.
const char = data.charCodeAt(i);
// Delaem khesh
hash = ((hash << 5) - hash) + char;
// Preobrazovuem v 32-bitnoe chislo
hash &= hash;
}
}

Khorosho

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;

// Preobrazovuem v 32-bitnoe chislo
hash &= hash;
}
}

vernut'sia

Ne kommentiruite nenuzhnyi kod

Dlia etogo sushchestvuiut sistemy kontrolia versii. Ostav'te staryi kod v istorii sistemy kontrolia versii.

Plokho

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

Khorosho

doStuff();

vernut'sia

Ne vedite zhurnal kommentariev

Pomnite: nuzhno ispol'zovat' sistemu kontrolia versii! Net neobkhodimosti v neispolniaemom kode, zakommentirovannom kode i osobenno v zhurnale kommentariev. Ispol'zuite git log, chtoby poluchit' istoriiu!

Plokho

/**
* 2016-12-20: Removed monads, didn't understand them (RM)
* 2016-10-01: Improved using special monads (JP)
* 2016-02-03: Removed type-checking (LI)
* 2015-03-14: Added combine with type-checking (JR)
*/
function combine(a, b) {
return a + b;
}

Khorosho

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

vernut'sia

Izbegaite pozitsionnykh markerov

Oni, kak pravilo, prosto meshaiut. Pust' funktsii i imena peremennykh vmeste s sootvetstvuiushchim uglubleniem i formatirovaniem daiut vizual'nuiu strukturu koda.

Plokho

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

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

Khorosho

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

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

vernut'sia

Perevody

Takzhe dostupna na drugikh iazykakh:

vernut'sia

About

No description, website, or topics provided.

Resources

Readme

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 6