Bir değer anlamlı bir şekilde bulunamadığında, yeni bir Boole alanı boş başvurudan daha mı iyidir?


39

Örneğin Member, bir lastChangePasswordTime olan bir sınıfım olduğunu varsayalım:

class Member{
  .
  .
  .
  constructor(){
    this.lastChangePasswordTime=null,
  }
}

lastChangePasswordTime, anlamsız olabilir, çünkü bazı üyeler şifrelerini asla değiştiremez.

Ancak boş değerlerin kötü olması durumunda, bir değer anlamlı olarak bulunamadığında neler kullanılmalıdır? ve https://softwareengineering.stackexchange.com/a/12836/248528 , anlamlı olmayan bir değeri temsil etmek için null kullanmamalıyım. Bu yüzden bir Boolean bayrağı eklemeye çalışıyorum:

class Member{
  .
  .
  .
  constructor(){
    this.isPasswordChanged=false,
    this.lastChangePasswordTime=null,
  }
}

Ancak bunun oldukça eski olduğunu düşünüyorum:

  1. İsPasswordChanged yanlış olduğunda, lastChangePasswordTime değeri null olmalıdır ve lastChangePasswordTime == null değerinin kontrol edilmesi neredeyse aynıdır isPasswordChanged değerinin yanlış olması nedeniyle, lastChangePasswordTime == null öğesinin doğrudan kontrol edilmesini tercih ederim

  2. Buradaki mantığı değiştirirken, her iki alanı da güncellemeyi unutabilirim .

Not: Bir kullanıcı şifreleri değiştirdiğinde, şu zamanı kaydederim:

this.lastChangePasswordTime=Date.now();

Ek Boole alanı burada boş başvurudan daha mı iyi?


51
Bu soru dile özgüdür, çünkü iyi bir çözüm oluşturan şey, büyük ölçüde programlama dilinizin ne sunduğuna bağlıdır. Örneğin, C ++ 17 veya Scala'da, std::optionalveya kullanırsınız Option. Diğer dillerde, kendinize uygun bir mekanizma inşa etmeniz gerekebilir nullya da daha deyimsel olduğu için aslında buna benzer bir şeye başvurabilirsiniz .
Christian Hackl

16
LastChangePasswordTime'in parola oluşturma zamanına ayarlanmasını istememesinin bir nedeni var mı (oluşturma, sonuçta bir moddur)?
Kristian H

@HristianHackl hmm, farklı "mükemmel" çözümler olduğunu kabul ediyorum, ancak ayrı bir boole kullanmanın genel olarak boş / sıfır denetimler yapmaktan daha iyi bir fikir olmasına rağmen (ana) bir dil göremiyorum. C / C ++ hakkında uzunca bir süredir aktif olmadığım için emin değilim.
Frank Hopkins

@ FrankHopkins: Bir örnek değişkenlerin başlatılmadan bırakılabildiği dillerdir, örn. C veya C ++. lastChangePasswordTimeorada başlatılmamış bir işaretçi olabilir ve bunu herhangi bir şeyle karşılaştırmak tanımsız davranış olacaktır. İşaretçiyi NULL/ nullptryerine, özellikle modern C ++ 'da ( göstericiyi hiç kullanmazsınız) başlatmamak için gerçekten zorlayıcı bir neden değil , kim bilir? Başka bir örnek, işaretçileri olmayan veya işaretçiler için kötü desteği olan diller olabilir. (FORTRAN 77 akla geliyor ...)
Christian Hackl

Bunun için 3 kılıflı bir enum kullanırdım :)
J. Doe

Yanıtlar:


72

Eğer anlamsız bir değeriniz nullyoksa, kasten ve dikkatli olmanız durumunda neden kullanılmaması gerektiğini anlamıyorum.

Amacınız, yanlışlıkla başvurmayı önlemek için boş değeri çevrelemekse, değeri isPasswordChangedboş bir denetimin sonucunu döndüren bir işlev veya özellik olarak oluşturmanızı öneririm , örneğin:

class Member {
    DateTime lastChangePasswordTime = null;
    bool isPasswordChanged() { return lastChangePasswordTime != null; }

}

Bence, bu şekilde yapmak:

  • Bağlamı kaybedebilecek bir boş denetlemeden daha iyi kod okunabilirliği sağlar.
  • Bahsettiğiniz isPasswordChangeddeğeri korumak konusunda gerçekten endişelenmenize gerek kalmaması ihtiyacını ortadan kaldırıyor.

Verileri sürdürme şekliniz (muhtemelen bir veritabanında), boş değerlerin korunmasını sağlamaktan sorumlu olacaktır.


