Tuntud eelarvamused hoiab teid tagasi: on aeg võtta kasutusele noolefunktsioonid

“Ankur” - Näitleja212 - (CC BY-NC-ND 2.0)

Õpetan JavaScripti elatiseks. Hiljuti segasin oma õppekava ümber, et õpetada lokitud noolefunktsioone varem - paari esimese tunni jooksul. Liigutasin selle varem õppekavasse, kuna see on äärmiselt väärtuslik oskus ja õpilased saavad nooltega kurrida palju kiiremini, kui ma arvasin.

Kui nad saavad sellest aru ja saavad seda varem ära kasutada, miks mitte õpetada seda varem?

Märkus. Minu kursused pole mõeldud inimestele, kes pole kunagi varem koodirida puudutanud. Enamik õpilasi liituvad pärast vähemalt mõne kuu kodeerimist - iseseisvalt, alglaadimislaagris või ametialaselt. Olen aga näinud, et paljud nooremarendajad, kellel on vähe kogemusi või puuduvad kogemused, valivad need teemad kiiresti.

Olen näinud, et kamp üliõpilasi on ühe tunni kestva tunni jooksul tuttav kargastatud noolefunktsioonidega. (Kui olete programmi „Õppige JavaScripti koos Eric Elliottiga” liige, saate praegu vaadata 55-minutilist ES6 karri ja kompositsiooni õppetundi).

Nähes, kui kiiresti õpilased selle kätte saavad ja oma uut leitud karriõigust kasutama hakkavad, olen alati natuke üllatunud, kui postitan Twitterisse karritud noolefunktsioone ja Twitterverse reageerib nördimusega mõttest tekitada see “loetamatu” kood inimesed, kes peavad seda säilitama.

Esiteks lubage mul tuua teile näide sellest, millest me räägime. Esimest korda märkasin tagasilööki Twitteri vastus sellele funktsioonile:

const secret = msg => () => msg;

Olin šokeeritud, kui inimesed Twitteris süüdistasid mind inimeste segadusse ajamises. Kirjutasin selle funktsiooni, et näidata, kui lihtne on ES6-s kõvera funktsioone väljendada. See on lihtsaim praktiline rakendus ja sulgemise väljendus, mille JavaScriptis välja mõelda suudan. (Seotud: “Mis on sulgemine?”).

See on samaväärne järgmise funktsioonilausega:

const secret = function (msg) {
  tagastamise funktsioon () {
    tagasta msg;
  };
};

secret () on funktsioon, mis võtab sõnumi ja tagastab uue funktsiooni, mis tagastab sõnumi. Sulgemise eeliseks on sõnumi väärtuse fikseerimine ükskõik millisele väärtusele, mille salajasena sisestate ().

Kasutage seda järgmiselt:

const mySecret = salajane ('tere');
minu saladus(); // 'Tere'

Selgub, et „topeltnool“ on inimesed segaseks ajanud. Olen kindel, et see on tõsiasi:

Tuttaval on line-noolefunktsioonid kõige loetavam viis karritud funktsioonide väljendamiseks JavaScriptis.

Paljud inimesed on mulle väitnud, et pikemat vormi on lihtsam lugeda kui lühemat. Neil on osaliselt õigus, kuid enamasti on need valed. See on sõnavaesem ja selgem, kuid mitte hõlpsamini loetav - vähemalt mitte kellelegi, kes noolufunktsioone tunneb.

Vastuväited, mida ma Twitteris nägin, ei olnud lihtsalt õpilaste sujuva õppimiskogemuse taustal. Minu kogemuse kohaselt võtavad õpilased kaardil noolefunktsioone nagu kalad vette. Mõne päeva jooksul pärast nende õppimist on nad nooltega ühes. Nad viskavad neid vaevata igasuguste kodeerimisprobleemide lahendamiseks.

