Eğer boş ise, neden modern diller onu uygular? [kapalı]


82

Java veya C # gibi dil tasarımcılarının boş referansların varlığına ilişkin sorunları bildiğinden eminim (bkz. Boş referanslar gerçekten kötü bir şey mi? ). Ayrıca bir seçenek türü uygulamak, boş başvurulardan çok daha karmaşık değildir.

Neden yine de dahil etmeye karar verdiler? Boş referansların bulunmamasının hem dil yaratıcılarından hem de kullanıcılardan daha kaliteli kodları (özellikle daha iyi kütüphane tasarımı) teşvik edeceği (hatta zorlayacak) eminim.

Basitçe muhafazakarlık yüzünden mi - "diğer dillerde var, bizde de olmalı ..."?


99
null harika. Onu seviyorum ve her gün kullanıyorum.
Pieter B

17
@PieterB Ancak referansların çoğu için mi kullanıyorsunuz, yoksa çoğu referansın boş kalmamasını mı istiyorsunuz? Bu argüman null verilerinin olmaması gerektiği, sadece açık ve seçmeli olması gerektiğidir.

11
@PieterB Fakat çoğunluk null edilemez olduğunda, temerrütten ziyade istisnasızlığı boşuna yapmak mantıklı olmaz mı? Her zamanki seçenek türlerinin tasarımının, eksiklik ve paket açma için açık denetlemeye zorlamak olmasına rağmen, birinin, kabul edilemeyecek referansları almak için iyi bilinen Java / C # / ... anlambilgisine de sahip olabileceğini unutmayın. boşsa). En azından bazı hataları önler ve eksik boş kontrolleri çok daha pratik yapmaktan şikayet eden statik bir analiz yapar.

20
WTF size bağlı mı? Yazılımla ilgili olabilecek ve yanlış giden her şeyden, sıfırdan vazgeçmeye çalışmak hiç sorun değil. HER ZAMAN bir AV / segfault oluşturur ve böylece sabitlenir. Bu konuda endişelenmen gereken çok fazla böcek kıtlığı var mı? Eğer öyleyse, çok fazla boş yerim var ve hiçbiri boş referanslar / işaretçilerle ilgili problemler yaratmıyor.
Martin James

13
@MartinJames "HER ZAMAN bir AV / segfault oluşturur ve böylece sabitlenir" - hayır, hayır değil.
14'te

Yanıtlar:


97

Yasal Uyarı: Herhangi bir dil tasarımcısını şahsen tanımadığım için size verdiğim cevap spekülatif olacaktır.

Gönderen Tony Hoare kendisi:

Milyar dolarlık hatam diyorum. 1965'teki boş referansın icadıydı. O zamanlar, nesne yönelimli bir dilde (ALGOL W) referanslar için ilk kapsamlı tip sistemi tasarlıyordum. Amacım, derleyici tarafından otomatik olarak yapılan kontrollerle, referansların tüm kullanımının kesinlikle güvenli olmasını sağlamaktı. Fakat boş bir referansta bulunma eğilimine karşı koyamadım, çünkü uygulanması çok kolaydı. Bu, son kırk yılda muhtemelen bir milyar dolarlık acı ve hasara neden olan sayısız hata, güvenlik açığı ve sistem çökmesine neden oldu.

Vurgu madeni.

Doğal olarak o zaman ona kötü bir fikir gibi gelmedi. O bu aynı nedenle kısmen koruyan oldu olasıdır - bu hızlı sıralama Turing ödüllü mucidi iyi bir fikir gibi görünüyordu, eğer birçok kişi şaşırtıcı değil hala o kötülük neden anlamıyorum. Kısmen de muhtemeldir, çünkü yeni dillerin hem pazarlama hem de öğrenme eğrisi nedenleriyle eski dillere benzemesi uygundur. Konuşma konusu olan mesele:

“C ++ programcılarının peşindeydik. Yarısından çoğunu Lisp'a sürüklemeyi başardık.” -Guy Steele, Java filminin ortak yazarı

