Pazīstamības aizspriedumi jūs kavē: ir pienācis laiks izmantot bultu funkcijas

“Enkurs” - Aktieris212 - (CC BY-NC-ND 2.0)

Es mācu JavaScript iztikai. Nesen esmu mainījies ap savu mācību programmu, lai ātrāk - dažās pirmajās stundās - iemācītu aizrautās bultiņas funkcijas. Es to pārvietoju agrāk mācību programmā, jo tā ir ārkārtīgi vērtīga prasme, un studenti daudz ātrāk uztver kursēšanu ar bultiņām, nekā man likās.

Kāpēc viņi to var saprast un izmantot agrāk, kāpēc gan to nemācīt agrāk?

Piezīme: Mani kursi nav paredzēti cilvēkiem, kuri nekad nav pieskārušies koda rindiņai. Lielākā daļa studentu pievienojas pēc tam, kad vismaz dažus mēnešus ir pavadījuši kodēšanu - paši, bootcamp vai profesionāli. Tomēr esmu redzējis, ka daudzi jaunākie izstrādātāji ar nelielu pieredzi vai bez pieredzes ātri izvēlas šīs tēmas.

Esmu redzējis, ka liels skaits studentu vienas stundas laikā nodarbojas ar praktiskām zināšanām par kāroto bultu funkcijām. (Ja esat “Uzziniet JavaScript kopā ar Ēriku Eliotu” dalībnieks, šobrīd varat skatīties 55 minūšu ES6 karija un kompozīcijas nodarbību).

Redzot, cik ātri studenti to paņem un sāk izmantot savas jaunatklātās karija spējas, es vienmēr esmu mazliet pārsteigts, kad čivināt ievietoju kāroto bultu funkcijas, un Twitterverse reaģē ar sašutumu par domu radīt šo “neizlasāmo” kodu cilvēki, kuriem tas būs jāuztur.

Pirmkārt, ļaujiet man sniegt jums piemēru, par ko mēs runājam. Pirmo reizi, kad pamanīju atspēkojumu, bija Twitter reakcija uz šo funkciju:

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

Es biju satriekts, kad Twitter tīklā cilvēki apsūdzēja mani centienos sajaukt cilvēkus. Es uzrakstīju šo funkciju, lai parādītu, cik viegli ir izteikt curied funkcijas ES6. Tas ir vienkāršākais praktiskais pielietojums un noslēguma izpausme, ko es domāju par JavaScript. (Saistīts: “Kas ir slēgšana?”).

Tas ir līdzvērtīgs šādam funkcijas izteicienam:

const secret = function (msg) {
  atgriešanas funkcija () {
    atgriezt msg;
  };
};

noslēpums () ir funkcija, kas ņem ziņojumu un atgriež jaunu funkciju, kas atgriež ziņojumu. Lai aizvērtu msg vērtību jebkurai vērtībai, kuru jūs noslēpjat (), tiek izmantota slēgšana.

To izmantojat šādi:

const mySecret = slepens ('hi');
mySecret (); // 'Sveiki'

Izrādās, “dubultā bulta” ir tā, kas mulsina cilvēkus. Esmu pārliecināts, ka tas ir fakts:

Pazīstot, bulttaustiņu funkcijas ir lasāmākais veids, kā JavaScript izteikt kārotās funkcijas.

Daudzi cilvēki man ir iebilduši, ka garāko veidlapu ir vieglāk lasīt nekā īsāku. Viņiem daļēji ir taisnība, bet lielākoties nepareizi. Tas ir izteiksmīgāks un skaidrāks, bet nav vieglāk lasāms - vismaz ne kādam, kurš pārzina bultu funkcijas.

Iebildumi, kurus es redzēju Twitter, vienkārši nebija tie, kas ļāva vienmērīgi mācīties maniem studentiem. Pēc manas pieredzes studenti uztver bultu funkcijas, piemēram, zivis ņem ūdeni. Dienu laikā pēc to apgūšanas viņi ir viens ar bultiņām. Viņi pieliek tos bez piepūles, lai risinātu visa veida kodēšanas problēmas.

Es neredzu nekādas pazīmes, ka bultu funkcijas viņiem būtu “grūti” iemācīties, lasīt vai saprast - ja viņi jau sākotnēji ir veikuši ieguldījumus to apgūšanā dažu 1 stundu stundu un mācību sesiju laikā.

