Šta je nasumično programiranje i kako ga izbeći?

Iako može delovati primamljivo da se nešto obavi na brzinu bez udubljavanja i razumevanja problema i/li okruženja, posledice toga u programiranju mogu biti štetne i uzeti mnogo više vremena od promišljanja.

Nevenka Rangelov - 2. Avgust, 2018.

Nastavljamo serijal članaka baziranih na i inspirisanih klasičnim naslovom Pragmatični programer, koji smo započeli tekstom o procenjivanju rokova.

Prenosićemo glavne teme iz ove knjige (za koju vas svakako ohrabrujemo da je pročitate u celosti), uz primere iz prakse, bilo kao dopune glavnim temama ili kao zasebne članke. Pošaljite nam svoje kritike ili predloge za primere iz prakse ovde.

Ako ste gledali stare crno-bele ratne filmove možda se sećate nekih scena u kojima vojnik nailazi na čistinu za koju nije siguran da li je minsko polje ili je bezbedno da tuda prođe. Onda krene napred ispipavajući bajonetom zemlju oko sebe, i trza se očekujući eksploziju. Nakon što posle dosta koraka ništa ne eksplodira, on se ispravi i nastavi da korača normalno, da bi nakon nekoliko koraka bio raznesen u komade.

Njegovo “ispitivanje” nije imalo nikakav efekat osim što ga je dovelo do pogrešnog zaključka sa katastrofalnom posledicom.

I za developere se može reći da rade u nekoj vrsti “minskog polja” gde ih na svakom koraku čeka gomila zamki i prepreka. Kao i vojnik iz prethodne priče i developer treba da bude oprezan od izvlačenja lažnih zaključaka. Da bi izbegao te zamke, pre svega treba da izbegava “nasumično programiranje” – oslanjanje na sreću i slučajne uspehe. Programiranje bi, nasuprot tom pristupu, trebalo da bude promišljen i isplaniran proces.

Primer iz prakse

Pre petnaestak godina sam radio na jednom velikom projektu u Belgiji koji je imao za cilj da se proširi u Francusku, i u skladu sa time da doda verziju sajta na tom jeziku. Pregledao sam kod, video da je ideja da se celokupan sadržaj kopira iz jednog dataseta u drugi, rešio da refaktorišem kod tako da piše rekurzivnom metodom koja bi imala mnogo manje linija koda.

Nisam dublje razmišljao o problemu, probao sam lokalno sa malim skupom podataka, prebacio sa BE na FR i radilo je. Posle deploymenta sam otišao na sajt, izabrao drugi “jezik” da bude “FR”, nisam odabrao “BE” kao originalni i kliknuo sam “copy”.

Bukvalno sam stao na nagaznu minu jer je sada moj kod umesto da kopira, brisao i brisao i brisao… Dok nije obrisao sve iz baze vezano za prevode. To je uključilo i fajlove vezane za prevode, te smo završili sa obrisanom bazom i manjkom od nekih 16.000 fajlova.

Trebalo nam je (nisam popravljao sam svoju brljotinu) nešto manje od 24h da sve vratimo na staro, ali ovde sam lepo iskusio posledice nasumišnog progamiranja.

Podelio bih i jedan noviji primer, od pre desetak dana, nije baš programiranje ali jeste slično. “Žurio” sam da izvršim izmene na alatu koji koristimo interno za selekciju kandidata, dodao sam npm package za mysql i umesto da probam u virtuelnoj mašini, probao sam na lokalnom računaru koji je miljama daleko od serverskih podešavanjao, uradio deploy i napustio računar.

To je rezultiralo time da je se platforma obarala svakih nekoliko minuta pa kandidati koji su rešavali testove nisu imali sačuvane odgovore, a mi nismo sačuvali obraz.

Dejan Jaćimović, StuntCoders

Šta znači programirati nasumično?

