Tarkibga o'tish

Goda enum va iota

Go dasturlash tilida boshqa ayrim tillardagi kabi alohida:

enum

kalit so'zi mavjud emas.

Masalan, Java, C# yoki boshqa tillarda ma'lum bir qiymatlar to'plamini enum sifatida e'lon qilish mumkin.

Go'da esa shu vazifa odatda:

  • nomlangan tur;
  • const konstantalari;
  • kerak bo'lsa iota;

yordamida bajariladi.

Masalan, tizimda buyurtmaning holatlari bo'lsin:

unknown
pending
processing
completed

Bularni oddiy int sifatida saqlash mumkin:

const (
    StatusUnknown    = 0
    StatusPending    = 1
    StatusProcessing = 2
    StatusCompleted  = 3
)

Lekin bunda qiymatlar oddiy int bo'lib qoladi.

Go'da buning o'rniga alohida nomlangan tur yaratish yaxshiroq:

type Status int

Keyin konstantalarni shu turga bog'lash mumkin:

const (
    StatusUnknown Status = iota
    StatusPending
    StatusProcessing
    StatusCompleted
)

Natijada kodda oddiy son bilan emas, ma'nosi aniq bo'lgan Status turi bilan ishlaymiz.

iota esa bu yerda:

0
1
2
3

qiymatlarini qo'lda yozmaslikka yordam beradi.

Nomlangan tur va konstantalar

Quyidagi misolni ko'ramiz:

package main

import "fmt"

type Status int

const (
    StatusUnknown Status = iota
    StatusPending
    StatusProcessing
    StatusCompleted
)

func main() {
    status := StatusProcessing

    fmt.Println(status)
    fmt.Println(status == StatusCompleted)
}

Natija:

2
false

Bu koddagi eng muhim qism:

type Status int

Bu yerda int asosida yangi Status turi yaratildi.

Statusning asosiy turi int, lekin Go uchun u alohida nomlangan tur hisoblanadi.

Keyin:

const (
    StatusUnknown Status = iota
    StatusPending
    StatusProcessing
    StatusCompleted
)

orqali Status uchun mumkin bo'lgan asosiy konstantalar e'lon qilindi.

Birinchi qatorda:

StatusUnknown Status = iota

yozilgan.

const guruhi boshlanganda iota qiymati:

0

bo'ladi.

Shuning uchun:

StatusUnknown == 0

Keyingi qatorda yangi ifoda yozilmagan:

StatusPending

Go oldingi ifodani takrorlaydi va iota bittaga oshadi.

Natijada:

StatusPending == 1

Keyingi qiymatlar:

StatusProcessing == 2
StatusCompleted  == 3

bo'ladi.

Shuning uchun:

status := StatusProcessing
fmt.Println(status)

natijasi:

2

bo'ladi.

Keyin:

status == StatusCompleted

tekshirilmoqda.

status qiymati:

2

StatusCompleted esa:

3

bo'lgani uchun natija:

false

chiqadi.

Nega oddiy int emas, alohida tur ishlatamiz?

Quyidagicha yozish ham mumkin edi:

const (
    StatusUnknown    = 0
    StatusPending    = 1
    StatusProcessing = 2
    StatusCompleted  = 3
)

Lekin bu qiymatlar oddiy int bo'lib qoladi.

Alohida:

type Status int

turini yaratish kodning ma'nosini aniqroq qiladi.

Masalan:

func SetStatus(status Status) {
}

funksiyasi ko'rinishidan ham parametr oddiy son emas, aynan tizim holati ekanini tushunish mumkin.

Bu:

func SetStatus(status int) {
}

ko'rinishidan ancha tushunarli.

Nomlangan turga keyinchalik metodlar ham qo'shish mumkin:

func (s Status) String() string {
    // ...
}

yoki:

func (s Status) Valid() bool {
    // ...
}

Shuning uchun nomlangan tur faqat sonlarga boshqa nom berish emas. U ma'lum bir tushunchani alohida tur sifatida ifodalash imkonini beradi.

Nol qiymatni Unknown uchun ishlatish

Go'da har bir turning zero value, ya'ni boshlang'ich qiymati mavjud.

int asosidagi tur uchun bu:

0

bo'ladi.

Masalan:

var status Status

deb yozsak va unga hech qanday qiymat bermasak:

status == 0

bo'ladi.

Shuning uchun ko'pincha enumga o'xshash turlarda 0 qiymatini:

StatusUnknown

kabi noma'lum yoki hali belgilanmagan holatga ajratish foydali.

Masalan:

