Дзесять распашчатак JavaScript, які викорыстоўваюцца ў практыцы, і наследкі ўжывання кожнага з яных
Клазы захавання, карты пошуку, об’екты рэзультатаў, AbortController, allSettled, фабрыкі і маленькія кэшы: калі кожны патэрн ўзмацняе роботу і калі ён пагубна адбіваецца.
Крыніцы: I Reuse These 10 JavaScript Patterns in Almost Every Project
Незалежна адзінай кансэпцыя коду — чы гэта панель керавання на базе React, API на Node.js чы маленькі скрыпт для автоматызацыі — ў канечнай лініі сталкаецца з тымі ж проблемамі: функцыі прымаюць няпূраныя данні, асінхронныя адпаведзі прыходзяць не па порядку, умовныя выразы становяцца занадта сложнымі через змены бізнес-правіл, копіі тых самых працэў перастаюць супараднацца, а обработка памылак размывае межу між „гэта не выйшла“ і „проста не было чаго вярнуць“. Фрамворкі прыходзяць і зникаюць, але гэтыя проблемы застаюцца выключна стабільнымі. Дзесять патэранаў, прыведзеных нижэй, — это цялеспрабавныя, але не выказнічныя способы ўсунуць іх. Кожны з іх скармалюе набор станоў, пра якія павінна думаць функцыя, або спрыяе болей яснаму відображэнню абавескі за ўсё, і па кожным з іх вы таксама дазнаецеся, дзе ён перестае дапамагаць, адтакуль ніхто з іх не можа заменіць сабою разумнае ўзятые рашэнні.
1. Клазы-захаванні спамагаюць падтрымваць чытаемасць галоўнага шляху
Калі рэальная праца функцыі знаходзится ўнутрь кальколькоў вялізаных блакоў if, чытальніку патрэбнае зберагаць шмат вопытаў: чы є вхідныя даны, чы мае права апылальнік, чы была знайдзена інформацыя, чы яна знаходзіцца ў стане, які дазволяе такую змяну? Кожная дапамогаючая умова дадае ўзлегкавач на адступ і ўсё большае колькасць пунктав, якія трэба памятаць.
Клаза-захаваннік адпавядае на кожны з гэтых вопытаў з самага пачатку і завершвае свою роботу, калі адпаведзенне — «нет». У прыкладе нижэй спачатку адхоўваецца запыт з недастаткам ID, потым — запыт ад неавтарызаванага пользователя, пасля чого — запыт з недастаткам стацыі, а ўсё толькі пасля чатырох пераканаў выкалічваецца апдэйт.
async function publishArticle(articleId, currentUser) {
if (!articleId) {
throw new Error("Article ID is required");
}
if (!currentUser?.permissions.includes("article:publish")) {
throw new Error("You are not allowed to publish articles");
}
const article = await articleRepository.findById(articleId);
if (!article) {
throw new Error("Article not found");
}
if (article.status !== "draft") {
throw new Error(`Cannot publish an article in ${article.status} status`);
}
return articleRepository.update(articleId, {
status: "published",
});
}
Праўя выгода — гэта не меншыя вводкі; гэта зростаючая паверенасць. Пасля першага контроля ідэнтыфікатор існуе. Пасля другага, аберанты маюць правы на доступ. Пасля трэцяго, article ёсць рэальны об’ект, таму не патрэбна опцыйная ланцюгавае вызначэнне для працы з рэштой коду. Вызов update можа быць прачытан без жадных нерашэных праблем. Є адна маленькая спецыяльная ситуацыя, якую варта зазначыць: currentUser?.permissions.includes(...) яшчэ і так выклікае TypeError, якщо об’ект пользователя не мае масіву permissions, і гэта самэ ў тым родзе несупараднасці, якую усуне наступны патэрн.
Функцыі-захаванні ствараюць проблемы, калі ў довгай функцыі є багато выходаў па кожнай дробнай умове. Яны найэфектывнейшы для пераканальных умоў, недзеясаваных стаўак, нехваткі рэсурсоў і ситуацый, якія вже завершыліся. Якща функцыі-захаванні пачынаюць реалізаваць кальколька разных бізнес-правіл, значыць функцыя, верагатна, прымеўае занадта многія рашэнні і яе трэба раздзеліць.
2. Нормалізаваць даныя раз, на межы
JavaScript дазволяе значэнням з адной і той жа сэнсам выклікватыся у розных формах. Флаг можа быць true, "true", 1 або "1". ID можа быць числам у базе дадзеных і стрэлкамі ў URL. Дата можа быць об’ектам Date, стрэлкамі ISO, стрэлкамі локалізацыі або часовым пазначэнням. Якщо кожная функцыя інтэрпретуе іх сама, прыкладнае рашэння паступова накаплівае кальколька суперасунковых вярсей таго, як выглядаюць правільныя даныя.
Рашэнь — пераканаваць значэнні аднойчы, у тым момант, калі яны пераходзяць за значымы межа, прычымо да таго, калі іншы код яны не будзе выкарыстоўваць:
- Адпрацоўвальнік API ператварае строкі на рэжыме транспорту ў тыя типы, якія чакае слой сервісу.
- Адаптар дадзэнняяўказвае зусібны адпаведны рэспанс на внутранюю структуру проекту.
- Адпрацоўвальнік адправы формы выкарыстоўвае функціі для адчысцення строк і обработкі необавязковых палей прычымо да запуску бізнес-логікі.
Час мае значэнне. Калі пераканаванне адкладваецца, захоўнічыя пераконтрэны праспяваюцца ў всіх месцах: адна функцыя пераканваецца status === true, іншая — status === "true", трэця выкарыстоўвае прынцып істыннасці. Кожная з яных працуе для сваго вызывача, але значэнне status больш не ёсць стабільным.
Сужаныя внутрашніе кантракты таксама робяць тэсты болей сфармаванымі. Калі шар запыткаў вялікі час уже перадае справжні булевы значэнне, службе не патрабуюцца тэсты для кожнага можлівага текстовага варіянта гэтага значэння. Шар нормалізацыі все ж такі патрабуе своіх сабэйшых тэстаў, але всё, што знаходзіцца пасля яго, становіцца простым.
Нормалізацыя — гэта не тое ж сама, што прымкненне чаго-небудзь без адзінай пераканання. Яна ўпрыемна, калі намеравана падтрымка кальколя разных форматаванняў; двусмысленныя або некалькольваныя значэнні все ж такі трэба адхіляць. Ператварэнне "abc" у NaN і далейшая ўзяцеўка гэтага значэння — гэта не нормалізацыя, а перанесенне процэсу верыфікацыі на які-небудзь пазнейшы, больш заплутаны момент.
3. Об’екты опцый робяць вызовы самаопіснымі і простэйшымі для расширэння
Параметры пазовага типу ўсё ж прыемны для аднаго чы роўна двух явных вводаў. Але калі функцыя становіцца больш складной, яны стаюць нестабільнымі, особліва калі калькі параметраў маюць аднаковы тип. Калі вы вызываеце createUser(name, email, true, false, 3), чытальніку даводзится звертацца да самай дэфініцыі, каб з’ясавіць, што значыць кожны параметр, а типы з TypeScript у дэкларацыі ніяк не памагаюць у момент вызыву.
Калі функцыя начынае прымаць калькі значэнняў у стыле налашчання, зазвычай ясней выглядае адзін об’ект з налашчаннямі:
await createUser({
name,
email,
sendWelcomeEmail: true,
requireVerification: false,
retryLimit: 3,
});
Такі вызыв сам сабе ўсё раз’ясняе, необавязковыя атрыбуты можаць мець значэння за замовчаннем (зазвычай праз деструктурыраванне ў сігнатуре), а дадаць новы параметр не змушвае кожнага вызывальніка пераранжаваць свои аргументы. Найбольшая практычнасць даесягае функцыям-сэрвісам, дапаможнім калектарам налашчанняў, загрузчыкам дадзеных і іншым утилітам, якія постаўляюцца ў развіцыі.
Ён таксама усунея класычны «запах» булінавых параметраў. Простае true у вызове практычна ніколи не адзначае, якую дзейснасць ён актываў; імяваная власнасць такое робіць.
Не застаўляйте яго ў всіх месцах. add({ a, b }) не ўсё ясней за add(a, b). Выбірайце об’ект options, калі аргументы описваюць настройкі, калі калькі з іх є необавязковымі, калі калькі значэнняяў маюць аднойчы тип, або калі сігнатура можа продовжваць змяняцца.
4. Карты абшукаў ператвараюць розгалужэнняяў у ясныя зв’язкі
Ланцюгі запэўненняў if або дугі блакі switch часта з’яўляюцца, калі адна значэнне выбірае обробнік, форматавальнік, верыфікатор або стратэгію. Кожны новы кейс падлягае контрольнаму тэксту і додае ўтрымоц, дзе трэба вярнуцца да стандартной дзейснасці.
Карта асоціяцыяў працоўна паказвае зв’язок: гэты ключ асоціюецца з тым паведаннем. Чыргун статусу выбірае працоўнік для обробкі, а неканальны статус спрычынае явныю памылкай узамест на тыхое працэсаванне.
const statusHandlers = {
pending: handlePendingOrder,
paid: handlePaidOrder,
cancelled: handleCancelledOrder,
};
function processOrder(order) {
const handler = statusHandlers[order.status];
if (!handler) {
throw new Error(`Unsupported order status: ${order.status}`);
}
return handler(order);
}
Гэты патэрн ўзьміцца на выгоду, калі галузі ўзаемна незалежныя і маюць аднаковыя правілы: серыялізаторы і форматары, працоўнікі змаганняў і статаусу, верыфікаторы, правілы доступу. Падтрымка новага кэсу значыць дадаць адну ентрыту, а не расшырваць умовнае вырачэнне.
Існуе адзін тонкі падача з простымі об’ектамі. Ключы, якія спадкуваны з Object.prototype, такія як "toString" або "constructor", таксама ўзьмоцца як функцыі, таму нечаканая значэння статуса можа выбраць адную з іх у замяну на выкананне захістных мераў. Якщо ключы падаюць з вводу корыстніка, належыць использоваць Map, об’ект, створаны з Object.create(null), або пераконтрацца за дапамой Object.hasOwn.
Утримайцеся ад викорыстання карточак для пошуку, калі логіка справаўна последовая. Якщо рашэнне залежыць ад комбінацый стану, паканальных выбораў аб упорядкованных переходаў, ўзяць яе у вачыну карточкай зазвычай маскіруе поток справаў у замяну на яго адчытнае паказанне. Карточка ўжываецца тады, калі адны ключ выбірае адну цэлую, самастоятельна зрозумелую дзеяння.
Не трэба размешчаць складную логіку пры самай карце. Функцыі-обрабоўчыкі з іменамі даюць болей чыткія выводы стэка, іх лёгча теставаць адсакроза, і так карца можа сфокусавацца на выборы.
5. Об’екты рэзультата аддзеляюць чаканую нявыпавальнасць ад неудачнай екзекуцыі
Эксцэпцыі — гэта правильны інструмент для нечаканага, але багато няудачных рэзультатаў ёсць абычнымі. Пошук можа нічога не знайсці. Рэўізію можна адхіліць, калі адреса электранайты ўжо выкарыстоўваная. Пераход стану можа быть забаронены чыннымі правіламі. Якшо всё гэта прыходзіць у вобразе загальных эксцэпцый, тыя, хто іх вызывае, вынужданы аналізаваць паведамленняя пра адказы чы то ловіць шырокія типы адказоў, толькі каб дазнацца, што сталася.
Калі аперацыя мае калькольнае вынікі, вяртанне малога об’екта рэзультата прычыняе, што яны становяцца часткай кантракту. Функцыя зніжэння нижчэй вяртае як даныя значэнні "не знайдзена" і "выйшла за термін", кожна з якіх мае стабільны, чытаемы прыладайма code, а праз успех — скорэктаваны замовленне.
function applyDiscount(order, discount) {
if (!discount) {
return {
ok: false,
code: "DISCOUNT_NOT_FOUND",
message: "The requested discount does not exist.",
};
}
if (discount.expiresAt < new Date()) {
return {
ok: false,
code: "DISCOUNT_EXPIRED",
message: "The discount has expired.",
};
}
return {
ok: true,
value: {
...order,
total: order.total - discount.amount,
},
};
}
Апелянтары тепер можаць прыймать рашэнні на адказе ok і code без чытання тексту, назначанага для людзей. Точныя назвы поль не так важны, як адносна стабільна структура дадзеных: у дзеярых проектах гэта моделюецца як дыскурмініраваная юнія на TypeScript па параметры ok, у іншых викорыстоўваюцца success, data і error. Галоўнае — каб вынік быў чыста адзначаны. У рэальнай реалізацыі таксама трэба было б вялічынуць, што будзе, калі зніжэнне перасягне загальную суму замовлення, адтолькі ўсуненне, паказана тут, можа даць ад’ёмную суму.
Об’екты рэзультатаў не можаць заменіць эксцэпцыі ў всіх ситуацыях. Выключайце эксцэпцыі, калі залежнасць не працуе як чакалася, калі нарушана інварыянтнае правіло, або калі ныякі слой не можа зробіць нічага практычнага ў зв’язку з проблемай. Вяртайце рэзультаты, калі неудача є стандартной часткай данай сферы і апэлюючы код павінен на яе рэагаваць.
Дадатковыя прынтэсы — гэта можлівасць тэставання: кейсы неудачы прывязаны да типу, які вяртаецца, таму іх лёгка пераканаць. А шырокі блок try/catch больш не спаўнае разам адмовленую бізнес-правілу з перарваным з’ўязкам з базай дадзеных.
6. AbortController делае асінхронную власнасць явной
Багато бягаў у фронтэндзе выходзяць з-за перакрыцча асінхронных заданняў. Пользователь выправляе тэрмін пошуку, пачынаецца другі запит, а першы завершаецца пасляўсі. Кожны запит успел, але на экране паказваюцца застарэлыя рэзультаты.
Прытэк не ёсць проблемай; проблема — у правах на кераванне. Аплікацыя павінна вырашыць, які з рэзультатаў падчас выканання ёш можа адначасова змяніць стан. AbortController выражае гэтае рашэнне:
- У полі пошуку — абарыць пярэдні прытэк кожны раз, калі змянюецца запит.
- У компаненте — абарыць незавершаную роботу, калі компанент адключаецца.
- У робочым лянцюгу залежных крокаў — перадаць аднаковы
signalчерез усе крокі, так ўбытак адзінаго з іх зупініць усю лянцюговую працу.
Свэдчыць пра намераваная ануляцыя. Абартацыя зазвычай означае, што прыкладна програма вынікла, што рэзультат больш не мае значэння, і гэта разлічна ад збою. За дапамогою fetch абартаваныя запыткі вядуцься з адмоўкай, па якой name адмоўкі ёсць "AbortError", таму можна яе чытальна адрозніць. Журналаванне кожной абартацыі як адмоўкі заполняе систему монітарынгу зайвай інфамацыяй, тады як спрыятліванне справжніх сетавых збоў як абартацый ведае ўтрату корисных даказванняў.
Чым большэй меры аплікацыя павязана з запрашамі на фоне, рэальным пошукам, пераходам па маршрутах і абэрантнымі запрашамі, тым больша ўжо значэнне гэтага. Прыступленне да асінхронных задач у JavaScript адбываецца без працы; AbortController вялічынуе момент, калі гэта задача перестае належаць таму, хто яе запускаў. Чырпнуць глэбачэй у кантэксте пошуку: пазнакоміцеся з тым, як вылечыць ситуаціі канкурэнціі, якія немагчыма рашыць за дапамою тэхнікы debouncing.
7. Promise.allSettled — для зберагчэння частковых рэзультатаў незалежных задач
Promise.all падходзіць тады, калі сумарны рэзультат не мае значэння, якщо хоць адна з операцый не выйдзе. Але ён занадта агрэсіўны, калі задачы незалежныя: адна паразітная рэакцыя ведае пра знехадзенне ўсіх рэзультатаў, якія такім чынам былі атрыбутаваны.
Разглядзім канцэлерыю, складваную з паказанняў пра выручку, спавешчэнняў, няўзледжыцельнае абсалаванне і стану системы, прычаму кожны элемент падае з разных джэрел. Якщо неабавязковая служба выключыцца, тады празьмяванне всей канцэлерыі часта стварае горшыя умовы для викорыстання, чым паказанне таго, што было завантажана, і пазначэнне таго, што не завантажылася.
Promise.allSettled чакае на завершэнне кожной обяцанні і адзельна паведамляе пра кожны рэзультат у вачынку з status: "fulfilled" і value, або з status: "rejected" і reason. Адвокар можа выкарыстоўваць успешныя значэння, пераказаць пра неудачы і адляціцца, чы робота в цэлым все ўсё ж мае сэнс.
Не прагніце даўаць даныя, толькі каб група запаведзей выглядала стойкаю. Дэякія групы запаведзей, з пунктуазнай точкі зору бізнесу, ўособлеваюць як адна транзакцыя. Якщо ствараецца замовленне, резервуецца запасы і здзейснюецца платеж за дапамогою карткі, усе гэта должна або успець, або не задацца разам; збір узгоджаных рэзультатаў залучае нестабільны стан; патрэбны компенсуючыя дзеяння або болей эфектыўная стратэгія коордынацыі.
Вжывайте allSettled, калі задачы справды ўзаемна незалежныя, а вызываючы код мае чыткі план на частковую невярнасць. Асалодны вопыт не ў тым, який памочнік Promise будзе зручным, а ў тым, чы можна яшчэ отрымаць правдзівую адпаведь, калі хоць адна частка зазнае невярнасці. Больш варыянтаў практычнае застосавання описана ў дзевяці шаблонах Promise для надзеянага асінхроннага JavaScript.
8. Функцыі чыстай трансформаціі спрыяюць простам тэставанню правіл дадзеных
Большая частка коду прытакоў сумешчае форматаванне дадзейнасці з ўзяцьмам, логаваннем, атрыбутаваннем, зберагчыцьцю і апдэйтамі стану. Тады для тэставання адной простай правілы патрэбна перабудова всіяго сераўаку навакол яе.
Выкарыстоўванне стабільных трансфармацый у маленькіх чыстых функцыйях вырашае гэты проблема. Чыстая функцыя прымеўляе вхідны данні, вяртае выходны результат і не зменяе нічога паza сабой. Тыповыя прыклады — нормалізацыя записаў API, вычысленне падрыхтаваных значэнняў, групаванне элементаў, складанне об’ектаў запыту або пераклад дадзейнасці домэну ў модель адображэння.
Глыбокая перадчыннасць — гэта відчутныя межы між ўзяцьмам дадзейнасці і яе адрабаткай. Функцыя, якая групавае записы працавнікаў, не патрэбуе ведаць, якім чынам яны былі заванесены. Функцыя, якая стварае пэйлоад запыту, не патрэбуе стану компонентаў, якщо значэнняя можна перадаць як аргументы.
Дыягностыка таксама становіцца простэйшай. Калі правы вхідны данны даюць неправы выходны результат, можна самостойна працаваць з ператворэнням. Якщо ператворэнне правельнае, далейшая розследавання спрямовываецца на тое, што створыла вхідныя данны або выкарыстала выходны результат.
Не кожная выразнае структура заслуговае на свойя функцыі; купка адна-рядковых обертакоў ускладнюе навігацыю без дадзення новага значэння. Выделяйце такія структуры, калі ператворэнне кодуе правіло бізнесу, якое вы викорыстоўвайце знову, мае кілька варыянтаванняй або заслуговае на сваі тэсты.
9. Фабрыкі адкрываюць залежнасці та спакульную настройку
Фабрыка — адны з простейшых способаў кантролювання таго, чым залежыць модуль. У працоўніку замест імпорту кліента базы дадзенаў, прыстрою для логавання, кэшу чы настройкаў з глобальнага месца, фабрыка прыме яны як аргументы і вяртае функцыі, якія працуюць з імі.
Гэта ўсё боле корыстна ў сервісах з адзюльтайн-боку і спяльных модулях. Залежнасці стаюць виднымі, зменшываецца скрытыя взаінадзейвання, а тэсты можу працаваць з фальшывым рэпазітарыем або логгерам без неабяжнага імпорту модуляў, тады калі ў працэсе вырабніцтва викорыстоўваюцца рэальныя элементы. Бізнес-логіка застаецца прыкладнай да паведання, а не да стварэння елементаў, якія з ёю взаідзейнуюць.
Фабрыкі таксама падходяць для груп функцый, якія выкарыстоўваюць стабільную настройку. Фабрыка HTTP-кліента можа зафіксаваць базовы URL, токен аутаналізацыі, таймаут і логгер аднойчы і стварыць методы, якія ўсе іх викорыстоўваюць; клаудзіры буду перадаваць гэтую настройку, таму яна не будзе перадавалася з кожным вызывам.
Існуе рызик стварэння саўстрактной рамкі для вводу залежнасцяў, калі ў базе коду вже існуе такая рамка або калі паслужыць звычны імпорт. Іспользуйце фабрыкі, калі залежнасці значным чынам зменяюць працэздатнасць, калі тэстам патрэбны контроль над імі, або калі павінны сусістаць калькі інстансаў з розным налаштаванням. Мэта — адкрываць залежнасці, а не ствараць ўтолькі што ўжо ентытную рамку для адтульківання.
10. Кэшы Map і WeakMap дапамагаюць ухіліцца ад павтарэння роботы
Map і WeakMap ўтвараюць хорашыя локальныя кэшы, калі той самы дорогі вычысленні аб’екта або метаданы, спецыфічныя для аб’екта, інакш бы павінны быць вычыслены занова. Парсер можа запам’ятаваць рэзультаты для кожнага вхіднага строкі. Форматар можа перысваіць дорогі вырашаныя данні. WeakMap, ключы якога павінны быть аб’ектамі, можа прыўязваць метаданы да гэтых аб’ектаў, не заставляючы іх застацца жывымі, калі нічога іншаго не аб’язваеся да іх.
Запіс у кэш — гэта простая частка. Адказ на пытанне, чы рэзультат у кэшы ўсё яшчэ актуальны, — гэта складная частка.
Кэшы выклікаюць проблемы стаўкамі, якія можна спакою прагназаваць:
- Ключ не уключае паданых дадзенняў, якія вплываюць на рэзультат, таму кэш, які выглядае нормальна ў тэстах, суплявае застарелыя значэнні ў рэальных умовах.
- Адпаведныя дадзенні зміняюцца, але нічога не анулюе запис у кэшы.
- Размах кэша не мае меж, як у
Mapна рэвэлі модуля, які існуе працоўваючы серверны процэс.
Перш чым дадаць кэш, неабходна выявіць рэальныя падтрымлівыя затраты і задаць ўсе параметры: власніка, структуру ключа, тымп працы і правіла анулювання. Часта перасчытанне є болей безпечным і дастаткова шырокім. Кэш становіць ся корыстным тады, калі робота є дорогай, паданыя дадзенні стабільныя, а сфера ўплыву можа застацца вузкай.
Гэты шаблон добра паказывае весь спіс. Map значна спрыяе стварэнню кэша, але простата рэалізацыя не значыць правильная: прышвартованне часу выконання будзе рэальным толькі тады, калі можна буда паспрацаваць, калі элемент перестае быць правдзивым.
Шаблоны должны усунуць неабязковасці прыняць рашэння, а не ўховваць іх
У сукупнасці гэтыя патэрны здобліяюць свае месца шляхом усунення неяснасцей, а не практыкаваннем функцыйянальных якостей мовы. Захоўнікі рана пазбавляюцца некоректных шляхоў; нормалізацыя на входзе даёт внутршнему коду прыкладныя значэння; об’екты з параметрамі робяць вызывы чытабельнымі та адкрываюць можлівасці для розшырэння; карты абшукаў адкрываюць прымітныя зв’язкі; об’екты рэзультаатаў захоўваюць вынікі значымымі; AbortController прызначае адпаведальнасць за асінхронную роботу; allSettled чыста адзвярцвае частковыя неудачы; чыстыя функцыі ізолююць рашэнні ад парадоксальных наследкаваў; фабрыкі адкрываюць залежнасці; а маленькія кэшы праходзяць праз павтаральную роботу, калі ўжо ясная іх чынныця.
Кожны з іх таксама можа быць неправильна прыменена. Абаротаванне вызову з двума аргументамі ў об’екте options — цэла просто формальнасць; вярненне рэзультатаў з коду, які павінен кінуць аберанцыю, плутае ситуацыю; карта можа закрыць архітектуру з упорядкованай логікой; кэш можа стаць застарэлым; а фабрыка — гэта абстракцыя, якой ніхто не патрэбуе. Шаблон стае шкодным, калі яго выбираюць за тое, насколькі ён выглядае вышукана, а не за рэальную дапамогу, яку ён надае.
Ключовыя выводы
- Оцэнюйце шаблон па тых компромісах, якія ён ствароўвае, а не па тым, насколькі ён выглядае складна.
- Валідзіруйце структуры, чыя затраты є видныя, а чыія перавагі застаюцца пасля пазнейшых змян.
- Хорашыя шаблоны чытка паказваюць прыпускі, адпаведальнасці, способы збою і дзеяздатныя шляхі функцыі.
- Найлепшыя з іх стаюць непазначальнымі: чытары замечаюць, што проблема стае простейшай, а не сам шаблон.