IF ifadesini ters çevirme


39

Bu yüzden birkaç yıldır programlama yapıyorum ve son zamanlarda ReSharper'ı daha fazla kullanmaya başladım. ReSharper'ın bana her zaman önerdiği bir şey "iç içe geçirmeyi azaltmak için" ifadesini "tersine çevirmektir".

Diyelim ki bu kod bende:

foreach (someObject in someObjectList) 
{
    if(someObject != null) 
    {
        someOtherObject = someObject.SomeProperty;
    }
}

Ve ReSharper bunu yapmamı önerecek:

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;
}

ReSharper, HER ZAMAN, ne kadar iç içe geçiyor olursa olsun, IF'leri tersine çevirdiğimi önerecek gibi görünüyor. Bu problem, en azından bazı durumlarda iç içe geçmeyi sevmemdir. Bana göre bazı yerlerde neler olup bittiğini okumak ve anlamak daha kolay görünüyor. Bu her zaman böyle değildir, ancak bazen iç içe geçme konusunda daha rahat hissediyorum.

Sorum şu: kişisel tercih veya okunabilirlik dışında, yuvalamayı azaltmak için bir neden var mı? Performans farkı veya farkında olamayacağım başka bir şey var mı?



24
Buradaki en önemli şey "Resharper Önerileri". Öneriye göz atın, eğer kodu okumayı kolaylaştırır ve tutarlıysa, kullanın. Her döngü için / ile aynı şeyi yapar.
Jon Raynor

Genelde bu yeniden keskinleşen ipuçlarını indirgiyorum, her zaman takip etmiyorum, bu yüzden artık kenar çubuğunda görünmüyorlar.
CodesInChaos

1
ReSharper, genellikle iyi bir şey olan negatif çıkardı. Bununla birlikte, eğer blok gerçekten sizin örneğiniz kadar basitse, buraya güzel bir şekilde uyabilecek üçlü bir koşul sunmadı .
dcorking

2
ReSharper da şartlı sorguyu taşımayı önermiyor mu? foreach (someObjectList.Wist içinde someObject.Where (object => object! = null)) {...}
Roman Reiner

Yanıtlar:


63

Değişir. Örneğinizde ters çevrilmemiş ififadeye sahip olmak sorun değil, çünkü gövdesi ifçok kısa. Ancak, genellikle vücut büyüdüğü zaman ifadeleri tersine çevirmek istersiniz.

Biz sadece insanız ve bir anda birden fazla şeyi kafalarımızda tutmak zordur.

Bir ifadenin gövdesi büyük olduğunda, ifadeyi tersine çevirmek yalnızca iç içe geçirmeyi azaltmakla kalmaz, aynı zamanda geliştiriciye bu ifadenin üzerindeki her şeyi unutabileceklerini ve yalnızca geri kalanına odaklanmalarını söyler. En kısa zamanda yanlış değerlerden kurtulmak için ters çevrilmiş ifadeleri kullanmanız çok muhtemeldir. Bunların oyunun dışında olduğunu bildiğiniz zaman geriye kalan tek şey aslında işlenebilen değerlerdir.


64
Bu şekilde geliştirici fikrinin gelgitini görmeyi çok seviyorum. Oradaki olağanüstü popüler sanrı çünkü kodunda çok fazla yuvalama var break, continueve çoklu returnyapılandırılmıştır değildir.
JimmyJames

6
sorun, kırılma vb. hala kafanızda tutmanız gereken kontrol akışıdır 'bu x olamaz ya da üstteki döngüden çıkmış olur', yine de bir istisna atacak olan boş bir kontrol varsa daha fazla düşünmeye gerek duymayabilir. Ama daha nüanslı 'perşembe günleri bu durum için bu kodun sadece yarısını çalıştırın'
Ewan

2
@Ewan: 'Bu x olamaz ya da üstteki döngüden çıkmış olurdu' ile 'bu x olamaz ya da bu bloğa girmezdi' arasındaki fark nedir?
Collin