(Kaynak: http://www.paulgraham.com/icad.html )

Ve tabii ki, C ++ 'ın boş değeri var çünkü C' nin değeri boş ve C'nin tarihsel etkisine girmeye gerek yok. C #, Microsoft'un Java uygulaması olan J ++ 'yı değiştirdi ve aynı zamanda C ++' ı Windows geliştirme için tercih edilen dil olarak değiştirdi, bu yüzden her ikisinden de alabilirdi.

EDIT İşte, Hoare'den göz önünde bulundurmaya değer bir başka alıntı:

Genel olarak programlama dilleri eskisinden çok daha karmaşıktır: nesne yönelimi, kalıtım ve diğer özellikler hala tutarlı ve bilimsel olarak iyi temellenmiş bir disiplin veya doğruluk teorisi açısından düşünülmemektedir. . Hayatım boyunca bir bilim insanı olarak takip ettiğim orijinal önermem, bir doğruluk ölçütünü, kullanıcılarına tuzak koymayan, iyi bir programlama dili tasarımına yakınlaşmanın bir aracı olarak kullanmaktır. programın farklı bileşenlerinin açıkça spesifikasyonunun farklı bileşenlerine karşılık geldiği için, bileşimle ilgili olarak bu konuda bir neden olabilirsiniz. [...] Derleyici de dahil olmak üzere araçlar doğru bir program yazmanın ne demek olduğuna dair bazı teorilere dayanmak zorundadır. - Philip L. Frana, 17 Temmuz 2002, Cambridge, İngiltere; Charles Babbage Enstitüsü, Minnesota Üniversitesi. [ Http://www.cbi.umn.edu/oh/display.phtml?id=343]

Yine, benimkine vurgu yap. Sun / Oracle ve Microsoft, şirketlerdir ve herhangi bir şirketin özü paradır. Kendilerine kazandırmanın yararları null, eksilere ağır basmış olabilir veya konuyu tam olarak düşünmek için çok sıkı bir son tarih almış olabilirler. Muhtemelen son teslim tarihleri ​​nedeniyle meydana gelen farklı bir dil farkına bir örnek olarak:

Cloneable'ın kırılması utanç verici bir şey, ama oluyor. Orijinal Java API'leri, kapanış pazar penceresini karşılamak için sıkı bir süre içerisinde çok hızlı bir şekilde yapıldı. Orijinal Java ekibi inanılmaz bir iş çıkardı, ancak API'lerin tümü mükemmel değil. Klonlanabilir, zayıf bir nokta ve insanların sınırlarının farkında olmaları gerektiğini düşünüyorum. -Josh Bloch

(Kaynak: http://www.artima.com/intv/bloch13.html )


32
Sevgili downvoter: cevabımı nasıl geliştirebilirim?
Doval

6
Aslında soruya cevap vermedin; sadece bazı gerçekler sonrası görüşler ve “maliyet” hakkında el sallama konusunda bazı alıntılar yaptınız. (Eğer sıfır milyar dolarlık bir hata ise, MS ve Java tarafından uygulamaya
konan tasarruflar

29
@DougM Ne yapmamı, son 50 yıldaki her dil tasarımcısını bulmamı ve nullkendi dilinde neden uygulanmadığını sormamı mı bekliyorsun? Bu soruya verilecek herhangi bir cevap, bir dil tasarımcısından gelmediği sürece spekülatif olacaktır. Eric Lippert dışında bu sitedeki sık rastlananları bilmiyorum. Son bölüm sayısız nedenlerden dolayı kırmızı bir ringa balığı. MS ve Java'nın API'leri üzerine yazılan 3. parti kodunun miktarı açıkça API'nin içindeki kod miktarından ağır basmaktadır. Müşterileriniz istiyorsa null, onları verin null. Ayrıca kabul ettiklerini de nullonlara paraya mal olduğunu varsayalım .
Doval

3
Verebileceğiniz tek cevap spekülatif ise, açılış paragrafınızda açıkça belirtin. (Size cevap ne şekilde artırabileceğini sordu, ben cevap Herhangi parantez sadece görmezden çekinmeyin olabilir yorumdur, yani sonuçta İngilizce için hangi parantez bu..)
DougM

7
Bu cevap makul; Benimkine biraz daha önem verdim. ICloneable.NET'te de benzer şekilde kırıldığını unutmayın ; Maalesef burası Java eksikliklerinin zaman içinde öğrenilmediği bir yer.
Eric Lippert

121

Java veya C # gibi dil tasarımcılarının boş referansların varlığına ilişkin sorunları bildiğinden eminim

Tabii ki.

Ayrıca bir seçenek türü uygulamak boş referanslardan çok daha karmaşık değildir.

Naçizane size katılmıyorum! C # 2'deki null değerine giren tasarım konuları karmaşık, tartışmalı ve zordu. Hem dillerin hem de çalışma zamanlarının tasarım ekiplerini aylarca süren tartışmalara, prototiplerin uygulanmasına, vb. Götürdüler ve gerçekte açılabilir boks anlamını aslında çok tartışmalı olan C # 2.0'a çok yakın bir şekilde değiştirdiler.

Neden yine de dahil etmeye karar verdiler?

Tüm tasarım, incelikli ve kaba bir şekilde uyumsuz hedefler arasından seçim yapma sürecidir; Göz önünde bulundurulması gereken faktörlerden yalnızca birkaçı taslağını verebilirim:

  • Dil özelliklerinin dikliği genellikle iyi bir şey olarak kabul edilir. C #, null değer türlerine, null olmayan değer türlerine ve null referans değerlerine sahiptir. Null olmayan referans türleri mevcut değildir, bu da tip sistemini ortogonal yapar.

  • Mevcut C, C ++ ve Java kullanıcılarına aşinalık önemlidir.

  • COM ile birlikte çalışabilirlik önemlidir.

  • Diğer tüm .NET dilleriyle birlikte çalışabilirlik önemlidir.

  • Veritabanlarıyla kolay birlikte çalışabilirlik önemlidir.

  • Anlambilimin tutarlılığı önemlidir; TheKingOfFrance 'in null' a eşit olması durumunda, her zaman "şu anda Fransa Kralı yok" anlamına mı geliyor, ya da "Kesinlikle bir Fransa Kralı var; şu an kim olduğunu bilmiyorum" anlamına mı geliyor? ya da "Fransa’da bir Krala sahip olma kavramı saçma, bu yüzden soruyu sorma!" anlamına mı geliyor? Null, tüm bunları ve daha fazlasını C # ile ifade edebilir ve tüm bu kavramlar faydalıdır.

  • Performans maliyeti önemlidir.

  • Statik analize yatkın olmak önemlidir.

  • Tip sisteminin tutarlılığı önemlidir; Null olmayan bir referansın hiçbir zaman geçersiz olduğu gözlemlenen koşullar altında olmadığını her zaman bilebilir miyiz ? NULL olmayan bir referans türü olan bir nesnenin yapıcısına ne demeli? Peki ya referansı doldurması gereken kod bir istisna attığı için böyle bir nesnenin sonlandırıcısında, nesnenin sonlandırıldığı yerde ? Garantileri konusunda size yalan söyleyen bir tip sistem tehlikelidir.

  • Peki ya anlambilimin tutarlılığı? Boş değerler kullanıldığında yayılır, ancak boş referanslar kullanıldığında istisnalar atar. Bu tutarsız; Bu tutarsızlık bir fayda ile haklı mı?

  • Bu özelliği diğer özellikleri bozmadan da uygulayabilir miyiz? Bu özellik gelecekteki başka hangi olası özellikleri engelliyor?

  • Sahip olduğun ordu ile savaşa girersin, istediğin kişiyle değil. Unutmayın, C # 1.0 jeneriklere sahip değildi, bu yüzden Maybe<T>alternatif olarak konuşmak tamamen marşsız bir şey. Çalışma zamanı ekibi yalnızca boş başvuruları kaldırmak için jenerik ürün eklerken, .NET iki yıl boyunca kaymalı mıydı?

  • Tip sisteminin tutarlılığı ne durumda? Nullable<T>Herhangi bir değer türü için söyleyebilirsiniz - hayır, bekleyin, bu bir yalan. Söyleyemezsin Nullable<Nullable<T>>. Yapabilmelisin mi? Eğer öyleyse, istenen anlambilim nedir? Tüm bu tip sistemin sadece bu özellik için özel bir durumu var mıdır?

Ve bunun gibi. Bu kararlar karmaşık.


12
Her şey için +1 fakat özellikle jenerik ürün yetiştiriyor. Hem Java hem de C # tarihinde jeneriklerin bulunmadığı bir zaman dilimi olduğunu unutmak kolaydır.
Doval

2
Belki aptal bir soru (sadece bir BT lisansıyım) - ancak, kullanmadan önce "değere sahip" kontrol gerektiren normal bir null referansı olarak sözdizimi düzeyinde (CLR ile ilgili hiçbir şey bilmeden) uygulanamadı. kod? Seçenek türlerinin çalışma zamanında herhangi bir çeke ihtiyacı olmadığına inanıyorum.
mrpyo

2
@mrpyo: Elbette, bu olası bir uygulama seçimi. Diğer tasarım seçimlerinden hiçbiri kaybolmaz ve bu uygulama seçiminin kendine göre birçok avantaj ve dezavantajı vardır.
Eric Lippert

1
@mrpyo "Değer-değer" kontrolünü zorlamanın iyi bir fikir olmadığını düşünüyorum. Teorik olarak bu çok iyi bir fikir, ancak pratikte IMO, Java'daki derleyici benzeri denetlenen istisnaları ve onu kandıran insanları catcheshiçbir şey yapmadan yerine getirmek için her türlü boş kontrolü getirecek . Muhtemelen geçersiz bir durumda çalışmaya devam etmek yerine sistemin havaya uçurulmasının daha iyi olacağını düşünüyorum.
NothingsImpossible

2
@voo: Null edilemeyen referans türündeki diziler birçok nedenden dolayı zor. Pek çok olası çözüm var ve hepsi farklı operasyonlara maliyet getiriyor. Supercat'in önerisi, bir öğenin atanmadan önce yasal olarak okunup okunamayacağını takip etmektir, bu da maliyetler getirir. Sizinki bir dizi başlatıcının dizi görünmeden önce her eleman üzerinde çalışmasını sağlamaktır, bu da farklı bir maliyet kümesi uygular. Yani burada ovmak: olursa olsun, birisi için verimli değildir şikayetçi oluyor birini seçer bu tekniklerin hangi onların evcil senaryosu. Bu, özelliğe karşı ciddi bir noktadır.
Eric Lippert

28

Null, değer eksikliğini temsil etmek için çok geçerli bir amaca hizmet eder.

Özellikle liberal olarak kullanıldığında, null suistimalleri ve baş ağrıları ve neden olabilecek acıları hakkında bildiğim en vokal insanım diyeceğim.

Benim kişisel duruşum, insanların sadece gerekli ve uygun olduğunu haklı çıkarabilecekleri zaman null kullanabilmeleridir .

Null'leri yasaklama örneği:

Ölüm Tarihi, tipik olarak nULL olabilecek bir alandır. Ölüm tarihi olan üç olası durum vardır. Ya kişi öldü, tarih belli, kişi öldü ve tarih bilinmiyor, ya da kişi ölmedi ve bu nedenle ölüm tarihi yoktur.

Ölüm Tarihi ayrıca bir DateTime alanıdır ve "bilinmeyen" veya "boş" bir değere sahip değildir. Kullanılan dile bağlı olarak değişen yeni bir tarih-saat oluşturduğunuzda çıkacak varsayılan tarihe sahip olmakla birlikte, teknik olarak o kişinin ölmesi ve teknik olarak “boş değeriniz” olarak işaretlenmesi gibi bir olasılık var. varsayılan tarihi kullanın.

Verilerin durumu doğru bir şekilde göstermesi gerekir.

Ölü kişinin ölüm tarihi olduğu biliniyor (3/9/1984)

Basit, '3/9/1984'

Kişi öldü ölüm tarihi bilinmiyor

Peki en iyisi nedir? Boş , '0/0/0000' veya '01 / 01/1869 '(veya varsayılan değeriniz nedir?)

Kişi ölü değil ölüm tarihi geçerli değildir

Peki en iyisi nedir? Boş , '0/0/0000' veya '01 / 01/1869 '(veya varsayılan değeriniz nedir?)

Öyleyse her değeri düşünelim ...

  • Boş , dikkatli olmanız gereken bazı sonuçları ve endişeleri var, örneğin ilk önce boş olmadığını onaylamadan kazara manipüle etmeye çalışmak, örneğin bir istisna atar, ancak aynı zamanda en iyi durumu gösterir ... Kişi ölmezse ölüm tarihi yok ... hiçbir şey ... boş ...
  • 0/0/0000 , Bu, bazı dillerde tamam olabilir ve tarihin uygun bir gösterimi bile olabilir. Ne yazık ki, bazı diller ve validasyon bunu geçersiz bir tarih olarak reddeder, bu da çoğu zaman işe yaramaz hale getirir.
  • 1/1/1869 (veya varsayılan datetime değeriniz ne olursa olsun) , buradaki sorun işlenmesi zorlaşır . Bunu, değer eksikliğiniz olarak kullanabilirsiniz, tüm kayıtlarımı filtrelemek istersem, ölüm tarihim olmadığı için ne olur? Veri bütünlüğü sorunlarına neden olabilecek o tarihte ölen insanları kolayca filtreleyebiliyordum.

Aslında bazen edilir Do şey temsil etmek ve emin bazen değişken türü bunun için iyi çalışır, ama çoğu zaman değişken türlerinin hiçbir şey temsil etmek gerekiyor gerekir.

Elmaları yoksa 0 elmaları var, ama ne kadar elmaları olduğunu bilmiyorsam?

Elbette, null kötüye kullanılır ve potansiyel olarak tehlikelidir, ancak zaman zaman gereklidir. Bu sadece birçok durumda varsayılandır, çünkü ben bir değer verene kadar bir değerin olmaması ve bir şeyin onu temsil etmesi gerekir. (Boş)


37
Null serves a very valid purpose of representing a lack of value.Bir Optionveya Maybetype, type sistemini atlatmadan bu çok geçerli amaca hizmet eder.
Doval

34
Hiç kimse değer-değer eksikliğinin olmaması gerektiğini, eksik olabilecek değerlerin potansiyel olarak eksik olan her değerden ziyade açıkça belirtilmesi gerektiğini savunuyor .

2
Sanırım RualStorge SQL ile ilgili konuşuyordu, çünkü her sütunun işaretlenmesi gerektiğini belirten kamplar var NOT NULL. Benim sorum RDBMS ile ilgili değildi ...
mrpyo

5
"Değer yok" ve "bilinmeyen değer" arasında ayrım yapmak için +1
David

2
Bir kişinin durumunu ayırmak daha mantıklı olmaz mıydı? Yani bir Persontür vardır statetip alanını Statebir ayrımcılığa birliği olduğunu Aliveve Dead(dateOfDeath : Date).
jon-hanson,

10

"Diğer dillerde var, bizde de olmalı ..." kadar Joneses'e ayak uydurmuş gibi gitmeyecektim. Herhangi bir yeni dilin en önemli özelliği, diğer dillerde mevcut kütüphanelerle birlikte çalışabilmesidir (okuma: C). C boş işaretçilere sahip olduğundan, birlikte çalışabilirlik katmanı mutlaka boş (veya kullandığınızda patlayan "başka bir" yok "eşdeğeri) kavramına ihtiyaç duyar.

Dil tasarımcısı Seçenek Türlerini kullanmayı seçebilir ve sizi null yolundaki her şeyi null yolunu kullanmaya zorlayabilirdi . Ve bu neredeyse kesinlikle daha az hataya yol açacaktır.

Ancak (özellikle Java ve C # için giriş zamanlamaları ve hedef kitleleri nedeniyle) bu birlikte çalışabilirlik katmanı için seçenek türleri kullanmak, benimsemelerine torpil vermediyse büyük olasılıkla zarar görecektir. Seçenek türü tamamen geçilir, orta ila 90'ların sonundaki C ++ programcılarının canını sıkıcı - veya birlikte çalışabilirlik katmanı, sıfırlarla karşılaşırken istisnalar atar, orta ila 90'ların sonundaki sıkıntıları C ++ programcılarından rahatsız eder. ..


3
İlk paragraf bana mantıklı gelmiyor. Java, önerdiğiniz biçimde bir C birlikte çalışmasına sahip değildir (JNI var ancak referanslar ile ilgili her şey için bir düzine çemberin içinden atlar ; artı pratikte nadiren kullanılır), diğer "modern" diller için de aynıdır.

@delnan - üzgünüm, bu tür bir birlikte çalışma içeren C # ile daha aşinayım. Temel Java kütüphanelerinin çoğunun altta JNI kullandığını varsaymıştım.
Telastyn

6
Boş vermeye izin vermek için iyi bir argüman yaparsınız, ancak teşvik etmeden boşluğa izin verebilirsiniz . Scala buna güzel bir örnek. Null kullanan Java apis ile sorunsuz bir şekilde birlikte çalışabilir, ancak OptionScala'da kullanmak kadar kolay olan bir kullanım için kullanmanız önerilir val x = Option(possiblyNullReference). Uygulamada, insanların faydalarını görmeleri çok uzun sürmez Option.
Karl Bielefeldt

1
Seçenek türleri, C # ne yazık ki sahip olmadığı (statik olarak doğrulanmış) desen eşleştirmesiyle el ele gider. F # yapar ve harikadır.
Steven Evers

1
@SteveEvers Özel kurucusu olan soyut bir temel sınıf, mühürlü iç sınıflar ve Matchdelegeleri argüman olarak alan bir yöntem kullanarak sahte olabilir . Daha sonra lambda ifadelerini Match(adlandırılmış argümanları kullanmak için bonus puanlar) Matchiletir ve doğru olanı çağırırsınız.
Doval

7

Her şeyden önce, bir sıfırlık kavramının gerekli olduğu konusunda hepimizin hemfikir olabileceğini düşünüyorum . Bilginin yokluğunu göstermemiz gereken bazı durumlar var .

nullReferanslara (ve işaretçilere) izin vermek bu kavramın yalnızca bir uygulamasıdır ve sorunların bilindiği bilinmesine rağmen muhtemelen en popüler olanıdır: C, Java, Python, Ruby, PHP, JavaScript, ... hepsi benzerini kullanır null.

Neden ? Peki alternatif nedir?

Haskell gibi işlevsel dillerde siz Optionveya Maybetürünüz; ancak bunlar üzerine inşa edilmiştir:

  • parametrik tipler
  • cebirsel veri tipleri

Şimdi, orijinal C, Java, Python, Ruby veya PHP bu özelliklerden herhangi birini destekliyor mu? Hayır. Java'nın kusurlu jenerik dili dilin tarihinde son zamanlarda yer almaktadır ve bir şekilde diğerlerinin de onları uyguladığından şüpheliyim.

İşte aldın. nullkolaydır, parametrik cebirsel veri tipleri daha zordur. İnsanlar en basit alternatife gitti.


+1 "boş, kolay, parametrik cebirsel veri türleri daha zor." Ancak bence parametrik yazım ve ADT'lerin daha zor olması çok da önemli değildi; Sadece gerektiği gibi algılanmadıkları için. Java bir nesne sistemi olmadan gönderildiyse, diğer taraftan, kaymış olurdu; OOP "gösterme" özelliği idi, eğer sahip değilseniz, hiç kimse ilgilenmedi.
Doval,

@Doval: Pekala, OOP Java için gerekli olabilirdi, ama C için değildi :) Ama Java'nın basit olmayı hedeflediği doğru. Ne yazık ki, insanlar basit bir dilin çok garip olan basit programlara yol açtığını varsayıyor gibi görünmektedir (Brainfuck çok basit bir dildir ...), ancak karmaşık dillerin (C ++ ...) ya da her derde deva olmadığına kesinlikle katılıyoruz. inanılmaz derecede faydalı olabilirler.
Matthieu M.

1
@MatthieuM .: Gerçek sistemler karmaşıktır. Karmaşıklıkları modellenen gerçek dünya sistemine uygun, iyi tasarlanmış bir dil, karmaşık sistemin basit kodla modellenmesine izin verebilir. Bir dili fazla basitleştirmeye çalışmak, karmaşıklığı basitçe onu kullanan programcının üzerine itmek.
supercat,

@supercat: Daha fazla katılamadım. Ya da Einstein'ın aynen ifade ettiği gibi: "Her şeyi mümkün olduğunca basit ama basitleştirmeyin."
Matthieu M.,

@MatthieuM .: Einstein birçok yönden bilge idi. "Her şeyin bir nesne olduğunu, referans olarak saklanabileceğini" kabul etmeye çalışan diller Object, pratik uygulamaların paylaşılmayan değişken nesnelere ve paylaşılabilir değişken nesnelere (her ikisi de değerler gibi davranmalı) ve ayrıca paylaşılabilir ve paylaşılamaz nesnelere ihtiyaç duyduğunu kabul etmiyor varlıklar. ObjectHer şey için tek bir tür kullanmak , bu tür ayrımlara duyulan ihtiyacı ortadan kaldırmaz; onları doğru bir şekilde kullanmayı zorlaştırır.
supercat,

5

Boş / nil / hiçbiri kötü değildir.

Yanıltıcı olarak bilinen ünlü konuşmacısı "The Billion dollar Mistake" i izlerseniz, Tony Hoare herhangi bir değişkenin null alabilmesinin nasıl sağlanabileceğinin büyük bir hata olduğunu söyler. Alternatif - Seçeneklerini kullanarak - yok değil aslında boş referanslar kurtulmak. Bunun yerine, hangi değişkenlerin boş bırakılmasına izin verildiğini ve hangilerinin izin vermediğini belirlemenizi sağlar.

Nitekim, uygun istisna işlemeyi uygulayan modern dillerle, boş değer dereference hataları, diğer istisnalardan farklı değildir; Boş referanslara bazı alternatifler (örneğin, Null Object pattern) hataları gizler ve olayların daha sonraları sessizce başarısız olmasına neden olur. Bence hızlı başarısız olmak çok daha iyi .

Öyleyse soru şu ki, neden diller Seçenekler'i uygulayamıyor? Nitekim, tüm zamanların tartışmasız en popüler dili olan C ++, atanamayan nesne değişkenlerini tanımlama yeteneğine sahiptir NULL. Bu, Tony Hoare konuşmasında bahsettiği "boş sorun" için bir çözüm. Neden bir sonraki en popüler yazılı dil olan Java'da yok? Birileri , özellikle kendi tip sisteminde, neden bu kadar çok kusuru olduğunu sorabilir . Dillerin sistematik olarak bu hatayı yaptığını söyleyebileceğinizi sanmıyorum. Bazıları yapar, bazıları yapmaz.


1
Java'nın uygulama açısından en güçlü yönlerinden biri, ancak dil açısından zayıf yönleri, yalnızca ilkel olmayan bir tür olmasıdır: Sözde Nesne Referansı. Bu, çalışma zamanını büyük ölçüde basitleştirerek, bazı son derece hafif JVM uygulamalarını mümkün kılar. Bu tasarım, ancak, her tip bir varsayılan değere sahip ve karışık Nesne için mümkündür varsayılan referans gerektiği anlamına gelir null.
supercat,

Eh, herhangi bir oranda bir ilkel olmayan tür kök . Bu neden dil açısından zayıf bir nokta? Bu gerçeğin neden her türün varsayılan bir değere sahip olduğunu (veya bunun yerine birden çok kök türünün neden türlerin varsayılan değere sahip olmasına izin verdiğini) anlamıyorum ya da bunun bir zayıflık olduğunu anlamıyorum.
BT,

Bir alan ya da dizi elemanı başka hangi ilkel olmayan türleri tutabilir? Zayıflık, bazı referansların kimliğin kapsüllenmesi için, bazılarının ise burada tanımlanan nesneler içinde yer alan değerleri kapsüllemek için kullanılmasıdır. Kimliği kapsüllemek için kullanılan referans tipi değişkenler nulliçin tek mantıklı varsayılandır. Bununla birlikte, değeri kapsüllemek için kullanılan referanslar, bir tipin makul bir varsayılan örneği olabileceği veya inşa edebileceği durumlarda makul bir varsayılan davranışa sahip olabilir. Referansların nasıl davranması gerektiğinin pek çok yönü, değeri nasıl içerdiklerine ve
kapsamadıklarına

... Java tipi sistemin bunu ifade etmenin yolu yok. Eğer foobir tek referansı tutan int[]içeren {1,2,3}ve kod isteyen foobir başvuru tutmak için int[]içeren {2,2,3}, en hızlı şekilde bu artırmak olacaktır ulaşmak için foo[0]. Kod yöntem olduğunu bildirmek isterse footutar {1,2,3}, diğer yöntem diziyi değiştirmek ne de noktanın ilerisine bir başvuru kalıcı olmaz foodeğiştirmek isteyeyim, bunu sağlamak için en hızlı yol diziye bir başvuru geçmek olacaktır. Java "geçici salt okunur başvuru" türüne
sahipse

... dizi, geçici bir referans olarak güvenli bir şekilde geçirilebilir ve değerini korumak isteyen bir yöntem, onu kopyalaması gerektiğini bilirdi. Böyle bir türün yokluğunda, bir dizinin içeriğini güvenli bir şekilde ortaya çıkarmak için tek yol, ya bunun bir kopyasını çıkarmak ya da sadece bu amaç için oluşturulan bir nesneyi içine almaktır.
supercat

4

Çünkü programlama dilleri teknik olarak doğru olmaktan ziyade pratik olarak yararlı olacak şekilde tasarlanmıştır. Gerçek şu ki, nulldevletler ya kötü ya da eksik veriler ya da henüz kararlaştırılmamış bir durumdan dolayı ortak bir durumdur. Teknik olarak üstün çözümler tamamen boş durumlara izin vermek ve programcıların hata yapmaları gerçeğini emmekten ziyade hantaldır.

Örneğin, bir dosyayla çalışan basit bir komut dosyası yazmak istersem şöyle bir sözde kod yazabilirim:

file = openfile("joebloggs.txt")

for line in file
{
  print(line)
}

joebloggs.txt yoksa ve basitçe başarısız olacaktır. Sorun şu ki, muhtemelen basit olan komut dosyaları için ve daha karmaşık koddaki birçok durum için bunun var olduğunu ve başarısızlık olmayacağını biliyorum, bu yüzden beni zamanımı boşa harcamayı kontrol etmeye zorluyor. Daha güvenli alternatifler, beni potansiyel başarısızlık durumuyla doğru bir şekilde başa çıkmaya zorlayarak güvenliğine kavuşurlar, ancak çoğu zaman bunu yapmak istemiyorum, sadece devam etmek istiyorum.


13
Ve burada, null değerinde tam olarak neyin yanlış olduğuna dair bir örnek verdiniz. Düzgün bir şekilde uygulanan "openfile" işlevi, gerçekleşen olayın tam bir açıklamasıyla orada çalışmayı durduracak bir istisna (eksik dosya için) atmalıdır. Bunun yerine, eğer null değerini döndürürse, ileriye doğru yayılır for line in fileve bu kadar basit bir program için uygun olan ancak çok daha karmaşık sistemlerde gerçek hata ayıklama sorunlarına neden olan anlamsız boş referans istisnası atar. Eğer boşlar olmasaydı, "openfile" tasarımcısı bu hatayı yapamazdı.
mrpyo

2
+1 "Çünkü programlama dilleri genellikle teknik olarak doğru değil pratik olarak faydalı olacak şekilde tasarlandı"
Martin Ba

2
Tanıdığım her seçenek türü, kısa devre dışı bırakma yöntemini tek bir kısa ekstra yöntem çağrısı ile yapmanızı sağlar (Rust örneği:) let file = something(...).unwrap(). POV'nize bağlı olarak, hatalarla başa çıkmamanın kolay bir yolu veya boş değerin gerçekleşemeyeceği kesin bir iddia. Boşa zaman çok az olduğunu ve bir şey olmadığını anlamaya gerek yok çünkü başka yerlerde zamandan tasarruf edebilirsiniz boş. Diğer bir avantaj (kendi başına ekstra aramaya değebilir), hata durumunu açıkça görmezden gelmenizdir ; başarısız olduğunda, neyin yanlış gittiğinden ve düzeltmenin nereye gitmesi gerektiğinden şüphe yoktur.

4
@mrpyo Bütün diller istisnaları ve / veya istisnaları işlemeyi desteklemez (bir deneyin / yakalayın). İstisnalar da kötüye kullanılabilir - "akış kontrolü olarak istisna" ortak bir kalıptır. Bu senaryo - bir dosya mevcut değil - bu anti-paternin en sık alıntı yapılan örneği AFAIK. Kötü bir antrenmanı başka biriyle değiştiriyor görünüyorsun
David,

8
@mrpyo if file exists { open file }bir yarış durumundan muzdarip. Bir dosyayı açmanın başarılı olup olmadığını bilmenin tek güvenilir yolu onu açmayı denemektir.

4

Orada net ve pratik kullanımları vardır NULL(ya nilya Nilya nullya Nothingya da her ne tercih ettiğiniz dil denir) pointer.

Bir istisna sistemine sahip olmayan diller için (örn. C), bir işaretçinin döndürülmesi gerektiğinde boş bir işaretçi bir hata işareti olarak kullanılabilir. Örneğin:

char *buf = malloc(20);
if (!buf)
{
    perror("memory allocation failed");
    exit(1);
}

Burada bir NULLgeri dönüş malloc(3)başarısızlık işareti olarak kullanılır.

Method / function argümanlarında kullanıldığında, argüman için varsayılan kullanım olduğunu gösterebilir veya çıktı argümanını yok sayar. Aşağıdaki örnek

İstisna mekanizmasına sahip diller için bile, boş bir işaretçi, özellikle istisna işlemesi pahalı olduğunda (örneğin Objective-C) yumuşak hatanın (yani kurtarılabilir hataların) göstergesi olarak kullanılabilir.

NSError *err = nil;
NSString *content = [NSString stringWithContentsOfURL:sourceFile
                                         usedEncoding:NULL // This output is ignored
                                                error:&err];
if (!content) // If the object is null, we have a soft error to recover from
{
    fprintf(stderr, "error: %s\n", [[err localizedDescription] UTF8String]);
    if (!error) // Check if the parent method ignored the error argument
        *error = err;
    return nil; // Go back to parent layer, with another soft error.
}

Burada yumuşak hata, yakalanmadığı takdirde programın çökmesine neden olmaz. Bu, Java gibi çılgınca denemeyi ortadan kaldırır ve yumuşak hatalar kesintiye uğramadığı için program akışında daha iyi bir kontrole sahiptir (ve geriye kalan birkaç istisna hariç, genellikle kurtarılamaz ve yakalanamaz)


5
Sorun, asla içermesi gerekmeyen değişkenleri ayırt etmenin bir yolu nullolmamasıdır. Örneğin, Java'da 5 değer içeren yeni bir tür istersem, bir enum kullanabilirim, ancak elde ettiğim şey 6 değeri tutabilen bir tür (5 istediğim + null). Tip sistemindeki bir kusur.
Doval

@Doval Durum buysa, NULL'a bir anlam verin (veya bir varsayılanınız varsa, bunu varsayılan değerin eş anlamlısı olarak kabul edin) veya NULL'u (hiçbir zaman ilk sırada görünmemesi gereken) yumuşak hatanın işareti olarak kullanın (yani hata, ancak en azından henüz çökmedi))
Maxthon Chan

1
@MaxtonChan Nullyalnızca bir türün değerleri veri taşımadığında (örneğin, enum değerleri) bir anlam atanabilir. Değerleriniz daha karmaşık bir şey olur olmaz (örneğin bir yapı), nullbu tip için anlamlı bir anlam verilemez. nullBir yapı veya liste olarak kullanmanın bir yolu yoktur . Ve, yine, nullbir hata sinyali olarak kullanımdaki sorun neyin null döndüğünü veya null kabul ettiğini söyleyemememizdir. Programınızdaki herhangi bir değişken , hiç kimsenin yapmadığı her kullanımdan önce her nullbirini kontrol etme konusunda çok titiz olmadığınız sürece olabilir null.
Doval,

1
@Doval: nullKullanılabilir bir varsayılan değer olarak değiştirilemez bir referans tipine sahip olmanın belirli bir doğal zorluğu olmazdı (örneğin string, önceki Ortak Nesne Modeli altında olduğu gibi, boş bir dize gibi davranmak için varsayılan değere sahip ). Tüm bunlar, sanal olmayan üyeleri çağırmak callyerine , dilleri kullanmak için gerekli olacaktı callvirt.
supercat,

@supercat Bu iyi bir nokta, ama şimdi değişmez ve değişmez türler arasında ayrım yapmak için destek eklemeniz gerekmez mi? Bir dile ne kadar önem vereceğinden emin değilim.
Doval,

4

İki ilgili, ancak biraz farklı konular var:

  1. Hiç olmalı mı null? Yoksa Maybe<T>null yararlı olduğunda her zaman kullanmalı mıyım?
  2. Tüm referanslar geçersiz sayılabilir mi? Değilse, hangisi varsayılan olmalıdır?

    Açıkça belirtilebilecek referans türlerini açıkça string?veya benzer olarak bildirmek null, programlayıcıların alıştığından çok farklı olmadan , sorunların çoğundan (ancak hepsinden değil) kaçınacaktır .

En azından tüm referansların geçersiz sayılmaması gerektiği konusunda hemfikirim. Ancak sıfırdan kaçınmak, karmaşıklığı olmadan değildir:

.NET, tüm alanları default<T>önce yönetilen kodla erişilmeden önce başlatır . Bunun anlamı, referans türleri için ihtiyacınız olan nullveya eşdeğer bir şey olduğu ve değer türlerinin kod çalıştırmadan bir tür sıfıra başlatılabileceği anlamına gelir . Bunların her ikisinin de ciddi olumsuz yönleri olsa da, defaultbaşlatmanın basitliği bu olumsuz yönlerden ağır basmış olabilir.

  • Örneğin alanlar , thisişaretçiyi yönetilen koda göstermeden önce alanların başlatılmasını gerektirerek bunun üzerinde çalışabilirsiniz . Spec # bu rotaya, C # ile karşılaştırıldığında yapıcı zincirlemesinden farklı sözdizimi kullanarak gitti.

  • İçin statik alanlar size basitçe gizleyemezsiniz beri tarla başlatıcısı yayınlanabilir kodun ne tür güçlü kısıtlamalar poz sürece bu sağlanması zordur thisişaretçi.

  • Referans türlerinin dizileri nasıl başlatılır? Uzunluktan List<T>daha büyük kapasiteli bir dizi tarafından desteklenen bir tane düşünün . Kalan elemanların bir değere sahip olması gerekir .

Başka bir sorun da benzeri yöntemler izin olmamasıdır bool TryGetValue<T>(key, out T value)dönmek default(T)olarak valuehiçbir şey bulamazlarsa. Bu durumda, out parametresinin her şeyden önce tasarımın kötü olduğunu iddia etmek kolaydır ve bu yöntemin ayrımcı bir birliktelik veya belki de yerine geri dönmesi gerekir .

Bu sorunların tümü çözülebilir, ancak "boş ve her şey yolunda yasaklama" kadar kolay değil.


List<T>O gerektirecektir çünkü IMHO en iyi örneğidir ya her o Tarka deposunda her öğenin bir olması, varsayılan bir değere sahip Maybe<T>ekstra bir "isValid" alanına sahip olsa bile Tbir ise Maybe<U>veya kod o List<T>davranır farklı olarak Tkendisi nULL olabilecek bir tür olup olmadığını . T[]Elemanların başlangıçtaki değere, bu seçeneklerin en az kötüsü olduğunu düşünürdüm , ama elbette, öğelerin varsayılan değere sahip olması gerektiği anlamına gelir .
supercat,

Pas nokta 1'i takip ediyor - hiç boş değil. Seylan 2'yi takip eder - varsayılan olarak boş değil. Boş olabilecek referanslar açıkça bir referans veya boş içeren bir birlik tipi ile bildirilir, ancak boş, hiçbir zaman düz bir referansın değeri olamaz. Sonuç olarak, dil tamamen güvenlidir ve NullPointerException yoktur çünkü anlamsal olarak mümkün değildir.
Jim Balter,

2

Çoğu yararlı programlama dili, veri öğelerinin rasgele diziler halinde yazılmasına ve okunmasına izin verir; böylece bir program çalıştırılmadan önce okuma ve yazma sırasını statik olarak belirlemek genellikle mümkün olmaz. Kodun aslında okumadan önce her yuvaya faydalı veri depolayacağı ancak bunun zor olacağını kanıtlayacağı birçok durum vardır. Bu nedenle, kodun henüz faydalı bir değere sahip olmayan bir şeyi okumaya teşebbüs etmek için en azından teorik olarak mümkün olduğu programları çalıştırmak çoğu zaman gerekli olacaktır. Kodun böyle yapmasının yasal olup olmadığı, kodun denenmesini engellemenin genel bir yolu yoktur. Tek soru, bu olduğunda ne olması gerektiğidir.

Farklı diller ve sistemler farklı yaklaşımlar kullanır.

  • Bir yaklaşım, yazılı olmayan bir şeyi okuma girişiminin acil bir hatayı tetikleyeceğini söylemek olacaktır.

  • İkinci bir yaklaşım, saklanan değerin semantik olarak yararlı olması için bir yol olmasa bile, okunması mümkün olmadan önce her yerde bir miktar değer sağlamak için kod gerektirmektir.

  • Üçüncü bir yaklaşım, problemi basitçe görmezden gelmek ve “doğal olarak” ne olursa olsun gerçekleşmesine izin vermektir.

  • Dördüncü bir yaklaşım, her türün varsayılan bir değere sahip olması gerektiğini ve başka bir şeyle yazılmayan herhangi bir yuvanın bu değere varsayılan olacağını söylemektir.

Yaklaşım # 4, yaklaşım # 3'ten çok daha güvenlidir ve genel olarak yaklaşım # 1 ve # 2'den daha ucuzdur. Bu daha sonra bir referans tipi için varsayılan değerin ne olması gerektiği sorusunu bırakıyor. Değişmez referans türleri için, çoğu durumda bir varsayılan örneği tanımlamak mantıklı olacaktır ve bu türdeki herhangi bir değişken için varsayılanın bu örneğe referans olması gerektiğini söyleyecektir. Değişken referans türleri için, bu çok yardımcı olmaz. Yazılmadan önce değişken bir referans türünü kullanmaya teşebbüs edilirse, genellikle kullanım girişimi tuzağından başka bir güvenli işlem yapılmaz.

Tek bir dizi varsa semantik, konuşma customersÇeşidi Customer[20]ve bir girişimleri Customer[4].GiveMoney(23)için saklanan bir şey kalmadan Customer[4], yürütme tuzağına sahip oluyor. Bir kişi, okuma girişiminin Customer[4], kod denemeye kadar beklemek yerine derhal tuzağa düşmesi gerektiğini savunabilir GiveMoney, ancak bir slotu okumak, bir değeri tutmadığını anlamak ve bundan faydalanmak için yeterli durumlar vardır. okuma girişiminin başarısız olmasının genellikle büyük bir sıkıntı olacağı bilgisi.

Bazı diller, birinin belirli değişkenlerin hiçbir zaman boş bırakmaması gerektiğini ve boş bir saklama girişiminde hemen bir tuzak tetiklemesi gerektiğini belirtir. Bu yararlı bir özellik. Bununla birlikte, genel olarak, programcıların referans dizileri oluşturmasına izin veren herhangi bir dilin, ya boş dizi elemanlarının olasılığına izin vermesi gerekecek ya da dizi elemanlarının başlatılmasını, muhtemelen anlamlı olamayacak olan verilere zorlaması gerekecektir.


Bir olmaz Maybe/ Optionsize referans için bir değer yoksa bu yana tipi, 2. ile sorunu çözmek henüz gelecekte tane ama olacak, sadece saklayabilir Nothingbir yer Maybe <Ref type>?
Doval

@Doval: Hayır, problemi çözmez - en azından tekrar null referans vermeden olmaz. Bir "hiçbir şey" türünün bir üyesi gibi davranmalı mı? Eğer öyleyse, hangisi? Yoksa istisna mı atmalı? Bu durumda, nulldoğru ve mantıklı bir şekilde kullanmaktan nasıl daha iyi bir şekilde yararlanabilirsiniz?
cHao

@Doval: Bir arkanın destek türü a mı yoksa a mı List<T>olmalı ? A'nın destek türünden ne haber ? T[]Maybe<T>List<Maybe<T>>
supercat,

@supercat Bir destek türünün ne kadar Maybemantıklı olduğundan emin değilim , Listçünkü Maybetek bir değere sahip. Bunu mu demek istediniz Maybe<T>[]?
Doval,

@cHao Nothingyalnızca tür değerlerine atanabilir Maybe, bu yüzden atamak gibi değil null. Maybe<T>ve Tiki farklı tiptedir.
Doval,
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.