“Semuanya adalah Peta”, apakah saya melakukan ini dengan benar?

69

Saya menyaksikan pembicaraan Stuart Sierra " Thinking In Data " dan mengambil salah satu ide darinya sebagai prinsip desain dalam game yang saya buat ini. Perbedaannya adalah dia bekerja di Clojure dan saya bekerja di JavaScript. Saya melihat beberapa perbedaan utama antara bahasa kami dalam hal:

  • Clojure adalah pemrograman yang fungsional secara idiomatis
  • Kebanyakan negara tidak berubah

Saya mengambil ide dari slide "Semuanya adalah Peta" (Dari 11 menit, 6 detik hingga> 29 menit). Beberapa hal yang dia katakan adalah:

  1. Setiap kali Anda melihat fungsi yang membutuhkan 2-3 argumen, Anda bisa membuat case untuk mengubahnya menjadi peta dan hanya meneruskan peta. Ada banyak keuntungan untuk itu:
    1. Anda tidak perlu khawatir tentang urutan argumen
    2. Anda tidak perlu khawatir tentang informasi tambahan apa pun. Jika ada kunci tambahan, itu bukan urusan kami. Mereka mengalir begitu saja, mereka tidak ikut campur.
    3. Anda tidak perlu mendefinisikan skema
  2. Berlawanan dengan mengirimkan Obyek, tidak ada data yang disembunyikan. Tapi, dia membuat kasus bahwa persembunyian data dapat menyebabkan masalah dan dinilai terlalu tinggi:
    1. Performa
    2. Kemudahan implementasi
    3. Segera setelah Anda berkomunikasi melalui jaringan atau lintas proses, Anda harus tetap memiliki kedua belah pihak menyetujui representasi data. Itu pekerjaan ekstra yang bisa Anda lewati jika Anda hanya bekerja pada data.
  3. Paling relevan dengan pertanyaan saya. Ini adalah 29 menit dalam: "Jadikan fungsi Anda dapat dikompilasi". Berikut contoh kode yang ia gunakan untuk menjelaskan konsep:

    ;; Bad
    (defn complex-process []
      (let [a (get-component @global-state)
            b (subprocess-one a) 
            c (subprocess-two a b)
            d (subprocess-three a b c)]
        (reset! global-state d)))
    
    ;; Good
    (defn complex-process [state]
      (-> state
        subprocess-one
        subprocess-two
        subprocess-three))
    

    Saya mengerti sebagian besar programmer tidak terbiasa dengan Clojure, jadi saya akan menulis ulang ini dengan gaya imperatif:

    ;; Good
    def complex-process(State state)
      state = subprocess-one(state)
      state = subprocess-two(state)
      state = subprocess-three(state)
      return state
    

    Inilah kelebihannya:

    1. Mudah diuji
    2. Mudah untuk melihat fungsi-fungsi tersebut secara terpisah
    3. Mudah mengomentari satu baris ini dan melihat apa hasilnya dengan menghapus satu langkah
    4. Setiap subproses dapat menambahkan lebih banyak informasi ke negara. Jika subproses seseorang perlu mengkomunikasikan sesuatu ke subproses tiga, itu semudah menambahkan kunci / nilai.
    5. Tidak ada boilerplate untuk mengekstraksi data yang Anda butuhkan dari negara hanya agar Anda dapat menyimpannya kembali. Cukup lewati seluruh negara bagian dan biarkan subproses menetapkan apa yang dibutuhkan.

Sekarang, kembali ke situasi saya: Saya mengambil pelajaran ini dan menerapkannya pada permainan saya. Artinya, hampir semua fungsi tingkat tinggi saya mengambil dan mengembalikan gameStateobjek. Objek ini berisi semua data permainan. EG: Daftar badGuys, daftar menu, jarahan di tanah, dll. Berikut adalah contoh fungsi pembaruan saya:

update(gameState)
  ...
  gameState = handleUnitCollision(gameState)
  ...
  gameState = handleLoot(gameState)
  ...

Yang ingin saya tanyakan di sini adalah, sudahkah saya membuat beberapa kekejian yang memutarbalikkan ide yang hanya praktis dalam bahasa pemrograman fungsional? JavaScript tidak berfungsi secara idiomatis (meskipun dapat ditulis seperti itu) dan sangat sulit untuk menulis struktur data yang tidak dapat diubah. Satu hal yang mengkhawatirkan saya adalah dia berasumsi bahwa masing-masing subproses itu murni. Mengapa asumsi itu perlu dibuat? Jarang ada fungsi saya yang murni (maksud saya, mereka sering memodifikasi gameState. Saya tidak punya efek samping rumit lain selain itu). Apakah ide-ide ini berantakan jika Anda tidak memiliki data abadi?

Saya khawatir bahwa suatu hari saya akan bangun dan menyadari bahwa seluruh desain ini palsu dan saya benar-benar baru saja menerapkan anti-pola Big Ball Of Mud .


Sejujurnya, saya telah mengerjakan kode ini selama berbulan-bulan dan ini sangat bagus. Saya merasa seperti saya mendapatkan semua keuntungan yang diklaimnya. Kode saya sangat mudah untuk saya pikirkan. Tapi saya tim satu orang jadi saya memiliki kutukan pengetahuan.

Memperbarui

Saya telah mengkode 6+ bulan dengan pola ini. Biasanya saat ini saya lupa apa yang telah saya lakukan dan di situlah "apakah saya menulis ini dengan cara yang bersih?" ikut bermain. Jika tidak, saya akan benar-benar berjuang. Sejauh ini, saya tidak berjuang sama sekali.

Saya mengerti bagaimana set mata lain diperlukan untuk memvalidasi perawatannya. Yang bisa saya katakan adalah saya peduli tentang rawatan pertama dan terutama. Saya selalu menjadi penginjil paling keras untuk kode bersih di mana pun saya bekerja.

Saya ingin membalas langsung kepada mereka yang sudah memiliki pengalaman pribadi yang buruk dengan cara pengkodean ini. Saya tidak mengetahuinya saat itu, tetapi saya pikir kita benar-benar berbicara tentang dua cara penulisan kode yang berbeda. Cara saya melakukannya tampaknya lebih terstruktur daripada apa yang orang lain alami. Ketika seseorang memiliki pengalaman pribadi yang buruk dengan "Semuanya adalah peta" mereka berbicara tentang betapa sulitnya mempertahankan karena:

  1. Anda tidak pernah tahu struktur peta yang membutuhkan fungsi
  2. Fungsi apa pun dapat mengubah input dengan cara yang tidak pernah Anda harapkan. Anda harus melihat seluruh basis kode untuk mencari tahu bagaimana kunci tertentu masuk ke peta atau mengapa itu menghilang.

Bagi mereka yang memiliki pengalaman seperti itu, mungkin basis kode adalah, "Semuanya membutuhkan 1 dari N jenis peta." Milik saya adalah, "Semuanya membutuhkan 1 dari 1 jenis peta". Jika Anda tahu struktur 1 tipe itu, Anda tahu struktur segalanya. Tentu saja, struktur itu biasanya tumbuh seiring waktu. Itu sebabnya ...

