“Her şey bir Harita”, bunu doğru yapıyorum?


69

Stuart Sierra'nın " Data In Thinking " konusundaki konuşmasını izledim ve yaptığım oyunda bu fikirlerden birini tasarım ilkesi olarak aldım. Aradaki fark Clojure'de çalışıyor ve ben JavaScript'te çalışıyorum. Buradaki dillerimiz arasında bazı büyük farklılıklar görüyorum:

  • Clojure deyimsel olarak işlevsel programlama
  • Çoğu devlet değişmez

Fikrini "Her Şey Bir Harita" slaytından aldım (11 dakika, 6 saniye ila> 29 dakika arası). Söylediği bazı şeyler:

  1. Ne zaman 2-3 argüman alan bir işlev görürseniz, onu bir haritaya dönüştürmek ve sadece bir haritayı geçmek için bir dava açabilirsiniz. Bunun bir çok avantajı vardır:
    1. Argüman sırası hakkında endişelenmenize gerek yok
    2. Herhangi bir ek bilgi için endişelenmenize gerek yok. Fazladan anahtarlar varsa, bu bizim endişemiz değil. Sadece akarlar, karışmazlar.
    3. Bir şema tanımlamanız gerekmez
  2. Bir Nesne'ye geçmenin aksine, hiçbir veri gizleme yoktur. Ancak, veri gizlemenin sorunlara yol açabileceğini ve abartıldığını iddia ediyor:
    1. Verim
    2. Uygulama kolaylığı
    3. Ağ üzerinden veya işlemler arasında iletişim kurar kurmaz, her iki tarafın da veri gösterimi konusunda hemfikir olmanız gerekir. Bu sadece veri üzerinde çalışıyorsanız, atlayabileceğiniz fazladan bir iş.
  3. Benim sorumla en alakalı. Bu işlem 29 dakikadır: "İşlevlerinizi daha iyi hale getirin". İşte kavramı açıklamak için kullandığı kod örneği:

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

    Programcıların çoğunluğunun Clojure ile aşina olmadığını anlıyorum, bu yüzden bunu zorunlu olarak yeniden yazacağım:

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

    İşte avantajlar:

    1. Test etmek kolay
    2. Bu fonksiyonlara yalıtımlı olarak bakmak kolaydır
    3. Bunun bir satırını yorumlamak kolaydır ve tek bir adımı kaldırarak sonucun ne olduğunu görün
    4. Her alt süreç, duruma daha fazla bilgi ekleyebilir. Alt işlemin birinciyi alt işlem 3'e iletmesi gerekiyorsa, bir anahtar / değer eklemek kadar basit.
    5. İhtiyacınız olan verileri, geri kaydedebilmeniz için eyalet dışına çıkarmanız için bir kazan yok. Sadece tüm durumu iletin ve alt işlemin ihtiyaç duyduğu şeyi atamasına izin verin.

Şimdi durumuma döndüm: Bu dersi aldım ve oyunuma uyguladım. Yani, üst düzey işlevlerimin neredeyse tümü bir gameStatenesneyi alıyor ve döndürüyor. Bu nesne oyunun tüm verilerini içerir. EG: Bir badGuys listesi, bir menü listesi, zemindeki yağma vb. Güncelleme fonksiyonumun bir örneği:

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

Burada sormak istediğim , yalnızca işlevsel bir programlama dilinde pratik olan bir fikri saptıran bir suistimal yarattım mı? JavaScript deyimsel olarak işlevsel değildir (bu şekilde yazılabilir olsa da) ve değişmez veri yapıları yazmak gerçekten zordur. Beni ilgilendiren şeylerden biri, bu alt işlemlerin her birinin saf olduğunu varsayması . Bu varsayımın neden yapılması gerekiyor? İşlevlerimden herhangi birinin saf olması çok nadir (bununla demek istediğim, sık sık değiştirdikleri anlamına gelir gameState. Bunun dışında başka karmaşık yan etkilere sahip değilim). Değişmez verileriniz yoksa, bu fikirler dağılıyor mu?

Bir gün uyanacağım ve tüm bu tasarımın sahte olduğunu fark edeceğimden ve gerçekten de sadece Büyük Top Çamur Topuz modelini uyguladığım için endişeleniyorum .


Açıkçası, bu kod üzerinde aylardır çalışıyorum ve harikaydı. İddia ettiği tüm avantajları elde ettiğimi hissediyorum. Kodum, benim için mantıklı gelmesi çok kolaydır . Ama ben tek kişilik bir ekibim, bu yüzden bilgi lanetine sahibim.

Güncelleme

Bu kalıpla 6 + ay kodluyorum. Genellikle bu zamana kadar yaptıklarımı unutuyorum ve orası “bunu temiz bir şekilde yazdım mı?”. devreye giriyor. Yapmasaydım, gerçekten mücadele ederdim. Şimdiye kadar hiç mücadele etmiyorum.

Sürdürülebilirliğini doğrulamak için başka bir göz grubunun nasıl gerekli olacağını anlıyorum. Söyleyebileceğim tek şey, her şeyden önce sürdürülebilirliği önemsiyorum. Nerede çalışırsam çalışayım, temiz kod için her zaman en gürültücü olan benim.

Bu kodlama yöntemiyle zaten kişisel olarak kötü bir deneyime sahip olanlara doğrudan cevap vermek istiyorum. O zaman bilmiyordum ama sanırım kod yazmanın iki farklı yolundan bahsediyoruz. Yaptığım yol, diğerlerinin yaşadıklarından daha fazla yapılandırılmış gibi görünüyor. Birisi "Her şey bir haritadır" konusunda kötü bir kişisel deneyime sahip olduğunda, sürdürmenin ne kadar zor olduğu hakkında konuşurlar:

  1. İşlevin gerektirdiği haritanın yapısını asla bilmezsin
  2. Herhangi bir fonksiyon girişi asla beklemeyeceğiniz şekilde değiştirebilir. Belirli bir anahtarın haritaya nasıl girdiğini veya neden kaybolduğunu bulmak için kod tabanının her yerine bakmanız gerekir.

Böyle bir deneyime sahip olanlar için, belki de üssü "Her şey N tür haritaların 1 alır." Oldu. Mine, "Her şey 1 tür harita 1 alır" dir. Bu 1 tipin yapısını biliyorsanız, her şeyin yapısını bilirsiniz. Tabii ki, bu yapı genellikle zamanla büyür. Bu yüzden...

Referans uygulamasının aranacağı bir yer var (yani: şema). Bu referans uygulaması, oyunun kullandığı koddur, bu nedenle eski olamaz.

İkinci noktaya gelince, referans uygulamasının dışındaki haritaya anahtar eklemiyorum / kaldırmıyorum, sadece orada olanları değiştiriyorum. Ayrıca geniş bir otomatik testler grubum var.

Bu mimari sonunda kendi ağırlığının altına düşerse, ikinci bir güncelleme ekleyeceğim. Aksi takdirde, her şeyin yolunda gittiğini varsayalım :)