Viņi viegli nolasa kārotās bultu funkcijas, kuras vēl nekad nav redzējuši, un man paskaidro, kas notiek. Viņi, protams, raksta paši, kad es viņiem uzaicinu.

Citiem vārdiem sakot, tiklīdz viņi iepazīstas ar kreiso bultu funkciju redzēšanu, viņiem nav problēmu ar viņiem. Viņi tos lasa tikpat viegli, cik jūs lasāt šo teikumu - un viņu izpratne ir atspoguļota daudz vienkāršākā kodā ar mazāk kļūdu.

Kāpēc daži cilvēki domā, ka mantotās funkciju izpausmes izskatās vieglāk lasāmas

Iepazīšanās ar aizspriedumiem ir mērāma cilvēka kognitīvā novirze, kas liek mums pieņemt pašiznīcinošus lēmumus, neraugoties uz labāka varianta apzināšanos. Mēs turpinām izmantot tos pašus vecos modeļus, neskatoties uz to, ka zinām par labākiem modeļiem, kas nav ērti un ieradumi.

Jūs varat uzzināt daudz vairāk par aizspriedumiem par ģimenes pazīmēm (un daudziem citiem veidiem, kā sevi apmānīt) no izcilās grāmatas “Atsaucošais projekts: draudzība, kas mainīja mūsu domas”. Šī grāmata būtu jālasa katram programmatūras izstrādātājam, jo ​​tā mudina kritiskāk domāt un pārbaudīt savus pieņēmumus, lai neiekristu dažādos izziņas slazdos - un arī stāsts par to, kā šie izziņas slazdi tika atklāti, ir tiešām labs .

Mantoto funkciju izteicieni, iespējams, rada kļūdas jūsu kodā

Šodien es pārrakstīju kāroto bultu funkciju no ES6 uz ES5, lai es varētu to publicēt kā atvērtā pirmkoda moduli, kuru cilvēki varēja izmantot vecos pārlūkos bez transpilācijas. ES5 versija mani šokēja.

ES6 versija bija vienkārša, īsa un eleganta - tikai 4 līnijas.

Es droši domāju, ka šī bija funkcija, kas pierādīs Twitter, ka bultas funkcijas ir augstākas, un ka cilvēkiem vajadzētu atteikties no savām mantotajām funkcijām, piemēram, sliktā ieraduma, kāds viņi ir.

Tāpēc es tweetu:

Tālāk ir parādīts funkciju teksts, ja attēls jums nedarbojas:

// aizrauts ar bultām
const composeMixins = (... mixins) => (
  instance = {},
  mix = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => sajaukt (... mixins) (piemērs);
// vs ES5 stils
var composeMixins = function () {
  var mixins = [] .slice.call (argumenti);
  atgriešanas funkcija (piemēram, sajaukšana) {
    if (! instance) instance = {};
    ja (! sajauc) {
      samaisa = funkcija () {
        var fns = [] .slice.call (argumenti);
        atgriešanas funkcija (x) {
          atgriezt fns.reduce (funkcija (acc, fn) {
            atgriešanās fn (acc);
          }, x);
        };
      };
    }
    atgriezt mix.apply (null, mixins) (instance);
  };
};

Attiecīgā funkcija ir vienkāršs iesaiņojums ap cauruli (), standarta funkcionālās programmēšanas utilīta, ko parasti izmanto funkciju sastādīšanai. Caurules () funkcija pastāv lodash kā lodash / flow, Ramda kā R.pipe (), un tai pat ir savs operators vairākās funkcionālās programmēšanas valodās.

Tam vajadzētu būt pazīstamam visiem, kas pārzina funkcionālo programmēšanu. Kā vajadzētu tā galvenajai atkarībai: samaziniet.

Šajā gadījumā tas tiek izmantots, lai sacerētu funkcionālos sajaukumus, taču tā nav būtiska detaļa (un viss cits emuāra ieraksts). Šeit ir svarīga informācija:

Funkcija uzņem neierobežotu skaitu funkcionālu maisījumu un atgriež funkciju, kas tos pēc kārtas izmanto cauruļvadā - piemēram, montāžas līnijā. Katrs funkcionāls maisījums lieto gadījumu kā ievadi un iesprauž tajā dažas lietas, pirms to nodod nākamajai funkcijai cauruļvadā.

Ja izlaižat gadījumu, jums tiek izveidots jauns objekts.

Dažreiz mēs varētu vēlēties komponēt maisījumus atšķirīgi. Piemēram, iespējams, vēlēsities nodot compose (), nevis pipe (), lai mainītu prioritātes secību.

Ja jums nav jāpielāgo uzvedība, jūs vienkārši atstājat noklusējuma iestatījumus un saņemat parastu darbību ().

Tikai fakti

Atzinumi par lasāmību malā ir objektīvi fakti, kas attiecas uz šo piemēru:

  • Man ir vairāku gadu pieredze gan ar ES5, gan ar ES6 funkciju izpausmēm, bultiņām vai kā citādi. Šajos datos pazīstamības aizspriedumi nav mainīgi.
  • Es uzrakstīju ES6 versiju pēc dažām sekundēm. Tajā nebija nulles kļūdu (par ko es esmu informēts - tas iztur visus savus vienības testus).
  • ES5 versijas uzrakstīšana man prasīja vairākas minūtes. Vismaz vairāk nekā vienu reizi vairāk laika. Minūtes vs sekundes. Divreiz zaudēju vietu funkciju ievilkumos. Es uzrakstīju 3 kļūdas, kuras visas man vajadzēja atkļūdot un labot. Divas no kurām man nācās ķerties pie console.log (), lai izdomātu, kas notiek.
  • ES6 versija ir 4 koda rindiņas.
  • ES5 versija ir 21 rindiņa gara (17 faktiski satur kodu).
  • Neskatoties uz garlaicīgo liekulību, ES5 versija faktiski zaudē daļu informācijas ticamības, kas ir pieejama ES6 versijā. Tas ir daudz garāks, bet sazinās mazāk, lasiet sīkāk.
  • ES6 versijā ir 2 funkciju parametru izklājumi. ES5 versijā netiek izlaistas izplatības, un tā vietā tiek izmantots netiešo argumentu objekts, kas apgrūtina funkcijas paraksta lasāmību (uzticamības 1. pakāpe).
  • ES6 versijā funkcijas parakstā ir definēts noklusējuma sajaukums, lai jūs varētu skaidri redzēt, ka tā ir parametra vērtība. ES5 versija aizsedz šo detaļu un tā vietā paslēpj to dziļi funkcijas pamatfunkcijā. (uzticības pakāpe pazemināta 2).
  • ES6 versijai ir tikai 2 atkāpes līmeņi, kas palīdz noskaidrot tās lasīšanas struktūru. ES5 versijai ir 6, un ligzdošanas līmeņi drīzāk aizēno, nevis veicina funkcijas struktūras lasāmību (ticamības pakāpes pazemināšana 3).

ES5 versijā caurule () aizņem lielāko daļu funkcionālā korpusa - tik daudz, ka to definēt inline ir mazliet neprātīgi. Tas patiešām ir jāizdala atsevišķā funkcijā, lai ES5 versija būtu salasāma:

var pipe = function () {
  var fns = [] .slice.call (argumenti);
  atgriešanas funkcija (x) {
    atgriezt fns.reduce (funkcija (acc, fn) {
      atgriešanās fn (acc);
    }, x);
  };
};
var composeMixins = function () {
  var mixins = [] .slice.call (argumenti);
  atgriešanas funkcija (piemēram, sajaukšana) {
    if (! instance) instance = {};
    if (! mix) mix = pipe;
    atgriezt mix.apply (null, mixins) (instance);
  };
};

Tas man šķiet nepārprotami lasāmāks un saprotamāks.

Paskatīsimies, kas notiek, ja ES6 versijai piemērojam tādu pašu lasāmības “optimizāciju”:

const pipe = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x);
const composeMixins = (... mixins) => (
  instance = {},
  samaisa = caurule
) => sajaukt (... mixins) (piemērs);