29
Açılış için +1. Boş değerin ahlaksız kullanımı, genellikle yalnızca boş değerin beklenmeyen bir sonuç olduğu durumlarda, yani (bir tüketiciye) mantıklı olmayacağı durumlarda onaylanmaz. Eğer boş anlamlıysa, o zaman beklenmeyen bir sonuç değildir.
flater

28
Aynı durumu koruyan iki değişken olmadığından, biraz daha güçlü koyardım. Başarısız olacak. Boş değerin gizlenmesi, boş değerin, dışardan çok iyi bir şey olduğunu, örneğin içinde anlamlı olduğunu varsayarsak. Daha sonra karar verebileceğiniz bu durumda 1970-01-01, şifrenin hiç değiştirilmediğini belirtir. O zaman programın geri kalanının mantığı ile ilgilenmek zorunda değilsiniz.
Bent

3
isPasswordChangedTıpkı null olup olmadığını kontrol etmeyi unuttuğun gibi aramayı unutabilirsin. Burada hiçbir şey kazanılmadığını görüyorum.
Gherman

9
@Alman Tüketici, boş değerin anlamlı olduğu veya mevcut değeri kontrol etmesi gerektiğine dair bir anlayışa sahip değilse, her iki şekilde de kaybedilir, ancak yöntem kullanılıyorsa, kodu okuyan herkes için açıktır. bir çek ve bu değerin boş / mevcut olmaması konusunda makul bir şans olduğu anlamına gelir. Aksi halde, sadece "neden olmasın" boş bir çek ekleyen bir geliştirici olup olmadığı ya da kavramın bir parçası olup olmadığı açık değildir. Elbette, biri öğrenebilir, ancak bir yolla bilgiye doğrudan erişebilmeniz için uygulamaya bakmanız gerekir.
Frank Hopkins

2
@ FrankHopkins: İyi nokta, ancak eşzamanlılık kullanılıyorsa, tek bir değişkenin incelenmesi bile kilitlenmeyi gerektirebilir.
Christian Hackl

63

nullskötü değil. Onları düşünmeden kullanmak. Bu nulltam olarak doğru cevabın verildiği bir durumdur - tarih yoktur.

Çözümünüzün daha fazla sorun yarattığını unutmayın. Tarihin bir şeye ayarlanmasının anlamı nedir, ama isPasswordChangedyanlış mı? Bir nulldeğerin açıkça tanımlanmış, açık bir anlamı olduğu ve diğer bilgilerle çakışamadığı durumlarda, özel olarak yakalamanız ve tedavi etmeniz gereken bir çakışan bilgi durumu oluşturdunuz .

Yani hayır, çözümünüz daha iyi değil. Bir nulldeğere izin vermek , buradaki doğru yaklaşımdır. nullDurum ne olursa olsun kötülük olduğunu iddia eden insanlar neden nullvar olduğunu anlamıyorlar .


17
Tarihsel olarak nullvar, çünkü Tony Hoare 1965'te milyar dolarlık hatayı yaptı. nullTabii ki "kötülük" olduğunu iddia eden insanlar , konuyu aşırı derecede basitleştiriyorlar, elbette, fakat bu, modern dillerin veya programlama stillerinin nullyerini değiştirmek, yerini değiştirmek iyi bir şey. özel seçenek türleri ile.
Christian Hackl

9
@HristianHackl: Tarihsel ilgiden mahrum - Hoare hiç daha iyi bir alternatifin ne olacağını söyledi (tamamen yeni bir paradigmaya geçmeden)?
AnoE

5
@Hayır: İlginç bir soru. Hoare'ın bu konuda ne söyleyeceğini bilmiyorum, ama boş programlama, bugünlerde modern ve iyi tasarlanmış programlama dillerinde bir gerçektir.
Christian Hackl

10
@Tom wat? C # ve Java gibi dillerde, DateTimealan bir işaretçidir. Tüm kavramı nullsadece işaretçiler için anlamlıdır!
Todd Sewell

16
@ Tom: Boş işaretçiler, sadece ne tür bir sonuç ortaya çıkacak olursa olsun, kör dizine alınmalarını ve izinsiz bırakılmalarını sağlayan diller ve uygulamalar için tehlikelidir. Tuzakların boş bir işaretçiyi dizine almaya veya kaldırmaya çalıştığı bir dilde, sahte bir nesneye ilişkin göstericiden daha az tehlikelidir. Anormal bir şekilde sonlandırılmış bir programa sahip olmanın, anlamsız sayılar üretmesini tercih etmesinin birçok durumu olabilir ve onları hapseden sistemlerde, boş göstericiler bunun için doğru reçetedir.
supercat