const (
    StatusUnknown Status = iota
    StatusPending
    StatusProcessing
    StatusCompleted
)

Endi:

var status Status

deb e'lon qilingan qiymat avtomatik ravishda:

StatusUnknown

holatiga teng bo'ladi.

Bu yaxshi dizayn, chunki zero value tasodifan haqiqiy biznes holatini bildirmaydi.

Agar birinchi qiymatni:

StatusCompleted Status = iota

deb qo'ysak, yangi e'lon qilingan:

var status Status

tasodifan Completed holatini bildirgan bo'lardi.

Ko'p tizimlarda bu xavfli.

Shuning uchun:

0 = Unknown

yoki:

0 = Unspecified

kabi holat ajratish yaxshi amaliyot hisoblanadi.

iota qanday ishlaydi?

iota — faqat const deklaratsiyasi ichida ishlatiladigan maxsus identifikator.

U har bir yangi const guruhida:

0

dan boshlanadi.

Masalan:

const (
    A = iota
    B
    C
)

qiymatlar:

A = 0
B = 1
C = 2

bo'ladi.

Lekin iotani faqat to'g'ridan-to'g'ri son sifatida ishlatish shart emas.

U formula ichida ham ishlatilishi mumkin.

Masalan:

const (
    A = iota + 1
    B
    C
)

Natija:

A = 1
B = 2
C = 3

bo'ladi.

Birinchi qatorda:

A = iota + 1

iota:

0

bo'ladi.

Demak:

0 + 1 = 1

Keyingi qatorda:

B

uchun alohida ifoda yozilmagan.

Go oldingi:

iota + 1

ifodasini takrorlaydi.

Bu qatorda iota:

1

bo'lgani uchun:

1 + 1 = 2

hosil bo'ladi.

C qatorida esa:

iota = 2

Natija:

2 + 1 = 3

Ifoda yozilmagan qator nima qiladi?

Masalan:

const (
    A = iota + 1
    B
    C
)

aslida quyidagiga yaqin:

const (
    A = iota + 1
    B = iota + 1
    C = iota + 1
)

Lekin har bir qatorga kelganda iota qiymati bittaga oshib boradi.

Shuning uchun bir xil formulani qayta-qayta yozish shart emas.

Bu iotaning asosiy qulayliklaridan biri.

iota har bir ConstSpec qatorida oshadi

Muhim qoida:

iota har bir konstanta nomida emas, har bir ConstSpec qatorida bittaga oshadi.

Masalan:

const (
    A, B = iota, iota
    C, D = iota, iota
)

Birinchi qator davomida ikkala iota ham:

0

bo'ladi.

Shuning uchun:

A = 0
B = 0

Ikkinchi qatorda esa:

iota = 1

Shuning uchun:

C = 1
D = 1

bo'ladi.

Demak, bitta qatorda nechta iota yozilishidan qat'i nazar, ular o'sha qator uchun bir xil qiymatga ega.

Bu keyingi misollarda diapazonlar yaratishda foydali bo'ladi.

_ yordamida iota qiymatini tashlab ketish

Ba'zan iotaning birinchi qiymati kerak bo'lmasligi mumkin.

Masalan, darajalarni:

1
2
3

dan boshlamoqchimiz.

Buning bir usuli:

const (
    PriorityLow Priority = iota + 1
    PriorityMedium
    PriorityHigh
)

Boshqa usuli esa _ orqali birinchi qiymatni tashlab ketish:

const (
    _ Priority = iota
    PriorityLow
    PriorityMedium
    PriorityHigh
)

Birinchi qatorda:

iota = 0

Lekin qiymat:

_

ga berilgan.

_ — blank identifier.

Bu qiymatdan keyinchalik foydalanib bo'lmaydi.

Keyingi qatorda:

iota = 1

bo'ladi.

Shuning uchun:

PriorityLow    = 1
PriorityMedium = 2
PriorityHigh   = 3

hosil bo'ladi.

iota yordamida o'lcham qiymatlari

iota formula bilan ishlashi sabab faqat oddiy:

0, 1, 2, 3

ketma-ketligini yaratish uchun emas.

Masalan, fayl o'lchamlarini yaratish mumkin:

type Size uint64

const (
    _ Size = 1 << (10 * iota)
    KB
    MB
    GB
)

Bu kod birinchi qarashda murakkab ko'rinishi mumkin.

Bosqichma-bosqich ko'ramiz.

Birinchi qator:

_ Size = 1 << (10 * iota)

Bu qatorda:

iota = 0

Demak:

1 << (10 * 0)

ya'ni:

1 << 0

natija:

1

Lekin bu qiymat _ga berilgan va ishlatilmaydi.