Ada satu tempat untuk mencari implementasi referensi (yaitu: skema). Implementasi referensi ini adalah kode yang digunakan game agar tidak ketinggalan zaman.

Adapun poin kedua, saya tidak menambahkan / menghapus kunci ke peta di luar implementasi referensi, saya hanya bermutasi apa yang sudah ada. Saya juga memiliki serangkaian besar tes otomatis.

Jika arsitektur ini akhirnya runtuh karena bobotnya sendiri, saya akan menambahkan pembaruan kedua. Kalau tidak, anggap semuanya berjalan dengan baik :)

Daniel Kaplan
sumber
2
Pertanyaan keren (+1)! Saya merasa ini adalah latihan yang sangat berguna untuk mencoba dan menerapkan idiom fungsional dalam bahasa yang tidak berfungsi (atau tidak sangat fungsional).
Giorgio
15
Siapa pun yang akan memberi tahu Anda bahwa menyembunyikan informasi gaya OO (dengan properti dan fungsi accessor) adalah hal yang buruk karena hit kinerja (biasanya dapat diabaikan), dan kemudian memberitahu Anda untuk mengubah semua parameter Anda menjadi peta, yang memberi Anda overhead (yang lebih besar) pencarian hash setiap kali Anda mencoba untuk mengambil nilai, dapat diabaikan dengan aman.
Mason Wheeler
4
@MasonWheeler katakanlah Anda benar tentang ini. Apakah Anda akan membatalkan setiap poin yang ia buat karena satu hal ini salah?
Daniel Kaplan
9
Dalam Python (dan saya percaya sebagian besar bahasa dinamis, termasuk Javascript), objek sebenarnya hanya sintaksis gula untuk dict / map.
Lie Ryan
6
@ EvanPlaice: Notasi O-besar bisa menipu. Fakta sederhananya adalah, segala sesuatu berjalan lambat dibandingkan dengan akses langsung dengan dua atau tiga instruksi kode mesin individual, dan pada sesuatu yang terjadi sesering pemanggilan fungsi, overhead itu akan bertambah dengan sangat cepat.
Mason Wheeler

Jawaban:

42

Saya telah mendukung aplikasi di mana 'semuanya adalah peta' sebelumnya. Itu ide yang buruk. TOLONG jangan lakukan itu!

Saat Anda menentukan argumen yang diteruskan ke fungsi, itu membuatnya sangat mudah untuk mengetahui nilai apa yang dibutuhkan fungsi. Ini menghindari melewati data asing ke fungsi yang hanya mengganggu programmer - setiap nilai yang disahkan menyiratkan bahwa itu diperlukan, dan itu membuat programmer yang mendukung kode Anda harus mencari tahu mengapa data diperlukan.

Di sisi lain, jika Anda melewatkan semuanya sebagai peta, programmer yang mendukung aplikasi Anda harus sepenuhnya memahami fungsi yang dipanggil dengan segala cara untuk mengetahui nilai apa yang dibutuhkan peta. Lebih buruk lagi, sangat menggoda untuk menggunakan kembali peta yang dilewati ke fungsi saat ini untuk meneruskan data ke fungsi berikutnya. Ini berarti bahwa programmer yang mendukung aplikasi Anda perlu mengetahui semua fungsi yang dipanggil oleh fungsi saat ini untuk memahami apa fungsi saat ini. Itu persis kebalikan dari tujuan untuk menulis fungsi - mengabstraksi masalah sehingga Anda tidak perlu memikirkannya! Sekarang bayangkan 5 panggilan mendalam dan 5 panggilan lebar masing-masing. Banyak sekali yang harus diingat dan banyak kesalahan yang harus dilakukan.

"semuanya adalah peta" juga tampaknya mengarah pada penggunaan peta sebagai nilai balik. Aku telah melihatnya. Dan, sekali lagi, itu menyakitkan. Fungsi-fungsi yang dipanggil harus tidak pernah saling menimpa nilai balik satu sama lain - kecuali jika Anda mengetahui fungsionalitas segalanya dan tahu bahwa nilai peta input X perlu diganti untuk panggilan fungsi berikutnya. Dan fungsi saat ini perlu memodifikasi peta untuk mengembalikan nilainya, yang terkadang harus menimpa nilai sebelumnya dan kadang-kadang tidak.

edit - contoh

Berikut adalah contoh di mana ini bermasalah. Ini adalah aplikasi web. Input pengguna diterima dari lapisan UI dan ditempatkan di peta. Kemudian fungsi dipanggil untuk memproses permintaan. Set fungsi pertama akan memeriksa input yang salah. Jika ada kesalahan, pesan kesalahan akan diletakkan di peta. Fungsi panggilan akan memeriksa peta untuk entri ini dan menulis nilai dalam ui jika ada.

Set fungsi berikutnya akan memulai logika bisnis. Setiap fungsi akan mengambil peta, menghapus beberapa data, memodifikasi beberapa data, beroperasi pada data dalam peta dan meletakkan hasilnya di peta, dll. Fungsi selanjutnya akan mengharapkan hasil dari fungsi sebelumnya di peta. Untuk memperbaiki bug di fungsi berikutnya, Anda harus menyelidiki semua fungsi sebelumnya serta pemanggil untuk menentukan di mana saja nilai yang diharapkan mungkin telah ditetapkan.

Fungsi selanjutnya akan menarik data dari database. Atau, lebih tepatnya, mereka akan meneruskan peta ke lapisan akses data. DAL akan memeriksa apakah peta berisi nilai-nilai tertentu untuk mengontrol bagaimana kueri dieksekusi. Jika 'justcount' adalah kunci, maka kueri akan menjadi 'hitung pilih foo dari bilah'. Salah satu fungsi yang sebelumnya disebut mungkin memiliki fungsi yang menambahkan 'justcount' ke peta. Hasil kueri akan ditambahkan ke peta yang sama.

Hasilnya akan menggelembung ke penelepon (logika bisnis) yang akan memeriksa peta untuk apa yang harus dilakukan. Beberapa di antaranya akan berasal dari hal-hal yang ditambahkan ke peta oleh logika bisnis awal. Beberapa akan datang dari data dari database. Satu-satunya cara untuk mengetahui dari mana asalnya adalah menemukan kode yang menambahkannya. Dan lokasi lain yang juga bisa menambahkannya.

Kode ini secara efektif merupakan kekacauan monolitik, yang harus Anda pahami secara keseluruhan untuk mengetahui dari mana satu entri di peta berasal.

atk
sumber
2
Paragraf kedua Anda masuk akal bagi saya dan itu terdengar sangat menyebalkan. Saya mendapatkan pengertian dari paragraf ketiga Anda bahwa kita tidak benar-benar berbicara tentang desain yang sama. "gunakan kembali" adalah intinya. Itu salah untuk menghindarinya. Dan saya benar-benar tidak bisa berhubungan dengan paragraf terakhir Anda. Saya memiliki setiap fungsi mengambil gameStatetanpa mengetahui apa-apa tentang apa yang terjadi sebelum atau sesudahnya. Ini hanya bereaksi terhadap data yang diberikan. Bagaimana Anda masuk ke situasi di mana fungsi-fungsi akan saling menginjak-injak kaki? Bisakah Anda memberi contoh?
Daniel Kaplan
2
Saya menambahkan contoh untuk mencoba dan membuatnya sedikit lebih jelas. Semoga ini bisa membantu. Juga, ada perbedaan antara melewati objek negara yang terdefinisi dengan baik dibandingkan dengan melewati gumpalan yang menetapkan diubah oleh banyak tempat karena berbagai alasan, pencampuran logika ui, logika bisnis, dan logika akses basis data
atk
28

