Галоўная / Артыкулы

Семь прытакоў, якія спрабуюць зробіць код прыемнейшым для змены наступным разработчыкам

Клазы абаранення, назвы, які выявляюць намеры, межы API, точныя типы, чыстыя функцыі прынятнага рашэння, структураваныя памылкі і сфармаваныя запросы прынятнага коду, з прыкладамі на JavaScript.

Выдавец: REACTAPP.TOP10 хв чытання2220 слоў

Крыніцы: 7 Coding Habits That Separate Senior Developers From the Rest

Выучэнне іншага фреймворка з часам перестаея робіць вас значная лепшым інжынерам. Тое, што далей дае рэзультаты, — это спосаб, якім вы пішаце і зменяеце код: апытны разрабатчыкі часта ствараюць менш коду, з болей ясным намерэнням, які лёгкая чытаць, теставаць і меняць, калі змянююцца трэбавання. У гэтым артыкуле рассказваецца пра сем конкрэтных прычын, кожная з яых супроводжваецца прыкладамі JavaScript або TypeScript «дагэўна-пасля», і пояснюецца конкрэтная выгода, яку кожна з іх дае.

Ніякі з гэтых прычын не вымагае новага інструменту. Цэлкам рашэння, якія вы прыменяеце пад час напісання функцыі, прызначэння зменной, моделювання дадзейнаў або стварэння pull request.

1. Захавайце видным «шчаслівы» шлях за дапамогою клазаў-захаванняў

Падазроўваю, што функцыя павінна знайсці пользователя, паўерыцца, што аблікакцыя актыва, паўерыцца, што у пользователя є правы, і толькі пасля чаго выконаць дзеянне. Натуральны першы варыянт — гэта калі адна умова знаходзится ўнутры іншай:

function performAction(userId) {
  const user = findUser(userId);
if (user) {
    if (user.isActive) {
      if (user.hasPermission) {
        return executeAction(user);
      }
    }
  }
  return null;
}

Це працюе, але сама важлівая операцыя знаходзится на трохі глęбшам рэвэрсе, а оператыя return null як альтэратыва на самым низу візуальна не пав’язана з услявамі, якія ўзводзяць да яе.

Альтэрнатыва — негайна адхіленне ад невярных кейсоў за дапамогою клазаў-захаванняў, так што кожна неудачная праблема будзе вырашаная на сваёй лініі. (Індэнтация ў гэтым фрагменте неравнамірная; усе чатыры заповедзі пасля пошуку должны знаходзіцца на аднам рэвэрсе.)

function performAction(userId) {
  const user = findUser(userId);
    if (!user) return null;
      if (!user.isActive) return null;
      if (!user.hasPermission) return null;
      return executeAction(user);
}

Тепер галоўны шлях выглядае проста: перакананне, перакананне, перакананне, дзеянне. Гэта не значыць, што вкладаная логіка завжды неправильная. Калі галузі выражаюць справжнія розныя наследкі, а не толькі праблемы, формат if/else можа быць найяснейшым. Мета — не дазволіць важлівайй бізнес-логіцы захавацца пад услявамі, якія на самай справе ўжо толькі служаюць фільтрамі.

Адэт: спачатку разбіраць асобыя случаі, а звычайны ход задачі залишаць без спецыяльных змян. Любой, хто запускае функцыю, должен бачыць, што зазвычай выходзіць, без неабясненага аналізу пяці роўняў клаўзэ. Да дапамогі гэтага кожны захід можа пазней вернуць более конкрэтны результат або паказаць более точную памылку, чаго важка дасягнуць у версіі з вкладанымі структурамі.

2. Называйце элементы так, як яны значаць

Мало чаго павышае чытаемасць так жа дарэчна, як кращыя назвы. Паглядзіце на гэтыя два рядкі:

const data = getOrder();
process(data);

Што такое data, і шта робіць з яго process? Ёсць неабходнасць чытаць іншы код, каб дазнацца. Пораўняйце:

const pendingOrder = getPendingOrder();
processOrder(pendingOrder);

Тыя ж два рядкі тепер паведамляюць, які тип порядку выкарыстоўваецца і шта з яным відбываецца. Хорашыя назвы зменшуюць псіхічны навантажэння, неабходнае для розумеў коду, таму што чытальнік больш не павінен реконструюваць значэнне на адной з контексту.

Назвы функцыйяў таксама заслуговуюць на аднойчынную увагу. Такі загальны дзеяслова, як гэты, маскуюць сутнасць функцыі:

handle(order);

тады калі гэтае выкладзенне яе чыста адразу паводзіць да розумэння:

calculateOrderTotal(order);

