খামার

খামার

লার্নিং অনুশীলনী

ভূমিকা

আদর্শ জগতে সবকিছু নিখুঁতভাবে কাজ করে। কিন্তু বাস্তব জগতে ভুল হয়ে যেতে পারে, আর আমাদের সফটওয়্যার যাতে মজবুত ও নির্ভরযোগ্য হয় তা নিশ্চিত করতে এই পরিস্থিতিগুলো আমরা কীভাবে সামলাই সেটাই গুরুত্বপূর্ণ। এক্সেপশন প্রোগ্রামিংয়ের একটি অত্যন্ত গুরুত্বপূর্ণ ধারণা, যা দিয়ে আমরা এরর এবং অপ্রত্যাশিত পরিস্থিতিগুলো সুন্দরভাবে সামলাতে পারি।

একটি এরর রেইজ করলে, যদি তা হ্যান্ডেল করা না হয়, তবে প্রোগ্রামটি থেমে যায় এবং একটি এরর মেসেজ দেখায়। বেশিরভাগ ক্ষেত্রেই আপনি চান না যে এরর ঘটলে আপনার প্রোগ্রাম থেমে যাক; বরং আপনি চান এররটি হ্যান্ডেল করে প্রোগ্রামটি চালিয়ে যেতে।

একটি এক্সেপশন রেইজ করা

Crystal-এ এক্সেপশন raise কিওয়ার্ড দিয়ে রেইজ করা হয়, আর এতে হয় একটি String অথবা একটি Exception অবজেক্ট দেওয়া যায়। হ্যান্ডেল করা না হলে প্রোগ্রামটি থেমে যাবে এবং এরর মেসেজটি প্রিন্ট করবে।

raise "This is an error"

Crystal-এ বেশ কয়েকটি বিল্ট-ইন এক্সেপশন আছে, যেমন ArgumentError, IndexError, KeyError, IOError, SystemCallError, TypeError, ZeroDivisionError এবং আরও অনেক। এগুলোর ক্ষেত্রে আপনাকে এক্সেপশনের জন্য একটি মেসেজ পাস করতে হবে।

raise ArgumentError.new("This is an argument error")

এক্সেপশন হ্যান্ডেল করা

কোনো এক্সেপশন রেইজ হলে আমরা আমাদের প্রোগ্রাম ক্র্যাশ করাতে চাই না। তাই, যখন আমরা জানি কোডের কোনো অংশ এরর-প্রবণ, তখন আমরা সেটিকে একটি begin ব্লকে মুড়ে দিতে পারি এবং rescue ব্লক দিয়ে এক্সেপশনটি রেসকিউ করতে পারি। begin ব্লকটি এমন কোডের শুরু চিহ্নিত করে যা একটি এক্সেপশন রেইজ করতে পারে, আর rescue ব্লকটি এক্সেপশনটি হ্যান্ডেল করে।

begin
 raise "This is an error"
rescue
 puts "An error occurred!"
end

rescue ব্লকের সাথে একটি ভ্যারিয়েবলও উল্লেখ করা যায়, যাতে এক্সেপশন অবজেক্টটি পাওয়া যায়।

begin
 raise "This is an error"
rescue ex
 puts "An error occurred: #{ex.message}"
end

rescue ব্লকে নির্দিষ্ট একটি এক্সেপশন টাইপও উল্লেখ করা যায়, যাতে কেবল সেই এক্সেপশনটিই ধরা পড়ে।

begin
 raise ArgumentError.new("This is an argument error")
rescue ArgumentError
 puts "An argument error occurred!"
end

# or

begin
 raise ArgumentError.new("This is an argument error")
rescue ex : ArgumentError
 puts "An argument error occurred: #{ex.message}"
end

বিভিন্ন ধরনের এক্সেপশন হ্যান্ডেল করতে একাধিক rescue ব্লক ব্যবহার করা যায়। নিচের উদাহরণে প্রথম rescue ব্লকটি একটি ArgumentError ধরবে, আর দ্বিতীয় rescue ব্লকটি অন্য যেকোনো এক্সেপশন ধরবে।

begin
 raise ArgumentError.new("This is an argument error")
rescue ArgumentError
 puts "An argument error occurred!"
rescue
 puts "An error occurred!"
end

begin ব্লকে একটি else ব্লকও থাকতে পারে, যা কোনো এক্সেপশন রেইজ না হলে চালানো হয়।

begin
 puts "No error occurred"
rescue
 puts "An error occurred!"
else
 puts "No error occurred"
end

সবশেষে, একটি ensure ব্লক আছে যা এক্সেপশন রেইজ হোক বা না হোক সবসময়ই চালানো হয়।

begin
 raise "This is an error"
rescue
 puts "An error occurred!"
ensure
 puts "This is always executed"
end

মেথড কনভেনশন

