Cum să evitați blestemul optimizării premature

Publicat: 2022-03-11

Este aproape demn de garantat, într-adevăr. De la începători la experți, de la arhitectură până la ASM și optimizarea orice, de la performanța mașinii la performanța dezvoltatorului, sunt șanse destul de mari ca tu și echipa ta să-ți scurtcircuitați propriile obiective.

Ce? Pe mine? Echipa mea ?

Este o acuzație destul de grea de nivel. Lasă-mă să explic.

Optimizarea nu este Sfântul Graal, dar poate fi la fel de greu de obținut. Vreau să vă împărtășesc câteva sfaturi simple (și un munte de capcane) pentru a ajuta la transformarea experienței echipei dvs. de la una de autosabotare la una de armonie, împlinire, echilibru și, eventual, optimizare.

Ce este optimizarea prematură?

Optimizarea prematură încearcă să optimizeze performanța:

  1. La prima codificare a unui algoritm
  2. Înainte ca benchmark-urile să confirme că trebuie
  3. Înainte de a profila, indică locul în care este logic să te deranjezi cu optimizarea
  4. La un nivel mai scăzut decât dictează proiectul dumneavoastră în prezent

Acum, sunt un optimist, Optimus.

Cel puțin, o să mă prefac că sunt un optimist în timp ce scriu acest articol. La rândul tău, poți pretinde că numele tău este Optimus, așa că asta îți va vorbi mai direct.

Ca cineva în tehnologie, probabil că uneori te întrebi cum ar putea fi $year și totuși, în ciuda tuturor progreselor noastre, este cumva un standard acceptabil ca $task să consume atât de enervant de timp. Vrei să fii slabă. Eficient. Minunat. Cineva ca programatorii Rockstar, pe care aceste postări de locuri de muncă le cer, dar cu colet de lider. Deci, atunci când echipa ta scrie cod, îi încurajezi să o facă corect de prima dată (chiar dacă „corect” este un termen foarte relativ, aici). Ei știu că acesta este calea codificatorului inteligent și, de asemenea, calea celor care nu trebuie să piardă timpul refactoring mai târziu.

Simt că. Forța perfecționismului este uneori puternică și în mine. Vrei ca echipa ta să petreacă puțin timp acum pentru a economisi mult timp mai târziu, pentru că toată lumea și-a desfășurat partea din „Shitty Code Other People Wrote (What the Hell Were Were They Think?)” Asta e SCOPWHWTT pe scurt, pentru că știu că îți plac acronimele nepronunțabile.

De asemenea, știu că nu vrei ca codul echipei tale să fie acela pentru ei înșiși sau pentru oricine altcineva.

Deci, haideți să vedem ce se poate face pentru a vă ghida echipa în direcția corectă.

Ce să optimizați: Bine ați venit la This Being an Art

În primul rând, când ne gândim la optimizarea programelor, de multe ori presupunem imediat că vorbim despre performanță. Chiar și asta este deja mai vag decât poate părea (viteză? utilizarea memoriei? etc.), așa că hai să ne oprim aici.

Să o facem și mai ambiguă! Doar la început.

Creierului meu din pânză de păianjen îi place să creeze ordine acolo unde este posibil, așa că va fi nevoie de fiecare gram de optimism pentru a considera ceea ce sunt pe cale să spun a fi un lucru bun .

Există o regulă simplă de optimizare (performanță) care spune Nu o face . Acest lucru sună destul de ușor de urmărit în mod rigid, dar nu toată lumea este de acord cu el. Nici eu nu sunt în totalitate de acord cu asta. Unii oameni vor scrie pur și simplu cod mai bun decât alții. Sperăm că, pentru orice persoană dată, calitatea codului pe care l-ar scrie într-un proiect nou-nouț se va îmbunătăți în general în timp. Dar știu că, pentru mulți programatori, nu va fi așa, pentru că cu cât știu mai multe, cu atât vor fi tentați să optimizeze prematur.

