سؤال

الجميع على علم بـ Dijkstra رسائل إلى المحرر:انتقل إلى البيان الذي يعتبر ضارا (أيضًا هنا نسخة .html و هنا .pdf) وكانت هناك دفعة هائلة منذ ذلك الوقت لتجنب بيان goto كلما أمكن ذلك.على الرغم من أنه من الممكن استخدام goto لإنتاج تعليمات برمجية مترامية الأطراف لا يمكن صيانتها، إلا أنها تظل موجودة لغات البرمجة الحديثة.وحتى المتقدمة استمرار يمكن وصف هيكل التحكم في Scheme بأنه goto متطور.

ما هي الظروف التي تبرر استخدام goto؟متى يكون من الأفضل تجنبه؟

كسؤال متابعة:توفر لغة C زوجًا من الوظائف، setjmp وlongjmp، التي توفر القدرة على الانتقال ليس فقط داخل إطار المكدس الحالي ولكن داخل أي من إطارات الاستدعاء.هل يجب اعتبارها خطيرة مثل goto؟أكثر خطورة؟


أعرب ديكسترا نفسه عن أسفه لهذا اللقب الذي لم يكن مسؤولاً عنه.في نهاية EWD1308 (أيضًا هنا .pdf) كتب:

وأخيرا قصة قصيرة للتسجيل.في عام 1968 ، نشرت اتصالات ACM نصًا لي تحت العنوان "يعتبر بيان GOTO ضارًا"، والتي في السنوات اللاحقة التي سيتم الرجوع إليها في أغلب الأحيان ، للأسف ، غالبًا ما يكون المؤلفون الذين لم يروا أكثر من عنوانه ، والذي أصبح حجر الزاوية في شهرتي من خلال أن أصبح قالبًا:سنرى جميع أنواع المقالات تحت عنوان "X تعتبر ضارة" لأي X تقريبًا ، بما في ذلك واحدة بعنوان "Dijkstra تعتبر ضارة".لكن ماذا حدث؟لقد قدمت ورقة تحت العنوان "قضية ضد بيان GOTO"، والتي ، من أجل تسريع نشرها ، تحول المحرر إلى" رسالة إلى المحرر "، وفي هذه العملية أعطاها لقبًا جديدًا للاختراع الخاص به!كان المحرر نيكلوس ويرث.

هناك ورقة بحثية كلاسيكية مدروسة جيدًا حول هذا الموضوع، والتي يمكن مطابقتها مع ورقة ديكسترا البرمجة المنظمة مع الانتقال إلى البيانات, ، بقلم دونالد إي.نوث.تساعد القراءة على إعادة تأسيس السياق والفهم غير العقائدي للموضوع.في هذه المقالة، يتم ذكر رأي ديكسترا في هذه القضية وهو أكثر قوة:

دونالد إي.نوث: أعتقد أنه من خلال تقديم مثل هذا الرأي ، لا أختلف في الواقع بشكل حاد مع أفكار Dijkstra ، لأنه كتب مؤخرًا ما يلي:"من فضلك لا تقع في فخ الاعتقاد بأنني عقائدي رهيب حول [الذهاب إلى البيان]. لدي شعور غير مريح بأن الآخرين يخرجون منه ، كما لو أن المشكلات المفاهيمية للبرمجة يمكن حلها من خلال خدعة واحدة ، من خلال شكل بسيط من انضباط الترميز!"

هل كانت مفيدة؟

المحلول

العبارات التالية هي تعميمات.في حين أنه من الممكن دائمًا المطالبة بالاستثناء، إلا أنه عادةً (حسب تجربتي ورأيي المتواضع) لا يستحق المخاطرة.

  1. يوفر الاستخدام غير المقيد لعناوين الذاكرة (إما GOTO أو المؤشرات الأولية) فرصًا كثيرة جدًا لارتكاب أخطاء يمكن تجنبها بسهولة.
  2. كلما زادت الطرق المتاحة للوصول إلى "موقع" معين في الكود، قلّت ثقة الشخص بشأن حالة النظام في تلك المرحلة.(انظر أدناه.)
  3. البرمجة المنظمة IMHO لا تتعلق بـ "تجنب GOTOs" بقدر ما تتعلق بجعل بنية الكود تتوافق مع بنية البيانات.على سبيل المثال، بنية بيانات متكررة (على سبيل المثال.تتم معالجة المصفوفة والملف المتسلسل وما إلى ذلك) بشكل طبيعي بواسطة وحدة متكررة من التعليمات البرمجية.وجود هياكل مدمجة (على سبيل المثال.بينما، من أجل، حتى، لكل، وما إلى ذلك) يسمح للمبرمج بتجنب الملل الناتج عن تكرار نفس أنماط التعليمات البرمجية المبتذلة.
  4. حتى لو كانت GOTO عبارة عن تفاصيل تنفيذ منخفضة المستوى (ليس هذا هو الحال دائمًا!) فهي أقل من المستوى الذي يجب أن يفكر فيه المبرمج.كم عدد المبرمجين الذين يوازنون دفاتر الشيكات الشخصية الخاصة بهم في النظام الثنائي الخام؟كم عدد المبرمجين الذين يشعرون بالقلق بشأن أي قطاع على القرص يحتوي على سجل معين، بدلاً من مجرد توفير مفتاح لمحرك قاعدة البيانات (وكم عدد الطرق التي يمكن أن تسوء بها الأمور إذا كتبنا جميع برامجنا من حيث قطاعات القرص الفعلي)؟

