while (true) ve loop kırma - anti-patern?


32

Aşağıdaki kodu göz önünde bulundurun:

public void doSomething(int input)
{
   while(true)
   {
      TransformInSomeWay(input);

      if(ProcessingComplete(input))
         break;

      DoSomethingElseTo(input);
   }
}

Bu sürecin sınırlı fakat girdi bağımlı bir adım içerdiğini varsayalım; döngü, algoritmanın bir sonucu olarak kendiliğinden sona erecek şekilde tasarlanmıştır ve süresiz olarak çalışacak şekilde tasarlanmamıştır (bir dış olay tarafından iptal edilinceye kadar). Döngünün bitip bitmeyeceğini görmek için yapılan test mantıklı bir adım kümesinin ortasında olduğundan, while döngüsü şu anda anlamlı bir şeyi kontrol etmemektedir; Bunun yerine kontrol, kavramsal algoritma içindeki "uygun" yerde gerçekleştirilir.

Bunun kötü bir kod olduğu söylendi çünkü döngü koşulu tarafından kontrol edilmeyen son koşul nedeniyle hataya daha açık. Döngüyü nasıl terk edeceğinizi anlamak daha zor ve kırılma koşulu gelecekteki değişiklikler nedeniyle yanlışlıkla atlanabileceği veya atlanabileceği için böcekleri davet edebilir.

Şimdi, kod aşağıdaki gibi yapılandırılabilir:

public void doSomething(int input)
{
   TransformInSomeWay(input);

   while(!ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
      TransformInSomeWay(input);
   }
}

Ancak bu, DRY'yi ihlal eden, koddaki bir yönteme yapılan bir çağrıyı çoğaltır; Eğer TransformInSomeWaydaha sonra başka bir metod ile ikame edilmiştir, iki arama bulunan ve değiştirilmiş (ve kod daha karmaşık bir parça daha az belirgin olabilir iki olduğu gerçeği) olması gerekir.

Ayrıca şöyle yazabilirsiniz:

public void doSomething(int input)
{
   var complete = false;
   while(!complete)
   {
      TransformInSomeWay(input);

      complete = ProcessingComplete(input);

      if(!complete) 
      {
         DoSomethingElseTo(input);          
      }
   }
}

... ama şimdi tek amacı koşul kontrolünü döngü yapısına kaydırmak olan ve aynı zamanda orijinal mantıkla aynı davranışı sağlamak için birçok kez kontrol edilmesi gereken bir değişkene sahipsin.

Bana göre, bu kodun gerçek dünyada uyguladığı algoritma göz önüne alındığında, orijinal kodun en okunaklı olduğunu söylüyorum. Kendin içinden geçiyor olsaydın, böyle düşünürdün ve algoritmaya aşina insanlar için sezgisel olurdu.

Peki hangisi "daha iyi"? mantığın döngü çevresinde yapılandırılmasıyla durum döngüsünün durum döngüsünün sorumluluğunu vermek daha iyi olur mu? Yoksa, mantığın gereklilikleri veya algoritmanın kavramsal bir tanımını belirtildiği gibi "doğal" bir şekilde yapılandırması daha iyi midir, bu döngünün yerleşik yeteneklerini atlamak anlamına gelebilir mi?


12
Muhtemelen, ancak kod örneklerine rağmen, sorulan soru IMO’nun daha kavramsal, bu da daha çok bu SE alanı ile uyumludur. Bir kalıp nedir ya da anti kalıp nedir burada sıkça tartışılır ve asıl soru budur; sonsuz bir döngü çalıştırmak ve daha sonra kötü bir şey kırmak için bir döngü yapılandırmak mı?
KeithS

3
do ... untilOnlar olmak zorunda olmadığı için yapı da döngüler bu tür için yararlıdır "astarlanmış."
Blrfl

2
Döngü ve bir buçuk durumda hala gerçekten iyi bir cevap yoksundur.
Loren Pechtel

1
“Ancak, bu, DRY'yi ihlal eden, koddaki bir yönteme yapılan çağrıyı kopyalıyor” Bu iddiaya katılmıyorum ve ilkenin yanlış anlaşılması gibi görünüyor.
Andy,