hiçbir şey önemli olan x'in önemi ve anlamı değil
Ewan

4
@Ewan: Bence break/ continue/ returnaşırı gerçek bir avantaj elsebloğu. Erken çıkışlarda 2 blok bulunur : Çıkış bloğu ve orada kalan blok; ile elsevardır 3 blok : başka ve ne olursa olsun (şube alınan her türlü kullanılır) sonra gelirse. Daha az blok almayı tercih ederim.
Matthieu M.,

33

Olumsuzluklardan kaçınmayın. CPU onları sevse bile, insanlar onları ayrıştırırken emilir.

Ancak, deyimler saygı. Biz insanlar alışkanlık yaratıklarıyız, bu nedenle yabani otlarla dolu yolları yönlendirmek için iyi giyilmiş yolları tercih ediyoruz.

foo != nullNe yazık ki, bir deyim haline geldi. Ayrı parçaları artık zar zor fark ediyorum. Şuna bakıyorum ve sadece, "ah, fooşimdiden işaret etmek güvenli" diye düşünüyorum .

Bunu bozmak istiyorsan (oh, bir gün, lütfen) gerçekten güçlü bir davaya ihtiyacın var. Gözlerimin zahmetsizce kaymasını ve anında anlamasını sağlayacak bir şeye ihtiyacınız var.

Ancak, bize verdiğiniz şey şuydu:

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;
}

Bu bile yapısal olarak aynı değil!

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;

    if(someGlobalObject == null) continue;
    someSideEffectObject = someGlobalObject.SomeProperty;
}

Bu benim tembel beynimin yapmasını beklediği şeyi yapmıyor. Bağımsız kod eklemeye devam edebileceğimi düşündüm ama yapamam. Bu, şimdi düşünmek istemediğim şeyler hakkında düşünmemi sağlıyor. Deyimi kırdın ama bana daha iyisini vermedin. Çünkü beni, beni yakan tek bir olumsuzluktan korumak istedin, bu kötü küçük benlik ruhumda.

Çok üzgünüm, belki ReSharper zamanın% 90'ını önermek için haklı ama boş kontrollerde en çok göz ardı edileceği bir köşe davası bulduğunuzu düşünüyorum. Araçlar, okunabilir kodun ne olduğunu size öğretmede iyi değildir. Bir insana sor. Bir meslektaş değerlendirmesi yapın. Ama aracı körlemesine takip etme.


1
Eğer döngüde daha fazla kodunuz varsa, nasıl çalıştığı her şeyin nasıl bir araya geldiğine bağlıdır. Bağımsız bir kod değil. Sorudaki düzeltilmiş kod örnek için doğruydu, ancak tecrit edilmediğinde doğru olmayabilir - bu sizin hedeflediğiniz nokta mıydı? (Olumsuz olanlar için +1 olsa olumsuzlukları önlemek değil)
Baldrickk

@Baldrickk boş if (foo != null){ foo.something() }olsaydı foo, umursamadan kod eklemeye devam etmemi sağlıyor . Bu değil. Bunu şimdi yapmama gerek kalmasa bile, "peki ihtiyaç duyarlarsa onu yeniden değerlendirebilirler" diyerek geleceği mahvetmek için motive olamıyorum. Feh. Yazılım yalnızca değiştirmesi kolaysa yumuşaktır.
candied_orange

4
"Kod vermeden kod eklemeye devam et" Kodun birbiriyle ilişkili olması gerekir (aksi takdirde büyük olasılıkla ayrı olmalıdır) ya da herhangi bir kod ekleyerek davranışı değiştirme potansiyeline sahip olarak değiştirdiğiniz fonksiyonun / bloğun işlevselliğini anlamaya özen gösterilmelidir. amaçlananın ötesinde. Bunun gibi basit durumlar için, iki şekilde de önemli olduğunu sanmıyorum. Daha karmaşık durumlar için, kodun önemini anlamasını beklerdim, bu yüzden hangisinin en net olduğunu düşündüğümden hangisini seçeceğimi seçerdim.
Baldrickk

