Çağrı zincirinin derinliklerinde sadece birkaç seviye kullanılacak olan geçme parametrelerinin (anti-) modeli için bir isim var mı?


209

Bazı eski kodlarda global değişkenin kullanımına alternatifler bulmaya çalışıyordum. Ancak bu soru teknik alternatifler ile ilgili değil, ben çoğunlukla terminoloji konusunda endişeliyim .

Açık bir çözüm, global kullanmak yerine bir parametreyi işleve aktarmaktır. Bu eski kod tabanında, uzun çağrı zincirindeki tüm işlevlerin, değerin sonunda kullanılacağı nokta ile önce parametreyi alan işlev arasında değişmesi gerektiği anlamına gelir.

higherlevel(newParam)->level1(newParam)->level2(newParam)->level3(newParam)

benim örneğimde newParamdaha önce global bir değişkendi, ancak bunun yerine daha önce kodlanmış bir değer olabilirdi. Mesele şu ki, şimdi newParam'ın değeri elde edildi higherlevel()ve yol boyunca “seyahat etmek” gerekiyor level3().

Bu tür bir durum / model için, değiştirilmemiş değeri sadece "ileten" birçok fonksiyona bir parametre eklemeniz gereken bir isim olup olmadığını merak ediyordum .

Umarım, uygun terminolojiyi kullanmak, yeniden tasarımı için çözümler hakkında daha fazla kaynak bulmamı ve bu durumu iş arkadaşlarına açıklamamı sağlayacaktır.


94
Bu, küresel değişkenlerin kullanılması üzerinde bir gelişmedir . Her bir fonksiyonun hangi duruma bağlı olduğunu açıkça ortaya koyar (ve saf fonksiyonlara giden yolda bir adımdır). Bir parametreyi "geçirme" olarak adlandırdığını duydum, ancak bu terminolojinin ne kadar yaygın olduğunu bilmiyorum.
gardenhead

8
Bu, belirli bir cevaba sahip olmak için çok geniş bir spektrumdur. Bu seviyede, buna sadece "kodlama" derdim.
Machado

38
Bence "sorun" sadece basitlik. Bu temelde bağımlılık enjeksiyonu. Fonksiyonların parametre listelerini şişirmeden, daha derinlemesine yuvalanmış bir üyeye sahipse, zincirleme bağımlılığı otomatik olarak enjekte edecek mekanizmalar olabileceğini tahmin ediyorum. Belki de farklı karmaşıklık seviyelerine sahip bağımlılık enjeksiyon stratejilerine bakmak, eğer varsa, aradığınız terminolojiye yol açabilir.
boş

7
Her ne kadar iyi bir kalıp / antipattern / kavram / çözüm olup olmadığı konusundaki tartışmayı takdir etsem de, gerçekten bilmek istediğim, eğer bunun için bir isim varsa.
ecerulm

3
Ayrıca , arama yığını boyunca bir tesisat hattını indirirken olduğu gibi , en yaygın şekilde diş açması olarak adlandırıldığını duydum .
wchargin

Yanıtlar:


202

Verinin kendisine "tramp data" denir . Bir kod parçasının, aracılar aracılığıyla belirli bir mesafedeki başka bir kod parçası ile iletişim kurduğunu gösteren bir "kod kokusu" dur.

  • Özellikle çağrı zincirindeki kodun sertliğini arttırır. Çağrı zincirindeki herhangi bir yöntemi nasıl yeniden değerlendireceğiniz konusunda daha kısıtlısınız.
  • Veri / yöntem / mimarlık hakkındaki bilgiyi, en az umurunda olmayan yerlere dağıtır. Yeni geçen verileri bildirmeniz gerekiyorsa ve bildirimde yeni bir içe aktarma gerekiyorsa, ad alanını kirletmiş olursunuz.

Global değişkenleri kaldırmak için yeniden yapılanma yapmak zordur ve tramp verileri bunu yapmanın bir yöntemidir ve çoğu zaman en ucuz yoldur. Masrafları var.