কিছু মেথডের দুটি সংস্করণ থাকে, একটি ! দিয়ে আর অন্যটি ছাড়া। এর অর্থ দুটি ভিন্ন বিষয় হতে পারে। একটি হলো মেথডটি অবজেক্টটি পরিবর্তন করে, আর অন্যটি হলো মেথডটি একটি এক্সেপশন রেইজ করতে পারে।

তবে বুলিয়ান ধারণায় উল্লেখ করা মেথডের শেষে ? দেওয়ার আরেকটি কনভেনশনও আছে। কিছু মেথড ডিফল্টভাবে একটি এক্সেপশন রেইজ করে, তবে তাদের আবার ? দিয়ে শেষ হওয়া একটি সংস্করণও থাকে, যা এক্সেপশন রেইজ করার বদলে nil রিটার্ন করে।

আপনি যখন কোনো এরর রেইজ হওয়া এড়াতে চান, তখন এটি আদর্শ। এটি পারফরম্যান্সের জন্য উপকারী হতে পারে, কারণ এতে একটি স্ট্যাকট্রেস তৈরি করতে হয় না, আর সঠিকভাবে সেট করা হলে এটি কোডকে আরও নিরাপদ করে তুলতে পারে।

কাস্টম এক্সেপশন

আপনি Exception ক্লাস থেকে ইনহেরিট করে নিজের এক্সেপশনও তৈরি করতে পারেন। তা করতে গিয়ে আপনি ঐচ্ছিকভাবে initialize মেথড ওভাররাইড করে এক্সেপশনের মেসেজ সেট করতে পারেন। এটি করতে মেসেজটি দিয়ে @message নামের একটি ইনস্ট্যান্স ভ্যারিয়েবলে অ্যাসাইন করা যায়।

class MyException < Exception
 def initialize
   @message = "This is my exception"
 end
end

raise MyException.new

নির্দেশনা

যে দিনটির জন্য আপনি এতদিন অপেক্ষা করেছিলেন, সেটি অবশেষে এসে গেছে, আর এখন আপনি আল্পসের বুকে একটি সুন্দর খামারের গর্বিত মালিক।

তবুও সকালে খুব ভোরে উঠে গরুগুলোকে খাবার দেওয়া আপনার এখনও ভালো লাগে না। যেহেতু আপনি একজন দুর্দান্ত ইঞ্জিনিয়ার, তাই আপনি একটি ফিড ডিসপেনসার বানালেন, FEED-M-ALL।

আপনার প্রজেক্ট শেষ করার জন্য শেষ যে জিনিসটির দরকার, সেটি হলো এমন এক টুকরো কোড, যা হিসাব করে প্রতিটি গরু কতটুকু ফিড পাবে। প্রতিটি গরু যেন সমান পরিমাণ পায়, সেটি জরুরি, আর দ্বন্দ্ব এড়ানোও দরকার। গরুগুলো খুবই সংবেদনশীল।

ভাগ্য ভালো, ফিডের পরিমাণ হিসাব করার সব ফর্মুলা আপনাকে নিজে বের করতে হবে না। আপনি ইন্টারনেটে পাওয়া এক রহস্যময় এক্সটার্নাল লাইব্রেরি ব্যবহার করেন। এটি নাকি সবচেয়ে সুখী গরু এনে দেবে। লাইব্রেরিটি এমন একটি ক্লাস প্রকাশ করে, যা নিচের ইন্টারফেসটি পূরণ করে। নিজে লেখা কোডে আপনি এটির উপরেই ভরসা করবেন।

class FodderCalculator
  def fodder_amount! : Number 
  end

  def fattening_factor! : Number
  end
end

কোড নিয়ে কাজ করার সময় আপনি এরর হ্যান্ডলিং আরও উন্নত করবেন, যাতে এটি আরও মজবুত হয় এবং পরে খামারের দৈনন্দিন কাজে ব্যবহার করার সময় debug করা সহজ হয়।

1. খাদ্য সমানভাবে ভাগ করুন

সবার আগে আপনি এমন কোড লেখায় মন দেন, যা দিয়ে প্রতি গরুর ফিডের পরিমাণ হিসাব করা যায়।

এমন একটি মেথড TheFarm.divide_food লিখুন, যা আর্গুমেন্ট হিসেবে একটি FodderCalculator অবজেক্ট এবং ইন্টিজার হিসেবে গরুর সংখ্যা নেয়। এই কাজে ধরে নিন, পাঠানো গরুর সংখ্যা সবসময় শূন্যের চেয়ে বেশি। মেথডটি প্রতি গরুর খাদ্যের পরিমাণ রিটার্ন করবে।

