Teme: Web razvoj

Moj stil, tvoj stil – koji je bolji?

Skraćena veza: https://pedja.supurovic.net/veza/2868

Šta je stil? Ova reč potiče od starogrčke reči στύλος, koja je u početku predstavljala naročitu rezaljku kojom se pisalo po voštanim tablicama. Kasnije je taj naziv usledio za svaku vrstu pisaljke. A pisaljki ima raznih i tako i raznih stilova pisanja. Reč se vremenom rasprostranila na mnoge delatnosti pa i u mnoge jezike, dobijajući opštije značenje: način kako nešto pišemo, crtamo, stvaramo, gradimo i uopšte, kako nešto radimo.

Stil nije zakon i pravilo, stil je način kako nešto činimo, a načina može biti raznih. Da li je pogrešno pisati štampanim ili pisanim slovima, ćirilicom ili latinicom, serifnim ili neserifnim slovima? Da li je ispravan dorski ili jonski stil gradnje? Da li je ispravno Rembrantovo ili Pikasovo slikanja? Da li je ispravan karate ili džiudžica? Koji je stil oblačenja pogrešan? Nijedan stil nije pogrešan – svi oni ispunjavaju svoju funkciju, samo na različite načine.

Ne bez osnova, pitanje stila se vezuje i za pitanje ukusa, pa tako i poznatu izreku „ukusi su različiti“ možemo primeniti i na stil. Ne postoji dobar ili loš, ispravan ili pogrešan stil. Stil može biti manje ili više primeren, ali i to je opet uglavnom pitanje ukusa i praktičnosti.

Za pisanje na ovu temu me je podstakao Bluzmenov članak o stilu u programiranju u kome je on upravo upao u zamku određivanja ispravnog stila pisanja programskog koda i otišao daleko da svoj stil nameće kao ispravan. On je, jednostavno, neke svoje navike proglasio za pravila i pokušava da ih nametne kao takve. Nije mu uspelo čak ni da izbegne da nipodaštava one koji se ne koriste njegovim stilom.

Prvo sam mislio da ovo napišem kao komentar na njegov članak ali s obzirom na obim, uputnije je da to bude zaseban članak.

Bluzmen naveo nekoliko primera „pogrešnog stila“ i u većini slučajeva je promašio.

Grananje programa

