SOLID? A geçiş yaptıktan sonra büyük oranda artan sınıf sayısını yönetmek ve düzenlemek


49

Son birkaç yılda, yavaş yavaş adım adım ilerleyerek daha iyi yazılmış bir koda geçiyoruz, bir seferde birkaç adım. Sonunda en azından SOLID'e benzeyen bir şeye geçmeye başlıyoruz, ancak henüz tam olarak orada değiliz. Geçiş yaptıktan bu yana, geliştiricilerin en büyük şikayetlerinden biri, daha önce her görevin yalnızca geliştiricinin 5-10 dosyaya dokunmasını gerektirdiği düzinelerce ve düzinelerce dosyayı incelemeye ve geçmeye devam edememeleri.

Geçiş yapmaya başlamadan önce, mimarimiz aşağıdakine benzer şekilde düzenlenmiştir (bir veya iki büyüklük sırası ile daha fazla dosya verilir):

Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI

Dosya bilge, her şey inanılmaz derecede doğrusal ve kompakt. Açıkçası çok sayıda kod kopyalama, sıkı bağlantı ve baş ağrısı vardı, ancak herkes onu geçip çözebilirdi. Tamamı acemiler, Visual Studio'yu hiç bu kadar açmayan insanlar, birkaç hafta içinde çözebilirlerdi. Genel dosya karmaşıklığının olmayışı, acemi geliştiricilerin ve yeni işe alınan kişilerin çok fazla rampa süresi olmadan da katkıda bulunmaya başlamalarını nispeten basit hale getiriyor. Ancak, kod stilinin herhangi bir yararı pencereden çıktığında bu oldukça fazla.

Kod tabanımızı daha iyi hale getirmek için yaptığımız her girişimi gönülden onaylıyorum, ancak takımın geri kalanından böyle büyük paradigma kaymaları konusunda bir miktar geri bildirim almak çok yaygın. Şu anda en büyük yapıştırma noktalarından birkaçı:

  • Birim Testleri
  • Sınıf Sayısı
  • Akran Değerlendirmesi Karmaşıklığı

Ünite testleri, zaman kaybı olduklarına inandıkları ve kodlarını her parçadan ayrı olarak bir bütün olarak daha hızlı ele alabildiklerine inandıkları için takıma inanılmaz zor bir satış oldu. Birim testlerini SOLID için bir onay olarak kullanmak, çoğunlukla boşuna olmuştur ve bu noktada çoğunlukla bir şaka haline gelmiştir.

Sınıf sayısı muhtemelen üstesinden gelinmesi gereken en büyük engeldir. 5-10 dosya almak için kullanılan görevler şimdi 70-100 alabilir! Bu dosyaların her biri ayrı bir amaca hizmet etse de, dosyaların büyük bir bölümü ezici olabilir. Takımın tepkisi çoğunlukla inatçı ve kafa çiziciydi. Önceden bir görevin bir veya iki veri havuzu, bir veya iki model, bir mantık katmanı ve bir denetleyici yöntemi gerektirmiş olabilir.

Şimdi, basit bir dosya kaydetme uygulaması oluşturmak için dosyanın var olup olmadığını kontrol etmek için bir sınıfınız var, meta veri yazmak için bir sınıf, soyutlamak için bir sınıf var, DateTime.Nowböylece birim sınaması için zamanlar enjekte edebilir, mantık içeren her dosya için arayüzler, dosyalar Dışarıdaki her sınıf için birim testleri ve DI kabınıza her şeyi eklemek için bir veya daha fazla dosya içerecek şekilde.

Küçük ve orta büyüklükteki uygulamalar için SOLID çok kolay bir satıştır. Herkes fayda ve bakım kolaylığını görür. Bununla birlikte, çok büyük ölçekli uygulamalarda SOLID için iyi bir değer teklifi görmüyorlar. Bu yüzden, bizi artan acıları geçmemiz için organizasyon ve yönetimi iyileştirmenin yollarını bulmaya çalışıyorum.


Yeni tamamlanan bir göreve dayalı olarak, dosya hacminin bir örneğini biraz daha güçlendireceğimi düşündüm. Bir dosya senkronizasyon isteği almak için yeni mikro hizmetlerimizden bazılarında bazı işlevleri uygulama görevi verildi. İstek alındığında, hizmet bir dizi arama yapar ve kontrol eder ve sonunda belgeyi bir ağ sürücüsüne ve 2 ayrı veritabanı tablosuna kaydeder.

Belgeyi ağ sürücüsüne kaydetmek için birkaç özel sınıfa ihtiyacım vardı:

- IBasePathProvider 
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect

- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests

- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider 
- NewGuidProviderTests

- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests

- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request) 
- PatientFileWriter
- PatientFileWriterTests

Yani bu oldukça basit bir tasarruf sağlamak için toplam 15 ders (POCO ve iskele hariç). Bu sayı, birkaç sistemdeki varlıkları temsil etmek üzere POCO'lar yaratmam gerektiğinde, diğer ORM'lerimizle uyumlu olmayan üçüncü taraf sistemlerle iletişim kurmak için birkaç depo oluşturduğumda ve belirli işlemlerin karmaşıklıklarını ele almak için mantık yöntemleri oluşturduğumda önemli ölçüde yükseldi.


52
"5-10 dosya almak için kullanılan görevler şimdi 70-100 alabilir!" Nasıl cehennemde? Bu hiçbir şekilde normal değil. Bu kadar çok dosyayı değiştirmeyi gerektiren ne tür değişiklikler yapıyorsunuz?
Euphoric

