Bir alıcı, nesnesi geçersiz durumdaysa bir istisna atmalıdır mı?


55

Genelde bir OOP sorunu olduğunu düşünmeme rağmen, özellikle Java'da sık sık bu soruna rastlarım. Yani: bir istisna yaratmak, bir tasarım problemini ortaya çıkarır.

Bir String namealan ve bir String surnamealan olan bir sınıfım olduğunu varsayalım .

Daha sonra bu alanları bir tür belgede görüntülemek üzere bir kişinin tam adını oluşturmak için kullanır, bir fatura söyleyin.

public void String name;
public void String surname;

public String getCompleteName() {return name + " " + surname;}

public void displayCompleteNameOnInvoice() {
    String completeName = getCompleteName();
    //do something with it....
}

Şimdi displayCompleteNameOnInvoiceismim atanmadan önce çağrılırsa , sınıfımın davranışını bir hata atarak güçlendirmek istiyorum . İyi bir fikir gibi görünüyor, değil mi?

İstisna yükseltme kodunu getCompleteNamemetoda ekleyebilirim . Ancak bu şekilde, sınıf kullanıcısı ile 'örtük' bir sözleşmeyi ihlal ediyorum; Genelde, alıcıların, değerleri belirlenmemişse istisnalar atması beklenmez. Tamam, bu standart bir alıcı değil, çünkü tek bir alanı geri getirmiyor, ancak kullanıcı bakış açısından bu ayrım düşünmek için çok ince olabilir.

Veya istisnayı içinden fırlatabilirim displayCompleteNameOnInvoice. Fakat bunu yapmak için doğrudan nameveya surnamealanları test etmeliyim ve böyle yapmak, onu temsil ettiğim soyutlamayı ihlal eder getCompleteName. Adın tamamını kontrol etmek ve oluşturmak bu yöntem sorumluluğudur. Kararı diğer verilere dayanarak, bazı durumlarda yeterli olduğuna bile karar verebilirdi surname.

Yani tek olasılık yönteminin semantik değiştirmek gibi görünüyor getCompleteNameiçin composeCompleteName, onunla bir daha 'etkin' davranış ve, istisna atan yeteneğini gösteriyor ki.

Bu daha iyi tasarım çözümü mü? Her zaman sadelik ve doğruluk arasındaki en iyi dengeyi arıyorum. Bu sorun için tasarım referansı var mı?


8
"Genel olarak, alıcıların, değerleri belirlenmemişse istisnalar atması gerekmez." - O zaman ne yapmaları gerekiyor?
Rotem

2
@scriptin Sonra bu mantığı takip ederek, displayCompleteNameOnInvoiceeğer getCompleteNamegeri dönerse bir istisna atabilir null, değil mi?
Rotem

2
@Rotem olabilir. Soru, olması gerektiği ile ilgilidir.
Scriptin

22
Bu konuda, Yanlışlıklar Programcıları , İsimler Hakkında İnanma , 'ilk ad' ve 'soyad' kavramlarının neden çok dar olduğu konusunda çok iyi bir okumadır .
Lars Viklund

9
Nesneniz geçersiz bir duruma sahipse, çoktan batırdınız.
user253751

Yanıtlar:


151

Sınıfınızın bir isim vermeden oluşturulmasına izin vermeyin.


9
Bu ilginç ama radikal bir çözüm. Sınıflarınızın çoğu zaman daha akıcı olması gerekir, ancak belirli yöntemlerin çağrılması halinde ve ancak aksi takdirde kullanılması gereken çok fazla açıklama gerektiren küçük sınıfların çoğalmasıyla sonuçlanırsınız.
AgostinoX

71
Bu bir yorum değil. Tavsiyede cevabı uygularsa, artık açıklanan sorunu yaşamaz. Bu bir cevap.
DeadMG

14
@AgostinoX: Mutlaka gerekli ise sınıflarınızı sadece akışkan yapmalısınız. Aksi takdirde, mümkün olduğu kadar değişmez olmalıdır.
DeadMG

25
@gnat: Burada PSE'deki her sorunun ve her cevabın kalitesini sorgulayarak harika bir iş çıkarıyorsunuz, ama bazen IMHO biraz fazla özelsin.
Doktor Brown

9
Geçerli olarak isteğe bağlı olabilirlerse, ilk etapta atması için hiçbir sebep yoktur.
DeadMG

75

DeadMG tarafından verilen cevap hemen hemen onu işaret ediyor, ancak biraz daha farklı ifade etmeme izin verin: Geçersiz durumdaki nesnelere sahip olmaktan kaçının. Bir nesne, yerine getirdiği görev bağlamında "bütün" olmalıdır. Ya da olmamalı.

Akışkanlık istiyorsanız, Oluşturucu Deseni gibi bir şey kullanın . Ya da inşaatın içindeki bir nesnenin “gerçek olana” karşı ayrı bir yeniden birleşmesi olan, herhangi birinin geçerli bir duruma sahip olduğu garanti edilen ve gerçekte o durumda tanımlanan işlemleri (örn. getCompleteName) Ortaya koyan başka bir şey .


7
So if you want fluidity, use something like the builder pattern- akışkanlık veya değişmezlik (bir şekilde demek istediğin sürece). Kişi değişmez nesneler oluşturmak istiyor ancak bazı değerleri ayarlamayı ertelemek istiyorsa, izlenmesi gereken model onları bir oluşturucu nesnede birer birer ayarlamak ve sadece doğru için gerekli tüm değerleri topladığımızda değişmez bir tane oluşturmaktır. devlet ...
Konrad Morawski

1
@KonradMorawski: Kafam karıştı. İfademi netleştiriyor veya çelişiyor musunuz? ;)
back2dos

3
Bunu tamamlamaya çalıştım ...
Konrad Morawski

40

Geçersiz durumu olan bir nesneniz yok.

Bir yapıcının veya yapıcının amacı, nesnenin durumunu tutarlı ve kullanılabilir bir şeye ayarlamaktır. Bu garanti olmadan, problemler nesnelerde uygunsuz şekilde başlatılmış durumla ortaya çıkar. Eşzamanlılıkla uğraşıyorsanız ve bu sorun tamamen kurulmadan önce nesneye erişebilirse bu sorun daha da karmaşıklaşır.

Öyleyse, bu bölümle ilgili soru, "neden adın veya soyadınızın boş olmasına izin veriyorsunuz?" Bu, sınıfta düzgün çalışması için geçerli bir şey değilse, izin vermeyin. Nesnenin oluşumunu doğru bir şekilde doğrulayan bir kurucu veya kurucu bulundurun ve doğru değilse, konuyu bu noktada dile getirin. Ayrıca , "bunun boş kalamayacağını" bildirmek ve kodlama ve analizde uygulamak için var olan @NotNullek açıklamalardan birini kullanmak isteyebilirsiniz .

Bu garantinin uygulanmasıyla, kod hakkında, ne yaptığı ve nedenlerini tuhaf yerlere istisnalar atması veya alıcı işlevlerinin etrafına aşırı kontroller koyması gerekmemesi çok daha kolay hale gelir.

Daha fazlasını yapan alıcılar.

Bu konuda orada biraz var. Sen var getters içinde Ne Kadar Mantık ve ne Alıcı ve ayarlayıcıların içinde izin edilmelidir? buradan ve Stack Overflow'ta ek mantık gerçekleştiren alıcılar ve ayarlayıcılar . Bu, sınıf tasarımında tekrar tekrar ortaya çıkan bir konudur.

Bunun özü aşağıdakilerden geliyor:

Genelde, alıcıların, değerleri belirlenmemişse istisnalar atmaması gerekir.

Ve haklısın. Yapmamalılar. Dünyanın geri kalanına 'aptal' görünmeli ve bekleneni yapmalılar. Buraya çok fazla mantık koymak, en az şaşkınlık yasasının ihlal edildiği sorunlara yol açmaktadır. Ve bununla yüzleşelim, alıcıları gerçekten sarmak istemiyorsunuz try catchçünkü bunu yapmayı ne kadar sevdiğimizi biliyoruz.

Orada durumlar da vardır gerekir bir kullanma getFooböyle JavaBeans çerçeve olarak yöntemini ve bir şey olduğunda EL böylece (a fasulye almak için bekliyor çağıran <% bar.foo %>aslında çağıran getFoo()sınıfta - 'fasulye kompozisyonunu yapıyor ya katılmayacağına kenara koyma bu görüşe bırakılsın mı? 'çünkü biri açıkça diğerinin doğru cevabı alabildiği durumlarla kolayca karşılaşabilir)

Belirli bir alıcının bir arabirimde belirtilmesinin veya yeniden yapılandırılmış olan sınıf için daha önce açığa çıkmış olan genel API'nin bir parçası olmasının mümkün olduğunu fark edin (daha önceki bir sürüm daha önce 'completeName' verilmiş ve bir yeniden düzenleme kırılmıştır) iki alana ayrılır).

Günün sonunda...

Akla gelmesi en kolay olan şeyi yapın. Bu kodu korumak için tasarladığınızdan daha fazla zaman harcayacaksınız (tasarım ne kadar az zaman harcarsanız, o kadar fazla zaman harcayacaksınız). İdeal bir seçim o korumak için çok zaman almayacaktır şekilde dizayn etmesi, ancak günlerce düşünmeye oraya oturma ya - bu gerçekten bir tasarım tercihi olmadığı sürece edecek sonradan etkileri gün var .

Alıcının semantik saflığına sahip olmaya çalışmak bir private T foo; T getFoo() { return foo; }noktada başınızı belaya sokar. Bazen kod sadece bu modele uymaz ve bu şekilde kalmaya çalıştığı çarpıtmalar sadece mantıklı gelmez ... ve sonunda tasarımı zorlaştırır.

Bazen tasarımın ideallerinin kodda istediğiniz şekilde gerçekleştirilemediğini kabul edin. Yönergeler kurallardır - zincir değil.


2
Açıkçası benim örneğim, kodlama stilini bir engelleme meselesi üzerine düşünerek geliştirmek için sadece bir bahaneydi. Ama ben ve diğerlerinin kuruculara verdikleri önemden oldukça şaşkınım. Teoride sözlerini tutuyorlar. Uygulamada, kalıtımla başa çıkmak için hantal hale geliyorlar, sınıftan bir arayüz çıkardığınızda kullanılamazlar, javabeanların ve yanlış gitmezsem bahar gibi diğer çerçeveler tarafından benimsenmeyenler. Bir 'sınıf' fikir şemsiyesi altında, birçok farklı nesne kategorisi yaşamaktadır. Bir değer nesnesi doğrulayıcı bir kurucuya sahip olabilir, ancak bu sadece bir türdür.
AgostinoX

2
Kod hakkında mantıklı olmak, bir API kullanarak kod yazabilmek veya kodu koruyabilmek için anahtardır. Bir sınıf onu yapar geçersiz bir durumda olmasını sağlayan bir yapıcı varsa çok düşünmek zor "de, bu ne yapıyor?" çünkü şimdi düşünülmüş veya amaçlanan sınıfın tasarımcısından daha fazla durum düşünmelisiniz. Bu ekstra kavramsal yük, daha fazla hatanın cezası ve kod yazmak için daha uzun bir süre ile gelir. Öte yandan, koda bakıp "adınız ve soyadınız asla boş kalmaz" diyebilirseniz, kod kullanarak kod yazmanız çok daha kolay hale gelir.

2
Eksik, mutlaka geçersiz olduğu anlamına gelmez. Fişi olmayan bir fatura devam ediyor olabilir ve sunduğu ortak API bunun geçerli bir durum olduğunu yansıtır. Eğer surnameya nameda null olmak nesne için geçerli bir durum ise, ona göre kullanın. Ancak, eğer geçerli bir durum değilse - sınıf içindeki yöntemlerin istisnalar atmasına neden oluyorsa, başlatıldığı noktadan itibaren bu durumda olması önlenmelidir. Eksik nesnelerin etrafından geçebilirsiniz, ancak geçersiz olanların etrafından dolaşmak sorunludur. Boş bir soyadı olağandışıysa, daha sonradan daha önce önlemeyi deneyin.

2
Okunacak ilgili bir blog yazısı: Tasarım Nesnesi Başlatma ve bir örnek değişkeni başlatmak için dört yaklaşıma dikkat edin. Bunlardan biri, bir istisna oluşturduğuna dikkat ederek geçersiz bir durumda olmasına izin vermektir. Bundan kaçınmaya çalışıyorsanız, bu yaklaşım geçerli değildir ve her zaman geçerli bir nesne olmasını içeren diğer yaklaşımlarla çalışmanız gerekir. Blogdan: "Geçersiz durumları olabilen nesnelerin kullanımı her zaman geçerli olanlardan daha zor ve anlamak daha zor." ve bu anahtar.

5
“Akla gelmesi en kolay olan şeyi yapın.” Bu. Bu
Ben

5

Ben bir Java adamı değilim, ama bu sunduğunuz her iki kısıtlamaya da uyuyor gibi görünüyor.

getCompleteNameisimleri başlatılmamışsa bir istisna displayCompleteNameOnInvoiceatmaz ve yapar.

public String getCompleteName()
{
    if (name == null || surname == null)
    {
        return null;
    }
    return name + " " + surname;
}

public void displayCompleteNameOnInvoice() 
{
    String completeName = getCompleteName();
    if (completeName == null)
    {
        //throw an exception.
    }
    //do something with it....
}

Bunun neden doğru bir çözüm olduğunu genişletmek için: Bu şekilde, arayan kişinin getCompleteName()mülkün gerçekten depolanıp saklanmadığını veya anında hesaplandığını umursamaya ihtiyacı yoktur.
Bart van Ingen Schenau

18
Bu çözümün neden bu kadar saçma sapan olduğunu genişletmek için getCompleteName, çirkin olan her çağrıda boşluğu kontrol etmeniz gerekir .
DeadMG

Hayır, @DeadMG, sadece eğer getCompleteName null değerini döndürürse istisna atılması gereken durumlarda kontrol etmeniz gerekir. Olması gereken bu. Bu çözüm doğru.
Dawood ibn Kareem

1
@DeadMG Evet, ancak DİĞER'in dersin getCompleteName()geri kalanında, hatta programın diğer bölümlerinde ne kullandığını bilmiyoruz . Bazı durumlarda, iade nulltam olarak gerekli olan şey olabilir. Ancak, özelliği “bu durumda bir istisna atmak” olan BİR yöntem olsa da, kod vermemiz gereken şey kesinlikle budur.
Dawood ibn Kareem

4
Bunun en iyi çözüm olduğu durumlar olabilir, ancak bunların hiçbirini hayal edemiyorum. Çoğu durumda, bu aşırı karmaşık görünüyor: Bir yöntem eksik verilerle atıyor, diğeri null döndürüyor. Korkarım ki bu arayanlar tarafından yanlış kullanılmış olabilir.
sleske

4

Kimse sorunuzu cevaplamıyor gibi görünüyor.
İnsanların inanmak istediklerinden farklı olarak, "geçersiz nesnelerden kaçının" çoğu zaman pratik bir çözüm değildir.

Cevap:

Evet olmalı.

Kolay örnek: FileStream.LengthC # /. NET'te , NotSupportedExceptioneğer dosya aranamadığında atıyor .


Yorumlar genişletilmiş tartışmalar için değildir; bu konuşma sohbete taşındı .
maple_shaft

1

Bütün kontrol adı işini başardın.

getCompleteName()hangi fonksiyonları kullanacağını bilmek zorunda değildir. Bu nedenle, bir namearamayı displayCompleteNameOnInvoice()yapmamak getCompleteName()sorumluluğu değildir .

Ancak, namebunun için açık bir önkoşuldur displayCompleteNameOnInvoice(). Bu nedenle, devleti kontrol etme sorumluluğunu üstlenmelidir (İsterseniz , kontrol etme sorumluluğunu başka bir yönteme devretmelidir ).

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.