Ukoliko programiramo nasumično, dešavaće nam se da testiramo neki proizvoljan deo koda i on će možda raditi. Međutim, ako nastavimo sa takvim programiranjem neko duže vreme, sve su šanse da će program u nekom trenutku prestati da radi, ali tada će biti skoro nemoguće da utvrdimo uzrok.

Zašto? Zato što nismo znali ni zašto je radio dok je radio. U takvoj situaciji je vrlo mala verovatnoća da ćemo znati gde je nastao problem kada program prestane da radi.

Zato ne treba da nas ponese lažno samopouzdanje kada nešto radi, a nismo sigurni kako i zbog čega. Nekada je lako pomešati srećnu slučajnost sa promišljenim planom.

Slučajnosti u implementaciji nastaju prosto jer je kod u nekom trenutku tako napisan, i može se desiti da se oslonite na nezabeleženu grešku ili granične uslove i nastavite da kodirate na osnovu toga. Tada se možete zavarati i pomisliti zašto rizikovati i dirati nešto što radi? To je loš pristup iz nekoliko razloga.

Za početak, postoji mogućnost da to što ste napravili uopšte ne radi već samo deluje tako.
Granični uslov na koji se oslanjate je možda samo slučajnost. U drugim okolnostima, kod će možda raditi drugačije, a nedokumentovano ponašanje će se možda promeniti sa sledećom verzijom biblioteke.
Takođe, dodatni i nepotrebni pozivi usporavaju vaš kod i povećavaju rizik od nastanka bagova.

Osnovni principi dobre modularizacije i skrivanja implementacije iza malih, dobro dokumentovanih interfejsa mogu biti od pomoći za kod koji pišete, a koji će drugi pozivati. Za funkcije koje sami pozivate – oslanjajte se na dokumentovano ponašanje, a ako iz nekog razloga to nije moguće, onda dobro zabeležite svoje pretpostavke.
Kod nasumičnog programiranja mogu nastati i nezgode u kontekstu – na koje se sve pretpostavke oslanjate koje nisu zagarantovane? Da svi vaši korisnici razumeju engleski, da za modul koji pišete nije neophodan grafički interfejs itd.

Ovakve slučajnosti i nepouzdane pretpostavke nas mogu zavarati na svim nivoima – od formiranja zahteva do testiranja. Testiranje je posebno podložno lažnim kauzalnostima i slučajnim ishodima i pretpostavkama.

Savet: ne pretpostavljaj, dokaži.

Ljudi na svim nivoima operišu sa mnogo pretpostavki u glavi – ali ove pretpostavke su retko zabeležene i često su u konfliktu između developera. Pretpostavke koji nisu zasnovane na dobro utvrđenim činjenicama su kamen spoticanja svih projekata.

Primer iz prakse

1) Pojavljuje se bag na produkciji i po automatizmu se logujem na produkcijski server da ga sredim što pre. Nakon izvesnog vremena nalazim šta je pravilo problem, sređujem i po automatizmu refaktorišem test koji prati sve to i pokrećem isti da vidim da li je sve ok.

I tu počinje kraj – test da bi se pokrenuo mora da isprazni bazu i napuni svojim podacima i nakon izvršenja opet isprazni bazu i kaže da li je uspelo ili ne. Tako je na produkciji u jednom momentu nestalo sve, pa se pojavili “test” podaci na live serveru, i onda nestalo sve opet. Backup je naravno spasao dan, a posle toga smo onemogućili da se testovi izvršavaju na produkcijskom serveru.

2) Kad biram paket koji ću koristi, uglavnom se oslanjam na ocene i komentare, što nekad zna da se ispostavi kao mač sa dve oštrice. Radio sam na nekom projektu gde smo koristili JWT za autentifikaciju. Šta se desilo? Pa oslonili smo se previše na njega i na njegov paket i kada se desila nenajavljena i nestandardna promena, nama je došlo do ozbiljnog pucanja u komunikaciji između određenih delova projekta. Sredili smo to, ali kako se ta komunikacija dešava u realnom vremenu dosta podataka smo izgubili.

Aco Gagić, GrowIT/PHP Srbija

Kako programirati promišljeno?

