Her bir onaylı göstericiyi boş bırakmamak mantıklı mı?


65

Yeni bir işte, şöyle kod için kod incelemelerinde işaretlenmeye başladım:

PowerManager::PowerManager(IMsgSender* msgSender)
  : msgSender_(msgSender) { }

void PowerManager::SignalShutdown()
{
    msgSender_->sendMsg("shutdown()");
}

Bana son yöntemin okuması gerektiği söylendi:

void PowerManager::SignalShutdown()
{
    if (msgSender_) {
        msgSender_->sendMsg("shutdown()");
    }
}

yani ben gereken bir koyun NULLetrafında nöbet msgSender_bunun özel veri üyesi olmasına rağmen değişken. Bu 'bilgelik' parçasını nasıl hissettiğimi tarif etmek için kendimi patlayıcı maddeler kullanmaktan alıkoymak zor. Bir açıklama istediğimde, bir sınıf öğrencinin, bir yıl, bir sınıfın nasıl çalışması gerektiği ve kazara yapmaması gereken bir üyeyi kazara silmesi gerektiği hakkında (ve bunu daha NULLsonra ayarlamak üzere) nasıl kafasının karıştığı hakkında korku hikayeleri hakkında bir likitlik alıyorum. (görünüşe göre) ve ürün piyasaya sürüldükten hemen sonra alanda işler patladı ve biz her şeyiNULL kontrol etmenin daha iyi olduğunu "zor yoldan öğrendik, bize güvenin" .

Bana göre bu, kargo kültü programlaması gibi görünüyor , basit ve basit. İyi niyetli birkaç meslektaşım, “onu almam” konusunda bana yardım etmeye çalışıyor ve bunun daha sağlam bir kod yazmamda bana nasıl yardımcı olacağını görüyorlar ama… .

Bir kodlama standardının , bir fonksiyonda referans verilen her bir işaretçinin NULLilk önce - hatta özel veri üyeleri için kontrol edilmesini gerektirmesi uygun mudur ? (Not: Bir bağlam vermek için, bir hava trafiği kontrol sistemi ya da başka bir 'başarısızlığa eşittir insan ölmek' ürünü değil, tüketici elektroniği cihazı yapıyoruz.)

EDIT : Yukarıdaki örnekte, msgSender_ortak çalışan isteğe bağlı değildir. Eğer öyleyse NULL, bir hata olduğunu gösterir. Yapıcıya geçmesinin tek nedeni, PowerManagersahte bir IMsgSenderalt sınıfla test edilebilir .

ÖZET : Bu soruya gerçekten çok güzel cevaplar vardı, herkese teşekkürler. Birini @aaronps 'dan başlıca kısalıklarından dolayı kabul ettim. Oldukça geniş bir genel anlaşma olduğu anlaşılmaktadır:

  1. Korunan her işaretçi NULLiçin zorunlu görevlendirme gereğinden fazla abartılıdır , ancak
  2. Bunun yerine (eğer mümkünse) bir referans veya bir constişaretçi kullanarak tüm tartışmayı adım adım yapabilirsiniz ve
  3. assertifadeler, bir NULLişlevin ön koşullarının karşılandığını doğrulamak için gardiyanlara daha aydınlık bir alternatiftir .

14
Bir dakika boyunca hataların genç programcıların etki alanı olduğunu düşünmeyin. Tüm tecrübe seviyelerindeki geliştiricilerin% 95'i bir süre sonra bir şey bulur. Kalan% 5'i dişlerinden uzanıyor.
Blrfl

56
Birisinin boş olanı denetleyen ve sessizce hata veren bir kod yazdığını görürsem, onları derhal kovmak isterdim.
Winston Ewert

16
Sessizce başarısız olan şeyler neredeyse en kötü şey En azından hava patladığında patlamanın nerede olduğunu biliyorsunuz. Kontrol etmek nullve hiçbir şey yapmamak, hatayı idamın altına çekmenin bir yoludur ve kaynağa geri dönmeyi çok daha zorlaştırır.
MirroredFate

1
+1, çok ilginç bir soru. Cevabımın yanı sıra, amacına göre bir ünite testine uyum sağlamak için kod yazmanız gerektiğinde, gerçekten yaptığınız şey yanlış bir güvenlik hissi için daha fazla risk almanızdır (daha fazla kod satırı = böcekler için daha fazla olasılık). Referansları kullanmak için yeniden tasarlama yalnızca kodun okunabilirliğini artırmakla kalmaz, aynı zamanda çalıştığını kanıtlamak için yazmanız gereken kod (ve testler) miktarını da azaltır.
Seth

6
@Rig, sessiz bir başarısızlık için uygun durumlar olduğuna eminim. Ama birisi sessiz koyar (mantıklı bir alanda çalışan sürece), gerçekten onlar kod koyarak bir proje üzerinde çalışıyor istemiyorum genel uygulama olarak başarısız olursa.
Winston Ewert

Yanıtlar:


66

'Sözleşmeye' bağlıdır:

Eğer geçerli bir PowerManager zorunluluk varsa IMsgSender, null değerini asla kontrol etmeyin, daha erken ölmesine izin verin.

Öte yandan, bu takdirde OLABİLİR bir var IMsgSender, o zaman bu kadar basit, kullandığınız her zaman kontrol etmeniz gerekir.

Genç programcının öyküsü hakkında son yorum, sorun aslında test prosedürlerinin eksikliği.


4
Bu arada yukarıya güzel özetliyor hissediyorum çok az ve öz cevap 1: Ayrıca ( "boş olup olmadığını kontrol asla" demek cesareti vardı "o ... bağlıdır" eğer boş geçersiz). Bir Yerleştirme assert()bir işaretçi dereferences her üye işlevi olası bir sürü insan yatıştırmak olacak bir uzlaşmadır, ama bu bile bana 'spekülatif paranoya' gibi hissediyor. Kontrol etme yeteneğimin ötesindeki şeylerin listesi sonsuzdur ve bir kez onlar için endişelenmeye başladığımda, gerçekten kaygan bir eğim haline gelir. Testlerime, meslektaşlarım ve hata ayıklayıcım güvenmeyi öğrenmek geceleri daha iyi uyumama yardımcı oldu.
evadeflow

2
Kodu okurken, bu iddialar kodun nasıl çalışması gerektiğini anlamada çok yardımcı olabilir. Asla beni yönlendiren bir kod temeli oluşturmamıştım 'Keşke her yerde bütün bu deliller olmasaydı', ama tam tersini söylediğim yerde çok şey gördüm.
Kristof Provost

1
Sade ve basit, bu sorunun doğru cevabı.
Matthew Azkimov

2
Bu en yakın doğru cevaptır, ancak 'null' nü asla kontrol etme 'ile aynı fikirdeyim, her zaman bir üye değişkenine atanacakları noktada geçersiz parametreleri kontrol et.
James

Yorumlarınız için teşekkürler. Birisi şartnameyi okumazsa ve orada geçerli bir şeye sahip olması gerektiğinde null değeri ile başlatırsa, daha erken çökmesine izin vermek daha iyidir.
aaronps

77

Kodun okuması gerektiğini düşünüyorum:

PowerManager::PowerManager(IMsgSender* msgSender)
  : msgSender_(msgSender)
{
    assert(msgSender);
}

void PowerManager::SignalShutdown()
{
    assert(msgSender_);
    msgSender_->sendMsg("shutdown()");
}

Bu aslında NULL’u korumaktan daha iyidir, çünkü bu, NULL ise, fonksiyonun asla çağrılmaması gerektiğini açıkça ortaya msgSender_koyar. Ayrıca, bunun gerçekleşip oluşmayacağını fark etmenizi de sağlar.

Meslektaşlarınızın ortak "bilgeliği", bu hatayı tahmin edilemez sonuçlarla sessizce görmezden gelecektir.

Genel olarak, nedenlerine daha yakın olduğu tespit edilirse, düzeltilmesi daha kolaydır. Bu örnekte, önerilen NULL koruma, farkedilmeyen bir hatayla sonuçlanabilecek veya sonuçlanamayacak olan, ayarlanmayan bir kapatma mesajına yol açar. İşleve geri çalışmak için, SignalShutdowntüm uygulamanın yeni öldüğünden, kullanışlı bir züppe arkadan izine ya da doğrudan işaret eden kaçak çökeltme işlemine nazaran daha zor bir zamanınız olur SignalShutdown().

Biraz sezgiseldir, ancak herhangi bir sorun olduğunda en kısa sürede çökmek kodunuzu daha sağlam hale getirme eğilimindedir. Çünkü aslında problemleri buluyorsunuz ve bunun da çok açık nedenleri var.


10
Assert'ı çağırmakla OP'nin kod örneği arasındaki büyük fark, assert'in yalnızca hata ayıklama yapılarıyla etkinleştirilmesidir (#define NDEBUG) Ürün müşteri eline geçtiğinde ve hata ayıklama devre dışı bırakıldığında ve hiç kullanılmamış bir kod yolu birleşimi bırakmak için yürütülür. İşaretçi, söz konusu işlev yerine getirilmesine rağmen null, size koruma sağlamaz.
atk

17
Muhtemelen yapıyı müşteriye göndermeden önce test etmişsinizdir, ancak evet, bu olası bir risktir. Yine de çözümler kolaydır: ya sadece etkin olanları etkin bir şekilde gönderin (yine de test ettiğiniz sürüm budur) ya da hata ayıklama modunda iddia eden kendi makronuzla değiştirin ve yalnızca serbest bırakma modunda günlüğe kaydeder, günlükleri + geri dönüşleri, çıkar, ....
Kristof Provost