73
"Tramp verileri" ni arayarak Safari üyeliğimde "Kod Tamamlandı" kitabını bulabildim. Kitapta "Küresel verileri kullanma nedenleri" adlı bir bölüm var ve bunun nedenlerinden biri de "Küresel verilerin kullanımı serseri verilerini ortadan kaldırabilir". :). "Serseri verileri" nin, küresellerle başa çıkmayla ilgili daha fazla literatür bulmama izin vereceğini hissediyorum. Teşekkürler!
ecerulm

9
@ JimmyJames, bu işlevler elbette bir şeyler yapar. Sadece daha önce sadece global olan belirli yeni parametreyle değil.
ecerulm

174
20 yıllık programlamada, kelimenin tam anlamıyla bu terimi daha önce hiç duymamıştım ya da ne anlama geldiği hemen belli değildi. Cevaptan şikayet etmiyorum, sadece terimin çok kullanılmadığını / bilinmediğini öne sürüyorum. Belki de sadece benim.
Derek Elkins,

6
Bazı küresel veriler iyi. Ona "global veri" demek yerine "çevre" diyebilirsiniz - çünkü budur. Ortam, örneğin, appdata (pencerelerde) için bir dize yolu veya mevcut projemde tüm bileşenler tarafından kullanılan GDI + fırçaları, kalemleri, yazı tipleri vb.
Robinson,

7
@ Robinbin oldukça değil. Örneğin, resim yazma kodunuzun% AppData% öğesine dokunmasını gerçekten istiyor musunuz, yoksa nereye yazacağınızı tartışmayı mı tercih ediyorsunuz? Global devlet ile bir argüman arasındaki fark budur. "Çevre" kolayca enjekte edilen bir bağımlılık olabilir, sadece çevre ile etkileşimden sorumlu olanlar için mevcuttur. GDI + fırçalar vb. Daha makul, ancak bu gerçekten sizin için yapamayacağınız bir ortamda bir kaynak yönetimi durumu - temelde API'lerin ve / veya dil / kütüphanelerinizin / çalışma zamanınızın eksikliği.
Luaan

102

Bunun kendi içinde bir anti-patern olduğunu sanmıyorum. Bence sorun, işlevleri bir zincir olarak düşünmenizdir, çünkü her birini bağımsız bir kara kutu olarak düşünmeniz gerekir ( NOT : özyinelemeli yöntemler bu tavsiyenin dikkate değer bir istisnasıdır.)

Örneğin, iki takvim tarihi arasındaki gün sayısını hesaplamam gerektiğini ve böylece bir işlev oluşturmam gerektiğini varsayalım:

int daysBetween(Day a, Day b)

Bunu yapmak için, yeni bir fonksiyon yarattım:

int daysSinceEpoch(Day day)

Sonra ilk işim basitleşir:

int daysBetween(Day a, Day b)
{
    return daysSinceEpoch(b) - daysSinceEpoch(a);
}

Bunda hiçbir anti-patern yok. DaysBetween yönteminin parametreleri başka bir yönteme aktarılıyor ve yöntemde başka türlü belirtilmiyor, ancak yine de bu yöntemin yapması gerekeni yapması için gerekli.

Tavsiye edeceğim şey, her bir fonksiyona bakmak ve birkaç soru ile başlamak:

  • Bu işlevin açık ve net bir hedefi var mı, yoksa "bazı şeyleri yap" yöntemi mi? Genellikle işlevin adı burada yardımcı olur ve içinde ismiyle açıklanmayan bir şey varsa, bu bir kırmızı bayraktır.
  • Çok fazla parametre var mı? Bazen bir yöntem yasal olarak çok sayıda girdiye ihtiyaç duyabilir, ancak bu kadar çok parametreye sahip olmak, onu kullanmayı veya anlamayı zorlaştırır.

Tek bir amaç olmadan bir yönteme dahil edilmiş bir kod karmakarışıklığına bakıyorsanız, bunu çözerek başlamalısınız. Bu can sıkıcı olabilir. Çıkarmak ve ayrı bir yönteme geçmek için en kolay şeylerle başlayın ve uyumlu bir şey elde edene kadar tekrarlayın.

