অন্যদিন আমি একটি রুবি এক্সেপশন ইন্ট্রোডাকশন খুঁজছিলাম যা বिगিনারদের জন্য লেখা - যারা বেসিক রুবি সিনট্যাক্স জানে কিন্তু এক্সেপশন কী এবং কেন এটি উপযোগী সেটি খুব ভালোভাবে জানে না। আমি পাইনি, তাই আমি নিজে চেষ্টা করলাম। আশা করি আপনি এটা উপযোগী পাবেন। কোনো পয়েন্ট যদি বিভ্রান্তিকর হয়, তবে টুইট করুন @StarrHorne-এ। :)
এক্সেপশন কী?
এক্সেপশন হলো রুবির অপ্রত্যাশিত ইভেন্ট হ্যান্ডেল করার পদ্ধতি।
আপনি যদি কখনো আপনার কোডে টাইপো করলে, যা আপনার প্রোগ্রামকে SyntaxError বা NoMethodError মেসেজের সাথে ক্র্যাশ করে দেয়, তবে আপনি এক্সেপশন একশনে দেখেছেন।
রুবিতে একটি এক্সেপশন রেইজ করলে, جهان থেমে যায় এবং আপনার প্রোগ্রাম শাট ডাউন করা শুরু করে। যদি কিছু প্রসেসটি না রোকা, তবে আপনার প্রোগ্রাম শেষ পর্যন্ত একটি এরর মেসেজের সাথে এক্সিট করবে।
এখানে একটি উদাহরণ। নিচের কোডে, আমরা শূন্য দিয়ে ভাগ করার চেষ্টা করি। এটি অসম্ভব, তাই রুবি একটি এক্সেপশন রেইজ করে ZeroDivisionError নামে। প্রোগ্রাম কুইট করে এবং একটি এরর মেসেজ প্রিন্ট করে।
1 / 0
# প্রোগ্রাম ক্র্যাশ করে এবং আউটপুট দেয়: "ZeroDivisionError: divided by 0"
ক্র্যাশ হওয়া প্রোগ্রাম আমাদের ইউজারদের রেগে তোলে। তাই আমরা সাধারণত এই শাটডাউন প্রসেসটি রোকতে চাই, এবং এররটাকে বুদ্ধিমানীপূর্ণভাবে রেসপন্স করতে চাই।
এটিকে "রescu করা", "হ্যান্ডল করা", বা "ক্যাচ করা" এক্সেপশন বলা হয়। এগুলো সব একই অর্থ বহন করে। রুবিতে এটিকে কীভাবে করবেন:
begin
# এখানে যেকোনো এক্সেপশন...
1/0
rescue
# ...এই কোডটি রান করবে
puts "Got an exception, but I'm responding intelligently!"
do_something_intelligent()
end
# এই প্রোগ্রাম ক্র্যাশ করে না।
# আউটপুট: "Got an exception, but I'm responding intelligently!"
এক্সেপশনটি এখনও ঘটল, কিন্তু এটি প্রোগ্রামকে ক্র্যাশ করান না কারণ এটি "রescu করা" হয়েছিল। এক্সিট করার বদলে, রুবি rescue ব্লকের কোড রান করে, যা একটি মেসেজ প্রিন্ট করে।
এটি ভালো, কিন্তু এর এক বড় লিমিটেশন আছে। এটি আমাদের বলে "কিছু ভুল হয়েছে," আমাদের জানাতে না কী ভুল হয়েছে।
যে সব ইনফরমেশন "কী ভুল হয়েছে" সেটি নিয়ে থাকবে এক্সেপশন অবজেক্টে।
এক্সেপশন অবজেক্ট
এক্সেপশন অবজেক্টগুলো সাধারণ রুবি অবজেক্ট। সেগুলো এক্সেপশন আপনি যা রescu করলেন তার "কী ঘটло" সেটির সকল ডেটা ধারণ করে।
এক্সেপশন অবজেক্ট পেতে, আপনি একটি কিছুটা ভিন্ন rescue সিনট্যাক্স ব্যবহার করবেন।
# সকল এরর রescu করে, এবং এক্সেপশন অবজেক্টটি `e`-তে রাখে
rescue => e
# শুধু ZeroDivisionError রescu করে এবং এক্সেপশন অবজেক্টটি `e`-তে রাখে
rescue ZeroDivisionError => e
দ্বিতীয় উদাহরণে, ZeroDivisionError হলো e-এ থাকা অবজেক্টের ক্লাস। আমরা যে সব "টাইপ" এক্সেপশন নিয়ে কথা বলেছি সেগুলো আসলে ক্লাস নাম।
এক্সেপশন অবজেক্টগুলো উপযোগী ডিবাগ ডেটাও ধারণ করে। চলুন আমাদের ZeroDivisionError-এর এক্সেপশন অবজেক্ট দেখি।
begin
# এখানে যেকোনো এক্সেপশন...
1/0
rescue ZeroDivisionError => e
puts "Exception Class: #{ e.class.name }"
puts "Exception Message: #{ e.message }"
puts "Exception Backtrace: #{ e.backtrace }"
end
# আউটপুট:
# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
# Exception Backtrace: ...ব্যাকট্রেস একটি অ্যারে হিসেবে...
বেশির রুবি এক্সেপশনের মত, এটিও একটি মেসেজ এবং একটি ব্যাকট্রেস ধারণ করে তার ক্লাস নামের পাশাপাশি।
নিজস্ব এক্সেপশন রেইজ করা
এ পর্যন্ত আমরা শুধু এক্সেপশন রescu করার কথা বলেছি। আপনি আপনার নিজস্ব এক্সেপশনও ট্রিগার করতে পারেন। এই প্রসেসকে "রেইজিং" বলা হয়। আপনি এটি raise মেথড কল করে করেন।
যখন আপনি নিজস্ব এক্সেপশন রেইজ করেন, তবে আপনি বেছে নেন কোন টাইপের এক্সেপশন ব্যবহার করবেন। আপনি এরর মেসেজও সেট করতে পারেন।
এখানে একটি উদাহরণ:
begin
# ArgumentError রেইজ করে "you messed up!" মেসেজসহ
raise ArgumentError.new("You messed up!")
rescue ArgumentError => e
puts e.message
end
# আউটপুট: You messed up!
আপনি দেখতে পাচ্ছেন, আমরা একটি নতুন এরর অবজেক্ট তৈরি করছি (একটি ArgumentError) একটি কাস্টম মেসেজ ("You messed up!") সহ এবং এটি raise মেথডে পাস করছি।
এটি রুবি হলেও, raiseকে কয়েকভাবে কল করা যায়:
# এটি আমার পছন্দের কারণ এটি খুব এক্সপ্লিসিট
raise RuntimeError.new("You messed up!")
# ...একই ফলাফল দেয়
raise RuntimeError, "You messed up!"
# ...একই ফলাফল দেয়। কিন্তু আপনি শুধু RuntimeErrors এইভাবে রেইজ করতে পারেন
raise "You messed up!"
কাস্টম এক্সেপশন তৈরি
রুবির বিল্ট-ইন এক্সেপশনগুলো চমৎকার, কিন্তু সেগুলো প্রতিটি সম্ভাব্য ইউজ কেস কভার করে না।
কী হব যদি আপনি একটি ইউজার সিস্টেম বানাচ্ছেন এবং ইউজার সাইটের একটি অফ-লিমিট্স অংশ অ্যাক্সেস করার চেষ্টা করলে এক্সেপশন রেইজ করতে চান? রুবির কোনো স্ট্যান্ডার্ড এক্সেপশন ফিট করে না, তাই আপনার সেরা বেট হলো একটি নতুন ধরনের এক্সেপশন তৈরি করা।
একটি কাস্টম এক্সেপশন তৈরি করতে, শুধু একটি নতুন ক্লাস তৈরি করুন যা StandardError-এর থেকে ইনহেরিট করে।
class PermissionDeniedError < StandardError
end
raise PermissionDeniedError.new()
এটি শুধু একটি সাধারণ রুবি ক্লাস। এর মানে আপনি যেকোনো অন্যান্য ক্লাসের মত মেথড এবং ডেটা যোগ করতে পারেন। চলুন "action" নামে একটি অ্যাট্রিবিউট যোগ করি:
class PermissionDeniedError < StandardError
attr_reader :action
def initialize(message, action)
# পেরেন্টের কনস্ট্রাক্টর কল করুন মেসেজ সেট করার জন্য
super(message)
# অ্যাকশনটি একটি ইনস্ট্যান্স ভেরিয়েবলে স্টোর করুন
@action = action
end
end
# তারপর, যখন ইউজার এমন কিছু ডিলিট করার চেষ্টা করে যেটার পারমিশন নেই,
# আপনি এমন কিছু করতে পারেন:
raise PermissionDeniedError.new("Permission Denied", :delete)
ক্লাস হায়ারার্কি
আমরা StandardError-কে সাবক্লাস করে একটি কাস্টম এক্সেপশন তৈরি করেছি, যা নিজে Exception-এর থেকে সাবক্লাস।
আসলে, আপনি যদি রুবিতে যেকোনো এক্সেপশনের ক্লাস হায়ারার্কি দেখেন, তবে আপনি দেখবেন এটি τελentu Exception-এ ফিরে যায়। এখানে, আমি আপনাকে প্রুফ করব। এগুলো রুবির অধিকাংশ বিল্ট-ইন এক্সেপশন, হায়ারার্কিক্যালি প্রদর্শিত:
Exception
NoMemoryError
ScriptError
LoadError
NotImplementedError
SyntaxError
SignalException
Interrupt
StandardError
ArgumentError
IOError
EOFError
IndexError
LocalJumpError
NameError
NoMethodError
RangeError
FloatDomainError
RegexpError
RuntimeError
SecurityError
SystemCallError
SystemStackError
ThreadError
TypeError
ZeroDivisionError
SystemExit
আপনাকে এই সব মেমোরাইজ করতে হবে না, অবশ্যই। আমি আপনাকে এগুলো দেখাচ্ছি কারণ এই হায়ারার্কির ধারণাটি খুব গুরুত্বপূর্ণ একটি নির্দিষ্ট কারণে।
একটি নির্দিষ্ট ক্লাসের এরর রescu করলে তার চাইল্ড ক্লাসের এররও রescu হয়।
আরেকবার বলি...
যখন আপনি rescue StandardError করেন, তবে আপনি শুধু StandardError ক্লাসের এক্সেপশনই রescu করেন না বরং তার চাইল্ডদেরও। চার্টে দেখলে পাবেন এটা অনেক: ArgumentError, IOError, ইত্যাদি।
যদি আপনি rescue Exception করেন, তবে আপনি প্রতিটি একক এক্সেপশন রescu করবেন, যা খুব খারাপ ধারণা।
সকল এক্সেপশন রescu (খারাপ পথে)
আপনি যদি চাইলে চিৎকার খেতে, stack overflow-এ যান এবং এমন কোড পোস্ট করুন:
// এটা করবেন না
begin
do_something()
rescue Exception => e
...
end
উপরের কোড সকল এক্সেপশন রescu করবে। এটা করবেন না! এটা আপনার প্রোগ্রামকে আশ্চর্যजनকভাবে ভাঙে দেবে।
এটি কারণ রুবি এক্সেপশন ব্যবহার করে শুধু এররের জন্য নয়। এটি অপারেটিং সিস্টেম থেকে মেসেজ হ্যান্ডেল করার জন্যও ব্যবহার করে যাকে "সিগন্যাল" বলা হয়। আপনি যদি কখনো "ctrl-c" চাপি প্রোগ্রাম এক্সিট করার জন্য, তবে আপনি একটি সিগন্যাল ব্যবহার করেছেন। সকল এক্সেপশন সাপ্রেস করলে, আপনি সেই সিগন্যালগুলোও সাপ্রেস করবেন।
এক্সেপশনের কিছু ধরনও আছে - সিনট্যাক্স এররের মত - যেগুলো খুবই আপনার প্রোগ্রামকে ক্র্যাশ করাতে হবে। আপনি সেগুলো সাপ্রেস করলে, আপনি কখনো জানবেন না আপনি কখন টাইপো বা অন্য ভুল করলেন।
সকল এরর রescu (সঠিক পথে)
ফিরে যান ক্লাস হায়ারার্কি চার্টে এবং আপনি দেখবেন আপনি যেসব এরর রescu করতে চান সেগুলো সব StandardError-এর চাইল্ড।
এটি মানে যদি আপনি "সকল এরর" রescu করতে চান তবে আপনাকে StandardError রescu করা উচিত।
begin
do_something()
rescue StandardError => e
# শুধু আপনার অ্যাপের এক্সেপশন গুলা নিঘল। SyntaxError জিনিসপত্র আলাদা রাখা হল।
end
আসলে, আপনি যদি এক্সেপশন ক্লাস স্পেসিফাই না করেন, তবে রুবি অনুমান করে আপনি StandardError বোঝাচ্ছেন।
begin
do_something()
rescue => e
# এটি StandardError রescu করার সমান
end
নির্দিষ্ট এরর রescu (সেরা অ্যাপ্রোচ)
এখন আপনি জানেন কীভাবে সকল এরর রescu করতে হয়, আপনাকে জানা উচিত যে এটি সাধারণত একটি খারাপ আইডিয়া, একটি কোড স্মেল, harmful 고려 করা হয়, ইত্যাদি।
সেগুলো সাধারণত ইঙ্গিত দেয় যে আপনি খুব লেজি ছিলেন নির্দিষ্ট এক্সেপশনগুলো বের করতে যেগুলো রescu করতে হবে। এবং সেগুলো почти সবসময় ফিরে আসে আপনাকে হান্ট করতে।
তাই সময় নিন এবং সঠিকভাবে করুন। নির্দিষ্ট এক্সেপশন রescu করুন।
begin
do_something()
rescue Errno::ETIMEDOUT => e
// এটি শুধু Errno::ETIMEDOUT এক্সেপশন রescu করবে
end
আপনি একই rescue ব্লকে একাধিক ধরনের এক্সেপশনও রescu করতে পারেন, তাই কোনো এক্সকিউজ নেই। :)
begin
do_something()
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
end