Ist es eine angemessene Verwendung von #define, um die Eingabe von wiederholtem Code zu vereinfachen?

17

Gibt es eine Ansicht darüber, ob die Verwendung von #define zum Definieren vollständiger Codezeilen zur Vereinfachung der Codierung eine gute oder schlechte Programmierpraxis ist? Wenn ich zum Beispiel ein paar Wörter zusammen drucken müsste, würde ich mich über das Tippen ärgern

<< " " <<

Einfügen eines Leerzeichens zwischen Wörtern in einer cout-Anweisung. Ich könnte es einfach tun

#define pSpace << " " <<

und Typ

cout << word1 pSpace word2 << endl;

Für mich addiert oder subtrahiert dies weder die Klarheit des Codes, noch erleichtert es die Eingabe. Es gibt andere Fälle, in denen ich mir vorstellen kann, dass das Tippen viel einfacher ist, normalerweise zum Debuggen.

Irgendwelche Gedanken dazu?

EDIT: Danke für die tollen Antworten! Diese Frage kam mir erst, nachdem ich viel getippt hatte, aber ich hätte nie gedacht, dass es andere, weniger verwirrende Makros geben würde. Für diejenigen, die nicht alle Antworten lesen möchten, ist die beste Alternative, die Makros Ihrer IDE zu verwenden, um sich wiederholende Eingaben zu reduzieren.

gsingh2011
quelle
74
Es ist dir klar, weil du es erfunden hast. Für alle anderen ist es nur verschleiert. Auf den ersten Blick sieht es nach einem Syntaxfehler aus. Wenn es kompiliert, würde ich überlegen, was zum Teufel und dann feststellen, dass Sie ein Makro haben, das nicht in allen Großbuchstaben ist. Meiner Meinung nach macht dies die Pflege des Codes nur schrecklich. Ich würde dies definitiv ablehnen, wenn es zur Codeüberprüfung käme und ich gehe nicht davon aus, dass Sie viele finden werden, die dies akzeptieren würden. Und du sparst 3 Zeichen !!!!!!!!!
Martin York
11
Wenn Sie die Wiederholbarkeit mit Funktionen oder Ähnlichem nicht vernünftigerweise ausschließen können, ist der bessere Ansatz zu lernen, was Ihr Editor oder Ihre IDE tun kann, um Ihnen zu helfen. Texteditor-Makros oder "Snippet" -Hotkeys können Sie vor dem Tippen bewahren, ohne die Lesbarkeit zu beeinträchtigen.
Steve314
2
Ich habe das schon einmal gemacht (mit größeren Stücken von Boilerplate), aber meine Praxis bestand darin, den Code zu schreiben, dann den Präprozessor auszuführen und die Originaldatei durch die Präprozessorausgabe zu ersetzen. Sparte mir das Tippen, ersparte mir (und anderen) den Wartungsaufwand.
TMN
9
Sie haben 3 Zeichen gespeichert und gegen verwirrende Aussagen eingetauscht. Sehr gutes Beispiel für ein schlechtes Makro, imho: o)
MaR
7
Viele Editoren haben eine erweiterte Funktion für genau dieses Szenario: "Kopieren und Einfügen"
Chris Burt-Brown,

Antworten:

111

Das Schreiben von Code ist einfach. Das Lesen von Code ist schwierig.

Sie schreiben einmal Code. Es lebt seit Jahren, die Leute lesen es hundertmal.

Optimieren Sie den Code zum Lesen, nicht zum Schreiben.

tdammers
quelle
11
Ich stimme zu 100%. (Eigentlich wollte ich diese Antwort selbst schreiben.) Code wird einmal geschrieben, kann aber Dutzende, Hunderte oder sogar Tausende Male gelesen werden, vielleicht von Dutzenden, Hunderten oder sogar Tausenden von Entwicklern. Die Zeit, die zum Schreiben von Code benötigt wird, ist völlig irrelevant. Es kommt nur auf die Zeit an, die zum Lesen und Verstehen erforderlich ist.
sbi
2
Der Präprozessor kann und sollte zur Optimierung des Codes zum Lesen und Verwalten verwendet werden.
SK-logic
2
Und selbst wenn Sie in ein oder zwei Jahren nur den Code lesen: Sie werden diese Dinge selbst vergessen, während Sie dazwischen andere Dinge tun.
Johannes
2
"Codieren Sie immer so, als wäre der Typ, der am Ende Ihren Code verwaltet, ein gewalttätiger Psychopath, der weiß, wo Sie leben." - (Martin Golding)
Dylan Yaga
@Dylan - andernfalls findet er Sie nach ein paar Monaten, in denen dieser Code gepflegt wird - (Ich)
Steve314
28