2
Harika bir soru (+1)! İşlevsel deyimleri işlevsel olmayan (ya da çok işlevsel olmayan) bir dilde denemek ve uygulamak için çok yararlı bir egzersiz buluyorum.
Giorgio,

15
OO tarzı bilgilerin gizlenmesinin (özellikler ve erişim işlevleriyle birlikte) performans çarpışmalarından dolayı genellikle kötü bir şey olduğunu söyleyen ve ardından tüm parametrelerinizi size veren bir haritaya dönüştürmenizi söyleyen herkes Bir değer almaya çalıştığınız her seferde (çok daha büyük) bir karma arama ek yükü güvenle göz ardı edilebilir.
Mason Wheeler,

4
@MasonWheeler bu konuda haklı olduğunuzu söylesin. Bu bir şey için yanlış yaptığı her noktayı geçersiz kılacak mısın?
Daniel Kaplan,

9
Python'da (ve Javascript dahil olmak üzere en dinamik dillerin olduğuna inanıyorum), nesne zaten bir dikt / harita için sadece sözdizimi şekeridir.
Lie Ryan,

6
@EvanPlaice: Big-O notasyonu aldatıcı olabilir. Basit gerçek şu ki, iki veya üç ayrı makine kodu talimatı ile doğrudan erişime kıyasla herhangi bir şey yavaş ve işlev çağrısı kadar sık ​​gerçekleşen bir şey üzerinde, bu ek yük çok hızlı bir şekilde toplanacak.
Mason Wheeler

Yanıtlar:


42

Daha önce 'her şey bir harita' olan bir uygulamayı destekledim. Bu korkunç bir fikir. LÜTFEN yapmayın!

İşleve iletilen argümanları belirttiğinizde, işlevin hangi değerlere ihtiyaç duyduğunu bilmek çok kolaydır. Program dışı verileri doğrudan programlayıcıyı rahatsız eden işleve aktarmaktan kaçınır - geçen her değer, bunun gerekli olduğunu belirtir ve kodunuzu destekleyen programcının verinin neden gerekli olduğunu çözmesi gerekir.

Öte yandan, her şeyi bir harita olarak geçirirseniz, uygulamanızı destekleyen programcının, haritanın hangi değerleri içermesi gerektiğini bilmek için çağrılan işlevi her şekilde tam olarak anlaması gerekir. Daha da kötüsü, verileri bir sonraki fonksiyonlara aktarmak için mevcut işleve gönderilen haritayı tekrar kullanmak çok caziptir. Bu, uygulamanızı destekleyen programcının, geçerli işlevin ne yaptığını anlamak için mevcut işlev tarafından çağrılan tüm işlevleri bilmesi gerektiği anlamına gelir. Fonksiyonları yazma amacının tam tersi budur - problemleri ortadan kaldırmak, böylece onları düşünmenize gerek kalmaz! Şimdi 5 çağrı derin ve 5 çağrı geniş hayal edin. Bu, aklınızda bulundurmanız gereken çok şey ve bir sürü hata yapmaktan ibarettir.

“her şey bir harita” da haritanın bir geri dönüş değeri olarak kullanılmasına yol açıyor gibi görünüyor. Onu gördüm. Ve yine, bu bir acı. Çağrılan işlevlerin hiçbir zaman dönüş değerinin üzerine yazılmaması gerekir - her şeyin işlevselliğini bilmiyorsanız ve bir sonraki işlev çağrısı için giriş haritası X değerinin değiştirilmesi gerektiğini bilmiyorsanız. Ve şimdiki fonksiyonun değerini döndürmek için haritayı değiştirmesi gerekiyor, ki bu bazen önceki değerin üzerine yazmalı ve bazen de yapmamalı.

düzenle - örnek

İşte bunun problemli olduğu yere bir örnek. Bu bir web uygulamasıydı. Kullanıcı girişi UI katmanından kabul edildi ve bir haritaya yerleştirildi. Sonra isteği işlemek için işlevler çağrıldı. İlk fonksiyon seti hatalı girişi kontrol eder. Bir hata olsaydı, hata mesajı haritaya yerleştirilirdi. Çağıran fonksiyon bu giriş için haritayı kontrol eder ve eğer varsa, kullanıcı arayüzündeki değeri yazar.

Bir sonraki fonksiyon seti iş mantığını başlatır. Her işlev haritayı alır, bazı verileri kaldırır, bazı verileri değiştirir, haritadaki veriler üzerinde çalışır ve sonucu haritaya koyar, vb. Sonraki işlevler haritadaki önceki işlevlerden sonuç bekler. Bir sonraki fonksiyondaki bir hatayı düzeltmek için, beklenen değerin ayarlanmış olabileceği her yeri belirlemek için önceki tüm fonksiyonları ve bir arayanı araştırmanız gerekiyordu.

Bir sonraki fonksiyonlar veriyi veritabanından çeker. Veya daha doğrusu, haritayı veri erişim katmanına iletirlerdi. DAL, sorgunun nasıl yürütüleceğini kontrol etmek için haritanın belirli değerler içerip içermediğini kontrol eder. Eğer 'justcount' bir anahtar ise, sorgu 'bardan foo'yu seç' olacaktır. Önceden çağrılan işlevlerden herhangi biri, haritaya "justcount" ekleyen bende olabilir. Sorgu sonuçları aynı haritaya eklenir.

Sonuçlar, haritanın ne yapılacağını kontrol eden arayana (iş mantığı) kabarcıklı olacaktır. Bunlardan bazıları, ilk işletme mantığı ile haritaya eklenen şeylerden kaynaklanıyor. Bazıları veritabanındaki verilerden gelirdi. Nereden geldiğini bilmenin tek yolu, onu ekleyen kodu bulmaktı. Ve onu ekleyebilecek diğer konum.

Kod etkili bir yekpare bir karışıklıktı, haritadaki tek bir girişin nereden geldiğini bilmek tamamen anlamıştı.


2
İkinci paragrafınız bana mantıklı geliyor ve gerçekten de berbat görünüyor. Üçüncü paragrafınızdan da aynı tasarımdan bahsetmediğimizi anladım. "yeniden kullanım" meselesi. Bundan kaçınmak yanlış olur. Ve son paragrafınızla gerçekten ilişki kuramıyorum. Her fonksiyondan gameStateönce veya sonra olanlarla ilgili hiçbir şey bilmeden alıyorum . Sadece verilen verilere tepki verir. Fonksiyonların birbirlerinin ayak parmaklarına basacakları bir duruma nasıl girdin? Bir örnek verebilir misin?
Daniel Kaplan,

2
Denemek ve biraz daha net hale getirmek için bir örnek ekledi. Umarım yardımcı olur. Ayrıca, ui mantığını, iş mantığı ve veritabanı erişim mantığı karıştırma, birçok nedenden dolayı birçok yerde tarafından değiştirilebilir setleri bir damla etrafında geçen karşı iyi tanımlanmış devlet nesnenin etrafında geçen arasında bir fark var
ATK

