Czy typy zerowalne są lepsze od magicznych liczb?


22

Ostatnio dyskutuję z pewnym współpracownikiem. Używamy C #, ale może to dotyczyć dowolnego języka z typami zerowalnymi. Powiedzmy na przykład, że masz wartość reprezentującą maksimum. Jednak ta maksymalna wartość jest opcjonalna. Twierdzę, że preferowana byłaby liczba zerowa. Mój współpracownik opowiada się za zastosowaniem zera, powołując się na precedens. To prawda, że ​​takie gniazda sieciowe często wykorzystywały zero, aby reprezentować nieograniczony czas oczekiwania. Gdybym miał dzisiaj napisać kod dotyczący gniazd, osobiście użyłbym wartości zerowej, ponieważ uważam, że lepiej reprezentowałby fakt, że NIE ma limitu czasu.

Która reprezentacja jest lepsza? Oba wymagają sprawdzenia warunku dla wartości oznaczającej „brak”, ale uważam, że typ zerowy przekazuje zamiar nieco lepiej.


6
Jeśli używana jest liczba, ustaw ją na stałą, a nie bezpośrednio w kodzie.
Renato Dinhani

@ RenatoDinhaniConceição, które nie mogą być ogólną regułą. W przeciwnym razie kończysz wszystko programowaniem .
Simon Bergot,

Odpowiedzi:


24

Rozważać:

  • Język,

  • Struktura,

  • Kontekst.

1. Język

Użycie ∞ może być rozwiązaniem dla maksimum.

  • Na przykład JavaScript ma nieskończoność. C # nie¹.

  • Na przykład Ada ma zakresy. C # nie.

W języku C # istnieje int.MaxValue, ale nie można go użyć w twoim przypadku. int.MaxValueto maksymalna liczba całkowita, 2 147 483 647. Jeśli w kodzie masz maksymalną wartość czegoś, na przykład maksymalną akceptowaną presję, zanim coś wybuchnie, użycie 2 147 483 647 nie ma sensu.

2. Ramy

.NET Framework jest dość niespójny w tym punkcie, a jego użycie magicznych wartości można skrytykować.

Na przykład "Hello".IndexOf("Z")zwraca magiczną wartość -1. To może łatwiej (prawda?), Aby manipulować wynik:

int position = "Hello".IndexOf("Z");
if (position > 0)
{
    DoSomething(position);
}

zamiast używać niestandardowej struktury:

SearchOccurrence occurrence = "Hello".IndexOf("Z");
if (occurrence.IsFound)
{
    DoSomething(occurrence.StartOffset);
}

ale wcale nie jest intuicyjny. Dlaczego -1nie -123? Początkujący może również błędnie pomyśleć, że 0oznacza to również „Nie znaleziono” lub po prostu pomylił się (position >= 0).

3. Kontekst

Jeśli twój kod jest związany z przekroczeniem limitu czasu w gniazdach sieciowych, używanie czegoś, z czego wszyscy korzystali przez dziesięciolecia w celu zachowania spójności, nie jest złym pomysłem . Zwłaszcza, 0że limit czasu jest bardzo jasny: jest to wartość, która nie może wynosić zero. Zastosowanie niestandardowej klasy w tym przypadku może utrudnić zrozumienie:

class Timeout
{
    // A value indicating whether there is a timeout.
    public bool IsTimeoutEnabled { get; set; }

    // The duration of the timeout, in milliseconds.
    public int Duration { get; set; }
}
  • Czy mogę ustawić Durationna 0, jeśli IsTimeoutEnabledjest to prawda?
  • Jeśli IsTimeoutEnabledjest to fałsz, co się stanie, jeśli ustawię wartość Duration100?

Może to prowadzić do wielu błędów. Wyobraź sobie następujący fragment kodu:

this.currentOperation.Timeout = new Timeout
{
    // Set the timeout to 200 ms.; we don't want this operation to be longer than that.
    Duration = 200,
};

this.currentOperation.Run();

Operacja trwa przez dziesięć sekund. Czy widzisz, co jest nie tak z tym kodem, bez czytania dokumentacji Timeoutklasy?

Wniosek

  • nulldobrze wyraża pogląd, że wartości tu nie ma. Nie jest pod warunkiem. Niedostępne. Nie jest to ani liczba, ani ciąg zerowy / pusty, czy cokolwiek innego. Nie używaj go do maksymalnych lub minimalnych wartości.

  • int.MaxValuejest ściśle związany z samym językiem. Nie należy stosować int.MaxValueograniczenia prędkości maksymalnej w Vehicleklasie lub maksymalnej dopuszczalnej prędkości dla statku powietrznego itp.

  • Unikaj magicznych wartości jak -1w kodzie. Wprowadzają w błąd i prowadzą do błędów w kodzie.

  • Utwórz własną klasę, która byłaby prostsza, z określonymi wartościami minimalnymi / maksymalnymi. Na przykład VehicleSpeedmoże mieć VehicleSpeed.MaxValue.

  • Nie postępuj zgodnie z poprzednimi wytycznymi i używaj magicznych wartości, jeśli jest to ogólna konwencja od dziesięcioleci w bardzo konkretnej dziedzinie, używana przez większość ludzi piszących kod w tym polu.

  • Nie zapomnij mieszać podejść. Na przykład:

    class DnsQuery
    {
        public const int NoTimeout = 0;
    
        public int Timeout { get; set; }
    }
    
    this.query.Timeout = 0; // For people who are familiar with timeouts set to zero.
    // or
    this.query.Timeout = DnsQuery.NoTimeout; // For other people.
    