7
@James - Davanın% 90'ında, başarısız NULL çekinden de iyileşemezsiniz. Ancak böyle bir durumda kod sessizce başarısız olur ve hata çok daha sonra ortaya çıkabilir - örneğin, dosyaya kaydettiğinizi düşündünüz, ancak aslında boş kontrol bazen sizi akıllıca bırakmaz. Olmaması gereken her durumdan iyileşemezsin. Bunların gerçekleşmeyeceğini doğrulayabilirsiniz, ancak NASA uydu veya nükleer santral için kod yazmadan bütçenizin olduğunu sanmıyorum. “Neler olduğu hakkında hiçbir fikrim yok - programın bu durumda olması gerekmiyor” demenin bir yoluna ihtiyacınız var.
Maciej Piechotka

6
@James atma ile katılmıyorum. Bu aslında gerçekleşebilecek bir şeymiş gibi görünmesini sağlıyor. Varlıklar, imkansız olması gereken şeyler içindir. Başka bir deyişle, koddaki gerçek hatalar için. İstisnalar beklenmeyen, ancak mümkün olan şeyler içindir. İstisnalar, ele alabileceğiniz ve ondan kurtulabileceğiniz şeyler içindir. Koddaki mantık hataları, kurtarılamaz ve ikisini de denememelisiniz.
Kristof Provost

2
@James: Gömülü sistemler konusundaki deneyimlerime göre, yazılım ve donanım her zaman bir aygıtı tamamen kullanılamaz hale getirmek için oldukça zor olacak şekilde tasarlanmıştır. Çoğu durumda, yazılımın çalışması durursa cihazın tamamen sıfırlanmasını sağlayan bir donanım bekçisi bulunur.
Bart van Ingen Schenau

30

MsgSender Eğer asla olmak null, sen koymalıyız nullsadece yapıcı check. Bu aynı zamanda sınıfa giren diğer girişler için de geçerlidir - giriş noktasında bütünlük kontrolünü 'modül' - sınıf, fonksiyon vb.

Temel kuralm modül sınırları arasında bütünlük kontrolleri yapmaktır - bu durumda sınıf. Ek olarak, bir sınıf, sınıf üyelerinin yaşam sürelerinin bütünlüğünü çabucak zihinsel olarak doğrulamanın mümkün olamayacağı kadar küçük olmalıdır - uygun olmayan silmeler / boş atamalar gibi hataların önlenmesini sağlar. Gönderinizdeki kodda yapılan boş denetim, geçersiz kullanımların aslında işaretçiye boş atadığını varsayar; bu, her zaman böyle değildir. 'Geçersiz kullanım', normal kodlarla ilgili varsayımların geçerli olmadığı anlamına geldiğinden, her tür işaretçi hatasını (örneğin geçersiz bir silme, artış vb.) Yakalayacağımızdan emin olamayız.

Ek olarak - eğer argümanın hiçbir zaman boş olamayacağından eminseniz, sınıfı kullanımınıza bağlı olarak referansları kullanmayı düşünün. Aksi takdirde, ham bir işaretçiyi kullanmayı std::unique_ptrveya std::shared_ptryerine kullanmayı düşünün .


11

Hayır, işaretçinin varlığının her işaretleyicinin uygunluğunu kontrol etmek mantıklı değildir NULL.

Boş işaretçi kontrolleri, ön koşulların yerine getirilmesini sağlamak veya isteğe bağlı bir parametre sağlanmadığında uygun önlemlerin alınmasını sağlamak için işlev argümanları (yapıcı argümanları dahil) konusunda değerlidir ve sınıfın içindekilerini maruz bıraktıktan sonra bir sınıfın değişmezini kontrol etmeleri için değerlidirler. Ancak bir göstericinin NULL olmasının tek nedeni bir hatanın varlığıysa, kontrol etmenin bir anlamı yoktur. Bu hata, işaretçiyi kolayca başka bir geçersiz değere ayarlamış olabilir.

Seninki gibi bir durumla karşılaşsaydım iki soru sorardım:

  • Bu genç programcının hatası neden serbest bırakılmadan önce yakalanmadı? Örneğin bir kod incelemesinde veya test aşamasında? Hatalı bir tüketici elektroniği cihazını pazara getirmek, güvenlik açısından kritik bir uygulamada kullanılan hatalı bir cihazı piyasaya sürmek kadar pahalı olabilir, bu nedenle şirketin test konusunda ciddi olmasını beklerdim. diğer KG faaliyetleri.
  • Boşluk denetimi başarısız olursa, hangi hata işlemeyi gerçekleştirmemi bekliyorsunuz, yoksa boşaltma assert(msgSender_)denetimi yerine sadece yazabilir miyim ? Eğer sadece null check-in yaptıysanız, bir çökmeyi engellemiş olabilirsiniz, ancak daha kötü bir durum yaratmış olabilirsiniz, çünkü yazılım, bir işlemin gerçekleştiği sırada bir işlem gerçekleştiğine dayanarak devam eder. Bu, yazılımın diğer bölümlerinin dengesizleşmesine neden olabilir.

9

