Kuidas kirjutada kuulikindlat koodi rakenduses Go: töövoog serveritele, mis ei saa tõrkuda

Aeg-ajalt võib teid tabada hirmutav ülesanne: ehitada üles server, mille tõrkeks pole tegelikult lubatud - projekt, kus vea hind on erakordselt kõrge. Milline on sellise ülesande lähenemise metoodika?

Kas teie server peab tõesti olema kuulikindel?

Enne sellesse liigsesse töövoogu sukeldumist peaksite endalt küsima - kas minu server peab tõesti olema kuulikindel? Halvimateks ettevalmistamiseks on vaja palju kulusid ja see pole alati seda väärt.

Kui vea hind ei ole erakordselt kõrge, on täiesti kehtiv viis teha mõistlikud pingutused, et asjad toimiksid, ja kui teie server puruneb, siis lihtsalt tegelege sellega. Tänapäevased jälgimisriistad ja pidevad tarnimise kaasaegsed töövood võimaldavad meil kiiresti tootmises probleeme märgata ja need peaaegu kohe lahendada. Paljudel juhtudel on see piisavalt hea.

Projektis, mille kallal täna töötan, pole seda. Töötan plokiahela - hajutatud serveriinfrastruktuuri koodi turvaliseks täitmiseks konsensuse alusel vähese usaldusega keskkonnas. Selle tehnoloogia üks rakendusi on digitaalsed valuutad. See on õpiku näide, kus vea hind on sõna otseses mõttes kõrge. Loomulikult soovime, et selle rakendamine oleks võimalikult kuulikindel.

Siiski on ka muid juhtumeid, isegi kui valuutadega ei tegelda, kui kuulikindlal koodil on mõte. Hoolduskulud tõusevad kiiresti sageli ebaõnnestuva andmebaasi jaoks. Kui suudate probleeme arendustsüklis varem tuvastada, kui nende lahendamise kulud on endiselt madalad, on hea võimalus maksta tagasi kuulidekindlasse metoodikasse tehtud investeering.

Kas TDD on maagiline vastus?

Testipõhist arendust (TDD) kutsutakse sageli kui hõbedast kuulitõrke koodi vastu. See on puristlik arendusmetoodika, kus uut koodi ei lisata, välja arvatud juhul, kui see vastab ebaõnnestunud testile. See protsess tagab 100-protsendilise testkatte ja loob sageli illusiooni, et teie koodi testitakse iga võimaliku stsenaariumi korral.

See pole nii. TDD on suurepärane metoodika, mis töötab mõne jaoks hästi, kuid sellest üksi ei piisa. Veelgi hullem, TDD sisendab vale usaldust koodi vastu ja võib arendajad paranoiliste servajuhtumite kaalumisel laisad olla. Näitan sellest hiljem head näidet.

Testid on olulised - need on võti

Pole tähtis, kas kirjutate teste enne fakti või pärast seda, kasutades sellist tehnikat nagu TDD või mitte. Oluline on ainult see, et teil oleks testid. Testid on parim kaitseliin oma koodi kaitsmiseks tootmises purunemise eest.

Kuna me hakkame kogu oma testikomplekti käitama väga sageli - võimaluse korral pärast iga uut koodirida -, peavad testid olema automatiseeritud. Ükski osa usaldusest meie koodi vastu ei pruugi tuleneda käsitsi laaditavast kvaliteediprotsessist. Inimesed teevad vigu. Inimese tähelepanu detailidele halveneb pärast seda, kui olete sada korda järjest teinud sama mõttetegemise ülesande.

Testid peavad olema kiired. Lõõmavalt kiire.

Kui testikomplekti käivitamiseks kulub rohkem kui paar sekundit, muutuvad arendajad tõenäoliselt laisaks, vajutades koodi ilma seda käivitamata. See on üks suurepäraseid asju Go kohta - sellel on üks kiireimaid tööriistakettasid. See kompileerib, taasloob ja testib sekunditega.

Testid on ka olulised võimalikud avatud lähtekoodiga projektides. Näiteks plokiahelad on peaaegu usuliselt avatud lähtekoodiga. Koodbaas peab olema avatud usalduse loomiseks - paljastama end auditi jaoks ja looma detsentraliseeritud õhkkonna, kus ükski juhtorgan ei kontrolli projekti.