¹ Możesz stworzyć swój własny typ, który zawiera nieskończoność. Tutaj mówię tylko o rodzimym inttypie.


1
„używanie czegoś, co było używane przez dziesięciolecia w celu zachowania spójności, nie jest złym pomysłem” / „Nie stosuj się do poprzednich wytycznych i używaj magicznych wartości, jeśli jest to ogólna konwencja od dziesięcioleci w bardzo konkretnej dziedzinie, używana przez większość ludzi pisze kod w tym polu ”. - Myślę, że gdzieś jest literówka?
deworde

1
@deworde Wierzę, że MainMa odnosi się do wytycznych, które sam dał powyżej.
Joshua Drake

1
Nie zgadzam się na przykładzie indexOf, ponieważ -1 jest poza ciągiem, którym Z na pewno jest.
Joshua Drake

5
„Na przykład JavaScript ma nieskończoność. C # nie.” - co?
BlueRaja - Danny Pflughoeft

+1 szczególnie za „Stwórz własną klasę”, co bym zasugerował. Za każdym razem, gdy gołe intnie mówi wystarczająco dużo o typie, aby ograniczyć problem, rozważ nową strukturę z większą ilością informacji (na przykład instancje struktury, które reprezentują na przykład wartości magiczne lub wyliczenie na niej, aby wskazać). Lub rozważ programowanie kontraktowe lub inne rozwiązania, ale myślę, że niestandardowa struktura jest najprostsza.
CodexArcanum

12

Null nie jest lepszy od magicznej liczby.

Ważną rzeczą jest NAZWANIE wartości, które mają efekty magiczne, jeśli musisz mieć takie wartości, i upewnienie się, że definicje tych nazw są w miejscu, które zobaczy każdy, kto wpadnie na magiczną wartość i wtf.

if (timeout == 4298435) ... // bad.
if (timeout == null) ... // bad.
if (timeout == NEVER_TIME_OUT) ... // yay! puppies and unicorns!

2
Ok, może zależy to bardziej od języka, ale w języku C # prawdopodobnie zrobiłbyś: if (timeout.HasValue) zamiast bezpośredniego porównania z null.
Matt H

2
Null nie jest gorszy od magicznej liczby. Dzięki magicznym liczbom nigdy nie wiesz, co to jest magiczna liczba ... może to być 0, -1 lub coś innego. null jest po prostu null.
marco-fiset

9
Null oznacza brak wartości. Jest to koncepcja, którą wiele magicznych liczb zamierza wyrazić. Możliwość użycia wartości null z typem zerowalnym jest O DUŻO lepszym rozwiązaniem niż wybranie jednej dowolnej wartości z zakresu możliwych wartości dla typu danych.
17 z 26

2
Użycie „null” jako wartości magicznej, jeśli twój typ ma „null”, jest w porządku. Ważną rzeczą jest NAZWAĆ to, ponieważ na pewno jako strzelający następny facet, który przyjdzie, nie będzie wiedział, co miałeś na myśli. Wartość null może oznaczać „nieskończoność”, „jeszcze nieokreślony”, „błąd w kodzie tworzącym strukturę danych” lub dowolną liczbę innych rzeczy. Tylko nazwa informuje następnego programistę, że miałeś na myśli tę wartość i jakie zachowanie miałeś ją wywołać.
mjfgates

1
@CodeInChaos: Wiem, że możesz zrobić oba, ale wolę HasValue. W zasadzie nie jestem wielkim fanem zer, ale typy zerowalne używające HasValue wydają mi się nieco bliższe typowi Option / Może, który jestem fanem.
Matt H

10

MAGIC_NUMBERkodu należy bezwzględnie zawsze unikać, gdy tylko jest to możliwe. nulljest znacznie wyraźniejszym wyrazem intencji.


6

W języku C # wiele klas CLR ma element statyczny Empty:

  • System.String.Empty
  • System.EventArgs.Empty
  • System.Guid.Empty
  • System.Drawing.Rectangle.Empty
  • System.Windows.Size.Empty

Dzięki temu nie musisz pamiętać, czy użyć magicznej wartości, czy null, aby zbudować pusty obiekt.

Ale co, jeśli masz do czynienia z prostym typem wartości, takim jak int? W takim przypadku zastanów się, czy padasz ofiarą Prymitywnej Obsesji . Jest całkiem możliwe, że twoja pozornie prosta właściwość liczbowa skorzystałaby z własnej klasy lub struktury, co pozwoliłoby ci określić element Emptyczłonkowski, a także dodać inne zachowanie specyficzne dla tego rodzaju wartości.


