Aku terlalu 'pintar' yang akan dibaca oleh Jr. devs? Terlalu banyak pemrograman fungsional di JS?

I'm Sr. front-end dev, coding di Babel ES6. Bagian dari aplikasi kita membuat panggilan API, dan berdasarkan pada model data yang kita dapatkan dari API panggilan, bentuk-bentuk tertentu harus diisi.

Bentuk-bentuk tersebut disimpan di sebuah doubly-linked list (jika back-end mengatakan beberapa data yang tidak valid, kita bisa dengan cepat mendapatkan pengguna kembali ke halaman satu mereka kacau dan kemudian mendapatkan mereka kembali pada target, hanya dengan memodifikasi daftar.)

Pokoknya, di sana's banyak fungsi yang digunakan untuk menambahkan halaman, dan I'm wondering if I'm terlalu pintar. Ini hanya gambaran dasar - sebenarnya algoritma ini jauh lebih kompleks, dengan banyak halaman yang berbeda dan jenis halaman, tapi ini'akan memberikan anda sebuah contoh.

Ini adalah bagaimana saya berpikir, seorang programmer pemula akan menangani hal itu.

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

Sekarang, dalam rangka untuk lebih diuji, I'sudah diambil semua orang jika pernyataan dan membuat mereka terpisah, berdiri sendiri fungsi, dan kemudian aku peta di atas mereka.

Sekarang, diuji adalah satu hal, tapi begitu mudah dibaca dan aku bertanya-tanya jika saya'm membuat hal-hal yang kurang dapat dibaca di sini.

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

}

Berikut ini's keprihatinan saya. Ke me bagian bawah adalah lebih terorganisir. Kode itu sendiri dipecah menjadi potongan yang lebih kecil yang dapat diuji dalam isolasi. TAPI aku'm berpikir: Jika saya harus membaca bahwa sebagai junior developer, tidak terpakai untuk konsep-konsep seperti menggunakan Identitas functors, currying, atau ternary pernyataan, saya akan mampu untuk mengerti apa solusi terakhir adalah lakukan? Adalah lebih baik untuk melakukan hal-hal yang "yang salah, lebih mudah" kadang-kadang?

Mengomentari pertanyaan (18)

Dalam kode anda, anda telah membuat beberapa perubahan:

  • destructuring tugas untuk mengakses bidang dalam halaman adalah perubahan yang baik.
  • mengeluarkan parseFoo() fungsi dll. adalah mungkin perubahan yang baik.
  • memperkenalkan sebuah functor ... sangat membingungkan.

Salah satu bagian yang paling membingungkan di sini adalah bagaimana anda mencampur fungsional dan pemrograman imperatif. Dengan anda functor anda tidak't benar-benar mengubah data, anda menggunakannya untuk lulus bisa berubah daftar melalui berbagai fungsi. Itu doesn't tampak seperti sangat berguna abstraksi, kita sudah memiliki variabel untuk itu. Hal yang mungkin harus telah disarikan – hanya parsing bahwa item jika ada – yang masih ada dalam kode anda secara eksplisit, tapi sekarang kami harus berpikir sekitar sudut. Misalnya, it's agak non-jelas bahwa parseFoo(foo) akan kembali fungsi. JavaScript doesn't memiliki statis jenis sistem untuk memberitahukan anda apakah ini adalah legal, jadi kode tersebut adalah benar-benar rawan kesalahan tanpa nama yang lebih baik (makeFooParser(foo)?). Saya don't melihat manfaat apapun dalam kebingungan.

Apa yang saya'a mengharapkan untuk melihat gantinya:

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

Tapi yang's tidak ideal baik, karena tidak jelas dari panggilan situs yang item akan ditambahkan ke halaman daftar. Jika bukan parsing fungsi yang murni dan kembali (mungkin kosong) daftar yang kami dapat secara eksplisit menambah halaman, yang mungkin menjadi lebih baik:

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

Manfaat tambahan: logika tentang apa yang harus dilakukan ketika barang kosong sekarang telah pindah ke individu parsing fungsi. Jika hal ini tidak tepat, anda tetap dapat memperkenalkan conditional. Yang berubah-ubah dari halaman daftar sekarang ditarik bersama-sama ke dalam satu fungsi, bukan menyebar di beberapa panggilan. Menghindari non-lokal mutasi adalah jauh lebih besar bagian dari pemrograman fungsional dari abstraksi dengan nama yang lucu seperti Ketunggalan.

