Apakah menguntungkan menggunakan 'goto' dalam bahasa yang mendukung loop dan fungsi? Jika demikian, mengapa?


201

Saya sudah lama berada di bawah kesan yang gotoseharusnya tidak pernah digunakan jika memungkinkan. Sementara membaca libavcodec (yang ditulis dalam C) tempo hari, saya perhatikan banyak kegunaannya. Apakah pernah menguntungkan untuk digunakan gotodalam bahasa yang mendukung loop dan fungsi? Jika demikian, mengapa?

Jawaban:


242

Ada beberapa alasan untuk menggunakan pernyataan "goto" yang saya sadari (beberapa sudah berbicara dengan ini):

Keluar dari suatu fungsi dengan bersih

Seringkali dalam suatu fungsi, Anda dapat mengalokasikan sumber daya dan harus keluar di banyak tempat. Pemrogram dapat menyederhanakan kode mereka dengan meletakkan kode pembersihan sumber daya di akhir fungsi, dan semua "titik keluar" dari fungsi akan kebagian label pembersihan. Dengan cara ini, Anda tidak perlu menulis kode pembersihan di setiap "titik keluar" dari fungsi.

Keluar dari loop bersarang

Jika Anda berada dalam loop bersarang dan harus keluar dari semua loop, goto dapat membuat ini jauh lebih bersih dan lebih sederhana daripada pernyataan break dan jika-cek.

Peningkatan kinerja tingkat rendah

Ini hanya valid dalam kode perf-critical, tetapi pernyataan goto mengeksekusi sangat cepat dan dapat memberi Anda dorongan ketika bergerak melalui suatu fungsi. Namun ini adalah pedang bermata dua, karena kompiler biasanya tidak dapat mengoptimalkan kode yang berisi gotos.

Perhatikan bahwa dalam semua contoh ini, foto dibatasi untuk cakupan fungsi tunggal.


15
Cara yang tepat untuk keluar dari loop bersarang adalah dengan refactor loop dalam menjadi metode yang terpisah.
jason

129
@Jason - Bah. Itu banyak banteng. Mengganti gotodengan returnhanya konyol. Ini bukan "refactoring" apa pun, itu hanya "penggantian nama" sehingga orang-orang yang tumbuh dalam gotolingkungan yang tertekan (yaitu kita semua) merasa lebih baik menggunakan apa yang secara moral setara dengan a goto. Saya lebih suka melihat loop di mana saya menggunakannya dan melihat sedikit goto, yang dengan sendirinya hanya alat , daripada melihat seseorang telah memindahkan loop di suatu tempat yang tidak terkait hanya untuk menghindari a goto.
Chris Lutz

18
Ada beberapa tempat di mana gotos penting: misalnya, lingkungan C ++ pengecualian-kurang. Dalam sumber Silverlight kami memiliki puluhan ribu (atau lebih) pernyataan goto untuk fungsi aman yang ada melalui penggunaan makro - codec media utama dan perpustakaan sering bekerja melalui nilai balik dan tidak pernah ada pengecualian, dan sulit untuk menggabungkan mekanisme penanganan kesalahan ini di cara pemain tunggal.
Jeff Wilcox

79
Itu perlu dicatat bahwa semua break, continue, returnpada dasarnya goto, hanya dalam kemasan yang bagus.
el.pescado

16
Saya tidak melihat bagaimana do{....}while(0)seharusnya ide yang lebih baik daripada goto, kecuali untuk fakta itu bekerja di Jawa.
Daftar Jeremy

906

Setiap orang yang anti- gotomengutip, secara langsung atau tidak langsung, artikel GoTo Dianggap Berbahaya oleh Edsger Dijkstra untuk memperkuat posisi mereka. Sayang sekali artikel Dijkstra hampir tidak ada hubungannya dengan cara gotopernyataan digunakan hari ini dan dengan demikian apa yang dikatakan artikel itu memiliki sedikit atau tidak dapat diterapkan pada adegan pemrograman modern. The gotopingir meme-kurang sekarang agama, ke kanan ke kitab sucinya didikte dari atas, imam tinggi dan pengucilan (atau lebih buruk) dari bidah dirasakan.

Mari kita masukkan makalah Dijkstra ke dalam konteks untuk menjelaskan sedikit tentang masalah ini.

Ketika Dijkstra menulis makalahnya, bahasa-bahasa populer saat itu adalah bahasa prosedural yang tidak terstruktur seperti BASIC, FORTRAN (dialek-dialek sebelumnya) dan berbagai bahasa rakitan. Itu cukup umum bagi orang-orang yang menggunakan bahasa tingkat tinggi untuk melompati seluruh basis kode mereka dalam untaian eksekusi yang memutarbalikkan yang memunculkan istilah "kode spageti". Anda dapat melihat ini dengan melompat ke game Trek klasik yang ditulis oleh Mike Mayfield dan mencoba mencari cara bagaimana hal-hal bekerja. Luangkan waktu beberapa saat untuk memeriksanya.

INI adalah "penggunaan pernyataan go" yang tidak terkendali bahwa Dijkstra mencerca dalam makalahnya pada tahun 1968. INI adalah lingkungan tempat tinggalnya yang membuatnya menulis makalah itu. Kemampuan untuk melompat ke mana pun Anda suka dalam kode Anda pada titik mana pun yang Anda suka adalah apa yang dia kritik dan minta dihentikan. Membandingkannya dengan kekuatan anemia gotodi C atau bahasa lain yang lebih modern seperti itu cukup mudah terjadi.

Saya sudah bisa mendengar nyanyian para kultus yang terangkat saat mereka menghadapi bidat. "Tapi," mereka akan menyanyikan, "Anda dapat membuat kode sangat sulit dibaca gotodalam C." Oh ya? Anda dapat membuat kode menjadi sangat sulit untuk dibaca tanpa goto. Seperti yang ini:

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

Tidak gototerlihat, jadi pasti mudah dibaca, bukan? Atau bagaimana dengan yang ini:

a[900];     b;c;d=1     ;e=1;f;     g;h;O;      main(k,
l)char*     *l;{g=      atoi(*      ++l);       for(k=
0;k*k<      g;b=k       ++>>1)      ;for(h=     0;h*h<=
g;++h);     --h;c=(     (h+=g>h     *(h+1))     -1)>>1;
while(d     <=g){       ++O;for     (f=0;f<     O&&d<=g
;++f)a[     b<<5|c]     =d++,b+=    e;for(      f=0;f<O
&&d<=g;     ++f)a[b     <<5|c]=     d++,c+=     e;e= -e
;}for(c     =0;c<h;     ++c){       for(b=0     ;b<k;++
b){if(b     <k/2)a[     b<<5|c]     ^=a[(k      -(b+1))
<<5|c]^=    a[b<<5      |c]^=a[     (k-(b+1     ))<<5|c]
;printf(    a[b<<5|c    ]?"%-4d"    :"    "     ,a[b<<5
|c]);}      putchar(    '\n');}}    /*Mike      Laman*/

Juga gototidak ada. Karena itu harus dapat dibaca.

Apa poin saya dengan contoh-contoh ini? Bukan fitur bahasa yang membuat kode tidak terbaca dan tidak dapat dipelihara. Bukan sintaks yang melakukannya. Programmer buruk yang menyebabkan ini. Dan programmer yang buruk, seperti yang Anda lihat pada item di atas, dapat membuat fitur bahasa apa pun tidak dapat dibaca dan tidak dapat digunakan. Seperti forloop di sana. (Kamu bisa melihatnya, kan?)

Agar adil, beberapa konstruksi bahasa lebih mudah disalahgunakan daripada yang lain. Namun, jika Anda seorang programmer C, saya akan mengintip lebih dekat sekitar 50% dari penggunaan #definejauh sebelum saya pergi melawan goto!

