Jr. devs tarafından okunabilecek kadar zeki miyim? JS'mde çok fazla işlevsel programlama? [kapalı]


133

Babel ES6’yı kodlayan bir Sr. Uygulamamızın bir kısmı API çağrısı yapar ve API çağrısından geri aldığımız veri modeline dayanarak, belirli formların doldurulması gerekir.

Bu formlar çift bağlantılı bir listede saklanır (arka uç verilerin bir kısmının geçersiz olduğunu söylerse, kullanıcıyı çabucak karıştırdıkları bir sayfaya geri getirebilir ve daha sonra hedefine geri getirebiliriz. liste.)

Her neyse, sayfa eklemek için kullanılan birçok fonksiyon var ve çok zeki olup olmadığımı merak ediyorum. Bu sadece temel bir bakış - gerçek algoritma, tonlarca farklı sayfa ve sayfa türüyle çok daha karmaşık, ancak bu size bir örnek verecek.

Bence acemi bir programcı başa çıkacaktı.

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;
}

Şimdi, daha test edilebilir olmak için, ifadeleri ayırıp ayrı hale getirdiklerinde, hepsini tek başıma yaptım ve sonra onların üzerinde haritalandırdım.

Şimdi, test edilebilir bir şey, ama okunabilir ve merak ediyorum burada işleri daha az okunabilir hale getirip getirmediğimi.

// 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

}

İşte benim endişem. To bana alt daha organize olduğunu. Kodun kendisi yalıtılmış olarak test edilebilen daha küçük parçalara bölünür. AMA Düşünüyorum: Eğer küçük bir geliştirici olarak, Kimlik işlevlerini kullanma, körleme veya üçlü ifadeleri kullanma gibi kavramları kullanmayanları okumak zorunda olsaydım, ikinci çözümün ne yaptığını bile anlayabilir miydim? İşleri bazen "yanlış, daha kolay" bir şekilde yapmak daha mı iyidir?


13
JS'de yalnızca 10 yıllık kendi kendine öğretileri olan biri olarak, kendimi bir Jr. olarak Babel ES6
görürdüm

26
OMG - 1999'dan beri sektörde aktif olan ve 1983'ten beri kodlayan ve sizler için en zararlı geliştirici ürünsünüz. "Zeki" olduğunu düşündüğünüz şey "pahalı" ve "bakımı zor" ve "bir böcek kaynağı" olarak adlandırılır ve iş ortamında yeri yoktur. İlk örnek basittir, anlaşılması kolaydır ve ikinci örnek karmaşıkken, anlaşılması zor ve kesin olarak doğru değilken çalışır. Lütfen böyle bir şey yapmayı bırak. Gerçek dünya için geçerli olmayan bazı akademik anlamda dışında, daha iyi DEĞİLDİR.
user1068

15
Burada sadece Brian Kerninghan'dan alıntı yapmak istiyorum: "Herkes hata ayıklamanın ilk etapta bir program yazmaktan iki kat daha zor olduğunu bilir. Öyleyse, yazarken olabildiğince zekiyseniz, nasıl hata ayıklayacaksınız? " - tr.wikiquote.org/wiki/Brian_Kernighan / "Programlama Stilinin Öğeleri", 2. basım, 2. bölüm
MarkusSchaber

7
@Logister Coolness basitlikten öte birincil bir amaç değildir. Burada itiraz etmektir karşılıksız o kod hakkında ikna etmeye sert ve beklenmedik köşe durumlarda içerdiği olasılığını artırır çünkü doğruluk düşmanı (mutlaka birincil endişe) 'dir karmaşıklığı. Daha önce ifade ettiğim ve test etmenin daha kolay olduğu iddiasıyla ilgili kuşku duyduğum için, bu tarz için inandırıcı bir tartışma görmedim. Güvenlikten en az ayrıcalık kuralına benzer bir şekilde, belki de basit şeyler yapmak için güçlü dil özelliklerini kullanmaktan çekinmemesi gerektiğini söyleyen bir kural vardır.
sdenham