Çok fazla parametreniz varsa, Nesne yeniden düzenleme yöntemini kullanın .


2
Şey, ben (anti-) ile tartışmalı olmak istemedim. Fakat hala birçok fonksiyon imzasını güncellemek zorunda olan “durum” için bir isim olup olmadığını merak ediyorum. Sanırım bir antipattern'den daha "kod kokusu" var. Bir globalin ortadan kaldırılmasını sağlamak için imza 6 işlevini güncellemek zorunda kalırsam, bu eski kodda düzeltilecek bir şey olduğunu söylüyor. Ancak, parametrelerin geçmesinin genellikle sorun olmadığını düşünüyorum ve altta yatan sorunun nasıl çözüleceği konusundaki katkısını takdir ediyorum.
ecerulm

4
@ ecerulm Ben farkında değilim ama şunu söyleyeceğim, deneyimlerimin küreleri parametrelere dönüştürmenin kesinlikle onu ortadan kaldırmaya başlamanın doğru bir yol olduğunu söyleyeceğim. Bu, paylaşılan durumu ortadan kaldırır, böylece daha fazla refactor yapabilirsiniz. Bu kodla ilgili daha fazla sorun olduğunu tahmin ediyorum ama açıklamanızda ne olduklarını bilmek için yeterli değil.
JimmyJames

2
Genelde bu yaklaşımı da takip ediyorum ve muhtemelen bu durumda da yapacağım. Sadece bununla ilgili kelime bilgimi / terminolojimi geliştirmek istedim, böylece daha fazla araştırma yapabilir ve gelecekte daha iyi, daha odaklı sorular yapabilirim.
ecerulm

3
@ ecerulm Bunun için bir isim olduğunu sanmıyorum. 'Kuru ağız' gibi hastalık dışı durumların yanı sıra pek çok hastalıkta da görülen bir semptom gibi. Kodun yapısının açıklamasını çıkarırsanız, belirli bir şeye işaret edebilir.
JimmyJames

@ ecerulm Size düzeltilmesi gereken bir şey olduğunu söyler - şimdi bir şeyin değişmesi, bunun global bir değişken olduğu zaman olduğundan daha belirgindir.
immibis

61

BobDalgleish zaten bu (anti-) desenin " tramp data " olarak adlandırıldığını kaydetti .

Tecrübelerime göre, aşırı serseri verilerinin en yaygın nedeni, bir nesnede veya veri yapısında gerçekten kapsanması gereken bir sürü bağlantılı durum değişkenine sahip olmasıdır. Bazen, verileri düzgün şekilde düzenlemek için bir grup nesneyi yerleştirmek bile gerekebilir.

Basit bir örnek için, gibi özelliklere sahip özelleştirilebilir bir oyuncu karakteri vardır bir oyun düşünün playerName, playerEyeColorböylece ve. Tabii ki, oyuncu oyun haritası üzerinde fiziksel bir konuma ve mevcut ve maksimum sağlık seviyesi gibi diğer özelliklere de sahiptir.

Böyle bir oyunun ilk adımında, tüm bu özellikleri global değişkenlere dönüştürmek mükemmel bir seçim olabilir - sonuçta, sadece bir oyuncu vardır ve oyundaki hemen hemen her şey bir şekilde oyuncuyu içerir. Dolayısıyla, küresel durumunuz aşağıdaki gibi değişkenler içerebilir:

playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100

Ancak bir noktada, belki de oyuna çok oyunculu bir mod eklemek istediğiniz için bu tasarımı değiştirmeniz gerekebileceğini fark edebilirsiniz. İlk girişim olarak, tüm bu değişkenleri yerel hale getirmeyi ve bunları gerektiren işlevlere geçirmeyi deneyebilirsiniz. Ancak, oyununuzdaki belirli bir eylemin aşağıdaki gibi bir işlev çağrısı zincirini içerdiğini görebilirsiniz:

mainGameLoop()
 -> processInputEvent()
     -> doPlayerAction()
         -> movePlayer()
             -> checkCollision()
                 -> interactWithNPC()
                     -> interactWithShopkeeper()

... ve interactWithShopkeeper()işlev sahibinin oyuncuyu ismiyle ele aldığından, aniden tüm bu işlevler playerNamearasında serseri verileri olarak geçmeniz gerekir . Ve tabii ki, dükkan sahibi mavi gözlü oyuncuların saf olmadığını düşünüyorsa ve onlar için daha yüksek fiyatlar talep ederse, o zaman tüm fonksiyonlar zincirinden geçmeniz gerekir .playerEyeColor

Uygun çözüm, bu durumda, adını kapsüller bir oyuncu nesnesini tanımlamak için elbette, göz rengi, pozisyon, sağlık ve oyuncu karakterin başka özelliklerinin olduğunu. Bu şekilde, o tek nesneyi bir şekilde oynatıcıyı içeren tüm fonksiyonlara geçirmeniz yeterlidir.

Ayrıca, yukarıdaki işlevlerin birçoğu doğal olarak o oyuncu nesnesinin yöntemlerine dönüştürülebilir ve bu da otomatik olarak oyuncuların özelliklerine erişmelerini sağlar. Bir şekilde, bu sadece sözdizimsel bir şekerdir, çünkü bir nesneyle ilgili bir yöntemi çağırmak, nesneyi etkin bir şekilde gizli bir parametre olarak nesneyi etkin bir şekilde ilettiğinden, ancak kodu doğru kullanıldığında daha net ve daha doğal görünmesini sağlar.

Tabii ki, tipik bir oyun, sadece oyuncudan çok daha fazla "küresel" duruma sahip olacaktı; örneğin, neredeyse kesinlikle oyunun gerçekleştiği bir tür haritaya ve harita üzerinde hareket eden oyuncu olmayan karakterlerin bir listesine ve belki de üzerine yerleştirilen nesnelere vb. sahip olabilirsiniz. Bunların hepsini tramp nesnesi olarak da geçirebilirsiniz, ancak bu, yöntem argümanlarınızı yeniden karıştırır.

Bunun yerine, çözüm nesnelerin kalıcı veya geçici ilişkileri olan diğer nesnelere referansları saklamasını sağlamaktır. Bu nedenle, örneğin, oyuncu nesnesi (ve muhtemelen herhangi bir NPC nesnesi de) muhtemelen geçerli seviye / haritaya atıfta bulunacak olan "oyun dünyası" nesnesine bir referans kaydetmelidir, böylece böyle bir yöntemin player.moveTo(x, y)gerekmesi gerekmez açıkça haritaya bir parametre olarak verilecek.

Benzer şekilde, eğer oyuncu karakterimiz, onları takip eden evcil bir köpeğe sahip olsaydı, köpeği tanımlayan tüm durum değişkenlerini tek bir nesnede doğal olarak gruplandırırdık ve oyuncu nesnesine köpeğe bir referans verdik (böylece oyuncu , köpeği ismiyle çağırın) ve tersi (köpeğin oyuncunun nerede olduğunu bilmesi için). Ve elbette, oyuncuyu ve köpeği daha genel bir "aktör" nesnesinin her iki alt sınıfına da nesneler yapmak isterdik, böylece aynı kodu her iki harita üzerinde hareket ettirmek için de aynı kodu tekrar kullanabiliriz.

Ps. Bir oyunu örnek olarak kullanmama rağmen, bu tür sorunların ortaya çıktığı başka tür programlar da var. Benim tecrübelerime göre, altta yatan problem her zaman aynı olma eğilimindedir: gerçekten bir veya daha fazla birbirine bağlı nesnede bir araya getirilmek isteyen bir dizi ayrı değişken (yerel veya global). İşlevlerinize izinsiz giren "tramp data", "global" seçenek ayarlarından veya önbelleğe alınmış veritabanı sorgularından veya durum vektörlerinden sayısal bir simülasyonda olsun, çözüm her zaman verilerin ait olduğu doğal içeriği tanımlamak ve bunu bir nesneye dönüştürmek içindir. (veya seçtiğiniz dilde en yakın eşdeğer neyse).