Jadi, bagi mereka yang sudah repot membaca sejauh ini, ada beberapa poin penting yang perlu diperhatikan.

  1. Makalah Dijkstra tentang gotopernyataan ditulis untuk lingkungan pemrograman gotoyang jauh lebih berpotensi merusak daripada di kebanyakan bahasa modern yang bukan assembler.
  2. Secara otomatis membuang semua penggunaan gotokarena ini adalah rasional seperti mengatakan "Aku mencoba bersenang-senang sekali tetapi tidak suka jadi sekarang aku menentangnya".
  3. Ada penggunaan sah dari pernyataan (anemia) modern gotodalam kode yang tidak dapat digantikan secara memadai oleh konstruksi lain.
  4. Tentu saja ada penggunaan tidak sah dari pernyataan yang sama.
  5. Ada juga, penggunaan tidak sah dari pernyataan kontrol modern seperti " godo" kekejian di mana doloop selalu salah diputus dari penggunaan breakdi tempat a goto. Ini sering lebih buruk daripada penggunaan bijaksana goto.

42
@pocjoc: Menulis optimasi berekor-ekor di C, misalnya. Ini muncul dalam implementasi penerjemah / runtime bahasa fungsional, tetapi mewakili kategori masalah yang menarik. Kebanyakan kompiler yang ditulis dalam C menggunakan goto untuk alasan ini. Ini adalah penggunaan niche yang tidak terjadi di sebagian besar situasi, tentu saja, tetapi dalam situasi itu diperlukan banyak akal untuk digunakan.
zxq9

66
Mengapa semua orang berbicara tentang makalah Dijkstra "Pergi untuk dianggap berbahaya" tanpa menyebutkan jawaban Knuth padanya, "Pemrograman terstruktur dengan pergi ke pernyataan"?
Oblomov

22
@ Nietzche-jou "ini adalah pria jerami yang sangat terang-terangan [...]" Dia dengan sarkastik menggunakan dikotomi palsu untuk menunjukkan mengapa stigma itu tidak logis. Strawman adalah kekeliruan logis di mana Anda sengaja salah menggambarkan posisi lawan agar tampak bahwa posisi mereka mudah dikalahkan. Misalnya "Ateis hanya ateis karena mereka membenci Tuhan". Dikotomi yang salah adalah ketika Anda menganggap ekstrim sebaliknya benar ketika setidaknya ada satu kemungkinan tambahan (yaitu hitam dan putih). Misalnya "Tuhan membenci homoseksual, oleh karena itu dia suka heteros."
Braden Best

27
@ JLRishe Anda membaca terlalu dalam tentang sesuatu yang bahkan tidak ada di sana. Itu hanya kesalahan logis yang benar jika orang yang menggunakannya dengan jujur ​​percaya itu masuk akal. Tapi dia mengatakannya dengan sinis, jelas menunjukkan bahwa dia tahu itu konyol, dan ini adalah bagaimana dia menunjukkannya. Dia tidak "menggunakannya untuk membenarkan pendapatnya"; dia menggunakannya untuk menunjukkan mengapa konyol mengatakan bahwa goto secara otomatis membuat kode menjadi spageti. Yaitu Anda masih dapat menulis kode menyebalkan tanpa gotos. ITULAH intinya. Dan dikotomi palsu sarkastik adalah metodenya untuk menggambarkannya dengan cara yang lucu.
Braden Best

32
-1 untuk membuat argumen yang sangat besar mengapa komentar Dijkstra tidak berlaku, sementara lupa untuk menunjukkan apa keuntungan gotosebenarnya (yang merupakan pertanyaan yang diposting)
Maarten Bodewes

154

Mematuhi praktik terbaik secara membabi buta bukanlah praktik terbaik. Gagasan untuk menghindari gotopernyataan sebagai bentuk utama dari kontrol aliran adalah untuk menghindari pembuatan kode spageti yang tidak dapat dibaca. Jika digunakan dengan hemat di tempat-tempat yang tepat, mereka kadang-kadang bisa menjadi cara paling sederhana, paling jelas untuk mengekspresikan ide. Walter Bright, pencipta kompiler Zortech C ++ dan bahasa pemrograman D, sering menggunakannya, tetapi dengan bijaksana. Bahkan dengan gotopernyataan, kodenya masih dapat dibaca dengan sempurna.

Intinya: Menghindari gotodemi menghindari gotoadalah sia-sia. Apa yang benar-benar ingin Anda hindari adalah menghasilkan kode yang tidak dapat dibaca. Jika gotokode -laden Anda dapat dibaca, maka tidak ada yang salah dengan kode itu.


36

Karena gotomembuat penalaran tentang alur program sulit 1 (alias. "Kode spaghetti"), gotoumumnya hanya digunakan untuk mengkompensasi fitur yang hilang: Penggunaan gotosebenarnya dapat diterima, tetapi hanya jika bahasa tidak menawarkan varian yang lebih terstruktur untuk mendapatkan tujuan yang sama. Ambil contoh Keraguan:

Aturan dengan goto yang kita gunakan adalah bahwa goto boleh-boleh saja untuk melompat maju ke satu titik pembersihan keluar dalam suatu fungsi.

Ini benar - tetapi hanya jika bahasa tidak mengizinkan penanganan pengecualian terstruktur dengan kode pembersihan (seperti RAII atau finally), yang melakukan pekerjaan yang sama dengan lebih baik (seperti yang dibuat khusus untuk melakukannya), atau ketika ada alasan bagus untuk tidak untuk menggunakan penanganan pengecualian terstruktur (tetapi Anda tidak akan pernah memiliki kasus ini kecuali pada tingkat yang sangat rendah).

Di sebagian besar bahasa lain, satu-satunya penggunaan yang dapat diterima gotoadalah untuk keluar dari loop bersarang. Dan bahkan di sana hampir selalu lebih baik untuk mengangkat loop luar ke dalam metode sendiri dan menggunakannya return.

Selain itu, gotoadalah tanda bahwa tidak cukup pemikiran untuk masuk ke bagian kode tertentu.


1 Bahasa modern yang mendukung gotopenerapan beberapa batasan (mis. gotoMungkin tidak masuk atau keluar dari fungsi) tetapi masalahnya pada dasarnya tetap sama.

Kebetulan, hal yang sama tentu saja juga berlaku untuk fitur bahasa lainnya, terutama pengecualian. Dan biasanya ada aturan ketat di tempat untuk hanya menggunakan fitur-fitur ini di mana ditunjukkan, seperti aturan untuk tidak menggunakan pengecualian untuk mengontrol aliran program yang tidak biasa.


4
Hanya ingin tahu di sini, tetapi bagaimana dengan kasus menggunakan gotos untuk membersihkan kode. Dengan pembersihan, maksud saya, tidak hanya alokasi memori, tetapi juga, katakanlah logging kesalahan. Saya membaca banyak posting, dan ternyata, tidak ada yang menulis kode yang mencetak log .. hmm ?!
shiva

37
"Pada dasarnya, karena sifat goto yang cacat (dan saya percaya bahwa ini tidak kontroversial)" -1 dan saya berhenti membaca di sana.
o0 '.

14
Peringatan terhadap goto berasal dari era pemrograman terstruktur, di mana pengembalian awal juga dianggap jahat. Memindahkan loop bersarang menjadi fungsi memperdagangkan satu "kejahatan" dengan yang lain, dan membuat fungsi yang tidak memiliki alasan nyata untuk ada (di luar "itu memungkinkan saya untuk menghindari penggunaan goto!"). Memang benar bahwa goto adalah alat yang paling kuat untuk menghasilkan kode spaghetti jika digunakan tanpa kendala, tetapi ini adalah aplikasi yang sempurna untuk itu; itu menyederhanakan kode. Knuth berpendapat untuk penggunaan ini, dan Dijkstra berkata "Jangan jatuh ke dalam perangkap percaya bahwa saya sangat dogmatis tentang goto".
Lumpur

