Apakah ini membenarkan pernyataan goto?


15

Saya menemukan pertanyaan ini beberapa detik yang lalu, dan saya menarik beberapa materi dari sana: Apakah ada nama untuk konstruksi 'break n'?

Ini tampaknya menjadi cara rumit yang tidak perlu bagi orang-orang untuk menginstruksikan program untuk keluar dari double-nested for loop:

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

Saya tahu buku-buku teks suka mengatakan pernyataan goto adalah iblis, dan saya sendiri tidak suka sama sekali, tetapi apakah ini membenarkan pengecualian terhadap aturan tersebut?

Saya mencari jawaban yang berhubungan dengan n-nested for loop.

CATATAN: Apakah Anda menjawab ya, tidak, atau di suatu tempat di antara, jawaban yang benar-benar tertutup tidak diterima. Terutama jika jawabannya tidak, maka berikan alasan yang baik dan sah mengapa (yang tidak terlalu jauh dari peraturan Stack Exchange).


2
Jika Anda dapat menulis kondisi loop dalam for (j = 10; j > 0 && !broken; j--)maka Anda tidak perlu break;pernyataan itu. Jika Anda memindahkan deklarasi brokenke sebelum loop luar dimulai, maka jika itu bisa ditulis for (i = 0; i < 10 && !broken; i++)maka saya pikir tidak ada break; yang diperlukan.
FrustratedWithFormsDesigner

8
Contoh yang diberikan adalah dari C # - referensi C # pada goto: goto (C # Reference) - "Pernyataan goto juga berguna untuk keluar dari loop yang bersarang."

OMG, saya tidak pernah tahu c # punya pernyataan goto! Hal-hal yang Anda pelajari di StackExchange. (Saya tidak ingat kapan terakhir kali saya berharap untuk kemampuan pernyataan goto, sepertinya tidak pernah muncul).
John Wu

6
IMHO itu benar-benar tergantung pada "agama pemrograman" Anda dan moonphase saat ini. Bagi saya (dan sebagian besar orang di sekitar saya) boleh saja menggunakan goto untuk keluar dari sarang yang dalam jika loop agak kecil, dan alternatifnya adalah pemeriksaan berlebihan terhadap variabel bool yang diperkenalkan hanya agar kondisi loop terputus. Ini lebih ketika Anda ingin keluar setengah tubuh, atau ketika setelah loop paling dalam ada beberapa kode yang perlu memeriksa bool juga.
PlasmaHH

1
@ JohnWu gotosebenarnya diperlukan dalam saklar C # untuk beralih ke kasus lain setelah mengeksekusi kode apa pun di dalam kasus pertama. Itu adalah satu-satunya kasus saya menggunakan goto.
Dan Lyons

Jawaban:


33

Kebutuhan nyata untuk pernyataan masuk muncul dari Anda memilih ekspresi kondisional yang buruk untuk loop .

Anda menyatakan bahwa Anda ingin lingkaran luar terus berlanjut selama i < 10dan yang terdalam terus berlanjut selama j > 0.

Tetapi pada kenyataannya bukan itu yang Anda inginkan , Anda tidak memberi tahu loop kondisi sebenarnya yang Anda ingin mereka evaluasi, maka Anda ingin menyelesaikannya dengan menggunakan break atau goto.

Jika Anda memberi tahu loop niat sebenarnya Anda untuk memulai , maka tidak perlu istirahat atau kebagian.

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}

Lihat komentar saya di jawaban lain. Kode asli dicetak idi akhir loop luar, jadi penambahan arus pendek penting untuk membuat kode terlihat 100% setara. Saya tidak mengatakan apa-apa dalam jawaban Anda salah, saya hanya ingin menunjukkan bahwa melanggar loop segera adalah merupakan aspek penting dari kode.
pswg

1
@ pswg Saya tidak melihat kode pencetakan idi akhir loop luar dalam pertanyaan OP.
Tulains Córdova

OP mereferensikan kode dari pertanyaan saya di sini , tetapi mengabaikan bagian itu. Saya tidak memilih Anda, BTW. Jawabannya sepenuhnya benar sehubungan dengan memilih kondisi loop yang benar.
pswg

3
@ pswg Saya kira jika Anda benar-benar benar-benar sangat ingin membenarkan goto, Anda dapat membuat perangkat masalah yang membutuhkan satu, di sisi lain, saya telah pemrograman secara profesional selama dua dekade dan tidak memiliki satu pun masalah dunia nyata yang perlu satu.
Tulains Córdova