Põhjendamatu välise panuse saamine avatud lähtekoodiga projektis on ilma mõistliku testita. Välised toetajad vajavad kiiret viisi, et kontrollida, kas nende panus rikub olemasolevat käitumist. Tegelikult peab kogu testkomplekt töötama automaatselt iga tõmbetaotluse korral ja ebaõnnestuma automaatselt, kui PR rikkus midagi.

Testi täielik katvus on eksitav mõõdik, kuid see on oluline. 100-protsendilise leviala saavutamine võib tunduda liigne, kuid kui järele mõelda, pole mõtet koodi saata tootmisele, mida varem ei tehtud.

Testide täielik katvus ei tähenda tingimata, et meil on piisavalt teste, ja see ei tähenda, et meie testid oleksid tähendusrikkad. Kindel on see, et kui meil pole 100% -list katvust, ei piisa sellest, et pidada end kuulikindlaks, kuna meie koodi osi ei testitud kunagi.

Sellegipoolest on olemas selline asi nagu liiga palju teste. Ideaalis peaks iga viga, millega kokku puutume, ühe testi purustama. Kui meil on üleliigseid teste - erinevad testid, mis kontrollivad sama asja -, muudavad olemasoleva koodi muutmine ja protsessis olemasoleva käitumise rikkumine ebaõnnestunud testide parandamisel liiga palju kulusid.

Miks on Go suurepärane valik kuulikindla koodi jaoks?

Go on staatiliselt kirjutatud. Tüübid pakuvad lepingut erinevate koos töötavate kooditükkide vahel. Ilma ehituse ajal automaatse tüübikontrollita ja kui tahaksime kinni pidada rangetest katvuseeskirjadest, peaksime need lepingutestid ise rakendama. See kehtib keskkondade nagu Node.js ja JavaScripti kohta. Põhjalike lepingutestide käsitsi kirjutamine on palju lisatööd, mida eelistame vältida.

Mine on lihtne ja dogmaatiline. Go on tuntud paljude traditsiooniliste keelefunktsioonide, näiteks klassikalise optilise pärandi pärandi tõttu. Keerukus on kuulikindel koodi halvim vaenlane. Probleemid kipuvad õmblustes roomama. Ehkki tavalist juhtumit on lihtne testida, viib see lõpuks ikkagi kummalise serva juhtumi, mida te pole mõelnud.

Ka dogma on selles mõttes abiks. Go-is on sageli vaid üks viis midagi teha. See võib pärssida inimese vaba vaimu, kuid kui on üks viis midagi teha, on seda ühte asja valesti mõistmine keerulisem.

Go on lühike, kuid ekspressiivne. Loetavat koodi on kergem üle vaadata ja kontrollida. Kui kood on liiga sõnaline, võib selle põhieesmärgi uputada katlapleki müra. Kui kood on liiga lakooniline, on seda raske jälgida ja mõista.

Go loob nende kahe vahel kena tasakaalu. Keeleversioone pole palju, nagu näiteks Java või C ++, kuid keel on endiselt väga selgesõnaline ja paljusõnaline sellistes valdkondades nagu vigade käsitlemine - tänu sellele on lihtne kontrollida, kas olete kontrollinud kõiki võimalikke marsruute.

Go-l on selged vigade ja taastumise teed. Kuulmiskindla koodi nurgakivi on käitusaja vigade hasartne lahendamine. Go-l on range vigade tagastamise ja levitamise viis. Sellised keskkonnad nagu Node.js - kus juhtimisprotsessi erinevad maitsed, näiteks tagasikutsumised, lubadused ja asünk on omavahel segunenud - põhjustavad sageli leket nagu käsitamata lubaduste tagasilükkamine. Nendest toibumine on peaaegu võimatu.

Go-l on ulatuslik standardkogu. Sõltuvused lisavad riski, eriti kui need pärinevad allikatest, mida pole tingimata hästi hooldatud. Serveri saatmisel saadate sellega kõik oma sõltuvused. Teie vastutate ka nende tõrgete eest. Killustatud sõltuvustega üleküllastatud keskkondi, nagu Node.js, on kuulikindlam hoida raskem.

