Refactoring - क्या यह केवल कोड को फिर से लिखना उचित है, जब तक कि सभी परीक्षण पास न हो जाएं?


9

मैंने हाल ही में RailsConf 2014 की "ऑल द लिटिल थिंग्स" देखी। इस बातचीत के दौरान, सैंडी मेट्ज़ ने एक फ़ंक्शन का उल्लेख किया जिसमें एक बड़ा नेस्टेड-स्टेटमेंट शामिल है:

def tick
    if @name != 'Aged Brie' && @name != 'Backstage passes to a TAFKAL80ETC concert'
        if @quality > 0
            if @name != 'Sulfuras, Hand of Ragnaros'
                @quality -= 1
            end
        end
    else
        ...
    end
    ...
end

पहला कदम फ़ंक्शन को कई छोटे लोगों में तोड़ना है:

def tick
    case name
    when 'Aged Brie'
        return brie_tick
    ...
    end
end

def brie_tick
    @days_remaining -= 1
    return if quality >= 50

    @quality += 1
    @quality += 1 if @days_remaining <= 0
end

मुझे जो दिलचस्प लगा वह यह था कि इन छोटे कार्यों को लिखा गया था। brie_tick, उदाहरण के लिए, मूल tickफ़ंक्शन के संबंधित भागों को निकालकर नहीं लिखा गया था , लेकिन test_brie_*यूनिट परीक्षणों का उल्लेख करके खरोंच से । एक बार जब इन सभी यूनिट परीक्षणों को पारित कर दिया brie_tickगया , तो इस पर विचार किया गया। एक बार जब सभी छोटे कार्य किए गए थे, तो मूल अखंड tickफ़ंक्शन को हटा दिया गया था।

दुर्भाग्य से, प्रस्तुतकर्ता इस बात से अनजान था कि इस दृष्टिकोण के कारण चार में से तीन *_tickकार्य गलत हो रहे थे (और दूसरा खाली था!)। ऐसे किनारे मामले हैं जिनमें *_tickकार्यों का व्यवहार मूल tickकार्य से भिन्न होता है । उदाहरण के लिए, @days_remaining <= 0में brie_tickहोना चाहिए < 0- इसलिए brie_tickसही ढंग से काम नहीं करता है जब days_remaining == 1और कहा जाता है quality < 50

यहाँ क्या गलत हो गया है? क्या यह परीक्षण की विफलता है - क्योंकि इन विशेष किनारों के मामलों के लिए कोई परीक्षण नहीं थे? या फिर से संपर्क करने में विफलता - क्योंकि कोड को खरोंच से फिर से लिखे जाने के बजाय चरण-दर-चरण बदलना चाहिए था?


2
मुझे यकीन नहीं है कि मुझे सवाल मिलेगा। बेशक कोड को फिर से लिखना ठीक है। मुझे यकीन नहीं है कि आपके द्वारा विशेष रूप से " बस कोड को फिर से लिखना ठीक है" से क्या मतलब है । यदि आप पूछ रहे हैं कि "क्या इसमें बहुत अधिक विचार किए बिना कोड को फिर से लिखना ठीक है," जवाब नहीं है, जिस तरह से उस तरीके से कोड लिखना ठीक नहीं है।
जॉन वू

ऐसा अक्सर परीक्षण योजनाओं के कारण होता है जो मुख्य रूप से सफलता के मामलों के परीक्षण मामलों पर केंद्रित होते हैं और त्रुटि उपयोग के मामलों या उप-उपयोग के मामलों को कवर करने पर बहुत कम (या बिल्कुल भी नहीं) होते हैं। तो यह मुख्य रूप से कवरेज का एक रिसाव है। परीक्षण का एक रिसाव।
Laiv

@ जॉनव्हु - मैं इस धारणा के तहत था कि रीफैक्टरिंग आम तौर पर स्रोत कोड ("एक्सट्रैक्ट-मेथड आदि) के लिए छोटे परिवर्तनों की एक श्रृंखला के रूप में किया गया था, न कि केवल कोड को फिर से लिखने के द्वारा (जिससे मेरा मतलब है कि इसे फिर से स्क्रैच से भी लिखे बिना। मौजूदा कोड को देखते हुए, जैसा कि लिंक किए गए प्रेजेंटेशन में किया गया है)।
user200783

@ जॉनव्हु - क्या स्क्रैच से स्वीकार्य रीएक्टरिंग तकनीक है? यदि नहीं, तो इस तरह के दृष्टिकोण को फिर से शुरू करने पर इस तरह की एक अच्छी प्रस्तुति को देखना निराशाजनक है। OTOH यदि यह स्वीकार्य है, तो व्यवहार में अनपेक्षित परिवर्तनों को लापता परीक्षणों पर दोषी ठहराया जा सकता है - लेकिन क्या आश्वस्त होने का कोई तरीका है कि परीक्षण सभी संभावित किनारे मामलों को कवर करते हैं?
user200783

@ User200783 यह एक बड़ा सवाल है, क्या यह नहीं है (मैं कैसे सुनिश्चित करूं कि मेरे परीक्षण व्यापक हैं?) व्यावहारिक रूप से, मैं शायद कोई भी बदलाव करने से पहले कोड कवरेज रिपोर्ट चलाऊंगा, और कोड के किसी भी क्षेत्र की सावधानीपूर्वक जांच करूंगा। व्यायाम करना सुनिश्चित करें, यह सुनिश्चित करने के लिए कि वे तर्क को फिर से लिखें, विकास टीम उनके प्रति जागरूक है।
जॉन वू

जवाबों:


11

क्या यह परीक्षण की विफलता है - क्योंकि इन विशेष किनारों के मामलों के लिए कोई परीक्षण नहीं थे? या फिर से संपर्क करने में विफलता - क्योंकि कोड को खरोंच से फिर से लिखे जाने के बजाय चरण-दर-चरण बदलना चाहिए था?

दोनों। फाउलर्स की मूल पुस्तक से केवल मानक चरणों का उपयोग करके री-राइट करना निश्चित रूप से कम त्रुटि वाला है, इसलिए अक्सर इस प्रकार के शिशु चरणों का उपयोग करना बेहतर होता है। यहां तक ​​कि अगर हर किनारे के मामले के लिए कोई इकाई परीक्षण नहीं हैं, और भले ही पर्यावरण स्वत: रिफ्लेक्टरिंग प्रदान नहीं करता है, लेकिन "कोड की व्याख्या करने वाला चर" या "एक्सट्रैक्ट फंक्शन" जैसे एक एकल कोड में परिवर्तन के व्यवहार के विवरण को बदलने का बहुत कम मौका है। किसी फ़ंक्शन के पूर्ण पुनर्लेखन से मौजूदा कोड।

कभी-कभी, हालांकि, कोड के एक भाग को फिर से लिखना यह वही है जो आपको चाहिए या करना चाहिए। और अगर मामला यही है, तो आपको बेहतर परीक्षणों की आवश्यकता है।

ध्यान दें कि रिफ्लेक्टरिंग टूल का उपयोग करते समय, कोड बदलते समय, हमेशा छोटे या बड़े चरणों को लागू करने की परवाह किए बिना त्रुटियों को पेश करने का एक निश्चित जोखिम होता है। इसीलिए रिफैक्टरिंग को हमेशा परीक्षणों की आवश्यकता होती है। यह भी ध्यान दें कि परीक्षण केवल बग की संभावना को कम कर सकते हैं, लेकिन कभी भी उनकी अनुपस्थिति को साबित नहीं करते हैं - फिर भी कोड और शाखा कवरेज जैसी तकनीकों का उपयोग करने से आपको एक उच्च आत्मविश्वास स्तर मिल सकता है, और कोड अनुभाग के फिर से लिखने के मामले में, यह है अक्सर ऐसी तकनीकों को लागू करने के लायक है।


1
धन्यवाद, यह समझ में आता है। इसलिए, यदि व्यवहार में अवांछनीय परिवर्तनों का अंतिम समाधान व्यापक परीक्षण करना है, तो क्या आश्वस्त होने का कोई तरीका है कि परीक्षण सभी पुराने मामलों को कवर करते हैं? उदाहरण के लिए, इसके बारे में 100% कवरेज करने के लिए संभव हो जाएगा brie_tick, जबकि अभी भी कभी नहीं समस्याग्रस्त परीक्षण @days_remaining == 1उदाहरण के लिए, द्वारा मामला है, के साथ परीक्षण @days_remainingकरने के लिए सेट 10और -10
user200783

2
आप कभी भी पूरी तरह से निश्चित नहीं हो सकते हैं कि परीक्षण सभी संभव किनारों को कवर करते हैं, क्योंकि यह सभी संभावित इनपुट के साथ परीक्षण करने के लिए संभव नहीं है। लेकिन परीक्षणों में अधिक उलझन हासिल करने के बहुत सारे तरीके हैं। आप उत्परिवर्तन परीक्षण में देख सकते हैं , जो परीक्षणों की प्रभावशीलता का परीक्षण करने का एक तरीका है।
bdsl

1
इस उदाहरण में, परीक्षण को विकसित करते समय मिस्ड शाखाओं को एक कोड कवरेज टूल के साथ पकड़ा जा सकता था।
कोबजार

2

यहाँ क्या गलत हो गया है? क्या यह परीक्षण की विफलता है - क्योंकि इन विशेष किनारों के मामलों के लिए कोई परीक्षण नहीं थे? या फिर से संपर्क करने में विफलता - क्योंकि कोड को खरोंच से फिर से लिखे जाने के बजाय चरण-दर-चरण बदलना चाहिए था?

उन चीजों में से एक जो विरासत कोड के साथ काम करने के बारे में वास्तव में कठिन है: वर्तमान व्यवहार की पूरी समझ प्राप्त करना।

लिगेसी कोड बिना किसी परीक्षण के जो व्यवहार को विवश करता है, वह जंगली में एक सामान्य पैटर्न है। जो आपको एक अनुमान के साथ छोड़ देता है: क्या इसका मतलब यह है कि अप्रतिबंधित व्यवहार मुक्त चर हैं? या आवश्यकताएं जो अंडरस्क्राइब्ड हैं?

बात से :

अब यह वास्तविक रीफैक्टरिंग है रिफ्लेक्टिंग की परिभाषा के अनुसार; मैं इस कोड को रिफलेक्टर करने जा रहा हूं। मैं इसके व्यवहार में बदलाव किए बिना इसकी व्यवस्था बदलने जा रहा हूं।

यह अधिक रूढ़िवादी दृष्टिकोण है; यदि आवश्यकताएं अंडरस्क्राइब की जा सकती हैं, यदि परीक्षण सभी मौजूदा लॉजिक को कैप्चर नहीं करते हैं, तो आपको बहुत सावधानी बरतनी होगी कि आप कैसे आगे बढ़ते हैं।

कुछ के लिए, आप यह दावा कर सकते हैं कि यदि परीक्षण अपर्याप्त रूप से सिस्टम के व्यवहार का वर्णन कर रहे हैं, तो आपको "परीक्षण की विफलता" है। और मुझे लगता है कि यह उचित है - लेकिन वास्तव में उपयोगी नहीं है; यह जंगली में होने वाली एक आम समस्या है

या फिर से संपर्क करने में विफलता - क्योंकि कोड को खरोंच से फिर से लिखे जाने के बजाय चरण-दर-चरण बदलना चाहिए था?

मुद्दा यह नहीं है कि परिवर्तन चरण-दर-चरण होना चाहिए था; बल्कि इसलिए कि उच्च त्रुटि दर की वजह से उपकरण (मानव कीबोर्ड ऑपरेटर के बजाय गाइडेड ऑटोमेशन) को रिफलैक्ट करने का विकल्प अच्छी तरह से परीक्षण कवरेज के साथ गठबंधन नहीं किया गया था।

इसे या तो उच्च विश्वसनीयता के साथ रीफैक्टरिंग टूल का उपयोग करके या सिस्टम पर बाधाओं को सुधारने के लिए परीक्षणों की एक व्यापक बैटरी को शुरू करके संबोधित किया जा सकता था ।

इसलिए मुझे लगता है कि आपका संयोजन खराब चुना गया है; ANDनहीं है OR


2

Refactoring आपके कोड के बाहरी दृश्यमान व्यवहार को नहीं बदलना चाहिए। यही लक्ष्य है।

यदि आपकी इकाई परीक्षण विफल हो जाते हैं, तो यह इंगित करता है कि आपने व्यवहार को बदल दिया है। लेकिन यूनिट टेस्ट पास करना कभी लक्ष्य नहीं होता है। यह आपके लक्ष्य को प्राप्त करने में कम या ज्यादा मदद करता है। यदि रीफैक्टरिंग बाहरी रूप से दृश्यमान व्यवहार को बदल देती है, और सभी यूनिट परीक्षण पास हो जाते हैं, तो आपका रीफैक्टरिंग विफल हो गया।

इस मामले में कार्य इकाई परीक्षण आपको केवल सफलता की गलत भावना देते हैं। लेकिन क्या गलत हो गया? दो बातें: रीफैक्टरिंग लापरवाह था, और यूनिट परीक्षण बहुत अच्छे नहीं थे।


1

यदि आप "सही" को "परीक्षण पास" के रूप में परिभाषित करते हैं, तो परिभाषा के अनुसार , अप्रयुक्त व्यवहार को बदलना गलत नहीं है।

यदि किसी विशेष एज व्यवहार को परिभाषित किया जाना चाहिए , तो इसके लिए एक परीक्षण जोड़ें, यदि नहीं, तो क्या होता है इसकी परवाह नहीं करना ठीक है। यदि आप वास्तव में पांडित्यपूर्ण हैं, तो आप एक परीक्षण लिख सकते हैं जो यह जांचता trueहै कि उस किनारे के मामले में दस्तावेज़ में यह है कि आपको परवाह नहीं है कि व्यवहार क्या है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.