Таксама, простае send не адзначае, што самэў і каму ён надаецца:

send(order);

А описовая назва адпавядае на оба пытанні:

sendOrderConfirmation(order);

Дзягучыя назвы не ёсць метай; мета — назвы, якія выражаюць намер. Корыстны наследкі — гэта тое, што нечыярые назвы часта адкрываюць нечыярые функцыйі. Якщо handle важка перэназваць, можа быть, ён выканае калькі несувязанных задач і заслуговае на раздзелэнне.

Адвычка: пры выборы назвы запытайце ся, чы ўсё ж такі хто прачытае толькі гэтыя рядкі, не адзіраючыся нікуды інша, зможа зрозумець, што представляе гэтае значэнне чы функцыя. Якщо ні, продовжайце шукаць лепшую назву.

3. Закрыйце зовнішнія системы за межамі

Прыкладныя програмы взаімаецца з многамі зовнішнімі системамі: прадаўцамі платэжных апаратаў, службамі электранявіскаў, хмарнымі сховішчамі, прадаўцамі ідэнтыфікацыйных даных, API трохтоўскіх аплікацый і внутранімі мікросервісамі. Частая памылка — калі форматы ўсіх адпаведзенняў з цых систем проста вводзяцца без перакантролю ў код аплікацыі:

const customer = await stripeCustomer.get();
console.log(customer.email_address);
console.log(customer.customer_name);

Кожная лінія коду, якая чытае email_address або customer_name, тепер залежыць ад точных назваў поль у прадаўцы. Як толькі прадаўца перэназывае поле або змінюе структуру свага адпаведзення, гэтыя змены прабіваюцца ў усі частыні кодавой базы.

Рашэння — стварыць простую функцію-перакантроль, якая вызывае зовнішнюй сервіс і перакладае адпаведзенне у формат, які ўжо існуе ў вашай аплікацыі:

async function getCustomer(customerId) {
  const response = await externalService.getCustomer(customerId);
return {
    email: response.email_address,
    name: response.customer_name
  };
}

Рэштка коду выкорыстоўвае гэты внутраній формат і ніколі не бачыць назваў, якія вжывае прадаўцы:

const customer = await getCustomer(customerId);
console.log(customer.email);
console.log(customer.name);

Калі змінюецца зовнішня API, патрэбна толькі адчынка ўнутры getCustomer да апдэйту. Той жа рубік таксама являецца натуральным месцам для пераканання адпаведзя, нормалізацыі відсутняйых значэнняў і замены іх на фальшывыя пад час тэстаў. Гэта і є ідэя адаптароў і шарама проты корупцыі ў большых архітектурах, прыменяваная на рэгламенте адзінай функцыі.

Звычка: трэба заставляць зовнішняю складнасць на краях. Бізнес-логіка не патрабуе знаць прымускі кожнага сервісу трэціх сторон, і гэта адделенне становіцца ўсё цяжэйшым чым больша колькасць інтеграцый.

4. Зробіць недзеясныя станы важкімі да представлення

Парадны спосаб прызначэння моделі — зробіць кожнае полье неабавязковым, таму што гэта быстро і ніколі не спрычыняе проблем з компіляцыёй:

type Order = {
  id?: string;
  paymentId?: string;
  status?: string;
};

Затраты праўільна ўсюдзе, дзе викорыстоўваецца модэль. Кожны корыстувач павінен адбываць пераканальныя перагляды, таму што нічога не гарантуе наявнасць пэўнага поля:

if (order.id) {
  // ...
}
if (order.paymentId) {
  // ...
}
if (order.status === "paid") {
  // ...
}

Глыбэйшая проблема — не занадта вялікі об’ём інфармацыі. Тып не сказвае нічога пра тое, якія комбінацыі поль є дзеўальнымі. Заказ з ідэнтыфікатарам платежа, але без самага платежа, або статус "paid" без фактычнага платежа, з точкі зору кампайлера є дзеўальнымі.

Болей тачны модэль описвае конкрэтны статус:

type PaidOrder = {
  id: string;
  paymentId: string;
  status: "paid";
};

Значэнне типу PaidOrder гарантуе тры рэчы:

  • у яго є ідэнтыфікатар
  • у яго є ідэнтыфікатар платежа
  • яго статус адносна точна є "paid"

Такі типы служаюць адночасна дакументацыяй і захоўнікам безпекі пад час кампілявання. У практыцы вы моглі б задаць адны тип на кожны статус, напрыклад, замовленне у процесе обробкі без ідэнтыфікатора платежа і замовленне, якое вже оплачанае, з такім ідэнтыфікаторам, і аб’еднаныя ў спецыяльны союз на асаблівасці status. TypeScript можа тады автаматычна сузіць тип пасля пераканалення на кшталт order.status === "paid" і павесты вас, калі новы статус не адобразжаны. Для прыклада рэалізаціі гэтага патэрану адзірнайце замену флагаў загрузкі спецыяльным союзам на асаблівасці.