5
finally? Jadi menggunakan pengecualian untuk hal-hal selain penanganan kesalahan itu baik tetapi menggunakan gotoitu buruk? Saya pikir pengecualian cukup tepat dinamai.
Christian

3
@Mud: dalam kasus yang saya temui (setiap hari), membuat fungsi "yang tidak memiliki alasan nyata untuk ada" adalah solusi terbaik. Alasan: menghapus detail dari fungsi tingkat atas, sehingga hasilnya memiliki aliran kontrol yang mudah dibaca. Saya pribadi tidak menemui situasi di mana goto menghasilkan kode yang paling mudah dibaca. Di sisi lain, saya menggunakan beberapa pengembalian, dalam fungsi-fungsi sederhana, di mana orang lain mungkin lebih suka untuk kebagian titik keluar tunggal. Saya akan senang melihat contoh tandingan di mana goto lebih mudah dibaca daripada refactoring ke fungsi-fungsi yang terkenal.
ToolmakerSteve

35

Nah, ada satu hal yang selalu lebih buruk daripada goto's; penggunaan aneh dari operator alur program lain untuk menghindari kebohongan:

Contoh:

    // 1
    try{
      ...
      throw NoErrorException;
      ...
    } catch (const NoErrorException& noe){
      // This is the worst
    } 


    // 2
    do {
      ...break; 
      ...break;
    } while (false);


    // 3
    for(int i = 0;...) { 
      bool restartOuter = false;
      for (int j = 0;...) {
        if (...)
          restartOuter = true;
      if (restartOuter) {
        i = -1;
      }
    }

etc
etc

2
do{}while(false)Saya pikir dapat dianggap idiom. Anda tidak diizinkan untuk tidak setuju: D
Thomas Eding

37
@trinithis: Jika "idiomatik", itu hanya karena kultus anti-goto. Jika Anda memperhatikannya dengan seksama, Anda akan menyadari bahwa itu hanya cara untuk mengatakan goto after_do_block;tanpa benar-benar mengatakan itu. Kalau tidak ... "loop" yang berjalan tepat satu kali? Saya akan menyebutnya penyalahgunaan struktur kontrol.
cao

5
@ Thomas Mengedit Ada satu pengecualian untuk poin Anda. Jika Anda pernah melakukan pemrograman C / C ++ dan harus menggunakan #define, Anda akan tahu, itu {} sementara (0) adalah salah satu standar untuk merangkum beberapa baris kode. Sebagai contoh: #define do {memcpy (a, b, 1); sesuatu ++;} sementara (0) lebih aman dan lebih baik daripada #define memcpy (a, b, 1); something ++
Ignas2526

10
@ Ignas2526 Anda baru saja menunjukkan dengan baik berapa #definebanyak, berkali-kali jauh lebih buruk daripada menggunakan gotosesekali: D
Luaan

3
Memberi +1 untuk daftar cara yang baik yang orang coba hindari dari goto, ke ekstrem; Saya telah melihat menyebutkan teknik semacam itu di tempat lain, tetapi ini adalah daftar yang bagus dan ringkas. Merenungkan mengapa, dalam 15 tahun terakhir, tim saya tidak pernah membutuhkan teknik-teknik itu, atau menggunakan goto. Bahkan dalam aplikasi klien / server dengan ratusan ribu baris kode, oleh banyak programmer. C #, java, PHP, python, javascript. Kode klien, server, dan aplikasi. Tidak menyombongkan diri, juga tidak menganjurkan untuk satu posisi, hanya benar-benar ingin tahu mengapa beberapa orang menghadapi situasi yang meminta goto sebagai solusi yang paling jelas, dan yang lain tidak ...
ToolmakerSteve

28

Dalam pernyataan C # switch doest tidak memungkinkan jatuh . Jadi goto digunakan untuk mentransfer kontrol ke label kasus sakelar tertentu atau label default .

Sebagai contoh:

switch(value)
{
  case 0:
    Console.Writeln("In case 0");
    goto case 1;
  case 1:
    Console.Writeln("In case 1");
    goto case 2;
  case 2:
    Console.Writeln("In case 2");
    goto default;
  default:
    Console.Writeln("In default");
    break;
}

Sunting: Ada satu pengecualian pada aturan "no fall-through". Fall-through diizinkan jika pernyataan kasus tidak memiliki kode.


3
Switch fall-through didukung dalam .NET 2.0 - msdn.microsoft.com/en-us/library/06tc147t(VS.80).aspx
rjzii

9
Hanya jika kasing tidak memiliki badan kode. Jika memang memiliki kode maka Anda harus menggunakan kata kunci goto.
Matthew Whited

26
Jawaban ini sangat lucu - C # menghilangkan fall-through karena banyak yang melihatnya berbahaya, dan contoh ini menggunakan goto (juga dianggap berbahaya oleh banyak orang) untuk menghidupkan kembali perilaku asli, yang diduga berbahaya, TETAPI hasil keseluruhan sebenarnya kurang berbahaya ( karena kode memperjelas bahwa kesalahan itu disengaja!).
thomasrutter

9
Hanya karena kata kunci ditulis dengan huruf GOTO tidak menjadikannya goto. Kasus yang disajikan ini bukan kebohongan. Ini adalah pernyataan switch switch untuk fallthrough. Kemudian lagi, saya tidak terlalu mengenal C #, jadi saya bisa salah.
Thomas Eding

1
Nah, dalam hal ini sedikit lebih dari gagal (karena Anda dapat mengatakan goto case 5:ketika Anda berada dalam kasus 1). Sepertinya jawaban Konrad Rudolph benar di sini: gotomengkompensasi fitur yang hilang (dan kurang jelas dari fitur sebenarnya). Jika yang benar-benar kita inginkan adalah gagal, mungkin default terbaik adalah tidak jatuh, tetapi sesuatu seperti continuememinta secara eksplisit.
David Stone

14

#ifdef TONGUE_IN_CHEEK

Perl memiliki gotoyang memungkinkan Anda untuk menerapkan panggilan ekor orang miskin. :-P

sub factorial {
    my ($n, $acc) = (@_, 1);
    return $acc if $n < 1;
    @_ = ($n - 1, $acc * $n);
    goto &factorial;
}

#endif

Oke, sehingga tidak ada hubungannya dengan C goto. Lebih serius, saya setuju dengan komentar lain tentang penggunaan gotountuk pembersihan, atau untuk mengimplementasikan perangkat Duff , atau sejenisnya. Ini semua tentang menggunakan, bukan menyalahgunakan.

(Komentar yang sama dapat berlaku untuk longjmp, pengecualian, call/ccdan sejenisnya --- mereka memiliki kegunaan yang sah, tetapi dapat dengan mudah disalahgunakan. Misalnya, melemparkan pengecualian murni untuk melarikan diri dari struktur kontrol yang bersarang mendalam, dalam keadaan yang sama sekali tidak luar biasa .)


Saya pikir ini adalah satu-satunya alasan untuk menggunakan goto di Perl.
Brad Gilbert

12

Saya telah menulis lebih dari beberapa baris bahasa majelis selama bertahun-tahun. Pada akhirnya, setiap bahasa tingkat tinggi mengkompilasi ke goto. Oke, sebut mereka "cabang" atau "lompatan" atau apa pun yang lain, tetapi mereka gotos. Adakah yang bisa menulis assembler goto-less?

Sekarang yakin, Anda dapat menunjukkan kepada programmer Fortran, C atau BASIC bahwa menjalankan kerusuhan dengan gotos adalah resep untuk spaghetti bolognaise. Namun jawabannya bukan untuk menghindarinya, tetapi menggunakannya dengan hati-hati.

Pisau dapat digunakan untuk menyiapkan makanan, membebaskan seseorang, atau membunuh seseorang. Apakah kita melakukannya tanpa pisau karena takut pada yang terakhir? Demikian pula goto: digunakan dengan sembarangan menghalangi, digunakan dengan hati-hati itu membantu.


1
Mungkin Anda ingin membaca mengapa saya percaya ini pada dasarnya salah di stackoverflow.com/questions/46586/…
Konrad Rudolph

15
Siapa pun yang mengedepankan "semuanya kompilasi ke JMP!" Argumen pada dasarnya tidak memahami titik di balik pemrograman dalam bahasa tingkat yang lebih tinggi.
Nietzche-jou

7
Yang benar-benar Anda butuhkan adalah kurangi-dan-cabang. Segala sesuatu yang lain adalah untuk kenyamanan atau kinerja.
David Stone

8

Lihatlah When To Use Goto When Programming di C :

Meskipun penggunaan goto hampir selalu merupakan praktik pemrograman yang buruk (tentunya Anda dapat menemukan cara yang lebih baik untuk melakukan XYZ), ada kalanya itu benar-benar bukan pilihan yang buruk. Beberapa bahkan mungkin berpendapat bahwa, ketika berguna, itu adalah pilihan terbaik.

Sebagian besar dari apa yang saya katakan tentang goto benar-benar hanya berlaku untuk C. Jika Anda menggunakan C ++, tidak ada alasan kuat untuk menggunakan goto sebagai pengganti pengecualian. Namun dalam C, Anda tidak memiliki kekuatan mekanisme penanganan pengecualian, jadi jika Anda ingin memisahkan penanganan kesalahan dari sisa logika program Anda, dan Anda ingin menghindari penulisan ulang kode pembersihan beberapa kali di seluruh kode Anda, maka goto bisa menjadi pilihan yang baik.

Apa yang saya maksud? Anda mungkin memiliki beberapa kode yang terlihat seperti ini:

int big_function()
{
    /* do some work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* clean up*/
    return [success];
}

Ini bagus sampai Anda menyadari bahwa Anda perlu mengubah kode pembersihan Anda. Maka Anda harus melalui dan membuat 4 perubahan. Sekarang, Anda mungkin memutuskan bahwa Anda bisa merangkum semua pembersihan menjadi satu fungsi; itu bukan ide yang buruk. Tapi itu berarti bahwa Anda harus berhati-hati dengan pointer - jika Anda berencana untuk membebaskan pointer di fungsi pembersihan Anda, tidak ada cara untuk mengaturnya kemudian arahkan ke NULL kecuali Anda meneruskan pointer ke pointer. Dalam banyak kasus, Anda tidak akan menggunakan pointer itu lagi, sehingga mungkin tidak menjadi perhatian utama. Di sisi lain, jika Anda menambahkan pointer baru, pegangan file, atau hal lain yang perlu dibersihkan, maka Anda perlu mengubah fungsi pembersihan lagi; dan kemudian Anda harus mengubah argumen ke fungsi itu.

Dengan menggunakan goto, itu akan menjadi

int big_function()
{
    int ret_val = [success];
    /* do some work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
end:
    /* clean up*/
    return ret_val;
}

Manfaatnya di sini adalah bahwa kode Anda yang mengikuti akhir memiliki akses ke semua yang diperlukan untuk melakukan pembersihan, dan Anda telah berhasil mengurangi jumlah titik perubahan secara signifikan. Manfaat lain adalah Anda beralih dari memiliki beberapa titik keluar untuk fungsi Anda menjadi hanya satu; tidak ada kemungkinan Anda secara tidak sengaja kembali dari fungsi tanpa membersihkan.

Selain itu, karena goto hanya digunakan untuk melompat ke satu titik, bukan berarti Anda membuat banyak kode spageti melompat-lompat dalam upaya mensimulasikan panggilan fungsi. Sebaliknya, goto sebenarnya membantu menulis kode yang lebih terstruktur.


Singkatnya, gotoharus selalu digunakan hemat, dan sebagai pilihan terakhir - tetapi ada waktu dan tempat untuk itu. Pertanyaannya seharusnya bukan "apakah Anda harus menggunakannya" tetapi "apakah ini pilihan terbaik" untuk menggunakannya.


7

Salah satu alasan goto buruk, selain gaya pengkodean adalah Anda dapat menggunakannya untuk membuat loop yang tumpang tindih , tetapi tidak bersarang :

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

Ini akan membuat struktur flow-of-control yang aneh, tetapi mungkin legal, di mana urutan seperti (a, b, c, b, a, b, a, b, ...) dimungkinkan, yang membuat peretas kompiler tidak senang. Rupanya ada sejumlah trik optimasi pintar yang mengandalkan jenis struktur ini tidak terjadi. (Saya harus memeriksa salinan buku naga saya ...) Hasil dari ini mungkin (menggunakan beberapa kompiler) adalah bahwa optimasi lain tidak dilakukan untuk kode yang berisi gotos.

Mungkin berguna jika Anda tahu itu hanya, "oh, omong-omong", kebetulan membujuk kompiler untuk memancarkan kode lebih cepat. Secara pribadi, saya lebih suka mencoba menjelaskan kepada kompiler tentang apa yang mungkin dan apa yang tidak sebelum menggunakan trik seperti goto, tetapi boleh dibilang, saya mungkin juga mencoba gotosebelum meretas assembler.


3
Baiklah ... membawa saya kembali ke masa pemrograman FORTRAN di perusahaan informasi keuangan. Pada 2007.
Marcin

5
Setiap struktur bahasa dapat disalahgunakan dengan cara yang membuatnya tidak dapat dibaca atau berkinerja buruk.
HANYA SAYA PENDAPAT benar

@ JUST: Intinya bukan tentang keterbacaan, atau kinerja yang buruk, tetapi asumsi dan jaminan tentang grafik flow-of-control. Setiap penyalahgunaan goto akan meningkatkan kinerja (atau keterbacaan).
Anders Eurenius

3
Aku berpendapat salah satu alasan gotoadalah berguna yang memungkinkan Anda untuk membangun loop seperti ini, yang akan membutuhkan banyak contortions logis sebaliknya. Lebih lanjut saya berpendapat bahwa jika pengoptimal tidak tahu cara menulis ulang ini, maka bagus . Perulangan seperti ini seharusnya tidak dilakukan untuk kinerja atau keterbacaan, tetapi karena itulah urutan yang harus terjadi. Dalam hal ini saya tidak ingin secara khusus pengoptimal bermain-main dengannya.
cHao

1
... terjemahan langsung dari algoritma yang diperlukan ke dalam kode berbasis GOTO mungkin jauh lebih mudah untuk divalidasi daripada kekacauan variabel flag dan konstruksi loop yang disalahgunakan.
supercat

7

Saya merasa lucu bahwa beberapa orang akan memberikan daftar kasus dimana goto dapat diterima, mengatakan bahwa semua penggunaan lainnya tidak dapat diterima. Apakah Anda benar-benar berpikir bahwa Anda tahu setiap kasus di mana goto adalah pilihan terbaik untuk mengekspresikan suatu algoritma?

Sebagai ilustrasi, saya akan memberi Anda sebuah contoh yang belum ditampilkan di sini:

Hari ini saya menulis kode untuk memasukkan elemen dalam tabel hash. Tabel hash adalah cache dari perhitungan sebelumnya yang dapat ditimpa sesuka hati (mempengaruhi kinerja tetapi tidak benar).

Setiap ember tabel hash memiliki 4 slot, dan saya memiliki banyak kriteria untuk memutuskan elemen mana yang akan ditimpa ketika ember penuh. Sekarang ini berarti membuat hingga tiga melewati ember, seperti ini:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    goto add;

// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
    goto add;

// Additional passes go here...

add:
// element is written to the hash table here

Sekarang jika saya tidak menggunakan goto, seperti apa kode ini?

Sesuatu seperti ini:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    break;

if (add_index >= ELEMENTS_PER_BUCKET) {
  // Otherwise, find first empty element
  for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
    if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
      break;
  if (add_index >= ELEMENTS_PER_BUCKET)
   // Additional passes go here (nested further)...
}

// element is written to the hash table here

Akan terlihat lebih buruk dan lebih buruk jika lebih banyak pass ditambahkan, sementara versi dengan goto menjaga level indentasi yang sama setiap saat dan menghindari penggunaan pernyataan palsu jika hasilnya yang tersirat oleh eksekusi dari loop sebelumnya.

Jadi ada kasus lain di mana goto membuat kode lebih bersih dan lebih mudah untuk menulis dan memahami ... Saya yakin ada lebih banyak lagi, jadi jangan berpura-pura tahu semua kasus di mana goto berguna, membeda-bedakan yang bagus yang Anda tidak bisa bisa dipikirkan.


1
Dalam contoh yang Anda berikan, saya lebih suka refactor yang cukup signifikan. Secara umum, saya mencoba untuk menghindari komentar satu-baris yang mengatakan apa yang dilakukan potongan kode selanjutnya. Sebagai gantinya, saya memecahnya menjadi fungsinya sendiri yang dinamai mirip dengan komentar. Jika Anda melakukan transformasi seperti itu, maka fungsi ini akan memberikan tinjauan tingkat tinggi tentang apa yang dilakukan fungsi tersebut, dan masing-masing fungsi baru akan menyatakan bagaimana Anda melakukan setiap langkah. Saya merasa bahwa jauh lebih penting daripada oposisi apa pun untuk gotomemiliki fungsi masing-masing berada pada tingkat abstraksi yang sama. Menghindari itu gotoadalah bonus.
David Stone

2
Anda belum benar-benar menjelaskan bagaimana menambahkan lebih banyak fungsi menghilangkan kebuntuan, beberapa tingkat lekukan dan pernyataan palsu jika ...
Ricardo

Akan terlihat seperti ini, menggunakan notasi wadah standar: container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();Saya menemukan pemrograman semacam itu lebih mudah dibaca. Setiap fungsi dalam kasus ini dapat ditulis sebagai fungsi murni, yang jauh lebih mudah untuk dipikirkan. Fungsi utama sekarang menjelaskan apa yang dilakukan kode hanya dengan nama fungsi, dan kemudian jika Anda mau, Anda dapat melihat definisi mereka untuk mengetahui bagaimana melakukannya.
David Stone

3
Fakta bahwa setiap orang harus memberikan contoh, tentang bagaimana algoritma tertentu harus secara alami menggunakan goto - adalah refleksi menyedihkan tentang betapa sedikit pemikiran algoritmik yang terjadi hari ini !! Tentu saja, contoh @ Ricardo adalah (salah satu dari banyak) contoh sempurna di mana goto elegan dan jelas.
Fattie

6

Aturan dengan goto yang kita gunakan adalah bahwa goto boleh-boleh saja untuk melompat maju ke satu titik pembersihan keluar dalam suatu fungsi. Dalam fungsi yang sangat kompleks, kami mengendurkan aturan itu untuk memungkinkan lompatan lain ke depan. Dalam kedua kasus kami menghindari bersarang mendalam jika pernyataan yang sering terjadi dengan pengecekan kode kesalahan, yang membantu keterbacaan dan pemeliharaan.


1
Saya bisa melihat sesuatu seperti itu berguna dalam bahasa seperti C. Namun, ketika Anda memiliki kekuatan C ++ konstruktor / destruktor, itu umumnya tidak begitu berguna.
David Stone

1
"Dalam fungsi yang sangat kompleks, kami melonggarkan aturan itu untuk memungkinkan lompatan lain ke depan" Tanpa contoh, itu terdengar seperti, jika Anda membuat fungsi kompleks lebih kompleks dengan menggunakan lompatan. Bukankah pendekatan yang "lebih baik" adalah untuk refactor dan membagi fungsi-fungsi kompleks itu?
MikeMB

1
Ini adalah tujuan terakhir yang saya gunakan goto. Saya tidak ketinggalan goto di Jawa, karena sudah coba-akhirnya yang bisa melakukan pekerjaan yang sama.
Patricia Shanahan

5

Diskusi yang paling bijaksana dan teliti tentang pernyataan goto, penggunaannya yang sah, dan konstruksi alternatif yang dapat digunakan sebagai pengganti "pernyataan goto berbudi luhur" tetapi dapat disalahgunakan semudah pernyataan goto, adalah artikel Donald Knuth " Pemrograman Terstruktur dengan Pernyataan goto" " , dalam Survei Komputasi Desember 1974 (volume 6, no. 4. hlm. 261 - 301).

Tidak mengherankan, beberapa aspek dari makalah berusia 39 tahun ini sudah ketinggalan zaman: Peningkatan pesanan dalam kekuatan pemrosesan membuat beberapa peningkatan kinerja Knuth tidak terlalu mencolok untuk masalah ukuran sedang, dan konstruksi bahasa pemrograman baru telah ditemukan sejak saat itu. (Sebagai contoh, coba-tangkap blok subsume Zahn's Construct, meskipun mereka jarang digunakan dengan cara itu.) Tetapi Knuth membahas semua sisi argumen, dan harus membaca sebelum ada orang yang mengulangi masalah ini lagi.


3

Dalam modul Perl, Anda sesekali ingin membuat subrutin atau penutupan dengan cepat. Masalahnya adalah, begitu Anda telah membuat subrutin, bagaimana Anda bisa mendapatkannya. Anda bisa menyebutnya, tetapi kemudian jika subrutin menggunakannya caller()tidak akan membantu seperti seharusnya. Di situlah goto &subroutinevariasi dapat membantu.

Ini adalah contoh cepat:

sub AUTOLOAD{
  my($self) = @_;
  my $name = $AUTOLOAD;
  $name =~ s/.*:://;

  *{$name} = my($sub) = sub{
    # the body of the closure
  }

  goto $sub;

  # nothing after the goto will ever be executed.
}

Anda juga dapat menggunakan bentuk ini gotountuk memberikan bentuk dasar dari optimasi panggilan ekor.

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

(Dalam Perl 5 versi 16 yang akan lebih baik ditulis sebagai goto __SUB__;)

Ada modul yang akan mengimpor tailpengubah dan yang akan mengimpor recurjika Anda tidak suka menggunakan bentuk ini goto.

use Sub::Call::Tail;
sub AUTOLOAD {
  ...
  tail &$sub( @_ );
}

use Sub::Call::Recur;
sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;
  recur( $n-1, $tally * $n );
}

Sebagian besar alasan lain untuk menggunakan gotolebih baik dilakukan dengan kata kunci lain.

Menyukai redosedikit kode:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

Atau buka lastsedikit kode dari banyak tempat:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

2

Jika demikian, mengapa?

C tidak memiliki jeda multi-level / berlabel, dan tidak semua aliran kontrol dapat dengan mudah dimodelkan dengan iterasi C dan primitif keputusan. goto pergi jauh ke arah memperbaiki kelemahan ini.

Terkadang lebih jelas untuk menggunakan variabel flag dari beberapa jenis untuk mempengaruhi semacam pseudo-multi-level break, tapi itu tidak selalu lebih unggul daripada goto (setidaknya goto memungkinkan seseorang untuk dengan mudah menentukan ke mana kontrol berjalan, tidak seperti variabel flag ), dan kadang-kadang Anda tidak ingin membayar harga kinerja bendera / liuk lainnya untuk menghindari kebotakan.

libavcodec adalah sepotong kode yang sensitif terhadap kinerja. Ekspresi langsung dari aliran kontrol mungkin merupakan prioritas, karena akan cenderung berjalan lebih baik.


2

Sama juga tidak ada yang pernah menerapkan pernyataan "DATANG DARI" ....


8
Atau dalam C ++, C #, Java, JS, Python, Ruby .... dll dll. Hanya mereka menyebutnya "pengecualian".
cao

2

Saya menemukan do {} sementara penggunaan (false) benar-benar menjijikkan. Bisa dibayangkan mungkin meyakinkan saya perlu dalam beberapa kasus aneh, tetapi tidak pernah bahwa itu adalah kode yang masuk akal bersih.

Jika Anda harus melakukan beberapa loop seperti itu, mengapa tidak membuat ketergantungan pada variabel flag eksplisit?

for (stepfailed=0 ; ! stepfailed ; /*empty*/)

Harus tidak /*empty*/menjadi stepfailed = 1? Bagaimanapun, bagaimana ini lebih baik dari pada do{}while(0)? Dalam keduanya, Anda harus breakkeluar dari situ (atau milik Anda stepfailed = 1; continue;). Sepertinya tidak perlu bagi saya.
Thomas Eding

2

1) Penggunaan goto yang paling umum yang saya ketahui adalah meniru penanganan pengecualian dalam bahasa yang tidak menawarkannya, yaitu dalam C. (Kode yang diberikan oleh Nuclear di atas hanya itu.) Lihatlah kode sumber Linux dan Anda akan melihat bazillion gotos digunakan seperti itu; ada sekitar 100.000 foto dalam kode Linux menurut survei cepat yang dilakukan pada tahun 2013: http://blog.regehr.org/archives/894 . Penggunaan goto bahkan disebutkan dalam panduan gaya pengkodean Linux: https://www.kernel.org/doc/Documentation/CodingStyle . Sama seperti pemrograman berorientasi objek yang ditiru menggunakan struct yang diisi dengan pointer fungsi, goto memiliki tempat dalam pemrograman C. Jadi siapa yang benar: Dijkstra atau Linus (dan semua coders kernel Linux)? Ini teori vs praktik pada dasarnya.

