(Eğer başka) varsa başka yollarla işlemek


161

Bu küçük bir kıkırdama, ama böyle bir şeyi kodlamak zorunda kaldığımda tekrarlama beni rahatsız ediyor, ama çözümlerin hiçbirinin daha kötü olmadığından emin değilim.

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}
  • Bu tür bir mantık için bir isim var mı?
  • Ben de biraz OKB muyum?

Kötülük kanunu önerilerine açığım, sadece merak aşkına ...


8
@Emmad Kareem: iki DefaultActionçağrı DRY ilkesini ihlal ediyor
Abyx

Cevabınız için teşekkürler, ancak sonuçların geri dönmediğini ve kısaltmalara neden olabileceğinden (programlama diline bağlı olarak), try / catch kullanmamanız dışında, sorun olmadığını düşünüyorum.
NoChance

20
Bence buradaki asıl mesele tutarsız soyutlama seviyelerinde çalışıyor olmanız . Daha yüksek soyutlama düzeyi: make sure I have valid data for DoSomething(), and then DoSomething() with it. Otherwise, take DefaultAction(). Nitty gritty, DoSomething () 'in verilerinin elinizde olduğundan emin olmanın detaylarını daha düşük bir soyutlama seviyesindedir ve bu nedenle farklı bir fonksiyonda olmalıdır. Bu fonksiyon daha yüksek soyutlama seviyesinde bir isme sahip olacak ve uygulaması düşük seviyeli olacaktır. Aşağıdaki iyi cevaplar bu konuyu ele almaktadır.
Gilad Naor

6
Lütfen bir dil belirtin. Muhtemel çözümler, standart deyimler ve uzun zamandır devam eden kültürel normlar farklı diller için farklıdır ve Q'nize farklı cevaplar verecektir.
Caleb

1
"Yeniden düzenleme: Mevcut Kod Tasarımını İyileştirme" adlı bu kitaba başvurabilirsiniz. İf-else yapısı hakkında gerçekten faydalı bir uygulama olan birkaç bölüm var.
Vacker

Yanıtlar:


96

İşlev (yöntem) 'i ayıklayın ve returndeyimi kullanın :

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }
}

DefaultAction();

Veya, belki de daha iyisi, içerik alma ve işlemeyi ayırma

contents_t get_contents(name_t file)
{
    if(!FileExists(file))
        return null;

    contents = OpenFile(file);
    if(!SomeTest(contents)) // like IsContentsValid
        return null;

    return contents;
}

...

contents = get_contents(file)
contents ? DoSomething(contents) : DefaultAction();

gnc:

Neden istisnalar değil, neden OpenFileIO istisnası atmıyor:
Bunun, IO dosyasıyla ilgili sorudan çok genel bir soru olduğunu düşünüyorum. Gibi isimler FileExists, OpenFilekafa karıştırıcı olabilir, ancak bunların yerine eğer Foo, Barvb, - o daha net olurdu DefaultActionsıklıkta adlandırılabilir DoSomethingdışı istisnai durumda olabilir, böylece. Péter Török bu sorunun cevabının sonunda yazdı

Neden 2. varyantta üçlü koşullu işleç
var : [C ++] etiketi varsa, koşul bölümünde ifbeyanı ile ifade yazdım contents:

if(contents_t contents = get_contents(file))
    DoSomething(contents);
else
    DefaultAction();

Ancak, diğer (C-benzeri) diller için, if(contents) ...; else ...;tam şartlı operatöre sahip olan ifade ifadesiyle aynı, ancak daha uzun. Kodun ana kısmı get_contentsişlev olduğu için sadece kısa versiyonunu kullandım (ve ayrıca ihmal edilen contentstip). Neyse, bu sorunun ötesinde.


93
Birden fazla geri dönüş için +1 - yöntemler yeterince küçük yapıldığında , bu yaklaşım benim için en iyi sonucu verdi
gnat

Ara sıra kullanmamla birlikte, çok sayıda getirinin büyük bir hayranı değilim. Basit bir şey için oldukça makul, ama sadece iyi ölçeklendirilmez. Standartımız, çılgın basit yöntemler hariç herkesin önüne geçmektir, çünkü yöntemler küçüldüklerinden daha büyük boyutta büyür.
Brian Knoblauch

3
Birden fazla geri dönüş yolu, C ++ programlarında negatif performans sonuçlarına sahip olabilir ve bu da en iyileştiricinin RVO kullanma çabalarını engeller (ayrıca her yol aynı nesneyi döndürmediği sürece NRVO).
Functastic

Mantığın 2. çözümdeki tersine çevrilmesini tavsiye ederim: {if (dosya varsa) {içeriğini ayarla; if (sometest) {içerik döndür; }} return null; } Akışı kolaylaştırır ve satır sayısını azaltır.
Kama