Greške u kodu želimo da uhvatimo što ranije u razvojnom ciklusu, a idealno bi bilo da zapravo i pravimo manje grešaka. Zato se nasuprot nasumičnom okrećemo promišljenom, isplaniranom programiranju. Evo nekoliko saveta koji čine suštinu promišljenog programiranja:

Savet za kraj: sledeći put kada deluje da nešto radi, ali nisi siguran/a zašto, postaraj se da nije u pitanju samo slučajnost.

Primer iz prakse: problem “svetih krava”

U praksi je povremeno moguće naići na problem svetih krava: u pitanju je deo softverskog sistema (klasa ili modul) koji je iz nekog razloga nedodirljiv kada su promene u pitanju. U velikom broju slučajeva, ovaj kod obavlja neku važnu funkciju, ali u kompaniji više nema nikoga ko je bio uključen u njegovo pisanje od samog početka. Ovo dovodi do toga da razvojni tim nema adekvatno razumevanje određenih delova sopstvenog sistema (što je nedopustivo za jedan razvojni tim) i počinje da izbegava bilo kakve prepravke na njima pravdajući se da “to radi uz pomoć magije“, ili “da je bolje da ga ne diramo“. Vremenom, pisanje novog koda koji se oslanja na ove funkcionalnosti postaje sve komplikovanije – zato jer se izbegava rešavanje problema tamo gde je nastao.

Međutim, nakon nekog vremena, ovakva situacija postaje neodrživa, a svaka promena sve skuplja – ovo je posebno izraženo ukoliko nedodirljivi kod obavlja neku kritičnu funkciju. Pre ili kasnije, tim će morati da se suoči sa problemom i rasplete ovo klupko; nevolja je u tome što je uvek bolje da se ovakva promena desi što je moguće ranije, jer u protivnom možete doći u situaciju da “rasklopite“ čitavu aplikaciju i upustite se u refactoring koji traje nedeljama (pa i mesecima). Generalno, što duže čekate na ovakvu promenu, to će ona više će boleti.

U praksi sam se sreo sa situacijom gde je ovaj nedodirljivi kod nastao potpuno spontano, kao deo regularnog razvoja aplikacije. Međutim, u jednom trenutku je ceo tim shvatio da moramo da nešto promenimo, jer su promene koje su se oslanjale na taj kod postale previše komplikovane. Situacija je postala još jasnija kada smo sagledali roadmap projekta i shvatili šta nas sve čeka: većina tih izmena bi se pretvorila u pravu agoniju sa trenutnim stanjem. Izmene nisu bile lake ni jednostavne – u našem slučaju, čitava monolitna aplikacija je pažljivo podeljena na dva mikroservisa. Ceo proces su obavila dva programera tokom perioda od desetak nedelja, sve vreme integrišući promene na kojima je radio ostatak tima sa svojim prepravkama. Sada, sa vremenske distance od skoro dve godine, vidimo da je ovo bila dobra odluka, jer je sistem koji smo dobili daleko lakši za razumevanje, održavanje, deployment i monitoring.

Neki od načina da utegnete vaš kod su redovni code review proces, korišćenje alata za statičku analizu koda, korišćenje konvencija za pisanje koda ali i redovno posvećivanje pažnje refactoring procesu u okviru regularnog razvojnog ciklusa. Ukoliko znate da je potrebno promeniti nešto i da će ta promena imati veliki uticaj na razumevanje koda i lakoću održavanja, onda isplanirajte taj task, procenite ga i učinite ga delom vašeg narednog sprinta. Istovremeno, veoma je važno da se mendžmentu predoči važnost ovakvih taskova, jer oni smanjuju ukupan tehnički dug projekta, a znatno olakšavaju život razvojnom timu. Kod ne sme biti nedodirljiv – i tim bi uvek morao da diskutuje o najboljem mogućem rešenju, ostavljajući ego po strani.

Miroslav Lazović, Bakson

Rubrika pragmatično programiranje je pod pokroviteljstvom StuntCodersa →