28

Şahsen, ben bu modeli her iki paradigmada da tavsiye etmem. Başlangıçta yazmayı kolaylaştırır, daha sonra düşünmeyi zorlaştırır pahasına.

Örneğin, her alt işlem işlevi hakkında aşağıdaki soruları yanıtlamaya çalışın:

  • Hangi alanları stategerektirir?
  • Hangi alanları değiştirir?
  • Hangi alanlar değişmedi?
  • İşlevlerin sırasını güvenle yeniden düzenleyebilir misiniz?

Bu kalıpla, tüm soruları okumadan bu soruları cevaplayamazsınız.

Nesne yönelimli bir dilde, kalıp daha az anlamlıdır, çünkü izleme durumu nesnelerin yaptığı şeydir.


2
“Değişmezliğin faydaları, değişmez nesneleriniz büyüdükçe azalır” Neden? Bu, performans veya sürdürülebilirlik hakkında bir yorum mu? Lütfen bu cümleyi açıklayınız.
Daniel Kaplan,

8
@tieTYT Değiştirilemezler küçük bir şey olduğunda iyi çalışır (örneğin sayısal bir tür). Bunları kopyalayabilir, oluşturabilir, atabilir, çok düşük maliyetle uçurabilirsiniz. Yüzlerce değişken olmasa da, derin ve büyük haritalar, ağaçlar, listeler ve düzinelerce oluşan tüm oyun durumlarıyla uğraşmaya başladığınızda, kopyalamanın veya silmenin maliyeti artar (ve uç ağırlıkları pratik olmaz).

3
Anlıyorum. Bu, "zorlayıcı dillerde değişmeyen veriler" sorunu mu, yoksa "değişken veriler" sorunu mu? IE: Belki de bu Clojure kodunda bir sorun değildir. Fakat JS’de nasıl olduğunu görebiliyorum. Ayrıca tüm kazan kodunu yazmak için de bir acıdır.
Daniel Kaplan,

3
@MichaelT ve Karl: dürüst olmak gerekirse, değişmezlik / verimlilik hikayesinin diğer tarafından gerçekten bahsetmelisiniz. Evet, saf kullanım korkunç derecede verimsiz olabilir, bu yüzden insanlar daha iyi yaklaşımlar buldular. Daha fazla bilgi için Chris Okasaki'nin çalışmalarına bakın.

3
@ MattFenwick Ben şahsen oldukça değişkenler gibi. İş parçacığı ile uğraşırken değişkenler hakkında bir şeyler biliyorum ve onları güvenle çalıştırabilir ve kopyalayabilirim. Bir parametre çağrısına koydum ve bir başkası bana döndüğünde değiştireceği konusunda endişelenmeden bir başkasına ilettim. Eğer biri karmaşık bir oyun durumundan bahsediyorsa (bunu örnek olarak kullanan soru - değişmez olarak nethack oyun durumu olarak 'basit' bir şey düşünmekten korkardım), değişmezlik muhtemelen yanlış bir yaklaşımdır.

12

Yaptığınız şey etkili bir şekilde manuel devlet monad'ı; Yapacağım şey (basitleştirilmiş) bir bağlayıcı birleştirici oluşturmak ve aşağıdakileri kullanarak mantıksal adımlarınız arasındaki bağlantıları yeniden ifade etmek:

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,
);

stateBindAlt işlemlerden çeşitli alt işlemleri oluşturmak için bile kullanabilirsiniz ve hesaplamanızı uygun bir şekilde yapılandırmak için bir bağlayıcı birleştirme ağacı ağacına devam edebilirsiniz.

Tam, basitleştirilmemiş Devlet monadının açıklaması ve genel olarak JavaScript'teki monadların mükemmel bir tanıtımı için bu blog gönderisine bakın .


1
Tamam, bunu inceleyeceğim (ve daha sonra yorumlayacağım). Fakat modeli kullanma fikri hakkında ne düşünüyorsunuz?
Daniel Kaplan,

1
@tieTYT Ben kalıbın kendisinin çok iyi bir fikir olduğunu düşünüyorum; Genel olarak devlet monadı, sözde değişken algoritmalar (değişmez ama değişkenliğe benzeyen algoritmalar) için kullanışlı bir kod yapılandırma aracıdır.
Ptharien'ın Alevi,

2
Bu modelin esasen bir Monad olduğunu not etmek için +1. Ancak, gerçekten değişkenliği olan bir dilde iyi bir fikir olduğunu kabul etmiyorum. Monad, küresel / değişebilir durumun mutasyona izin vermeyen bir dilde kabiliyetini sağlamanın bir yoludur. IMO, değişmezliği zorlamayan bir dilde, Monad modeli sadece zihinsel mastürbasyondur.
Yalan Ryan

6
@LieRyan Monads genel olarak aslında mutasyon veya globals ile ilgisi yoktur; sadece Devlet monad'ı özel olarak yapar (çünkü bunu yapmak için özel olarak tasarlanmıştır). Ayrıca, Devlet monadının mutasyona tabi bir dilde faydalı olmadığına da inanıyorum, ancak altındaki mutasyonlara dayanan bir uygulama verdiğim değişmez olandan daha verimli olabilir (bundan emin değilim). Monadic arayüz, kolayca erişilemeyen yüksek seviyeli yetenekler sağlayabilir, stateBindverdiğim birleştirici bunun çok basit bir örneğidir.
Ptharien'ın Alevi,

1
@ Lieie I İkinci Ptharien'in yorumu - çoğu monad devlet ya da değişkenlikle ilgili değildir ve hatta birincisi küresel devletle ilgili değildir . Monadlar aslında OO / zorunlu / değişken dillerde oldukça iyi çalışıyorlar.

11

Dolayısıyla, bu yaklaşımın Clojure'deki etkinliği arasında çok fazla tartışma var gibi görünüyor. Veri toplamalarını bu şekilde desteklemek için neden Clojure'u yarattığına ilişkin olarak Rich Hickey'in felsefesine bakmak faydalı olabilir :

Fogus: Öyleyse rastlantısal karmaşıklıklar azaldıktan sonra, Clojure eldeki sorunu nasıl çözebilir? Örneğin, idealize edilmiş nesne yönelimli paradigma yeniden kullanımı teşvik etmek içindir, ancak Clojure klasik olarak nesne yönelimli değildir - yeniden kullanma kodumuzu nasıl yapılandırabiliriz?

Hickey: OO hakkında tartışır ve yeniden kullanırdım, ama kesinlikle, şeyleri tekrar kullanabilmek, araba yapmak yerine tekerlekleri yeniden icat etmediğiniz için problemi daha basit hale getirir. Ve Clojure, JVM'de olmak birçok tekerlekleri — kütüphaneleri — mevcut kılar. Bir kütüphaneyi tekrar kullanılabilir yapan nedir? Bir veya birkaç şeyi iyi yapmalı, göreceli olarak kendi kendine yeterli olmalı ve müşteri koduyla ilgili birkaç talepte bulunmalıdır. Bunların hiçbiri OO'nun dışına çıkmıyor ve tüm Java kütüphaneleri bu kritere uymuyor, ancak bir çoğu bunu yapıyor.