Bu örnek, bir giriş parametresinin null olup olmamasından çok nesnenin ömrü hakkında daha fazla görünmektedir. Her zaman geçerli PowerManagerolması gerektiğinden bahsettiğinizden beri , argümanı işaretçi ile (böylece boş bir işaretçi olasılığına izin vererek) bir tasarım kusuru olarak görüyorum.IMsgSender

Böyle durumlarda, arayanın gereksinimlerini dilin uyguladığı şekilde ara yüzünü değiştirmeyi tercih ederim:

PowerManager::PowerManager(const IMsgSender& msgSender)
  : msgSender_(msgSender) {}

void PowerManager::SignalShutdown() {
    msgSender_->sendMsg("shutdown()");
}

Bu şekilde yeniden yazmak, kullanım ömrü boyunca PowerManagerreferans almamız gerektiğini söylüyor IMsgSender. Bu da, aynı zamanda, IMsgSenderdaha uzun sürmesi PowerManagergereken ve içinde boş bir imleç kontrolüne ya da iddialarına ihtiyaç duymadığını da ortadan kaldırması gereken zımni bir gereksinim ortaya koyuyor PowerManager.

Aynı şeyi, aşağıdakilerden IMsgSenderdaha uzun yaşamaya zorlamak için akıllı bir işaretçi kullanarak (boost veya c ++ 11 yoluyla) da yazabilirsiniz PowerManager:

PowerManager::PowerManager(std::shared_ptr<IMsgSender> msgSender) 
  : msgSender_(msgSender) {}

void PowerManager::SignalShutdown() {
    // Here, we own a smart pointer to IMsgSender, so even if the caller
    // destroys the original pointer, we still have a valid copy
    msgSender_->sendMsg("shutdown()");
}

Bu yöntem, kullanım IMsgSenderömrünün kendisinden daha uzun olacağının garanti edilememesi durumunda tercih edilir PowerManager(yani x = new IMsgSender(); p = new PowerManager(*x);).

† İşaretçilerle ilgili olarak: aşırı boş denetim, kodu okumayı zorlaştırır ve kararlılığı iyileştirmez ( daha kötü olan kararlılık görünümünü iyileştirir ).

Bir yerlerde, birisinin tutması gereken hafızanın adresi var IMsgSender. std::bad_allocGeçersiz işaretçilerin arasından geçmemek için tahsisin başarılı olduğundan emin olmak (kütüphane dönüş değerlerini kontrol etmek veya istisnaları uygun şekilde ele almak ) bu işlevin sorumluluğudur .

Sahip PowerManagerolmadığı için IMsgSender(sadece bir süre ödünç alıyor), bu hafızanın tahsis edilmesinden veya imha edilmesinden sorumlu değildir. Bu referansı tercih etmemin başka bir nedeni.

†† Bu işte yeniliğinizden beri mevcut kodu hacklemenizi bekliyorum. Bu yüzden, tasarım kusuru ile demek istediğim, kusurun üzerinde çalıştığınız kodda olmasıdır. Böylece, kodunuzu işaretleyen insanlar, çünkü boş göstericileri denetlemiyorlar, işaretçiler gerektiren kodları yazmak için kendilerini işaretliyorlar :)


1
Bazen ham işaretçiler kullanmakta yanlış bir şey yoktur. Std :: shared_ptr gümüş bir mermi değildir ve argüman listesinde kullanmak, özensiz mühendislik için bir tedavidir, ancak mümkün olduğunda onu kullanmanın 'leet' olduğunu kabul ediyorum. Eğer böyle hissederseniz java kullanın.
James

2
Ham işaretçilerin kötü olduğunu söylemedim! Kesinlikle onların yeri var. Ancak, bu C + + , referanslar (ve paylaşılan işaretçileri, şimdi) bir nedeni dilin parçalarıdır. Onları kullanın, başarılı olmanıza neden olurlar.
Seth

Akıllı işaretçi fikrini seviyorum, ancak bu özel konserde bir seçenek değil. Referansların kullanılmasını önermek için +1 (@Max ve diğer birçoğunun sahip olduğu gibi). Ancak, tavuk ve yumurta bağımlılığı sorunları nedeniyle, örneğin enjeksiyon için aday olabilecek bir nesnenin, içine enjekte edilen yuvasına bir işaretçi (veya referans) koyması gerektiğinde, bir referans enjekte etmek mümkün değildir . Bu bazen sıkı sıkıya bağlı bir tasarım gösterebilir; ancak, bir nesne ile yineleyici arasındaki ilişkide olduğu gibi, ikincisini eskisi olmadan kullanmanın hiçbir zaman bir anlamı yoktur.
evadeflow

8

İstisnalar gibi, koruma koşulları da yalnızca hatadan kurtulmak için ne yapmanız gerektiğini biliyorsanız veya daha anlamlı bir istisna mesajı vermek istiyorsanız kullanışlıdır.