Persönlich hasse ich es. Es gibt eine Reihe von Gründen, warum ich Menschen von dieser Technik abhalte:

  1. Während der Kompilierung können Ihre tatsächlichen Codeänderungen erheblich sein. Der nächste Typ kommt und fügt sogar eine schließende Klammer in #define oder einen Funktionsaufruf ein. Was an einem bestimmten Punkt des Codes geschrieben ist, ist weit davon entfernt, was nach der Vorverarbeitung dort sein wird.

  2. Es ist nicht lesbar. Es kann für Sie klar sein .. fürs Erste .. wenn es nur diese eine Definition ist. Wenn es zur Gewohnheit wird, werden Sie bald Dutzende von #Defines haben und beginnen, den Überblick zu verlieren. Aber am schlimmsten ist, dass niemand sonst verstehen kann, was word1 pSpace word2genau bedeutet (ohne das #define nachzuschlagen).

  3. Dies kann zu einem Problem für externe Tools werden. Angenommen, Sie haben am Ende irgendwie eine #Definition, die eine schließende Klammer, aber keine öffnende Klammer enthält. Alles mag gut funktionieren, aber Editoren und andere Tools mögen das function(withSomeCoolDefine;als etwas Besonderes ansehen (dh sie werden Fehler melden und so weiter). (Ähnliches Beispiel: Ein Funktionsaufruf in einem Define. Können Ihre Analysetools diesen Aufruf finden?)

  4. Wartung wird viel schwieriger. Sie haben alle diese zusätzlich zu den üblichen Problemen definieren, die die Wartung mit sich bringt. Darüber hinaus kann mit dem obigen Punkt auch die Werkzeugunterstützung für das Refactoring negativ beeinflusst werden.

Frank
quelle
4
Ich habe mich schließlich aus fast allen Makros verbannt, weil ich mühsam versucht habe, sie direkt in Doxygen zu verarbeiten. Es ist schon einige Jahre her und insgesamt denke ich, dass sich die Lesbarkeit ein wenig verbessert hat - unabhängig davon, ob ich Doxygen verwende oder nicht.
Steve314
16

Mein Hauptgedanke dabei ist, dass ich beim Schreiben von Code in der Regel niemals "Erleichtern des Tippens" verwende.

Meine Hauptregel beim Schreiben von Code ist, dass er leicht lesbar ist. Das Grundprinzip dahinter ist einfach, dass Code eine Größenordnung öfter gelesen wird, als er geschrieben wurde. Daher wird die Zeit, die Sie verlieren , um es sorgfältig, ordentlich und korrekt zu schreiben, in der Tat investiert, um das Lesen und Verstehen zu beschleunigen.

Als solches unterbricht das von Ihnen verwendete #define einfach die übliche Art von alternierendem <<und anderem Zeug . Es verstößt gegen die Regel der geringsten Überraschung und ist meiner Meinung nach keine gute Sache.

Didier Trosset
quelle
1
+1: "Code wird eine Größenordnung öfter gelesen als geschrieben" !!!!
Giorgio
14

Diese Frage gibt ein klares Beispiel dafür, wie Sie Makros schlecht verwenden können. Um andere Beispiele zu sehen (und unterhalten zu werden), sehen Sie sich diese Frage an .

Nachdem ich das gesagt habe, werde ich Beispiele aus der Praxis geben, die meiner Meinung nach eine gute Integration von Makros darstellen.

Das erste Beispiel wird in CppUnit angezeigt , einem Unit-Test-Framework. Wie bei jedem anderen Standard-Testframework erstellen Sie eine Testklasse und müssen dann irgendwie angeben, welche Methoden im Rahmen des Tests ausgeführt werden sollen.

#include <cppunit/extensions/HelperMacros.h>

class ComplexNumberTest : public CppUnit::TestFixture  
{
    CPPUNIT_TEST_SUITE( ComplexNumberTest );
    CPPUNIT_TEST( testEquality );
    CPPUNIT_TEST( testAddition );
    CPPUNIT_TEST_SUITE_END();