1
Açıkça " Döngünüm var ve döngü ifadesini kontrol etmiyorum" anlamına gelen (;;) için C stilini tercih etmem dışında, ilk yaklaşımınız kesinlikle doğru. Bir döngü var. Çıkıp çıkmamaya karar vermeden önce yapmanız gereken bazı işler var. Ardından, bazı koşullar yerine getirildiğinde çıkın. Sonra asıl işi yapar ve baştan başlarsın. Bu kesinlikle iyi, anlaşılması kolay, mantıklı, gereksiz ve doğru olması kolay. İkinci değişken sadece korkunç, üçüncüsü ise sadece anlamsız; Eğer dönecek döngü geri kalanı çok uzun ise korkunç dönüyor.
gnasher729

Yanıtlar:


14

İlk çözümünüze sadık kalın. Döngüler çok karışabilir ve bir veya daha fazla temiz ara sizin için, kodunuza, optimizerine ve programın performansına göre ayrıntılı bir if-ifadeleri ve değişkenleri eklemekten çok daha kolay olabilir. Her zamanki yaklaşımım, "while (true)" gibi bir şey içeren bir döngü oluşturmak ve elde edebileceğim en iyi kodlamayı elde etmektir. Çoğunlukla mola (ları) standart bir döngü kontrolü ile değiştirebilirim ve yapabilirim; sonuçta çoğu döngüler oldukça basittir. Bitiş koşulu , bahsettiğiniz nedenlerden ötürü döngü yapısı tarafından kontrol edilmelidir, ancak tamamen yeni değişkenler ekleme, if-ifadeleri ekleme ve yinelenen kod, hepsi daha da fazla hataya maruz kalacak şekilde kontrol edilmemelidir .


"if-ifadeleri ve yinelenen kod, hepsi daha da fazla eğilimli." - Evet, insanları "gelecekteki böcekler" olarak adlandırdığımda deniyorum
Pat

13

Bu sadece döngü gövdesi önemsiz değilse anlamlı bir problemdir. Vücut çok küçükse, o zaman bu şekilde analiz etmek önemsizdir ve hiç sorun olmaz.


7

Diğer çözümleri ara vermeden daha iyi bulmakta güçlük çekiyorum. Şey, yapmayı sağlayan Ada yolunu tercih ederim.

loop
  TransformInSomeWay(input);
exit when ProcessingComplete(input);
  DoSomethingElseTo(input);
end loop;

ancak çözüm, en temiz görüntü oluşturma çözümü.

POV, eksik bir kontrol yapısı olduğunda, en temiz çözümün veri akışını kullanmadan diğer kontrol yapıları ile öykünmesidir. (Benzer şekilde, kontrol yapılarını içerenlere saf veri akışı çözümlerini tercih etmek için bir veri akışı sorunum olduğunda *).

Döngüden nasıl çıkacağınızı anlamak daha zor.

Yanılmayın, zorluk çoğunlukla aynı olmasaydı tartışma gerçekleşmezdi: durum aynı ve aynı yerde mevcut.

Hangisi

while (true) {
   XXXX
if (YYY) break;
   ZZZZ;
}

ve

while (TTTT) {
    XXXX
    if (YYYY) {
        ZZZZ
    }
}

amaçlanan yapıyı daha iyi hale getirmek? İlkine oy veriyorum, şartların uyuşup uyuşmadığını kontrol etmek zorunda değilsin. (Ama çentiklerime bakın).


1
Ah, bak, bir dil yok doğrudan orta çıkış döngüler destekliyoruz! :)
mjfgates

Eksik kontrol yapılarının kontrol yapılarına benzetilmesi hakkındaki noktanı beğeniyorum. Bu muhtemelen GOTO ile ilgili sorulara da iyi bir cevap. Bir dil, anlamsal gerekliliklere uygun olduğunda bir dilin kontrol yapılarını kullanmalıdır, ancak bir algoritma başka bir şeye ihtiyaç duyuyorsa, algoritma için gereken kontrol yapısını kullanmalısınız.
supercat,

3

