For-if antipattern


9

Bu blog yazısında for-if anti-pattern hakkında okuyordum ve bunun neden bir anti-pattern olduğunu anladığımdan emin değilim.

foreach (string filename in Directory.GetFiles("."))
{
    if (filename.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))
    {
        return new StreamReader(filename);
    }
}

Soru 1:

Çünkü return new StreamReader(filename);içinde for loopmi? ya da forbu durumda bir döngüye ihtiyacınız olmadığı gerçeği ?

Blogun yazarının bunun daha az çılgın versiyonuna işaret ettiği gibi:

if (File.Exists("desktop.ini"))
{
    return new StreamReader("desktop.ini");
} 

her ikisi de bir yarış koşulundan muzdarip çünkü dosya oluşturulmadan önce silinirse StreamReader, bir File­Not­Found­Exception.

Soru 2:

İkinci örneği düzeltmek için, if ifadesi olmadan yeniden yazar ve bunun yerine StreamReadertry-catch bloğu ile kuşatırsınız ve eğer atarsa uygun File­Not­Found­Exceptionşekilde catchblokta işler misiniz ?



1
@amon - Teşekkürler, ancak başka bir noktaya değinmiyorum, sadece makalenin ne dediğini ve nasıl düzelteceğimi anlamaya çalışıyorum.

3
Bu kadar düşünmem. Blog posteri, hiç kimsenin yazamadığı, ona bir isim verdiği ve ona anti-desen adını verdiği aptalca bir kod oluşturuyor. İşte bir tane daha: "Buna anlamsız atama kalıbı diyorum: x = x; Bunun her zaman yapıldığını görüyorum. Çok aptal. Sanırım bunun hakkında bir blog yazacağım."
Martin Maat

4
To fix the second example, would you re-write it without the if statement, and instead surround the StreamReader with a try-catch block, and if it throws a File­Not­Found­Exception you handle it in the catch block accordingly?- Evet, aynen böyle yapardım. Yarış koşulunun çözülmesi, bazı "kontrol akışı olarak istisnalar" kavramından daha önemlidir ve onu zarif ve temiz bir şekilde çözer.
Robert Harvey

1
Dosyalardan hiçbiri belirtilen ölçütleri karşılamıyorsa ne yapar? Geri dönüyor nullmu? Örneğinizin koduna benzeyen kodu temizlemek için genellikle LINQ kullanabilirsiniz: İki LINQ çağrısı arasında operatöre return Directory.GetFiles(".").FirstOrDefault(fileName => fileName.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))?.Select(fileName => new StreamReader(filename)); dikkat edin ?.. Ayrıca insanlar böyle nesne yaratmanın LINQ'nun en uygun kullanımı olmadığını iddia edebilirler, ancak bence burada sorun yok. Bu, sorunuzun bir cevabı değildir, ancak bir kısmını araştırır.
Panzercrisis

Yanıtlar:


7

Bu, aşağıdaki gibi bir antipatterndir:

loop over a set of values
   if current value meets a condition
       do something with value
   end
end

ve ile değiştirilebilir

do something with value

Bunun klasik bir örneği aşağıdaki gibidir:

for (var i=0; i < 5; i++)
{
    switch (i)
        case 1:
            doSomethingWith(1);
            break;
        case 2:
            doSomethingWith(2);
            break;
        case 3:
            doSomethingWith(4);
            break;
        case 4:
            doSomethingWith(4);
            break;
    }
}

Aşağıdakiler işe yaradığında:

doSomethingWith(1);
doSomethingWith(2);
doSomethingWith(3);
doSomethingWith(4);

Kendinizi bir döngü ve bir ifveya gerçekleştirirken bulursanız switch, durup ne yaptığınızı düşünün. Eğer işleri aşırı karmaşık ve tüm döngü ve test sadece basit bir "sadece yap" hattı ile değiştirilebilir. Bazen de, bu döngüyü yapmanız gerektiğini (örneğin, tek bir koşulla birden fazla öğe eşleşebilir) bulabilirsiniz, bu durumda desen iyi olur.

Bu yüzden bir anti-desen: "döngü ve test" desenini alıp kötüye kullanıyor.

İkinci sorunuzla ilgili olarak: evet. "Deneme yap" kalıbı, kodunuzun tüm cihazdaki test edilen öğenin durumunu değiştirebilecek tek iş parçacığı olmadığı her durumda "test et ve sonra yap" deseninden daha sağlamdır.

Bu kodla ilgili sorun:

if (File.Exists("desktop.ini"))
{
    return new StreamReader("desktop.ini");
}

arasındaki zaman içinde olmasıdır File.Existsve StreamReaderbu dosyayı açmaya çalışırken, başka bir iş parçacığı veya işlem dosyası silebilir. Böylece bir istisna alacaksınız. Bu nedenle, bu istisnanın aşağıdaki gibi bir şeyle korunması gerekir:

try
{
    return new StreamReader("desktop.ini");
}
catch (File­Not­Found­Exception)
{
    return null; // or whatever
}

@Flater iyi bir noktaya değiniyor mu? Bu bir antipattern mi? Kontrol akışı olarak istisnalar kullanıyor muyuz?

Kod şöyle bir şey okursa:

try
{
    if (!File.Exists("desktop.ini")
    {
        throw new IniFileMissingException();
        return new StreamReader("desktop.ini");
    }
}
catch (IniFileMissingException)
{
    return null;
}

o zaman gerçekten istisnaları yüceltilmiş bir goto olarak kullanırdım ve gerçekten de bir anti-desen olurdu. Ancak bu durumda, sadece yeni bir akış oluşturma istenmeyen davranışı üzerinde çalışıyoruz, bu yüzden bu anti-modelin bir örneği değil. Ancak bu anti-desen etrafında çalışmanın bir örneğidir.

Tabii ki, gerçekten istediğimiz, akışı oluşturmanın daha zarif bir yoludur. Gibi bir şey:

return TryCreateStream("desktop.ini", out var stream) ? stream : null;

Ve try catchkendinizi bu kodu çok kullanarak bulursanız bu kodu böyle bir yardımcı yöntemle sarmanızı tavsiye ederim .


1
@Flater, bunun ideal olmadığını kabul etme eğilimindeyim (bunun cevabın bahsettiği anti-modelin bir örneğini tartıştığım halde). Kod, niyetini göstermeli, mekaniği değil. Ben Scala'nın gibi bir şey lehine olurdu Yani Try, örneğin return Try(() => new StreamReader("desktop.ini")).OrElse(null);, ama biz aksak sürümü ile işin zorunda C #, (bir 3. parti kitaplığı kullanmadan) o yapıyı desteklemez.
David Arno

1
@Flater, istisnaların istisnai olması gerektiğine tamamen katılıyorum. Ama nadiren öyleler. Örneğin, mevcut olmayan bir dosya neredeyse istisnai değildir, ancak dosyayı bulamazsa bir dosya File.Openatar. Zaten istisnai bir istisnamız olduğu için, bunu kontrol akışının bir parçası olarak yakalamak bir zorunluluktur (biri uygulamanın çökmesine izin vermek istemiyorsa).
David Arno

1
@Daha: ikinci kod örneği dosyayı açmanın doğru yoludur. Başka her şey bir yarış koşulu getirir. Dosyaların varlığını kontrol etseniz bile, bu kontrol arasında ve gerçekten açtığınızda, bir şey bu dosyayı sizin için kullanılamaz hale getirebilir ve Open () çağrısı patlar. Bu yüzden zaten istisna için hazır olmalısınız. Yani sadece kontrol rahatsız etmeyebilir ve sadece deneyin ve açın. İstisnalar, işleri yapmamıza yardımcı olan araçlardır ve bunların kullanımı dini dogma olarak görülmemelidir.
whatsisname

1
@Flater: Dosya işlemlerini denemeyi / yakalamayı önlemenin herhangi bir yolu yarış koşullarını tanıtır. Dosyaya yazmak istediğiniz dosya sistemi, File.Create'ı çağırmadan hemen önce kullanılamayabilir, bu da tüm denetimleri geçersiz kılar.
whatsisname

1
@Flater " Her yöntem çağrısının bir dönüş türüne ihtiyaç duyduğunu etkili bir şekilde savunuyorsunuz ... istisnaları etkili bir şekilde farklı bir şekilde yeniden keşfetmeye çalışıyor ". Doğru. Yine de yeniden icat benim değil. Yıllarca fonksiyonel dillerde kullanılmaktadır. Genellikle sendika türlerini kullanarak "kontrol akışı sorunu olarak istisnalar" dan kaçınırlar (kafa karıştırıcı bir şekilde bir nefeste bir anti-patern olduğunu iddia eder ve daha sonra bir sonraki lehine tartışırsınız), örneğin bu durumda bir Maybe<Stream>tip kullanırız nothingdosya mevcut değilse döndürür ve varsa akış sağlar.
David Arno

1

Soru 1: (bu bir döngü önleyici mi?)

Evet, çünkü sorgulanabilir bir alt sistemde depolanan öğeler için kendi aramanızı yapmanız gerekmez.

Özette, dosya sistemi, bir veritabanı gibi, sorgulara yanıt verebilir. Sorunulabilir bir alt sistemle etkileşime girerken, alt sistemin dışında eşleştirmeyi gerçekleştirmek veya alt sistemin yerel sorgu yeteneklerini kullanmak için böyle bir alt sistemin içeriğini bize numaralandırmasını isteyip istemediğimiz konusunda temel bir seçeneğimiz vardır.

Bir an için dosya sistemindeki bir dizindeki dosya yerine veritabanında bir kayıt aradığınızı varsayalım. Görmeyi tercih eder misin

SELECT * FROM SomeTable;

ve daha sonra ID = 100'ü arayan veya imleçlenebilir alt sistemin tam olarak aradığınızı bulmak için elinden geleni yapmasına izin veren döndürülen imleç üzerinde döngü (örneğin C # 'da)?