Keyingi qatorda:

KB

oldingi ifoda takrorlanadi.

Bu safar:

iota = 1

Demak:

1 << (10 * 1)

ya'ni:

1 << 10

Bu:

1024

ga teng.

Shuning uchun:

KB = 1024

Keyingi qatorda:

iota = 2

Demak:

MB = 1 << 20

Natija:

1048576

Keyingi qiymat:

GB = 1 << 30

Natija:

1073741824

Demak:

KB = 1024
MB = 1048576
GB = 1073741824

Bu yerda iota formula ichidagi o'zgaruvchi kabi ishlatildi.

Har bir const guruhida iota qaytadan boshlanadi

iota butun dastur bo'yicha bittadan oshib yurmaydi.

Har safar yangi:

const (
    ...
)

guruhi ochilganda iota yana:

0

dan boshlanadi.

Masalan:

const (
    A = iota
    B
)

const (
    C = iota
    D
)

Natija:

A = 0
B = 1

C = 0
D = 1

Ikkinchi const guruhi birinchisini davom ettirmaydi.

Bu juda muhim.

Chunki loyihada bir nechta alohida enumga o'xshash turlar bo'lishi mumkin va ularning iota qiymatlari bir-biriga ta'sir qilmaydi.

Matn ko'rinishini berish

Enumga o'xshash qiymatlarning yana bir muammosi bor.

Masalan:

fmt.Println(StatusProcessing)

agar hech qanday qo'shimcha metod yozilmagan bo'lsa:

2

chiqaradi.

Dastur ichida bu son yetarli bo'lishi mumkin.

Lekin:

  • loglarda;
  • debugging vaqtida;
  • CLI dasturda;
  • foydalanuvchiga ko'rsatiladigan natijada;

2 nimani anglatishini tushunish qiyin.

Shuning uchun nomlangan turga String() metodini qo'shish mumkin.

Masalan:

package main

import "fmt"

type Status int

const (
    StatusUnknown Status = iota
    StatusPending
    StatusProcessing
    StatusCompleted
)

func (s Status) String() string {
    switch s {
    case StatusUnknown:
        return "unknown"

    case StatusPending:
        return "pending"

    case StatusProcessing:
        return "processing"

    case StatusCompleted:
        return "completed"

    default:
        return fmt.Sprintf("Status(%d)", s)
    }
}

func main() {
    fmt.Println(StatusProcessing)
    fmt.Println(Status(99))
}

Natija:

processing
Status(99)

Bu qanday ishlaydi?

Status turida:

String() string

metodi mavjud:

func (s Status) String() string

Bu fmt.Stringer interface'iga mos keladi:

type Stringer interface {
    String() string
}

Shuning uchun fmt paketidagi funksiyalar Status qiymatini chiqarayotganda uning String() metodidan foydalanishi mumkin.

Masalan:

fmt.Println(StatusProcessing)

oddiy:

2

o'rniga:

processing

chiqaradi.

switch yordamida nomni tanlash

Metod ichida:

switch s {
case StatusUnknown:
    return "unknown"

case StatusPending:
    return "pending"

case StatusProcessing:
    return "processing"

case StatusCompleted:
    return "completed"
}

orqali har bir sonli qiymatga matn berilgan.

Masalan:

StatusProcessing

qiymati:

2

bo'lsa ham, String():

processing

qaytaradi.

default nima uchun kerak?

Metod oxirida:

default:
    return fmt.Sprintf("Status(%d)", s)

bor.

Buni yozish muhim.

Chunki Go'dagi enumga o'xshash tur faqat e'lon qilingan konstantalar bilan cheklanmagan.

Masalan:

Status(99)

yozish mumkin.

Shuning uchun String() faqat:

return "unknown"

qilib yuborsa, haqiqiy noma'lum qiymatni yashirib qo'yishi mumkin.

Buning o'rniga:

Status(99)

chiqishi debugging va loglarda ancha foydali.

Biz darhol:

Tizimga kutilmagan 99 qiymati kelibdi

deb ko'ra olamiz.

Katta enumlarda o'nlab yoki yuzlab case yozish noqulay bo'lishi mumkin.

Bunday holatlarda String() metodini avtomatik generatsiya qiluvchi vositalardan foydalanish mumkin.

Lekin generatsiya qilingan fayllarni loyiha va repozitoriy qoidalariga mos boshqarish kerak.

Enum yopiq to'plam emas

Go'dagi enumga o'xshash yondashuvning juda muhim farqi bor.

Quyidagicha tur yaratdik:

type Status int

va faqat to'rtta konstanta e'lon qildik:

const (
    StatusUnknown Status = iota
    StatusPending
    StatusProcessing
    StatusCompleted
)

Bu qiymatlar:

0
1
2
3

bo'ladi.

Lekin bu:

Status faqat 0, 1, 2 yoki 3 bo'lishi mumkin

degani emas.

Quyidagi kod ham kompilyatsiyadan o'tadi:

status := Status(99)

Bu mutlaqo haqiqiy Status qiymati hisoblanadi.

Shuning uchun Go'dagi bu yondashuv boshqa ayrim tillardagi qat'iy enum kabi yopiq to'plam yaratmaydi.

Nega bu muhim?

Agar qiymatlar faqat o'z kodimiz ichida ishlatilsa, odatda konstantalardan foydalanamiz:

StatusPending
StatusCompleted

Lekin tashqi manbadan ma'lumot kelganda vaziyat boshqacha.

Masalan:

  • HTTP request;
  • JSON;
  • database;
  • message broker;
  • fayl;
  • boshqa servis;

orqali:

99

kelishi mumkin.

Uni:

Status(99)

ga aylantirish texnik jihatdan mumkin.

Lekin u biznes qoidalariga mos kelmasligi mumkin.

Shuning uchun tashqi qiymatlarni validatsiya qilish kerak.

Masalan:

func (s Status) Valid() bool {
    return s >= StatusUnknown && s <= StatusCompleted
}

Endi:

status := Status(2)

if !status.Valid() {
    // noto'g'ri status
}

deb tekshirish mumkin.

Status(99) uchun:

Status(99).Valid()

natijasi:

false

bo'ladi.

Har doim diapazon tekshiruvi yetarlimi?

Quyidagi:

return s >= StatusUnknown && s <= StatusCompleted

usuli konstantalar ketma-ket bo'lganda yaxshi ishlaydi.

Masalan:

0
1
2
3

Lekin qiymatlar:

1
5
10
20

kabi bo'lsa, diapazon tekshiruvi noto'g'ri bo'lishi mumkin.

Bunday holatda switch ishlatish yaxshiroq:

func (s Status) Valid() bool {
    switch s {
    case StatusUnknown,
        StatusPending,
        StatusProcessing,
        StatusCompleted:
        return true

    default:
        return false
    }
}

Bu usul faqat haqiqatan e'lon qilingan qiymatlarni qabul qiladi.

iota va tashqi formatlar

iota ishlatishda yana bir muhim xavf bor.

Faraz qilamiz:

const (
    StatusUnknown Status = iota
    StatusPending
    StatusProcessing
    StatusCompleted
)

qiymatlar:

0
1
2
3

bo'ladi.

Endi bu sonlarni database'ga yozdik deb tasavvur qilamiz.

Masalan:

2 = processing
3 = completed

Keyinchalik kodga yangi holat qo'shdik:

const (
    StatusUnknown Status = iota
    StatusPending
    StatusWaiting
    StatusProcessing
    StatusCompleted
)

Endi qiymatlar:

StatusUnknown    = 0
StatusPending    = 1
StatusWaiting    = 2
StatusProcessing = 3
StatusCompleted  = 4

bo'lib qoldi.

Oldingi database'dagi:

2

qiymati avval:

processing

edi.

Yangi kodda esa:

waiting

bo'lib qoldi.

Bu juda jiddiy xatoga olib kelishi mumkin.

Shuning uchun iota qiymatlari tashqi tizim bilan bog'langan bo'lsa, ehtiyot bo'lish kerak.

Bunga quyidagilar kiradi:

  • database;
  • public API;
  • protobufga o'xshash protokol;
  • fayl formati;
  • message broker;
  • boshqa servis bilan kelishilgan kodlar.

Bunday holatda ikki xil xavfsizroq yondashuv bor.

Faqat oxiriga yangi qiymat qo'shish

Masalan:

const (
    StatusUnknown Status = iota
    StatusPending
    StatusProcessing
    StatusCompleted

    StatusCanceled
)

Bunda eski qiymatlar o'zgarmaydi.

Aniq sonlarni yozish

Agar kodlar tashqi shartnoma bo'lsa, yanada aniqroq usul:

const (
    StatusUnknown    Status = 0
    StatusPending    Status = 10
    StatusProcessing Status = 20
    StatusCompleted  Status = 30
)

Endi yangi qiymat qo'shish eski qiymatlarni avtomatik siljitmaydi.

Chuqurlashtirish: bit bayroqlar

