Галоўная / Артыкулы / Дзевяць прычын на рэвэлі коду, якія спрабоююць павышыць доверлівасць работы высокакваліфікаваных інжынераў

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

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

1471 слоў

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

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

Еўро штоўкіх прычын, якія з’являюцца дастаткова часта, каб іх варыла свэдама застосоўваць.

1. Клаузылі захавання проты „пірамід беды“

Паўзучая пачатковая памылка — гэта грабленне логікі верыфікацыі, пакуль яна не стварае „сходнік“ з індэнтавання:

function processWithdrawal(account, amount) {
  if (account.isActive) {
    if (amount > 0) {
      if (account.balance >= amount) {
        return debit(account, amount);
      }
    }
  }
  throw new Error("Withdrawal failed");
}

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

function processWithdrawal(account, amount) {
  if (!account.isActive) throw new AccountInactiveError();
  if (amount <= 0) throw new InvalidAmountError();
  if (account.balance < amount) throw new InsufficientFundsError();

  return debit(account, amount);
}

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

2. Назвы, якія описваюць бізнес, а не тип дадзеных

Называнне зменных па ўсёй іх форме, а не па значэнні — data, res, obj, list — ўсё ж можа быць зрозумелым у аднам файле. Але калі ў кодбазе чырвоны файл, у кожным з якіх data мае іншае значэнне, такі падход становіць ся вельмі проблематычным.

data = fetch(order_id)
if data["status"] == "done":
    process(data)

Пораўнянне з:

order = fetch_order(order_id)
if order.is_fulfilled:
    archive_order(order)

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

3. Адна ўзлегкая лінія межы вашым кодам і зовнішнім светам

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

// scattered everywhere
const price = apiResponse.line_items[0].unit_price_cents / 100;

Лепшы падход — пераклаць адпаведзь цікаво раз, пры самай межы:

function toLineItem(raw) {
  return {
    label: raw.description,
    priceInDollars: raw.unit_price_cents / 100,
  };
}

Якщо постаўчык пазней перэіменуе unit_price_cents на price, павінна змяніцца толькі адна функцыя. Усе, што знаходзится далей, застаецца недастрыжаным і ніколі не з’явіць разніцы.

4. Моделі дадзеных, якія не можаць крацаць

Тып, створаны з дзесятка необавязковых поль, — это тып, які перастаў старацца точна адбіваць рэальнасць:

type Ticket = {
  id?: string;
  assignee?: string;
  resolvedAt?: Date;
  resolution?: string;
};

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

type OpenTicket = { id: string; assignee?: string };
type ResolvedTicket = { id: string; assignee: string; resolvedAt: Date; resolution: string };

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

5. Раздзеліце запит ад каманды

Калі бізнес-правілы і парадуктывны эфекты заплутваюцца адзін з іншым, тэставанне стае болючым — а калі тэставанне стае болючым, правілы вогнаў перестаюць тэставацца ўзагалі:

def promote_employee(employee_id):
    emp = get_employee(employee_id)
    if emp.tenure_months < 12:
        raise Error("Not eligible yet")
    if emp.current_rating < 3:
        raise Error("Rating too low")
    give_raise(emp)
    notify_hr(emp)
    log_promotion(emp)

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

def promotion_eligibility(emp):
    if emp.tenure_months < 12:
        return Ineligible("Not enough tenure")
    if emp.current_rating < 3:
        return Ineligible("Rating too low")
    return Eligible()

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

6. Каментарыях павінны быць пояснення «чаму», а не «што»

Каментар, які проста павтарае тое, што вже сказана ў кодзе, являецца зайвым матэрыялам:

// increment the counter
counter++;

Каментар, які паказвае прычыны рашэння, — гэты каментар, які варта заставіць:

// Retry once — the vendor's webhook occasionally arrives before
// the payment record finishes committing on their end.
retryOnce(processWebhook, payload);

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

7. Адказы, якія вяду да корисных дадзеных

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

{
  "code": "INVALID_DATE_RANGE",
  "message": "End date must be after start date.",
  "field": "endDate"
}

Няма нічога нештасныя у тым, калі код фронтэнду пераглядае текст абярыцення, каб вырашыць, якое паведамленне паказаць — на прыклад, if (err.message.includes("date")). Такі падход з самага пачатку є нестойкым. Як толькі хтось зменяе формулюванне паведамлення бэкэнду, логіка інтэрфейса тыхо перестае працаваць. Коды існуюць для таго, каб машыны моглі на ўсём гэтым базаваць свои рашэнні; паведамлення існуюць для таго, каб людзі моглі ўсё гэта прачытаць. Раздзеленыя ўсе гэтыя элементы значыць, што ніхто з іх не паказваецца ў ролі іншаго.

8. Адна просьба аб перагляде, адная ідея

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

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

9. Спрыяйце першаму варыянту як варыянту-праекту

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

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

Аднойчыны мотыў

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

Спадневаная літэратура

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