Secara pribadi, saya tidak akan merekomendasikan pola itu dalam paradigma yang manapun. Ini membuatnya lebih mudah untuk menulis pada awalnya dengan mengorbankan membuatnya lebih sulit untuk dipikirkan nanti.

Misalnya, coba jawab pertanyaan berikut tentang masing-masing fungsi subproses:

  • Bidang mana yang statedibutuhkan?
  • Kolom mana yang dimodifikasi?
  • Bidang mana yang tidak berubah?
  • Bisakah Anda mengatur ulang urutan fungsi dengan aman?

Dengan pola ini, Anda tidak dapat menjawab pertanyaan-pertanyaan itu tanpa membaca seluruh fungsi.

Dalam bahasa berorientasi objek, polanya bahkan lebih tidak masuk akal, karena melacak keadaan adalah apa yang dilakukan objek.

Karl Bielefeldt
sumber
2
"manfaat dari keabadian menurun semakin besar objek Anda yang abadi". Mengapa? Apakah itu komentar tentang kinerja atau pemeliharaan? Tolong uraikan kalimat itu.
Daniel Kaplan
8
@tieTYT Immutables bekerja dengan baik ketika ada sesuatu yang kecil (tipe numerik misalnya). Anda dapat menyalinnya, membuatnya, membuangnya, terbang dengan bobot yang lebih murah. Ketika Anda mulai berurusan dengan seluruh kondisi permainan yang terdiri dari peta, pohon, daftar, dan lusinan yang dalam dan besar, jika tidak ratusan variabel, biaya untuk menyalin atau menghapusnya naik (dan bobot terbang menjadi tidak praktis).
3
Saya melihat. Apakah itu masalah "data tidak berubah dalam bahasa imperatif" atau masalah "data tidak berubah"? IE: Mungkin ini bukan masalah dalam kode Clojure. Tapi saya bisa melihat bagaimana ini di JS. Ini juga menyusahkan untuk menulis semua kode boilerplate untuk melakukannya.
Daniel Kaplan
3
@MichaelT dan Karl: agar adil, Anda harus benar-benar menyebutkan sisi lain dari kisah ketidakberdayaan / efisiensi. Ya, penggunaan naif bisa sangat tidak efisien, itu sebabnya orang telah datang dengan pendekatan yang lebih baik. Lihat karya Chris Okasaki untuk informasi lebih lanjut.
3
@MattFenwick Saya pribadi sangat suka kekal. Ketika berhadapan dengan threading, saya tahu hal-hal tentang yang abadi dan dapat bekerja dan menyalinnya dengan aman. Saya memasukkannya ke dalam panggilan parameter dan meneruskannya ke yang lain tanpa khawatir bahwa seseorang akan memodifikasinya ketika kembali kepada saya. Jika seseorang berbicara tentang kondisi permainan yang kompleks (pertanyaan menggunakan ini sebagai contoh - saya akan ngeri memikirkan sesuatu yang 'sederhana' seperti kondisi permainan nethack sebagai kekal), ketidakmampuan mungkin pendekatan yang salah.
12

Apa yang tampaknya Anda lakukan adalah, secara efektif, monad negara manual; apa yang akan saya lakukan adalah membangun kombinator mengikat (disederhanakan) dan menyatakan kembali koneksi antara langkah logis Anda menggunakan itu:

function stateBind() {
    var computation = function (state) { return state; };
    for ( var i = 0 ; i < arguments.length ; i++ ) {
        var oldComp = computation;
        var newComp = arguments[i];
        computation = function (state) { return newComp(oldComp(state)); };
    }
    return computation;
}

...

stateBind(
  subprocessOne,
  subprocessTwo,
  subprocessThree,
);

Anda bahkan dapat menggunakan stateBinduntuk membangun berbagai sub proses dari sub proses, dan melanjutkan ke bawah pohon kombinator yang mengikat untuk menyusun komputasi Anda dengan tepat.

Untuk penjelasan tentang monad lengkap dan tidak disederhanakan, dan pengantar yang sangat baik untuk monad secara umum dalam JavaScript, lihat posting blog ini .

Api Ptharien
sumber
1
OK, saya akan memeriksanya (dan berkomentar nanti). Tapi apa yang Anda pikirkan tentang ide menggunakan pola?
Daniel Kaplan
1
@ TTTYT Saya pikir polanya sendiri adalah ide yang sangat bagus; State monad secara umum adalah alat penataan kode yang berguna untuk algoritma pseudo-mutable (algoritma yang tidak dapat diubah tetapi meniru kemampuan untuk berubah).
Ptharien's Flame
2
+1 untuk mencatat bahwa pola ini pada dasarnya adalah Monad. Namun, saya tidak setuju bahwa itu ide yang baik dalam bahasa yang sebenarnya memiliki kemampuan berubah-ubah. Monad adalah cara untuk memberikan kemampuan global / negara yang bisa berubah dalam bahasa yang tidak memungkinkan mutasi. IMO, dalam bahasa yang tidak memaksakan imutabilitas, pola Monad hanyalah masturbasi mental.
Lie Ryan
6
@LieRyan Monads secara umum sebenarnya tidak ada hubungannya sama sekali dengan ketidakstabilan atau global; hanya State monad yang melakukannya (karena itulah yang dirancang khusus untuk itu). Saya juga tidak setuju bahwa State monad tidak berguna dalam bahasa dengan kemampuan berubah-ubah, meskipun implementasi yang bergantung pada kemampuan berubah-ubah di bawahnya mungkin lebih efisien daripada yang tidak berubah yang saya berikan (walaupun saya sama sekali tidak yakin tentang itu). Antarmuka monadik dapat memberikan kemampuan tingkat tinggi yang tidak mudah diakses, stateBindkombinasi yang saya berikan adalah contoh yang sangat sederhana.
Flame Ptharien
1
@LieRyan I komentar kedua Ptharien - kebanyakan monad bukan tentang negara atau kemampuan berubah, dan bahkan yang demikian, secara khusus bukan tentang keadaan global . Monads sebenarnya bekerja cukup baik dalam bahasa OO / imperatif / bisa berubah.
11

Jadi, tampaknya ada banyak diskusi antara efektivitas pendekatan ini di Clojure. Saya pikir mungkin berguna untuk melihat filosofi Rich Hickey tentang mengapa ia menciptakan Clojure untuk mendukung abstraksi data dengan cara ini :

Fogus: Jadi begitu kompleksitas insidental telah dikurangi, bagaimana Clojure dapat membantu menyelesaikan masalah yang ada? Sebagai contoh, paradigma berorientasi objek yang ideal dimaksudkan untuk mendorong penggunaan kembali, tetapi Clojure tidak berorientasi objek klasik — bagaimana kita bisa menyusun kode kita untuk digunakan kembali?