Pentru mulți programatori... cu cât știu mai multe, cu atât mai multe modalități vor fi tentați să optimizeze prematur.

Deci, acest lucru Nu o face nu poate fi o știință exactă, ci are doar menirea de a contracara dorința interioară a tehnicianului tipic de a rezolva puzzle-ul. Acesta, la urma urmei, este ceea ce atrage mulți programatori în acest domeniu, în primul rând. Am înțeles. Dar roagă-le să -l salveze , să reziste tentației. Dacă ai nevoie de o soluție de rezolvare a puzzle-urilor chiar acum , poți oricând să te încurci în Sudoku al ziarului de duminică, sau să ia o carte Mensa sau să joci coduri de golf cu o problemă artificială. Dar lăsați-l afară din repo până la momentul potrivit. Aproape întotdeauna aceasta este o cale mai înțeleaptă decât pre-optimizarea.

Amintiți-vă, această practică este destul de notorie încât oamenii se întreabă dacă optimizarea prematură este rădăcina tuturor relelor. (Nu aș merge chiar atât de departe, dar sunt de acord cu sentimentul.)

Nu spun că ar trebui să alegem modul cel mai mort cerebral la care ne putem gândi la fiecare nivel de design. Desigur că nu. Dar, în loc să alegem pe cel mai inteligent, putem lua în considerare alte valori:

  1. Cel mai ușor de explicat noului tău angajat
  2. Cel mai probabil să treacă o revizuire a codului de către cel mai experimentat dezvoltator
  3. Cel mai întreținut
  4. Cel mai rapid de scris
  5. Cel mai ușor de testat
  6. Cel mai portabil
  7. etc.

Dar aici problema se arată a fi dificilă. Nu este vorba doar despre evitarea optimizării pentru viteză, dimensiunea codului, amprenta memoriei, flexibilitate sau rezistența la viitor. Este vorba despre echilibru și despre dacă ceea ce faci este de fapt în concordanță cu valorile și obiectivele tale. Este în întregime contextuală și uneori chiar imposibil de măsurat în mod obiectiv.

Este o artă. (Cf Arta programarii computerelor.)

De ce este acesta un lucru bun? Pentru că viața este așa. E dezordonat. Creierul nostru orientat spre programare vrea uneori să creeze ordine în haos atât de rău încât ajungem să înmulțim ironic haosul. Este ca paradoxul de a încerca să forțezi pe cineva să te iubească. Dacă crezi că ai reușit asta, nu mai este dragoste; Între timp, ești acuzat de luare de ostatici, probabil că ai nevoie de mai multă dragoste ca niciodată, iar această metaforă trebuie să fie una dintre cele mai incomode pe care le-aș fi putut alege.

Oricum, dacă crezi că ai găsit sistemul perfect pentru ceva, ei bine... bucură-te de iluzie cât durează, cred. E în regulă, eșecurile sunt ocazii minunate de a învăța.

Optimizarea prematură vă complică adesea produsul în mod inutil și inutil.

Ține cont de UX

Să explorăm cum se încadrează experiența utilizatorului printre aceste priorități potențiale. La urma urmei, chiar și a dori ca ceva să funcționeze bine este, la un anumit nivel, despre UX.

Dacă lucrați la o interfață de utilizare, indiferent de cadru sau limbă folosită codul, va exista o anumită cantitate de boilerplate și repetiție. Cu siguranță poate fi valoros din punct de vedere al timpului de programare și al clarității codului să încerce să reducă acest lucru. Pentru a ajuta cu arta de a echilibra prioritățile, vreau să împărtășesc câteva povești.