43
Görev başına daha fazla dosya değiştirmeniz gerekmesi (önemli ölçüde daha fazla!) Yanlış SOLID yaptığınız anlamına gelir. Bütün mesele, kodunuzu (zaman içinde) gözlenen değişiklik kalıplarını yansıtacak şekilde düzenlemek ve değişiklikleri kolaylaştırmaktır. SOLID'deki her ilke, arkasında belirli bir akıl yürütmeyle gelir (ne zaman ve neden uygulanması gerektiği); Bu durumda kendinizi bu kadar kör uygulayarak kendinizi ele geçirmişe benziyorsunuz. Birim testi (TDD) ile aynı şey; Eğer tasarım / mimari yapmanın iyi bir yolunu anlamadan yapıyorsanız, kendinizi bir deliğe kazacaksınız.
Filip Milovanović

60
SOLID'i, işi yapmanıza yardımcı olacak pratik bir araç yerine bir din olarak açıkça kabul ettiniz. SOLID'deki bir şey daha fazla iş yapıyor veya işleri zorlaştırıyorsa, yapmayın.
whatsisname,

25
@Euphoric: Sorun her iki şekilde de ortaya çıkabilir. Sanırım 70-100 sınıfın fazla aşırılı olma ihtimaline cevap verdiğini sanıyorum. Ancak bunun 5-10 dosyaya (daha önce 20KLOC dosyalarında daha önce çalışmıştım ...) sıkıştırılmış büyük bir proje olması imkansız değildir ve 70-100 aslında doğru miktarda dosyadır.
Flater

18
"Nesne mutluluğu hastalığı" dediğim bir düşünce bozukluğu var; bu, OO tekniklerinin büyük bir kod tabanında çalışma maliyetlerini azaltmak için olası birçok teknikten biri olmaktan ziyade, kendileri için bir amaç olduğuna inanıyor. Özellikle "SOLID mutluluk hastalığı" gibi gelişmiş bir formunuz var. SOLID amaç değil. Kod tabanını koruma maliyetini düşürmek amaçtır. Önerilerinizi bu bağlamda değerlendirin, doktriner SOLID olup olmadığına bakın. (Önerileriniz de muhtemelen doktriner değildir, SOLID de dikkate alınması gereken iyi bir nokta.)
Eric Lippert

Yanıtlar:


104

Şimdi, basit bir dosya kaydetme uygulaması oluşturmak için dosyanın zaten var olup olmadığını kontrol etmek için bir sınıfınız var, meta verileri yazmak için bir sınıf, DateTime'ı soyutlamak için bir sınıf var.Şimdi böylece birim sınama zamanları, her bir dosya için arayüzler enjekte edebilirsiniz. Mantık, orada her sınıf için birim sınamaları içeren dosyalar ve DI kabınıza her şeyi eklemek için bir veya daha fazla dosya.

Sanırım tek bir sorumluluk fikrini yanlış anladın. Bir sınıfın tek sorumluluğu "bir dosyayı kaydet" olabilir. Bunu yapmak için, bu sorumluluğu bir dosyanın var olup olmadığını kontrol eden bir yönteme, meta verileri yazan bir yöntemi vb. Denetleyen bir yönteme dönüştürebilir.

Soyutlamak için bir sınıf DateTime.Nowiyi geliyor. Ancak bunlardan sadece birine ihtiyacınız var ve diğer çevresel özelliklerle çevresel özellikleri soyutlama sorumluluğu taşıyan tek bir sınıfa ayrılabilir. Yine birden fazla alt sorumluluğu olan tek bir sorumluluk.

"Mantık içeren her dosya için arayüzlere" ihtiyacınız yoktur, yan etkileri olan sınıflar için arayüzlere, örneğin dosyalara veya veritabanlarına okuyan / yazan sınıflar; ve o zaman bile, yalnızca bu işlevselliğin halka açık alanları için ihtiyaç duyulur. Bu nedenle, örneğin AccountRepo, herhangi bir arayüze ihtiyacınız olmayabilir, yalnızca o depoya enjekte edilen gerçek veritabanı erişimi için bir arayüze ihtiyacınız olabilir.

Ünite testleri, zaman kaybı olduklarına inandıkları ve kodlarını her parçadan ayrı olarak bir bütün olarak daha hızlı ele alabildiklerine inandıkları için takıma inanılmaz zor bir satış oldu. Birim testlerini SOLID için bir onay olarak kullanmak, çoğunlukla boşuna olmuştur ve bu noktada çoğunlukla bir şaka haline gelmiştir.

Bu da yanlış anlaşılmış birim testlerinin olduğunu gösteriyor. Bir birim testinin "birimi" bir kod birimi değildir. Bir kod bile nedir? Bir sınıf? Bir metod? Bir değişken? Tek bir makine talimatı? Hayır, "birim" bir yalıtım birimini, yani kodun diğer bölümlerinden izole edilmiş olarak çalışabilen kodu belirtir. Bir otomatik testin bir birim testi olup olmadığına dair basit bir test, sonucunu etkilemeden diğer tüm birim testlerinizle paralel olarak çalıştırabilmenizdir. Ünite testlerinde birkaç kural daha var, ama bu sizin temel önleminiz.

Eğer kodunuzun bölümleri gerçekten diğer bölümleri etkilemeden bir bütün olarak test edilebilirse, o zaman bunu yapın.

Her zaman pragmatik olun ve her şeyin bir uzlaşma olduğunu unutmayın. DRY ne kadar çok uyursanız, kodunuz o kadar sıkı bağlanır. Soyutlamaları ne kadar çok tanıtırsanız, kod o kadar kolay test edilir, ancak o kadar zor anlarsınız. İdeolojiden kaçının ve ideal ile basit tutmak arasında iyi bir denge kurun. Hem geliştirme hem de bakım için maksimum verimliliğin tatlı noktası yatıyor.