Hickey: Saya akan berdebat tentang OO dan menggunakan kembali, tetapi tentu saja, dapat menggunakan kembali hal-hal membuat masalah lebih mudah, karena Anda tidak menciptakan kembali roda daripada membuat mobil. Dan Clojure berada di JVM membuat banyak roda — perpustakaan — tersedia. Apa yang membuat perpustakaan dapat digunakan kembali? Itu harus melakukan satu atau beberapa hal dengan baik, relatif mandiri, dan membuat beberapa tuntutan pada kode klien. Tidak ada yang keluar dari OO, dan tidak semua perpustakaan Java memenuhi kriteria ini, tetapi banyak yang melakukannya.

Ketika kita turun ke level algoritma, saya pikir OO dapat secara serius menggagalkan penggunaan kembali. Secara khusus, penggunaan objek untuk mewakili data informasi sederhana hampir bersifat kriminal dalam pembuatan bahasa mikro per-informasi, yaitu metode kelas, dibandingkan metode yang jauh lebih kuat, deklaratif, dan generik seperti aljabar relasional. Menciptakan kelas dengan antarmuka sendiri untuk menyimpan informasi adalah seperti menciptakan bahasa baru untuk menulis setiap cerita pendek. Ini anti-penggunaan kembali, dan, saya pikir, menghasilkan ledakan kode dalam aplikasi OO khas. Clojure menghindari ini dan bukannya menganjurkan model asosiatif sederhana untuk informasi. Dengan itu, seseorang dapat menulis algoritma yang dapat digunakan kembali di berbagai jenis informasi.

Model asosiatif ini hanyalah salah satu dari beberapa abstraksi yang disuplai dengan Clojure, dan ini adalah dasar sebenarnya dari pendekatannya untuk menggunakan kembali: fungsi pada abstraksi. Memiliki serangkaian fungsi yang terbuka dan besar beroperasi pada abstraksi yang dapat diperpanjang dan kecil, merupakan kunci untuk penggunaan kembali secara algoritmik dan interoperabilitas perpustakaan. Sebagian besar fungsi Clojure didefinisikan dalam hal abstraksi-abstraksi ini, dan penulis perpustakaan merancang format input dan output mereka dalam hal itu juga, mewujudkan interoperabilitas yang luar biasa antara perpustakaan yang dikembangkan secara independen. Ini sangat kontras dengan DOM dan hal-hal lain yang Anda lihat di OO. Tentu saja, Anda dapat melakukan abstraksi serupa di OO dengan antarmuka, misalnya, koleksi java.util, tetapi Anda dapat dengan mudah tidak melakukannya, seperti di java.io.

Fogus mengulangi poin-poin ini dalam bukunya Functional Javascript :

Sepanjang buku ini, saya akan mengambil pendekatan menggunakan tipe data minimal untuk mewakili abstraksi, dari set ke pohon ke tabel. Namun, dalam JavaScript, meskipun jenis objeknya sangat kuat, alat yang disediakan untuk bekerja dengannya tidak sepenuhnya fungsional. Alih-alih, pola penggunaan yang lebih besar yang terkait dengan objek JavaScript adalah melampirkan metode untuk keperluan pengiriman polimorfik. Untungnya, Anda juga dapat melihat objek JavaScript yang tidak disebutkan namanya (tidak dibangun melalui fungsi konstruktor) hanya sebagai penyimpanan data asosiatif.

Jika satu-satunya operasi yang dapat kita lakukan pada objek Buku atau instance dari tipe Karyawan adalah setTitle atau getSSN, maka kita telah mengunci data kita menjadi mikro-bahasa per-potong-informasi (Hickey 2011). Pendekatan yang lebih fleksibel untuk memodelkan data adalah teknik data asosiatif. Objek JavaScript, bahkan tanpa mesin prototipe, adalah kendaraan yang ideal untuk pemodelan data asosiatif, di mana nilai-nilai yang disebutkan dapat disusun untuk membentuk model data tingkat tinggi, diakses dengan cara yang seragam.

Meskipun alat untuk memanipulasi dan mengakses objek JavaScript sebagai peta data jarang dalam JavaScript itu sendiri, untungnya Underscore menyediakan sejumlah operasi yang bermanfaat. Di antara fungsi-fungsi paling sederhana untuk dipahami adalah _.keys, _.values, dan _.pluck. Baik _.keys dan _.values ​​diberi nama sesuai dengan fungsinya, yaitu untuk mengambil objek dan mengembalikan array kunci atau nilainya ...

pooya72
sumber
2
Saya membaca wawancara Fogus / Hickey ini sebelumnya, tetapi saya tidak mampu memahami apa yang dia bicarakan sampai sekarang. Terima kasih atas jawaban anda. Masih tidak yakin apakah Hickey / Fogus akan memberikan desain saya berkat mereka. Saya khawatir saya mengambil semangat saran mereka ke ekstrem.
Daniel Kaplan
9

Pengacara Setan

Saya pikir pertanyaan ini layak untuk advokat iblis (tapi tentu saja saya bias). Saya pikir @KarlBielefeldt membuat poin yang sangat bagus dan saya ingin mengatasinya. Pertama saya ingin mengatakan bahwa poinnya bagus.

Karena dia menyebutkan ini bukan pola yang baik bahkan dalam pemrograman fungsional, saya akan mempertimbangkan JavaScript dan / atau Clojure di balasan saya. Satu kesamaan yang sangat penting antara kedua bahasa ini adalah mereka diketik secara dinamis. Saya akan lebih setuju dengan poinnya jika saya mengimplementasikan ini dalam bahasa yang diketik secara statis seperti Java atau Haskell. Tapi, saya akan mempertimbangkan alternatif dari pola "Semuanya adalah Peta" untuk menjadi desain OOP tradisional dalam JavaScript dan bukan dalam bahasa yang diketik secara statis (saya harap saya tidak menyiapkan argumen strawman dengan melakukan ini, tolong beritahu saya).

Misalnya, coba jawab pertanyaan berikut tentang masing-masing fungsi subproses:

  • Bidang negara mana yang dibutuhkannya?

  • Kolom mana yang dimodifikasi?

  • Bidang mana yang tidak berubah?

Dalam bahasa yang diketik secara dinamis, bagaimana Anda biasanya menjawab pertanyaan-pertanyaan ini? Parameter pertama suatu fungsi mungkin dinamai foo, tetapi apakah itu? Array? Sebuah Objek? Objek array objek? Bagaimana Anda mengetahuinya? Satu-satunya cara saya tahu adalah untuk

  1. baca dokumentasinya
  2. lihat fungsi tubuhnya
  3. lihat tes
  4. tebak dan jalankan program untuk melihat apakah itu berfungsi.

Saya tidak berpikir pola "Semuanya adalah Peta" membuat perbedaan di sini. Ini masih satu-satunya cara saya tahu untuk menjawab pertanyaan-pertanyaan ini.

Juga perlu diingat bahwa dalam JavaScript dan sebagian besar bahasa pemrograman imperatif, siapa pun functiondapat meminta, memodifikasi, dan mengabaikan status apa pun yang dapat diakses dan tanda tangan tidak membuat perbedaan: Fungsi / metode dapat melakukan sesuatu dengan keadaan global atau dengan singleton. Tanda tangan sering berbohong.