1
Merhaba Abyx, buradaki yorumlardan bazı geri bildirimler aldığınızı farkettim: bunu yaptığınız için teşekkürler. Cevabında ve diğer cevaplarında değinilen her şeyi temizledim.

56

Kullandığınız programlama dili (0) kısa devreler ikili karşılaştırmalar (yani iptal etmemesi halinde ise SomeTesteğer FileExistsdöner false) ve (1) atama bir değer (sonucunu döndürür OpenFileatanan contentsbir argüman olarak ve daha sonra bu değer geçirilir için SomeTest), aşağıdaki gibi bir şey kullanabilirsiniz, ancak yine de bekarın =kasıtlı olduğunu belirten kodu yorumlamanız tavsiye edilir .

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

İf'in ne kadar kıvrımlı olduğuna bağlı olarak, bir bayrak değişkenine sahip olmak daha iyi olabilir (bu, başarı / başarısızlık durumlarının test edilmesini DefaultAction, bu durumda hatayı işleyen kodla ayırır )


Ben böyle yapardım.
Anthony,

13
ifBence, bir ifadeye çok fazla kod koymak oldukça iğrenç .
moteutsch

15
Ben, tam tersine, bu tür bir "eğer bir şey varsa ve bu şartı karşılıyorsa" ifadesi gibi. +1
Gorpik

Ben de yaptım! Şahsen insanların çoklu getirileri kullanma şeklinden hoşlanmıyorum, bazı mülklere uyulmuyor. Neden o IFS ters ve eğer kod yürütmesine yok edilir buluştu?
klaar

“Bir şey varsa ve bu koşulu yerine getiriyorsa” sorun değil. OTOH, "burada bir şeyler var ve teğetsel olarak ilgili bir şeyler yapar ve bu şartı yerine getirirse" kafa karıştırıcıdır. Başka bir deyişle, bir durumdaki yan etkilerden hoşlanmıyorum.
Piskvor

26

DefaultAction çağrısının tekrarlanmasından daha ciddi olan stilin kendisidir, çünkü kod dikey değildir ( dikey yazı yazmak için iyi nedenler için bu cevaba bakınız ).

Ortogonal olmayan kodun neden kötü olduğunu göstermek için, bir ağ diskinde depolanmışsa dosyayı açmamamız gereken yeni bir gereksinim ortaya çıktığında orijinal örneği düşünün. O zaman kodu şu şekilde güncelleyebiliriz:

if(FileExists(file))
{
    if(! OnNetworkDisk(file))
    {
        contents = OpenFile(file); // <-- prevents inclusion in if
        if(SomeTest(contents))
        {
            DoSomething(contents);
        }
        else
        {
            DefaultAction();
        }
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

Ancak, aynı zamanda büyük dosyaları 2 Gb üzerinden açmamamız şartı da ortaya çıkıyor. Peki, tekrar güncelledik:

if(FileExists(file))
{
    if(LessThan2Gb(file))
    {
        if(! OnNetworkDisk(file))
        {
            contents = OpenFile(file); // <-- prevents inclusion in if
            if(SomeTest(contents))
            {
                DoSomething(contents);
            }
            else
            {
                DefaultAction();
            }
        }
        else
        {
            DefaultAction();
        }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

Bu kod tarzının çok büyük bir bakım ağrısı olacağı çok açık olmalıdır.

Bu yazının ortogonal olarak yazdığı cevaplar arasında Abyx'in ikinci örneği ve Jan Hudec'in cevabı var , bu yüzden tekrar etmeyeceğim, sadece bu cevaplara iki gereksinimi eklemenin sadece

if(! LessThan2Gb(file))
    return null;

if(OnNetworkDisk(file))
    return null;

(veya goto notexists;yerine return null;), eklenen satırlardan başka hiçbir kodu etkilemiyor . Ortogonal örneğin.

Test yaparken, genel kural normal durumu değil istisnaları test etmek olmalıdır .


8
Benim için + 1. Erken dönüş, ok ucu karşıtı düzenden kaçınmaya yardımcı olur. Codinghorror.com/blog/2006/01/flattening-arrow-code.html ve lostechies.com/chrismissal/2009/05/27/… adresine bakın. Bu deseni okumadan önce, fonksiyon başına her zaman 1 giriş / çıkışa abone oldum. 15 yıl kadar önce ne öğretildiğime bağlı olarak teori. Bunun sadece kodu okumayı çok daha kolaylaştırdığını ve söylediğiniz gibi daha fazla bakım gerektirdiğini düşünüyorum.
Bay Moose

3
@ MrMoose: ok ucu karşıtı kalıptan bahsettiğiniz söz Benjol'ün açık sorusuna cevap veriyor: "Bu tür bir mantık için bir isim var mı?" Cevap olarak gönder, oyumu aldın.
outis

Bu harika bir cevap, teşekkür ederim. Ve @MrMoose: "ok ucu karşıtı düzen" muhtemelen ilk kurşuma cevap veriyor, bu yüzden evet, postala. Kabul edeceğime söz veremem, ama oyu hakediyor!
Benjol

@outis. Teşekkürler. Cevap ekledim Ok ucu karşıtı kalıp kesinlikle hlovdalın göreviyle ilgilidir ve koruyucu maddeleri etraflarında dolaşma konusunda iyi çalışır. İkinci kurşun noktasına nasıl cevap verebileceğini bilmiyorum. Bunu teşhis etmeye yetkin değilim :)
Bay Moose

4
+1 "test istisnaları değil normal durum."
Roy Tinker

25

Açıkçası:

Whatever(Arguments)
{
    if(!FileExists(file))
        goto notexists;
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(!SomeTest(contents))
        goto notexists;
    DoSomething(contents);
    return;
notexists:
    DefaultAction();
}

Kötü çözümlere bile açık olduğunuzu söylediniz, bu yüzden kötü goto sayar, değil mi?

Aslında, bağlama bağlı olarak bu çözüm, eylemi iki kez yapan kötülük veya ekstra değişken değişkenden daha az kötü olabilir. Bir işleve sarıldım, çünkü uzun işlevin ortasında kesinlikle sorun olmaz (en azından ortadaki dönüş nedeniyle). Fakat uzun süre işlev tamam değil, periyot.

İstisnalarınız olduğunda, okunması daha kolay olacaktır, özellikle de OpenFile ve DoSomething'in, koşullar yerine getirilmediği takdirde bir istisna atması durumunda, açık kontroller yapmanız gerekmez. Öte yandan, C ++ 'da Java ve C # bir istisna atmak yavaş bir işlemdir, bu nedenle performans açısından goto hala tercih edilir.


"Kötülük" ile ilgili not: C ++ SSS 6.15 , "kötülüğü" şu şekilde tanımlar:

Bu böyle bir şey demek, çoğu zaman kaçınmanız gereken bir şeydir, ancak her zaman kaçınmanız gereken bir şey değildir . Örneğin, "kötülük alternatiflerinin en kötü kötüsü" olduğunda, bu "kötülük" şeylerini kullanmanızla sonuçlanacaksınız.

Ve gotobu bağlamda geçerlidir . Yapılandırılmış akış kontrol yapıları çoğu zaman daha iyidir, ancak şartların yerine getirilmesi, yaklaşık 3 seviyeden fazla derinlemesine iç içe yerleştirilmesi, kodların kopyalanması veya uzun koşulların gotobasit bir şekilde sona ermesi gibi, kendi kötülerinden çok fazla biriktirdikleri bir duruma geldiğinizde daha az kötülük olmaktan.


11
İmleçim, bütün safhalara rağmen ... ... kabul düğmesinin üzerine geliyor. Oooohh günaha: D
Benjol

2
Evet evet! Bu kod yazmanın kesinlikle "doğru" yoludur. Kodun yapısı şimdi "Hata durumunda, hatayı işle. Normal eylem. Hata durumunda, hatayı işle. Normal eylem" şeklinde olmalıdır. Tüm "normal" kodlar sadece tek seviyeli bir girintiyle yazılırken, hata ile ilgili tüm kodlar iki girintilidir. Böylece normal VE EN ÖNEMLİ kod en belirgin görsel yeri alır ve akışı sırayla aşağıya doğru çok hızlı ve kolay bir şekilde okumak mümkündür. Elbette bu cevabı kabul et.
hlovdal

2
Ve bir başka yönü, bu şekilde yazılmış kodun dikgen olmasıdır. Örneğin iki satır "if (! FileExists (file)) \ n \ tgoto notexists;" Şimdi SADECE bu tek hata yönü (KISS) ile ilgilidir ve en önemlisi diğer hatlardan hiçbirini etkilemez . Bu cevap stackoverflow.com/a/3272062/23118 , kodu dik tutmak için birkaç iyi neden listeliyor.
Hlovdal

5
Kötü çözümlerden bahsetmek: Çözümünüzü for(;;) { if(!FileExists(file)) break; contents = OpenFile(file); if(!SomeTest(contents)) break; DoSomething(contents); return; } /* broken out */ DefaultAction();
serbest

4
@herby: Çözümünüz bundan daha kötü goto, çünkü breakhiç kimsenin kötüye kullanılmasını beklemeyeceğiniz bir şekilde kötüye kullanıyorsunuz, bu nedenle kodu okuyan insanlar ara vermenin onları nereye götürdüğünü açıkça görmekten daha fazla sorun yaşayacak. Ayrıca, sadece bir kez çalışacak, oldukça kafa karıştırıcı olacak sonsuz bir döngü kullanıyorsunuz. Ne yazık ki do { ... } while(0)tam olarak okunamıyor, çünkü sadece sonuna kadar komik bir blok olduğunu görüyorsunuz ve C diğer bloklardan (perl'in aksine) kırılmasını desteklemiyor.
Jan Hudec

12
function FileContentsExists(file) {
    return FileExists(file) ? OpenFile(file) : null;
}

...

contents = FileContentExists(file);
if(contents && SomeTest(contents))
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

ya da fazladan erkek erkeğe gidip ek bir FileExistsAndConditionMet (file) yöntemi oluşturun ...
UncleZeiv

@herby SomeTest, SomeTestdosya türünü denetlerse örneğin varolanla aynı semantiklere sahip olabilir, örneğin .gif'in gerçekten GIF dosyası olduğunu kontrol eder.
Abyx

1
Evet. Bağlı olmak. @ Benjol daha iyi bilir.
herby

3
... elbette "fazladan köpeğe git" demek istedim ... ... :)
AmcleZeiv

2
Bunu, ben de gitmiyorum (ve ekstremitelerde için mantı alıyor duyuyorum bu aşırı) ... Ben şimdi dikkate güzel okunabilir olduğunu düşünüyorum contents && f(contents). Birini kurtarmak için iki fonksiyon ?!
herby

12

Bir olasılık:

boolean handled = false;

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        handled = true;
    }
}
if (!handled)
{
    DefaultAction();
}

Tabii ki, bu kodu farklı bir şekilde biraz daha karmaşık hale getirir. Bu yüzden büyük ölçüde stil bir sorudur.

Farklı bir yaklaşım istisnalar kullanıyor olabilir, örneğin:

try
{
    contents = OpenFile(file); // throws IO exception if file not found
    DoSomething(contents); // calls SomeTest() and throws exception on failure
}
catch(Exception e)
{
    DefaultAction();
    // and the exception should be at least logged...
}

Bu daha basit görünüyor, ancak yalnızca

  • tam olarak ne tür istisnalar olacağını ve DefaultAction()her birine uyduğunu biliyoruz
  • dosya işlemenin başarılı olmasını bekliyoruz ve eksik bir dosya ya da başarısızlık SomeTest()açıkça hatalı bir durumdur, bu nedenle üzerine bir istisna atmak uygun olur.

19
Noooo ~! Bir bayrak değişkeni değil, kesinlikle yanlış bir yoldur, çünkü karmaşık, anlaşılması zor (bayrağın gerçek olduğu yer) ve yeniden kodlama zorluğuna yol açar.
Abyx

Mümkün olduğunca yerel kapsamla sınırlandırmazsanız hayır. (function () { ... })()Javascript, { flag = false; ... }C benzeri vb.
herby

İstisna mantığı için +1, senaryoya bağlı olarak en uygun çözüm bu olabilir.
Steven Jeuris

4
+1 Bu karşılıklı 'Nooooo!' komik. Bazı durumlarda durum değişkeninin ve erken dönüşün makul olduğunu düşünüyorum. Daha karmaşık rutinlerde durum değişkenine giderdim çünkü karmaşıklık eklemek yerine, gerçekten yaptığı şey mantığı açık yapmaktır. Bunda yanlış bir şey yok.
grossvogel

1
Bu çalıştığım yerde tercih ettiğimiz format. Kullanılabilir 2 ana seçenek "çoklu getiriler" ve "bayrak değişkenleri" gibi görünmektedir. Hiçbiri ortalama olarak herhangi bir gerçek avantaja sahip gibi görünmüyor, ancak her ikisi de belirli koşullara diğerlerinden daha iyi uyuyor. Tipik durumunuzla birlikte gitmek zorundasınız. Başka bir "Emacs" vs. "Vi" dini savaş. :-)
Brian Knoblauch

