কম্পিউটার টিউটোরিয়াল

TracePoint দিয়ে রুবি ডিবাগিংে বিপ্লব: দ্রুত বাগ ফিক্স ও গভীর অন্তর্দৃষ্টি

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

এই পোস্টে, আমি একটি সিম্পল উদাহরণ ব্যবহার করে ২টি আকর্ষণীয় তথ্য দেখাবো যা আমি ডিবাগিং সম্পর্কে জানলাম:

  1. অधিকাংশ সময়, বাগটি খুঁজে বের করা কঠিন নয়, কিন্তু আপনার প্রোগ্রাম কীভাবে ডিটেইলসে কাজ করে সেটি বুঝতে কঠিন। একবার আপনার এই গভীর বোঝাপড়া হয়ে গেলে, আপনি সাধারণত বাগটি তাত্ক্ষণিকভাবে স্পট করতে পারেন।
  2. মেথড কল লেভেলে আপনার প্রোগ্রাম নিরীক্ষণ করা সময়সাপেক্ষ, এবং এটি আমাদের ডিবাগিং প্রসেসের মেজর বটলनेक।

তারপর, আমি দেখাবো TracePoint কীভাবে ডিবাগিং-এ আমাদের আপরোচ পরিবর্তন করতে পারে প্রোগ্রামকে "আমাদের বলতে" দিয়ে যে সেটি কী করছে।

ডিবাগিং আপনার প্রোগ্রাম এবং এর ডিজাইন বুঝতে

ধরি আমাদের একটি রুবি প্রোগ্রাম আছে plus_1 নামের এবং সেটি সঠিকভাবে কাজ করছে না। আমরা কীভাবে এটা ডিবাগ করব?

# plus_1.rb def plus_1(n) n + 2 end input = ARGV[0].to_i puts(plus_1(input))
$ ruby plus_1.rb 1 3

আদর্শভাবে, আমাদের ৩ ধাপে বাগটি সমাধান করতে পারতে হবে:

  1. ডিজাইন থেকে এক্সপেক্টেশন শিখুন
  2. কারেন্ট ইমপ্লিমেন্টেশন বুঝুন
  3. বাগটি ট্রেস করুন

ডিজাইন থেকে এক্সপেক্টেশন শিখুন

এখানে এক্সপেক্টেড বিহেভিয়ার কী? plus_1 তার আর্গুমেন্টে 1 যোগ করা উচিত, যেটি আমাদের কমান্ড লাইন থেকে ইনপুট। কিন্তু আমরা কীভাবে "জানি" এটা?

রিয়েল-ওয়ার্ল্ড কেসে, আমরা এক্সপেক্টেশন বুঝতে পারি টেস্ট কেস পড়ে, ডকুমেন্ট, মকআপ, অন্য লোকদের ফিডব্যাক চেয়ে, ইত্যাদি। আমাদের বোঝাপড়া নির্ভর করে প্রোগ্রামটি কীভাবে "ডিজাইন" করা হয়েছে।

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

তবে, এই ধাপে অনেক ফ্যাক্টর থাকতে পারে, যেমন টিম কোঅর্ডিনেশন, ডেভেলপমেন্ট ওয়ার্কফ্লো, ইত্যাদি। TracePoint আপনাকে সেগুলোতে সাহায্য করতে পারবে না, তাই আজ সেগুলো নিয়ে চিন্তা করব না।

কারেন্ট ইমপ্লিমেন্টেশন বুঝুন

একবার আমরা প্রোগ্রামের এক্সপেক্টেড বিহেভিয়ার বুঝতে পারলে, আমাদের শিখতে হবে এটি বর্তমানে কীভাবে ফাংশন করছে।

বেশির ভাগ কেসে, প্রোগ্রাম কীভাবে কাজ করে সেটি পুরোপুরি বুঝতে আমাদের নিচের ইনফরমেশন দরকার:

  • প্রোগ্রাম এক্সিকিউশন সময় কল হওয়া মেথডগুলো
  • মেথড কল এবং রিটার্নের অর্ডার
  • প্রতিটি মেথড কলে পাস করা আর্গুমেন্ট
  • প্রতিটি মেথড কল থেকে রিটার্ন হওয়া ভ্যালু
  • প্রতিটি মেথড কল সময় যেকোনো সাইড ইফেক্ট যেন ডেটা মিউটেশন বা ডেটাবেস রিকোয়েস্ট

আমাদের উদাহরণটিকে উপরের ইনফরমেশন দিয়ে বর্ণনা করি:

# plus_1.rb def plus_1(n) n + 2 end input = ARGV[0].to_i puts(plus_1(input))
$ ruby plus_1.rb 1 3
  1. plus_1 নামে একটি মেথড ডিফাইন করা
  2. ARGV থেকে ইনপুট ("1") রিট্রিভ করা
  3. "1" এর ওপর to_i কল করা, যা 1 রিটার্ন করে
  4. 1 অ্যাসাইন করা লোকাল ভেরিয়েবল input-এ
  5. plus_1 মেথড কল করা input(1) আর্গুমেন্ট হিসেবে। প্যারামিটার n এখন 1 ভ্যালু ধারণ করছে
  6. 1 এর ওপর + মেথড কল করা আর্গুমেন্ট 2 সহ, এবং রেজাল্ট 3 রিটার্ন করা
  7. স্টেপ ৫-এর জন্য 3 রিটার্ন করা
  8. puts কল করা
  9. 3 এর ওপর to_s কল করা, যা "3" রিটার্ন করে
  10. "3" পাস করা স্টেপ ৮-এর puts কলে, যা একটি সাইড ইফেক্ট ট্রিগার করে স্ট্রিং প্রিন্ট করার Stdout-এ। তারপর এটি nil রিটার্ন করে।

বর্ণনাটি ১০০% একুরেট নয়, কিন্তু একটি সিম্পল ব্যাখ্যা জন্য পর্যাপ্ত।

বাগ সমাধান করা

এখন যেহেতু আমরা শিখেছি আমাদের প্রোগ্রাম কীভাবে কাজ করা উচিত এবং কীভাবে আসলে কাজ করছে, আমরা বাগ খুঁজে বের করতে শুরু করতে পারি। আমাদের উপলব্ধ ইনফরমেশন দিয়ে, আমরা বাগ খুঁজে বের করতে পারি মেথড কলগুলো ফলো করে আপওয়ার্ড (স্টেপ ১০ থেকে শুরু) বা ডাউনওয়ার্ড (স্টেপ ১ থেকে শুরু)। এই কেসে, আমরা সেটি করতে পারি ট্রেস ব্যাক করে সেই মেথডে যেটি ৩ রিটার্ন করেছিল প্রথমে⁠—যেটি হল 1 + 2 step 6-এ।

এটি বাস্তবতার থেকে দূরে!

অবশ্যই, আমরা সবাই জানি রিয়েল ডিবাগিং উদাহরণের মতো সিম্পল নয়। রিয়েল-লাইফ প্রোগ্রাম এবং আমাদের উদাহরণের মধ্যে 크িটিক্যাল পার্থক্য হল সাইজ। আমরা ১০ স্টেপে একটি ৫-লাইন প্রোগ্রাম ব্যাখ্যা করেছি। একটি ছোট রেলস অ্যাপের জন্য কত স্টেপ লাগবে? এটি বেসিক্যালি імপসিবল একটি রিয়েল প্রোগ্রামকে এত ডিটেইল ব্রেক ডাউন করা যেমন আমরা উদাহরণের জন্য করেছি। আপনার প্রোগ্রামের ডিটেইল বোঝাপড়া ছাড়া, আপনি বাগ ট্র্যাক ডাউন করতে পারবেন না একটি স্পষ্ট পথে, তাই আপনাকে অনুমান বা অনুমান করতে হবে।

ইনফরমেশন এক্সপেনসিভ

আপনি সম্ভবত ইতিমধ্যে লক্ষ্য করেছেন, ডিবাগিং-এ কী ফ্যাক্টর সেটি হল আপনার কতটুকু ইনফরমেশন আছে। কিন্তু 그만큼 ইনফরমেশন রিট্রিভ করতে কী লাগে? চলুন দেখি:

# plus_1_with_tracing.rb def plus_1(n) puts("n = #{n}") n + 2 end raw_input = ARGV[0] puts("raw_input: #{raw_input}") input = raw_input.to_i puts("input: #{input}") result = plus_1(input) puts("result of plus_1 #{result}") puts(result)
$ ruby plus_1_with_tracing.rb 1 raw_input: 1 input: 1 n = 1 result of plus_1: 3 3 

আপনি দেখতে পাচ্ছেন, আমরা এখানে শুধু ২ টাইপের ইনফরমেশন পাচ্ছি: কিছু ভেরিয়েবলের ভ্যালু এবং আমাদের puts এর ইভ্যালুয়েশন অর্ডার (যেটি ইমপ্লাই করে প্রোগ্রামের এক্সিকিউশন অর্ডার)।