SELECT * FROM SomeTable WHERE ID = 100;

Çoğumuzun, alt sistemin tam olarak ilgilenilen sorguyu gerçekleştirmesine izin vermeyi seçeceğini düşünmeliyim. Alternatif, alt sistemle potansiyel olarak çok sayıda gidiş-dönüş, verimsiz eşitlik testi ve hem veritabanlarının hem de dosya sistemlerinin bize sağladığı herhangi bir dizin veya diğer arama hızlandırıcılarının kullanılmasını sürdürmeyi içerir.


Soru 2: İkinci örneği düzeltmek için, if ifadesi olmadan yeniden yazar ve bunun yerine StreamReader'ı bir try-catch bloğu ile çevreler misiniz ve bir FileNotFoundException özel durumu atarsa ​​buna göre catch bloğunda işler misiniz?

Evet, çünkü belirli bir API bu şekilde çalışır - bu bir kütüphane işlevi olduğundan gerçekten bizim seçimimiz değildir. Eğer kontrol etmeden önce, çağrıdan önce ek bir değer sunulmaz: yine de try / catch kullanmalıyız, çünkü (1) FileNotFound dışında başka hatalar olabilir ve (2) yarış durumu.


0

Soru 1:

Yeni StreamReader (dosya adı) döndürmesi nedeniyle mi; for döngüsü içinde? ya da bu durumda bir for döngüsüne ihtiyacınız olmadığı gerçeği?

Akış okuyucunun bununla hiçbir ilgisi yoktur. Anti-desen nedeniyle arasındaki niyet net bir çatışma çıkar foreachve if:

Amacı nedir foreach?

Cevabınızın şöyle olacağını düşünüyorum: "Tekrar tekrar belirli bir kod parçası yürütmek istiyorum"

Kaç dosyayı işlemeyi bekliyorsunuz?

Yalnızca belirli bir klasörde (uzantısı dahil) Belirli bir dosya adı olabilir, çünkü bu kod bulmak için tasarlanmıştır kanıtlıyor biri uygulanabilir dosyası.

Bu, hemen bir değer döndürdüğünüz ile de doğrulanır. Var olsa bile, aslında ikinci bir maç umurunda değil.


Bunun bir anti-desen olmadığı bir durum var.

  • Alt dizinlere ( Directory.GetFiles(".", SearchOption.AllDirectories)) de bakarsanız , aynı dosya adına (uzantı dahil) sahip birden fazla dosya bulmak mümkündür
  • Kısmi dosya adı eşleşmeleri (örneğin, adı ile başlayan "Test_"her "*.zip"dosya veya her dosya) arıyorsanız .

Bu iki durumun da birden fazla eşleşmeyi gerçekten işlemenizi gerektireceğini ve bu nedenle hemen bir değer döndürmeyeceğini unutmayın.


Soru 2:

İkinci örneği düzeltmek için, if ifadesi olmadan yeniden yazar ve bunun yerine StreamReader'ı bir try-catch bloğu ile kuşatırsınız ve bir FileNotFoundException özel durumu atarsa ​​buna göre catch bloğunda işlersiniz?

İstisnalar pahalıdır. Asla uygun akış mantığı yerine kullanılmamalıdır. İstisnalar, adlarının istisnai durumlara işaret ettiği gibi .

Bu nedenle if,.

SoftwareEngineering.SE'deki bu cevaba göre :

