while (true) dan loop-breaking - anti-pattern?


32

Pertimbangkan kode berikut:

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

      if(ProcessingComplete(input))
         break;

      DoSomethingElseTo(input);
   }
}

Asumsikan bahwa proses ini melibatkan sejumlah langkah yang terbatas tetapi bergantung pada input; loop dirancang untuk berakhir dengan sendirinya sebagai akibat dari algoritma, dan tidak dirancang untuk berjalan tanpa batas waktu (sampai dibatalkan oleh peristiwa luar). Karena tes untuk melihat apakah loop harus diakhiri berada di tengah serangkaian langkah logis, loop sementara itu sendiri saat ini tidak memeriksa sesuatu yang berarti; Sebaliknya cek dilakukan di tempat "layak" dalam algoritma konseptual.

Saya diberitahu bahwa ini adalah kode yang buruk, karena lebih rentan terhadap bug karena kondisi akhir tidak diperiksa oleh struktur loop. Lebih sulit untuk mengetahui bagaimana Anda keluar dari loop, dan dapat mengundang bug karena kondisi kerusakan mungkin dilewati atau dihilangkan secara tidak sengaja karena perubahan di masa mendatang.

Sekarang, kode dapat disusun sebagai berikut:

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

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

Namun, ini menggandakan panggilan ke metode dalam kode, melanggar KERING; jika TransformInSomeWaykemudian diganti dengan metode lain, kedua panggilan harus ditemukan dan diubah (dan fakta bahwa ada dua mungkin kurang jelas dalam potongan kode yang lebih kompleks).

Anda juga bisa menulis seperti:

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

      complete = ProcessingComplete(input);

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

... tetapi Anda sekarang memiliki variabel yang hanya bertujuan untuk mengubah kondisi-memeriksa ke struktur lingkaran, dan juga harus diperiksa beberapa kali untuk memberikan perilaku yang sama dengan logika asli.

Bagi saya, saya katakan bahwa dengan algoritma yang diterapkan kode ini di dunia nyata, kode asli adalah yang paling mudah dibaca. Jika Anda mengalaminya sendiri, ini adalah cara Anda akan memikirkannya, dan itu akan menjadi intuitif bagi orang-orang yang terbiasa dengan algoritma.

Jadi, mana yang "lebih baik"? Apakah lebih baik memberikan tanggung jawab memeriksa kondisi ke loop sementara dengan menyusun logika di sekitar loop? Atau lebih baik untuk menyusun logika dengan cara "alami" seperti yang ditunjukkan oleh persyaratan atau deskripsi konseptual dari algoritma, meskipun itu mungkin berarti melewati kemampuan bawaan loop?


12
Mungkin, tetapi terlepas dari sampel kode, pertanyaan yang diajukan adalah IMO lebih konseptual, yang lebih sesuai dengan bidang SE ini. Apa itu pola atau anti-pola sering dibahas di sini, dan itulah pertanyaan sesungguhnya; apakah menyusun loop untuk dijalankan tanpa batas dan kemudian memutusnya merupakan hal yang buruk?
KeithS

3
The do ... untilmembangun juga berguna untuk jenis-jenis loop karena mereka tidak harus "prima."
Blrfl

2
Kasing-dan-a-setengah masih tidak memiliki jawaban yang benar-benar baik.
Loren Pechtel

1
"Namun, ini duplikat panggilan ke metode dalam kode, melanggar KERING" Saya tidak setuju dengan pernyataan itu dan sepertinya kesalahpahaman prinsip.
Andy

1
Selain saya lebih suka gaya-C untuk (;;) yang secara eksplisit berarti "Saya memiliki loop, dan saya tidak memeriksa dalam pernyataan loop", pendekatan pertama Anda benar-benar benar. Anda memiliki satu lingkaran. Ada beberapa pekerjaan yang harus dilakukan sebelum Anda dapat memutuskan apakah akan keluar. Kemudian Anda keluar jika beberapa kondisi terpenuhi. Kemudian Anda melakukan pekerjaan yang sebenarnya, dan mulai dari awal. Benar-benar baik, mudah dimengerti, logis, tidak berlebihan, dan mudah untuk diperbaiki. Varian kedua hanya mengerikan, sedangkan yang ketiga tidak ada gunanya; berubah menjadi mengerikan jika sisa loop yang masuk ke if sangat panjang.
gnasher729

Jawaban:


14

