Aşinalık Bias Sizi Geri Bekliyor: Ok İşlevlerini Kucaklamanın Zamanı

“Çapa” - Actor212 - (CC BY-NC-ND 2.0)

Ben bir geçim için JavaScript öğretiyorum. Son zamanlarda, ilk birkaç derste, daha önce körlenmiş ok fonksiyonlarını öğretmek için müfredatımın çevresinde karıştırıldım. Müfredatta daha önce taşıdım, çünkü bu çok değerli bir beceri ve öğrenciler oklarla körelmeyi düşündüğümden daha hızlı alıyorlar.

Eğer bunu anlarlarsa ve bundan daha önce yararlanırlarsa, neden daha önce öğretmediniz?

Not: Kurslarım, daha önce hiçbir zaman bir kod satırına dokunmamış kişiler için tasarlanmamıştır. Çoğu öğrenci kodlamadan en az birkaç ay geçirdikten sonra katılır - kendi başlarına, önyükleme kampında veya profesyonelce. Ancak, çok az tecrübesi olan veya hiç tecrübesi olmayan birçok genç geliştiricinin bu konuları hızlı bir şekilde çözdüğünü gördüm.

Bir grup öğrencinin, 1 saatlik tek bir ders süresince köri ok işlevleriyle çalışma aşinalıklarına sahip olduğunu gördüm. (“Eric Elliott ile JavaScript Öğrenin” üyesi iseniz, şu anda 55 dakikalık ES6 Köri ve Kompozisyon dersini izleyebilirsiniz).

Öğrencilerin onu ne kadar çabuk aldıklarını ve yeni bulunan köri güçlerini kullanmaya başladığını görünce, Twitter'da körüklenmiş ok işlevlerini gönderdiğimde her zaman biraz şaşırdım ve Twitterverse, bu “okunamaz” kodunu girdiği düşüncesiyle öfkeye yanıt verdi sürdürmesi gereken insanlar.

İlk önce, neden bahsettiğimize dair bir örnek vereyim. Tepkimeyi ilk gördüğümde Twitter'ın bu işleve olan tepkisi oldu:

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

Twitter'daki insanlar beni insanları şaşırtmaya çalışmakla suçladığında şok oldum. Bu işlevi, ES6'da curried fonksiyonlarını ifade etmenin ne kadar kolay olduğunu göstermek için yazdım. JavaScript'te aklıma gelen en basit pratik uygulama ve kapanış ifadesidir. (İlgili: “Bir Kapatma Nedir?”).

Aşağıdaki işlev ifadesine eşdeğerdir:

const secret = işlev (msg) {
  return işlevi () {
    msg dönüş;
  };
};

secret (), msg alan ve msg döndüren yeni bir işlev döndüren bir işlevdir. Msg'nin değerini, hangi değerle sırrınıza koyduğunuza sabitlemek için faydalanır ().

İşte nasıl kullandığınız:

const mySecret = secret ('merhaba');
benim sırrım(); // 'Merhaba'

Anlaşılan, “çift ok” insanların kafasını karıştıran şey. Bunun bir gerçek olduğuna ikna oldum:

Tanıdık bir şekilde, satır içi ok işlevleri, curried işlevleri JavaScript'te ifade etmenin en kolay yoludur.

Birçok insan bana, daha uzun formun okunmasının kısa formdan daha kolay olduğunu iddia etti. Onlar kısmen doğru, ama çoğunlukla yanlış. Daha ayrıntılı ve daha açık, ancak okunması kolay değil - en azından ok işlevlerini bilen biriyle değil.

Twitter'da gördüğüm itirazlar, öğrencilerimin zevk aldığı pürüzsüz öğrenme deneyimiyle kavga değildi. Deneyimlerime göre öğrenciler balığın suya aldığı gibi körlenmiş ok işlevlerine giriyorlar. Onları öğrendikten sonra birkaç gün içinde oklar bulunur. Her türlü kodlama zorluğuyla başa çıkmak için onları zahmetsizce asıyorlar.