La un loc de muncă, compania pentru care lucram a folosit un sistem de întreprindere cu sursă închisă, care se baza pe o stivă tehnologică cu opinie. De fapt, era atât de opinie, încât vânzătorul care ni l-a vândut a refuzat să facă personalizări ale UI care nu se potriveau cu opiniile stivei, deoarece a fost atât de dureros pentru dezvoltatorii lor. Nu le-am folosit niciodată stiva, așa că nu-i condamn pentru asta, dar adevărul a fost că acest compromis „bun pentru programator, rău pentru utilizator” a fost atât de greoi pentru colegii mei în anumite contexte, încât am încheiat până la scrierea unui supliment terță parte care reimplementează această parte a interfeței de utilizare a sistemului. (A fost un stimulent uriaș de productivitate. Colegilor mei le-a plăcut! Peste un deceniu mai târziu, încă economisește timp și frustrare tuturor celor de acolo.)

Nu spun că opinia este o problemă în sine; doar că prea mult a devenit o problemă în cazul nostru. Ca contraexemplu, unul dintre marile atrageri ale lui Ruby on Rails este tocmai faptul că este obișnuit, într-un ecosistem front-end în care se obține ușor vertij de la prea multe opțiuni. (Dă-mi ceva cu opinii până îmi pot da seama pe ale mele!)

În schimb, s-ar putea să fii tentat să încoronezi UX regele tuturor în proiectul tău. Un obiectiv demn, dar permiteți-mi să spun a doua mea poveste.

La câțiva ani după succesul proiectului de mai sus, unul dintre colegii mei a venit la mine să-mi ceară să optimizez UX-ul prin automatizarea unui anumit scenariu dezordonat din viața reală care se ivea uneori, astfel încât să poată fi rezolvat cu un singur clic. Am început să analizez dacă este chiar posibil să proiectez un algoritm care să nu aibă elemente false pozitive sau negative din cauza numeroaselor și ciudate cazuri marginale ale scenariului. Cu cât vorbeam mai mult cu colegul meu despre asta, cu atât mi-am dat seama că cerințele pur și simplu nu aveau să dea roade. Scenariul a apărut doar din când în când – lunar, să zicem – și, în prezent, a avut nevoie de câteva minute pentru rezolvarea unei persoane. Chiar dacă l-am putea automatiza cu succes, fără erori, ar dura secole pentru ca timpul necesar de dezvoltare și întreținere să fie plătit în termeni de timp economisit de colegii mei. Oamenii care îi plăceau din mine au avut un moment dificil spunând „nu”, dar a trebuit să scurtez conversația.

Deci, lăsați computerul să facă tot ce poate pentru a ajuta utilizatorul, dar numai într-o măsură normală. De unde știi totuși în ce măsură este asta?

Ce este ușor și greu pentru computere

O abordare pe care îmi place să o adopt este să profilez UX-ul așa cum dezvoltatorii tăi își profilează codul. Aflați de la utilizatorii dvs. unde petrec cel mai mult timp făcând clic sau tastând același lucru iar și iar și vedeți dacă puteți optimiza acele interacțiuni. Poate codul dvs. să facă niște presupuneri educate cu privire la ceea ce vor introduce cel mai probabil și să facă acest lucru implicit fără introducere? În afară de anumite contexte interzise (confirmarea EULA fără clic?), acest lucru poate face cu adevărat o diferență pentru productivitatea și fericirea utilizatorilor tăi. Faceți niște teste de utilizare pe hol dacă puteți. Uneori, este posibil să aveți dificultăți în a explica cu ce este ușor de ajutat computerele și ce nu... dar, în general, această valoare este probabil să fie de o importanță destul de mare pentru utilizatorii dvs.

Evitarea optimizării premature: când și cum să optimizați

În ciuda explorării noastre a altor contexte, acum să presupunem în mod explicit că optimizăm un anumit aspect al performanței mașinii brute pentru restul acestui articol. Abordarea sugerată de mine se aplică și altor ținte, cum ar fi flexibilitatea, dar fiecare țintă va avea propriile sale probleme; punctul principal este că optimizarea prematură pentru orice va eșua probabil.

Deci, în ceea ce privește performanța, ce metode de optimizare sunt de urmat de fapt? Să săpăm.