Namun ada gotcha yang biasa untuk tidak memiliki dukungan tingkat kompiler dan memeriksa untuk konstruksi / pola umum: lebih mudah untuk menggunakannya salah dan memperkenalkan bug tanpa pemeriksaan waktu kompilasi. Windows dan Visual C ++ tetapi dalam mode C menawarkan penanganan pengecualian melalui SEH / VEH karena alasan ini: pengecualian berguna bahkan di luar bahasa OOP, yaitu dalam bahasa prosedural. Tetapi kompiler tidak selalu dapat menyimpan bacon Anda, bahkan jika ia menawarkan dukungan sintaksis untuk pengecualian dalam bahasa. Pertimbangkan sebagai contoh dari kasus terakhir bug "goto gagal" Apple SSL yang terkenal, yang hanya menggandakan satu goto dengan konsekuensi yang merusak ( https://www.imperialviolet.org/2014/02/22/applebug.html ):

if (something())
  goto fail;
  goto fail; // copypasta bug
printf("Never reached\n");
fail:
  // control jumps here

Anda dapat memiliki bug yang sama persis menggunakan pengecualian yang didukung compiler, misalnya di C ++:

struct Fail {};

try {
  if (something())
    throw Fail();
    throw Fail(); // copypasta bug
  printf("Never reached\n");
}
catch (Fail&) {
  // control jumps here
}

Tetapi kedua varian bug dapat dihindari jika kompiler menganalisis dan memperingatkan Anda tentang kode yang tidak terjangkau. Misalnya kompilasi dengan Visual C ++ di tingkat peringatan / W4 menemukan bug dalam kedua kasus. Java misalnya melarang kode yang tidak dapat dijangkau (di mana ia dapat menemukannya!) Karena alasan yang cukup bagus: itu kemungkinan merupakan bug dalam kode rata-rata Joe. Selama goto construct tidak membolehkan target yang tidak mudah diketahui oleh kompiler, seperti gotos ke alamat yang dihitung (**), kompiler tidak akan kesulitan untuk menemukan kode yang tidak terjangkau di dalam fungsi dengan gotos daripada menggunakan Dijkstra -Kode yang disetujui.

(**) Catatan Kaki: Gotos ke nomor baris yang dihitung dimungkinkan dalam beberapa versi Basic, misalnya GOTO 10 * x di mana x adalah variabel. Agak membingungkan, dalam Fortran "goto dihitung" mengacu pada konstruk yang setara dengan pernyataan beralih dalam C. Standar C tidak mengizinkan goto dihitung dalam bahasa, tetapi hanya gotos ke label yang dinyatakan secara statik / sintaksis. Namun GNU C memiliki ekstensi untuk mendapatkan alamat label (operator unary, awalan &&) dan juga memungkinkan goto ke variabel tipe void *. Lihat https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html untuk informasi lebih lanjut tentang sub-topik yang tidak jelas ini. Sisa dari posting ini tidak peduli dengan fitur GNU C yang tidak jelas itu.

Foto C standar (yaitu tidak dihitung) biasanya tidak menjadi alasan mengapa kode yang tidak dapat dijangkau tidak dapat ditemukan pada waktu kompilasi. Alasan yang biasa adalah kode logika seperti berikut. Diberikan

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

Sama sulitnya bagi kompilator untuk menemukan kode yang tidak terjangkau dalam salah satu dari 3 konstruksi berikut:

void tough1() {
  if (computation1() != computation2())
    printf("Unreachable\n");
}

void tough2() {
  if (computation1() == computation2())
    goto out;
  printf("Unreachable\n");
out:;
}

struct Out{};

void tough3() {
  try {
    if (computation1() == computation2())
      throw Out();
    printf("Unreachable\n");
  }
  catch (Out&) {
  }
}

(Maafkan gaya pengkodean yang terkait dengan brace saya, tetapi saya mencoba untuk menjaga agar contohnya seringkas mungkin.)

Visual C ++ / W4 (bahkan dengan / Ox) gagal menemukan kode yang tidak dapat dijangkau dalam semua ini, dan karena Anda mungkin tahu masalah menemukan kode yang tidak dapat dijangkau secara umum tidak dapat diputuskan. (Jika Anda tidak mempercayai saya tentang hal itu: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )

Sebagai masalah terkait, Coto bisa digunakan untuk meniru pengecualian hanya di dalam tubuh fungsi. Pustaka C standar menawarkan sepasang fungsi setjmp () dan longjmp () untuk meniru keluar / pengecualian non-lokal, tetapi mereka memiliki beberapa kelemahan serius dibandingkan dengan apa yang ditawarkan bahasa lain. Artikel Wikipedia http://en.wikipedia.org/wiki/Setjmp.h menjelaskan dengan cukup baik masalah terakhir ini. Pasangan fungsi ini juga berfungsi di Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), tetapi hampir tidak ada yang menggunakannya di sana karena SEH / VEH lebih unggul. Bahkan di Unix, saya pikir setjmp dan longjmp sangat jarang digunakan.

2) Saya pikir penggunaan kedua yang paling umum dari goto di C adalah mengimplementasikan multi-level break atau multi-level continue, yang juga merupakan kasus penggunaan yang tidak kontroversial. Ingatlah bahwa Java tidak mengizinkan label goto, tetapi mengizinkan label break atau melanjutkan label. Menurut http://www.oracle.com/technetwork/java/simple-142616.html , ini sebenarnya kasus penggunaan yang paling umum dari gotos di C (90% kata mereka), tetapi dalam pengalaman subjektif saya, kode sistem cenderung untuk menggunakan goto untuk penanganan kesalahan lebih sering. Mungkin dalam kode ilmiah atau di mana OS menawarkan penanganan pengecualian (Windows) maka keluar multi-level adalah kasus penggunaan yang dominan. Mereka tidak benar-benar memberikan perincian tentang konteks survei mereka.