ilgili ve iç içe aynı şey değildir.
candied_orange


20

Bir mola bu yuvalama daha kötü olup olmadığını eski argümandır.

İnsanlar parametre kontrolleri için erken getirileri kabul ediyor gibi görünmekle birlikte, bir yöntemin büyük kısmında kaşlarını çattı.

Şahsen daha fazla yuvalanma anlamına gelse bile devam etmem, kırılmam, vb. Ancak çoğu durumda her ikisinden de kaçınabilirsin.

foreach (someObject in someObjectList.where(i=>i != null))
{
    someOtherObject = someObject.SomeProperty;
}

Açıkçası yuvalama, çok katmanınız olduğunda işleri takip etmenizi zorlaştırır

foreach (someObject in someObjectList) 
{
    if(someObject != null) 
    {
        someOtherObject = someObject.SomeProperty;
        if(someObject.x > 5) 
        {
            x ++;
            if(someObject.y > 5) 
            {
                x ++;
            }
        }
    }
}

En kötüsü ikisinde de olsa

foreach (someObject in someObjectList) 
{
    if(someObject != null) 
    {
        someOtherObject = someObject.SomeProperty;
        if(someObject.x > 5) 
        {
            x ++;
            if(someObject.y > 5) 
            {
                x ++;
                return;
            }
            continue;
        }
        someObject.z --;
        if(myFunctionWithSideEffects(someObject) > 6) break;
    }
}

11
Bu satırı sevin: foreach (subObject in someObjectList.where(i=>i != null))Neden böyle bir şeyi hiç düşünmedim bilmiyorum.
Barry Franklin

@BarryFranklin ReSharper, foreach üzerinde "LINQ ifadesine dönüştür" ifadesiyle, sizin için bunu yapacak bir öneri olarak yeşil noktalı bir alt çizgi içermelidir. İşleme bağlı olarak, Sum veya Max gibi diğer linq yöntemlerini de yapabilir.
Kevin Fee

@BarryFranklin Muhtemelen düşük bir seviyeye ayarlamak ve sadece isteğe bağlı olarak kullanmak istediğiniz bir öneri. Bu örnek gibi önemsiz durumlar için iyi çalışsa da, daha karmaşık kodda Linqifiable olan parçalara daha karmaşık koşulsal kısımlar seçme yöntemini buluyorum ve geri kalan vücut orijinalden daha zor anlaşılan şeyler yaratma eğilimindedir. Genel olarak en azından öneriyi deneyeceğim çünkü tüm bir döngüyü Linq'e dönüştürebildiğinde sonuçlar genellikle makul; ancak yarım buçuk sonuç IMO’ya çok hızlı bir şekilde yaklaşıyor.
Dan Neely,

İronik olarak, yeniden biçimlendirici tarafından yapılan düzenleme aynı yuvalama seviyesine sahiptir, çünkü aynı zamanda bir astarı izleyen bir if vardır.
Peter,

he, olsa ayraç yok! görünüşe göre izin verilen: /
Ewan

6

Bununla ilgili sorun continue, koda daha fazla satır eklerseniz mantığın karmaşıklaşması ve hata içermesi olasıdır. Örneğin

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;

    ...  imagine that there is some code here ...

    // added later by a Junior Developer
    doSomeOtherFunction(someObject);
}

Aramak istediğiniz musunuz doSomeOtherFunction()için tüm SomeObject veya yalnızca boş olmayan olur? Orijinal kod ile seçeneğiniz var ve daha net. İle continuebiri "Ah, evet, oraya biz boş değerlere atlanır" unutabilirsiniz ve mümkün veya doğru olmayabilir yöntemin başlangıcında o aramayı koymak sürece bile, tüm someObjects için arama seçeneği yok diğer sebeplerden dolayı.