Tetap dengan solusi pertama Anda. Loop dapat menjadi sangat terlibat dan satu atau lebih istirahat bersih dapat jauh lebih mudah pada Anda, orang lain melihat kode Anda, pengoptimal, dan kinerja program daripada menguraikan pernyataan jika dan variabel tambahan. Pendekatan saya yang biasa adalah mengatur loop dengan sesuatu seperti "while (true)" dan mendapatkan pengkodean terbaik yang saya bisa. Seringkali saya dapat dan kemudian mengganti break dengan kontrol loop standar; kebanyakan loop cukup sederhana. Kondisi akhir harus diperiksa oleh struktur loop untuk alasan yang Anda sebutkan, tetapi tidak dengan biaya menambahkan seluruh variabel baru, menambahkan pernyataan if, dan kode berulang, yang semuanya bahkan lebih rentan terhadap bug.


"Jika-pernyataan, dan kode berulang, yang semuanya lebih rentan terhadap bug." - ya ketika mencoba orang yang saya sebut "bug masa depan"
Pat

13

Ini hanya masalah yang berarti jika loop body non-sepele. Jika tubuhnya sangat kecil, maka menganalisanya seperti ini sepele, dan tidak masalah sama sekali.


7

Saya kesulitan menemukan solusi lain yang lebih baik daripada yang rusak. Yah, saya lebih suka cara Ada yang memungkinkan untuk dilakukan

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

tetapi solusi break adalah rendering terbersih.

POV saya adalah bahwa ketika ada struktur kontrol yang hilang, solusi terbersih adalah menirunya dengan struktur kontrol lainnya, tidak menggunakan aliran data. (Dan juga, ketika saya memiliki masalah aliran data untuk lebih memilih solusi aliran data murni daripada yang melibatkan struktur kontrol *).

Lebih sulit untuk mengetahui bagaimana Anda keluar dari loop,

Jangan salah, diskusi tidak akan terjadi jika kesulitannya tidak sama: kondisinya sama dan ada di tempat yang sama.

Yang mana dari

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

dan

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

membuat struktur yang dimaksudkan lebih baik? Saya memberikan suara untuk yang pertama, Anda tidak perlu memeriksa bahwa kondisinya cocok. (Tapi lihat lekukan saya).


1
Oh, lihat, bahasa yang tidak langsung mendukung loop mid-keluar! :)
mjfgates

Saya suka poin Anda tentang meniru struktur kontrol yang hilang dengan struktur kontrol. Itu mungkin jawaban yang bagus untuk pertanyaan terkait GOTO juga. Seseorang harus menggunakan struktur kontrol bahasa setiap kali mereka memenuhi persyaratan semantik, tetapi jika suatu algoritma membutuhkan sesuatu yang lain seseorang harus menggunakan struktur kontrol yang diperlukan oleh algoritma.
supercat

3

sementara (benar) dan istirahat adalah idiom yang umum. Ini adalah cara kanonik untuk menerapkan loop keluar-keluar dalam bahasa mirip-C. Menambahkan variabel untuk menyimpan kondisi keluar dan if () di sekitar paruh kedua loop adalah alternatif yang masuk akal. Beberapa toko mungkin secara resmi melakukan Standarisasi pada satu atau yang lain, tetapi tidak ada yang unggul; mereka setara.

Seorang programmer yang baik yang bekerja dalam bahasa-bahasa ini harus dapat mengetahui apa yang terjadi secara sekilas jika dia melihat salah satunya. Ini mungkin membuat pertanyaan wawancara kecil yang bagus; berikan seseorang dua loop, satu menggunakan setiap idiom, dan tanyakan kepada mereka apa perbedaannya (jawaban yang tepat: "tidak ada", dengan mungkin tambahan "tapi saya biasanya menggunakan satu itu.")


2

Alternatif Anda tidak seburuk yang Anda kira. Kode yang baik harus:

  1. Tunjukkan dengan jelas dengan nama variabel yang baik / komentar dalam kondisi apa loop harus diakhiri. (Kondisi melanggar tidak harus disembunyikan)

  2. Tunjukkan dengan jelas apa urutan operasi yang dilakukan. Di sinilah menempatkan operasi ke-3 opsional ( DoSomethingElseTo(input)) di dalam if adalah ide yang bagus.

Yang mengatakan, mudah untuk membiarkan naluri perfeksionis, pemoles kuningan menghabiskan banyak waktu. Saya hanya akan men-tweak jika seperti yang disebutkan di atas; komentar kode dan lanjutkan.


