Czy wszystkie magiczne liczby są takie same?

77

W ostatnim projekcie musiałem przekonwertować bajty na kilobajty kibibajta . Kod był dość prosty:

var kBval = byteVal / 1024;

Po napisaniu tego resztę funkcji działałem i przeszedłem.

Ale później zacząłem się zastanawiać, czy właśnie umieściłem magiczną liczbę w moim kodzie. Część mnie mówi, że było dobrze, ponieważ liczba jest stałą i powinna być łatwo zrozumiana. Ale inna część mnie uważa, że ​​byłoby bardzo jasne, gdyby było owinięte określoną stałą BYTES_PER_KBYTE.

Czy więc liczby, które są dobrze znanymi stałymi, są naprawdę tak magiczne, czy nie?


Powiązane pytania:

Kiedy liczba jest magiczną liczbą? i Czy każda liczba w kodzie jest uważana za „magiczną liczbę”? - są podobne, ale mają znacznie szersze pytania niż te, które zadaję. Moje pytanie koncentruje się na dobrze znanych stałych liczbach, które nie zostały uwzględnione w tych pytaniach.

Eliminowanie magicznych liczb: kiedy należy powiedzieć „nie”? jest również powiązany, ale koncentruje się na refaktoryzacji, w przeciwieństwie do tego, czy liczba stała jest liczbą magiczną, czy nie.

Społeczność
źródło
17
I rzeczywiście pracował nad projektem, gdzie stworzył stałych podobnych FOUR_HUNDRED_FOUR = 404. Pracowałem nad innym projektem, w którym bojownikami używali stałych ciągów zamiast literałów, więc mieli dziesiątki wierszy w kodzie, które wyglądały jak:DATABASE = "database"
Rob
82
Zdecydowanie skorzystaj 1024, bo w przeciwnym razie zespół deweloperów spędza cały swój czas na kłótniach o to, czy są to „kilobajty” czy „kibibajty”.
Steven Burnap,
6
Można uznać, że 1024 to kibi, a #define KIBIjako 1024, MEBIjako 1024 * 1024…
ysdx
6
@Rob Y: brzmi jak dobrzy programiści z Fortranu. Ponieważ ten język programowania zmusił programistów do zrobienia tego. Tak, tam zobaczysz stałe, ZERO=0, ONE=1, TWO=2a kiedy programy zostaną przeniesione do innych języków (lub programiści nie zmienią zachowania podczas zmiany języka), zobaczysz to również tam i musisz się modlić, aby nikt nigdy nie zmienił na ONE=2...
Holger
4
@NoctisSkytower Mój zespół woli używać jawnych instrukcji podziału zamiast operatorów przesuwania bitów ze względu na wiele używanych przez nas języków i potencjalnie niespójną implementację w tych językach. Podobnie wartości ujemne są niespójnie obsługiwane przy przesunięciu bitowym. Chociaż niekoniecznie mamy ujemne wartości bajtów, z pewnością mamy wartości ujemne z innymi przeliczanymi jednostkami miary.

Odpowiedzi:

103

Nie wszystkie magiczne liczby są takie same.

Myślę, że w tym przypadku ta stała jest OK. Problem z liczbami magicznymi polega na tym, że są one magiczne, tzn. Nie jest jasne, jakie jest ich pochodzenie, dlaczego wartość jest tym, czym jest, ani czy wartość jest poprawna, czy nie.

Ukrywanie 1024 za BYTES_PER_KBYTE oznacza również, że nie widzisz od razu, czy jest poprawne, czy nie.

Spodziewałbym się, że ktoś natychmiast wie, dlaczego wartość wynosi 1024. Z drugiej strony, jeśli konwertujesz bajty na megabajty, zdefiniowałbym stałą BYTES_PER_MBYTE lub podobną, ponieważ stała 1 048 576 nie jest tak oczywista, że ​​jej 1024 ^ 2 lub że to nawet poprawne.