হিসাব করতে হলে প্রথমে সব গরুর জন্য মোট ফিডের পরিমাণ বের করতে হবে। এটি করা হয় fodder_amount! মেথডটি কল করে এবং গরুর সংখ্যাটি পাস করে। এর সঙ্গে লাগবে একটি ফ্যাক্টর, যা দিয়ে এই পরিমাণ গুণ করতে হবে। এই ফ্যাক্টরটি আপনি পান fattening_factor! মেথডটি কল করে। এই দুটি মান আর গরুর সংখ্যা দিয়ে এখন আপনি প্রতি গরুর খাদ্যের পরিমাণ হিসাব করতে পারেন (float64 হিসেবে)।

আপনি যে মেথডগুলো কল করেন, তাদের কোনোটি যদি একটি এরর রিটার্ন করে, তবে এক্সিকিউশন থেমে যাবে এবং 0 রিটার্ন করবে।

fodder_calculator = FodderCalculator.new(50, 1.5)
TheFarm.divide_food(fodder_calculator, 5)
# => 15.0

fodder_calculator = FodderCalculator.new(50, nil)
TheFarm.divide_food(fodder_calculator, 5)
# => 0

2. গরুর সংখ্যা যাচাই করুন

উপরের প্রথম কাজটি করতে গিয়ে আপনি বুঝতে পারেন, যে এক্সটার্নাল লাইব্রেরিটি ব্যবহার করছেন, সেটি আপনার ভাবনার মতো ভালো মানের নয়। যেমন, এটি ইনভ্যালিড ইনপুট ঠিকভাবে হ্যান্ডল করতে পারে না। নিজের কোডে ইনপুট মানের একটি যাচাই যোগ করে আপনি এই সীমাবদ্ধতাটি পাশ কাটাতে চান।

এমন একটি ফাংশন লিখুন, TheFarm.validate_input_and_divide_food, যার সিগনেচার উপরের TheFarm.divide_food-এর মতোই।

  • পাঠানো গরুর সংখ্যা 0-এর বেশি হলে ফাংশনটি TheFarm.divide_food কল করবে এবং সেই কলে যা রিটার্ন আসে তা ফেরত দেবে।
  • গরুর সংখ্যা 0 বা তার কম হলে ফাংশনটি "Number of cows must be greater than 0" বার্তা দিয়ে একটি এরর রেইজ করবে।
TheFarm.validate_input_and_divide_food(fodderCalculator, 5)
# => 15

TheFarm.validate_input_and_divide_food(fodderCalculator, -2)
# Error: Number of cows must be greater than 0

3. এরর হ্যান্ডলিং উন্নত করুন

পাঠানোর আগে গরুর সংখ্যা যাচাই করা ভালো পদক্ষেপ ছিল, তবে অস্পষ্ট এরর বার্তাটি নিয়ে আপনি পুরোপুরি সন্তুষ্ট নন। আপনি ঠিক করেন আরও ভালো কিছু করবেন, আর সেজন্য InvalidCowsError নামে একটি কাস্টম এরর ক্লাস বানাবেন।

কাস্টম এররটিতে থাকবে গরুর সংখ্যা (Int32) এবং একটি কাস্টম বার্তা (String), আর Expection মেথডটি ডেটাটি নিচের ফরম্যাটে সিরিয়ালাইজ করবে:

{number of cows} cows are invalid: {custom message}

কাস্টম এররটি হাতে নিয়ে এখন এমন একটি ফাংশন লিখুন, TheFarm.validate_number_of_cows, যা ইন্টিজার হিসেবে গরুর সংখ্যা নেয় এবং একটি এরর (বা nil) রিটার্ন করে।

  • গরুর সংখ্যা 0-এর কম হলে ফাংশনটি একটি InvalidCowsError রিটার্ন করে, যার কাস্টম বার্তা থাকে "there are no negative cows"।
  • গরুর সংখ্যা 0 হলে ফাংশনটি একটি InvalidCowsError রিটার্ন করে, যার কাস্টম বার্তা থাকে "no cows don't need food"।
  • অন্যথায় ফাংশনটি nil রিটার্ন করে, যা বোঝায় যাচাই সফল হয়েছে।
TheFarm.validate_number_of_cows(5)
# => nil

TheFarm.validate_number_of_cows(-5)
# => InvalidCowsError: -5 cows are invalid: there are no negative cows

এই ভ্যালিডেশন ফাংশনটি দাঁড় করানোর কঠিন পরিশ্রমের পর আপনি খেয়াল করেন, সন্ধ্যা হয়ে গেছে; তাই ডেস্ক ছেড়ে পাহাড়ের উপরে সূর্যাস্ত উপভোগ করতে চলে যান। নতুন ভ্যালিডেশন ফাংশনটি সত্যিই কোডে যোগ করার কাজটি আপনি অন্য এক দিনের জন্য তুলে রাখেন।

GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
Crystal Exercism

খামার শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, Crystal ট্র্যাকের 26টি কনসেপ্ট142টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।