2009-08-13 16 views
7

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

मैं इसकी आवश्यकता को समझता हूं, लेकिन मेरे सहकर्मियों ने ऐसा करने के तरीके से सहमत नहीं है, और न ही मुझे एक बेहतर विकल्प पता है।

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

मेरे साथ सबसे बड़ी समस्या इकाई परीक्षण में है। जब मैं कक्षा #include के वैश्विक चर का परीक्षण करना चाहता हूं तो मैं एक नकली वस्तु कैसे डाल सकता हूं?

यहाँ छद्म कोड में स्थिति है:

foo.h

#include "Task.h" 
class Foo : Task { 
public: 
    Foo(int n); 
    ~Foo(); 
    doStuff(); 
private: 
    // copy and assignment operators here 
} 

bar.h

#include <pthread.h> 
#include "Task.h" 

enum threadIndex { THREAD1 THREAD2 NUM_THREADS }; 
struct tThreadConfig { 
    char  *name, 
    Task  *taskptr, 
    pthread_t threadId, 
    ... 
}; 
void startTasks(); 

bar.cpp

#include "Foo.h" 

Foo foo1(42); 
Foo foo2(1337); 
Task task(7331); 

tThreadConfig threadConfig[NUM_THREADS] = { 
    { "Foo 1", &foo1, 0, ... }, 
    { "Foo 2", &foo2, 0, ... }, 
    { "Task", &task, 0, ... } 
}; 

void FSW_taskStart() { 
    for (int i = 0; i < NUMBER_OF_TASKS; i++) { 
     threadConfig[i].taskptr->createThread(); 
    } 
} 

यदि मुझे कम या ज्यादा कार्य चाहिए तो क्या होगा? Foo1 के निर्माता में तर्कों का एक अलग सेट? मुझे लगता है कि मुझे एक अलग बार.h और bar.cpp होना होगा, जो आवश्यकतानुसार बहुत अधिक काम की तरह लगता है।

+0

मुझे लगता है तुम्हारा मतलब 'और foo1' और नहीं '% foo1' (मापांक ऑपरेटर)? – DaveR

+0

धन्यवाद। कॉपी/पेस्ट –

उत्तर

4

यदि आप पहले इस तरह के कोड का परीक्षण करना चाहते हैं तो मैं Working Effectively With Legacy Code पढ़ने की सिफारिश करता हूं this भी देखें।

मूल रूप से नकली/नकली वस्तुओं और कार्यों को सम्मिलित करने के लिए लिंकर का उपयोग करना अंतिम उपाय होना चाहिए लेकिन अभी भी पूरी तरह वैध है।

हालांकि आप नियंत्रण के उलटा उपयोग भी कर सकते हैं, ढांचे के बिना यह क्लाइंट कोड को कुछ ज़िम्मेदारी धक्का दे सकता है। लेकिन यह वास्तव में परीक्षण में मदद करता है। उदाहरण FSW_taskStart()

tThreadConfig threadConfig[NUM_THREADS] = { 
    { "Foo 1", %foo1, 0, ... }, 
    { "Foo 2", %foo2, 0, ... }, 
    { "Task", %task, 0, ... } 
}; 

void FSW_taskStart(tThreadConfig configs[], size_t len) { 
    for (int i = 0; i < len; i++) { 
     configs[i].taskptr->createThread(); 
    } 
} 

void FSW_taskStart() { 
    FSW_taskStart(tThreadConfig, NUM_THREADS); 
} 

void testFSW_taskStart() { 
    MockTask foo1, foo2, foo3; 
    tThreadConfig mocks[3] = { 
      { "Foo 1", &foo1, 0, ... }, 
      { "Foo 2", &foo2, 0, ... }, 
      { "Task", &foo3, 0, ... } 
     }; 
    FSW_taskStart(mocks, 3); 
    assert(foo1.started); 
    assert(foo2.started); 
    assert(foo3.started); 
} 

का परीक्षण करने के लिए अब आप सुनिश्चित करने के लिए है कि समारोह वास्तव में धागे शुरू के रूप में आवश्यक के 'FSW_taskStart' के लिए आप कर रहे हैं धागे नकली संस्करण पारित कर सकते हैं कर सकते हैं कर सकते हैं। दुर्भाग्यवश आपको इस तथ्य पर भरोसा करना है कि मूल FSW_taskStart सही तर्क पारित करता है लेकिन अब आप अपने बहुत सारे कोड का परीक्षण कर रहे हैं।

+0

की बजाय मुझे पुनः लिखने के लिए यही मिलता है, ठीक है, मैं भाग्यशाली हूं कि यह अभी तक विरासत कोड नहीं है, इसलिए मैं अभी चीजें प्राप्त करना चाहता हूं। मेरे पास पहले से ही आईओसी दिमाग में था, लेकिन एक उदाहरण देखकर मुझे विश्वास था कि यह संभव है। इस तरह, उत्पादन कोड वैश्विक चर का उपयोग कर सकता है, ताकि आप भविष्यवाणी कर सकें कि वे स्मृति में कहां होंगे, लेकिन परीक्षण कोड में बार.cpp शामिल नहीं हो सकता है। मैं अभी भी यकीन नहीं कर रहा हूँ। जिस तरह से आपने प्रस्तावित किया है, मुझे किसी भी .h फ़ाइलों को बदलने की आवश्यकता नहीं होगी, यूनिट टेस्ट की परियोजना में अलग-अलग .cpp फ़ाइलों को शामिल करें? –