 private:
     Complex *m_10_1, *m_1_1, *m_11_2;
 public:
     void setUp();
     void tearDown();
     void testEquality();
     void testAddition();
}

Wie Sie sehen, enthält die Klasse als erstes Element einen Makroblock. Wenn ich eine neue Methode hinzufüge testSubtraction, ist es offensichtlich, was Sie tun müssen, um sie in den Testlauf aufzunehmen.

Diese Makroblöcke werden folgendermaßen erweitert:

public: 
  static CppUnit::Test *suite()
  {
    CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>( 
                                   "testEquality", 
                                   &ComplexNumberTest::testEquality ) );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
                                   "testAddition",
                                   &ComplexNumberTest::testAddition ) );
    return suiteOfTests;
  }

Was würdest DU am liebsten lesen und pflegen?

Ein weiteres Beispiel ist das Microsoft MFC-Framework, in dem Sie Nachrichten Funktionen zuordnen:

BEGIN_MESSAGE_MAP( CMyWnd, CMyParentWndClass )
    ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )
    ON_COMMAND_RANGE(ID_FILE_MENUITEM1, ID_FILE_MENUITEM3, OnFileMenuItems)
    // ... Possibly more entries to handle additional messages
END_MESSAGE_MAP( )

Also, was sind die Dinge, die "gute Makros" von der schrecklichen bösen Art unterscheiden?

  • Sie erfüllen eine Aufgabe, die auf keine andere Weise vereinfacht werden kann. Das Schreiben eines Makros zur Bestimmung eines Maximums zwischen zwei Elementen ist falsch, da Sie dasselbe mit einer Vorlagenmethode erreichen können. Es gibt jedoch einige komplexe Aufgaben (z. B. das Zuordnen von Nachrichtencodes zu Elementfunktionen), die in der C ++ - Sprache nicht elegant ausgeführt werden.

  • Sie haben eine extrem strenge, formale Verwendung. In beiden Beispielen werden die Makroblöcke durch Starten und Beenden von Makros angekündigt, und die dazwischen liegenden Makros werden immer nur in diesen Blöcken angezeigt. Sie haben normales C ++, entschuldigen sich kurz mit einem Makroblock und kehren dann wieder zum Normalzustand zurück. In den Beispielen für "böse Makros" sind die Makros über den gesamten Code verteilt, und der unglückliche Leser kann nicht wissen, wann die C ++ - Regeln gelten und wann nicht.

Andrew Shepherd
quelle
5

Es ist auf jeden Fall besser, wenn Sie Ihren bevorzugten IDE- / Texteditor für das Einfügen von Codefragmenten optimieren, deren erneute Eingabe Sie als mühsam empfinden. Und besser ist "höfliche" Bezeichnung zum Vergleich. Eigentlich kann ich mir keinen ähnlichen Fall vorstellen, wenn die Vorverarbeitung die Makros des Editors übertrifft. Nun, vielleicht ist es eines - wenn Sie aus mysteriösen und unglücklichen Gründen ständig verschiedene Tools zum Codieren verwenden. Aber es ist keine Rechtfertigung :)

Es kann auch eine bessere Lösung für komplexere Szenarien sein, wenn die Textvorverarbeitung etwas viel Unlesbareres und Komplizierteres bewirken kann (denken Sie an parametrisierte Eingaben).

shabunc
quelle
2
+1. In der Tat: Lassen Sie den Redakteur die Arbeit für Sie erledigen. Sie werden das Beste aus beiden Welten bekommen, wenn Sie zum Beispiel eine Abkürzung für das machen << " " <<.
unperson325680
-1 für "Es kann auch eine bessere Lösung für komplexere Szenarien sein, wenn die Textvorverarbeitung viel unleserlicher und komplizierter sein kann (denken Sie an parametrisierte Eingaben)." - Wenn es so komplex ist, machen Sie eine Methode dafür, auch dann eine Methode dafür. zB Dieses Übel habe ich kürzlich im Code gefunden ..... #define printError (x) {puts (x); return x}
mattnz
@mattnz, ich habe Schleifen-Konstrukte gemeint, if / else-Konstrukte, Vorlagen für die Komparator-Erstellung und so weiter - solche Sachen. In IDEs können Sie mit einer solchen parametrisierten Eingabe nicht nur schnell einige Codezeilen eingeben, sondern auch schnell durch Parameter iterieren. Niemand versucht, mit Methoden zu konkurrieren. Methode ist Methode)))
Shabunc
4