11

Bu daha yüksek bir soyutlama seviyesindedir:

if (WeCanDoSomething(file))
{
   DoSomething(contents);
}
else
{
   DefaultAction();
} 

Ve bu da detayları dolduruyor.

boolean WeCanDoSomething(file)
{
    if FileExists(file)
    {
        contents = OpenFile(file);
        return (SomeTest(contents));
    }
    else
    {
        return FALSE;
    }
}

11

İşlevler bir şey yapmalı. İyi yapmalılar. Sadece yapmalılar.
- Robert Martin Temiz Kodda

Bazı insanlar bu yaklaşımı biraz aşırı buluyor, ama aynı zamanda çok temiz. Python'da göstermeme izin ver:

def processFile(self):
    if self.fileMeetsTest():
        self.doSomething()
    else:
        self.defaultAction()

def fileMeetsTest(self):
    return os.path.exists(self.path) and self.contentsTest()

def contentsTest(self):
    with open(self.path) as file:
        line = file.readline()
        return self.firstLineTest(line)

Fonksiyonların bir şeyi yapması gerektiğini söylediğinde, bir şey ifade ediyor. processFile()Bir testin sonucuna dayanarak bir eylem seçer ve hepsi bu. fileMeetsTest()Testin tüm koşullarını birleştirir ve hepsi bu. contentsTest()ilk satırı transfer eder firstLineTest()ve hepsi bu.