Najočitiji je upravo prvi primer (kod Bluzmena označen kao (1.1). On tvrdi da je konstrukcija

if ($a == 1)
{
    return true;
}
else
{
    return false;
}

stilski pogrešna.  On tvrdi kako je else nepotreban u ovakvom kodu i nudi pojednostavljen kod

if ($a == 1)
{
    return true;
}
return false;

Mogu da se složim da primer sadrži stilsku grešku ali ne ovu o kojoj Bluzmen govori.

Njegovo, „ispravno“, rešenje je u stvari lošije od onog koje ispravlja. Greška u oba primera je u tome  što oni sadrže dve return komande. U proceduralnom (i objektnom programiranju kao nadogradnji) blok komandi ne treba da ima dva izlaza. Namerno kažem ne treba, a ne ne sme, jer će kod da radi i ako ih ima, ali to nije u skladu sa pravilima logike i čitkosti koda. Nije zabranjeno, ali je itekako nepreporučljivo pisati takav kod.

Lošije od dva izlaza je samo to da izlai budu u na različitom nivou dubine u strukturi koda, a Bluzmen loš kod menja upravo još lošijim – stavljajući izlaze na različite nivoe.

Dobar način je da blok komandi, kao što ima samo jedan ulaz na početku, tako ima i samo jedan izlaz koji se nalazi tamo gde i treba da bude (i gde ga svako očekuje) – na kraju bloka.

Bluzmen to previđa jer mu smeta else u uslovu, smatrajući ga viškom. Baš naprotiv, taj else značajno povećava čitkost koda. Svaki programer će već letimičnim pogledom na prvi primer imati predstavu kako taj kod radi, dok će u drugom slučaju morati malo da razmisli, pogotovo, ako taj kod nije ovako prost, sa jednom do dve komande pa je zato očigledan. Zamislite samo da u Bluzmenovom primeru umesto te jedne return komande i bloku uslova stoji duži niz. Da li bi onda bilo očigledno da ta grana u stvari prekida izvršavanje celog bloka? Ne bih rekao. A zamislite još da je struktura znatno komplikovanija, sa ugnježdenim uslovima i petljama i da negde u nekoj dubini stoji jedan return. Ko bi se u tome snašao a da se dobrano ne udubi u analizu koda?

Druga stvar koju Bluzmen previđa je da njegovo rešenje odstupa od logike koja je u programiranju neophodna.

Ako imate neki uslov, i ako je potrebno da se ako je uslov ispunjen obavi neka radnja, a ako nije, obavi druga radnja, onda i programska konstrukcija treba da prati tu logiku. Zato služi else. U if bloku se izvršava kod u slučaju da je uslov ispunjen, a u else  se izvršava kod u slučaju da uslov nije ispunjen. To je logično i čitko.

Bluzmenova konstrukcija radi potpuno drugačije. Ona izvršava jedan kod ako je uslov ispunjen, a kod koji treba da se izvrši ako uslov nije uspunjen je napisan kao da se izvršava u svakom slučaju (posle if bloka). Naravno, taj drugi kod neće biti izvršen ako je  uslov ispunjen, ali samo zato što se u if bloku nalazi komanda koja prekida rad – dakle potpuno neočekivano i nelogično na silu se prekida izvršavanje koda. Naopako!!!

Ako već govorimo o ispravnom stilu onda bi taj kod trebalo da izgleda ovako:

if ($a == 1)
{
    $result = true;
}
else
{
    $result = false;
}
return $result;

Ovakav kod poštuje i načela logike i čitkosti: blok ima jasan početak i kraj, a grananje uslovom se vrši na logičan i očekivan način.

Bluzmen opravdanje za svoj „pravilan“ stil nalazi upravo u nameri da ostvari veću čitkost koda i misli da je postiže tako što skraćuje kod, ali potuno zaboravlja da je logičnost koda vrlo bitna za njegovu čitkost – a on je logiku zanemario u potpunosti. On je logiku  je izvrnuo da bi postigao skraćenje koda.

Šta je postigao – da neko drugi ko analizira kod, mora da se bavi tom izvrnutom logikom umesto stvarnom logikom posla koji program treba da uradi.

Ako već pričamo o stilu i čitkosti, ja u stvari više volim da ovaj kod napišem ovako:

if ($a == 1) {
  $result = true;
} else {
  $result = false;
}
return $result;

Meni ovakvo pisanje sa vitičastim zagradama stopljenim u kod deluje preglednije ali, naravno, nemam nameru nekoga da ubeđujem da je takav stil bolji.

U sledećem primeru (1.2), Bluzmen pravi potpuno istu grešku, samo još očigledniju. Drugi primer mu je u stvari isti kao i prvi, samo ima više koda. On ponovo „pravilan način pisanja koda“ zasniva na neprikladnom izlasku u sred koda.

Bluzmen se vodi principom da na početku bloka proveri da li su svi neophodni uslovi ispunjeni, pa ako nisu da odmah prekine izvršavanje koda prisilnim izlaskom iz bloka. To, naravno, nije pogrešno, ali svakako to ne sme biti nametano kao ispravan stil.

Ako se držimo pravila čitkosti i logike, onda je normalno da se na početku koda proveri da li su svi preduslovi ispunjeni, ako jesu, da se uradi posao koji je potreban, a ako nisu da se taj problem na odgovarajući način reguliše, ali ne nasilnim izlaskom u sred bloka, već regularnim, na kraju bloka.

Ovaj njegov primer podstiče na još jednu dilemu: da li uslov treba da bude pozitivan ili negativan, ili da li treba u if proveravati da li nešto jeste ili nešto nije. Mislim da tu treba težiti logici posla, a if treba da sledi ono što je važnije, odnosno onome zbog čega je kod i napisan, a sporedne tokove staviti u drugi plan.

Primer:

if ([preduslov je ispunjen]) {
  [uradi glavni posao]
} else {
  [obradi izuzetak]
}
return;

Ovaj kod je čitak jer on vodi tok prema logici posla koji treba uraditi. Očekivano je da, pošto kod radi neki posao, do njega se dolazi kada su preduslovi ispunjeni. Zato if konstrukcija glavni posao stavlja u prvi plan, a izuzetak, da preduslovi nisu ispunjeni stavlja u drugi plan. Nekome ko čita ovakav kod, stvari su postavljene logično i očekivano te će mu biti lakše da se snađe.

Ako se uradi obrnuto:

if ([preduslov nije ispunjen]) {
  [obradi izuzetak]
} else {
  [uradi glavni posao]
}
return;

pravimo komplikaciju – izuzetak stavljamo  u prvi plan i otežavamo onome ko čita program, jer on sada mora da traži gde je važan deo koda u bloku. Naravno, ni ovakvo pisanje nije pogrešno. Program će raditi u svakom slučaju, a ponekada je i dobro ovako uraditi kada je važno naglastiti izuzetke. Ipak, ja bih se pre opredelio za prvi način, koji u prvi plan stavlja glavni posao.

Situacija može biti i obrnuta, ako uslov nije ispunjen tada treba da se radi glavni posao. I tada bih  if uslov prilagodio tome da se glavni posao stavlja u prvi plan.

Još jednom naglašavam – važno je izbegavati da se iz bloka izlazi na više mesta. Izlaz treba da bude jedan – onaj na kraju bloka.

Skraćeno grananje programa

Bluzmen je iz rukava izvukao još jednu zanimljivu konstrukciju u PHP-u  skraćenu strukturu uslovnog izraza (tačka 1.6 u njegovom članku).

Naime, zbog prirode poslova u kojima se PHP jezik koristi, a to je uglavnom generisanje HTML koda, vrlo je česta potreba da se HTML kod generiše u zavisnosti od nekog uslova. To naravno može da radi i klasična if komanda ali je ona zbog svoje strukture nezgrapna i čini kod koji je i inače sastavljen od  mešavine PHP i HTML još nečitkijim. Zato je uvedena konstrukcija uslovnog izraza koja ima oblik

[uslov] ? [izraz1] : [izraz2]

Izraz se sastoji od uslova, vrednosti koju izraz vraća ako je uslov ispunjen i vrednosti koju izraz vraća ako uslov nije ispunjen. Očigledno je ovo mnogo kraće i lakše za korišćenje. Pogledajte ovaj kod:

<div class="red">
<span class = "naziv">Status:</span>
<span class="vrednost"><?php (!empty($m_status) ? $m_status : "[nije definisano]"</span>
</div>

Dovoljno je „zbrčkan“ mešanjem PHP i HTML koda, a zamislite kako bi tek izgledalo da je umesto uslovnog izraza morao da se uglavi običan if.

Međutim, Bluzmen ovu konstrukciju preporučuje umesto klasičnog if  i kada za tim nema potrebe. Kada pišete čist PHP kod, onda treba da koristite klasičan uslov jer je to ono što je očekivano i prirodno i čitko. Ovaj skraćeni uslovni izraz koristite prvenstveno kada mešate PHP i HTML ili u nekim zbrčkanim izrazima kojima sklapate neki string od mnogo delova pa je jednostavnije da upotrebite skraćene uslovne izraze umesto da komplikujete sa if konstrukcijama.

Imenovanje promenljivih, funkcija, klasa…

Bluzmen se u svom primeru 1.3 bavi imenima promenljivih (on ih zove varijablama, što nije pogrešno ali i to ukazuje na njegov stil). Sasvim ispravno, zalaže se da se ne koriste kriptična imena od jednog ili par slova iz kojih nije logično čemu promenljive služe.

Davno je prošlo vreme programskih jezika u kojima su promenljive mogle da imaju samo jedno slovo u imenu, ili je dužina imena promenljive uticala na potrošnju memorije. Odavno nije tako i ne postoji nijedan razlog da promenljivama ne dajemo jasna imena.

Postoji u stvari jedan izuzetak. Zadržao se običaj da se u brojačkim petljama, za imena brojača koriste jednoslovna imena i to čak prvenstvo imaju slova i, n, m, j i k. Mnogim programerima je neobično da vide drugačije imenovane promenljive ovih brojača.

No, iako je imenovanje promenljivih vitalno za čitkost programa Bluzmen tome posvećuje neobično malo pažnje. Gde su praktični saveti kako to raditi?

Pre svega, imenovanje se ne odnosi samo na promenljive nego na sve elemente koda kojima programer sam može da nadene ime. To su i promenljive, i funcije, i tipovi i klase i imena datoteka modula i ko zna šta sve još jedan programski jezik može da sadrži.

Dobar je običaj uvesti određena pravila u način davanja imena. Ta pravila možemo uzeti od nekoga ko ih je već razradio, ali ih možemo i sami osmisliti. Po pravilu, svaki programski jezik ima već neku opštu preporuku oko imenovanja i uopšte stila pisanja koda, i dobro je pridržavati se toga, jer tako drugi programeri mogu lakše da prate vaš kod,  već naviknuti na usvojeni stil pisanja.

Ono što je važno, to je da se usvojenih pravila pridržavamo. Čak i ako odstupimo od uobičajenih pravila, uvodeći svoja, nijednom programeru neće biti teško da im se prilagodi ako su ona dosledno primenjivana.

Prvi problem sa kojim se srećemo to su imena koja se sastoje od više reči. Neko to rešava tako što umesto znaka razmaka stavlja donju crtu, neko tako što ne odvaja reči podvlakom, ali zato svako prvo slovo u reči piše kao veliko slovo. O korišćenju velikih i malih slova u nazivima treba posebno dobro razmisliti ako se koristi neki programski jezik koji pravi razliku između njih (na primer C, C# i slični jezici).

Kod imenovanja posebnih elemenata kao što su tipovi, klase, strukture, moduli, standardni objekti i slično, dobro je da u imenu koristite prefiks ili sufiks koji opisuje tip objekta. Tako ostavljate slobodan prostor da koristite ista imena za promenljive ali bez oznake tipa ali i izbegavate eventualne probleme ako elemente različitog tipa poželite da imenujete isto. Prefiks će obezbediti jedinstvenost imena.

Korišćenje promenljivih

Kada smo baš kod promenljivih evo nekih preporuka:

Pravite razliku između lokalnih promenljivih, parametara i globalnih promenljivih. Ja imam običaj da lokalnim promenljivim dajem prefiks m, parameterima p, a globalnim promenljivim g. Tako je u kodu lako prepoznati poreklo i viljivost neke promenljive.

Neretko,naići ćete i na davanje imena prema tipu. Tako programer naglašava kojeg tipa treba da bude podatak u promenljivoj. Ovo koristim, ali nisam dosledan. Uglavnom naznačavanje tipa upotrebim kada je to važno: recimo, kada se vrši transformacija podataka iz jednog tipa u drugi, imena promenljivih koja sadrže i oznaku tipa značajno povećavaju razumljivost koda.

Promenljive koristite u njihovom domenu, pre svega o tome vodite računa kada im menjate vrednost.

Sa lokalnom promenljivom možete raditi šta god želite jer je ona bitna samo u bloku u kome je i definisana – ona samo u tom bloku i postoji.

Parametar je bolje da ne koristite u bloku nego na početku bloka njegovu vrednost dodelite lokalnoj promenljivoj i nju dalje koristite. Iako je Bluzmen čvrsto protiv „neotrebnog“ korišćenja promenljivih (vidi tačku 1.7. u njegovom članku), brzo ćete se uveriti koliko je to praktično.

U toku razvoja, često ćete doći u situaciju da je potrebno da izmenite vrednost koju ste dobili kao parametar, ali i da morate sačuvati originalnu vrednost. Zar nije logičnije da parametar uvek zadržava svoju vrednost (iako je parametar u suštini lokalna promenljiva, on sadrži vrednost koju ste dobili spolja, izvan bloka koda), a da za potrebe koda imate lokalnu promenljivu kojoj na početku dodelite vrednost iz parametra a dalje možete sa njom da radite šta god želite?

Ako ne uradite tako, već parametar koristite u kodu, pa vam zatreba da sačuvate njegovu početnu vrednost, tada ćete svakako morati da napravite novu promenljivu, ali sa potpuno obrnutom logikom – ta nova promenljiva će da čuva vrednost jer ste sam parametar već upotrebili u kodu.

Naročito vodite računa o menjanju vrednosti globalnih promenljivih. U principu korišćenje globalnih promenljivih treba izbegavati jer one itekako mogu da naprave zabunu, ali ako imate jasna i čvrsta pravila onda one mogu mnogo da olakšaju posao.

Prvo pravilo treba da bude da kao globalne promenljive koristite samo one koje su zaista neophodne da budu globalne – obično su to neki globalni statusi aplikacije koji treba da budu dostupni u svakom trenutku u bilo kom delu programa.

Drugo, globalne promenljive tretirajte kao konstante. To znači, da im na početku programa dodelite vrednost i da tu vrednot dalje ne menjate. Samo u izuzetnim slučajevima, dozvolite menjanje vrednosti globalnih promenljivih.

Postoji još jedna stvar vezana za globalne promenljive. U nekim jezicima, kao što je na primer PHP, iako ste neku promenljivu definisali kao globalnu, opet u svakoj funkciji morate da je redeklarišete kao globalnu da biste je zaista mogli i koristiti. Ume da bude zamorno ako niz globalnih promenljivih koji vam je neophodan svaki put morate da redeklarišete. Zato je zgodno koristiti jednu globalnu promenljivu koja je složene strukture. U PHP-u možete napraviti globalnu promenljivu koja je u stvari asocijativni niz, pa u nju stavljati sve što vam je potrebno. Tako uvek treba da redeklarišete samo jednu promenljivu. I drugi programski jezici imaju slične konstrukcije.

Svaku promenljivu inicijalizujte. Neki programski jezici vam ne dozvoljavaju da koristite promenljivu, ako je pre toga ne definišpete i date joj početnu vrednost. Neki programski jezici su ležerniji i upravo u njima je vrlo važno da dosledno svakoj promenljivoj na početku date neku početnu vrednost inače ćete se vrlo brzo naći u nebranom grožđu, pošto će, umesto vas, sam kompajler ili interpreter da dodeli neku vrednost po svom nahođenju.

U funkcijama, dobar je običaj da definišete promenljivu koja će sadržavati rezultat obrade koji funkcija vraća. Dobro je da imate neko univerzalno ime za takvu promenljivu (na u PHP-u na primer za tu namenu uvek koristim ime $m_result, tako da mi je lako da se snađem prilikom analize koda – svaka funkcija ima tu promenljivu). Delphi (Paskal uopšte) vas na to čak i primorava, no to ne iznenađuje jer je on jedan kulturan i gospodski programski jezik koji će uvek stajati na posebnom mestu kakvu god lestvicu programskih jezika pravili.

Pomoćne promenljive

Nemojte izbegavati pomoćne promenljive ako će to povećati čitkost koda. Broj promenljivih ne utiče naročito na potrošnju resursa (osim ako zaista ne sadrže ogromnu količinu podataka) te nema razloga štedeti na njima, naročito zato što su pomoćne promenljive po pravilu lokalne. Iz nekog razloga Bluzmen se oštro protivi „nepotrebnom“ korišćenju lokalnih promenljivih (vidi njegovu tačku 1.6).

Ako kod radi neku komplikovanu obradu, slobodno tu obradu podelite na više komandi, čak i ako možete da ih izvedete kao jednu komandu (recimo neka komplikovana matematička formula). Međupromenljive će kod učiniti čitkijim, a i lakšim za testiranje.

Evo primera čitkosti na praktičnom zadatku. Radi se o zadatku koji se radi već u prvim koracima u učenju programiranja. Zadatak se zove zamena vrednosti dvema promenljivima. Prikazana su tri moguća rešenja.

– prvo rešenje koristi dve pomoćne promenljive

– drugo rešenje koristi jednu pomoćnu promenljivu

– treće rešenje ne koristi pomoćne promenljive uopšte

Rešenje 1

<?php

$a = 5;
$b = 3;

$c = $a;
$d = $b;

$a = $d;
$b = $c;

echo "a = $a; ";
echo "b = $b";

?>
Rešenje 2

<?php

$a = 5;
$b = 3;

$c = $a;

$a = $b;
$b = $c;

echo "a = $a; ";
echo "b = $b";

?>
Rešenje 3

<?php

$a = 5;
$b = 3;

$a = $a + $b;
$b = $a - $b;
$a = $a - $b;

echo "a = $a; ";
echo "b = $b";

?>

Koje rešenje je najčitkije? Prvo ima previše koda zbog manipulacije sa vrednostima previše promenljivih. Treće rešenje ima najmanje koda ali ćete razumeti samo ako ga analizirate. Drugo rešenje, sa jednom promenljivom, nije najkraće, ali je jasno i očigledno na prvi pogled – najčitkije je.

Zaključak: ne treba preterivati sa promenljivama, ali ne treba ni štedeti po svaku cenu.

Da li je promenljiva prazna?

U tački 1.4 u svom članku Bluzmen preporučuje korišćenje funcije empty() za proveru da li je promenljiva prazna. Opet, on to radi uz neobično uopštavanje i zanemarivanje vrlo bitnih stvari.

Njemu je razlog za korišćenje funkcije empty() način da se obrade situacije kada dobijemo vrednost promenljive koja nije ispravna (na primer nije definisana, sadrži NULL, ili sadrži neki drugi tip vrednosti). Ovakvo razmišljanje može da bude opasno i da vam napravi takvu zbrku da prestanete uopšte da koristite ovu funkciju.

Šta se može desiti ako umesto

if ($string == ''){}

upotrebite

if (empty($string)){}

Pre svega, empty() će sakriti da li je promenljiva uopšte definisana, da li zadrži NULL ili zaista sadrži prazan string. Kao što sam već rekao, za siguran kod neophodno je da sve promenljive budu definisane i da im se dodele ispravne početne vrednosti. Funkcija empty() će sakriti slučaj da ste propustili da ispravno definišete promenljivu.

Druga stvar, funkcija empty() ne razlikuje tipove. Ona proverava da li promenljiva ima neku vrednost koja se za taj tip smatra praznom vrednošću. Ako promenljiva ima vrednost, ali je ona pogrešnog tipa, funkcija empty() će i to da zanemari.

Evo primera: šta ako promenljiva $string sadrži vrednost 0 (nula, numerički). Funkcija empty() će vratiti true, što jeste tačno za numerički tip, ali vi tu očekujete string, a string ‘0’ nije prazan string. Interesantno, isto se dešava čak i ako promenljiva $string sadrži vrednost ‘0’ (cifra nula kao string). I tada će empty() vratiti true, što tek nije tačno, jer prazan string je prazan string, a ne ‘0’.

Evo probajte:

<?php
$a = "0";
echo "empty " . empty ($a) . "<br>";
echo "== '' " . ($a == "") . "<br>";
echo "isset " . isset ($a) . "<br>";
echo "vrednost: $a";
?>

Greške koje se na ovaj način mogu napraviti nisu očigledne i oduzimaju mnogo vremena. Zato treba voditi računa da sebi ne otežavamo njihovo pronalaženje.

Funkciju empty() treba koristiti upravo kada se proveravaju i postavljaju početne vrednosti promenljivih. Na primer ako funkcija dobija parametar, jedan od načina da se dobijena vrednost dovede u opseg dozvoljenih vrednosti je i da se proveri funkcijom empty().

Kad smo kod ove funkcije nikako ne treba zaboraviti da se napomene još jedna funkcija isset(). Ona pravi razliku između definisane i nedefinisane vrednosti. T0 je nešto što često treba da se proverava – ne da li je promenljiva prazna nego da li je promenljiva definisana.

Funkcija u uslovu petlje

U tački 1.5 Bluzmen se bavi korišćenjem funkcija u uslovu petlje i sa pravom skreće pažnju na korišćenje funkcija koje svaki put daju isti rezultat.

Dakle ne

while ($i < strlen ($string))
{
    // neki kod
    $i++;
}

već

$len = strlen ($string);
while ($i < $len)
{
    // neki kod
    $i++;
}

naravno, pod uslovom da se u petlji ne menja vrednost promenljive $string.

Međutim, Bluzmen ide dalje pa pokazuje da se kod može dodatno skratiti (on očigledno poistovećuje skraćenje koda sa povećanjem čitkosti) tako što će se koristiti brojač unazad, pa tako nema potrebe za pisanjem uslova u petlji:

$len = strlen ($string);
while ($len--)
{
    // neki kod
}

Ovakav kod je dobar da se neki nadobudni mladi programer (pritom ne mislim na Bluzmena, on je stara kajla) dokazuje u društvu kako je domišljat, ali sa čitkošću koda nema mnogo veze. Naprotiv, ovakvim kodom mi sakrivamo logiku programa i primoravamo onoga ko čita program da ga analizira da bi video šta se dešava. Ovakav kod, iako sasvim ispravan, nije uobičajen i ne sledi prirodnu logiku.

Brojanje unazad nije prirodna logika te ga koristite samo onda kada je to zaista neophodno, na primer, ako obrađujete indeksirani niz i iz njega izbacujete neke elemente, tada treba da idete do kraja niza ka početku da bi vaš brojač bio u skladu sa stvarnim rednim brojevima elemenata u nizu.

Ilustracije radi, pozivam vas da rešite sledeći zadatak. Napišite u PHP-u program koji računa faktorijel zadatog broja, ali pod jednim uslovom – ne smete koristiti nijednu uslovnu komandu u programu. Da stvar bude zanimljivija, onaj ko da najkraće rešenje (najmanji broj komandi) dobija na poklon majicu. Rešenje ne treba da sadrži korisnički interfejs nego da bude čist kod – dakle na početku se promenljivoj u kodu dodeli broj za koji treba izračunati faktorijel, a na kraju koda se sa echo ispiše rezultat. Ako se pojavi više ispravnih rešenja sa istim brojem komandi, nagrada ide onome ko je pre poslao rešenje. Rešenja nemojte slati kao komentare jer će biti odmah vidljiva, nego mi ih pošaljite kao privatnu poruku. Rok za slanje rešenja je tri dana, nakon koga ću sva rešenja objaviti ovde i objaviti ko je dobio nagradu.

Cilj ovog zadatka nije da se dokazujete kao programeri, na kraju krajeva ovo je trivijalan zadatak, već da se vidi koliko je takav kod nečitkiji i da je neophodno analizirati ga da bi se videlo šta radi.

Komentari u kodu

Bluzmen je poropustio da u svom članku o stilu dođe i do pisanja komentara, a oni su itekako odraz dobrog stila.

Komentari su, kratko rečeno, neophodni. Ma koliko vi pisali jasan i čitak kod, on može da bude dovoljno složen da nije očigledno šta i kako radi. Zato služe komentari, da tamo gde su stvari neočigledne skrenete pažnju i pomognete u razumevanju koda.

Komentare ne pišete samo za druge. Pišete ih i za sebe, jer nakon nekoliko meseci ili godina, ako morate da se vratite nekom svom kodu da ga prepravite, bićete u istoj poziciji kao i neko ko ga prvi put vidi – moraćete da ga analizirate da biste videli šta kod radi.

Zlatno pravilo pisanja komentara je da ih treba pisati samo u neophodnom obimu – dakle ni premalo ni previše. Premalo komentara ne pomaže dovoljno, a previše komentara odmaže jer zatrpava kod. Naročito nemojte pisati komentar koji objašnjava nešto što je više nego očigledno.

Trudite se da u svaku datoteku na vrhu kao komentar upišete neke osnovne podatke: čemu kod u njoj služi, šta je neophodno da bi kod mogao da radi, ako zavisi od nekih biblioteka, kada je započeto pisanje koda, kada je urađena poslednja izmena, a dobro je i da postoji deo gde se upisuju napomene o svim bitnijim izmenama u kodu, kada su urađene i ko ih je uradio.

Kada uvodite globalnu promenljivu dobro je da uz nju napišete i komentar kojim objašnjavate njenu namenu.

Ako definišete klasu, napišite čemu ona služi.

Kod definisanja funkcije, obavezno opišite njenu namenu, i opišite parametre koji joj se prosleđuju sa tipovima i dozvoljenim vrednostima, kao i opis vrednosti koju funkcija vraća.

U samom kodu, na mestu gde znate da može biti nejasno, a naročito tamo gde ste i sami proveli dosta vremena mozgajući kako da nešto rešite, napišite u kratkim crtama šta i kako ste uradili da ne morate ponovo da ceo problem analizirate i rešavate iz početka.

Kad god u kodu radite nešto neuobičajeno, kao što su, na primer, ovi Bluzmenove nasilni izlasci u sred bloka komandi, ili brojačke petlje koje broje unazad i slično, stavite u komentaru napomenu, makar da bi prilikom letimičnog pregleda koda to mesto bilo uočljivije.

I prazan red može da bude komentar. Neke celine u kodu odvojte praznim redom, isto kao i što u običnom tekstu odvajate pasuse. To može značajno da poveća čitkost čak i ako ta celina nema zaseban komentar. Dobri kandidati za to su grane u kodu, petlje, složeni matematički izrazi koje ste zbog čitkosti podelili u više celina i slično.

Redosled izvršavanja operacija u izrazima

Ovo je još jedna tema koju je Bluzmen preskočio. Svako je još u osnovnoj školi naučio da se u matematičkom izrazu prvo vrši množenje i deljenje a tek zatim sabiranje i oduzimanje. Kada učite neki programski jezik, obavezno i to dođe na dnevni red.

Neki programeri se oslanjaju na redosled izvršavanja operacija u izrazima. Neko će napisati izraz a / b + c / d i dobiti tačan rezultat, a ja ću uvek napisati (a / b) + (c / d) i dobiti takođe tačan rezultat.

Ne stavljam ja te zagrade jer ne verujem programskom jeziku, nego zato što mi je tako kod znatno čitkiji. Ako je izraz komplikovan i dug, i pogotovo ako se nalazi recimo u uslovu, onda ću dodatno podeliti celine u njemu po redovima, uraditi uvlačenje po nivoima zagrada i tako učiniti da mi on bude jasan i čitak.

Isto preporučujem i vama.

Nazubljivanje koda

Uvlačenje redova po dubini u strukturi koda je toliko postalo normalno i uobičajeno da kada nekoga pitate šta je važno za čitkost koda, to će lako da zaboravi da navede.

Nazubljivanje se vrši jednotavno i većina programskih uređivača tekst to rade automatski. Kako ušete u novi nivo dubine koda tako se ceo tekst ko jipripada tom nivou uvlaši malo udesno. Sve komande koje su u istom bloku, na istom nivou su podjednako uvučene. Tako se jasno vidi koji kod pripada kom bloku, što je izuzetno bitno kada je kod komplikvan i ima mnogo grananja, i petlji.

Kod nazubljivanja je dobro korsititi znakove razmaka umesto tabova i ako se u praksi podjednako sreću oba pristupa. Tabovi su zgodni jer svako može dapodesi „širinu“ uvlake kako mu odgovara, ali zato znakovi razmaka imaju prednost baš u tome što je širina konstantna nezavisno u kom programu se kod pregleda. To je važno kada se kod prikazuje na način kada je teško ili nemoguće kontrolisati širinu taba (recimo na vebu), pogotvo što su često širine tabova podrazumevano podešene na neke pretarano velike vrednosti pa to u stvari kod učini nečitkim jer postane previše uvučen.

Koliko uvlačenje je dovoljno? Ima raznih ukusa a ja sam našao da mi najviše odgovara širina od dva znaka. to ej dovoljno da e uvlačenje lepo vidi a ako je struktura komplikovana tekst ne beži previše u desno.

Naravoučenije

Ne bih voleo da neko ovaj moj članak protumači kao nekakav napad na Bluzmena. Naprotiv, njega cenim i kao programera a i na drugim poljima.

Svojim člankom me je samo podstakao da se pozabavim temom koja je bitna za svakog, naročito svežeg i neiskusnog programera. Navika je se teško osloboditi, a ako su one loše, to ume da bude problem. Bluzmen je u svom članku potencirao kao ispravne neke načine pisanja koda koji su daleko od dobrog, a u stvari su rezultat njegovih loših navika. Samo zato što je on na to navikao, čini mu se da je to i dobro. A nije tako. Pri tom, Nisam nastojao da dokazujem da Bluzmenov stil ne valja, već da se on ne može prihvatiti kao pravilo i jedini ispravan način pisanja programa.

Pokušao sam koristeći njegove primere kao osnov da pokažem šta zaista jeste čitko, jasno i pregledno pisanje koda. Sigurno je da i ja imam neke svoje navike koje nudim kao „bolji“ način, čak i ako toga nisam ni svestan. Verovatno sam i ja, kao i Bluzmen, u svom članku preskočio da se osvrnem na neka pitanja na ovu temu. Ali zato su tu čitaoci, pa će i oni reagovati na moj članak, kao što sam ja na Bluzmenov.

Još jednom ću da naglasim da je stil, kao i ukus, stvar ukusa. Neko više voli ovako, neko onako. Krajnji rezultat je isti – program radi svoj posao. Pitanje stila se poteže tek ako više programera radi na istom kodu, jer oni treba da razumeju ono što je neko drugi radio da bi mogli da nastave da rade na tom kodu.

Ako već mora da se pravi poređenje koji je stil bolji, upravo to treba da bude kriterijum – koliko neki stil pisanja koda doprinosi da se drugi programer brzo i lako upozna sa tim kodom, da ga razume i da može da nastavi da radi na njemu.

Način pisanja koda je nešto što programera prati u celoj njegovoj karijeri. Napredovanjem, menjanjem posla, učešćem u raznim timovima, programer se sreće sa različitim stilovima i mora da ih se pridržava. Negde će mu njegov lični stil tolerisati, a negde neće. Ipak, najlakše je prihvatiti stil programiranja koji je čitak, lak za razumevanje i praćenje i koji prati logiku kojom se problemi i inače rešavaju. Sa takvim stilom će vas svugde lakše prihvatiti.

Svoj stil izgradite tako što ćete proučiti preporuke za programski jezik koji koristite i stilove kojim pišu drugi programeri. Prihvatite ono što je uobičajeno i ono što vam se čini svrsishodnim. Da li je vaš stil dobar utvrdićete kada se prvi put budete sreli sa svojim kodom nakon dužeg vremena. Koliko je stil programiranja dobar, toliko ćete se lako vratiti tom programskom kodu.

I na kraju, nemojte biti previše konzervativni. Ne inistirajte na stilu po svaku cenu. Nekada je dobro i odstupiti od njega, ako će to u konkretnom slučaju pojednostaviti kod ili ga učiniti čitkijim. Naročito nemojte preterano insistirati na svojim navikama ako radite u timovima. Obično u timovima postoje načelna pravila u pisanju koda ali se na njima ne insistira po svaku cenu. Zato, nemojte ni vi.

21 comments to Moj stil, tvoj stil – koji je bolji?

  • Primaš li rešenja i u drugim programskim jezicima (Python)?

  • Zbog poređenja, izabrao sam samo jednu platformu, onu koja je najrasprostranjenija – PHP. Za ovu svrhu ne mogu se porediti rešenja u dva različita programska jezika. A i ne znam Python.

  • bluesman

    Molim te pre nego što bilo šta odgovorim, ispravi moj nickname i članku, svuda je pogrešno napisan.

  • Članak je napisan ćirilicom. Tvoj nadimak na engleskom je preslovljen na ćirilicu. Latinična verzija članka se dobija preslovljavanjem sa ćirilice na latinicu.

  • bluesman

    I u latiničnoj verziji je pogrešno, da sam hteo takav nick kako me ti krstiš – sam bih ga izabrao takvog. Ja to kapiram kao omalovažavanje, ok, ako nećeš da promeniš – onda ništa … ignorišem te.

  • Tvoja volja. Nadimak ti je transkribovan po pravilima transkripcije, iz engleskog jezika u srpski. Ako ti to smeta, piši Matici Srpskoj, možda izmene pravopis da ti učine.

  • Ivan

    Slazem se sa tvojim tekstom, iako nisam citao bluesman-ov.

    Pre svega, slazem se da mnogi programeri upadaju u zamku prerane optimizacije koda, koja je po staroj izreci: izvor svog zla. Mada, u nekim primerima. Ima smisla naglo prekinuti dalje izvrsavanje funkcije i procedure, kako bi se ustedelo na procesorskim ciklusima i vremenu izvrsavanja koda.

    Naravno, pre nego sto iko i odluci na takav potez. Potrebno je da, posto zavrsi pisanje koda i utvrdi da mu aplikacija radi dobro ali sporo, odluci da je optimizuje! Uradi dobru analizu. Po mogucstvu, koristiti neki profiler za te stvari.

    Najbolji stil je onaj koji je najcitkiji i najlogicniji. Optimizacijom koda dolazimo do onog koda koji je nezgrapan i tesko citljiv, ali to je nesto sto prihvatamo kako bismo ustedeli koji ciklus vise. Naravno, tek na kraju development process-a i to samo onde gde je neophodno! Uz dosta komentara i veoma oprezno.

  • Svakako, u programiranju je dozvoljeno sve što radi posao. Ako će se dobiti bolji rezultat (brzina, manja potrošnja resursa i slično) onda se svakako ne treba slepo držati uobičajenog čitkog stila.

  • Nikola

    Peđa, interesuje me kako bi izgledalo rešenje zadatka kog si postavio u PHPu, jer je baš trivijalno u Pythonu i Javi, pa rekoh da ne propuštam nešto? Hvala unapred.

    • Jeste trivijalno, upravo zato sam to i postavio kao zadatak. Dobio sam samo jedno rešenje, ali nikako da stignem da to pustim na blog. Potrudiću se da bude što pre.

  • Evo i rezultata nagradne igre. Borko Kucin, koji je prvi i jedini poslao rešenje osvojo je simboličnu nagradu u obliku majice. Ona će mu biti poslata poštom. Čestitam Borku i zahvaljujem mu se na trudu.

    Naravno, zadatak je bio trivijalan, pa nisam ni očekivao da će neko poslati pogrešno rešenje, mada sam, s obzirom na čitanost ovog članka očekivao da će se još neko javiti sa svojim rešenjem. Bilo bi interesantno uporediti ih jer ima više načina da se uradi ovaj zadatak.

    No, da se vratim na temu. Borkovo rešenj ću iskoristiti da pokažem da kraći kod ne mora da bude i čitkiji.

    Evo njegovog rešenja (primer je izračunavanje faktorijela od broja četiri):

    <?php
    
    $n = 4;
    $result = 1;
    while ($n--) {
      $result=$result*($n+1);
    }
    echo $result;
    
    ?>
    

    Da biste razumeli kako ovaj program radi morate ga malo analizirati, jer način njegovog rada niej očigledan zato što ne prati uobičajenu definiciju faktorijela.

    Eko kako izgleda školsko rešenje:

    <?php
    
    $n = 4;
    $result = 1;
    if ($n > 1) {
      for ($i = 2; $i <= $n; $i++) {
        $result = $result * ($i);
      }
    }
    echo $result;
    
    ?>
    

    Ono jeste duže, ali prati definiciju faktorijela tako da je program očigledan i lako je razumeti kako radi.

    Zbog jednostavnosti, oba programa su napisana sa pretpostavkom da je ulazna vrednost pozitivan ceo broj tako da se ne vrši provera.

  • Nikola

    Peđa,

    Koliko vidim, obe verzije koriste neku vrstu kondicionala, bilo da je to u while petlji ili eksplicitni if statement, što ceo zadatak pravi baš trivijalnim. U tom slučaju imate i kraću verziju (PHP 5.3)

    <?php
    $factorial = function($n) use (&$factorial) {
    return $n ? $n * $factorial($n – 1) : 1;
    };
    echo $factorial($_SERVER[1]);

    dok verzija bez bilo kakvog kondicionala prelazi 100 LOC.

  • Ne možemo petlju smatrati baš isključivo uslovnim korakom. Ipak je to petlja i njen sastavni deo je uslov za izlaz iz petlje.

    Ne znam šta je 100 LOC.

    Trivijalnost ovde nije poenta. Poenta je u tome da kako se „unapređuje“ kod, tako on postaje nepregledniji.

    Borkov primer to dovoljno dobro ilustruje, tvoj još bolje, a verujem da bi neko mogao da smisli još zapetljanije rešenje i time samo još bolje ilustrovao ono što je i bilo cilj da se pokaže: da izbegavanje klasičnog grananja u kodu samo zato da bi ono bilo izbegnuto, nema neku svrhu, već samo pravi kod nečitljivijim, te je to uopšte uzevši, loša praksa, a ne jedini ispravan način pisanja programa, kako je to Bluzmen u svom članku predstavio.

  • Nikola

    LOC (LoC) – lines of code. Slažem se da je sastavni deo svake petlje izlaz iz petlje, ali školski kad se kaže uradi nešto bez kondicionala, to znači uradi bez bilo čega što implicira kondicional – to znači i bez petlji 🙂 Drugim rečima, jednostavan inkrementer možete napisati i ovako (PHP 5.3):

    <?php
    $n=0;
    for(;;){
    if($n == 10){goto a;}
    $n++;
    }
    a:
    echo $n;

    gde sada eksplicitno uključuje if statement (kod gore NIKAKO ne koristiti). Sve ovo na stranu, u srednjim i većim softverskim firmama, jedino merilo dal je kod dobar ili nije jeste pozitivan ishod code review-a. Kad si junior software developer, skoro sigurno je da code review nećeš proći iz prve 🙂 Tu je bitna čitljivost (u smislu brzog skeniranja), komentari, samodokumentovanost, dobre prakse (npr. ne nazivaj promenljive kriptičnim imenima) i još ogroman broj drugih stvari. U PHP-u koji ima GC „ugrađen“ u sebe obično nije bitno da li uvodiš još 10 lokalnih promenljivih, pošto će one da budu sakupljene kad isteknu. Zlatno pravilo: ako imaš performance problem, uvek je lakše refaktorovati i optimizovati čitljiv kod nego kriptičan; da li je nešto kriptično ili nije obično se određuje dizajn guidelineovima te firme. Izmedju školskog i kriptičnog koda biram školski u svako doba, pod uslovom da je taj neko dobro naučio tu školu 🙂

  • Petlja ne može da funkcioniše ako ne postoji uslov za izlaz iz nje tako da je bespredmetno insistirati na tome da petlja ne sme sadržavati uslov.

    Tako i ovaj tvoj primer sa „praznim“ for opet sadrži uslov, iako se on ne vidi i ne koristi pa si od njega napravio zamenu za goto. U principu ti on nije ni treba, jer ako si se već okrenuo kao goto komandi, onda for nema svrhu.

    Na kraju, i ovo tvoje je dobar primer, jer pokazuje kako stvari počinju da postaju nečitkije kako se odstupa od školskog pristupa.

  • Nikola

    Moguće je da sam napravio zbrku sa korišćenjem reči petlja 🙂 Ne insistiram da petlja (petlja u širem smislu – for, while) ne sme sadržati uslov, već da svaka petlja koja sadrži uslov jeste kondicionalni blok koda (verujem da je ova rečenica redudantna :)). U akademskim primenama (kada kažeš školski), kada se kaže bez kondicionala, misli se bez if-else konstrukta, komparatora, break iteratora, short circuit conditiona kao i mase drugih language-specific konstrukta, osim ako nije drugačije naznačeno. Takođe, u kodu gore, umesto goto a; za ispis sam mogao da iskoristim i echo $n; exit(); Potpuno je isto da li ćeš koristiti while(true) pa break-out uslov ili while(neki uslov) pa petlja prestaje kada uslov prestane da važi. Dalje, formalno, for i while petlje ne mogu da postoje bez uslova, ali taj uslov može da bude nepromenljiv, kao što je while(true) petlja, te iz petlje nema izlaza. Takođe, evo petlje (u užem smislu) bez uslova (PHP 5.3):

    <?php
    $u = 0; // Školski 🙂
    h:
    $u++;
    echo $u.PHP_EOL;
    goto h;

    nije preterano korisna petlja, al je petlja. Takođe, biti školski jeste biti maksimalno egzaktan, barem kada je postavka zadatka u pitanju 🙂 [Disclaimer: ne tvrdim da je moj kod ili pristup školski (čak suprotno) :)]

    • Upravo sam i mislio na takvo korišćenje goto naredbe, koje isključuje potrebu za for naredbom.

      Što se tiče diskusije u vezi značenja izraza „uslov“ i petlji, ne vidim svrhu tome. Sasvim je jasno da je uslov u petlji neophodan deo petlje i ne može se tako rigidno posmatrati kao uslovni korak jer je to samo deo petlje.

      Ako hoćeš da kažeš da ti nisi poslao rešenje zato što si mislio da ne smeš koristiti petlju, trebalo je samo da pitaš, mada mislim da je bilo sasvim očigledno da je nekakva petlja u rešenju zadatka neophodna, a pogotovo kada se uzme u obzir da je rečeno da se radi o trivijalnom zadatku, to bi trebalo da bude sasvim jasno.

      No, i pored toga, tvoji primeri su veoma korisni, jer upućuju na moguće pristupe u programiranju koji daju rešenja, ali koji nikako ne mogu da se smatraju čitkim, što i jeste bio cilj članka pa i cele diskusije.

  • Nikola

    Ne treba mi majica ako me to pitaš 🙂 U raspravu sam ušao iz čisto sebičnog razloga moram priznati, a to nije majica 🙂 Hteo sam da saznam kako u jeziku kao što je PHP (koji ne znam, moj prof. background je C i Java btw.), while petlja se ne smatra >i< kondicionalnom funkcijom, bez obzira što joj to nije primarna namena. Realno, ti možeš da radiš poređenje sa while funkcijom, bez obzira koliko je to besmisleno i to možeš da uradiš u istom kontekstu u kom bi koristio if statement. Razumem da tema ovog blogposta nije ovo o čemu ja drvim sada, ali oko osnovnih konstrukta i funkcija ne sme biti pregovora: treba tačno znati kako koji izgleda i šta radi. Ako bi ti neko na ispitu iz uvoda u programiranje (ICSP) postavio zadatak napravi faktorijel BEZ bilo kakvih kondicionala, ne bi prošao dobro ako bi koristio for ili while loop 🙂 Ako si raspoložen, pokušaj da uradiš ovaj zadatak tako.
    Btw, na datavoyage.com imaš iframe na dnu strane koji nije ubijen, pretpostavljam da je to ostatak one zaraze od pre.

  • Dok za while i mogu donekle da prihvatim da se tumači kao uslovni korak, za for ne mogu. Ona je po definiciji brojačka petlja i u njenim izvornom smislu uslov i ne postoji – zadaje se početna i krajnja vrednost brojača i korak brojanja. U ceolikim jezicima je njena sintaksa proširena tako da sadrži i uslov tako da joj je i funkcionalnost proširena. Ipak, ja je i dalje gledam kao brojačku petlju.

    I eto, pao bih na ispitu. 🙂

    Hvala za info za iframe. Krenuo je novi talas napada. Očigledno se nisam oslobodio pošasti.

  • Boban Jovanović

    Stil koji godinama koristim, a koji kombinuje kompaktnost i čitljivost koda je oblika:

    if ($a == 1) {
        $result = true;
    }
    else {
        $result = false;
    }
    return $result;
    

    Pravilo je da svaki blok počinje velikom zagradom u istom redu sa komandom, a završava se u posebnom redu, na početku.

Leave a Reply to Nikola

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Popunite izraz tako da bude tačan: *