2015-07-15

To goto or not to goto

Ovo nije post o tome da li da idem ili ne, jeste da je i potencijalni odlazak vezan za programiranje, ali ovo je priča o goto, omrženoj komandi. Svi koji se bave programiranjem su u ranoj fazi (osim ako nisu kao neki, započeli fortranom kao prvim jezikom) naučili da se goto ne koristi. Da samo loši programeri koriste goto. Da je to fuj-no-no-kakano i ko samo i pomisli na goto oćelaviće prerano, oslepeće, zavrsiće kao profesor informatike, ... .

Tako sam se i ja klonio goto komande, izbegavao je u širokom luku. Uvek sam bio svestan da postoji, nekada me je continue ili break podsećalo na goto, ali njih niko nije toliko ispljuvao :). I tako jednog dana sam radio nešto što se često radi kada se programira za kernel. Napisao "lep" kod (bez goto), ali ček, previše je, da kažemo, nečitljiv. Hm, ali ako bi iskoristio goto ... . Ne, ne, ne i ne. Kroz glavu mi prolazi ono ćelavenje, ćorav već jesam, ali profesor informatike ... . Imao sam strašnu dilemu, nisam znao šta da radim. Sa jedne strane "pravilo" kojim su me vaspitali, sa druge strane čitljivost i praktičnost koda.

Bacio sam se dosta na istraživanje, čitao pametnice (koje su uglavnom smislili i pisali Linux kernel) koje savetuju da je ok koristiti tu i tamo goto zarad čitljivosti koda. I finalno sam presudio. Na ovom mestu i u ovoj situaciji goto je najbolje rešenje. Ovo sada nije da Vam se pravdam, samo nekada se treba zapitati da li su sve dogme dobre (reče čovek koji uporno radi u c-u).

Da izložimo slučaj, predstaviću dva koda napisana bez upotrebe goto, i jedan napisan sa goto, pa sami presudite šta je bolje, ili još bolje dajte predlog novog koda bez goto. Zadatak je bio da se odradi niz od recimo 5 (u mom realnom životu je bilo 12) operacija. Ali ako neka od njih ne uspe, potrebno je opozvati prethodno pozvane. Mozda zvuci složeno, ali da evo pojednostavimo: pozivamo doA(), pa doB(), ali ako nije uspeo doB(), potrebno je pozvati undoA(). I tako do naših 5 poziva.

response_t work(void)
{
  if(doA() == fail)
    return fail;
  if(doB() == fail)
  {
    undoA();
    return fail;
  }
  if(doC() == fail)
  {
    undoB();
    undoA();
    return fail;
  }
  if(doD() == fail)
  {
    undoC();
    undoB();
    undoA();
    return fail;
  }
  if(doE() == fail)
  {
    undoD();
    undoC();
    undoB();
    undoA();
    return fail;
  }
  return done;
}
Ovo smo baš hteli da napišemo, ali čemu ovoliko ponavljanja, ajde da to ugnjezdimo u pitalice.
response_t work(void)
{
  if(doA() != fail)
  {
    if(doB() != fail)
    {
      if(doC() != fail)
      {
        if(doD() != fail)
        {
          if(doE() != fail)
            return done;
          undoD();
        }
        undoC();
      }
      undoB();
    }
    undoA();
  }
}
return fail;
Čisto da jos jednom podsetim, moj realni, životni primer je imao 12 operacija, ovo je samo sa 5 i već vidite da nije jednostavno. Ajde da pokušamo koristeci goto:
response_t work(void)
{
  if(doA() == fail)
    goto failA;
  if(doB() == fail)
    goto failB;
  if(doC() == fail)
    goto failC;
  if(doD() == fail)
    goto failD;
  if(doE() == fail)
    goto failE;
  return done;
failE:
  undoD();
failD:
  undoC();
failC:
  undoB();
failB:
  undoA();
failA:
  return fail;
}
Šta Vam je čitljivije i šta zahteva najmanji mentalni napor da shvatite o čemu se radi. Naravno da se generiše uvek "isti" mašinski kod, ali baš me zanima šta bi ste Vi izabrali. goto ili ne goto?

P.S. Naravno u ovim primerima doX funkcije nemaju argumente i vraćaju uvek isti tip, ali u praksi su svakojake, sa različitim argumentima i različitim povratnim vrednostima :).

No comments:

Post a Comment