27
İnsanların "yöntemlerin sadece bir şeyi yapması gerektiğini" tekrarlayan mantralarına uymaya çalıştıklarında ve teknik olarak bir yöntem haline getirilebildiklerinden, bir satırlık yöntemlerle tonlarca bittiklerinde benzer bir baş ağrısının ortaya çıktığını da eklemek isterim. .
Logarr

8
Re "Her zaman pragmatik olun ve her şeyin bir uzlaşma olduğunu unutmayın" : Bob Amca'nın müritleri bunun için bilinmemektedir (asıl niyet ne olursa olsun).
Peter Mortensen

13
İlk kısmı özetlemek için genellikle bir kahve stajyerine sahipsiniz, tam bir fiş takma takımı, flip-switch, şeker ihtiyacını kontrol et, doldurma, açık buzdolabı, süt alma kaşık-kaşık, bardak-bardak, pour-coffee, add-sugar, add-milk, stir-cup ve teslim kupası stajyerleri. ; P
Justin Time,

12
OP'nin sorununun temel nedeni, tek bir görevi yerine getirmesi gereken fonksiyonlar ile tek bir sorumluluğu
alephzero

6
"Kurallar, bilge adamların ve aptalların itaatinin rehberliği içindir." - Douglas Bader
Calanus,

29

5-10 dosya almak için kullanılan görevler şimdi 70-100 alabilir!

Bu tek sorumluluk ilkesinin (SRP) tam tersidir . Bu noktaya varmak için, bir çok ince taneli bir şekilde işlevini bölünmüş olmalı, ama bu SRP hakkında ne değil - önemli fikir görmezden yapıyor kohezyonluk .

SRP'ye göre, yazılımın olası nedenleriyle tanımlanan çizgiler boyunca modüllere bölünmesi gerekir, böylece başka bir yerde değişiklik yapılmadan tek bir tasarım değişikliği tek bir modüle uygulanabilir . Bu anlamda tek bir "modül" birden fazla sınıfa karşılık gelebilir, ancak bir değişiklik onlarca dosyaya dokunmanızı gerektiriyorsa, bu gerçekten birden çok değişiklik veya SRP'yi yanlış yapıyorsunuzdur.

Başlangıçta SRP'yi formüle eden Bob Martin, durumu açıklığa kavuşturmak için birkaç yıl önce bir blog yazısı yazdı . SRP'nin amaçları için bir “değişim nedeninin” ne olduğunu bir süre tartışır. Tamamıyla okunmaya değer, ancak özel ilgiyi hakediyor olanlar arasında SRP'nin bu alternatif ifadesi var:

Aynı nedenlerle değişen şeyleri bir araya getirin . Değişen şeyleri farklı nedenlerle ayırın.

(benimkine vurgu yapar). SRP, işleri mümkün olan en küçük parçalara bölmekle ilgili değil . Bu iyi bir tasarım değil ve ekibiniz direnme hakkına sahip. Kod tabanınızın güncellenmesini ve sürdürülmesini zorlaştırır. Birim test değerlendirmelerine dayanarak ekibinizi satışa çıkarmaya çalışıyor olabilirsiniz, fakat bu at arabasını attan önce koymak olacaktır.

Benzer şekilde, arayüz ayrıştırma ilkesi mutlak olarak alınmamalıdır. Kodunuzu, SRP'den daha iyi bir şekilde bölmek için bir neden yoktur ve genel olarak SRP ile oldukça uyumludur. Bir arayüz içeren bazı yöntemler bazı müşteriler bunu kırmak için bir neden değildir kullanmayın. Yine uyum arıyorsun.

Ek olarak, derin miras hiyerarşilerini tercih etmek için açık-kapalı prensibi veya Liskov ikame ilkesini almamanızı tavsiye ederim. Üst sınıfları olan bir alt sınıftan daha sıkı bir bağlantı yoktur ve sıkı bağlantı bir tasarım problemidir. Bunun yerine, kalıtıma ilişkin kompozisyonu, bunu yapmanın mantıklı olduğu her yerde tercih edin. Bu, kuplajınızı azaltacaktır ve bu nedenle belirli bir değişikliğin dokunması gerekebilecek dosya sayısı ve bağımlılık inversiyonuna güzel bir şekilde uyum sağlar.


1
Sanırım sadece hattın yerini bulmaya çalışıyorum. Son zamanlarda yapılan bir görevde, oldukça basit bir işlem gerçekleştirmem gerekti, ancak mevcut iskele veya işlevsellik olmadan bir kod tabanında idi. Dolayısıyla, yapmam gereken her şey çok basitti, fakat hepsi oldukça benzersizdi ve paylaşılan derslere sığmadı. Benim durumumda, bir dokümanı ağ sürücüsüne kaydetmem ve iki ayrı veritabanı tablosuna kaydetmem gerekiyordu. Her adımı çevreleyen kurallar oldukça özeldi. Dosya adı oluşturma (basit bir kılavuz) bile testi daha kolay hale getirmek için birkaç sınıfa sahiptir.
JD Davis

3
Yine, @ TestDavis, sadece test edilebilirlik amacıyla tek bir sınıftan birden fazla sınıf seçmek, atı attan önce koymak ve doğrudan birleşik işlevselliklerin birlikte gruplandırılması için çağrılan SRP'ye doğru gidiyor. Size ayrıntılar konusunda tavsiyede bulunamıyorum, ancak bireysel işlevsel değişikliklerin birçok dosyayı değiştirmeyi gerektirdiği sorun, haklı çıkarmaya çalışmanız gereken değil, ele almanız gereken (ve kaçınmaya çalışmak) bir konudur.
John Bollinger

