পাঠ ৪৩ · ৫৮-এর মধ্যে · মডিউল ৯
Home / Courses / Concepts of Programming Languages & Compiler Design / এক্সেপশন হ্যান্ডলিং

এক্সেপশন হ্যান্ডলিং মেকানিজম

Exception handling mechanisms
৮ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • এক্সেপশন হ্যান্ডলিং কেন এরর-কোড-চেকিংয়ের একটি স্ট্রাকচার্ড বিকল্প
  • try/raise/catch-এর মূল টার্মিনোলজি ও একসাথে কীভাবে কাজ করে
  • স্ট্যাক আনওয়াইন্ডিং — কল স্ট্যাক ধরে ফ্রেম-বাই-ফ্রেম হ্যান্ডলার অনুসন্ধানের প্রকৃত মেকানিজম
  • Python দিয়ে স্ট্যাক-আনওয়াইন্ডিং সিমুলেশন — হ্যান্ডলার-পাওয়া ও unhandled — দুটো দৃশ্যেই

১ · কেন এক্সেপশন হ্যান্ডলিং — এরর-কোড-চেকিংয়ের বিকল্প

এক্সেপশন হ্যান্ডলিংException Handlingএকটি স্ট্রাকচার্ড কন্ট্রোল-ফ্লো মেকানিজম, যা এক্সিকিউশনের সময় অস্বাভাবিক/এরর কন্ডিশন শনাক্ত ও সামাল দেয়, ম্যানুয়াল এরর-কোড চেকিং ছাড়াই। হলো একটি স্ট্রাকচার্ড কন্ট্রোল-ফ্লো মেকানিজম, যা রানটাইমে অস্বাভাবিক বা এরর কন্ডিশন শনাক্ত করে সামাল দেয়। এর আগেকার একটি বিকল্প পদ্ধতি ছিল এরর-কোড চেকিং — প্রতিটি ফাংশন একটি স্পেশাল রিটার্ন মান (যেমন -1 বা NULL) দিয়ে ব্যর্থতা সিগন্যাল করত, আর প্রতিটি কলার ম্যানুয়ালি সেই মান চেক করে এগিয়ে দিত (propagate) — বাস্তবে এই পদ্ধতিতে একটি চেক ভুলে বাদ পড়ে গেলে এরর নীরবে হারিয়ে যেত। এক্সেপশন হ্যান্ডলিং ঠিক এই সমস্যাটাই সমাধান করতে ডিজাইন করা হয়েছিল — এরর কন্ডিশন এখন স্বয়ংক্রিয়ভাবে কল চেইন ধরে উপরে উঠে যায়, যতক্ষণ না কেউ এটা এক্সপ্লিসিটভাবে ধরে (catch) সামলায়।

২ · মূল মেকানিজম — try, raise, catch

try ব্লক
যে কোড ব্লকের ভেতরে একটি এক্সেপশন রেইজ হতে পারে, সেটি একটি try ব্লকে রাখা হয়।
raise/throw
একটি অস্বাভাবিক কন্ডিশন ঘটেছে সিগন্যাল করে — সাধারণ সিকোয়েনশিয়াল ফ্লো থেকে সাথে সাথে কন্ট্রোল সরিয়ে নেয়।
catch/except
একটি নির্দিষ্ট try-এর সাথে যুক্ত থাকে, ম্যাচিং রেইজড এক্সেপশন ধরে সামলায় — প্রোগ্রাম ক্র্যাশ করার বদলে।

৩ · স্ট্যাক আনওয়াইন্ডিং — আসল কন্ট্রোল-ফ্লো মেকানিজম

একটি গভীরভাবে নেস্টেড ফাংশন-কল চেইনের ভেতরে এক্সেপশন রেইজ হলে, কন্ট্রোল স্বাভাবিকভাবে রিটার্ন করে না — বরং স্ট্যাক আনওয়াইন্ডিংStack Unwindingএক্সেপশন রেইজ হলে কল স্ট্যাক থেকে অ্যাক্টিভেশন রেকর্ড এক-এক করে পপ করে, প্রতিটি এনক্লোজিং কলারের ফ্রেমে ম্যাচিং হ্যান্ডলার খোঁজার প্রক্রিয়া। নামের একটি প্রক্রিয়া ঘটে — কল স্ট্যাক থেকে অ্যাক্টিভেশন রেকর্ড এক ফ্রেম করে "আনওয়াইন্ড" (পপ) হতে থাকে, এবং প্রতিটি এনক্লোজিং কলারের ফ্রেমে ম্যাচিং catch ব্লক আছে কি না চেক করা হয় — যতক্ষণ না হয় একটি হ্যান্ডলার পাওয়া যায় (এক্সিকিউশন সেখানে রিজিউম হয়), অথবা স্ট্যাক সম্পূর্ণ শেষ হয়ে যায় (প্রোগ্রাম একটি unhandled-exception এরর দিয়ে টার্মিনেট হয়)। এই মেকানিজম বোঝার জন্য কল স্ট্যাক/অ্যাক্টিভেশন রেকর্ড কী তা জানা জরুরি — এর বিস্তারিত M11/L48-এ আসবে, কিন্তু এখনই মূল ধারণাটা স্পষ্ট: প্রতিটি ফাংশন কল একটি ফ্রেম, আর এক্সেপশন সেই ফ্রেমগুলো ধরে বাইরের দিকে হ্যান্ডলার খুঁজতে থাকে।