Saya tidak mencoba membuat dikotomi palsu antara "Semuanya adalah Peta" dan kode OO yang dirancang dengan buruk . Saya hanya mencoba menunjukkan bahwa memiliki tanda tangan yang menerima parameter berbutir lebih sedikit / lebih halus / kasar tidak menjamin Anda tahu cara mengisolasi, mengatur dan memanggil fungsi.

Tetapi, jika Anda mengizinkan saya untuk menggunakan dikotomi palsu itu: Dibandingkan dengan menulis JavaScript dengan cara OOP tradisional, "Semuanya adalah Peta" tampaknya lebih baik. Dengan cara OOP tradisional, fungsi tersebut mungkin memerlukan, mengubah atau mengabaikan keadaan yang Anda lewati atau menyatakan bahwa Anda tidak lulus. Dengan pola "Semuanya adalah Peta" ini, Anda hanya memerlukan, mengubah, atau mengabaikan keadaan yang Anda lewati di.

  • Bisakah Anda mengatur ulang urutan fungsi dengan aman?

Dalam kode saya, ya. Lihat komentar kedua saya untuk jawaban @ Evicatos. Mungkin ini hanya karena saya membuat game, saya tidak bisa mengatakannya. Dalam gim yang memperbarui 60x per detik, tidak masalah apakah dead guys drop lootitu good guys pick up lootatau sebaliknya. Setiap fungsi masih melakukan apa yang seharusnya dilakukan terlepas dari urutan apa yang mereka jalankan. Data yang sama hanya akan dimasukkan ke mereka pada updatepanggilan yang berbeda jika Anda menukar pesanan. Jika Anda memiliki good guys pick up lootkemudian dead guys drop loot, orang-orang baik akan mengambil jarahan di depan updatedan itu bukan masalah besar. Manusia tidak akan bisa melihat perbedaannya.

Setidaknya ini adalah pengalaman umum saya. Saya merasa sangat rentan mengakui hal ini di depan umum. Mungkin menganggap ini baik-baik saja adalah hal yang sangat , sangat buruk untuk dilakukan. Beri tahu saya jika saya melakukan kesalahan besar di sini. Tapi, jika saya memiliki, itu sangat mudah untuk mengatur ulang fungsi sehingga order dead guys drop lootkemudian good guys pick up lootlagi. Ini akan memakan waktu lebih sedikit daripada waktu yang dibutuhkan untuk menulis paragraf ini: P

Mungkin Anda berpikir "orang mati harus membuang barang rampasan terlebih dahulu. Akan lebih baik jika kode Anda menerapkan perintah itu". Tapi, mengapa musuh harus menjatuhkan jarahan sebelum kamu bisa mengambil jarahan? Bagi saya itu tidak masuk akal. Mungkin jarahan dijatuhkan 100 yang updateslalu. Tidak perlu memeriksa apakah orang jahat yang sewenang-wenang harus mengambil barang jarahan yang sudah ada di tanah. Itu sebabnya saya pikir urutan operasi ini sepenuhnya sewenang-wenang.

Wajar untuk menulis langkah-langkah yang dipisahkan dengan pola ini, tetapi sulit untuk melihat langkah-langkah Anda dalam OOP tradisional. Jika saya menulis OOP tradisional, cara berpikir yang alami dan naif adalah membuat dead guys drop lootpengembalian Lootobjek yang harus saya lewati good guys pick up loot. Saya tidak akan dapat menyusun ulang operasi tersebut sejak yang pertama mengembalikan input yang kedua.

Dalam bahasa berorientasi objek, polanya bahkan lebih tidak masuk akal, karena melacak keadaan adalah apa yang dilakukan objek.

Objek memiliki status dan idiom untuk mengubah keadaan dan membuat riwayatnya hilang begitu saja ... kecuali jika Anda menulis kode secara manual untuk melacaknya. Dengan cara apa melacak status "apa yang mereka lakukan"?

Juga, manfaat dari immutability semakin menurun semakin besar objek abadi Anda dapatkan.

Benar, seperti yang saya katakan, "Jarang ada fungsi saya yang murni". Mereka selalu hanya beroperasi pada parameter mereka, tetapi mereka mengubah parameter mereka. Ini adalah kompromi yang saya rasa harus saya buat ketika menerapkan pola ini ke JavaScript.

Daniel Kaplan
sumber
4
"Parameter pertama suatu fungsi mungkin dinamai foo, tapi apa itu?" Itu sebabnya Anda tidak memberi nama parameter Anda "foo", tetapi "pengulangan", "orang tua", dan nama-nama lain yang membuatnya jelas apa yang diharapkan ketika dikombinasikan dengan nama fungsi.
Sebastian Redl
1
Saya harus setuju dengan Anda dalam semua hal. Satu-satunya masalah Javascript benar-benar menimbulkan dengan pola ini, adalah bahwa Anda bekerja pada data yang bisa berubah, dan dengan demikian, Anda sangat mungkin untuk bermutasi. Namun ada, perpustakaan yang memberi Anda akses ke clojure datastructures di javascript biasa, meskipun saya lupa apa namanya. Melewati argumen sebagai objek juga tidak pernah terdengar, jquery melakukan ini di beberapa tempat, tetapi mendokumentasikan bagian mana dari objek yang mereka gunakan. Secara pribadi, saya akan memisahkan bidang UI dan bidang GameLogic, tetapi apa pun yang bekerja untuk Anda :)
Robin Heggelund Hansen
@SebastianRedl Untuk apa saya harus lulus parent? Apakah repetitionsdan susunan angka atau string atau tidak penting? Atau mungkin pengulangan hanyalah angka untuk mewakili jumlah represi yang saya inginkan? Ada banyak apis di luar sana yang hanya mengambil objek opsi . Dunia adalah tempat yang lebih baik jika Anda memberi nama dengan benar, tetapi itu tidak menjamin Anda akan tahu cara menggunakan api, tidak ada pertanyaan yang diajukan.
Daniel Kaplan
8

Saya telah menemukan bahwa kode saya cenderung berakhir terstruktur seperti:

  • Fungsi yang mengambil peta cenderung lebih besar dan memiliki efek samping.
  • Fungsi yang mengambil argumen cenderung lebih kecil dan murni.

Saya tidak memulai untuk membuat perbedaan ini, tetapi sering kali itu berakhir pada kode saya. Saya tidak berpikir menggunakan satu gaya harus meniadakan yang lain.

Fungsi murni mudah untuk unit test. Yang lebih besar dengan peta lebih banyak masuk ke area uji "integrasi" karena cenderung melibatkan lebih banyak bagian yang bergerak.

Dalam javascript, satu hal yang banyak membantu adalah menggunakan sesuatu seperti pustaka Meteor's Match untuk melakukan validasi parameter. Itu membuatnya sangat jelas apa fungsi yang diharapkan dan dapat menangani peta dengan cukup bersih.

Sebagai contoh,

function foo (post) {
  check(post, {
    text: String,
    timestamp: Date,
    // Optional, but if present must be an array of strings
    tags: Match.Optional([String])
    });

  // do stuff
}

