Karmaşık durumlu sınıflarımı ve testlerini nasıl basitleştirebilirim?


9

Java'da yazılmış, çok karmaşık gerçek dünyadaki iş nesnelerine karşılık gelen bazı sınıflarımız olan dağıtılmış bir sistem projesindeyim. Bu nesneler, kullanıcının (veya başka bir ajanın) söz konusu nesnelere uygulayabileceği eylemlere karşılık gelen birçok yöntem içerir. Sonuç olarak, bu sınıflar çok karmaşık hale geldi.

Sistem genel mimari yaklaşımı, birkaç sınıfa odaklanan birçok davranışa ve birçok olası etkileşim senaryosuna yol açmıştır.

Örnek olarak ve işleri kolay ve net tutmak için Robot ve Araba'nın projemde sınıf olduğunu varsayalım.

Yani, Robot sınıfında aşağıdaki modelde birçok yöntemim olurdu:

  • uyku(); isSleepAvaliable ();
  • uyanık(); isAwakeAvaliable ();
  • (Yön) yürümek; isWalkAvaliable ();
  • sürgün (Yön); isShootAvaliable ();
  • turnOnAlert (); isTurnOnAlertAvailable ();
  • turnOffAlert (); isTurnOffAlertAvailable ();
  • () Şarj; isRechargeAvailable ();
  • Kapat(); isPowerOffAvailable ();
  • stepInCar (Car); isStepInCarAvailable ();
  • stepOutCar (Car); isStepOutCarAvailable ();
  • kendini imha(); isSelfDestructAvailable ();
  • ölmek(); isDieAvailable ();
  • yaşıyor(); isAwake (); isAlertOn (); getBatteryLevel (); getCurrentRidingCar (); getAmmo ();
  • ...

Araba sınıfında, benzer olurdu:

  • aç(); isTurnOnAvaliable ();
  • kapat(); isTurnOffAvaliable ();
  • (Yön) yürümek; isWalkAvaliable ();
  • () Yakıt ikmali; isRefuelAvailable ();
  • kendini imha(); isSelfDestructAvailable ();
  • () Kilitlenmesine; isCrashAvailable ();
  • isOperational (); Ison (); getFuelLevel (); getCurrentPassenger ();
  • ...

Bunların her biri (Robot ve Araba), bazı eyaletlerde bazı eylemlerin mümkün olduğu ve bazılarının mümkün olmadığı bir durum makinesi olarak uygulanır. Eylemler nesnenin durumunu değiştirir. İşlem yöntemleri IllegalStateExceptiongeçersiz bir durumda çağrıldığında atar ve isXXXAvailable()yöntemler o sırada eylemin mümkün olup olmadığını söyler. Bazıları devletten kolayca çıkarılabilir olsa da (örneğin, uyku durumunda, uyanık mevcuttur), bazıları yoktur (ateş etmek, uyanık, canlı, cephaneye sahip olmak ve araba kullanmamak).

Ayrıca, nesneler arasındaki etkileşimler de karmaşıktır. Örneğin, Araba sadece bir Robot yolcuyu tutabilir, bu yüzden başka bir tane girmeye çalışırsa, bir istisna atılmalıdır; Araba düşerse, yolcu ölmelidir; Robot bir aracın içinde ölmüşse, arabanın kendisi iyi olsa bile dışarı çıkamaz; Robot bir arabanın içindeyse, dışarı çıkmadan başka bir tanesine giremez; vb.

Bunun sonucu, daha önce de söylediğim gibi, bu sınıflar gerçekten karmaşık hale geldi. İşleri daha da kötüleştirmek için Robot ve Araba etkileşime girdiğinde yüzlerce olası senaryo var. Ayrıca, bu mantığın çoğunun diğer sistemlerde uzak verilere erişmesi gerekir. Sonuç olarak birim testi çok zorlaştı ve bir sürü test problemimiz var, bunlardan biri kısır döngüde diğerine neden oluyor:

  • Testcases kurulumları çok karmaşıktır, çünkü egzersiz yapmak için önemli ölçüde karmaşık bir dünya yaratmaları gerekir.
  • Test sayısı çok fazla.
  • Test paketinin çalışması birkaç saat sürer.
  • Test kapsamımız çok düşük.
  • Test kodu, test ettikleri koddan haftalar veya aylar sonra yazılır veya hiç yazılmaz.
  • Birçok test de kırıldı, çünkü test edilen kodun gereksinimleri değişti.
  • Bazı senaryolar o kadar karmaşıktır ki, kurulum sırasında zaman aşımına uğramadan başarısız olurlar (her testte bir zaman aşımı yapılandırdık, en kötü durumlarda 2 dakika uzunluğunda ve bu süre boyunca zaman aşımına uğradığında bile, sonsuz bir döngü olmamasını sağladık).
  • Hatalar düzenli olarak üretim ortamına girer.

Bu Robot ve Araba senaryosu, gerçekte sahip olduğumuz şeyin aşırı basitleştirilmesidir. Açıkçası, bu durum yönetilemez. Yani, yardım ve öneriler istiyorum: 1, sınıfların karmaşıklığını azaltmak; 2. Nesnelerim arasındaki etkileşim senaryolarını basitleştirin; 3. Test süresini ve test edilecek kod miktarını azaltın.

EDIT:
Bence devlet makineleri konusunda net değildim. Robot devletler "uyku", "uyanık", "şarj", "ölü", vb ile bir devlet makinesidir. Araba başka bir devlet makinesidir.

DÜZENLEME 2: Sistemimin gerçekte ne olduğunu merak ediyorsanız, etkileşimde bulunan sınıflar Sunucu, IPAdresi, Disk, Yedekleme, Kullanıcı, Yazılım Lisansı vb. Şeylerdir. Robot ve Araba senaryosu bulduğum bir durum bu benim sorunumu açıklamak için yeterince basit olurdu.


Code Review.SE'ye sormayı düşündünüz mü? Bunun dışında, sizinki gibi tasarım için Extract Class türünün yeniden düzenlenmesi hakkında düşünmeye başlardım
gnat

Kod incelemesi düşündüm, ama bu doğru yer değil. Ana sorun kodun kendisinde değil, sorun, birkaç sınıfta yoğunlaşan birçok davranışa ve birçok olası etkileşim senaryosuna yol açan sistem genel mimari yaklaşımındadır.
Victor Stafusa

@gnat Verilen Robot ve Araba senaryosunda Extract Class'ı nasıl uygulayacağımı gösteren bir örnek verebilir misiniz?
Victor Stafusa

Araba ile ilgili eşyaları Robot'tan ayrı bir sınıfa çıkarırdım. Ben de uyku + uyanık ile ilgili tüm yöntemleri özel bir sınıfa ayıklamak istiyorum. Çıkarmayı hak ettiği düşünülen diğer “adaylar” güç + şarj yöntemleri, hareketle ilgili şeyler. Vb Not, bu yeniden düzenleme olduğu için, robot için harici API muhtemelen kalmalıdır; ilk aşamada sadece iç kısımları değiştirirdim. BTDTGTTS
gnat

Bu bir Kod İnceleme sorusu değildir - mimari konu dışıdır.
Michael K

Yanıtlar:


8

Devlet zaten kullanmadığınız takdirde tasarım desen, kullanım olabilir.

Böylece örnek, devam etmek için - çekirdek fikri her ayrı durum için bir iç sınıf oluşturmak olduğunu SleepingRobot, AwakeRobot, RechargingRobotve DeadRobotortak bir arabirim uygulama, tüm olacağını sınıfları.

RobotSınıftaki yöntemler ( sleep()ve gibi isSleepAvaliable()), mevcut iç sınıfa delege eden basit uygulamalara sahiptir.

Durum değişiklikleri mevcut iç sınıfı farklı bir sınıfla değiştirerek uygulanır.

Bu yaklaşımın avantajı, devlet sınıflarının her birinin (sadece bir olası durumu temsil ettiği için) çok daha basit olması ve bağımsız olarak test edilebilmesidir. Uygulama dilinize bağlı olarak (belirtilmemiş), yine de her şeyin aynı dosyada olmasına kısıtlanmış olabilir veya işleri daha küçük kaynak dosyalara bölebilirsiniz.


Java kullanıyorum.
Victor Stafusa

İyi öneri. Bu şekilde her uygulama, tüm durumları aynı anda test eden 2.000 satırlık bir junit sınıfına sahip olmadan ayrı ayrı test edilebilen net bir odağa sahiptir.
OliverS

3

Kodunuzu bilmiyorum, ama "uyku" yöntemi örneği alarak, aşağıdaki "basit" kod gibi bir şey olduğunu varsayalım:

public void sleep() {
 if(!dead && awake) {
  sleeping = true;
  awake = false;
  this.updateState(SLEEPING);
 }
 throw new IllegalArgumentException("robot is either dead or not awake");
}

Bence entegrasyon testleri ile birim testleri arasında bir fark yaratmanız gerekiyor . Tüm makine durumunuzda çalışan bir test yazmak kesinlikle büyük bir iştir. Uyku yönteminizin düzgün çalışıp çalışmadığını test eden daha küçük birim testleri yazmak daha kolaydır. Bu noktada, makine durumunun düzgün bir şekilde güncellenip güncellenmediğini veya "araba" nın, makine durumunun "robot" ... tarafından güncellenmiş olduğu gerçeğine doğru yanıt verip vermediğini bilmenize gerek yoktur.

Yukarıdaki kod göz önüne alındığında, "machineState" nesnesini alay ediyorum ve ilk testim olurdu:

testSleep_dead() {
 robot.dead = true;
 robot.awake = false;
 robot.setState(AWAKE);
 try {
  robot.sleep();
  fail("should have got an exception");
 } catch(Exception e) {
  assertTrue(e instanceof IllegalArgumentException);
  assertEquals("robot is either dead or not awake", e.getMessage());
 }
}

Benim kişisel görüşüm, böyle küçük birim testleri yazmanın ilk yapılacak şey olması gerektiğidir. Bunu sen yazdın:

Testcases kurulumları çok karmaşıktır, çünkü egzersiz yapmak için önemli ölçüde karmaşık bir dünya yaratmaları gerekir.

Bu küçük testleri yapmak çok hızlı olmalı ve “karmaşık dünyanız” gibi önceden başlatacak hiçbir şeyiniz olmamalıdır. Örneğin, bir IOC kapsayıcısına (örneğin Spring) dayanan bir uygulama ise, birim testleri sırasında içeriği başlatmanız gerekmez.

Karmaşık kodunuzun büyük bir yüzdesini birim testleri ile ele aldıktan sonra, daha fazla zaman alan ve daha karmaşık entegrasyon testleri oluşturmaya başlayabilirsiniz.

Son olarak, kodunuz karmaşıksa (şimdi söylediğiniz gibi) veya yeniden düzenlendikten sonra yapılabilir.


Devlet makineleri konusunda net olmadığımı düşünüyorum. Robot devletler "uyku", "uyanık", "şarj", "ölü", vb ile bir devlet makinesidir. Araba başka bir devlet makinesidir.
Victor Stafusa

@Victor Tamam, isterseniz örnek kodumu düzeltmekten çekinmeyin. Bana aksini söylemedikçe, birim testler konusundaki düşüncemin hala geçerli olduğunu düşünüyorum, umarım en azından.
Jalayn

Örneği düzelttim. Kolayca görünür hale getirme ayrıcalığım yok, bu yüzden önce hakem tarafından gözden geçirilmelidir. Yorumunuz yardımcı olur.
Victor Stafusa

2

Arayüz Ayrışma İlkesi hakkındaki Wikipedia makalesinin "Kökeni" bölümünü okuyordum ve bu soruyu hatırlattım.

Makaleyi alıntılayacağım. Sorun: "... bir ana Job sınıfı .... çeşitli farklı müşterilere özgü çok sayıda yöntem içeren bir yağ sınıfı." Çözüm: "... Job sınıfı ve tüm müşterileri arasında bir arayüz katmanı ..."

Sorununuz Xerox'un sahip olduğu bir permütasyon gibi görünüyor. Bir yağ sınıfı yerine iki tane var ve bu iki yağ sınıfı çok sayıda müşteri yerine birbiriyle konuşuyor.

Yöntemleri etkileşim türüne göre gruplayabilir ve ardından her tür için bir arabirim sınıfı oluşturabilir misiniz? Örneğin: RobotPowerInterface, RobotNavigationInterface, RobotAlarmInterface sınıfları?

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.