1
Bu cevap, olabilecek bir sorun sınıfına bazı çözümler sunar. Küresellerin kullanıldığı, farklı bir çözümü gösterecek durumlar olabilir. Metotları oyuncu sınıfının bir parçası yapmanın, nesneleri nesnelere metotlara aktarmakla eşdeğer olduğu fikrine kapılıyorum. Bu, bu şekilde kolayca çoğaltılamayan polimorfizmi yok sayar. Örneğin, hareketler ve farklı özellik türleri hakkında farklı kuralları olan farklı türde oyuncular oluşturmak istersem, bu nesneleri tek bir yöntem uygulamasına geçirmek çok fazla koşullu mantık gerektirecektir.
JimmyJames

6
@JimmyJames: Polimorfizm hakkındaki düşüncen iyi, ve ben de kendim yapmayı düşündüm, ama cevabın daha da uzun sürmesini engellemek için bıraktım. Yapmaya çalıştığım nokta (belki de kötü bir şekilde), yalnızca veri akışı açısından, aralarında çok az fark olduğu foo.method(bar, baz)ve method(foo, bar, baz)eskiyi tercih etmenin başka nedenleri (polimorfizm, kapsülleme, yerellik vb.) Olduğu idi .
Ilmari Karonen

@IlmariKaronen: Gelecekte fonksiyon prototiplerini nesnelerdeki gelecekteki değişikliklerden / eklemelerden / silmelerden / yeniden yapılanmalardan (örn. PlayerAge) korumaya almasının kanıtıdır. Bu tek başına paha biçilmezdir.
smci

34

Bunun için belirli bir adın farkında değilim, ancak tarif ettiğiniz sorunun sadece böyle bir parametrenin kapsamı için en iyi uzlaşmayı bulma problemi olduğunu belirtmekte fayda var.

  • Genel bir değişken olarak, program belirli bir boyuta ulaştığında kapsam çok büyük

  • Tamamen yerel bir parametre olarak, çağrı zincirlerinde çok sayıda tekrarlayan parametre listesine yol açtığında kapsam çok küçük olabilir.

  • bir takas olarak, genellikle böyle bir parametreyi bir veya daha fazla sınıfta üye değişken haline getirebilirsiniz ve buna tam olarak uygun sınıf tasarımı diyeceğim .


10
Uygun sınıf tasarımı için +1. Bu, bir OO çözümünü bekleyen klasik bir problem gibi görünüyor.
l0b0

21

Tarif ettiğiniz modelin tam olarak bağımlılık enjeksiyonu olduğuna inanıyorum . Bazı yorumcular bunun bir model olduğunu , bir anti-model olmadığını ve aynı fikirde olma eğiliminde olduklarını savundular .

Aynı zamanda @ JimmyJames'in cevabı ile de aynı fikirdeyim, burada her bir işlevi tüm girdilerini açık parametreler olarak alan bir kara kutu olarak görmenin iyi bir programlama uygulaması olduğunu iddia ediyor . Yani, bir fıstık ezmesi ve jöleli sandviç yapan bir işlev yazıyorsanız,

Sandwich make_sandwich() {
    PeanutButter pb = get_peanut_butter();
    Jelly j = get_jelly();
    return pb + j;
}
extern PhysicalRefrigerator g_refrigerator;
PeanutButter get_peanut_butter() {
    return g_refrigerator.get("peanut butter");
}
Jelly get_jelly() {
    return g_refrigerator.get("jelly");
}

ancak bağımlılık enjeksiyonunu uygulamak ve bunun yerine böyle yazmak daha iyi bir uygulama olacaktır :

Sandwich make_sandwich(Refrigerator& r) {
    PeanutButter pb = get_peanut_butter(r);
    Jelly j = get_jelly(r);
    return pb + j;
}
PeanutButter get_peanut_butter(Refrigerator& r) {
    return r.get("peanut butter");
}
Jelly get_jelly(Refrigerator& r) {
    return r.get("jelly");
}