Bit bayroqlar bir qiymatda bir nechta mustaqil holatni saqlash kerak bo‘lganda ishlatiladi. Boshlang‘ich bosqichda oddiy iota ketma-ketligini tushunish yetarli; ushbu bo‘limga keyinroq qaytishingiz mumkin.

iota faqat oddiy enumga o'xshash ketma-ketlik yaratish uchun emas.

U bit flag, ya'ni bit bayroqlar yaratishda ham juda qulay.

Masalan, foydalanuvchida quyidagi ruxsatlar bo'lsin:

  • o'qish;
  • yozish;
  • o'chirish.

Oddiy enumda:

Read   = 0
Write  = 1
Delete = 2

qilish mumkin.

Lekin bitta foydalanuvchida bir vaqtning o'zida bir nechta ruxsat bo'lishi kerak.

Masalan:

Read + Write

Bunday holatda bitmask ishlatish mumkin.

type Permission uint8

const (
    PermissionRead Permission = 1 << iota
    PermissionWrite
    PermissionDelete
)

Bu yerda qiymatlar:

PermissionRead   = 1
PermissionWrite  = 2
PermissionDelete = 4

bo'ladi.

Nega?

Birinchi qator:

1 << iota

uchun:

iota = 0

Demak:

1 << 0 = 1

Binary ko'rinishda:

00000001

Keyingi qator:

iota = 1

Demak:

1 << 1 = 2

Binary:

00000010

Keyingi:

1 << 2 = 4

Binary:

00000100

Muhim tomoni — har bir qiymatda alohida bit yoqilgan.

Bit bayroqlarni birlashtirish

Bit flaglarni:

|

bitwise OR operatori yordamida birlashtirish mumkin.

Masalan:

permissions := PermissionRead | PermissionWrite

Binary ko'rinishda:

PermissionRead  = 00000001
PermissionWrite = 00000010

| ishlatilganda:

00000001
00000010
--------
00000011

hosil bo'ladi.

Bu decimal ko'rinishda:

3

Demak:

permissions == 3

Lekin bu 3 oddiy ma'noda uchinchi enum qiymati emas.

Uning ichida ikkita bayroq mavjud:

Read
Write

Bayroq mavjudligini tekshirish

Masalan, qiymatda PermissionRead mavjudligini tekshirmoqchimiz.

Buning uchun:

value & permission

ishlatiladi.

Masalan:

func has(value, permission Permission) bool {
    return value&permission != 0
}

Agar:

permissions := PermissionRead | PermissionWrite

bo'lsa:

has(permissions, PermissionRead)

natija:

true

bo'ladi.

Lekin:

has(permissions, PermissionDelete)

natija:

false

bo'ladi.

Bit flaglar quyidagi holatlarda qulay:

  • permissionlar;
  • feature flaglar;
  • notification sozlamalari;
  • fayl rejimlari;
  • protokol flaglari;
  • bir nechta mustaqil true/false holatni bitta son ichida saqlash.

Misollar

1. Hafta kunlarini birdan boshlash

Bu misolda iota + 1 yordamida hafta kunlariga 1 dan boshlab qiymat beriladi.

package main

import "fmt"

type Weekday int

const (
    Monday Weekday = iota + 1
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    Sunday
)

func main() {
    fmt.Println(Monday, Wednesday, Sunday)
}

Birinchi qatorda:

Monday Weekday = iota + 1

yozilgan.

Bu qatorda:

iota = 0

Demak:

Monday = 0 + 1 = 1

Keyingi qatorda alohida ifoda yozilmagan:

Tuesday

Go oldingi:

iota + 1

ifodasini takrorlaydi.

Bu qatorda:

iota = 1

Shuning uchun:

Tuesday = 2

Shu tartibda:

Monday    = 1
Tuesday   = 2
Wednesday = 3
Thursday  = 4
Friday    = 5
Saturday  = 6
Sunday    = 7

bo'ladi.

Shuning uchun:

fmt.Println(Monday, Wednesday, Sunday)

natijasi:

1 3 7

bo'ladi.

Bu usul 0 qiymatini hafta kuni sifatida ishlatmaslik kerak bo'lgan vaziyatlarda qulay.

Shuningdek, foydalanuvchi ko'radigan yoki boshqa tizimdagi:

1 = Monday
...
7 = Sunday

formatga moslashishni osonlashtirishi mumkin.

2. Nol qiymatni noma'lum holat uchun saqlash

Bu misolda turning zero value qiymati alohida Unknown holatini bildiradi.

package main

import "fmt"

type DeliveryStatus int

const (
    DeliveryUnknown DeliveryStatus = iota
    DeliveryAccepted
    DeliveryOnTheWay
    DeliveryArrived
)