To samo dotyczy wartości podyktowanych wymaganiami lub standardami, które są używane tylko w jednym miejscu. Uważam, że umieszczenie stałej z właściwym komentarzem do odpowiedniego źródła jest łatwiejsze w obsłudze niż zdefiniowanie jej gdzie indziej i konieczność ścigania obu części, np .:

// Value must be less than 3.5 volts according to spec blah.
SomeTest = DataSample < 3.50

Uważam, że lepiej niż

SomeTest = DataSample < SOME_THRESHOLD_VALUE

SOME_THRESHOLD_VALUEMoim zdaniem, tylko gdy jest używany w wielu miejscach, kompromis staje się warty zdefiniowania stałej.

Jaka jest nazwa?
źródło
67
„Problem z magicznych liczb jest, gdy są magiczne” - To jest takie genialne wyjaśnienie tego pojęcia! Mówię poważnie! +1 za samo zdanie.
Jörg W Mittag,
20
Oto jeden, który właśnie wymyśliłem: „to nie liczba jest problemem, to magia”.
Jörg W Mittag,
10
1024 jest oczywiste dla kogo? Czy nie jest to uzasadnienie dla każdej magicznej liczby? Wszystkie magiczne liczby są używane, ponieważ są oczywiste dla każdego, kto je napisał. Czy 9.8 nie jest również oczywiste? Dla mnie to całkiem oczywiste, że to przyspieszenie grawitacyjne na Ziemi, ale mimo to stworzyłbym stałą, ponieważ to, co dla mnie oczywiste, może nie być oczywiste dla kogoś innego.
Tulains Córdova,
15
Nie. Komentarz taki jak ten w twoim „lepszym” przykładzie to masywna czerwona flaga. Jest to kod, który nawet nie przechodzi testu czytelności osoby piszącej w tym czasie. Dam przykład. e^i*pi = -1jest o wiele bardziej wyraźny (lepszy) niż 2.718^i*3.142 = -1. Zmienne mają znaczenie i nie służą tylko do wspólnego kodu. Kod jest napisany do czytania jako pierwszy, kompilowania jako drugi. Zmieniają się też specyfikacje (bardzo dużo). Podczas gdy 1024 prawdopodobnie nie powinno być w konfiguracji, 3.5 brzmi tak, jak powinno być.
Nathan Cooper
51
Nie użyłbym też stałej dla 1024 ^ 2; 1024*1024proszę!
Wyścigi lekkości na orbicie
44

Zadaję dwa pytania dotyczące magicznych liczb.

Czy numer ma nazwę?

Nazwy są przydatne, ponieważ możemy odczytać nazwę i zrozumieć cel liczby za nią. Stałe nazewnictwa mogą zwiększyć czytelność, jeśli nazwa jest łatwiejsza do zrozumienia niż liczba, którą zastępuje, a stała nazwa jest zwięzła.

Oczywiście stałe takie jak pi, e i in. mają sensowne imiona. Wartość taka jak 1024 może być, BYTES_PER_KBale oczekiwałbym również, że każdy programista będzie wiedział, co oznacza 1024. Odbiorcami kodu źródłowego są profesjonalni programiści, którzy powinni mieć doświadczenie w poznawaniu różnych potęg dwóch i dlaczego są używane.

Czy jest używany w wielu lokalizacjach?

Podczas gdy nazwy stanowią jedną siłę stałych, drugą jest możliwość ponownego użycia. Jeśli wartość może ulec zmianie, można ją zmienić w jednym miejscu, zamiast konieczności szukania jej w wielu lokalizacjach.

Twoje pytanie

W przypadku twojego pytania użyłbym tego numeru w obecnej postaci.

Nazwa: istnieje nazwa dla tego numeru, ale nie jest to tak naprawdę przydatne. Nie reprezentuje stałej matematycznej ani wartości określonej w żadnym dokumencie wymagań.

Lokalizacje: nawet jeśli jest używany w wielu lokalizacjach, nigdy się nie zmieni, co neguje tę korzyść.