الحواشي على ما سبق:

بالنسبة للنقطة الثانية خذ بالكود التالي:

a = b + 1
/* do something with a */

عند نقطة "افعل شيئًا" في الكود، يمكننا أن نذكر ذلك بثقة عالية a أكبر من b.(نعم، أنا أتجاهل احتمال تجاوز عدد صحيح غير محصور.دعونا لا نتعثر في مثال بسيط.)

ومن ناحية أخرى، إذا تمت قراءة الكود بهذه الطريقة:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

إن تعدد الطرق للوصول إلى التصنيف 10 يعني أنه يتعين علينا أن نعمل بجهد أكبر لنكون واثقين بشأن العلاقات بين a و b في تلك النقطة.(في الواقع، في الحالة العامة، لا يمكن حسم الأمر!)

فيما يتعلق بالنقطة 4، فإن فكرة "الذهاب إلى مكان ما" في الكود هي مجرد استعارة.لا شيء "يذهب" حقًا إلى أي مكان داخل وحدة المعالجة المركزية باستثناء الإلكترونات والفوتونات (للحرارة المهدورة).في بعض الأحيان نتخلى عن استعارة أخرى أكثر فائدة.أتذكر أنني واجهت (قبل بضعة عقود!) لغة حيث

if (some condition) {
  action-1
} else {
  action-2
}

تم تنفيذه على جهاز افتراضي من خلال تجميع الإجراء-1 والإجراء-2 كإجراءات روتينية بدون معلمات خارج الخط، ثم استخدام رمز تشغيل VM واحد ذي وسيطتين والذي يستخدم القيمة المنطقية للشرط لاستدعاء أحدهما أو الآخر.كان المفهوم ببساطة هو "اختر ما تريد استدعاؤه الآن" بدلاً من "اذهب إلى هنا أو اذهب إلى هناك".مرة أخرى، مجرد تغيير في الاستعارة.

نصائح أخرى

XKCD's GOTO Comic

قال أحد زملائي في العمل إن السبب الوحيد لاستخدام GOTO هو إذا قمت ببرمجة نفسك حتى الآن في الزاوية بحيث يكون هذا هو السبيل الوحيد للخروج.بمعنى آخر، التصميم المناسب مسبقًا ولن تحتاج إلى استخدام GOTO لاحقًا.

اعتقدت أن هذا الهزلي يوضح أنه بشكل جميل "يمكنني إعادة هيكلة تدفق البرنامج ، أو استخدام" Goto "قليلاً بدلاً من ذلك." Goto هي طريقة ضعيفة للخروج عندما يكون لديك تصميم ضعيف. فيلوسيرابتور تفترس الضعفاء.

في بعض الأحيان يكون من الصحيح استخدام GOTO كبديل لمعالجة الاستثناءات ضمن وظيفة واحدة:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

يبدو أن كود COM يقع في هذا النمط في كثير من الأحيان.

لا أستطيع إلا أن أتذكر استخدام goto مرة واحدة.كان لدي سلسلة من خمس حلقات معدودة متداخلة وكنت بحاجة إلى أن أكون قادرًا على الخروج من البنية بأكملها من الداخل مبكرًا بناءً على شروط معينة:

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

كان بإمكاني بسهولة الإعلان عن متغير فاصل منطقي واستخدامه كجزء من الشرط لكل حلقة، ولكن في هذه الحالة قررت أن GOTO كان عمليًا وقابل للقراءة تمامًا.

لم يهاجمني أي فيلوسيرابتور.

لقد كان لدينا هذا بالفعل مناقشة وأنا أقف جانبا وجهة نظري.

علاوة على ذلك، لقد سئمت من الأشخاص الذين يصفون الهياكل اللغوية ذات المستوى الأعلى بأنها "goto مقنعين" لأنه من الواضح أنهم لم يفهموا الهدف على الاطلاق.على سبيل المثال:

حتى بنية التحكم في الاستمرارية المتقدمة في Scheme يمكن وصفها بأنها goto متطورة.

هذا محض هراء. كل يمكن تنفيذ هيكل التحكم من حيث goto لكن هذه الملاحظة تافهة تمامًا وعديمة الفائدة. goto لا تعتبر ضارة بسبب آثارها الإيجابية ولكن بسبب نتائجها السلبية والتي تم القضاء عليها عن طريق البرمجة المنظمة.

وبالمثل، فإن القول بأن "GOTO هي أداة، وكما هو الحال مع جميع الأدوات، يمكن استخدامها وإساءة استخدامها" هو أمر غير صحيح تمامًا.لا يوجد عامل بناء حديث يستخدم صخرة ويطالب بأنه "أداة". تم استبدال الصخور بالمطارق. goto تم استبدالها بهياكل التحكم.إذا تقطعت السبل بعامل البناء في البرية دون مطرقة، فإنه بالطبع سيستخدم صخرة بدلا من ذلك.إذا كان على المبرمج أن يستخدم لغة برمجة أدنى لا تحتوي على الميزة X، حسنًا، بالطبع قد يتعين عليه استخدامها goto بدلاً من.ولكن إذا استخدمتها في أي مكان آخر بدلاً من ميزة اللغة المناسبة، فمن الواضح أنها لم تفهم اللغة بشكل صحيح وتستخدمها بشكل خاطئ.انها حقا بهذه البساطة.

