Bir koşulu fazladan kontrol etmek kötü bir stil mi?


10

Sık sık kendimi belirli bir durumu tekrar tekrar kontrol bulduğum kodumdaki konumlara ulaşıyorum.

Size küçük bir örnek vermek istiyorum: "a" ile başlayan satırları, "b" ile başlayan satırları ve diğer satırları içeren bir metin dosyası olduğunu varsayalım ve aslında sadece ilk iki tür satırla çalışmak istiyorum. Kodum (python kullanarak, ancak sözde kod olarak okuyun) böyle bir şey olurdu:

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a")):
        # do stuff
    elif (line.startsWith("b")):
        # magic
    else:
        # this else is redundant, I already made sure there is no else-case
        # by using clear_lines()
# ...

Burada sadece bu durumu kontrol etmeyeceğim, aynı zamanda başka işlevlerde de çalışabileceğimi hayal edebilirsiniz.

Gürültü mü düşünüyorsunuz yoksa koduma bir değer katıyor mu?


5
Temel olarak savunmacı bir şekilde kod verip vermemenizle ilgilidir. Bu kodun çok fazla düzenlendiğini görüyor musunuz? Bunun son derece güvenilir olması gereken bir sistemin parçası olması muhtemel mi? assert()Orada test etmeye yardımcı olmak için orada şut çok fazla zarar görmüyorum , ama bunun ötesinde muhtemelen aşırı. Bununla birlikte, duruma bağlı olarak değişecektir.
Latty

'else' durumunuz aslında ölü / erişilemez koddur. Bunu yasaklayan sistem çapında herhangi bir gereksinim olmadığını kontrol edin.
NWS

@NWS: Diğer davayı tutmam gerektiğini mi söylüyorsun? Üzgünüm seni tamamen anlamıyorum.
marktani

2
özellikle soruyla ilgili değil - ama bu 'iddiayı' değişmez hale getireceğim - çizgileri dizgi olarak ele almak ve onlara dışarıdan temsil ederler. Ben CodeReview
MattDavey

Bunu mu demek istediniz elif (line.startsWith("b"))? Bu arada, koşullar üzerindeki bu parantezleri güvenle kaldırabilirsiniz, Python'da deyimsel değildir.
tokland

Yanıtlar:


14

Bu çok yaygın bir uygulamadır ve bununla başa çıkmanın yolu yüksek dereceli filtrelerdir .

Esasen, filtre yöntemine, filtrelemek istediğiniz liste / sıra ile birlikte bir işlev iletirsiniz ve sonuçta oluşan liste / sıra yalnızca istediğiniz öğeleri içerir.

Python sözdizimine aşina değilim (yine de, yukarıdaki bağlantıda görüldüğü gibi bir işlev içeriyor), ancak c # / f # gibi görünüyor:

c #:

var linesWithAB = lines.Where(l => l.StartsWith("a") || l.StartsWith("b"));
foreach (var line in linesWithAB)
{
    /* line is guaranteed to ONLY start with a or b */
}

f # (ölçülebilir kabul edilir, aksi takdirde List.filter kullanılır):

let linesWithAB = lines
    |> Seq.filter (fun l -> l.StartsWith("a") || l.StartsWith("b"))

for line in linesWithAB do
    /* line is guaranteed to ONLY start with a or b */

Yani, açıklamak gerekirse: denenmiş ve test edilmiş kod / kalıpları kullanırsanız, bu kötü bir stildir. Bu ve listeyi bellekteki clear_lines () ile göründüğünüz şekilde değiştirmek, iş parçacığı güvenliğinizi ve sahip olabileceğiniz paralellik umutlarını kaybeder.


3
Bir not olarak, bunun için piton sözdizimi bir jeneratör ifadesi olacaktır: (line for line in lines if line.startswith("a") or line.startswith("b")).
Latty