Ok işlevlerinin öğrenmeleri, okuması ya da anlamaları için “zor” olduğuna dair herhangi bir işaret görmüyorum - bir kez birkaç saatlik bir ders boyunca ve öğrenim oturumlarında öğrenmeye ilk yatırımlarını yaptıklarında.

Daha önce hiç görmedikleri curried ok fonksiyonlarını kolayca okuyorlar ve neler olduğunu açıklıyorlar. Onlara meydan okuduğumda doğal olarak kendi yazılarını yazarlar.

Başka bir deyişle, kıvrımlı ok fonksiyonlarını görmeye aşina olur olmaz, onlarla herhangi bir problemleri yoktur. Onları bu cümleyi okuduğunuz kadar kolay okurlar - ve anlayışları daha az hata içeren çok daha basit kodlarla yansıtılır.

Bazıları Eski Fonksiyon İfadelerinin Neden Okunması Daha Kolay Olduğunu Düşünüyor?

Alışkanlık önyargısı, daha iyi bir seçeneğin farkında olmamıza rağmen, kendimizi tahrip edici kararlar almaya iten ölçülebilir bir insan bilişsel önyargıdır. Konfor ve alışkanlıktan daha iyi kalıplar hakkında bilgi sahibi olmamıza rağmen aynı eski kalıpları kullanmaya devam ediyoruz.

Tanıdıklık yanlılığı hakkında (ve kendimizi kandırdığımız başka bir çok yol) “The Undoing Project: Fikrimizi Değiştiren Bir Dostluk” kitabından çok daha fazla şey öğrenebilirsiniz. Bu kitap her yazılım geliştirici için okunmalıdır, çünkü çeşitli bilişsel tuzaklara düşmemek için daha eleştirel düşünmenizi ve varsayımlarınızı test etmenizi teşvik eder - ve bu bilişsel tuzakların nasıl keşfedildiğinin hikayesi de gerçekten iyidir .

Eski İşlev İfadeleri Muhtemelen Kodunuzda Hatalara Neden Oluyor

Bugün, ES6’dan ES5’e curried bir ok işlevi yeniden yazıyordum. Böylece, insanların eski tarayıcılarda transplantasyon yapmadan kullanabilecekleri açık kaynaklı bir modül olarak yayınlayabiliyordum. ES5 versiyonu beni şok etti.

ES6 sürümü basit, kısa ve zarif - sadece 4 satır.

Kesin olarak, Twitter'ın ok işlevlerinin üstün olduğunu ve insanların eski alışkanlıklarını, kötü alışkanlıklar gibi terk etmeleri gerektiğini ispatlayacak bir işlev olduğunu düşündüm.

Bu yüzden tweetledim:

Görüntünün sizin için çalışmaması durumunda, işlevlerin metni:

// oklarla süslenmiş
const composeMixins = (... karışımları) => (
  örnek = {},
  mix = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => karışım (... karışımlar) (örnek);
// vs ES5 tarzı
var composeMixins = function () {
  var mixins = [] .slice.call (argümanlar);
  return işlevi (örnek, mix) {
    if (! example) example = {};
    if (! mix) {
      mix = function () {
        var fns = [] .slice.call (argümanlar);
        dönüş işlevi (x) {
          return fns.reduce (işlev (acc, fn) {
            dönüş fn (acc);
          }, x);
        };
      };
    }
    geri dönüş mix.apply (null, mixins) (örnek);
  };
};

Söz konusu işlev, genellikle işlevleri oluşturmak için kullanılan standart bir işlevsel programlama aracı olan pipe () etrafındaki basit bir sarmalayıcıdır. Bir pipe () işlevi lodash'da lodash / flow, Ramda'da R.pipe () işlevini içerir ve hatta birkaç işlevsel programlama dilinde kendi operatörüne sahiptir.

Fonksiyonel programlamaya aşina olan herkese tanıdık gelmelidir. Birincil bağımlılığı olması gerektiği gibi: Azaltın.

Bu durumda, işlevsel karışımları oluşturmak için kullanılıyor, ancak bu alakasız bir ayrıntı (ve diğer bir blog postası). İşte önemli detaylar:

İşlev, herhangi bir sayıda işlevsel karışımı alır ve bunları bir boru hattı - montaj hattı gibi birbiri ardına uygulayan bir işlevi döndürür. Her bir işlevsel karışım örneği bir girdi olarak alır ve boru hattındaki bir sonraki işleve geçirmeden önce üzerine bir şeyler yapıştırır.

Örneği atlarsanız, sizin için yeni bir nesne oluşturulur.

Bazen karışımları farklı şekillerde oluşturmak isteyebiliriz. Örneğin, öncelik sırasını tersine çevirmek için pipe () yerine compose () komutunu geçmek isteyebilirsiniz.

Davranışı özelleştirmeniz gerekmiyorsa, yalnızca varsayılanı yalnız bırakın ve standart pipe () davranışı alın.

Sadece gerçekler

Okunabilirlik hakkındaki görüşler bir yana, bu örnekle ilgili nesnel gerçekler:

  • Hem ES5 hem de ES6 işlev ifadeleri, oklar veya diğerleriyle ilgili çok yıllı bir deneyime sahibim. Bilinenlik yanlılığı bu verilerde değişken değildir.
  • ES6 sürümünü birkaç saniye içinde yazdım. Sıfır hata içeriyordu (bildiğim kadarıyla - bütün birim testlerinden geçiyor).
  • ES5 sürümünü yazmam birkaç dakika sürdü. En azından bir büyüklük sırası daha fazla zaman. Saniye vs dakika. Fonksiyon girintilerindeki yerimi iki kez kaybettim. Hepsi hata ayıklamak ve düzeltmek zorunda olduğum 3 hata yazdım. İkisinden neler olup bittiğini anlamak için console.log () 'a başvurmak zorunda kaldım.
  • ES6 sürümü 4 kod satırıdır.
  • ES5 sürümü 21 satır uzunluğundadır (17 aslında kod içerir).
  • Sıkıcı ayrıntılarına rağmen, ES5 sürümü aslında ES6 sürümünde mevcut olan bilgilerin doğruluğunu yitirir. Daha uzun, ancak daha az iletişim kurar, ayrıntılar için okumaya devam edin.
  • ES6 sürümü, fonksiyon parametreleri için 2 spread içerir. ES5 sürümü formaları göz ardı eder ve bunun yerine işlev imzasının okunabilirliğini zedeleyen (fidelity downgrade 1) örtük argümanlar nesnesini kullanır.
  • ES6 sürümü, işlev imzasındaki karışım varsayılanını tanımlar, böylece bir parametrenin değeri olduğunu açıkça görebilirsiniz. ES5 sürümü bu ayrıntıyı gizler ve bunun yerine işlev gövdesinin derinliklerine gizler. (aslına uygunluğu düşürme 2).
  • ES6 sürümü, nasıl okunması gerektiğinin yapısını netleştirmeye yardımcı olan yalnızca 2 girinti seviyesine sahiptir. ES5 sürümü 6'ya sahiptir ve işlev yapısının okunabilirliğine yardımcı olmaktan ziyade iç içe geçmiş düzeyler gizlenir (aslına uygunluk düşürme 3).

ES5 sürümünde, pipe () işlev gövdesinin çoğunu işgal eder - öyle ki satır içi tanımlamak biraz çılgınca olur. ES5 sürümünü okunabilir hale getirmek için gerçekten ayrı bir işleve ayrılması gerekiyor:

var pipe = function () {
  var fns = [] .slice.call (argümanlar);
  dönüş işlevi (x) {
    return fns.reduce (işlev (acc, fn) {
      dönüş fn (acc);
    }, x);
  };
};
var composeMixins = function () {
  var mixins = [] .slice.call (argümanlar);
  return işlevi (örnek, mix) {
    if (! example) example = {};
    if (! mix) mix = boru ise;
    geri dönüş mix.apply (null, mixins) (örnek);
  };
};

Bu açıkça bana daha okunaklı ve anlaşılabilir görünüyor.

ES6 sürümüne aynı okunabilirliği “optimizasyonu” uyguladığımızda ne olacağını görelim:

const pipe = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x);
const composeMixins = (... karışımları) => (
  örnek = {},
  karışım = boru
) => karışım (... karışımlar) (örnek);