Algoritma seviyesine indiğimizde, OO'nun yeniden kullanımı ciddi şekilde engelleyebileceğini düşünüyorum. Özellikle, basit bilgi verilerini temsil etmek için nesnelerin kullanımı, bilgi başına mikro dilleri, yani ilişkisel cebir gibi çok daha güçlü, bildirmeli ve jenerik yöntemlere karşı sınıf yöntemleri, yani sınıf yöntemleri konusunda neredeyse suçludur. Bir bilgi parçasını tutabilmek için kendi arayüzüne sahip bir sınıfı icat etmek her kısa hikayeyi yazmak için yeni bir dil icat etmek gibidir. Bu, yeniden kullanım önlemedir ve bence, tipik OO uygulamalarında kod patlamasıyla sonuçlanır. Clojure bunu engeller ve bunun yerine bilgi için basit bir ilişkisel modeli savunur. Bununla beraber, bilgi türleri arasında tekrar kullanılabilecek algoritmalar yazılabilir.

Bu ilişkisel model, Clojure ile birlikte verilen birçok soyutlamadan sadece biridir ve bunlar yeniden kullanım yaklaşımının gerçek temelleridir: soyutlamalar üzerine fonksiyonlar. Açık ve büyük bir işlev kümesinin, açık ve küçük, genişletilebilir bir soyutlama kümesi üzerinde çalışması, algoritmik yeniden kullanım ve kitaplık birlikte çalışabilirliğinin anahtarıdır. Clojure fonksiyonlarının büyük çoğunluğu bu soyutlamalar açısından tanımlanır ve kütüphane yazarları, bağımsız olarak geliştirilen kütüphaneler arasında muazzam birlikte çalışabilirliği gerçekleştirerek kendi giriş ve çıkış formatlarını da tasarlar. Bu, DOM'ların ve OO'da gördüğünüz diğer şeylerin tam aksinedir. Tabii ki, OO'da arayüzlerle, örneğin java.util koleksiyonlarıyla benzer bir soyutlama yapabilirsiniz, ancak java.io'daki gibi kolayca yapamazsınız.

Fogus, İşlevsel Javascript adlı kitabında bu noktaları yineliyor :

Bu kitap boyunca, soyutlamaları temsil etmek için kümelerden ağaçlara, tablolardan minimal veri türlerini kullanma yaklaşımını ele alacağım. Bununla birlikte, JavaScript'te nesne türleri aşırı derecede güçlü olmasına rağmen, onlarla çalışmak için sağlanan araçlar tamamen işlevsel değildir. Bunun yerine, JavaScript nesneleriyle ilişkili daha geniş kullanım şekli, polimorfik gönderim amaçları için yöntemler eklemektir. Neyse ki, adsız (yapıcı işleviyle oluşturulmuş değil) bir JavaScript nesnesini de yalnızca bir ilişkisel veri deposu olarak görüntüleyebilirsiniz.

Bir Book nesnesinde veya bir Çalışan türünün bir örneğinde gerçekleştirebileceğimiz tek işlem setTitle veya getSSN ise, verilerimizi bilgi başına mikro dillere kilitledik (Hickey 2011). Veri modellemede daha esnek bir yaklaşım, ilişkisel veri tekniğidir. JavaScript nesneleri, hatta prototip makineleri olsa bile, ilişkisel veri modellemesi için ideal araçlardır, adlandırılmış değerlerin tek tip yollarla erişilen daha üst düzey veri modelleri oluşturmak için yapılandırılabilir.

Her ne kadar JavaScript'i manipüle etmek ve veri haritaları olarak erişmek için araçlar JavaScript'in içinde seyrek olmakla birlikte, içtenlikle Underscore çok faydalı bir kullanım sağlar. Kavramak için en basit fonksiyonlar arasında _.keys, _.values ​​ve _.pluck vardır. Hem _.keys hem de _.values, bir nesneyi alıp anahtarlarının veya değerlerinin bir dizisini döndürmek üzere işlevlerine göre adlandırılır ...


2
Bu Fogus / Hickey röportajını daha önce okudum, fakat şu ana kadar ne hakkında konuştuğunu anlayamadım. Cevabınız için teşekkürler. Yine de, Hickey / Fogus'un tasarımımı olsa da nimetlerini vereceklerinden emin değilim. Onların tavsiyelerinin ruhunu aşırıya götürdüğüm için endişeleniyorum.
Daniel Kaplan,

9

şeytanın Avukatı

Bence bu soru şeytanın savunucusunu hak ediyor (ama elbette önyargılıyım). @KarlBielefeldt'in çok iyi puanlar verdiğini düşünüyorum ve bunlara hitap etmek istiyorum. İlk önce puanlarının mükemmel olduğunu söylemek istiyorum.

Bunun işlevsel programlamada bile iyi bir model olmadığını söylediğinden, cevaplarımda JavaScript ve / veya Clojure düşüneceğim. Bu iki dil arasındaki son derece önemli bir benzerlik, dinamik olarak yazılmış olmalarıdır. Bunu, Java ya da Haskell gibi statik olarak yazılmış bir dilde uygulasaydım, noktalarına daha uygun olurdum. Ancak, "Her şey bir Haritadır " modelinin alternatifini JavaScript'te geleneksel bir OOP tasarımı olarak ve statik olarak yazılmış bir dilde değil olarak düşüneceğim. Lütfen bana haber ver).

Örneğin, her alt işlem işlevi hakkında aşağıdaki soruları yanıtlamaya çalışın:

  • Hangi devlet alanlarını gerektiriyor?

  • Hangi alanları değiştirir?

  • Hangi alanlar değişmedi?

Dinamik olarak yazılmış bir dilde, normalde bu soruları nasıl cevaplarsınız? Bir fonksiyonun ilk parametresi isimlendirilebilir foo, fakat bu nedir? Bir dizi mi? Bir obje? Nesne dizilerinin bir nesne? Nasıl buldun? Bildiğim tek yol

  1. belgeleri oku
  2. fonksiyon gövdesine bak
  3. testlere bak
  4. tahmin edip çalışıp çalışmadığını görmek için programı çalıştırın.

"Her şey bir haritadır" kalıbının burada bir fark yarattığını sanmıyorum. Bunlar hala bu soruları cevaplamanın bildiğim tek yolu.

Ayrıca, JavaScript ve en zorunlu programlama dillerinde, functionerişebileceği herhangi bir durumu gerektirebilecek, değiştirebilecek ve görmezden gelebilecek ve imzanın hiçbir fark yaratmayacağını unutmayın: İşlev / yöntem, küresel durumla veya tektonla bir şeyler yapabilir. İmzalar genellikle yalan söyler.