Jadi ya, kode anda terlalu pintar. Silakan mendaftar kepandaian anda tidak pintar menulis kode, tetapi untuk menemukan cara-cara cerdik untuk menghindari kebutuhan untuk terang-terangan kepandaian. Desain terbaik don't melihat fancy, tapi terlihat jelas kepada siapa pun yang melihat mereka. Dan baik abstraksi yang ada untuk menyederhanakan pemrograman, tidak untuk menambahkan lapisan ekstra yang harus saya perbaiki dalam pikiran saya dulu (di sini, mencari tahu bahwa functor adalah setara dengan sebuah variabel, dan dapat secara efektif menjadi elided).

Tolong: jika ragu-ragu, menjaga kode sederhana dan bodoh (CIUMAN prinsip).

Komentar (4)

Jika anda berada dalam keraguan, itu mungkin terlalu pintar! Contoh kedua memperkenalkan disengaja kompleksitas dengan ekspresi seperti foo ? parseFoo(foo) : x => x, dan keseluruhan kode adalah lebih kompleks yang berarti lebih sulit untuk mengikuti.

Informasi manfaat, bahwa anda dapat menguji potongan individual, bisa dicapai dengan cara yang sederhana dengan hanya membobol fungsi individu. Dan pada contoh kedua anda dan pasangan jika tidak terpisah iterasi, sehingga anda benar-benar mendapatkan kurang isolasi.

Apapun perasaan anda tentang gaya fungsional secara umum, ini jelas sebuah contoh di mana hal ini membuat kode yang lebih kompleks.

Saya menemukan sedikit sinyal peringatan bahwa anda mengasosiasikan sederhana dan mudah kode dengan "para pengembang pemula". Ini adalah berbahaya mentalitas. Dalam pengalaman saya itu adalah sebaliknya: pengembang Pemula yang cenderung terlalu kompleks dan pintar kode, karena itu membutuhkan lebih banyak pengalaman untuk dapat melihat yang paling sederhana dan paling jelas solusi.

Saran terhadap "pintar kode" tidak benar-benar tentang apakah atau tidak kode menggunakan konsep-konsep canggih yang pemula mungkin tidak mengerti. Melainkan adalah tentang menulis kode yang lebih lebih kompleks atau rumit dari yang diperlukan. Hal ini membuat code lebih sulit untuk mengikuti untuk semua, pemula dan ahli sama, dan mungkin juga untuk diri sendiri beberapa bulan ke depan.

Komentar (7)

Jawaban ini saya datang sedikit terlambat, tapi aku masih ingin berpadu. Hanya karena anda're menggunakan terbaru ES6 teknik atau menggunakan paling populer paradigma pemrograman doesn't berarti bahwa kode anda lebih benar, atau yang junior's kode yang salah. Pemrograman fungsional (atau teknik lain) harus digunakan ketika itu's benar-benar dibutuhkan. Jika anda mencoba untuk menemukan terkecil kesempatan untuk menjejalkan terbaru teknik pemrograman ke dalam setiap masalah, anda akan selalu berakhir dengan sebuah rekayasa solusi.

Mengambil langkah mundur, dan mencoba untuk verbalisasi masalah anda're berusaha untuk memecahkan kedua. Pada dasarnya, anda hanya ingin fungsi addPages untuk mengubah bagian-bagian yang berbeda dari apiData ke dalam satu set dari pasangan kunci-nilai, kemudian tambahkan semua dari mereka menjadi PagesList.