29

Programlama diline bağlı olarak, optionalveri türü gibi iyi alternatifler olabilir . C ++ (17) 'de, std :: isteğe bağlı olacaktır . Yok olabilir veya temel veri türünün keyfi bir değeri olabilir.


8
Orada Optionalda java; boş Optionalolmanın anlamı sana bağlı ...
Matthieu M.

1
İsteğe bağlı olarak da bağlanmak için buraya geldi. Bunu, onlarda olan bir dilde bir tür olarak kodlardım.
Zipp

2
@ FrankHopkins Java'da hiçbir şeyin yazmanızı engellemediği için utanç verici; Optional<Date> lastPasswordChangeTime = null;ancak bunun için pes etmem. Bunun yerine, çok katı bir takım kuralı aşılamak isterdim, hiçbir zaman isteğe bağlı değerlerin atanmasına izin verilmez null. İsteğe bağlı, bundan kolayca vazgeçmek için çok fazla hoş özellik satın alıyor. Kolayca atama varsayılan değerler ( orElseya da tembel değerlendirmeyle: orElseGet) atış hataları ( orElseThrow), (boş güvenli bir şekilde değerlerini dönüştürmek map, flatmapvb)
Alexander

5
@ FrankHopkins Deneme isPresentneredeyse her zaman bir kod kokusu, benim deneyimime göre, ve bu opsiyonelleri kullanan cihazların "noktayı eksik" olduğuna dair oldukça güvenilir bir işaret. Aslında, temelde faydası yoktur ref == null, ancak sık kullanmanız gereken bir şey değildir. Ekip kararsız olsa bile, statik bir analiz aracı, çoğu vakayı yakalayabilen linter veya FindBugs gibi kanunları düzenleyebilir.
Alexander

5
@ FrankHopkins: Java topluluğunun sadece uzun yıllar alabilen küçük bir paradigma değişikliğine ihtiyacı olabilir. Eğer Scala bakarsak, örneğin, desteklediği nulldil Java% 100 uyumlu olduğu için, ama kimse bunu kullanır ve Option[T]benzeri mükemmel fonksiyonel programlama desteği ile biryere olduğunu map, flatMapgider vb, desen eşleştirme ve, uzak, çeklerin çok ötesinde == null. Teoride, bir seçenek türünün yüceltilmiş bir göstergeden biraz daha fazlası gibi göründüğü konusunda haklısınız, ancak gerçeklik bu argümanı yanlış kanıtlıyor.
Christian Hackl,

11

Doğru null kullanarak

Kullanmanın farklı yolları var null. En yaygın ve anlamsal olarak doğru yol, tek bir değere sahip olabileceğinize veya sahip olamayacağınız zaman kullanmaktır . Bu durumda, bir değer ya nullveri tabanındaki bir kayıt gibi bir şeye eşittir veya buna benzer bir şeydir.

Bu durumlarda, daha sonra çoğunlukla bu şekilde kullanın (sözde kodda):

if (value is null) {
  doSomethingAboutIt();
  return;
}

doSomethingUseful(value);

Sorun

Ve çok büyük bir problemi var. Sorun şu ki, çağıracağınız zaman doSomethingUseful, değeri kontrol etmemiş olabilirsiniz null! Öyle değilse, program muhtemelen çökecektir. Ve kullanıcı, "korkunç hata: aranan değer ama boş aldı!" Gibi bir şeyle bırakılarak, iyi bir hata mesajı bile görmeyebilir. (güncellemeden sonra: Segmentation fault. Core dumped.bazı durumlarda null hakkında hiçbir hata ve yanlış manipülasyon gibi , hatta daha da kötüsü gibi daha az bilgilendirici bir hata olsa da)

Durumlar için çek yazmayı nullve işlemeyi unutmak oldukça yaygın bir hatadır . Bu nedenle , 2009'da QCon Londra adlı bir yazılım konferansında, 1965'te milyar dolarlık bir hata yaptığını söyleyen Tony Hoare’in : https://www.infoq.com/presentations/Null-References-The-Billion-Dollar- hata-Tony-Hoarenullnull

Sorunu önlemek

Bazı teknolojiler ve diller nullfarklı şekillerde unutmayı imkansız hale getirir ve böcek miktarını azaltır.

Örneğin Haskell, Maybeboş değer yerine monad'a sahiptir. Bunun DatabaseRecordkullanıcı tarafından tanımlanan bir tür olduğunu varsayalım . Haskell'de bir tür değer Maybe DatabaseRecordeşit Just <somevalue>veya eşit olabilir Nothing. Daha sonra farklı şekillerde kullanabilirsiniz, ancak nasıl kullanıyor olursanız olun, Nothingbilmeden bazı işlemleri uygulayamazsınız .