এই ইনফরমেশন কত কষ্ট?

 def plus_1(n) + puts("n = #{n}") n + 2 end -input = ARGV[0].to_i -puts(plus_1(input)) +raw_input = ARGV[0] +puts("raw_input: #{raw_input}") +input = raw_input.to_i +puts("input: #{input}") + +result = plus_1(input) +puts("result of plus_1: #{result}") + +puts(result)

ন केवल আমাদের ৪টি puts কোডে যোগ করতে হয়েছে, বরং আলাদা ভ্যালু প্রিন্ট করার জন্য, আমাদের লজিকও স্প্লিট করতে হয়েছে যাতে কিছু ভ্যালুর ইন্টারমিডিয়েট স্টেট অ্যাক্সেস করা যায়। এই কেসে, আমরা ৪টি অ্যাডিশনাল আউটপুট পেয়েছি ইন্টারন্যাল স্টেটের জন্য ৮ লাইন পরিবর্তনের সাথে। গড়ে ১ লাইন আউটপুটের জন্য ২ লাইন পরিবর্তন! এবং যেহেতু পরিবর্তনের সংখ্যা প্রোগ্রামের সাইজের সাথে লিনিয়ারলি বাড়ে, আমরা এটিকে O(n) অপারেশনের সাথে তুলনা করতে পারি।

ডিবাগিং কেন এক্সপেনসিভ?

আমাদের প্রোগ্রাম অনেক গোল লক্ষ্য করতে পারে লেখার সময়: মেইনটেইনেবিলিটি, পারফরম্যান্স, সিম্পলিসিটি, ইত্যাদি। কিন্তু সাধারণত "ট্রেসেবিলিটি" জন্য নয়, মানে, ইনস্পেকশনের জন্য ভ্যালু পাওয়া, যা সাধারণত কোড মডিফিকেশন দরকার করে, যেমন চেইনড মেথড কল স্প্লিট করা।

  • আপনি যত বেশি ইনফরমেশন পাবেন, তত বেশি অ্যাডিশন/চেঞ্জ আপনাকে কোডে করতে হবে।

তবে, একবার ইনফরমেশনের পরিমাণ একটি নির্দিষ্ট পয়েন্টে পৌঁছালে, আপনি সেটি দক্ষতার সাথে প্রসেস করতে পারবেন না। তাই আমাদের হয় ইনফরমেশন ফিল্টার আউট করতে হবে অথবা লেবেল করতে হবে বুঝতে সাহায্য করার জন্য।

  • ইনফরমেশন যত প্রিসাইজ, তত বেশি অ্যাডিশন/চেঞ্জ আপনাকে কোডে করতে হবে।

শেষে, যেহেতু কাজটি কোডবেস স্পর্শ করা জড়িত⁠—যেটি খুব ভিন্ন হতে পারে বাগগুলোর মধ্যে (যেমন কন্ট্রোলার vs মডেল লজিক)⁠—এটিকে অটোমেট করা কঠিন। আপনার কোডবেস যদি ট্রেসিং-ফ্রেন্ডলি হয় (যেমন এটি "Law of Demeter" স্ট্রিক্টলি ফলো করে), অধিকাংশ সময়, আপনাকে ভিন্ন ভেরিয়েবল/মেথড নাম ম্যানুয়ালি টাইপ করতে হবে।

(আসলে, রুবিতে কিছু ট্রিক আছে এটা এড়াতে⁠—যেমন __method__। কিন্তু এখানে জটিল না করি।)

TracePoint: রক্ষাকর্তা

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

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

TracePoint.trace(:call, :return, :c_call, :c_return) do |tp| event = tp.event.to_s.sub(/(.+(call|return))/, '\2').rjust(6, " ") message = "#{event} of #{tp.defined_class}##{tp.callee_id} on #{tp.self.inspect}" # if you call `return` on any non-return events, it'll raise error message += " => #{tp.return_value.inspect}" if tp.event == :return || tp.event == :c_return puts(message) end def plus_1(n) n + 2 end input = ARGV[0].to_i puts(plus_1(input))

আপনি কোডটি রান করলে, আপনি দেখবেন:

return of #<Class:TracePoint>#trace on TracePoint => #<TracePoint:c_return `trace'@plus_1_with_trace_point.rb:1> call of Module#method_added on Object return of Module#method_added on Object => nil call of String#to_i on "1" return of String#to_i on "1" => 1 call of Object#plus_1 on main return of Object#plus_1 on main => 3 call of Kernel#puts on main call of IO#puts on #<IO:<STDOUT>> call of Integer#to_s on 3 return of Integer#to_s on 3 => "3" call of IO#write on #<IO:<STDOUT>> 3 return of IO#write on #<IO:<STDOUT>> => 2 return of IO#puts on #<IO:<STDOUT>> => nil return of Kernel#puts on main => nil 

