Шта значи када је код „лако закључити“?

Вероватно сте довољно пута чули израз „лако расуђивати“ да вам уши крваре.

Први пут кад сам чуо овај израз, нисам имао појма шта је особа под тим подразумевала.

Да ли то значи функције које су лако разумљиве?

Да ли то значи функције које раде исправно?

Да ли то значи функције које је лако анализирати?

Након неког времена, чуо сам „лако се расуђује“ у толико контекста да сам схватио да је то само још једна полу-бесмислена модна реч програмера.

... Али да ли је стварно бесмислено?

Истина је да израз има значајно значење. Обухвата прилично сложену идеју, што је чини декодирањем помало незгодном. На страну лукавство, разумевање на високом нивоу како изгледа код „лако се расуђује“ апсолутно нам помаже у писању бољих програма.

У том циљу, овај пост ће бити посвећен сецирању израза „лако је размишљати“, јер се односи на техничке разговоре које водимо као програмери.

Разумевање понашања вашег програма

Једном када напишете део кода, обично желите да разумете и понашање програма, његову интеракцију са другим деловима програма и својства која показује.

На пример, узмите део кода испод. Ово треба множити низ бројева са 3.

Како можемо да тестирамо да ли ради како је предвиђено? Један логичан начин је пролазак гомиле низова као улаза и осигуравање да он увек враћа низ са сваком ставком помноженом са 3.

Засад изгледа добро. Тестирали смо да функција ради оно што ми желимо.

Али како да знамо да не ради оно што ми не желимо? На пример, пажљивим прегледом можемо видети да функција мутира изворни низ.

Да ли смо то намеравали? Шта ако су нам потребне референце и на изворни низ и на резултирајући низ? Штета, претпостављам.

Даље, да видимо шта ће се догодити ако прођемо исти низ гомилу различитих времена - да ли увек враћа исти резултат за дати улаз?

Ух Ох. Изгледа да када смо први пут проследили низ [1, 2, 3] функцији, он се вратио [3, 6, 9] , али се касније вратио [49, 98, 147] . То су врло различити резултати.

То је зато што се функција мултиплиБиТхрее ослања на екстерни мултипликатор променљивих . Дакле, ако спољно стање програма проузрокује промену променљивог множитеља између позива функције мултиплиБиТхрее , понашање функције се мења чак и ако исти низ пренесемо у функцију.

Ееек. Не изгледам више тако сјајно. Копајмо мало дубље.

До сада смо тестирали савршене уносе низа. А шта ако бисмо ово урадили:

Оно што у свету?!?

Програм је изгледао сјајно на површини - међутим, када смо узели неколико минута да га проценимо, то је била друга прича.

Видели смо да понекад враћа грешку, понекад враћа исту ствар коју сте му додали и само повремено враћа очекивани резултат. Штавише, има неке нежељене нежељене ефекте (мутирање оригиналног низа) и изгледа да није доследан у ономе што враћа за дати улаз (јер се ослања на спољно стање).

Погледајмо сада мало другачију функцију мултиплиБиТхрее :

Као и горе, и ми можемо да тестирамо исправност.

Засад добро изгледам.

Хајде да тестирамо и да ли ради оно што не желимо. Да ли мутира оригинални низ?

Јок. Оригинални низ је нетакнут!

Да ли враћа исти излаз за дати улаз?

Иеп! Будући да је променљива мултипликатора сада у опсегу функције, чак и ако прогласимо дупликат мултипликатора променљиве у глобалном опсегу, то неће утицати на резултат.

Да ли враћа исту ствар ако пренесемо гомилу различитих врста аргумената?

Иеп! Сада се функција понаша предвидљивије - или враћа грешку или нови резултујући низ.

У овом тренутку, колико смо сигурни да ова функција ради тачно оно што ми желимо? Да ли смо покрили све ивичне случајеве? Покушајмо још неколико:

Проклети. Изгледа да је за нашу функцију потребно још мало посла. Када сам низ садржи неочекиване ставке, попут недефинисаних или низова, поново видимо чудно понашање.

Покушајмо да га поправимо додавањем још једне провере у нашу проверу фор-лооп за неважеће елементе низа:

Уз ову нову функцију, зашто не бисте поново испробали та два рубна случаја:

Слатко. Сада такође враћа грешку ако било која ставка у низу није број, уместо неког случајног функи излаза.

На крају, дефиниција

Пролазећи кроз горње кораке, полако смо изградили функцију о којој је лако размишљати јер има следеће кључне особине:

  1. Нема нежељене нежељене ефекте
  2. Не ослања се на спољно стање нити утиче на њега
  3. С обзиром на исти аргумент, он ће увек враћати исти одговарајући излаз (познат и као „референтна транспарентност“).