Goto هو مكان منخفض للغاية في قائمة الأشياء التي يجب تضمينها في البرنامج فقط من أجله.هذا لا يعني أنه غير مقبول.

يمكن أن يكون Goto رائعًا لأجهزة الدولة.عبارة التبديل في الحلقة هي (حسب الأهمية النموذجية):(أ) لا يمثل في الواقع تدفق التحكم، (ب) قبيح، (ج) من المحتمل أن يكون غير فعال اعتمادًا على اللغة والمترجم.لذلك ينتهي بك الأمر إلى كتابة وظيفة واحدة لكل ولاية ، والقيام بأشياء مثل "Return Next_state ؛" التي تبدو حتى غوتو.

من المؤكد أنه من الصعب برمجة أجهزة الحالة بطريقة تجعلها سهلة الفهم.ومع ذلك، لا تتعلق أي من هذه الصعوبات باستخدام goto، ولا يمكن تقليل أي منها باستخدام هياكل التحكم البديلة.ما لم تكن لغتك تحتوي على بنية "آلة الحالة".الألغام لا.

في تلك المناسبات النادرة عندما تكون الخوارزمية الخاصة بك أكثر قابلية للفهم حقًا من حيث المسار عبر سلسلة من العقد (الحالات) المتصلة بواسطة مجموعة محدودة من التحولات المسموح بها (gotos)، بدلاً من أي تدفق تحكم أكثر تحديدًا (الحلقات، الشرطيات، وما إلى ذلك) )، فيجب أن يكون ذلك واضحًا في الكود.ويجب عليك رسم مخطط جميل.

يمكن أن يكون setjmp/longjmp مفيدًا لتنفيذ الاستثناءات أو السلوك الشبيه بالاستثناء.على الرغم من عدم الإشادة بها عالميًا، إلا أن الاستثناءات تعتبر بشكل عام بنية تحكم "صالحة".

تعتبر setjmp/longjmp "أكثر خطورة" من goto بمعنى أنه من الصعب استخدامها بشكل صحيح، ناهيك عن كونها مفهومة.

لم يكن هناك أبدًا ، ولن يكون هناك أي لغة يصعب فيها كتابة التعليمات البرمجية السيئة.--دونالد كنوث.

لن يؤدي إخراج goto من C إلى تسهيل كتابة تعليمات برمجية جيدة في C.في الواقع، من الأفضل أن تفوت النقطة التي هي C مفترض لتكون قادرة على العمل كلغة تجميع مجيدة.

بعد ذلك، ستكون "المؤشرات تعتبر ضارة"، ثم "الكتابة البطية تعتبر ضارة".إذن من سيبقى للدفاع عنك عندما يأتون لإزالة بنية البرمجة غير الآمنة الخاصة بك؟إيه؟

في لينكس:باستخدام goto في رمز Kernel في Kernel Trap، هناك مناقشة مع Linus Torvalds و"شخص جديد" حول استخدام GOTOs في كود Linux.هناك بعض النقاط الجيدة جدًا هناك، وقد ارتدى لينوس تلك الغطرسة المعتادة :)

بعض المقاطع:

لينوس:"لا ، لقد تم غسل دماغ من قبل أشخاص CS الذين اعتقدوا أن نيكلوس ويرث كان يعرف فعليًا ما كان يتحدث عنه.لم يفعل.ليس لديه أدنى فكرة عن الافتراض ".

-

لينوس:"أعتقد أن غوتو بخير ، وغالبًا ما تكون أكثر قابلية للقراءة من كميات كبيرة من المسافة البادئة."

-

لينوس:"بالطبع ، بلغات غبية مثل باسكال ، حيث لا يمكن أن تكون الملصقات وصفية ، يمكن أن تكون غوتو سيئة."

شركة، goto يعمل فقط ضمن نطاق الوظيفة الحالية، والتي تميل إلى توطين أي أخطاء محتملة. setjmp و longjmp فهي أكثر خطورة بكثير، كونها غير محلية ومعقدة وتعتمد على التنفيذ.ولكن من الناحية العملية، فهي غامضة جدًا وغير شائعة بحيث لا تسبب العديد من المشكلات.

أعتقد أن خطورة goto في C مبالغ فيه إلى حد كبير.تذكر أن الأصلي goto حدثت هذه الحجج في أيام لغات مثل لغة BASIC القديمة، حيث كان المبتدئون يكتبون كود السباغيتي مثل هذا:

3420 IF A > 2 THEN GOTO 1430

هنا يصف لينوس الاستخدام المناسب لـ goto: http://www.kernel.org/doc/Documentation/CodingStyle (الفصل 7).

اليوم، من الصعب رؤية الصفقة الكبيرة حول GOTO بيان لأن الأشخاص "البرمجة المنظمة" فازوا في الغالب بالنقاش وأن لغات اليوم لديها هياكل تحكم كافية في التدفق لتجنبها GOTO.

احسب عدد gotos في برنامج C الحديث.أضف الآن عدد break, continue, ، و return صياغات.علاوة على ذلك، قم بإضافة عدد مرات الاستخدام if, else, while, switch أو case.هذا حول كم GOTOكان من الممكن أن يكون برنامجك كذلك لو كنت تكتب بلغة FORTRAN أو BASIC في عام 1968 عندما كتب ديكسترا رسالته.