Kabul ediyorum, bunu ekliyorum. Vikipedi'den alıntı yapmak gerekirse, "Martin bir sorumluluğu değiştirmek için bir neden olarak tanımlar ve bir sınıf veya modülün değiştirilmesi gereken bir ve yalnızca bir nedeni olması gerektiği sonucuna varır (yani yeniden yazılır)." ve "daha yakın zamanda belirtti" Bu ilke insanlarla ilgilidir. "" Aslında, bunun SRP'deki "sorumluluğun" işlevsellik değil paydaşlara atıfta bulunduğuna inanıyorum. Bir sınıf, yalnızca bir paydaşın (programınızı değiştirmenizi gerektiren kişi) ihtiyaç duyduğu değişikliklerden sorumlu olmalı, böylece değişiklik isteyen farklı paydaşlara yanıt olarak mümkün olduğunca FEW şeyleri değiştirmelisiniz.
Corrodias

12

5-10 dosya almak için kullanılan görevler şimdi 70-100 alabilir!

Bu bir yalan. Görevler hiçbir zaman yalnızca 5-10 dosya almadı.

10'dan daha az dosya içeren herhangi bir görevi çözemezsiniz. Neden? Çünkü C # kullanıyorsun. C #, yüksek düzeyde bir dildir. Merhaba dünya oluşturmak için 10'dan fazla dosya kullanıyorsunuz.

Ah, onları farketmediğine eminim, çünkü sen yazmadın. Demek onlara bakmıyorsun. Onlara güven.

Sorun dosya sayısı değil. Artık güvenmediğin çok şey var.

Bu sınamaların nasıl yapıldığını ve bu dosyaları geçtikten sonra nasıl çalıştığını anlamaya çalışın. Bunu yapmak birim test noktasıdır. Hiç kimse dosya sayısını umursamıyor. Güvenemeyecekleri şeyleri önemsiyorlar.

Küçük ve orta büyüklükteki uygulamalar için SOLID çok kolay bir satıştır. Herkes fayda ve bakım kolaylığını görür. Bununla birlikte, çok büyük ölçekli uygulamalarda SOLID için iyi bir değer teklifi görmüyorlar.

Değişim, çok büyük ölçekli uygulamalarda, yaptığınız hiçbir materyalden zordur. Burada uygulanacak en iyi bilgelik Bob Amca'dan gelmiyor. Miras Tüzüğü ile Etkili Çalışma adlı kitabında Michael Feathers'tan geliyor.

Yeniden yazma festivali başlatmayın. Eski kod zor kazanılmış bilgiyi temsil eder. Sorunu ortadan kaldırmak, çünkü sorunları var ve yeni ve gelişmiş paradigmada ifade edilmiyor X sadece yeni bir sorun kümesi istiyor ve zor kazanılmış bir bilgi istemiyor.