Saya pikir perfeksionis, naluri pemolesan kuningan menghemat waktu dalam jangka panjang. Semoga dengan latihan, Anda mengembangkan kebiasaan seperti itu sehingga kode Anda sempurna dan kuningannya dipoles tanpa menghabiskan banyak waktu. Tetapi tidak seperti konstruksi fisik, perangkat lunak dapat bertahan selamanya dan digunakan di tempat yang tak terbatas. Penghematan dari kode yang bahkan 0,00000001% lebih cepat, lebih dapat diandalkan, dapat digunakan, dan dapat dipelihara bisa besar. (Tentu saja, sebagian besar kode saya yang banyak dipoles adalah dalam bahasa yang lama ketinggalan zaman atau menghasilkan uang untuk orang lain belakangan ini, tetapi hal itu membantu saya mengembangkan kebiasaan baik.)
RalphChapin

Ya saya tidak bisa menyebut Anda salah :-) Ini adalah pertanyaan pendapat dan jika keterampilan yang baik keluar dari pemoles kuningan: bagus.
Pat

Saya pikir ini kemungkinan untuk dipertimbangkan. Saya tentu tidak ingin menawarkan jaminan apa pun. Itu membuat saya merasa lebih baik untuk berpikir bahwa pemoles kuningan saya meningkatkan keterampilan saya, jadi saya mungkin berprasangka tentang hal ini.
RalphChapin

2

Orang-orang mengatakan itu adalah praktik yang buruk untuk digunakan while (true), dan seringkali mereka benar. Jika whilepernyataan Anda mengandung banyak kode yang rumit dan tidak jelas kapan Anda keluar dari if, maka Anda dibiarkan dengan sulit untuk mempertahankan kode dan bug sederhana dapat menyebabkan loop tak terbatas.

Dalam contoh Anda, Anda benar untuk digunakan while(true)karena kode Anda jelas dan mudah dipahami. Tidak ada gunanya membuat kode yang tidak efisien dan lebih sulit untuk dibaca, hanya karena beberapa orang menganggap itu selalu praktik yang buruk untuk digunakan while(true).

Saya dihadapkan dengan masalah yang sama:

$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;
}

Menggunakan do ... while(true)menghindari yang isset($array][$key]tidak perlu dipanggil dua kali, kode itu lebih pendek, lebih bersih, dan lebih mudah dibaca. Sama sekali tidak ada risiko bug loop tak terbatas diperkenalkan dengan else break;di akhir.

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

Sangat baik untuk mengikuti praktik yang baik dan menghindari praktik yang buruk tetapi selalu ada pengecualian dan penting untuk tetap berpikiran terbuka dan menyadari pengecualian itu.


0

Jika aliran kontrol tertentu secara alami dinyatakan sebagai pernyataan istirahat, pada dasarnya tidak pernah menjadi pilihan yang lebih baik untuk menggunakan mekanisme kontrol aliran lainnya untuk meniru pernyataan istirahat.

(perhatikan bahwa ini termasuk membuka sebagian loop dan menggeser badan loop ke bawah sehingga loop mulai bertepatan dengan uji kondisional)

Jika Anda dapat mengatur ulang loop Anda sehingga aliran kontrol diekspresikan secara alami dengan memiliki pemeriksaan bersyarat pada awal loop, bagus. Bahkan mungkin layak meluangkan waktu untuk memikirkan apakah itu mungkin. Tapi tidak, jangan mencoba memasukkan pasak persegi ke dalam lubang bundar.


0

Anda juga bisa menggunakan ini:

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

Atau kurang membingungkan:

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

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

1
Sementara beberapa orang menyukainya, saya menegaskan bahwa kondisi loop umumnya harus disediakan untuk tes bersyarat, daripada pernyataan yang melakukan hal-hal.

5
Ekspresi koma dalam kondisi loop ... Itu mengerikan. Kamu terlalu pintar. Dan itu hanya berfungsi dalam kasus sepele ini di mana TransformInSomeWay dapat diekspresikan sebagai ekspresi.
gnasher729

0

Ketika kompleksitas logika menjadi sulit maka menggunakan loop tanpa batas dengan jeda putaran untuk kontrol aliran benar-benar masuk akal. Saya punya proyek dengan beberapa kode yang terlihat seperti berikut. (Perhatikan pengecualian di akhir loop.)

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();

Untuk melakukan logika ini tanpa melanggar loop tanpa batas, saya akan berakhir dengan sesuatu seperti berikut:

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);
}

Jadi pengecualian sekarang dilemparkan ke dalam metode pembantu. Juga memiliki banyak if ... elsesarang. Saya tidak puas dengan pendekatan ini. Karena itu saya pikir pola pertama adalah yang terbaik.


Memang, saya mengajukan pertanyaan ini pada SO dan ditembak jatuh ke dalam api ... stackoverflow.com/questions/47253486/…
intrepidis
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.