Algajate programmeerijate peamised probleemid
Programmeerimisel on vigade tegemine õppimise loomulik osa. Seetõttu on vajalik õpetada ka vidadega toimetulemist ja nende parandamist.
Selles peatükis on toodud algajate programmeerijate peamised mured, mida oleme täheldanud programmeerimise õpetamisel ja soovitused nendega toime tulla. Vead on esitatud Pythoni programmeerimise algkursuse peamiste teemade järgi.
Andmetüübid
Sobimatute operatsioonide tegemine sõne ja arvu puhul. Näiteks sõne ja arvu liitmine '23' + 3
või sõne ja arvu võrdlemine 'Maarja' > 23
.
Programmeerimise õpetamise alguses tuleks tähelepanu pöörata erinevate andmetüüpide operatsioonidele. Tasub proovida teha läbi näide, kus on liidetakse sõne ja arv. Selle näite abil saab tutvutada ka veateateid ning nendega seonduvat. Oluline on selle näite juures rõhutada, et taolist operatsiooni ei tohiks teha, kuigi õpetajana täpselt seda demonstreerite. Lisaks võib läheneda mõttest, et päriselus ei ole ka võimalik sõnale arvu juurde liita või sellest lahutada, näiteks karu + 3 ei ole loogiline.
Ujukoma arvude puhul kasutatakse koma mitte punkti.
Korduvalt tasub rõhutada, et koma asemel kasutatakse arvudes punkti. Sageli seostab õpilane koma kasutamist matemaatilisele kirjaviisiga. Lisaks võib mainida, et koma on juba Pythonis kasutusel, näiteks argumentide eraldajana funktsiooni väljakutsel või elementide eraldajana järjendis.
Kasutaja sisendi teisendamisel täisarvuks kasutatakse int
ja input
funktsioone vales järjekorras (input(int(...))
) või unustatakse sisendit täisarvuks teisendada.
Kasutaja sisendi tutvustamisel tuleks rõhutada seda, et kasutaja sisend on alati sõne tüüpi ja vajadusel on seda vaja teisendada arvuks. Samuti seda, et kui kasutatakse erinevaid funktsioone üksteise sees, siis täitmist alustatakse alati sisemisest funktsioonist. Selle näite puhul on esmalt vaja kasutajalt sisend küsida ja seejärel see teisendada täisarvuks ehk int(input(...))
.
Unustatakse ära plussmärgid sõnede liitmisel. Näiteks print("Mina olen " vanus " aastat vana.")
.
Võib selgitada seda, et nii nagu matemaatikas peab liidetavate vahel olema plussmärk. Erinevus on selles, et programmeerimises saab lisaks arvudele, liita ka sõnesid.
Samuti võib print
funktsioonis kõik lauseosad argumentidena esitada (eraldaja on koma), näiteks print("Mina olen", vanus, "aastat vana.")
, kuid siis ei pruugi see kinnistada sõnede liitmist, aga annab võimaluse print
funktsiooni ka teisiti kasutada. Seda võiks tutvustada siis, kui õpilane juba oskab sõnesid liita.
Teisendatakse kasutaja sisestus sõneks, mida ei ole vaja teha. Näiteks valik = str(input("Failinimi: "))
.
Kuna kasutaja sisestus on alati sõne tüüpi, siis selle teisendamine sõneks ei ole vajalik ega optimaalne.
Muutujad
Väga sageli kasutatakse jutumärke või ülakomasid muutujanime ümber kui soovitakse muutuja väärtust ekraanile kuvada.
a = 1 print('a')
Võib selgitada seda, et muutujal on nimi ja nimesid kasutatakse sellisena nagu nad on. Seega peab ka muutuja nimesid kasutama ilma ülakomade või jutumärkideta. Lisaks esitatakse sõnesid jutumärkide või ülakomade vahel.
Programmis on defineerimata muutujad.
Defineerimata muutujate kohta kuvatakse vastav veateade, mida võib õpilastele muutujate teema juures tutvustada ja selgitada. Sellise vea põhjus võib olla see, et kirjutatakse muutuja nimi valesti või ei ole sellist muutujat programmis olemas. Muutujate teema tutvustamisel on oluline, et iga muutuja deklareerimisel on vaja mõelda, miks seda muutujat on vaja luua ja kus programmis on vaja seda kasutada. Juba programmeerimise õpetamise alguses tuleks õpilasi suunata oma koodi läbi mõtlema.
Kasutatakse mittesobivaid muutuja nimesid, näiteks algab muutujanimi numbriga või on sama nimega, mis on funktsioon.
Muutujate tutvustamisel on vajalik rääkida ka muutujate nimede reeglitest. Siin on mõned reeglid. Muutujate nimede ABC:
• Tõstutundlikud, st ühe ja sama tähe suur ja väike esitus on erinevad (Püüton != püüton
).
• Ei sisalda kirjavahemärke ega tühikuid.
• Ei alga arvuga.
• Koosneb tähtedest, tähtedest ja numbritest.
• Esimene märk on täht või _alakriips.
• Võtmesõnad on kasutatud, neid ei tohi nimena kasutada
False None True and as assert break class continue def del elif else except finally for from global if import in is lambda nonlocal not or pass raise return try while with yield print.
Tingimuslause
Tingimuslause teema juures puutuvad algajad esimest korda kokku Pythoni treppimisega ja sellega, kui olulised on taanded.
Kasutatakse valesid taandeid või unustatakse koolon ära.
Võib selgitada tingimuslause struktuuri ja treppimist e tühikutega joondamist. Pythonis on taanded väga olulised (taane on vaikimisi 4 tühikut). Taanete abil määratakse, millised laused kuuluvad tingimuslause vastava haru juurde, ja millised mitte. Need laused, mis on sama tasemega joondatud, kuuluvad kokku. Siin tasub harjutada tingimuslause koostamist. Näiteks võib lasta õpilastel parandada vigast tingimuslauset, kus on koolon puudu või taanded valed. Lisaks võib välja printida mõne keerulise tingimuslause ja lõigata see ridade kaupa tükkideks ning read segada. Seejärel peavad õpilased panema tingimuslause taas õigesti kokku.
Vead, mis tekivad siis, kui tingimuslause koosneb mitmest harust. Näiteks kasutatakse mitut sama tasemega if
-haru, mis otseselt ei ole vale, kuid võib segadust tekitada. Kasutatakse ka ainult elif
-harusid, ilma if
-haruta. Või lisatakse else
-haru, aga if
-haru on puudu.
Mitmeharuliste tingimuslauste teema alguses on vaja selgitada selle struktuuri loogikat. if
-osa peab alati tingimuslauses olemas olema, sest ilma selleta ei ole see tingimuslause. Lisaks on if
-haru tingimuse kontrollimine prioriteetsem kui elif
- või else
-osa tingimus. See tähendab seda, et if
-haru tingimust kontrollitakse alati. Isegi siis kui on mitu if
-haru tingimuslauses, siis kõiki nende tingimusi kontrollitakse. elif
-haru on prioriteedi järjekorras järgmine, pärast if
-haru, ja selle tingimust kontrollitakse siis, kui if
-haru tingimus ei ole tõene. Sellepärast on ka selle nimeks else if
ehk elif
. else
-osa on kõige vähem prioriteetsem ja selle haru tegevusi tehakse siis, kui if
-või elif
-osa tingimused ei ole tõesed. else
-osal ei ole tingimust vaja, sest if
-ja elif
-osades kontrollitakse kõik vajalik ära. Lisaks ei saa olla else-osa ilma if-osata, sest else-osa eesmärk on teha oma tegevused siis, kui kõik eelnevad tingimused ei ole tõesed ja kui neid tingimusi ei ole, siis ei saa ka else
-osa tegevusi teha.
Tingimuses kasutatakse võrduse esitamiseks üht võrdusmärki, näiteks a = b
.
Siinkohal on paslik selgitada, et muutujate omistamisel on juba kasutusel ühekordne võrdusmärk ja võrdust tuleb esitada kahe võrdusmärgi abil, a == b
.
Tehakse vigu liittingimuse puhul ja kasutatakse alati tõest tingimust, näiteks a == 3 or 4
.
Võib hajutada tehete and ja or järjekorda ning võtta osadeks liittingimused. Näiteks tingimuse a ==3 or 4
osad on:
• a == 3
→ kui a
väärtus on 3
, siis True
, kui ei ole 3, siis False
or
• 4
→ alati tõene
Näiteda = 3
→ 3 == 3 or 4
→ True or True
→ True
a = 5
→ 5 == 3 or 4
→ False or True
→ True
Analüüsimise tulemusel näeme, et tingimus on alati tõene.
Üldiselt on tingimuste koostamine algajale päris keeruline ja seda võib eraldi harjutada alustades lihtsamatest tingimustest ja jõudes keerukamate liittingimuste koostamiseni.
While-tsükkel
Esineb raskusi tsüklimuutujast arusaamisel.
Tsüklimuutujast võib mõelda kui mootorist, mis hoiab tsüklit töös. Selleks, et seda mootorit tööle panna, peab selle väärtus muutma. while
-tsükli puhul võib tsüklimuutjat kasutada sageli tsüklitingimuses sees, sest nii saame kontrollida tsükli sammude arvu.
Unustatud on tsüklimuutujat defineerida või suurendada.
Üldiselt ei saa while
-tsükkel ilma muutujata töötada. while
-tsükli teema juures on vajalik rääkida ka selle struktuurist. while
-tsükkel koosneb päisest ja kehast. Päises on tsüklitingimus ja kehas tehakse kõik need tegevused, mis peavad toimuma iga tsükli sammul, sh ka tsüklimuutuja väärtuse muutmine. Tsüklimuutuja defineeritakse tüüpiliselt enne tsüklit.
Tsüklis on tsüklimuutujat vale sammu võrra suurendatud või selle algväärtus on vale.
Tsüklimuutuja algväärtuse ja selle suurendamisel peaks enne läbi mõtlema, mitu korda tsükkel peaks töötama ja selle põhjal määrama algväärtuse ja mitme võrra on vaja tsüklimuutujat suurendada. Näiteks on nende tsüklite sammude arv on võrdne.
muutuja = 0 while muutuja < 3: muutuja += 1 muutuja = 1 while muutuja < 7: muutuja += 2
Järjend
Unustatakse ära, et indekseerimine algab 0-st.
Ilmselt tekib õpilastel küsimus, et miks üldse algab indekseerimine 0-st. Lihtne vastus on see, et programmeerimiskeele loojad on nii määrnud, sest on ka neid keeli, mille indekseerimine algab 1-st, nt R, Fortran. Tegelikult on sellel ka loogiline põhjus. Arvutite maailm on binaarne ehk koosneb ainult 0-dest ja 1-st, kus esimene arv on 0 ehk see on väiksem arv, mida saab kahendsüsteemis esitada. Seega peaks ka indeksid algama kõige väiksemast arvust. Teiseks on kõiki jadaga seotud operatsioone lihtsam ja selgem teha, kui indekseerimine algab 0-st, kuid selle mõistmiseks on vaja mõista matemaatilisi jadasid. Veel võib üheks põhjuseks pidada ka seda, et programmi on lihtsam lugeda ja aru saada, kui indekseerimine algab 0-st. Näiteks on for i in range(len(lst))
näeb selgem välja kui for i in range(len(lst)+1)
. Märkus: range
funktsiooniga loodud poolõigu viimane otspunkt ei ole kaasa arvatud. Kui soovime järjendist kõikide elementide indekseid, siis 1-ga algava indekseerimisel peame lisama len(lst) + 1
.
Järjendi läbimisel ei kasutata tsüklit, vaid püütakse iga elementi eraldi analüüsida, näiteks if
-lause abil.
lst = [2011, 2012, 2013, 2014] if aasta==2011: print(str(aasta) + ".aastal oli vastuvõetuid " +str(lst[0])) elif aasta==2012: print(str(aasta) + ".aastal oli vastuvõetuid " +str(lst[1])) elif aasta==2013: print(str(aasta) + ".aastal oli vastuvõetuid " +str(lst[2])) elif aasta==2014: print(str(aasta) + ".aastal oli vastuvõetuid " +str(lst[3])) else: print("Pole andmeid!")
Selline järjendi läbimine ei ole mõistlik, sest iga elemendi kohta on vaja programmi lisada eraldi lause, mis võtab palju aega. See tegevus muutub võimatuks siis, kui pole teada, mitu elementi on järjendis, näiteks failist lugemisel järjendisse. Samuti on ebareaalne teha iga elemendi jaoks eraldi lauset, kui elemente on tuhandeid isegi miljoneid. Siinkohal võib kasutada üksikult üldisele lähenemist. Võib proovida teha korduv tegevus esmalt ühe elemendi kohta ja hiljem üldistada seda nii, et tegevust saaks rakendada tsüklis iga elemendiga.
For-tsükkel
for
-tsüklis kasutatakse while-tsüklile omast tingimust. Näiteks for i > 5:
.
for
-tsükli puhul võib ka mõelda, et sellel on oma tingimus nagu on while
-tsüklis, kuid see tingimus on alati sama: for
-tsükli laused täidetakse nii mitu korda kui on jadas elemente ehk for
-tsükkel töötab seni kuni on jadas elemente. Jadaks võib olla näiteks järjend, range
funktsiooniga loodud struktuur või sõne.
Unustatakse for
-tsükli päises range
funktsiooni kasutada ja kirjutatakse for i in len(järjend):
.
Siinkohal on oluline rõhutada, et for
-tsüklit saab kasutada ainult itereeritavate struktuuridega. range
funktsioon loob järjendi-laadse struktuuri, mida on võimalik for
-tsükliga läbida. len
funktsioon tagastab aga täisarvu, mida ei ole võimalik tsükliga kasutada.
Mured, mis tekivad range
funktsiooni argumentide vale kasutusega. Näiteks kasutatakse rohkem argumente kui range
funktsioon lubab (range(1, 10, 2, 0)
), kasutatakse argumentidena ebasobivat tüüpi väärtusi (range(0, 10.5, a > 2)
) või unustatakse, et range
genereerib täisarvud poollõigust, kus lõigu viimane otspunkt ei ole kaasa arvatud. Näiteks soovitakse luua järjend, kus on arvud 1 - 10, aga unustatakse, et otspunkt ei ole kaasa arvatud.
list(range(1, 10)) [1, 2, 3, 4, 5, 6, 7, 8, 9]
range
funktsiooni tutvustamisel võiks rõhutada kolme olulist aspekti range
funktsiooni kohta:
• range
funktsiooni argumentideks sobivad ainult täisarvud
• range
funktsioonis kasutatud lõpp-punkt ei ole loodud struktuuris kaasa arvatud.
• range
funktsiooni võib kasutada kuni kolme argumendiga. Need on range(algus, lõpp, samm)
just sellises järjekorras.
Eelnevad punktid võib õpilane lisada kommentaaridena oma programmi.
Samuti võib õpetada range funktsiooni näitel Pythoni dokumentatsiooni lugemist, kus on kirjas, kuidas funktsiooni kasutatakse.
Funktsioon
Unustatakse, et return
lõpetab funktsiooni töö, kuid funktsioonis kirjutatakse peale return
-lauset veel lauseid.
Kõik laused, mis on pärast return
-lauset, ei täideta. Seda võib demonstreerida näitega, kus kasutatakse ka Thonny silumise võimalust. Näiteks
def fun(m): tulemus = m**2 + 2*m + 4 return tulemus if m > 2: return m
Globaalsete muutujate kasutamine funktsiooni sees läbi mõtlemata.
Globaalsete muutujate kasutamine funktsioonis ei ole otseselt vale. Probleemiks on see, et globaalsete muutujate kasutamine tekitab palju segadust ja selle tulemusel on lihtne vigu teha. Näiteks, kui kasutatakse funktsiooni korduvalt ja programmi töö jooksul muudetakse ka globaalsete muutujate väärtusi, siis funktsiooni tulemus võib olla vale (pole see, mida oodati). Seega on soovitav vältida globaalsete muutujate kasutamist funktsioonis.
Kasutatakse väljaspool funktsiooni selle lokaalset muutujat ja tekib viga. Näiteks
def fun(m): tulemus = m**2 + 2*m + 4 return tulemus print(tulemus) # Kuvatakse nimeviga, mille sõnum on name 'tulemus' is not defined.
Funktsioonis defineeritud muutujaid ei saa sellest väljaspool kasutada, sest need tekivad kui funktsiooni kasutatakse ja kaovad, kui funktsiooni töö on lõppenud. Seega ei ole funktsioonis loodud muutujaid olemas väljaspool funktsiooni ja neid ei saa kasutada.
Kutsutakse funktsioon välja ilma sulgudeta või vajaliku argumendita.
Funktsioonide teema tutvustamisel võib alguses tutvustada funktsioone, mida õpilased on juba kasutanud, aga nad ei teadnud, et neid nimetatakse funktsioonideks. Näiteks print
, input
, int
jne. Varem nimetati neid näiteks käskudeks. Kõigi nende funktsioonide kasutamisel on nad kasutanud sulge ja sageli lisanud ka argumendi. Näiteks print
funktsiooni puhul on argumendiks olnud muutujad või sõned, mida tahetakse väljastada. Nüüd, kui tehakse ise funktsioone, siis on samamoodi vaja kasutada sulge ja vajadusel ka argumenti. Harjutada võiks funktsioonide väljakutsumist korduvalt ja erinevate argumentidega.
Funktsioonis on vajalik parameeter/argument puudu või ei kasutata seda funktsioonis
Funktsiooni defineerimisele funktsiooni teema alguses võiks tutvustada funktsiooni struktuuri. Funktsiooni definitsioon koosneb kahest osast: funktsiooni päisest ja kehas. Päises on funktsiooni nimi ja sulgude sees on vajadusel parameeter/parameetrid. Kehas on laused, mida käivitatakse funktsiooni rakendamisel. Näiteks
def fun(m): |
Päis |
tulemus = m**2 + 2*m + 4 return tulemus |
Keha |
Näiteks võib harjutada defineeritud funktsioonid osade määramist ja funktsioonide väljakutsumist.
Funktsioonis, mis peab tagastama, on return
-lause puudu.
Funktsioonid võib jagada peamiselt kaheks: funtksioonid, mis tagastavad (nt arvutavad) ja need, mis teevad midagi (nt väljastavad). Funktsioonid, mis tagastavad peavad sisaldama return
-lauset. Üldiselt on tagastavate funktsioonide defineerimine algajale keerulisem kui väljastavate funktsioonide defineerimine. Funktsioonide defineerimist, mis tagastavad, võib proovida harjutada matemaatikas kasutatavate funktsioonidega. Samuti võivad õpilased lahendada ülesannet, kus on vaja leida kõik need funtksioonid, mis tagastavad funktsioonide hulgast.
Funktsiooni ei rakendata, vaid kopeeritakse põhiprogrammis uuesti.
Funktsioonide kasutamise eesmärk on korduvaid programmi osi vältida (DRY-printsiip, Don't Repeat Yourself). Kui funktsioon kopeerida uuesti iga kord kui seda kasutatake, siis ei ole see optimaalne ega ka mõistlik. Funktsioonide korduvat kasutamist programmides on passlik hajutada.
Andmevahetus
Fail ei asu programmiga samas kasutas või on failitee vale/puudu.
Failist lugemisel on oluline tähelepanu pöörata sellele, et õpilane lisab loetava faili samasse kausta, kus on Püütoni programm, mis seda faili loeb. Seda tasub korduvalt rõhutada. Samuti võib lisada programmi ka täpse failitee.
Failist lugedes ei eemaldata üleliigsed reavahetused ja tekivad vead. Näiteks liites failist loetud reale sõne, lisatakse nende vahele reavahetus, mis on üleliigne.
Selle parandamiseks peaks kasutama strip meetodit, mis eemaldab üleliigsed reavahetused, näiteks rida.strip()
.
Failist lugemisel unustatakse arvud teisendada täisarvuks või ujukomaarvuks.
Andmevahetuse teema juures on oluline korduvalt rõhutada seda, et failist loetud andmed on alati sõne tüüpi ja vajadusel on neid vaja teisendada mõnda teise tüüpi, näiteks täisarvuks või ujukomaarvuks. Miks on andmed sõne tüüpi? See on universaalseim viis ükskõik missuguste andmete lugemiseks, sest sõne tüüp on kõige paindlikum ja üldisem tüüp, mida kasutada. Kui andmed oleks alati näiteks täisarvu tüüpi ja failis on andmed näiteks inimeste nimedega (tekst), siis ei saaks neid andmed kasutada, sest nimesid ei saa täisarvuna esitada. Seega teisendamine sõnest arvuks on palju mõistlikum ja optimaalsem tegevus.
Failist lugemisel on kasutatakse vale tekstifaili kodeeringut või see puudub.
Üldiselt kasutatakse failikodeeringuks UTF-8, kuid kui tekstifail on salvestatud mõne muus kodeeringus ja failist lugemisel on kodeering ikkagi UTF-8, siis andmete kasutamisel programmis tekivad vead. Näiteks ei kuvata tähti õigesti. Selle lahendamiseks tuleks fail salvestada UTF-8 kodeeringus näiteks näiteks Thonny’i või Notepad++ abil. Sageli on võib ka fail olla salvestatud UTF-8-BOM kodeeringus, kus lisatakse faili esimesele reale signatuur, mis failist lugemisel põhjustab vigu. Lahenduseks on tekstifail salvestada õiges kodeeringus ilma kodeeringu signatuurita või lisada kodeeringuks UTF-8-SIG
. Failikodeering on mõistlik alati lisada, sest nii kuvatakse ka erilised tähed nagu ä või õ korrektselt.
Muu
Õpilane kasutab programmi faili nimes mooduli nime, näiteks turtle.py
.
Kõikide nende teemade juures, kus on vaja importida mõnda moodulit, on vaja rõhutada ja ka materjalidesse lisada, et mooduli nime ei saa kasutada programmi nimena. Õpilasel tekib ilmselt küsimus, et miks ei või kasutada mooduli nime faili nimena. Põhjuseks on see, et sel juhul ei pöörduda õpilase programmis mooduli enda Püütoni faili poole, vaid selle poole, mida õpilane kirjutas. Õpilase loodud Püütoni programm asendab mooduli enda faili. Selle tulemusel tekivad vead, mille sõnumiks on tõenäoliselt see, et vastavat funktsiooni ei leitud, sest õpilase kirjutatud programmis ei ole seda funktsiooni.
Lisaks eelnenud probleemide võivad algajad teha veel vigu, mille kohta võite lugeda Raigo Kodasmaa magistritöö raames valminud materjalist: https://courses.cs.ut.ee/2018/eprogalused/fall/Main/Veateated.