źródło
1
Powodem używania stałych zamiast magicznych liczb jest nie tylko to, że te liczby się zmienią, ale także ze względu na czytelność i samokumentację.
Tulains Córdova,
4
@ user61852: nazwane stałe nie zawsze są bardziej czytelne. Często są, ale nie zawsze.
whatsisname
2
Osobiście używam tych dwóch pytań: „Czy ta wartość kiedykolwiek się zmieni w czasie trwania programu?” i „Czy programiści, których spodziewam się, że będą pracować nad tym oprogramowaniem, zrozumieją, do czego służy ten numer?”
Steven Burnap,
4
Masz na myśli problem Y2K? Nie jestem pewien, czy ma to znaczenie tutaj. Tak, było wiele kodów takich jak „data - 1900”, ale w tym kodzie problemem nie była magiczna liczba „1900”.
Steven Burnap,
1
Ta odpowiedź mogłaby skorzystać ze wzmianki, że niektóre „oczywiste” liczby, z których 1024 zdecydowanie jest jedna, są takie, że inni programiści bardzo chętnie zapisują je jako liczby, nawet jeśli ktoś zdefiniuje dla nich stałą nazwaną. Z jednej strony najprawdopodobniej nawet nie pomyślałbym o przeszukaniu kodu źródłowego pod kątem istniejącej stałej dla 1024, gdybym nie wiedział, że jest taka, gdybym musiał użyć 1024 w konwersji liczby bajtów.
hyde
27

Ten cytat

Problemem jest nie liczba, to magia.

jak powiedział przez Jörg W Mittag odpowiedzi na to pytanie całkiem dobrze.

Niektóre liczby po prostu nie są magiczne w określonym kontekście. W przykładzie podanym w pytaniu jednostki miary zostały określone przez nazwy zmiennych, a operacja, która miała miejsce, była dość jasna.

To 1024nie jest magiczne, ponieważ kontekst wyraźnie pokazuje, że jest to odpowiednia, stała wartość do wykorzystania podczas konwersji.

Podobnie przykład:

var numDays = numHours / 24; 

jest równie jasne i nie magiczne, ponieważ dobrze wiadomo, że są 24 godziny na dobę.

Społeczność
źródło
21
Ale ... ale ... 24 mogą się zmienić! Ziemia zwalnia obroty i ostatecznie będzie miała 25 godzin! (Oczywiście do tego czasu wszyscy będziemy martwi, sprawiając, że utrzymanie tego oprogramowania stanie się problemem kogoś innego)
14
Co dzieje się po wdrożeniu oprogramowania na Marsie? Powinieneś wstrzykiwać tę stałą ...
durron597
8
@ durron597: co jeśli twój program działa wystarczająco długo, aby Ziemia zwolniła w tym czasie . Nie powinieneś wprowadzać stałej, a raczej funkcji, która akceptuje znacznik czasu (teraz domyślny) i zwraca liczbę godzin w dniu, w którym upływa znacznik czasu ;-)
Steve Jessop
13
Musisz nauczyć się YAGNI.
whatsisname
3
@ durron597 Nic specjalnego nie dzieje się, gdy oprogramowanie mierzące czas jest wdrażane na Marsie, ponieważ zgodnie z konwencją dni Marsa trwają 24 godziny, ale każda godzina jest o 2,7% dłuższa niż na Ziemi . Oczywiście ani dzień gwiezdny Ziemi, ani słoneczny dzień Ziemi nie ma dokładnie 24 godzin (dokładne liczby znajdują się na tej samej stronie), więc i tak nie możesz użyć 24 ! Jak wspomniała Izkata, sekundy przestępne bolą. Może miałbyś więcej szczęścia, używając stałej 24na Marsie niż na Ziemi!
CVn
16

Inni plakaty wspominali, że konwersja jest „oczywista”, ale się nie zgadzam. W chwili obecnej pierwotne pytanie obejmuje:

kilobajty kibibajtów