গভীর ফ্রেমে exception রেইজড বর্তমান ফ্রেমে ম্যাচিং handler আছে? না ফ্রেম পপ (আনওয়াইন্ড) — পরবর্তী বাইরের ফ্রেমে যাও পুনরায় চেক হ্যাঁ / স্ট্যাক শেষ handler-এ resume, অথবা unhandled টার্মিনেট
প্রতিটি ফ্রেমে ম্যাচিং হ্যান্ডলার আছে কি না চেক হয়; না থাকলে ফ্রেম পপ করে বাইরের ফ্রেমে চেষ্টা চলতে থাকে — হ্যান্ডলার পাওয়া বা স্ট্যাক শেষ হওয়া পর্যন্ত।

৪ · কোড: স্ট্যাক আনওয়াইন্ডিং সিমুলেশন — দুটো দৃশ্য

নিচের কোড সেলে একটি এক্সপ্লিসিট toy কল স্ট্যাক (ফ্রেমের নামের একটি তালিকা, গভীরতম ফ্রেম প্রথমে) নিয়ে simulate_stack_unwinding ফাংশনটি ঠিক উপরের ফ্লো-ডায়াগ্রামের প্রক্রিয়াটি বাস্তবায়ন করে — প্রতিটি ফ্রেমে ম্যাচিং হ্যান্ডলার খোঁজে, বাইরের দিকে এগোতে থাকে। দুটো পৃথক দৃশ্য চালানো হয়েছে — একটিতে মাঝপথের একটি ফ্রেমে হ্যান্ডলার পাওয়া যায় (আনওয়াইন্ডিং সেখানে থেমে যায়), আরেকটিতে কোনো ফ্রেমেই হ্যান্ডলার নেই (আনওয়াইন্ডিং একেবারে নিচ পর্যন্ত পৌঁছে unhandled রিপোর্ট করে)।

Python
def simulate_stack_unwinding(call_chain, exception_type, handlers_by_frame):
    """
    call_chain: exception রেইজ হওয়ার সময়ের সবচেয়ে গভীর ফ্রেম থেকে শুরু করে
                বাইরের দিকে ফ্রেমের নামের তালিকা (index 0 = সবচেয়ে গভীর)
    handlers_by_frame: {frame_name: সেই ফ্রেমে হ্যান্ডল-করা exception টাইপের সেট}
    """
    print(f"'{exception_type}' এক্সেপশন রেইজড -- '{call_chain[0]}' ফ্রেমে")
    for depth, frame in enumerate(call_chain):
        handled_types = handlers_by_frame.get(frame, set())
        if exception_type in handled_types:
            print(f"  [{depth}] ফ্রেম '{frame}' -- ম্যাচিং handler পাওয়া গেছে -> এক্সিকিউশন এখানে রিজিউম হবে")
            return frame
        else:
            print(f"  [{depth}] ফ্রেম '{frame}' -- কোনো ম্যাচিং handler নেই -> স্ট্যাক আনওয়াইন্ড করে বাইরে যাওয়া হচ্ছে")
    print("  স্ট্যাক শেষ -- কোনো handler পাওয়া যায়নি -> unhandled exception, প্রোগ্রাম টার্মিনেট")
    return None

call_chain = ["parse_token", "parse_expr", "parse_statement", "compile_file", "main"]

print("=== দৃশ্য ১: মাঝপথে একটি ফ্রেমে handler পাওয়া যায় ===")
handlers_1 = {
    "parse_statement": {"SyntaxError"},
    "main": {"SyntaxError", "IOError"},
}
result_1 = simulate_stack_unwinding(call_chain, "SyntaxError", handlers_1)
print(f"ফলাফল: '{result_1}' ফ্রেমে হ্যান্ডল হয়েছে\n")

