क्या बहुत अधिक मुखर लिखना संभव है?


33

मैं assertC ++ कोड में चेक लिखने का एक बड़ा प्रशंसक हूं, जो विकास के दौरान मामलों को पकड़ने का एक तरीका है जो संभवतः नहीं हो सकता है, लेकिन मेरे कार्यक्रम में तर्क बग के कारण होता है। यह सामान्य रूप से एक अच्छा अभ्यास है।

हालाँकि, मैंने देखा है कि मेरे द्वारा लिखे गए कुछ फ़ंक्शंस (जो एक जटिल वर्ग का हिस्सा हैं) में 5+ एसेर्स हैं जो यह महसूस करते हैं कि यह संभवतः पठनीयता और स्थिरता के संदर्भ में एक बुरा प्रोग्रामिंग अभ्यास हो सकता है। मुझे लगता है कि यह अभी भी बहुत अच्छा है, क्योंकि प्रत्येक को मुझे कार्यों के पूर्व और बाद की स्थितियों के बारे में सोचने की आवश्यकता होती है और वे वास्तव में बग्स को पकड़ने में मदद करते हैं। हालाँकि, मैं सिर्फ यह पूछना चाहता था कि क्या बड़ी संख्या में जाँच आवश्यक होने पर मामलों में तर्क त्रुटियों को पकड़ने के लिए बेहतर प्रतिमान हैं।

Emacs टिप्पणी : चूंकि Emacs मेरी पसंद का IDE है, इसलिए मैंने इसे मुखर बयानों से थोड़ा ग्रे कर दिया है जो अव्यवस्था की भावना को कम करने में मदद करता है जो वे प्रदान कर सकते हैं। यहाँ मैं अपने .emacs फ़ाइल में जोड़ रहा हूँ:

; gray out the "assert(...)" wrapper
(add-hook 'c-mode-common-hook
  (lambda () (font-lock-add-keywords nil
    '(("\\<\\(assert\(.*\);\\)" 1 '(:foreground "#444444") t)))))

; gray out the stuff inside parenthesis with a slightly lighter color
(add-hook 'c-mode-common-hook
  (lambda () (font-lock-add-keywords nil
    '(("\\<assert\\(\(.*\);\\)" 1 '(:foreground "#666666") t)))))

3
मुझे यह स्वीकार करना चाहिए कि यह एक ऐसा सवाल है जिसने मेरे दिमाग को बार-बार पार किया है। इस पर दूसरों की राय सुनने के इच्छुक हैं।
कप्तान सेंसिबल

जवाबों:


45

मैंने ऐसे सैकड़ों बग देखे हैं जिन्हें अगर कोई और जोर से लिखता तो तेजी से हल हो जाता, और एक भी ऐसा नहीं जिसे कम लिखकर जल्दी हल किया जाता ।

[सी] ould [बहुत अधिक जोर] संभावित रूप से पठनीयता और स्थिरता के संदर्भ में एक बुरा प्रोग्रामिंग अभ्यास हो सकता है [?]

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

इसके अलावा, मेरे अनुभव में, हमेशा से ही सुधार होता है, ठीक वैसे ही जैसे कि यूनिट टेस्ट से होता है। मुखर एक पवित्रता प्रदान करते हैं कि कोड का उपयोग किया जा रहा है जिस तरह से इसका उपयोग करने का इरादा था।


1
अच्छा उत्तर। मैंने इस सवाल के साथ एक विवरण भी जोड़ा कि मैं Emacs के साथ पठनीयता कैसे सुधारता हूं।
एलन ट्यूरिंग

2
"यह मेरा अनुभव है कि जो लोग अच्छा लिखते हैं वे भी पठनीय कोड लिखते हैं" << उत्कृष्ट बिंदु। कोड को पठनीय बनाना व्यक्तिगत प्रोग्रामर के लिए है क्योंकि यह वह तकनीक है जो वह है या उसे उपयोग करने की अनुमति नहीं है। मैंने देखा है कि अच्छी तकनीकें गलत हाथों में अपठनीय हो जाती हैं, और यहां तक ​​कि जो सबसे बुरा माना जाएगा वह यह है कि अमूर्त और टिप्पणी के उचित उपयोग से बुरी तकनीक पूरी तरह से स्पष्ट, यहां तक ​​कि सुरुचिपूर्ण हो जाएगी।
ग्रेग जैक्सन

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

@CodesInChaos, तर्क के अनुसार, यह समस्या के सूत्रीकरण में एक त्रुटि की ओर इशारा करता है - अर्थात, बग डिज़ाइन में था, इसलिए अभिकथन और (अन्य) कोड के बीच बेमेल है।
लॉरेंस

12

क्या बहुत अधिक मुखर लिखना संभव है?

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

