O tendință comună pe care am observat-o în proiectele Elm este că Elm vă ghidează către soluții simple. Am descoperit că acest lucru este adevărat chiar și pentru oamenii care sunt noi în Elm. Am antrenat o echipă într-un mediu de întreprindere prin adoptarea lui Elm pentru un nou proiect. Au construit un proiect anterior cu AngularJS și s-au luptat cu el. Sau, mai degrabă, nu se luptaseră cu asta. Cadrul ar accepta orice soluție cu care au venit. Când au început să lucreze cu Elm, s-ar putea să le fi durat ceva mai mult pentru a-și da seama „cum stochează intrarea dintr-un câmp de intrare în modelul meu”. Dar pentru că Elm nu le-a permis să folosească hack-uri temporare, abordarea lor a fost total diferită. În loc să copieze-lipi codul din StackOverflow care folosește o variabilă globală sau modifică DOM, „doar pentru a-l face să funcționeze deocamdată”, Elm i-a forțat să găsească o soluție fără a se baza pe niciun hack. Rezultatul a fost că luni mai târziu, în loc să lupte împotriva unei baze de cod instabile care este înfricoșătoare de schimbat, au lucrat cu un cod remarcabil de fiabil. Ei puteau face schimbări cu ușurință și fără teamă. Diferența era ziua și noaptea.

Am avut recent o experiență similară care ilustrează modul în care Elm te ghidează către simplitate. Sunt autorul unui pachet Elm pentru interogări GraphQL sigure de tip, dillonkearns/elm-graphql (consultați „discuția mea recentă Elm Conf” pentru a afla mai multe). Începeam să primesc aceeași cerere de caracteristici de la mai mulți utilizatori diferiți. A fost o solicitare rezonabilă, dar când a apărut aș explica provocările tehnice și cât de semnificativă ar fi reproiectarea pentru a o susține.

Provocarea: chei unice

Interogările GraphQL primesc răspunsuri JSON, răspunsurile având aceeași „formă” ca și interogarea. Deci această interogare:

{
  currentUser {
    avatar
  }
}

ar returna JSON astfel:

{
  "currentUser": {
    "avatar": "https://avatar.com/user.jpg"
  }
}

Singura avertizare este că, dacă solicitați mai multe câmpuri cu același nume cu argumente diferite, trebuie să oferiți aliasuri de câmp pentru a spune în mod explicit GraphQL sub ce cheie să transfere datele respective.

{
  currentUser {
    avatar(size: 12)
    avatar(size: 48)
  }
}
# ERROR - Field 'avatar' has an argument conflict: is size 12 or 48?

Interogarea de mai sus are ca rezultat o eroare, deoarece este ambiguă.

dillonkearns/elm-graphql se ocupă de detaliile de nivel scăzut ale construirii și deserializării interogărilor GraphQL, astfel încât 1) să puteți utiliza caracteristici ale limbajului Elm de nivel înalt în loc de o bibliotecă complicată și 2) să vă garanteze că orice interogare pe care o creați este validă. Prin urmare, biblioteca are nevoie de o modalitate de a evita crearea de interogări ambigue și nevalide ca aceasta.

Soluția originală

Algoritmul meu anterior pentru a mă asigura că câmpurile au fost aliate atunci când era necesar a fost să creez un numărător și să folosesc acel număr pentru fiecare alias de câmp.

Folosind această abordare, biblioteca mea ar genera o interogare ca aceasta:

{
  currentUser {
    avatar(size: 12)
    avatar2: avatar(size: 48)  # this alias tells GraphQL to return this data under the key "avatar2"
  }
}

Acest lucru a funcționat destul de bine. Dar când ați creat un set de selecție ca mai sus, luând avatarele mici și medii, trebuia să construiți „Decodorul Json” corespunzător pe baza a ceea ce știa la acel moment (Decodificatoarele Json sunt modul în care Elm deserializează JSON în date Elm tastate) . Așa că nu am putut expune un API pentru a combina două seturi de selecție ca acesta împreună, deoarece ar fi creat decodorul pentru a extrage datele din aliasul său avatar2. Dar dacă ne-am îmbina într-un alt set de câmpuri care includeau un avatar de dimensiune medie și completă, decodoarele lor Json ar încerca să obțină acele date dintr-un câmp numit avatar și avatar2 atunci când ar trebui să le caute sub avatar3 și avatar4. Aș putea rezolva această problemă prin reproiectarea codului pentru a genera deserializatoarele (Decodificatoare Json) în ultimul minut, dar acest lucru ar însemna crearea unei structuri de date personalizate, astfel încât să amân acest lucru. Ar adăuga mult mai multă complexitate și puncte de eșec în care aș putea introduce o eroare.