Ma ei näe ühtegi märki selle kohta, et noolefunktsioone on neil raske õppida, lugeda või mõista - kui nad on mõne tunni kestnud õppetunni ja õppesessiooni jooksul teinud algse investeeringu nende õppimisse.

Nad lugesid hõlpsasti läbi neid varem nähtud ruume noolefunktsioone ja selgitasid mulle, mis toimub. Nad kirjutavad loomulikult oma, kui esitan neile väljakutse.

Teisisõnu, niipea, kui nad on tuttavaks saanud kaardunud noolefunktsioonide nägemisega, pole neil nendega probleeme. Nad loevad neid sama lihtsalt kui seda lauset lugedes - ja nende arusaam kajastub palju lihtsamas koodis, milles on vähem vigu.

Miks mõned inimesed arvavad, et pärandfunktsioonide väljendid on lugemiseks hõlpsamad

Tundlikkuse kallutatus on inimese mõõdetav kognitiivne kallutatus, mis paneb meid tegema hävitavaid otsuseid hoolimata sellest, et oleme teadlik paremast võimalusest. Me kasutame samu vanu mustreid vaatamata paremate mustrite tundmisele mugavuse ja harjumuse huvides.

Suurepärasest raamatust “Undoing Project: a Friendship, mis muutis meie meelt” saate tuttavate eelarvamuste (ja ka paljude muude viiside abil end lollitada) kohta õppida palju rohkem. Seda raamatut peaks lugema iga tarkvaraarendaja, sest see julgustab teid kriitilisemalt mõtlema ja oma eeldusi proovile panema, et mitte langeda mitmesugustesse kognitiivsetesse lõksudesse - ja ka lugu sellest, kuidas need kognitiivsed lõksud avastati, on tõesti hea .

Pärandfunktsioonilaused põhjustavad tõenäoliselt teie koodis vigu

Täna kirjutasin kirutud noolefunktsiooni ümber ES6-st ES5-le, et saaksin selle avaldada avatud lähtekoodiga moodulina, mida inimesed saaksid kasutada vanades brauserites ilma ümberistutamata. ES5 versioon šokeeris mind.

ES6 versioon oli lihtne, lühike ja elegantne - ainult 4 rida.

Arvasin kindlalt, et see funktsioon tõestas Twitterile, et noolefunktsioonid on paremad ja inimesed peaksid loobuma oma pärandfunktsioonidest nagu halb komme.

Nii et ma säutsusin:

Siin on funktsioonide tekst juhuks, kui pilt ei tööta teie jaoks:

// nooltega karritud
const composeMixins = (... mixins) => (
  näiteks = {},
  mix = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => sega (... segab) (näiteks);
// vs ES5-stiil
var composeMixins = function () {
  var mixins = [] .slice.call (argumendid);
  tagastamise funktsioon (eksemplar, segu) {
    if (! esinemisjuhtum) näiteks = {};
    if (! sega) {
      sega = funktsioon () {
        var fns = [] .slice.call (argumendid);
        tagastamise funktsioon (x) {
          return fns.reduce (funktsioon (acc, fn) {
            tagasi fn (acc);
          }, x);
        };
      };
    }
    return mix.apply (null, mixins) (näiteks);
  };
};

Vaadeldav funktsioon on lihtne ümbris toru ümber (), tavaline funktsionaalne programmeerimisutiliit, mida tavaliselt kasutatakse funktsioonide komponeerimiseks. Toru () funktsioon eksisteerib lodash kui lodash / flow, Ramdas nagu R.pipe () ja sellel on isegi oma operaator mitmes funktsionaalses programmeerimiskeeles.

See peaks olema tuttav kõigile, kes tunnevad funktsionaalset programmeerimist. Nagu ka selle peamine sõltuvus: vähendage.

Sel juhul kasutatakse seda funktsionaalsete segude koostamiseks, kuid see on ebaoluline detail (ja terve muu ajaveebi postitus). Siin on olulised üksikasjad:

Funktsioon võtab suvalise arvu funktsionaalseid miksiine ja tagastab funktsiooni, mis rakendab neid üksteise järel torujuhtmes - nagu monteerimisliin. Iga funktsionaalne miksin võtab eksemplari sisendina ja lindistab sellele mõned asjad, enne kui edastab selle järgmisele torujuhtme funktsioonile.

Kui jätate näiteks eksemplari, luuakse teile uus objekt.

Mõnikord võiksime segude komponeerida erinevalt. Näiteks võiksite eelistada tähtsuse järjekorra muutmiseks pipe () asemel kompositsiooni ().

Kui te ei pea käitumist kohandama, jätate vaikimisi rahule ja saate tavapärase käitumise ().

Lihtsalt faktid

Arvamused loetavuse kõrval, siin on selle näite objektiivsed faktid:

  • Mul on mitmeaastane kogemus nii ES5 kui ka ES6 funktsioonide avaldiste, noolte või muul viisil. Tundlikkuse kallutatus ei ole nendes andmetes muutuja.
  • Kirjutasin ES6 versiooni mõne sekundiga. Selles ei olnud ühtegi viga (millest olen teadlik - see läbib kõik oma ühiku testid).
  • ES5 versiooni kirjutamine võttis mul mitu minutit. Vähemalt suurusjärku rohkem aega. Minutid vs sekundid. Kaotasin oma koha funktsiooni taandes kaks korda. Kirjutasin 3 viga, mida pidin kõik siluma ja parandama. Neist kaks pidin kasutama juhtnuppu console.log (), et aru saada, mis toimub.
  • ES6 versioon on 4 koodirida.
  • ES5 versioon on 21 rida pikk (17 sisaldab tegelikult koodi).
  • Vaatamata tüütavale paljususele, kaotab ES5 versioon tegelikult osa teabe usaldusväärsusest, mis ES6 versioonis saadaval on. See on palju pikem, kuid suhtleb vähem, loe lähemalt.
  • ES6 versioon sisaldab 2 funktsiooniparameetrite tabelit. ES5 versioon jätab levikud vahele ja kasutab selle asemel kaudsete argumentide objekti, mis kahjustab funktsiooni allkirja loetavust (truudusastme alandamine 1).
  • ES6 versioon määratleb funktsiooni allkirjastamise segu vaikeseade, nii et näete selgelt, et see on parameetri väärtus. ES5 versioon varjab selle detaili ja peidab selle hoopis funktsiooni korpuses sügavale. (truudusastme alandamine 2).
  • ES6 versioonil on ainult kaks taandetaset, mis aitab selgitada struktuuri, kuidas seda tuleks lugeda. ES5 versioonil on 6 ja pesastustasandid pigem varjavad kui funktsiooni struktuuri loetavust (varjatud versioon 3).

ES5 versioonis hõivab toru () suurema osa funktsioonikehast - nii palju, et selle sisemine määratlemine on pisut hull. ES5 versiooni loetavaks muutmiseks tuleb see tõesti eraldada eraldi funktsiooniks:

var pipe = function () {
  var fns = [] .slice.call (argumendid);
  tagastamise funktsioon (x) {
    return fns.reduce (funktsioon (acc, fn) {
      tagasi fn (acc);
    }, x);
  };
};
var composeMixins = function () {
  var mixins = [] .slice.call (argumendid);
  tagastamise funktsioon (eksemplar, segu) {
    if (! esinemisjuhtum) näiteks = {};
    if (! mix) mix = pipe;
    return mix.apply (null, mixins) (näiteks);
  };
};

See tundub minu jaoks selgelt loetavam ja arusaadavam.

Vaatame, mis juhtub, kui rakendame sama loetavuse „optimeerimist“ ES6 versioonile:

const pipe = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x);
const composeMixins = (... mixins) => (
  näiteks = {},
  sega = toru
) => sega (... segab) (näiteks);