Начине на које можемо гарантовати ова својства

Постоји много различитих начина на које можемо гарантовати да је о нашем коду лако размишљати. Погледајмо неколико:

Јединствени тестови

Прво, можемо да напишемо јединичне тестове како бисмо изоловали делове кода и проверили да ли функционишу како је предвиђено:

Овакви јединични тестови помажу нам да потврдимо да се наш код понаша исправно и дају нам живу документацију о томе како мали делови целокупног система функционишу. Упозорење код јединичних тестова је да је невероватно лако пропустити проблематичне случајеве ивица ако нисте врло пажљиви и темељити.

На пример, никада не бисмо схватили да се оригинални низ мутира, ако не бисмо некако мислили да га тестирамо. Дакле, наш код је једнако робустан колико и наши тестови.

Врсте

Поред тестова, можда ћемо користити и типове како бисмо олакшали расуђивање о коду. На пример, ако бисмо користили проверивач статичног типа за ЈаваСцрипт попут Флов, могли бисмо бити сигурни да је улазни низ увек низ бројева:

Типови нас приморавају да изричито кажемо да је улазни низ низ бројева. Они помажу у стварању ограничења на нашем коду која спречавају многе врсте рунтиме грешака као што смо раније видели. У нашем случају више не морамо да размишљамо о провери како бисмо били сигурни да је свака ставка у низу број - ово је гаранција која нам се даје код типова.

Непроменљивост

На крају, још једна ствар коју можемо учинити је да користимо непроменљиве податке. Непроменљиви подаци само значе да се подаци не могу мењати након што се креирају. Ово помаже у избегавању нежељених нежељених ефеката.

У нашем ранијем примеру, на пример, да је улазни низ непроменљив, спречио би непредвидиво понашање где је оригинални низ мутиран. А да је множилац непроменљив, спречио би ситуације у којима неки други део програма може мутирати наш множитељ.

Неки од начина на које можемо искористити благодати непроменљивости је коришћење функционалног програмског језика који инхерентно обезбеђује непроменљивост или коришћење спољне библиотеке, попут Иммутабле.јс, која намеће непроменљивост поврх постојећег језика.

Као забавно истраживање користићу Елм, откуцани функционални програмски језик, да покажем како нам непроменљивост помаже:

Овај мали исечак ради исто што и наша ЈаваСцрипт функција мултиплиБиТхрее од раније, осим што је сада у Елму. Будући да је Елм типизирани језик, видећете у 6. реду да дефинишемо типове улаза и излаза за функцију мултиплиБиТхрее као листу бројева. Сама функција користи основну операцију мапирања за генерисање резултујућег низа.

Сада када смо дефинисали своју функцију у Елм-у, направимо последњу рунду истих тестова које смо урадили за нашу ранију функцију мултиплиБиТхрее :

Као што видите, резултат је оно што смо очекивали и оригиналАрраи није мутиран.

Сада, бацимо Елм на трик и покушајмо мутирати множитељ:

Аха! Елм ​​вас ограничава у томе. Баца врло пријатељску грешку.

Шта ако бисмо као аргумент предали низ, уместо низа бројева?

Изгледа да је Елм и то ухватио. Будући да смо аргумент прогласили Списком бројева, не можемо проследити ништа осим Списка бројева чак и ако смо покушали!

У овом примеру смо мало преварили користећи функционални програмски језик који има и типове и непроменљивост. Поента коју сам желео да докажем је да са ове две карактеристике више не морамо размишљати о ручном додавању чекова за све ивичне случајеве како бисмо стекли три својства о којима смо разговарали. Врсте и непроменљивост гарантују да за нас, и заузврат, можемо лакше да расуђујемо о нашем коду?

Сада је на вама ред да разложите свој код

Изазивам вас да одвојите тренутак следећи пут када чујете да неко каже: „КСИЗ олакшава расуђивање о коду“ или „АБЦ чини тешко расуђивање о коду“. Замените ту модну модну реч са горе поменутим својствима и покушајте да разумете шта та особа значи. Која својства има део кода због којих је лако расуђивати?

Лично, извођење ове вежбе ми је помогло да критички размишљам о коду, а заузврат ме мотивисало да размишљам о томе како да напишем програме о којима је лакше размишљати. Надам се да ће то учинити и за вас!

Волео бих да чујем ваше мишљење о другим својствима која су ми можда недостајала, а која сматрате важним. Молимо оставите повратне информације у коментарима!