كانت لغات البرمجة في ذلك الوقت تفتقر إلى التحكم في التدفق.على سبيل المثال، في Dartmouth BASIC الأصلي:

  • IF البيانات لم يكن لها ELSE.إذا كنت تريد واحدة، كان عليك أن تكتب:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • حتى لو كان الخاص بك IF البيان لا يحتاج إلى ELSE, ، كان لا يزال يقتصر على سطر واحد، والذي يتكون عادة من أ GOTO.

  • لم يكن هناك DO...LOOP إفادة.لغيرFOR الحلقات، كان عليك إنهاء الحلقة بأمر صريح GOTO أو IF...GOTO العودة إلى البداية.

  • لم يكن هناك SELECT CASE.كان عليك أن تستخدم ON...GOTO.

لذلك، انتهى بك الأمر مع كثير ل GOTOس في برنامجكولا يمكنك الاعتماد على تقييد GOTOs ضمن روتين فرعي واحد (لأن GOSUB...RETURN كان مثل هذا المفهوم الضعيف للإجراءات الفرعية)، لذلك هذه GOTOيمكن أن تذهب في أى مكان.ومن الواضح أن هذا جعل من الصعب متابعة تدفق السيطرة.

وهنا مكان مكافحةGOTO جاءت الحركة من.

يمكن أن يوفر Go To نوعًا من البديل لمعالجة الاستثناءات "الحقيقية" في حالات معينة.يعتبر:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

من الواضح أنه تم تبسيط هذا الرمز ليشغل مساحة أقل، لذا لا تنشغل كثيرًا بالتفاصيل.لكن فكر في بديل رأيته مرات عديدة إنتاج رمز من قبل المبرمجين يذهبون إلى أطوال سخيفة لتجنب استخدام goto:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

الآن من الناحية الوظيفية، يقوم هذا الرمز بنفس الشيء تمامًا.في الواقع، الكود الذي تم إنشاؤه بواسطة المترجم متطابق تقريبًا.ومع ذلك، في حماسة المبرمج لاسترضاء نوغوتو (إله التوبيخ الأكاديمي المخيف)، لقد كسر هذا المبرمج تمامًا المصطلح الأساسي الذي يستخدمه while تمثل الحلقة رقمًا حقيقيًا لقابلية قراءة الكود. هذا ليس أفضل.

لذا، المغزى من القصة هو، إذا وجدت نفسك تلجأ إلى شيء غبي جدًا لتجنب استخدام goto، فلا تفعل ذلك.

دونالد إي.أجاب كنوث على هذا السؤال في كتاب "البرمجة المتعلمة" الصادر عام 1992 عن CSLI.في الصفحة.17 هناك مقال "البرمجة المنظمة مع عبارات goto" (بي دي إف).أعتقد أنه ربما تم نشر المقال في كتب أخرى أيضًا.

توضح المقالة اقتراح Dijkstra وتصف الظروف التي يكون فيها ذلك صالحًا.ولكنه يقدم أيضًا عددًا من الأمثلة المضادة (المشكلات والخوارزميات) التي لا يمكن إعادة إنتاجها بسهولة باستخدام الحلقات المنظمة فقط.

تحتوي المقالة على وصف كامل للمشكلة وتاريخها وأمثلة وأمثلة مضادة.

لقد انجذب جاي بالو بإضافة إجابة، وسأضيف 0.02 جنيه إسترليني.لو لم يكن برونو رانشيرت قد فعل ذلك بالفعل، لكنت قد ذكرت مقالة "البرمجة المنظمة باستخدام عبارات GOTO" لـ Knuth.

الشيء الوحيد الذي لم أشاهد مناقشته هو نوع التعليمات البرمجية التي، على الرغم من أنها ليست شائعة تمامًا، يتم تدريسها في كتب فورتران المدرسية.أشياء مثل النطاق الممتد لحلقة DO والمسارات الفرعية ذات التشفير المفتوح (تذكر أن هذا سيكون Fortran II أو Fortran IV أو Fortran 66 - وليس Fortran 77 أو 90).هناك على الأقل احتمال أن تكون التفاصيل النحوية غير دقيقة، لكن المفاهيم يجب أن تكون دقيقة بدرجة كافية.المقتطفات في كل حالة موجودة داخل وظيفة واحدة.

لاحظ أن الكتاب ممتاز ولكنه مؤرخ (ونفدت طباعته)'عناصر أسلوب البرمجة، الطبعة الثانية" بواسطة Kernighan & Plauger يتضمن بعض الأمثلة الواقعية لإساءة استخدام GOTO من كتب البرمجة المدرسية في عصرها (أواخر السبعينيات).ومع ذلك، فإن المادة أدناه ليست من هذا الكتاب.

نطاق ممتد لحلقة DO

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