6
Kodunuz gençlere benziyor. Bir kıdemliden ilk örneği yazmasını bekliyorum.
sed

Yanıtlar:


322

Kodunuzda birden fazla değişiklik yaptınız:

  • alanlarına erişmek için atama tahrip pagesiyi bir değişimdir.
  • parseFoo()fonksiyonların çıkarılması vs. iyi bir değişiklik olabilir.
  • bir functor tanıtmak… çok kafa karıştırıcı.

Buradaki en kafa karıştırıcı parçalardan biri, işlevsel ve zorunlu programlamayı nasıl karıştırdığınızdır. İşlevinizle verileri gerçekten dönüştürmüyorsunuz, çeşitli işlevler arasında değişken bir liste iletmek için kullanıyorsunuz . Bu çok kullanışlı bir soyutlama gibi görünmüyor, bunun için zaten değişkenlerimiz var. Muhtemelen soyutlanması gereken şey - sadece eğer varsa, o öğeyi ayrıştırmak - hala açık bir şekilde kodunuzdadır, fakat şimdi köşeyi düşünmek zorundayız. Örneğin, parseFoo(foo)bir işlevi geri getirecek belli değil . JavaScript’in yasal olup olmadığını size bildiren statik türde bir sistemi yoktur, bu nedenle bu kod gerçekten daha iyi bir adsız ( makeFooParser(foo)?) Hataya açıktır . Bu şaşırtmaca hiçbir yarar görmüyorum.

Onun yerine görmeyi beklediğim şey:

if (foo) parseFoo(pages, foo);
if (bars) parseBar(pages, bars);
if (bazes) parseBaz(pages, bazes);
return pages;

Ancak bu da ideal değildir, çünkü çağrı sitesinden öğelerin sayfa listesine ekleneceği açık değildir. Bunun yerine, ayrıştırma işlevleri safsa ve sayfalara açıkça ekleyebileceğimiz (muhtemelen boş) bir liste döndürürse, bu daha iyi olabilir:

pages.addAll(parseFoo(foo));
pages.addAll(parseBar(bars));
pages.addAll(parseBaz(bazes));
return pages;

Ekstra fayda: Öğe boşken ne yapılması gerektiğine dair mantık şimdi ayrı ayrı ayrıştırma işlevlerine taşındı. Bu uygun değilse, hala şartlı koşullar getirebilirsiniz. Bir değişebilirlik pageslistesi artık yerine birden çok çağrılar arasında yaymak, tek bir işlevi bir araya çekilir. Yerel olmayan mutasyonlardan kaçınmak, komik isimleri olan soyutlamalardan çok, işlevsel programlamanın çok daha büyük bir parçasıdır Monad.

Yani evet, kodunuz çok zekiydi. Lütfen akıllılığınızı akıllı kod yazmamaya, ancak açık akıllılık gereksinimini önlemek için akıllı yollar bulmak için uygulayın. En iyi tasarımlar süslü görünmüyor , ancak onları gören herkese açık gözüküyor. Ve iyi soyutlamalar, programlamayı basitleştirmek için, aklımda çözmem gereken ekstra katmanlar eklememek için değil (burada, functorun bir değişkene eşdeğer olduğunu ve etkili bir şekilde seçilebileceğini bulmak) vardır.

Lütfen: şüpheniz varsa, kodunuzu basit ve aptalca tutun (KISS ilkesi).


2
Simetri bakış açısından, neyin let pages = Identity(pagesList)farklı olduğu var parseFoo(foo)? Göz önüne alındığında, muhtemelen olurdu ... {Identity(pagesList), parseFoo(foo), parseBar(bar)}.flatMap(x -> x).
ArTs

8
(Benim eğitimsiz göze) eşlenmiş listesi toplamak için üç iç içe lambda ifadeleri sahip biraz olabileceğini açıklayan için teşekkür ederiz çok zeki.
Thorbjørn Ravn Andersen

2
Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
yannis