1
+1 (zorunlu) zorunlu uygulamasının clear_linesgerçekten kötü bir fikir olduğunu belirtmek için . Python'da, dosyanın tamamını belleğe yüklemekten kaçınmak için muhtemelen jeneratörleri kullanırsınız.
tokland

Giriş dosyası kullanılabilir bellekten daha büyük olduğunda ne olur?
Blrfl

@Blrfl: jeneratör terimi c # / f # / python arasında tutarlıysa, @tokland ve @Lattyware'in c # / f # verimi ve / veya verimine dönüştüğü şey! ifadeleri. Seq.filter sadece IEnumerable <T> koleksiyonlarına uygulanabileceğinden, f # örneğimde biraz daha açıktır, ancak linesoluşturulan bir koleksiyonsa her iki kod örneği de çalışır .
Steven Evers

@mcwise: Bu şekilde çalışan diğer tüm işlevlere bakmaya başladığınızda, hepsi birlikte zincirlenip oluşturulabildiğinden gerçekten seksi ve inanılmaz derecede etkileyici olmaya başlar. Bak skip, take, reduce( aggregate.NET cinsinden) map( select.NET içinde) ve orada daha ama bu gerçekten sağlam bir başlangıçtır.
Steven Evers

14

Kısa süre önce, açıkladığınıza çok benzeyen Motorola S kayıt formatını kullanarak bir ürün yazılımı programcısı uygulamak zorunda kaldım . Biraz zaman baskımız olduğundan, ilk taslağım fazlalıkları görmezden geldi ve uygulamamda gerçekten kullanmam gereken altkümeye dayalı basitleştirmeler yaptı. Testlerimi kolayca geçti, ancak bir başkası denediğinde çok başarısız oldu. Sorunun ne olduğuna dair hiçbir ipucu yoktu. Her şey yolunda gitti ama sonunda başarısız oldu.

Bu yüzden, sorunun bulunduğu yeri daraltmak için tüm gereksiz kontrolleri uygulamaktan başka seçeneğim yoktu. Bundan sonra, sorunu bulmak yaklaşık iki saniye sürdü.

Doğru şekilde yapmam iki saat daha fazla zamanımı aldı, ancak diğer insanların zamanının bir gününü sorun giderme konusunda boşa harcadı. Birkaç işlemci döngüsünün bir gün boşa giderilmeye değer olması çok nadirdir.

Bununla birlikte, okuma dosyaları söz konusu olduğunda, tüm dosyayı belleğe okumak ve bellekte işlemek yerine, yazılımınızı okumak ve bir kerede bir satır işlemek için tasarlamak genellikle yararlıdır. Bu şekilde hala çok büyük dosyalar üzerinde çalışacaktır.


"Birkaç işlemci döngüsünün bir günlük sorun giderme işlemine değmesi çok nadirdir." Cevabınız için teşekkürler, iyi bir noktanız var.
marktani

5

elseDurumda bir istisna oluşturabilirsiniz . Bu şekilde gereksiz değildir. İstisnalar, gerçekleşmesi gerekmeyen, ancak yine de kontrol edilen şeyler değildir.

clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a)):
        # do stuff
    if (line.startsWith("b")):
        # magic
    else:
        throw BadLineException
# ...

İkincisinin daha az açık olduğu için kötü bir fikir olduğunu iddia ediyorum - daha sonra bir ekleme yapmaya karar verirseniz "c", daha az net olabilir.
Latty

İlk öneri haklıdır ... ikincisi ("b" olduğunu varsayalım) kötü bir fikirdir
Andrew

@ Lattyware Cevabı geliştirdim. Yorumlarınız için teşekkürler.
Tulains Córdova

1
@Andrew cevabı geliştirdim. Yorumlarınız için teşekkürler.
Tulains Córdova

3

Sözleşmeli tasarımda , her fonksiyonun işini belgelerinde açıklandığı gibi yapması gerektiğini tahmin eder. Bu nedenle, her işlevin bir ön koşul listesi, yani işlevin girdilerindeki koşullar ve post-koşullar, yani işlevin çıktısının koşulları vardır.

