Atenţie! Aceasta este o versiune veche a paginii, scrisă la 2007-10-27 16:29:03.
Revizia anterioară   Revizia următoare  

Infoarena3

Pe scurt, codul din spatele site-ului infoarena2 este intr-o stare foarte proasta si trebuie rescris. Pagina aceasta incearca sa explice in detalii problemele din cod, si incearca sa demonstreze ca rescrierea soft-ului este cel mai bun mod de a avansa site-ul. Vom tine o sedinta a asociatiei in care vom hotari exact ce facem. Pagina este momentan incompleta.

Putina istorie

Site-ul infoarena1 a fost scris acum vreo 4-5 ani de Cristi pentru a fi prezentat la InfoEducatie, un concurs de soft de la Galaciuc. Site-ul era foarte impresionant si a castigat concursul 2 ani la rand. Mai mult, site-ul era atat de bun incat a intrat in "productie" si a reusit sa adune o comunitate in jurul lui. Comunitatea a produs un numar impresionant de probleme si concursuri si infoarena un loc de adunare pentru olimpicii romani.

Mult timp soft-ul din spatele site-ului a ramas aproape identic cu ce a venit Cristi la Galaciuc; s-au facut doar niste fix-uri absolut necesare. Am incercat sa punem pe picioare un site de development si sa bagam codul in subversion dar nimeni nu s-a atins de cod. Au crescut niste lucruri pe langa infoarena1; un "portal editabil" de informatica si o integrare urata cu MediaWiki. Eventual am ajuns la concluzia ca nu se poate face nimic in infoarena1 si trebuie rescris de la 0.

In vara lui 2006 ne-am adunat 5-6 flacai, ne-am dus in munti si ne-am incuiat in casa ca sa facem infoarena2 (multumim din nou lui Vali pentru logistica). Intre noi il aveam doar pe Cristi cu o oarecare experienta in web-development (php/mysql), in rest eram in cea mai mare parte incepatori. Site-ul nu a fost terminat acolo in munti si a fost nevoie de un numar de "coding camp"-uri pentru a pune site-ul pe picioare. Eventual site-ul a ajuns la nivelul de functionalitate din infoarena1, si a fost lansat spre sfarsitul lui 2006. Parti de genul editarea de runde si probleme (o cerinta critica) au fost finalizate doar in 2007.

Softul infoarena2 este evident mult mai bun decat infoarena1. Asta se poate vedea cel mai bine in calitatea si dimensiunile site-ului. Numarul de probleme din arhiva este mai mult decat dublu, si a devenit posibil pentru comunitatea site-ului sa ne ajute mult mai mult.

Din pacate dezvoltarea la site-ul infoarena2 a stagnat de multa vreme. Au mai fost tentative de CodingCamp-uri si chiar s-au adaugat feature-uri noi, dar progresul este mult prea incet. Exista multe cauze pentru aceasta stagnare, dar eu (Leonard) consider ca problema principala este este calitatea codului. Infoarena2 este un soft complicat si incurcat in care este mult prea dificil sa faci modificari, iar asta scade motivatia developerilor.

Putem sa incercam niste "boost-uri" de motivare prin CodingCamp-uri dar scopul este sa avem un grup de indivizi care lucreaza de acasa in timpul lor liber. Pentru asta trebuie ca programarea sa fie usoara si distractiva, iar in infoarena2 nu este cazul. Soft-ul nostru este mult mai "frumos" decat multe produse comerciale, dar la infoarena2 nu exista motivatia financiara (si nici nu vrem sa existe).

Problemele infoarena2

Pe parcursul dezvoltarii infoarena2 noi (Cristi, Leonard, Mircea, Vali, etc...) am facut un numar de greseli majore la care acum simtim efectele. Daca am incepe din nou programarea la proiectul infoarena3 nu am sa face din nou aceleasi greseli si rezultatul ar fi mult mai bun. Daca stim care au fost gresile si cum sa le evitam nu vom ajunge din nou in aceasi situatie.

Poate parea trist ca aruncam la gunoi aproape un an de efort, dar nu este cazul. Vom pastra tot continului site-ului, care valoreaza enorm (si asta tine de fapt infoarena.ro in viata). Si vom pastra lectiile infoarena2, care sunt mult mai valoroase decat codul php. Daca am fi alti omaeni care am rescrie codul probabil ca am face aceleasi prostii, si atunc ar mai bine sa ne tina de treaba la infoarean2.

Am facut o lista cu ce anume am gresit in infoarena2 si cum putem face mai bine (in infoarena3). Este posibil sa reparam multe dintre probleme fara o rescriere, dar nu toate. Multe dintre aceste greseli vizeaza arhitectura fundamentala a site-ului. Acestea nu pot fi reparate decat printr-un efort enorm, iar acel efort cumulat ar fi mai mare decat o rescriere de la 0.

