Büyük bir boole ifadesi, aynı yöntemden yordama yöntemlerine ayrılandan daha okunabilir mi? [kapalı]


63

Anlaması daha kolay, büyük bir boole ifadesi (oldukça karmaşık) ya da aynı ifadeyi yordama yöntemlerine (okumak için fazladan fazla kod) ayrıştırmak nedir?

Seçenek 1, büyük boole ifadesi:

    private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
    {

        return propVal.PropertyId == context.Definition.Id
            && !repo.ParentId.HasValue || repo.ParentId == propVal.ParentId
            && ((propVal.SecondaryFilter.HasValue && context.SecondaryFilter.HasValue && propVal.SecondaryFilter.Value == context.SecondaryFilter) || (!context.SecondaryFilter.HasValue && !propVal.SecondaryFilter.HasValue));
    }

Seçenek 2, Koşulları yordama yöntemlerine ayrılır:

    private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
    {
        return MatchesDefinitionId(context, propVal)
            && MatchesParentId(propVal)
            && (MatchedSecondaryFilter(context, propVal) || HasNoSecondaryFilter(context, propVal));
    }

    private static bool HasNoSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
    {
        return (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);
    }

    private static bool MatchedSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
    {
        return (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
    }

    private bool MatchesParentId(TValToMatch propVal)
    {
        return (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
    }

    private static bool MatchesDefinitionId(CurrentSearchContext context, TValToMatch propVal)
    {
        return propVal.PropertyId == context.Definition.Id;
    }

İkinci yaklaşımı tercih ediyorum, çünkü yöntem adlarını yorum olarak görüyorum, ancak problemli olduğunu biliyorum çünkü kodun ne yaptığını anlamak için tüm yöntemleri okumak zorundasınız, bu nedenle kodun amacını ortadan kaldırıyor.


13
Seçenek 2, Martin Fowler'ın yeniden düzenleme kitabında önerdiğine benzer. Ayrıca, yöntem adlarınız tüm rastgele ifadelerin amacı olarak işlev görür, yöntemlerin içeriği yalnızca zaman içinde değişebilecek uygulama ayrıntılarıdır.
programcı

2
Gerçekten aynı ifade mi? "Veya", "Ve" den daha az önceliğe sahiptir, Zaten ikincisi niyetinizi söyler, diğeri (ilk) tekniktir.
packer,

3
@ Thepacker ne diyor. Bunu ilk yoldan yapmanın bir hata yapmanıza neden olduğu gerçeği, ilk yolun hedef kitlenizdeki çok önemli bir sektör için kolayca anlaşılamadığına dair iyi bir ipucudur. Kendin!
Steve Jessop

3
Seçenek 3: İkisinden de hoşlanmıyorum. İkincisi gülünç derecede ayrıntılı, birincisi ikincisiyle eşdeğer değil. Parantez yardımı.
David Hammen,

3
Bu bilgiçlik edici olabilir, ancak kod bloğunda hiçbir ifadeniz yok if. Sorunuz Boole ifadeleriyle ilgili .
Kyle Strand

Yanıtlar:


88

Anlaması daha kolay olan ne

İkinci yaklaşım. Bunu anlamak sadece kolay değil, aynı zamanda yazmak, test etmek, yeniden düzenlemek ve genişletmek de daha kolay. Her istenen koşul güvenli bir şekilde ayrı ayrı çözülebilir ve kendi yöntemiyle ele alınabilir.

bu sorunlu çünkü kodu anlamak için tüm yöntemleri okumak zorundasınız.

Yöntemlerin uygun şekilde adlandırılması sorun değil. Aslında, yöntem adının koşulun amacını tanımladığı şekliyle anlaşılması daha kolay olacaktır.
Bir seyirci if MatchesDefinitionId()için daha açıklayıcıif (propVal.PropertyId == context.Definition.Id)

[Şahsen ilk yaklaşım gözlerimi acıtıyor.]


12
Yöntem isimleri iyiyse, o zaman anlamak da daha kolaydır.
BЈовић

Ve lütfen, onları (yöntem adları) anlamlı ve kısa yapın. 20'den fazla karakter yöntemi ismimi ağrıtıyor. MatchesDefinitionId()sınırda.
Mindwin

2
@Mindwin Metot adlarını "kısa" tutmakla onları anlamlı tutmak arasında bir seçim yapılıyorsa, her zaman ikinci kelimeyi alın derim. Kısa iyidir, ancak okunabilirlik pahasına değildir.
Ajedi32

@ Ajedi32 kişi, yöntemin yöntem adına ne yaptığına dair bir yazı yazmak ya da gramer olarak sağlam bir yöntem isimlerine sahip olmak zorunda değildir. Eğer biri kısaltma standartlarını açık tutarsa ​​(çalışma grubu veya organizasyon genelinde) kısa isimler ve okunabilirlikle ilgili bir problem olmayacaktır.
Mindwin

Zipf yasasını kullanın: kullanımlarını caydırmak için işleri daha ayrıntılı hale getirin .
HoosierEE

44

Bu, yüklem işlevlerinin kullanılabileceği tek yerse, boolbunun yerine yerel değişkenleri de kullanabilirsiniz :

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    bool matchesDefinitionId = (propVal.PropertyId == context.Definition.Id);
    bool matchesParentId = (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
    bool matchesSecondaryFilter = (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
    bool hasNoSecondaryFilter = (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);

    return matchesDefinitionId
        && matchesParentId
        && matchesSecondaryFilter || hasNoSecondaryFilter;
}

Bunlar ayrıca daha fazla parçalanabilir ve daha okunabilir hale getirmek için yeniden sıralanabilir, örneğin;

bool hasSecondaryFilter = propVal.SecondaryFilter.HasValue;

ve ardından tüm örneklerini değiştirme propVal.SecondaryFilter.HasValue. O zaman hemen ortaya çıkan bir şey, hasNoSecondaryFiltermantıksal VE ihmal edilen HasValueözellikler üzerinde mantıksal ve ihmal edilenler matchesSecondaryFilterüzerinde mantıksal kullanır HasValue- yani tam tersi değildir.


3
Bu çözüm oldukça iyi ve kesinlikle pek çok benzer kod yazdım. Çok okunabilir. Olumsuz, yayınladığım çözüme kıyasla, hızdır. Bu yöntemle, ne olursa olsun koşullu testler gerçekleştirirsiniz. Benim çözümümde işlemler, işlenen değerlere bağlı olarak büyük ölçüde azaltılabilir.
Buvin,

5
@BuvinJ Burada gösterilenler gibi testler oldukça ucuz olmalı, bu nedenle koşulların bazılarının pahalı olduğunu bilmediğim sürece ya da bu performansa duyarlı bir kod olmadıkça daha okunaklı bir versiyona giderdim.
salı

1
@svick Hiç şüphe yok ki, bunun çoğu zaman bir performans sorununu ortaya koyması pek mümkün değildir. Yine de, okunabilirliği kaybetmeden işlemleri azaltabilirseniz, neden olmasın? Bunun benim çözümümden daha okunaklı olduğuna ikna olmadım. Testlere kendi kendini belgeleme "isimleri" veriyor - bu güzel… Özel kullanım durumunun ve testlerin kendi başlarına ne kadar anlaşılır olduğunu düşünüyorum.
Buvin,

Yorum eklemek de okunabilirliğe yardımcı olabilir ...
BuvinJ

@BuvinJ Bu çözümle ilgili gerçekten sevdiğim şey, son satır dışındaki her şeyi göz ardı ederek, ne yaptığını çabucak anlayabilmem. Bunun daha okunaklı olduğunu düşünüyorum.
salı

42

Genel olarak, ikincisi tercih edilir.

Bu çağrı sitesini daha yeniden kullanılabilir hale getirir. DRY'yi destekler (yani ölçütler değiştiğinde değiştirilecek daha az yeriniz olur ve daha güvenilir bir şekilde yapabilirsiniz). Ve çoğu zaman bu alt kriterler, bunu yapmanıza izin veren, başka bir yerde bağımsız olarak yeniden kullanılacak şeylerdir.

Oh, ve bu, üniteyi test etmeyi çok daha kolaylaştırıyor ve doğru yaptığınızdan emin olmanızı sağlıyor.


1
Evet, cevabınız aynı zamanda repostatik bir alan / özellik gibi genel bir değişken olan kullanımını düzeltmeyi de içermelidir . Statik yöntemler deterministik olmalı ve global değişken kullanmamalıdır.
David Arno

3
@DavidArno - bu harika olmasa da, eldeki soruya teğet görünüyor. Ve daha kodu olmadan bu kadar akla yatkın tasarım böyle çalışmak için yarı geçerli nedeni olduğunu.
Telastyn

1
Evet, boşuna deposu. Kodu biraz şaşırtmak zorunda kaldım, interweb'lerde olduğu gibi müşteri kodunu paylaşmak istemiyorum :)
willem

23

Bu iki seçenek arasındaysa, ikincisi daha iyidir. Ancak bunlar sadece seçenek değil! Tek işlevi çoklu ifs'e bölmeye ne dersiniz? Ek testlerden kaçınmak için fonksiyondan çıkmanın yollarını test edin, kabaca tek bir hat testinde "kısa devre" öykünür.

Bunu okumak daha kolaydır (örneğiniz için mantığı iki kez kontrol etmeniz gerekebilir, ancak kavram doğrudur):

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    if( propVal.PropertyId != context.Definition.Id ) return false;

    if( repo.ParentId.HasValue || repo.ParentId != propVal.ParentId ) return false;

    if( propVal.SecondaryFilter.HasValue && 
        context.SecondaryFilter.HasValue && 
        propVal.SecondaryFilter.Value == context.SecondaryFilter ) return true;

    if( !context.SecondaryFilter.HasValue && 
        !propVal.SecondaryFilter.HasValue) return true;

    return false;   
}

3
Neden yayınladıktan birkaç saniye sonra bunun için bir oy kullandım? Lütfen oy verdiğinde yorum yaz! Bu cevap kadar çabuk çalışır ve okunması daha kolaydır. Peki sorun ne?
Buvin,

2
@BuvinJ: Kesinlikle yanlış bir şey yok. Orijinal kodla aynıdır, ancak bir düzine parantezle ve ekranın sonundan geçen tek bir satırla savaşmanız gerekmez. Bu kodu yukarıdan aşağıya okuyabilir ve hemen anlayabilirim. WTF sayısı = 0
gnasher729 10:16

1
Fonksiyonun sonundan başkasına dönmek, kodu daha az okunabilir yapar, daha fazla okunamaz hale getirir, IMO. Tek çıkış noktasını tercih ederim. Bazı iyi argümanlar bu linkte iki yönlü. stackoverflow.com/questions/36707/…
Brad Thomas

5
@Brad thomas Tek bir çıkış noktasıyla aynı fikirdeyim. Genellikle derin iç içe parantezlere yol açar. Dönüş yolu bitiriyor, bu yüzden benim için okuması çok daha kolay.
Borjab

1
@BradThomas Borjab ile tamamen katılıyorum. Derin yuvalardan kaçınmak aslında bu tarzı uzun koşullu ifadeleri parçalamaktan daha sık kullanmamın nedenidir. Kendimi tonlarca yerleştirmeyle kod yazarken buluyorum. Sonra, bir ya da iki yuvadan neredeyse hiç geçmeyecek yollar aramaya başladım ve kodum sonuç olarak okunması ve bakımı çok daha kolay hale geldi. Eğer işlevinizden çıkmanın bir yolunu bulursanız, en kısa zamanda yapın! Derin yuvalardan ve uzun koşullardan kaçınmanın bir yolunu bulabilirseniz, yapın!
Buvin,

10

Seçenek 2'yi daha çok seviyorum, ancak bir yapısal değişiklik önerebilirim. Koşullu son satırındaki iki kontrolü tek bir aramada birleştirin.

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    return MatchesDefinitionId(context, propVal)
        && MatchesParentId(propVal)
        && MatchesSecondaryFilterIfPresent(context, propVal);
}

private static bool MatchesSecondaryFilterIfPresent(CurrentSearchContext context, 
                                                    TValToMatch propVal)
{
    return MatchedSecondaryFilter(context, propVal) 
               || HasNoSecondaryFilter(context, propVal);
}

Bunu önerme sebebim, iki kontrolün tek bir işlevsel ünite olması ve parantezin koşullu bir şekilde iç içe geçmesidir: Hem kodu başlangıçta yazma açısından hem de okuyan kişinin bakış açısından. Bu, özellikle, ifadenin alt öğelerinin aynı modeli izlememesi durumunda geçerlidir.

MatchesSecondaryFilterIfPresent()Kombinasyon için en iyi isim olup olmadığından emin değilim ; ama daha iyi bir şey hemen akla geliyor.


Çok güzel, yöntemlerin içinde ne yapıldığını açıklamaya çalışmak, çağrıları yeniden yapılandırmaktan daha iyidir.
klaar

2

C # dilinde olmasına rağmen kod çok nesneye yönelik değildir. Statik yöntemler kullanıyor ve statik alanlara benziyor (örn. repo). Genel olarak, statik durumun yeniden kullanma kabiliyetini zorlaştırırken kodunuzu yeniden kırmak için zorlaştırması ve test etmesini zorlaştırdığı ve sorunuza göre şu şekildedir: bunun gibi statik kullanım nesneye yönelik yapıdan daha az okunabilir ve korunabilirdir.

Bu kodu daha çok nesne yönelimli bir forma dönüştürmelisiniz. Bunu yaptığınızda, nesnelerin, alanların vb. Karşılaştırma yapmak için basit bir talep (örneğin if ( a.compareTo (b) ) { }, tüm saha karşılaştırmalarını içerebilecek.)

C #, nesneler ve alanları ile ilgili karşılaştırmalar yapmak için zengin bir arayüz ve sistem yardımcı programına sahiptir. Bariz ötesinde .Equalsyöntemle başlayanlar için, içine bakmak IEqualityComparer, IEquatableve yardımcı programlar gibi System.Collections.Generic.EqualityComparer.Default.


0

İkincisi kesinlikle tercih edilir, ilk yolu olan vakaları gördüm ve okumak her zaman imkansızdır. Bunu ilk şekilde yapma hatasını yaptım ve yöntemleri tahmin etmek için değiştirmem istendi.


0

İkisinin de aynı olduğunu söyleyebilirim, okunabilirlik için biraz boşluk eklerseniz ve okuyucuya daha belirsiz kısımlarda yardımcı olması için yorumlarınızı eklerseniz.

Unutmayın: İyi yorumlar okuyucuya kodu yazarken ne düşündüğünüzü söyler .

Önerdiğim gibi değişikliklerle, muhtemelen daha az karmaşık ve dağınık olduğu için eski yaklaşımla giderdim. Alt program çağrıları dipnotlar gibidir: faydalı bilgiler sağlar ancak okuma akışını bozarlar. Eğer tahminler daha karmaşık olsaydı, onları somutlaştırılmış kavramların anlaşılabilir parçalara yerleştirilebilmesi için onları ayrı yöntemlere bölerdim.


+ 1'i hakediyor. Diğer cevaplara dayanarak popüler olmasa da, düşünce için iyi yemek. Thanks :)
willem

1
@willem Hayır, + 1'i haketmiyor. İki yaklaşım aynı değildir. Ek yorumlar aptal ve gereksizdir.
BЈовић

2
İyi bir kod ASLA ASLA anlaşılabilir olmak üzere yorumlara bağlıdır. Aslında yorumlar, bir kodun sahip olabileceği en kötü karmaşa. Kodun kendisi için konuşması gerekir. Ayrıca, OP'nin değerlendirmek istediği iki yaklaşım, bir tanesi ne kadar boşluk eklese de, hiçbir zaman “aynı hakkında” olamaz.
Wonderbell

Anlamlı bir fonksiyon adına sahip olmak, yorumu okumaktan daha iyidir. "Kodu Temizle" kitabında belirtildiği gibi, bir yorum atma kodunu ifade etmede başarısızlıktır. İşlev çok daha net bir şekilde ifade ettiğinde ne yaptığınızı neden açıklayın?
Borjab

0

Peki, yeniden kullanmak isteyeceğiniz parçalar varsa, bunları ayrı bir şekilde adlandırılmış fonksiyonlara ayırmak en iyi fikirdir.
Bunları asla kullanmayın olabilir bile, bunu yaparken daha iyi koşullar yapılandırmak ve onlar açıklayan onlara etiketleyebilmek izin verebilir demek .

Şimdi ilk seçeneğinize bakalım ve ne de sizin girintiniz olduğunu ve her şeyi yararlı kıldığınızı ya da şartlı her şeyi iyi yapılandırdığınızı kabul edin:

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal) {
    return propVal.PropertyId == context.Definition.Id && !repo.ParentId.HasValue
        || repo.ParentId == propVal.ParentId
        && propVal.SecondaryFilter.HasValue == context.SecondaryFilter.HasValue
        && (!propVal.SecondaryFilter.HasValue || propVal.SecondaryFilter.Value == context.SecondaryFilter.Value);
}