كان أحد أسباب هذا الهراء هو البطاقة المثقبة ذات الطراز القديم.قد تلاحظ أن التسميات (خارج التسلسل بشكل جيد لأن ذلك كان نمطًا أساسيًا!) موجودة في العمود 1 (في الواقع، يجب أن تكون في الأعمدة من 1 إلى 5) وأن الكود موجود في الأعمدة من 7 إلى 72 (العمود 6 كان استمرارًا عمود العلامة).سيتم إعطاء الأعمدة من 73 إلى 80 رقمًا تسلسليًا، وكانت هناك آلات تقوم بفرز مجموعات البطاقات المثقوبة في ترتيب رقم تسلسلي.إذا كان لديك برنامجك على بطاقات متسلسلة وتحتاج إلى إضافة بضع بطاقات (أسطر) في منتصف الحلقة، فسيتعين عليك إعادة كل شيء بعد تلك الأسطر الإضافية.ومع ذلك، إذا قمت باستبدال بطاقة واحدة بعناصر GOTO، فيمكنك تجنب إعادة تسلسل جميع البطاقات - لقد قمت للتو بإدخال البطاقات الجديدة في نهاية الروتين بأرقام تسلسلية جديدة.اعتبرها المحاولة الأولى لـ "الحوسبة الخضراء" - توفير البطاقات المثقوبة (أو بشكل أكثر تحديدًا، توفير جهد إعادة الكتابة - وتوفير أخطاء إعادة المفاتيح اللاحقة).

أوه، قد تلاحظ أيضًا أنني أغش ولا أصرخ - فقد تمت كتابة Fortran IV بأحرف كبيرة بشكل طبيعي.

روتين فرعي مفتوح الترميز

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

إن GOTO بين التسميات 76 و54 هو نسخة من goto المحسوبة.إذا كان المتغير i له القيمة 1، فانتقل إلى التسمية الأولى في القائمة (123)؛إذا كانت لها القيمة 2، فانتقل إلى الثانية، وهكذا.الجزء من 76 إلى goto المحسوب هو روتين فرعي مفتوح الترميز.لقد كان جزءًا من التعليمات البرمجية يتم تنفيذه مثل روتين فرعي إلى حد ما، ولكنه مكتوب في نص الوظيفة.(كان لدى فورتران أيضًا وظائف بيانية - والتي كانت عبارة عن وظائف مضمنة تم تركيبها في سطر واحد.)

كانت هناك بنيات أسوأ من goto المحسوبة - يمكنك تعيين تسميات للمتغيرات ثم استخدام goto المعين.البحث في غوغل غوتو المعينة يخبرني أنه تم حذفه من Fortran 95.استعد لثورة البرمجة المنظمة التي يمكن القول إلى حد ما أنها بدأت علنًا برسالة أو مقالة Dijkstra "GOTO تعتبر ضارة".

وبدون بعض المعرفة بأنواع الأشياء التي تم القيام بها في فورتران (وفي اللغات الأخرى، والتي سقط معظمها على جانب الطريق بحق)، فمن الصعب علينا نحن الوافدين الجدد أن نفهم نطاق المشكلة التي كان ديكسترا يتعامل معها.حسنًا، لم أبدأ البرمجة إلا بعد مرور عشر سنوات على نشر تلك الرسالة (لكن من سوء حظي أن أبرمج في Fortran IV لفترة من الوقت).

غوتو تعتبر مفيدة.

بدأت البرمجة في عام 1975.بالنسبة للمبرمجين في حقبة السبعينيات، كانت عبارة "اعتبرت ضارة" تشير بشكل أو بآخر إلى أن لغات البرمجة الجديدة ذات هياكل التحكم الحديثة تستحق المحاولة.لقد جربنا اللغات الجديدة.لقد تحولنا بسرعة.لم نعد أبدًا.

لم نعد أبدًا، ولكن إذا كنت أصغر سنًا، فأنت لم تكن هناك أبدًا في المقام الأول.

الآن، قد لا تكون الخلفية في لغات البرمجة القديمة مفيدة جدًا إلا كمؤشر على عمر المبرمج.ومع ذلك، يفتقر المبرمجون الشباب إلى هذه الخلفية، لذلك لم يعودوا يفهمون الرسالة التي ينقلها شعار "اعتبر ضارًا". للجمهور المستهدف في وقت تقديمه.

الشعارات التي لا يفهمها المرء ليست مفيدة للغاية.وربما يكون من الأفضل أن ننسى مثل هذه الشعارات.مثل هذه الشعارات لا تساعد.

ومع ذلك، فإن هذا الشعار الخاص، "Goto يعتبر ضارًا"، قد اتخذ حياة أوندد خاصة به.

هل يمكن عدم إساءة استخدام goto؟إجابة:بالتأكيد، ولكن ماذا في ذلك؟عمليا كل عنصر البرمجة يستطيع تعرض للاساءة.المتواضع bool على سبيل المثال، يتم إساءة استخدامه في كثير من الأحيان أكثر مما يود البعض منا تصديقه.

على النقيض من ذلك، لا أستطيع أن أتذكر أنني التقيت بحالة فعلية واحدة لإساءة استخدام goto منذ عام 1990.

ربما لا تكون المشكلة الأكبر في goto تقنية، بل اجتماعية.يبدو أحيانًا أن المبرمجين الذين لا يعرفون الكثير يشعرون أن إهمال goto يجعلهم يبدون أذكياء.قد تضطر إلى إرضاء هؤلاء المبرمجين من وقت لآخر.هكذا هي الحياة.

أسوأ ما في goto اليوم هو أنه لا يتم استخدامه بشكل كافٍ.