Bir hatanın yutulması (bir istisna veya bekçi kontrolü olarak algılanmış olsun), hatanın önemi olmadığında yapılacak en doğru şeydir. Hataların yutulduğunu görmem için en yaygın yer hata günlüğü kodudur - bir durum mesajını kaydedemediğiniz için bir uygulamayı çökertmek istemezsiniz.

Ne zaman bir işlev çağrılır ve bu isteğe bağlı bir davranış değildir, sessizce değil yüksek sesle başarısız olmalıdır.

Düzenleme: Junior programcı hikayeniz hakkında düşünmek üzerine, ne olduğu söylenmediyse, özel bir üyenin boşuna olmasına izin verilmeyecek gibi geldi. Uygunsuz yazı ile ilgili problemleri vardı ve okuma üzerine onaylayarak düzeltmeye çalışıyorlar. Bu geriye doğru. Tanımladığınız zaman, hata çoktan gerçekleşti. Bunun için kod gözden geçirme / derleyici için uygulanabilir çözüm, bekçi koşulları değil, alıcılar ve ayarlayıcılar veya const üyeleridir.


7

Diğerlerinin de belirttiği gibi, bu yasal olarakmsgSender mümkün olup olmamasına bağlıdır . Aşağıdaki, asla NULL olmaması gerektiğini varsayar . NULL

void PowerManager::SignalShutdown()
{
    if (!msgSender_)
    {
       throw SignalException("Shut down failed because message sender is not set.");
    }

    msgSender_->sendMsg("shutdown()");
}

Takımınızdaki diğer kişiler tarafından önerilen "düzeltme" Ölü Programların Yalan Söyle Deme ilkesini ihlal ediyor . Hatalar olduğu gibi bulmak gerçekten zor. Davranışını sessiz bir şekilde eski bir soruna dayanarak değiştiren bir yöntem, yalnızca ilk hatayı bulmayı zorlaştırmakla kalmaz, aynı zamanda 2. hatayı da ekler.

Genç boşuna bakmayarak hasara yol açtı. Ya bu kod parçası tanımsız bir durumda çalışmaya devam ederek hasara yol açıyorsa (cihaz açık ancak program kapalı olduğunu düşünüyor ")? Belki de programın başka bir kısmı, cihaz kapalıyken güvenli olan bir şey yapacaktır.

Bu yaklaşımlardan herhangi biri sessiz hatalardan kaçınır:

  1. Bu cevabın önerdiği şekilde iddiaları kullanın , ancak üretim kodunda açık olduklarından emin olun. Bu, elbette, eğer üretimde bulunmayacağı varsayımıyla başka iddialar yazılsa sorunlara yol açabilir.

  2. Eğer boş ise bir istisna at.


5

Yapıcıdaki boşluğu hapsetmeye katılıyorum. Ayrıca, üye başlıkta ilan edilmişse:

IMsgSender* const msgSender_;

Öyleyse, işaretçi başlatma işleminden sonra değiştirilemez, bu nedenle yapım aşamasında düzgün olması durumunda onu içeren nesnenin ömrü boyunca iyi olacaktır. (Nesne işaret etmek olacaktır değildir const.)


Bu harika bir tavsiye, teşekkür ederim! Çok güzel, aslında, üzgünüm, daha fazla oy kullanamıyorum. Belirtilen soruya doğrudan bir cevap değildir, ancak iden kazayla pointer oluĢma göstergesinin herhangi bir olasılığını ortadan kaldırmak için mükemmel bir yoldur NULL.
evadeflow,

@evadeflow Ben "herkesle aynı fikirdeyim" diye düşündüm soruyu ana itiraz etti. Yine de şerefe! ;-)
Grimm Opiner

Bu noktada kabul edilen cevabı değiştirmek biraz zor olurdu, soruyu ifade ettiği şekilde. Fakat bu tavsiyenin gerçekten olağanüstü olduğunu düşünüyorum ve özür dilerim. Bir constişaretçi ile, assert()kurucu dışına ihtiyaç duyulmaz , bu nedenle bu cevap @ Kristof Provost'unkinden biraz daha iyi görünür (bu aynı zamanda olağanüstüydi, ancak soruyu dolaylı olarak da ele aldı). Diğerlerinin de oy kullanacağını umuyorum. assertsadece işaretçiyi yapabileceğiniz zaman her yöntemde anlam ifade etmiyor const.
evadeflow

@evadeflow İnsanlar Temsilciler için sadece Questins'i ziyaret ediyor, şimdi cevaplandı kimse cevap vermeyecek. ;-) Sadece işaretçilerle ilgili bir soru olduğu için girdim ve "Her zaman her şey için akıllı işaretçiler kullan" tugayına göz kulak oluyorum.
Grimm Opiner

3

Bu Tamamen Tehlikeli!

Tüm işaretçileri boş bırakmadan kontrol etmek için aynı şeyi zorlayan en yüksek "standartlara" sahip bir C kod üssünde kıdemli bir geliştirici altında çalıştım. Geliştirici böyle şeyler yapmayı bırakacaktı:

// Pre: vertex should never be null.
void transform_vertex(Vertex* vertex, ...)
{
    // Inserted by my "wise" co-worker.
    if (!vertex)
        return;
    ...
}

Bir keresinde böyle bir işlevde önkoşulun böyle bir kontrolünü kaldırmayı ve bunun assertne olacağını görmek için bir ile değiştirmeyi denedim .

Korku için, kod tabanında temel olarak bu işleve boşa giden binlerce kod satırı buldum, ancak geliştiricilerin kafasının karıştığı, uğraştığı ve işler işe yarayana kadar daha fazla kod eklediği yer.

Diğer korkularım için, bu sorunun kod tabanı null değerlerini denetleyen her yerde yaygın olduğunu gördüm. Kod temeli, en açık şekilde belgelenen ön koşulları bile sessizce ihlal edebilmek için bu kontrollere dayanmak üzere on yıllar boyunca büyümüştü. Bu ölümcül kontrollerin lehine kaldırılmasıyla asserts, kod tabanında yıllarca süren tüm mantıksal insan hataları açığa çıkacaktı ve onlarda boğulacaktık.

Bu + zaman gibi görünen ve masum görünen iki kod satırı ve binlerce birikmiş hatayı maskelemek için bir ekip aldı.

Bunlar, yazılımın çalışması için var olan diğer hatalara bağlı olan uygulama türleridir. Bu bir kabus senaryosu . Ayrıca, bu tür önkoşulları ihlal etmekle ilgili her mantıksal hatayı gizlice, hatanın gerçekleştiği asıl siteden uzakta bir milyon satırlık kod ortaya çıkarır, çünkü tüm bu boş kontroller sadece hatayı gizler ve biz de unutulan bir yere ulaşana kadar hatayı gizler. hatayı gizlemek için.

Bir boş göstericinin bir önkoşulu ihlal ettiği her yerde sadece boştakileri kontrol etmek için, yazılımınız iddia başarısızlıklarına ve üretimin çökmesine karşı görev kritik olmadıkça, bu senaryonun potansiyelinin tercih edilebileceği düşünüldüğünde, delilik.

Bir kodlama standardının, bir fonksiyonda referans verilen her bir göstericinin NULL için (yani özel veri üyeleri için) kontrol edilmesini gerektirmesi makul mu?

Ben de kesinlikle hayır derim. "Güvenli" bile değil. Çok iyi olabilir ve kod üssünüz boyunca yıllar boyunca en korkunç senaryolara yol açabilecek her türlü hatayı maskeleyebilir.

assertburaya gitmenin yolu bu. Önkoşul ihlalleri farkedilmeden gitmesine izin verilmemelidir, aksi takdirde Murphy kanunu kolayca devreye girebilir.


1
"assert", gidilecek yoldur - ancak herhangi bir modern sistemde, bir imleç aracılığıyla her bir hafıza erişiminin donanıma yerleştirilmiş bir boş gösterici assertine sahip olduğunu unutmayın. Yani, "assert (p! = NULL); return * p;" bile, assert bile çoğunlukla anlamsızdır.
gnasher729

@ gnasher729 Ah, bu çok iyi bir nokta, ancak assertön koşulların ne olduğunu ve sadece iptal etmek için bir yol değil, her ikisini de bir dokümantasyon mekanizması olarak görmeye meyilliyim . Ancak aynı zamanda, hangi kod satırının bir hataya neden olduğunu ve assert koşulunun başarısız olduğunu ("çarpışmanın belgelenmesi") gösteren iddia başarısızlığının niteliğini de sevdim. Bir hata ayıklayıcısının dışında bir hata ayıklama yapısı kullanıyorsak ve bekçi kalmazsak kullanışlı olabilir.

@ gnasher729 Veya bunun bir bölümünü yanlış anlamadım. Bu modern sistemler iddianın hangi kaynak kod satırında başarısız olduğunu gösteriyor mu? Genelde bu noktada bilgiyi kaybettiklerini ve sadece segfault / erişim ihlali gösterebileceğini hayal ediyorum, ancak geliştirmemimin çoğu için hala "7" den uzak durdum.

Örneğin, MacOS X ve iOS'ta, uygulamanız geliştirme sırasında boş bir işaretçi erişimi nedeniyle çökerse, hata ayıklayıcı size gerçekleştiği tam kod satırını söyleyecektir. Başka garip bir yönü daha var: Şüpheli bir şey yaparsanız, derleyici size uyarılar vermeye çalışacaktır. Bir işaretçiyi bir işleve iletir ve erişirseniz, derleyici size işaretçinin NULL olabileceği konusunda bir uyarı vermez, çünkü çok sık gerçekleşir, uyarılarda boğulursunuz. Ancak, eğer bir karşılaştırma yaparsanız (p == NULL) ... derleyici, p'nin NULL olduğu için iyi bir şans olduğunu varsayar,
gnasher729