İşlev, girdiler ön koşullara uyuyorsa, çıktının post koşullar tarafından açıklandığı gibi olacağını istemcilere garanti etmelidir. Ön koşullardan en az birine uyulmazsa, işlev istediği şeyi yapabilir (çökme, herhangi bir sonuç döndürme, ...). Bu nedenle, ön ve son koşullar işlevin anlamsal bir tanımıdır.

Sözleşme sayesinde, bir işlev istemcilerin doğru kullandığından ve bir istemci işlevin işini doğru yaptığından emin olur.

Bazı diller sözleşmeleri yerel olarak veya özel bir çerçeve aracılığıyla ele alır. Diğerleri için, en iyisi @Lattyware'in dediği gibi, varsayımlar sayesinde ön koşulları ve sonrası koşulları kontrol etmektir. Ancak bu savunmacı programlama demem, çünkü bence bu kavram daha çok (insan) kullanıcının girdilerine karşı korumaya odaklanmıştır.

Sözleşmelerden yararlanırsanız, çağrılan işlev mükemmel şekilde çalıştığından ve çift kontrole ihtiyaç duymadığınızdan veya çağrılan işlev işlevsiz olduğundan ve çağrı işlevi istendiği gibi davranabileceğinden artık kontrol edilen durumdan kaçınabilirsiniz.

Daha zor olan kısım, hangi işlevin neyden sorumlu olduğunu tanımlamak ve bu rolleri kesin olarak belgelendirmektir.


1

Aslında başlangıçta clear_lines () 'a ihtiyacınız yoktur. Eğer çizgi "a" veya "b" değilse, şartlılıklar tetiklemez. Bu satırlardan kurtulmak istiyorsanız diğerini clear_line () haline getirin. Durduğu gibi belgenizden iki geçiş yapıyorsunuz. Clear_lines () işlevini başlangıçta atlar ve foreach döngüsünün bir parçası olarak yaparsanız, işlem sürenizi yarıya indirirsiniz.

Bu sadece kötü bir stil değil, hesaplama açısından da kötü.


2
Bu çizgiler başka bir şey için kullanılıyor olabilir ve "a"/ "b"çizgileri ile uğraşmadan önce ele alınması gerekir . Bunun muhtemel olduğunu söylememek ( açık isim onların atıldığını ima eder), sadece ihtiyaç duyulması olasılığı vardır. Bu çizgi seti gelecekte tekrar tekrar yinelenirse, çok fazla anlamsız yinelemeden kaçınmak için bunları önceden kaldırmak da faydalı olabilir.
Latty

0

Aslında geçersiz bir dize (örneğin hata ayıklama metni) bulursanız bir şey yapmak istiyorsanız, o zaman kesinlikle iyi olduğunu söyleyebilirim. Bilinmeyen bir nedenden dolayı çalışmayı durdurduğunda birkaç ekstra satır ve birkaç ay boyunca satırın nedenini bulmak için çıktıya bakabilirsiniz.

Bununla birlikte, sadece görmezden gelmek güvenliyse veya kesinlikle geçersiz bir dize almayacağınızdan eminseniz, ekstra şubeye gerek yoktur.

Şahsen ben her zaman beklenmedik bir durum için en azından bir iz çıktısı koymak için - ben tam olarak neyin yanlış gittiğini söyleyen çıkış ekli bir hata olduğunda hayatı daha kolay hale getirir.


0

... "a" ile başlayan satırları, "b" ile başlayan satırları ve diğer satırları içeren bir metin dosyası olduğunu ve aslında sadece ilk iki satırla çalışmak istediğini varsayalım. Kodum (python kullanarak, ancak sözde kod olarak okuyun) böyle bir şey olurdu:

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if ...

if...then...elseİnşaatlardan nefret ediyorum . Tüm sorunu önlerdim:

process_lines_by_first_character (lines,  
                                  'a' => { |line| ... a code ... },
                                  'b' => { |line| ... b code ... } )
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.