Çok fazla iddia yazmak mümkün mü?


33

assertProgramımdaki mantıksal hatalar nedeniyle gerçekleşmeyen olayları yakalamanın bir yolu olarak C ++ kodunda çek yazmanın büyük bir hayranıyım . Bu genel olarak iyi bir uygulamadır.

Ancak, yazdığım bazı fonksiyonların (karmaşık bir sınıfın parçası olan) okunabilirlik ve bakım açısından potansiyel olarak kötü bir programlama uygulaması olabileceğini düşünen 5+ varsayımına sahip olduğunu fark ettim. Her biri benim için fonksiyonların ön ve son koşullarını düşünmemi gerektirdiği için hala harika olduğunu düşünüyorum. Bununla birlikte, çok sayıda kontrolün gerekli olduğu durumlarda mantık hatalarını yakalamak için daha iyi paradigmalar olup olmadığını sormak için bunu sadece ortaya koymak istedim.

Emacs yorumu : Emacs benim IDE seçimim olduğundan, sağlayabilecekleri karışıklık hissini azaltmaya yardımcı olan açıklayıcı ifadeleri hafifçe griyorum. İşte benim .emacs dosyama ne eklerim:

; 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
Bunun aklımdan geçip geçen bir soru olduğunu itiraf etmeliyim. Bu konuda başkalarının görüşlerini duymak istiyorum.
Yüzbaşı Sensible

Yanıtlar:


45

Birisi daha fazla yazmış olsaydı daha hızlı çözülebilecek yüzlerce hata gördüm ve daha az yazarak daha çabuk çözülebilecek tek bir tane bile görmedim .

[C] [çok fazla iddia] okunabilirlik ve bakım açısından potansiyel olarak kötü bir programlama uygulaması olabilir [?]

Belki de okunabilirlik bir sorun olabilir - benim iyi deneyimler yazanların da okunaklı kod yazmaları benim deneyimim olmasına rağmen. Argümanların çöp olmadığını doğrulamak için bir fonksiyon bloğunun başladığını görmek için beni hiçbir zaman rahatsız etmiyor - sadece ondan sonra boş bir satır koyun.

Ayrıca benim tecrübelerime göre, sürdürülebilirlik, birim testlerde olduğu gibi, varsayımlarla her zaman geliştirilir. Varlıklar, kodun kullanılma amacına uygun şekilde kullanıldığından emin olmak için bir akıl kontrolü sağlar.


1
İyi cevap. Ayrıca Emacs ile okunabilirliği nasıl geliştirdiğime dair bir açıklama ekledim.
Alan Turing

2
"Benim iyi şeyler yazan insanların da okunaklı kod yazmaları benim deneyimim oldu" << mükemmel nokta. Kodları okunaklı kılmak, kullandığı ve kullanmasına izin verilmeyen teknikler olduğu kadar programcıya kalmıştır. İyi tekniklerin yanlış ellerde okunamaz hale geldiğini ve hatta kötü tekniklerin soyutlamanın ve yorumların doğru kullanılmasıyla tamamen net, hatta zarif hale geleceğini düşündüğünü gördüm.
Greg Jackson

Hatalı iddiaların neden olduğu birkaç uygulama çöküşü yaşadım. Bu yüzden eğer biri (kendim) daha az onay yazsaydı , varolmayacak hataları gördüm .
KodlarInChaos

@CodesInChaos Muhtemelen, yazım hataları bir yana, bu sorunun formüle edilmesinde bir hataya işaret eder - yani, hata tasarımdaydı, dolayısıyla iddialar ve (diğer) kod arasındaki uyuşmazlık.
Lawrence

12

Çok fazla iddia yazmak mümkün mü?

Eee, tabii ki oyle. [Burada iğrenç bir örnek düşünün.] Ancak, aşağıda ayrıntılı olarak verilen yönergeleri uygulayarak, bu limiti pratikte zorlamakta zorlanmayacaksınız. Ben de iddiaların büyük bir hayranıyım ve bunları bu ilkelere göre kullanıyorum. Bu tavsiyenin çoğu iddialara özel değildir, ancak yalnızca kendilerine uygulanan genel iyi mühendislik uygulamalarını içerir.

Çalışma süresi ve ikili ayak izini göz önünde bulundurun

İddialar harika, ancak programınızı kabul edilemez derecede yavaşlatırlarsa ya çok can sıkıcı olacak ya da er ya da geç kapatacaksınız.

