目次
1. ما هي عبارة goto
تُعد عبارة goto
إحدى تراكيب التحكم في لغة C، وتُستخدم للقفز إلى وسم (Label) محدد من أجل التحكم في تدفق البرنامج. وعلى عكس العديد من تراكيب التحكم الأخرى، فإن goto
تسمح بالقفز إلى أي مكان في البرنامج، مما يمنح مرونة عالية في التحكم بتدفق التنفيذ. ومع ذلك، فإن استخدامها بشكل غير منظم قد يؤثر سلبًا على قابلية قراءة الكود وصيانته، لذا يجب الحذر عند استخدامها.البنية الأساسية لعبارة goto
صيغة كتابة عبارة goto
كما يلي:goto label;
عند تنفيذ goto
، يقفز تدفق البرنامج إلى الموضع الذي تم تعريف الوسم فيه. الوسم هو معرّف يُكتب قبل التعليمة المستهدفة كما يلي:label_name:
فيما يلي مثال بسيط يوضح كيفية عمل goto
في برنامج بلغة C:مثال على استخدام goto
#include <stdio.h>
int main() {
int i = 0;
start: // تعريف الوسم
printf("قيمة i: %dn", i);
i++;
if (i < 5) {
goto start; // القفز إلى الوسم
}
printf("انتهاء الحلقةn");
return 0;
}
يقوم هذا الكود باستخدام goto
للقفز إلى الوسم start
وتكرار الطباعة حتى تصل قيمة i
إلى 5. ورغم أن goto
يسمح بالقفز إلى أي موضع، إلا أن الإفراط في استخدامه قد يجعل البرنامج صعب الفهم، لذا يُفضل الحذر.استخدامات واحتياطات عبارة goto
في لغة C، يمكن التفكير في استخدام goto
في الحالات التالية:- معالجة الأخطاء: عند حدوث خطأ، يمكن القفز مباشرة إلى جزء مخصص لتحرير الموارد.
- الخروج من الحلقات المتداخلة: عند الحاجة للخروج من حلقات متعددة في وقت واحد لتبسيط الكود.
goto
قد يعقد بنية الكود في البرامج الكبيرة، مما يؤدي إلى ما يُعرف بـ “كود الإسباجيتي”. لذلك يجب استخدامها فقط عند الضرورة، مع مراعاة قابلية القراءة والصيانة.2. تاريخ عبارة goto
والجدل حولها
تُعتبر عبارة goto
من أقدم تراكيب التحكم في البرمجة، إذ وُجدت منذ اللغات الأولى قبل ظهور لغة C. ومع ذلك، فقد أثارت الكثير من الجدل، خاصة مع انتشار البرمجة الهيكلية، حيث انقسم المبرمجون بين مؤيد ومعارض لاستخدامها. في هذا القسم سنستعرض تاريخ goto
والجدل الدائر حولها.أصل goto
ودورها المبكر
في بدايات البرمجة، كانت goto
من الوسائل القليلة المتاحة للتحكم في تدفق الكود، إذ لم تكن هناك تراكيب متقدمة مثل الحلقات أو عبارات الشرط الحديثة. ولذلك، كانت البرامج تعتمد بكثرة على القفز بين المواضع باستخدام goto
، وهو ما أدى إلى ظهور ما يُعرف لاحقًا بـ “كود الإسباجيتي” الذي يصعب تتبعه وصيانته. ومع تزايد المشكلات الناتجة عن هذا الأسلوب، ظهرت تراكيب التحكم الحديثة مثل if
وfor
وwhile
، مما جعل الحاجة إلى goto
أقل بكثير.البرمجة الهيكلية وجدلية goto
في سبعينيات القرن الماضي، أعلن عالم الحاسوب الشهير إدجر ديكسترا أن goto
“ضارة” في مقالته الشهيرة Goto Statement Considered Harmful، مما كان له تأثير كبير على انتشار البرمجة الهيكلية. وفقًا لرأيه، فإن goto
تجعل من الصعب فهم تدفق البرنامج، لذا يُفضل الاستغناء عنها لصالح تراكيب التحكم الأخرى التي تجعل الكود أكثر وضوحًا وقابلية للصيانة.وضع goto
في العصر الحديث
اليوم، لا تُعتبر goto
خيارًا مفضلًا في معظم لغات البرمجة، لكنها لا تزال موجودة في بعض اللغات مثل C وتُستخدم في سيناريوهات خاصة، خاصة في معالجة الأخطاء أو تحرير الموارد. ورغم ذلك، تبقى القاعدة العامة هي تفضيل تراكيب التحكم الأخرى متى أمكن ذلك، مع استخدام goto
فقط عند الضرورة القصوى.3. مزايا وعيوب goto
تمنح goto
قدرة على التحكم المرن في تدفق البرنامج، لكن لها أيضًا سلبيات واضحة تؤثر على قابلية القراءة والصيانة.مزايا goto
- تبسيط معالجة الأخطاء المعقدة — تسمح
goto
بالقفز مباشرة إلى جزء مخصص لتحرير الموارد عند حدوث خطأ، مما يقلل من تشعب الشروط المتداخلة.
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (!file) {
printf("فشل فتح الملفn");
goto cleanup;
}
char *buffer = (char *)malloc(256);
if (!buffer) {
printf("فشل حجز الذاكرةn");
goto cleanup;
}
// تنفيذ عمليات أخرى
cleanup:
if (file) fclose(file);
if (buffer) free(buffer);
printf("تم تحرير المواردn");
return 0;
}
- الخروج السريع من الحلقات المتداخلة — في حالة وجود عدة حلقات متداخلة، يمكن لـ
goto
الخروج منها جميعًا دفعة واحدة.
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (i * j > 30) {
goto exit_loop;
}
printf("i=%d, j=%dn", i, j);
}
}
exit_loop:
printf("انتهاء الحلقةn");
عيوب goto
- انخفاض قابلية القراءة — القفز بين المواضع يجعل تتبع تدفق الكود أصعب.
- زيادة احتمالية الأخطاء — خصوصًا إذا كانت الوسوم غير واضحة أو موضوعة في أماكن غير مناسبة.
- التسبب في “كود الإسباجيتي” — الإفراط في استخدام
goto
يؤدي إلى كود متشابك يصعب صيانته.
4. أمثلة على الاستخدام المناسب لعبارة goto
تُستخدم عبارة goto
أحيانًا كأداة فعّالة في مواقف محددة، خاصة في معالجة الأخطاء والخروج من الحلقات المتداخلة. فيما يلي أمثلة عملية على هذه الحالات.استخدام goto
في معالجة الأخطاء
بما أن لغة C لا تحتوي على آلية مدمجة لمعالجة الاستثناءات مثل try-catch
، فإن goto
توفر طريقة بسيطة للقفز إلى جزء من الكود مخصص لتحرير الموارد عند حدوث خطأ. مثال — معالجة الأخطاء عند التعامل مع عدة موارد:#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file1 = NULL;
FILE *file2 = NULL;
char *buffer = NULL;
file1 = fopen("file1.txt", "r");
if (!file1) {
printf("فشل فتح file1.txtn");
goto error;
}
file2 = fopen("file2.txt", "r");
if (!file2) {
printf("فشل فتح file2.txtn");
goto error;
}
buffer = (char *)malloc(1024);
if (!buffer) {
printf("فشل حجز الذاكرةn");
goto error;
}
// تنفيذ عمليات أخرى
printf("تم تنفيذ العمليات بنجاحn");
free(buffer);
fclose(file2);
fclose(file1);
return 0;
error:
if (buffer) free(buffer);
if (file2) fclose(file2);
if (file1) fclose(file1);
printf("تم تحرير الموارد بعد حدوث خطأn");
return -1;
}
الخروج من الحلقات المتداخلة
عند الحاجة للخروج من عدة حلقات متداخلة في وقت واحد، يمكن أن يكونgoto
حلًا أكثر بساطة من استخدام متغيرات وسيطة أو شروط معقدة. مثال — الخروج من حلقة مزدوجة:#include <stdio.h>
int main() {
int i, j;
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
if (i * j > 30) {
goto exit_loop;
}
printf("i = %d, j = %dn", i, j);
}
}
exit_loop:
printf("تم الخروج من الحلقة بسبب تحقق الشرطn");
return 0;
}
متى يجب التفكير في استخدام goto
- تحرير الموارد — عند التعامل مع عدة موارد وتحتاج لتحريرها جميعًا في مكان واحد عند حدوث خطأ.
- الخروج من حلقات معقدة — عندما تكون الحلقات متداخلة بعمق وتريد الخروج منها دفعة واحدة عند تحقق شرط معين.
ملاحظات عند الاستخدام
رغم أنgoto
مفيدة في بعض المواقف، إلا أنه يجب تقليل استخدامها قدر الإمكان للحفاظ على وضوح الكود وتجنب التعقيد المفرط، خاصة في المشاريع الكبيرة.5. الحالات التي يجب تجنب goto
فيها والبدائل المتاحة
في بعض الحالات، يؤدي استخدام goto
إلى تقليل وضوح الكود وزيادة صعوبة صيانته. فيما يلي مواقف يُفضل فيها تجنب goto
، مع بدائل عملية.حالات يجب تجنب goto
فيها
- عندما تكون قابلية القراءة أولوية — القفز بين المواضع يربك القارئ، خاصة في المشاريع الكبيرة أو العمل الجماعي.
- عندما يمكن استخدام معالجة أخطاء منظمة — من الأفضل تقسيم الكود إلى دوال أصغر لمعالجة الأخطاء بدون الحاجة إلى
goto
. - في الحلقات المتداخلة جدًا — يمكن استخدام متغيرات تحكم أو تراكيب تحكم بديلة لتجنب التعقيد.
بدائل goto
1. استخدام متغير تحكم (Flag)
#include <stdio.h>
int main() {
int i, j;
int exit_flag = 0;
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
if (i * j > 30) {
exit_flag = 1;
break;
}
printf("i = %d, j = %dn", i, j);
}
if (exit_flag) break;
}
printf("انتهاء الحلقةn");
return 0;
}
2. تقسيم الكود إلى دوال
#include <stdio.h>
#include <stdlib.h>
int read_file(FILE **file, const char *filename) {
*file = fopen(filename, "r");
if (!*file) {
printf("فشل فتح %sn", filename);
return -1;
}
return 0;
}
int allocate_memory(char **buffer, size_t size) {
*buffer = (char *)malloc(size);
if (!*buffer) {
printf("فشل حجز الذاكرةn");
return -1;
}
return 0;
}
int main() {
FILE *file1 = NULL;
char *buffer = NULL;
if (read_file(&file1, "file1.txt") < 0) {
return -1;
}
if (allocate_memory(&buffer, 1024) < 0) {
fclose(file1);
return -1;
}
free(buffer);
fclose(file1);
printf("تمت المعالجة بنجاحn");
return 0;
}
3. استخدام break
و continue
في الحلقات غير المعقدة، يمكن الاكتفاء بهذين التركيبين للخروج أو تخطي التكرارات بدلاً من goto
.6. أفضل الممارسات عند استخدام goto
- الاقتصار على الضرورة — لا تستخدمها إلا عند تعقيد البدائل.
- تحرير الموارد — اجعلها وسيلة موحدة لتحرير الموارد في نهاية الدالة.
- تسمية واضحة للوسوم — مثل
cleanup
أوerror
بدل الأسماء الغامضة. - تجنب الإفراط — الإكثار منها يزيد التعقيد وصعوبة الصيانة.
- عدم خلطها مع تراكيب تحكم أخرى — لتجنب تدفق معقد يصعب تتبعه.
- إجراء مراجعة للكود — لضمان أن استخدامها هو الخيار الأفضل.
7. الخلاصة
عبارةgoto
في لغة C أداة قوية لكنها ذات حدين. يمكن أن تكون مفيدة في مواقف مثل معالجة الأخطاء أو الخروج من الحلقات المتداخلة، لكنها قد تجعل الكود معقدًا وصعب القراءة إذا أُسيء استخدامها. القاعدة الذهبية هي أن تكون خيارًا أخيرًا بعد تجربة جميع البدائل، ومع مراعاة وضوح الكود وقابليته للصيانة.