Die anderen haben bereits erklärt, warum Sie es nicht tun sollten. Ihr Beispiel verdient es offensichtlich nicht, mit einem Makro implementiert zu werden. Aber gibt es eine breite Palette von Fällen , in denen Sie haben Makros für einen aus Gründen der Lesbarkeit zu verwenden.

Ein berüchtigtes Beispiel für eine sinnvolle Anwendung einer solchen Technik ist das Clang- Projekt: Sehen Sie, wie .defDateien dort verwendet werden. Mit Makros und#include Sie eine einzelne, manchmal vollständig deklarative Definition für eine Sammlung ähnlicher Dinge bereitstellen, die in Typdeklarationen, caseAnweisungen, Standardinitialisierer usw. aufgerollt wird. Dies erhöht die Wartbarkeit erheblich: Sie werden nie vergessen, neue hinzuzufügen caseAnweisungen überall, wenn Sie enumbeispielsweise eine neue hinzugefügt haben .

Wie bei jedem anderen leistungsstarken Tool müssen Sie den C-Präprozessor daher sorgfältig verwenden. In der Programmierkunst gibt es keine generischen Regeln, wie "das sollten Sie nie verwenden" oder "das müssen Sie immer verwenden". Alle Regeln sind nur Richtlinien.

SK-Logik
quelle
3

Es ist niemals angebracht, #defines so zu verwenden. In Ihrem Fall könnten Sie dies tun:

class MyCout 
{
public:
  MyCout (ostream &out) : m_out (out), m_space_pending (false)
  {
  }

  template <class T>
  MyCout &operator << (T &value)
  { 
    if (m_space_pending)
      m_out << " ";

    m_out << value;
    m_space_pending = false;
    return *this;
  }

  MyCout &operator << (const char *value)
  {
    if (m_space_pending)
      m_out << " ";

    m_out << value;
    m_space_pending = true;
    return *this;
  }

  MyCout &operator << (char *value) { return operator << (static_cast <const char *> (value)); }
  MyCout &operator << (ostream& (*fn)(ostream&)) { m_out << fn; return *this; }

private:
  ostream
    &m_out;

  bool
    m_space_pending;
};

int main (int argc, char *argv [])
{
  MyCout
    space_separated (cout);

  space_separated << "Hello" << "World" << endl;
}
Skizz
quelle
2

Nein.

Für Makros, die im Code verwendet werden sollen, ist es eine gute Richtlinie zum Testen der Eignung, die Erweiterung mit Klammern (für Ausdrücke) oder geschweiften Klammern (für Code) zu umgeben und zu prüfen, ob sie noch kompiliert werden:

// These don't compile:

#define pSpace (<< " " <<)
cout << word1 pSpace word2 << endl;

#define space(x) (" " << (x))
cout << word1 << space(word2) << endl;

// These do:

#define FOO_FACTOR (38)
x = y * FOO_FACTOR;

#define foo() (cout << "Foo" << endl)
foo();

#define die(c) { if ((c)) { exit(1); } }
die(foo > 8);

#define space(x) (" " + string((x)))
cout << "foo" << space("bar") << endl;

Makros, die in Deklarationen verwendet werden (wie das Beispiel in Andrew Shepherds Antwort), können mit einem lockeren Regelwerk davonkommen, solange sie den umgebenden Kontext nicht stören (z. B. zwischen publicund wechseln private).

Blrfl
quelle
1

Es ist eine einigermaßen gültige Sache, in einem reinen "C" -Programm zu tun.

Es ist unnötig und verwirrend in einem C ++ - Programm.

Es gibt viele Möglichkeiten, um das wiederholte Eingeben von Code in C ++ zu vermeiden. Durch die Nutzung der von Ihrer IDE bereitgestellten Funktionen (selbst mit vi würde ein einfaches " %s/ pspace /<< " " <</g" soviel Tipparbeit sparen und dennoch lesbaren Standardcode erzeugen). Sie könnten eine private Methode definieren, um dies zu implementieren, oder für komplexere Fälle wäre eine C ++ - Vorlage übersichtlicher und einfacher.

