Семь сігналав паведамленні, які прадвяваюць дорогі будучыя змены
Выучыце, як выявляць семь проблем у коде, на якія зразкаюць рэвізоры ў раннім перыоде, — ад флагаў у режыме boolean да паспраўленых адносоваў, — і як ацэніць, калі кожна з іх ёсць справжняя проблема.
Большая частка проблематычнага коду не выглядае зламаным. Ён компілюецца, праходзіць тэсты і правільна працуе ў дамавай працэ, але апытныя эксперты часта можуць за хвіліну з’ясавіць, што наступныя змены, інцидэты або перадачы коду прынесу проблемы. У гэтым кяле рассказваецца пра сем знакоў, на якія яны реагуюць, пояснюецца, што кожны з іх предвеча, і паказваецца, як адрозніць справжняю проблему ад патэрана, які толькі выглядае падазроўна, ўпэўніваючы вас у можлівасці застосаваць той самы критэрыя пад час своіх пераглядаў.
Што на самай працэ розпаведае «запах коду»
Аптэрызаваны рэвіюер рэдка калі мусіць чытаць цэлы модуль, каб зразумець проблему. Булевы аргумент, мэта якога неясна там, дзе выклікаецца функцыя, інструмент, які практычна здобыў дзесятак неабавязковых функцый, catch, які ператварае кожную памылку на аднаковую, выглядаючую нявіннае значэнне: нічга з гэтаго сёньдзе не спрабоюе ўсё зламаць. Проблема заключаецца ў тым, чаго гэта можа захацецца завтра.
Гэты інстынкт выходзіць з адпаведнага дазвычкі, а не з строгай інструкцыі па стылі. Людзі, якія гады працавалі з системамі, бачылі, як адна скопіяваная правіла ператвараецца на тры супераслаганыя версіі, як практычная абстракцыя стае месца, дзе знаходзяцься всі спецыяльныя кейсы, а стандартны варыянт можа маскаваць зупінку роботы прыладу на недзелі. Тое, што яны научыліся бачыць, — гэта месца, дзе калека коду скупляе неяснасці.
Ця падчынна дапамагае спрытваць запах як паведамленне, а не як вынік. Ён не дазваляе стварыць заключэння, што рэалізацыя некоректная; ён толькі паказвае, што кода трэба аднавіць болей рэтельна:
- Кальвін або модуль несе адпаведная адказга за гэта рашэнне?
- Як гэта будзе працаваць, калі змянююцца трэбавання?
- Чы будзе адмова ўсё равна видна, калі яна выйдзе?
- Чы наступны разрабоўка зможа зрозумець, чаму код складзіцца так, не шукаючы таго, хто яго напісаў?
Семь наведзеных нижэй сігналаў – гэтыя, якія найбольш надзеянна ператвараюцца на збыткі, якщо ніхто не ставить гэтыя запытанні з самага пачатку.
1. Адна функцыя, якая выканае кальвін некалякуюцых задач
Дзейнікі большога дужына прыводзяць да крытыкі, але сама дужына рэдка калі ўскладнюе работу апытнага рэвізора. Дзейнік можа складацца з многа рэчэй, таму што ён выкананае адна чыстая операцыя паэтапна, у зрозумелых кроках. Іншы дзейнік можа поместыцца ў двадцать рэчэй, але застацца складным у выкарыстанні, таму што ён адразу ж займаецца пераканаленням дадзеных, пераглядам прав, збережэнням у базе дадзеных, форматаванням адпаведнай вясці, запускам упытакоў і перакладамі памылак.
Асалодныя проблемы — гэта тое, што сумешаныя абавескі даюць дзейніку многа незалежныя прычыны для змены. Новая правіла доступу яго паўтараюць. Змены у формате вясці таксама яго паўтараюць. Міграцыя схемы таксама яго паўтараюць. Новыя трэбаванні да упытакоў таксама яго паўтараюць. Абавескі, якія павінны развивацца окрема, ў канечнасці збіраюцца ў аднам месцы, і нават незначная правка вымагае розумэння всего процесу.
Прыгучым сігналам ёсць назва, яка не можа чыстаўскі апісаць функцыю. Часам такая, як processOrder, можа пераглядаць інфармацыю пра кліента, вылічваць цены, стачыраваць запасы, ствараць інтэнцію платежу, зберагаць інформацыю пра замовленне та надсілать підтверджэння на электронную пошту. Назва застаецца нечыткай, таму што точная назва займала бы цэлы абзац.
Чаму механічны выкалыванне — не рашэння
Разбіўка функцыі на меншыя часткі не дапамагае автаматычна. Выкалыванне фрагментаў па колькасці лінакоў можа распрабрацаваць тую ж плутаноść у большай колькасці файлаў, пры чым кожная схованая залежнасц застаецца. Кращы падход — шуканне меж, якія маюць значэнне:
- Дзе прыманяецца бізнесовае рашэнне?
- Калькі частка ёсць парадуксам для званачайнага системы?
- Калькі частка — це чыстая трансформацыя дадзеных?
- Калькі частка керуе транзакцыяй?
Калі гэтыя швы стануць виднымі, код можна будзе арганізаваць так, як фактычна праця выканана. Асалідным проблемай ніколі не была колькасць работы, яку выканае функцыя. Проблемай было тое, што ніхто не мог змяніць адзін прыемак без таго, каб не врачаваў адночасна калькі некалязаных прыемакоў.
2. Булевыя параметры, якіе актываюць заштоўныя режымы
Булевы аргумент здаецца безнебяжным, таму што ён несе адзін біт. У практыцы ён часта таямна пераносіць цэлы альтернатыўны парадакт выканання роботы пад іменем вялікай функцыі. Разглянем такі вызов, як той, што паказваны нижэй, який майже нічога не паведамляе чытальніку пра тое, што будзе адбывацца:
await updateAccount(accountId, true, false);
Ён можа быць зрозумелы толькі пасля аналізу самай рэалізацыі – трэба выявіць, чы рошучая значэнне true выклікае апдэйт, надае спавешчанне, абыходзіць праз верыфікацыю, зберагае історыю дзейнаў або актывацыя ўчастка. Авар функцыі можа памятаць прызначэнне кожага параметра, але іншыя людзі змушаны восстанавляць яго значэнне на адваротнае, пры чым падмененыя літералы будуць праходзіць усе перакананні на адпаведнасць типам.
Комбінацыі, якіх ніхто не планаваў
Проблема паглыбляецца з ростам колькасці флажоў. Тры булевыя значэння ствараюць васьмі можлівых комбінацыяў, з якіх дзеякія ніхто нават не расследаваў. Якшо функцыя прымея forceUpdate, skipValidation і sendNotification, што будзе, калі пры апдэйті праз прымус будзе абыходзіцца верыфікацыяю, але ўсё рава будзе надана інформацыя? Система типамі спакойна дазволяе такі вызов, нават якшо цэлкам не падходзіць для данай сферы дзеянаў.
Рэвізоры пазначаюць гэта, таму што набор флагоў зазвычай означае, што адна функцыя выпалюе ролю калькі лічбы операцый. Яны можа раздзеляць внутршняе зместо, але не маюць аднаковага назначэння. Рэедытаванне дакументаў кантактаў, перакананне абачэння і застосаванне адміністратыўнага перакрыцча можа ўсе запісвацца ў той самы рядок, прычаму ніхто з вызываючых не павінен памяцать, які шаблон флагоў выбирае які процес работы.
Выбор правильнага рефакторавання
У дзеяннях, калі дастаткова замена на аб’ект з адзінам аргументам, пры яком свойства маюць назвы, таму што updateAccount(id, { notify: true }) прынямні чытаецца на месцы вызову. У іншых дзеяннях адзінаковыя функцыі, такія як verifyAccount і overrideAccount, чытна паказваюць бізнес-мету і абсалютна усуняюць немагчымыя комбінацыі. Выбор шляху залежыць ад таго, чы розныя дзеяння ўсё-такі є варыяцыямі адной операцыі чы то разнымі операцыямі, якія проста дзелюць код.
Булевыя значэння самі па сабе не ўскладненыя. Простая названая опцыя, такая як includeArchived, ўсё абсалютна разумная. Апазнакай являецца флаг, який зміняе значэнне функцыі, а не проста коректуе якісь вядомы деталі.
3. Адна і тая же бізнес-правіла, выражаная ў разных формах
Тэкст, скопіяваны і вярнуты, лёгкая да адзначэння. Падвойваныя ведамасці ўжо болей небяпечныя, таму што кожна копія можа выглядаць абсалютна інако.
Возьміце правило дазволу. Яго можна пабачыць у вялікай меры як схованую кнопку ў інтэрфейсе, як пераконтанне па ролі ў кантралеры, як умову статусу ў сэрвісе і як клазу WHERE у запытэ. Кожны слой, здаецца, захоўвае систему, але кожны з іх можа містіць нейкшую, аднак нешто іншую, канцэпцію таго, хто мае права дзейснаваць дзіяльнасць.
Усё працюе нормальна пакуль не змяніцца правілы. Одна канцэльнайта прыймае новае правіло, іншая застаёцца з старой логікай, а запланаваная задача обходзіць іх обе, пераходячы безпасова да внутраньнага сэрвіса. Інтэрфейс тепер прасіць аб дзеянні, якое сервер адмовляецца выканаць. Одна частка прыемлівання спрацоўвае з корыстнікам як з автарызаваным у зв’язку з його ролю, тады як іншая настаіць на тым, што ён павінен быць власнікам даных.
Аптэкты з большым адказам зазначаюць павтараючыяся комбінацыі статусаў, ролей, флагоў і дат, таму што такія умовы часта выражаюць тое ж самае бізнес-значэнне, напісанае колькіразоў. Яны спытваюць, дзе знаходзіцца офіцыйная рашынка, і чы не выкананае іншымі слоямі гэтая рашынка чы проста яна перазначаецца.
Слойвы з разнымі цялямі — гэта нормальна
Гэта не значыць, што правілу можа быць прысутна толькі ў адной лініі коду ў всім системе. Інтерфейс можа прадставляць правы на доступ, каб паспрыяць ўжыванню, тады как бэкенд должен яго выкананаць для безпекі. Абмежэння базы дадзеных можа захаваць тую ж самую незмянную вялічыну на рэвэрсным роўні. У гэтых слойваў є адныя другім не падобныя заведамы. Проблема выступае, калі кожны з іх адпавядае на бізнес-запыт сваімі справамі.
Здоровыя працэўныя падходы надаюць правілу адного чысткага власніка і дазволяюць іншым часткам выкарыстоўваць, апэлюваць чы ўспамінаваць рэзультат. Калі власнасць неясная, кожны будучы змін у правілах ператвараецца на пошук: трэба знайсці кожнае месца, дзе правіло таямніча пракодавана, і верыць, што ўсе яны знайдзены. Кожны, хто страждзеў ад змін у правілах, ведае, што павтарэнне інформацыі зазвычай коштуе больш, чым павтарэнне коду.
4. Адна спяльная дапаможніца з аб’ектам варыянтавання, які расте
Багатыя проблемныя абстракціі пачынаюць сваю жыццё як разумныя дапаможнікі. Калі два месца выкарыстоўваюць падобную логіку, спяльная частка выделяецца, і рэзультат стае малым, сфокусаваным і простым у розумеенні.
Тады два аб’ядчыкі пачынаюць хацець разныя рэштунакі. Аднаму патрэбны перапрыбуткі; іншаму не трэба ніколі павтараць свой запит. Аднаму выгодна кэшаванне; іншаму не трэба ніколі бачыць застарелыя значэння. Аднамы вяртае цэлы пакет дадзеных; іншы — ў вигляде стріму. У замяне на раздзелэнне гэтых процесаў, дапаможны прыемнік стварае новы варыянт для кожной разніцы.
Чырз некалькі часоў об’ект варыянтаў пакрывае пераклад бядаў, перкаштовванне рэзультаатаў, настройкі кэшавання, колькасць перапрыбуткаў, обробку аутэнтыкацыі, функцыі-хукі і калькі логічных пераключачаў. Большасць аб’ядчыкаў выкорыстоўвае два чы тры з гэтых элементаў, але будзь-яя змена можа паціснуць на ўсе іх.
Это ўлоскі, таму што абстракцыя перастала усунваць складнасць і пачала яе концентруваць. Месца вызова выглядаюць простымі, але для розумэння системы тепер неабходна інформацыя пра тое, як кожны варыянт взаімаецца з кожным іншым. Вялікі колькасць вызываючых не ўскладненне таго, што абстракцыя правільная; інодзе гэта проста паказвае, што адпаведны помачнік стаў немагчымым для айхання.
Падобная сінтаксіс проты спакульная дзеяўнасць
Ключовы вопыт — чы розныя вызываючы элементы маюць стабільную дзеяўнасць, чы проста падобны код. Две операцыі могу адправляць HTTP-запросы, але спрацоўваць з неудачамі та павторнымі спробамі адначасна разначынна. Два формы можу мець тыя ж поля, але разную перакананню данных, працэўніку засылкі та обробцы проектаў. Їх з’еднанне усувае відразу відчыняюцуюся дуплікацыю, але робіць кожную рэальную разніцу важкай для выражэння.
Зазвычай кращым рашэнням ёсць введэнне болей узкай абстракцыі пазней. У зменшэнне на аднае выкананне всіх задач, трэба дзеліцца толькі аднай стабільной часткай — напрыклад, адной трансфармацыяй, адной політікай чыста аднам вызовам низкага рэвэля, і чыніць разлічныя выканання задач явнымі. Перадзейнаеўшы матэрыялы застаёцца цінным; проблемай якраз ёсць ситуацыя, калі кожная новая ваказка дадае ўтолькі што адну новую опцыю, якая нікому з тых, хто ўжо яе выкарыстоўвае, не патрэбна і якую ён не розумее. Чытаць пра тое ж падабныя праблемы з акцэнам на фронтэнд можна тут: чаму абстракцыі фронтэнда таямна ператвараюцца ў тэхнічны борг.
5. Адрабатка памылак, якая маскіруе неудачу як успех
Блок catch можа змусіць прыкладнэе рашэння выглядаць надзеяным, пры тым таямна паслабляючы довер'е да яго.
Уявіце сабэрфас з доступу да дадзеннях, які у разе памылкі базы дадзення вяртае []. Або сервіс, якога HTTP-запыт не задаецца і які проста вяртае null. Фронтэнд перакладае кожны адхоўны запыт у стан пустоты. Нічога не зламваецца, але система больш не можа разлічыць законныя прыводы абсэнцы дадзенняў ад неудачнай роботы.
Адгукнікі быстра выявляюць гэта, таму што ведаюць, як гэта выглядае пад час інцидэту. Дашборд адзвяртае нуль записаў, калі база дадзення не працуе. Кліент бачыць „Няма замовэнняў“ пасля таго, як яго сесія тыха заканчылася. Фонавая задача пазначае элемент як завершаны, таму што залежнасць не вярнула нічога практычнае. Інтэрфейс застаецца спакойным, таму што заменіў правду на правадзельную адпаведь.
Пытанні, якія трэба задаць кожнаму блоку адгуквання
Пры пераглядзе абрабаткі памылак пераканайцеся ў трох рэчах:
- Чы гэты первоначальны прычыні захаваны, напрыклад як
causeновай памялкі, чы прынеймна ў журналах? - Чы можа той, хто вызывае функцію, адзначыць запланаваны рэзультат дзеяння ад тэхнічнага краху?
- Чы слой, який ловіць памялку, насправды мае право прымкнуць рашэнне?
Калі код на гэтым роўні не мае можлівасці восстанавіцца, спробаваць зноў, анулюваць частковую работу або запростаць разумныя альтэратыўныя рашэнні, ловленне памялкі можа нічога не даць, а толькі знішчыць доказы.
Нічога з гэтага не значыць, што первасныя асывенты павінны дастацца корыстнікам. Паведамлення, якія паказваюцца людзям, должны быць безпечныя і зрозумелыя, а внутранія памялкі часта трэба перакладваць у стабільныя катагорыі домэна. Трэбаванне такое, каб пераклад захаваў достатню значымасць, каб як аплікацыя, так і разработчыкі моглі правильна адказаць.
Празны рэзультат должен значыць, што аперацыя завершылася успешна і нічога не было знайдзена. Неудачная аперацыя павінна застацца неудачай, як толькі система ня будзе спецыяльна заданая безпечны альтэрнатыўны варыянт. Адналічэнне гэтых двух варыянтаў для зручнасці спамічвае адлучэнне бягаў і паказвае корыстувачам хыбную інфармацыю. Хорашая обработка адказоў на адказы павінна выявляць неудачу; паслабленая обработка адказоў на адказы яе хавае.
6. Малая вяробнеча з шырым, некалякуючым разлікам
Апытныя рэвізоры часта ацэнююць структуру просьбы пра з’еднанне рэпозітарыяў перад тым, як чытаць адзінаковыя лініі. Калі простая вяробнеча распрастраняецца па дужым списку некалякуючых файлоў, гэта зазвычай указвае на схованую взаімазалежнасц або на задачу, якая зьвісілася ад свайго меты.
Дадзенне аднаго поля можа правамерна паўтарыцца ў аспектах стойкага зберагачэння дадзеных, служб, адпаведных API-адказоў і интерфейса; гэта ўзаемна сувязаныя змены ў разных слоях системы. Проблема выступае тады, калі той жа просьба пра перагляд таксама перапішвае спяльныя функцыі, зменшвае назвы несувязаных типаў, зменяе способы рашэння проблем, пераскладвае модулі і апдэйтуе вялікія файлы з копіямі дадзеных.
Кожна з гэтых змян можа быць павышэннем якосці, але разам яны об’едначаюць кальколька прычын змены ў адну структуру. Адгукнікі должны адразу оценіць новую функцыяльнасць, процес рефакторавання, змены ў контрактах і процедуры чысткі. Якщо якаясь регрэсія застаеся непазначанай, знайсці відпаведную змену стае сложней. Якщо функцыяльнасць трэба вярнуць у пачатковы стан, несувязаныя павышэння якосці таксама будуць вярнуты.
Чаму кодаванне за дапомогай ШІ робіць гэта ўсё болей распашчастым
Інструменты генеравання коду спакоўваюць у цю патэрн, таму што стварэнне абоўскуго перапісву коштае практычна нічога. Якщо запрашваеце выправлення аднаго бага, вас можа чакаць выкалібраваная заменная функцыя з новымі іменамі, іншым прабегам керавання, новымі абстракціямі та змененым падходам да обробкі адзінакоў. Їе стварэнне было майже безкоштовным; пры тым перакананне ў яе правільнасці все такі ж выклікае у команды той самы час на перагляд, як і ранейш.
Хорашым правілам є падтрымліваць розмах змян у працэпадзе з тым, насколькі ясна можа быць ўся інформацыя пра яе. Складная діагназа зазвычай вымагае складнага рашэння, а болей шырокія змяны могу быць практыкаваны ў окремай просьбе пра змяны, дзе яе можна будзе пераглянуць і працаваць з яе перавагамі. Гэта не значыць, што ўсюды трэба ствараць мініатюрныя просьбы пра змяны; дзеяные архітектурныя змяны за сваёю прыродай є шырокімі. Ключовы вопыт — чы роўна кожная значымая змяна спрыяе аднаму цэлесапраўедаму рысунку. Калі разніця ў коде большая, чым сама проблема, зазвычай адночасна рашаецца калькі пытанняў без ўскладнення, і стае важка выявіць прычыну і наследкі.
7. Код, який працуе, але стойкі да пераканання
Дзеянне дэйтаўных рэалізацыяў іноды ствараецца быстра самэлькі таму, што іх прыемнасць важка спазірваць чы рэгулюваць. Функцыя безпаслявна чытае системны гадзіннік, стварае сабе неабходныя залежнасці, мутуе глобальны стан, выконвае колькасць зовнішнях вызоў, пакрывае сабе сваеіяныя аберанцы і практычна нічога не вяртае. У спрыяючых умовах ўсё можа быць нормальна, але паказаць, як ён зламваецца, становіцца складным задачай.
Магчымасць тэставання часта залежыць ад якосці абмежэнняў. Якщо правілу бізнесу нельга прыняць у дзеянне без запуску всей аплікацыі, заполнення рэкордаў базы дадзенаў, вызоў рэальных служб і чакання на рэальныя таймеры, значыць гэта правіла, верагодна, пераплетзена з занадта многімі парадуктамі.
Асінхронныя рабочыя пралэты выклікаюць гэтыя проблемы
Проблема ўсё болей выражаная у багатыядерных асінхронных прабегах. Уявіце функцыю, якая зберагае змены, выпускае домэннае запісанне, паведамляе корыстніка і дадае рэкорд у журнале аудыту. Якшо трэці крок збяжае, чы можа тэст выявіць, якія крокі вже завершыліся? Чы можна прымусіць залежнасць выйсці за межы часу? Чы можна сімуляваць два запуску функцыі? Чы можна воспаваць застарэлую адчынку без пасляльгавання на випадковасці часу?
Код, які лёгка ў напісанні, але важкаў у перакананні, стварае довер'я да ручных тэстаў. Хтось праходзіць па успешнаму шляху, бачыць правільны рэзультат і выводзіць, што функцыя готова. Пасля чаго у працэйнай среде з’яўляюцца канкурантнасць, перапрыбуткі, незвычныя даны і непаўнавыконаныя операцыі, якіх ніхто не перабіраў локальна.
Пераканвацца на рэвэлі, дзе існуе рызык
Чыстасць у всім і насмешкі над усім таксама не ўтвараюць рашэння. Тэсты, якія базуюцца пераважна на мокіх, часта фіксуюць внутрэшні деталі на месцы, не зважаючы на тое, як фактычна база дадзеных, сеть чыя транзакцыі паводзяцца. Кращы падход — выбраць тые гарантіі, якія маюць значэння, і пераканаліцца кожную з іх там, дзе насправды распалюецца рызык:
- Чыстыя трансфармацыі можна тэставаць як самастоятны элементы.
- Інваріанты базы дадзеных часта выклікаюць патрэбу ў інтеграцыйных тэстах з рэальной базай дадзеных.
- Для абмежэнняў служб можа знадобіцца тэставанне на адповідачнасць.
- Распрастраненыя формы паводзжэння можа залежаць ад структураваных логаў і ID-ў корэляцыі, таму што не кожныя умовы працы можна вернаваты точна.
Ўжыванне гадзіна чыста залежнасцяў у зьвязку з адмовай ад выкарыстоўвання глобальных змэнных часта ёсьць самым маленькім змянам, якае дапамагае контролюваць важлівыя рашэньні. Проблема не ў низкай ступені пакрыцья, а ў кодзе, у яком ключовыя рашэньні не можна контролюваць, спазіраваць чы адказваць на яны, калі «шчаслівы» шлях перестае працаваць.
Ключовыя выводы
Жадны з эйх сігналоў не ёсьць автаматычным вердыктом. Дзяўгі функцыя можа представляць адну чыстую операцыю, булевы параметр можа быть цэлкам зрозумелы, спяльны хелпер можа фіксаваць практычна стабільную працэю, а масштабныя змяны можу быць нэвыхіднымі. Спрыяйце лісту як спосабу выбору таго, дзе трэба глыбэй дакапівацца, а не як чартку для механічнага падтрымання.
Калі вы аналізуеце, ключавыя запытаньні ўключаюць:
- Чы адпаведальнасці змешаны, так што адна змяна тягне за сабой некалякія рашэньні?
- Чы вызываючыя можу зрозумець функцыю без расшифравання пазіцыйных флагоў?
Тое, што насправды фіксуюць аптыры з большым дазнаннем, — это відкладзена нявяснасць: вераганне, што наступная вимога змусіць каго-небудзь разумець несувязаную логіку, што наступны інцыдент заставіць залишыцца задаць мало дакладнаў для расследавання, што наступны разработчык не будзе ведаць, якая дзейнасць была намерованай, або што наступны корыстувач дадае ўсё больш апцый на абстракцыю, якая вже несе занадта многа. Яны быстра распазнаюць гэтыя сігналы, таму што вони вже платілі за іх: расплутвалі перанасыценых памагальнікаў, аналізавалі ісключэнняя, якія былі паглочаныя, прымиралі суперсучасныя правіла і пераглядалі „маленькія“ функцыі, якія паўзначальна паўтрапілі ў весь код. Мета не ў тым, каб усунуць код, які выглядае неразбірным; мета — знайсці код, які зробіць будучую работу сложнейшай, чым трэба.