Nagu ES5 optimeerimine, on ka see versioon sõnavabam (see lisab uue muutuja, mida seal varem polnud). Erinevalt ES5 versioonist pole see versioon pärast torude määratluse kokkuvõtmist oluliselt loetavam. Lõppude lõpuks oli sellele juba funktsiooni signatuuris selgelt määratud muutuja nimi: mix.

Segu määratlus sisaldus juba omal real, mistõttu on ebatõenäoline, et lugejad läheksid segadusse, kus see lõpeb ja ülejäänud funktsioon jätkub.

Nüüd on meil 1 asemel 2 sama muutujat esindavat muutujat. Kas oleme palju juurde saanud? Pole muidugi mitte.

Miks on ES5 versioon ilmselgelt parem, kui sama funktsiooni kokku tõmmata?

Sest ES5 versioon on ilmselgelt keerukam. Selle keerukuse allikas on selle teema tuum. Ma väidan, et keerukuse allikaks on süntaksimüra ja süntaksimüra varjab funktsiooni tähendust, mitte ei aita.

Lülitame käike ja kõrvaldame veel mõned muutujad. Kasutagem mõlema näite korral ES6 ja võrrelgem ainult noolefunktsioone vs pärandfunktsioonide avaldisi:

var composeMixins = funktsioon (... mixins) {
  tagastamise funktsioon (
    näiteks = {},
    mix = function (... fns) {
      tagastamise funktsioon (x) {
        return fns.reduce (funktsioon (acc, fn) {
          tagasi fn (acc);
        }, x);
      };
    }
  ) {
    tagastatav segu (... seguinid) (näiteks);
  };
};

See tundub minu jaoks märkimisväärselt loetavam. Ainus, mida me muutisime, on see, et kasutame puhke- ja vaikesüntakside eeliseid. Muidugi peate selle versiooni loetavuse huvides tundma puhke- ja vaikesüntaksit, kuid isegi kui te seda ei tee, on minu arvates ilmne, et see versioon on endiselt vähem täis.

See aitas palju, kuid mulle on siiski selge, et see versioon on endiselt piisavalt täis, et toru () enda jaoks funktsiooniks võtmine aitaks ilmselgelt:

const pipe = funktsioon (... fns) {
  tagastamise funktsioon (x) {
    return fns.reduce (funktsioon (acc, fn) {
      tagasi fn (acc);
    }, x);
  };
};
// Pärandfunktsioonide avaldised
const composeMixins = funktsioon (... mixins) {
  tagastamise funktsioon (
    näiteks = {},
    sega = toru
  ) {
    tagastatav segu (... seguinid) (näiteks);
  };
};

See on parem, eks? Nüüd, kui seguülesanne võtab ainult ühe rea, on funktsiooni struktuur palju selgem - kuid minu maitse jaoks on süntaksimüra endiselt liiga palju. Rakenduses composeMixins () pole mulle lühidalt selge, kus üks funktsioon lõpeb ja teine ​​algab.

Funktsioonide kehade väljakutsumise asemel näib see funktsiooni märksõna visuaalselt sulanduvat selle ümbritsevate identifikaatoritega. Minu funktsioonis on peidus funktsioone! Kus parameetri allkiri lõpeb ja funktsiooni keha algab? Ma suudan seda välja mõelda, kui vaatan tähelepanelikult, kuid see pole minu jaoks visuaalselt ilmne.

Mis oleks, kui saaksime funktsiooni märksõnast lahti saada ja tagasiväärtused välja kutsuda, osutades neile visuaalselt suure rasvaga noolega =>, selle asemel, et kirjutada tagasisõna, mis sulandub ümbritsevate tunnustega?

Selgub, saame ja siin näeb välja järgmine:

const composeMixins = (... mixins) => (
  näiteks = {},
  sega = toru
) => sega (... segab) (näiteks);

Nüüd peaks olema selge, mis toimub. composeMixins () on funktsioon, mis võtab suvalise arvu segusid ja tagastab funktsiooni, mis võtab kaks valikulist parameetrit, näiteks ja segu. See tagastab torustiku esinemise tulemuse läbi komponeeritud segude.