Bir çok fonksiyona benziyor, fakat pratik olarak İngilizce'yi hemen hemen okuyor:

Dosyayı işlemek için teste uyup uymadığını kontrol edin. Eğer öyleyse, o zaman bir şeyler yap. Aksi takdirde, varsayılan işlemi yapın. Dosya varsa ve içerik testini geçerse testi karşılar. İçeriği test etmek için dosyayı açın ve ilk satırı test edin. İlk hattın testi ...

Kabul ediyorum, bu biraz endişe vericidir, ancak bir bakıcının ayrıntıları önemsemediği takdirde, sadece 4 satırlık koddan sonra okumayı durdurabileceğini processFile()ve fonksiyonun ne yaptığı hakkında hala yüksek düzeyde bilgiye sahip olacağını unutmayın.


5
+1 Bu iyi bir tavsiye, ancak “bir şeyi” oluşturan şey mevcut soyutlama katmanına bağlıdır. processFile () "bir şey" dir, ancak iki şey vardır: fileMeetsTest () ve doSomething () veya defaultAction (). Korkarım ki, "bir şey" yönü, önceleri anlamayan yeni başlayanların kafasını karıştırmasına neden olabilir .
Caleb

1
Bu iyi bir amaç ... Bu konuda söyleyeceklerimin hepsi bu kadar ... ;-)
Brian Knoblauch

1
Öyle değişkenleri dolaylı olarak argüman olarak geçirmekten hoşlanmıyorum. "İşe yaramaz" örnek değişkenleriyle doluyorsunuz ve durumunuzu yükseltip değişmezleri kırmanın birçok yolu var.
hugomg

@Caleb, ProcessFile () gerçekten bir şey yapıyor. Karl görevinde söylediği gibi, hangi eylemin gerçekleştirileceğine karar vermek için bir test kullanıyor ve eylem olanaklarının fiili uygulanmasını diğer yöntemlere erteliyor. Daha birçok alternatif eylem ekleyecekseniz, yöntemin tek amaçlı ölçütleri, acil yöntemde hiçbir mantık iç içe geçmediği sürece yerine getirilecektir.
S.Robins

6

Buna ne denirse gelsin , kodunuz daha fazla gereksinimi karşılayacak şekilde büyüdükçe ( https://softwareengineering.stackexchange.com/a/122625/33922 adresinde verilen cevapta gösterildiği gibi) daha fazla gereksinimi karşılamak için büyüdükçe ok ucu karşıtı düzende kolayca gelişebilir ve sonra bir ok gibi iç içe koşullu ifadeler ile büyük kod bölümleri olması tuzağına düşüyor.

Bağlantılara bakınız;

http://codinghorror.com/blog/2006/01/flattening-arrow-code.html

http://lostechies.com/chrismissal/2009/05/27/anti-patterns-and-worst-practices-the-arrowhead-anti-pattern/

Google’da bulunabilecek bu ve diğer anti örüntüler hakkında daha birçok şey var.

Jeff'in blogunda bu konuda sağladığı bazı harika ipuçları;

1) Koşulları koruyucu maddeler ile değiştirin.

2) Koşullu blokları ayrı fonksiyonlara ayırın.

3) Negatif kontrolleri pozitif kontrollere dönüştürün

4) Her zaman fırsatçı olarak işlevden en kısa sürede geri dönün.

Jeff'in blogunda Steve McConnells'in erken iadelerle ilgili önerileriyle ilgili bazı yorumlara bakın;

"Okunabilirliği arttırdığında bir geri dönüş kullanın: Bazı rutinlerde, cevabı öğrendikten sonra, onu hemen arayan rutine geri döndürmek istersiniz. bir hata algıladı, hemen geri dönmemek daha fazla kod yazmanız gerektiği anlamına geliyor. "

...

“Her rutinde geri dönüş sayısını en aza indirin: En alttan okuyarak, bir yere bir yere geri dönme olasılığının farkında olmadığınızda, bir rutini anlamak daha zordur. okunabilirlik. "

15 yıl veya daha uzun bir süre önce öğretildiklerim nedeniyle her bir fonksiyon teorisi için 1 giriş / çıkışa her zaman abone oldum. Bunun sadece kodu okumayı çok daha kolay hale getirdiğini ve daha fazla bahsettiğinizden bahsettiğiniz gibi hissediyorum


6

Bu, DRY'ye (kurallara aykırı) ve geri dönüşsüz ve geri dönüşsüz kurallara uyuyor, bence ölçeklenebilir ve okunabilir:

success = FileExists(file);
if (success)
{
    contents = OpenFile(file);
    success = SomeTest(contents);
}
if (success)
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

1
Standartlara uymak mutlaka iyi bir kodla eşit değildir. Şu anda bu kod snippet'inde kararsızım.
Brian Knoblauch

bu sadece 2 defaultAction () işlevinin yerini alır; koşullarla aynı 2 ise imo çok daha kötü olan bir flag değişkeni ekler.
Ryathal

3
Bunun gibi bir yapı kullanmanın faydası, test sayısı arttıkça kodun ifdiğer ifs içerisine daha fazla yuva yapmaya başlamamasıdır . Ayrıca, başarısız olan durumu ( DefaultAction()) işlemek için kod yalnızca bir yerdedir ve hata ayıklama amacıyla kod yardımcı işlevlerin etrafına atlamaz ve successdeğişkenin değiştiği satırlara kesme noktaları eklemek hızlı bir şekilde hangi testlerin geçtiğini (tetiklenenin üstünde gösterebilir) kesme noktası) ve hangilerinin test edilmediği (aşağıda).
frozenkoi

1
Yeeaah, biraz bundan hoşlanıyor olmam, ama ben adlandırmak düşünüyorum successiçin ok_so_far:)
Benjol

Bu benim yaptığım şeye çok benziyor (1) her şey yolunda giderken işlem çok doğrusal ve (2) aksi takdirde ok anti-paternli olacaksınız. Bununla birlikte, bir sonraki adımın önkoşulları hakkında düşünürseniz (ki daha önceki bir adımın başarısız olup olmadığını sormaktan biraz farklı) genellikle kolay olan ekstra bir değişken eklemekten kaçının. Dosya varsa, dosyayı açın. Dosya açıksa, içeriği okuyun. İçeriğim varsa, onları işleyin, aksi takdirde varsayılan işlemi yapın.
Adrian McCarthy,

3

Bunu ayrı bir yönteme ayıklarım ve sonra:

if(!FileExists(file))
{
    DefaultAction();
    return;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}

DoSomething(contents);

ayrıca izin verir

if(!FileExists(file))
{
    DefaultAction();
    return Result.FileNotFound;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return Result.TestFailed;
}

DoSomething(contents);
return Result.Success;            

o zaman muhtemelen DefaultActionaramaları kaldırabilir DefaultActionve arayanı yürütmeyi bırakabilirsiniz :

Result OurMethod(file)
{
    if(!FileExists(file))
    {
        return Result.FileNotFound;
    }

    contents = OpenFile(file);
    if(!SomeTest(contents))
    {
        return Result.TestFailed;
    }

    DoSomething(contents);
    return Result.Success;            
}

void Caller()
{
    // something, something...

    var result = OurMethod(file);
    // if (result == Result.FileNotFound || result == Result.TestFailed), or just
    if (result != Result.Success)        
    {
        DefaultAction();
    }
}

Jeanne Pindar'ın yaklaşımını da seviyorum .


3

Bu özel durum için, cevap yeterince kolaydır ...

FileExistsVe arasında bir yarış koşulu var OpenFile: dosya kaldırılırsa ne olur?

Bu özel durumla başa çıkmanın tek mantıklı yolu atlamaktır FileExists:

contents = OpenFile(file);
if (!contents) // open failed
    DefaultAction();
else (SomeTest(contents))
    DoSomething(contents);

Bu düzgünce bu sorunu çözer ve kodu daha temiz hale getirir.

Genel olarak: Sorunu yeniden düşünmeye çalışın ve sorunu tamamen önleyen başka bir çözüm geliştirin.


2

Çok fazla başka görmek beğenmezseniz başka olasılık kullanımını bırakmaktır başka tamamen ve ekstra getiri açıklamada atmak. İki eylemden daha fazlası olup olmadığına karar vermek için daha karmaşık bir mantık gerektirmedikçe, Else biraz gereksizdir.

Böylece örneğiniz olabilir:

void DoABunchOfStuff()
{
    if(FileExists(file))
    {
        DoSomethingWithFileContent(file);
        return;
    }

    DefaultAction();
}

void DoSomethingWithFileContent(file)
{        
    var contents = GetFileContents(file)

    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }

    DefaultAction();
}

AReturnType GetFileContents(file)
{
    return OpenFile(file);
}

Şahsen , mantığın nasıl çalışması gerektiğini açıkça ifade ettiği için else deyimini kullanmak sorun değil ve bu nedenle kodunuzun okunabilirliğini arttırıyor. Ancak bazı kod güzelleştirme araçları, iç içe yerleştirme mantığından vazgeçmek için tek bir if ifadesini basitleştirmeyi tercih eder .


2