Artık tüm bağımlılıklarını okunaklılık için harika olan fonksiyon imzasında açıkça belgeleyen bir işleve sahipsiniz. Sonuçta, öyle gerçek sırayla bu make_sandwichbir erişime ihtiyaç Refrigerator; bu nedenle eski fonksiyon imzası, buzdolabını girişlerinin bir parçası olarak almayarak temelde belirsizdi.

Bir bonus olarak, sınıf hiyerarşinizi doğru yaparsanız, dilimlemekten kaçının, vb., make_sandwichİşlevini a MockRefrigerator! (Bu şekilde ünite testi yapmanız gerekebilir çünkü ünite testi ortamınız herhangi bir PhysicalRefrigerators erişimine sahip olmayabilir .)

Bağımlılık enjeksiyonunun tüm kullanımlarının, benzer şekilde adlandırılmış bir parametreye çağrı yığınının altından sıyrılmasını gerektirmediğini anlıyorum , bu yüzden tam olarak sorduğunuz soruyu yanıtlamıyorum ... ama bu konuyu daha fazla okumak için arıyorsanız, "bağımlılık enjeksiyonu" kesinlikle sizin için alakalı bir anahtar kelimedir.


10
Bu çok açık bir şekilde karşıt bir kalıptır. Bir buzdolabının geçmesi için kesinlikle bir çağrı yok. Şimdi, jenerik bir IngredientSource kaynağını geçmek işe yarayabilir, ama ya ekmeği ekmek çantasından alırsanız, ya lardan orkinos, buzdolabından peynir ... malzemelerin kaynağına bir bağımlılık enjekte ederek bunları oluşturma işlemine Bir sandviçin içine malzemeler, endişelerin ayrılmasını ihlal etmiş ve kod kokusu yapmışsınız.
Dewi Morgan,

8
@DewiMorgan: Açıkçası, Refrigeratoriçine bir IngredientSource"genelleştirme" kavramını genelleştirmek veya hatta "sandviç" kavramını genelleştirmek için refactor yapabilirsiniz template<typename... Fillings> StackedElementConstruction<Fillings...> make_sandwich(ElementSource&); buna "genel programlama" denir ve oldukça güçlüdür, ancak kesinlikle OP'nin gerçekten şimdi almak istediğinden çok daha gizlidir. Sandviç programları için uygun soyutlama düzeyi hakkında yeni bir soru açmaktan çekinmeyin. ;)
Quuxplusone

11
Hiçbir hata yok ol, olmayan kulanıcı gerektiğini değil erişebilir make_sandwich().
dotancohen

2
@Dewi - XKCD bağlantısı
Gavin Lock

19
Kodunuzdaki en ciddi hata, fıstık ezmesini buzdolabında tutmanızdır.
Malvolio

15

Bu, kuplajın ders kitabı tanımıdır , bir modül diğerini derinden etkileyen bir bağımlılığa sahiptir ve bu değiştiğinde dalgalanma etkisi yaratır. Diğer yorumlar ve cevaplar bunun küresel üzerinde bir gelişme olduğunun doğru olduğu için doğrudur, çünkü eşleşme artık yıkıcı yerine programcının görmesi için daha açık ve kolaydır. Bu, düzeltilmemesi gerektiği anlamına gelmez. Eğer bir süre orada olsaydı ağrılı olabilse de, kaplini çıkarmak ya da azaltmak için yeniden ateşleme yapabilmelisiniz.


3
Eğer level3()ihtiyaçları newParam, bu kesin bağlanması, ama kod nedense farklı parçalar birbiriyle iletişim zorundayız. Bu fonksiyon parametresini kullanırsa, mutlaka hatalı bir fonksiyon parametresi çağırmazdım. Bence zincirin problemli yanı, onun için tanıtılan ve onu kullanmanın dışında hiçbir yararı olmayan ek bir birleşmedir . İyi cevap, kuplaj için +1. level1()level2()newParam
boş