Więc już wiem, że autor jest lub był zdezorientowany. Strona Wikipedii dodaje zamieszanie:

1000 = KB kilobyte (metric)
1024 = kB kilobyte (JEDEC)
1024 = KiB kibibyte (IEC)

Zatem „Kilobajt” może oznaczać zarówno współczynnik 1000, jak i 1024, przy czym jedyną różnicą w skrótach jest wielkość liter „k”. Ponadto 1024 może oznaczać kilobajt (JEDEC) lub kibibajt (IEC). Dlaczego nie zniszczyć tego zamieszania wprost stałą ze znaczącą nazwą? BTW, ten wątek często używał „BYTES_PER_KBYTE”, i to nie mniej dwuznaczne. KBYTE: czy to KIBIBYTE czy KILOBYTE? Wolałbym zignorować JEDEC i mieć BYTES_PER_KILOBYTE = 1000i BYTES_PER_KIBIBYTE = 1024. Nigdy więcej zamieszania.

Powodem, dla którego ludzie tacy jak ja i wielu innych mają „bojujące” (by zacytować tutaj komentatora) opinie na temat nazewnictwa magicznych liczb, jest przede wszystkim udokumentowanie tego, co zamierzasz zrobić, i usunięcie niejasności. I rzeczywiście wybrałeś jednostkę, która doprowadziła do wielu zamieszania.

Jeśli widzę:

int BYTES_PER_KIBIBYTE = 1024;  
...  
var kibibytes = bytes / BYTES_PER_KIBIBYTE;  

Wtedy od razu widać, co zamierzał zrobić autor, i nie ma dwuznaczności. Mogę sprawdzić stałą w ciągu kilku sekund (nawet jeśli jest w innym pliku), więc nawet jeśli nie jest to „natychmiastowe”, jest wystarczająco blisko do natychmiastowej.

W końcu może być oczywiste, gdy piszesz, ale będzie mniej oczywiste, gdy wrócisz do niego później, a może być jeszcze mniej oczywiste, gdy ktoś inny go edytuje. Utworzenie stałej zajmuje 10 sekund; debugowanie problemu z jednostkami może zająć pół godziny lub dłużej (kod nie rzuci się na ciebie i nie powie, że jednostki się mylą, musisz sam wyliczyć matematykę, aby to zrozumieć, i prawdopodobnie polujesz na 10 różnych dróg, zanim sprawdzisz jednostki).

Shaz
źródło
2
Dobra odpowiedź przeciwna. Byłoby silniej, gdybyś wziął pod uwagę kulturę zespołu. Jeśli uwierzyłeś w mój profil SE , jestem wystarczająco dorosły, aby wyprzedzić te szczególne standardy. Więc jedyne zamieszanie wynika z „jaki jest obecny (nie) standardowy termin?” Prawdopodobnie byłbyś bezpieczny, zakładając, że pracuję z zespołem innych dinozaurów, które mają te same (terminowe) trudności.
@ GlenH7: IMHO, jednostki oparte na potędze dwóch powinny były być przechowywane do przechowywania, ponieważ są przydzielane do porcji po dwa rozmiary. Czy minimalny rozmiar alokacji wynosi 4096 bajtów, czy bardziej sensowne jest posiadanie jednostki dla ilości pamięci wymaganej do przechowywania 256 plików o minimalnym rozmiarze lub ilości pamięci wymaganej do przechowywania 244.140625 takich plików? Osobiście uważam, że różnica między megabajtami tworzącymi dyski twarde a innymi megabajtami jest analogiczna do różnicy między calami przekątnej telewizora a rzeczywistymi calami przekątnej.
supercat,
@Ryan: W tym konkretnym przypadku wolę raczej walczyć o przyjęcie standardowych jednostek - KB to 1000 bajtów lub kod jest nieprawidłowy, a 1024 bajty to KiB lub kod jest nieprawidłowy. To jedyny sposób, w jaki możemy ominąć problem „jednostki są niejednoznaczne”. Różni ludzie definiujący „magiczne stałe” (jak KB) inaczej nie pomogą.
Brendan
11