ES5 optimizasyonu gibi, bu sürüm daha ayrıntılı (daha önce orada olmayan yeni bir değişken ekler). ES5 sürümünden farklı olarak, bu sürüm, boru tanımını çıkardıktan sonra önemli ölçüde daha okunaklı değildir. Sonuçta, zaten fonksiyon imzasında açıkça atanmış değişken bir adı vardı: mix.

Karışımın tanımı zaten kendi satırında yer almaktaydı, bu da okurların nerede bittiği hakkında kafa karışıklığı yaratmalarını ve fonksiyonun geri kalanının devam etmesini olanaksız kılıyor.

Şimdi 1 yerine aynı şeyi temsil eden 2 değişkenimiz var. Çok kazandık mı? Belli ki hayır, hayır.

Öyleyse ES5 versiyonu neden aynı fonksiyonla soyutlanmış olarak daha iyi?

Çünkü ES5 sürümü açıkça daha karmaşık. Bu karmaşıklığın kaynağı bu meselenin özüdür. Karmaşıklığın kaynağının sözdizimi gürültüsünden kaynaklandığını ve sözdizimi gürültüsünün işlevin anlamını engellediğini, yardımcı olmadığını gizlediğini iddia ediyorum.

Hadi vites değiştirelim ve daha fazla değişken kaldıralım Her iki örnek için ES6'yı kullanalım ve yalnızca eski işlev ifadeleri ile ok işlevlerini karşılaştıralım:

var composeMixins = function (... mixins) {
  dönüş işlevi (
    örnek = {},
    mix = function (... fns) {
      dönüş işlevi (x) {
        return fns.reduce (işlev (acc, fn) {
          dönüş fn (acc);
        }, x);
      };
    }
  ) {
    dönüş karışımı (... karışımlar) (örnek);
  };
};

Bu bana çok daha okunaklı görünüyor. Tek yaptığımız şey dinlenme ve varsayılan parametre sözdiziminden faydalanıyor olmamız. Tabii ki, bu sürümün daha okunaklı olması için istirahat ve varsayılan sözdizimine aşina olmanız gerekecek, ancak olmasanız bile, bu sürümün hala daha az karmaşık olduğu açıktır.

Bu çok yardımcı oldu, ancak bu versiyonun hala pipoyu () kendi işlevine sokmak için yeterince yardımcı olacağı konusunda yeterince karışık olduğu hala açık:

const pipe = işlevi (... fns) {
  dönüş işlevi (x) {
    return fns.reduce (işlev (acc, fn) {
      dönüş fn (acc);
    }, x);
  };
};
// Eski işlev ifadeleri
const composeMixins = function (... karışımları) {
  dönüş işlevi (
    örnek = {},
    karışım = boru
  ) {
    dönüş karışımı (... karışımlar) (örnek);
  };
};

Bu daha iyi, değil mi? Şimdi karışım ataması sadece tek bir satır kaplar, işlevin yapısı çok daha açıktır - ama benim zevkime hitap eden çok fazla sözdizimi gürültüsü var. ComposeMixins () işlevinde, bir işlevin bittiği ve diğerinin başladığı bir bakışta bana açık değil.

İşlev gövdelerini çağırmak yerine, bu işlev anahtar sözcüğü, etrafındaki tanımlayıcılarla görsel olarak uyum içinde görünmektedir. İşlevimde saklanan işlevler var! Parametre imzası nerede bitiyor ve fonksiyon gövdesi nerede başlıyor? Yakından bakarsam çözebilirim, ama benim için görsel olarak açık değil.

Function anahtar sözcüğünden kurtulabilirsek ve çevreleyen tanımlayıcılarla harmanlanan bir return anahtar sözcüğü yazmak yerine, büyük bir yağ okuyla görsel olarak işaretleyerek geri dönüş değerlerini söyleyebilirsek ne olur?

Anlaşılıyor, yapabiliriz ve işte şöyle görünüyor:

const composeMixins = (... karışımları) => (
  örnek = {},
  karışım = boru
) => karışım (... karışımlar) (örnek);