6
@null Eğer gerçekten kullanmamışlarsa, arayanlarından almak yerine bir değer oluşturabilirler.
Random832

3

Bu cevap doğrudan sorunuzu cevaplamamakla birlikte, nasıl geliştirileceğinden bahsetmeden geçmesine izin vereceğime inanıyorum (dediğiniz gibi, bunun bir anti-patern olabileceğini düşünüyorum). Umarım siz ve diğer okuyucular, "sürtük verisinden" kaçınılması konusunda bu ek yorumdan değer elde edebilirler (Bob Dalgleish olarak bizim için çok faydalı bir şekilde adlandırılmış).

Bu sorunu önlemek için daha fazla OO yapmayı öneren cevaplara katılıyorum. Bununla birlikte, bu argüman geçişini derinlemesine azaltmanın bir başka yolu da, " sadece bir çok argümanı geçtiğiniz bir sınıfı geçmeniz! " bir. Örneğin, işte bazı kodlardan önce :

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   FilterAndReportStuff(stuffs, desiredName);
}

public void FilterAndReportStuff(IEnumerable<Stuff> stuffs, string desiredName) {
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   ReportStuff(stuffs.Filter(filter));
}

public void ReportStuff(IEnumerable<Stuff> stuffs) {
   stuffs.Report();
}

Bunun yapılması gereken daha da kötüleştiğini unutmayın ReportStuff. Kullanmak istediğiniz Muhabir örneğini geçmek zorunda kalabilirsiniz. Ve ele alınması gereken her türlü bağımlılık, işlevini iç içe işlevine dönüştürür.

Benim önerim, adımların bilgisinin bir yöntem çağrıları zincirine yayılmak yerine tek bir yöntemde yaşamasını gerektirdiği şeyleri daha yüksek bir seviyeye çekmek. Elbette gerçek kodda daha karmaşık olurdu, ama bu size bir fikir veriyor:

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   var filteredStuffs = stuffs.Filter(filter)
   filteredStuffs.Report();
}

Buradaki en büyük farkın, bağımlılıkları uzun bir zincirden geçirmeniz gerekmediğine dikkat edin. Sadece bir seviyeye değil, birkaç seviyeye kadar yassılaştırsanız bile, bu seviyelerde bir miktar "yassılaşma" elde edersiniz, böylece süreç bu seviyede bir dizi adım olarak görülürse, bir gelişme göstermiş olursunuz.

Bu hala prosedürsel olsa ve henüz hiçbir şey bir nesneye dönüştürülmemiş olsa da, bir şeyi bir sınıfa dönüştürerek ne tür bir kapsüllemeye başlayacağınıza karar vermek için iyi bir adımdır. Önceden senaryoda yer alan derin zincirleme yöntem , gerçekte ne olduğunun ayrıntılarını gizler ve kodu anlaşılmasını çok zorlaştırabilir. Bunları abartıp üst düzey kodun yapmaması gereken şeyleri bilmesini veya tek bir sorumluluk ilkesini ihlal eden çok fazla şey yapan bir yöntem yapmasını sağlarken, genel olarak işleri düzleştirmenin biraz yardımcı olduğunu buldum. netlik ve daha iyi kod için artan değişiklik yapma konusunda

Tüm bunları yaparken test edilebilirliği göz önünde bulundurmanız gerektiğini unutmayın. Zincirleme yöntem aslında ünite testini zorlaştırır çünkü test etmek istediğiniz dilim için montajda iyi bir giriş ve çıkış noktanız yok. Bu düzleştirme işleminde, yöntemleriniz artık çok fazla bağımlılık almadığından, test etmeleri daha kolay, alay gerektirmez!

Geçenlerde, hepsinin alay edilmesi gereken, 17 bağımlılık gibi bir şey alan (yazmadığım) bir sınıfa birim testleri eklemeye çalıştım! Her şeyi henüz anlamadım, ancak sınıfı, her biri ilgilendiği ayrı isimlerden biriyle ilgili olan üç sınıfa böldüm ve bağımlılık listesini en kötüsü için 12'ye, en kötüsü için de 8'e düşürdüm. en iyisi.