Dan jika itu's semua ada untuk itu, mengapa repot-repot menggunakan identitas fungsi dengan ternary operator, atau menggunakanfunctoruntuk input parsing? Selain itu, mengapa anda berpikir itu's pendekatan yang tepat untuk menerapkanpemrograman fungsional` yang menyebabkan efek samping (dengan bermutasi daftar)? Mengapa semua hal itu, ketika semua yang anda butuhkan adalah hanya ini:

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

(dapat dijalankan jsfiddle [di sini][1])

Seperti yang anda lihat, pendekatan ini masih menggunakan pemrograman fungsional, tapi di moderasi. Juga perhatikan bahwa karena semua transformasi 3 fungsi tidak menyebabkan efek samping apapun, mereka mati mudah untuk menguji. Kode di addPages ini juga sepele dan sederhana yang pemula atau ahli dapat memahami dengan hanya sekilas saja.

Sekarang, bandingkan kode dengan apa yang anda've datang dengan di atas, apakah anda melihat perbedaan? Tidak diragukan lagi, pemrograman fungsional dan ES6 sintaks yang kuat, tapi jika anda mengiris masalah dengan cara yang salah dengan teknik ini, anda'll berakhir dengan kode messier.

Jika anda don't terburu-buru ke dalam masalah, dan menerapkan teknik-teknik yang tepat di tempat yang tepat, anda dapat memiliki kode yang fungsional di alam, sementara masih sangat terorganisir dan dipelihara oleh semua anggota tim. Karakteristik ini tidak saling eksklusif.

Komentar (6)

Kedua cuplikan not lebih diuji dari yang pertama. Itu akan cukup mudah untuk mengatur semua diperlukan tes untuk salah satu dari dua potongan.

Perbedaan yang nyata antara dua potongan ini bisa dimengerti. Aku bisa baca dulu cuplikan cukup cepat dan memahami apa's terjadi. Kedua cuplikan, tidak begitu banyak. It's jauh lebih intuitif, serta secara substansial lebih lama.

Yang membuat pertama cuplikan mudah untuk mempertahankan, yang adalah kualitas yang berharga dari kode. Saya menemukan sangat sedikit dari nilai di kedua cuplikan.

Komentar (0)

TD;DR

  1. Anda dapat menjelaskan kode anda untuk Junior Pengembang dalam 10 menit atau kurang?
  2. Dua bulan dari sekarang, anda bisa memahami kode anda?

Analisis Rinci

Kejelasan dan mudah Dibaca

Kode asli mengesankan jelas dan mudah untuk memahami untuk setiap tingkat programmer. Hal ini dalam gaya akrab dengan semua orang.

Mudah dibaca sebagian besar didasarkan pada keakraban, tidak beberapa matematika menghitung token. IMO, pada tahap ini dalam waktu, anda memiliki terlalu banyak ES6 di rewrite. Mungkin dalam beberapa tahun I'll perubahan ini bagian dari jawaban saya. :-) BTW, aku juga suka @b0nyb0y jawaban yang masuk akal dan jelas membahayakan.

Testability

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

Dengan asumsi bahwa PagesList.add() memiliki tes, yang seharusnya, hal ini benar-benar sederhana kode dan tidak ada alasan yang jelas untuk bagian ini perlu terpisah khusus pengujian.

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

Sekali lagi, saya melihat ada kebutuhan yang mendesak untuk pengujian terpisah dari bagian ini. Kecuali PagesList.add() memiliki masalah yang tidak biasa dengan nulls atau duplikat atau input lainnya.

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

Kode ini juga sangat mudah. Dengan asumsi bahwa customBazParser diuji dan doesn't kembali terlalu banyak "khusus" hasil. Jadi sekali lagi, kecuali jika ada situasi yang rumit dengan `PagesList.add(), (yang ada bisa menjadi seperti I'm tidak akrab dengan domain anda) aku don't melihat mengapa bagian ini membutuhkan pengujian khusus.

Secara umum, pengujian seluruh fungsi harus bekerja dengan baik.

Disclaimer: Jika ada alasan khusus untuk menguji semua 8 kemungkinan tiga jika () pernyataan, maka ya, jangan berpisah tes. Atau, jikaPagesList.add()` sensitif, ya, berpisah tes.

Struktur: Apakah itu layak putus menjadi tiga bagian (seperti Gaul)

Di sini anda memiliki argumen yang terbaik. Secara pribadi, saya don't pikir asli kode "terlalu panjang" (I'm tidak SRP fanatik). Tapi, jika ada beberapa jika (apiData.halaman.bla) bagian, kemudian SRP air mata itu's kepala jelek dan itu akan bernilai membelah. Terutama jika KERING berlaku dan fungsi yang dapat digunakan di tempat-tempat lain dari kode.

Saya salah satu saran

YMMV. Untuk menyimpan baris kode, dan logika, saya bisa menggabungkan jika dan mari ke dalam satu baris: misalnya

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

Ini akan gagal jika apiData.halaman.arrayOfBars adalah Angka atau String, tapi begitu akan kode asli. Dan untuk saya itu adalah jelas (dan berlebihan idiom).

Komentar (0)