See on ka turvalisuse seisukohast riskantne, kuna olete nii haavatav kui teie kõige nõrgem sõltuvus. Go ulatuslik standardkogu on hästi hooldatud ja vähendab sõltuvust välistest sõltuvustest.

Arengukiirus on endiselt kiire. Selliste keskkondade nagu Node.js peamine veetlus on äärmiselt kiire arengutsükkel. Kood võtab lihtsalt vähem aega kirjutamist ja muutute produktiivsemaks.

Go säilitab need eelised üsna hästi. Ehitustööriistakett on piisavalt kiire, et tagasiside oleks kohene. Kompileerimisaeg on tühine ja tundub, et kood töötab nagu seda on tõlgendatud. Keelel on piisavalt abstraktsioone, nagu prügivedu, et suunata tehnilised jõupingutused põhifunktsioonidele.

Mängime toimiva näitega

Nüüd, kui sissejuhatused on läbi, on aeg sukelduda mõnda koodi. Vajame näidet, mis on piisavalt lihtne, et saaksime keskenduda metoodikale, kuid piisavalt keeruline, et omada sisu. Minu arvates on kõige lihtsam võtta midagi oma päevast päeva, nii et ehitagem server, mis töötleb valuutalaadseid tehinguid. Kasutajad saavad kontrollida konto saldo. Samuti saavad kasutajad raha üle kanda ühelt kontolt teisele.

Hoiame asju väga lihtsana. Meie süsteemil on ainult üks server. Samuti ei tegele me kasutajate autentimise ega krüptograafiaga. Need on tooteomadused, samas kui tahame keskenduda kuulikindla tarkvara aluse ehitamisele.

Keerukuse jaotamine hallatavateks osadeks

Keerukus on kuulikindel koodi halvim vaenlane. Üks parimaid viise keerukusega toimetulemiseks on jagamine ja vallutamine - jagage probleem väiksemateks probleemideks ja lahendage igaüks eraldi. Kuidas jaguneme? Järgime murede lahususe põhimõtet. Iga osa peaks käsitlema ühte muret.

See käib käsikäes populaarse mikroteenuste arhitektuuriga. Meie server koosneb teenustest. Igal teenusel on selge vastutus ja neile antakse selgelt määratletud liides teiste teenustega suhtlemiseks.

Kui oleme oma serveri sellisel viisil üles ehitanud, saame vabalt otsustada, kuidas iga teenus töötab. Saame käivitada kõiki teenuseid ühes ja samas protsessis koos, muuta iga teenus eraldi serveriks ja RPC kaudu suhelda või jagada teenuseid erinevate masinate jaoks.

Kuna alustame alles tegevust, hoiame asja lihtsana - kõik teenused jagavad sama protsessi ja suhtlevad otse raamatukogudena. Saame seda otsust tulevikus hõlpsalt muuta.

Millised teenused meil peaksid olema? Meie server on tükeldamiseks pisut liiga lihtne, kuid selle põhimõtte demonstreerimiseks teeme seda niikuinii. Saldode kontrollimiseks ja tehingute tegemiseks peame vastama klientide HTTP päringutele. Üks teenus saab hakkama kliendi HTTP-liidesega - nimetame seda PublicApi. Teine teenus kuulub riigile - kõigi saldode pearaamatule - ja seda nimetatakse ka StateStorage'iks. Kolmas teenus ühendab need kaks ja rakendab meie saldode muutmise lepingu äriloogikat. Kuna plokiahelad lubavad rakenduste arendajatel tavaliselt neid lepinguid juurutada, tuleb nende teenindamise eest maksta kolmas teenus - nimetame seda VirtualMachine'iks.

Paigutame oma projektis teenuste koodi kausta / teenused / publicapi, / teenused / virtuaalne masin ja / teenused / osariik.

Teenuse piiride selge määratlemine

Teenuste juurutamisel tahame, et saaksime töötada igaühega eraldi. Võimalik, et määrake erinevatele arendajatele isegi erinevad teenused. Kuna teenused on üksteisest sõltuvad ja me kavatseme nende rakendamisega paralleelselt töötada, peame kõigepealt määratlema selged liidesed nende vahel. Selle liidese abil saame teenust individuaalselt testida ja kõike muud pilkata.