func main() {
    var status DeliveryStatus

    fmt.Println(status == DeliveryUnknown)

    status = DeliveryOnTheWay

    fmt.Println(status)
}

Dastlab:

var status DeliveryStatus

deb o'zgaruvchi yaratildi.

Unga qiymat berilmagan.

DeliveryStatus int asosidagi tur bo'lgani uchun uning zero value qiymati:

0

bo'ladi.

Bizning konstantalarimizda:

DeliveryUnknown DeliveryStatus = iota

ham:

0

ga teng.

Shuning uchun:

status == DeliveryUnknown

natijasi:

true

bo'ladi.

Bu yaxshi dizayn.

Chunki hali delivery holati aniq belgilanmagan bo'lsa:

0 = Unknown

deb tushunish mumkin.

Keyin:

status = DeliveryOnTheWay

deb haqiqiy holat berildi.

Qiymatlar:

DeliveryUnknown  = 0
DeliveryAccepted = 1
DeliveryOnTheWay = 2
DeliveryArrived  = 3

bo'ladi.

Shuning uchun:

fmt.Println(status)

natijasi:

2

bo'ladi.

Bu yondashuvda haqiqiy biznes holatlari 1 dan boshlanadi, 0 esa "hali noma'lum" degan ma'noda ishlatiladi.

3. Keraksiz birinchi qiymatni tashlab ketish

Bu misolda _ yordamida birinchi iota qiymati ishlatilmaydi.

package main

import "fmt"

type Priority int

const (
    _ Priority = iota
    PriorityLow
    PriorityMedium
    PriorityHigh
)

func main() {
    fmt.Println(
        PriorityLow,
        PriorityMedium,
        PriorityHigh,
    )
}

Birinchi qatorda:

_ Priority = iota

iota:

0

ga teng.

Lekin qiymat:

_

blank identifieriga berilgan.

Shuning uchun bu qiymat uchun alohida konstanta nomi mavjud emas.

Keyingi qatorda:

iota = 1

Demak:

PriorityLow = 1

Keyingi:

PriorityMedium = 2

va:

PriorityHigh = 3

bo'ladi.

Natija:

1 2 3

Bu usul 0 haqiqiy qiymat bo'lmasligi kerak bo'lgan holatlarda qulay.

Masalan, 0:

hali priority belgilanmagan

degan yashirin holat sifatida qolishi mumkin.

4. Qiymatlarni o'n qadam bilan oshirish

iota formula ichida ishlatilishi mumkinligini ko'rgan edik.

Quyidagi misolda kodlar:

10
20
30

ko'rinishida yaratiladi.

package main

import "fmt"

type DepartmentCode int

const (
    DepartmentSales DepartmentCode = (iota + 1) * 10
    DepartmentSupport
    DepartmentWarehouse
)

func main() {
    fmt.Println(
        DepartmentSales,
        DepartmentSupport,
        DepartmentWarehouse,
    )
}

Birinchi qatorda:

(iota + 1) * 10

formula ishlatilgan.

Bu qatorda:

iota = 0

Demak:

(0 + 1) * 10 = 10

Shuning uchun:

DepartmentSales = 10

Keyingi qator uchun:

iota = 1

Demak:

(1 + 1) * 10 = 20

Shuning uchun:

DepartmentSupport = 20

Keyingi:

(2 + 1) * 10 = 30

Demak:

DepartmentWarehouse = 30

Natija:

10 20 30

Bu usul kodlar orasida joy qoldirish kerak bo'lganda ishlatilishi mumkin.

Lekin agar bu qiymatlar database yoki tashqi API bilan doimiy shartnoma bo'lsa, iota o'rniga aniq son yozish ko'pincha xavfsizroq.

5. Bir satrda bog'liq qiymatlar yaratish

Bitta ConstSpec satrida bir nechta konstanta e'lon qilish mumkin.

Bu qatordagi barcha iotalar bir xil qiymatga ega bo'ladi.

Masalan:

package main

import "fmt"

const (
    SmallMin, SmallMax = iota * 10, iota*10 + 9
    MediumMin, MediumMax
    LargeMin, LargeMax
)

func main() {
    fmt.Println(SmallMin, SmallMax)
    fmt.Println(MediumMin, MediumMax)
    fmt.Println(LargeMin, LargeMax)
}

Birinchi qatorda:

SmallMin, SmallMax = iota * 10, iota*10 + 9

iota:

0

ga teng.

Ikkala ifodadagi iota ham bir xil:

0

Shuning uchun:

SmallMin = 0 * 10     = 0
SmallMax = 0 * 10 + 9 = 9