Căutând abordarea simplă

Mi-a fost foarte teamă de ideea de a face acest redesign. Sigur, a fost realizabil, dar nu mi s-a părut corect să introduci atât de multă complexitate pentru a rezolva o problemă atât de simplă. Am vrut să mă gândesc la designul bibliotecii, atât în ​​ceea ce privește API-ul public, cât și implementarea sa la nivel scăzut. Este ceva ce fac des în Elm. Dacă aș rezolva această problemă într-un alt limbaj precum TypeScript, ar fi ușor să adăugați o stare globală pentru a ține contorizarea de câte ori au fost create câmpuri cu un anumit nume. Din experiența anterioară cu această abordare, acest lucru ar duce la rapoarte de erori, teste cu false pozitive și negative și alte persoane care ating codul și nu înțeleg de unde vine acest comportament ciudat sau ce trebuie să facă pentru a-l păstra în mod corespunzător. Aceasta este problema cu starea implicită. Este foarte util să rezolvi problemele rapid. Dar, pe termen lung, începi să simți costul real. Statul este într-adevăr lucrul care face codul dificil de raționat și schimbat, iar citirea/modificarea codului este ceea ce ne petrecem majoritatea orelor de codare făcând. Deci, dacă putem minimiza starea, este un câștig uriaș!

Aș fi putut crea o stare în aplicația mea Elm pentru a rezolva această problemă, dar ar trebui să fie explicită și, prin urmare, este mai obositoare decât în ​​alte limbi. Există, desigur, o mulțime de locuri în care aveți nevoie de stare într-o aplicație Elm, dar sunteți mai în acord cu cât de multă stare aveți și câte locuri depind de acea stare din cauza explicității sale. În cele din urmă, mi s-a părut că acesta este un compromis excelent, pe cât de frustrant poate fi atunci când vrei să „doar că ceva funcționează rapid”. Când vine vorba de mentenanță, această abordare explicită plătește chiar și pe termen scurt.

Soluția mai simplă

În cele din urmă, mi-a venit ideea de a folosi aliasuri de câmp bazate pe hash:

{
  currentUser {
    avatarABC: avatar(size: 12)
    avatarXYZ: avatar(size: 48)
  }
}

Prin generarea unui hash unic bazat pe argumentele câmpului (imaginați-vă că ABC este o reprezentare hash a (size: 12)), aveți garanția că veți evita interogările ambigue. Și nu trebuie să știți ce câmpuri sunt folosite în interogarea înconjurătoare! Această soluție este mult mai ușoară și mai puțin predispusă la erori, deoarece tot ce aveți nevoie este un anumit câmp pentru a-i determina aliasul. Nu este nevoie să vă uitați la frații săi sau să vă faceți griji dacă va avea mai mulți frați mai târziu. Puteți citi mai multe despre detaliile implementării finale pe „această problemă Github”.

Concluzia

Deci de ce este asta atât de important? Una dintre calitățile cheie pe care le caut într-un limbaj, într-un cadru, într-o tehnică de codare etc., este dacă are tendința spre simplitate. De exemplu, dacă refactorizarea cu un limbaj sau cadru este plictisitoare sau predispusă la erori, o veți face mai puțin. Dacă se simte rezistent la glonț și ușor ca în Elm, atunci codul va avea tendința de a fi mai ușor de întreținut. De-a lungul timpului, bazele de cod Elm tind puternic spre a avea o stare minimă. Și mai puțină stare înseamnă mai puține piese în mișcare, modificări mai puțin costisitoare și mai puține erori.

Ofer „discuții introductive gratuite Elm”. Trimite-mi un mesaj dacă echipa ta este curioasă să afle mai multe despre Elm! Sau distribuiți un comentariu dacă ați avut o experiență similară cu un limbaj sau cadru care vă ghidează către o soluție mai simplă.