Aceasta nu este o inițiativă de bază, este Triple-Eh

TL;DR este: Lucrați în jos de sus. Optimizările de nivel superior pot fi făcute mai devreme în proiect, iar cele de nivel inferior ar trebui lăsate pentru mai târziu. Acesta este tot ce aveți nevoie pentru a obține cel mai mult sensul expresiei „optimizare prematură”; a face lucrurile din această ordine are o probabilitate mare de a pierde timpul echipei tale și de a fi contra-eficient. La urma urmei, nu scrieți întregul proiect în codul mașinii de la început, nu-i așa? Deci, modul nostru de operare AAA este de a optimiza în această ordine:

  1. Arhitectură
  2. Algoritmi
  3. Asamblare

Înțelepciunea comună spune că algoritmii și structurile de date sunt adesea cele mai eficiente locuri de optimizare, cel puțin în ceea ce privește performanța. Rețineți, totuși, că arhitectura determină uneori ce algoritmi și structuri de date pot fi utilizate.

Odată am descoperit o bucată de software care făcea un raport financiar interogând o bază de date SQL de mai multe ori pentru fiecare tranzacție financiară, apoi făcând un calcul foarte simplu din partea clientului. Micii întreprinderi care foloseau software-ul i-au trebuit doar câteva luni de utilizare până când chiar și cantitatea lor relativ mică de date financiare să însemne că, cu desktop-uri noi și un server destul de robust, timpul de generare a rapoartelor era deja de până la câteva minute, iar acest lucru a fost una pe care trebuiau să o folosească destul de frecvent. Am ajuns să scriu o instrucțiune SQL simplă, care conținea logica de însumare — zădărnicindu-le arhitectura prin mutarea lucrării pe server pentru a evita toate dublarile și călătoriile dus-întors în rețea — și chiar și în valoare de câțiva ani mai târziu, versiunea mea ar putea genera același raport în doar milisecunde pe același hardware vechi.

Uneori nu ai influență asupra arhitecturii unui proiect, deoarece este prea târziu în proiect pentru ca o schimbare a arhitecturii să fie fezabilă. Uneori, dezvoltatorii tăi pot ocoli așa cum am făcut-o în exemplul de mai sus. Dar dacă sunteți la începutul unui proiect și aveți ceva de spus în arhitectura lui, acum este momentul să optimizați asta.

Arhitectură

„Dacă constructorii au construit clădiri în felul în care programatorii au scris programe, atunci prima ciocănitoare care a apărut ar distruge civilizația.” - Legea lui Weinberg

Într-un proiect, arhitectura este partea cea mai scumpă de schimbat după fapt, așa că acesta este un loc în care poate avea sens să se optimizeze la început. Dacă aplicația dvs. urmează să livreze date prin struți, de exemplu, veți dori să le structurați în pachete cu frecvență joasă și cu sarcină utilă mare pentru a evita înrăutățirea blocajului. În acest caz, ar fi mai bine să aveți o implementare completă a Tetris pentru a vă distra utilizatorii, deoarece un spinner de încărcare pur și simplu nu o va reduce. (Glume la o parte: cu ani în urmă instalam prima mea distribuție Linux, Corel Linux 2.0, și am fost încântat că procesul de instalare de lungă durată includea doar asta. După ce am văzut de atâtea ori ecranele comerciale ale programului de instalare Windows 95 încât le-am memorat, acest era o gură de aer proaspăt la acea vreme.)