print("=== দৃশ্য ২: কোথাও কোনো handler পাওয়া যায় না ===")
handlers_2 = {
    "main": {"IOError"},   # SyntaxError-এর জন্য কোনো handler নেই
}
result_2 = simulate_stack_unwinding(call_chain, "SyntaxError", handlers_2)
print(f"ফলাফল: {result_2} -- unhandled exception")

    
দৃশ্য ১-এ আনওয়াইন্ডিং শুরু হয় parse_token থেকে, দুটো ফ্রেম (parse_token, parse_expr) পার হয়ে parse_statement-এ থেমে যায় — কারণ সেখানেই প্রথম ম্যাচিং হ্যান্ডলার পাওয়া যায়, compile_file ও main পর্যন্ত যাওয়ার দরকারই হয় না। দৃশ্য ২-এ একই এক্সেপশন, একই কল চেইন — কিন্তু কোথাও SyntaxError-এর হ্যান্ডলার না থাকায় আনওয়াইন্ডিং সবগুলো ফ্রেম (৫টি) পার হয়ে একেবারে শেষে unhandled রিপোর্ট করে। এই দুটো দৃশ্যের পার্থক্যই দেখায় কেন handler "কোথায়" বসানো হয়েছে সেটা গুরুত্বপূর্ণ — খুব গভীরে হ্যান্ডলার থাকলে দ্রুত ধরা পড়ে, একেবারেই না থাকলে পুরো প্রোগ্রাম ক্র্যাশ করে।
মূল কথা · Key takeaway

এক্সেপশন হ্যান্ডলিং শুধু try/catch সিনট্যাক্স নয় — এর নিচে আছে একটি নির্দিষ্ট, প্রেডিক্টেবল কন্ট্রোল-ফ্লো মেকানিজম: কল স্ট্যাক ধরে বাইরের দিকে হ্যান্ডলার খোঁজা। এই মেকানিজম বোঝা মানে "কেন আমার এরর এখানে ধরা পড়ল না" — এই ধরনের বাস্তব ডিবাগিং প্রশ্নের উত্তর দিতে পারা।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ এক্সেপশন হ্যান্ডলিং ঠিক কোন সমস্যাটা সমাধান করে, যা এরর-কোড চেকিং সমাধান করতে পারেনি?

এরর-কোড চেকিং পদ্ধতিতে প্রতিটি কলারকে ম্যানুয়ালি প্রতিটি ফাংশন কলের রিটার্ন মান চেক করতে হয় — এই চেক একটি জায়গায়ও ভুলে বাদ পড়লে, এরর নীরবে হারিয়ে যায় (silent failure), যা পরে ভয়ংকর, খুঁজে-পাওয়া-কঠিন বাগ তৈরি করে। এক্সেপশন হ্যান্ডলিং-এ এরর প্রচার (propagation) স্বয়ংক্রিয় — একটি এক্সেপশন রেইজ হলে সেটি নিজে থেকেই কল স্ট্যাক ধরে উপরে উঠতে থাকে (স্ট্যাক আনওয়াইন্ডিং), যতক্ষণ না কেউ এক্সপ্লিসিটভাবে ধরে — প্রোগ্রামারের প্রতিটি স্তরে ম্যানুয়াল চেক লেখার দরকার হয় না, তাই ভুলে-বাদ-পড়ে-যাওয়া চেকের সমস্যা থাকে না।

প্র ০২ উপরের দৃশ্য ২-তে, "handlers_2"-তে "main" ফ্রেমে একটি IOError হ্যান্ডলার থাকা সত্ত্বেও কেন সেটি SyntaxError-কে ধরতে পারল না?

কারণ একটি হ্যান্ডলার শুধুমাত্র সেই নির্দিষ্ট এক্সেপশন টাইপের (বা তার সাথে সম্পর্কিত টাইপের) জন্যই কাজ করে, যেটার জন্য এটি ডিক্লেয়ার করা হয়েছে — handlers_2["main"] = {"IOError"} মানে main ফ্রেম শুধু IOError-ই ধরবে, অন্য কোনো টাইপ নয়। যেহেতু এখানে "SyntaxError" in {"IOError"} মিথ্যা, তাই main-এও কোনো ম্যাচ পাওয়া যায়নি — এবং যেহেতু main-ই কল চেইনের শেষ (সবচেয়ে বাইরের) ফ্রেম, তাই স্ট্যাক সম্পূর্ণ আনওয়াইন্ড হয়ে unhandled হিসেবে রিপোর্ট হয়েছে। এটা একটা গুরুত্বপূর্ণ, বাস্তব পয়েন্ট — শুধু কোথাও একটা handler থাকলেই চলে না, সঠিক এক্সেপশন টাইপের জন্য handler থাকতে হয়।

প্র ০৩ স্ট্যাক আনওয়াইন্ডিং প্রক্রিয়াটি M11/L48-এর কল স্ট্যাক/অ্যাক্টিভেশন রেকর্ড ধারণার সাথে ঠিক কীভাবে সম্পর্কিত?