Belki de akıcı bir tarz ikinci örnekte iyi sonuç verir?
user1068

225

Şüpheniz varsa, muhtemelen çok zekice! İkinci örnek , gibi ifadelerle kazayla karmaşıklık ortaya çıkarırfoo ? parseFoo(foo) : x => x ve genel olarak kod daha karmaşıktır; bu, takip edilmesi daha zor olur.

Parçaları ayrı ayrı test edebilmenizin öngörülen faydası, sadece bireysel fonksiyonlara ayrılarak daha basit bir şekilde sağlanabilir. Ve ikinci örnekte, aksi halde ayrı yinelemeleri birleştiriyorsunuz, bu yüzden aslında daha az izolasyon elde ediyorsunuz .

Genel olarak fonksiyonel stil hakkındaki duygularınız ne olursa olsun, bu açıkça kodu daha karmaşık hale getirdiği bir örnektir.

Basit ve anlaşılır kodu "acemi geliştiriciler" ile ilişkilendirmenizde bir uyarı sinyali buluyorum. Bu tehlikeli bir zihniyet. Tecrübelerime göre tam tersi: Acemi geliştiriciler aşırı karmaşık ve akıllı kodlara yatkındır, çünkü en basit ve en net çözümü görebilmek için daha fazla deneyim gerektirir.

"Zeki kod" a tavsiye, gerçekte, bir aceminin anlayamayacağı ileri düzey kavramlar kullanıp kullanmamayla ilgili değildir. Aksine, gerekenden daha karmaşık veya kıvrımlı kod yazmakla ilgilidir . Bu, kodu herkesin , acemilerin ve uzmanların ve hatta bazı aylar boyunca muhtemelen sizin için takip etmenizi zorlaştırır .


156
"Acemi geliştiriciler aşırı karmaşık ve zekice kodlara yatkındır, çünkü en basit ve en net çözümü görebilmek için daha fazla deneyim gerektirir" sizinle daha fazla hemfikir olamazlar. Mükemmel cevap!
Bonifacio

23
Aşırı karmaşık kod da oldukça pasif-agresif. Çok az başkalarının kolayca okuyabileceği veya hata ayıklayabileceği bir kod üretiyorsunuz ... bu sizin için iş güvenliği anlamına gelir ve yokluğunuzdaki diğer herkes için cehennem olur. Teknik belgelerinizi tamamen Latince olarak da yazabilirsiniz.
Ivan

14
Akıllı kod her zaman gösterişli bir şey olduğunu sanmıyorum. Bazen doğal hissettirir ve ikinci bir muayenede sadece saçma görünüyor.

5
"Gösteriş yapma" ile ilgili ifadeyi, düşündüğümden daha yargılayıcı geldiği için kaldırdım.
JacquesB

11
@BaileyS - Kod incelemesinin önemini vurguladığını düşünüyorum; kodlayıcıya doğal ve basit hissettiren, özellikle bu şekilde kademeli olarak geliştirildiğinde, kolayca bir yorumcuya katlanılabilir. Kod daha sonra evrişimi gidermek için yeniden yazılana / tekrar yazılana kadar incelemeyi geçemez.
Myles

21

Bu cevabım biraz geç geliyor, ama ben hala içeri girmek istiyorum. Sadece en yeni ES6 tekniklerini kullanıyorsanız ya da en popüler programlama paradigmasını kullanıyor olmanız, kodunuzun daha doğru olduğu ya da küçüklerin kodu olduğu anlamına gelmez. Hata. Gerektiğinde gerektiğinde İşlevsel Programlama (veya başka herhangi bir teknik) kullanılmalıdır. En son programlama tekniklerini her problemin içine sığdırmak için en ufak bir şans bulmaya çalışırsanız, her zaman aşırı tasarlanmış bir çözümle sonuçlanacaksınız.

Geri bir adım atın ve bir saniye için çözmeye çalıştığınız sorunu sözlü olarak denemeye çalışın. Temel olarak , sadece addPagesfarklı kısımlarını apiDatabir anahtar-değer çiftleri setine dönüştüren bir fonksiyon istiyor , ardından hepsini ekliyorsunuz PagesList.