Diedit untuk menambahkan: ternyata kedua pola penggunaan ini ditemukan dalam buku C Kernighan dan Ritchie, sekitar halaman 60 (tergantung pada edisi). Hal lain yang perlu diperhatikan adalah bahwa kedua use case hanya melibatkan forward gotos. Dan ternyata edisi MISRA C 2012 (tidak seperti edisi 2004) sekarang mengizinkan gotos, asalkan itu hanya yang maju.


Baik. "Elephant in the room" adalah kernel Linux, demi kebaikan, sebagai salah satu contoh basis kode yang sangat penting di dunia - dimuat dengan goto. Tentu saja. Jelas sekali. "Anti-goto-meme" hanyalah rasa ingin tahu dari beberapa dekade yang lalu. TENTU SAJA, ada sejumlah hal dalam pemrograman (terutama "statika", memang "global" dan misalnya "elseif") yang dapat disalahgunakan oleh non-profesional. Jadi, jika Anda sepupu anak sedang belajar di 2 program, Anda memberi tahu mereka "oh jangan gunakan global" dan "jangan pernah gunakan yang lain jika".
Fattie

Bug gagal goto tidak ada hubungannya dengan goto. Masalahnya disebabkan oleh pernyataan if tanpa kawat gigi sesudahnya. Hampir setiap salinan pernyataan yang disisipkan dua kali akan menyebabkan masalah. Saya menganggap jenis gelang telanjang itu jika jauh lebih berbahaya daripada goto.
muusbolla