Tabelul ia_parameter_values

Una dintre tintele infoarena2 a fost sa avem mai multe tipuri de runde si probleme. Fiecare tip de runda sau de problema are alti "parametri", ne-am gandit sa tinem toti acei parametri intr-un sigur tabel de forma "id-obiect", "nume-parametru", "valoare". Este o idee foarte proasta care nu are absolut nici un merit.

Ar trebui sa avem pentru fiecare tip de problema sau runda un tabel de genul ia_classic_task, care contine o coloana task_id si apoi cate o coloana pentru fiecare parametru. Eu (Leonard) am incercat aceasta transformare dar nu am reusit (din cauza repercursiuni in restul site-ului) si am renuntat.

Tabelul ia_score

Tabelul ia_score are coloanele: score_id, user_id, task_id, round_id si score. Primele 4 coloane sunt sunt nulabile, asa ca tabelul nu are PK. Ideea era sa tinem scoruri per runda cu task_id NULL si eventual statistici per task/round cu user_id NULL. Astfel puteam sa tinem toate scorurile in acelasi tabel. Din pacate am ajuns in situatia de a avea mai putine statistici decat in infoarena1. Asta cred ca este singurul punct in care infoarena1 depaseste infoarena2.

Este un tabel oribil si mi-e rusine de ce am facut acolo. Este incet, urat si greu de facut query-uri.

Acest tabel ar trebui spart in mai multe tabele fara coloane nulabile. Idea fundamental gresita de aici a fost economisirea numarului de tabele din baza de date. Este ca si cum ai incerca sa folosesi cat mai putine functii facand copy-paste in main().

Id-uri VARCHAR (64)

Id-urile pentru utilizatori sunt numere, dar restul sunt VARCHAR (64) cu niste validari facuta in cod prin regex-uri. Ar fi mai bine sa avem toate id-urile drept numere, spre exemplu task_id int si task_name string. task_name se poate obtine foarte usor din task_id adaugand un join trivial.

MySQL nu face index pe hash-uri pentru tabele pe disc (doar pentru tabele din memorie). MySQL sorteaza id-urile alfabetic tinand cont de colatii (latin2 pentru noi). Tinand string-uri peste tot crestem dimensiunile tabelelor, iar asta este oribil pentru tabele de genul ia_score sau ia_job. S-ar merita de facut niste teste de performanta, se poate compara un tabel de scor exclusiv numeric cu unul plin de string-uri.

Tabele de wiki: ia_textblock, ia_textblock_history, ia_file.

Tabelele ia_textblock si ia_textblock_histor au aceasi structura, doar ca ia_textblock tine ultima versiune. Asta inseamna ca orice fel de istorie are nevoie de query-ul complicate pe baza de UNION. Ar trebui sa tinem un singur tabel, eventual cu un tabel aditional care duplica doar datele cele mai recente.

Atasamentele sunt un sistem distict, sunt fisiere pe disc sub-ordonate unui textblock din baza de date. Manipularea atasamentelor este FOARTE neplacuta, si pot aparea desincronizari in diverese locuri. Securitatea atasamentelor depinde de cea a textblock-urilor (si indirect a problemelor) in moduri complicate si imprevizibile. Ar fi mai bine sa avem un singur suport de "BLOB-uri" versionate.

Nu avem suport de redenumire si stergere care sa pastreze istoria, si nici o idee resonabil de design care ar permite asa ceva in baza de date. Trebuie investigat daca se poate folosi subversion in loc de MySQL pentru fisiere versionate.

Securitatea, si magia din wiki

Pe parcursul dezvoltarii infoarena2 ne-am dorit sa evitam pe cat posibil functionalitatea "magica" din wiki, si am mers prea departe. Am pornit de la idea ca orice pagina este o pagina de wiki, si paginile de runde sau probleme sunt doar un caz oarecare de pagina wiki. Securitatea paginilor de utilizatori si de probleme trebuie totusi sa fie subordonata problemelor. Noi am realizat asta adaugand un "descriptor de securitate" ca string pentru fiecare pagina de wiki. Pagina problema/adunare are la securitate un string "task: adunare", si asa vizibilitatea paginii depinde de vizibilitatea task-ului. Este un sistem prea generic, incurcat si greu de folosit sau extins.

Ar fi mai bine ca orice url de format problema/xxx sa intre prin controller-ul de task-uri, care isi subordoneaza textblock-urile care incep cu problema/xxx. Similar am avea controllere de news, blog, user page care subordoneaza tot ce incepe cu news, blog sau user/costel. Pentru restul paginilor am avea un controler distinct de wiki. Codul pentru bucatile de editare si istorie poate fi refolosit in 1000 de moduri, dar nu este acceptabil sa nu poti ajunge de la editarea de enunt la editarea de problema fara sa modifici in address bar.