"Her şey bir Haritadır" ile kötü tasarlanmış bir OO kodu arasında yanlış bir ikilik kurmaya çalışmıyorum . Sadece daha az / daha fazla ince / kaba taneli parametrelerde yer alan imzalara sahip olmanın, nasıl yalıtılacağını, ayarlanacağını ve bir fonksiyonu arayacağınızı garanti etmediğini belirtmeye çalışıyorum.

Ancak, bu sahte ikilemi kullanmama izin verirseniz: JavaScript'i geleneksel OOP biçiminde yazmakla karşılaştırıldığında, "Her Şey Bir Harita" daha iyi görünüyor. Geleneksel OOP yönteminde işlev, girdiğiniz durumu gerektirebilir, değiştirebilir veya görmezden gelebilir veya geçmediğinizi belirtebilir . Bu "Her Şey bir Haritadır" modeliyle, yalnızca geçirdiğiniz durumu gerektirir, değiştirir veya yok sayın içinde.

  • İşlevlerin sırasını güvenle yeniden düzenleyebilir misiniz?

Benim kodumda evet. @ Evicatos'un cevabı için ikinci yorumuma bakın. Belki de bu sadece bir oyun yapıyorum çünkü söyleyemem. Saniyede 60x güncellenen bir oyunda, o dead guys drop lootzaman good guys pick up lootya da tam tersi gerçekten önemli değil . Her bir işlev, çalıştırılma sırasına bakılmaksızın yapılması gerekenleri hala tam olarak yapar. updateSiparişi değiştirirseniz aynı veriler farklı aramalarda kendilerine beslenir . Eğer good guys pick up lootöyleyse dead guys drop loot, iyi adamlar bir sonraki yağma alır updateve bu önemli değil. Bir insan farkı anlayamaz.

En azından bu benim genel tecrübem oldu. Bunu herkese açık olarak kabul etmekte gerçekten savunmasız hissediyorum. Belki bu düzelecek düşünen bir olduğunu çok , çok yapmak kötü bir şey. Burada çok büyük bir hata yaptıysam haberim olsun. Ben Ama eğer, bu düzendir böylece işlevleri yeniden düzenlemek için son derece kolaydır dead guys drop lootsonra good guys pick up loottekrar. Bu paragrafı yazmak için harcadığı zamandan daha az zaman alacaktır: P

Belki "ölü adamlar düşünmek gerekir ilk yağma bırakın. Kodunuzu bu emri zorlanan eğer daha iyi olurdu". Ama neden düşmanları yağmalayabilmeniz için yağma almalı? Bana göre bu mantıklı değil. Belki yağma 100 yıl updatesönce atıldı . Keyfi bir kötü adamın zaten yerde olan ganimetini toplayıp toplamadığını kontrol etmek gerekmez. Bu yüzden bu operasyonların sırasının tamamen keyfi olduğunu düşünüyorum.

Bu desenle ayrıştırılmış adımlar yazmak doğaldır, ancak birleştirilmiş adımlarınızı geleneksel OOP'ta fark etmek zordur. Eğer geleneksel OOP yazıyor olsaydım, doğal ve saf düşünme tarzı, dead guys drop lootgeri dönüşü Lootiçine girmem gereken bir nesne haline getirmektir good guys pick up loot. Birincisi ikinci girdiyi döndürdüğü için bu işlemleri yeniden sıralayamam.

Nesne yönelimli bir dilde, kalıp daha az anlamlıdır, çünkü izleme durumu nesnelerin yaptığı şeydir.

Nesnelerin bir durumu vardır ve tarihini değiştirmek için devleti değiştirmek aptalcadır ... izini sürmek için manuel olarak kod yazmazsanız. İzleme devleti hangi şekilde "ne yapıyorlar"?

Ayrıca, değişmezliğin faydaları, değişmez nesneleriniz büyüdükçe azalır.

Doğru, dediğim gibi, "İşlevlerimin herhangi birinin saf olması nadirdir". Onlar her zaman yalnızca kendi parametrelerine işletmek, ama onların parametreleri mutasyona. Bu, bu kalıbı JavaScript'e uygularken yapmam gerektiğini düşündüğüm bir uzlaşma.


4
"Bir fonksiyonun ilk parametresi foo olarak adlandırılabilir, fakat bu nedir?" Bu yüzden parametrelerinize "foo" değil, "tekrarlar", "ebeveyn" ve işlev adıyla birleştirildiğinde ne beklendiğini açıklatan diğer isimler verin.
Sebastian Redl,

1
Her konuda seninle aynı fikirdeyim. Javascript'in bu desenle ilgili olarak ortaya çıkardığı tek sorun, değişken veriler üzerinde çalışıyor olmanız ve bu nedenle, durumu değiştirmemeniz çok muhtemeldir. Ancak, düz javascript'te clojure veri yapılarına erişmenizi sağlayan bir kütüphane var. Bir nesne olarak argümanları iletmek ikisinden birini duymaz, jquery bunu çoklu yerler yapar, ancak nesnenin hangi kısımlarını kullandıklarını belgeler. Şahsen, kullanıcı arayüzü alanlarını ve GameLogic alanlarını ayıracağım, ama senin için ne işe yararsa :)
Robin Heggelund Hansen

@SebastianRedl Ne için geçmem gerekiyor parent? Mı repetitionsve numaraları veya dizeleri dizisi veya fark etmez? Ya da belki tekrarlar sadece istediğim tekrar sayısını gösteren bir sayıdır? Orada sadece bir seçenek nesnesi alan birçok apis var . Eğer doğru bir şekilde isimlendirirseniz, dünya daha iyi bir yer olabilir, ancak hiçbir soruyu sormadan api'nin nasıl kullanılacağını bileceğinizi garanti etmez.
Daniel Kaplan,

8

Kodumun böyle yapılandırılmış olarak sona erme eğiliminde olduğunu buldum:

  • Haritaları alan işlevler daha büyük olma eğilimindedir ve yan etkileri vardır.
  • Argümanları alan işlevler daha küçük olma eğilimindedir ve saftır.

Bu ayrımı yaratmak için yola çıkmadım ama çoğu zaman kodumda bittiği gibi. Bir stil kullanmanın diğerini mutlaka olumsuz etkilediğini sanmıyorum.

Saf fonksiyonlar ünite testi kolaydır. Haritalı daha büyük olanlar, daha fazla hareketli parça içerme eğiliminde olduklarından "entegrasyon" test alanına daha fazla girerler.

Javascript'te, çok yardımcı olan bir şey, parametre doğrulama işlemini gerçekleştirmek için Meteor's Match kitaplığı gibi bir şey kullanmaktır. Bu, fonksiyonun ne beklediğini çok net bir şekilde gösterir ve haritaları oldukça temiz bir şekilde ele alabilir.

Örneğin,

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
}