रन-टाइम और बाइनरी फ़ुटप्रिंट ओवरहेड को ध्यान में रखें

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

मुझे उस फ़ंक्शन की लागत के सापेक्ष अभिकथन की लागत का अनुमान लगाना पसंद है जो इसमें निहित है। निम्नलिखित दो उदाहरणों पर विचार करें।

// Precondition:  queue is not empty
// Invariant:     queue is sorted
template <typename T>
const T&
sorted_queue<T>::max() const noexcept
{
  assert(!this->data_.empty());
  assert(std::is_sorted(std::cbegin(this->data_), std::cend(this->data_)));
  return this->data_.back();
}

समारोह अपने आप में एक है हे (1) आपरेशन लेकिन दावे के लिए खाते हे ( एन ) भूमि के ऊपर। मुझे नहीं लगता कि आप ऐसे चेक को सक्रिय करना चाहेंगे जब तक कि बहुत विशेष परिस्थितियों में नहीं।

यहां इसी तरह के दावे के साथ एक और समारोह है।

// Requirement:   op : T -> T is monotonic [ie x <= y implies op(x) <= op(y)]
// Invariant:     queue is sorted
// Postcondition: each item x in the queue is replaced by op(x)
template <typename T>
template <typename FuncT>
void
sorted_queue<T>::apply_monotonic_function(FuncT&& op)
{
  assert(std::is_sorted(std::cbegin(this->data_), std::cend(this->data_)));
  std::transform(std::cbegin(this->data_), std::cend(this->data_),
                 std::begin(this->data_), std::forward<FuncT>(op));
  assert(std::is_sorted(std::cbegin(this->data_), std::cend(this->data_)));
}

फ़ंक्शन स्वयं एक O ( n ) ऑपरेशन है, इसलिए यह जोर के लिए एक अतिरिक्त O ( n ) ओवरहेड जोड़ने के लिए बहुत कम दर्द होता है । एक फ़ंक्शन को एक छोटे से धीमा करना (इस मामले में, शायद 3 से कम) निरंतर कारक एक ऐसी चीज है जिसे हम आमतौर पर डिबग बिल्ड में रख सकते हैं लेकिन शायद रिलीज़ बिल्ड में नहीं।

अब इस उदाहरण पर विचार करें।

// Precondition:  queue is not empty
// Invariant:     queue is sorted
// Postcondition: last element is removed from queue
template <typename T>
void
sorted_queue<T>::pop_back() noexcept
{
  assert(!this->data_.empty());
  return this->data_.pop_back();
}

जबकि कई लोग संभवतः इस O (1) जोर के साथ पिछले उदाहरण में दो O ( n ) दावे के साथ अधिक सहज होंगे , वे मेरे विचार से नैतिक रूप से समकक्ष हैं। प्रत्येक फ़ंक्शन की जटिलता के आदेश पर ओवरहेड जोड़ता है।

अंत में, "वास्तव में सस्ते" दावे हैं जो उस फ़ंक्शन की जटिलता से प्रभावित होते हैं जिसमें वे निहित हैं।

// Requirement:   cmp : T x T -> bool is a strict weak ordering
// Precondition:  queue is not empty
// Postcondition: if x is returned, then there is no y in the queue
//                such that cmp(x, y)
template <typename T>
template <typename CmpT>
const T&
sorted_queue<T>::max(CmpT&& cmp) const
{
  assert(!this->data_.empty());
  const auto pos = std::max_element(std::cbegin(this->data_),
                                    std::cend(this->data_),
                                    std::forward<CmpT>(cmp));
  assert(pos != std::cend(this->data_));
  return *pos;
}

यहाँ, हमारे पास O ( n ) फ़ंक्शन में दो O (1) दावे हैं । यह शायद रिलीज ओवरडल्स में भी इस ओवरहेड को रखने के लिए कोई समस्या नहीं होगी।

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

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