প্রতিটি ফাংশন কল, চলার সময়, কল স্ট্যাকে একটি অ্যাক্টিভেশন রেকর্ড (ফ্রেম) তৈরি করে — যেখানে সেই কলের লোকাল ভ্যারিয়েবল, প্যারামিটার, ও রিটার্ন-অ্যাড্রেসের মতো তথ্য থাকে (M11/L48-এ পূর্ণাঙ্গ বিস্তারিত আসবে)। এক্সেপশন রেইজ হলে, "স্ট্যাক আনওয়াইন্ডিং" মানে আক্ষরিক অর্থেই এই অ্যাক্টিভেশন রেকর্ডগুলো এক-এক করে কল স্ট্যাক থেকে পপ করা — প্রতিটি পপ হওয়া ফ্রেমই আসলে একটি ফাংশন কল যেটা এখন "বাতিল" হয়ে যাচ্ছে, স্বাভাবিক রিটার্নের মতো নয়। তাই এক্সেপশন হ্যান্ডলিং বোঝার জন্য কল স্ট্যাক কীভাবে কাজ করে তা বোঝা সরাসরি প্রাসঙ্গিক — এই পাঠের toy সিমুলেশনে call_chain লিস্টটাই সেই কল স্ট্যাকের একটি সরলীকৃত প্রতিরূপ।

অনুশীলন

  1. চিন্তা করুন: উপরের কোড সেলে একটি তৃতীয় দৃশ্য চিন্তা করুন যেখানে call_chain-এর সবচেয়ে গভীর ফ্রেমেই (parse_token) একটি ম্যাচিং হ্যান্ডলার আছে। ফলাফল কী হবে, এবং এটি কেন "সবচেয়ে দ্রুত" রেজোলিউশনের উদাহরণ?

    যদি handlers_by_frame = {"parse_token": {"SyntaxError"}, ...} হয়, তাহলে simulate_stack_unwinding লুপের প্রথম ইটারেশনেই (depth=0) ম্যাচ পেয়ে যাবে — কোনো ফ্রেম আনওয়াইন্ড না করেই সাথে সাথে হ্যান্ডলড হবে। এটা "সবচেয়ে দ্রুত" কারণ এক্সেপশন যেখানে রেইজ হয়েছে, ঠিক সেই ফ্রেমেই যদি হ্যান্ডলার থাকে (যেমন কোডের ঠিক সেই try ব্লকের চারপাশেই except), তাহলে কোনো ফ্রেম পপ করার দরকারই পড়ে না — সবচেয়ে কম "আনওয়াইন্ড দূরত্ব"।

  2. পরীক্ষা করুন: কোড সেলে দৃশ্য ১-এ exception_type-এর মান "SyntaxError"-এর বদলে "IOError" করে Run চেপে দেখুন ফলাফল কোথায় বদলায়।

    handlers_1-এ "parse_statement"-এর হ্যান্ডলার সেট শুধু {"SyntaxError"}, "IOError" সেখানে নেই — তাই সেই ফ্রেমে আর ম্যাচ হবে না, আনওয়াইন্ডিং সেখান দিয়ে পার হয়ে যাবে। কিন্তু "main"-এর হ্যান্ডলার সেট {"SyntaxError", "IOError"} — দুটোই আছে — তাই ফলাফল বদলে গিয়ে "main" ফ্রেমে হ্যান্ডলড হবে, আগের মতো "parse_statement"-এ নয়। এটা দেখায় একই কল চেইন, একই হ্যান্ডলার-ম্যাপ হলেও, শুধু এক্সেপশনের টাইপ বদলালেই আনওয়াইন্ডিং কোথায় থামবে তা সম্পূর্ণ বদলে যেতে পারে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ পরবর্তী পাঠ — কোরুটিন ও কন্টিনিউয়েশন — কন্ট্রোল ফ্লোর আরও একটি উন্নত রূপ কভার করবে।
  • Data Structures & Algorithms কোর্স সঙ্গী কোর্স কল স্ট্যাক নিজেই একটি স্ট্যাক ডেটা স্ট্রাকচার — এই পাঠের আনওয়াইন্ডিং প্রক্রিয়া আসলে সেই স্ট্যাকের উপরেই একটি পপ-অপারেশন সিরিজ, সেই কোর্সে স্ট্যাক ডেটা স্ট্রাকচারের ভিত্তি শেখানো হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture ও Programming Languages & Compiler Design — সব এক জায়গায়।
আগের পাঠ
কন্ট্রোল স্ট্রাকচার — সিলেকশন ও ইটারেশন