Go’da xatolarni boshqarish
Dastur ishlayotganda fayl topilmasligi, noto‘g‘ri qiymat kiritilishi yoki tarmoq so‘rovi bajarilmasligi mumkin. Go’da bunday kutiladigan muammolar error qiymati orqali boshqariladi.
Har bir funksiya error qaytarmaydi. Faqat bajarilishi muvaffaqiyatsiz tugashi mumkin bo‘lgan funksiya odatda natija bilan birga error qaytaradi.
error nima?
error standart interfeys bo‘lib, bitta metodga ega:
Xato bo‘lmasa, funksiya nil qaytaradi. Xato yuz bersa, uning sababini ifodalovchi error qiymati qaytariladi.
Quyidagi funksiya ikki float64 sonni bo‘ladi:
package main
import (
"errors"
"fmt"
)
func bolish(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("nolga bo‘lish mumkin emas")
}
return a / b, nil
}
func main() {
natija, err := bolish(10, 4)
if err != nil {
fmt.Println("Xato:", err)
return
}
fmt.Println("Natija:", natija)
}
Natija:
bolish() muvaffaqiyatli bajarilganda hisob natijasi va nil qaytaradi. Ikkinchi argument nol bo‘lsa, natijaning nol qiymati va xato qaytariladi.
Xatoni tekshirish
Xato qaytaradigan funksiya chaqirilganda avval err tekshiriladi:
Natija:
Xatodan keyin natijani ishlatish xavfli bo‘lishi mumkin. Shu sababli xato aniqlanganda uni qaytarish, foydalanuvchiga tushunarli xabar ko‘rsatish yoki boshqa mos qaror qabul qilish kerak.
Bu “har bir funksiyada xato tekshiriladi” degani emas. Faqat funksiya error qaytarsa, chaqiruvchi uni e’tiborsiz qoldirmasligi kerak. Ayrim hollarda xatoni ataylab e’tiborsiz qoldirish mumkin, lekin sabab koddan aniq ko‘rinishi lozim.
Xato yaratish
O‘zgarmas matnli sodda xato uchun errors.New() ishlatiladi:
Xato xabariga qiymat qo‘shish kerak bo‘lsa, fmt.Errorf() qulay:
func yoshniTekshir(yosh int) error {
if yosh < 0 {
return fmt.Errorf("noto‘g‘ri yosh: %d", yosh)
}
return nil
}
Xato matni qisqa va muammoning sababini tushuntiradigan bo‘lsin. Odatda xato matni kichik harf bilan boshlanadi va oxiriga nuqta qo‘yilmaydi. Bu xatoga yuqori qatlamlarda qo‘shimcha matn biriktirishni osonlashtiradi.
Xatoni yuqoriga uzatish
Funksiya xatoni o‘zi hal qila olmasa, uni chaqiruvchiga qaytaradi. Faqat return err qilish mumkin, ammo xato qaysi amal paytida yuz bergani yo‘qolib qoladi.
Quyidagi misolda xato uch qatlam orqali yuqoriga uzatiladi:
package main
import (
"fmt"
"os"
)
func faylniOqish(nom string) ([]byte, error) {
data, err := os.ReadFile(nom)
if err != nil {
return nil, fmt.Errorf("%q faylini o‘qish: %w", nom, err)
}
return data, nil
}
func sozlamaniYuklash() ([]byte, error) {
data, err := faylniOqish("config.json")
if err != nil {
return nil, fmt.Errorf("sozlamani yuklash: %w", err)
}
return data, nil
}
func run() error {
data, err := sozlamaniYuklash()
if err != nil {
return err
}
fmt.Println(string(data))
return nil
}
func main() {
if err := run(); err != nil {
fmt.Println("Dastur ishga tushmadi:", err)
}
}
config.json mavjud bo‘lmasa, xabar taxminan quyidagicha bo‘ladi:
Dastur ishga tushmadi: sozlamani yuklash: "config.json" faylini o‘qish: open config.json: no such file or directory
Har bir qatlam o‘zi bajarayotgan amal haqida kontekst qo‘shdi. Xato faqat foydalanuvchiga xabar berish mumkin bo‘lgan yuqori qatlamda chiqarildi. Pastki funksiyalar xatoni chiqarib, yana qaytarmadi. Bu bitta xatoning logda bir necha marta takrorlanishiga yo‘l qo‘ymaydi.
%w asl xatoni o‘raydi (wrap). Natijada xato matniga kontekst qo‘shiladi va asl sababni keyin ham tekshirish mumkin bo‘ladi. Oddiy %v matnni qo‘shadi, lekin xatolar zanjirini saqlamaydi.
errors.Is() bilan sababni tekshirish
O‘ralgan xatoni matni orqali solishtirish ishonchsiz. Ma’lum xato qiymatini zanjir ichidan topish uchun errors.Is() ishlatiladi:
package main
import (
"errors"
"fmt"
"os"
)
func faylniOqish(nom string) ([]byte, error) {
data, err := os.ReadFile(nom)
if err != nil {
return nil, fmt.Errorf("%q faylini o‘qish: %w", nom, err)
}
return data, nil
}
func main() {
_, err := faylniOqish("config.json")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("Sozlama fayli topilmadi")
return
}
if err != nil {
fmt.Println("Xato:", err)
}
}
faylniOqish() xatoga kontekst qo‘shgan bo‘lsa ham, errors.Is() zanjir ichidagi os.ErrNotExist sababini topadi.
Dasturga tegishli doimiy xato holati uchun sentinel error e’lon qilish mumkin:
Chaqiruvchi uni errors.Is(err, ErrUserNotFound) bilan tekshiradi. Sentinel error faqat chaqiruvchi shu holatga alohida munosabat bildirishi kerak bo‘lganda foydali.
Custom error va errors.As()
Xatoga faqat matn emas, qo‘shimcha ma’lumot ham biriktirish kerak bo‘lsa, maxsus xato turi (custom error type) yaratiladi.
Masalan, forma maydonidagi xatoda maydon nomini alohida saqlaymiz:
package main
import (
"errors"
"fmt"
)
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
func userYarat(name string, age int) error {
if name == "" {
return &ValidationError{
Field: "name",
Message: "bo‘sh bo‘lmasligi kerak",
}
}
if age < 0 {
return &ValidationError{
Field: "age",
Message: "manfiy bo‘lmasligi kerak",
}
}
return nil
}
func register(name string, age int) error {
if err := userYarat(name, age); err != nil {
return fmt.Errorf("foydalanuvchini ro‘yxatdan o‘tkazish: %w", err)
}
return nil
}
func main() {
err := register("", 20)
if err == nil {
return
}
var validationErr *ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("%s maydonida xato: %s\n", validationErr.Field, validationErr.Message)
return
}
fmt.Println("Kutilmagan xato:", err)
}
Natija:
ValidationError Error() metodiga ega bo‘lgani uchun error interfeysini bajaradi. register() uni %w bilan o‘ragan. errors.As() zanjir ichidan *ValidationError turini topib, uning Field va Message maydonlarini ishlatish imkonini berdi.
Qisqacha farq:
errors.Is()ma’lum xato qiymati yoki sabab mavjudligini tekshiradi;errors.As()ma’lum xato turini topadi va uning ma’lumotlarini olishga yordam beradi.
defer va resurslarni yopish
defer funksiya tugashidan oldin bajariladigan chaqiruvni rejalashtiradi. U fayl kabi ochilgan resursni barcha chiqish yo‘llarida yopish uchun qulay:
package main
import (
"fmt"
"os"
)
func faylHajmi(nom string) (int64, error) {
file, err := os.Open(nom)
if err != nil {
return 0, fmt.Errorf("faylni ochish: %w", err)
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return 0, fmt.Errorf("fayl ma’lumotini olish: %w", err)
}
return info.Size(), nil
}
func main() {
size, err := faylHajmi("config.json")
if err != nil {
fmt.Println("Xato:", err)
return
}
fmt.Println("Fayl hajmi:", size)
}
defer file.Close() faqat fayl muvaffaqiyatli ochilgandan keyin yozildi. file.Stat() xato qaytarsa ham, funksiya tugashida fayl yopiladi.
Bir nechta defer oxirgi qo‘shilganidan boshlab teskari tartibda bajariladi. Ularni uzoq sikl ichida ko‘p marta yig‘ish resurslarning funksiya tugaguncha ochiq qolishiga olib kelishi mumkin.
panic va recover
panic oddiy xatoni qaytarish usuli emas. U dastur odatiy davom eta olmaydigan holat yoki dasturchi xatosi uchun ishlatiladi. Fayl topilmasligi, noto‘g‘ri foydalanuvchi kiritishi va tarmoq uzilishi kabi kutiladigan holatlar error bilan qaytariladi.
recover() deferred funksiya ichida shu goroutine’dagi panic tarqalishini to‘xtata oladi:
package main
import "fmt"
func xavfsizIshgaTushir(fn func()) {
defer func() {
if value := recover(); value != nil {
fmt.Println("Panic ushlandi:", value)
}
}()
fn()
}
func main() {
xavfsizIshgaTushir(func() {
panic("kutilmagan ichki holat")
})
}
Natija:
recover() bajarilishni panic qatoridan davom ettirmaydi. Panic yuz bergan funksiya yakunlanadi, boshqaruv esa panicni ushlagan deferred funksiya tugagach yuqori qatlamga qaytadi.
Kutubxona yoki odatiy biznes funksiyasida panicni error o‘rniga ishlatmang. recover() ham xatolarni yashirish uchun emas, masalan serverdagi bitta noto‘g‘ri so‘rov butun jarayonni to‘xtatmasligi uchun eng yuqori chegarada qo‘llanadi.
Xatolar bilan ishlash tartibi
Amalda quyidagi tartib foydali:
- Xato yuz berishi mumkin bo‘lgan funksiya
errorqaytarsin. - Xatoni shu qatlam hal qila olsa, mos qaror qabul qilsin.
- Hal qila olmasa,
%wbilan foydali kontekst qo‘shib yuqoriga qaytarsin. - Chaqiruvchi alohida holatni ajratishi kerak bo‘lsa,
errors.Is()yokierrors.As()ishlatsin. - Xatoni foydalanuvchiga ko‘rsatish yoki logga yozishni tizim chegarasida bir marta bajaring.
panicni kutiladigan xatolar uchun ishlatmang.
Xulosa
- Faqat muvaffaqiyatsiz tugashi mumkin bo‘lgan funksiyalar odatda
errorqaytaradi. err != nilbo‘lsa, xatoni qaytarish yoki shu joyda mazmunli boshqarish kerak.fmt.Errorf()va%wxatoga kontekst qo‘shib, asl sababni saqlaydi.errors.Is()xato sababini,errors.As()esa maxsus xato turini tekshiradi.deferochilgan resurslarni ishonchli yopishga yordam beradi.panic/recoveroddiyerroroqimining o‘rnini bosmaydi.
Keyingi darsda xatoni stderr’ga yozish va mos exit code qaytarishni buyruq qatori dasturi misolida ko‘ramiz.
Misollar
1. O‘zgarmas xato yaratish
Bu misolda bo‘sh nom uchun oldindan yaratilgan xato qiymati qaytariladi.
package main
import (
"errors"
"fmt"
)
var ErrEmptyName = errors.New("nom bo‘sh bo‘lmasligi kerak")
func validateName(name string) error {
if name == "" {
return ErrEmptyName
}
return nil
}
func main() {
err := validateName("")
if err != nil {
fmt.Println("Xato:", err)
}
}
ErrEmptyName bir marta yaratiladi va aniq xato holatini bildiradi. validateName() muvaffaqiyatli bajarilganda nil qaytaradi. Bo‘sh string turning nol qiymati bo‘lgani uchun misolda u kiritilmagan nomni ifodalaydi.
2. Xato xabariga qiymat qo‘shish
Bu misolda fmt.Errorf() noto‘g‘ri yosh qiymatini xato xabarida saqlaydi.
package main
import "fmt"
func validateAge(age int) error {
if age < 0 {
return fmt.Errorf("yosh manfiy bo‘lmasligi kerak: %d", age)
}
return nil
}
func main() {
if err := validateAge(-4); err != nil {
fmt.Println("Xato:", err)
return
}
fmt.Println("Yosh qabul qilindi")
}
age < 0 sharti faqat manfiy qiymatlarni rad etadi. 0 ham haqiqiy yosh bo‘lishi mumkin, shu sabab <= 0 ishlatilmagan. %d xabarga aynan qaysi qiymat kelganini qo‘shadi.
3. Natija bilan birga xato qaytarish
Bu misolda ombordagi mavjud miqdordan ko‘p mahsulot so‘ralsa, natijaning nol qiymati va xato qaytariladi.
package main
import (
"errors"
"fmt"
)
func remaining(stock, requested int) (int, error) {
if requested < 1 {
return 0, errors.New("so‘ralgan miqdor musbat bo‘lishi kerak")
}
if requested > stock {
return 0, errors.New("omborda yetarli mahsulot yo‘q")
}
return stock - requested, nil
}
func main() {
left, err := remaining(12, 5)
if err != nil {
fmt.Println("Xato:", err)
return
}
fmt.Println("Qoldi:", left)
}
Xato holatida int turning nol qiymati 0 qaytariladi, lekin chaqiruvchi avval errni tekshiradi. Muvaffaqiyatli holatda 12 - 5 hisoblanib, 7 va nil qaytadi.
4. Sentinel xatoni o‘rab uzatish
Bu misolda pastki qatlamdagi xatoga kontekst qo‘shiladi va errors.Is() bilan asl sabab topiladi.
package main
import (
"errors"
"fmt"
)
var ErrProductNotFound = errors.New("mahsulot topilmadi")
func findProduct(id int) error {
if id != 10 {
return ErrProductNotFound
}
return nil
}
func loadProduct(id int) error {
if err := findProduct(id); err != nil {
return fmt.Errorf("%d identifikatorli mahsulotni yuklash: %w", id, err)
}
return nil
}
func main() {
err := loadProduct(25)
if errors.Is(err, ErrProductNotFound) {
fmt.Println("Boshqa mahsulot tanlang")
return
}
fmt.Println("Mahsulot yuklandi")
}
%w ErrProductNotFound qiymatini xatolar zanjirida saqlaydi. Shu sabab xato matniga qo‘shimcha ma’lumot qo‘shilgan bo‘lsa ham, errors.Is() aniq sababni topadi. 10 mavjud mahsulot identifikatori sifatida tanlangan.
5. Maxsus xato turidan ma’lumot olish
Bu misolda xato turi noto‘g‘ri maydon nomi va qiymatini alohida saqlaydi.
package main
import (
"errors"
"fmt"
)
type FieldError struct {
Field string
Value int
}
func (e *FieldError) Error() string {
return fmt.Sprintf("%s maydonida noto‘g‘ri qiymat: %d", e.Field, e.Value)
}
func validateCount(count int) error {
if count < 1 {
return &FieldError{Field: "count", Value: count}
}
return nil
}
func main() {
err := validateCount(0)
var fieldErr *FieldError
if errors.As(err, &fieldErr) {
fmt.Println("Maydon:", fieldErr.Field)
fmt.Println("Qiymat:", fieldErr.Value)
}
}
FieldErrorning Error() metodi bor, shuning uchun *FieldError error interfeysini bajaradi. errors.As() xatoni kerakli turga ajratib, Field va Value maydonlarini o‘qishga imkon beradi. count kamida 1 bo‘lishi kerakligi uchun 0 xato qiymat sifatida berilgan.
6. O‘ralgan xatolar zanjirini ko‘rish
Bu misolda errors.Unwrap() xatolar zanjirini tashqi qatlamdan asl sababgacha ochadi.
package main
import (
"errors"
"fmt"
)
func prepareReport() error {
base := errors.New("ma’lumot mavjud emas")
loadErr := fmt.Errorf("ma’lumotni yuklash: %w", base)
return fmt.Errorf("hisobotni tayyorlash: %w", loadErr)
}
func main() {
err := prepareReport()
for err != nil {
fmt.Println(err)
err = errors.Unwrap(err)
}
}
Har bir %w yangi tashqi qatlam yaratadi. Sikl joriy xatoni chiqarib, keyin errors.Unwrap() bilan ichki sababga o‘tadi. Eng ichki xatodan keyin Unwrap() nil qaytaradi va sikl tugaydi.
7. Bir nechta xatoni birlashtirish
Bu misolda mustaqil tekshiruvlardan chiqqan xatolar errors.Join() bilan bitta error qiymatiga birlashtiriladi.
package main
import (
"errors"
"fmt"
)
var ErrNameRequired = errors.New("nom kerak")
var ErrPriceInvalid = errors.New("narx musbat bo‘lishi kerak")
func validateProduct(name string, price int) error {
var nameErr error
var priceErr error
if name == "" {
nameErr = ErrNameRequired
}
if price < 1 {
priceErr = ErrPriceInvalid
}
return errors.Join(nameErr, priceErr)
}
func main() {
err := validateProduct("", 0)
fmt.Println(err)
fmt.Println(errors.Is(err, ErrNameRequired))
fmt.Println(errors.Is(err, ErrPriceInvalid))
}
nameErr va priceErr dastlab nil, chunki hali xato aniqlanmagan. errors.Join() nil qiymatlarni tashlab, mavjud xatolarni birlashtiradi. Natijada errors.Is() ikkala sababni ham topadi.
8. Erta qaytishda deferni bajarish
Bu misolda funksiya xato bilan erta tugasa ham, defer orqali rejalashtirilgan tozalash bajariladi.
package main
import (
"errors"
"fmt"
)
func process(value int) error {
fmt.Println("Jarayon boshlandi")
defer fmt.Println("Tozalash bajarildi")
if value < 0 {
return errors.New("qiymat manfiy bo‘lmasligi kerak")
}
fmt.Println("Qiymat:", value)
return nil
}
func main() {
if err := process(-1); err != nil {
fmt.Println("Xato:", err)
}
}
value manfiy bo‘lgani uchun funksiya natijani chiqarishdan oldin qaytadi. Shunga qaramay, qaytishdan avval deferred fmt.Println() bajariladi. defer xato tekshiruvidan oldin yozilgani uchun ikkala chiqish yo‘lida ham ishlaydi.
9. Bir nechta deferning tartibi
Bu misol deferred chaqiruvlar oxirgi qo‘shilganidan boshlab bajarilishini ko‘rsatadi.
package main
import "fmt"
func steps() {
defer fmt.Println("1-qadam yopildi")
defer fmt.Println("2-qadam yopildi")
defer fmt.Println("3-qadam yopildi")
fmt.Println("Asosiy ish bajarildi")
}
func main() {
steps()
}
Avval asosiy ish haqidagi xabar chiqadi. Keyin deferred chaqiruvlar 3, 2, 1 tartibida bajariladi. Bu tartib ketma-ket ochilgan resurslarni teskari tartibda yopishga mos keladi.
10. panicni xatoga aylantirish
Bu misolda chegaradagi funksiya kutilmagan panic qiymatini oddiy errorga aylantiradi.
package main
import "fmt"
func safeRun(action func()) (err error) {
defer func() {
if value := recover(); value != nil {
err = fmt.Errorf("amal to‘xtadi: %v", value)
}
}()
action()
return nil
}
func main() {
err := safeRun(func() {
panic("ichki holat buzildi")
})
if err != nil {
fmt.Println("Xato:", err)
}
}
err nomlangan qaytish qiymati sifatida e’lon qilingan. Deferred funksiya recover() orqali panic qiymatini oladi va errga yozadi. Bu usul faqat tizim chegarasidagi kutilmagan holat uchun mos; odatiy tekshiruvlarda funksiya bevosita error qaytarishi kerak.