Go-তে এরর হ্যান্ডলিং এক্সেপশনের মাধ্যমে করা হয় না।
বরং, এরর হলো এমন টাইপের সাধারণ মান, যেগুলো বিল্ট-ইন error ইন্টারফেসটি ইমপ্লিমেন্ট করে।
error ইন্টারফেসটি খুবই সরল।
এতে মাত্র একটি মেথড Error() থাকে, যেটি এরর মেসেজটি একটি স্ট্রিং হিসেবে রিটার্ন করে।
type error interface {
Error() string
}
আপনি যখনই এমন একটি ফাংশন ডিফাইন করেন, যেখানে এক্সিকিউশনের সময় একটি এরর ঘটতে পারে, যেটি কলারের কাছে পৌঁছানো দরকার, তখন রিটার্ন টাইপের একটি হিসেবে error অন্তর্ভুক্ত করতে হয়।
ফাংশনের একাধিক রিটার্ন ভ্যালু থাকলে, প্রথা অনুযায়ী error সবসময় শেষটি হয়।
func DoSomething() (int, error) {
// ...
}
আপনাকে সবসময় নিজে থেকেই এরর ইন্টারফেসটি ইমপ্লিমেন্ট করতে হয় না।
একটি সরল এরর তৈরি করতে আপনি errors.New() ফাংশনটি ব্যবহার করতে পারেন, যেটি স্ট্যান্ডার্ড লাইব্রেরির errors প্যাকেজের অংশ।
এখানে শুধু একটি স্ট্রিং হিসেবে এরর মেসেজটি পাস করতে হয়, আর errors.New() আপনার মেসেজটি ধারণ করে এবং error ইন্টারফেসটি ইমপ্লিমেন্ট করে এমন একটি মান তৈরি করার কাজটি সামলে নেবে।
ফাংশন যদি একটি এরর রিটার্ন করে, তবে বাকি সব রিটার্ন প্যারামিটারের জন্য শূন্য মান রিটার্ন করা ভালো অভ্যাস:
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
কোনো এরর থাকলে সব ফাংশনই বাকি রিটার্ন ভ্যালুগুলোর জন্য শূন্য মান রিটার্ন করে, এমন ধরে নেওয়া আপনার উচিত নয়। কোনো এরর ঘটলে অন্য রিটার্ন ভ্যালুগুলোর কোনোটিই ব্যবহার করা নিরাপদ নয়, এটাই ধরে নেওয়া সর্বোত্তম। একমাত্র ব্যতিক্রম সেসব ক্ষেত্রে, যেখানে ডকুমেন্টেশন স্পষ্টভাবে বলে যে এরর হলেও অন্য রিটার্ন ভ্যালুগুলো অর্থবহ।
এমন সরল এরর যদি আপনি একাধিক জায়গায় ব্যবহার করতে চান, তবে সরাসরি errors.New ব্যবহার না করে এররের জন্য একটি ভ্যারিয়েবল ডিক্লেয়ার করা উচিত।
প্রথা অনুযায়ী, ভ্যারিয়েবলের নাম Err বা err দিয়ে শুরু হওয়া উচিত (এটি এক্সপোর্টেড কি না, তার উপর নির্ভর করে)।
এই এরর ভ্যারিয়েবলগুলোকে প্রায়ই সেন্টিনেল এরর বলা হয়।
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
ফাংশন এক্সিকিউশনের সময় কোনো এরর হয়নি বোঝাতে এররের জন্য nil রিটার্ন করুন:
func Foo() (int, error) {
return 10, nil
}
আপনি যদি এমন কোনো ফাংশন কল করেন যেটি একটি এরর রিটার্ন করে, তবে এরর ভ্যালুটি err নামের একটি ভ্যারিয়েবলে রাখা কমন ব্যাপার।
ফাংশনের প্রকৃত ফলাফল ব্যবহার করার আগে আপনাকে যাচাই করতে হবে যে কোনো এরর হয়নি।
আপনার কোডের "হ্যাপি পাথ"-কে নেস্টেড হওয়া থেকে বাঁচাতে আগে এরর কেসগুলো সামলানো উচিত।
nil-এর সাথে এরর তুলনা করতে আমরা == আর != ব্যবহার করতে পারি, আর err যদি nil না হয় তাহলে আমরা জানি যে একটি এরর হয়েছে।
func processUserFile() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
বেশিরভাগ সময়েই উপরের উদাহরণে দেখানো মতো ফাংশন স্ট্যাকের উপরে এররটি রিটার্ন করা হয়। এরর সামলানোর আরেকটি উপায় হতে পারে এটি লগ করে অন্য কোনো কাজ চালিয়ে যাওয়া। এররটি হয় রিটার্ন করা, নয় লগ করা, দুটোই নয়, এটাই ভালো অভ্যাস।
Go-তে বেশিরভাগ ফাংশনই রিটার্ন ভ্যালুর একটি হিসেবে এরর রাখে, তাই Go কোডে আপনি সর্বত্র if err != nil প্যাটার্নটি দেখতে পাবেন/ব্যবহার করবেন।
শুধু এরর মেসেজ স্ট্রিংয়ের চেয়ে বেশি তথ্য যদি আপনার এররে থাকতে চান, তবে আপনি একটি কাস্টম এরর টাইপ তৈরি করতে পারেন।
আগেই বলা হয়েছে, Go-তে যা কিছু error ইন্টারফেসটি ইমপ্লিমেন্ট করে (অর্থাৎ যার একটি Error() string মেথড আছে) তা একটি এরর হিসেবে কাজ করতে পারে।
সাধারণত কাস্টম এরর টাইপ তৈরি করতে একটি স্ট্রাক্ট ব্যবহার করা হয়।
প্রথা অনুযায়ী, কাস্টম এরর টাইপের নাম Error দিয়ে শেষ হওয়া উচিত।
এছাড়া, Error() string মেথডটি পয়েন্টার রিসিভার দিয়ে সেট আপ করা সবচেয়ে ভালো, এর যুক্তি জানতে এই Stackoverflow মন্তব্যটি দেখুন।
মনে রাখবেন, এর মানে হলো আপনাকে আপনার কাস্টম এররের একটি পয়েন্টার রিটার্ন করতে হবে, নইলে এটি error হিসেবে গণ্য হবে না, কারণ নন-পয়েন্টার মানটি Error() string মেথডটি প্রদান করে না।
type MyCustomError struct {
message string
details string
}
func (e *MyCustomError) Error() string {
return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
// ...
return &MyCustomError{
message: "...",
details: "...",
}
}
যে দিনটির জন্য আপনি এতদিন অপেক্ষা করেছিলেন, সেটি অবশেষে এসে গেছে, আর এখন আপনি আল্পসের একটি সুন্দর খামারের গর্বিত মালিক।
সকালে খুব ভোরে উঠে গরুদের খাবার খাওয়ানো আপনার এখনও ভালো লাগে না।
যেহেতু আপনি একজন দুর্দান্ত ইঞ্জিনিয়ার, তাই আপনি একটি ফুড ডিসপেনসার বানান, যার নাম FEED-M-ALL।
আপনার প্রকল্প শেষ করার জন্য সর্বশেষ যে জিনিসটি দরকার, তা হলো এমন এক টুকরো কোড, যা হিসাব করে প্রতিটি গরু কতটুকু গোখাদ্য পাওয়া উচিত। প্রতিটি গরু যেন সমান পরিমাণ পায়, সেটি জরুরি; আপনাকে দ্বন্দ্ব এড়িয়ে চলতে হবে। গরু খুবই সংবেদনশীল।
ভাগ্য ভালো, গোখাদ্যের পরিমাণ হিসাব করার সব সূত্র আপনাকে নিজে বের করতে হবে না। আপনি ইন্টারনেটে খুঁজে পাওয়া এক রহস্যময় এক্সটার্নাল লাইব্রেরি ব্যবহার করেন। এটি ব্যবহার করলে গরুরা সবচেয়ে খুশি থাকার কথা। লাইব্রেরিটি এমন একটি টাইপ প্রকাশ করে, যা নিচের ইন্টারফেসটি পূরণ করে। নিজে যে কোডটি লিখবেন, সেখানে আপনি এটির উপর নির্ভর করবেন।
type FodderCalculator interface {
FodderAmount(int) (float64, error)
FatteningFactor() (float64, error)
}
কোড নিয়ে কাজ করার সময় আপনি এরর হ্যান্ডলিং আরও উন্নত করবেন, যাতে এটি আরও নির্ভরযোগ্য হয় এবং পরে দৈনন্দিন খামারের কাজে ব্যবহার করার সময় Debug করা সহজ হয়।
সবার আগে আপনি এমন কোড লেখায় মনোযোগ দেন, যা প্রতিটি গরুর জন্য গোখাদ্যের পরিমাণ হিসাব করতে দরকার।
এমন একটি ফাংশন DivideFood লিখুন, যা আর্গুমেন্ট হিসেবে একটি FodderCalculator এবং ইন্টিজার হিসেবে গরুর সংখ্যা নেয়।
এই কাজের জন্য আপনি ধরে নিতে পারেন, পাঠানো গরুর সংখ্যা সবসময় শূন্যের চেয়ে বেশি।
ফাংশনটি প্রতিটি গরুর জন্য খাবারের পরিমাণ float64 হিসেবে রিটার্ন করবে, আর কোনো এরর ঘটলে সেই এরর রিটার্ন করবে।
হিসাব করার জন্য প্রথমে সব গরুর মোট গোখাদ্যের পরিমাণ বের করতে হবে।
এটি করা হয় FodderAmount মেথড কল করে এবং গরুর সংখ্যা পাঠিয়ে।
এছাড়া এমন একটি ফ্যাক্টর দরকার, যা দিয়ে এই পরিমাণ গুণ করতে হবে।
এই ফ্যাক্টরটি আপনি পান FatteningFactor মেথড কল করে।
এই দুটি মান আর গরুর সংখ্যা দিয়ে এখন আপনি প্রতিটি গরুর জন্য খাবারের পরিমাণ হিসাব করতে পারেন (float64 হিসেবে)।
DivideFood ফাংশন থেকে এটিই রিটার্ন করা উচিত।
আপনি যে মেথডগুলো কল করেন, তার কোনোটি এরর রিটার্ন করলে কার্যক্রম থেমে যাবে এবং সেই এররটি (যেমন আছে তেমনই) DivideFood ফাংশন থেকে রিটার্ন করা উচিত।
// For this example, we assume FodderAmount returns 50
// and FatteningFactor returns 1.5.
DivideFood(fodderCalculator, 5)
// => 15 <nil>
// Now assuming FodderAmount returns an error with message "something went wrong".
DivideFood(fodderCalculator, 5)
// => 0 "something went wrong"
উপরের প্রথম কাজটি করতে গিয়ে আপনি বুঝতে পারলেন, আপনি যে এক্সটার্নাল লাইব্রেরিটি ব্যবহার করছেন, তা আপনার ভাবনার মতো উচ্চমানের নয়। যেমন, এটি অবৈধ ইনপুট ঠিকভাবে হ্যান্ডল করতে পারে না। আপনি নিজের কোডে ইনপুট মানের জন্য একটি যাচাই যোগ করে এই সীমাবদ্ধতা কাটিয়ে উঠতে চান।
এমন একটি ফাংশন ValidateInputAndDivideFood লিখুন, যার সিগনেচার উপরের DivideFood-এর মতোই।
DivideFood কল করবে এবং সেই কলে যা রিটার্ন আসে তা রিটার্ন করবে।"invalid number of cows" বার্তাসহ একটি এরর রিটার্ন করবে।ValidateInputAndDivideFood(fodderCalculator, 5)
// => 15 <nil>
ValidateInputAndDivideFood(fodderCalculator, -2)
// => 0 "invalid number of cows"
পাঠানোর আগে গরুর সংখ্যা যাচাই করা ভালো পদক্ষেপ ছিল, তবে অনির্দিষ্ট এরর বার্তাটি নিয়ে আপনি পুরোপুরি সন্তুষ্ট নন।
আপনি ঠিক করেন InvalidCowsError নামের একটি কাস্টম এরর টাইপ বানিয়ে আরও ভালো কিছু করবেন।
কাস্টম এররটিতে গরুর সংখ্যা (int) আর একটি কাস্টম বার্তা (string) থাকবে, এবং Error মেথডটি ডেটাকে নিচের ফরম্যাটে সিরিয়ালাইজ করবে:
{number of cows} cows are invalid: {custom message}
কাস্টম এররটি হাতে নিয়ে এমন একটি ফাংশন ValidateNumberOfCows লিখুন, যা ইন্টিজার হিসেবে গরুর সংখ্যা নেয় এবং একটি এরর (অথবা nil) রিটার্ন করে।
InvalidCowsError রিটার্ন করে, যার কাস্টম বার্তা "there are no negative cows"।InvalidCowsError রিটার্ন করে, যার কাস্টম বার্তা "no cows don't need food"।nil রিটার্ন করে, যা বোঝায় ভ্যালিডেশন সফল হয়েছে।err := ValidateNumberOfCows(-5)
err.Error()
// => "-5 cows are invalid: there are no negative cows"
এই ভ্যালিডেশন ফাংশনটি সেট আপ করার কঠিন কাজ শেষে আপনি খেয়াল করেন, সন্ধ্যা হয়ে গেছে, আর আপনি ডেস্ক ছেড়ে পাহাড়ের উপরে সূর্যাস্ত উপভোগ করতে বেরিয়ে পড়েন। নতুন ভ্যালিডেশন ফাংশনটি সত্যিকারের কোডে যোগ করার কাজটি আপনি অন্য একদিনের জন্য ফেলে রাখেন।
Exercism-এ সাইন আপ করুন, Go ট্র্যাকের 34টি কনসেপ্ট166টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।