Kódolási stílus – javaslatok
Érdemes szem előtt tartani a következő dolgokat.
Olvashatóság – alapok
- Írjunk jól olvasható kódot. Ennek a legegyszerűbb módja azt írni, amit gondolunk, és nem nyelvi elemekkel trükközni a szép, egyszerű programok írása helyett.
- Törekedjük arra, hogy a programkódunk (a feladat megoldása) minél
jobban hasonlítson a feladat szövegére. Ne vigyünk bele a megoldásba
felesleges csavarokat! Pl. ha 1-től 100-ig ki
kell írni a számokat, a szép megoldás ez:
for (i = 1; i <= 100; ++i) printf("%d\n", i);
A működő, de nem annyira szerencsés megoldások:for (i = 1; i < 101; ++i) printf("%d\n", i); // hol a 101 a feladat szövegében? for (i = 0; i < 100; ++i) printf("%d\n", i+1);
(Nem vicc, találkozni ilyenekkel.) - Használjunk értelmes változóneveket. Például, mit csinál a következő program?
int main() { int a, b, c, d; d = 2; a = 0; while (a < 20) { b = 1; for (c = 2; b && c <= d/2; c++) if (d % c == 0) b = 0; if (b) { printf("%d ", d); a++; } d++; } return 0; }
Megoldás
Ugyanazt, mint ez:
int prim(int szam) { int oszto; for (oszto = 2; oszto <= szam/2; oszto++) if (szam % oszto == 0) return 0; return 1; } int main() { int db, vizsgalt; vizsgalt = 2; db = 0; while (db < 20) { if (prim(vizsgalt)) { printf("%d ", vizsgalt); db++; } vizsgalt++; } return 0; }
- Kommenteljünk. Sokat.
- Ne optimalizáljunk feleslegesen, csak ha kiderül, hogy lassú a programunk.
- Indentáljük a kódot, még papíron is. Vegyük észre ezáltal azt is, hogy mennyire hasonlít a megírt kód a struktogramra. Ez segít átlátni a program szerkezetét!
- Használjunk néven nevezett konstansokat és felsorolt típusokat mágikus számok helyett!
Karbantarthatóság – a program struktúrája
- Soha, de soha ne copy-pasteljünk kódot! A copy-pastelt kód helyett használjunk ciklust vagy függvényt a feladattól függően.
- Ne használjunk goto-t, break-et, continue-t feleslegesen. Egy-két nagyon speciális esettől eltekintve szebb kódot lehet írni nélkülük.
- Függvény – hacsak nem ez a feladata – nem csinál I/O-t.
Azért függvény, hogy paraméterekben fogadjon és visszatérési
értékben/paraméterben adjon vissza dolgokat. Az I/O a hívó
és a felhasználói felület dolga. Az esetleges diagnosztikai kiíratások
persze lehetnek kivételek, de az is inkább valami naplózó keretrendszer
használatával (vagy pl.
#ifdef DEBUG
-okkal védve) javasolt. - Függvény ne hívjon kilépéssel, hasonlókkal kapcsolatos dolgokat, mert a
felsőbb szintű kódot ez meglepetéssel fogja érinteni (fájlok lezárása,
takarítás elmarad). Ha szükséges, akkor legyen a programnak valami
fatal_error()
függvénye, azt hívjuk. - Mivel a C-ben nincsenek kivételek (exception), ezért persze bonyolult a hibakezelés, sok lehetőségünk nincs, mint hibajelző visszatérési értékeke használata, de ekkor is különítsük el a valós paramétereket a hibajelzésre használatos flagektől, ha lehet.
- Legyen egyértelmű specifikáció arról, hogy mik a paraméterek és kinek a felelősége ellenőrizni a bemenő paraméterek értelmességét.
- Alakítsunk ki értelmes adatszerkezeteket.
Nyelvi eszközök
- A
for()
ciklus tipikusan a „valahonnan valahová el akarok jutni, valamilyen lépésközzel” jellegű feladatokra való, awhile()
ciklus a „tekerjünk addig, amíg valami feltétel fennáll” jellegűekhez. - Ezért ha valóban ilyen feladatot oldunk meg, akkor a ciklus fejlécében ezek legyenek, a ciklus törzsében meg a tennivalók. Ne rakjuk át az iterátort a ciklustörzsbe, ne rakjuk a ciklustörzset be a fejlécbe stb.
- Lehetőleg ne legyen üres ciklustörzs, vagy ha mégis, akkor a ciklustörzs helyére tegyünk kommentet, hogy ordítson, hogy üres.
- Operátorok: a mellékhatásokkal járó műveleteknél ne arra törekedjünk, hogy mindenáron a mellékhatást használjuk ki főhatásnak.
- Ne keverjük a logikai és az aritmetikai kifejezéseket, hiába van rá mód.
Például: egy szám akkor osztható egy másikkal, ha az osztás
maradéka nulla. Vagyis:
- helyes:
if (szam%oszto==0)
... osztás maradéka, egyenlő, nullával. - (stilisztikailag) helytelen:
if (!(szam%oszto))
... a maradékot tagadom?! Kérdés: igaz-e az a kijelentés, hogy „Öt.”? Ez így nyilván nem is kijelentés...
- helyes:
- A pointer aritmetika sok feladatnál szép és hatékony, de ha az algoritmusunk nem arr
épül, kerüljük a használatát! A tömb az legyen tömb:
- Helyes:
t[10]
, olvashatóság szempontjából helytelen:*(t+10)
- Ugyanez három dimenzióban:
t[2][5][7]
vs.*(*(*(t+2)+5)+7)
.
- Helyes:
- Az inicializálatlan változó veszélyes hibaforrás,
de ez nem jelenti azt, hogy minden változónak kényszeresen kezdeti értéket kell adni
definiáláskor. Helyes:
double tomb[100], szum; szum=0; for (i=0; i<100; ++i) szum+=tomb[i];
Helytelen (stílus):double tomb[100], szum=0; /* * * sok sornyi kod... * */ /* hova lett a szum=0? valami más * összeghez adódnak pluszba hozzá? */ for (i=0; i<100; ++i) szum+=tomb[i];
Ha kicsit átírjuk a programot, és máshova másoljuk ezt a részt, végképp ki fog maradni az értékadás... Az erőltetett hamar-kezdeti-értékadás következménye szokott lenni az ilyen jellegű kód is:int oszto=2; /* <- inicializalt valtozo... de minek? */ /* kiirja a szamok primtenyezos felbontasat */ for (i=2; i<=10; i++) { printf("%d: ", i); szam=i; while (szam>1) { while (szam%oszto==0) { printf("%d ", oszto); szam/=oszto; } oszto++; } oszto=2; /* elso ranezesre: ez meg mit keres itt?! */ /* hiszen mar nem csinalunk vele semmit! */ /* most vegeztunk a primtenyezokkel! minek */ /* az osztonak ujra erteked adni akkor? */ printf("\n"); }
A lenti sokkal jobb, sőt így csak egyszer kell leírni:for (i=2; i<=10; i++) { printf("%d: ", i); oszto=2; /* <- ENNEK ITT A HELYE */ szam=i; while (szam>1) { while (szam%oszto==0) { printf("%d ", oszto); szam/=oszto; } oszto++; } printf("\n"); }
Pointerek, tömb átadása függvénynek, dinamikus memóriakezelés
- C-ben egy tömböt függvénynek átadva a tömb kezdőcíme adódik át,
azaz egy pointer. Ezt a pointert formálisan nem lehet megkülönböztetni
az egyetlen változóra mutató pointertől:
void intet_novel(int *i); int x=3; intet_novel(&x);
void tombot_kiir(int *i, int meret); int tomb[10]; tombot_kiir(tomb, 10);
void tombot_kiir(int i[], int meret); // jól olvasható!
- Ha egy függvény pointerrel tér vissza, a dokumentációjába kötelező berakni, hogy az dinamikusan foglalt memóriaterület vagy nem, továbbá hogy kinek a dolga felszabadítani azt, ha igen.
- A dinamikusan foglalt memóriát akkor foglaljuk, amikor először szükség van rá (ne előbb), és akkor szabadítsuk fel, amikor már nincs rá szükség (ne később)! Annyival is nehezebb megfeledkezni bármelyikről.
- A dinamikusan foglalt memóriaterületeket rendeljük hozzá gondolatban (és a dokumentációban) valakihez. Ez egyértelművé teszi, melyik programrésznél van a felszabadítás helye.
- Kövessük a
malloc()
-free()
, és azfopen()
-fclose()
logikáját! Ezt ismeri mindenki.
Fájl megnyitása, fájlművelet, bezárás:FILE *fp; fp=fopen("fajl.txt", "rt"); fprintf(fp, "Hello"); fclose(fp);
Saját halmaz típus:typedef struct Halmaz { int elemszam; int *dintomb; } Halmaz;
Halmaz *h; h=uj_halmaz(); halmaz_betesz(h, 5); halmaz_betesz(h, 9); halmaz_felszabadit(h);