Evet, pek çok yuvalama çirkin ve muhtemelen kafa karıştırıcıdır, ancak bu durumda geleneksel stili kullanırdım.

Bonus sorusu: Neden bu listede başlayacak boş değerler var? İlk başta hiçbir zaman boş değer eklememek daha iyi olabilir, bu durumda işlem mantığı çok daha basittir.


12
Döngünün içinde null-çekini, üstünde kaydırma yapmadan göremeyeceğiniz kadar kod olmamalıdır.
Bryan Boettcher

@Bryan Boettcher kabul etti, ancak tüm kodlar çok iyi yazılmış değil. Hatta birkaç karmaşık kod satırı bile devam etmeyi unutmaya (ya da yanlış anlaşılmaya) neden olabilir. Uygulamada sorun yok returnçünkü "temiz bir mola" oluşturuyorlar, ancak IMO breakve continuekarışıklığa yol açıyor ve çoğu durumda en iyi şekilde kaçınılması mümkün.
user949300

4
@ user949300 On yıllık geliştirmeden sonra, hiçbir zaman bir mola bulamadım veya karışıklığa neden olmaya devam ettim. Onları kafa karışıklığına dahil ettim ama asla sebep olmadılar. Genellikle, geliştirici hakkındaki zayıf kararlar bunun nedenidir ve hangi silahı kullanırlarsa kullansınlardı.
corsiKa

1
@corsiKa Apple SSL hatası aslında bir baş döndürücü hata, ancak kolayca bir ara verilmiş ya da devam eden bir hata olabilirdi. Ancak kod korkunç ...
user949300

3
@BryanBoettcher sorun bana devam ediyor ya da çoklu geri dönüşler olduğunu düşünen aynı devlerin aynı zamanda büyük yöntem gövdelerinde gömülmesinin de yolunda olduğunu düşünüyor. Bu yüzden devam / çoklu iadelerle yaşadığım her deneyime çok büyük miktarda kod geldi.
Andy,

3

Fikir erken dönüş. Erken returnbir döngüde erken olur continue.

Bu soruya pek çok iyi cevap: Bir fonksiyondan erken dönmeli miyim yoksa bir if ifadesi mi kullanmalıyım?

Sorunun fikir temelli olarak kapatılmasına rağmen, 430+ oy erken dönüş lehine, ~ 50 oy "bağlı" ve 8'in erken geri dönüşe karşı olduğuna dikkat edin. Bu yüzden, son derece güçlü bir fikir birliği var (ya bu sayılır, ya da sayma konusunda kötüyüm, ki bu mantıklı).

Şahsen, eğer döngü gövdesi erken devam ederse ve bunun için yeterince uzun sürerse (3-4 satır), döngü gövdesini erken devam etmek yerine erken dönüş kullandığım bir fonksiyona çıkartma eğilimindeyim.


2

Bu teknik, yönteminizin ana kütlesi bu koşullar karşılandığında gerçekleştiğinde ön koşulları sertifikalandırmak için kullanışlıdır.

İşimi sık sık tersine çeviriyoruz ifsve başka bir hayalet kullanıyoruz. Bir PR'ı incelerken meslektaşımın üç yuva seviyesini nasıl kaldırabildiğine işaret edebiliyordum. İfs'imizi çıkardığımızdan beri daha az sıklıkta olur.

İşte çıkarılabilecek bir şeyin oyuncak örneği

function foobar(x, y, z) {
    if (x > 0) {
        if (z != null && isGamma(y)) {
            // ~10 lines of code
            // return something
        }
        else {
           return y
        }
    }
    else {
      return y + z
    }
}

Bunun gibi, TEK ilginç bir durumun olduğu (diğerlerinin basitçe geri döndüğü veya küçük deyim blokları olan) böyle bir kod yaygındır. Yukarıdakileri tekrar yazmak çok önemlidir.

