گولانگ میں خرابی کا انتظام معیار
خرابی کی اقسام
خرابی کو بیان کرنے کے لئے کچھ اختیارات ہیں۔ جس اختیار کو منتخب کرنا ہوتا ہے جو آپ کے موقع پر سب سے بہترین ہو، اس سے پہلے درج ذیل نکتوں کو در نظر میں رکھیں:
- کیا کالر کو خرابی کا مطابقتی کرنے کی ضرورت ہے؟ اگر ہاں، تو ہمیں
errors.Is
یاerrors.As
کو دعم دینا ہوگا جس کے لئے ہمیں ٹاپ لیول خرابی متغیرات یا کسٹم ٹائپ دعویٰ کرنا ہوگا۔ - خرابی کا پیغام ایک مستقل سٹرنگ ہے یا سیاق و سباقی معلومات کی ضرورت ہوتی ہے؟ مستقل سٹرنگ کے لئے ہم
errors.New
استعمال کرسکتے ہیں، لیکن اگر سیاق و سباقی معلومات کی ضرورت ہو، تو ہمیںfmt.Errorf
یا کسٹم خرابی کا استعمال کرنا ہوگا۔ - کیا ہم نئی خرابیاں نیچے کی فنکشنز سے واپس بھیج رہے ہیں؟ اگر ہاں، تو خرابی پشیگی کے حصے کا حوالہ دیکھیں۔
خرابی کا مطابقت؟ | خرابی کا پیغام | رہنمائی |
---|---|---|
نہیں | مستقل | errors.New |
نہیں | سیاق و سباقی | fmt.Errorf |
ہاں | مستقل | ٹاپ لیول var کے ساتھ errors.New |
ہاں | سیاق و سباقی | کسٹم error ٹائپ |
مثال کے طور پر، مستقل سٹرنگ کی خرابیوں کو ظاہر کرنے کے لئے errors.New
کا استعمال کریں۔ اگر کالر کو اس خرابی کا مطابقت کرنا ہو اور اس کو ہینڈل کرنا ہو، تو اسے مطابقت کے لئے ایک متغیر کے طور پر نکالیں تاکہ errors.Is
کے ساتھ مطابقت ہو سکے۔
خرابی کا مطابقت نہیں
// پیکیج فو
func Open() error {
return errors.New("کھولا نہیں جا سکا")
}
// پیکیج بار
if err := foo.Open(); err != nil {
// خرابی کا ہینڈل نہیں کر سکتے۔
panic("نامعلوم خرابی")
}
خرابی کا مطابقت
// پیکیج فو
var ErrCouldNotOpen = errors.New("کھولا نہیں جا سکا")
func Open() error {
return ErrCouldNotOpen
}
// پیکیج بار
if err := foo.Open(); err != nil {
if errors.Is(err, foo.ErrCouldNotOpen) {
// خرابی کا ہینڈل کریں
} else {
panic("نامعلوم خرابی")
}
}
سیاق و سباقی معلومات کے ساتھ خرابیوں کے لئے، اگر کالر کو مطابقتی کرنے کی ضرورت نہیں ہو تو fmt.Errorf
کا استعمال کریں۔ اگر کالر کو یہ مطابقت کرنے کی ضرورت ہے تو کسٹم error
کا استعمال کریں۔
خرابی کا مطابقت نہیں
// پیکیج فو
func Open(file string) error {
return fmt.Errorf("%q فائل نہیں مل سکی", file)
}
// پیکیج بار
if err := foo.Open("testfile.txt"); err != nil {
// خرابی کا ہینڈل نہیں کر سکتے۔
panic("نامعلوم خرابی")
}
خرابی کا مطابقت
// پیکیج فو
type NotFoundError struct {
File string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%q فائل نہیں مل سکی", e.File)
}
func Open(file string) error {
return &NotFoundError{File: file}
}
// پیکیج بار
if err := foo.Open("testfile.txt"); err != nil {
var notFound *NotFoundError
if errors.As(err, ¬Found) {
// خرابی کا ہینڈل کریں
} else {
panic("نامعلوم خرابی")
}
}
یاد رہے کہ اگر آپ پیکیج سے خرابی متغیرات یا ٹائپز بیرونی کرتے ہیں تو وہ پیکیج کا عوامی API کا حصہ بن جاتے ہیں۔
خرابی کو جھپٹانا
جب دوسرے طریقہ کار کو بلانے کے دوران خرابی واقع ہوتی ہے تو عام طور پر اسے حل کرنے کے لئے تین طریقے ہوتے ہیں:
- اصل خرابی کو وہی سلسلہ کار کے طور پر واپس بھیجیں۔
-
fmt.Errorf
کا استعمال کریں%w
کے ساتھ تاکید شامل کرنے کے لئے اور پھر اسے واپس بھیجیں۔ -
fmt.Errorf
کا استعمال کریں%v
کے ساتھ تاکید شامل کرنے کے لئے اور پھر اسے واپس بھیجیں۔
اگر کوئی اضافی سلسلہ کار کو شامل کرنے کا سوال نہ ہو تو، اصل خرابی کو وہی سلسلہ کار کے طور پر واپس بھیجیں۔ یہ اصل خرابی کی قسم اور پیغام کو محفوظ رکھے گا۔ یہ خاص طور پر مناسب ہے جب وراثتی خرابی کا پیغام کافی معلومات شامل کرتا ہے کہ خرابی کہاں سے ابتداء ہوئی۔
دوسری طرف، خرابی کے پیغام میں اظہار کریں کہ "رابطہ انکار کر دیا گیا" جیسی مبہم خرابیاں پیش نہ ہوں۔ بدل کر، آپ کو موزوں خرابیاں ملیں گی، جیسے "خدمت فونکو: رابطہ انکار کیا گیا"۔
اپنی خرابیوں میں سلسلہ کار کو شامل کرنے کے لئے fmt.Errorf
کا استعمال کریں اور %w
یا %v
فعل کا انتخاب کریں جون کالر کیوں کے لئے جڑیاں پتہ کر سکتا ہے۔
- اس عمل کے لئے
%w
استعمال کریں جب کالر کو اصل خرابی تک رسائی ہو گی۔ یہ بہترین ترین خرابیوں کے لئے ایک اچھا ڈیفالٹ ہے، لیکن یہ جان رکھیں کہ کالر اس رویہ پر منحصر ہو سکتا ہے۔ لہذا، جان رکھیں کہ خرابیاں جو معروف متغیرات یا اقسام ہیں، فعل کو قرار دینے اور انہیں فنکشن کے حصول کا حصہ بنانا اور تجربہ بھی کرنا چاہیے۔ - اس صورتحال میں
%v
کا استعمال کریں جس سے اصل خرابی چھپ جائے۔ کالر اسے میچ نہیں کر پائے گا، لیکن اگر ضرورت ہو تو آپ %w کی طرف لوٹ سکتے ہیں۔
واپسی خرابی میں تاکید شامل کرنے کے دوران ترتیب دینے والے جملے جیسے "ناکام ہوگیا" کا استعمال متن میں کمتر رکھیں۔ جب خرابی اسٹیک سے گزرتی ہوتی ہے، یہ لیے لیے ایک سے ایک ایک درجے پر ٹکڑوں میں وسیع ہوجاتی ہے۔
توصیہ نہیں:
s, err := store.New()
if err != nil {
return fmt.Errorf(
"failed to create new store: %w", err)
}
// failed to x: failed to y: failed to create new store: the error
تجویز کی گئی:
s, err := store.New()
if err != nil {
return fmt.Errorf(
"new store: %w", err)
}
// x: y: new store: the error
لیکن، جب خرابی کو دوسرے سسٹم میں بھیجا جاتا ہے، یہ واضح ہونا چاہیے کہ پیغام ایک خرابی ہے (مثال کے طور پر، لاگز میں "err" ٹیگ یا "Failed" پریفکس۔)
غلط نامکری
غلط ارز کو بہتر انداز میں سٹور کے نئے ایس کرنے کے دوران خرابی واقع ہی جاتی ہے۔
خرابیوں کا سامنا کرنا
جب کالر درستگی کی سمجھ کے مطابق، کالے سے ایک خرابی کا مواجہ کرتا ہے تو وہ مختلف طریقوں سے خرابی کا سامنا کرسکتا ہے۔
اس میں شامل ہیں لیکن اس سے محدود نہیں ہیں:
- اگر کالے نے مخصوص خرابی کی تعریف پر متفق ہوا ہے تو، خرابی کو
errors.Is
یاerrors.As
کے ساتھ میچ کرنا اور مختلف طریقوں میں برانچنگ کا سامنا کرنا - اگر خرابی قابل بازیابی ہو، تو خرابی کو لاگ کرنا اور شاندار طریقے سے پست ہونا
- اگر یہ کسی ڈومین خاص کی ناکامی کی شرط کو ظاہر کرتی ہے، تو ایک معین خرابی واپس کرنا
- خرابی کو واپس کرنا، چاہے وہ چھپی ہوئی ہو یا سیدھی ہو
کالر نے خرابی کو کیسے سامنا کرتا ہے، یہ ٹریٹ کرنا چاہیے کے نام پر ہوتا ہے، وہ بطور عام قاعدہ ہر خرابی کا صرف ایک بار ہی سامنا کرنا چاہیے۔ مثلاً، کالر کو خرابی کو لاگ نہیں کرنا چاہئے اور پھر اسے واپس کرنا چاہئے، کیونکہ اس کا کالر شاید خرابی سے سامنا کرے۔
مثال کے طور پر، مندرجہ ذیل صورتوں کو دیکھیں:
بُرا: خرابی کو لاگ کرنا اور اسے واپس کرنا
اس خرابی پر شیفٹ کرنے والے دیگر کالر بھی اس پر مشابہ امکانات اختیار کرسکتے ہیں۔ اس سے بے فائدہ ایپلیکیشن لاگوں میں بہت زیادہ شور پیدا ہوگا۔
u, err := getUser(id)
if err != nil {
// برا: تفصیل دیکھیں
log.Printf("Could not get user %q: %v", id, err)
return err
}
اچھا: خرابی کو چھپانا اور واپس کرنا
اوپر کی طرف خرابیاں اس خرابی کا سامنا کریں گی۔ %w
استعمال کرنے سے یہ یقینی بنایا جاتا ہے کہ وہ خرابی کو errors.Is
یا errors.As
کے ساتھ میچ کرسکتے ہیں اگر واقعی میں اہم ہو۔
u, err := getUser(id)
if err != nil {
return fmt.Errorf("get user %q: %w", id, err)
}
اچھا: خرابی کو لاگ کرنا اور شاندار طریقے سے پست ہونا
اگر عمل ضرورت سے زیادہ نہیں ہوتا تو ہم بغیر تجاوز کے اس سے برابر ہونے کی پیشکش کرسکتے ہیں۔
if err := emitMetrics(); err != nil {
// میٹرکس لکھنے میں ناکامی ہونی چاہئے نہیں
// ایپلیکیشن کو ٹوٹنے نہیں دینی چاہئیں
log.Printf("Could not emit metrics: %v", err)
}
اچھا: خرابی کا میچ کرنا اور شاندار طریقے سے پست ہونا
اگر کالے نے اپنی ایک مخصوص خرابی کی تعریف کی ہے اور ناکامی قابل بازیاب ہے، تو اس خرابی کو میچ کریں اور شاندار طریقے سے پست ہو جائیں۔ بقایا معاملات کے لئے، خرابی کو چھپانے اور واپس کرنے کے لئے۔ اوپر کی طرف خرابیاں دوسری خرابیوں کا سامنا کریں گی۔
tz, err := getUserTimeZone(id)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
// صارف موجود نہیں ہے۔ یو ٹی سی استعمال کریں۔
tz = time.UTC
} else {
return fmt.Errorf("get user %q: %w", id, err)
}
}
انکشافی ناکامیوں کا سامنا کرنا
ٹائپ انکشاف غلط قسم کی پتہ چلاتے وقت ایک ریٹرن ارزُ هو کے ساتھ پینک ہو جائے گا۔ اس لئے ہمیشہ "کاما، اوکے" ڈیل کا استعمال کریں۔
تجویز نہیں کیا گیا:
t := i.(string)
تجویز کیا گیا ہے:
t, ok := i.(string)
if !ok {
// خرابی کو شاندار طریقے سے سامنا کریں
}
پینک استعمال سے بچیں
تفصیلات میں پروڈکشن ماحول میں کوڈ رننگ کو بینہ جوشی سے پینک کرنا چاہئے. پینک کی عام وجہ ہے کے یہ بنیادی طور پر بتریات کے بعد بنتا ہے. اگر کوئی خرابی واقع ہوتی ہے، تو فنکشن کو خرابی کو واپس کرنا چاہئے اور بلا سوچے سمجھے کالر کو فیصلہ کرنے دینا چاہئے کے وہ اس کو کیسے حل کرے۔
مناسب نہیں:
func run(args []string) {
if len(args) == 0 {
panic("ایک ارگمنٹ درکار ہے")
}
// ...
}
func main() {
run(os.Args[1:])
}
مناسب:
func run(args []string) error {
if len(args) == 0 {
return errors.New("ایک ارگمنٹ درکار ہے")
}
// ...
return nil
}
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
پینک/بحران سے نجات اور بحرانی صورتحال (مثلاً: صفر حوالہ) کے وقوعات کے وقت پینک کا مطالبہ نہیں ہونا چاہئے. ابتدائی پروگرام کے دوران یہ ایک استثناء ہے: پروگرام کو بحران پیدا کرنے والے صورتحالات کو پروگرام شروع ہونے کے دوران ہینڈل کیا جانا چاہئے۔
var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML"))
ٹیسٹ کوڈ میں بھی پینک کی بجائے، بہتر ہوتا ہے کہ t.Fatal
یا t.FailNow
استعمال کریں تاکہ ناکامیوں کو درست طریقے سے نشان زد کیا جا سکے۔
مناسب نہیں:
// func TestFoo(t *testing.T)
f, err := os.CreateTemp("", "test")
if err != nil {
panic("ٹیسٹ کی ترتیب کرنے میں ناکام ہوگیا")
}
مناسب:
// func TestFoo(t *testing.T)
f, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal("ٹیسٹ کی ترتیب کرنے میں ناکام ہوگیا")
}