আমাদের কোড এখন অনেক বেশি পঠনযোগ্য। চমৎকার না? এটি প্রোগ্রাম এক্সিকিউশনের অধিকাংশ প্রিন্ট করে অনেক ডিটেইলসহ! আমরা এটিকে আমার আগের এক্সিকিউশন ব্রেকডাউন সাথে ম্যাপ করি:

  1. plus_1 নামে একটি মেথড ডিফাইন করা
  2. ARGV থেকে ইনপুট ("1") রিট্রিভ করা
  3. to_i কল করা "1" এর ওপর, যা 1 রিটার্ন করে
  4. 1 অ্যাসাইন করা লোকাল ভেরিয়েবল input-এ
  5. plus_1 মেথড কল করা input(1) আর্গুমেন্ট হিসেবে। প্যারামিটার n এখন 1 ভ্যালু ধারণ করছে
  6. + মেথড কল করা 1 এর ওপর আর্গুমেন্ট 2 সহ, এবং রেজাল্ট 3 রিটার্ন করা
  7. স্টেপ ৫-এর জন্য 3 রিটার্ন করা
  8. puts কল করা
  9. to_s কল করা 3 এর ওপর, যা "3" রিটার্ন করে
  10. "3" পাস করা স্টেপ ৮-এর puts কলে, যা একটি সাইড ইফেক্ট ট্রিগার করে স্ট্রিং প্রিন্ট করার Stdout-এ। এবং তারপর এটি nil রিটার্ন করে।
# ইগনোর করুন, এটি TracePoint নিজে নিজে ট্রেস করছে ;D return of #<Class:TracePoint>#trace on TracePoint => #<TracePoint:c_return `trace'@plus_1_with_trace_point.rb:1> call of Module#method_added on Object # 1. `plus_1` নামে একটি মেথড ডিফাইন করা। return of Module#method_added on Object => nil call of String#to_i on "1" # 3-1. `to_i` কল করা `"1"`-এর ওপর return of String#to_i on "1" => 1 # 3-2. যা `1` রিটার্ন করে call of Object#plus_1 on main # 5. `plus_1` মেথড কল করা `input`(`1`) আর্গুমেন্ট হিসেবে। return of Object#plus_1 on main => 3 # 7. `3` রিটার্ন করা স্টেপ ৫-এর জন্য call of Kernel#puts on main # 8. `puts` কল করা call of IO#puts on #<IO:<STDOUT>> call of Integer#to_s on 3 # 9. `to_s` কল করা `3`-এর ওপর, যা `"3"` রিটার্ন করে return of Integer#to_s on 3 => "3" call of IO#write on #<IO:<STDOUT>> # 10-1. `"3"` পাস করা স্টেপ ৮-এর `puts` কলে # 10-2. যা একটি সাইড ইফেক্ট ট্রিগার করে স্ট্রিং প্রিন্ট করার Stdout-এ 3 # অরিজিনাল আউটপুট return of IO#write on #<IO:<STDOUT>> => 2 return of IO#puts on #<IO:<STDOUT>> => nil return of Kernel#puts on main => nil # 10-3. এবং তারপর এটি `nil` রিটার্ন করে। 

আমরা বলতেছি এটা আমার আগে বলা চেয়ে আরও ডিটেইলড! তবে, আপনি লক্ষ্য করতে পারেন স্টেপ ২, ৪ এবং ৬ আউটপুটে নেই। দুর্ভাগ্যক্রমে, সেগুলো TracePoint-দ্বারা ট্র্যাকযোগ্য নয় নিচের কারণগুলো জন্য:

    1. ARGV থেকে ইনপুট ("1") রিট্রিভ করা
    • ARGV এবং পরের [] করেন্ট মোমেন্টে কল/সি_কল হিসেবে বিবেচিত হয় না
    1. 1 অ্যাসাইন করা লোকাল ভেরিয়েবল input-এ
    • বর্তমানে, ভেরিয়েবল অ্যাসাইনমেন্টের জন্য কোনো ইভেন্ট নেই। আমরা (সোর্ট অফ) ট্র্যাক করতে পারি line ইভেন্ট + রেজেক্স দিয়ে, কিন্তু সেটি একুরেট হবে না
    1. + মেথড কল করা 1 এর ওপর আর্গুমেন্ট 2 সহ, এবং রেজাল্ট 3 রিটার্ন করা
    • নির্দিষ্ট মেথড কল যেমন বিল্ট-ইন + বা অ্যাট্রিবিউট অ্যাকসেসর মেথড ট্র্যাকযোগ্য নয় করেন্ট মোমেন্টে