Am avea tabele ia_news, ia_blog_post si ia_wiki care sunt "deasupra" lui ia_textblock, iar ia_textblock ar fi folosit doar pentru versionarea unor bucati de text. Securitatea private/protected/public (care este foarte utila si absolut ok) poate fi un simplu enum in ia_wiki. Acel enum trebuie editat folosind un simplu dropdown.

Efortul necesar pentru o astfel de transformare in infoarena2 mi se pare absolut enorm.

Layer de logica

Infoarena2 pretinde ca foloseste o arhitectura MVC, dar MVC este o notiunea foarte larga si vag definita. Nu este interesanta o discutie detaliata asupra ce inseamna MVC, asa ca voi discuta doar ce se foloseste efectiv in site-ul infoarena2.

Url-urile sunt parsate in index.php si in functie de o logic complicata si nu foarte interesanta fiecare request http este pasat la un "controller". Un controller este in principiu o functie php din www/controllers. Acel controller face ceva cu requestul, de obicei niste query-uri in baza da date, si apoi constrieste un hash de "date pentru afisat" care il trimite la un view.

View-urile nu sunt functii, sunt fisiere .php din www/views. Lansarea unui view este o operatie sinucigasa, se executa fisierul de view folosind hash-ul de date si se trimite direct pe "teava". In acel view se poate folosi textile, care poate executa macro-uri care(de obicei) se duc pana in baza. Asta inseamna ca poti sa te duci in baza dupa executia controller-ului, dar nu mi se pare nimic rau in asta. Macro-urile sunt efectiv niste mini-controllere.

Problema este ca noi din controllere ne ducem direct in baza si logica fragila de genul securitate este imprastiata si combinata cu cod jegos de parsat/validat request-ul (in controller) sau construit query-uri sql (in functiile de db). Aceasta problema are o rezolvare destul de clara si larg acceptata in industrie (dar nu de pentru noi in vara 2006).

Intre codul de controller (UI) si codul de baza de data (DB) se mai pune niste cod de "business logic" (BL). Tot ce inseamna parsarea request-ului se face in UI si tot ce inseamna contruirea de SQL se face in DB. BL contine de fapt tot codul cu adevarat interesant pentru functionarea corecta a site-ul. Codul de DB nu trebuie sa aiba grija decat sa construiasca query-uri (si sa evite sql injection) iar codul de UI se ocupa de a vedea ce butoane a apasat utilizatorul.

Unul dintre avantajele majore este ca se pot scrie usor teste pentru BL. Daca BL-ul nu are greseli atunci UI-ul nu poate sa strice nimic in baza (doar experienta utilizatorului). A se vedea punctul urmator.

Testele pe baza de curl

PHP este un limbaj foarte fragil, unde este foarte usor sa faci greseli grosolane. Asta este o problema generica a limbajelor dinamice de genul php, python, ruby, javascript fata de limbajele de genul C, C++, C# si java. Asta face ca in limbajele dinamice sa fie mult mai important sa testezi codul.

Teste curente sunt facute pe baza de curl, curl fiind o librarie de access HTTP. Testele construiesc un request complet http pe care il trimit pe fir, asteapta ca apache sa raspunda, iar apoi verifica niste chestii din request. Contruirea requestului se face folosind array, si verificarile se face cu functii de genul preg_match si strstr. Ambele faze sunt greu de facut, dar avantajul acestui sistem ca testeaza tot codul si este foarte "realist".

Pentru a usura testarea am facut controllerele noastre de editare sa accepte un parametru de form absent drept "nu vreau sa editez acest parametru". Asta este foarte util in teste dar e un comportament artifical care a "nascut" niste bug-uri foarte urate. In retrospectiva a fost o idee proasta, care a introdus o cuplare oribila intre controllere si codul de test. Ambele sunt acum foarte greu de modificat.

Ar fi mai bine sa testam functii BL care nu au nici o treaba cu HTTP. Daca BL-ul este ok atunci sigur bug-urile din UI nu pot sa strice nimic in baza. UI-ul se poate testa apoi de mana, sau folosind ceva de genul selenium

Caching exagerat

Infoarena2 suporta mai multe forme de caching:

  1. Cache de textile parsat, inainte de executia macro-urilor. Majoritatea request-urilor nu executa codul de textile.
  2. Cache de imagini redimensionate. Cand se cere o imagine redimensionata ea este salvata pe disc si se evita operatiile de grafic pentru avatari etc.
  3. Cache de obiecte din baza de date, folosind memcached sau eaccelerator. Acest cache tine obiecte de genul useri, task-uri si runde.

