Tek bir yapılandırma nesnesi kötü bir fikir midir?


43

Uygulamalarımın çoğunda, diskten çeşitli ayarları okumaktan sorumlu bir singleton veya statik "config" nesnem var. Hemen hemen tüm sınıflar, çeşitli amaçlar için kullanır. Temelde bu sadece ad / değer çiftlerinin bir karma tablosu. Salt okunur, bu yüzden çok fazla küresel devlete sahip olduğum gerçeğiyle fazla ilgilenmedim. Ama şimdi birim testine başladığımdan beri, sorun olmaya başlıyor.

Bir sorun, genellikle çalıştırdığınız aynı yapılandırma ile test etmek istememenizdir. Bunun için birkaç çözüm var:

  • Config nesnesine SADECE test için kullanılan bir ayarlayıcı verin, böylece farklı ayarlardan geçebilirsiniz.
  • Tek bir yapılandırma nesnesini kullanmaya devam edin, ancak onu bir tekilden, ihtiyaç duyduğu her yere ilettiğiniz bir örneğe değiştirin. Daha sonra bir kez uygulamanızda ve bir kez testlerinizde farklı ayarlarla yapılandırabilirsiniz.

Fakat her iki durumda da, hala ikinci bir sorunla karşılaşıyorsunuz: hemen hemen her sınıf config nesnesini kullanabilir. Bu yüzden bir testte, test edilen sınıf için konfigürasyon ayarlamanız, fakat aynı zamanda bağımlılıklarının ALL'sini ayarlamanız gerekir. Bu, test kodunuzu çirkin yapabilir.

Bu tür config nesnesinin kötü bir fikir olduğu sonucuna varmaya başladım. Ne düşünüyorsun? Bazı alternatifler neler? Her yerde yapılandırma kullanan bir uygulamayı yeniden düzenlemeye nasıl başlıyorsunuz?


Yapılandırma diskteki bir dosyayı okuyarak ayarlarını alır, değil mi? Peki neden sadece okuyabileceği bir "test.config" dosyası yok?
Anon.

Bu birinci sorunu çözüyor, ikincisi değil.
JW01

@ JW01: Öyleyse, tüm testlerinizin belirgin bir şekilde farklı bir yapılandırmaya ihtiyacı var mı? Testiniz sırasında bu konfigürasyonu bir yere kurmanız gerekecek , değil mi?
Anon.

Doğru. Ancak, tek bir ayar havuzu kullanmaya devam edersem (test için farklı bir havuza sahip), o zaman tüm testlerim aynı ayarları kullanıyor. Davranışı farklı ayarlarla test etmek isteyebileceğim için bu ideal değil.
JW01

“Yani bir testte, test edilen sınıf için konfigürasyon ayarlamanız ve aynı zamanda TÜM bağımlılıklarının ayarlanması gerekir. Bu test kodunuzu çirkin yapabilir.” Bir örnek var mı Tüm uygulamanızın config sınıfına bağlanan yapısının değil, daha çok şeyleri "çirkin" yapan config sınıfının yapısının bir duygu olduğunu hissediyorum. Bir sınıfın konfigürasyonunu ayarlamak, bağımlılıklarını otomatik olarak konfigüre etmemeli mi?
AndrewKS

Yanıtlar:


35

Tek bir yapılandırma nesnesiyle ilgili bir sorunum yok ve tüm ayarları tek bir yerde tutmanın avantajını görebiliyorum.

Ancak, bu tek nesneyi her yerde kullanmak, config nesnesiyle onu kullanan sınıflar arasında yüksek düzeyde bir birleştirme ile sonuçlanacaktır. Config sınıfını değiştirmeniz gerekirse, kullanıldığı her örneği ziyaret etmeniz ve hiçbir şeyi bozmadığınızı kontrol etmeniz gerekebilir.

Bununla başa çıkmanın bir yolu, uygulamanın farklı kısımlarının ihtiyaç duyduğu config nesnesinin kısımlarını ortaya çıkaran çoklu arayüzler oluşturmaktır. Diğer sınıfların config nesnesine erişmesine izin vermek yerine, bir arayüzün örneklerini ihtiyaç duyan sınıflara iletirsiniz. Bu şekilde, config kullanan uygulamanın bölümleri, bütün config sınıfından ziyade arayüzdeki daha küçük alan koleksiyonuna bağlıdır. Son olarak, birim testi için arayüzü uygulayan özel bir sınıf oluşturabilirsiniz.