+1

+1। डीआईपी और आईओसी विशेष रूप से .. के लिए अच्छा आर्किटेक्चर पैटर्न है। यदि आप कार्य हस्ताक्षर को संशोधित नहीं कर सकते हैं, तो आप कुछ संकेत के साथ कर सकते हैं। अपनी संदर्भ वस्तु को पारित करने के बजाय आप इसे वापस करने वाले फ़ंक्शन को कॉल कर सकते हैं। यह फ़ंक्शन आईओसी का उपयोग कर सकता है और इसके प्रारंभिकरण के आधार पर वास्तविक संदर्भ ऑब्जेक्ट्स या मॉक ऑब्जेक्ट को वापस कर सकता है ... – neuro

+1

@drhorrible, पुस्तक का नाम थोड़ा भ्रामक हो सकता है ;-) विरासत कोड की उनकी परिभाषा इकाई परीक्षण के बिना कोड है। यह मुख्य रूप से विभिन्न कठिन परिदृश्यों में इकाई परीक्षण के लिए तकनीकों का संग्रह है। – iain

-1

आप malloc का उपयोग कर स्मृति आवंटित कर सकते हैं और फिर नए ऑपरेटर उस स्थिति

void* mem = malloc(3*sizeof(SomeClass)); 
SomeClass *a = new(mem) SomeClass(); 
mem += sizeof(SomeClass); 
SomeClass *b = new(mem) SomeClass(); 
mem += sizeof(SomeClass); 
SomeClass *c = new(mem) SomeClass(); 

ताकि आप malloc कर सकते हैं सभी स्मृति तो यह आवंटन के रूप में आप चाहते हैं पर वस्तु बनाने के लिए मिलता है। नोट: सुनिश्चित करें कि आप मैन्युअल रूप से deconstruction को कॉल करते हैं क्योंकि जब आप

+0

-1, मॉलोक इन वातावरणों में नए के रूप में प्रतिबंधित है। यह लाभ के बिना दर्द जोड़ता है। – MSalters

+0

तो शुरुआत में स्मृति के एक ब्लॉक को mallocing प्रतिबंधित है? एक मूर्ख नियम माना जाता है। – Lodle

3

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

प्रत्येक थ्रेड मुख्य फ़ंक्शन एक मानचित्र को निर्भरता (ड्राइवर, मेलबॉक्स, इत्यादि) से गुजरता है और उन वर्गों में संग्रहीत करता है जो उन्हें उपयोग करेंगे (कुछ वैश्विक चर तक पहुंचने के बजाय)।

प्रत्येक पर्यावरण (लक्ष्य, सिम्युलेटर, यूनिट टेस्ट ...) के लिए आप एक "कॉन्फ़िगरेशन" फ़ंक्शन बनाते हैं जो सभी आवश्यक ऑब्जेक्ट्स, ड्राइवरों और सभी थ्रेड बनाता है, जिससे थ्रेड्स उनकी निर्भरताओं की सूची देता है। उदाहरण के लिए, लक्ष्य कॉन्फ़िगरेशन एक यूएसबी ड्राइवर बना सकता है और इसे कुछ कॉमम्स थ्रेड में इंजेक्ट कर सकता है, जबकि कॉमम्स यूनिट टेस्ट कॉन्फ़िगरेशन एक स्टब यूएसबी ड्राइवर बना सकता है जो परीक्षण नियंत्रित करता है।

यदि आपको महत्वपूर्ण चर के लिए इस "पारदर्शिता" की ज़रूरत है, तो उनके लिए कक्षाएं बनाएं, जो उन्हें एक ज्ञात पते पर रखेगी, और जहां आवश्यक हो वहां इन कक्षाओं को इंजेक्ट करें।

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

मोटे तौर पर:

// Config specific to one target. 
void configure_for_target_blah(System_config& cfg) 
{ // create drivers 
    cfg.drivers.push_back("USB", new USB_driver(...)) 
    // create threads 
    Thread_cfg t; 
    t.main = comms_main; // main function for that thread 
    t.drivers += "USB"; // List of driver names to pass as dependencies 
    cfg.threads += t; 
} 

// Main function for the comms thread. 
void comms_main(Thread_config& cfg) 
{ 
    USB_driver* usb = cfg.get_driver("USB"); 
    // check for null, then store it and use it... 
} 

// Same main for all configs. 
int main() 
{ 
    System_config& cfg; 
    configure_for_target_blah(cfg); 
    //for each cfg.drivers 
    // initialise driver 
    //for each cfg.threads 
    // create_thread with the given main, and pass a Thread_config with dependencies 
} 
संबंधित मुद्दे