İçerdiği işlevin maliyetine göre bir iddianın maliyetini ölçmeyi seviyorum. Aşağıdaki iki örneği inceleyin.

// 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();
}

Fonksiyonun kendisi bir O (1) işlemidir ancak iddialar ek yükü O ( n ) olarak hesaplar . Çok özel durumlarda olmadığı sürece böyle çeklerin aktif olmasını istediğinizi sanmıyorum.

İşte benzer iddiaları ile başka bir işlev.

// 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_)));
}

İşlev, bir O ( n ) işlemidir, bu nedenle iddia için ek bir O ( n ) ek yükü eklemek çok daha az acıtır . Bir işlevi küçük bir değere (bu durumda, muhtemelen 3'ten az) kısmak, genellikle bir hata ayıklama yapısında göze alabileceğimiz ancak serbest bırakma yapısında olmayan bir şeydir.

Şimdi bu örneği ele alalım.

// 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();
}

Birçok insan bu O (1) iddiasında önceki örnekteki iki O ( n ) iddiasına göre çok daha rahat olsa da , benim görüşüme göre ahlaki olarak eşdeğerdir. Her biri, fonksiyonun karmaşıklığının sırasına ek olarak gelir.

Son olarak, içinde bulundukları işlevin karmaşıklığının egemen olduğu “gerçekten ucuz” iddialar var.

// 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;
}

Burada, bir O ( n ) fonksiyonunda iki O (1) iddiamız var . Muhtemelen bu ek yükü serbest bırakma yapılarında bile tutmak sorun olmayacak.

Bununla birlikte, asimptotik karmaşıklıkların her zaman yeterli bir tahmin vermediğini unutmayın, çünkü pratikte, her zaman sınırlı son sabit tarafından sınırlanan girdi boyutları ile uğraşıyoruz ve “Big- O ” tarafından gizlenen sabit faktörler çok ihmal edilemeyebilir.

Öyleyse şimdi farklı senaryolar belirledik, onlar hakkında ne yapabiliriz? (Muhtemelen de) kolay bir yaklaşım, “İçlerinde bulundukları işleve hakim olan iddiaları kullanma” gibi bir kuralı takip etmek olacaktır. Bazı projeler için işe yarayabilirken, diğerleri daha farklı bir yaklaşıma ihtiyaç duyabilir. Bu, farklı durumlar için farklı değerlendirme makroları kullanılarak yapılabilir.

#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;

}

Artık üç makro kullanabilirsiniz MY_ASSERT_LOW, MY_ASSERT_MEDIUMve MY_ASSERT_HIGHstandart kütüphanenin “tek beden herkese uyar” yerine asserthakim, ne hakim ve sırasıyla bunların içeren fonksiyonunun karmaşıklığı hakim ne de hakimdir beyanlarına ilişkin makro. Yazılımı oluştururken, MY_ASSERT_COST_LIMITne tür iddiaların çalıştırılabilir hale getirilmesi gerektiğini seçmek için ön işlemci sembolünü önceden tanımlayabilirsiniz . Sabitler MY_ASSERT_COST_NONEve MY_ASSERT_COST_ALLherhangi bir assert makrosuna karşılık gelmezler ve MY_ASSERT_COST_LIMITtüm iddiaları sırayla kapatmak veya açmak için değerler olarak kullanılırlar .

Burada iyi bir derleyicinin kod oluşturmayacağı varsayımına güveniyoruz

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

ve dönüşüm

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

içine

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

Bugünlerde güvenli bir varsayım olduğuna inanıyorum.

Yukarıdaki kodu değiştirmek üzereyseniz, geçen iddiaların ek yükünü azaltmak için derleyiciye özel açıklamaları __attribute__ ((cold))açık my::assertion_failedveya __builtin_expect(…, false)açık gibi düşünün !(CONDITION). Sürüm oluşturma işlemlerinde, işlev çağrısı yerine, bir tanılama mesajını kaybetme durumunda ayak izini azaltmak my::assertion_failedgibi bir şeyle değiştirmeyi de düşünebilirsiniz __builtin_trap.

Bu tür optimizasyonlar, yalnızca tüm mesaj dizelerini birleştirerek biriktirilen ikilinin ek boyutunu göz önünde bulundurmadan, çok kompakt bir fonksiyonda (zaten argümanlar olarak verilen iki tamsayının karşılaştırılması gibi) oldukça ucuz iddialarda geçerlidir.