لا يوجد مثل هذه الأشياء GOTO تعتبر ضارة.

GOTO هي أداة، وكما هو الحال مع جميع الأدوات، يمكن استخدامها و سوء المعاملة.

ومع ذلك، هناك العديد من الأدوات في عالم البرمجة التي تميل إلى أن تكون كذلك سوء المعاملة أكثر من كونه مستخدم, ، وGOTO هو واحد منهم.ال مع بيان دلفي هو آخر.

أنا شخصياً لا أستخدمه أيضًا في الكود النموذجي, ، ولكن كان لدي الاستخدام الغريب لكليهما اذهب إلى و مع التي كانت مبررة، وكان الحل البديل يحتوي على المزيد من التعليمات البرمجية.

الحل الأفضل هو أن يحذرك المترجم فقط من أن الكلمة الأساسية كانت ملوث, ، وسيتعين عليك وضع بعض التوجيهات العملية حول البيان للتخلص من التحذيرات.

إنه مثل إخبار أطفالك بذلك لا تعمل بالمقص.المقص ليس سيئًا، لكن بعض استخدامه ربما لا يكون أفضل طريقة للحفاظ على صحتك.

منذ أن بدأت في القيام ببعض الأشياء في نواة لينكس، لم تعد تزعجني gotos كثيرًا كما كانت تفعل من قبل.في البداية شعرت بالرعب نوعًا ما عندما رأيت أنهم (رجال النواة) أضافوا gotos إلى الكود الخاص بي.لقد اعتدت منذ ذلك الحين على استخدام gotos، في بعض السياقات المحدودة، وسأستخدمها الآن بنفسي أحيانًا.عادةً ما تكون عبارة عن انتقال إلى نهاية الوظيفة للقيام بنوع من التنظيف والإنقاذ، بدلاً من تكرار نفس عملية التنظيف والإنقاذ في عدة أماكن في الوظيفة.وعادةً، لا يكون شيئًا كبيرًا بما يكفي لتسليمه إلى وظيفة أخرى - على سبيل المثال.يعد تحرير بعض المتغيرات المحلية (k) malloc'ed حالة نموذجية.

لقد كتبت رمزًا يستخدم setjmp/longjmp مرة واحدة فقط.كان في برنامج تسلسل طبلة MIDI.حدث التشغيل في عملية منفصلة عن كل تفاعلات المستخدم، واستخدمت عملية التشغيل ذاكرة مشتركة مع عملية واجهة المستخدم للحصول على المعلومات المحدودة التي تحتاجها لإجراء التشغيل.عندما أراد المستخدم إيقاف التشغيل، قامت عملية التشغيل فقط بـ "العودة إلى البداية" لفترة طويلة للبدء من جديد، بدلاً من بعض عمليات التفكيك المعقدة أينما تم تنفيذها عندما أراد المستخدم إيقافها.لقد كان يعمل بشكل رائع، وكان بسيطًا، ولم أواجه أي مشاكل أو أخطاء متعلقة به في تلك الحالة.

setjmp/longjmp لها مكانها - ولكن هذا المكان هو المكان الذي لن تزوره على الأرجح إلا مرة واحدة لفترة طويلة جدًا.

يحرر:لقد نظرت للتو إلى الكود.لقد كان في الواقع siglongjmp() الذي استخدمته، وليس longjmp (ليس هذا أمرًا كبيرًا، لكنني نسيت أن siglongjmp موجود بالفعل.)

لم يكن الأمر كذلك أبدًا، طالما كنت قادرًا على التفكير بنفسك.

إذا كنت تكتب VM في لغة C، فقد اتضح أن استخدام gotos (دول مجلس التعاون الخليجي) المحسوبة مثل هذا:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

يعمل بشكل أسرع بكثير من المفتاح التقليدي داخل الحلقة.

لأن goto يمكن استخدامها للخلط بين البرمجة الوصفية

Goto هو على حد سواء أ مستوى عال و أ مستوى منخفض تعبير التحكم، ونتيجة لذلك لا يحتوي على نمط تصميم مناسب يناسب معظم المشكلات.

إنه مستوى منخفض بمعنى أن goto هي عملية بدائية تنفذ شيئًا أعلى مثل while أو foreach أو شيء ما.

إنه مستوى عال بمعنى أنه عند استخدامه بطرق معينة، فإنه يأخذ تعليمات برمجية يتم تنفيذها بتسلسل واضح، وبطريقة غير متقطعة، باستثناء الحلقات المنظمة، ويحولها إلى أجزاء منطقية، مع ما يكفي من gotos، يتم إعادة تجميع حقيبة المنطق ديناميكيًا.

لذلك، هناك ركيك و شر جانب ل goto.

ال الجانب المبتذل هو أن goto الذي يشير إلى الأعلى يمكنه تنفيذ حلقة معقولة تمامًا ويمكن أن يقوم goto الذي يشير إلى الأسفل بعمل معقول تمامًا break أو return.بالطبع فعلية while, break, ، أو return سيكون أكثر قابلية للقراءة، حيث لن يضطر الإنسان الفقير إلى محاكاة تأثير goto من أجل الحصول على الصورة الكبيرة.لذا، فكرة سيئة بشكل عام.