0

İlki kesinlikle korkunç. Sen || aynı hattaki iki şey için; Bu, kodunuzdaki bir hatadır veya kodunuzu gizlemeye niyetlidir.

    return (   (   propVal.PropertyId == context.Definition.Id
                && !repo.ParentId.HasValue)
            || (   repo.ParentId == propVal.ParentId
                && (   (   propVal.SecondaryFilter.HasValue
                        && context.SecondaryFilter.HasValue 
                        && propVal.SecondaryFilter.Value == context.SecondaryFilter)
                    || (   !context.SecondaryFilter.HasValue
                        && !propVal.SecondaryFilter.HasValue))));

Bu, en azından yarı yarıya iyi biçimlendirilmiş (biçimlendirme karmaşıksa, bunun nedeni if-koşulunun karmaşık olmasından kaynaklanmaktadır) ve en azından içerisindeki herhangi bir şeyin saçma olup olmadığını anlama şansınız var. Biçimlendirilmiş çöplerinizle karşılaştırıldığında, başka bir şey daha iyi. Ancak, sadece aşırılıkları yapabiliyor gibi görünüyorsunuz: Ya bir if ifadesinin karışıklığı ya da dört tamamen anlamsız yöntem.

Unutmayın (cond1 && cond2) || (! cond1 && cond3) olarak yazılabilir