Адэт: не падавайце на спрыткія з апшарунковымі поламі толькі таму, што яны спрыяюць лёгкай рэдагаванні першай версіі. З’ясавайце, якія станы на самае праўда магуць быць, і стварайце сваія типы адпаведна да ўсёга гэтага. Гэта не пазбавіць вас ад усіх бягоў, але значна ускладніць стварэнне цэлага класу некоректных дадзеных.

5. Раздзеляйце рашэнні ад дзеянняў

Бізнес-правілы стаюць важкімі для тэставання, калі яны пераплетаюцца з побачнымі эфектамі. Возьмімо функцыю, якую могуць выкарыстоўваць толькі параболелыя корыстувальнікі:

async function processUser(userId) {
  const user = await database.getUser(userId);
if (user.isVerified && user.age >= 18) {
    await database.enableFeature(userId);
    await emailService.send(user.email);
  }
}

Сама правіла, па якойям трэба быць параболелым і не меньш чым 18 гадоў, сумешаныя з чытаннем данных з базы, запісам у базу і адправкай электранаўпы. Для тэставання правіла трэба імітаваць усе тры дзеяння.

Вынесіце рашэнне у чыстую функцыю, якая прымеўа данні і вяртае булева значэнне:

function canUseFeature(user) {
  return user.isVerified && user.age >= 18;
}

Функцыя керування пасуючы выкарыстоўвае данні корыстніка, задае запит правілу і выконвае додатковыя дзеянні толькі калі запит працюе правільна:

async function processUser(userId) {
  const user = await database.getUser(userId);
  if (!canUseFeature(user)) {
      return;
    }
  await database.enableFeature(userId);
  await emailService.send(user.email);
}

Тепер правілу можна працаваць безпосередня, выкарыстоўваючы просты об’ект:

expect(
  canUseFeature({
    isVerified: true,
    age: 25
  })
).toBe(true);

Няма базы дадзенаў, няма службы паштоў, няма макаў: толькі сама рашэнне і яе вхідныя даны. Чыгуча, што такая структура дазволяе лёгка адрабатваць крайнія случаи, напрыклад неперакананага 30-гадовага чалавека або перакананага корыстніка, якому ровна 18 гадоў.

Звычка: аддзеляць пытанне "чы гэта трэба робіць?" ад пытання "як гэта робіць?". Хорашы прыклады для такага аддзелення включаюць:

  • перакананні ў доступе і правах
  • расчыткі цэнаў
  • перакананні вхідных дадзенаў
  • рашэнне пра тое, чы хто-такі трэба паведаміць
  • палітікі перапрыбутку
  • правіла аблікавання і функцый

Чым большое значэнне мае правілам для бізнесу, тым большая парадоксальна выгода ад таго, калі яно рэалізавана ў маленькай, чыстай і добра працаванай функцыі.

6. Зробіце канты прыдатнымі для людзей і для програмнага забезпечэння

Такі кант практычна не дае нічога для працы:

throw new Error("Something went wrong");

У ям няма інформаціі пра тое, што зламалася, дзе, чырвонай прычыны або чы мае той, хто вызваў функцію, можлівасцей чагось зрабіць.

Кращы кант мае стабільны код пад часом з поведамленнем, якое можна прачытаць людзём. У прыкладзе выкарыстоўваецца клас AppError, які расшыроўвае клас Error і зберагае оба значэння; гэты клас не ўжо вбудованы.

throw new AppError(
  "PAYMENT_FAILED",
  "The payment could not be completed."
);

Працаваючы як серыяваны данні для адпаведзення API, ён дае кліентам штось прыдатнае для працы:

{
  "code": "PAYMENT_FAILED",
  "message": "The payment could not be completed."
}

Важна адміністрацыя: паведамленні піскаюцца для людзей, а коды — для систем. Паведамленні можна перапісаць чы распрацаваць, але значэння коду должны застацца стабільнымі, каб кліенты моглі надзеясціваць на іх безпечна. Напрыклад, фронтэнд можа адрагавацца на конкретную прычыну абярання:

if (error.code === "PAYMENT_FAILED") {
  showPaymentError();
}

Логі таксама патрабуюць адпаведнага стаўлення. Простае паведамленне важкае для дзейства о 3 гадзіні ночы:

console.log("Payment failed");