Lihat http://docs.meteor.com/#match untuk informasi lebih lanjut.

:: PEMBARUAN ::

Rekaman video Stuart Sierra tentang Clojure / "Clojure in the Large" Barat juga menyentuh masalah ini. Seperti OP, ia mengontrol efek samping sebagai bagian dari peta sehingga pengujian menjadi lebih mudah. Dia juga memiliki posting blog yang menguraikan alur kerja Clojure saat ini yang tampaknya relevan.

alanning
sumber
1
Saya pikir komentar saya kepada @Evicatos akan menguraikan posisi saya di sini. Ya, saya bermutasi dan fungsinya tidak murni. Tetapi, fungsi saya sangat mudah untuk diuji, terutama jika ditinjau dari cacat regresi yang tidak saya rencanakan untuk diuji. Setengah kredit jatuh ke JS: Sangat mudah untuk membangun "peta" / objek dengan hanya data yang saya butuhkan untuk pengujian saya. Maka itu sesederhana menyerahkannya dan memeriksa mutasi. Efek samping selalu terwakili di peta, sehingga mudah untuk diuji.
Daniel Kaplan
1
Saya percaya penggunaan kedua metode secara pragmatis adalah cara yang "benar" untuk dilakukan. Jika pengujian mudah bagi Anda dan Anda dapat mengurangi masalah meta dalam mengkomunikasikan bidang yang diperlukan ke pengembang lain, maka itu terdengar seperti kemenangan. Terima kasih atas pertanyaan Anda; Saya senang membaca diskusi menarik yang telah Anda mulai.
alanning
5

Argumen utama yang dapat saya pikirkan terhadap praktik ini adalah bahwa sangat sulit untuk mengatakan data apa yang sebenarnya dibutuhkan suatu fungsi.

Apa artinya itu adalah bahwa programmer masa depan dalam basis kode harus tahu bagaimana fungsi yang dipanggil bekerja secara internal - dan setiap fungsi panggilan bersarang - untuk menyebutnya.

Semakin saya memikirkannya, semakin banyak objek gameState Anda yang berbau global. Jika seperti itulah cara itu digunakan, mengapa menyebarkannya?

Mike Partridge
sumber
1
Ya, karena saya biasanya bermutasi, itu global. Kenapa repot-repot menyebarkannya? Saya tidak tahu, itu pertanyaan yang valid. Tetapi nyali saya mengatakan bahwa jika saya berhenti melewatinya, program saya akan langsung menjadi lebih sulit untuk dipikirkan. Setiap fungsi dapat melakukan segala sesuatu atau tidak sama sekali bagi negara global. Caranya sekarang, Anda melihat bahwa potensi dalam fungsi tanda tangan. Jika Anda tidak tahu, saya tidak yakin tentang semua ini :)
Daniel Kaplan
1
BTW: re: argumen utama yang menentangnya: Tampaknya benar apakah ini dalam clojure atau javascript. Tapi itu poin berharga untuk dibuat. Mungkin manfaat yang terdaftar jauh lebih besar daripada yang negatif.
Daniel Kaplan
2
Saya sekarang tahu mengapa saya repot-repot menyebarkannya meskipun itu adalah variabel global: Ini memungkinkan saya untuk menulis fungsi murni. Jika saya mengubah gameState = f(gameState)ke saya f(), itu jauh lebih sulit untuk diuji f. f()dapat mengembalikan hal yang berbeda setiap kali saya menyebutnya. Tapi mudah untuk f(gameState)mengembalikan hal yang sama setiap kali diberi input yang sama.
Daniel Kaplan
3

Ada nama yang lebih pas untuk apa yang Anda lakukan daripada bola besar lumpur . Apa yang Anda lakukan disebut pola objek Dewa . Itu tidak terlihat seperti itu pada pandangan pertama, tetapi dalam Javascript ada sedikit perbedaan antara

update(gameState)
  ...
  gameState = handleUnitCollision(gameState)
  ...
  gameState = handleLoot(gameState)
  ...

dan

{
  ...
  handleUnitCollision: function() {
    ...
  },
  ...
  handleLoot: function() {
    ...
  },
  ...
  update: function() {
    ...
    this.handleUnitCollision()
    ...
    this.handleLoot()
    ...
  },
  ...
};

Apakah itu ide yang baik atau tidak mungkin tergantung pada keadaan. Tapi ini tentu sejalan dengan cara Clojure. Salah satu tujuan Clojure adalah untuk menghapus apa yang disebut Rich Hickey "kompleksitas insidental". Berbagai objek yang berkomunikasi tentu lebih kompleks daripada objek tunggal. Jika Anda membagi fungsionalitas menjadi beberapa objek, Anda tiba-tiba harus khawatir tentang komunikasi dan koordinasi dan membagi tanggung jawab. Itu adalah komplikasi yang hanya terkait dengan tujuan awal Anda menulis sebuah program. Anda akan melihat perkataan Rich Hickey menjadi mudah . Saya pikir ini adalah ide yang sangat bagus.

pengguna7610
sumber
Terkait, pertanyaan yang lebih umum [ programmers.stackexchange.com/questions/260309/... pemodelan data vs kelas tradisional)
user7610
"Dalam pemrograman berorientasi objek, objek dewa adalah objek yang tahu terlalu banyak atau melakukan terlalu banyak. Objek dewa adalah contoh anti-pola." Jadi objek dewa bukanlah hal yang baik, namun pesan Anda tampaknya mengatakan sebaliknya. Itu agak membingungkan saya.
Daniel Kaplan
@tieTYT Anda tidak melakukan pemrograman berorientasi objek, jadi tidak masalah
user7610
Bagaimana Anda sampai pada kesimpulan itu (yang "tidak apa-apa")?
Daniel Kaplan
Masalah dengan objek Allah dalam OO adalah bahwa "Objek menjadi sangat sadar akan segala sesuatu atau semua objek menjadi begitu tergantung pada objek dewa sehingga ketika ada perubahan atau bug untuk memperbaikinya, itu menjadi mimpi buruk nyata untuk diterapkan." sumber Dalam kode Anda ada objek lain di samping objek Dewa, jadi bagian kedua tidak masalah. Mengenai bagian pertama, objek Tuhan Anda adalah program dan program Anda harus menyadari segalanya. Jadi itu juga oke.
user7610
2

Saya baru saja menghadapi topik ini sebelumnya hari ini ketika bermain dengan proyek baru. Saya bekerja di Clojure untuk membuat game Poker. Saya mewakili nilai nominal dan setelan sebagai kata kunci, dan memutuskan untuk mewakili kartu sebagai peta

{ :face :queen :suit :hearts }

Saya bisa saja membuat mereka daftar atau vektor dari dua elemen kata kunci. Saya tidak tahu apakah itu membuat perbedaan memori / kinerja jadi saya hanya akan menggunakan peta untuk saat ini.

Jika saya berubah pikiran kemudian, saya memutuskan bahwa sebagian besar program saya harus melalui "antarmuka" untuk mengakses potongan-potongan kartu, sehingga detail implementasi dikontrol dan disembunyikan. Saya punya fungsi