while (true) ve break ortak bir deyimdir. C-benzeri dillerde orta-çıkış döngüleri uygulamanın kurallı yolu. Çıkış koşulunu saklamak için bir değişken eklemek ve döngünün ikinci yarısı etrafındaki bir if () makul bir alternatiftir. Bazı dükkanlar bir veya diğerine Resmi Olarak Standartlaşabilir, ancak ikisi de üstün değildir; onlar eşdeğerdir.

Bu dillerde çalışan iyi bir programcı, birisini görürse bir bakışta neler olup bittiğini öğrenebilmelidir. İyi bir röportaj sorusu olabilir; biri her bir deyimi kullanan iki döngüyü uzatın ve aradaki farkın ne olduğunu sorun (uygun cevap: "hiçbir şey", belki de "ilavesiyle alıyorum" ama alışılmış bir şekilde BUNU kullanın).)


2

Alternatifleriniz düşündüğünüz kadar kötü değil. İyi kod şunları yapmalı:

  1. Döngünün sonlandırılması gereken koşullar altında iyi değişken isimleri / yorumları ile açıkça belirtiniz. (Kırılma koşulu gizlenmemelidir)

  2. İşlemlerin sırasının ne olduğunu açıkça belirtin. Bu, isteğe bağlı 3. işlemi ( DoSomethingElseTo(input)) if'in içine koymak iyi bir fikirdir.

Bununla birlikte, mükemmeliyetçi, pirinç cilalama içgüdülerinin çok fazla zaman tüketmesine izin vermek kolaydır. Yukarıda da belirtildiği gibi sadece ince ayar yapardım; kodu yorumlayın ve devam edin.


Mükemmeliyetçi, pirinç cilalama içgüdülerinin uzun vadede zaman kazandırdığını düşünüyorum. Umarım, uygulamada, bu tür alışkanlıklar geliştirirsiniz, böylece kodunuz mükemmeldir ve pirinç çok fazla zaman harcamadan cilalanır . Ancak fiziksel yapıdan farklı olarak, yazılım sonsuza kadar sürebilir ve sonsuz sayıda yerde kullanılabilir. % 0.00000001 bile daha hızlı, daha güvenilir, kullanılabilir ve bakımı kolay kodlardan elde edilen tasarruf büyük olabilir. (Tabii ki, benim çok-cilalı kodun en uzun eskimiş veya bu gün başkası için para kazanmak dilde, ama vermedi beni iyi alışkanlıklar yardım gelişir.)
RalphChapin

Eh ben yanlış diyemem :-) Bu ise bir görüş soru ve iyi beceriler pirinç-parlatma dışarı geldiyse: harika.
Pat

Zaten düşünmek için bir olasılık olduğunu düşünüyorum. Kesinlikle herhangi bir garanti vermek istemem. Pirinç cilalama becerilerimin becerilerimi geliştirdiğini düşünmek daha iyi hissettiriyor, bu nedenle bu konuda önyargılı olabilirim.
RalphChapin

2

İnsanlar, kullanımın kötü bir uygulama olduğunu while (true)ve çoğu zaman haklı olduğunu söylüyorlar . Senin Eğer whiledeyim karmaşık kod birçok içerir ve eğer, o zaman kodu korumak zor kalır ve basit bir hata sonsuz döngüye neden olabilecek patlak zaman belli değildir.

Örneğinizde kullanmak doğru, while(true)çünkü kodunuz açık ve anlaşılması kolay. Kodu okumak için verimsiz ve zor olmanın bir anlamı yoktur, çünkü bazı insanlar bunu kullanmak her zaman kötü bir uygulama olduğunu düşünürler while(true).

Benzer bir sorunla karşılaştım:

$i = 0
$key = $str.'0';
$key1 = $str1.'0';
while (isset($array][$key] || isset($array][$key1])
{
    if (isset($array][$key]))
    {
        // Code
    }
    else
    {
        // Code
    }
    $key = $str.++$i;
    $key1 = $str1.$i;
}

Gereksiz yere iki kez çağrılması, kodun daha kısa, daha temiz ve okunması daha kolay şekilde kullanılmasını do ... while(true)önler isset($array][$key]. else break;Sonunda sonsuz bir döngü hatası oluşması riski yoktur .