çünkü neden başka türlü test etmediniz, bu nedenle test etmeden kullanırsanız bundan sonra bir uyarı verecektir. Eğer kendi assert benzeri makronuzu kullanıyorsanız, bir şekilde yazmak için biraz zeki olmalısınız, böylece "my_assert (p! = NULL," Bu bir boş gösterici, aptal! "); * P = 1;" sana bir uyarı vermedi.
gnasher729

2

Örneğin, Objective-C , bir nilnesne üzerindeki her yöntem çağrısını sıfır-ish değerine göre değerlendirilemeyen bir işlem olarak değerlendirir. Sorunuzda önerilen nedenlerden dolayı, bu tasarım kararının Amaç-C'deki bazı avantajları vardır. Her yöntem çağrısının sıfır korumalı teorik konsepti, eğer iyi bir şekilde tanıtılırsa ve tutarlı bir şekilde uygulanırsa, bazı yararları vardır.

Bununla birlikte, kod bir ekosistemde yaşıyor, bir boşlukta değil. Sıfır korumalı davranış aptalca olmaz ve C ++ 'da şaşırtıcı olur ve bu nedenle zararlı olarak kabul edilmelidir. Özet olarak, kimse C ++ kodunu bu şekilde yazmaz, bu yüzden yapmayın ! Buna karşın, bir karşı örnek olarak, C veya C ++ ' free()da arayarak veya deleteon a çağrı NULLyapmamanın garanti edilmediğine dikkat edin.


Örneğinizde, kurucuya msgSenderboş olmayan bir iddia koymak muhtemelen faydalı olacaktır . Yapıcısı Eğer bir yöntemi olarak adlandırılan üzerine msgSenderhemen yine orada çökmesine olacağından, daha sonra böyle bir iddia, gerekli olacaktır. Bununla birlikte, yalnızca gelecekteki kullanım için sakladığı msgSender için SignalShutdown(), değerin nasıl olduğu hakkında bir yığın izine bakmaktan açıkça anlaşılmayacaktır NULL, bu nedenle yapıcıdaki bir iddia, hata ayıklamayı önemli ölçüde kolaylaştıracaktır.

Daha da iyisi, yapıcı const IMsgSender&muhtemelen olamayacak bir referans kabul etmelidir NULL.


1

Boş referanslardan kaçınmanızın istenmesinin nedeni, kodunuzun sağlam olmasını sağlamaktır. Uzun zaman önce junior programcı örnekleri sadece örneklerdir. Herkes kodu kazara kırabilir ve özellikle küreler ve sınıf küreler için boş bir kuralsızlığa neden olabilir. C ve C ++ 'da, doğrudan bellek yönetimi özelliği ile, yanlışlıkla daha da mümkündür. Şaşırmış olabilirsiniz, ancak bu tür şeyler çok sık gerçekleşir. Çok bilgili, çok deneyimli ve çok üst düzey geliştiriciler tarafından bile.

Her şeyi kontrol etmenize gerek yok, ancak null olma olasılığına sahip olan hakemlere karşı korunmanız gerekir. Bu genellikle farklı işlevlerde tahsis edildiğinde, kullanıldığında ve onaylandığında ortaya çıkar. Diğer fonksiyonlardan birinin değiştirilmesi ve fonksiyonunuzu bozması mümkündür. Aynı zamanda, diğer işlevlerden birinin arızalı olarak çağırılması da mümkündür (mesela yıkıcıdan ayrı olarak çağırılabilecek bir serbest bırakıcı varsa).

İş arkadaşlarınızın size assert kullanmakla birlikte anlattıkları yaklaşımı tercih ediyorum. Test ortamındaki çarpışma dolayısıyla üretimde incelikle düzelme ve bozulma sorunu olduğu daha açıktır.

Ayrıca, örtü veya destek gibi sağlam bir kod doğruluğu aracı kullanıyor olmalısınız. Ve tüm derleyici uyarılarını ele almalısınız.

Düzenleme: Diğerlerinin de belirttiği gibi, birkaç kod örneğinde olduğu gibi sessizce başarısız olmak da genelde yanlış olanıdır. Eğer fonksiyonunuz boş olan değerden kurtulamazsa, arayana bir hata döndürmelidir (veya istisna atar). Arayan kişi, çağrı sırasını düzeltmek, düzeltmek veya bir hatayı (veya bir istisna atarak) arayana göndermekten vb. Sorumludur. Sonunda, bir işlev zarif bir şekilde kurtarılabilir ve devam edebilir, zarif bir şekilde kurtarılabilir ve başarısız olabilir (bir kullanıcı için dahili bir hata nedeniyle işlem başarısız olan ancak gerçek zamanlı olarak çıkmayan bir veritabanı gibi) veya işlev uygulama durumunun bozuk olduğunu belirler ve kurtarılamaz ve uygulama çıkar.