Bu kodun nasıl olduğunu karşılaştırın

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;
}

aşağıdaki derleme içine derlenmiştir

_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:

aşağıdaki koddayken

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

bu derleme verir

_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:

ki kendimi çok daha rahat hissediyorum. (Örnekler kullanılarak GCC 5.3.0 ile test edildi -std=c++14, -O3ve -march=native4.3.3-2-ARCH x86_64 GNU / Linux bayrakları. Yukarıdaki snippet'lerde gösterilmemiştir bildirgeleridir test::positive_difference_1stve test::positive_difference_2ndhangi bir ilave __attribute__ ((hot))için. my::assertion_failedİlan edildi __attribute__ ((cold)).)

Onlara bağlı fonksiyonda ön koşulları belirtin

Belirtilen sözleşmede aşağıdaki işleve sahip olduğunuzu varsayalım.

/**
 * @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;

Yazmak yerine

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

Her arama sitesinde, bu mantığı bir kez tanımına ekleyin. 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;
}

ve daha fazla uzatmadan arayın.

const auto frequency = count_letters(text, letter);

Bunun aşağıdaki avantajları vardır.

  • Onay kodunu yalnızca bir kez yazmanız gerekir. Fonksiyonların amacı, bunların çağrılmasıdır - çoğu zaman bir kereden fazla - bu assert, kodunuzdaki toplam ifade sayısını azaltmalıdır .
  • Önkoşulları, onlara bağlı olan mantığa yakın kontrol eden mantığı tutar. Bunun en önemli yön olduğunu düşünüyorum. Müşterileriniz arayüzünüzü kötüye kullanırsa, iddiaları doğru uyguladıkları varsayılmaz, bu nedenle işlev onlara söylerse daha iyidir.

Açık dezavantajı, çağrı sitesinin kaynak konumunu teşhis mesajına sokmamanızdır. Bunun küçük bir sorun olduğuna inanıyorum. İyi bir hata ayıklayıcı, sözleşme ihlalinin kökenini rahatça izlemenize izin vermelidir.

Aynı düşünce, aşırı yüklenmiş operatörler gibi “özel” fonksiyonlar için de geçerlidir. Yineleyiciler yazarken, genellikle - yineleyicinin yapısı izin veriyorsa - onlara bir üye işlevi verin

bool
good() const noexcept;

Bu, yineleyiciyi kaldırmanın güvenli olup olmadığını sormaya izin verir. (Elbette, pratikte, yineleyiciyi serbest bırakmanın güvenli olmayacağını garanti etmek neredeyse her zaman mümkündür . assert(iter.good())ifadeleri ile yineleyici kullanır, yineleyici uygulamasında assert(this->good())ilk satır olarak tek bir koymak istiyorum operator*.

Standart kitaplığı kullanıyorsanız, kaynak kodunuzdaki ön koşullarını elle belirtmek yerine, hata ayıklama yapılarında denetimlerini açın. Bir yineleyicinin başvurduğu kabın hala var olup olmadığını test etmek gibi daha karmaşık kontroller yapabilirler. (Daha fazla bilgi için libstdc ++ ve libc ++ (devam eden çalışma) belgelerine bakın .)

Faktör ortak koşulları

Doğrusal bir cebir paketi yazdığınızı varsayalım. Pek çok işlev karmaşık ön koşullara sahip olacak ve bunları ihlal etmek çoğu zaman böyle bir şekilde hemen tanınamayan yanlış sonuçlara neden olacaktır. Bu işlevlerin ön koşullarını ortaya koyması çok iyi olurdu. Bir yapı hakkında size bazı özellikler anlatan bir dizi tahmin belirlerseniz, bu iddialar çok daha okunaklı hale gelir.

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

Aynı zamanda daha kullanışlı hata mesajları verecektir.

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

çok daha fazla yardımcı olur

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

ilk önce nereye bakmanız gerektiğini, gerçekte neyin test edildiğini bulmak için bağlamdaki kaynak koduna bakın.

Eğer bir varsa classönemsiz olmayan değişmezler ile, muhtemelen dahili devlet ile haberci ve karşılığında ilgili geçerli bir durumda nesneyi terk ediyoruz sağlamak istiyoruz gelmiş zaman zaman onlara assert için iyi bir fikirdir.

Bu amaçla, privategeleneksel olarak çağırdığım bir üye fonksiyonu tanımlamayı faydalı buldum class_invaraiants_hold_. Yeniden uyguladığınızı varsayalım std::vector(Çünkü hepimiz yeterince iyi olmadığını biliyoruz.), Bunun gibi bir işlevi olabilir.

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;
}

Bununla ilgili birkaç şey dikkat edin.

  • Öngörü işlevinin kendisidir constve noexceptkılavuza göre iddiaların yan etkileri olmayacaktır. Mantıklıysa, ilan edin constexpr.
  • Tahmini hiçbir şey kendisi göstermiyor. Bunun gibi iddiaların içinde çağrılmak istenmektedir assert(this->class_invariants_hold_()). Bu şekilde, iddialar derlenirse, çalışma zamanı ek yükü bulunmadığından emin olabiliriz.
  • İşlev içindeki kontrol akışı, büyük bir ifadeden ziyade if, erken returns ile çoklu ifadelere bölünür . Bu, fonksiyonun bir hata ayıklayıcıda adım adım ilerlemesini ve iddia ateşlenirse değişmezin hangi kısmının kırıldığını bulmayı kolaylaştırır.

Aptalca şeylere güvenme

Bazı şeyler üzerinde durmak mantıklı değil.

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

Bu iddialar, kodu küçük bir parçadan daha okunaklı veya akla gelmesi kolay hale getirmez. Her C ++ programcısı std::vector, yukarıdaki kodun sadece ona bakarak doğru olduğundan emin olmak için nasıl çalıştığından emin olmalıdır . Asla bir konteynerin büyüklüğünü iddia etmemen gerektiğini söylemiyorum. Bazı önemsiz olmayan kontrol akışını kullanarak öğeler eklediyseniz veya kaldırdıysanız, böyle bir iddia yararlı olabilir. Ancak, sadece yukarıda belirtilen iddiasızlık kodunda yazılmış olanı tekrarlarsa, elde edilen hiçbir değer yoktur.

Ayrıca kütüphane işlevlerinin doğru çalıştığını iddia etmeyin.

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

Kütüphaneye bu kadar az güveniyorsanız, bunun yerine başka bir kütüphane kullanmayı düşünün.

Öte yandan, kütüphanenin dokümantasyonu% 100 net değilse ve kaynak kodunu okuyarak sözleşmeleri hakkında güven kazanıyorsanız, bu “çıkarılan sözleşmeyi” iddia etmek çok mantıklı olacaktır. Kitaplığın gelecekteki bir sürümünde kırılırsa, hemen farkedeceksiniz.

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());

Bu, varsayımlarınızın doğru olup olmadığını size söylemeyecek olan aşağıdaki çözümden daha iyidir.

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();
  }

Program mantığını uygulamak için iddiaları kötüye kullanmayın

İddialar yalnızca başvurunuzu derhal öldürmeye değecek böcekleri ortaya çıkarmak için kullanılmalıdır. Bu duruma uygun tepki de hemen bırakmak olsa bile, başka bir durumu doğrulamak için kullanılmamalıdırlar.

Bu nedenle, şunu yaz…

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

…bunun yerine.

assert(server_reachable());

Ayrıca, güvensiz girişi doğrulamak ya da sizin std::mallocyapmadığınızı kontrol etmek için asla iddiaları kullanmayın . İddiaları hiçbir zaman kapatmayacağınızı bilseniz bile, sürüm oluşturmada bile bir iddia, programın hatasız olduğu ve görünürde yan etkileri olmadığı göz önüne alındığında, okuyucuya her zaman doğru olan bir şeyi kontrol ettiğini bildirir. Bu iletişim kurmak istediğiniz mesaj türü değilse, istisna gibi alternatif bir hata işleme mekanizması kullanın . İddiaya girmeyen çekleriniz için bir makro sarmalayıcı bulundurmanın uygun olduğunu düşünüyorsanız, bir tane yazmaya devam edin. Sadece "assert", "varsayalım", "zorunlu", "emin" veya benzeri bir şey demeyin. İç mantığı , elbette asla derlenmemesi dışında olduğu gibi olabilir .returnnullptrthrowassert

Daha fazla bilgi

John Lakos' konuşma bulundu Savunma Programlama Done Right CppCon'14 (verilen, 1 st kısmı , 2 nd parçası ) çok aydınlatarak. Hangi iddiaların etkinleştirilebileceğini ve başarısız olan istisnalara nasıl tepki verebileceğimi bu cevaba verdiğimden daha fazla uyarlama fikrini benimsemiştir.


4
Assertions are great, but ... you will turn them off sooner or later.- Umarım daha önce, kodlar gönderilmeden önceki gibi. Programın üretimde ölmesi gereken şeyler iddialarda değil “gerçek” kodun bir parçası olmalıdır.
Blrfl

4

Zamanla daha az değerlendirme yazdığımı düşünüyorum çünkü bunların çoğu "derleyici çalışıyor" ve "kitaplık çalışıyor" anlamına geliyor. Tam olarak neyi test ettiğinizi düşünmeye başladığınızda, daha az şey yazacağınızdan şüpheleniyorum.

Örneğin, bir koleksiyona bir şey ekleyen bir yöntemin, koleksiyonun var olduğunu iddia etmesi gerekmez - bu, genellikle mesajın sahibi olan sınıfın bir önkoşuludur veya kullanıcıya geri göndermesi gereken önemli bir hatadır. . Bu yüzden bir kez kontrol edin, çok erken, sonra varsayalım.

Bana yapılan iddialar bir hata ayıklama aracı ve genellikle bunları iki şekilde kullanacağım: masamda bir hata bulma (ve kontrol edilmiyorlar. Eh, belki de bir anahtar olabilir); ve müşteri masasında bir hata bulma (ve check-in yaptırırlar). İki kere de, bir istisnayı olabildiğince erken zorladıktan sonra yığın izini oluşturmak için çoğunlukla iddiaları kullanıyorum. Bu şekilde kullanılan iddiaların kolayca heisenbug'lara yol açabileceğini unutmayın - hata, iddiaların etkinleştirildiği hata ayıklama yapısında asla gerçekleşmeyebilir.


4
“Genellikle mesajın sahibi olan sınıfın bir ön şartı ya da kullanıcıya geri dönmesi gereken ölümcül bir hata” derken fikrinizi anlamıyorum. Öyleyse bir kez kontrol edin, çok erken, sonra farz edin. ” Varsayımlarınızı doğrulamamak için ne kullanıyorsunuz?
5gon12eder,

4

Çok az sayıda iddia: bu kodu değiştirmede iyi şanslar gizli varsayımlarla çözüldü.

Çok fazla iddia: okunabilirlik sorunlarına yol açabilir ve potansiyel olarak kod kokusuna neden olabilir - assert ifadelerine yerleştirilmiş çok fazla varsayımda bulunduğunda doğru tasarlanmış sınıf, işlev, API mı?

Gerçekten de hiçbir şeyi kontrol etmeyen ya da her bir fonksiyondaki derleyici ayarları gibi şeyleri kontrol etmeyen iddialar olabilir: /

Tatlı noktayı hedefleyin, ancak daha azına değil (başkasının daha önce de söylediği gibi, iddiaların "daha fazlası", çok azının veya tanrının bize yardım etmesinden daha az zararlıdır - hiçbiri).


3

Yalnızca bir boolean CONST yöntemine başvuruda bulunan bir Assert işlevi yazabiliyor olsaydınız harika olurdu, bu yolla iddialarınızın test edilmesinde bir Boole const yönteminin kullanılmasını sağlayarak yan etkilerinin bulunmadığından emin olabilirsiniz.

özellikle bir lambda (c ++ 0x cinsinden) bir sınıfa ek açıklama yapamayacağınızı sanmıyorum, çünkü bunun için lambda kullanamayacağınızı düşündüğünüzden, okunabilirlikten biraz daha fazla etkilenecektir.

Bana sorarsanız, ancak iddialardan dolayı belirli bir kirlilik seviyesi görmeye başlarsam iki şeye karşı temkinli olurum:

  • iddiada hiçbir yan etkinin olmadığından emin olmak (yukarıda açıklandığı gibi bir yapı tarafından sağlanır)
  • geliştirme testi sırasındaki performans; Bu, assert tesisine seviyelerin eklenmesi (logging gibi) ile ele alınabilir; bu yüzden performansı geliştirmek için bazı iddiaları bir geliştirme yapısından devre dışı bırakabilirsiniz.

2
Kutsal, "kesin" kelimesini ve türetmelerini beğenirsiniz. 8 kullanıyorum.
Casey Patton

evet, üzgünüm kelimeleri çok fazla
kısmama

2

C # 'da C ++' dan çok daha fazla yazdım, ancak iki dil birbirinden çok uzak değil. Net'te Olmaması gereken durumlar için Varlıklar kullanıyorum, ancak devam etmenin bir yolu olmadığında da sıklıkla istisnalar atıyorum. VS2010 hata ayıklayıcısı, Sürüm derlemesi ne kadar optimize edilmiş olursa olsun, bir istisna hakkında bana pek çok iyi bilgi veriyor. Mümkünse ünite testleri eklemek de iyi bir fikirdir. Bazen kayıt olmak, aynı zamanda hata ayıklama yardımcısı olarak iyi bir şeydir.

Peki, çok fazla iddia olabilir mi? Evet. Durdur / Yoksay / Devam Et arasında seçim yapmak, bir dakikada 15 kez can sıkıcı hale gelir. Bir istisna yalnızca bir kez atılır. Çok fazla iddianın olduğu noktayı ölçmek zordur, ancak iddialarınız iddiaların, istisnaların, ünite testlerinin ve kayıtların rolünü yerine getirirse, bir şeyler yanlış olur.

Olmaması gereken senaryolarla ilgili iddiaları saklı tutarım. Öncelikle aşırı iddialı olabilirsiniz, çünkü iddialar yazmak daha hızlıdır, ancak daha sonra kodu yeniden hesaba katın - bazılarını istisnalara, bazılarını testlere vb. Dönüştürün. Her TODO yorumunu temizlemek için yeterli disipline sahipseniz, yeniden çalışmayı planladığınız her birinin yanına yorum yapın ve TODO'yu daha sonra ele almayı UNUTMAYIN.


Kodunuz dakikada 15 iddia vermezse, daha büyük bir sorun olduğunu düşünüyorum. İddialar hiçbir zaman hatasız kodda başlatılmamalı ve bunu yapmalı, daha fazla hasarı önlemek veya ne olup bittiğini görmek için sizi bir hata ayıklayıcısına düşürmek için uygulamayı öldürmelidir.
5gon12eder,

2

Sizinle çalışmak istiyorum! Çok fazla yazan assertsbiri harika. "Çok fazla" diye bir şey olup olmadığını bilmiyorum. Benim için çok daha yaygın olanı, çok az yazan ve nihayetinde, basitçe tekrar tekrar kolayca çoğaltılabilen dolunayda ortaya çıkan ölümcül UB sorunuyla karşılaşan insanlardır assert.

Mesaj başarısız

Düşünebildiğim tek şey, asserteğer zaten yapmadıysanız, başarısızlık bilgisini içine gömmek.

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

Bu şekilde, zaten yapmadıysanız, artık fazla hissetmediğinizi hissetmeyebilirsiniz, çünkü şimdi varsayımlarınızı ve ön koşullarınızı belgelendirmede daha güçlü bir rol oynamaya karar verdiniz.

Yan etkiler

Tabii ki assertkötüye kullanılabilir ve bunun gibi hatalar ortaya çıkabilir:

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

... eğer foo()yan etkileri tetiklerse, bu konuda çok dikkatli olmalısınız, ama eminim ki zaten çok liberal bir iddiada bulunmuşsunuzdur (“deneyimli bir savcı”). Umarım, test prosedürünüz de varsayımlara dikkat etmeniz kadar dikkatlidir.

Hata ayıklama hızı

Hata ayıklama hızı genel olarak öncelik listemizin altında olsa da, bir defasında bir hata kodunu yazıp, hata ayıklayıcısını kullanarak hata ayıklama işlemini çalıştırmadan önce serbest bırakmadan 100 kat daha yavaş olduğunu iddia ettim .

Öncelikle bunun gibi fonksiyonlara sahip olduğum içindi:

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]
    );
}

... her çağrı için operator[]sınır kontrol iddiası yapılacağı yer. Performans açısından kritik olanların bazılarını, sadece uygulama detay düzeyinde güvenliği sağlamak için küçük bir maliyetle ciddi bir şekilde hata ayıklama yapısını hızlandırmak istemeyen güvenli eşdeğerlerle değiştirdim. üretkenliği çok belirgin biçimde düşürmek (daha hızlı hata ayıklama elde etmenin avantajını kullanmak, bir kaç değerlendirme kaybetmenin maliyetinden ağır basar, ancak sadece operator[]genel olarak değil, en kritik, ölçülmüş yollarda kullanılan bu çapraz ürün işlevi gibi işlevler için ).

Tek Sorumluluk İlkesi

Daha fazla kötülükle gerçekten yanlış gidebileceğinizi düşünmemekle birlikte (en azından çok azının yanında hata yapmak çok daha iyidir), iddiaların kendileri bir sorun olmayabilir, ancak bunun bir göstergesi olabilir.

Örneğin, tek bir işlev çağrısında 5 iddia varsa, çok fazla şey yapıyor olabilir. Arabiriminin çok fazla önkoşulu ve girdi parametresi olabilir; örneğin, sadece sağlıklı bir sayıda iddiayı neyin oluşturduğuyla (bunun için genellikle "ne kadar neşeli!" Diye cevap veririm) konuyla ilgili olmadığını düşünüyorum, ancak bu olabilir. olası bir kırmızı bayrak (veya çok büyük olasılıkla değil).


1
Eh, teoride "çok fazla" iddiası olabilir, ancak bu problem çok hızlı bir şekilde ortaya çıksa da: Eğer iddia, fonksiyonun etinden çok daha uzun sürerse. Kuşkusuz, henüz vahşi doğada, bunun tersi sorunun yaygın olduğunu belirttiğimi hatırlayamıyorum.
Deduplicator

@Teduplicator Ah evet, o kritik vektör matematik rutininde bu olayla karşılaştım. Her ne kadar kesinlikle çok azında çok fazla tarafında hata yapmak çok daha iyi görünüyor!

-1

Kodunuza çek eklemek çok makul. Düz assert için (C ve C ++ derleyicisine yerleştirilmiş olan) kullanım şeklim, başarısız bir assert'in kodda düzeltilmesi gereken bir hata olduğu anlamına gelir. Bunu biraz cömertçe yorumluyorum; Bir web isteği sonra başarısız bir onaylama işlemi diğer davalarına olmadan bunun için bir statü 200 ve assert dönmek için beklemek eğer gelmez assert haklı nedenle gerçekten benim kodunda bir hata gösteriyor.

Bu yüzden insanlar sadece kodun ne yaptığını kontrol eden bir iddiada bulundukları zaman gereksiz olduğunu söyler. Bu assert, kodun ne yaptığını düşündüğünü kontrol eder ve assert'in tek amacı, kodda hiçbir hata bulunmadığını doğrulamaktır. Ve iddia aynı zamanda dokümantasyon olarak da hizmet verebilir. Bir döngü yürüttükten sonra i == n olduğunu ve koddan% 100 açık olmadığını kabul edersem, "assert (i == n)" yardımcı olacaktır.

Farklı durumlarla başa çıkmak için repertuarınızda sadece "iddia" olmaktan daha iyidir. Örneğin, bir hatayı gösterecek bir şeyin olmadığını kontrol ettiğim durum, ancak bu koşul üzerinde çalışmaya devam etmeye devam ediyor. (Örneğin, eğer bir miktar önbellek kullanırsam, hataları kontrol edebilirim ve bir hata beklenmedik bir şekilde olursa, önbelleği bir kenara atarak hatayı düzeltmek güvenli olabilir. Geliştirme sırasında neredeyse söylenecek bir şey istiyorum. ve hala devam etmeme izin veriyor.

Başka bir örnek, bir şeyin olmasını beklememem durumunda, genel bir geçici çözümüm var, ama bu olursa, onu bilmek ve incelemek istiyorum. Yine neredeyse bir assert gibi bir şey, gelişme sırasında bana söylemeliyim. Ama değil tam olarak bir iddia .

Çok fazla değerlendirme: Bir kullanıcı programın kullanımına geçtiğinde bir programın çökmesi durumunda, yanlış negatifler nedeniyle çöken herhangi bir iddiaya sahip olmamanız gerekir.


-3

Değişir. Kod gereklilikleri açıkça belgelenirse, iddia her zaman gerekliliklere uygun olmalıdır. Bu durumda bu iyi bir şey. Bununla birlikte, herhangi bir gereklilik veya kötü yazılmış gereklilik yoksa, yeni programcıların gereksinimlerin ne olduğunu anlamak için her seferinde birim testine başvurmak zorunda kalmadan kodu düzenlemesi zor olacaktır.


3
bu, önceki 8
cevapta
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.