Am'm a Sr. front-end dev, codificare în Babel ES6. O parte din aplicația noastră face un apel API, și se bazează pe modelul de date ne întoarcem de apel API, anumite forme trebuie să fie completat.
Aceste forme sunt stocate într-o listă dublu-legate (dacă back-end spune că unele dintre aceste date este invalid, putem ajunge rapid la utilizator înapoi la o pagină-au dat peste cap și apoi a le obține înapoi la țintă, pur și simplu prin modificarea listei.)
Oricum, nu's o grămadă de funcții folosite pentru a adăuga pagini, și am'm întrebam dacă am'm a fi prea inteligent. Aceasta este doar o prezentare generală de bază - cu algoritmul este mult mai complex, cu tone de diferite pagini și tipuri de pagini, dar acest lucru'll vă dau un exemplu.
Acest lucru este cum, cred eu, un programator novice s-ar descurca.
export const addPages = (apiData) => {
let pagesList = new PagesList();
if(apiData.pages.foo){
pagesList.add('foo', apiData.pages.foo){
}
if (apiData.pages.arrayOfBars){
let bars = apiData.pages.arrayOfBars;
bars.forEach((bar) => {
pagesList.add(bar.name, bar.data);
})
}
if (apiData.pages.customBazes) {
let bazes = apiData.pages.customBazes;
bazes.forEach((baz) => {
pagesList.add(customBazParser(baz));
})
}
return pagesList;
}
Acum, în scopul de a fi mai testabile, am'am luat toate aceste declarații dacă și le-a făcut separat, de sine stătătoare funcții, și apoi am harta peste ele.
Acum, testabile este un lucru, dar așa este ușor de citit și mă întreb dacă am'm a face lucrurile mai ușor de citit aici.
// file: '../util/functor.js'
export const Identity = (x) => ({
value: x,
map: (f) => Identity(f(x)),
})
// file 'addPages.js'
import { Identity } from '../util/functor';
export const parseFoo = (data) => (list) => {
list.add('foo', data);
}
export const parseBar = (data) => (list) => {
data.forEach((bar) => {
list.add(bar.name, bar.data)
});
return list;
}
export const parseBaz = (data) => (list) => {
data.forEach((baz) => {
list.add(customBazParser(baz));
})
return list;
}
export const addPages = (apiData) => {
let pagesList = new PagesList();
let { foo, arrayOfBars: bars, customBazes: bazes } = apiData.pages;
let pages = Identity(pagesList);
return pages.map(foo ? parseFoo(foo) : x => x)
.map(bars ? parseBar(bars) : x => x)
.map(bazes ? parseBaz(bazes) : x => x)
.value
}
Aici's îngrijorarea mea. Să mi partea de jos este mult mai organizat. Codul în sine este rupt în bucăți mai mici care sunt testabile în mod izolat. DAR eu'm gândesc: Dacă am avut de a citi ca un dezvoltator junior, neutilizate la astfel de concepte ca folosind Identitatea functori, curtau, sau ternare declarații, aș fi capabil de a înțelege chiar și ceea ce acesta din urmă soluție este de a face? Este mai bine să faci lucrurile "gresit, mai ușor" uneori?
În codul dvs., ați făcut mai multe modificări:
parseFoo()
funcții etc. este o eventual schimbare în bine.Una dintre cele mai confuze piese aici este modul de amestecare funcționale și imperativ de programare. Cu functor nu't într-adevăr transformarea datelor, utilizați-l pentru a trece o mutabil lista prin diverse funcții. Asta nu't părea ca un foarte util abstractizare, avem deja variabile pentru asta. Lucru pe care, eventual, trebuie să fi fost captată doar de parsare acel element dacă există – este încă acolo, în codul dumneavoastră în mod explicit, dar acum trebuie să ne gândim în jurul valorii de colț. De exemplu,'s oarecum non-evident că parseFoo(foo)
va reveni o funcție. JavaScript nu't au un tip static sistem pentru a vă anunța dacă acest lucru este legal, deci un astfel de cod este foarte predispus la erori, fără un nume mai bun (makeFooParser(foo)
?). Eu nu't vedea nici un beneficiu în această confuzie.
Ceea ce am'd aștept să văd în loc de:
if (foo) parseFoo(pages, foo);
if (bars) parseBar(pages, bars);
if (bazes) parseBaz(pages, bazes);
return pages;
Dar asta's nu este ideal, deoarece nu este clar de site-ul care elementele vor fi adăugate la lista pagini. Dacă în loc de parsare funcții sunt pur și întoarce-o (eventual gol), lista care putem adăuga în mod explicit la pagini, care ar putea fi mai bine:
pages.addAll(parseFoo(foo));
pages.addAll(parseBar(bars));
pages.addAll(parseBaz(bazes));
return pages;
Avantajul: logica despre ce să facă atunci când elementul este gol, a fost mutat în individ parsarea funcții. Dacă acest lucru nu este necesar, puteți introduce în continuare condiționale. La schimbarea de "pagini" lista este acum tras împreună într-o singură funcție, în loc de a răspândi pe mai multe telefoane. Evitarea non-locale mutații este o mult mai mare parte din programare funcțională decât abstracții cu nume amuzante cum ar fi Monadă
.
Deci da, codul a fost prea inteligent. Vă rugăm să se aplice inteligența ta nu pentru a scrie inteligent cod, dar pentru a găsi modalități de inteligent pentru a evita necesitatea pentru lipsă de inteligență. Cele mai bune modele don't uite de lux, dar uite evident pentru oricine le vede. Și bine abstracțiuni sunt acolo pentru a simplifica programarea, nu pentru a adăuga straturi suplimentare care trebuie să untangle în mintea mea în primul rând (aici, imaginind că functorul este echivalent cu o variabilă, și poate fi efectiv elided).
Vă rugăm: dacă aveți dubii, ține-ți cod simplu și prost (KISS principiu).
Dacă sunteți în dubiu, probabil că este prea inteligent! Cel de-al doilea exemplu prezintă accidentale complexitate cu expresii de genul foo ? parseFoo(foo) : x => x
, și în general, codul este mult mai complexă, ceea ce înseamnă că este mai greu să-l urmeze.
Pretinsa beneficia, pe care o puteți testa bucăți individual, ar putea fi realizat într-un mod mai simplu de a sparge în funcții individuale. Și în al doilea exemplu de cuplu altfel separate iterații, astfel încât veți obține de fapt, mai puțin izolare.
Oricare ar fi sentimentele tale despre stil funcțional, în general, acest lucru este în mod clar un exemplu în care o face codul mai complexe.
Am găsit un pic de un semnal de avertizare în care te asociezi simplă și directă cu codul "dezvoltatori novice". Acest lucru este periculos mentalitate. În experiența mea, este exact opusul: dezvoltatori Novice sunt predispuse la a fi prea complex și inteligent cod, deoarece este nevoie de mai multă experiență pentru a fi capabil de a vedea cele mai simple și mai clare soluție.
Sfatul împotriva "inteligent cod" nu este cu adevărat despre dacă este sau nu cod utilizează concepte avansate care un novice ar putea să nu înțeleagă. Mai degrabă este vorba de scrierea de cod care este mai complexe sau complicate decât este necesar. Acest lucru face codul mai greu de urmat pentru toată lumea*, începători și experți deopotrivă, și, probabil, de asemenea, pentru tine cateva luni pe linie.
Acest răspuns de-al meu vine un pic cam tarziu, dar eu încă mai doresc pentru a interveni în discuție. Doar pentru că ai're folosind cele mai recente ES6 tehnici sau folosind cele mai populare paradigmă de programare nu't înseamnă neapărat că codul este mai corect, sau ca junior's cod este greșit. Programare funcțională (sau orice altă tehnică) ar trebui să fie utilizat atunci când l's, de fapt, nevoie. Dacă încerci să găsești cea mai mică șansă de a se ghiftui mai recente tehnici de programare în fiecare problemă se va termina întotdeauna cu o soluție de inginerie.
Ia un pas înapoi, și să încerce să verbalizeze problema're încercarea de a rezolva pentru o secundă. În esență, tu vrei doar o funcție addPages
pentru a transforma diferite părți ale apiData
într-un set de perechi cheie-valoare, apoi adăugați-le pe toate într PagesList
.
Și dacă asta's tot acolo este să-l, de ce deranjez folosind funcția de identitate " cu " operatorul ternar, sau folosind
functor pentru intrare parsing? În plus, de ce crezi că l's o abordare corectă a solicita o programare funcțională` care cauzeaza efecte secundare (prin mutații lista)? De ce toate aceste lucruri, atunci când tot ce ai nevoie este doar asta:
const processFooPages = (foo) => foo ? [['foo', foo]] : [];
const processBarPages = (bar) => bar ? bar.map(page => [page.name, page.data]) : [];
const processBazPages = (baz) => baz ? baz.map(page => [page.id, page.content]) : [];
const addPages = (apiData) => {
const list = new PagesList();
const pages = [].concat(
processFooPages(apiData.pages.foo),
processBarPages(apiData.pages.arrayOfBars),
processBazPages(apiData.pages.customBazes)
);
pages.forEach(([pageName, pageContent]) => list.addPage(pageName, pageContent));
return list;
}
(a runnable jsfiddle [aici][1])
După cum puteți vedea, această abordare folosește încă programare funcțională, dar cu moderatie. De asemenea, rețineți că, deoarece toate 3 funcțiilor de transformare provoca efecte secundare de nici un fel, ele sunt foarte ușor pentru a testa. Codul în
addPages` este, de asemenea, banal și modest, care începători sau experți poate intelege doar cu o simpla privire.
Acum, comparați acest cod cu ce ai'am venit cu de mai sus, nu vezi diferenta? Fără îndoială, `programarea funcțională și ES6 sintaxe sunt puternice, dar dacă felie greșit problema cu aceste tehnici,'ll end up cu și mai nașpa cod.
Dacă tu nu't se grăbească în problemă, și aplicarea de tehnici potrivite în locurile potrivite, poti avea cod, care este funcțional în natură, în timp ce încă este foarte organizat și întreținut de către toți membrii echipei. Aceste caracteristici nu sunt reciproc exclusive.
Cel de-al doilea fragment nu este mai testabile decât prima. Ar fi destul de simplu pentru a configura toate testele necesare, fie pentru unul dintre cele două fragmente.
Diferența reală dintre cele două fragmente este inteligibilitatea. Pot să citesc primul fragment destul de repede și să înțeleagă ceea ce's întâmplă. Cel de-al doilea fragment, nu atât de mult. L's mult mai puțin intuitivă, precum și în mod substanțial mai mult.
Care face primul fragment mai ușor să se mențină, ceea ce este o calitate valoroasă de cod. Mi se pare foarte putin din valoare, în al doilea fragment.
TD;DR.
Analiză Detaliată
Claritate și Lizibilitate
Codul original este impresionant de clare și ușor de înțeles pentru orice nivel de programator. Este într-un stil familiar pentru toată lumea.
Lizibilitate este în mare măsură bazată pe familiaritate, nu unele calculul matematic de jetoane. IMO, la această etapă, în timp, ai prea mult ES6 în rescrie. Poate că în câțiva ani am'll schimba această parte a răspunsului meu. :-) BTW, îmi place, de asemenea, @b0nyb0y răspundă cât mai rezonabile și clare compromis.
Testabilitate
if(apiData.pages.foo){
pagesList.add('foo', apiData.pages.foo){
}
Presupunând că PagesList.add() a teste, care ar trebui, acest lucru este complet cod simplu și nu există nici un motiv evident pentru această secțiune nevoie specială de testare separate.
if (apiData.pages.arrayOfBars){
let bars = apiData.pages.arrayOfBars;
bars.forEach((bar) => {
pagesList.add(bar.name, bar.data);
})
}
Din nou, nu văd nici o nevoie imediată pentru orice speciale separate de testare din această secțiune. Dacă PagesList.add() are probleme neobișnuite cu null-uri sau duplicate sau alte intrări.
if (apiData.pages.customBazes) {
let bazes = apiData.pages.customBazes;
bazes.forEach((baz) => {
pagesList.add(customBazParser(baz));
})
}
Acest cod este, de asemenea, foarte simplu. Presupunând că customBazParser
este testat și nu't se întoarcă prea multe "speciale" rezultate. Deci, din nou, cu excepția cazului în care există situații dificile cu PagesList.add(), (de care nu ar putea fi la fel de am'm nu sunt familiarizați cu domeniul dvs.) eu nu't vedea de ce această secțiune nevoi speciale de testare.
În general, testarea întregului funcție ar trebui să funcționeze bine.
Disclaimer: Dacă există motive speciale pentru a testa toate cele 8 posibilități de trei dacă()
declarații, atunci da, se despart de teste. Sau, dacă PagesList.adaugă()
este sensibil, da, despărțit de teste.
Structura: Este în valoare de rupere în sus, în trei părți (cum ar fi Galia)
Aici ai cel mai bun argument. Personal, eu nu't cred codul original este "prea mult" (am'm nu o SRP fanatic). Dar, dacă au mai fost câteva dacă (apiData.pagini.bla)
secțiuni, apoi SRP ridicǎ de l's urâte și ar fi în valoare de divizare. Mai ales dacă este USCAT se aplică și funcții ar putea fi utilizate în alte locuri din cod.
Mea o sugestie
YMMV. Pentru a salva o linie de cod și de o anumită logică, s-ar putea combina daca și să într-o singură linie: de exemplu
let bars = apiData.pages.arrayOfBars || [];
bars.forEach((bar) => {
pagesList.add(bar.name, bar.data);
})
Acest lucru va eșua în cazul în apiData.pagini.arrayOfBars este un Număr sau Șir de caractere, dar și codul original. Și pentru mine este mai clară (și o suprasolicitat idiom).