ال الجانب الشيطاني يتضمن روتينًا لا يستخدم goto للوقت أو الاستراحة أو العودة، بل يستخدمه لما يسمى منطق السباغيتي.في هذه الحالة، يقوم المطور المبتهج بإنشاء أجزاء من التعليمات البرمجية من متاهة من أدوات goto، والطريقة الوحيدة لفهمها هي محاكاتها عقليًا ككل، وهي مهمة متعبة للغاية عندما يكون هناك العديد من أدوات goto.أعني، تخيل مشكلة تقييم الكود حيث يكون ملف else ليس بالضبط عكس if, ، حيث تتداخل ifقد يسمح في بعض الأمور التي كان يرفضها الخارجي if, ، الخ، الخ.

أخيرًا، لتغطية الموضوع حقًا، يجب أن نلاحظ أن جميع اللغات المبكرة باستثناء لغة الغول قدمت في البداية عبارات فردية فقط تخضع لإصداراتها من if-then-else.لذا، فإن الطريقة الوحيدة للقيام بالكتلة الشرطية هي القيام بذلك goto حوله باستخدام الشرطية العكسية.أعلم أنه جنوني، لكني قرأت بعض المواصفات القديمة.تذكر أن أجهزة الكمبيوتر الأولى تمت برمجتها باستخدام رمز الآلة الثنائية، لذا أفترض أن أي نوع من HLL كان منقذًا للحياة؛أعتقد أنهم لم يكونوا انتقائيين للغاية بشأن ميزات HLL التي حصلوا عليها بالضبط.

وبعد أن قلت كل ما اعتدت على التمسك واحدة goto في كل برنامج كتبته "فقط لإزعاج الأصوليين".

إن إنكار استخدام عبارة GOTO للمبرمجين يشبه إخبار النجار بعدم استخدام المطرقة لأنها قد تلحق الضرر بالجدار أثناء دق المسمار.المبرمج الحقيقي يعرف كيف ومتى يستخدم GOTO.لقد تابعت بعضًا مما يسمى بـ "البرامج المنظمة" ورأيت مثل هذا الكود الفظيع فقط لتجنب استخدام GOTO، حتى أتمكن من إطلاق النار على المبرمج.حسنًا، دفاعًا عن الجانب الآخر، رأيت بعض أكواد السباغيتي الحقيقية أيضًا، ومرة ​​أخرى، يجب إطلاق النار على هؤلاء المبرمجين أيضًا.

فيما يلي مثال صغير واحد فقط عن الكود الذي وجدته.

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

-----------------------أو----------------------

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10

"في هذا الرابط http://kerneltrap.org/node/553/2131"

ومن المفارقات أن إزالة goto أدت إلى ظهور خطأ:تم حذف استدعاء Spinlock.

ينبغي اعتبار الورقة الأصلية على أنها "الدخول غير المشروط الذي يعتبر ضارًا".وكان على وجه الخصوص يدعو إلى شكل من أشكال البرمجة يعتمد على الشرط (if) والتكرارية (while) يبني، بدلاً من الاختبار والقفز الشائع في التعليمات البرمجية المبكرة. goto لا يزال مفيدًا في بعض اللغات أو الظروف، حيث لا يوجد هيكل تحكم مناسب.

حول المكان الوحيد وأنا أتفق غوتو استطاع يتم استخدامه عندما تحتاج إلى التعامل مع الأخطاء، وكل نقطة معينة يحدث فيها خطأ تتطلب معالجة خاصة.

على سبيل المثال، إذا كنت تحصل على الموارد وتستخدم الإشارات أو كائنات المزامنة، فيجب عليك الحصول عليها بالترتيب ويجب عليك دائمًا تحريرها بالطريقة المعاكسة.

تتطلب بعض التعليمات البرمجية نمطًا غريبًا جدًا للاستيلاء على هذه الموارد، ولا يمكنك كتابة بنية تحكم سهلة الصيانة والفهم للتعامل بشكل صحيح مع كل من الاستيلاء على هذه الموارد وتحريرها لتجنب الجمود.

من الممكن دائمًا القيام بذلك بشكل صحيح دون الانتقال إلى goto، ولكن في هذه الحالة وفي حالات أخرى، يعد Goto هو الحل الأفضل في المقام الأول لسهولة القراءة وقابلية الصيانة.

-آدم

أحد استخدامات GOTO الحديثة هو بواسطة مترجم C# لإنشاء أجهزة الحالة للعدادين المحددة بواسطة عائد العائد.

GOTO هو شيء يجب استخدامه من قبل المترجمين وليس المبرمجين.

حتى يتم تصنيف الفواصل والاستمرار في C وC++ (من بين المذنبين الآخرين)، سيستمر goto في القيام بدور.

إذا كان GOTO نفسه شريرًا، فسيكون المترجمون أشرارًا، لأنهم يقومون بإنشاء JMPs.إذا كان القفز إلى مجموعة من التعليمات البرمجية، وخاصة اتباع المؤشر، أمرًا شريرًا بطبيعته، فإن تعليمات RETurn ستكون شريرة.بل الشر في احتمال الإساءة.

في بعض الأحيان، اضطررت إلى كتابة تطبيقات كان عليها تتبع عدد من الكائنات حيث كان على كل كائن أن يتبع تسلسلًا معقدًا من الحالات استجابةً للأحداث، ولكن الأمر برمته كان بالتأكيد خيطًا واحدًا.التسلسل النموذجي للحالات، إذا تم تمثيله برمز زائف سيكون:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