Daha fazla bilgi için http://docs.meteor.com/#match adresine bakın .

:: GÜNCELLEME ::

Stuart Sierra’nın Clojure / West’in “Large in Clojure” adlı video kaydı da bu konuya değiniyor. OP gibi, haritanın bir parçası olarak yan etkileri de kontrol ediyor, böylece test yapmak çok kolaylaşıyor. Ayrıca , geçerli görünen Clojure iş akışını anlatan bir blog yazısı var .


1
Sanırım @Evicatos'a yaptığım yorumlar buradaki konumum üzerinde duracak. Evet, mutasyon yapıyorum ve fonksiyonlar saf değil. Ancak, fonksiyonlarımın test edilmesi gerçekten kolay, özellikle de test etmeyi planlamadığım regresyon kusurları için. Kredinin yarısı JS'ye gidiyor: Sadece test için ihtiyaç duyduğum verilerle bir "harita" / nesne oluşturmak çok kolay. O zaman onu içeri sokmak ve mutasyonları kontrol etmek kadar basit. Yan etkiler her zaman temsil edildiği yer haritası, böylece test etmek kolaydır.
Daniel Kaplan

1
Her iki yöntemin pragmatik kullanımının "doğru" yol olduğuna inanıyorum. Sınama sizin için kolaysa ve gerekli alanları diğer geliştiricilere iletmenin meta sorununu azaltabilirsiniz, o zaman bu kazanma gibi geliyor. Sorunuz için teşekkürler; Başladığınız ilginç tartışmayı okumaktan zevk aldım.
Ağustos'ta

5

Bu uygulamaya karşı düşünebildiğim ana argüman, bir fonksiyonun gerçekte hangi verilere ihtiyaç duyduğunu söylemenin çok zor olduğudur.

Bunun anlamı, kod tabanındaki gelecekteki programcıların, çağrılacak işlevin dahili olarak nasıl çalıştığını - ve iç içe geçmiş herhangi bir çağrının - onu çağırmak zorunda kalacağıdır.

Ne kadar çok düşünürsem, gameState nesneniz o kadar çok küresel kokuyor. Eğer böyle kullanılıyorsa, neden etraftan dolaştırılsın?


1
Evet, genellikle mutasyona uğradığım için bu global bir durumdur. Neden etraftan geçiyorsun? Bilmiyorum, bu geçerli bir soru. Ama bağırsaklarım, eğer onu geçmeyi bırakırsam, programımın anında nedeninin zorlaştığını söyledi. Her işlev küresel devlete her şeyi ya da hiçbir şey yapamaz . Şimdi olduğu gibi, fonksiyon imzasında bu potansiyeli görüyorsunuz. Söyleyemezseniz, bunlardan hiçbirinden emin değilim :)
Daniel Kaplan,

1
BTW: re: Buna karşı ana argüman: Bu clojure veya javascript olup olmadığı doğru görünüyor. Ancak bu yapılacak değerli bir nokta. Belki de listelenen faydalar bunun olumsuzluğundan ağır basmaktadır.
Daniel Kaplan,

2
Şimdi neden küresel bir değişken olmasına rağmen neden bu kadarını geçtiğimi biliyorum: Saf işlevler yazmamı sağlıyor . Benim gameState = f(gameState)için değiştirirseniz f(), test etmek çok daha zor f. f()her aradığımda farklı bir şey döndürebilir. Ancak f(gameState)aynı girdiyi her verdiğinde aynı şeyi iade etmek kolaydır .
Daniel Kaplan,

3

Yaptıkların için Büyük çamur topundan daha uygun bir isim var . Yaptığınız şeye Tanrı nesne kalıbı denir . İlk bakışta bu şekilde görünmüyor, ancak Javascript'te çok az fark var

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

ve

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

Bunun iyi bir fikir olup olmadığı muhtemelen şartlara bağlıdır. Fakat kesinlikle Clojure ile aynı doğrultuda. Clojure'un amaçlarından biri Rich Hickey'in "tesadüfi karmaşıklık" dediği şeyi kaldırmak . Birden fazla iletişim kuran nesne kesinlikle tek bir nesneden daha karmaşıktır. İşlevselliği birden fazla nesneye bölerseniz, aniden iletişim, koordinasyon ve bölünme sorumlulukları konusunda endişelenmeniz gerekir. Bunlar, yalnızca bir program yazma hedefinize özgü olan komplikasyonlardır. Rich Hickey'nin konuşmasının Simple'ın kolaylaştığını görmelisin . Bence bu çok iyi bir fikir.


İlgili, daha genel bir soru [ programmers.stackexchange.com/questions/260309/… geleneksel sınıflara göre veri modellemesi)
user7610

"Nesneye yönelik programlamada, bir tanrı nesnesi çok fazla şey bilen veya çok fazla şey yapan bir nesnedir. Tanrı nesnesi bir anti-patern örneğidir." Yani tanrı nesnesi iyi bir şey değil, mesajınız tam tersini söylüyor gibi görünüyor. Bu benim için biraz kafa karıştırıcı.
Daniel Kaplan

@tieTYT nesne yönelimli programlama yapmıyorsunuz, bu yüzden sorun değil
user7610

Bu sonuca nasıl geldiniz ("Her şey yolunda")?
Daniel Kaplan

OO'da Tanrı nesnesinin sorunu, “Nesne her şeyin çok farkında ya da tüm nesnelerin tanrı nesnesine o kadar bağımlı hale gelir ki, düzeltilmesi gereken bir değişiklik veya hata olduğunda, gerçek bir kabus haline gelir”. kaynak Kodunuzda, Tanrı nesnesinin yanında başka bir nesne vardır, bu yüzden ikinci kısım bir sorun değildir. İlk bölümde ilgili olarak, Tanrın nesne olan programın ve program her şeyin farkında olmalıdır. Yani bu da tamam.
user7610

2

Yeni bir projeyle oynarken bu konuyla bugün daha erken karşılaştım. Poker oyunu yapmak için Clojure'da çalışıyorum. Yüz değerlerini ve uygunlukları anahtar kelimeler olarak gösterdim ve bir kartı bir harita gibi göstermeye karar verdim

{ :face :queen :suit :hearts }

İki anahtar kelime öğesinin listelerini veya vektörlerini de yapabilirdim. Bir hafıza / performans farkı yaratıp yaratmadığını bilmiyorum bu yüzden şimdilik sadece haritalara gidiyorum.

Yine de fikrimi değiştirirsem, programımın çoğu bölümünün bir kartın parçalarına erişmek için bir "arayüzden" geçmesi gerektiğine karar verdim, böylece uygulama detayı kontrol edilip gizlenir. Fonksiyonlarım var

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

Programın geri kalanının kullandığı Kartlar fonksiyon olarak harita olarak geçiyor, ancak fonksiyonlar haritalara erişmek için üzerinde anlaşmaya varılmış bir arayüz kullanıyor ve bu nedenle karışıklığa uğramamalı.