Örneğin, çağrılan bu işlev için ve için zeroAsDefaultdöndürür :xJust x0Nothing

zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
    Nothing -> 0
    Just x -> x

Christian Hackl, C ++ 17 ve Scala'nın kendi yöntemlerinin olduğunu söylüyor. Dolayısıyla, dilinizde böyle bir şey olup olmadığını öğrenmeyi denemek isteyebilirsiniz.

Null hala geniş kullanımda

Daha iyi bir şeyiniz yoksa kullanmak nulliyidir. Sadece izlemeye devam et. İşlevlere bildirim yazın, yine de size yardımcı olacaktır.

Ayrıca bu çok ilerici görünmeyebilir ancak meslektaşlarınızın kullanmak nullveya başka bir şey isteyip istemediğinizi kontrol etmeniz gerekir . Muhafazakar olabilir ve bazı nedenlerden dolayı yeni veri yapılarını kullanmak istemeyebilirler. Örneğin bir dilin daha eski sürümlerini desteklemektedir. Bu tür şeyler projenin kodlama standartlarında ilan edilmeli ve ekiple uygun şekilde tartışılmalıdır.

Teklifin üzerine

Ayrı bir boole alanı kullanmanızı öneririz. Ama yine de kontrol etmelisin ve yine de kontrol etmeyi unutabilirsin. Yani burada kazanılan hiçbir şey yok. Başka bir şeyi bile unutabilirseniz, her seferinde her iki değeri de güncellemek gibi, o zaman daha da kötüdür. Kontrol etmeyi unutma sorunu nullçözülmediyse, bunun anlamı yoktur. Sakınmak nullzordur ve onu daha da kötüleştirecek şekilde yapmamalısınız.

Null kullanılmaz

Son olarak nullyanlış kullanmanın yaygın yolları vardır . Bu yollardan biri, diziler ve dizgiler gibi boş veri yapıları yerine kullanmaktır. Boş bir dizi, diğerleri gibi uygun bir dizidir! Birden fazla değere uyan, boş olabilen, yani 0 uzunluğa sahip veri yapıları için hemen hemen her zaman önemli ve kullanışlıdır.

Cebir açısından, dizgiler için boş bir dize, sayılar için 0'a çok benzer, yani kimlik:

a+0=a
concat(str, '')=str

Boş dize, dizelerin genel olarak monoid hale gelmesini sağlar: https://en.wikipedia.org/wiki/Monoid Bunu elde edemezseniz, sizin için o kadar önemli değildir.

Şimdi bu örnekle programlama için neden önemli olduğunu görelim:

for (element in array) {
  doSomething(element);
}

Burada boş bir dizi geçirirsek kod iyi çalışır. Sadece hiçbir şey yapmaz. Ancak bir nullburadan geçersek, "null üzerinden geçemiyorum, üzgünüm" gibi bir hatayla karşılaşacağız. İçeri sarabiliriz ifama bu daha az temiz ve tekrar, kontrol etmeyi unutabilirsin

Null nasıl kullanılır

Yapması doSomethingAboutIt()gereken ve özellikle de bir istisna atıp atmaması gerekip gerekmediği bir başka karmaşık konudur. Kısacası null, verilen bir görev için kabul edilebilir bir girdi değerinin olup olmadığına ve bunun cevabında beklenenin ne olduğuna bağlıdır . İstisnalar, beklenmeyen olaylar içindir. Bu konuya daha fazla girmeyeceğim. Bu cevap çok uzun zamandır.


5
Aslında, horrible error: wanted value but got null!daha tipik olandan çok daha iyi Segmentation fault. Core dumped....
Toby Speight

2
@TobySpeight Doğru, ancak kullanıcının terimleri gibi görünen bir şeyi tercih ederim Error: the product you want to buy is out of stock.
Gherman

Tabii - Sadece daha da kötü olabileceğini gözlemledim (ve çoğu zaman) . Neyin daha iyi olacağı konusunda tamamen hemfikirim! Ve cevabınız hemen hemen bu soruya söyleyeceğim şeyi: +1.
Toby Speight

2
@Alman: İzin verilmediği nullyerlere geçmek nullbir programlama hatasıdır, yani koddaki bir hata. Stokta olmayan bir ürün, bir hata değil, sıradan iş mantığının bir parçasıdır. Bir hatanın tespitini kullanıcı arayüzünde normal bir mesaja çeviremez ve denememelisiniz. Hemen çökmek ve daha fazla kod yürütmemek, özellikle önemli bir uygulama ise (örneğin gerçek finansal işlemleri yapan) tercih edilen işlemdir.
Christian Hackl