أنا متأكد من أن هذا ليس جديدًا، ولكن الطريقة التي تعاملت بها في C(++) كانت تحديد بعض وحدات الماكرو:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

بعد ذلك (بافتراض أن الحالة هي 0 في البداية) تتحول آلة الحالة المنظمة أعلاه إلى التعليمات البرمجية المنظمة:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

مع وجود اختلاف في هذا، يمكن أن يكون هناك اتصال وإرجاع، لذلك يمكن لبعض أجهزة الحالة أن تعمل مثل الإجراءات الفرعية لأجهزة الحالة الأخرى.

هل هو غير عادي؟نعم.هل يتطلب الأمر بعض التعلم من جانب المشرف؟نعم.هل هذا التعلم يؤتي ثماره؟أعتقد ذلك.هل يمكن أن يتم ذلك بدون GOTOs التي تقفز إلى الكتل؟لا.

أتجنب ذلك نظرًا لأن زميل العمل/المدير سوف يشكك بلا شك في استخدامه إما في مراجعة الكود أو عندما يتعثر فيه.على الرغم من أنني أعتقد أن لها استخدامات (حالة معالجة الأخطاء على سبيل المثال) - فسوف تتعارض مع بعض المطورين الآخرين الذين سيواجهون نوعًا ما من المشاكل معها.

لا يستحق كل هذا العناء.

لقد وجدت نفسي مجبرًا على استخدام goto، لأنني حرفيًا لم أستطع التفكير في طريقة أفضل (أسرع) لكتابة هذا الرمز:

كان لدي جسم معقد، وكنت بحاجة لإجراء بعض العمليات عليه.إذا كان الكائن في حالة واحدة، فيمكنني إجراء نسخة سريعة من العملية، وإلا كان علي إجراء نسخة بطيئة من العملية.كان الأمر أنه في بعض الحالات، في منتصف العملية البطيئة، كان من الممكن إدراك أنه كان من الممكن القيام بذلك من خلال العملية السريعة.

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

كان هذا جزءًا مهمًا من السرعة لرمز واجهة المستخدم في الوقت الفعلي، لذلك أعتقد بصدق أن GOTO كان له ما يبرره هنا.

هوغو

تقريبًا في جميع المواقف التي يمكن فيها استخدام goto، يمكنك فعل الشيء نفسه باستخدام بنيات أخرى.يتم استخدام Goto بواسطة المترجم على أي حال.

أنا شخصياً لا أستخدمه بشكل صريح أبدًا، ولا أحتاج إلى ذلك أبدًا.

شيء واحد لم أر من أي من الإجابات هنا أن الحل "goto" غالبًا ما يكون أكثر فعالية من أحد حلول البرمجة المنظمة التي يتم ذكرها غالبًا.

خذ بعين الاعتبار حالة الحلقات المتداخلة المتعددة، حيث يتم استخدام "goto" بدلاً من مجموعة من if(breakVariable) ومن الواضح أن الأقسام أكثر كفاءة.غالبًا ما يكون الحل "ضع حلقاتك في دالة واستخدم الإرجاع" غير معقول تمامًا.في الحالة المحتملة التي تستخدم فيها الحلقات متغيرات محلية، يتعين عليك الآن تمريرها جميعًا من خلال معلمات الوظيفة، ومن المحتمل التعامل مع الكثير من المشكلات الإضافية التي تنشأ من ذلك.

الآن فكر في حالة التنظيف، التي استخدمتها كثيرًا بنفسي، وهي شائعة جدًا لدرجة أنني من المفترض أن أكون مسؤولاً عن بنية Try{} Catch {} غير المتوفرة في العديد من اللغات.عدد عمليات التحقق والمتغيرات الإضافية المطلوبة لإنجاز نفس الشيء هو أسوأ بكثير من تعليمة واحدة أو اثنتين لإجراء القفزة، ومرة ​​أخرى، حل الوظيفة الإضافية ليس حلاً على الإطلاق.لا يمكنك أن تخبرني أن هذا أكثر قابلية للإدارة أو للقراءة.

الآن قد لا تكون مساحة التعليمات البرمجية واستخدام المكدس ووقت التنفيذ ذات أهمية كافية في العديد من المواقف للعديد من المبرمجين، ولكن عندما تكون في بيئة مضمنة بها 2 كيلو بايت فقط من مساحة التعليمات البرمجية للعمل بها، فإن 50 بايت من التعليمات الإضافية لتجنب تعليمات محددة بوضوح "goto" أمر مثير للضحك، وهذه ليست حالة نادرة كما يعتقد العديد من المبرمجين رفيعي المستوى.

كانت عبارة "goto ضارة" مفيدة جدًا في التحرك نحو البرمجة المنظمة، حتى لو كانت دائمًا مبالغة في التعميم.في هذه المرحلة، سمعناها جميعًا بما يكفي لنكون حذرين من استخدامها (كما ينبغي لنا).عندما تكون الأداة المناسبة للمهمة بشكل واضح، فلا داعي للخوف منها.

يمكنك استخدامه للانفصال عن حلقة متداخلة بعمق، ولكن في معظم الأحيان يمكن إعادة هيكلة التعليمات البرمجية الخاصة بك لتكون أكثر نظافة بدون حلقات متداخلة بعمق.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top