Şimdi neler olup bittiği açık olmalı. composeMixins (), herhangi bir sayıda mixin alan ve iki isteğe bağlı parametre alan, örneğin ve mix alan bir fonksiyon döndüren bir fonksiyondur. Boru oluşumunun sonucunu, oluşan karışımlar aracılığıyla verir.

Sadece bir şey daha… Eğer aynı optimizasyonu pipe () 'a uygularsak, sihirli bir şekilde tek-linere dönüşür:

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

Tek satırda bu tanım ile, onu kendi işlevine sokma avantajı daha az açıktır. Unutma, bu işlev Lodash, Ramda ve bir sürü başka kütüphanede bir yardımcı program olarak var, fakat başka bir kütüphaneyi ithal etmenin yükü gerçekten önemli mi?

Kendi çizgisine çekmeye bile değer mi? Muhtemelen. Gerçekten iki farklı işlevdir ve bunları ayırmak bunu daha net hale getirir.

Öte yandan, çevrimiçi duruma getirmek, parametre imzasına baktığınızda tür ve kullanım beklentilerini netleştirir. İşte, satır içi oluşturduğumuzda ne olur:

const composeMixins = (... karışımları) => (
  örnek = {},
  mix = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => karışım (... karışımlar) (örnek);

Şimdi asıl işleve döndük. Bu arada, hiçbir anlamı reddetmedik. Aslında, parametrelerimizi ve varsayılan değerleri satır içi olarak bildirerek, işlevin nasıl kullanıldığı ve parametrelerin değerlerinin nasıl görünebileceği hakkında bilgiler ekledik.

ES5 sürümünde tüm bu ekstra kod sadece gürültü oldu. Sözdizimi gürültüsü. Kıvrımlı ok fonksiyonlarına aşina olmayan insanları suçlamaktan başka hiçbir işe yaramadı.

Kıvrılmış ok işlevleriyle ilgili yeterli bilgi edindikten sonra, orijinal sürümün daha okunaklı olduğu açıktır çünkü kaybolmak için daha az sözdizimi vardır.

Aynı zamanda hataya daha az eğilimlidir, çünkü böceklerin saklanabileceği çok daha az yüzey alanı vardır.

Ok işlevlerine yükseltme yapacak olursanız, eski işlevlerde saklanan birçok hata olduğundan şüpheleniyorum.

Ayrıca, ES6’da mevcut olan özlü sözdizimini daha fazla benimsemeyi ve tercih etmeyi öğrenirseniz, ekibinizin çok daha verimli olacağını düşünüyorum.

Açıkça anlaşılırsa bazen işlerin anlaşılmasının daha kolay olduğu doğru olsa da, genel bir kural olarak daha az kodun daha iyi olduğu da doğrudur.

Daha az kod aynı şeyi başarır ve daha fazla iletişim kurarsa, hiçbir anlamdan ödün vermeden, nesnel olarak daha iyidir.

Farkı bilmenin anahtarı anlamdır. Daha fazla kod daha fazla anlam eklemek için başarısız olursa, bu kod olmamalıdır. Bu kavram çok basit, doğal dil için iyi bilinen bir stil kılavuzudur.

Aynı stil kılavuzu kaynak kodu için de geçerlidir. Kucakla, kodun daha iyi olacak.

Günün sonunda, karanlıkta bir ışık. ES6 sürümünün daha az okunabilir olduğunu söyleyen başka bir tweet'in yanıtı:

ES6, kurutma ve işlev kompozisyonuna aşina olma zamanı.

Sonraki adımlar

“Eric Elliott ile JavaScript Öğrenin” üyeleri şu anda 55 dakikalık ES6 Curry & Composition dersini izleyebilir.

Üye değilseniz, eksiksiniz!

Eric Elliott, “JavaScript Uygulamalarını Programlama” (O’Reilly) ve “JavaScript'i Eric Elliott ile Öğren” yazarıdır. Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC ve Usher, Frank Ocean, Metallica ve daha birçokları dahil olmak üzere en iyi kayıt sanatçıları için yazılım deneyimlerine katkıda bulundu.

Zamanının çoğunu San Francisco Körfez Bölgesi'nde, dünyanın en güzel kadını ile geçiriyor.