Bunun için vardır hepsi buysa, neden kullanarak rahatsız identity functionolan ternary operatorya da kullanan functorgirdi ayrıştırma için? Ayrıca, neden yan etkilerefunctional programming neden olan uygulamanın uygun bir yaklaşım olduğunu düşünüyorsunuz (listeyi değiştirerek)? Neden tüm bu şeyleri, ihtiyacınız olan şey sadece bu olduğunda:

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;
}

( burada çalıştırılabilir bir jsfiddle )

Gördüğünüz gibi, bu yaklaşım hala kullanıyor functional programmingama ölçülü olarak. Ayrıca, her 3 dönüştürme işlevinin hiçbir şekilde yan etkiye neden olmadığından, test edilmeleri çok kolay olduğunu unutmayın. Ayrıca kod addPages, acemiler veya uzmanların sadece bir bakışta anlayabildiği kadar önemsiz ve alçakgönüllü.

Şimdi, bu kodu yukarıda karşılaştığınızla karşılaştırın, farkı görüyor musunuz? Kuşkusuz functional programmingve ES6 sözdizimleri güçlüdür, ancak sorunu bu tekniklerle yanlış bir şekilde keserseniz , daha da karmaşık kodlarla bitirdiniz.

Soruna acele etmezseniz ve doğru teknikleri doğru yerlere uygularsanız, tüm ekip üyeleri tarafından çok düzenli bir şekilde organize edilir ve korunabilirken doğada işlevsel olan kodlara sahip olabilirsiniz. Bu özellikler karşılıklı olarak özel değildir.


2
Bu geniş yayılımlı tutumu işaret etmek için +1 (mutlaka OP için geçerli değildir): "Sadece en yeni ES6 tekniklerini kullanıyorsanız veya en popüler programlama paradigmasını kullanmanız, kodunuzun daha doğru olduğu anlamına gelmez, ya da o çocuğun kodu yanlış. "
Giorgio

+1. Sadece küçük bir bilgisel ifade, bu bağımlılığı kaldırmak için _.concat yerine spread (...) operatörünü kullanabilirsiniz.
YoTengoUnLCD

1
@YoTengoUnLCD Ah, iyi yakala. Artık benim ve ekibimin hala bazı lodashkullanımlarımızı öğrenme haberi altında olduğumuzu biliyorsunuz . Bu kod kullanabilir spread operatorveya [].concat()kodun şeklini bozulmadan tutmak isterse bile kullanabilir .
b0nyb0y

Üzgünüz, ama bu kod listesi hala OP'nin yayınındaki orijinal "küçük kod" dan çok daha az açık. Temel olarak: Eğer kaçınabiliyorsanız, asla üçlü operatör kullanmayın. Çok gergin. "Gerçek" işlevsel bir dilde if-ifadeleri ifadeler değil ifadelerdir ve bu nedenle daha okunaklı olur.
Olle Härstedt

@ OlleHärstedt Umm, bu yaptığınız ilginç bir iddia. Mesele şu ki, İşlevsel Programlama paradigması veya oradaki herhangi bir paradigma, hiçbir zaman sözdiziminden çok daha az olan herhangi bir "gerçek" işlevsel dile bağlı değildir. Bu nedenle, şartlı yapıların "asla" kullanılmaması veya kullanılması gerektiğini dikte etmek bir anlam ifade etmiyor. A , ister ister ister istemeseniz, ternary operatornormal bir ififade kadar geçerlidir . Arasındaki okunabilirliği tartışma if-elseve ?:kampın hiç biten, yani içine alamayacak. Söyleyeceğim tek şey, eğitimli gözlerle, bunun gibi çizgiler çok "gergin".
b0nyb0y

5

İkinci snippet, birinciden daha test edilemez . İki snippet'ten biri için gerekli tüm testleri ayarlamak oldukça kolay olacaktır.