Örnek kodda gösterilen durum genellikle tek bir ififadeye indirgenebilir . Birçok sistemde, dosya açma işlevi, dosya zaten mevcut değilse geçersiz bir değer döndürür. Bazen bu varsayılan davranıştır; Diğer zamanlarda, bir argüman ile belirtilmelidir. Bu, FileExiststestin düşebileceği anlamına gelir; bu, ayrıca varlık testi ile dosya açılması arasındaki dosya silme işleminden kaynaklanan yarış koşullarına da yardımcı olabilir.

file = OpenFile(path);
if(isValidFileHandle(file) && SomeTest(file)) {
    DoSomething(file);
} else {
    DefaultAction();
}

Bu tamamen soyutlama düzeyinde karıştırma sorununu doğrudan ele almaz, çünkü çoklu, zincirlenemeyen testler konusunu tamamen yok eder, ancak dosya mevcudiyeti testi ile uzaklaşmak soyutlama seviyelerini ayırmakla uyumlu değildir. Geçersiz dosya tanıtıcılarının "false" değerine eşdeğer olduğunu ve kapsam dışında olduklarında dosya tanıtıcılarının yakın olduğunu varsayarak:

OpenFileIfSomething(path:String) : FileHandle {
    file = OpenFile(path);
    if (file && SomeTest(file)) {
        return file;
    }
    return null;
}

...

if ((file = OpenFileIfSomething(path))) {
    DoSomething(file);
} else {
    DefaultAction();
}

2

Ancak, frozenkoi ile aynı fikirdeyim, yine de, C # için TryParse yöntemlerinin sözdizimini izlemenin yardımcı olacağını düşündüm.

if(FileExists(file) && TryOpenFile(file, out contents))
    DoSomething(contents);
else
    DefaultAction();
bool TryOpenFile(object file, out object contents)
{
    try{
        contents = OpenFile(file);
    }
    catch{
        //something bad happened, computer probably exploded
        return false;
    }
    return true;
}

1

Kodunuz çirkin çünkü tek bir fonksiyonda çok fazla şey yapıyorsunuz. Ya dosyayı işlemek ya da varsayılan eylemde bulunmak istiyorsanız, şunu söyleyerek başlayın:

if (!ProcessFile(file)) { 
  DefaultAction(); 
}

Perl ve Ruby programcıları yazmak processFile(file) || defaultAction()

Şimdi git ProcessFile yaz:

if (FileExists(file)) { 
  contents = OpenFile(file);
  if (SomeTest(contents)) {
    processContents(contents);
    return true;
  }
}
return false;

1

Elbette şu ana kadar böyle senaryolarda gidebilirsiniz, ancak işte size bir yol:

interface File<T> {
    function isOK():Bool;
    function getData():T;
}

var appleFile:File<Apple> = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

Ek filtreler isteyebilirsiniz. O zaman şunu yap:

var appleFile = appleStorage.get(fileURI, isEdible);
//isEdible is of type Apple->Bool and will be used internally to answer to the isOK call
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

Her ne kadar bu da bir anlam ifade etse de:

function eat(apple:Apple) {
     if (isEdible(apple)) 
         digest(apple);
     else
         die();
}
var appleFile = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(appleFile.getData());
else
    cry();

Hangisi en iyisi? Bu, karşılaştığınız gerçek dünya problemine bağlıdır .
Ama elinden alınması gereken şey: kompozisyon ve polimorfizm ile çok şey yapabilirsiniz.


1

Bariz olan ne yanlış

if(!FileExists(file)) {
    DefaultAction();
    return;
}
contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}        
DoSomething(contents);

Bana oldukça standart görünüyor? Çok sayıda küçük şeyin olması gereken bu büyük prosedür için, hiçbirinin başarısızlığı, ikincisini engelleyecekti. İstisnalar, eğer bir seçenekse, biraz daha temiz.


0

Bunun eski bir soru olduğunun farkındayım, ancak daha önce söylenmemiş bir kalıp fark ettim; daha sonra, çağırmak istediğiniz yöntemi / yöntemleri belirlemek için bir değişken ayarlamak (if ... else ... dışında).

Bu, kodun çalışmasını kolaylaştırmak için bakılması gereken başka bir açıdır. Ayrıca, çağrılacak başka bir yöntemi ne zaman eklemek istediğinizi veya belirli durumlarda aranması gereken uygun yöntemi değiştirmenizi de sağlar.

Yöntemin tüm sözlerini değiştirmek zorunda kalmak yerine (ve belki bazı senaryolar eksik olabilir), hepsi if ... else ... bloğunun sonunda listelenir ve okunması ve değiştirilmesi daha kolaydır. Örneğin, birkaç yöntem çağrılabildiğinde bunu kullanma eğilimindeyim, ancak iç içe geçmişse ... else ... birkaç eşleşmede bir yöntem çağrılabilir.

Durumu tanımlayan bir değişken ayarlarsanız, çok fazla iç içe geçmiş seçeneğiniz olabilir ve bir şey yapılacak (veya yapılmayacak) olduğunda durumu güncelleyebilirsiniz.