Ca un exemplu de schimbare a arhitecturii fiind costisitoare, motivul pentru care raportul SQL menționat mai sus este atât de nescalabil în primul rând este clar din istoria sa. Aplicația a evoluat de-a lungul timpului, de la rădăcinile sale în MS-DOS și o bază de date personalizată, care nu era inițial multi-utilizator. Când furnizorul a făcut, în sfârșit, trecerea la SQL, schema și codul de raportare par să fi fost portate unul câte unul. Acest lucru le-a lăsat ani de zile de îmbunătățiri impresionante de performanță de peste 1.000% de presărat pe parcursul actualizărilor lor, ori de câte ori au ajuns să finalizeze schimbarea arhitecturii utilizând efectiv avantajele SQL pentru un anumit raport. Bun pentru afaceri cu clienți blocați, cum ar fi angajatorul meu de atunci și încercând în mod clar să acorde prioritate eficienței codificării în timpul tranziției inițiale. Dar satisfacerea nevoilor clienților, în unele cazuri, este cam la fel de eficient precum un ciocan învârte un șurub.

Arhitectura se referă parțial la anticiparea în ce măsură va avea nevoie proiectul tău pentru a putea scala și în ce moduri. Deoarece arhitectura este la un nivel atât de înalt, este dificil să ne concretizăm „a face și a nu” fără să ne concentrăm asupra tehnologiilor și domeniilor specifice.

Nu l-aș numi așa, dar toți ceilalți o fac

Din fericire, internetul este plin de înțelepciune adunată despre majoritatea tuturor tipurilor de arhitectură imaginate vreodată. Când știi că este timpul să-ți optimizezi arhitectura, cercetarea capcanelor se reduce aproape la a descoperi cuvântul la modă care descrie viziunea ta genială. Sunt șanse ca cineva să fi gândit în același mod ca și tine, să fi încercat, să eșueze, să fi repetat și să fi publicat despre asta într-un blog sau într-o carte.

Identificarea cuvintelor la modă poate fi dificil de realizat doar prin căutare, deoarece pentru ceea ce numiți FLDSMDFR, altcineva a inventat deja termenul SCOPWHWTT și descrie aceeași problemă pe care o rezolvați, dar folosind un vocabular complet diferit decât ați face-o. Comunitățile de dezvoltatori în ajutor! Apăsați StackExchange sau HashNode cu o descriere cât mai amănunțită, plus toate cuvintele la modă care nu sunt arhitectura dvs., astfel încât să știe că ați făcut suficiente cercetări preliminare. Cineva va fi bucuros să vă lumineze.

Între timp, unele sfaturi generale ar putea fi un bun motiv de gândire.

Algoritmi și asamblare

Având în vedere o arhitectură favorabilă, aici este locul în care programatorii din echipa ta vor obține cel mai mult T-bling pentru timpul lor. Evitarea de bază a optimizării premature se aplică și aici, dar programatorii dvs. ar face bine să ia în considerare unele dintre detaliile de la acest nivel. Sunt atât de multe de gândit când vine vorba de detalii de implementare, încât am scris un articol separat despre optimizarea codului orientat către programatorii de primă linie și seniori.

Dar odată ce tu și echipa ta ați implementat ceva neoptimizat din punct de vedere al performanței, chiar lăsați-l la Nu o faceți ? Nu optimizezi niciodată ?

Ai dreptate. Următoarea regulă este, numai pentru experți, Nu o faceți încă .

Este timpul pentru Benchmark!

Codul tău funcționează. Poate că este atât de lent încât știi deja că va trebui să optimizați, pentru că este un cod care va rula des. Poate nu sunteți sigur sau aveți un algoritm O(n) și vă dați seama că probabil este bine. Indiferent de caz, dacă acest algoritm ar putea merita vreodată optimizat, recomandarea mea în acest moment este aceeași: rulați un benchmark simplu.

De ce? Nu este clar că algoritmul meu O(n³) nu poate fi mai rău decât orice altceva? Ei bine, din două motive:

  1. Puteți adăuga benchmark-ul la suita dvs. de teste, ca măsură obiectivă a obiectivelor dvs. de performanță, indiferent dacă acestea sunt în prezent îndeplinite.
  2. Chiar și experții pot încetini din neatenție lucrurile. Chiar și atunci când pare evident. Chiar evident.