Bunun yerine, test edilemeyen eski kodunuzu test edilebilir hale getirmenin yollarını bulun (Tüyler'deki eski kod). Bu metaforda bir gömlek gibidir. Büyük parçalar, ekleri çıkardığınız şekilde ayırmak için geri alınabilecek doğal dikişlere birleştirilir. Bunu, kodun geri kalanını izole etmenizi sağlayan test "manşonları" takmanıza izin vermek için yapın. Şimdi test manşetlerini oluştururken manşetlere güveniyorsunuz çünkü bunu bir çalışma gömleğiyle yaptınız. (ow, bu metafor incinmeye başlıyor).

Bu fikir, çoğu mağazada olduğu gibi, sadece güncel gereksinimlerin çalışma kodunda olduğu varsayımından kaynaklanmaktadır. Bu, kanıtlanmış çalışma kodunun her bir özelliğini kaybetmeden kanıtlanmış çalışma kodunda değişiklik yapmanıza izin veren testlerde bunu kilitlemenizi sağlar. Şimdi, bu ilk test dalgasıyla, "eski" (test edilemez) kodun test edilebilir olmasını sağlayan değişiklikler yapmaya başlayabilirsiniz. Kalın olabilir, çünkü dikiş testleri sizi her zaman yaptığı gibi söyleyerek destekliyor ve yeni testler kodunuzun gerçekte yaptığınız şeyi yaptığını gösteriyor.

Bunlardan herhangi biri ile ne ilgisi var:

SOLID? A geçiş yaptıktan sonra büyük oranda artan sınıf sayısını yönetmek ve düzenlemek

Soyutlama.

Kötü soyutlamalar ile herhangi bir kod üssünden nefret ettirmemi sağlayabilirsiniz. Kötü bir soyutlama, içime bakmamı sağlayan şey. İçeri baktığımda beni şaşırtma. Beklediğim gibi ol.

Bana iyi bir isim verin, arayüzün nasıl kullanılacağını gösteren okunaklı testler (örnekler) verin ve düzenleyin, böylece bir şeyler bulabilirim ve 10, 100 veya 1000 dosya kullanırsak umurumda olmaz.

İyi tanımlayıcı isimlerle bir şeyler bulmama yardım ediyorsun. İyi isimleri olan şeyleri, iyi isimleri olan şeylere koyun.

Tüm bu hakları yaparsanız, bir işi bitirdiğinizde yalnızca 3 ila 5 dosyaya bağlı olarak dosyaları soyutlayacaksınız. 70-100 dosya hala orada. Ama onlar 3 ila 5'in arkasına saklanıyorlar. Bu sadece 3 ila 5'in bu hakkı için güvenirseniz işe yarar.

Bu yüzden gerçekten ihtiyacınız olan şey, tüm bu şeyler için iyi isimler bulmak ve insanların güvendiği testler yapmak. O olmadan beni de delirtiyorsun.

@Delioth büyüyen ağrılar konusunda iyi bir noktaya değinir . Bulaşık makinesinin yukarısındaki dolapta bulunan yemeklere alıştığınızda, kahvaltı barı üzerinde olmalarına alışmak biraz zaman alır. Bazı şeyleri zorlaştırır. Bazı şeyleri kolaylaştırır. Ancak insanlar bulaşıkların nereye gittiğini kabul etmiyorlarsa her türlü kabusa neden olur. Büyük bir kod tabanında sorun, aynı anda bulaşıkların yalnızca bazılarını taşıyabileceğinizdir. Şimdi iki yerde bulaşık var. Kafa karıştırıcı. Yemeklerin olması gerektiği yerde olduğuna güvenmek zorlaştırıyor. Bunu geçmek istiyorsan yapman gereken tek şey bulaşıkları yıkamak.

Buradaki sorun, kahvaltı barında bulaşıkları yıkamanın tüm bu saçmalıkları geçmeden önce buna değip değmeyeceğini gerçekten bilmek istersiniz. Bunun için tavsiye edebileceğim tek şey kamp gitmek.

Yeni bir paradigmayı ilk kez denediğinizde, uygulamanız gereken en son yer geniş bir kod tabanındadır. Bu takımın her üyesi için de geçerli. Hiç kimse bunu SOLID'in çalıştığı, OOP'nin çalıştığı ya da fonksiyonel programlamanın çalıştığı inancına dayandırmamalıdır. Her ekip üyesi, ne olursa olsun, bir oyuncak projesinde, yeni fikirle oynama şansına sahip olmalıdır. En azından nasıl çalıştığını görmelerini sağlar. Ne işe yaramadığını görmelerini sağlar. Onlar büyük bir karışıklık yapmadan önce doğru yapmayı öğrenmelerini sağlar.

İnsanlara oynamak için güvenli bir yer vermek, yeni fikirleri benimsemelerine ve bulaşıkların yeni evlerinde gerçekten işe yarayabileceklerine güvenmelerini sağlayacaktır.


3
Sorunun bazı ağrılarının da sadece ağrı arttığından bahsetmeye değer olabilir - evet, evet, bu bir şey için 15 dosya yapmaları gerekebilir ... şimdi bir daha asla bir GUIDProvider veya bir BasePathProvider yazmak zorunda kalmayacaklar , ya da bir ExtensionProvider, vb. Yeni bir greenfield projesine başladığınızda elde ettiğiniz aynı tür engeldir - çoğunlukla önemsiz, yazması aptal ve yine de yazılması gereken destekleyici özellikler. Onları oluşturmak için berbat, ama bir kez orada olduklarında, onlar hakkında düşünmene gerek kalmamalı ... hiç.
Delio

@Delioth Bu durumun olduğuna inanmaya inanılmaz bir şekilde inanıyorum. Önceden, bazı işlevler alt kümesine ihtiyacımız varsa (Diyelim ki AppSettings'te yer alan bir URL'yi istedik diyelim), etrafta geçen ve kullanılan büyük bir sınıfa sahibiz. Yeni yaklaşımla, AppSettingssadece bir url veya dosya yolu almak için bütünüyle dolaşmak için hiçbir neden yoktur .
JD Davis,

1
Yeniden yazma festivali başlatmayın. Eski kod zor kazanılmış bilgiyi temsil eder. Sorunu ortadan kaldırmak, çünkü sorunları var ve yeni ve gelişmiş paradigmada ifade edilmiyor X sadece yeni bir sorun kümesi istiyor ve zor kazanılmış bir bilgi istemiyor. Bu. Kesinlikle.
Flot2011

10

Kodunuz çok iyi ayrılmamış gibi görünüyor ve / veya görev boyutunuz çok büyük.

Kod değişikliği ya da büyük ölçekli yeniden düzenleme yapmazsanız, kod değişiklikleri 5-10 dosya olmalıdır . Tek bir değişiklik çok fazla dosyaya dokunursa, muhtemelen değişiklikleriniz art arda demektir. Bazı iyileştirilmiş soyutlamalar (daha fazla sorumluluk, arayüz ayrımı, bağımlılık inversiyonu) yardımcı olmalıdır. Aynı zamanda çok fazla sorumluluk almanız ve biraz daha pragmatizm kullanmanız da mümkündür - daha kısa ve daha ince tip hiyerarşileri. Bu, kodun ne yaptığını bilmek için onlarca dosyayı anlamanız gerekmediğinden kodun anlaşılmasını kolaylaştıracaktır.

Aynı zamanda çalışmanızın çok büyük olduğuna dair bir işaret olabilir. "Hey, bu özelliği ekle" yerine (kullanıcı arabirimi değişiklikleri ve api değişiklikleri ile veri erişim değişiklikleri ve güvenlik değişiklikleri ile test değişiklikleri ve ... gerektiren) daha kullanışlı parçalara bölünür. İncelenmesi ve anlaşılması daha kolay hale gelir, çünkü bitler arasında uygun sözleşmeler yapmanızı gerektirir.

Ve elbette, birim testleri bunların hepsine yardımcı olur. Seni düzgün arayüzler yapmaya zorluyorlar. Sizi, kodunuzu test etmek için gereken bitleri enjekte edecek kadar esnek hale getirmeye zorlar (test etmesi zorsa, yeniden kullanımı zor olacaktır). Ve insanları fazla mühendislik işlerinden uzaklaştırıyorlar çünkü ne kadar çok mühendislik yapıyorsanız o kadar çok test etmeniz gerekiyor.


2
5-10 dosyadan 70-100 dosyaya kadar varsayımsal olandan biraz daha fazlası var. Son görevim, yeni mikro hizmetlerimizden birinde bazı işlevler oluşturmaktı. Yeni hizmetin bir istek alması ve bir belge kaydetmesi gerekiyordu. Bunu yaparken, kullanıcı varlıklarını 2 ayrı veritabanında temsil etmek için sınıflara ve her biri için repolara ihtiyacım vardı. Yazmam gereken diğer tabloları temsil eden repolar. Dosya veri kontrolü ve ad oluşturma işlemlerini işlemek için atanmış sınıflar. Ve liste devam ediyor. Söylemeye gerek yok, mantık içeren her sınıf bir arayüz ile temsil edildi, böylece birim testleri için alay edilebilir.
JD Davis

1
Eski kod tabanlarımıza kadar hepsi birbirine sıkı sıkıya bağlı ve inanılmaz derecede monolitik. SOLID yaklaşımıyla, sınıflar arasındaki tek bağlantı POCO'larda olmuştur, diğer her şey DI ve arayüzlerden geçer.
JD Davis,

3
@JDDavis - bekle, neden bir mikro servis doğrudan çoklu veritabanlarıyla çalışıyor?
Telastyn

1
Dev menajerimizle bir uzlaşma oldu. O kitlesel monolitik ve prosedürel yazılımı tercih ediyor. Bu nedenle, mikro hizmetlerimiz olması gerekenden çok daha fazla makro. Altyapımız iyileştikçe, yavaşça işler kendi mikro servislerine geçecek. Şimdilik, belirli işlevselliği mikro hizmetlere dönüştürmek için boğucu yaklaşımını takip ediyoruz. Birden fazla hizmetin belirli bir kaynağa erişmesi gerektiğinden, bunları kendi mikro servislerine de taşıyoruz.
JD Davis,

4

Burada daha önce bahsedilen bazı şeyleri açıklamak istiyorum, ancak daha çok nesne sınırlarının çizildiği bir bakış açısıyla. Etki Alanına Dayalı Tasarım'a benzer bir şey izliyorsanız, nesneleriniz muhtemelen işinizin yönlerini temsil edecektir. Customerve Orderörneğin, nesneler olacaktır. Şimdi, başlangıç ​​noktanız olan sınıf adlarına dayanarak bir tahminde bulunacak olsaydım, sınıfınızda herhangi bir hesap AccountLogiciçin çalıştırılacak kod vardı . Ancak, OO’da her sınıfın bağlamı ve kimliği vardır. Bir nesneyi alıp bir sınıfa geçirmemelisiniz ve bu sınıfa nesnede değişiklikler yapmalısınız . Buna anemik model denir ve OO'yu pek iyi temsil etmiyor. Bunun yerine,AccountAccountLogicAccountAccountsınıfın Account.Close()veya gibi davranışa sahip olması gerekir Account.UpdateEmail()ve bu davranışlar yalnızca hesabın bu örneğini etkiler.

Şimdi, NASIL bu davranışlar ele alınmaktadır (ve çoğu durumda) soyutlamalar tarafından temsil edilen bağımlılıklara (yani arayüzler) yüklenebilir. Account.UpdateEmailörneğin, bir veritabanını veya dosyayı güncellemek veya bir servis veriyoluna mesaj göndermek vb. gibi olabilir. Ve gelecekte de değişebilir. Dolayısıyla, Accountsınıfınız örneğin IEmailUpdatebir AccountRepositorynesnenin uyguladığı birçok arayüzden biri olabilen bir bağımlılığa sahip olabilir . Nesneye tam bir IAccountRepositoryarabirim iletmek istemezsiniz, Accountçünkü Accountnesnenin erişmesini istemeyebileceğiniz ancak AccountRepositoryher ikisini de uygulayabilseniz bile, diğer tüm hesaplarda arama ve bulma gibi çok fazla şey yapacağı için IAccountRepositoryve IEmailUpdatearayüzlerAccountnesne yalnızca ihtiyacı olan küçük bölümlere erişebilir. Bu, Arabirim Ayrıştırma İlkesini korumanıza yardımcı olur .

Gerçekçi olarak, başkalarının da belirttiği gibi, bir sınıf patlamasıyla uğraşıyorsanız, olasılıkla SOLID prensibini (ve, uzantı olarak, OO) yanlış bir şekilde kullanıyor olmalısınız. SOLID, kodunuzu basitleştirmenize yardımcı olmalı, karmaşıklaştırmamalı. Fakat SRP gibi şeylerin ne anlama geldiğini gerçekten anlamak zaman alır. Yine de daha önemli olan, SOLID'in nasıl çalıştığını etki alanınıza ve sınırlı bağlamlara (başka bir DDD terimi) çok bağımlı hale getirecek olmasıdır. Gümüş kurşun ya da herkese uyan tek beden yok.

Birlikte çalıştığım insanlara vurgulamayı sevdiğim bir şey daha: yine, bir OOP nesnesinin davranışı olması gerekir ve aslında, davranışı tarafından tanımlanır, verileri değil. Nesnenizde özellikler ve alanlar dışında hiçbir şey yoksa, muhtemelen amaçladığınız davranış olmasa da, yine de davranışı vardır. Başka bir set mantığı olmayan, halka yazılabilir / ayarlanabilen bir özellik, içerdiği sınıfın davranışının, herhangi bir sebeple ve herhangi bir zamanda herhangi bir yerdeki herhangi birinin, herhangi bir zamanda herhangi bir gerekli ticari mantık veya validasyon olmadan bu özelliğin değerini değiştirmesine izin verdiği anlamına gelir. Bu genellikle insanların amaçladığı davranış değildir, ancak anemik bir modeliniz varsa, genellikle sınıflarınızın bunları kullanan kişilere duyurduğu davranış budur.


2

Yani bu oldukça basit bir tasarruf sağlamak için toplam 15 ders (POCO ve iskele hariç).

Bu delilik .... ama bu sınıflar kendim yazacağım bir şeye benziyor. Öyleyse onlara bir göz atalım. Şimdilik arayüzleri ve testleri görmezden gelelim.

  • BasePathProvider- IMHO, dosyalar ile çalışan önemsiz herhangi bir projenin buna ihtiyacı var. Böylece, zaten böyle bir şey olduğunu ve olduğu gibi kullanabileceğinizi varsayardım.
  • UniqueFilenameProvider - Tabii, zaten aldın, değil mi?
  • NewGuidProvider - Aynı durum, sadece GUID kullanmaya bakmıyorsanız.
  • FileExtensionCombiner - Aynı dava.
  • PatientFileWriter - Sanırım, bu şu anki görev için ana sınıftır.

Bana göre iyi görünüyor: Dört yardımcı sınıfa ihtiyaç duyan yeni bir sınıf yazmanız gerekiyor. Dört yardımcı sınıfın hepsi de oldukça yeniden kullanılabilir durumda, bu yüzden zaten kod bazında bir yerlerde olduklarına bahse girerim Aksi takdirde, ya şanssızlık (dosyalarınızı yazıp GUID'leri kullanan gerçekten takımınızdaki kişi siz misiniz ???) ya da başka bir problem.


Test sınıflarıyla ilgili olarak, yeni bir sınıf oluşturduğunuzda veya güncellediğinizde, test edilmesi gerekir. Yani beş sınıf yazmak da beş test sınıfı yazmak anlamına geliyor. Ancak bu, tasarımı daha da karmaşık hale getirmiyor:

  • Test sınıflarını başka bir yerde asla kullanmayacaksınız, çünkü bunlar otomatik olarak yürütülecek ve hepsi bu.
  • Test edilen sınıfları güncellemediğiniz veya dokümantasyon olarak kullanmadığınız sürece (bir sınıfın nasıl kullanılması gerektiğini açıkça gösterir), onlara bir daha bakmak istersiniz.

Arabirimlerle ilgili olarak, yalnızca DI çerçeveniz veya test çerçeveniz sınıflarla ilgilenemediğinde gereklidir. Onları kusurlu araçlar için ücretli olarak görebilirsiniz. Veya onları daha karmaşık şeyler olduğunu unutmanıza izin veren yararlı bir soyutlama olarak görebilirsiniz - bir arabirimin kaynağını okumak uygulamanın kaynağını okumaktan çok daha az zaman alır.


Bu bakış açısı için minnettarım. Bu özel durumda, oldukça yeni bir mikro hizmete işlevsellik yazıyordum. Maalesef, ana kod tabanımızda bile, yukarıdakilerin bir kısmı kullanımdayken, hiçbiri uzaktan yeniden kullanılabilir bir şekilde değildir. Yeniden kullanılabilir olması gereken her şey statik bir sınıfa girmiştir ya da sadece kodun etrafına kopyalanıp yapıştırılmıştır. Hala biraz ileri gittiğimi düşünüyorum, ancak her şeyin tamamen parçalanması ve ayrılması gerekmediğine katılıyorum .
JD Davis,

@JDDavis Diğer cevaplardan farklı bir şeyler yazmaya çalışıyordum (çoğunlukla aynı fikirdeyim). Bir şeyi kopyalayıp yapıştırdığınızda, bir şeyi genelleştirmek yerine tekrar kullanılamaz hale gelen başka bir kod parçası oluşturduğunuzdan, sizi bir gün daha kopyalayıp yapıştırmaya zorlayacak şekilde yeniden önlersiniz. IMHO, ikinci en büyük günah, sadece kuralları çok iyi takip ettikten sonra. Aşağıdaki kuralların sizi daha üretken hale getirdiği (özellikle gelecekteki değişikliklerle) ve zaman zaman onları biraz kırmak, çabanın uygunsuz olamayacağı durumlarda yardımcı olur. Hepsi göreceli.
maaartinus

@JDDavis Ve her şey araçlarınızın kalitesine bağlıdır. Örnek: DI'nin girişimci ve karmaşık olduğunu iddia eden insanlar var, ben de çoğunlukla ücretsiz olduğunu iddia ediyorum . +++Kuralları çiğnemeyle ilgili: Dört sınıf var, kodları daha çirkin hale getiren (en azından gözlerim için) büyük bir yeniden düzenleme işleminden sonra bunları enjekte edebildiğim yerlere ihtiyacım var, bu yüzden onları tek bir programa koymaya karar verdim (daha iyi bir programcı daha iyi bir yol bulabilir, ancak bundan memnunum; bu tekillerin sayısı uzun zamandır değişmiyor).
maaartinus

Bu cevap, OP soruyu örneğe eklediğinde, ne düşündüğümü ifade ediyor. @JDDavis Basit kazançlar için fonksiyonel araçları kullanarak bazı kazan kodunu / sınıflarını kaydedebileceğinizi ekleyeyim. Örneğin bir GUI sağlayıcısı - bunun için yeni bir sınıfa yeni bir arayüz sokmak yerine, neden sadece bunun için kullanmıyor ve yapıcıya Func<Guid>benzer bir isimsiz yöntem enjekte ediyor ()=>Guid.NewGuid()? Ve bunu test etmenize gerek yok. Net framework işlevi, Microsoft'un sizin için yaptığı bir şey. Toplamda, bu size 4 ders kurtaracak.
Doktor Brown,

... ve sunduğunuz diğer davaların aynı şekilde basitleştirilip basitleştirilemeyeceğini kontrol etmelisiniz (muhtemelen hepsi değil).
Doktor Brown,

2

Soyutlamalara bağlı olarak, tek sorumluluk sınıfları oluşturma ve ünite testleri yazma kesin bilimler değildir. Öğrenirken bir yöne çok ileri doğru sallanmak, aşırıya kaçmak ve daha sonra mantıklı bir norm bulmak tamamen normaldir. Sadece sarkacınız çok fazla sallandı gibi duruyor ve hatta sıkışmış bile olabilir.

Burası rayların dışına çıktığından şüphelendiğim yer:

Ünite testleri, zaman kaybı olduklarına inandıkları ve kodlarını her parçadan ayrı olarak bir bütün olarak daha hızlı ele alabildiklerine inandıkları için takıma inanılmaz zor bir satış oldu. Birim testlerini SOLID için bir onay olarak kullanmak, çoğunlukla boşuna olmuştur ve bu noktada çoğunlukla bir şaka haline gelmiştir.

Çoğu SOLID ilkesinden gelen avantajlardan biri (kesinlikle tek avantaj değil) kodumuz için birim testleri yazmayı kolaylaştırmasıdır. Bir sınıf bir soyutlamaya bağlıysa, soyutlamalar ile alay edebiliriz. Ayrılmış soyutlamalar alay etmek kolaydır. Bir sınıf bir şeyi yaparsa, daha düşük karmaşıklığa sahip olması muhtemeldir; bu, olası tüm yolları bilmek ve test etmek daha kolay demektir.

Ekibiniz birim testleri yazmıyorsa, ilgili iki şey oluyor:

Öncelikle, tüm bu arabirimleri ve sınıfları, tüm faydalarını anlamadan oluşturmak için birçok ekstra çalışma yapıyorlar. Ünite testlerinin yazma hayatımızı nasıl kolaylaştırdığını görmek biraz zaman alır ve pratik yapar. Birim testler yazmayı öğrenen kişilerin buna bağlı kalmasının nedenleri var, ancak bunları kendiniz bulmak için yeterince uzun süre devam etmeniz gerekiyor. Eğer takımınız buna teşebbüs etmezse, yaptıkları ekstra işin geri kalanı gibi hissedecekler, işe yaramazlar.

Örneğin, yeniden ateşlenmeye ihtiyaçları olduğunda ne olur? Yüzlerce küçük sınıfa sahiplerse ancak değişikliklerinin işe yarayıp yaramadığını onlara söyleyecek sınavları yoksa, bu ekstra sınıflar ve arayüzler bir gelişme değil, bir yük gibi görünecek.

İkincisi, birim testleri yazmak, kodunuzun gerçekten ne kadar soyutlama gerektirdiğini anlamanıza yardımcı olabilir. Dediğim gibi, bu bir bilim değil. Kötü bir şekilde başlıyoruz, her yere bakıyoruz ve daha iyi oluyoruz. Birim testlerinin, SOLID'i tamamlamanın kendine özgü bir yolu vardır. Bir soyutlama eklemeniz veya bir şeyi parçalara ayırmanız gerektiğinde ne biliyorsunuz? Başka bir deyişle, "Yeterince KATI" olduğunuzu nereden biliyorsunuz? Çoğu zaman cevap, bir şeyi test edemediğiniz zamandır.

Belki de kodunuz, çok küçük soyutlamalar ve sınıflar oluşturmadan test edilebilir. Ama eğer testleri yazmıyorsanız, nasıl söyleyebilirsiniz? Ne kadar uzağa gideriz? Her şeyi daha küçük ve daha küçük parçalara ayırarak saplantılı hale gelebiliriz. Bu bir tavşan deliği. Kodumuz için testler yazma yeteneği, hedefimize ne zaman ulaştığımızı görmemize yardımcı olur, böylece takıntıyı durdurabilir, devam edebilir ve daha fazla kod yazarken eğlenebiliriz.

Birim testleri her şeyi çözen gümüş bir kurşun değildir, ancak geliştiricilerin hayatlarını iyileştiren gerçekten harika bir kurşuntur. Mükemmel değiliz ve testlerimiz de değil. Ancak testler bize güven veriyor. Kodumuzun doğru olmasını bekliyoruz ve tersi olduğunda şaşırıyoruz. Mükemmel değiliz ve testlerimiz de değil. Fakat kodumuz test edildiğinde kendimize güveniriz. Kurallarımız uygulandığında çivilerimizi ısırmamız daha az olası ve bu sefer neyin kıracağını ve bizim hatamız olup olmayacağını merak ediyoruz.

Buna ek olarak, bir kez asıldığımızda, birim testleri yazmak kod geliştirmeyi yavaşlatıyor, daha hızlı hale getiriyor. Bir samanlıkta iğneye benzer sorunları bulmak için eski kodu gözden geçirmek veya hata ayıklamak için daha az zaman harcıyoruz.

Hatalar azalır, daha çok şey yaparız ve kaygıyı güvenle değiştiririz. Bu bir solma veya yılan yağı değil. Bu gerçek. Birçok geliştirici bunu onaylayacaktır. Eğer ekibiniz bunu yaşamamışsa, o öğrenme eğrisini geçmesi ve huzuru aşması gerekir. Anında sonuç alamayacaklarını fark ederek bir şans verin. Ama olduğu zaman, yaptıkları için mutlu olacaklar ve asla geriye bakmayacaklar. (Ya da izole edilmiş pariahlar olacaklar ve birim testlerinin ve diğer birçok birikmiş programlama bilgisinin zaman kaybı olduğu konusunda öfkeli blog yazıları yazacaklar.)

Geçiş yaptıktan bu yana, geliştiricilerin en büyük şikayetlerinden biri, daha önce her görevin yalnızca geliştiricinin 5-10 dosyaya dokunmasını gerektirdiği düzinelerce ve düzinelerce dosyayı incelemeye ve geçmeye devam edememeleri.

Tüm ünite testleri geçtiğinde akran incelemesi çok daha kolaydır ve bu incelemenin büyük bir kısmı sadece testlerin anlamlı olmasını sağlamaktır.

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.