Genel olarak, kontrol akışı için istisnaların kullanılması, dikkate değer bir duruma ve dile özgü öksürük istisnalarının öksürüğü olan bir anti-kalıptır.

Genel olarak neden bir anti-desen olduğuna dair hızlı bir özet olarak:

  • İstisnalar, özünde, karmaşık GOTO ifadeleridir
  • Bu nedenle istisnalar dışında programlama, kodu okumak ve anlamak daha zor olur
  • Çoğu dilde, istisnasız olarak sorunlarınızı çözmek için tasarlanmış mevcut kontrol yapıları vardır
  • Verimlilik argümanları, kontrol akışı için istisnaların kullanılmadığı varsayımıyla optimize edilen modern derleyiciler için tartışmalı olma eğilimindedir.

Daha ayrıntılı bilgi için Ward'ın wiki'sindeki tartışmayı okuyun .

Bunu bir try / catch'e sarmanız gerekip gerekmediği, büyük ölçüde durumunuza bağlıdır:

  • Bir yarış koşuluyla karşılaşma olasılığınız nedir?
  • Aslında bu durumla başa çıkabiliyor musunuz, yoksa bu sorunun kullanıcıyla nasıl başa çıkacağını mı düşünüyorsunuz çünkü nasıl başa çıkacağınızı bilmiyorsunuz.

Hiçbir şey "daima kullan" meselesi değildir. Demek istediğimi kanıtlamak için:

Ayrı çalışmalar, bir kask, koruyucu gözlük ve kurşun geçirmez yelek giydiğinizde incinme olasılığınızın daha düşük olduğunu kanıtlamıştır.

Öyleyse neden hepimiz bu güvenlik ekipmanını takmıyoruz?

Basit cevap, onları giymenin dezavantajları olmasıdır:

  • Paraya mal olur
  • Hareketinizi daha hantal yapar
  • Giymek oldukça sıcak olabilir.

Şimdi bir yere gidiyoruz: profesyoneller ve eksiler var . Başka bir deyişle, bu ekipmanı sadece profesyonellerin eksilerini aştığı durumlarda takmak mantıklıdır .

  • İnşaat işçilerinin işleri sırasında yaralanmaları çok daha muhtemeldir. Emniyet kaskından faydalanırlar.
  • Ofis çalışanları ise, yaralanma şansı çok daha düşüktür. Güvenlik kaskları buna değmez.
  • Bir SWAT ekibi üyesinin, bir ofis çalışanına kıyasla vurulma olasılığı daha yüksektir.

Aramayı dene / yakala? Bu, bunu yapmanın yararlarının, uygulamanın maliyetinden daha ağır basmayacağına bağlıdır.

Diğerlerinin, onu sarmanın yalnızca birkaç tuşa basıldığını iddia edebileceğini unutmayın, bu yüzden açıkça yapılması gerekir. Ancak tüm tartışma bu değildir:

  • İstisnayı yakaladıktan sonra ne yapacağınıza karar vermeniz gerekir.
  • Kod tabanının her yerinde farklı dosyalara çok sayıda farklı çağrı varsa, bir dene / yakala seçeneğine karar vermeniz genellikle bu durumların tümünü sarmanız gerektiği anlamına gelir . Bunun uygulanması için gereken çaba miktarı üzerinde önemli bir etkisi olabilir.
  • O bunu yeterince mümkündür kasten istiyoruz değil bir istisna işlemek.
    • Başvurunuz de durum işlenecek olacağı Not bazı noktada, ama bu durum büyüdü hemen sonra mutlaka değildir.

Yani, seçim senin. Bunu yapmanın bir yararı var mı? Uygulamayı geliştirmek için daha fazla çaba harcamanın ötesinde uygulamayı geliştirdiğini düşünüyor musunuz?

Güncelleme - Yazdığım bir yorumdan diğer cevaba kadar, bunun sizin için de önemli olduğunu düşünüyorum:

Çevrelere çok bağlı.

  • Akış okuyucunun açılmasından önce if(!File.Exists) File.Create(), akış okuyucuyu açarken dosyanın olmaması gerçekten istisnai bir durumdur .
  • Dosya adı mevcut dosyalar listesinden seçildiyse, ani yokluğu yine istisnadır .
  • Henüz dizine karşı henüz test etmediğiniz bir dizeyle çalışıyorsanız; dosyanın olmaması mükemmel bir mantıksal sonuçtur ve bu nedenle istisnai değildir .
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.