Nu mă credeți în al doilea punct?

Cum să obțineți rezultate mai bune de la hardware de 1.400 USD decât de la hardware de 7.000 USD

Jeff Atwood, de la StackOverflow, a subliniat odată că uneori (de obicei, după părerea lui) poate fi mai rentabil să cumpărați doar hardware mai bun decât să petreceți timp valoros de programare pentru optimizare. Bine, deci să presupunem că ai ajuns la o concluzie rezonabil obiectivă că proiectul tău s-ar potrivi acestui scenariu. Să presupunem în continuare că ceea ce încercați să optimizați este timpul de compilare, deoarece este un proiect Swift puternic la care lucrați și acesta a devenit un blocaj destul de mare pentru dezvoltatori. Timp de cumpărături hardware!

Ce ar trebui să cumperi? Ei bine, evident, yen pentru yen, hardware-ul mai scump tinde să funcționeze mai bine decât hardware-ul mai ieftin. Deci, evident, un Mac Pro de 7.000 USD ar trebui să vă compilați software-ul mai repede decât un Mac Mini de gamă medie, nu?

Gresit!

Se dovedește că, uneori, mai multe nuclee înseamnă o compilare mai eficientă... și în acest caz particular, LinkedIn a descoperit la greu că opusul este adevărat pentru stiva lor.

Dar am văzut că managementul a făcut o greșeală suplimentară: nici măcar nu au făcut benchmarkări înainte și după, și au descoperit că o actualizare hardware nu le-a „simțit” software-ul mai rapid. Dar nu era nicio modalitate de a ști cu siguranță; și mai departe, ei încă nu aveau idee unde era blocajul, așa că au rămas nemulțumiți în ceea ce privește performanța, consumând timpul și banii pe care erau dispuși să îi aloce problemei.

OK, am evaluat deja. Pot să optimizez încă ??

Da, presupunând că ai decis că trebuie. Dar poate că această decizie va aștepta până când mai mulți/toți ceilalți algoritmi vor fi implementați, astfel încât să puteți vedea cum se potrivesc părțile mobile și care sunt cele mai importante prin profilare. Acest lucru poate fi la nivel de aplicație pentru o aplicație mică sau se poate aplica doar unui subsistem. Oricum, amintiți-vă, un anumit algoritm poate părea important pentru aplicația generală, dar chiar și experții, în special experții, sunt predispuși să diagnosticheze greșit acest lucru.

Gândește-te înainte de a întrerupe

„Nu știu despre voi, dar...”

Gavin Belson din Silicon Valley spunând: „Nu vreau să trăiesc într-o lume în care altcineva face lumea un loc mai bun... mai bun decât noi”.

Ca un ultim motiv de gândire, luați în considerare modul în care puteți aplica ideea de optimizare falsă într-o perspectivă mult mai largă: proiectul sau compania în sine, sau chiar un sector al economiei.

Știu, este tentant să credem că tehnologia va salva situația și că noi putem fi eroii care o facem să se întâmple.

În plus, dacă noi nu o facem, o va face altcineva.

Dar amintiți-vă că puterea corupe, în ciuda celor mai bune intenții. Nu voi trimite link la niciun articol anume aici, dar dacă nu ați rătăcit prin niciunul, merită să căutați câteva informații despre impactul mai larg al perturbării economiei și despre cine servește aceasta uneori în cele din urmă. S-ar putea să fii surprins de unele dintre efectele secundare ale încercării de a salva lumea prin optimizare.

Post-scriptum

Ai observat ceva, Optimus? Singura dată când te-am numit Optimus a fost la început și acum la sfârșit. Nu ai fost numit Optimus pe tot parcursul articolului. O să fiu sincer, am uitat. Am scris tot articolul fără să vă spun Optimus. La sfârșit, când mi-am dat seama că ar trebui să mă întorc și să-ți presăr numele pe tot textul, o voce mică din interiorul meu a spus, nu o face .