Goning shartli konstruksiyalari ataylab bezaksiz: if va switch, shart atrofida qavslar talab qilinmaydi, shoxni bitta qatorga siqib qo'yadigan uchlik (ternary) operator ham yo'q. Bu yo'qlik e'tiborsizlik emas, dizayn qarori: Go mualliflari uchlik operatorni aynan shuning uchun qoldirmadiki, u mantiqni o'qishga yaroqsiz darajada siqib tashlash uchun suiiste'mol qilinishga moyil, va ular aqlli bir qatorlik kodga murojaat qilgandan ko'ra oddiy if yozganingizni afzal ko'rishadi. Ushbu bobda ikkala konstruksiya hamda Godagi shartli kodni aynan Go kodiga o'xshatib ko'rsatadigan bir nechta odat — boshlang'ich ifodalar, tip bo'yicha switch, qo'riqchi shartlar — ko'rib chiqiladi.
Agar siz C oilasidagi biror tilda ishlagan bo'lsangiz, asosiy shakllar tanishtirishga muhtoj emas:
if age >= 18 {
fmt.Println("You are an adult")
}
if age >= 18 {
fmt.Println("Adult")
} else {
fmt.Println("Minor")
}
if score >= 90 {
grade = "A"
} else if score >= 80 {
grade = "B"
} else if score >= 70 {
grade = "C"
} else {
grade = "F"
}
Aynan Goga xos narsa — shartdan oldin, nuqtali vergul bilan ajratilgan ixtiyoriy boshlang'ich ifoda:
if err := doSomething(); err != nil {
return err
}
Boshlang'ich ifodada e'lon qilingan o'zgaruvchi — bu yerda err — butun if/else if/else zanjiri bilan chegaralanadi va boshqa hech qayerda mavjud emas. Bu kichik, ammo ataylab qo'yilgan tartib: zanjirdan chiqqaningizdan keyin o'sha o'zgaruvchi shunchaki yo'q bo'ladi, shuning uchun uning funksiyada keyinroq tasodifan o'qilishi (yoki soyalanishi) xavfi yo'q.
Bu, katta farq bilan, siz Goda yozadigan eng ko'p uchraydigan boshqaruv oqimi patterni: xatoni u paydo bo'lgan zahoti, uni hosil qilgan ifodaning o'zida tekshirish.
if err := process(); err != nil {
log.Fatal(err)
}
Boshlang'ich ifodada bir nechta qiymatni ham xuddi shunday e'lon qilish mumkin:
if x, y := getValues(); x > 0 && y > 0 {
fmt.Println(x, y)
}
// bu yerda na x, na y mavjud
Goning switch konstruksiyasi ilk qarashda boshqa tillardagidek tanish ko'rinadi, ammo bitta muhim jihati bilan farq qiladi: holatlar standart bo'yicha keyingisiga o'tib ketmaydi (no fallthrough by default).
switch day {
case "Monday":
fmt.Println("Start of work week")
case "Friday":
fmt.Println("TGIF!")
case "Saturday", "Sunday":
fmt.Println("Weekend!")
default:
fmt.Println("Midweek")
}
Har bir case bloki tugagach, avtomatik ravishda to'xtaydi — har bir qatorda ortiqcha break yozish shart emas va uni unutib qoldirish oqibatida keyingi holatga tasodifan tushib ketish xavfi yo'q. Bu C/C++ va Java kabi tillarda ko'p uchraydigan klassik xatoliklarning oldini oladi. Bitta case bir nechta qiymatni qabul qilishi mumkin (masalan, yuqoridagi "Saturday", "Sunday").
Agar tekshiriladigan qiymatni ko'rsatmasdan shunchaki switch deb yozsangiz, u o'z-o'zidan o'qishga juda qulay bo'lgan if / else if zanjiriga aylanadi:
switch {
case x < 0:
fmt.Println("Negative")
case x == 0:
fmt.Println("Zero")
case x > 0:
fmt.Println("Positive")
}
Bu shakl uchta yoki undan ortiq else if shoxlaridan iborat zinapoyaning o'rniga juda mos keladi: shartlar tekis va chiroyli ro'yxat ko'rinishida o'qiladi.
if kabi, switch ham boshlang'ich ifodani qabul qiladi:
switch x := getValue(); x {
case 1:
fmt.Println("One")
case 2:
fmt.Println("Two")
}
// bu yerda x mavjud emas
Agar siz keyingi case blokiga ataylab o'tishni istasangiz, buning uchun maxsus fallthrough kalit so'zi mavjud:
switch x {
case 1:
fmt.Println("One")
fallthrough
case 2:
fmt.Println("Two or fell through from One")
case 3:
fmt.Println("Three")
}
Tip bo'yicha switch (type switch) — oddiy qiymat bo'yicha emas, balki interfeysda (interface{} yoki any) saqlangan obyektning haqiqiy dinamik tipi bo'yicha shoxlanishning eng idiomatik usulidir:
func printValue(v interface{}) {
switch val := v.(type) {
case int:
fmt.Printf("Integer: %d\n", val)
case string:
fmt.Printf("String: %s\n", val)
case []int:
fmt.Printf("Slice of ints: %v\n", val)
case nil:
fmt.Println("nil")
default:
fmt.Printf("Type: %T, Value: %v\n", val, val)
}
}
Har bir case bloki ichida val o'zgaruvchisi o'sha case da ko'rsatilgan aniq tipga aylanadi — birinchi blokda int, ikkinchisida string. Natijada siz interface{} yoki uning zamonaviy muqobil nomi (alias) any orqali uzatilgan dinamik qiymat bilan ishlashda ham qat'iy tip xavfsizligiga qaytasiz. Bitta holat bir vaqtda bir nechta tipni sanashi mumkin, bunda val toraymasdan interface tipini saqlab qoladi, chunki kompilyator sanalgan tiplardan qaysi biri aslida amal qilayotganini bila olmaydi:
switch v := value.(type) {
case int, int8, int16, int32, int64:
fmt.Printf("Integer: %v\n", v) // bu yerda v hamon `any` — bir nechta tip mos keldi
case float32, float64:
fmt.Printf("Float: %v\n", v)
}
Buni alohida tip tasdiqlashlariga murojaat qilish bilan solishtiring:
if v, ok := value.(int); ok {
fmt.Println("int:", v)
} else if v, ok := value.(string); ok {
fmt.Println("string:", v)
}
Bu ishlaydi, lekin yomonroq o'qiladi va ikki-uchta tipdan oshganda yomon miqyoslanadi — tip bo'yicha switch xuddi shu narsani to'g'riroq aytadi va ko'pchilik Go uslub qo'llanmalari (hamda ko'pchilik Go dasturchilari) aynan shuni ko'rishni kutadi. Tip bo'yicha switch va tip tasdiqlashlari any bilan, interface{} ga dekodlangan JSON bilan yoki encoding/json ning standart dekodlashi kabi umumiy, ammo tipsiz API'lardan qaytgan qiymatlar bilan ishlaganda doimo uchraydi. 28-bob to'liq mexanikani, shu jumladan tasdiqlash shaklining xavfsizlik murosalarini ko'rib chiqadi.
Aytib qo'yilgach, amaliy qoida deyarli o'z-o'zidan ravshan: bitta qiymatni bir nechta ehtimol bilan solishtirayotgan bo'lsangiz switch ga, bitta mantiqiy shart yoki erta qaytaradigan qo'riqchi uchun esa if ga murojaat qiling. Bitta o'zgaruvchini tekshiradigan uchta yoki undan ko'p else if shoxi zanjiri switch ga o'tkazish uchun deyarli mexanik signal:
// batafsil
if x == 1 {
// ...
} else if x == 2 {
// ...
} else if x == 3 {
// ...
}
// tozaroq
switch x {
case 1:
// ...
case 2:
// ...
case 3:
// ...
}
Xuddi shu mantiq yuqorida ko'rsatilgan ifodasiz switch shakli orqali oraliqlar va murakkab shartlarga ham tarqaladi.
Idiomatik Go kodi asosiy muvaffaqiyatli oqimni (happy path) chuqur ichma-ich joylashgan else bloklari ichiga yashirish o'rniga, chekka holatlar va xatoliklarni avval tekshirib, funksiyadan darhol qaytishni (early return) qat'iy ma'qul ko'radi:
func validate(user User) error {
if user.Name == "" {
return fmt.Errorf("name required")
}
if user.Email == "" {
return fmt.Errorf("email required")
}
if user.Age < 0 {
return fmt.Errorf("invalid age")
}
return nil
}
Har bir qo'riqchi shart (guard clause) bitta noto'g'ri holatni tekshirib, xatolik bo'lsa darhol funksiyadan chiqadi. Natijada funksiyaning asosiy bajariladigan mantiqiy qismi bir nechta qavat if / else ichida emas, balki chap tomonda, asosiy qatorda tekis joylashadi. Bu pattern Go tilida xatolar bilan ishlashning asosi hisoblanadi:
func process(data []byte) error {
if len(data) == 0 {
return fmt.Errorf("empty data")
}
if data[0] != 0xFF {
return fmt.Errorf("invalid header")
}
// Asosiy mantiqiy kodlar bu yerda, ortiqcha ichma-ich bloklarsiz davom etadi
return nil
}
switch holatlar mashinasi va buyruqlarni taqsimlashda shunga o'xshash rol o'ynaydi, bunda String() metodi yoki buyruq ishlovchisi kichik, yopiq qiymatlar to'plamidan birini xatti-harakatga moslashi kerak bo'ladi:
type State int
const (
Idle State = iota
Running
Paused
Stopped
)
func (s State) String() string {
switch s {
case Idle:
return "Idle"
case Running:
return "Running"
case Paused:
return "Paused"
case Stopped:
return "Stopped"
default:
return "Unknown"
}
}
func handleCommand(cmd string, args []string) error {
switch cmd {
case "create":
return create(args)
case "read":
return read(args)
case "update":
return update(args)
case "delete":
return delete(args)
default:
return fmt.Errorf("unknown command: %s", cmd)
}
}
Holat tanasi bir-ikki qatordan oshganda uni handleCommand allaqachon qilgani kabi o'z nomli funksiyasiga ajrating.
Holatlarining hammasi yaxshi nomlangan funksiyalarga bir qatorlik murojaatdan iborat
switchqancha holat to'plamasin o'qishga qulay bo'lib qolaveradi, xuddi shu switch har bir shoxida ichki mantiq bilan yozilsa esa tezda o'qib bo'lmaydigan devorga aylanadi.
Keyingi bobga o'tishdan oldin qo'lda bajarib ko'rish foydali bo'lgan bir nechta kichik masala. Har biri bir necha daqiqada yozib sinaladigan darajada qisqa va har biri bu bobda ko'rib chiqilgan narsaning boshqa bir burchagiga tegadi.
Baho hisoblagichi. Sonli ball berilgan, harfli bahoni qaytaring ("A" — 90 va undan yuqori, "B" — 80-89, "C" — 70-79, "D" — 60-69, undan pasti "F"), buni if/else if zanjiri bilan qiling. Bu shartlar diskret qiymatlar emas, oraliqlar bo'lgani uchun if switch dan yaxshiroq o'qiladigan holat.
Haftaning kuni. 1 dan 7 gacha butun son berilgan, kun nomini switch yordamida qaytaring. Bu esa switch shubhasiz yaxshiroq mos keladigan holat.
Parol tekshirgichi. Parol kamida 8 belgidan iborat ekanini hamda unda bosh harf, kichik harf va raqam borligini tekshiring, bool qaytaring. Bu masala qo'riqchi shartlar uchun juda mos: oxirida bir qancha mantiqiy qiymatni to'plab && bilan birlashtirish o'rniga, biror qoida buzilgan zahoti false qaytaring.
Harorat o'zgartirgichi. from va to birlik satrlari berilgan, Selsiy va Farengeyt orasida o'zgartiring. Buni (from, to) juftligi bo'yicha switch (yoki ichma-ich switch lar) bilan qiling va qo'llab-quvvatlanmaydigan har qanday birikma uchun xato qaytaring.
Kabisa yilini aniqlash. Yil 4 ga bo'linsa kabisa yili hisoblanadi, asr yillari bundan mustasno — ular 400 ga bo'linishi kerak. Bu &&, || va % ni to'g'ri birlashtirishning ixcham sinovi.
if, switch va boshlang'ich ifoda idiomasiga ko'nikish darhol foyda beradi, chunki bundan keyin yozadigan deyarli har bir funksiyangiz faqat xatolarni boshqarish uchun ham ulardan kamida bittasiga tayanadi. Qarorlar ko'rib chiqilgach, keyingi bob takrorlashga o'tadi: Goning yagona, moslashuvchan for sikli va uni qanday yozishingizga qarab oladigan bir nechta shakli.