cond1 ? cond2 : cond3

bu karışıklığı azaltacaktır. Yazardım

if (propVal.PropertyId == context.Definition.Id && !repo.ParentId.HasValue) {
    return true;
} else if (repo.ParentId != propVal.ParentId) {
    return false;
} else if (propVal.SecondaryFilter.HasValue) {
    return (   context.SecondaryFilter.HasValue
            && propVal.SecondaryFilter.Value == context.SecondaryFilter); 
} else {
    return !context.SecondaryFilter.HasValue;
}

-4

Bu iki çözümden de hoşlanmıyorum, ikisi de mantıklı gelmeleri zor ve okunması zor. Sadece küçük yöntemler için küçük yöntemlere soyutlama yapmak her zaman sorunu çözmez.

İdeal olarak, özellikleri metaprogrmatik olarak karşılaştıracağınızı düşünüyorum, bu nedenle yeni bir özellik tanımlamıyorsunuz veya her yeni özellik kümesini karşılaştırmak istediğinizde dallanıyorsanız.

C # hakkında emin değilim, ancak javascript içinde böyle bir şey çok daha iyi olurdu ve en azından MatchesDefinitionId ve MatchesParentId yerine

function compareContextProp(obj, property, value){
  if(obj[property])
    return obj[property] == value
  return false
}

1
C # 'da böyle bir şeyi uygulamak için bir problem olmamalı.
Snoop

compareContextProp(propVal, "PropertyId", context.Definition.Id)OP5'in boolean kombinasyonunun ~ 5 karşılaştırmalarının nasıl yapıldığını okumaktan daha kolay olacağını görmüyorum propVal.PropertyId == context.Definition.Id. Oldukça daha uzundur ve çağrı sitesindeki karmaşıklığı gerçekten gizlemeden ekstra bir katman ekler. (eğer önemliyse, aşağı oylamadım)
Ixrec 22:16
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.