Sissejuhatus
Koodimisstiil on dokument, mille eesmärk on kirjeldada missugune peaks kirjutatud programmikood välja nägema. Stiili jälgimise peamine eesmärk on parandada koodi loetavust ja tagada ühtlane stiilikasutus üle kogu programmi. Parem loetavus täidab ka mitmeid varjatud eesmärke – näiteks on koodist vigu oluliselt lihtsam leida, seda nii sul endal kui ka sind abistavatel või sinuga koos töötavatel inimestel.
Koodimisstiilid erinevad ja seda mitmeti. Ühes programmeerimiskeeles võib sageli esineda mitu konkureerivat koodimisstiili. Samuti erinevad koodimisstiili juhised ühest programmeerimiskeelest teise. Lisaks erinevad ka koodimisstiili nõuded ettevõttest ettevõttesse. Eriti oluline on viimane – kui sinu tööandjal on kindel koodimisstiili nõue, mida kõik töötajad aktsepteerivad ja kasutavad, siis tuleb sellest ka kinni pidada, hoolimata personaalsest eelistusest (või läheneda argumenteeritult miks seda tuleks muuta).
Selles õppeaines on samuti määratletud koodimisstiil, mida tuleb jälgida ja mis on siin kirjeldatud. Oleme koodimisstiili koostamisel lähtunud võimalikult algajasõbralikust lähenemisest, mis peaks tagama maksimaalset koodi loetavust.
Esmalt kodeerima hakates võivad sellised nõuded tunduda küsitava väärtusega kui isegi töötavat programmi on keeruline kirjutada, kuid see ei tähenda, et stiili ja funktsionaalsust tuleks eraldi käsitleda. Need käivad käsikäes ja toetavad üksteist. Alustades stiilist kinni pidamist kohe esimesest nädalast kujuneb see sul nö musklimällu ja mõne aja pärast ei pea sa enam sellele teadlikult mõtlema ega ümber õppima.
Märkus: Kui sul on juba välja kujunenud koodimisstiil, mis on laialdaselt aktsepteeritud ja mis ei lange täielikult kokku siin kirjeldatuga (näiteks ettevõttes kus töötad), võib seda kasutada. Sellegipoolest tuleb jälgida parimaid praktikaid, isegi kui varasemalt pole kõiki neist rakendatud. Näiteks võivad erineda sinu muutujate kirjutamise stiil (snake_case) või loogeliste sulgude asukoht (rea lõpus, mitte eraldi real).
Koodirea pikkus
Klassikaliselt on soovitav koodirea pikkus 80 tähemärki. See tuleb ajalooliselt monitoride võimekusest kuvada 80 tähemärki rea kohta.
Kuna tehnika on vahepeal oluliselt arenenud, siis Programmeerimine 1 ja 2 kursustes on tegu rangelt soovitatava pikkusega (tulevane tööandja nii leebe ei pruugi olla). St kui lähed mõne tähemärgi üle, siis sellest veel numbrit ei tehta. Küll aga 90 ja enama tähemärgi korral tuleb juba kas ridu poolitada või koodi ümber struktureerida!
Koodirea pikkuse jälgimiseks veendu, et su koodiredaktor kuvaks sulle rea pikkuse kohta indikaatori.
Näiteks Geany puhul: Edit -> Preferences -> Editor -> Display
Vali Long line marker: Enabled ja column: 80
Koodirea poolitamine
C keel ei hooli üldiselt tühikutest ja tabidest, seega poolitada ridu saame ilma ühtegi lisasümbolit kasutamata. Ridade poolitamine on osaliselt reguleeritud aga osaliselt loominguline, et tulemus jääks loetav.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Printing the contents of the structure printf("%7d %4s %10s %10s %2d.%2d.%4d\n", student.id, student.field, student.fName, student.lName, student.birthday.day, student.birthday.month, student.birthday.year); printf("End of structure"); // Long equations example x1 = (-linearCoefficient + sqrt((linearCoefficient * linearCoefficient) - (4 * quadraticCoefficient * constant))) / (2 * quadraticCoefficient); x2 = (-linearCoefficient - sqrt((linearCoefficient * linearCoefficient) - (4 * quadraticCoefficient * constant))) / (2 * quadraticCoefficient); |
- Rida poolita enne 80 tähemärgi täitumist.
- Poolitatud read peavad olema trepitud vähemalt ühe võrra.
- Ridade poolitamine on osaliselt ka loominguline tegevus. Olenevalt poolitamise iseloomust võib loetavuse huvides olla mõistlik ka rohkem treppida.
- Ridu tasub poolitada operaatorite ees või järel.
- Järgnevad koodiread jätkuvad esialgselt taandelt.
Ära nii tee:
Järgnev koodirida on nii pikk, et vajab horisontaalset kerimist ja on raskesti jälgitav.
1 |
printf("%s %s %d - got %.2f of the votes\n", (candidates + total)->candidate, (candidates + total)->party, (candidates + total)->votes, ((float)(candidates + total)->votes / (float)votes) * 100); |
Sõnede poolitamine
Sõnede poolitamisel kehtivad täiendavad reeglid. Nimelt ei tohi sõne poolitada jutumärkide vahelt lihtsalt rida vahetades.
1 2 3 |
// Splitting strings printf("This is an extremely long sentence that needs to be split up in " "order to adhere to the line length requirements.\n"); |
- Sõne poolitamiseks lõpeta esmalt jutumärgid ära ja seejärel alusta jutumärgid uuesti järgmisel real.
- Poolitatud sõne tuleks alustada samakaugelt kui eelneval real jutumärgid algasid.
- Alternatiivina võib tükeldada prinditava teksti mitme funktsioonikutse vahel ära.
Koodi kommenteerimine
Koodi kommenteerimise eesmärk on suurendada programmikoodi loetavust ja hallatavust. Programmi kirjutamise hetkel võib tunduda kõik äärmiselt lihtne ja arusaadav kuna see on sul parasjagu mõttes. Paraku tuleme me enamasti programmide juurde tagasi mõne päeva, nädala, kuu või miks mitte ka aastate pärast ning siis on juba väga keeruline kommenteerimata koodi lahti mõtestada. Veel hullem, vigu leida, parandada ja muudatusi sisse viia.
Hästi kommenteeritud kood pole mitte oluline vaid sinule, vaid tulevikus ka su kolleegidele, kes sinuga koos töötama peavad ja sama koodibaasi haldavad.
Kommenteerida tuleks eelkõige “huvitavaid” koodilõike. Arusaam mis on huvitav ja mis mitte tekib kogemusega. Kindlasti ei ole mõtet igat koodirida kommenteerida – see teeb koodi loetamatuks. Kommentaarid võiksid olla iga mõne koodirea järel. Alustades võiksid sihiks võtta, et keskmiselt iga 5 koodirea kohta on vähemalt 1 kommentaar. Kogemuste tekkides näed, et vahel on vaja igale reale kommentaari, vahel aga näed, et 10-20 rea kohta pole ühtegi vaja.
Koodi kommenteerimiseks on mitmeid võimalusi:
- Eraldi real olev kommentaar
- Samal real olev kommentaar
- Mitmerealised kommentaarid (vt Faili päis)
1 2 3 4 5 |
// This is a comment on a separate line if (debug == true) { printf("Debugging on"); // This is an inline comment } |
- Soovitav on kommentaar paigutada eelnevale reale iseseisvalt – sedapidi saavutad kõige parema loetavuse.
- Kommentaari algustähise ja sisu vahel on tühik.
- Kommentaar algab kahe kaldkriipsuga ja lõppeb rea lõppedes.
- Eelista lühikesi ja konkreetseid kommentaare pikkadele ja lohisevatele.
- Pea kinni rea maksimaalsest pikkusest.
Milline on hea ja milline halb kommentaar
Hea kommentaar selgitab
- mida sa teed
- miks sa seda teed
Kusjuures vastama ei pea mõlemale küsimusele korraga.
1 2 |
// Calculate hypotenuse of a right triangle. float hyp = sqrt((base * base) + (height * height)); |
Ära nii tee! Halb kommentaar selgitab kuidas sa seda teed.
1 2 |
// Add squares of base and height together and take a square root float hyp = sqrt((base * base) + (height * height)); |
Halvaks teeb sellise kommentaari see, et esiteks ei anna see mitte midagi uut – sa kordad juba seda mis on koodis kirjas. Teiseks, kui koodis on viga, siis kommentaar ei selgita mida sa tegelikult teha soovisid – näiteks andes probleemse koodi sõbrale lugemiseks pole võimalik tal leida ebakõla kommentaari ja koodis kirjutatu vahel..
Faili päis
Kõikide .c ja .h failide päisesse tuleks lisada koodifaili ja autori(te) nimed ning lühike selgitus mida fail sisaldab. Avalike projektide puhul lisatakse reeglina ka autori veebileht ja/või e-postiaadress.
Sageli lisatakse failidesse ka loomise ja viimase muutmise aeg või faili versiooninumber. Osadel juhtudel hoitakse faili päises ka muudatuste ajalugu, kuid viimasel ajal paikneb see pigem versioonihalduses (Nt Git).
1 2 3 4 5 6 7 8 9 |
/** * File: singularity.c * Author: Risto Heinsar * Created: 19.08.2014 * Last edit: 27.08.2015 * * Description: Experimental program to test dividing by * zero */ |
Faili päise puhul kasutatakse kommenteerimise viisi, kus kommentaar algab märkidega /* ja lõpeb märkidega */. Kõik vahepealne loetakse kommentaariks.
Muutujate deklareerimine ja initsialiseerimine
1 2 3 4 5 6 7 8 |
// Single variable float average; // Declaration and initialization double trackLength = 500.0; // Multiple variable declaration, initialization not allowed int hours, minutes, seconds; |
- Muutujate nimed peaksid kirjeldama nende sisu
- Nimetamisel kasutame kaameliküüru ( lowerCamelCase ) meetodit – st ühesõnalised muutujad algavad väikese tähega ja mitmesõnaliste puhul on järgnevad sõnad suure algustähega.
- Ühest tüübist võib deklareerida mitut muutujat, eraldades need koma ja tühikuga, kuid sellisel juhul ei tohi neid algväärtustada samal real.
- Globaalmuutujate kasutamist tuleb vältida (välja arvatud mõned erijuhud).
- Muutujaid võid deklareerida nii funktsiooni alguses (C90 standard) või seal kus kasutusele võtad (C99 standard). Viimase puhul arvesta, et osad kompilaatorid ei pruugi seda toetada.
Ära nii tee!
1 2 3 |
int a, b, c, x, k, l, m, n; int items = 6, total, costPerItem = 8; int viiendaloenduriesimesehulgapikkusesimeseliteratsioonil; |
- Esimese rea puhul on väga keeruline aru saada mis muutuja mis eesmärki täidab. Ühetähelisi muutujaid tuleks vältida enamikes kohtades. Üldjuhul on ühetähelised muutujad mõistlikud vaid tsükliloenduritena või üldmõistetavas tähenduses (nt n – objektide hulk).
- Teise näite puhul on segatud algväärtustatud ja algväärtustamata muutujad. Kõik algväärtustatud muutujad deklareeri eraldi ridadel, sedasi vähendad tõenäosust tähelepanematusest vigu teha.
- Viimase näite puhul võib olla tegu küll põhjaliku kirjeldusega, kuid on loetamatu, kuna sõnade eraldust pole näha (kaameliküüru meetod!). Samuti tasub kaaluda kui pikk on mõistlik muutuja nimetus.
Viitmuutujate deklareerimine (pointer)
Viitade puhul on olulised samad põhireeglid nagu tavaliste muutujate puhul. Küll aga lisanduvad mõned reeglid.
1 2 3 4 5 |
// single pointer int *pData; // multiple pointers int *pFirst, *pLast; |
- Sümbol * paikneb muutuja nime ees
- Tavaliselt pannakse viitmuutuja ette väike p täht, näitamaks, et tegu on viidaga.
- Viitmuutujad deklareeritakse eraldi tavamuutujatest.
Ära nii tee!
1 2 |
int* pData; int* pList, val; |
- Pannes tärni andmetüübi järele tekitab see petliku mulje, et kõik antud real deklareeritavad muutujad on viidad.
- Teise rea puhul võib tekkida petlik mulje, et deklareeritakse 2 viitmuutujat. Tegelikult on viit vaid pList. val on tavaline int tüüp muutuja. Deklareeri alati viidad eraldi tavalistest muutujatest!
Arvutused ja loogikaavaldised
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Simple binary math operation a = b + c; // Slightly more complicated math operation average = (firstResult + secondResult) / 2; // Logic operation. Both binary and unary operators. (((month == 1) || (month == 2)) && !holiday) // Unary operation (bitwise inversion) a = ~a // Variable decrement k-- |
- Kõik toimingud, mis kasutavad vähemalt 2 operandi tuleb eraldada tühikutega
- Unaarsed operaatorid kirjutatakse kokku operandiga. Näiteks !holiday ehk NOT holiday, ~a ehk INV a (bitikaupa inverteerimine), k-- (dekrementeerimine 1 võrra)
Ära nii tee!
1 |
P=(V2+(C/d)*1.25)/tmp+sqrt(x); |
Tühikute puudumise tõttu on väga raske jälgida tehte korrektset kirjapanekut või seda muuta.
#define makrod
1 2 3 |
#define DATAPOINTS 10 #define MAX_SIZE 5 #define INPUTFILE "settings.ini" |
- Nimetus kirjutatakse suurtähtedes
- Üks kasutusvaldkondadest on maagiliste numbrite vältimine koodis
- Mitmesõnalised nimed kirjutatakse altkriipsuga
Ära nii tee!
1 2 |
#define limit 10 #define ARRAYMINVAL 0 |
- Esimesel juhul võib tekkida hilisemalt segadus, et limit väärtust on võimalik muuta, kuna nimetamisstiil on sama nagu deklareeritud muutujatel.
- Teisel on taaskord loetavus kehv kuna sõnade eraldajaid pole näha, olgugi, et kirjeldus on piisav.
Tingimuslaused
1 2 3 4 5 6 7 8 9 10 11 12 |
if (value < 0) { printf("Value is negative!\n"); } else if (value > 0) { printf("Value is positive!\n"); } else { printf("Value is zero!\n"); } |
- Loogelised sulud algavad ja lõppevad eraldi real.
- Loogelised sulud on samal kaugusel vasakust äärest – st joonduvad kenasti üksteise all.
- Loogeliste sulgude sisu on 1 võrra edasi trepitud (4 tühikut)
- Märksõna else asub eraldi real. else ja eelneva lõppeva loogelise vahel tühja rida ei ole!
- Märksõna if järel on 1 tühik.
- Aritmeetiliste ja loogikaoperaatorite (=, +, ==, <=, <, >, ||, && jne) ees ja järel on üks tühik. See ei laiene unaarsete operaatoritele (~, ^, &. |, !)
Ära nii tee!
1 2 3 4 5 6 7 8 9 |
if (value == 0) { printf("Value is zero!\n"); } else { printf("Value is non-zero!\n"); } |
- Loogeliste paigutamine tekitab segadust olenemata järgitavast koodimisstiilist.
- Tühi rida enne else märksõna tekitab olukorra kus seotus eelneva if lausega pole koheselt arusaadav. Samuti tekitab kiusatust sinna vahele mõne koodirea kirjutamiseks, mis oleks juba viga.
Switch lause
1 2 3 4 5 6 7 8 9 10 11 12 |
switch (variable) { case 0: // actions break; case 1: // actions break; default: // actions break; } |
- Märksõna switch järel on 1 tühik
- Märksõna case ja selle väärtuse vahel on 1 tühik
- Switch lause sisu on loogeliste vahel. Loogelised on eraldi ridadel ja horisontaalselt joondus (sama nagu if-lause)
- switchi sisu on trepitud 1 võrra
- case sisu on trepitud ühe võrra
Treppimine
Treppimine (indentation) on tegevus, mille käigus me muudame koodi taanet (kaugus vasakust äärest). Koodi treppimine on oluline loetavuse parendamiseks. Trepitud koodi puhul on hea jälgida millised koodiread on millise koodiploki sees.
Treppimise astmeks meie antavates kursustes on valitud 4 tühikut. Kui kasutad treppimiseks tabulaatori (tab) klahvi on soovitav oma koodiredaktoris see seadistada asenduma 4 tühikuga. Vastasel juhul võib koode näha välja loetamatu avades mõne teise redaktoriga (erinevad koodiredaktorid võivad näidata tabi näiteks 2, 4 või ka 8 tühikuga).
Väldi tabi ja tühiku ristkasutust!
Järgnevalt on esitatud üks viisakalt trepitud koodi kondikava.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
int main(void) { while (true) { if (condition) { stmt; break; } stmt; } if (condition) { stmt; } stmt; } |
Märka järgnevat:
- Koodiplokkide loogelised sulud asetsevad alati üksteise all. Sedasi on lihtne aru saada mis laused on koodiploki sees.
- Algavad ja lõppevad loogelised sulud on eraldi ridadel.
- Loogeliste sulgude vahele jääv sisu trepitakse ühe võrra (4 tühikut)
- Vihje: enamikes koodiredaktorites saad taanet suurendada valitud (1 või rohkem) koodiridadel kasutades tab klahvi. Taande vähendamiseks valitud ridadel saab kasutada shift+tab klahvikombinatsiooni.
- Soovitus: Seadista oma koodiredaktor sedasi, et algava sulu järel tekitatakse automaatselt ka lõppev sulg. Kui seda võimalust pole, tee seda ise! Vastasel juhul võib ununeda märgis. Geany seadistuse soovituses oleme just nii teinud.
- Soovitus: lase oma koodiredaktoril ise treppida. Enamik koodiredaktoreid lisab koodi taanet automaatselt kui oled pärast algavat loogelist sulgu vajutanud enter klahvi. Sedasi väldid vigu, peavalu ja asjatut manuaalset tööd.
Ära nii tee:
Järgneva koodi puhul on väga keeruline aru saada mis laused mille sees asuvad
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
int main(void){ int i; for(i=0;i<10;i++) { printf("%d is an "); switch(i%2) { case 0: printf("even number\n"); break; case 1: printf("odd number\n"); } } } |
Treppimise sügavus
Väldi enama kui 3 koodiploki üksteise sisse paigutamist. Sama piirang kehtib enamikes ettevõtetes (osades lubatakse ka 4).
Liigne koodiplokkide üksteise sisse pesastamine muudab koodi raskesti jälgitavaks. Lahenduseks on koodi ümber organiseerimine (refaktoriseerimine). Näiteks võib osa koodist paigutada funktsiooni, pesastatud tingimuslaused saab kokku kirjutada ning osa koodis olevatest lausetes võib olla võimalik ümber paigutada.
Tsüklid
1 2 3 4 |
while (condition) { // loop this } |
1 2 3 4 5 |
do { // loop this } while (condition); |
1 2 3 4 |
for (i = 0; i < n; i++) { printf("%d\n", i); } |
- Märksõnade while ja for järel on 1 tühik
- Loogelised sulud on samal kaugusel (horisontaalselt joondus), eraldiseisvatel ridadel
- Loogeliste sulgude vahele jääva koodiploki read on trepitud 1 võrra.
- Märka kindlasti ka for lauses semikoolonite järel ja operaatorite ees ja järel olevaid tühikuid.
Pesastatud for tsüklid
Tsükleid üksteise sisse paigutades tuleb jälgida treppimist. Iga järgnev koodiplokk viib taanet 1 võrra edasi.
1 2 3 4 5 6 7 |
for (i = 0; i < n; i++) { for (j = 0; j < m; j++) { printf("%d %d\n", i, j); } } |
Funktsioonid
main() funktisoon
1 2 3 4 |
int main(void) { // Code } |
või
1 2 3 4 |
int main(int argc, char *argv[]) { // Code } |
- Esimest kirjapilti kasutatakse olukorras, kus programmile ei soovita edastada parameetreid käsurealt.
- Teist kirjapilti kasuta ainult siis kui soovid käsurealt parameetreid edastada (nt faili nimetus)
Enda loodud funktsioonid
1 2 3 4 5 |
float CalcPowerConsumption(float current, float voltage) { float power = current * voltage; return power; } |
- Funktsiooni nimest peab olema võimalik välja lugeda mida see teeb.
- Funktsioonide nimetamisel kasutame suure algustähega kaamelüküüru stiili ( UpperCamelCase ), kus kõik sõnad kirjutatakse kokku ning algava suure tähega. Sedasi saame eristada neid kenasti muutujatest, makrotest jt.
- Enda loodud funktsioonidel peab olema prototüüp, mis paikneb kas samas failis enne main() funktsiooni või eraldi päisefailis.
Funktsiooni kommentaarid
Funktsioonile peab alati eelnema kommentaar, mis annab selge arusaama mida funktsioon teeb (detailsemalt). Olenevalt funktsiooni keerukusest võib kasutada kas standartset või lihtsustatud kuju.
Standartne funktsiooni kommentaari kuju
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * Description: Asks the user for an integer in between the given limits. * Repeats until requirements are met and then returns the number. * Outputs a warning when input is out of range * * Parameters: min - lower limit for the user input (inclusive) * max - upper limit for the user input (inclusive) * * Return: Returns an integer within the specified limits */ int GetIntInRange(int min, int max) { int num; do { printf("> "); scanf("%d", &num); if (num < min || num > max) printf("\nRetry! Input must be in between %d and %d\n", min, max); } while (num < min || num > max); return num; } |
- Kirjeldus – sisaldab funktsiooni eesmärki, tööpõhimõtteid, piiranguid. Kui funktsiooni käigus muudetakse andmeid, mis pole funktsioonile lokaalsed, siis see tuleb samuti kirja panna (nt massiivid)
- Funktsiooni parameetrid – nende nimed ja väärtuse tähenduslikkus antud funktsioonis. Kui eksisteerivad, siis ka piirangud (eeltingimused) väärtustele
- Funktsiooni poolt tagastatav väärtus – andmetüüp ja selle tähenduslikkus
Lihtne kommentaari kuju
Antud kommentaari kuju tohib kasutada vaid väga lihtsate funktsioonide puhul.
1 2 3 4 5 6 7 8 |
/** * Function to calculate the power consumption given * the voltage and current the circuit is using. */ float CalcPowerConsumption(float current, float voltage) { return (current * voltage); } |
Funktsiooni prototüüp
Enda loodud funktsioonide puhul tuleb kirjeldada funktsiooni prototüüp. Funktsiooni prototüübid paiknevad vahetult enne main() funktsiooni või eraldi päisefailis.
1 |
float CalcPowerConsumption(float current, float voltage); |
- Prototüüp kirjeldab ära funktsiooni tagastuse, nime ja parameetrid (andmetüüp ja nimi).
- Funktsiooni prototüüp on sisuliselt koopia funktsiooni päisest selle deklaratsiooni juures.
Loendid ehk enumeration
Loendite nimetamise stiiliks kasuta UpperCamelCase loendi tunnuste nimetamiseks SCREAMING_SNAKE_CASE .
Kasutada võib üherealist definitsiooni
1 |
enum SystemStatus {STATUS_OFF, STATUS_IDLE, STATUS_BUSY}; |
või mitmerealist definitsiooni
1 2 3 4 5 6 |
enum SystemStatus { STATUS_OFF, STATUS_IDLE, STATUS_BUSY }; |
NB! Väldi loendite tüübi ümberdefineerimist ( typedef ) kuna see peidab informatsiooni.
Kirjed ehk struktuurid (struct)
Struktuuride puhul kasutame samu treppimise ja loogeliste sulgude paigutamise reegleid nagu varasemalt.
1 2 3 4 5 6 |
typedef struct person { char *fName; char *lName uint_64t identityCode; } person; |
Märkus! Kuigi tüüpide ümberdefineerimist käsitletakse sageli kui informatsiooni peitmisena on selle kursuse raames struktuuride ja ühendite (union) ümbernimetamine lubatud.