Tāpat kā ES5 optimizēšana, šī versija ir daudz izteiktāka (tā pievieno jaunu mainīgo, kura iepriekš nebija). Atšķirībā no ES5 versijas, pēc versijas par caurules definīciju šī versija nav ievērojami lasāmāka. Galu galā tam jau bija mainīgais nosaukums, kas tam skaidri piešķirts funkcijas parakstā: sajaukt.

Maisījuma definīcija jau bija ietverta savā rindiņā, tāpēc lasītājiem ir maz ticams, ka viņi varētu sajaukt to, kur tas beidzas, un pārējā funkcija turpinās.

Tagad mums ir 2 mainīgie, kas apzīmē vienu un to pašu, nevis 1. Vai mēs esam ieguvuši ļoti daudz? Acīmredzot nē.

Kāpēc ES5 versija acīmredzami ir labāka ar to pašu funkciju?

Jo ES5 versija acīmredzami ir sarežģītāka. Šīs sarežģītības avots ir šīs lietas būtība. Es apgalvoju, ka sarežģītības avots ir sintakse troksnis un sintakse troksnis aizēno funkcijas nozīmi, nevis palīdz.

Pāriesim pārnesumus un novērsīsim vēl dažus mainīgos lielumus. Izmantosim ES6 abiem piemēriem un salīdzināsim tikai bultas funkcijas salīdzinājumā ar mantoto funkciju izteicieniem:

var composeMixins = funkcija (... mixins) {
  atgriešanas funkcija (
    instance = {},
    mix = funkcija (... fns) {
      atgriešanas funkcija (x) {
        atgriezt fns.reduce (funkcija (acc, fn) {
          atgriešanās fn (acc);
        }, x);
      };
    }
  ) {
    atgriešanās maisījums (... mixins) (instance);
  };
};

Tas man šķiet ievērojami lasāmāks. Viss, ko esam mainījuši, ir tas, ka mēs izmantojam atpūtas un noklusējuma parametru sintakse priekšrocības. Protams, jums būs jāpārzina atpūta un noklusējuma sintakse, lai šī versija būtu lasāmāka, taču, pat ja jums tās nav, es domāju, ka ir acīmredzami, ka šī versija joprojām ir mazāk pārblīvēta.

Tas daudz palīdzēja, bet man joprojām ir skaidrs, ka šī versija joprojām ir pietiekami pārblīvēta, ka caurules () savākšana savā funkcijā acīmredzami palīdzētu:

const pipe = funkcija (... fns) {
  atgriešanas funkcija (x) {
    atgriezt fns.reduce (funkcija (acc, fn) {
      atgriešanās fn (acc);
    }, x);
  };
};
// Mantoto funkciju izteicieni
const composeMixins = funkcija (... mixins) {
  atgriešanas funkcija (
    instance = {},
    samaisa = caurule
  ) {
    atgriešanās maisījums (... mixins) (instance);
  };
};

Tas ir labāk, vai ne? Tagad, kad sajaukšanas piešķīrums aizņem tikai vienu līniju, funkcijas struktūra ir daudz skaidrāka - tomēr manā gaumē joprojām ir par daudz sintakses trokšņu. Rakstā composeMixins () man vienā mirklī nav skaidrs, kur beidzas viena funkcija un sākas otra.

Tā vietā, lai izsauktu funkciju ķermeņus, šķiet, ka šis funkcijas atslēgvārds vizuāli saplūst ar ap to esošajiem identifikatoriem. Manā funkcijā slēpjas funkcijas! Kur beidzas parametra paraksts un sākas funkcijas pamatteksts? Es to varēšu izdomāt, ja paskatos cieši, bet tas man nav vizuāli acīmredzams.

Ko darīt, ja mēs varētu atbrīvoties no funkcijas atslēgvārda un izsaukt atgriešanās vērtības, vizuāli norādot uz tām ar lielu tauku bultiņu =>, nevis rakstīt atgriešanās atslēgvārdu, kas saplūst ar apkārtējiem identifikatoriem?

Izrādās, ka mēs varam, un tas ir šāds:

const composeMixins = (... mixins) => (
  instance = {},
  samaisa = caurule
) => sajaukt (... mixins) (piemērs);

Tagad vajadzētu būt skaidram, kas notiek. composeMixins () ir funkcija, kas ņem neierobežotu daudzumu maisījumu un atgriež funkciju, kas ņem divus izvēles parametrus, piemēram, un sajaukumu. Tas atgriež cauruļvadu gadījuma rezultātu caur saliktiem maisījumiem.