Kuidas saame liidest määratleda? Üks võimalus on see dokumenteerida, kuid dokumentatsioon kipub vananenud ja koodiga sünkroonis olema. Me võiksime kasutada Go-liidese deklaratsioone. See on mõistlik, kuid toredam on liides määratleda agnostiliselt. Meie server pole piiratud ainult Go-ga. Võib juhtuda, et teeme ühe teenuse uuesti kasutuselevõtmise mõnes teises keeles, mis on selle nõudmistele sobivam.

Üks lähenemisviis on protobufi kasutamine - Google'i lihtne keele-agnostiline süntaks sõnumite ja teenuse lõpp-punktide määratlemiseks.

Alustame StateStorage'iga. Struktureerime oleku võtmeväärtuse poena:

Ehkki PublicApi juurde pääseb kliendi HTTP kaudu, on hea tava anda sellele samal viisil selge liides:

See nõuab, et me määratleksime tehingute ja aadresside andmestruktuurid:

Paigutame oma projektis teenuste .proto määratlused kategooriasse / tüübid / teenused ja üldised andmestruktuurid jaotisse / tüübid / protokoll. Kui definitsioonid on valmis, saab need kompileerida Go-koodiks. Selle lähenemisviisi eeliseks on see, et lepingule mittevastavat koodi lihtsalt ei kompileerita. Alternatiivsete meetodite jaoks oleks vaja, et kirjutaksime lepingutestid selgesõnaliselt.

Kõik definitsioonid, loodud Go-failid ja koostamisjuhised on saadaval siin. Kudos Square Engineeringule goprotowrapi valmistamise eest.

Pange tähele, et me ei integreerita veel RPC transpordikihti ja teenuste vahelised kõned on praegu tavalised teegi kõned. Kui oleme valmis teenuseid erinevatesse serveritesse jagama, saame lisada transpordikihi nagu gRPC.

Testide tüübid meie projektis

Kuna testid on kuulikindla koodi võtmeks, arutame kõigepealt, milliseid teste me kirjutame:

Ühiktestid

See on testimispüramiidi alus. Katsetame iga seadet eraldi. Mis on üksus? Go-is saame määratleda ühiku, mis peab olema paketi iga fail. Kui meil on /services/publicapi/handlers.go, paigutame selle ühikatsetuse samasse paketti /services/publicapi/handlers_test.go.

Eelistatav on paigutada ühiku testid testitud koodiga samasse paketti, nii et testidel oleks juurdepääs eksportimata muutujatele ja funktsioonidele.

Hooldus / integreerimine / komponendi testid

Järgmist tüüpi testidel on mitu nime, mis kõik viitavad ühele ja samale asjale - võetakse mitu ühikut ja katsetatakse neid koos. See on püramiidist ühel tasemel. Meie puhul keskendume kogu teenusele. Need testid määratlevad teenuse spetsifikatsioonid. Näiteks StateStorage'i teenuse jaoks paigutame need kausta / teenused / statestorage / spec.

Ainult eksporditud liideste kaudu juurdepääsu tagamiseks on soovitatav need testid asetada testitavast koodist erinevasse paketti.

Otsast lõpuni testid

See on testimispüramiidi ülaosa, kus katsetame kogu oma süsteemi koos kõigi teenustega koos. Need testid määratlevad süsteemi otsese spetsifikatsiooni, seetõttu paigutame need oma projekti kataloogi / e2e / spec.

Ka need testid tuleks panna testitavast koodist erinevasse paketti, et tagada juurdepääs ainult eksporditud liideste kaudu.

Millised testid peaksime kõigepealt kirjutama? Kas alustame baasist ja töötame üles? Või minna ülalt alla? Mõlemad lähenemisviisid kehtivad. Ülalt-alla lähenemisviisi eeliseks on hoone spetsifikatsioonid. Tavaliselt on kõige lihtsam kogu süsteemi spetsifikatsioonide üle järele mõelda. Isegi kui me jaotaksime oma süsteemi teenustele valesti, jääks süsteemi spetsifikatsioon samaks. See aitaks meil ka seda mõista.

Ülalt-alla alustamise puuduseks on see, et meie otstest testid on viimased, mis läbitakse (alles pärast kogu süsteemi juurutamist). See tähendab, et nad jäävad pikka aega ebaõnnestuma.

Otsast lõpuni testid

