Понимание как основа устойчивости к ошибкам
на примерах из жизни
Красной Шапочки
и пирожочков
Красной Шапочки
и пирожочков
Оба представления содержат ошибки, что не мешает коду работать
субъективное представление о том, как что-то в реальном мире работает
код — формальное описание такой модели
Изменения
Изменения
Изменения
Изменения
Иcкажение
Исправление ошибок
Добавление ошибок
Иcкажение
Сложность
Количество мысленных усилий, требуемых для воссоздания исходной когнитивной модели
function packPies(redRidingHood, packA, packB, pies) {
const parcel = [];/*1*/
for (/*2*/let i = 0; /*3*/i < pies.length;/*4*/i++) {
let pie = null;/*5*/
switch (pies[i].type) {/*6*/
case "HINKAL"/*7*/: pie = packA(pies[i]); break;/*8*/
case "CHEBUREK"/*9*/:
case "PIE"/*10*/: pie = packB(pies[i]); break;/*11*/
}
if (pie /*12*/) { parcel.push(pie);/*13*/ }
}
if (parcel.length /*14*/) {
redRidingHood.carry(parcel);/*15*/
}
}
function packPies(redRidingHood, packA, packB, pies) { // 1
const parcel = []; // 0
for (let i = 0; i < pies.length; i++) { // 1
let pie = null; // 0
switch (pies[i].type) { // 0
case "HINKAL": pie = packA(pies[i]); break; // 1
case "CHEBUREK": // 1
case "PIE": pie = packB(pies[i]); break; // 1
} // 0
if (pie) { parcel.push(pie); } // 1
} // 0
if (parcel.length) { // 1
redRidingHood.carry(parcel); // 0
} // 0
} // 0
a || b || c
a && b && c
switch + case + default
try + catch + finally
if for while switch ? && || catch
function packPies(redRidingHood, packA, packB, pies) { // 0
const parcel = []; // 0
for (let i = 0; i < pies.length; i++) { // 1
let pie = null; // 0
switch (pies[i].type) { // 1 + 1
case "HINKAL": pie = packA(pies[i]); break; // 0
case "CHEBUREK": // 0
case "PIE": pie = packB(pies[i]); break; // 0
} // 0
if (pie) { parcel.push(pie); } // 1 + 1
} // 0
if (parcel.length) { // 1
redRidingHood.carry(parcel); // 0
} // 0
} // 0
служебные слова и операторы, например
! != === !=== && || * *=
+ += - -= / /= %
() {} [] let const var
for if while catch case
идентификаторы, константы, типы, свойства объектов
1 'hello' true testName length
null undefined
function packPies(redRidingHood, packA, packB, pies) {
const parcel = [];
for (let i = 0; i < pies.length; i++) {
let pie = null;
switch (pies[i].type) {
case "HINKAL": pie = packA(pies[i]); break;
case "CHEBUREK":
case "PIE": pie = packB(pies[i]); break;
}
if (pie) { parcel.push(pie); }
}
if (parcel.length) {
redRidingHood.carry(parcel);
}
}
оператор | N | оператор | N | операнд | N | операнд | N |
---|---|---|---|---|---|---|---|
const | 1 | . | 5 | parcel | 4 | 1 | |
= | 5 | ++ | 1 | i | 6 | 1 | |
[] | 3 | switch | 1 | pies | 4 | 1 | |
; | 10 | case | 3 | length | 2 | packA | 1 |
for | 1 | break | 2 | null | 1 | packB | 1 |
{} | 4 | () | 6 | type | 1 | push | 1 |
let | 2 | if | 1 | pie | 5 | redRidingHood | 1 |
< | 1 | 0 | 1 | carry | 1 | ||
n1=15 N1=46 |
n2=16 N2=32 |
Длина программы | N = N1 + N2 = 46 + 32 = 78 |
Размер словаря | n = n1 + n2 = 15 + 16 = 31 |
Объем программы | V = N * log2n = 386,43 |
Сложность | D = (n1 * N2) / (2 * n2) = 15 |
Усилия программиста | E = V * D = 5796,43 |
Число багов | B = E2/3/3000 = 0,107 |
Объединяет предыдущие метрики в один показатель относительной надежности кода
"complexity": ["error", { "max": 10 }]
"max-lines-per-function": [
"error", {"max": 2, "skipBlankLines": true}
]
"max-depth": ["error", 3]
"cyclomatic-complexity": ["error", { "max": 10 }]
Если волк съел пирожок, попытаться еще N раз
phone
.addEventListener("callFromGrandma", makePie);
function makePie(event, counter = 0) {
bakePie(event)
.then(sendPie)
.then(grandmaGetsPie)
.catch(() => eatenByWolf(event, counter++));
}
function eatenByWolf(event, counter) {
if (counter < N) {
return makePie(event, counter);
} else {
return excuse();
}
}
Если волк съел пирожок, попытаться еще N раз
Работа с потоками событий в декларативном стиле.
fromEvent("callFromGrandma", phone)
.pipe(
concatMap(event => of(event).pipe(
concatMap(bakePies),
concatMap(sendPies),
retry(N)
)),
catchError(excuse)
)
.subscribe(grandma);
function eatenByWolf(event, counter) {
if (counter < N) {
return makePie(event, counter);
} else {
return excuse();
}
}
function eatenByWolf(event, counter) {
if (counter > N) {
return excuse();
}
return makePie(event, counter);
}
class PieFactory {
constructor() {
this.maker = new RedRidingHood();
}
bakePie(pie) { this.maker.bakePie(pie); }
sendPie(pie) { this.maker.sendPie(pie); }
}
const factory = new PieFactory();
fromEvent("callFromGrandma", phone)
.pipe(
concatMap(factory.bakePie),
concatMap(factory.sendPie)
).subscribe(grandmaGetsPie);
class PieFactory {
constructor() {
this.maker = new RedRidingHood();
this.friend = new Friend();
}
bakePie(pie) { return pie.type === 'PIE'
? this.maker.bakePie(pie)
: this.friend.buyPie(pie)
}
}
const factory = new RedRidingHood();
function basePresentPack(item) {
const box = putInBox(item);
const pack = addCard(box);
return item.type && item.type === 'HINKAL'
? addFork(pack)
: pack;
}
/*где-то в коде*/
const pie = bakePie(pieType);
const box = basePresentPack(pie);
function basePresentPack(item) {
const box = putInBox(item);
const pack = addCard(box);
return pack;
}
function piePack(pie) {
const pack = basePresentPack(item);
return pie.type === 'HINKAL'
? addFork(pack)
: pack;
}
function cook(recipe, ingredients) {
const mixture = mix(recipe, ingredients);
const cookedItem = bake(mixture);
const result = someCookingMagic(cookedItem);
return result;
}
function someCookingMagic(item) {
const result = decorate(item);
return sayAbracadabra(result);
}
function cook(recipe, ingredients) {
const mixture = mix(recipe, ingredients);
const cookedItem = bake(mixture);
const result = decorate(cookedItem);
return sayAbracadabra(result);
}
fromEvent("callFromGrandma", phone).pipe(
.pipe(
concatMap(event => {
const needPie = !!event.pie;
const pie = needPie ? event.pie : null;
const type = needPie ? pie.type : null;
const pieType = type || 'DEFAULT_TYPE';
return type ? bakePie(pieType) : null;
}),
concatMap(sendPie)
).subscribe(grandmaGetsPie);
fromEvent("callFromGrandma", phone).pipe(
concatMap(event => event.pie
? bakePie(event.pie.type || 'DEFAULT_TYPE')
: null
),
concatMap(sendPie)
).subscribe(grandmaGetsPie);
Тратят время и засоряют код?
// спечь пирожок по просьбе бабушки
function bakePie(grandmaRequest, resources) {
let pie = grandmaRequest.pie;
if (pie.ingredients.some(x => !resources[x])) {
pie = X.availablePie(resourses) /*1*/;
}
switch (pie.type /*2*/) {
case "PIE": return makeA();
case "HINKAL": return makeB();
case "CHEBUREK": return makeC();
..../*3*/
}
}
Два золотых правила
Два золотых правила
// 1: используем X для оптимизации (Y работало плохо)
// 2: не проверяем на null, потому что Z
// 3: список из JIRA-XXX
// bakePie(
// { type: 'PIE', ingredients: ['flour','oil','meat'] },
// { 'flour': true, 'oil': true, 'meat' true }
// )
Слишком много условий в коде
function cookForGrandma(recipeNames) {
getRecipesFromServer(recipeNames)
.then(cookByRecipe)
.catch(handleErr);
}
function cookByRecipe(recipes) {
if (!recipes) return;
recipes.forEach(recipe => {
if (recipe.ingredients) {
/* mixIngedients */
}
if (recipe.type === 'soup') {
/* Cook soup */
} else { /* Bake pie */ }
});
}
Удалите лишнее
Слишком много условий, и они нужны
function packPies(courier, packA, packB, pies) {
const parcel = [];
for (let i = 0; i < pies.length;i++) {
let pie = null;
switch (pies[i].type) {
case "HINKAL": pie = packA(pies[i]); break;
case "CHEBUREK":
case "PIE": pie = packB(pies[i]); break;
}
if (pie ) { parcel.push(pie); }
}
if (parcel.length ) {
courier.send(parcel);
}
}
Слишком много условий, и они нужны
Измените поведение/архитектуру, чтобы проверки больше не требовались
const pack = {'HINKAL': packA,'CHEBUREK': packB,'PIE': packB };
function packPies(courier, pies, packMap = pack) {
courier.send(pies
.filter(pie => packMap[pie.type])
.map(pie => packMap[pie.type](pie)));
}
Слишком много условий, и они точно нужны!!!
function getRecipe(pieType) {
return getHttpRecipe(pieType).then(extract);
)
/*где-то в коде*/
getRecipe('CHEBUREK')
.catch(error => {
if (error && error.message) {
notify(error);
}
return DEFAULT_RECIPE;
});
/*где-то еще в коде*/
getRecipe('HINKAL')
.catch(error => {
if (error) { console.log(error.message); }
return DEFAULT_RECIPE;
});
Слишком много условий, и они точно нужны!!!
Перенесите условие туда, где оно меньше всего мешает (на более базовый уровень).
function getRecipe(pieType) {
return getHttpRecipe(pieType).then(extract).catch(error => {
if (error.message) { notify(error.message); }
return DEFAULT_RECIPE;
});
)
Или объедините и обрабатывайте разом.
function bake(pieType) {
return getRecipe(pieType).then(mix).then(bake).catch(handleBakeError);
)
Задача не сложная и не очень важная, сделаем потом
// добавить loader
fromEvent("callFromGrandma", phone)
.pipe(
concatMap(event => of(event).pipe(
concatMap(bakePies),
concatMap(sendPies),
retry(N)
)),
catchError(excuse)
)
.subscribe(grandma);
Задача не сложная и не очень важная, сделаем потом
Стандартные вещи делайте сразу, пока они еще не требуют значительных усилий, например:
concatMap(event => of(event).pipe(
tap(loaderIncrement),
concatMap(bakePies),
concatMap(sendPies),
retry(N),
finally(loaderDecrement),
)),
Не надо обсуждать код, который работает
Не надо обсуждать код, который работает
Постоянная рекалибровка когнитивных моделей повышает устойчивость.
И тогда пирожочки будут реже теряться на пути к бабушке!