2

Ada yang bilang tidak ada alasan untuk goto di C ++. Ada yang mengatakan bahwa dalam 99% kasus ada alternatif yang lebih baik. Ini bukan alasan, hanya kesan irasional. Berikut adalah contoh yang solid di mana goto mengarah ke kode yang bagus, sesuatu seperti loop do-while yang disempurnakan:

int i;

PROMPT_INSERT_NUMBER:
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    goto PROMPT_INSERT_NUMBER;          
  }

std::cout << "your number is " << i;

Bandingkan dengan kode bebas-goto:

int i;

bool loop;
do {
  loop = false;
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    loop = true;          
  }
} while(loop);

std::cout << "your number is " << i;

Saya melihat perbedaan-perbedaan ini:

  • {}blok bersarang diperlukan (meskipun do {...} whileterlihat lebih akrab)
  • loopvariabel tambahan diperlukan, digunakan di empat tempat
  • butuh waktu lebih lama untuk membaca dan memahami pekerjaan dengan loop
  • yang looptidak memegang data apapun, itu hanya mengontrol aliran eksekusi, yang kurang dipahami dari label sederhana

Ada contoh lain

void sort(int* array, int length) {
SORT:
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    goto SORT; // it is very easy to understand this code, right?
  }
}

Sekarang mari kita singkirkan goto "jahat":