1
@EricDuminil Hata yapma olasılığını ortadan kaldırmak (veya azaltmak) istiyoruz null, arka plandan bile tamamen kendini değil .
Gherman

4

Daha önce verilen çok iyi cevapların hepsine ek olarak, bir alanın boş olmasına izin vermek istediğinizde, bir liste türü olup olmadığını dikkatlice düşünün. NULL olabilecek bir tür eşdeğerde 0 veya 1 öğelerin bir listesidir ve bu genellikle N öğelerinin bir listesine genellenebilir. Özellikle bu durumda, lastChangePasswordTimebir liste olarak düşünebilirsiniz passwordChangeTimes.


Bu mükemmel bir nokta. Aynısı seçenek türleri için de genellikle geçerlidir; bir seçenek, bir dizinin özel durumu olarak görülebilir.
Christian Hackl

2

Kendinize sorun: hangi davranış lastChangePasswordTime alanını gerektiriyor?

IsPasswordExpired () yöntemi için bu alana bir Üyenin şifresini sık sık değiştirmesinin istenip istenmeyeceğini belirlemek için ihtiyaç duyuyorsanız, alanı başlangıçta Üyenin oluşturulduğu zamana ayarlarım. IsPasswordExpired () uygulaması yeni ve mevcut üyeler için aynıdır.

class Member{
   private DateTime lastChangePasswordTime;

   public Member(DateTime lastChangePasswordTime) {
      // set value, maybe check for null
   }

   public bool IsPasswordExpired() {
      DateTime limit = DateTime.Now.AddMonths(-3);
      return lastChangePasswordTime < limit;
   }
}

Yeni oluşturulan üyelerin şifrelerini güncellemek zorunda olmaları için ayrı bir gereksiniminiz varsa , passwordShouldBeChanged adlı ayrı bir boolean alanı ekler ve oluşturulduktan sonra doğru olarak ayarlardım. Daha sonra, bu alana bir denetim eklemek için IsPasswordExpired () yönteminin işlevselliğini değiştirirdim (ve yöntemi ShouldChangePassword olarak yeniden adlandırdım).

class Member{
   private DateTime lastChangePasswordTime;
   private bool passwordShouldBeChanged;

   public Member(DateTime lastChangePasswordTime, bool passwordShouldBeChanged) {
      // set values, maybe check for nulls
   }

   public bool ShouldChangePassword() {
      return PasswordExpired(lastChangePasswordTime) || passwordShouldBeChanged;
   }

   private static bool PasswordExpired(DateTime lastChangePasswordTime) {
      DateTime limit = DateTime.Now.AddMonths(-3);
      return lastChangePasswordTime < limit;
   }
}

Niyetlerinizi kodda açıkça belirtin.


2

Öncelikle, boş olan şeytan kötülük dogmadır ve her zamanki gibi dogma ile en iyi şekilde bir kılavuz olarak çalışır, pas / pas testi yoktur.

İkincisi, durumunuzu, değerin hiçbir zaman boş olamayacağı mantıklı bir şekilde yeniden tanımlayabilirsiniz. InititialPasswordChanged, başlangıçta false olarak ayarlanmış bir Boolean'dır; PasswordSetTime, geçerli parolanın ayarlandığı tarih ve saattir.

Bunun düşük bir maliyete sahip olmasına rağmen, bir HER ZAMAN şifrenin en son ayarlanmasından bu yana ne kadar süre kaldığını hesaplayabilirsiniz.


1

Arayan kullanımdan önce kontrol ederse her ikisi de 'güvenli / akıllı / doğru' olur. Sorun, arayan kişi kontrol etmezse ne olacağıdır. Hangisi daha iyi, bazı null hata lezzet veya geçersiz bir değer kullanıyor?

Tek bir doğru cevap yok. Neye endişelendiğine bağlı.

Çarpışmalar gerçekten kötüyse ancak cevap kritik değilse veya kabul edilen bir varsayılan değere sahipse, belki de bir booleanı bayrak olarak kullanmak daha iyidir. Yanlış cevap kullanmak, çökmekten daha kötü bir sorunsa, null kullanmak daha iyidir.

'Tipik' vakaların çoğu için, hızlı başarısızlık ve arayanları kontrol etmeye zorlama kodu, aklı başında kodlama için en hızlı yoldur ve bu nedenle null varsayılan seçenek olmalıdır. “X, tüm kötülüklerin köküdür” iddialarına inanmam; normalde tüm kullanım durumlarını öngörmemişlerdir.

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.