$i = -1;
do
{
    $key = $str.++$i;
    $key1 = $str1.$i;
    if (isset($array[$key]))
    {
        // Code
    }
    elseif (isset($array[$key1]))
    {
        // Code
    }
    else
        break;
} while (true);

İyi uygulamaları takip etmek ve kötü uygulamalardan kaçınmak iyidir, ancak her zaman istisnalar vardır ve açık fikirli olmak ve bu istisnaları gerçekleştirmek önemlidir.


0

Belirli bir kontrol akışının bir break ifadesi olarak doğal olarak ifade edilmesi durumunda, bir break ifadesini taklit etmek için diğer akış kontrol mekanizmalarını kullanmak esasen asla daha iyi bir seçim değildir.

(bunun, ilmeğin kısmen açılmasını ve ilmek gövdesinin aşağı doğru kaydırılmasını içerdiğini, böylece ilmeğin şartlı test ile çakıştığını unutmayın)

Döngününüzü yeniden düzenleyebilirsiniz, böylece kontrolün akışı döngünün başında koşullu bir denetim yaparak doğal olarak ifade edilir, harika. Bunun mümkün olup olmadığını düşünerek biraz zaman harcamak bile faydalı olabilir. Ama öyle değil, kare bir çiviyi yuvarlak bir deliğe takmaya çalışmayın.


0

Bununla da gidebilirsin:

public void doSomething(int input)
{
   while(TransformInSomeWay(input), !ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
   }
}

Ya da daha az kafa karıştırıcı:

bool TransformAndCheckCompletion(int &input)
{
   TransformInSomeWay(input);
   return ProcessingComplete(input))
}

public void doSomething(int input)
{
   while(TransformAndCheckCompletion(input))
   {
      DoSomethingElseTo(input);
   }
}

1
Bazı insanlar bunu sevse de, döngü koşulunun genellikle işleri yapan ifadeler yerine koşullu testler için ayrılması gerektiğini iddia ediyorum.

5
Döngü koşulunda virgül ifadesi ... Bu korkunç. Sen çok zekisin. Ve sadece bu önemsiz durumda, TransformInSomeWay'in bir ifade olarak ifade edilebildiği durumlarda çalışır.
gnasher729 11:15

0

Mantığın karmaşıklığı bozulmaz hale geldiğinde, akış kontrolü için orta döngülü aralarla sınırlandırılmamış bir döngü kullanmak aslında en mantıklı olanıdır. Aşağıdaki gibi görünen bazı kod ile bir proje var. (Döngünün sonundaki istisnaya dikkat edin.)

L: for (;;) {
    if (someBool) {
        someOtherBool = doSomeWork();
        if (someOtherBool) {
            doFinalWork();
            break L;
        }
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
            break L;
        }
        someOtherBool3 = doSomeWork3();
        if (someOtherBool3) {
            doFinalWork3();
            break L;
        }
    }
    bitOfData = getTheData();
    throw new SomeException("Unable to do something for some reason.", bitOfData);
}
progressOntoOtherThings();

Sınırsız döngü bozmadan bu mantığı yapmak için, aşağıdaki gibi bir şeyle sonuçlanacaktı:

void method() {
    if (!someBool) {
        throwingMethod();
    }
    someOtherBool = doSomeWork();
    if (someOtherBool) {
        doFinalWork();
    } else {
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
        } else {
            someOtherBool3 = doSomeWork3();
            if (someOtherBool3) {
                doFinalWork3();
            } else {
                throwingMethod();
            }
        }
    }
    progressOntoOtherThings();
}
void throwingMethod() throws SomeException {
        bitOfData = getTheData();
        throw new SomeException("Unable to do something for some reason.", bitOfData);
}

Öyleyse istisna şimdi yardımcı bir yöntemle atılıyor. Ayrıca oldukça fazla if ... elseyuvalama var. Bu yaklaşımdan memnun değilim. Dolayısıyla ilk kalıbın en iyisi olduğunu düşünüyorum.


Aslında, bu soruyu SO'da sordum ve alevleri düşürdüm ... stackoverflow.com/questions/47253486/…
intrepidis
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.