Natija:

0–9

Keyingi qatorda ifoda yozilmagan:

MediumMin, MediumMax

Go oldingi ikkita ifodani takrorlaydi.

Bu qatorda:

iota = 1

Shuning uchun:

MediumMin = 10
MediumMax = 19

Keyingi qatorda:

iota = 2

Natija:

LargeMin = 20
LargeMax = 29

Demak, uchta diapazon hosil bo'ladi:

Small  = 0–9
Medium = 10–19
Large  = 20–29

Bu misol bitta qatorda bir nechta iota ishlatilganda ular o'sha qator uchun bir xil qiymat olishini ko'rsatadi.

6. Har bir const guruhida qayta boshlash

Quyidagi misol iota yangi const blokida qaytadan 0 bo'lishini ko'rsatadi.

package main

import "fmt"

const (
    North = iota
    East
)

const (
    Draft = iota
    Published
)

func main() {
    fmt.Println(North, East)
    fmt.Println(Draft, Published)
}

Birinchi const guruhida:

const (
    North = iota
    East
)

qiymatlar:

North = 0
East  = 1

bo'ladi.

Keyin yangi:

const (
    Draft = iota
    Published
)

guruhi ochildi.

Bu yerda iota:

2

dan davom etmaydi.

U yana:

0

dan boshlanadi.

Shuning uchun:

Draft     = 0
Published = 1

bo'ladi.

Natija:

0 1
0 1

Har bir alohida const guruhi o'z iota hisobiga ega.

Bu turli enumga o'xshash guruhlarning bir-biriga ta'sir qilmasligini ta'minlaydi.

7. Fayl ruxsatlarini birlashtirish

Bu misolda iota bit flag yaratish uchun ishlatiladi.

package main

import "fmt"

type FilePermission uint8

const (
    CanRead FilePermission = 1 << iota
    CanWrite
    CanExecute
)

func main() {
    permissions := CanRead | CanWrite

    fmt.Println(permissions)

    fmt.Println(permissions&CanRead != 0)
    fmt.Println(permissions&CanExecute != 0)
}

Avval qiymatlarni hisoblaymiz.

Birinchi:

CanRead FilePermission = 1 << iota

Bu yerda:

iota = 0

Demak:

1 << 0 = 1

Shuning uchun:

CanRead = 1

Binary:

00000001

Keyingi:

CanWrite = 1 << 1 = 2

Binary:

00000010

Keyingi:

CanExecute = 1 << 2 = 4

Binary:

00000100

Endi:

permissions := CanRead | CanWrite

deyapmiz.

Binary:

00000001  CanRead
00000010  CanWrite
--------
00000011

Natijada:

permissions = 3

Lekin bu 3 ichida ikkita mustaqil flag mavjud.

Keyin:

permissions&CanRead != 0

tekshirilmoqda.

CanRead biti yoqilganligi sabab natija:

true

bo'ladi.

Keyin:

permissions&CanExecute != 0

tekshiriladi.

CanExecute biti yoqilmagan.

Shuning uchun natija:

false

bo'ladi.

Bu yondashuv bitta qiymatda bir nechta mustaqil ruxsatni saqlash imkonini beradi.

8. Bayroqni qiymatdan olib tashlash

Bit flagni faqat qo'shish emas, o'chirish ham mumkin.

Buning uchun Go'da:

&^

operatori mavjud.

U bit clear, ya'ni ma'lum bitlarni tozalash uchun ishlatiladi.

package main

import "fmt"

type Notification uint8

const (
    NotifyEmail Notification = 1 << iota
    NotifySMS
    NotifyPush
)

func main() {
    settings := NotifyEmail | NotifySMS | NotifyPush

    settings &^= NotifySMS

    fmt.Println(settings&NotifyEmail != 0)
    fmt.Println(settings&NotifySMS != 0)
    fmt.Println(settings&NotifyPush != 0)
}

Avval qiymatlar:

NotifyEmail = 1
NotifySMS   = 2
NotifyPush  = 4

bo'ladi.

Binary:

NotifyEmail = 001
NotifySMS   = 010
NotifyPush  = 100

Keyin:

settings := NotifyEmail | NotifySMS | NotifyPush

uchala flag ham birlashtiriladi:

001
010
100
---
111

Demak, boshida uchala notification turi yoqilgan.

Keyin:

settings &^= NotifySMS

deb yozilgan.

Bu:

NotifySMSga tegishli bitni tozalab tashla

degan ma'noni anglatadi.

Natijada:

111

dan:

101

qoladi.

Demak:

Email = yoqilgan
SMS   = o'chirilgan
Push  = yoqilgan

Shuning uchun:

settings&NotifyEmail != 0

natija:

true
settings&NotifySMS != 0

natija:

false

va:

settings&NotifyPush != 0

natija:

true

bo'ladi.

Natijalar:

true
false
true

9. Enum qiymatlarini matnga aylantirish

Son ko'rinishidagi status foydalanuvchiga yoki logga chiqarilganda tushunarsiz bo'lishi mumkin.

Shuning uchun unga matn qaytaradigan metod yozish mumkin.

package main

import "fmt"

type OrderStatus int

const (
    OrderNew OrderStatus = iota
    OrderPaid
    OrderSent
)

func (s OrderStatus) Label() string {
    switch s {
    case OrderNew:
        return "yangi"

    case OrderPaid:
        return "to'langan"

    case OrderSent:
        return "yuborilgan"

    default:
        return "noma'lum"
    }
}

func main() {
    fmt.Println(OrderPaid.Label())
    fmt.Println(OrderStatus(20).Label())
}

Qiymatlar:

OrderNew  = 0
OrderPaid = 1
OrderSent = 2

bo'ladi.

Keyin:

OrderPaid.Label()

chaqirilmoqda.

Metod:

case OrderPaid:
    return "to'langan"

qatoriga tushadi.

Natija:

to'langan

bo'ladi.

Keyingi:

OrderStatus(20).Label()

juda muhim misol.

20 e'lon qilingan konstantalar ichida yo'q.

Lekin Go:

OrderStatus(20)

konvertatsiyasiga ruxsat beradi.

Shuning uchun switch ichidagi hech bir asosiy case mos kelmaydi va:

default:
    return "noma'lum"

ishlaydi.

Natija:

noma'lum

bo'ladi.

Bu yana bir bor Go'dagi enumga o'xshash tur yopiq qiymatlar to'plami emasligini ko'rsatadi.

Label() metodini ko'proq foydalanuvchi interfeysi uchun ishlatish mumkin.

Agar log va debugging uchun noma'lum sonning o'zini ham ko'rish kerak bo'lsa:

return fmt.Sprintf("noma'lum(%d)", s)

kabi yozish foydaliroq bo'lishi mumkin.

10. Tashqi format uchun aniq kodlarni saqlash

Har doim iota ishlatish shart emas.

Ayniqsa konstantalarning sonli qiymati tashqi tizim bilan kelishilgan bo'lsa, aniq sonlarni yozish xavfsizroq.

package main

import "fmt"

type PaymentCode int

const (
    PaymentCreated  PaymentCode = 100
    PaymentApproved PaymentCode = 200
    PaymentRejected PaymentCode = 400
)

func main() {
    codes := []PaymentCode{
        PaymentCreated,
        PaymentApproved,
        PaymentRejected,
    }

    for _, code := range codes {
        fmt.Println(code)
    }
}

Bu yerda:

PaymentCreated = 100
PaymentApproved = 200

va:

PaymentRejected = 400

qiymatlari qo'lda aniq belgilangan.

Masalan, bu qiymatlar boshqa API bilan kelishilgan deb tasavvur qilamiz:

100 = payment created
200 = payment approved
400 = payment rejected

Bunday vaziyatda:

iota

ishlatish xavfliroq bo'lishi mumkin.

Chunki yangi konstanta o'rtaga qo'shilsa, undan keyingi iota qiymatlari siljishi mumkin.

Aniq sonlar bilan:

const (
    PaymentCreated  PaymentCode = 100
    PaymentApproved PaymentCode = 200
    PaymentRejected PaymentCode = 400
)

yozilganda esa konstantalarni qayta tartiblash yoki orasiga yangi qiymat qo'shish mavjud kodlarni avtomatik o'zgartirmaydi.

Masalan:

const (
    PaymentCreated  PaymentCode = 100
    PaymentPending  PaymentCode = 150
    PaymentApproved PaymentCode = 200
    PaymentRejected PaymentCode = 400
)

deb yangi holat qo'shsak ham:

PaymentCreated  = 100
PaymentApproved = 200
PaymentRejected = 400

o'zgarishsiz qoladi.

Bu ayniqsa:

  • public API;
  • database;
  • boshqa servis bilan integratsiya;
  • message broker;
  • fayl formati;
  • uzoq vaqt saqlanadigan ma'lumot;

uchun muhim.

Shuning uchun iotani shunchaki kodni qisqartirish uchun ishlatishdan oldin sonlarning o'zi tashqi ma'no kasb etadimi yoki yo'qmi, shuni hisobga olish kerak.