void sort(int* array, int length) {
  bool seemslegit;
  do {
    seemslegit = true;
    for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
      swap(data[i], data[i+1]);
      seemslegit = false;
    }
  } while(!seemslegit);
}

Anda lihat itu adalah jenis yang sama menggunakan goto, itu adalah pola yang terstruktur dengan baik dan tidak meneruskan goto karena banyak mempromosikan sebagai satu-satunya cara yang disarankan. Tentunya Anda ingin menghindari kode "pintar" seperti ini:

void sort(int* array, int length) {
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    i = -1; // it works, but WTF on the first glance
  }
}

Intinya adalah bahwa goto dapat dengan mudah disalahgunakan, tetapi goto itu sendiri tidak bisa disalahkan. Perhatikan bahwa label memiliki cakupan fungsi dalam C ++, sehingga tidak mencemari lingkup global seperti dalam perakitan murni, di mana loop yang tumpang tindih memiliki tempatnya dan sangat umum - seperti pada kode berikut untuk 8.051, di mana tampilan 7segment terhubung ke P1. Program ini memutar segmen petir di sekitar:

; P1 states loops
; 11111110 <-
; 11111101  |
; 11111011  |
; 11110111  |
; 11101111  |
; 11011111  |
; |_________|

init_roll_state:
    MOV P1,#11111110b
    ACALL delay
next_roll_state:
    MOV A,P1
    RL A
    MOV P1,A
    ACALL delay
    JNB P1.5, init_roll_state
    SJMP next_roll_state

Ada keuntungan lain: goto dapat berfungsi sebagai loop bernama, kondisi dan aliran lainnya:

if(valid) {
  do { // while(loop)

// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket

  } while(loop);
} // if(valid)

Atau Anda dapat menggunakan goto setara dengan lekukan, sehingga Anda tidak perlu berkomentar jika Anda memilih nama label dengan bijak:

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;

1

Di Perl, gunakan label untuk "goto" dari satu loop - menggunakan pernyataan "terakhir", yang mirip dengan break.

Ini memungkinkan kontrol yang lebih baik atas loop bersarang.

Label goto tradisional didukung juga, tetapi saya tidak yakin ada terlalu banyak contoh di mana ini adalah satu-satunya cara untuk mencapai apa yang Anda inginkan - subrutin dan loop harus cukup untuk sebagian besar kasus.


Saya pikir satu-satunya bentuk goto yang akan Anda gunakan di Perl adalah goto &subroutine. Yang memulai subrutin dengan @_ saat ini, sambil mengganti subrutin saat ini di tumpukan.
Brad Gilbert

1

Masalah dengan 'goto' dan argumen paling penting dari gerakan 'pemrograman goto-less' adalah, bahwa jika Anda terlalu sering menggunakannya kode Anda, meskipun mungkin berperilaku dengan benar, menjadi tidak dapat dibaca, tidak dapat dipertahankan, tidak dapat ditinjau, dll. Dalam 99,99% dari kasus 'goto' mengarah ke kode spageti. Secara pribadi, saya tidak bisa memikirkan alasan yang bagus mengapa saya menggunakan 'goto'.


11
Mengatakan "Saya tidak dapat memikirkan alasan apa pun" dalam argumen Anda adalah, secara resmi, en.wikipedia.org/wiki/Argument_from_ignorance , meskipun saya lebih suka terminologi "bukti oleh kurangnya imajinasi".
HANYA SAYA OPINI

2
@ HANYA PENDAPATAN SAYA yang benar: Ini adalah kesalahan logis hanya jika itu digunakan post-hoc. Dari sudut pandang perancang bahasa mungkin argumen yang valid untuk menimbang biaya termasuk fitur ( goto). @ Penggunaan cschol mirip: Meskipun mungkin tidak merancang bahasa sekarang, dia pada dasarnya mengevaluasi upaya perancang.
Konrad Rudolph

1
@KonradRudolph: IMHO, memiliki bahasa memungkinkan gotokecuali dalam konteks di mana ia akan membawa variabel menjadi lebih murah daripada mencoba untuk mendukung setiap jenis struktur kontrol seseorang mungkin perlu. Menulis kode dengan gotomungkin tidak sebagus menggunakan beberapa struktur lain, tetapi kemampuan untuk menulis kode semacam itu gotoakan membantu menghindari "lubang-lubang dalam ekspresi" - konstruksi yang membuat bahasa tidak mampu menulis kode efisien.
supercat

1
@supercat Saya khawatir kita berasal dari sekolah desain bahasa yang berbeda secara fundamental. Saya keberatan bahasa menjadi ekspresif maksimal dengan harga yang dapat dimengerti (atau kebenaran). Saya lebih suka memiliki bahasa yang membatasi daripada yang permisif.
Konrad Rudolph

1
@ thb Ya tentu saja saya lakukan. Sering membuat kode lebih sulit untuk dibaca dan dipikirkan. Hampir setiap kali seseorang memposting kode yang berisi gotositus ulasan kode, menghilangkan gotologika kode yang sangat disederhanakan.
Konrad Rudolph

1