Test edilebilirlik, sizi daha iyi kod yazmaya zorlar . Birim testleri yazmalısınız, çünkü kodunuz hakkında farklı düşünmenizi sağlar ve birim testleri yazmadan önce kaç tane hata yapmış olursanız olun, başlangıçtan daha iyi bir kod yazarsınız.


2

Kelimenin tam anlamıyla Demeter Yasasını ihlal etmiyorsunuz, ancak probleminiz bir şekilde buna benzer. Sorunuzun amacı kaynakları bulmak olduğundan, Demeter Yasası'nı okumanızı ve bu tavsiyenin ne kadarını durumunuza uyguladığını görmenizi öneririm.


1
Detaylar konusunda biraz zayıf, bu muhtemelen olumsuzlukları açıklıyor. Yine de, ruhaniyette bu cevap tam olarak şudur: OP, Demeter Yasasını okumalı - ilgili terim budur.
Konrad Rudolph

4
Şimdi, Demeter Yasasının (yani "en az imtiyaz" ") ilgili olduğunu sanmıyorum. OP'nin davası, fonksiyonunun tramp verisine sahip olmaması durumunda işini yapamayacağı bir durumdur (çünkü çağrı yığındaki bir sonraki adamın buna ihtiyacı vardır, çünkü bir sonraki adamın buna ihtiyacı vardır). En düşük ayrıcalık / Demeter Kanunu, yalnızca parametre gerçekten kullanılmamışsa geçerlidir ve bu durumda düzeltme açıktır: kullanılmayan parametreyi kaldırın!
Quuxplusone

2
Bu sorunun durumunun Demeter Yasası ile hiçbir ilgisi yok ... Metod çağrısı zinciri ile ilgili yüzeysel bir benzerlik var, ama başka türlü çok farklı.
Eric King,

@Quuxplusone Mümkün olsa da, bu durumda açıklama oldukça kafa karıştırıcıdır, çünkü zincirleme çağrılar bu senaryoda pek bir anlam ifade etmemektedir: bunun yerine yuvalanmaları gerekir .
Konrad Rudolph

1
Sorun , LoD ihlallerine çok benzer, çünkü LoD ihlalleriyle başa çıkmak için önerilen olağan yeniden düzenleme, serseri verilerini tanıtmaktır. IMHO, bu kuplajı azaltmak için iyi bir başlangıç ​​noktasıdır ancak yeterli değildir.
Jørgen Fogh

1

En iyisinin (verimlilik, bakım kolaylığı ve uygulama kolaylığı açısından) her zaman etrafındaki her şeyi geçirme yükünden ziyade bazı değişkenleri genel olarak görmesi (devam etmesi gereken 15 veya daha fazla değişkeniniz olduğunu söyler) vardır. Bu nedenle, potansiyel karışıklığı (ad alanının ve değiştirilen şeylerin) hafifletilmesi için daha iyi kapsamayı destekleyen (C ++ 'ın özel statik değişkenleri olarak) programlama dilini bulmak mantıklıdır. Elbette bu sadece yaygın bir bilgidir.

Ancak, eğer İşlevsel Programlama yapıyorsa , OP tarafından belirtilen yaklaşım çok yararlıdır .


0

Burada hiçbir anti-patern yoktur, çünkü arayan aşağıdaki tüm seviyeleri bilmez ve umursamaz.

Birisi daha yüksek Seviyeyi (params) çağırıyor ve daha yüksek Seviyenin işini yapmasını bekliyor. Seviyenin paramlarla yaptığı şey, arayanların hiçbiri değildir. higherLevel problemi mümkün olan en iyi şekilde, bu durumda paramleri seviye 1'e (param) geçirerek ele alır. Bu kesinlikle tamam.

Bir çağrı zinciri görüyorsunuz - ama çağrı zinciri yok. Tepesinde işini en iyi şekilde yapan bir işlev var. Ve başka fonksiyonlar da var. Her fonksiyon herhangi bir zamanda değiştirilebilir.

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.