(defn face [card] (card :face))
(defn suit [card] (card :suit))

yang digunakan sisa program. Kartu diteruskan ke fungsi sebagai peta, tetapi fungsi menggunakan antarmuka yang disepakati untuk mengakses peta dan dengan demikian seharusnya tidak dapat mengacaukan.

Dalam program saya, kartu mungkin hanya akan menjadi peta bernilai 2. Dalam pertanyaan, seluruh status game dilewatkan sebagai peta. Status permainan akan jauh lebih rumit daripada kartu tunggal, tapi saya rasa tidak ada kesalahan untuk menggunakan peta. Dalam bahasa object-imperative saya bisa saja memiliki satu objek GameState besar dan memanggil metodenya, dan memiliki masalah yang sama:

class State
  def complex-process()
    state = clone(this) ; or just use 'this' below if mutation is fine
    state.subprocess-one()
    state.subprocess-two()
    state.subprocess-three()
    return state

Sekarang berorientasi objek. Apakah ada yang salah dengan itu? Saya tidak berpikir begitu, Anda hanya mendelegasikan pekerjaan ke fungsi yang tahu bagaimana menangani objek Negara. Dan apakah Anda bekerja dengan peta atau objek, Anda harus waspada kapan harus membaginya menjadi potongan-potongan kecil. Jadi saya katakan menggunakan peta tidak masalah, selama Anda menggunakan perawatan yang sama yang akan Anda gunakan dengan objek.

xen
sumber
2

Dari apa (kecil) yang saya lihat, menggunakan peta atau struktur bersarang lainnya untuk membuat satu objek keadaan global tidak berubah seperti ini cukup umum dalam bahasa fungsional, setidaknya yang murni, terutama ketika menggunakan State Monad sebagai @ Ptharien'sFlame mentioend .

Dua penghalang untuk menggunakan ini secara efektif yang telah saya lihat / baca (dan jawaban lain yang disebutkan di sini) adalah:

  • Memutasi nilai bersarang (dalam) di kondisi (tidak berubah)
  • Menyembunyikan sebagian besar negara dari fungsi yang tidak membutuhkannya dan hanya memberi mereka sedikit yang mereka butuhkan untuk bekerja / bermutasi

Ada beberapa teknik / pola umum yang berbeda yang dapat membantu meringankan masalah ini:

Yang pertama adalah Ritsleting : ini memungkinkan seseorang melintasi dan bermutasi jauh di dalam hierarki bersarang abadi.

Lain adalah Lensa : ini memungkinkan Anda fokus ke struktur ke lokasi tertentu dan membaca / mengubah nilai di sana. Anda dapat menggabungkan lensa yang berbeda bersama-sama untuk fokus pada hal-hal yang berbeda, seperti rantai properti yang dapat disesuaikan di OOP (di mana Anda dapat mengganti variabel untuk nama properti yang sebenarnya!)

Prismatik baru-baru ini membuat posting blog tentang penggunaan teknik semacam ini, antara lain, dalam JavaScript / ClojureScript, yang harus Anda periksa. Mereka menggunakan kursor (yang mereka bandingkan dengan ritsleting) ke kondisi jendela untuk fungsi:

Om mengembalikan enkapsulasi dan modularitas menggunakan kursor. Kursor menyediakan jendela yang dapat dimutakhirkan ke bagian tertentu dari kondisi aplikasi (seperti resleting), memungkinkan komponen untuk mengambil referensi hanya ke bagian yang relevan dari keadaan global, dan memperbaruinya dengan cara bebas konteks.

IIRC, mereka juga menyentuh ketidakmampuan dalam JavaScript dalam posting itu.

paul
sumber
OP bicara yang disebutkan juga membahas penggunaan fungsi pembaruan untuk membatasi ruang lingkup fungsi yang dapat diperbarui ke subtree dari peta keadaan. Saya pikir belum ada yang mengemukakan ini.
user7610
@ user7610 Bagus menangkap, saya tidak percaya saya lupa menyebutkan yang satu itu - saya suka fungsi itu (dan assoc-inet al). Kira saya hanya punya Haskell di otak. Saya ingin tahu apakah ada yang melakukan port JavaScript? Orang mungkin tidak mengungkitnya karena (seperti saya) mereka tidak menonton pembicaraan :)
paul
@ paul dalam arti yang mereka miliki karena tersedia di ClojureScript, tapi saya tidak yakin apakah itu "diperhitungkan" dalam pikiran Anda. Mungkin ada di PureScript dan saya percaya setidaknya ada satu perpustakaan yang menyediakan struktur data yang tidak dapat diubah dalam JavaScript. Saya berharap setidaknya salah satu dari mereka memiliki ini, kalau tidak mereka tidak nyaman untuk digunakan.
Daniel Kaplan
@tieTYT Saya telah memikirkan implementasi JS asli ketika saya membuat komentar itu tetapi Anda membuat poin bagus tentang ClojureScript / PureScript. Saya harus melihat ke dalam JS abadi dan melihat apa yang ada di luar sana, saya belum pernah bekerja dengan itu sebelumnya.
paul
1

Apakah ini ide yang bagus atau tidak, akan sangat tergantung pada apa yang Anda lakukan dengan keadaan di dalam subproses tersebut. Jika saya memahami contoh Clojure dengan benar, kamus-kamus negara bagian yang dikembalikan bukanlah kamus-kamus negara bagian yang sama yang dilewatkan. Mereka adalah salinan, mungkin dengan tambahan dan modifikasi, yang (saya asumsikan) Clojure mampu membuat secara efisien karena sifat fungsional bahasa tergantung padanya. Kamus kondisi asli untuk setiap fungsi tidak dimodifikasi dengan cara apa pun.

Jika saya memahami dengan benar, Anda akan memodifikasi obyek negara Anda masuk ke dalam fungsi javascript Anda daripada kembali salinan, yang berarti Anda melakukan sesuatu yang sangat, sangat, berbeda dari apa yang kode Clojure lakukan. Seperti yang ditunjukkan Mike Partridge, ini pada dasarnya hanya global yang secara eksplisit Anda berikan dan kembali dari fungsi tanpa alasan yang jelas. Pada titik ini saya pikir itu hanya membuat Anda berpikir Anda melakukan sesuatu yang sebenarnya tidak Anda lakukan.

Jika Anda benar-benar secara eksplisit membuat salinan negara, memodifikasinya, dan kemudian mengembalikan salinan yang dimodifikasi itu, kemudian lanjutkan. Saya tidak yakin itu selalu cara terbaik untuk mencapai apa yang Anda coba lakukan dalam Javascript, tetapi mungkin "dekat" dengan apa yang dilakukan contoh Clojure.