İki parçacık arasındaki asıl fark anlaşılırlıktır. İlk pasajı oldukça hızlı bir şekilde okuyabilir ve neler olduğunu anlayabilirim. İkinci pasaj, çok değil. Çok daha az sezgisel ve aynı zamanda daha uzun.

Bu, ilk snippet'in bakımını kolaylaştırır, bu da değerli bir kod kalitesidir. İkinci snippet'te çok az değer buluyorum.


3

TD; DR

  1. Kodunuzu Junior Developer'a 10 dakika veya daha kısa sürede açıklayabilir misiniz?
  2. Bundan iki ay sonra kodunu anlayabiliyor musun?

Detaylı analiz

Netlik ve Okunabilirlik

Orijinal kod etkileyici bir şekilde anlaşılır ve her seviye programcı için anlaşılması kolaydır. Herkese tanıdık bir tarzda .

Okunabilirlik, büyük ölçüde, matematiksel belirteçlerin bazı sayımlarına değil, aşinalıklara dayanmaktadır . IMO, zamanın bu aşamasında, yeniden yazma işleminizde çok fazla ES6 var. Belki birkaç yıl içinde cevabımın bu kısmını değiştiririm. :-) BTW, @ b0nyb0y'nin yanıtını makul ve net bir uzlaşma olarak seviyorum.

Testedilebilirlik

if(apiData.pages.foo){
   pagesList.add('foo', apiData.pages.foo){
}

PagesList.add () 'in test etmesi gerektiğini varsayarsak, bu tamamen kolay bir koddur ve bu bölümün özel ayrı testlere ihtiyaç duyması için açık bir neden yoktur.

if (apiData.pages.arrayOfBars){
      let bars = apiData.pages.arrayOfBars;
      bars.forEach((bar) => {
         pagesList.add(bar.name, bar.data);
      })
   }

Yine, bu bölümün herhangi bir özel testine hemen ihtiyacım yok. PagesList.add () öğesinde null, yinelenen veya diğer girdilerle ilgili olağandışı sorunlar olmadıkça.

if (apiData.pages.customBazes) {
      let bazes = apiData.pages.customBazes;
      bazes.forEach((baz) => {
         pagesList.add(customBazParser(baz)); 
      })
   } 

Bu kod da çok basittir. Bunun customBazParsertest edildiğini ve çok fazla "özel" sonuç döndürmediğini varsayalım. Bu nedenle, `PagesList.add (), (etki alanınıza aşina olmadığım gibi olabilir) ile ilgili zor durumlar olmadığı sürece, bu bölümün neden özel testler gerektirdiğini anlamıyorum.

Genel olarak, tüm fonksiyonun test edilmesi iyi sonuç vermelidir.

Feragatname : Üç if()ifadenin 8 olasılığının tümünü test etmek için özel nedenler varsa , o zaman evet, testleri bölün. Veya PagesList.add()hassas ise, evet, testleri ayırın.

Yapısı: Üç parçaya ayrılmaya değer mi? (Galyalı gibi)

Burada en iyi tartışmaya sahipsiniz. Şahsen, orijinal kodun "çok uzun" olduğunu sanmıyorum (SRP fanatiği değilim). Ancak, birkaç if (apiData.pages.blah)bölüm daha olsaydı , SRP bunun çirkin olduğunu ve ayrılmaya değer olacağını düşünüyor. Özellikle DRY uygulanırsa ve işlevler kodun diğer yerlerinde kullanılabilirse.

Bir önerim

YMMV. Bir kod satırı ve bir miktar mantık kaydetmek için, if ile let komutunu bir satırda birleştirebilirim: örn.

let bars = apiData.pages.arrayOfBars || [];
bars.forEach((bar) => {
   pagesList.add(bar.name, bar.data);
})

ApiData.pages.arrayOfBars bir Sayı veya Dize ise bu başarısız olur, ancak orijinal kod da olur. Ve bana göre daha açık (ve aşırı kullanılmış bir deyim).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.