Peen viga koodis
18.02.2008 | Gunnar
Lõuna-Eesti ässadele infosüsteemi kirjutades leidsin ühest koodinäitest vahva vea. Sellise mõnusa, mille peale algajad tihti tunde kulutavad ja lohakamad neist, olles kõik tööle saanud, ei saa isegi pihta, mis juhtus. Ja miks juhtus.
Aga asja juurde. Teemaks piltide suuruse muutmine nii, et muudetud suurusega pildi kohta on ette antud ühe külje pikkus. Vajalik on see selleks, et tulemuseks saadaval pildil oleks sama külgede suhe, mis originaalil. See garanteerib selle, et pilt ei moondu mõõtmete muutmisel.
Märkusena olgu öeldud veel see, et antud kood on mõeldud piltide suuruse vähendamiseks. Suurendamisel pole rastergraafika osas mõtet, sest pildi kvaliteet muutub järjest halvemaks, mida suuremaks püüame originaalist minna.
Viga sisaldav kood
Olgu ette antud tulemuseks saadava pildi laius. Kood, mille abil saame välja arvutada selle kõrguse on järgmine.
int srcHeight = originalImage.Height;
int thumbWidth = givenWidth;
int thumbHeight = (srcHeight / srcWidth) * thumbWidth;
Siin peitub nüüd üks kaval viga. Proovi see ära arvata enne, kui edasi loed. See kood töötab teatud juhtudel ja teatud juhtudel mitte. Arva ära, mis juhtudel see kood ei tööta ja arva ära, millised saad pildi mõõtmed mittetöötaval juhul.
Vaata seda koodi hoolega.
Viga
Okay, vea põhjustab järgmine rida.
Kui pildi laius on suurem pildi kõrgusest, siis tegeldes täisarvudega, saame sulgudes oleva jagatise tulemuseks nulli. Kui kasutaksime komakohaga arve, siis seda ei juhtuks, kuid täisarvudel komakohti pole ning kui jagatis jääb nulli ja ühe vahele on tulemuseks null.
Samuti on see jagatis võimeline automaatselt ümardama ka muid komakohti, kuid sel juhul ei teki viga, kui püüame saadud mõõtmetega pilti luua, sest kumbki mõõtmetest pole null.
Et vältida vigu ja ebatäpsusi, on mõttekas kirjutada antud rida ümber selliselt.
Vea olemus
See viga tekib puhtalt muutujatüüpide tõttu. Kuigi reegel on olemas, millele see probleem allub ja see kõik on kenasti dokumentatsioonis kirjas, lõbustame ennast siiski väikest viisi uurimisega.
Kontrollime näiteks, mis juhtub siis, kui kirjutame välja kahe täisarvu jagatise.
Response.Write(300/200);
Esimesel juhul on tulemuseks null ja teisel juhul üks. Oleks võinud ju oodata, et tulemuseks on 0.5 ja 1.5, kuid nii see paraku ei läinud. Nagu näeme, peitub ülaltoodud koodi probleem just selles sulgudega avaldises, kus jagamistehe tehakse.
Esimene rida eeltoodud koodist näitas, et saame nulli, kui jagaja on suurem. Teine rida näitas, et kui jagaja on väiksem ja arvud täpselt ei jagu, kaotame ära komakohad. Peatume korra teise rea juures. Aga äkki toimus automaatselt mingi ümardamine, millest me teadlikud pole?
Proovime selle selgeks teha järgneva koodi abil.
Kui toimub ümardamine, siis peaksime tulemuseks saama ühe, sest komakohtadega tulemuseks on 0.999. Kuid tulemuseks on jällegi null. Seega ei toimu ka mingit ümardamist ja me võime järeldada, et tegemist on siiski tüüpide eripärast tuleneva probleemiga.
Hetkel on see vaid hüpotees, meie esmane arusaam suuna kohta, kust peame kinnitust otsima. Järeldust me veel teha ei saa, sest üks katse on veel puudu. Nimelt katse, mis kinnitaks, et asi on muutujatüüpides. Ilma selle katseta võime ju kahtlustada näiteks seda, et kompilaatoris on viga.
Proovime, millised tulemused annab meile järgmine kood.
Response.Write((float)300/200);
Nüüd saame sellised tulemused, nagu võiks vahearvutuses saada. Kuid kas on siin ka kindel reegel tüüpide kohta? Mis saab siis, kui teeme vastupidi - esimene arvudest on täisarv ja teine komakohaga arv? Kontrollime järgmise koodi abil.
Response.Write(300/(float)200);
Tulemused saame samad, mis ennegi. Seega piisab kui üks arvudest on komakohaga, seejuures pole vahet kumb täpselt.
Lõpetuseks
Kuigi ma tõin siinkohal pika näite selle kohta, kuidas võiks probleemi analüüsida, tasub siiski alustada sellest, et tutvuda kasutatava programmeerimiskeele spetsifikatsiooni ja dokumentatsiooniga. Antud juhul oleks pidanud lugema näiteks sellistel teemadel nagu näiteks automaatsed tüüpide teisendused tehetes ning kogu siintoodud pikk analüüs oleks tegemata jäänud.
Ehkki toimiva lahenduseni jõudsime ju tegelikult päris kiiresti ja olekski võinud asja sinnapaika jätta, poleks see olnud korrektne lähenemine koodis esinevatele probleemidele. Kui esineb probleem, siis tuleb selgeks teha selle olemus ja selle põhjused. Ilma selle selguseta ei saa kindel olla ka üheski tehtud paranduses.

18.02.2008 kell 22:52
Igapäevaselt veebirakendusi lappides võib jah selline iidne asi nagu muutujatüüp ja cast-ing hakata unustusehõlma vajuma
Olid aga ajad, kus iga mees luges bitte ja teadis castingureegleid unepealt…
18.02.2008 kell 23:04
Jah, Urmo, olid ajad
18.02.2008 kell 23:22
Kui see on peen viga, mitte asi, mis kohe kui ‘/’ kirjutad pähe tuleb, hakkan vanaks jääma…
18.02.2008 kell 23:26
Vaata palju sellist jama esineb koodis, mida netis pakutakse. Üks artikkel, kus sellist viga nägin, oli üleval rippunud viisakas kaks aastat. Kommentaare oli igasuguseid, aga mitte keegi polnud seda pisiasja märganud.