Структураваны контэкст дапамагае шукати і діагноставаць інфармацыю:

logger.error("Payment failed", {
  orderId,
  provider,
  attempt
});

Будзьце адказныя ў тым, што падае ў гэты контэкст. Ніколы не логаваць:

  • пароляў
  • токенав доступу
  • клучаў API
  • іншых секрэтных дадзенняў
  • чуліватых персональных дадзенняў

Якщо вам патрэбна стандартная форма для тэла абярання HTTP, деталі проблемы RFC 9457 ўцямоцныя да прачытання.

Адэт: даць кожнаму неудачнаму результату формат, які можа прыняць чалавек і на які можа адказаць прыграма. Кідаўшы данні — гэта лёгкая частка; справжня праца — зробіць неудачы прагнозаванымі і лёгкімі да діагназавання.

7. Храніце кожную змяну сфармаванай

Пасляпэўны адэт стосуецца змены коду, а не яго напісання. Уявіце сабе адзин патрэба змены, які мячы:

  • нэўжоны функцыя прабавы
  • міграцыю базы дадзеных
  • рефактораванне службы платежаў
  • апдэйты фронтэнда
  • змены ў логіцы павторных спроб
  • некалькі несувязаных парадактав

Нават якшо ўсё гэта працюе, дужа складна праверыць усё рэшта. Адгукнікі працуюць швытка, рызыкі заховваюцца сярод іншых змян, а якшо ў працэўным сераверы чыгне якаясь проблема, адкатаўчы адну частку значыць адкатаўчы ўсё.

Раздзеліўшы на сфармаваныя змены, тая ж самая праца выглядае так:

PR #1 → Add checkout validation
PR #2 → Refactor payment service
PR #3 → Update checkout UI
PR #4 → Improve payment retry logic

Кожную з іх лёгкая:

  • працаваць над
  • тэст
  • дыбаггінг
  • вярнуць да пачатковага стану
  • зрозумець за месцы пазней на адной з історый
  • Сфармаваныя змены таксама спрыяюць саўместнай працы: калі з’являецца регрэсія, аб’ект для дакладнага аналізу є маленькі, а такі інструменты як git bisect паказваюць змену, якая сама по сабе мае значэнне.

    Адвычка: пры тым, як аднае ўжо змену запісваць да чыннага заведамства, спытайцеся, чы рэштаецца яна да той самай проблемы. Якщо ні, створыце для яе адзінлікі commit чы рэпрасут. Сфармаваная змена лёгкая да доверы, калі на вядомасць прыйшае, што вялікая змена проста пасканула тэсты.

    Аднае галоўная мета: спрыяць лёгкасці наступных змэн

    Гэтыя адвычкі спачатку здаюцца незв’язанымі, але ўсе яны спрямованы да адной меты. Хорашая інжынерная практыка — гэта не толькі стварэнне працюючага коду сёння, але і зменшэнне трудносцей пад час маніпуляцый завтра. Кожная адвычка вносіць свой вялікі васабок:

    • Візуальна, задоволена структура спрыяе легкаму розумінню логіки.
    • Значамыя назвы дапамагаюць чытальніку быстра зрозумець мету.
    • Граніцы, якія абмежваюць зовнішнія системы, контролююць наследкі ўсіх змян у іх.
    • Точныя типы выключаюць цэлыя катагоріі багоў.
    • Раздзелэнне рашэнняў і дзеянь дапамагае пераверываць бізнес-правіла.
    • Структураваныя адказы дапамагаюць діагноставаць неудачы.
    • Фармаваныя змены дапамагаюць легка развіваць кодавую базу.

    Фрэймворкі і бібліятекі з’яўляюцца і зникаюць, але гэтыя прынцыпы такі ж практычныя ў JavaScript, TypeScript, Python, Java, Go чы іншых мовах.

    Ключавыя выводы

    • Калі вы завершылі стварэнне функцыі, моделі, інтеграцыі чы прыемкі змян, запытайце не толькі, чы працюе ўсё гэта, але і чы наступны разработчык можа безпечна яго зменіць.
    • Клазы захисту, точныя назвы і точныя типы зменшуюць колькасць інформацыі, яку трэба запам’ятаваць чытальніку.
  • Граніцы і чыстыя функцыі прыняцтва рашэння обмежваюць меру, на якую можа распространяцца змяна, і спрыяюць простам тэставанню поведення.
  • Стабільныя коды каштоўкаў і структураваныя, без таёмніц, лог-файлы ператвараюць неудачы на інформацыю.
  • Маленькія, аднацелевыя змяны лёгчэе пераглядаць, вярнуць у пачатковы стан і даваць ўпэўненасць у яных.