Zdefiniowanie nazwy jako wartości liczbowej sugeruje, że ilekroć potrzebna jest inna wartość w jednym miejscu, które używa tej nazwy, prawdopodobnie będzie ona potrzebna we wszystkich. Sugeruje również, że zmiana wartości liczbowej przypisanej do nazwy jest uzasadnionym sposobem zmiany wartości. Taka implikacja może być użyteczna, gdy jest prawdziwa, i niebezpieczna, gdy jest fałszywa.

Fakt, że dwa różne miejsca używają konkretnej wartości literalnej (np. 1024), słabo sugeruje, że zmiany, które skłoniłyby programistę do zmiany jednego, mogą raczej zainspirować programistę do zmiany innych, ale ta implikacja jest znacznie słabsza niż miałaby zastosowanie. jeśli programista przypisał nazwę do takiej stałej.

Głównym zagrożeniem związanym z czymś takim #define BYTES_PER_KBYTE 1024jest to, że może to sugerować komuś, kto napotka, printf("File size is %1.1fkB",size*(1.0/BYTES_PER_KBYTE));że bezpiecznym sposobem na użycie kodu w tysiącach bajtów byłaby zmiana #defineinstrukcji. Taka zmiana może być katastrofalna, jeśli np. Jakiś inny niepowiązany kod odbierze rozmiar obiektu w kilobajtach i użyje tej stałej przy przydzielaniu do niego bufora.

Może być uzasadnione użycie #define BYTES_PER_KBYTE_FOR_USAGE_REPORT 1024i #define BYTES_PER_KBYTE_REPORTED_BY_FNOBULATOR 1024przypisanie innej nazwy dla każdego innego celu obsługiwanego przez stałą 1024, ale spowodowałoby to zdefiniowanie i użycie wielu identyfikatorów dokładnie jeden raz. Co więcej, w wielu przypadkach najłatwiej zrozumieć, co oznacza wartość, jeśli widzi się kod tam, gdzie jest używany, i najłatwiej jest dowiedzieć się, gdzie kod oznacza, jeśli widzi się wartości dowolnej stałej w nim użytej. Jeśli literał liczbowy zostanie użyty tylko raz w określonym celu, napisanie literału w miejscu, w którym jest używany, często daje bardziej zrozumiały kod niż przypisanie etykiety w jednym miejscu i użycie jej wartości w innym miejscu.

supercat
źródło
7

Skłoniłbym się do użycia tylko liczby, jednak myślę, że nie poruszono jednego ważnego problemu: ta sama liczba może oznaczać różne rzeczy w różnych kontekstach, co może skomplikować refaktoryzację.

1024 to także liczba KiB na MiB. Załóżmy, że używamy 1024 do reprezentowania tego obliczenia gdzieś lub w wielu miejscach, a teraz musimy to zmienić, aby zamiast tego obliczyć GiB. Zmiana stałej jest łatwiejsza niż globalne wyszukiwanie / zamiana, w której możesz przypadkowo zmienić niewłaściwy w niektórych miejscach lub pominąć go w innych.

Lub może to być nawet maska ​​wprowadzona przez leniwego programistę, który pewnego dnia musi zostać zaktualizowany.

To trochę wymyślony przykład, ale w niektórych bazach kodu może to powodować problemy podczas refaktoryzacji lub aktualizacji pod kątem nowych wymagań. W tym konkretnym przypadku nie uważałbym jednak, że zwykła liczba jest naprawdę złą formą, szczególnie jeśli można zawrzeć obliczenia w metodzie ponownego użycia, prawdopodobnie zrobiłbym to sam, ale uważam, że stała jest bardziej „poprawna”.

Jeśli jednak używasz nazwanych stałych, jak mówi supercat, ważne jest, aby zastanowić się, czy kontekst też ma znaczenie i czy potrzebujesz wielu nazw.

Nick P.
źródło