3

W takim przypadku wartość null jest świetnym sposobem na wskazanie, że nie ma wartości maksymalnej. Zasadniczo, gdy szczególny przypadek oznacza, że ​​dana wartość nie ma zastosowania, że ​​po prostu nie chcesz konfigurować funkcji, wartość null jest dobrym wskaźnikiem tego.

Problem z używaniem wartości NULL do reprezentowania przypadków specjalnych polega na tym, że istnieje tylko jedna wartość NULL i może istnieć wiele przypadków specjalnych. W tym przypadku przekazałbym wyliczenie jako dodatkowy parametr, który może wskazywać na szczególny przypadek lub normalnie użyć wartości int. (Jest to w zasadzie to, co robi dla ciebie Nullable <>, chociaż używa boolean zamiast wyliczenia i łączy parametry w jedną strukturę.)


3

W tym przypadku uważam, że typ zerowalny ma idealny sens.

Null oznacza brak wartości. Jest to wyraźnie inna koncepcja niż liczba o wartości 0.

Jeśli chcesz powiedzieć „Jeśli nie podam ci wartości, użyj maksimum”, wówczas podanie wartości null jest dokładnie poprawnym sposobem wyrażenia tego.


1

Null: wspólna wartość błędu, nieokreślona, ​​nieważna lub brak wartości.

Zero: rzeczywista, ale niekoniecznie logiczna lub intuicyjna wartość (w tym kontekście). Również wspólna wartość podczas inicjalizacji.

W kontekście twojego problemu timeoutInMillisecondswłaściwość jest opcjonalna i nie ma wzmianki, że narzut związany z tym podejściem dyskwalifikuje ją jako opcję.

Wniosek: istnieją wyjątki i rozwiązania różnią się w zależności od języka i domeny; w tym przypadku wybrałbym Null. Tam, gdzie (sądzę) niektórzy ludzie się mylą, to wtedy, gdy nie oddzielają dobrze danych od interfejsu. Oczekują, że każdy klient przeczyta dokumentację (lub implementację), aby ustalić, w jaki sposób te specjalne wartości mają być używane / obsługiwane - przypadki specjalne wlewają się do programu klienta i może to być dość niejasne. Dodając dobrą warstwę abstrakcji, użycie może być znacznie wyraźniejsze.


0

Null jest gorszy w użyciu niż MagicNumber. Null reprezentuje ideę wyrażoną lepiej, ale nie jest spójny na różnych platformach pod względem zachowania, używanie MagicNumberzawsze działa tak samo, co jest korzystne.

w zależności od używanego środowiska / języka null mógłby

  • po prostu być 0
  • może nie mieć wartości prawnej
  • może dawać nieoczekiwane wyniki z powodu logiki trójdrożnej

MagicNumber zawsze zachowuje się tak samo.


0

Jeśli zapomnisz sprawdzić magiczną liczbę (stanie się to dobrze), wtedy magiczna liczba będzie trwać przez chwilę z bezsensownymi danymi. O wiele lepiej mieć wartość zerową, która powoduje wyjątek tak szybko, jak to możliwe.


-1

Null nie jest jedyną alternatywą dla magicznej liczby.

public static int NO_TIMEOUT = 0;  // javaish

Null jest zły. W powyższym przykładzie możesz być w stanie tego uniknąć, ponieważ kod oczywiście byłby w stanie obsłużyć wartość zerową. Ale ogólnie rzecz biorąc, kiedy zaczyna się przekazywanie wartości zerowych, prędzej czy później pojawia się wyjątek wskaźnika zerowego. Może się to nie zdarzyć, gdy piszesz kod po raz pierwszy, ale kod jest utrzymywany znacznie dłużej niż tylko pierwsze wydanie. Często utrzymują go ludzie, którzy nie wiedzą tyle o systemie, co oryginalni programiści.

Scala (na przykład) ma fajną alternatywę w klasie Option. Klasa Option ma jedną z dwóch wartości, Niektóre - która otacza wartość, którą naprawdę chcesz, i Brak - która nie ma żadnej wartości.

To sprawia, że ​​dla każdego programisty jest oczywiste, że może nie być żadnej wartości i masz do tego lepszy kod. Cóż, i tak powinno to być oczywiste.

I nie wszystkie magiczne liczby stanowią problem. W zależności od kontekstu 0, 1, 1024 itd. Wszystko może być oczywiste. 347? Tak, tego należy unikać. :-)


4
-1: Uzasadnij „null is evil”.
deworde

4
Zdefiniowanie aliasu dla liczby nie zmienia faktu, że wciąż jest to liczba magiczna.
17 z 26

Być może masz inną definicję magicznej liczby niż ja. Proszę zobaczyć pl.wikipedia.org/wiki/…
Jon Strayer

1
Zgadzam się z Jonem Strayerem tutaj. Null jest przykładem ADT w języku, który tak naprawdę nie obsługuje ADT. OP może prawdopodobnie uciec od tego tutaj, ale ogólnie uważam, że każdy język, w którym null nie zawiódł, to nieco programiści.
Jeremy Wall
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.