Programımda kart muhtemelen sadece 2 değerli bir harita olacak. Soruda, oyun durumunun tamamı bir harita olarak aktarılıyor. Oyun durumu, tek bir karttan çok daha karmaşık olacak, ancak bir harita kullanma konusunda bir hata olduğunu düşünmüyorum. Bir nesne zorunluluğu olan dilde, tek bir büyük GameState nesnesine sahip olabilir ve yöntemlerini çağırabilir ve aynı sorunu yaşayabilirim:

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

Şimdi nesne yönelimli. Bunda özellikle yanlış olan bir şey var mı? Sanmıyorum, sen sadece bir Devlet nesnesini nasıl idare edeceğini bilen fonksiyonlara iş dağıtıyorsun. Haritalarla veya nesnelerle çalışıyor olsanız da, ne zaman küçük parçalara ayırmanız gerektiğine dikkat etmelisiniz. Bu yüzden, haritaları kullanmanın nesnelerle kullandığınız bakımı kullandığınız sürece gayet iyi olduğunu söylüyorum.


2

Gördüğüm kadarıyla (küçük), bunun gibi tek bir küresel değişmez durum nesnesi yapmak için haritalar veya diğer yuvalanmış yapıları kullanmak, işlevsel dillerde, en azından saf olanlarda, özellikle State Monad'ı @ Ptharien'sFlame olarak kullanırken oldukça yaygındır. mentioend .

Gördüğüm / okuduğum (ve burada bahsettiğim diğer cevaplar) etkili bir şekilde kullanmanın iki yolu:

  • (Değişmez) durumunda (derinden) bir yuvalanmış değeri mutasyona uğratmak
  • Devletin çoğunluğunu, ihtiyaç duymayan işlevlerden gizlemek ve onlara çalışmak / mutasyon üzerinde çalışmak için ihtiyaç duydukları birazını vermek

Bu sorunları hafifletmeye yardımcı olabilecek birkaç farklı teknik / ortak kalıp vardır:

Birincisi Fermuarlar : Bunlar, değişmez bir iç içe hiyerarşi içinde devleti derinlemesine geçmeye ve değiştirmeye izin veriyor.

Diğeri ise Lensler : Bunlar yapıya belirli bir yere odaklanmanıza ve oradaki değeri okumanıza / değiştirmenize izin verir . Farklı objelere odaklanmak için farklı lensleri bir araya getirebilirsiniz, OOP'da ayarlanabilir bir özellik zinciri gibi (gerçek özellik adları için değişkenlerin yerini alabileceğiniz!)

Prismatic geçtiğimiz günlerde, bu tür bir tekniğin başka şeylerin yanı sıra, JavaScript / ClojureScript'te kullanmanız gereken bir blog yazısı yayınladı. İşlevler için pencere durumuna geçmek için İmleçleri (fermuarlarla karşılaştırdıkları) kullanırlar:

Om, imleçleri kullanarak kapsülleme ve modülerliği geri yükler. İmleçler, uygulama durumunun belirli bölümlerine (fermuarlar gibi) güncelleştirilebilir pencereler sağlar, bileşenlerin yalnızca genel durumun ilgili bölümlerine referans almalarını ve bağlamsız bir şekilde güncelleştirmelerini sağlar.

IIRC, ayrıca bu yazıdaki JavaScript'teki değişkenliğe de değiniyorlar.


Söz konusu konuşma OP'si ayrıca, bir fonksiyonun durum haritasının bir alt ağacına güncelleyebileceği kapsamı sınırlandırmak için güncelleme işlevinin kullanımını da tartışır . Bence bunu henüz kimse açmadı.
user7610

@ user7610 İyi yakalamak, ondan bahsetmeyi unuttuğuma inanamıyorum - bu işlevi sevdim (ve assoc-indiğerleri). Sanırım beyinde Haskell vardı. Acaba JavaScript portu yapan var mı? İnsanlar muhtemelen gündeme gelmedi çünkü (benim gibi) konuşmayı izlemediler :)
paul

@paul bir anlamda onlar çünkü ClojureScript'te mevcut, ancak bunun aklınızda "önemli" olup olmadığından emin değilim. PureScript'te bulunabilir ve JavaScript'te değişmez veri yapıları sağlayan en az bir kitaplık olduğuna inanıyorum. Umarım en az bir tanesinde bunlardan vardır, aksi takdirde kullanımı uygun olmaz.
Daniel Kaplan,

@tieTYT Bu yorumu yaptığımda yerel bir JS uygulaması düşünüyordum, ancak ClojureScript / PureScript hakkında iyi bir noktaya değindiniz. Değişmez JS'ye bakmalı ve orada ne olduğuna bakmalıyım, daha önce hiç çalışmamıştım.
Paul,

1

Bunun iyi bir fikir olup olmadığı, bu alt işlemlerin içindeki devletle yaptığınız işe gerçekten bağlı olacaktır. Clojure örneğini doğru anladıysam, iade edilen devlet sözlükleri, gönderilen aynı devlet sözlükleri değildir. Bunlar, muhtemelen eklemeleri ve modifikasyonları olan kopyalardır. dilin işlevsel yapısı ona bağlıdır. Her işlevin orijinal durum sözlükleri hiçbir şekilde değiştirilmez.

Ben doğru anlamak ediyorsam, sen edilir javascript fonksiyonları içine geçmek devlet nesneleri değiştirerek yerine o Clojure kod yaptığı işlemleri çok, çok, farklı bir şey yapıyoruz demektir bir kopyasını, dönen. Mike Partridge'in de belirttiği gibi, bu temelde, açıkça sebepsiz bir şekilde geçtiğiniz ve işlevlerden gerçek bir sebep olmadan geri döndüğünüz bir dünyadır. Bu noktada bence basitçe yapamayacağınız bir şey yaptığınızı düşündürüyor .

Açıkça devletin kopyalarını alıyor, değiştiriyor ve ardından değiştirilmiş kopyayı geri veriyorsanız, devam edin. Javascript'te yapmaya çalıştığınız şeyi başarmanın en iyi yolunun bu olduğundan emin değilim, ama muhtemelen Clojure örneğinin yaptıklarına "yakın".


1
Sonunda gerçekten "çok, çok farklı" mı? Clojure örneğinde, eski halinin üzerine yeni halinin üzerine yazar. Evet, gerçek bir mutasyon yok, kimlik sadece değişiyor. Ancak, "iyi" örneğinde, yazıldığı gibi, alt işlem-iki'ye aktarılan kopyayı almak için hiçbir yolu yoktur. Bu değerin tanımı üzerine yazılmıştır. Bu nedenle bence "çok, çok farklı" bir şey gerçekten sadece bir dil uygulama detayı. En azından ne getirdiğin bağlamında.
Daniel Kaplan,