James Anderson
quelle
2
Nein, dies ist in reinem C nicht sinnvoll. Einzelwerte oder vollständige unabhängige Ausdrücke, die nur auf den Makroparametern beruhen, ja, und für letztere ist eine Funktion möglicherweise sogar die bessere Wahl. Solch ein halbgebackenes Konstrukt wie im Beispiel keineswegs.
Sichern Sie sich den
@secure - Ich stimme zu, dass es im Fall des angegebenen Beispiels keine gute Idee ist. Aber angesichts der fehlenden Vorlagen usw. gibt es gültige Verwendungen für "#DEFINE" -Makros in C.
James Anderson,
1

In C ++ kann dies durch Überladen von Operatoren behoben werden. Oder auch so etwas Einfaches wie eine variable Funktion:

lineWithSpaces(word1, word2, word3, ..., wordn) ist sowohl einfach als auch erspart Ihnen das Tippen pSpaces immer wieder zu .

In Ihrem Fall scheint das also keine große Sache zu sein, aber es gibt eine einfachere und robustere Lösung.

Im Allgemeinen gibt es nur wenige Fälle, in denen die Verwendung eines Makros ohne Einführung von Verschleierung erheblich kürzer ist, und meistens gibt es eine ausreichend kurze Lösung mit tatsächlichen Sprachfunktionen (Makros sind eher eine bloße Zeichenfolgensubstitution).

back2dos
quelle
0

Gibt es eine Ansicht darüber, ob die Verwendung von #define zum Definieren vollständiger Codezeilen zur Vereinfachung der Codierung eine gute oder schlechte Programmierpraxis ist?

Ja, es ist sehr schlimm. Ich habe sogar Leute gesehen, die das gemacht haben:

#define R return

Tippen zu speichern (was Sie erreichen wollen).

Ein solcher Code gehört nur an Orten wie diesen .

BЈовић
quelle
-1

Makros sind böse und sollten nur verwendet werden, wenn Sie wirklich müssen. Es gibt einige Fälle, in denen Makros anwendbar sind (hauptsächlich Debugging). In C ++ können Sie jedoch in den meisten Fällen stattdessen Inline- Funktionen verwenden.

Sakisk
quelle
2
Es gibt nichts in sich schlecht in jeder Programmiertechnik. Alle möglichen Tools und Methoden können verwendet werden, solange Sie wissen, was Sie tun. Es gilt für die berüchtigten goto, alle möglichen Makrosysteme usw.
SK-Logik
1
Das ist genau die Definition des Bösen in diesem Fall: "Etwas, das Sie die meiste Zeit vermeiden sollten, aber nicht etwas, das Sie die ganze Zeit vermeiden sollten". Es wird auf dem Link erklärt, dass das Böse zeigt.
Sakisk
2
Ich halte es für kontraproduktiv, etwas als "böse", "potenziell schädlich" oder sogar "verdächtig" zu brandmarken. Ich mag den Begriff "schlechte Praxis" und "Codegeruch" nicht. Jeder Entwickler muss im Einzelfall entscheiden, welche Praxis schädlich ist. Etikettierung ist eine schädliche Praxis - Menschen neigen dazu, nicht weiter darüber nachzudenken, ob etwas bereits von den anderen etikettiert wurde.
SK-logic
-2

Nein, Sie dürfen keine Makros zum Speichern der Eingabe verwenden .

Sie dürfen sie jedoch auch verwenden, um nicht geänderten Code von geänderten zu trennen und die Redundanz zu verringern. Für letztere müssen Sie Alternativen denken und nur dann ein Makro auswählen, wenn bessere Tools nicht funktionieren. (Zum Üben steht das Makro ganz am Ende der Zeile, was bedeutet, dass es das letzte Mittel ist ...)

Um das Tippen zu reduzieren, haben die meisten Editoren Makros, sogar intelligente Code-Schnipsel.

Balog Pal
quelle
"Nein, Sie dürfen keine Makros zum Speichern der Eingabe verwenden ." - Gute Arbeit, ich werde hier nicht auf Ihre Bestellung hören! Wenn ich einen Stapel von Objekten habe, die ich deklarieren / definieren / zuordnen / switch/ usw. muss, kann man wetten, dass ich Makros verwende, um Wahnsinn zu vermeiden - und die Lesbarkeit zu verbessern. Das Verwenden von Makros zum Speichern der Eingabe von Schlüsselwörtern, Kontrollabläufen und Ähnlichem ist dumm - aber zu sagen, dass niemand sie zum Speichern von Tastenanschlägen in gültigen Kontexten verwenden kann, ist genauso einfach.
Underscore_d