تسمية حالات المهمة دون إغلاق النوع
كل مهمة في jobrunner تمر بأربع حالات: تنتظر ثم تعمل ثم تنجح أو تفشل. نحن من قرر هذه الحالات و هي مجموعة محدودة نعرف كل ما فيها.
يمكننا تخزين الحالة في نص:
status := "pending"ثم نغيره عند بدء التنفيذ:
status = "runing"هذا السطر سليم بالنسبة للمترجم. "runing" نص و المتغير يقبل أي نص. البرنامج يبنى و يعمل و تكتشف الخطأ حين ترى مخرجات لا تفهمها.
المتغير النصي يقبل ما لا نهاية من القيم و نحن نريد أربع قيم فقط.
أسماء بدل نصوص
نعطي كل حالة اسم في الكود:
const (
StatusPending = 0
StatusRunning = 1
StatusSucceeded = 2
StatusFailed = 3
)الآن نكتب status := StatusRunning بدل النص. لو أخطأنا و كتبنا StatusRuning، رفض المترجم البناء لأن هذا الاسم غير موجود. صار الخطأ يظهر قبل التشغيل.
بقيت الأرقام. كتبناها بأيدينا و لا معنى لأي منها في ذاته. ما يهمنا الآن أن تختلف كل قيمة عن أختها و سنرى بعد قليل أن ترتيبها يحمل معنى أيضا.
iota يكتب الأرقام
داخل كتلة const تعطينا Go عداد اسمه iota. قيمته هي ترتيب تصريح الثابت (ConstSpec) داخل الكتلة، بدءا من 0. إذا احتوى التصريح نفسه على أكثر من اسم، تشترك تلك الأسماء في قيمة iota نفسها.
const (
StatusPending = iota
StatusRunning
StatusSucceeded
StatusFailed
)السطر الأول وحده يحمل تعبير. أما التصريحات التي بعده فتركناها بلا قيمة عمدا. حين لا نكتب تعبير لثابت داخل كتلة const، تكرر Go التعبير السابق. لذلك يستخدم كل تصريح لاحق iota و يحصل على قيمة مختلفة حسب موضعه.
القيم لم تتغير: 0 و 1 و 2 و 3. الذي تغير أن الترتيب هو ما ينتجها فإضافة حالة خامسة في آخر الكتلة لا تحتاج إلى تعديل أي رقم.
نوع باسم واحد
الحالات الأربع الآن أعداد صحيحة عادية. جمعتها كتلة const واحدة و جمعتها بادئة Status في أسمائها، لكن لا شيء في نوعها يميزها عن أي عدد آخر.
ننشئ للحالات نوع خاص:
type JobStatus intint هو النوع الذي تبنى عليه JobStatus، أي أن قيمها أعداد صحيحة. لكن JobStatus نوع قائم بذاته، لا اسم آخر لـ int.
ثم نربط الثوابت به:
type JobStatus int
const (
StatusPending JobStatus = iota
StatusRunning
StatusSucceeded
StatusFailed
)النوع مكتوب في السطر الأول وحده و يتكرر مع التعبير على بقية الأسطر.
صار للمجموعة اسم نستعمله لاحقا حين تحمل المهمة حالتها. و متغير من نوع int لا يدخل في JobStatus دون تحويل صريح:
var status JobStatus = StatusPending
var n int = 5
status = n // compile error: cannot use n (variable of type int) as JobStatus valueلماذا StatusPending أولا
عرفت في درس القيم الافتراضية أن int يبدأ من 0. و JobStatus مبني على int فمتغير منه يبدأ من 0 أيضا:
var status JobStatusوضعنا StatusPending أول الكتلة فصار 0 يعني "تنتظر". مهمة لم نحدد حالتها تبدأ منتظرة و هذا ما نريده.
هذا قرار في التصميم لا قاعدة عامة. في أنواع أخرى يكون الأنسب أن تعني 0 حالة غير صالحة، حتى ينكشف كل متغير نسينا ضبطه.
تحديث jobrunner
package main
import "fmt"
const (
version = "v0.1"
maxAttempts = 3
)
type JobStatus int
const (
StatusPending JobStatus = iota
StatusRunning
StatusSucceeded
StatusFailed
)
func main() {
name := "jobrunner"
status := StatusPending
fmt.Println(name, version)
fmt.Println("max attempts:", maxAttempts)
fmt.Println("status:", status)
}يطبع السطر الأخير status: 0 لأن قيمة StatusPending عدد. الاسم موجه لنا نحن حين نقرأ الكود. سنجعل الحالة تطبع نفسها بكلمة مفهومة حين نتمكن من إضافة سلوك إلى النوع.
التحدي
في المحرر، افتح ملف main.go و نفذ التعديلات التالية:
- عرف نوع اسمه
JobStatusمبني علىint. - في كتلة
constواحدة، عرفStatusPendingوStatusRunningوStatusSucceededوStatusFailedو اجعلiotaينتج قيمها. - في
main، أنشئstatusبالقيمةStatusPending.
ثم شغل البرنامج باستخدام:
go run main.goيجب أن تعرض نافذة التنفيذ:
jobrunner v0.1
max attempts: 3
status: 0بعد التأكد من أن البرنامج يعمل، اضغط على زر تشغيل و تحقق لإكمال الدرس.
الخلاصة
حين تكون القيم المعروفة مجموعة محدودة، لا نتركها نصوص حرة. ننشئ لها نوع مستقل و نضع قيمها في كتلة const واحدة. يعطي iota كل تصريح قيمته حسب موضعه. هكذا أصبحت حالات jobrunner تحمل أسماء يفحصها المترجم و جعلنا أولها القيمة الافتراضية عن قصد.
يعرف البرنامج الآن أربع حالات، لكنه يطبع واحدة منها دائما. كيف نجعله يختار بين مسارين حسب قيمة يفحصها أثناء التشغيل؟