#define MY_ASSERT_IMPL(COST, CONDITION)                                       \
  (                                                                           \
    ( ((COST) <= (MY_ASSERT_COST_LIMIT)) && !(CONDITION) )                    \
      ? ::my::assertion_failed(__FILE__, __LINE__, __FUNCTION__, # CONDITION) \
      : (void) 0                                                              \
  )

#define MY_ASSERT_LOW(CONDITION)                                              \
  MY_ASSERT_IMPL(MY_ASSERT_COST_LOW, CONDITION)

#define MY_ASSERT_MEDIUM(CONDITION)                                           \
  MY_ASSERT_IMPL(MY_ASSERT_COST_MEDIUM, CONDITION)

#define MY_ASSERT_HIGH(CONDITION)                                             \
  MY_ASSERT_IMPL(MY_ASSERT_COST_HIGH, CONDITION)

#define MY_ASSERT_COST_NONE    0
#define MY_ASSERT_COST_LOW     1
#define MY_ASSERT_COST_MEDIUM  2
#define MY_ASSERT_COST_HIGH    3
#define MY_ASSERT_COST_ALL    10

#ifndef MY_ASSERT_COST_LIMIT
#  define MY_ASSERT_COST_LIMIT MY_ASSERT_COST_MEDIUM
#endif

namespace my
{

  [[noreturn]] extern void
  assertion_failed(const char * filename, int line, const char * function,
                   const char * message) noexcept;

}

अब आप तीन मैक्रोज़ का उपयोग कर सकते हैं MY_ASSERT_LOW, MY_ASSERT_MEDIUMऔर MY_ASSERT_HIGHमानक पुस्तकालय के बजाय "एक आकार सभी को फिट बैठता है" assertस्थूल के लिए मैक्रो का उपयोग किया जाता है, जो न तो हावी होते हैं और न ही हावी होते हैं और क्रमशः उनके युक्त फ़ंक्शन की जटिलता पर हावी होते हैं। जब आप सॉफ़्टवेयर का निर्माण करते हैं, तो आप प्री-प्रोसेसर प्रतीक को यह निर्धारित करने के लिए पूर्व-परिभाषित कर सकते MY_ASSERT_COST_LIMITहैं कि किस प्रकार के दावे निष्पादन योग्य बनाने चाहिए। स्थिरांक MY_ASSERT_COST_NONEऔर MY_ASSERT_COST_ALLकिसी भी मुखर मैक्रो के अनुरूप नहीं होते हैं और इसका उपयोग मूल्यों के रूप MY_ASSERT_COST_LIMITमें सभी अभिक्रियाओं को बंद करने या क्रमशः करने के लिए किया जाता है।

हम यहां इस धारणा पर भरोसा कर रहे हैं कि एक अच्छा संकलक किसी भी कोड को उत्पन्न नहीं करेगा

if (false_constant_expression && run_time_expression) { /* ... */ }

और परिवर्तन

if (true_constant_expression && run_time_expression) { /* ... */ }

में

if (run_time_expression) { /* ... */ }

मेरा मानना ​​है कि आजकल एक सुरक्षित धारणा है।

आप ऊपर कोड में और सुधार करने के लिए के बारे में कर रहे हैं, की तरह संकलक विशेष एनोटेशन पर विचार __attribute__ ((cold))पर my::assertion_failedया __builtin_expect(…, false)पर !(CONDITION)पारित कर दिया दावे की भूमि के ऊपर कम करने के लिए। रिलीज़ बिल्ड में, आप किसी नैदानिक ​​संदेश को खोने की असुविधा पर फ़ुट-प्रिंट को कम करने के लिए फ़ंक्शन कॉल को my::assertion_failedकुछ के साथ बदलने पर भी विचार कर सकते हैं __builtin_trap

इस प्रकार के अनुकूलन वास्तव में केवल एक सस्ते फ़ंक्शन में प्रासंगिक हैं (जैसे दो पूर्णांक जो पहले से ही तर्क के रूप में दिए गए हैं) की तुलना एक फ़ंक्शन में है जो स्वयं बहुत कॉम्पैक्ट है, सभी संदेश स्ट्रिंग को शामिल करके संचित बाइनरी के अतिरिक्त आकार पर विचार नहीं करता है।

इस कोड की तुलना करें

int
positive_difference_1st(const int a, const int b) noexcept
{
  if (!(a > b))
    my::assertion_failed(__FILE__, __LINE__, __FUNCTION__, "!(a > b)");
  return a - b;
}

निम्नलिखित विधानसभा में संकलित है

_ZN4test23positive_difference_1stEii:
.LFB0:
        .cfi_startproc
        cmpl    %esi, %edi
        jle     .L5
        movl    %edi, %eax
        subl    %esi, %eax
        ret
.L5:
        subq    $8, %rsp
        .cfi_def_cfa_offset 16
        movl    $.LC0, %ecx
        movl    $_ZZN4test23positive_difference_1stEiiE12__FUNCTION__, %edx
        movl    $50, %esi
        movl    $.LC1, %edi
        call    _ZN2my16assertion_failedEPKciS1_S1_
        .cfi_endproc
.LFE0:

जबकि निम्नलिखित कोड

int
positive_difference_2nd(const int a, const int b) noexcept
{
  if (__builtin_expect(!(a > b), false))
    __builtin_trap();
  return a - b;
}

यह विधानसभा देता है

_ZN4test23positive_difference_2ndEii:
.LFB1:
        .cfi_startproc
        cmpl    %esi, %edi
        jle     .L8
        movl    %edi, %eax
        subl    %esi, %eax
        ret
        .p2align 4,,7
        .p2align 3
.L8:
        ud2
        .cfi_endproc
.LFE1:

जिसके साथ मैं ज्यादा सहज महसूस करता हूं। (उदाहरण का उपयोग करते हुए जीसीसी 5.3.0 के साथ परीक्षण किया गया -std=c++14, -O3और -march=native4.3.3-2-आर्क x86_64 जीएनयू / लिनक्स पर झंडे। ऊपर के टुकड़े में नहीं दिखाया गया की घोषणाओं हैं test::positive_difference_1stऔर test::positive_difference_2ndजो मैं जोड़ा __attribute__ ((hot))है। my::assertion_failedसाथ घोषित किया गया था __attribute__ ((cold))।)

फ़ंक्शन में उन पर निर्भरता को बढ़ाएँ

मान लें कि आपके पास निर्दिष्ट अनुबंध के साथ निम्नलिखित फ़ंक्शन है।

/**
 * @brief
 *         Counts the frequency of a letter in a string.
 *
 * The frequency count is case-insensitive.
 *
 * If `text` does not point to a NUL terminated character array or `letter`
 * is not in the character range `[A-Za-z]`, the behavior is undefined.
 *
 * @param text
 *         text to count the letters in
 *
 * @param letter
 *         letter to count
 *
 * @returns
 *         occurences of `letter` in `text`
 *
 */
std::size_t
count_letters(const char * text, int letter) noexcept;

लिखने के बजाय

assert(text != nullptr);
assert((letter >= 'A' && letter <= 'Z') || (letter >= 'a' && letter <= 'z'));
const auto frequency = count_letters(text, letter);

प्रत्येक कॉल-साइट पर, उस तर्क को एक बार परिभाषा में रखें count_letters

std::size_t
count_letters(const char *const text, const int letter) noexcept
{
  assert(text != nullptr);
  assert((letter >= 'A' && letter <= 'Z') || (letter >= 'a' && letter <= 'z'));
  auto frequency = std::size_t {};
  // TODO: Figure this out...
  return frequency;
}

और आगे की हलचल के बिना इसे कॉल करें।

const auto frequency = count_letters(text, letter);

इसके निम्नलिखित फायदे हैं।

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

स्पष्ट नुकसान यह है कि आपको निदान संदेश में कॉल-साइट का स्रोत स्थान नहीं मिलेगा। मेरा मानना ​​है कि यह एक मामूली बात है। एक अच्छा डिबगर आपको अनुबंध के उल्लंघन की उत्पत्ति का आसानी से पता लगाने में सक्षम होने में सक्षम होना चाहिए।

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

bool
good() const noexcept;

यह पूछने की अनुमति देता है कि क्या यह पुनरावृत्ति करने वाले के लिए सुरक्षित है। (बेशक, व्यवहार में, यह लगभग हमेशा केवल यह गारंटी देना संभव है कि यह itter को निष्क्रिय करने के लिए सुरक्षित नहीं होगा । लेकिन मेरा मानना ​​है कि आप अभी भी इस तरह के फ़ंक्शन के साथ बहुत सारे कीड़े पकड़ सकते हैं।) इसके बजाय मेरे सभी कोड को लिट करने की। assert(iter.good())बयानों के साथ पुनरावृत्ति का उपयोग करता है , मैं इसे पुनरावृत्ति के कार्यान्वयन में assert(this->good())पहली पंक्ति के रूप में एक ही operator*डालूँगा।

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

फैक्टर सामान्य स्थिति

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

template <typename MatrixT>
auto
cholesky_decompose(MatrixT&& m)
{
  assert(is_square(m) && is_symmetric(m));
  // TODO: Somehow decompose that thing...
}

यह अधिक उपयोगी त्रुटि संदेश भी देगा।

cholesky.hxx:357: cholesky_decompose: assertion failed: is_symmetric(m)

से बहुत अधिक मदद करता है, कहते हैं

detail/basic_ops.hxx:1289: fast_compare: assertion failed: m(i, j) == m(j, i)

वास्तव में परीक्षण किया गया था, यह पता लगाने के लिए आपको संदर्भ में स्रोत कोड को देखना होगा।

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

इस उद्देश्य के लिए, privateमुझे पारंपरिक रूप से कॉल करने वाले सदस्य फ़ंक्शन को परिभाषित करने के लिए उपयोगी लगा class_invaraiants_hold_। मान लीजिए कि आप फिर से लागू कर रहे थे std::vector(क्योंकि हम सभी जानते हैं कि यह पर्याप्त रूप से अच्छा नहीं है।), इसमें इस तरह का कार्य हो सकता है।

template <typename T>
bool
vector<T>::class_invariants_hold_() const noexcept
{
  if (this->size_ > this->capacity_)
    return false;
  if ((this->size_ > 0) && (this->data_ == nullptr))
    return false;
  if ((this->capacity_ == 0) != (this->data_ == nullptr))
    return false;
  return true;
}

इस बारे में कुछ बातें नोटिस करें।

  • प्रेडिकेट कार्य स्वयं है constऔर noexcept, दिशानिर्देशों के अनुसार कि अभिक्रियाओं के दुष्प्रभाव नहीं होंगे। अगर यह समझ में आता है, तो इसे भी घोषित करें constexpr
  • विधेय खुद कुछ भी मुखर नहीं करता है। इसे मुखर रूप में कहा जाता है , जैसे कि assert(this->class_invariants_hold_())। इस तरह, यदि अभिकथनों को संकलित किया जाता है, तो हम सुनिश्चित कर सकते हैं कि कोई रन-टाइम ओवरहेड न हो।
  • फ़ंक्शन के अंदर नियंत्रण प्रवाह एक बड़ी अभिव्यक्ति के बजाय ifशुरुआती returnएस के साथ कई बयानों में टूट गया है । इससे डिबगर में फ़ंक्शन के माध्यम से कदम रखना आसान हो जाता है और पता चलता है कि मुखरता से आग लगने पर हमलावर का क्या हिस्सा टूट गया था।

मूर्खतापूर्ण बातों पर जोर न दें

कुछ चीजें सिर्फ समझ में नहीं आती हैं।

auto numbers = std::vector<int> {};
numbers.push_back(14);
numbers.push_back(92);
assert(numbers.size() == 2);  // silly
assert(!numbers.empty());     // silly and redundant

इन दावों के बारे में तर्क करने के लिए कोड को एक छोटे से अधिक पठनीय या आसान भी नहीं बनाते हैं। हर C ++ प्रोग्रामर को पर्याप्त आत्मविश्वास होना चाहिए कि कैसे std::vectorकाम किया जाए कि उपरोक्त कोड को देखकर ही सही हो। मैं यह नहीं कह रहा हूं कि आपको कंटेनर के आकार पर जोर नहीं देना चाहिए। यदि आपने कुछ गैर-तुच्छ नियंत्रण प्रवाह का उपयोग करके तत्वों को जोड़ा या हटाया है, तो इस तरह का एक जोर उपयोगी हो सकता है। लेकिन अगर यह केवल वही दोहराता है जो ऊपर दिए गए गैर-अभिकथन कोड में लिखा गया था, तो कोई मूल्य प्राप्त नहीं हुआ है।

इसके अलावा पुस्तकालय कार्यों सही ढंग से काम करते हैं कि जोर नहीं है।

auto w = widget {};
w.enable_quantum_mode();
assert(w.quantum_mode_enabled());  // probably silly

यदि आप लाइब्रेरी पर भरोसा करते हैं, तो इसके बजाय दूसरे लाइब्रेरी का उपयोग करने पर विचार करें।

दूसरी ओर, यदि लाइब्रेरी का प्रलेखन 100% स्पष्ट नहीं है और आप स्रोत कोड को पढ़कर इसके अनुबंधों के बारे में विश्वास हासिल करते हैं, तो यह उस "अनुमानित अनुबंध" पर जोर देने के लिए बहुत मायने रखता है। यदि यह लाइब्रेरी के भविष्य के संस्करण में टूट गया है, तो आप जल्दी से नोटिस करेंगे।

auto w = widget {};
// After reading the source code, I have concluded that quantum mode is
// always off by default but this isn't documented anywhere.
assert(!w.quantum_mode_enabled());

यह निम्नलिखित समाधान से बेहतर है जो आपको यह नहीं बताएगा कि आपकी धारणा सही थी या नहीं।

auto w = widget {};
if (w.quantum_mode_enabled())
  {
    // I don't think that quantum mode is ever enabled by default but
    // I'm not sure.
    w.disable_quantum_mode();
  }

कार्यक्रम के तर्क को लागू करने के लिए दावे का दुरुपयोग न करें

कभी-कभी उन बगों को उजागर करने के लिए उपयोग किया जाना चाहिए जो आपके आवेदन को तुरंत मारने के योग्य हैं। उन्हें किसी अन्य शर्त को सत्यापित करने के लिए उपयोग नहीं किया जाना चाहिए, भले ही उस स्थिति के लिए उपयुक्त प्रतिक्रिया भी तुरंत छोड़ दी जाए।

इसलिए, यह लिखें ...

if (!server_reachable())
  {
    log_message("server not reachable");
    shutdown();
  }

…उसके बदले में।

assert(server_reachable());

इसके अलावा कभी भी अविश्वासित इनपुट को सत्यापित करने के लिए या आप std::mallocनहीं थे की जाँच returnकरें nullptr। यहां तक ​​कि अगर आपको पता है कि आप कभी भी दावे को बंद नहीं करेंगे, यहां तक ​​कि रिलीज़ बिल्ड में भी, एक पाठक पाठक को सूचित करता है कि यह कुछ ऐसा चेक करता है जो हमेशा सच होता है, यह देखते हुए कि प्रोग्राम बग-फ्री है और इसका कोई भी साइड-इफेक्ट नहीं है। यदि यह उस तरह का संदेश नहीं है जिसे आप संवाद करना चाहते हैं, तो throwएक अपवाद के रूप में वैकल्पिक त्रुटि हैंडलिंग तंत्र का उपयोग करें । यदि आपको अपने गैर-अभिकथन की जाँच के लिए मैक्रो रैपर लगाना सुविधाजनक लगता है, तो एक लेखन को आगे बढ़ाएं। बस इसे "मुखर", "मान", "आवश्यकता", "सुनिश्चित" या ऐसा कुछ न कहें। इसका आंतरिक तर्क इसके समान हो सकता है assert, सिवाय इसके कि इसे कभी संकलित नहीं किया जाता है, निश्चित रूप से।

अधिक जानकारी

मुझे जॉन लैकोस की बात डिफेंसिव प्रोग्रामिंग डेम राइट में मिली, जो CppCon'14 ( 1 सेंट भाग , 2 एन डी भाग ) में बहुत ही ज्ञानवर्धक है। वह इस बात को अनुकूलित करने का विचार लेता है कि कौन से दावे सक्षम हैं और कैसे इस जवाब में मैंने पहले भी असफल अपवादों पर प्रतिक्रिया दी।


4
Assertions are great, but ... you will turn them off sooner or later.- उम्मीद है कि जितनी जल्दी हो, कोड जहाजों से पहले। जिन चीजों को प्रोग्राम को उत्पादन में मरने की ज़रूरत है, उन्हें "वास्तविक" कोड का हिस्सा होना चाहिए, न कि मुखरता में।
ब्लरफ्ल

4

मुझे लगता है कि समय के साथ मैं कम लिखता हूं क्योंकि उनमें से कई "संकलक काम कर रहे हैं" और "पुस्तकालय काम कर रहे हैं"। एक बार जब आप यह सोचना शुरू कर देते हैं कि आप क्या परीक्षण कर रहे हैं, तो मुझे संदेह है कि आप कम जोर डालेंगे।

उदाहरण के लिए, एक विधि जो (कहती है) किसी संग्रह में कुछ जोड़ती है, यह दावा करने की आवश्यकता नहीं होनी चाहिए कि संग्रह मौजूद है - यह आम तौर पर संदेश के मालिक वर्ग का एक पूर्व शर्त है या यह एक घातक त्रुटि है जिसे उपयोगकर्ता को वापस करना चाहिए । इसलिए इसे एक बार जांच लें, बहुत जल्दी, फिर मान लें।

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


4
जब आप कहते हैं कि मुझे आपकी बात नहीं मिलती है "तो यह आम तौर पर या तो उस वर्ग का एक पूर्वसूचक है जो संदेश का मालिक है या यह एक घातक त्रुटि है जिसे उपयोगकर्ता को वापस करना चाहिए। इसलिए इसे एक बार पहले ही देख लें, फिर मान लें। " क्या आप अपनी मान्यताओं को सत्यापित करने के लिए नहीं हैं?
5gon12eder

4

बहुत कम मुखर: सौभाग्य उस कोड को छिपा मान्यताओं के साथ riddled बदल रहा है।

बहुत से दावे: पठनीयता की समस्या पैदा कर सकते हैं और संभावित रूप से कोड गंध हो सकते हैं - क्या वर्ग, फ़ंक्शन, एपीआई को सही तरीके से डिज़ाइन किया गया है जब यह मुखर बयानों में बहुत सारी धारणाएं रखता है?

ऐसे दावे भी हो सकते हैं जो वास्तव में किसी भी चीज़ की जाँच नहीं करते हैं या प्रत्येक फ़ंक्शन में कंपाइलर सेटिंग्स जैसी चीज़ों की जाँच करते हैं: /

मीठे स्थान के लिए निशाना लगाओ, लेकिन कोई कम नहीं (जैसा कि किसी और ने पहले ही कहा था, "अधिक" जोर देने से बहुत कम हानिकारक है या भगवान हमारी मदद करते हैं - कोई नहीं)।


3

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

यह पठनीयता से थोड़ा सा आकर्षित करेगा, विशेष रूप से क्योंकि मुझे नहीं लगता कि आप किसी वर्ग के लिए एक कांस्टेबल होने के लिए एक लंबो (सी ++ 0x) में एनोटेट नहीं कर सकते हैं, जिसका अर्थ है कि आप उसके लिए लंबोदा का उपयोग नहीं कर सकते हैं

ओवरकिल अगर आप मुझसे पूछें, लेकिन अगर मैं जोर देने के कारण पॉल्यूशन का एक निश्चित स्तर देखना शुरू कर दूंगा तो मैं दो चीजों से सावधान हो जाऊंगा:

  • यह सुनिश्चित करना कि मुखर में कोई दुष्प्रभाव नहीं हो रहा है (ऊपर बताए अनुसार निर्माण द्वारा प्रदान किया गया है)
  • विकास परीक्षण के दौरान प्रदर्शन; इसे मुखर सुविधा में स्तर (जैसे लॉगिंग) जोड़कर संबोधित किया जा सकता है; इसलिए आप प्रदर्शन को बेहतर बनाने के लिए कुछ बिल्डरों को विकास बिल्ड से निष्क्रिय कर सकते हैं

2
पवित्र बकवास आपको "निश्चित" शब्द और उसकी व्युत्पत्ति पसंद है। मैं 8 उपयोगों की गिनती करता हूं।
केसी पैटन

हाँ, माफ करना, मैं शब्दों पर बहुत अधिक - निश्चित, धन्यवाद
lurscher

2

मैंने C # में C C + से अधिक लिखा है, लेकिन दोनों भाषाएँ बहुत अलग नहीं हैं। .Net में मैं एसेर्ट्स का उपयोग उन स्थितियों के लिए करता हूं जो नहीं होनी चाहिए, लेकिन मैं अक्सर अपवादों को भी फेंक देता हूं जब जारी रखने का कोई तरीका नहीं होता है। VS2010 डिबगर मुझे एक अपवाद पर बहुत सारी अच्छी जानकारी दिखाता है, फिर चाहे रिलीज बिल्ड कितना भी अनुकूलित क्यों न हो। यदि आप कर सकते हैं तो यूनिट परीक्षणों को जोड़ना भी एक अच्छा विचार है। कभी-कभी लॉगिंग भी एक डीबगिंग सहायता के रूप में एक अच्छी बात है।

तो, क्या बहुत सारे दावे हो सकते हैं? हाँ। एबॉर्ट / इग्नोर / कंटीन्यू के बीच एक मिनट में 15 बार चुनने पर गुस्सा आता है। एक अपवाद केवल एक बार फेंका जाता है। उस बिंदु को परिमाणित करना कठिन है जिस पर बहुत अधिक मुखर हैं, लेकिन यदि आपके अभिकथन, अभिकथन, अपवादों, इकाई परीक्षणों और लॉगिंग की भूमिका को पूरा करते हैं, तो कुछ गलत है।

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


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

2

मैं आपके साथ काम करना चाहता हूं! कोई है जो बहुत लिखता assertsहै शानदार है। मुझे नहीं पता कि क्या "बहुत अधिक" जैसी कोई चीज है। मेरे लिए और अधिक आम लोग हैं, जो बहुत कम लिखते हैं और अंत में सामयिक घातक यूबी मुद्दे पर चलते हैं जो केवल एक पूर्णिमा पर दिखाते हैं जिसे एक सरल के साथ आसानी से दोहराया जा सकता था assert

विफल संदेश

एक चीज जो मैं सोच सकता हूं वह यह है कि assertयदि आप इसे पहले से नहीं कर रहे हैं , तो विफलता की जानकारी एम्बेड करें , जैसे:

assert(n >= 0 && n < num && "Index is out of bounds.");

इस तरह से आपको अब ऐसा महसूस नहीं हो सकता है कि आपके पास बहुत सारे हैं यदि आप पहले से ऐसा नहीं कर रहे थे, जैसा कि अब आप अपनी धारणाओं को आगे बढ़ाने और पूर्वधारणा बनाने में एक मजबूत भूमिका निभा रहे हैं।

दुष्प्रभाव

बेशक assertवास्तव में दुरुपयोग किया जा सकता है और त्रुटियों का परिचय दे सकता है, जैसे:

assert(foo() && "Call to foo failed!");

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

डिबगिंग की गति

डिबगिंग की गति आम तौर पर हमारी प्राथमिकता सूची में सबसे नीचे होनी चाहिए, लेकिन एक बार जब मैंने डिबग के माध्यम से डिबग बिल्ड को चलाने से पहले एक कोडबेस में इतना जोर दिया था कि रिलीज की तुलना में 100 गुना धीमी थी।

यह मुख्य रूप से था क्योंकि मेरे पास इस तरह के कार्य थे:

vec3f cross_product(const vec3f& lhs, const vec3f& rhs)
{
    return vec3f
    (
        lhs[1] * rhs[2] - lhs[2] * rhs[1],
        lhs[2] * rhs[0] - lhs[0] * rhs[2],
        lhs[0] * rhs[1] - lhs[1] * rhs[0]
    );
}

... जहां हर एक कॉल operator[]सीमा-जाँच का दावा करता है। मैंने उन कुछ प्रदर्शन-महत्वपूर्ण लोगों को असुरक्षित समकक्षों के स्थान पर समाप्त कर दिया, जो केवल कार्यान्वयन-विस्तार-स्तरीय सुरक्षा के लिए मामूली लागत पर डिबगिंग निर्माण में तेजी लाने के लिए जोर नहीं देते हैं, और केवल इसलिए कि इसकी गति हिट शुरू हो रही थी बहुत अधिक उत्पादकता को कम करने के लिए (तेजी से डिबगिंग प्राप्त करने का लाभ प्राप्त करने के लिए कुछ जोर देने की लागत पल्ला झुकना पड़ता है, लेकिन सिर्फ इस क्रॉस उत्पाद फ़ंक्शन जैसे कार्यों के लिए जो सबसे महत्वपूर्ण, मापा रास्तों में इस्तेमाल किया जा रहा था, operator[]सामान्य रूप से नहीं)।

एकल जिम्मेदारी सिद्धांत

जबकि मुझे नहीं लगता है कि आप वास्तव में अधिक एस्कॉर्ट्स के साथ गलत हो सकते हैं (कम से कम यह बहुत दूर है, बहुत अधिक की तुलना में बहुत अधिक की ओर से गलती करने के लिए बेहतर है), एसेर्ट्स खुद एक समस्या नहीं हो सकते हैं लेकिन एक का संकेत दे सकते हैं।

यदि आपके पास एकल फ़ंक्शन कॉल के लिए 5 दावे हैं, उदाहरण के लिए, यह बहुत अधिक हो सकता है। इसके इंटरफ़ेस में बहुत अधिक पूर्व शर्त और इनपुट पैरामीटर हो सकते हैं, उदाहरण के लिए, मुझे लगता है कि इस विषय के संबंध में असंबंधित है कि क्या एक स्वस्थ संख्या का गठन होता है (जिसके लिए मैं आम तौर पर जवाब देता हूं, "अधिक विलय!"), लेकिन यह हो सकता है एक संभावित लाल झंडा (या संभवतः नहीं)।


1
वैसे, सिद्धांत में "बहुत अधिक" जोर दिया जा सकता है, हालांकि यह समस्या वास्तव में तेजी से स्पष्ट हो जाती है: यदि मुखर फ़ंक्शन के मांस की तुलना में काफी अधिक समय लेता है। बेशक, मुझे याद नहीं आ रहा है कि जंगली में अभी तक, विपरीत समस्या प्रचलित है।
डेडुप्लिकेटर

@Deduplicator आह हाँ, मैं उन महत्वपूर्ण वेक्टर गणित दिनचर्या में उस मामले का सामना करना पड़ा। हालाँकि यह निश्चित रूप से बहुत अधिक की तुलना में बहुत अधिक के पक्ष में करने के लिए बहुत बेहतर लगता है!

-1

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

इसलिए जब लोग कहते हैं कि केवल वही कोड जाँचता है जो कोड करता है तो वह बहुत ही सही नहीं है। यह परखता है कि वे क्या सोचते हैं कि कोड क्या करता है, और मुखर का पूरा बिंदु यह जांचना है कि कोड में कोई बग की धारणा सही नहीं है। और मुखर प्रलेखन के रूप में अच्छी तरह से काम कर सकते हैं। अगर मुझे लगता है कि एक लूप को निष्पादित करने के बाद i == n और यह कोड से 100% स्पष्ट नहीं है, तो "assert (i == n)" मददगार होगा।

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

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

बहुत अधिक मुखर होना: यदि कोई मुखर उपयोगकर्ता के हाथ में होने पर आपके प्रोग्राम को क्रैश कर देता है, तो आपके पास झूठी नकारात्मकता के कारण क्रैश करने वाले कोई भी दावे नहीं होने चाहिए।


-3

निर्भर करता है। यदि कोड आवश्यकताओं को स्पष्ट रूप से प्रलेखित किया गया है, तो अभिक्रिया हमेशा आवश्यकताओं से मेल खाना चाहिए। किस मामले में यह अच्छी बात है। हालांकि, अगर कोई आवश्यकताएं नहीं हैं या बुरी तरह से लिखित आवश्यकताएं हैं, तो नए प्रोग्रामर के लिए कोड को संपादित किए बिना कोड को संपादित करने के लिए हर बार यह जानना मुश्किल होगा कि क्या आवश्यकताएं हैं।


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