Enne testide kirjutamist peame kaaluma, kas kirjutame kõike paljaste lihadeta või kasutame raamistikku. De-sõltuvuste raamistikele tuginemine on vähem ohtlik kui tuginemine tootmiskoodide raamistikele. Meie juhul, kuna Go standardteegil pole BDD-d suurt tuge ja see formaat sobib suurepäraselt spetsifikatsioonide määratlemiseks, valime raamistiku.

Seal on palju suurepäraseid kandidaate, nagu GoConvey ja Ginkgo. Minu isiklik eelistus on Ginkgo koos Gomega (kohutavad nimed, aga mida sa teha saad), mis kasutavad süntaksit nagu Kirjelda () ja See ().

Kuidas test välja näeb? Kasutaja tasakaalu kontrollimine:

Kuna meie server pakub maailmale avalikku HTTP-liidest, pääseme sellele veebi API-le juurde, kasutades rakendust http.Get. Aga tehingu tegemine?

Test on väga kirjeldav ja võib isegi asendada dokumentatsiooni. Nagu ülal näete, lubame kontodel negatiivse saldo saavutada. See on tootevalik. Kui seda ei lubataks, kajastaks test seda.

Kogu testfail on saadaval siin.

Teenuste integreerimine / komponentide testid

Nüüd, kus otspunktide vahelised testid on tehtud, läheme püramiidist alla ja viime kasutusele hoolduskatsed. Seda tehakse iga teenuse jaoks eraldi. Valime teenuse, mis sõltub teisest teenusest, kuna see juhtum on huvitavam.

Alustame VirtualMachine'iga. Selle teenuse protobufi liides on saadaval siin. Kuna VirtualMachine tugineb teenusele StateStorage ja helistab sellele, peame VirtualMachine'i isoleeritud testimiseks pilkama StateStorage'i. Mock-objekt võimaldab meil testi ajal kontrollida StateStorage'i vastuseid.

Kuidas saaksime Go-s pilkavaid objekte rakendada? Saame luua lihtsalt kondita tüve teostuse, kuid pilkava raamatukogu kasutamine annab meile ka testi käigus kasulikke väiteid. Minu eelistus on pilkamine.

StateStorage'i mudeli paigutame /services/statestorage/mock.go. Eksportimata muutujatele ja funktsioonidele juurdepääsu saamiseks on parem paigutada pilk samasse paketti kui pilkatud kood. Mõte on praegusel hetkel üsna tavaline katla, kuid kuna meie teenused muutuvad keerukamaks, võime siin leida mõne loogika. See on pilk:

Kui määrate erinevatele arendajatele erinevad teenused, on mõistlik kõigepealt rakendada mõnitusi ja jagada need meeskonna vahel.

Tulgem tagasi meie teenuse VirtualMachine teenustesti kirjutamiseni. Millist stsenaariumi peaksime siin täpselt katsetama? Parim on jälgida iga lõpp-punkti hooldus- ja kujundustestide liidest. Rakendame lõpp-punkti CallContract () testi kõigepealt meetodi argumendiga "GetBalance":

Pange tähele, et meie testitav teenus VirtualMachine saab lihtsa sõltuvuse süstimise teel osuti sõltuvuse StateStorage'ile oma Start () meetodil. Siit saame mööda pilkatud instantsist. Pange tähele ka rida 23, kus juhendame pilkamist, kuidas neile juurde pääseda. Kui selle ReadKey meetodit kutsutakse, peaks see tagastama väärtuse 100. Seejärel kontrollime, kas seda tõepoolest kutsuti reale 28 täpselt üks kord.

Need testid saavad teenuse spetsifikatsioonideks. VirtualMachine'i teenuse täielik komplekt on saadaval siin. Muude teenuste sviidid leiate siit ja siit.

Rakendame lõpuks üksuse

Meetodi "GetBalance" lepingu rakendamine on natuke liiga lihtne, nii et lähme selle asemel meetodi "Edastamine" pisut keerukama juurutamise juurde. Edastusleping peab lugema nii saatja kui ka saaja saldod, arvutama nende uued saldod, ja kirjutage need tagasi olekusse. Selle jaoks on teenuse integreerimise test väga sarnane äsja rakendatud versioonile:

Lõpuks alustame ettevõtlusega ja loome üksuse nimega processor.go, mis sisaldab lepingu tegelikku rakendamist. Selgub meie esialgne teostus:

See vastab teenuse integreerimise testile, kuid integratsioonitest sisaldab ainult tavalist stsenaariumi. Aga äärejuhtumid ja võimalikud rikked? Nagu näete, võib mõni StateStorage'i tehtud kõnedest ebaõnnestuda. Kui eesmärk on 100-protsendiline katvus, peame kontrollima kõiki neid juhtumeid. Ühikproov oleks selleks hea koht.

Kuna kõigi voogude saavutamiseks peame funktsiooni mitu korda kasutama erinevate sisendite ja piltide sätetega, muudaks tabelipõhine test selle protsessi pisut tõhusamaks. Go tavapärane eesmärk on vältida ühiskatsetes väljamõeldud raamistikke. Võime Ginkgo maha jätta, kuid peaksime tõenäoliselt Gomegat hoidma, nii et meie sobitajad näeksid välja sarnased meie varasemate testidega. See on test:

Kui teid varitseb sümbol Ω, ärge muretsege, see on lihtsalt tavaline muutuja nimi (hoiab kursorit Gomega). Võite selle ümber nimetada ükskõik, mis teile meeldib.

Aja jooksul ei näidanud me TDD ranget metoodikat, kus uus koodirida kirjutatakse ainult ebaõnnestunud testi lahendamiseks. Seda metoodikat kasutades rakendatakse protsessitransfeeri () ühiku test ja juurutamine mitme iteratsiooni korral.

Teenuse VirtualMachine üksusetestide täielik komplekt on saadaval siin. Muude teenuste ühiktestidega saab tutvuda siin ja siin.

Oleme 100% katvuse saavutanud, otstesti testid läbivad, meie teenuste integreerimise testid läbivad ja ühikatestid läbivad. Kood täidab oma tähtnõudeid ja on põhjalikult testitud.

Kas see tähendab, et kõik töötab? Kahjuks ei ole. Meie lihtsa teostuse all on endiselt varjatud mitu vastikut viga.

Stressitestide tähtsus

Kõik meie testid on seni testinud, kas ühel ajal käsitletakse ühte taotlust. Aga sünkroonimisprobleemid? Kõiki Go-siseseid HTTP-päringuid hallatakse gorutiinis. Kuna need gorutiinid töötavad samaaegselt, potentsiaalselt erinevates OS-i keermestes erinevatel protsessori tuumadel, seisame silmitsi sünkroonimisprobleemidega. Need on väga vastikud vead, mida pole lihtne leida.

Üks lähenemisviis sünkroonimisprobleemide leidmiseks on süsteemi rõhutamine paralleelselt paljude taotlustega ja veendumine, et kõik ikka töötab. See peaks olema otstestide test, sest tahame testida sünkroonimisprobleeme kogu meie süsteemis kõigi teenustega. Asetame oma projekti stressitestid kategooriasse / e2e / stress.

See näeb välja stressitesti:

Pange tähele, et stressitest sisaldab juhuslikke andmeid. Testi deterministlikuks muutmiseks on soovitatav kasutada pidevat seemet (vt rida 39). Iga kord, kui proovid läbi viime, on erinev stsenaarium, see pole hea mõte. Viletsus testide kaudu, mis mõnikord läbivad ja mõnikord ebaõnnestuvad, vähendab arendaja usaldust komplekti suhtes.

Keeruline osa stressitestide kohta HTTP kaudu on see, et enamikul masinatel on raske simuleerida tuhandeid samaaegseid kasutajaid ja avada tuhandeid samaaegseid TCP-ühendusi (näete kummalisi tõrkeid, näiteks „maksimaalsed failikirjeldused” või „ühenduse lähtestamine ühenduse poolt”). Ülaltoodud kood üritab sellega graatsiliselt tegeleda, piirates samaaegseid ühendusi partiidega 200 ja kasutades IdleConnection Transport seadeid TCP-seansside taaskasutamiseks partiide vahel. Kui see test on teie masinas ketendav, proovige vähendada partii suurust 100-ni.

Oh ei ... test ebaõnnestub:

Mis siin juhtub? StateStorage on rakendatud lihtsa mälusisese kaardina. Näib, et proovime sellele kaardile kirjutada paralleelselt erinevatest niitidest. Alguses võib tunduda, et peaksime tavalise kaardi lihtsalt niiditõmmatava sünkrokaardiga asendama, kuid meie probleem ulatub pisut sügavamale.

Vaadake protsessiTransfer () juurutamist. See loeb osariigist kaks korda ja kirjutab siis kaks korda. Lugemis- ja kirjutamiskomplekt ei ole aatomitehing, nii et kui mõni teine ​​lõim muudab olekut pärast selle põhjal loetud lõime, siis on meil andmed rikutud. Parandus on kindel, et protsessTransfer () ainult ühte eksemplari saab käivitada samaaegselt - näete seda siin.

Proovime uuesti stressitesti läbi viia. Oh ei, veel üks rike!

Selle mõistmiseks on vaja veel pisut silumist. Näib, et see juhtub siis, kui kasutaja üritab summa endale kanda (sama kasutaja on nii saatja kui ka saaja). Teostust vaadates on lihtne aru saada, miks see juhtub.

See on natuke häiriv. Oleme järginud TDD-taolist töövoogu ja tabasime endiselt kõva äriloogika viga. Kuidas see saab olla? Kas meie koodi pole testitud iga stsenaariumi korral, millel on 100% -line katvus?! Noh ... see viga on vigase toote nõude, mitte vigase rakenduse tulemus. Nõuded protsessTransfer () -le peaksid olema selgelt öeldud, et kui kasutaja kannab summa endale, ei juhtu midagi.

Äriloogikavea avastamisel peaksime seda alati esmalt ühikatestides kordama. Seda juhtumit on meie varasema lauapõhise testi juurde lisada väga lihtne. Parandus on ka lihtne - näete seda siin.

Kas oleme lõpuks kodus vabad?

Pärast stressitestide lisamist ja veendumist, et kõik möödub, kas meie süsteem töötab lõpuks ettenähtud viisil? Kas see on lõpuks kuulikindel?

Kahjuks ei ole.

Meil on ikka mõned vastikud vead, mida isegi stressitest ei paljastanud. Meie „lihtsa” funktsiooni protsessTransfer () on endiselt ohus. Mõelge, mis juhtub, kui jõuame selle piirini. Esimene olekusse kirjutamine õnnestus, kuid teine ​​ebaõnnestub. Varsti saadame vea, kuid juba rikkusime oma olekut, kirjutades sellele pooleldi küpsetatud andmed. Kui saadame vea, peame esimese kirjutamise tagasi võtma.

Seda on natuke keerulisem parandada. Parim lahendus on ilmselt meie liidese täielik muutmine. Selle asemel, et StateStorage'is oleks lõpp-punkt nimega WriteKey, millele me kaks korda helistame, peaksime selle tõenäoliselt ümber nimetama WriteKeys - lõpp-punkt, millele helistame üks kord, et kirjutada mõlemad võtmed ühes tehingus kokku.

Siin on suurem õppetund: metoodilisest testkomplektist ei piisa. Keerukate vigadega tegelemine nõuab arendajate kriitilist mõtlemist ja paranoilist loovust. Soovitatav on lasta kellelgi teisel teie koodi vaadata ja teie meeskonnas koodi üle vaadata. Veelgi parem, kui soovite oma koodi kuulikindlamaks muuta, avage oma hankimine ja kogukonna julgustamine seda auditeerima.

Kogu selle artikli kood on Githubis saadaval ühe näitehoidlana. Olete teretulnud kasutama seda projekti oma järgmise serveri stardikomplektina. Samuti olete teretulnud repo üle vaatama, avastama rohkem vigu ja muutma selle kuulikindlamaks. Ole loovalt paranoiline!

Tal on asutaja ettevõttele Orbs.com - miljonitele kasutajatele mõeldud suuremahuliste tarbijarakenduste avalike plokkkettide taristu jaoks. Lisateabe saamiseks ja Orbi valgete raamatute lugemiseks klõpsake siin. [Järgige telegrammis, Twitteris, Redditis]

Märkus. Kui olete huvitatud plokiahelast - tulge panustage! Orbs on täielikult avatud lähtekoodiga projekt, kus kõik saavad osaleda.