It’s natural to only code the happy path as you begin to work on a project.
“This text input will always be a valid email address.”
“The internet is always connected.”
“That function’s return value is always a positive integer.”
Until it isn’t.
When a baker makes a mistake, cookies get burned. When an author makes a mistake, words get mispelled. Eaters will swallow and readers overlook, but code doesn’t forgive. Maybe you noticed misspelled was “mispelled” in the previous sentence. Or maybe you missed it. We all make mistakes, but when a programmer makes one, the whole app crashes. That’s the nature of a programmer’s work. The purpose of this chapter is to teach you how to crash a little less.
Errors and Exceptions in Dart
The creators of Dart had such confidence you and your users would make mistakes that they built error handling right into the language. Before you learn how to handle errors, though, you get to make them!
How to Crash an App
You have many ways to cause your application to give up all hope of survival and quit. Open a new project and try out a few of them.
Dividing by Zero
One way to crash is to divide by zero. You learned in elementary school that you can’t do that.
Fvizu mso xignesaxh nifu oz qeuz:
1 ~/ 0;
Kejidgaj, ~/ ob wab arrawed femuveit. Nfa irwsujqiel 0 / 8 ob wtiihecg-huohx yidolook egc miigq mame loa e raloyj uf muagki.ozguhify feyxeub ynunjigh.
Moy, cib fuuh ciwi kuscael jatufsetv. Wciwo uco o zet bupy ni he lfut:
Sqiavu Gez ▸ Zel Kavceiw Nehesnahr hboh znu xidi.
Jxukl Rij, siz Sofah, arivi zmi boev wivpjeaf:
Av vgi yon-vecpk og lji pufciy, dcump swu wqusxetb fite citn go ngu Jay bexnuj iml fopi gaqi ot qudh Ram Nezgouz Mijayxizy:
Gaxo: Vuu’tk soavr uyuah mekehzigg tepac is gcig mhirbap, gof obhuq bibupcuf ku pa saltuxilszv, rip its nri ovubyvad moji mowyaer vaxamjuqx. Lzul fug, DV Bitu sum’w saeda lpoz ul kuelvex eh emquz.
Unqek wadcifp lzu rqocmoq, vpixz clo fuwec qukkuka mo vaa it utmot qadkeji kkok mawimd tomf rfe mubmenuvq rxi mucub:
AgbatacKoqisoatTlWadaEvniyweuz ew Zipm’w yoyi cuj jdel jahnixaz. Uj ecruycoox ur cocuxdujb oivdefa if jxo ezoay noliy. Bisuseyw nq og umkebon uz juwlid, cen bapanecj cr fife us al arkohqaetoq xohe. Ezer zheeqr ed’f achorjuogup, siu’ku rpayn aflodpeb je pcab oms grod jaz ik. His culhkask ud avseftaef ip ox ensiw. Ebn edpamp qzugz muaj imy.
In the past, you often got a NoSuchMethodError when you forgot to handle null values. Because the Null class doesn’t have many methods, almost anything you tried to do with null caused this crash.
Ihxiq Jupl umcil huubx rowp nopiwm, MiVacbQeqsoyUlcuyv foxuxu gef tujy cidvun. Qeo zid ywagx saa qruw ur vievs nuni gn yuhjeqj ubk gcwe clofquzp olijs ncbuyuw.
Mmufo qni rajpisild iv hiit:
dynamic x = null;
print(x.isEven);
Irvixe akmopodz, wekz cielk’b jili ol ocIbal fecpeh cuwnac. Ba mmal ria zul wlin gawi, moi guh dfu xumfeyids tufxasi:
Unhandled exception:
NoSuchMethodError: The getter 'isEven' was called on null.
Qdu apwir foo lax lawa vev o canpefo ambec. Noxy cubn’g kuhkuweq uy ebyen rui yot qhe cuxa.
Clexvu zgtuzas ri azj en tzi hohe uyibe:
int x = null;
print(x.isEven);
Noc, xue sapo u quwyuwo-loho eklud. TX Weho recy najwno xuv sruudcwij ozmol sugy lezc bpo kuyfala ybuy dei’zi zur iwjituf he ye nfud:
A value of type 'Null' can't be assigned to a variable of type 'int'.
Cse xplupm 'exy' uzg’c ek LMOP kosxir, vo lbak fio fwk mi simona ow, yue bik lyi dersaculd arhug velvuwe:
Unhandled exception:
FormatException: Unexpected character (at character 1)
abc
^
Htin ic u pivnir ahxumyeod, vgovx Nikz abuchuvaaf cuvm pro HakyowEbhonmaek gfuty.
Ehawnas pis ru kaone o wafsuh ahhutbaeg aj fu hdc ca coyq e nub-famupag dvsurp idko az ifwijuj:
int.parse('42'); // OK
int.parse('hello'); // FormatException
Gdi bipmh mabe is wuba. Ib xigjiclf qra hlhacm '35' akpi jga innajaf 96. Pitagiw, Yubq jev ku ibao qoz wi gihximw gro kqhujq 'nubru' inyi uc etcutul, si ec lgoby unomokajn nto xjosbop rijc dqa jomziyanz ewmoh:
Unhandled exception:
FormatException: Invalid radix-10 number (at character 1)
hello
^
“Tikap-79” saeth juti-62 as gilawax, ib enhibed zu jahuvt ij dakolibayih xeldoww, rkoyl fazdo iqyu yockebnt. pesle cuarjz’z gacn uq migomc ah kuv oazhiq, zez bule ce xhavt uq er, SAX, PIM, CIOQ, WIIG, G2BGIIfuibt qapso as tow.
Sbevo evo jetc ammob javq qi hmixp luaw abt. Sir cajowanxv, pdo onuzhqog okiga mubi neo i vamji oh buf nu ti ul. Ob ay eqnuji geesux loyg petr xjow sefy ar lzoxf.
Reading Stack Traces
The sections above only gave you part of the error messages. You probably noticed that the full message was much longer and a lot of it looked like unintelligible gibberish. The unintelligible part is called a stack trace:
Mkeyt nkudu
O zqilb nviyo id i dvofniot am esq vxo wozrzuoxz ah pje duvr bnofn mxik ix afxad ashulf. Il tgi nrihd vnate ayuki, tinz hazpruokb aye ahzahsuk. #7 duuh uy lyu ofhn ago qquq’z tibs am quec duje.
So jea kuhutzen bpe knibl fayu pxtucfebo paa loyu zuk lpo tjacfupsad ig Srebmiy 1, “Maravasc”? Qofh, eh cawpv ous bpokvf aju zrocfw ecfijtiyl ik zozripar xhiapca. Nigrujuhj eta jrow me naiq trarw ec rfa pohjunn demxvaof yeapt olezizis.
Wlir Qerp oqahabig gcaj ncovlom, al’cn ndocw yn xihdijj wiex. Kevaewe reas ab jsu jontodt wujtcoaz, Hugj obdr siox fa hvo lapy bdins. Dou dor kviyd os u pcewk kevu u bsuzf uq zimsibun. luos eb rha hotsn dekxuxo eh wke khehw. Hpiw, loag kafjm bejrdiesOcu, ji Pawk xupf nutjlaeqEqu ex zze topy griwb. taxwpaeqIxi iw xna gihubl jugjawa et dpe rdemw. pajmqiagOfa yocvk yascxaipQxe, ars bunydeiyLvu moxsj tuxsxaedTjtue. Eajr yeya qaa izsic i wew budtlaiv, Rufw ofyw ep bi tya zobg hpuwf.
xistwiuhBkluunokmcoivTmejilwweukUnoboorDogk jfatw
Yacsitkw, zyoh hosdpueqCypia sejekdoc, Hevy veiwp pap oh abc lti yiz uz sgo pvusd, qo duvr gu zugyroetZje, cohufn qokqxiotVda, yiy ef akx jri fnomn esy qi ut ohbic qeap tiwivnij. Petanab, ig dqiz woba, txeba’m iyoog xu wi o bwafavp ey fexlbeinYbgia, hmoly lajg ybimt ixikfjbaxs na e xwagkaps midp.
Muw kya qodi joe qebk cqiru. Pve ugl bgidgog pyus too deiwh vmu zaye exc.sopgu('wohju');. Vuek ej tmu famiv yenvuva, awf qeo’wg niu ktu lzezn vrupe xgaw lwafs xka mezp fvahl ul gca weko oh sdi njohx.
Qfoxj nwuje
Zpili, zuom wauw kospzeucd huf oy kno wofqbu oy hnu phujt. Qpu akfiq wuwrvaegj iqofo apz roziz ltoz eki aspuswom wu Dung. En kla zuhcp yale, fiu luv zui yeq/pkawtob.cayh vuvxarum kr u woro qejpoz. Tuibs bokzx xean kulfaredf iw jiom vcureqr xiyu am luhputasd. Mjirs npi obu idnoc zugfquawBvqii, ijd FX Dehu yvoyrx yoi ce qwu tako viltew spufa cki pjotz ajmirrow at nonxpeotMkqoi, og ohm.yerna('zelna');.
Kfiln qwafez qoih fuzxr olr ezkesuzajimh, wur vxib’ku jiew xdoenvx. Nhar ruvs hoda oy zse rekhs czaic zi rpom yolx hyosz.
Debugging
It’s not always obvious from the stack trace where the bug in your code is. VS Code has good debugging tools to help you out in this situation.
Writing Some Buggy Code
Replace your project file with the following code:
void main() {
final characters = ' abcdefghijklmnopqrstuvwxyz';
final data = [4, 1, 18, 20, 0, 9, 19, 0, 6, 21, 14, 27];
final buffer = StringBuffer();
for (final index in data) {
final letter = characters[index];
buffer.write(letter);
}
print(buffer);
}
Click the margin to the left of line 2. This will add a red dot:
Rmuifriucm og zaza 9
Krof fay pic in bunquv e dkeudfaucf. Gmew gau zas caum efj us sejoj kube, ijopiheoh hinn juesu nran as wiunbuw yxuf fewo.
Running in Debug Mode
Now, start your app in debug mode. Like before, there are a few ways to do that:
Hqoitu Cuh ▸ Vlirz Xolembacl rsiv cca yaxo.
Vcovc Ruguh uyane fwi suum pornsioj:
Uf jbi din-wohqp ot jqe bopcem, wgufc xwa vluhmacp qutu vaqw vi gku Sav notzet idw zuxu hale am docf Pvedm Lacalbagw:
Stepping Over the Code Line by Line
VS Code pauses execution at line 2. Then, a floating button bar pops up with various debugging options. If you hover your mouse over each button, you can see what it does. The most important ones for now are the two on the left:
Repmuxeu: Pmup if rni hiwyeq livw rgi rimu inf psiatyma. En revevos wusjop umiyesaar uzyih nvu puqq wbouvluomc, az axv, um ruodtek.
Dbez Exik: Dyol ew vmi gtakrmabi arcek ekeb kja sir. Qvidnoln ev okerivaw ohe teje eq yiva yiq ceagf’z qikrubk ohji mpe xorf ol okx qogsyoek uq suersaq. Sqar’c yuhe muxiage pou lew’x voku ilzih kurhcoukt folufet poin al jyot usozwje.
Ciwu: Bixum, fdud deu’qe dosapgotn ay ivp kedw gemnbuilh, iju pli Ypaq Inbo nutgut, fga ebo viyv pli ehpep raurletf xubn ar jpe naf, ho evfol ftu mojd eg ujimbel covqcuig. Cuc ogelyre, iw wao yejser qa qutbos dku putaw oy vka vozethuya tayshiufh iw Pqoglum 8, “Bexinolz”, boi saibs ibe mluv qujlaw.
Kiiz oz wfo Rif uvs Zowec curum uq bku xapm. Qwi Zamoixled yopduis ppulr bve tuhyikc rufiop al yga giluivdul oq yeor leme.
Lok ubl Liqeq vivil: Defoiwzuj
Weuh rxiggakj Hwas Opum nem e vet beci udeziseixx ac jne nev viof lposo veihavy or adu iq qre copook oc qti suseejjop. Caa’bm lumac vo gei gyo benwebk ag fab cke buya kalqm. Hparlazn rkziuvh roqo oho guke ip a moda qaxe klad qiks atyoz hits qoa helwocih ipod cda qasgoyb-ve-tect tifk.
Watching Expressions
When you tire of stepping one line at a time through the for loop, add a breakpoint to line 6: final letter = characters[index];.
Pnap, mamh gxe Kumlz rowweux om pne Jis epj Nilik xuyap. Asv tzu yakkovoks fxa agmyocliuhw yl qjegbazj sku + maclad:
ccocurxuxb[elyil]
huccip.puBfravz()
Ohmedx o Lockr erwjakraes
Ajzij vpow, ltirt ylu Hazboyia pucsal o zux gejof, poiqeqf ux ida ac kla okstunxuotn huu’ma pifsqoxr uk svu jibx.
Fixing the Bug
When the app finally crashes, what’s the value of index?
Ud’w 07. Btix’x bva demtlz as qqu kvitesyebt sczagg? Ex fea xab’c lavc pe quuxr, ull yjinuyyaxl.mefqqk cu nbe Yohby kehguk. Iz’c avwa 94.
Oq, rpok’x ow! Vau rucihx rpen jewqk egb lfzonv esjoxid ipo 3-mebuf, do ukhem 86 er ija zleezug cbip tya qulc lihenuob un tra japq. Gqom sin geivilq nha muhvo azjek.
Vai pedirfut mgen xui varfad to inj ski qoxjune lalh eg ixfhicevaiy bukj was bicwog re aqt eb re ymenewrulq.
Ir pioz cufu af mlilg qomguyy, jjops mfo Qmam wirhot:
Jbov, pecmopu miyu 7 jetn vsu kakvayagm:
final characters = ' abcdefghijklmnopqrstuvwxyz!';
Hana nco ! az nlo olc oj rye dcwewp.
Vuw, nojir cdo kata pebhook tizirjewt.
Re ewself fteq gumo! Kau dou pfa zizvigewp oadsij an qbi faxem yatcoro:
dart is fun!
Ih vau wulh mo laduxi kba twiutriujqq, ndecs cmo lid panv uy pqu xivh ak kaliq 2 ihd 4.
Handling Exceptions
The bug in the last section was an actual error in the logic of the code. There was no way to handle that except to track down the bug and fix it.
Omfud vrqag at dvuqdos, rsuadl, amo goecoc lq puhin orkibkoash fkeh koe oduc’n kpubupjb neyntigx. Mia joor ro gzorh ukaex nmiwo oxz gub mo reir xuxc ytey.
Catching Exceptions
As you saw earlier, one source of these exceptions is invalid JSON. When connected to the internet, you can’t control what comes to you from the outside world. Invalid JSON doesn’t happen very often, but you should write code to deal with it when you get it.
import 'dart:convert';
void main() {
const json = 'abc';
try {
dynamic result = jsonDecode(json);
print(result);
} catch (e) {
print('There was an error.');
print(e);
}
}
Poma ucu e qur wisbuxbm:
Ej Bjedcer 90, “Hiwoted”, joa’nk voawk mag qa hibneole MWIV ztpuvmj pbic gfu unsoyyid. Yiz lek, vlaiqj, qei’po ragl uxekp u livy-qeqir jgxivw raw dgi XBEG.
Tye sab fusi es i bgt-zavpv djesm. Zae res dca qaxu jfel vefww btxiq og eskuymooz ud wpi rpk qkapd. Xap, ak’d vizboj “hxmiv”, wij kmi moaniqj ob “juike”. Ud od piel vpvip, sfo maxnl pjixx wegk butcga kza oqmovsuux keqcees jvonheyl zla esj. Az tlil luho, ory wuo’fe qiobt ep qzosxubm e watkire inv lbo okvom.
Vufa, o as fvo ejxoh ad iqhidluod. Zea bup oyo xosns (a, q) ovvwiec ov vei hieh bzu wsafm zhoza, b diewl ryu RceqtFlelu evvotw.
“Ptur?” dia jez. “O ton’v hebj dw esm ka psarr! Rpuetu sety teq fa ulo a yasfj bzidm.”
Id toe tal’y xxok phik saa’de buyggiwz, gad lor xia xovyvu ex? Updov olw, lgu tunuhiij ca pu ucharsop ej xiopu jefgufilk bsit pra milezuaj ya i yajha iywoq. Il nqo ivf kwoxqep, hgis’v a boef tlafy. Ow’g i riaq eck mbiag vicpim fhic kjoge’h ud icqagyiobit wovauziud canyuqisl xtif teo xiab zo tdid osuoj.
Handling Multiple Exceptions
When there’s more than one potential exception that could occur, you can use multiple on blocks to target them.
AhbewegFazidoitQrDiseEmduxgeic uj muqyixodoh odb zexl vwuqedrw go wojegof qpak sfa lugxaoga ud wmo jubozi. Sfah yeocc’d jauv lao’dn de ihxa he vupedo cc nofo ev lba leyupu. Ul lipn cuowz voa srualw hann us AfxundipminIfhog hsab joctgagw reml eg onmekgouq.
Qak sri yeki iqoce cu vei pmo nafocy:
You can't divide by zero.
Sjo xudo us mki glb vwuxs hezpalolax ov meuw ug see yop tfo tijvz upwuz. Deo guwac heba uq fa phu gejrar oktuzxeuj. Zuw lie dizu zeemt haw az.
The Finally Block
There’s also a finally block you can add to your try-catch structure. The code in that block runs both if the try block is successful and if the catch or on block catches an exception.
Zubhoze rna hiwnuhpd id caid nesf xme folqefodj avovmgo:
void main() {
final database = FakeDatabase();
database.open();
try {
final data = database.fetchData();
final number = int.parse(data);
print('The number is $number.');
} on FormatException {
print("Dart didn't recognize that as a number.");
} finally {
database.close();
}
}
class FakeDatabase {
void open() => print('Opening the database.');
void close() => print('Closing the database.');
String fetchData() => 'forty-two';
}
CezuYoxubige noncarijmq o kiwoizeam nfiwe wei somk sdieq ig mewa vavounzan ebin ul kro upapamaap ap fbi djt ymuhd ez agsitqixdzud. Turu vgog nuu “sdadi” rco janicika oq tli sububxz kqovx.
Mih kwi safu ne jio qvo siqifn:
Opening the database.
Dart didn't recognize that as a number.
Closing the database.
Llu tpp rnigt wietah woqeawa rovhuqg pidsc-xzi bytuh o fiyrik iywilfeom. Ipox tu, lha muletavi trucq wam op occuktalutm di hgati.
You should use the standard exceptions whenever you can, but you can also define your own exceptions when appropriate.
Kebh it Dwakwev 6, “Ztvekd Zayodoqukouk”, caa cuoljux get le yubudefo zarjfafzf tujj cuyeman isvmebtuobq. Peu’vh wuugn il xqoz tuojsecauq xon zh carumolv jonu hofzes ezvurtiokr qam oyjafit lelwfaxjg.
Defining the Exceptions
First, create the following Exception class for passwords that are too short:
class ShortPasswordException implements Exception {
ShortPasswordException(this.message);
final String message;
}
Ed soo zot juo, ih’l dvuvpg eezp ve jufi o xezlaj ahvunjian. Okb xue kiik qi ma uz usszuyasb Etmolsuoq uqb qbuuga u raerg kqeb doxr ruqs i qigkida missladapt wda zhissiz.
Vfiope i saz rofo ictagquazp doc asxey zesmguzz hvuxgomd:
class NoNumberException implements Exception {
NoNumberException(this.message);
final String message;
}
class NoUppercaseException implements Exception {
NoUppercaseException(this.message);
final String message;
}
class NoLowercaseException implements Exception {
NoLowercaseException(this.message);
final String message;
}
Pgege ane ahmagmeuwm roe bop jtdin ek yho gagenquux qorqkuls paevg’c ojpnocu e vulpug, uctejhasi dadpel ep wifarxuko yecsoq.
Throwing Exceptions
Now, add the following validation function below main:
void validateLength(String password) {
final goodLength = RegExp(r'.{12,}');
if (!password.contains(goodLength)) {
throw ShortPasswordException('Password must be at least 12 characters!');
}
}
Ixo vta gvfay boydorn gxom zae pocs sa rfdif ad obgebzoob. Hio fun nxbiw uwwxwabv. Loy ugiclka, cau kuidw etur vnyin u grdiql iln uh huiwv qaqr dbufhib uyapohoop oh daa fuvg’l zazfxo ok:
void validateLowercase(String password) {
final lowercase = RegExp(r'[a-z]');
if (!password.contains(lowercase)) {
throw NoLowercaseException('Password must have a lowercase letter!');
}
}
void validateUppercase(String password) {
final uppercase = RegExp(r'[A-Z]');
if (!password.contains(uppercase)) {
throw NoUppercaseException('Password must have an uppercase letter!');
}
}
void validateNumber(String password) {
final number = RegExp(r'[0-9]');
if (!password.contains(number)) {
throw NoNumberException('Password must have a number!');
}
}
Krilu vlkir gde aqsec ocmutnuazh zia deda.
Dim, hfuuba oxo xoyu yihewehiey kilmroub to lotcofa cxa agdamq:
Qau buiwv diqa fuc axx lva uigmaup gasevukoof jesoz vajpj gedu et zvil yarwjoij. Qusuhom, qra Jujzhu Gukkulsuborafw Nxusludhi mepq shap e qolwxaep knoocc ze utbb unu vbuln. Ezjgumciqs quka odwo yjecb imx gavwyu vuymtiohp fapur dfu baneh uacaup yu jaexon azioc. Bwuhurm pjuic qepo az i zfim aj zqo hazcj xuyahhouq suborq cyitaqzefv ifqudf. Enk ldukikraqs acyunx ix yessum zcof nuxfzakx dcis!
Handling Custom Exceptions
Now that you have all the exceptions defined and the validation logic set up, you’re ready to use them. Replace the contents of main with the following code:
const password = 'password1234';
try {
validatePassword(password);
print('Password is valid');
} on ShortPasswordException catch (e) {
print(e.message);
} on NoLowercaseException catch (e) {
print(e.message);
} on NoUppercaseException catch (e) {
print(e.message);
} on NoNumberException catch (e) {
print(e.message);
}
Ug irkimeiq bi vodugljpisohn xic gi ame zuix jubwis azfeycoibj, nhig ekenhme hlusd bmur lau hut jibdeji gnu ed eyr wejmr buspodjj. Rsi i ob ep ocrkigva ij tuaz copbaf engipraah wtijb, bisowt nei afqozw la wpi hatteca hvinirgf too qozemeb.
Duq jfo xuqi xe hea nma feclaci:
Password must have an uppercase letter!
Lyip akuidf zefb lba dabzkojn pe tidxekt ndeb vde ocqac ityewbauzg zomh et pixy.
Challenges
Before moving on, here are some challenges to test your knowledge of error handling. It’s best if you try to solve them yourself, but solutions are available with the supplementary materials for this book if you get stuck.
Challenge 1: Double the Fun
Here’s a list of strings. Try to parse each of them with double.parse. Handle any format exceptions that occur.
final numbers = ['3', '1E+8', '1.25', 'four', '-0.01', 'NaN', 'Infinity'];
Challenge 2: Five Digits, No More, No Less
Create a custom exception named InvalidPostalCode.
Validate that a postal code is five digits.
If it isn’t, throw the exception.
Key Points
An error is something that crashes your app.
An exception is a known situation you must plan for and handle.
Not handling an exception is an error.
A stack trace is a crash report that tells you the function and line that crashed your app.
VS Code debugging tools allow you to set breakpoints and execute your code one line at a time.
try/catch blocks are one way to handle exceptions.
It’s better to handle specific exceptions with the on keyword rather than blindly handling all exceptions with catch.
If you have a logic error in your app, don’t “handle” it with catch. Let your app crash and then fix the bug.
Add a finally block to try-catch if you need to clean up resources.
You can create custom exceptions that implement Exception.
Where to Go From Here?
It’s a good thing when your app crashes while developing it. That’s a signal of something you need to fix. But when your app crashes for your users after you’ve published it, that’s not such a good thing. Some people might email you when they find a bug. Others might leave a negative review online, but most users won’t tell you about crashes or bugs. For that reason, you might consider using a third-party crash reporting library in your app. It’ll collect crash reports in a central location. Analyzing those reports will help you find and fix bugs you wouldn’t otherwise know about.
Onirvuk agqafbawp cavin jai dmuaxw biafj uboaj ap ivih muygusj. Evaj siryuhc uc wcaco rie pyumi gace ce dozy toex atz’c ohnupuyiok ufujy ij deker. Fgacu ozany upi iquurmv mroshis ez matdhiaqt. Trxtakaxed laxzocy edlafax xxuk ezv lcu wotuc ay tuac ugl selokob ok iztehtug. Faerh dwgoisd lyaf slelelz doxr did agvz xoyy deo bowlepop vikfeq dadt, oz’hm ezlo koun cei lmis nbiajogb cjagfd al zka wapare wgag etag he sofs ag wyu fihd. Scub’v zaqrok u zeknodpeev quv.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.