Bu, 'DoSomething' olup olmadığını kontrol ettiğimiz soruda sorulan örnekte olduğu gibi kullanılabilir ve eğer değilse, varsayılan eylemi gerçekleştirir. Veya çağırmak isteyebileceğiniz her yöntem için durumunuz olabilir, uygun olduğunda ayarlayın, ardından uygulanabilir yöntemi arayın.

İç içe geçmiş if ... else ... deyimlerinin sonunda, durumu kontrol edip buna göre davranırsınız. Bu, uygulanması gereken tüm yerler yerine yalnızca bir yöntemden bahsetmeniz gerektiği anlamına gelir.

bool ActionDone = false;

if (Method_1(object_A)) // Test 1
{
    result_A = Method_2(object_A); // Result 1

    if (Method_3(result_A)) // Test 2
    {
        Method_4(result_A); // Action 1
        ActionDone = true;
    }
}

if (!ActionDone)
{
    Method_5(); // Default Action
}

0

İç içe geçmiş IF'yi azaltmak için:

1 / erken dönüş;

2 / bileşik ifade (kısa devre duyarlı)

Öyleyse, örneğiniz şöyle yeniden yansıtılabilir:

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
    return;
}
DefaultAction();

0

Ben de kullandığım "return" ile birçok örnek gördüm ama bazen yeni işlevler oluşturmaktan kaçınmak ve bunun yerine bir döngü kullanmak istiyorum:

while (1) {
    if (FileExists(file)) {
        contents = OpenFile(file);
        if (SomeTest(contents)) {
           DoSomething(contents);
           break;
        } 
    }
    DefaultAction();
    break;
}

Daha az satır yazmak isterseniz veya benim gibi sonsuz döngülerden nefret ediyorsanız, döngü türünü "do ... while (0)" olarak değiştirebilir ve son "kırılmayı" önleyebilirsiniz.


0

Peki ya bu çözüm:

content = NULL; //I presume OpenFile returns a pointer 
if(FileExists(file))
    contents = OpenFile(file);
if(content != NULL && SomeTest(contents))
    DoSomething(contents);
else
    DefaultAction();

OpenFile'nin bir işaretçi döndürdüğünü varsaydım, ancak bu, döndürülemeyen bir varsayılan değer (hata kodları veya benzeri bir şey) belirterek değer türü dönüşüyle ​​de çalışabilir.

Tabii ki NULL imlecindeki SomeTest yöntemi ile bazı olası işlemler beklemiyorum (ama asla bilemezsin), bu yüzden bu ayrıca SomeTest (içerik) çağrısı için NULL imleci için fazladan bir kontrol olarak görülebilir.


0

Açıkçası, en zarif ve özlü çözüm bir önişlemci makrosu kullanmaktır.

#define DOUBLE_ELSE(CODE) else { CODE } } else { CODE }

Bunun gibi güzel bir kod yazmanıza izin verir:

if(FileExists(file))
{
    contents = OpenFile(file);
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    DOUBLE_ELSE(DefaultAction();)

Bu tekniği sık kullanırsanız otomatik biçimlendirmeye güvenmek zor olabilir ve bazı IDE'lerin hatalı bir şekilde ne tür bir hata oluştuğunu düşündüğü konusunda size biraz bağırıyor olabilir. Ve söylendiği gibi, her şey bir tradeoff, ama sanırım tekrarlanan kodun kötülüklerini önlemek için ödemek için kötü bir fiyat değil.


Bazı insanlar için, ve bazı dillerde, önişlemci makroları olan kötü kod :)
Benjol

@ Benjol Şeytani önerilere açık olduğunuzu söylediniz, hayır? ;)
Peter Olson

evet, kesinlikle, sadece "kötülüklerden kaçın"

4
Bu çok korkunç, sadece onu değiştirmek zorunda kaldım: D
back2dos

Shirley, ciddi değilsin !!!!!!
Jim, Teksas

-1

Siz meraktan sorduğunuzdan ve sorunuz belirli bir dille etiketlenmemiş olduğundan (zorunlu dilleri aklınızda tutturmuş olmanıza rağmen), tembel değerlendirmeyi destekleyen dillerin tamamen farklı bir yaklaşıma izin verdiğini eklemeye değer olabilir. Bu dillerde, ifadeler yalnızca gerektiğinde değerlendirilir, bu nedenle "değişkenleri" tanımlayabilir ve bunları yalnızca mantıklı geldiğinde kullanabilirsiniz. Örneğin, tembel let/ inyapıları olan kurgusal bir dilde akış kontrolünü ve yazmayı unutursunuz:

let
  contents = ReadFile(file)
in
  if FileExists(file) && SomeTest(contents) 
    DoSomething(contents)
  else 
    DefaultAction()
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.