Bu fikirleri daha fazla araştırmak istiyorsanız, SOLID ilkeleri , özellikle de Arayüz Ayrıştırma İlkesi ve Bağımlılık Tersine Çevirme İlkesi hakkında okuma yapmanızı öneririm .


7

Arayüzlerle ilgili ayar gruplarını ayırırım.

Gibi bir şey:

public interface INotificationEmailSettings {
   public string To { get; set; }
}

public interface IMediaFileSettings {
    public string BasePath { get; set; }
}

vb.

Şimdi, gerçek bir ortam için, tek bir sınıf bu arayüzlerin çoğunu uygulayacaktır. Bu sınıf, bir DB'den veya uygulama yapılandırmasından veya size sahip olandan çekebilir, ancak çoğu zaman ayarların çoğundan nasıl yararlanılacağını bilir.

Ancak arayüze göre ayrılma, gerçek bağımlılıkları çok daha belirgin ve ince taneli hale getirir ve bu, test edilebilirlik için bariz bir gelişmedir.

Ama .. Ben her zaman ayarları bir bağımlılık olarak enjekte etmek veya sağlamak zorunda kalmak istemiyorum. Tutarlılık ya da açıklık için yapmam gereken bir argüman var. Ancak gerçek hayatta gereksiz bir kısıtlama gibi görünüyor.

Böylece, cephe olarak statik bir sınıf kullanacağım, herhangi bir yerden ayarlara kolay giriş sağlayacağım ve bu statik sınıf içinde arayüzün uygulanmasını bulacağım ve ayarı alacağım.

Hizmet konumunun çoğundan hızlı bir şekilde başlayabildiğini biliyorum, ancak yapıcı aracılığıyla bir bağımlılık sağlamanın bir ağırlık olduğunu ve bazen bu ağırlığın taşımayı düşündüğümden daha fazla olduğunu görelim. Hizmet Yeri, statik tekil bir giriş noktasının rahatlığını (dikkatlice ölçülmüş ve uygun şekilde birkaç durumda) sunarken, arayüzlere programlama yoluyla test edilebilirliği sürdürmek ve çoklu uygulamalara izin vermek için çözümdür.

public static class AllSettings {
    public INotificationEmailSettings NotificationEmailSettings {
        get {
            return ServiceLocator.Get<INotificationEmailSettings>();
        }
    }
}

Bu karışımı tüm dünyaların en iyisi olarak buluyorum.


3

Evet, fark ettiğiniz gibi, global config nesnesi birim testini zorlaştırır. Ünite testi için "gizli" bir ayarlayıcıya sahip olmak hızlı bir kesmek, hoş olmasa da, çok faydalı olabilir: ünite testleri yazmaya başlamanızı sağlar, böylece kodunuzu zaman içinde daha iyi bir tasarıma yönlendirmek için yeniden düzenleyebilirsiniz.

(Zorunlu referans: Eski Kod ile Etkili Çalışma . Eski ve eski test kodları için bu ve daha pek çok değerli püf noktası içerir.)

Tecrübelerime göre, en iyisi global config mümkün olduğunca az bağımlılığa sahip olmak. Bu da mutlaka sıfır olmak zorunda değil - hepsi şartlara bağlı. Genel konfigürasyona erişen ve gerçek konfigürasyon özelliklerini oluşturdukları ve kullandıkları nesnelere ileten birkaç yüksek seviye "organizatör" sınıfına sahip olmak, organizatörler yalnızca bunu yaptıkları sürece, örneğin test edilebilir kodları kendileri kullanmadıkça iyi çalışabilir. Bu, mevcut fiziksel konfigürasyon şemasını tamamen tahrip etmiyorken, uygulamanın ünite test edilebilir önemli işlevselliğinin çoğunu veya tümünü sınamanıza izin verir.