(Görünüşte) popüler olmayan bir pozisyonu destekleme cesaretine sahip olduğu için +1. Bu konuda hiç kimse meslektaşlarımı savunmasaydı hayal kırıklığına uğrardım (onlar yıllardır kaliteli bir ürün ortaya koyan çok zeki insanlar). Ayrıca, uygulamaların "test ortamında çökmesi ... ve üretimde dikkatlice başarısız olması" fikrini de seviyorum. 'Zorbalık' NULLçeklerini zorunlu kılmanın bunu yapmanın yolu olduğuna ikna olmadım , ama işyerimin dışındaki birinden temel olarak söyleyen bir görüş almayı takdir ediyorum: "Aigh. Çok mantıklı görünmüyor ."
evadeflow

Malloc () işleminden sonra NULL için test eden insanlar gördüğümde sinirleniyorum. Bana modern işletim sistemlerinde belleği nasıl yönettiğini anlamadıklarını söylüyor.
Kristof Provost

2
Tahminime göre @ KristofProvost, malloc’un her zaman başarılı olduğu aşırı devir kullanan işletim sistemlerinden bahsediyor (ancak aslında yeterli bellek yoksa işletim sistemi daha sonra sürecinizi öldürebilir). Bununla birlikte, bu malloc'taki boş kontrolleri atlamak için iyi bir neden değildir: aşırı devir, platformlar arasında evrensel değildir ve etkin olsa bile, malloc'un başarısız olması için başka nedenler olabilir (örneğin, Linux'ta, ulimit ile ayarlanmış bir işlem adres alanı sınırı) ).
John Bartholomew

1
John ne dedi? Gerçekten fazla mesai hakkında konuşuyordum. Aşırı ödeme, Linux'ta varsayılan olarak etkindir (faturalarımı öder). Bilmiyorum, ama şüpheliyim, Windows'ta da aynıysa. Çoğu kasada, yine de kesinlikle çökmeyeceksin, asla ve asla kesinlikle olmayacak hataları ele almak için test edilecek bir sürü kod yazmaya hazır değilsen ...
Kristof Provost

2
Bana da ettik ekleyelim asla aslında doğru bir yetersiz bellek durumu idare edecek (bellek yetersiz realisticly mümkün GÜNCEL olan Linux çekirdeğinin dışında) kodu görüldü. Gördüğüm tüm kodlar yine de daha sonra kilitlenebilirdi. Gerçekleşen tek şey zaman kaybetmek, gerçek sorunu anlamak ve gizlemek için kodu zorlaştırmaktı. Boş başvuruların hata ayıklaması kolaydır (bir çekirdek dosyanız varsa).
Kristof Provost

1

Bir kullanımı pointer yerine ait referans söyle olur msgSenderise sadece bir seçenek ve burada boş çek doğru olurdu. Kod pasajı buna karar vermek için çok yavaş. Belki PowerManagerde değerli (veya test edilebilir) başka elementler vardır ...

İşaretçi ve referans arasında seçim yaparken, her iki seçeneği de iyice tartıyorum. Bir işaretçi için bir işaretçi kullanmak zorunda kalırsam (özel üyeler için bile), if (x)başvuruyu her değiştirdiğimde işlemi kabul etmem gerekir.


Bir yerde başka bir yorumda bahsettiğim gibi, bir referans enjekte etmenin mümkün olmadığı zamanlar vardır . Çok karşılaştığım bir örnek, tutulan nesnenin sahibinin referansına ihtiyaç duyduğu
yerdir

@evadeflow: Tamam, itiraf ediyorum, sınıf referansına ve işaretçi üyelerin görünürlüğüne (referans yapılamaz) bağlıdır; Sınıfın küçük ve düz olduğu ve işaretçilerin özel olduğu durumlarda, ben ...
Wolf

0

Ne olmak istediğine bağlı.

"Tüketici elektroniği cihazı" üzerinde çalıştığınızı yazdınız. , Her nasılsa, bir hata ayarlayarak tanıtıldı edilirse msgSender_için NULL, istediğiniz do

  • cihazın çalışmasını, atlamasını SignalShutdown, çalışmasının geri kalanını sürdürmesini sağlamak veya
  • cihazın çökmesine, kullanıcıyı yeniden başlatmaya zorlaması mı?

Kapatma sinyalinin gönderilmemesinin etkisine bağlı olarak, seçenek 1 geçerli bir seçenek olabilir . Kullanıcı müziğini dinlemeye devam edebilir, ancak ekranda yine de bir önceki parçanın adı gösteriliyorsa, bu durum cihazın tamamen çökmesine neden olabilir .

Tabii ki, seçenek 1'i seçerseniz, assert(diğerleri tarafından önerildiği gibi) böyle bir hatanın gelişim sırasında farkedilmeden sürünmesi olasılığını azaltmak için hayati önem taşır . ifBoş bekçi üretim kullanımda başarısızlık azaltılması için sadece vardır.

Şahsen, üretimin gelişimi için "erken çökme" yaklaşımını da tercih ediyorum, ancak bir hata durumunda kolayca düzeltilebilecek ve güncellenebilecek iş yazılımları geliştiriyorum. Tüketici elektroniği cihazları için bu o kadar kolay olmayabilir.

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.