1
Tujuan dari kode itu tidak memberikan kasus penggunaan yang valid untuk goto(saya yakin ada yang lebih baik), bahkan jika itu memunculkan pertanyaan yang tampaknya menggunakannya seperti itu, tetapi untuk menggambarkan tindakan dasar break(n)dalam bahasa yang tidak t mendukungnya.
pswg

34

Dalam hal ini, Anda bisa mengubah kode menjadi rutin yang terpisah, dan kemudian hanya returndari loop dalam. Itu akan meniadakan kebutuhan untuk keluar dari lingkaran luar:

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}

2
Artinya, ya, apa yang Anda perlihatkan adalah cara rumit yang tidak perlu untuk melepaskan diri dari loop bersarang, tetapi tidak, ada cara untuk refactor yang tidak membuat goto lebih menarik.
Roger

2
Hanya sebuah catatan: dalam kode asli ia mencetak isetelah akhir lingkaran luar. Jadi untuk benar-benar mencerminkan itu iadalah nilai penting, di sini return true;dan return false;harus sama-sama return i;.
pswg

+1, baru saja memposting jawaban ini, dan yakin ini seharusnya jawaban yang diterima. Saya tidak percaya jawaban yang diterima diterima karena metode itu hanya bekerja untuk subset spesifik dari algoritma nested loop, dan tidak selalu membuat kode lebih bersih. Metode dalam jawaban ini bekerja secara umum.
Ben Lee

6

Tidak, saya pikir tidak gotoperlu. Jika Anda menulisnya seperti ini:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

Memiliki &&!brokenpada kedua loop memungkinkan Anda keluar dari kedua loop ketika i==j.

Bagi saya, pendekatan ini lebih disukai. Kondisi lingkaran sangat jelas: i<10 && !broken.

Versi yang berfungsi dapat dilihat di sini: http://rextester.com/KFMH48414

Kode:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

Keluaran:

loop terputus ketika saya == j == 1
2
0

Mengapa Anda bahkan menggunakan brokendalam contoh pertama Anda sama sekali ? Cukup gunakan break pada loop dalam. Juga, jika Anda benar-benar harus menggunakan broken, mengapa menyatakannya di luar loop? Nyatakan saja di dalam iloop. Adapun whileloop, tolong jangan gunakan itu. Jujur saya pikir itu berhasil menjadi lebih mudah dibaca daripada versi goto.
luiscubal

@luiscubal: Variabel bernama brokendigunakan karena saya tidak suka menggunakan breakpernyataan (kecuali dalam switch-case, tapi hanya itu). Ini dinyatakan di luar loop luar jika Anda ingin memutuskan SEMUA loop ketika i==j. Anda benar, secara umum, brokenhanya perlu dinyatakan sebelum loop terluar bahwa itu akan rusak.
FrustratedWithFormsDesigner

Dalam pembaruan Anda, i==2di akhir loop. Kondisi keberhasilan juga harus memotong arus kenaikan jika harus 100% setara dengan a break 2.
pswg

2
Saya pribadi lebih broken = (j == i)suka daripada jika dan, jika bahasa memungkinkan, menempatkan deklarasi yang rusak di dalam inisialisasi loop luar. Meskipun sulit untuk mengatakan hal-hal ini untuk contoh khusus ini, karena seluruh loop adalah no-op (tidak mengembalikan apa-apa dan tidak memiliki efek samping) dan jika itu melakukan sesuatu, itu mungkin melakukan itu di dalam if (walaupun saya masih broken = (j ==i); if broken { something(); }lebih suka pribadi)
Martijn

@ Martijn Dalam kode asli saya itu mencetak isetelah akhir lingkaran luar. Dengan kata lain, satu-satunya pemikiran yang sangat penting adalah kapan tepatnya ia keluar dari lingkaran luar.
pswg

3

Jawaban singkatnya adalah "itu tergantung". Saya menganjurkan untuk memilih tentang keterbacaan dan pemahaman kode Anda. Cara goto mungkin lebih mudah dibaca daripada mengutak-atik kondisi, atau sebaliknya. Anda juga dapat mempertimbangkan prinsip kejutan terkecil, pedoman yang digunakan di toko / proyek dan konsistensi basis kode. Sebagai contoh, goto untuk meninggalkan switch atau untuk penanganan kesalahan adalah praktik umum dalam basis kode kernel Linux. Perhatikan juga bahwa beberapa programmer mungkin mengalami masalah dalam membaca kode Anda (baik dibesarkan dengan mantra "keburukan jahat", atau hanya tidak dipelajari karena guru mereka berpikir demikian) dan beberapa dari mereka bahkan mungkin "menghakimi Anda".