Bu zaten Spring gibi bir Java bağımlılık çözümüne yaklaşıyor. Böyle bir çerçeveye geçebilirseniz, sorun yok. Ancak, gerçek hayatta ve özellikle eski bir uygulamayla uğraşırken, DI'ye doğru yavaş ve titiz bir şekilde yeniden yapılanma, ulaşılabilecek en iyi uzlaşmadır.


Yaklaşımınızı gerçekten beğendim ve bir pasör fikrinin "gizli" tutulması veya bu konuda "hızlı saldırı" olarak kabul edilmesi gerektiğini önermem. Ünite testlerinin uygulamanızın önemli bir kullanıcısı / tüketicisi / parçası olduğu fikrini benimsiyorsanız, test_niteliklere ve yöntemlere sahip olmak artık kötü bir şey değil, değil mi? Sorunun asıl çözümünün bir DI çerçevesi kullanmak olduğuna katılıyorum, ancak sizinki gibi örneklerde, eski kodla çalışırken veya diğer basit durumlarda test kodunu yabancılaştırmamanın doğru bir şekilde uygulanmaması.
Filip Dupanović

1
@kRON, bunlar eski kod biriminin test edilebilir hale getirilmesi için son derece kullanışlı ve pratik püf noktalarıdır ve ben de bunları yaygın olarak kullanıyorum. Bununla birlikte, ideal olarak üretim kodu, yalnızca test amacıyla tanıtılan hiçbir özelliği içermemelidir. Uzun vadede, burası yeniden doğruladığım yer.
Péter Török

2

Tecrübelerime göre, Java dünyasında, Bahar bunun için kastedilmiştir. Çalışma zamanında ayarlanan belirli özelliklere göre nesneler (fasulye) oluşturur ve yönetir; aksi takdirde uygulamanız için şeffaftır. Anon'un test.config dosyası ile gidebilirsiniz. bahsettiğimizde, başka bir tuşa (örneğin, ana bilgisayar adı veya ortam) dayalı özellikleri ayarlamak için Spring'in kullanacağı yapılandırma dosyasına bir mantık dahil edebilirsiniz.

İkinci probleminizle ilgili olarak, bazı araştırmalarla bunu çözebilirsiniz, ancak göründüğü kadar şiddetli değil. Sizin durumunuzda bunun anlamı, örneğin, çeşitli diğer sınıfların kullandığı global bir Config nesnesine sahip olmamanızdır. Bu diğer sınıfları sadece Spring aracılığıyla konfigüre edersiniz ve daha sonra config nesnesine sahip olmazsınız, çünkü gerekli konfigürasyonun tümü Spring'e ulaştı, bu sınıfları doğrudan kodunuzda kullanabilirsiniz.


2

Bu soru gerçekten yapılandırmadan daha genel bir meseledir. "Singleton" kelimesini okuduğumda hemen, bu kalıpla ilgili tüm problemleri düşündüm, bunların çoğu test edilebiliyordu.

Singleton modeli "zararlı" olarak kabul edilir. Anlamı, her zaman yanlış olan bir şey değildir, ancak genellikle öyledir . Herhangi bir şey için bir singleton deseni kullanmayı düşünüyorsanız , düşünmeyi bırakın:

  • Hiç alt sınıfa ihtiyacınız olacak mı?
  • Hiç bir arayüze programlamaya ihtiyacınız olacak mı?
  • Buna karşı birim testleri yapman gerekiyor mu?
  • Sık sık değiştirmeniz gerekecek mi?
  • Uygulamanız birden fazla üretim platformunu / ortamını destekleyecek mi?
  • Hafıza kullanımı konusunda biraz endişeli misiniz?

Cevabınız bunlardan herhangi birine "evet" ise (ve muhtemelen aklıma gelmeyen birkaç şey), o zaman muhtemelen bir singleton kullanmak istemezsiniz. Konfigürasyonlar çoğu zaman, bir singleton'dan (veya bu konuda herhangi bir ani sınıfın) sağlayabileceğinden daha fazla esnekliğe ihtiyaç duyar.