function foobar(x, y, z) {
    if (x <=0) {
        return y + z
    }
    if (z == null || !isGamma(y) {
       return y
    }
    // ~ 10 lines of code
    // return something
}

Burada, kayda değer olan 10 satırlı önemli kodumuz, fonksiyonda üçlü girintili değildir. İlk stile sahipseniz, ya uzun bloğu bir yönteme yeniden yansıtmaya ya da girintiyi tolere etmeye özendirirsiniz. Bu, tasarrufların devreye girdiği yerdir. Bir yerde bu önemsiz bir tasarruftur, ancak birçok sınıftaki birçok yöntemde , kodu daha temiz hale getirmek için fazladan bir işlev oluşturma gereksinimini ortadan kaldırırsınız ve çok çirkin bir çok seviyeniz olmaz. girinti .


OP'nin kod örneğine yanıt olarak, bunun aşırı derecede bir çöküş olduğunu kabul ediyorum. Özellikle 1TB'de kullandığım tarz olduğundan, inversiyon kodun boyutunun artmasına neden oluyor.


0

Yeniden şekillendirme önerileri genellikle yararlıdır, ancak her zaman kritik olarak değerlendirilmelidir. Önceden tanımlanmış bazı kod kalıplarını arar ve dönüşümler önerir, ancak kodu insanlar için daha fazla veya daha az okunabilir kılan tüm faktörleri dikkate almaz.

Diğer tüm şeylerin eşit olması, daha az yerleştirme yapılması tercih edilir, bu yüzden ReSharper yerleştirmeyi azaltan bir dönüşüm önerecektir. Ancak diğer tüm şeyler eşit değildir : Bu durumda, dönüşüm, a'nın girişini gerektirir; continuebu, sonuçta ortaya çıkan kodun takip edilmesi daha zordur.

Gelen bazı durumlarda, bir için yuvalama düzeyi alışverişi continue olabilir gerçekten bir gelişme, ancak bu ReSharpers mekanik kurallar anlayışı dışındadır söz konusu kodun, semantik bağlıdır.

Senin durumunda ReSharper önerisi kodu daha da kötüleştirirdi. Sadece görmezden gel. Ya da daha iyisi: Önerilen dönüşümü kullanmayın, ancak kodun nasıl geliştirilebileceği hakkında biraz düşünün. Aynı değişkene birden fazla zaman atayabileceğinizi, ancak yalnızca son atama etkisinin geçerli olduğuna dikkat edin; Bu does bir böcek gibi görünüm. ReSharpers önerisi hatayı düzeltmiyor, ancak şüpheli bir mantığın devam ettiğini biraz daha açık hale getiriyor.


0

Buradaki en büyük nokta, döngünün amacının döngü içindeki akışı organize etmenin en iyi yolunu belirlediğini düşünüyorum. Kalan döngüden önce bazı elementleri filtreliyorsanız, continueerken çıkmak mantıklıdır. Bununla birlikte, bir döngü içinde farklı işlem türleri yapıyorsanız, bunu ifgerçekten belirgin kılmak daha anlamlı olabilir :

for(obj in objectList) {
    if(obj == null) continue;
    if(obj.getColour().equals(Color.BLUE)) {
        // Handle blue objects
    } else {
        // Handle non-blue objects
    }
}

Java Akışları veya diğer işlevsel deyimlerde, filtrelemeyi döngüden, aşağıdakileri kullanarak ayırabileceğinizi unutmayın:

items.stream()
    .filter(i -> (i != null))
    .filter(i -> i.getColour().equals(Color.BLUE))
    .forEach(i -> {
        // Process blue objects
    });

Bu arada, bu, ( unless) tek tek ifadelere uygulandığında ( ) okunması çok kolay bulduğum aşağıdaki kod türüne izin veren Perl'in tersine çevrilmesiyle ilgili sevdiğim şeylerden biri :

foreach my $item (@items) {
    next unless defined $item;
    carp "Only blue items are supported! Failed on item $item" 
       unless ($item->get_colour() eq 'blue');
    ...
}
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.