Secara umum, goto yang bergerak lebih jauh dalam fungsi yang sama sering diterima, terutama jika lompatannya tidak terlalu lama. Kasus penggunaan Anda untuk meninggalkan nested loop adalah salah satu contoh goto yang "tidak terlalu jahat".


2

Dalam kasus Anda, goto sudah cukup untuk perbaikan sehingga terlihat menggoda, tetapi itu bukan perbaikan yang layak melanggar aturan untuk tidak menggunakannya dalam basis kode Anda.

Pikirkan pengulas masa depan Anda: ia harus berpikir, "Apakah orang ini pembuat kode yang buruk atau apakah ini sebenarnya merupakan kasus di mana goto itu sepadan? Biarkan saya mengambil 5 menit untuk memutuskan ini untuk diri saya sendiri atau penelitian pada Internet." Mengapa Anda melakukan itu pada pembuat kode masa depan Anda ketika Anda tidak dapat menggunakan sepenuhnya goto?

Secara umum mentalitas ini berlaku untuk semua kode "buruk" dan bahkan mendefinisikannya: jika berfungsi sekarang, dan bagus dalam hal ini, tetapi menciptakan upaya untuk memverifikasi itu benar, yang di era ini goto akan selalu dilakukan, maka jangan lakukan.

Yang sedang berkata, ya, Anda menghasilkan salah satu kasus yang sedikit lebih sulit. Kamu boleh:

  • gunakan struktur kontrol yang lebih kompleks, seperti bendera boolean, untuk keluar dari kedua loop
  • letakkan lingkaran dalam di dalam rutinitasnya sendiri (kemungkinan ideal)
  • letakkan kedua loop dalam rutin dan kembali alih-alih putus

Sangat sulit bagi saya untuk membuat kasus di mana loop ganda tidak begitu kohesif sehingga seharusnya tidak menjadi rutinitas sendiri.

Ngomong-ngomong, diskusi terbaik tentang ini yang pernah saya lihat adalah Kode Lengkap Steve McConnell . Jika Anda senang memikirkan masalah-masalah ini secara kritis, saya sangat merekomendasikan membaca, bahkan secara langsung meskipun sangat luas.


1
Ini adalah tugas programmer kami untuk membaca dan memahami kode. Bukan hanya kode sederhana kecuali itu adalah sifat basis kode Anda. Ada, dan akan selalu menjadi pengecualian untuk generalitas (bukan aturan) terhadap penggunaan gotos karena biaya yang dipahami. Setiap kali manfaatnya melebihi biaya adalah ketika goto harus digunakan; seperti halnya untuk semua keputusan pengkodean dalam rekayasa perangkat lunak.
dietbuddha

1
@dietbuddha ada biaya dalam melanggar konvensi pengkodean perangkat lunak yang diterima yang harus dipertimbangkan secara memadai. Ada biaya dalam meningkatkan keragaman struktur kontrol yang digunakan dalam suatu program bahkan jika struktur kontrol ini mungkin cocok untuk suatu situasi. Ada biaya dalam memecahkan analisis kode statis. Jika ini semua masih layak maka siapa saya untuk berdebat.
djechlin

1

TLD; DR: Tidak secara spesifik, ya secara umum

Untuk contoh spesifik yang Anda gunakan, saya akan mengatakan tidak karena alasan yang sama seperti yang ditunjukkan oleh banyak orang lain. Anda dapat dengan mudah memperbaiki kode menjadi bentuk yang setara baik yang menghilangkan kebutuhan untuk goto.

Secara umum pelarangan terhadap gotoadalah generalisasi berdasarkan sifat yang dipahami gotodan biaya menggunakannya. Bagian dari rekayasa perangkat lunak adalah membuat keputusan implementasi. Justifikasi dari keputusan-keputusan itu terjadi dengan menimbang biaya terhadap manfaat dan kapan pun manfaatnya melebihi biaya pelaksanaannya dibenarkan. Kontroversi muncul karena penilaian yang berbeda, baik biaya maupun manfaatnya.

Intinya adalah akan selalu ada pengecualian di mana a gotodibenarkan, tetapi hanya untuk beberapa karena penilaian yang berbeda. Sayang sekali banyak insinyur hanya mengecualikan teknik dalam ketidaktahuan sengaja karena mereka membacanya di internet daripada meluangkan waktu untuk memahami mengapa.


Bisakah Anda merekomendasikan artikel tentang pernyataan kebagian lebih lanjut yang menjelaskan biayanya?
Thys

-1

Saya rasa kasus ini tidak membenarkan pengecualian, karena ini dapat ditulis sebagai:

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
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.