Wykorzystanie magicznych ciągów / liczb [zamknięte]

31

Jest to nieco kontrowersyjny temat i myślę, że jest tyle opinii, ile jest programistów. Ale ze względu na to chcę wiedzieć, jakie są powszechne praktyki w biznesie (lub w miejscach pracy).

W moim miejscu pracy mamy ścisłe wytyczne dotyczące kodowania. Jedna z nich poświęcona jest magicznym ciągom znaków / liczbom. Mówi (dla C #):

Nie używaj wartości literalnych, ani liczbowych, ani ciągów znaków w kodzie innym niż definiowanie stałych symbolicznych. Użyj następującego wzorca, aby zdefiniować stałe:

public class Whatever  
{  
   public static readonly Color PapayaWhip = new Color(0xFFEFD5);  
   public const int MaxNumberOfWheels = 18;  
}

Istnieją wyjątki: wartości 0, 1 i null prawie zawsze można bezpiecznie stosować. Bardzo często wartości 2 i -1 są również OK. Ciągi przeznaczone do rejestrowania lub śledzenia są wyłączone z tej reguły. Literały są dozwolone, gdy ich znaczenie jest jasne z kontekstu i nie podlega przyszłym zmianom.

mean = (a + b) / 2; // okay  
WaitMilliseconds(waitTimeInSeconds * 1000); // clear enough

Idealną sytuacją byłby jakiś oficjalny artykuł badawczy pokazujący wpływ na czytelność / łatwość konserwacji kodu, gdy:

  • Magiczne liczby / ciągi znaków są wszędzie
  • Magiczne ciągi / liczby są zastępowane ciągłymi deklaracjami w rozsądny sposób (lub w różnych stopniach zasięgu) - i proszę nie krzycz na mnie za używanie „rozsądnie”, wiem, że każdy ma inne pojęcie, co to „rozsądnie”
  • Liczby magiczne / liczby są zastępowane w nadmiarze i tam, gdzie nie musiałyby być (patrz mój przykład poniżej)

Chciałbym to zrobić, aby mieć pewne naukowe argumenty podczas kłótni z jednym z moich kolegów, który dąży do tego, aby zadeklarować stałe, takie jak:

private const char SemiColon = ';';
private const char Space = ' ';
private const int NumberTen = 10;

Innym przykładem może być (a ten jest w JavaScript):

var someNumericDisplay = new NumericDisplay("#Div_ID_Here");

Czy przyklejasz identyfikatory DOM na wierzchu pliku javascript, jeśli ten identyfikator jest używany tylko w jednym miejscu?

Przeczytałem następujące tematy:
StackExchange
StackOverflow
Bytes Społeczność IT
Istnieje wiele innych artykułów, a po ich przeczytaniu pojawiają się pewne wzorce.

Więc moim pytaniem powinno być używanie magicznych ciągów i liczb w naszym kodzie? W szczególności szukam odpowiedzi ekspertów, które w miarę możliwości są poparte referencjami.

Daniel Gruszczyk
źródło
2
Magiczna zmienna to zmienna, która ma znaczenie, które nie jest odzwierciedlone w jej treści. Wartość całkowita „10” odzwierciedla znaczenie liczby 10, więc nie ma potrzeby ustawiania jej na stałą. To samo dotyczy spacji i średnika. Z drugiej strony, jeśli masz wartość „%% ?? %%” i jest to jakiś niestandardowy separator, to MUSI on zostać umieszczony jako stały, ponieważ jego zawartość nie odzwierciedla faktu, że jest to separator.
Jeroen Vannevel,
23
NumberTen = 10Jest to bezcelowe, ponieważ liczba 10 nie zostanie ponownie zdefiniowana. MaxRetryCount = 10W tym momencie możemy chcieć zmienić maksymalną liczbę ponownych prób. private const char SemiColon = ';'; Głupi. private const char LineTerminator = ';'; Mądry.
Mike
1
Rzeczywiste pytanie nie jest jasne.
Tulains Córdova,

Odpowiedzi:

89

... podczas kłótni z jednym z moich kolegów, który dąży do tego, aby zadeklarować stałe takie jak:

private const char SemiColon = ';';
private const char Space = ' ';
private const int NumberTen = 10;

Argument, który musisz wysunąć z kolegą, nie dotyczy nazywania dosłownej przestrzeni, Spaceale jego zły wybór imienia dla jego stałych.

Załóżmy, że Twoim zadaniem jest przeanalizowanie strumienia rekordów, które zawierają pola oddzielone średnikami ( a;b;c) i same są oddzielone spacjami ( a;b;c d;e;f). Jeśli ktoś, kto napisał twoją specyfikację, zadzwoni do ciebie za miesiąc i powie: „popełniliśmy błąd, pola w rekordach są oddzielone symbolami potoku ( a|b|c d|e|f)”, co robisz?

W ramach schematu wartość-jak-nazwa, który preferuje twój kolega, musisz zmienić wartość literal ( SemiColon = '|') i żyć z kodem, który nadal używa SemiColondo czegoś, co tak naprawdę nie jest już średnikiem. Doprowadzi to do negatywnych komentarzy w recenzjach kodu . W celu zmniejszenia, że można zmienić nazwę na dosłownym PipeSymboli przejść i zmienić wszystkie wystąpienia SemiColondo PipeSymbol. Przy takim tempie równie dobrze mógłbyś po prostu użyć dosłownego średnika ( ';'), ponieważ będziesz musiał ocenić każde użycie z osobna i będziesz wprowadzać taką samą liczbę zmian.

Identyfikatory stałych muszą opisywać, co robi wartość , a nie jaka jest wartość , i tam kolega skręcił w lewo w chwasty. W opisanej powyżej aplikacji do dzielenia pól średnikiem jest separator pól, a stałe należy odpowiednio nazwać:

private const char FieldSeparator = ';';    // Will become '|' a month from now
private const char RecordSeparator = ' ';
private const int MaxFieldsPerRecord = 10;

W ten sposób, gdy zmienia się separator pól, zmieniasz dokładnie jeden wiersz kodu, deklarację stałej. Ktoś patrząc na zmianę zobaczy tylko jedną linię i natychmiast zrozumie, że separator pola zmienił się z średnika na symbol potoku. Pozostała część kodu, który nie musiał się zmieniać, ponieważ używał stałej, pozostaje taki sam, a czytelnik nie musi go przekopywać, aby zobaczyć, co jeszcze zostało zrobione.

Blrfl
źródło
W pełni się zgadzam. Kilka dekad temu pracowałem nad projektem, w którym wiadomości były wysyłane w segmentach, z których każdy używa 8 rejestrów ogólnych. Ktoś zadeklarował #define one 1 #define two 2 itp. (Lub jakikolwiek inny odpowiednik w brytyjskim urzędzie pocztowym Coral, wówczas wybranym języku). Z góry dobiegło słowo, że w polu długości będzie liczba bajtów, a nie segmentów, więc oczywiście kod został zmieniony na #define one 8 #define two 16 etc
Mawg
3
Jak głupie, jak nazwy takie jak średnikiem lub PipeSymbol wydawać, zmieniając jeden do drugiego za pomocą skryptu będzie dużo łatwiejsze niż zmiana każdy wpływa ;na |.
Brandin,
Co z przypadkiem, gdy dany literał łańcuchowy jest używany wielokrotnie w pliku, ale nie ma innego znaczenia niż jego wartość? Na przykład, jeśli testujesz, czy możesz otrzymać określony klucz na mapie w 20 scenariuszach różnic, czy powinienem zdefiniować stałą tak ?: public static final String MY_KEY_NAME = "MyKeyName"
Jordan McQueen
1
@JordanMcQueen Należy uzasadnić użycie nagich literałów, jeśli (i tylko jeśli) każdy z nich jest użyty dokładnie raz i nie jest potrzebny nigdzie indziej. Jeśli jest to coś jak każdy scenariusz samopoczucia kodu, który przetwarza inny format pliku, każdy format powinien określić swoją własną stałą (na przykład CSV_RECORD_SEPARATOR, TSV_RECORD_SEPARATORitd.)
Blrfl
8

Zdefiniowanie średnika jako stałej jest zbędne, ponieważ średnik jest już sam w sobie stały . To się nigdy nie zmieni.

To nie jest tak, że pewnego dnia ktoś ogłosi „zmiana terminologii, + to teraz nowy średnik”, a twój kolega chętnie spieszy się tylko z aktualizacją stałej (śmiali się ze mnie - spójrz na nie teraz).

Jest też kwestia spójności. Gwarantuję, że jego NumberTenstała NIE będzie używana przez wszystkich (większość programistów nie oszaleje), więc i tak nie spełni ona zamierzonego celu. Kiedy nadejdzie apokalipsa i globalnie przeskaluje „dziesięć” do 9, aktualizacja stałej NIE zadziała, ponieważ nadal pozostawi ci mnóstwo literałów 10w kodzie, więc teraz system staje się całkowicie nieprzewidywalny, nawet w zakresie rewolucyjnego założenia, że ​​„dziesięć” oznacza „9”.

Przechowywanie wszystkich ustawień jako stałych jest również czymś, o czym chciałbym się zastanowić. Nie należy tego robić lekko.

Jakie przykłady tego rodzaju użytkowania zebraliśmy do tej pory? Terminator linii ... maksymalna liczba ponownych prób ... maksymalna liczba kół ... czy jesteśmy pewni, że to się nigdy nie zmieni?

Kosztem jest to, że zmiana ustawień domyślnych wymaga ponownej kompilacji aplikacji, aw niektórych przypadkach nawet jej zależności (ponieważ wartości stałych numerycznych mogą zostać zakodowane podczas kompilacji).

Istnieje również aspekt testowania i kpiny. Zdefiniowałeś ciąg połączenia jako const, ale teraz nie możesz wyśmiewać dostępu do bazy danych (ustanowić fałszywe połączenie) w teście jednostkowym.

Konrad Morawski
źródło
4
„To się nigdy nie zmieni”. Kiedyś myślałem o apostrofie (zawsze związanym z wartością ASCII 39). Niektóre stare aplikacje służyły do ​​zwijania apostrofu. Ale teraz nowoczesne aplikacje traktują tę wartość ASCII jako prostą apostrofę zgodną ze starymi aplikacjami, a zamiast tego ludzie często używają (lewy pojedynczy cytat, Unicode 8217 ) dla aplikacji zgodnych z pokazywaniem innego glifu dla zwiniętego znaku. Ponieważ Europa używa przecinków tak, jak Amerykanie używają kropek jako miejsca dziesiętnego, trochę waham się przed ogłoszeniem „nie ... nigdy”.
TOOGAM,
@TOOGAM dobrze, Twój przykład uzasadnia o DecimalPointstałej - ale nie Commalub Periodstałymi. To spora różnica: ta pierwsza oznacza funkcję , rolę lub cel wartości. „Średnik” lub „przecinek” nie należą do tej kategorii.
Konrad Morawski
Dotyczy to przykładu z kropką dziesiętną. Jednak przykład apostrofu wydaje się być podobną (lub identyczną) kategorią jak przecinek (lub średnik).
TOOGAM,
@KonradMorawski Semicolon może być wykorzystywany do wielu celów, takich jak dzielenie łańcucha lub kończenie linii. Jest to jego znaczenie (nie wartość), którego należy używać do nazywania constance. Rozważ przyszłą zmianę, tzn. Jutro pozwolimy na przetworzenie 20 rekordów, więc constance o nazwie NumberTen jest poza kontekstem, podczas gdy maxRecord nadal będzie w porządku.
MaxZoom
5
private const char SemiColon = ';';
private const char Space = ' ';
private const int NumberTen = 10;

Więc twój kolega dąży do codziennego wpisu do WTF. Te definicje są głupie i zbędne. Jednak, jak zauważyli inni, następujące definicje nie byłyby głupie ani zbędne:

private const char StatementTerminator = ';';
private const char Delimiter = ' ';
private const int  BalanceInquiryCode = 10;

Liczby i łańcuchy „magiczne” są stałymi, które mają znaczenie przekraczające ich bezpośrednią, dosłowną wartość. Jeśli stała 10ma znaczenie wykraczające poza „dziesięć rzeczy” (powiedzmy jako kod dla określonej operacji lub warunku błędu), wtedy staje się „magią” i powinna zostać zastąpiona stałą symboliczną opisującą to abstrakcyjne znaczenie.

Poza wyraźnym opisaniem zamiarów, stałe symboliczne również oszczędzają ci bólów głowy, gdy źle literujesz. Prosta transpozycja z „CVS” do „CSV” w jednym wierszu kodu przeszła przez testy jednostkowe i kontrolę jakości i wprowadziła go do produkcji, gdzie spowodowała niepowodzenie określonej operacji. Tak, oczywiście testy jednostkowe i QA były niekompletne i to jest własny problem, ale użycie stałej symbolicznej pozwoliłoby uniknąć zgagi.

John Bode
źródło
3

Nie powinno być w tym nic kontrowersyjnego. Nie chodzi o to, czy używać magicznych liczb, czy nie, chodzi o to, aby mieć czytelny kod.
Rozważ różnicę między: if(request.StatusCode == 1)a if(request.HasSucceeded). W tym przypadku argumentowałbym, że ten drugi jest o wiele bardziej czytelny, ale to nie znaczy, że nigdy nie możesz mieć takiego kodu int MaxNumberOfWheels = 18.

PS: Właśnie dlatego absolutnie nienawidzę wytycznych dotyczących kodowania. Deweloperzy powinni być wystarczająco dojrzali, aby móc wykonywać takie oceny; nie powinni pozostawiać tego fragmentowi tekstu utworzonego przez boga, który wie kto.

Stefan Billiet
źródło
13
Kierowcy powinni być na tyle dojrzali, aby móc ocenić, po której stronie drogi jeżdżą;)
Konrad Morawski,
2
Wynik wywołania oceny może się różnić nawet między dojrzałymi programistami, więc nawet arbitralne wytyczne dotyczące kodowania mają na celu poprawę czytelności dzięki spójności. Nie ma to związku z faktem, że tworzenie stałej NumberTen nie ma sensu.
Mike Partridge,
1
Nie nalegałbym, aby były formalne, opatrzone pieczęcią itp. Mogą być nieformalne, ale należy je uzgodnić, a to już wykracza poza zwykłą dojrzałość osądu. Ale skasowałeś teraz swój komentarz Stefan :)
Konrad Morawski
1
@StefanBilliet - wcale. Chodzi mi o to, że czytelność poprawia się dzięki spójności. Problemem tutaj nie jest sama wytyczna kodowania, ale wytyczna doprowadzona do skrajności przez nieporozumienie.
Mike Partridge,
@MikePartridge Może powinienem był opracować; wytyczne w zakresie kodowania, które widziałem, są bardziej zgodne z ogólną instrukcją dotyczącą tego, jak ktoś gdzieś myślał, że oprogramowanie powinno być napisane, zamiast umów, takich jak ty i Konrad, prawdopodobnie myślą o :-)
Stefan Billiet