Acest ultim mod de caching are inclusiv suport de write-through (cand se salveaza un obiect se sterge cache-ul) care este foarte complex de implementat. Codul de cache este bagat prin foarte multe functii db_ si nu este deloc usor de inteles. Se combinat cod de DB, BL si caching in aceleasi functii.

Suportul de write-through nu este de fapt foarte util pentru ca evaluatorul si partea de web nu folosesc acelasi cache. Eventual am fost fortati sa facem rundele sa traiasca doar 5 secunde in cache. Mai mult, acest caching nu imbunatateste mult timpul de raspuns: Am masurat timpii de raspuns cu si fara cache la paginile de enunt si diferentele erau nesemnificative. Acest cod de caching complica mult codul si nu aduce nimic folositor.

Un plan de atac

Unele probleme din infoarena2 nu origineaza neaparat in cod, dar in modul nostru de lucru. Aici am pus niste idei pentru a imbunatati procesul de dezvoltari. Teoretic ar putea fi aplicate si in infoarena2.

Branching

Nu am folosit eficient branch-uri. Absolut toate modificarile le-am facut direct in trunk iar asta nu este o idee buna. Exista mereu schimbari mari care au nevoie de mai multe commit-uri pentru a fi complet functionale. Astfel de schimbari trebuie facute intai intr-un branch special si abia apoi copiate(svn merge) in trunk.

In asa fel trunk-ul devine mult mai stabil si nu este nimic rau in a abandona o idee. In trunk-ul infoarena2 avem cod mort pentru idei abandonate, care acum este dificil de extras. Branch-uri ar fi trebuit sa facem pentru cache-ing, editoare de probleme, validate_array, tag-uri, blog, dataset-uri etc.

Responsabilitati

Codul infoarena este open-source, iar oamenii lucreaza doar in timpul lor liber. Dar daca vrei sa ajuti infoarena atunci trebuie sa te tii de treaba si rezolvi tichetele pana la o anumita data. Ar ajuta mult ca un tichet "acceptat" sa aiba un owner care este responsabil sa rezolve acel tichet pana la o anumita data.

Bineinteles ca nu avem cum sa fortam pe nimeni sa lucreze la infoarena, dar se presupune ca un individ care vrea sa ne ajute cu adevarat nu vrea sa frece menta. Astfel de dead-line-uri ar fi un impuls interior pentru fiecare din noi. Atentie: nu ma refer la dead-line-uri de genul "ar fi frumos sa avem #123 rezolvat pana luna viitoare", ma refer la lucruri de genul "eu rezolv #124 pana maine si #125 poimaine".

Un asemenea sistem ar mentine mai multa ordine in tichetele din trac.

Demo-uri

S-a propus de multe ori sa folosim framework-uri si librarii avansate. Adaugarea unei dependinte necesita un efort mare de invatare care se poate dovedi eventual inutil. Inainte de a inghiti o noua dependinta trebuie sa ne asiguram ca se merita. Are sens sa face tichete speciale de "investigare". Individul responsabil de un asemenea tichet trebuie sa invete tehnologia respectiva, si sa ne ajute sa decidem daca merita folosita. Rezultatul unui asemenea tichet este un demo, care poate fi un branch de infoarena2 sau o chestie from-scratch. Acel demo trebuie sa demonstreze viabilitatea respective tehnologii.

Exemplu de demo-uri care ar merita facute:

  • SqlAlchemy: O schema sql alchemy pentru tabelele de scoruri, inclusiv teste de performanta.
  • Selenium: Cineva sa faca niste teste folosind Selenium, probabil peste infoarena2. Cum anume pot fi stocate in svn si rulate de oricine?
  • Form-uri: Nu avem o solutie definitiva pentru facut si validat form-uri. Cineva sa ia o asemenea librarie si sa faca CRUD de task-uri, si sa demonstreze ca e sensibil mai usor decat manual.
  • Tabele: Demo de tabel de scoruri prin AJAX. Cristi: this means you.
  • BL prin HTTP: Se poate face un BL accesibil direct prin http, fara a scrie controllere manuale? Poate evaluatorul sa acceseze functii de BL de pe live, fara access local la baza de date?
  • Wiki in subversion: Ar avea sens sa tinem textblock-uri si atasamente in subversion? Demo cu teste de viteza.

Arest la domiciliu

Facem intai un numar de demo-uri pentru a ne decide ce anume folosim in infoarena3. Ne adunam cativa oameni si contruim baza (index.py, structura de directoare, server debaza de date). Apoi chemam lumea pe santier. Facem un mare coding camp la Leonard si Mircea acasa, care sa dureze o saptamana intreaga sau chiar 2. Putem invita si utilizatori infoarena care vor sa ne ajute. Sigur ne putem face cu totii timp sa lansam infoarena3 (concediu). O lansare de success inseamna un site utilizabil la care va continua dezvoltarea si in timpul liber (*because it's fun!*).