Tikai vēl viena lieta… ja to pašu optimizāciju izmantojam arī caurulei (), tā maģiski pārvēršas par vienu oderi:

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

Izmantojot šo vienas līnijas definīciju, priekšrocība, ka to var izmantot savā funkcijā, nav tik skaidra. Atcerieties, ka šī funkcija pastāv kā utilīta Lodash, Ramda un daudzām citām bibliotēkām, bet vai tas tiešām ir vērts, importējot citu bibliotēku?

Vai ir vērts pat to izvilkt savā rindā? Droši vien. Tās patiešām ir divas dažādas funkcijas, un to nodalīšana to padara skaidrāku.

No otras puses, ja tas ir ievietots rindā, tas precizē veida un lietošanas cerības, kad skatāties parametra parakstu. Lūk, kas notiek, kad mēs to izveidojam tiešsaistē:

const composeMixins = (... mixins) => (
  instance = {},
  mix = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => sajaukt (... mixins) (piemērs);

Tagad mēs esam atgriezušies pie sākotnējās funkcijas. Pa šo laiku mēs neatmetām nekādu nozīmi. Faktiski, deklarējot parametrus un noklusējuma vērtības rindā, mēs pievienojām informāciju par to, kā funkcija tiek izmantota, un kā varētu izskatīties parametru vērtības.

Viss šis papildu kods ES5 versijā bija tikai troksnis. Sintakse troksnis. Tam nebija nekāda noderīga mērķa, izņemot to, lai aklimatizētu cilvēkus, kuri nav pazīstami ar kāroto bultu funkcijām.

Kad esat pietiekami iepazinies ar kāroto bultu funkcijām, vajadzētu būt skaidram, ka sākotnējā versija ir lasāmāka, jo ir daudz mazāk sintakses, lai varētu pazust.

Tas ir arī mazāk pakļauts kļūdām, jo ​​kļūdu slēpšanai ir daudz mazāks laukums.

Man ir aizdomas, ka mantotajās funkcijās slēpjas daudz kļūdu, kuras varētu atrast un novērst, ja atjauninātu bultu funkcijas.

Es arī ceru, ka jūsu komanda kļūs ievērojami produktīvāka, ja iemācītos vairāk atbalstīt un atbalstīt kodolīgo sintakse, kas pieejama ES6.

Lai gan ir taisnība, ka dažreiz lietas ir vieglāk saprast, ja tās ir skaidri izteiktas, tā ir taisnība, ka parasti mazāk kods ir labāks.

Ja ar mazāk kodu var paveikt to pašu un sazināties vairāk, nezaudējot nekādu nozīmi, tas ir objektīvi labāk.

Galvenais, lai zinātu atšķirību, ir nozīme. Ja vairāk kodam neizdodas pievienot vairāk nozīmes, tam nav jābūt. Šī koncepcija ir tik vienkārša, ka tā ir labi zināma dabiskās valodas stila vadlīnijas.

Tāda pati stila vadlīnija attiecas arī uz avota kodu. Apskāviens, un jūsu kods būs labāks.

Dienas beigās gaisma tumsā. Atbildot uz vēl vienu tvītu, kurā teikts, ka ES6 versija ir mazāk lasāma:

Laiks iepazīties ar ES6, kursēšanu un funkciju sastādīšanu.

Nākamie soļi

“Uzziniet JavaScript kopā ar Ēriku Elliotu” dalībnieki šobrīd var skatīties 55 minūšu ES6 karija un kompozīcijas nodarbību.

Ja jūs neesat biedrs, jūs to pazaudējat.

Ēriks Eliots ir sadaļu “Programmēšanas JavaScript lietojumprogrammas” (O’Reilly) un “Uzziniet JavaScript ar Ēriku Eliotu” autors. Viņš ir sniedzis ieguldījumu programmatūras pieredzē Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC un labākajiem ierakstu māksliniekiem, ieskaitot Usher, Frank Ocean, Metallica un daudziem citiem.

Lielāko daļu laika viņš pavada Sanfrancisko līča reģionā kopā ar skaistāko sievieti pasaulē.