GOTO dapat digunakan, tentu saja, tetapi ada satu hal yang lebih penting daripada gaya kode, atau jika kode tersebut dapat dibaca atau tidak yang harus Anda ingat ketika menggunakannya: kode di dalamnya mungkin tidak sekuat yang Anda miliki pikirkan .

Misalnya, lihat dua cuplikan kode berikut:

If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)

Kode yang setara dengan GOTO

If A == 0 Then GOTO FINAL EndIf
   A = 0
FINAL:
Write("Value of A:" + A)

Hal pertama yang kami pikirkan adalah bahwa hasil dari kedua bit kode tersebut adalah "Nilai A: 0" (tentu saja kami kira eksekusi tanpa paralelisme)

Itu tidak benar: dalam sampel pertama, A akan selalu menjadi 0, tetapi dalam sampel kedua (dengan pernyataan GOTO) A mungkin tidak menjadi 0. Mengapa?

Alasannya adalah karena dari titik lain dari program ini saya dapat memasukkan GOTO FINALtanpa mengendalikan nilai A.

Contoh ini sangat jelas, tetapi ketika program menjadi lebih rumit, kesulitan melihat hal-hal semacam itu meningkat.

Materi terkait dapat ditemukan dalam artikel terkenal dari Tn. Dijkstra "Kasus yang menentang pernyataan GO TO"


6
Ini sering terjadi di sekolah dasar BASIC. Dalam varian modern, Anda tidak diperbolehkan melompat ke tengah fungsi lain ... atau dalam banyak kasus, bahkan melewati deklarasi variabel. Pada dasarnya (pun tidak dimaksudkan), bahasa modern sebagian besar telah menghilangkan GOTO "tidak terkendali" yang Dijkstra bicarakan ... dan satu-satunya cara untuk menggunakannya saat ia berdebat, adalah dengan melakukan dosa keji tertentu lainnya. :)
cHao

1

Saya menggunakan goto dalam kasus berikut: ketika diperlukan untuk kembali dari fungsi di tempat yang berbeda, dan sebelum kembali, beberapa inisialisasi perlu dilakukan:

versi non-kebagian:

int doSomething (struct my_complicated_stuff *ctx)    
{
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
        db_disconnect(conn);
        return -1;      
        }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        free(temp_data);    
        db_disconnect(conn);    
        return -2;
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        pthread_mutex_unlock(ctx->mutex);
        free(temp_data);
        db_disconnect(conn);        
        return -3;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -4;  
    }

    if (ctx->something_else->additional_check) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -5;  
    }


    pthread_mutex_unlock(ctx->mutex);
    free(temp_data);    
    db_disconnect(conn);    
    return 0;     
}

versi goto:

int doSomething_goto (struct my_complicated_stuff *ctx)
{
    int ret=0;
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
            ret=-1;
           goto exit_db;   
          }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        ret=-2;
        goto exit_freetmp;      
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        ret=-3;
        goto exit;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
        ret=-4;
        goto exit_freekey; 
    }

    if (ctx->something_else->additional_check) {
        ret=-5;
        goto exit_freekey;  
    }

exit_freekey:
    rsa_free(key);
exit:    
    pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
    free(temp_data);        
exit_db:
    db_disconnect(conn);    
    return ret;     
}

Versi kedua membuatnya lebih mudah, ketika Anda perlu mengubah sesuatu dalam pernyataan deallokasi (masing-masing digunakan satu kali dalam kode), dan mengurangi peluang untuk melompati salah satu dari mereka, ketika menambahkan cabang baru. Memindahkan mereka dalam suatu fungsi tidak akan membantu di sini, karena deallokasi dapat dilakukan pada "level" yang berbeda.


3
Inilah sebabnya kami memiliki finallyblok di C #
John Saunders

^ seperti yang dikatakan @JohnSaunders. Ini adalah contoh "menggunakan goto karena bahasa tidak memiliki konstruk kontrol yang sesuai". NAMUN itu adalah bau kode yang membutuhkan beberapa poin goto dekat kembali. Ada gaya pemrograman yang lebih aman (lebih mudah untuk tidak mengacaukan) DAN tidak memerlukan goto, yang berfungsi dengan baik bahkan dalam bahasa tanpa "akhirnya": desain panggilan "pembersihan" sehingga tidak berbahaya untuk dilakukan saat tidak ada pembersihan diperlukan. Faktor segalanya kecuali pembersihan menjadi metode, yang menggunakan desain multi-return. Panggil metode itu, lalu lakukan panggilan pembersihan.
ToolmakerSteve

Perhatikan bahwa pendekatan yang saya jelaskan membutuhkan tingkat panggilan metode tambahan (tetapi hanya dalam bahasa yang kurang finally). Sebagai alternatif, gunakan gotos, tetapi ke titik keluar umum , yang selalu melakukan semua pembersihan. Tetapi setiap metode pembersihan dapat menangani nilai yang nol atau sudah bersih, atau dilindungi oleh uji bersyarat, sehingga dilewati saat tidak tepat.
ToolmakerSteve

@ToolmakerSteve ini bukan bau kode; sebenarnya, ini adalah pola yang sangat umum di C dan dianggap sebagai salah satu cara paling valid untuk menggunakan goto. Anda ingin saya membuat 5 metode, masing-masing dengan tes-sendiri yang tidak perlu, hanya untuk menangani pembersihan dari fungsi ini? Anda sekarang telah membuat kode DAN mengasapi kinerja. Atau Anda bisa menggunakan goto.
muusbolla

@muusbolla - 1) "buat 5 metode" - Tidak. Saya menyarankan satu metode yang membersihkan semua sumber daya yang memiliki nilai bukan nol. ATAU lihat alternatif saya, yang menggunakan gotos yang semuanya menuju ke titik keluar yang sama , yang memiliki logika yang sama (yang memerlukan tambahan jika per sumber daya, seperti yang Anda katakan). Tapi tidak apa-apa, ketika menggunakan CAnda benar - apa pun alasannya kode dalam C, hampir pasti merupakan trade-off yang lebih disukai kode "langsung". (Saran saya menangani situasi yang rumit di mana sumber daya tertentu mungkin dialokasikan atau tidak. Tapi ya, dalam kasus ini diperlukan kerja keras.)
ToolmakerSteve

0

Edsger Dijkstra, seorang ilmuwan komputer yang memiliki kontribusi besar di lapangan, juga terkenal karena mengkritik penggunaan GoTo. Ada artikel pendek tentang argumennya di Wikipedia .


0

Ini berguna untuk pemrosesan string karakter-bijaksana dari waktu ke waktu.

Bayangkan sesuatu seperti contoh printf-esque ini:

for cur_char, next_char in sliding_window(input_string) {
    if cur_char == '%' {
        if next_char == '%' {
            cur_char_index += 1
            goto handle_literal
        }
        # Some additional logic
        if chars_should_be_handled_literally() {
            goto handle_literal
        }
        # Handle the format
    }
    # some other control characters
    else {
      handle_literal:
        # Complicated logic here
        # Maybe it's writing to an array for some OpenGL calls later or something,
        # all while modifying a bunch of local variables declared outside the loop
    }
}

Anda bisa menolaknya goto handle_literalmenjadi pemanggilan fungsi, tetapi jika itu memodifikasi beberapa variabel lokal yang berbeda, Anda harus meneruskan referensi ke masing-masing kecuali bahasa Anda mendukung penutupan yang bisa diubah. Anda masih harus menggunakan acontinue pernyataan (yang bisa dibilang bentuk goto) setelah panggilan untuk mendapatkan semantik yang sama jika logika Anda membuat case lain tidak berfungsi.

Saya juga menggunakan gotos secara bijaksana dalam lexers, biasanya untuk kasus yang serupa. Anda tidak membutuhkan mereka sebagian besar waktu, tetapi mereka baik untuk memiliki kasus-kasus aneh.

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.