O(n) থেকে O(log n)

আপনি পূর্বের উদাহরণ থেকে দেখতে পাচ্ছেন, TracePoint-এর সঠিক ব্যবহার দিয়ে, আমরা প্রায় প্রোগ্রামকে "আমাদের বলতে" পারি সেটি কী করছে। এখন, আমাদের প্রয়োজনীয় লাইনের সংখ্যার কারণে, TracePoint আমাদের প্রোগ্রামের সাইজের সাথে লিনিয়ারলি বাড়ে না। আমি বলব পুরো প্রসেসটি একটি O(log(n)) অপারেশন হয়ে যায়।

পরবর্তী ধাপ

এই আর্টিকেলে, আমি ডিবাগিং-এর মূল কঠিনতা ব্যাখ্যা করেছি। আশা করি আমি আপনাকে কনভিন্স করেছি TracePoint কীভাবে একটি গেম-চেঞ্জার হতে পারে। কিন্তু আপনি যদি এখন TracePoint ট্রাই করেন, এটি আপনাকে সাহায্য করার চেয়ে বেশি ফ্রুস্ট্রেট করবে।

TracePoint-এ서 আসা ইনফরমেশনের পরিমাণের সাথে, আপনি শীঘ্রই নয়েজ দ্বারা জলমগ্ন হবেন। নতুন চ্যালেঞ্জ হল নয়েজ ফিল্টার আউট করা, মূল্যবান ইনফরমেশন রেখে। উদাহরণস্বরূপ, অধিকাংশ কেসে, আমরা শুধু নির্দিষ্ট মডেল বা সার্ভিস অবজেক্টের জন্য চিন্তা করি। এই কেসগুলোতে, আমরা কল ফিল্টার করতে পারি রিসিভারের ক্লাস দিয়ে, এইভাবে:

TracePoint.trace(:call) do |tp| next unless tp.self.is_a?(Order) # tracing logic end

একটি জিনিস মনে রাখতে হবে যে TracePoint-এর জন্য আপনি যে ব্লক ডিফাইন করেন সেটি হাজার হাজার বার ইভ্যালুয়েট হতে পারে। এই স্কেলে, আপনি ফিল্টারিং লজিক কীভাবে ইমপ্লিমেন্ট করছেন সেটি আপনার অ্যাপের পারফরম্যান্সে বড় প্রভাব ফেলতে পারে। উদাহরণস্বরূপ, আমি এই রিকমেন্ড করি না:

TracePoint.trace(:call) do |tp| trace = caller[0] next unless trace.match?("app") # tracing logic end

এই ২টি সমস্যার জন্য, আমি আরেকটি আর্টিকেল প্রস্তুত করেছি যাতে আপনি কিছু ট্রিক এবং গটচা জানতে পারেন কিছু উপযোগী বয়লারপ্লেটসহ টিপিক্যাল রুবি/রেলস অ্যাপ্লিকেশনের জন্য।

এবং আপনি যদি এই কনসেপ্ট আকর্ষণীয় পান, আমি একটি জেম তৈরি করেছি tapping_device নামে যা সব ইমপ্লিমেন্টেশন হ্যাসল হাইড করে।

উপসংহার

ডিবাগার এবং ট্রেসিং উভয়ই ডিবাগিং-এর জন্য চমৎকার টুল, এবং আমরা সেগুলো অনেক বছর ধরে ব্যবহার করছি। কিন্তু जैसा আমি এই আর্টিকেলে প্রদর্শন করেছি, সেগুলো ব্যবহার করতে হয় অনেক ম্যানুয়াল অপারেশন ডিবাগিং প্রসেসে। তবে, TracePoint-এর সাহায্যে, আপনি उनमें से অনেক অটোমেট করতে পারেন এবং এইভাবে আপনার ডিবাগিং পারফরম্যান্স বूस্ট করতে পারেন। আশা করি আপনি এখন TracePoint-কে আপনার ডিবাগিং টুলবক্সে যোগ করবেন এবং একবার ট্রাই দেবেন।


  1. রুবির grep মেথড মাস্টারিং: কোড উদাহরণসহ একটি ব্যবহারিক গাইড

  2. রুবির Map মেথড মাস্টারিং: উদাহরণসহ একটি ব্যবহারিক গাইড

  3. রুবিতে N-Queens চ্যালেঞ্জ মাস্টারিং: একটি ধাপে ধাপে সমাধান

  4. রুবিতে Transpose মেথড ব্যবহার করে সহজে রোকে কলামে রূপান্তর করুন