Veel üks asi ... kui rakendame sama optimeerimist ka torule (), muutub see võluväel ühevooderduseks:

const pipe = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x);

Kui see määratlus on üherealine, pole selle enda funktsioonile eraldamise eelis nii selge. Pidage meeles, et see funktsioon eksisteerib utiliidina Lodashis, Ramdas ja hunnikus teistes raamatukogudes, kuid kas see on tõesti mõtet teise raamatukogu importimisel lisakulusid tasuda?

Kas seda tasub isegi enda ridadesse tõmmata? Arvatavasti. Need on tegelikult kaks erinevat funktsiooni ja nende eraldamine teeb selle selgemaks.

Teisest küljest selgitab parameetri allkirja vaatamine selle tüübi ja kasutuse ootusi. Selle loomisel reaalajas toimib järgmine:

const composeMixins = (... mixins) => (
  näiteks = {},
  mix = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => sega (... segab) (näiteks);

Nüüd oleme tagasi algse funktsiooni juurde. Nii ei jätnud me mingit tähendust kõrvale. Tegelikult lisasime oma parameetrite ja vaikimisi väärtuste rivisse kuulutamise kaudu teavet selle kohta, kuidas funktsiooni kasutatakse ja millised võiksid parameetrite väärtused välja näha.

Kogu see lisakood ES5 versioonis oli lihtsalt müra. Süntaksimüra. Sellel ei olnud mingit kasulikku eesmärki, välja arvatud inimeste aklimatiseerimine, kes pole kursis noolte funktsioonidega.

Kui olete karrilise noolefunktsiooniga piisavalt tutvunud, peaks olema selge, et algne versioon on paremini loetav, kuna seal on palju vähem süntaksi, millesse eksida.

See on ka vähem veaohtlik, kuna vigade peitmiseks on palju vähem pinda.

Ma kahtlustan, et pärandfunktsioonides on palju vigu, mis leitakse ja kõrvaldatakse, kui peaksite noolefunktsioonidele üle minema.

Samuti loodan, et teie meeskond muutub märkimisväärselt produktiivsemaks, kui õpiksite omaks võtma ja eelistama rohkem ES6-s saadaolevat lühikest süntaksi.

Ehkki on tõsi, et mõnikord on asju lihtsam mõista, kui neid selgesõnaliselt öelda, on tõsi ka see, et üldiselt on parem vähem kood.

Kui vähem koodi suudab sama asja teostada ja suhelda rohkem ilma mingit tähendust ohverdamata, on see objektiivselt parem.

Erinevuse tundmise võti on tähendus. Kui mõni muu kood ei lisa täiendavat tähendust, siis seda koodi ei tohiks olla. See mõiste on nii põhiline, see on looduskeele üldtuntud stiilijuhis.

Sama stiili juhis kehtib ka lähtekoodi kohta. Võtke see omaks ja teie kood on parem.

Päeva lõpus pimedas tuli. Vastusena veel ühele säutsule, mis ütles, et ES6 versioon on vähem loetav:

Aeg tutvuda ES6-ga, curryerimise ja funktsioonide kompositsiooniga.

Järgmised sammud

„Õppige JavaScripti koos Eric Elliottiga” liikmed saavad praegu vaadata 55-minutilist ES6 karri ja kompositsiooni õppetundi.

Kui te ei ole liige, siis jääte ilma!

Eric Elliott on raamatute “Programmeerimise JavaScripti rakendused” (O’Reilly) ja “Õppige JavaScripti koos Eric Elliottiga” autor. Ta on kaasa aidanud Adobe Systems, Zumba Fitnessi, The Wall Street Journali, ESPNi, BBC tarkvarakogemustele ja tippsalvestusartistidele, sealhulgas Usher, Frank Ocean, Metallica ja paljudele teistele.

Ta veedab suurema osa ajast San Francisco lahe piirkonnas maailma ilusaima naisega.