Czy istnieje pogląd na to, czy użycie #define do zdefiniowania pełnych wierszy kodu w celu uproszczenia kodowania jest dobrą czy złą praktyką programistyczną? Na przykład, gdybym musiał wydrukować kilka słów razem, zirytowałbym się na pisaniu
<< " " <<
Aby wstawić spację między słowami w instrukcji cout. Mógłbym po prostu zrobić
#define pSpace << " " <<
i wpisz
cout << word1 pSpace word2 << endl;
Dla mnie to nie dodaje ani nie odejmuje jasności kodu i sprawia, że pisanie jest nieco łatwiejsze. Są inne przypadki, w których mogę wymyślić, gdzie pisanie będzie znacznie łatwiejsze, zwykle w celu debugowania.
Masz jakieś przemyślenia na ten temat?
EDYCJA: Dzięki za wszystkie świetne odpowiedzi! To pytanie przyszło mi do głowy po wielokrotnym pisaniu, ale nigdy nie myślałem, że będą inne, mniej mylące makra. Dla tych, którzy nie chcą czytać wszystkich odpowiedzi, najlepszą alternatywą jest użycie makr twojego IDE w celu ograniczenia powtarzania pisania.
źródło
Odpowiedzi:
Pisanie kodu jest łatwe. Czytanie kodu jest trudne.
Piszesz kod raz. Żyje latami, ludzie czytają to sto razy.
Zoptymalizuj kod do odczytu, a nie do pisania.
źródło
Osobiście nie cierpię tego. Istnieje wiele powodów, dla których zniechęcam ludzi do tej techniki:
W czasie kompilacji rzeczywiste zmiany kodu mogą być znaczące. Pojawia się następny facet, a nawet zawiera nawias zamykający w swoim #define lub wywołaniu funkcji. To, co jest napisane w pewnym punkcie kodu, jest dalekie od tego, co będzie tam po wstępnym przetwarzaniu.
To jest nieczytelne. Może być dla ciebie jasne ... na razie ... jeśli to tylko ta jedna definicja. Jeśli stanie się to nawykiem, wkrótce skończysz z dziesiątkami #define i sam zaczniesz tracić orientację. Ale co najgorsze, nikt inny nie będzie w stanie zrozumieć, co
word1 pSpace word2
dokładnie oznacza (bez szukania #define).Może to stanowić problem dla narzędzi zewnętrznych. Powiedzmy, że w jakiś sposób otrzymałeś #define, która zawiera nawias zamykający, ale brak nawiasu otwierającego. Wszystko może działać dobrze, ale redaktorzy i inne narzędzia mogą postrzegać coś
function(withSomeCoolDefine;
raczej jako osobliwego (tj. Będą zgłaszać błędy i tak dalej). (Podobny przykład: wywołanie funkcji w definicji - czy twoje narzędzia analityczne będą w stanie znaleźć to wywołanie?)Konserwacja staje się znacznie trudniejsza. Wszystkie te definicje oprócz zwykłych problemów, jakie niesie ze sobą konserwacja. Oprócz powyższego punktu, negatywny wpływ może mieć również wsparcie narzędzi dla refaktoryzacji.
źródło
Moją główną myślą na ten temat jest to, że podczas pisania kodu nigdy nie używam „ułatwiania pisania”.
Moją główną zasadą przy pisaniu kodu jest ułatwienie jego odczytu. Uzasadnieniem tego jest po prostu to, że kod jest odczytywany o rząd wielkości więcej razy, niż jest napisany. W związku z tym czas, który tracisz na pisanie go ostrożnie, uporządkowany, właściwie ułożony, jest w rzeczywistości inwestowany w dalsze czytanie i rozumienie o wiele szybciej.
W związku z tym #define, którego używasz, po prostu łamie zwykły sposób na przemian
<<
i inne rzeczy . Łamie zasadę najmniejszego zaskoczenia i nie jest dobrą rzeczą IMHO.źródło
To pytanie daje jasny przykład tego, jak źle korzystać z makr. Aby zobaczyć inne przykłady (i się bawić), zobacz to pytanie .
Powiedziawszy to, podam przykłady z tego, co uważam za dobre włączenie makr.
Pierwszy przykład pojawia się w CppUnit , który jest strukturą testów jednostkowych. Jak każda inna standardowa platforma testowa, tworzysz klasę testową, a następnie musisz w jakiś sposób określić, które metody powinny być uruchamiane w ramach testu.
Jak widać, klasa ma blok makr jako pierwszy element. Jeśli dodam nową metodę
testSubtraction
, oczywiste jest, co musisz zrobić, aby włączyć ją do uruchomienia testowego.Te bloki makr rozwijają się do czegoś takiego:
Które wolisz czytać i utrzymywać?
Innym przykładem jest platforma Microsoft MFC, w której mapujesz funkcje na wiadomości:
Jakie są zatem rzeczy, które odróżniają „Dobre makra” od okropnego zła?
Wykonują zadanie, którego nie można uprościć w żaden inny sposób. Pisanie makra w celu ustalenia maksimum między dwoma elementami jest niepoprawne, ponieważ można to zrobić przy użyciu metody szablonu. Ale są pewne złożone zadania (na przykład mapowanie kodów komunikatów na funkcje składowe), których język C ++ po prostu nie obsługuje elegancko.
Mają wyjątkowo surowe, formalne zastosowanie. W obu tych przykładach makra bloki są ogłaszane przez makra początkowe i końcowe, a makra między nimi będą pojawiać się tylko wewnątrz tych bloków. Masz normalny C ++, krótko usprawiedliwiasz się blokiem makr, a następnie wracasz do normy. W przykładach „złych makr” makra są rozproszone po całym kodzie, a nieszczęsny czytelnik nie ma możliwości dowiedzenia się, kiedy obowiązują reguły C ++, a kiedy nie.
źródło
Z pewnością będzie lepiej, jeśli dostroisz swój ulubiony edytor IDE / tekstu do wstawiania fragmentów kodu, które często męczą Cię przy ponownym wpisywaniu. I lepszy jest termin „uprzejmy” do porównania. Właściwie nie mogę myśleć o żadnym podobnym przypadku, gdy wstępne przetwarzanie bije makra edytora. Cóż, może być jeden - kiedy z jakichś tajemniczych i nieszczęśliwych powodów ciągle używasz innego zestawu narzędzi do kodowania. Ale to nie jest uzasadnienie :)
Może to być także lepsze rozwiązanie dla bardziej złożonych scenariuszy, gdy przetwarzanie wstępne tekstu może uczynić coś znacznie bardziej nieczytelnego i skomplikowanego (pomyśl o parametryzowanym wprowadzaniu).
źródło
<< " " <<
.Inni wyjaśnili już, dlaczego nie należy tego robić. Twój przykład oczywiście nie zasługuje na wdrożenie za pomocą makra. Ale istnieje szeroki zakres przypadków, gdzie mają korzystać z makr dla zachowania czytelności.
Znanym przykładem mądrego zastosowania takiej techniki jest projekt Clanga : zobacz, jak
.def
tam wykorzystywane są pliki. Za pomocą makr#include
możesz podać jedną, czasem całkowicie deklaratywną definicję zbioru podobnych rzeczy, które zostaną rozwinięte w deklaracje typów,case
instrukcje tam, gdzie to stosowne, domyślne inicjalizatory itp. Znacznie zwiększa to łatwość konserwacji: nigdy nie zapomnisz dodać nowegocase
wyciągi wszędzie po dodaniu nowegoenum
, na przykład.Tak jak w przypadku każdego innego potężnego narzędzia, musisz ostrożnie używać preprocesora C. W sztuce programowania nie ma ogólnych zasad, takich jak „nigdy nie powinieneś tego używać” lub „zawsze musisz tego używać”. Wszystkie zasady są jedynie wytycznymi.
źródło
Nigdy nie należy używać #definii w ten sposób. W twoim przypadku możesz to zrobić:
źródło
Nie.
W przypadku makr przeznaczonych do użycia w kodzie dobrą wskazówką do testowania stosowności jest otoczenie jej rozszerzenia nawiasami (dla wyrażeń) lub nawiasów klamrowych (dla kodu) i sprawdzenie, czy nadal będzie się kompilować:
Makra używane w deklaracjach (jak przykład w odpowiedzi Andrew Shepherda) mogą uciec z luźniejszym zestawem reguł, o ile nie zakłócają otaczającego kontekstu (np. Przełączanie między
public
iprivate
).źródło
Jest to dość uzasadniona rzecz do zrobienia w czystym programie „C”.
Jest to niepotrzebne i mylące w programie C ++.
Istnieje wiele sposobów na uniknięcie powtarzającego się pisania kodu w C ++. Dzięki korzystaniu z udogodnień dostarczonych przez twoje IDE (nawet z vi prosty „
%s/ pspace /<< " " <</g
” zaoszczędziłby tyle pisania i nadal generowałby standardowy czytelny kod). Możesz zdefiniować prywatne metody implementacji tego lub w bardziej skomplikowanych przypadkach szablon C ++ byłby czystszy i prostszy.źródło
W C ++ można to rozwiązać poprzez przeciążenie operatora. Lub nawet coś tak prostego jak funkcja variadic:
lineWithSpaces(word1, word2, word3, ..., wordn)
jest zarówno prosty, jak i oszczędza ciągłego pisaniapSpaces
.Chociaż w twoim przypadku może to nie wydawać się wielkim problemem, istnieje rozwiązanie, które jest prostsze i bardziej niezawodne.
Ogólnie rzecz biorąc, jest kilka przypadków, w których użycie makra jest znacznie krótsze bez wprowadzania zaciemnienia, a przeważnie istnieje wystarczająco krótkie rozwiązanie wykorzystujące rzeczywiste funkcje językowe (makra są raczej zwykłym zastąpieniem łańcucha).
źródło
Tak, jest bardzo źle. Widziałem nawet ludzi, którzy to robią:
aby zapisać pisanie (to, co próbujesz osiągnąć).
Taki kod należy tylko w takich miejscach jak to .
źródło
Makra są złe i powinny być używane tylko wtedy, gdy naprawdę musisz. Istnieje kilka przypadków, w których obowiązują makra (głównie debugowanie). Ale w C ++ w większości przypadków można zamiast tego użyć funkcji wbudowanych .
źródło
goto
, wszystkich możliwych systemów makro itp.Nie, nie możesz używać makr do zapisywania tekstu .
Jednak jesteś dozwolony, nawet wymagane jest użycie ich do oddzielenia niezmiennej części kodu od zmiany i zmniejszenia nadmiarowości. W tym drugim przypadku musisz pomyśleć o alternatywach i wybrać makro tylko wtedy, gdy lepsze narzędzia nie działają. (W przypadku makra ćwiczeniowego jest dość na końcu linii, więc posiadanie go oznacza ostateczność ...)
Aby ograniczyć pisanie, większość edytorów ma makra, nawet inteligentne fragmenty kodu.
źródło
switch
/ itd., Możesz się założyć, że użyję makr, aby uniknąć szaleństwa - i zwiększyć czytelność. Używanie makr do zapisywania słów kluczowych, kontroli przepływu i tym podobnych jest głupie - ale powiedzenie, że nikt nie może ich używać do zapisywania naciśnięć klawiszy w prawidłowych kontekstach, jest równie oczywiste.