त्रुटि प्रबंधन हर प्रोग्राम का एक महत्वपूर्ण हिस्सा है। कोड के एक टुकड़े के कार्यान्वयन के दौरान कौन सी त्रुटियां उत्पन्न हो सकती हैं, इसके बारे में सक्रिय होना महत्वपूर्ण है। इन त्रुटियों को इस तरह से संभाला जाना चाहिए जो यह सुनिश्चित करे कि आउटपुट उत्पन्न होता है जो प्रत्येक त्रुटि और उस चरण का उचित वर्णन करता है जिस पर यह एप्लिकेशन में हुई। फिर भी, इसे इस तरह से प्राप्त करना भी महत्वपूर्ण है जो यह सुनिश्चित करे कि आपका कोड कार्यात्मक और पठनीय बना रहे। आइए एक ऐसे सवाल का जवाब देकर शुरू करें जो आपके मन में पहले से ही हो सकता है: रेलवे-ओरिएंटेड प्रोग्रामिंग क्या है?
रेलवे-ओरिएंटेड प्रोग्रामिंग
एक फंक्शन जो एक विशेष उद्देश्य प्राप्त करता है, छोटे फंक्शन्स की रचना हो सकता है। ये फंक्शन्स विभिन्न चरणों को पूरा करते हैं जो अंततः अंतिम लक्ष्य की ओर ले जाते हैं। उदाहरण के लिए, एक फंक्शन जो डेटाबेस में उपयोगकर्ता के पते को अपडेट करता है और बाद में इस परिवर्तन के बारे में उपयोगकर्ता को सूचित करता है, उसमें निम्न चरण शामिल हो सकते हैं:
उपयोगकर्ता मान्य करें -> पता अपडेट करें -> सफल अपडेट पर मेल भेजें
इनमें से प्रत्येक चरण या तो विफल हो सकता है या सफल हो सकता है, और किसी भी चरण की विफलता पूरी प्रक्रिया की विफलता की ओर ले जाती है, क्योंकि फंक्शन का उद्देश्य प्राप्त नहीं होता है।
रेलवे-ओरिएंटेड प्रोग्रामिंग (ROP) Scott Wlaschin द्वारा गढ़ा गया एक शब्द है जो रेलवे स्विच सादृश्य को ऐसे फंक्शन्स में त्रुटि प्रबंधन पर लागू करता है। रेलवे स्विच (यूके में "पॉइंट्स" कहलाते हैं) ट्रेनों को एक ट्रैक से दूसरे ट्रैक पर मार्गदर्शन करते हैं। Scott इस सादृश्य को इस अर्थ में नियोजित करता है कि प्रत्येक चरण का सफलता/विफलता आउटपुट ठीक रेलवे स्विच की तरह कार्य करता है, क्योंकि यह आपको सफलता ट्रैक या विफलता ट्रैक पर ले जा सकता है।
सफलता/विफलता आउटपुट रेलवे स्विच के रूप में कार्य करते हैं
जब किसी भी चरण में त्रुटि होती है, तो हम विफलता आउटपुट द्वारा विफलता ट्रैक पर चले जाते हैं, जिससे बाकी चरणों को बायपास कर दिया जाता है। हालांकि, जब सफलता आउटपुट होता है, तो यह अगले चरण के इनपुट से जुड़ा होता है ताकि हमें हमारे अंतिम गंतव्य तक ले जाया जा सके, जैसा कि नीचे दी गई छवि में दिखाया गया है।
कई चरणों की सफलता/विफलता आउटपुट एक साथ जुड़े हुए
यह दो-ट्रैक सादृश्य रेलवे-ओरिएंटेड प्रोग्रामिंग के पीछे का विचार है। यह हर चरण में (यानी, एक प्रक्रिया का हिस्सा होने वाली हर विधि) इन सफलता/विफलता आउटपुट को लौटाने का प्रयास करता है ताकि यह सुनिश्चित हो सके कि एक चरण में विफलता पूरी प्रक्रिया की विफलता है। प्रत्येक चरण के सफल समापन से ही समग्र सफलता मिलती है।
रोजमर्रा की जिंदगी का एक उदाहरण
कल्पना करें कि आपका लक्ष्य The Milk Shakers नामक स्टोर से व्यक्तिगत रूप से दूध का एक कार्टन खरीदना है। संभावित चरण निम्न होंगे:
घर छोड़ें -> The Milk Shakers पर पहुंचें -> दूध का कार्टन उठाएं -> दूध के कार्टन के लिए भुगतान करें
यदि आप अपने घर से बाहर नहीं निकल सकते, तो पूरी प्रक्रिया विफल है क्योंकि पहला चरण विफल है। क्या होगा यदि आप घर से बाहर निकल गए और Walmart चले गए? प्रक्रिया अभी भी विफल है क्योंकि आप निर्दिष्ट स्टोर पर नहीं गए। तथ्य यह है कि आप Walmart से दूध प्राप्त कर सकते हैं इसका मतलब यह नहीं है कि प्रक्रिया जारी रहेगी। ROP प्रक्रिया को Walmart पर रोक देता है और एक विफलता आउटपुट लौटाता है जिससे आपको पता चलता है कि प्रक्रिया विफल हुई क्योंकि स्टोर The Milk Shakers नहीं था। हालांकि, यदि आप सही स्टोर पर गए होते, तो प्रक्रिया जारी रहती, आउटपुट्स की जांच करती और या तो प्रक्रिया समाप्त करती या अगले चरण पर आगे बढ़ती। यह अधिक पठनीय और सुंदर त्रुटि प्रबंधन सुनिश्चित करता है और इसे if/else और return स्टेटमेंट्स के बिना व्यक्तिगत चरणों को जोड़ते हुए कुशलतापूर्वक प्राप्त करता है।
Rails में, हम Dry Monads नामक एक जेम का उपयोग करके इस दो-ट्रैक रेलवे आउटपुट को प्राप्त कर सकते हैं।
Dry Monads का परिचय और वे कैसे काम करते हैं
मोनेड्स मूल रूप से एक गणितीय अवधारणा थी। मूल रूप से, वे कई विशेष फंक्शन्स की एक रचना या अमूर्त हैं जो, जब कोड में उपयोग किए जाते हैं, तो स्टेटफुल मूल्यों के स्पष्ट प्रबंधन को समाप्त कर सकते हैं। वे प्रोग्राम तर्क के लिए आवश्यक कम्प्यूटेशनल बॉयलरप्लेट कोड का एक अमूर्त भी हो सकते हैं। स्टेटफुल मूल्य किसी विशेष फंक्शन के लिए स्थानीय नहीं होते हैं, कुछ उदाहरणों में शामिल हैं: इनपुट्स, ग्लोबल वेरिएबल्स, और आउटपुट्स। मोनेड्स में एक बाइंड फंक्शन होता है जो इन मूल्यों को एक मोनेड से दूसरे मोनेड में पास करना संभव बनाता है; इसलिए, उन्हें कभी भी स्पष्ट रूप से संभाला नहीं जाता है। उन्हें अपवादों को संभालने, रोलबैक कमिट करने, रीट्राई लॉजिक आदि के लिए बनाया जा सकता है। आप मोनेड्स के बारे में अधिक जानकारी यहां पा सकते हैं।
इसके दस्तावेज़ में कहा गया है, जिसकी मैं आपको समीक्षा करने की सलाह देता हूं, dry monads Ruby के लिए सामान्य मोनेड्स का एक सेट है। मोनेड्स त्रुटियों, अपवादों, और फंक्शन्स को श्रृंखलाबद्ध करने का एक सुंदर तरीका प्रदान करते हैं ताकि कोड बहुत अधिक समझने योग्य हो और सभी वांछित त्रुटि प्रबंधन बिना सभी ifs and elses के हो। हम Result Monad पर ध्यान केंद्रित करेंगे क्योंकि यह ठीक वही है जो हमें पहले चर्चा की गई सफलता/विफलता आउटपुट को प्राप्त करने के लिए चाहिए।
आइए निम्न कमांड का उपयोग करके Railway-app नामक एक नया Rails ऐप शुरू करें:
rails new railway-app -T
कमांड में -T का मतलब है कि हम टेस्ट फ़ोल्डर को छोड़ देंगे क्योंकि हम टेस्टिंग के लिए RSpec का उपयोग करना चाहते हैं।
अब, हम अपने Gemfile में आवश्यक जेम्स जोड़ते हैं: सफलता/विफलता परिणामों के लिए gem dry-monads और टेस्ट और डेवलपमेंट ग्रुप में हमारे टेस्टिंग फ्रेमवर्क के रूप में gem rspec-rails। अब, हम जोड़े गए जेम्स को इंस्टॉल करने के लिए अपने ऐप में bundle install चला सकते हैं। हालांकि, हमारे टेस्ट फाइल्स और हेल्पर्स को जेनरेट करने के लिए, हमें निम्न कमांड चलाने की आवश्यकता है:
rails generate rspec:install
एक फंक्शन को कई चरणों में विभाजित करें
अपने फंक्शन को छोटी विधियों में विभाजित करना हमेशा उचित होता है जो आपके अंतिम लक्ष्य को प्राप्त करने के लिए मिलकर काम करती हैं। इन विधियों से त्रुटियां, यदि कोई हों, तो हमें यह पहचानने में मदद करती हैं कि हमारी प्रक्रिया वास्तव में कहां विफल हुई और हमारे कोड को साफ और पठनीय रखती है। इसे यथासंभव सरल बनाने के लिए, हम एक टोयोटा कार डीलरशिप की एक क्लास बनाएंगे जो उपयोगकर्ता को कार डिलीवर करती है यदि मांगा गया मॉडल और रंग उपलब्ध है, यदि निर्माण वर्ष वर्ष 2000 से पहले नहीं है, और यदि डिलीवर किए जाने वाला शहर आस-पास के शहरों की सूची में है। यह दिलचस्प होना चाहिए। :)
आइए डिलीवरी प्रक्रिया को कई चरणों में विभाजित करके शुरू करें:
- सत्यापित करें कि निर्माण वर्ष वर्ष 2000 से पहले नहीं है।
- सत्यापित करें कि मॉडल उपलब्ध है।
- सत्यापित करें कि रंग उपलब्ध है।
- सत्यापित करें कि डिलीवर किया जाने वाला शहर आस-पास का शहर है।
- एक संदेश भेजें जिसमें कहा गया हो कि कार डिलीवर की जाएगी।
अब जब हमारे पास अलग-अलग चरण तय हो गए हैं, तो आइए कोड में गोता लगाएं।
सफलता/विफलता आउटपुट परिणाम इनपुट करना
हमारे ऐप/मॉडल फ़ोल्डर में, आइए car_dealership.rb नामक एक फाइल बनाएं और इस क्लास को महत्वपूर्ण विवरणों के साथ इनिशियलाइज़ करें। फाइल के शीर्ष पर, हमें dry/monads की आवश्यकता है, और क्लास नाम के ठीक बाद, हमें DryMonads[:result, :do] शामिल करना होगा। यह हमें रिजल्ट मोनेड और डू नोटेशन (जो yield शब्द का उपयोग करके कई मोनेडिक ऑपरेशन्स के संयोजन को संभव बनाता है) उपलब्ध कराता है।
require 'dry/monads'
class CarDealership
include Dry::Monads[:result, :do]
def initialize
@available_models = %w[Avalon Camry Corolla Venza]
@available_colors = %w[red black blue white]
@nearby_cities = %w[Austin Chicago Seattle]
end
end
अब, हम अपनी deliver_car विधि जोड़ते हैं, जिसमें अन्य सभी चरण शामिल होंगे और यदि सभी चरण सफल होते हैं तो एक सफलता संदेश लौटाएगा। हम इन चरणों को एक दूसरे के साथ संयोजित या बाइंड करने के लिए yield शब्द जोड़ते हैं। इसका मतलब है कि इनमें से किसी भी चरण में एक विफलता संदेश deliver_car विधि का विफलता संदेश बन जाता है, और उनमें से किसी में भी एक सफलता आउटपुट सूची में अगले चरण के कॉल पर यील्ड करता है।
def deliver_car(year, model, color, city)
yield check_year(year)
yield check_model(model)
yield check_city(city)
yield check_color(color)
Success("A #{color} #{year} Toyota #{model} will be delivered to #{city}")
end
अब, आइए अन्य सभी विधियों को जोड़ें और उनके चेक के परिणामों के आधार पर उनसे सफलता/विफलता परिणाम जोड़ें।
def check_year(year)
year < 2000 ? Failure("We have no cars manufactured in year #{year}") : Success('Cars of this year are available')
end
def check_model(model)
@available_models.include?(model) ? Success('Model available') : Failure('The model requested is unavailable')
end
def check_color(color)
@available_colors.include?(color) ? Success('This color is available') : Failure("Color #{color} is unavailable")
end
def check_city(city)
@nearby_cities.include?(city) ? Success("Car deliverable to #{city}") : Failure('Apologies, we cannot deliver to this city')
end
वर्तमान में हमारे पास हमारी क्लास और सभी आवश्यक विधियां हैं। यह कैसे खेलेगा? आइए इस क्लास का एक नया इंस्टेंस बनाकर और deliver_car विधि को विभिन्न तर्कों के साथ कॉल करके पता करें।
good_dealer = CarDealership.new
good_dealer.deliver_car(1990, 'Venza', 'red', 'Austin')
# Failure("We have no cars manufactured in year 1990")
good_dealer.deliver_car(2005, 'Rav4', 'red', 'Austin')
# Failure("The model requested is unavailable")
good_dealer.deliver_car(2005, 'Venza', 'yellow', 'Austin')
# Failure("Color yellow is unavailable")
good_dealer.deliver_car(2000, 'Venza', 'red', 'Surrey')
# Failure("Apologies, we cannot deliver to this city")
good_dealer.deliver_car(2000, 'Avalon', 'blue', 'Austin')
# Success("A blue 2000 Toyota Avalon will be delivered to Austin")
ऊपर दिखाए अनुसार, deliver_car विधि का विफलता परिणाम उस विधि के आधार पर भिन्न होता है जिस पर यह विफल होती है। उस विधि की विफलता इसकी विफलता बन जाती है, और सभी विधियों की सफलता पर, यह अपना स्वयं का सफलता परिणाम लौटाती है। साथ ही, आइए न भूलें कि ये चरण व्यक्तिगत विधियां हैं जिन्हें deliver_car विधि से स्वतंत्र रूप से भी बुलाया जा सकता है। एक उदाहरण नीचे दिखाया गया है:
good_dealer.check_color('wine')
# Failure("Color wine is unavailable")
good_dealer.check_model('Camry')
# Success('Model available')
RSpec के साथ टेस्टिंग
उपरोक्त कोड का टेस्ट करने के लिए, हम अपने स्पेक फ़ोल्डर में जाते हैं और spec/models पथ में car_dealership_spec.rb नामक एक फाइल बनाते हैं। पहली पंक्ति पर, हमें अपने 'rails_helper' की आवश्यकता है। हम पहले विफलता के संदर्भ के लिए टेस्ट लिखेंगे और फिर सफलता के लिए।
require 'rails_helper'
describe CarDealership do
describe "#deliver_car" do
let(:toyota_dealer) { CarDealership.new }
context "failure" do
it "does not deliver a car with the year less than 2000" do
delivery = toyota_dealer.deliver_car(1990, 'Venza', 'red', 'Austin')
expect(delivery.success).to eq nil
expect(delivery.failure).to eq 'We have no cars manufactured in year 1990'
end
it "does not deliver a car with the year less than 2000" do
delivery = toyota_dealer.deliver_car(2005, 'Venza', 'yellow', 'Austin')
expect(delivery.success).to eq nil
expect(delivery.failure).to eq 'Color yellow is unavailable'
end
end
end
end
ऊपर दिखाए अनुसार, हम result.failure या result.success का उपयोग करके विफलता या सफलता परिणामों तक पहुंच सकते हैं। सफलता के संदर्भ के लिए, टेस्ट कुछ इस तरह दिखेंगे:
context "success" do
it "delivers a car when all conditions are met" do
delivery = toyota_dealer.deliver_car(2000, 'Avalon', 'blue', 'Austin')
expect(delivery.success).to eq 'A blue 2000 Toyota Avalon will be delivered to Austin'
expect(delivery.failure).to eq nil
end
end
अब, आप deliver_car विधि को दिए गए तर्कों को बदलकर विफलता संदर्भ में अन्य टेस्ट जोड़ सकते हैं। आप अपने कोड में अन्य चेक भी जोड़ सकते हैं उन स्थितियों के लिए जहां एक अमान्य तर्क प्रदान किया गया है (उदाहरण के लिए, वर्ष वेरिएबल के लिए एक स्ट्रिंग मान प्रदान किया गया है और इसी तरह)। अपने टर्मिनल में bundle exec rspec चलाने से टेस्ट चलते हैं और दिखाते हैं कि सभी टेस्ट पास होते हैं। आपको मूल रूप से अपने टेस्ट में एक ही समय में विफलता और सफलता परिणामों के लिए चेक जोड़ने की आवश्यकता नहीं है, क्योंकि हम एक विधि के आउटपुट के रूप में दोनों नहीं रख सकते। मैंने इसे केवल इस समझ में सहायता के लिए जोड़ा है कि सफलता परिणाम कैसा दिखता है जब हमारे पास विफलता परिणाम होता है और इसके विपरीत।
निष्कर्ष
यह सिर्फ dry-monads का एक परिचय है और कैसे इसका उपयोग आपके ऐप में रेलवे-ओरिएंटेड प्रोग्रामिंग प्राप्त करने के लिए किया जा सकता है। इसकी बुनियादी समझ को अधिक जटिल ऑपरेशन्स और लेनदेन पर आगे लागू किया जा सकता है। जैसा कि हमने देखा, ROP का उपयोग करके एक क्लीनर और अधिक पठनीय कोड न केवल प्राप्त करने योग्य है, बल्कि त्रुटि प्रबंधन विस्तृत और कम तनावपूर्ण है। हमेशा याद रखें कि अपनी प्रक्रिया को बनाने वाली विभिन्न विधियों में संक्षिप्त विफलता/सफलता संदेश संलग्न करें, क्योंकि यह दृष्टिकोण यह पहचानने में सहायता करता है कि त्रुटि कहां और क्यों हुई। यदि आप ROP के बारे में अधिक जानकारी चाहते हैं, तो हम Scott Wlaschin द्वारा इस प्रस्तुति को देखने की सलाह देते हैं।