2
Clojure örneğinde devam eden iki şey var: 1) ilk örnek, belirli bir düzende çağrılan işlevlere bağlıdır ve 2) işlevler saftır, böylece herhangi bir yan etkisi olmaz. İkinci örnekteki işlevler saf olduğundan ve aynı imzayı paylaştığı için, çağrıldıkları sıradaki gizli bağımlılıklar konusunda endişelenmenize gerek kalmadan bunları yeniden sıralayabilirsiniz. Durumunuzu fonksiyonlarınızda değiştiriyorsanız, aynı garantiniz yoktur. Durum mutasyonu, sürümünüzün söz konusu değil
Evicatos

1
Bana bir örnek göstermek zorunda kalacaksınız, çünkü bu konudaki deneyimimde, şeyleri istediğim zaman hareket ettirebilirim ve çok az bir etkisi olur. Kendime kanıtlamak için, update()fonksiyonumun ortasına iki rastgele alt işlem çağrısı getirdim . Biri en üste, diğeri aşağı doğru hareket ettirdim. Tüm sınavlarım hala geçti ve oyunumu oynadığımda hiçbir kötü etki fark etmedim. Benim fonksiyonlar hissetmek sadece Clojure örnek olarak composable. İkimiz de her adımdan sonra eski verilerimizi atıyoruz.
Daniel Kaplan,

1
Herhangi bir kötü etkiden geçen ve fark edilmeyen testleriniz, şu anda başka yerlerde beklenmeyen yan etkileri olan hiçbir durumu değiştirmediğiniz anlamına gelir. İşlevleriniz saf olmadığından, her zaman böyle olacağının garantisi yoktur. İşlevlerinizin saf olmadığını ancak her adımdan sonra eski verilerinizi çöpe attığınızı söylerseniz uygulamanızla ilgili bir şeyi yanlış anlamam gerektiğini düşünüyorum.
Evicatos

1
@Evicatos - Saf olmak ve aynı imzaya sahip olmak, fonksiyonların sırasının önemli olmadığı anlamına gelmez. Sabit ve yüzde indirimler uygulanmış bir fiyat hesapladığını hayal edin. (-> 10 (- 5) (/ 2))2.5 döndürür. (-> 10 (/ 2) (- 5))0 döndürür
Zak

1

Her bir sürece iletilen, bazen “tanrı nesnesi” olarak adlandırılan küresel bir devlet nesneniz varsa, birleşmeyi azaltan ve birleştirmeyi azaltan bir dizi faktörü şaşırtmak zorunda kalırsınız. Bu faktörlerin tümü uzun vadeli sürdürülebilirliği olumsuz yönde etkilemektedir.

Tramp Coupling Bu, verilerin neredeyse tümüne ihtiyaç duymayan çeşitli yöntemlerden geçirilmesinden, bununla gerçekten ilgilenebilecek bir yere getirilmesinden kaynaklanır. Bu tür bir eşleştirme, küresel verilerin kullanılmasına benzer, ancak daha fazla içerilebilir. Tramp kuplajı, etkileri lokalize etmek ve tüm sistemde hatalı bir kod parçasının sahip olabileceği hasarı içeren "bilmesi gereken" in tam tersidir.

Veri Gezinme Örneğinizdeki her alt işlem, tam olarak ihtiyaç duyduğu verilere nasıl ulaşılacağını bilmeli ve onu işleyebilmeli ve belki de yeni bir küresel durum nesnesi oluşturabilmelidir. Bu, sürtük kaplininin mantıklı bir sonucudur; veri üzerinde işlem yapabilmek için verilerin tüm içeriği gereklidir. Yine, yerel olmayan bilgi kötü bir şeydir.

Gönderinin @paul tarafından açıklandığı gibi bir "fermuar", "lens" veya "imleç" içinden geçiyorsanız, bu bir şeydir. Erişim içeriyor olacak ve verilerin okunmasını ve yazılmasını kontrol etmek için fermuarın vb.

Tek Sorumluluk İhlali Her "alt işlem bir", "alt işlem iki" ve "alt işlem üç" ün her birinin tek bir sorumluluğu vardır, yani içinde doğru değerlere sahip yeni bir küresel devlet nesnesi üretmek, korkunç bir indirgemedir. Sonunda hepsi bit, değil mi?

Buradaki noktama göre, oyununuzun tüm ana bileşenlerine sahip olmak, oyun delegasyonunun ve faktoring'in amacını yitirmesiyle aynı sorumluluklara sahip olmasıdır.

Sistem Etkisi

Tasarımınızın en önemli etkisi düşük bakımdır. Tüm oyunu aklınızda tutabileceğiniz gerçeği, muhtemelen mükemmel bir programcı olduğunuzu söylüyor. Tüm proje boyunca aklımda tutabileceğim birçok şey var. Yine de bu, sistem mühendisliğinin noktası değil. Mesele, bir kişinin aynı anda kafasında tutabildiğinden daha büyük bir şey için çalışabilecek bir sistem yapmaktır .

Başka bir programcı ya da iki ya da sekiz eklemek, sisteminizin neredeyse anında parçalanmasına neden olur.

  1. Tanrı objeleri için öğrenme eğrisi düzdür (yani, onların içinde yetkin olmak çok uzun zaman alır). Her ek programcının bildiğiniz her şeyi öğrenmesi ve başını tutması gerekir. Muazzam bir tanrı nesnesini koruyarak acı çekmek için yeterince para ödeyebileceğinizi varsayarsak, yalnızca sizden daha iyi olan programcıları işe alabileceksiniz.
  2. Test sağlama, açıklamanızda yalnızca beyaz kutudur. Test kurmak, çalıştırmak ve belirlemek için a) doğru şeyi yaptığını ve b) herhangi bir şey yapmadığını belirlemek için tanrı nesnesinin tüm ayrıntılarını ve test edilen modülü bilmeniz gerekir 10.000 yanlış şeyden. Olasılıklar aleyhinize büyük oranda yığılmış.
  3. Yeni bir özellik eklemek, a) her alt işlemden geçmenizi ve özelliğin içindeki herhangi bir kodu etkileyip etkilemediğini ve bunun tam tersini yapmayı gerektirir, b) genel durumunuzdan geçersiniz ve ekleri tasarlarsınız ve c) her birim testinden geçersiniz ve değiştirirsiniz Test edilen hiçbir birimin yeni özelliği olumsuz yönde etkilemediğini kontrol etmek için .

En sonunda

  1. Değişken tanrı objeleri benim programlama varlığımın laneti, bazıları benim yaptığım, bazıları da hapsolmuş.
  2. Devlet monad'ı ölçeklendirilmez. Devlet, test etme ve ima ettiği tüm işlemlere katlanarak katlanarak büyür. Modern sistemlerde devleti kontrol etme biçimimiz delegasyon (sorumlulukların bölümlenmesi) ve kapsamlaştırma (devletin yalnızca bir alt kümesine erişimi kısıtlama) gereğidir. “Her şey bir harita” yaklaşımı, kontrol devletinin tam tersidir.
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.