Php, variabile statice în cadrul metodelor de clasă și istoria unui bug

În general, sunt un dezvoltator de front-end. Dar, uneori, trebuie să lucrați cu partea de server. Echipa pe care o avem este mică, iar atunci când toți programatorii reale din backend sunt ocupați, este mai rapid să implementați anumite metode. Și uneori ne așezați împreună pentru a lucra la sarcini, pentru a nu pierde timpul cu privire la transferul de angajamente înainte și înapoi. Recent, în timpul uneia dintre aceste runde de programare pereche, coechipierul meu și cu mine am întâlnit un bug care ma impresionat atât de mult încât am decis să împărtășesc cu dvs.







Deci, după cină, m-am apropiat de colegul meu parpalac roman. tocmai și-a terminat operațiunile de testare și a lansat întregul pachet. Unul dintre teste a aruncat o excepție și a căzut. Da, ne-am gândit, acum vom repara bug-ul. Am testat singur, în afara pachetului, și a avut succes.

Înainte de a ne pierde puiul de după-amiază, am lansat de mai multe ori Codecimea. În pachet, testul a căzut, a trecut singur, a căzut într-un pachet ...

Am intrat în cod.

Metoda Fatalka Apelați la metoda privată a decolat de la metoda care convertește o entitate într-o matrice pentru trimiterea la client. Recent, mecanismul acestui proces sa schimbat un pic, dar nu toate clasele refactor, deci metoda este în valoare de a verifica dacă metoda supracomandată care returnează o listă cu câmpurile obligatorii (acesta este modul vechi) în clasa derivată. Dacă nu, lista câmpurilor se formează prin reflecție (aceasta este o metodă nouă) și se primesc primirii corespunzători. În cazul nostru, unul dintre preluători a fost declarat privat și, prin urmare, nu este disponibil din clasa de bază. Totul arată astfel:

Un cod ușor simplificat pentru a vă concentra asupra esenței

După cum puteți vedea, ieșirea reflectorului este stocată în variabila statică $ isClientPropsOriginal în interiorul metodei.

- Și ce, este o operație atât de gravă o reflecție? Am întrebat.
- Da, - Roman a dat din cap.

Punctul de întrerupere de pe linia cu reflexie nu a funcționat deloc în această clasă. Nu o dată. Variabila statică a fost deja setată la true. interpretul a urcat în metoda toClientModelNew și a scăzut. Am sugerat să te uiți la locul unde se desfășoară misiunea:

Variabila $ isClientPropsOriginal a fost "PaymentList". Aceasta este o altă clasă moștenită de la AbstractEntity. care este remarcabil pentru exact două lucruri: nu suprascrie metoda getClientProperties și a fost testată cu un test de unitate, care a fost deja efectuat cu succes puțin mai devreme.

- Cum poate fi asta? Am întrebat. - Variația statică din cadrul metodei se răstoarnă la moștenire? De ce nu am mai observat asta înainte?

Romanul a fost la fel de nedumerit ca al meu. În timp ce mă duceam la cafea, a schițat un mic test de unitate cu o imitație a ierarhiei noastre de clasă, dar nu a căzut. Am pierdut ceva. Variabila statică sa comportat incorect, nu așa cum ne-am așteptat, dar nu în toate cazurile și nu am putut înțelege de ce. Googling la cerere "variabila statică php în interiorul metodei de clasă" nu a oferit nimic valabil, cu excepția faptului că variabilele statice nu sunt bune. Păi, duh!







Acum, Roman a mers la cafea și am deschis cu ghilimele sandbox-ul PHP și am scris cel mai simplu cod:

Cumva ar trebui să funcționeze. Principiul de cea mai mică surpriză, toate afacerile. Dar avem o variabilă statică definită în cadrul metodei toClientModel. și este redefinită în clasa copilului. Și dacă scriem astfel:

"Cât de ciudat", m-am gândit. Dar există o logică aici. În al doilea caz, metoda care conține variabila statică este apelată prin intermediul părintelui. Foloseste instanta sa din clasa parent? Dar cum să ieșiți din această situație? Mi-am zgâriat spatele capului și mi-am adăugat puțin exemplul:

Aici este! Romanul tocmai sa întors și eu, mulțumit de mine, mi-am demonstrat realizările. Avea nevoie doar de câteva clicuri pe tastatură în PHPStorm pentru a refactoriza secțiunea cu variabila statică într-o metodă separată:

Dar acolo a fost! Eroarea noastră a persistat. După ce am privit cu atenție, am observat că metoda hasOriginalClientProps este declarată privată. în exemplul meu a fost public. O verificare rapidă a arătat că protejate și publice funcționează. dar privat nu funcționează.

Timpul nu a așteptat și am continuat sarcini suplimentare, dar totuși acest comportament a fost încurcat. Am decis să dau seama de ce se comportă PHP în acest fel. Documentația nu a reușit să sapă altceva decât sugestii vagi. Mai jos voi încerca să restabilesc imaginea a ceea ce se întâmplă, bazându-se pe o citire atentă a cărții internationale PHP. PHP Wiki. studierea codurilor sursă și informații despre modul în care sunt implementate obiectele în alte limbi de programare.

Funcția din interiorul interpretului PHP este descrisă de structura op_array. care, printre altele, conține o tabelă hash cu variabile statice ale acestei funcții. Când moșteniți. dacă nu există variabile statice, funcția este reutilizată în clasa copil și, dacă există, este creat un duplicat astfel încât clasa copil în metodă să aibă propriile variabile statice.

Până acum, atât de bine, dar dacă numim metoda parentală prin parent :: printCount (). apoi, desigur, vom cădea în metoda clasei părinte, care lucrează cu variabile statice. Prin urmare, Exemplul 2 nu funcționează și Exemplul 1 funcționează. Și când am învățat o variabilă statică într-o metodă separată, ca în exemplul 3, am salvat legarii: metoda A :: printCount încă provoca o copie a metodei A :: doPrintCount din clasa B (care, desigur, identic cu original A :: doPrintCount).

Acest comportament este repetat pe toate versiunile de PHP pe care le-am încercat în nisip. începând cu o păroasă 5.0.4.

De ce nu a simțit niciodată bug-ul din codul proiectului nostru? Aparent, entitățile au fost rareori create de diferite tipuri de grupuri și, dacă au fost create, au fost refactate simultan. Dar, când rulați testele într-o singură etapă, scriptul a primit două obiecte care lucrau prin mecanisme diferite, iar unul dintre ele a stricat cealaltă stare.

(pentru că în fiecare articol serios trebuie să existe concluzii)

  1. Variabilele statice sunt rele.

Ei bine, asta este, ca orice alt rău în programare, necesită o abordare prudentă și atentă. Desigur, ne puteți critica pentru utilizarea stării ascunse, dar cu o aplicare atentă vă permite să scrieți un cod destul de eficient. Cu toate acestea, static poate ascunde pietre subacvatice, dintre care unul i-am arătat. prin urmare

Nimeni nu poate garanta că jambul ascuns din codul dvs. nu iese după un alt refactoring. Așa că scrieți codul de testare și acoperiți-l cu teste. Dacă un astfel de bug a apărut în codul de luptă și nu în teste, acesta ar fi putut fi folosit cu ușurință toată ziua, mai degrabă decât unul și jumătate până la două ore, pentru depanarea acestuia.

  1. Nu-ți fie frică să intri în junglă.

Chiar și un lucru simplu, ca variabile statice, poate servi drept scuză pentru a vă adânci profund în documentația sistemului și codul sursă PHP. Și chiar ceva în ele să înțeleagă.

P.S. Îi mulțumesc lui Roman Parpalak pentru sfaturi valoroase în pregătirea materialului.







Articole similare

Trimiteți-le prietenilor: