कोड को कुछ सामान्य कोड में कैसे परिशोधित करें?


16

पृष्ठभूमि

मैं एक चालू C # प्रोजेक्ट पर काम कर रहा हूं। मैं C # प्रोग्रामर नहीं हूं, मुख्य रूप से C ++ प्रोग्रामर हूं। इसलिए मुझे मूल रूप से आसान और रीफैक्टरिंग कार्य सौंपे गए।

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

मैं यहां बहस करने के लिए नहीं हूं कि क्या उन्होंने इसे सही किया है। जैसा कि मैं पुन: सक्रिय कर रहा हूं, मैं सोच रहा हूं कि क्या मैं इसे सही तरीके से कर रहा हूं क्योंकि मेरा रिफलेक्टेड कोड जटिल लगता है! यहाँ मेरा कार्य सरल उदाहरण के रूप में है।

मुसीबत

छह वर्गों के होते हैं: A, B, C, D, Eऔर F। सभी वर्गों का एक कार्य है ExecJob()। सभी छह कार्यान्वयन बहुत समान हैं। असल में, सबसे पहले A::ExecJob()लिखा गया था। तब थोड़ा अलग संस्करण की आवश्यकता थी B::ExecJob()जिसे कॉपी-पेस्ट-संशोधन द्वारा लागू किया गया था A::ExecJob()। जब एक और थोड़ा अलग संस्करण की आवश्यकता थी, C::ExecJob()लिखा गया था और इसी तरह। सभी छह कार्यान्वयनों में कुछ सामान्य कोड होते हैं, फिर कोड की कुछ अलग लाइनें, फिर कुछ सामान्य कोड और इसी तरह। यहाँ कार्यान्वयन का एक सरल उदाहरण है:

A::ExecJob()
{
    S1;
    S2;
    S3;
    S4;
    S5;
}

B::ExecJob()
{
    S1;
    S3;
    S4;
    S5;
}

C::ExecJob()
{
    S1;
    S3;
    S4;
}

जहां SNसटीक बयानों का एक समूह है।

उन्हें सामान्य बनाने के लिए, मैंने एक और वर्ग बनाया है और एक फ़ंक्शन में सामान्य कोड को स्थानांतरित किया है। यह नियंत्रित करने के लिए कि बयानों के समूह को निष्पादित करने के लिए पैरामीटर का उपयोग करना चाहिए:

Base::CommonTask(param)
{
    S1;
    if (param.s2) S2;
    S3;
    S4;
    if (param.s5) S5;
}

A::ExecJob() // A inherits Base
{
    param.s2 = true;
    param.s5 = true;
    CommonTask(param);
}

B::ExecJob() // B inherits Base
{
    param.s2 = false;
    param.s5 = true;
    CommonTask(param);
}

C::ExecJob() // C inherits Base
{
    param.s2 = false;
    param.s5 = false;
    CommonTask(param);
}

ध्यान दें कि, यह उदाहरण केवल तीन वर्गों और ओवरसाइप्लेट किए गए कथनों को नियोजित करता है। व्यवहार में, CommonTask()फ़ंक्शन उन सभी पैरामीटर की जाँच के साथ बहुत जटिल दिखता है और कई और कथन हैं। इसके अलावा, वास्तविक कोड में, कई- CommonTask()क्लोकिंग फ़ंक्शंस हैं।

हालाँकि सभी कार्यान्वयन समान कोड साझा कर रहे हैं और ExecJob()फ़ंक्शंस cuter लग रहे हैं, दो समस्याएं हैं जो मुझे परेशान कर रही हैं:

  • किसी भी बदलाव के लिए CommonTask(), सभी छह (और भविष्य में अधिक हो सकते हैं) सुविधाओं का परीक्षण करने की आवश्यकता है।
  • CommonTask()पहले से ही जटिल है। यह समय के साथ अधिक जटिल हो जाएगा।

क्या मैं इसे सही तरीके से कर रहा हूं?


मार्टिन फाउलर की रिफैक्टरिंग पुस्तक में रीफ़ैक्टरिंग कोड के लिए कई विशिष्ट तकनीकें हैं जो आपको उपयोगी लग सकती हैं।
एलन

जवाबों:


14

जी हाँ, आप बिल्कुल सही राह पर हैं!

मेरे अनुभव में, मैंने देखा कि जब चीजें जटिल होती हैं, तो परिवर्तन छोटे चरणों में होते हैं। आपने जो किया है वह विकास प्रक्रिया (या रीफैक्टरिंग प्रक्रिया) में चरण 1 है। यहाँ चरण 2 और चरण 3 है:

चरण 2

class Base {
  method ExecJob() {
    S1();
    S2();
    S3();
    S4();
    S5();
  }
  method S1() { //concrete implementation }
  method S3() { //concrete implementation }
  method S4() { //concrete implementation}
  abstract method S2();
  abstract method S5();
}

class A::Base {
  method S2() {//concrete implementation}
  method S5() {//concrete implementation}
}

class B::Base {
  method S2() { // empty implementation}
  method S5() {//concrete implementation}
}

class C::Base {
  method S2() { // empty implementation}
  method S5() { // empty implementation}
}

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

चरण 3

interface JobExecuter {
  void executeJob();
}
class A::JobExecuter {
  void executeJob(){
     helper = new Helper();
     helper->S1();
     helper->S2();
     helper->S3();
     helper->S4();
     helper->S5();
  }
}

class B::JobExecuter {
  void executeJob(){
     helper = new Helper();
     helper->S1();
     helper->S3();
     helper->S4();
     helper->S5();
  }
}

class C::JobExecuter {
  void executeJob(){
     helper = new Helper();
     helper->S1();
     helper->S3();
     helper->S4();
  }
}

class Base{
   void ExecJob(JobExecuter executer){
       executer->executeJob();
   }
}

class Helper{
    void S1(){//Implementation} 
    void S2(){//Implementation}
    void S3(){//Implementation}
    void S4(){//Implementation} 
    void S5(){//Implementation}
}

यह 'स्ट्रैटेजी डिज़ाइन पैटर्न' है और आपके मामले के लिए एक अच्छा विकल्प है। नौकरी को निष्पादित करने के लिए अलग-अलग रणनीतियां हैं और प्रत्येक वर्ग (ए, बी, सी) इसे अलग तरीके से लागू करता है।

मुझे यकीन है कि इस प्रक्रिया में एक चरण 4 या चरण 5 है या बहुत बेहतर रिफैक्टिंग दृष्टिकोण है। हालाँकि, यह आपको डुप्लिकेट कोड को समाप्त करने देगा और सुनिश्चित करेगा कि परिवर्तन स्थानीयकृत हैं।


"चरण 2" में उल्लिखित समाधान के साथ मुझे जो बड़ी समस्या है वह यह है कि एस 5 का ठोस कार्यान्वयन दो बार मौजूद है।
user281377

1
हाँ, कोड दोहराव समाप्त नहीं हुआ है! और यह अमूर्त का एक और संकेतक है जो काम नहीं कर रहा है। मैं सिर्फ यह बताना चाहता हूं कि इस प्रक्रिया के बारे में मैं कैसे सोचता हूं; कुछ बेहतर खोजने के लिए एक कदम दर कदम दृष्टिकोण।
ग्वेन नोव

1
+1 बहुत अच्छी रणनीति (और मैं पैटर्न के बारे में बात नहीं कर रहा हूं )!
जोर्डो

7

आप वास्तव में सही काम कर रहे हैं। मैं यह कहता हूं क्योंकि:

  1. यदि आपको किसी सामान्य कार्य कार्यक्षमता के लिए कोड को बदलने की आवश्यकता है, तो आपको इसे सभी 6 वर्गों में बदलने की आवश्यकता नहीं है, यदि आप इसे एक सामान्य वर्ग में नहीं लिखते हैं तो कोड शामिल होगा।
  2. कोड की लाइनों की संख्या कम हो जाएगी।

3

आप इस तरह के कोड को इवेंट संचालित डिज़ाइन (.NET विशेष रूप से) के साथ साझा करते हुए देखते हैं। सबसे साझा तरीका यह है कि अपने साझा व्यवहार को यथासंभव छोटे हिस्से में रखा जाए।

उच्च स्तरीय कोड को छोटे तरीकों के एक समूह का पुन: उपयोग करने दें, साझा आधार से उच्च स्तर के कोड को छोड़ दें।

आपके पत्ती / कंक्रीट के कार्यान्वयन में आपके पास बहुत सारे बॉयलर प्लेट होंगे। घबराओ मत, यह ठीक है। यह सब कोड प्रत्यक्ष, समझने में आसान है। सामान टूटने पर आपको इसे कभी-कभी पुनर्व्यवस्थित करना होगा, लेकिन इसे बदलना आसान होगा।

आपको उच्च स्तरीय कोड में बहुत सारे पैटर्न दिखाई देंगे। कभी-कभी वे वास्तविक होते हैं, अधिकांश समय वे नहीं होते हैं। वहाँ पाँच मापदंडों के "विन्यास" समान दिखते हैं , लेकिन वे नहीं हैं। वे तीन पूरी तरह से अलग रणनीति हैं।

यह भी नोट करना चाहते हैं कि आप यह सब रचना के साथ कर सकते हैं और विरासत के बारे में कभी चिंता नहीं करें। आप कम युग्मन होगा।


3

यदि मैं आप होता तो मैं शायद शुरुआत में 1 और कदम जोड़ देता: एक यूएमएल-आधारित अध्ययन।

सभी सामान्य भागों को एक साथ विलय करने वाले कोड को फिर से बनाना हमेशा सबसे अच्छा कदम नहीं होता है, एक अच्छे दृष्टिकोण की तुलना में अस्थायी समाधान की तरह लगता है।

एक यूएमएल योजना बनाता है, चीजों को सरल लेकिन प्रभावी रखें, अपनी परियोजना के बारे में कुछ बुनियादी अवधारणाओं को ध्यान में रखें जैसे कि "इस सॉफ्टवेयर को क्या करना है?" "सॉफ्टवेयर सार, मॉड्यूलर, एक्स्टेंसिबल, ... आदि आदि के इस टुकड़े को रखने का सबसे अच्छा तरीका क्या है?" "मैं अपने सबसे अच्छे रूप में इनकैप्सुलेशन को कैसे लागू कर सकता हूं?"

मैं सिर्फ यह कह रहा हूं: अभी कोड के बारे में परवाह मत करो, आपको केवल तर्क के बारे में परवाह करना है, जब आपके पास एक स्पष्ट तर्क है, तो बाकी सभी एक बहुत ही आसान काम बन सकता है, अंत में इस तरह का समस्याओं का सामना करना पड़ रहा है जो केवल एक बुरे तर्क के कारण होता है।


यह पहला कदम होना चाहिए, इससे पहले कि कोई भी रिफैक्टरिंग की जाए। जब तक कोड को मैप करने के लिए पर्याप्त नहीं समझा जाता (uml या विल्स के कुछ अन्य मानचित्र), तब तक रिफैक्टरिंग अंधेरे में स्थापत्य होगी।
कजकाई

3

बहुत पहला कदम, कोई फर्क नहीं पड़ता कि यह कहाँ जा रहा है, जाहिरा तौर पर बड़ी विधि A::ExecJobको छोटे टुकड़ों में तोड़ना चाहिए ।

इसलिए, के बजाय

A::ExecJob()
{
    S1; // many lines of code
    S2; // many lines of code
    S3; // many lines of code
    S4; // many lines of code
    S5; // many lines of code
}

आपको मिला

A::ExecJob()
{
    S1();
    S2();
    S3();
    S4();
    S5();
}

A:S1()
{
   // many lines of code
}

A:S2()
{
   // many lines of code
}

A:S3()
{
   // many lines of code
}

A:S4()
{
   // many lines of code
}

A:S5()
{
   // many lines of code
}

यहां से जाने के लिए कई संभावित रास्ते हैं। मेरा इस पर लेना: अपनी कक्षा की बेसहारा और एक्सकजोब वर्चुअल का आधार बनाएं और बी, सी, ... को बहुत अधिक कॉपी-पेस्ट किए बिना बनाना आसान हो जाता है - बस एक संशोधित के साथ एक्सकजोब (अब पांच लाइनर) बदलें संस्करण।

B::ExecJob()
{
    S1();
    S3();
    S4();
    S5();
}

लेकिन आखिर इतने सारे वर्ग क्यों हैं? हो सकता है कि आप उन सभी को एक एकल वर्ग के साथ बदल सकते हैं जिसमें एक निर्माता है जिसे यह बताया जा सकता है कि कौन से कार्य आवश्यक हैं ExecJob


2

मैं अन्य उत्तरों से सहमत हूं कि आपका दृष्टिकोण सही लाइनों के साथ है, हालांकि मुझे नहीं लगता है कि विरासत में सामान्य कोड को लागू करने का सबसे अच्छा तरीका है - मैं रचना पसंद करता हूं। C ++ अकसर किये गए सवाल से, जो मुझे पहले से कहीं बेहतर समझा सकता है: http://www.parashift.com/c++-faq/priv-inherit-vs-compos.html


1

सिर्फ इसलिए कि आप अपनी कक्षाओं द्वारा इस्तेमाल किया कार्यों के लिए एक आम जगह की जरूरत है - सबसे पहले, आप यह सुनिश्चित करें कि विरासत वास्तव में इस काम के लिए यहाँ सही उपकरण है बनाना चाहिए Aकरने के लिए Fमतलब नहीं है कि एक आम आधार वर्ग यहाँ सही बात है - कभी कभी एक अलग सहायक क्लास बेहतर काम करता है। यह हो सकता है, यह नहीं हो सकता है। यह ए से एफ और आपके सामान्य आधार वर्ग के बीच "ए-ए" संबंध होने पर निर्भर करता है, कृत्रिम नाम एएफ से कहना असंभव है। यहां आपको इस विषय से संबंधित एक ब्लॉग पोस्ट मिल रही है।

मान लेते हैं कि आप तय करते हैं कि सामान्य आधार वर्ग आपके मामले में सही है। फिर दूसरी चीज जो मैं करूंगा, वह यह सुनिश्चित करना कि आपके कोड टुकड़े S1 से S5 प्रत्येक आपके बेस क्लास के अलग-अलग तरीकों S1()से लागू किए गए हैं S5()। बाद में "ExecJob" फ़ंक्शन इस तरह दिखना चाहिए:

A::ExecJob()
{
    S1();
    S2();
    S3();
    S4();
    S5();
}

B::ExecJob()
{
    S1();
    S3();
    S4();
    S5();
}

C::ExecJob()
{
    S1();
    S3();
    S4();
}

जैसा कि आप अभी देख रहे हैं, चूंकि S1 से S5 सिर्फ मेथड कॉल हैं, कोई भी कोड किसी भी अधिक को ब्लॉक नहीं करता है, कोड दोहराव को लगभग पूरी तरह से हटा दिया गया है, और आपको किसी भी पैरामीटर की जांच करने की आवश्यकता नहीं है, जिससे आपको बढ़ती जटिलता की समस्या से बचा जा सकता है। अन्यथा।

अंत में, लेकिन केवल एक तीसरे चरण (!) के रूप में, आप उन सभी ExecJob विधियों को अपने बेस क्लास में संयोजित करने के बारे में सोच सकते हैं, जहां उन हिस्सों के निष्पादन को मापदंडों द्वारा नियंत्रित किया जा सकता है, जिस तरह से आपने इसे सुझाया, या उपयोग करके टेम्पलेट विधि पैटर्न। आपको अपने आप को यह तय करना होगा कि असली कोड के आधार पर यह आपके मामले में प्रयास के लायक है या नहीं।

लेकिन बड़े तरीकों को छोटे तरीकों में तोड़ने की बुनियादी तकनीक IMHO, पैटर्न लागू करने की तुलना में कोड दोहराव से बचने के लिए बहुत अधिक महत्वपूर्ण है।

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