Evicatos
sumber
1
Pada akhirnya itu benar - benar "sangat, sangat berbeda"? Dalam contoh Clojure ia menimpa negara lamanya dengan negara baru. Ya, tidak ada mutasi nyata yang terjadi, identitasnya hanya berubah. Tetapi dalam contoh "baik" -nya, seperti yang ditulis, ia tidak memiliki cara untuk mendapatkan salinan yang disahkan ke dalam subproses-dua. Identifikasi nilai itu ditimpa. Oleh karena itu, saya pikir hal yang "sangat, sangat berbeda" sebenarnya hanyalah detail implementasi bahasa. Setidaknya dalam konteks apa yang Anda ungkapkan.
Daniel Kaplan
2
Ada dua hal yang terjadi dengan contoh Clojure: 1) contoh pertama tergantung pada fungsi yang dipanggil dalam urutan tertentu, dan 2) fungsinya murni sehingga tidak memiliki efek samping. Karena fungsi dalam contoh kedua adalah murni dan berbagi tanda tangan yang sama, Anda dapat memesan ulang tanpa harus khawatir tentang dependensi tersembunyi pada urutan pemanggilannya. Jika Anda mengubah status dalam fungsi Anda maka Anda tidak memiliki jaminan yang sama. Mutasi status berarti versi Anda tidak dapat dikomposasikan, yang merupakan alasan asli untuk kamus.
Evicatos
1
Anda harus menunjukkan kepada saya sebuah contoh karena dalam pengalaman saya dengan ini, saya dapat memindahkan barang sesuka hati dan itu memiliki efek yang sangat kecil. Hanya untuk membuktikannya pada diri saya sendiri, saya memindahkan dua panggilan subproses acak di tengah update()fungsi saya . Saya memindahkan satu ke atas dan satu ke bawah. Semua tes saya masih berlalu dan ketika saya memainkan permainan saya tidak melihat efek buruk. Saya merasa fungsi saya sama komposisi seperti contoh Clojure. Kami berdua membuang data lama kami setelah setiap langkah.
Daniel Kaplan
1
Tes yang Anda lewati dan tidak memperhatikan efek buruk berarti bahwa Anda saat ini tidak mengubah keadaan yang memiliki efek samping yang tidak terduga di tempat lain. Karena fungsi Anda tidak murni meskipun Anda tidak memiliki jaminan yang akan selalu terjadi. Saya pikir saya harus salah paham secara mendasar tentang implementasi Anda jika Anda mengatakan bahwa fungsi Anda tidak murni tetapi Anda membuang data lama Anda setelah setiap langkah.
Evicatos
1
@Evicatos - Menjadi murni dan memiliki tanda tangan yang sama tidak menyiratkan bahwa urutan fungsi tidak masalah. Bayangkan menghitung harga dengan diskon tetap dan persentase yang diterapkan. (-> 10 (- 5) (/ 2))mengembalikan 2.5.(-> 10 (/ 2) (- 5))mengembalikan 0.
Zak
1

Jika Anda memiliki objek keadaan global, kadang-kadang disebut "objek dewa", yang diteruskan ke setiap proses, Anda akhirnya mengacaukan sejumlah faktor, yang semuanya meningkatkan kopling, sekaligus mengurangi kohesi. Semua faktor ini berdampak negatif terhadap pemeliharaan jangka panjang.

Tramp Coupling Ini muncul dari melewatkan data melalui berbagai metode yang tidak perlu untuk hampir semua data, untuk mendapatkannya ke tempat yang benar-benar bisa menanganinya. Kopling semacam ini mirip dengan menggunakan data global, tetapi bisa lebih berisi. Tramp coupling adalah kebalikan dari "need to know", yang digunakan untuk melokalisasi efek dan untuk menahan kerusakan yang bisa terjadi pada satu kode yang salah pada seluruh sistem.

Navigasi Data Setiap sub-proses dalam contoh Anda perlu tahu cara mendapatkan data yang dibutuhkan secara tepat, dan perlu memprosesnya dan mungkin membangun objek keadaan global baru. Itu adalah konsekuensi logis dari tramp coupling; seluruh konteks suatu datum diperlukan untuk beroperasi pada datum. Sekali lagi, pengetahuan non-lokal adalah hal yang buruk.

Jika Anda memasukkan "ritsleting", "lensa", atau "kursor", seperti dijelaskan dalam posting oleh @paul, itu adalah satu hal. Anda akan mengandung akses, dan memungkinkan ritsleting, dll, untuk mengontrol membaca dan menulis data.

Pelanggaran Tanggung Jawab Tunggal Mengklaim masing-masing "subproses-satu", "subprocess-dua" dan "subprocess-tiga" hanya memiliki satu tanggung jawab tunggal, yaitu untuk menghasilkan objek negara global baru dengan nilai-nilai yang tepat di dalamnya, adalah reduksionisme mengerikan. Ini semua pada akhirnya, bukan?

Maksud saya di sini adalah memiliki semua komponen utama gim Anda memiliki semua tanggung jawab yang sama dengan gim Anda mengalahkan tujuan pendelegasian dan anjak piutang.

Dampak Sistem

Dampak utama dari desain Anda adalah rawatan yang rendah. Fakta bahwa Anda dapat menyimpan seluruh permainan di kepala Anda mengatakan bahwa Anda sangat mungkin seorang programmer yang hebat. Ada banyak hal yang telah saya rancang yang dapat saya ingat di seluruh proyek. Itu bukan poin dari rekayasa sistem. Intinya adalah membuat sistem yang dapat bekerja untuk sesuatu yang lebih besar daripada yang dapat disimpan oleh satu orang di kepalanya secara bersamaan .

Menambahkan programmer lain, atau dua, atau delapan, akan menyebabkan sistem Anda berantakan segera.

  1. Kurva belajar untuk benda-benda dewa itu datar (yaitu, butuh waktu yang sangat lama untuk menjadi kompeten di dalamnya). Setiap programmer tambahan perlu mempelajari semua yang Anda tahu, dan menyimpannya di kepala mereka. Anda hanya akan dapat menyewa programmer yang lebih baik dari Anda, dengan asumsi Anda dapat membayar mereka cukup untuk menderita dengan mempertahankan objek dewa besar.
  2. Penyediaan tes, dalam uraian Anda, hanya kotak putih. Anda perlu mengetahui setiap detail objek dewa, serta modul yang diuji, untuk mengatur tes, menjalankannya, dan menentukan bahwa a) melakukan hal yang benar, dan b) tidak melakukan apa pun dari 10.000 hal yang salah. Peluang sangat ditumpuk terhadap Anda.
  3. Menambahkan fitur baru mengharuskan Anda a) memeriksa setiap subproses dan menentukan apakah fitur tersebut memengaruhi kode apa pun di dalamnya dan sebaliknya, b) melewati kondisi global Anda dan merancang penambahan, dan c) memeriksa setiap unit test dan memodifikasinya untuk memeriksa bahwa tidak ada unit yang diuji mempengaruhi fitur baru tersebut secara negatif .

Akhirnya

  1. Objek dewa yang bisa berubah adalah kutukan dari keberadaan pemrograman saya, beberapa pekerjaan saya sendiri, dan beberapa yang saya telah terjebak.
  2. Monad negara tidak berskala. Negara tumbuh secara eksponensial, dengan semua implikasi untuk pengujian dan operasi yang menyiratkan. Cara kita mengontrol negara dalam sistem modern adalah dengan pendelegasian (pembagian tanggung jawab) dan pelingkupan (membatasi akses hanya ke sebagian negara). Pendekatan "semuanya adalah peta" adalah kebalikan dari mengontrol negara.
BobDalgleish
sumber