কম্পিউটার

রুবিতে দায়ী বানরপ্যাচিং

2011 সালে যখন আমি প্রথম পেশাদারভাবে রুবি কোড লিখতে শুরু করি, তখন ভাষা সম্পর্কে যে জিনিসটি আমাকে সবচেয়ে বেশি প্রভাবিত করেছিল তা হল এর নমনীয়তা। মনে হচ্ছিল যেন রুবির সাথে সবকিছুই সম্ভব। C# এবং Java এর মত ভাষার অনমনীয়তার সাথে তুলনা করলে, রুবি প্রোগ্রামগুলি প্রায় মনে হয় যে তারা জীবিত .

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

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

মানকিপ্যাচ কি?

বানরপ্যাচ লিখুন .

সংক্ষেপে, বিদ্যমান কোডের সাথে "বানর" মাঙ্কিপ্যাচ করে। বিদ্যমান কোডটি প্রায়শই এমন কোড যা আপনার সরাসরি অ্যাক্সেস নেই, যেমন একটি রত্ন বা রুবি স্ট্যান্ডার্ড লাইব্রেরি থেকে কোড। প্যাচগুলি সাধারণত একটি বাগ ঠিক করতে, কর্মক্ষমতা উন্নত করতে, ইত্যাদির জন্য মূল কোডের আচরণ পরিবর্তন করার জন্য ডিজাইন করা হয়৷

সবচেয়ে অপ্রত্যাশিত মাঙ্কিপ্যাচগুলি রুবি ক্লাসগুলি পুনরায় চালু করে এবং পদ্ধতিগুলি যোগ বা ওভাররাইড করে আচরণ পরিবর্তন করে৷

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

এই সংক্ষিপ্ত উদাহরণটি ক্লাস পুনরায় খোলার ধারণাটি ব্যাখ্যা করে:

class Sounds
  def honk
    "Honk!"
  end
end
 
class Sounds
  def squeak
    "Squeak!"
  end
end
 
sounds = Sounds.new
sounds.honk    # => "Honk!"
sounds.squeak  # => "Squeak!"

লক্ষ্য করুন যে উভয়ই #honk এবং #squeak পদ্ধতিগুলি Sounds-এ উপলব্ধ পুনরায় খোলার জাদুর মাধ্যমে ক্লাস।

মূলত, মাঙ্কিপ্যাচিং হল থার্ড-পার্টি কোডে ক্লাস পুনরায় খোলার কাজ।

মানকিপ্যাচিং কি বিপজ্জনক?

যদি পূর্ববর্তী বাক্যটি আপনাকে ভয় দেখায় তবে এটি সম্ভবত একটি ভাল জিনিস। মাঙ্কিপ্যাচিং, বিশেষ করে যখন অসতর্কভাবে করা হয়, তখন সত্যিকারের বিশৃঙ্খলা সৃষ্টি করতে পারে।

এক মুহুর্তের জন্য বিবেচনা করুন যদি আমরা Array#<< পুনরায় সংজ্ঞায়িত করি তাহলে কি হবে :

class Array
  def <<(*args)
    # do nothing 😈
  end
end

কোডের এই চার লাইনের সাথে, পুরো প্রোগ্রামের প্রতিটি একক অ্যারে উদাহরণ এখন ভেঙে গেছে।

আরো কি, #<< এর আসল বাস্তবায়ন এটা গেছে. রুবি প্রক্রিয়া পুনরায় আরম্ভ করা ছাড়াও, এটি ফিরে পাওয়ার কোন উপায় নেই৷

যখন মাঙ্কিপ্যাচিং ভয়ঙ্করভাবে ভুল হয়ে যায়

2011 সালে, আমি একটি বিশিষ্ট সামাজিক নেটওয়ার্কিং কোম্পানিতে কাজ করেছি। সেই সময়ে, কোডবেসটি রুবি 1.8.7 এ চলমান একটি বিশাল রেল মনোলিথ ছিল। কয়েকশ প্রকৌশলী দৈনিক ভিত্তিতে কোডবেসে অবদান রেখেছিলেন, এবং বিকাশের গতি ছিল খুব দ্রুত।

এক পর্যায়ে, আমার দল String#% মাঙ্কিপ্যাচ করার সিদ্ধান্ত নিয়েছে আন্তর্জাতিকীকরণের উদ্দেশ্যে বহুবচন লেখা সহজ করতে। আমাদের প্যাচ কী করতে পারে তার একটি উদাহরণ এখানে দেওয়া হল:

replacements = {
  horse_count: 3,
  horses: {
    one: "is 1 horse",
    other: "are %{horse_count} horses"
  }
}
 
# "there are 3 horses in the barn"
"there %{horse_count:horses} in the barn" % replacements

আমরা প্যাচটি লিখেছি এবং অবশেষে এটিকে উৎপাদনে স্থাপন করেছি... শুধুমাত্র এটি কাজ করেনি তা খুঁজে বের করার জন্য। আমাদের ব্যবহারকারীরা আক্ষরিক %{...} এর সাথে স্ট্রিং দেখছিলেন সুন্দরভাবে বহুবচনকৃত পাঠ্যের পরিবর্তে অক্ষর। এটা কোন মানে ছিল না. প্যাচটি আমার ল্যাপটপে উন্নয়ন পরিবেশে পুরোপুরি ভাল কাজ করেছে। কেন এটি উৎপাদনে কাজ করছিল না?

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

বেশ কয়েকদিন মাথা ঘামাবার পরে, একজন সহকর্মী একটি রেল ইনিশিয়ালাইজার ট্র্যাক করতে সক্ষম হন যা আরেকটি যোগ করেছে String#% বাস্তবায়ন যা আমরা কেউ আগে দেখিনি। জিনিসগুলিকে আরও জটিল করার জন্য, এই পূর্বের বাস্তবায়নে একটি বাগও ছিল, তাই আমরা প্রোডাকশন কনসোলে যে ফলাফলগুলি দেখেছি তা রুবির অফিসিয়াল ডকুমেন্টেশন থেকে আলাদা।

যদিও এটি গল্পের শেষ নয়। আগের মাঙ্কিপ্যাচ ট্র্যাক করার সময়, আমরা আরও তিনজনের কম খুঁজে পাইনি, সকলেই একই পদ্ধতিতে প্যাচ করছে। আমরা ভয়ে একে অপরের দিকে তাকালাম। এটা কিভাবে কাজ করে?

আমরা অবশেষে রেলের উত্সাহী লোডিং পর্যন্ত অসঙ্গতিপূর্ণ আচরণকে চাক করেছি। ডেভেলপমেন্টে, রেল অলস রুবি ফাইলগুলিকে লোড করে, যেমন, সেগুলিকে শুধুমাত্র তখনই লোড করে যখন সেগুলি require হয় d উৎপাদনে, যাইহোক, রেলগুলি আরম্ভ করার সময় অ্যাপের সমস্ত রুবি ফাইল লোড করে। এটি মাঙ্কিপ্যাচিংয়ে একটি বড় বানরের রেঞ্চ নিক্ষেপ করতে পারে৷

একটি ক্লাস পুনরায় খোলার পরিণতি

এই ক্ষেত্রে, প্রতিটি মাঙ্কিপ্যাচ String আবার খুলেছে ক্লাস এবং কার্যকরভাবে #% এর বিদ্যমান সংস্করণটিকে প্রতিস্থাপন করেছে অন্য একটি সঙ্গে পদ্ধতি। এই পদ্ধতির বেশ কয়েকটি প্রধান ত্রুটি রয়েছে:

  • শেষ প্যাচ প্রয়োগ করা হয়েছে "জয়", যার অর্থ, আচরণ লোড অর্ডারের উপর নির্ভরশীল
  • মূল বাস্তবায়ন অ্যাক্সেস করার কোন উপায় নেই
  • প্যাচগুলি প্রায় কোনও অডিট ট্রেল ছেড়ে যায় না, যা পরে তাদের খুঁজে পাওয়া খুব কঠিন করে তোলে

আশ্চর্যের বিষয় নয়, সম্ভবত, আমরা এই সবের মধ্যে পড়েছিলাম।

প্রথমে, আমরা জানতাম না যে খেলার সময় অন্যান্য বানরপ্যাচ ছিল। বিজয়ী পদ্ধতিতে ত্রুটির কারণে, এটি আসল বাস্তবায়নটি ভেঙে গেছে বলে মনে হয়েছিল। যখন আমরা অন্যান্য প্রতিযোগী প্যাচগুলি আবিষ্কার করেছি, তখন প্রচুর puts যোগ না করে কোনটি জিতেছে তা বলা অসম্ভব ছিল। বিবৃতি।

অবশেষে, এমনকি যখন আমরা আবিষ্কার করেছি যে কোন পদ্ধতিটি বিকাশে জিতেছে, অন্য একটি উৎপাদনে জিতবে। রুবি 1.8-এর বিস্ময়কর Method#source_location না থাকায় শেষবার কোন প্যাচ প্রয়োগ করা হয়েছে তা বলাও প্রোগ্রামগতভাবে কঠিন ছিল পদ্ধতি এখন আমাদের আছে।

আমি কি ঘটছে তা বের করার চেষ্টা করে অন্তত এক সপ্তাহ কাটিয়েছি, সম্পূর্ণভাবে এড়ানো যায় এমন সমস্যা তাড়া করার জন্য আমি মূলত সময় নষ্ট করেছি।

অবশেষে, আমরা LocalizedString চালু করার সিদ্ধান্ত নিয়েছি একটি সহগামী #% সহ মোড়ানো ক্লাস পদ্ধতি আমাদের String monkeypatch তারপর সহজভাবে হয়ে গেল:

class String
  def localize
    LocalizedString.new(self)
  end
end

যখন মাঙ্কিপ্যাচিং ব্যর্থ হয়

আমার অভিজ্ঞতায়, মাঙ্কিপ্যাচগুলি প্রায়শই দুটি কারণে ব্যর্থ হয়:

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

আসুন আরও বিশদে দ্বিতীয় বুলেট পয়েন্টটি দেখি।

এমনকি বেস্ট-লেড প্ল্যান...

মাঙ্কিপ্যাচিং প্রায়ই একই কারণে ব্যর্থ হয় যে কারণে আপনি এটির জন্য প্রথম স্থানে পৌঁছেছিলেন — কারণ আপনার আসল কোডে অ্যাক্সেস নেই। ঠিক সেই কারণে, মূল কোডটি আপনার অধীনে থেকে পরিবর্তিত হতে পারে।

এই উদাহরণটি একটি রত্ন হিসাবে বিবেচনা করুন যেটির উপর আপনার অ্যাপ নির্ভর করে:

class Sale
  def initialize(amount, discount_pct, tax_rate = nil)
    @amount = amount
    @discount_pct = discount_pct
    @tax_rate = tax_rate
  end
 
  def total
    discounted_amount + sales_tax
  end
 
  private
 
  def discounted_amount
    @amount * (1 - @discount_pct)
  end
 
  def sales_tax
    if @tax_rate
      discounted_amount * @tax_rate
    else
      0
    end
  end
end

দাঁড়াও, এটা ঠিক না। বিক্রয় কর সম্পূর্ণ পরিমাণে প্রয়োগ করা উচিত, ছাড়কৃত পরিমাণে নয়। আপনি প্রকল্পে একটি টান অনুরোধ জমা দিন. আপনি যখন রক্ষণাবেক্ষণকারীর জন্য আপনার PR মার্জ করার জন্য অপেক্ষা করছেন, তখন আপনি আপনার অ্যাপে এই মানকিপ্যাচটি যুক্ত করবেন:

class Sale
  private
 
  def sales_tax
    if @tax_rate
      @amount * @tax_rate
    else
      0
    end
  end
end

এটা পুরোপুরি কাজ করে। আপনি এটি চেক করুন এবং এটি সম্পর্কে ভুলে যান৷

অনেকদিন যাবত সব ঠিক আছে। তারপর একদিন ফাইন্যান্স টিম আপনাকে একটি ইমেল পাঠাবে কেন কোম্পানিটি এক মাস ধরে সেলস ট্যাক্স সংগ্রহ করছে না।

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

class Sale
  def initialize(amount, discount_pct, sales_tax_rate = nil)
    @amount = amount
    @discount_pct = discount_pct
    @sales_tax_rate = sales_tax_rate
  end
 
  def total
    discounted_amount + sales_tax
  end
 
  private
 
  def discounted_amount
    @amount * (1 - @discount_pct)
  end
 
  def sales_tax
    if @sales_tax_rate
      discounted_amount * @sales_tax_rate
    else
      0
    end
  end
end

দেখে মনে হচ্ছে প্রকল্পের রক্ষণাবেক্ষণকারীদের একজন @tax_rate এর নাম পরিবর্তন করেছে ইনস্ট্যান্স ভেরিয়েবলকে @sales_tax_rate . মানকিপ্যাচ পুরানো @tax_rate এর মান পরীক্ষা করে পরিবর্তনশীল, যা সবসময় nil হয় . কেউ খেয়াল করেনি কারণ কোনো ত্রুটি কখনো উত্থাপিত হয়নি। অ্যাপটি এমনভাবে চেপে গেল যেন কিছুই ঘটেনি৷

মানকিপ্যাচ কেন?

এই উদাহরণগুলি দেওয়া হলে, মনে হতে পারে মাঙ্কিপ্যাচিং সম্ভাব্য মাথাব্যথার মূল্য নয়। সুতরাং আমরা এটা কেন করব? আমার মতে, তিনটি প্রধান ব্যবহার-ক্ষেত্র রয়েছে:

  • ভাঙ্গা বা অসম্পূর্ণ তৃতীয় পক্ষের কোড ঠিক করতে
  • একটি পরিবর্তন বা উন্নয়নে একাধিক পরিবর্তন দ্রুত পরীক্ষা করতে
  • ইন্সট্রুমেন্টেশন বা টীকা কোড দিয়ে বিদ্যমান কার্যকারিতা মোড়ানো

কিছু ক্ষেত্রে, শুধু 3য়-পার্টি কোডে একটি বাগ বা পারফরম্যান্স সমস্যা সমাধানের কার্যকর উপায় হল একটি মাঙ্কিপ্যাচ প্রয়োগ করা।

কিন্তু মহান শক্তির সাথে মহান দায়িত্ব আসে।

দায়িত্বের সাথে মাঙ্কিপ্যাচিং

আমি বানরপ্যাচিং কথোপকথনটি ভাল বা খারাপ কিনা তার পরিবর্তে দায়িত্বের চারপাশে ফ্রেম করতে পছন্দ করি। অবশ্যই, খারাপভাবে করা হলে মাঙ্কিপ্যাচিং বিশৃঙ্খলা সৃষ্টি করতে পারে। যাইহোক, যদি কিছু যত্ন এবং পরিশ্রমের সাথে করা হয়, পরিস্থিতি যখন এটিকে নিশ্চিত করে তখন এটিতে পৌঁছানো এড়ানোর কোন কারণ নেই৷

আমি যে নিয়মগুলি অনুসরণ করার চেষ্টা করি তার তালিকা এখানে রয়েছে:

  1. প্যাচটিকে একটি সুস্পষ্ট নামের একটি মডিউলে মোড়ানো এবং Module#prepend ব্যবহার করুন এটি প্রয়োগ করতে
  2. নিশ্চিত করুন যে আপনি সঠিক জিনিসটি প্যাচ করছেন
  3. প্যাচের পৃষ্ঠের ক্ষেত্রফল সীমিত করুন
  4. নিজেকে এস্কেপ হ্যাচস দিন
  5. অতি যোগাযোগ করুন

এই নিবন্ধের বাকি অংশের জন্য, আমরা রেলের DateTimeSelector-এর জন্য একটি মাঙ্কিপ্যাচ লিখতে এই নিয়মগুলি ব্যবহার করতে যাচ্ছি তাই এটি ঐচ্ছিকভাবে বাতিল ক্ষেত্র রেন্ডারিং এড়িয়ে যায়। এটি এমন একটি পরিবর্তন যা আমি আসলে কয়েক বছর আগে রেলে করার চেষ্টা করেছি। আপনি এখানে বিস্তারিত জানতে পারেন।

বানরপ্যাচ বোঝার জন্য আপনাকে বাতিল ক্ষেত্র সম্পর্কে অনেক কিছু জানতে হবে না। দিনের শেষে, এটি যা করে তা হল build_hidden নামে একটি একক পদ্ধতি প্রতিস্থাপন করা। যেটি কার্যকরভাবে কিছুই করে না।

চলুন শুরু করা যাক!

Module#prepend ব্যবহার করুন

কোডবেসে আমি আমার পূর্ববর্তী ভূমিকার সম্মুখীন হয়েছিলাম, String#% এর সমস্ত বাস্তবায়ন String পুনরায় খোলার মাধ্যমে প্রয়োগ করা হয়েছে ক্লাস এখানে আমি আগে উল্লেখ করা ত্রুটিগুলির একটি বর্ধিত তালিকা:

  • ত্রুটিগুলি প্যাচ কোডের পরিবর্তে হোস্ট ক্লাস বা মডিউল থেকে উদ্ভূত হয়েছে বলে মনে হচ্ছে
  • আপনি প্যাচে সংজ্ঞায়িত যেকোন পদ্ধতি একই নামের সাথে বিদ্যমান পদ্ধতিগুলিকে প্রতিস্থাপন করে, যার অর্থ, আসল বাস্তবায়নকে আহ্বান করার কোনো উপায় নেই৷
  • কোন প্যাচ প্রয়োগ করা হয়েছে এবং তাই কোন পদ্ধতি "জিতেছে" তা জানার কোন উপায় নেই
  • প্যাচগুলি প্রায় কোনও অডিট ট্রেল ছেড়ে যায় না, যা পরে তাদের খুঁজে পাওয়া খুব কঠিন করে তোলে

পরিবর্তে, আপনার প্যাচটিকে একটি মডিউলে মোড়ানো এবং Module#prepend ব্যবহার করে এটি প্রয়োগ করা আরও ভাল। . এটি করার ফলে আপনি মূল বাস্তবায়নে কল করতে পারবেন এবং Module#ancestors-এ একটি দ্রুত কল করতে পারবেন। উত্তরাধিকার অনুক্রমের প্যাচটি দেখাবে যাতে জিনিসগুলি ভুল হলে এটি খুঁজে পাওয়া সহজ হয়৷

অবশেষে, একটি সহজ prepend আপনি যদি কোনো কারণে প্যাচ নিষ্ক্রিয় করতে চান তাহলে মন্তব্য করা সহজ।

এখানে আমাদের রেল মাঙ্কিপ্যাচের জন্য একটি মডিউলের সূচনা রয়েছে:

module RenderDiscardedMonkeypatch
end
 
ActionView::Helpers::DateTimeSelector.prepend(
  RenderDiscardedMonkeypatch
)

সঠিক জিনিস প্যাচ করুন

আপনি যদি এই নিবন্ধটি থেকে একটি জিনিস সরিয়ে নেন, তাহলে এটি হতে দিন:আপনি সঠিক কোডটি প্যাচ করছেন না জানলে একটি মাঙ্কিপ্যাচ প্রয়োগ করবেন না। বেশিরভাগ ক্ষেত্রে, আপনার অনুমানগুলি এখনও ধরে আছে তা প্রোগ্রাম্যাটিকভাবে যাচাই করা সম্ভব হওয়া উচিত (এটি সর্বোপরি রুবি)। এখানে একটি চেকলিস্ট রয়েছে:

  1. নিশ্চিত করুন যে আপনি যে ক্লাস বা মডিউলটি প্যাচ করার চেষ্টা করছেন সেটি বিদ্যমান আছে
  2. নিশ্চিত করুন যে পদ্ধতিগুলি বিদ্যমান এবং সঠিক সারমর্ম আছে
  3. আপনি যে কোডটি প্যাচ করছেন তা যদি একটি রত্নতে থাকে তবে রত্নটির সংস্করণটি পরীক্ষা করুন
  4. অনুমান ধরে না থাকলে একটি সহায়ক ত্রুটি বার্তা দিয়ে বেল আউট করুন

ব্যাট থেকে ডানদিকে, আমাদের প্যাচ কোডটি বেশ গুরুত্বপূর্ণ ধারণা তৈরি করেছে। এটি ActionView::Helpers::DateTimeSelector নামে একটি ধ্রুবক ধরে নেয় বিদ্যমান এবং একটি শ্রেণী বা মডিউল।

ক্লাস/মডিউল চেক করুন

এটি প্যাচ করার চেষ্টা করার আগে ধ্রুবক বিদ্যমান তা নিশ্চিত করুন:

module RenderDiscardedMonkeypatch
end
 
const = begin
  Kernel.const_get('ActionView::Helpers::DateTimeSelector')
rescue NameError
end
 
if const
  const.prepend(RenderDiscardedMonkeypatch)
end

দুর্দান্ত, কিন্তু এখন আমরা একটি স্থানীয় পরিবর্তনশীল (const) ফাঁস করেছি ) বিশ্বব্যাপী পরিসরে। এটা ঠিক করা যাক:

module RenderDiscardedMonkeypatch
  def self.apply_patch
    const = begin
      Kernel.const_get('ActionView::Helpers::DateTimeSelector')
    rescue NameError
    end
 
    if const
      const.prepend(self)
    end
  end
end
 
RenderDiscardedMonkeypatch.apply_patch

পদ্ধতি পরীক্ষা করুন

এর পরে, চলুন প্যাচ করা build_hidden পরিচয় করিয়ে দেই পদ্ধতি এটি বিদ্যমান আছে এবং সঠিক সংখ্যক আর্গুমেন্ট গ্রহণ করে তা নিশ্চিত করতে একটি চেক যোগ করা যাক (অর্থাৎ সঠিক অ্যারিটি আছে)। যদি এই অনুমানগুলি ধরে না থাকে তবে কিছু সম্ভবত ভুল:

module RenderDiscardedMonkeypatch
  class << self
    def apply_patch
      const = find_const
      mtd = find_method(const)
 
      if const && mtd && mtd.arity == 2
        const.prepend(self)
      end
    end
 
    private
 
    def find_const
      Kernel.const_get('ActionView::Helpers::DateTimeSelector')
    rescue NameError
    end
 
    def find_method(const)
      return unless const
      const.instance_method(:build_hidden)
    rescue NameError
    end
  end
 
  def build_hidden(type, value)
    ''
  end
end
 
RenderDiscardedMonkeypatch.apply_patch

মণি সংস্করণ পরীক্ষা করুন

অবশেষে, আসুন আমরা রেলের সঠিক সংস্করণ ব্যবহার করছি কিনা তা পরীক্ষা করে দেখি। যদি রেলগুলি আপগ্রেড করা হয়, তাহলে আমাদের প্যাচটিও আপডেট করতে হতে পারে (বা এটি সম্পূর্ণরূপে পরিত্রাণ পেতে)।

module RenderDiscardedMonkeypatch
  class << self
    def apply_patch
      const = find_const
      mtd = find_method(const)
 
      if const && mtd && mtd.arity == 2 && rails_version_ok?
        const.prepend(self)
      end
    end
 
    private
 
    def find_const
      Kernel.const_get('ActionView::Helpers::DateTimeSelector')
    rescue NameError
    end
 
    def find_method(const)
      return unless const
      const.instance_method(:build_hidden)
    rescue NameError
    end
 
    def rails_version_ok?
      Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR == 1
    end
  end
 
  def build_hidden(type, value)
    ''
  end
end
 
RenderDiscardedMonkeypatch.apply_patch

সহায়কভাবে বেইল আউট

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

আমরা কীভাবে আমাদের রেল প্যাচ পরিবর্তন করতে পারি তা এখানে:

module RenderDiscardedMonkeypatch
  class << self
    def apply_patch
      const = find_const
      mtd = find_method(const)
 
      unless const && mtd && mtd.arity == 2
        raise "Could not find class or method when patching "\
          "ActionView's date_select helper. Please investigate."
      end
 
      unless rails_version_ok?
        puts "WARNING: It looks like Rails has been upgraded since "\
          "ActionView's date_select helper was monkeypatched in "\
          "#{__FILE__}. Please reevaluate the patch."
      end
 
      const.prepend(self)
    end
 
    private
 
    def find_const
      Kernel.const_get('ActionView::Helpers::DateTimeSelector')
    rescue NameError
    end
 
    def find_method(const)
      return unless const
      const.instance_method(:build_hidden)
    rescue NameError
    end
 
    def rails_version_ok?
      Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR == 1
    end
  end
 
  def build_hidden(type, value)
    ''
  end
end
 
RenderDiscardedMonkeypatch.apply_patch

সারফেস এরিয়া সীমিত করুন

যদিও এটি একটি মাঙ্কিপ্যাচে সাহায্যকারী পদ্ধতিগুলিকে সংজ্ঞায়িত করা পুরোপুরি নিরীহ বলে মনে হতে পারে, মনে রাখবেন যে কোনও পদ্ধতি Module#prepend এর মাধ্যমে সংজ্ঞায়িত করা হয়েছে উত্তরাধিকার জাদু মাধ্যমে বিদ্যমান বেশী অগ্রাহ্য করা হবে. যদিও এটি মনে হতে পারে যে একটি হোস্ট ক্লাস বা মডিউল একটি নির্দিষ্ট পদ্ধতি সংজ্ঞায়িত করে না, এটি নিশ্চিতভাবে জানা কঠিন। এই কারণে, আমি প্যাচ করতে চাই এমন পদ্ধতিগুলিকে সংজ্ঞায়িত করার চেষ্টা করি৷

মনে রাখবেন যে এটি অবজেক্টের সিঙ্গলটন ক্লাসে সংজ্ঞায়িত পদ্ধতির ক্ষেত্রেও প্রযোজ্য, যেমন class << self-এর মধ্যে সংজ্ঞায়িত পদ্ধতি .

শুধুমাত্র একটি #build_hidden প্রতিস্থাপন করার জন্য আমাদের রেল প্যাচটি কীভাবে পরিবর্তন করবেন তা এখানে রয়েছে পদ্ধতি:

module RenderDiscardedMonkeypatch
  class << self
    def apply_patch
      const = find_const
      mtd = find_method(const)
 
      unless const && mtd && mtd.arity == 2
        raise "Could not find class or method when patching"\
          "ActionView's date_select helper. Please investigate."
      end
 
      unless rails_version_ok?
        puts "WARNING: It looks like Rails has been upgraded since"\
          "ActionView's date_selet helper was monkeypatched in "\
          "#{__FILE__}. Please reevaluate the patch."
      end
 
      const.prepend(InstanceMethods)
    end
 
    private
 
    def find_const
      Kernel.const_get('ActionView::Helpers::DateTimeSelector')
    rescue NameError
    end
 
    def find_method(const)
      return unless const
      const.instance_method(:build_hidden)
    rescue NameError
    end
 
    def rails_version_ok?
      Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR == 1
    end
  end
 
  module InstanceMethods
    def build_hidden(type, value)
      ''
    end
  end
end
 
RenderDiscardedMonkeypatch.apply_patch

নিজেকে এস্কেপ হ্যাচস দিন

যখন সম্ভব, আমি আমার মাঙ্কিপ্যাচের কার্যকারিতা অপ্ট-ইন করতে চাই। প্যাচ করা কোডটি কোথায় চাওয়া হবে তার উপর আপনার নিয়ন্ত্রণ থাকলেই এটি একটি বিকল্প। আমাদের রেল প্যাচের ক্ষেত্রে, এটি @options এর মাধ্যমে সম্ভব DateTimeSelector-এ হ্যাশ :

module RenderDiscardedMonkeypatch
  class << self
    def apply_patch
      const = find_const
      mtd = find_method(const)
 
      unless const && mtd && mtd.arity == 2
        raise "Could not find class or method when patching"\
          "ActionView's date_select helper. Please investigate."
      end
 
      unless rails_version_ok?
        puts "WARNING: It looks like Rails has been upgraded since"\
          "ActionView's date_selet helper was monkeypatched in "\
          "#{__FILE__}. Please reevaluate the patch."
      end
 
      const.prepend(InstanceMethods)
    end
 
    private
 
    def find_const
      Kernel.const_get('ActionView::Helpers::DateTimeSelector')
    rescue NameError
    end
 
    def find_method(const)
      return unless const
      const.instance_method(:build_hidden)
    rescue NameError
    end
 
    def rails_version_ok?
      Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR == 1
    end
  end
 
  module InstanceMethods
    def build_hidden(type, value)
      if @options.fetch(:render_discarded, true)
        super
      else
        ''
      end
    end
  end
end
 
RenderDiscardedMonkeypatch.apply_patch

চমৎকার! এখন কলকারীরা date_select এ কল করে অপ্ট-ইন করতে পারেন নতুন বিকল্পের সাহায্যকারী। অন্য কোন কোডপাথ প্রভাবিত হয় না:

date_select(@user, :date_of_birth, {
  order: [:month, :day],
  render_discarded: false
})

ওভার-কমিউনিকেট

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

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

আপনি এমনকি একটি সতর্কতা প্রিন্ট করতে পারেন বা একটি পূর্বনির্ধারিত তারিখে একটি পরীক্ষায় ব্যর্থ হতে পারেন যাতে প্যাচের অনুমানগুলি পুনরায় নিশ্চিত করার জন্য দলকে অনুরোধ করা যায় এবং এটি এখনও প্রয়োজনীয় কিনা তা বিবেচনা করুন৷

এখানে আমাদের Rails date_select এর চূড়ান্ত সংস্করণ প্যাচ, মন্তব্য এবং একটি তারিখ পরীক্ষা দিয়ে সম্পূর্ণ করুন:

# ActionView's date_select helper provides the option to "discard" certain
# fields. Discarded fields are (confusingly) still rendered to the page
# using hidden inputs, i.e. <input type="hidden" />. This patch adds an
# additional option to the date_select helper that allows the caller to
# skip rendering the chosen fields altogether. For example, to render all
# but the year field, you might have this in one of your views:
#
# date_select(:date_of_birth, order: [:month, :day])
#
# or, equivalently:
#
# date_select(:date_of_birth, discard_year: true)
#
# To avoid rendering the year field altogether, set :render_discarded to
# false:
#
# date_select(:date_of_birth, discard_year: true, render_discarded: false)
#
# This patch assumes the #build_hidden method exists on
# ActionView::Helpers::DateTimeSelector and accepts two arguments.
#
module RenderDiscardedMonkeypatch
  class << self
    EXPIRATION_DATE = Date.new(2021, 8, 15)
 
    def apply_patch
      if Date.today > EXPIRATION_DATE
        puts "WARNING: Please re-evaluate whether or not the ActionView "\
          "date_select patch present in #{__FILE__} is still necessary."
      end
 
      const = find_const
      mtd = find_method(const)
 
      # make sure the class we want to patch exists;
      # make sure the #build_hidden method exists and accepts exactly
      # two arguments
      unless const && mtd && mtd.arity == 2
        raise "Could not find class or method when patching "\
          "ActionView's date_select helper. Please investigate."
      end
 
      # if rails has been upgraded, make sure this patch is still
      # necessary
      unless rails_version_ok?
        puts "WARNING: It looks like Rails has been upgraded since "\
          "ActionView's date_select helper was monkeypatched in "\
          "#{__FILE__}. Please re-evaluate the patch."
      end
 
      # actually apply the patch
      const.prepend(InstanceMethods)
    end
 
    private
 
    def find_const
      Kernel.const_get('ActionView::Helpers::DateTimeSelector')
    rescue NameError
      # return nil if the constant doesn't exist
    end
 
    def find_method(const)
      return unless const
      const.instance_method(:build_hidden)
    rescue NameError
      # return nil if the method doesn't exist
    end
 
    def rails_version_ok?
      Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR == 1
    end
  end
 
  module InstanceMethods
    # :render_discarded is an additional option you can pass to the
    # date_select helper in your views. Use it to avoid rendering
    # "discarded" fields, i.e. fields marked as discarded or simply
    # not included in date_select's :order array. For example,
    # specifying order: [:day, :month] will cause the helper to
    # "discard" the :year field. Discarding a field renders it as a
    # hidden input. Set :render_discarded to false to avoid rendering
    # it altogether.
    def build_hidden(type, value)
      if @options.fetch(:render_discarded, true)
        super
      else
        ''
      end
    end
  end
end
 
RenderDiscardedMonkeypatch.apply_patch

উপসংহার

আমি সম্পূর্ণরূপে বুঝতে পারি যে আমি উপরে বর্ণিত কিছু পরামর্শগুলি ওভারকিলের মতো মনে হতে পারে। আমাদের রেল প্যাচে প্রকৃত প্যাচ কোডের চেয়ে অনেক বেশি প্রতিরক্ষামূলক যাচাইকরণ কোড রয়েছে!

আপনার ব্রডওয়ার্ডের জন্য একটি খাপ হিসাবে সমস্ত অতিরিক্ত কোডের কথা ভাবুন। এটি সুরক্ষার একটি স্তরে আবৃত থাকলে কাটা হওয়া এড়ানো অনেক সহজ।

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

পি.এস. আপনি যদি রুবি ম্যাজিক পোস্টগুলি প্রেস থেকে বের হওয়ার সাথে সাথে পড়তে চান তবে আমাদের রুবি ম্যাজিক নিউজলেটারে সাবস্ক্রাইব করুন এবং একটি পোস্ট মিস করবেন না!


  1. রুবিতে বিটওয়াইজ হ্যাক

  2. রুবিতে ল্যাম্বডাস ব্যবহার করা

  3. রুবিতে সন্নিবেশ বাছাই বোঝা

  4. রুবি 2.6-এ 9টি নতুন বৈশিষ্ট্য