Ağrısız bir singletonun neredeyse tüm faydalarını istiyorsanız, Spring veya Castle gibi bir bağımlılık enjeksiyon çerçevesi kullanın ya da ortamınız için uygun olanı kullanın. Bu şekilde, yalnızca bir kez beyan etmeniz yeterlidir ve konteyner, neye ihtiyacı olursa olsun otomatik olarak bir örnek sağlayacaktır.


0

Bunu C # ile ele almamın bir yolu, örnek oluşturma sırasında kilitlenen ve tüm verileri tüm istemci nesneleri tarafından kullanılmak üzere bir seferde başlatan tek bir nesneye sahip olmaktır. Eğer bu singleton, anahtar / değer çiftlerini idare edebiliyorsa, birçok farklı müşteri ve müşteri tipi tarafından kullanılmak üzere karmaşık anahtarlar dahil olmak üzere her türlü veriyi saklayabilirsiniz. Dinamik tutmak ve gerektiğinde yeni istemci verilerini yüklemek istiyorsanız, bir istemci ana anahtarının varlığını doğrulayabilir ve eksikse, söz konusu müşterinin verilerini ana sözlüğe ekleyerek yükleyebilirsiniz. Birincil konfigürasyon tektonunun, aynı müşteri tipinin katlarının hepsine, birincil konfigürasyon tektonu yoluyla erişilen aynı verileri kullandığı bir istemci veri setleri içermesi de mümkündür. Bu config nesne organizasyonunun çoğu, config istemcilerinizin bu bilgilere nasıl erişmesi gerektiğine ve bu bilgilerin statik veya dinamik olmasına bağlı olabilir. Hem anahtar / değer konfigürasyonlarını hem de ihtiyaç duyulana bağlı olarak belirli nesne API'lerini kullandım.

Config singletonsdan biri bir veritabanından yeniden yüklenecek mesajları alabilir. Bu durumda, ikinci bir nesne koleksiyonuna yüklerim ve sadece koleksiyonları değiştirmek için ana koleksiyonu kilitlerim. Bu, diğer dişlerde tıkanma okumalarını önler.

Config dosyalarından yükleme yapıyorsanız, bir dosya hiyerarşisine sahip olabilirsiniz. Bir dosyadaki ayarlar, diğer dosyalardan hangisinin yükleneceğini belirleyebilir. Bu mekanizmayı, her biri kendi yapılandırma ayarlarına sahip, isteğe bağlı birden fazla bileşene sahip C # Windows hizmetleriyle kullandım. Dosya yapısı kalıpları dosyalar arasında aynıydı, ancak birincil dosya ayarlarına dayanarak yüklendi veya yüklenmedi.


0

Tarif ettiğiniz sınıf , fonksiyonlar yerine veriler haricinde bir tanrılı antipattern nesnesi gibi ses çıkartıyor . Bu bir problem. Konfigürasyon verilerinin muhtemelen uygun bir nesnede okunması ve saklanması ve sadece bir nedenden dolayı gerekli olması durumunda tekrar okunması gerekir.

Ek olarak, uygun olmayan bir nedenden dolayı Singleton kullanıyorsunuz. Singletons, yalnızca birden fazla nesnenin varlığı kötü bir durum yaratacağı zaman kullanılmalıdır. Bu durumda bir Singleton kullanmak, bir yapılandırma okuyucusundan daha fazlasına sahip olmanın derhal bir hata durumuna neden olmaması gerektiği için uygun değildir. Birden fazla varsa, muhtemelen yanlış yapıyorsunuzdur, ancak yalnızca bir yapılandırma okuyucusuna sahip olmanız kesinlikle gerekli değildir.

Son olarak, böyle bir küresel devlet yaratmak, verilere doğrudan erişmeyi bilmeniz gerekenden daha fazla sınıfa izin verdiğiniz için kapsülleme ihlalidir.


0

İlgili ayarların "kümeleriyle" çok fazla ayar varsa, bunları ilgili uygulamalarla ayrı arayüzlere ayırmanın mantıklı olduğunu düşünüyorum - sonra DI ile ihtiyaç duyulan arayüzleri enjekte edin. Test edilebilirlik, daha düşük bağlantı ve SRP elde edersiniz.

Bu konuda Los Techies'ten Joshua Flanagan'dan ilham aldım; konuyla ilgili bir süre önce bir makale yazdı .

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.