È un uso appropriato di #define per facilitare la digitazione del codice ripetuto?


17

C'è qualche idea sul fatto che usare #define per definire linee complete di codice per semplificare la programmazione sia una buona o una cattiva pratica di programmazione? Ad esempio, se avessi bisogno di stampare un mucchio di parole insieme, mi annoierei a scrivere

<< " " <<

Inserire uno spazio tra le parole in un'istruzione cout. Potrei semplicemente fare

#define pSpace << " " <<

e digitare

cout << word1 pSpace word2 << endl;

Per me questo non aggiunge né sottrae dalla chiarezza del codice e rende la digitazione leggermente più semplice. Ci sono altri casi che mi vengono in mente in cui la digitazione sarà molto più semplice, di solito per il debug.

Qualche idea su questo?

EDIT: Grazie per tutte le ottime risposte! Questa domanda mi è venuta in mente dopo aver fatto molte digitazioni ripetitive, ma non avrei mai pensato che ci sarebbero state altre macro meno confuse da usare. Per coloro che non vogliono leggere tutte le risposte, la migliore alternativa è usare le macro del tuo IDE per ridurre la digitazione ripetitiva.


74
È chiaro per te perché l'hai inventato. Per tutti gli altri è stato solo offuscato. All'inizio sembra un errore di sintassi. Quando viene compilato, penso a cosa diavolo e poi scopro che hai una macro che non è in maiuscolo. A mio avviso, questo rende il codice orribile da mantenere, lo rifiuterei sicuramente se venisse per la revisione del codice e non mi aspetto che troverai molti che lo accetterebbero. E stai salvando 3 personaggi !!!!!!!!!
Martin York,

11
Laddove non puoi ragionevolmente valutare la ripetitività usando le funzioni o altro, l'approccio migliore è quello di imparare cosa il tuo editor o IDE può fare per aiutarti. Le macro dell'editor di testo o i tasti di scelta rapida "snippet" possono salvarti dalla digitazione senza compromettere la leggibilità.
Steve314,

2
L'ho già fatto prima (con grossi pezzi di boilerplate), ma la mia pratica era scrivere il codice, quindi eseguire il preprocessore e sostituire il file originale con l'output del preprocessore. Mi ha salvato la digitazione, mi ha risparmiato (e altri) la seccatura di manutenzione.
TMN,

9
Hai salvato 3 personaggi e li hai scambiati per dichiarazioni confuse. Ottimo esempio di macro scadente, imho: o)
MaR

7
Molti editor hanno una funzionalità avanzata proprio per questo scenario, si chiama "Copia e incolla"
Chris Burt-Brown,

Risposte:


111

Scrivere codice è facile. La lettura del codice è difficile.

Scrivi il codice una volta. Vive per anni, la gente lo legge cento volte.

Ottimizza il codice per la lettura, non per la scrittura.


11
Sono d'accordo al 100%. (In effetti, stavo per scrivere questa risposta da solo.) Il codice è scritto una volta, ma potrebbe essere letto dozzine, centinaia o addirittura migliaia di volte, forse a dozzine, a centinaia o persino a migliaia di sviluppatori. Il tempo necessario per scrivere il codice è totalmente irrilevante, l'unica cosa che conta è il tempo per leggerlo e comprenderlo.
sbi,

2
Il preprocessore può e deve essere utilizzato per ottimizzare il codice per la lettura e la manutenzione.
SK-logic,

2
E anche se sei solo tu a leggere il codice in uno o due anni: dimenticherai queste cose tu stesso mentre farai altre cose nel mezzo.
johannes,

2
"Codifica sempre come se il ragazzo che finisce per mantenere il tuo codice sarà uno psicopatico violento che sa dove vivi." - (Martin Golding)
Dylan Yaga,

@Dylan - altrimenti, dopo alcuni mesi a mantenere quel codice, ti troverà - (Io)
Steve314

28

Personalmente, lo detesto. Esistono diversi motivi per cui scoraggio le persone da questa tecnica:

  1. Al momento della compilazione, le modifiche al codice potrebbero essere significative. Il ragazzo successivo arriva e include anche una parentesi di chiusura nel suo #define o una chiamata di funzione. Ciò che è scritto in un determinato punto del codice è tutt'altro che ciò che sarà dopo la pre-elaborazione.

  2. È illeggibile. Potrebbe essere chiaro per te ... per ora ... se è solo questa definizione. Se diventa un'abitudine, presto finirai con dozzine di #define e inizierai a perdere traccia da solo. Ma la cosa peggiore di tutte, nessun altro sarà in grado di capire cosa word1 pSpace word2significhi esattamente (senza cercare #define).

  3. Potrebbe diventare un problema per gli strumenti esterni. Diciamo che in qualche modo finisci con un #define che include una parentesi quadra di chiusura, ma nessuna parentesi quadra aperta. Tutto può funzionare bene, ma i redattori e altri strumenti possono vedere qualcosa function(withSomeCoolDefine;di piuttosto strano (cioè, riporteranno errori e quant'altro). (Esempio simile: una chiamata di funzione all'interno di un define - i tuoi strumenti di analisi saranno in grado di trovare questa chiamata?)

  4. La manutenzione diventa molto più difficile. Hai tutti quelli definiti oltre ai soliti problemi che la manutenzione comporta. Oltre al punto precedente, anche il supporto degli strumenti per il refactoring può essere influenzato negativamente.


4
Alla fine mi sono bandito da quasi tutte le macro a causa delle seccature di provare a gestirle proprio in Doxygen. Sono passati parecchi anni ormai, e nel complesso penso che la leggibilità sia migliorata un po ', indipendentemente dal fatto che io stia usando Doxygen o meno.
Steve314,

16

Il mio pensiero principale su questo, è che non uso mai "rendere la digitazione più facile" come regola durante la scrittura del codice.

La mia regola principale quando si scrive il codice è di renderlo facilmente leggibile. La logica alla base di questo è semplicemente che il codice viene letto un ordine di grandezza più volte che è scritto. In quanto tale, il tempo che perdi a scriverlo con cura, in modo ordinato, correttamente disposto, viene di fatto investito nel fare ulteriori letture e comprendere molto più velocemente.

Come tale, il #define che usi semplicemente interrompe il solito modo di alternare <<e altre cose . Infrange la regola della minima sorpresa e non è una buona cosa.


1
+1: "Il codice viene letto un ordine di grandezza più volte che è scritto" !!!!
Giorgio,

14

Questa domanda fornisce un chiaro esempio di come è possibile utilizzare male le macro. Per vedere altri esempi (e divertirti) vedi questa domanda .

Detto questo, fornirò esempi reali di ciò che considero una buona incorporazione di macro.

Il primo esempio appare in CppUnit , che è un framework di unit test. Come qualsiasi altro framework di test standard, crei una classe di test e quindi devi in ​​qualche modo specificare quali metodi dovrebbero essere eseguiti come parte del test.

#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();
}

Come puoi vedere, la classe ha un blocco di macro come primo elemento. Se ho aggiunto un nuovo metodotestSubtraction , è ovvio che cosa è necessario fare per includerlo nell'esecuzione del test.

Questi macro blocchi si espandono in qualcosa del genere:

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;
  }

Quale preferiresti leggere e mantenere?

Un altro esempio è nel framework Microsoft MFC, in cui si mappano le funzioni ai messaggi:

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( )

Quindi, quali sono le cose che distinguono i "Macro buoni" dall'orribile genere malvagio?

  • Eseguono un compito che non può essere semplificato in nessun altro modo. Scrivere una macro per determinare un massimo tra due elementi è sbagliato, perché puoi ottenere lo stesso usando un metodo modello. Ma ci sono alcune attività complesse (ad esempio, la mappatura dei codici dei messaggi alle funzioni dei membri) che il linguaggio C ++ non gestisce in modo elegante.

  • Hanno un uso estremamente rigoroso e formale. In entrambi questi esempi i blocchi di macro vengono annunciati avviando e terminando le macro, e le macro tra di esse appariranno solo all'interno di questi blocchi. Hai C ++ normale, ti scusi brevemente con un blocco di macro e poi torni alla normalità. Negli esempi di "macro malvagie", le macro sono sparse in tutto il codice e il lettore sfortunato non ha modo di sapere quando si applicano le regole C ++ e quando non lo fanno.


5

Sarà sicuramente meglio se ottimizzi il tuo IDE / editor di testo preferito per inserire frammenti di codice che trovi noioso da riscrivere ancora e ancora. E meglio è il termine "educato" per il confronto. In realtà, non riesco a pensare a nessun caso simile quando la preelaborazione batte le macro dell'editor. Bene, potrebbe esserlo - quando per ragioni misteriose e infelici usi costantemente diversi strumenti per la codifica. Ma non è una giustificazione :)

Può anche essere una soluzione migliore per scenari più complessi, quando la preelaborazione del testo può fare cose molto più illeggibili e complicate (pensa all'input parametrizzato).


2
+1. Anzi: lascia che l'editor faccia il lavoro per te. Otterrai il meglio da entrambi i mondi se ad esempio fai un'abbreviazione di << " " <<.
unperson325680,

-1 per "Può anche essere una soluzione migliore per scenari più complessi, quando la preelaborazione del testo può fare cose molto più illeggibili e complicate (pensa all'input parametrizzato)" - Se è così complesso, crea un metodo per farlo, anche allora un metodo per questo. es. Questo male che ho trovato di recente nel codice ..... #define printError (x) {put (x); return x}
mattnz,

@mattnz, intendevo costrutti loop, costrutti if / else, modelli per la creazione di comparatori e così via - quel genere di cose. Negli IDE questo tipo di input parametrizzato consente non solo di digitare rapidamente poche righe di codice, ma anche di scorrere rapidamente i parametri. Nessuno cerca di competere con i metodi. il metodo è metodo)))
shabunc,

4

Gli altri hanno già spiegato perché non dovresti farlo. Il tuo esempio ovviamente non merita di essere implementato con una macro. Ma, c'è una vasta gamma di casi in cui si avete utilizzare le macro per motivi di leggibilità.

Un noto esempio di una saggia applicazione di tale tecnica è il progetto Clang : guarda come i .deffile vengono utilizzati lì. Con le macro e #includepuoi fornire un'unica definizione, a volte interamente dichiarativa, per una raccolta di cose simili che verranno srotolate in tipi di dichiarazioni, casedichiarazioni ovunque appropriate, inizializzatori predefiniti, ecc. Aumenta significativamente la manutenibilità: non dimenticherai mai di aggiungere nuove casedichiarazioni ovunque quando ne hai aggiunto una nuova enum, ad esempio.

Quindi, come con qualsiasi altro strumento potente, devi usare con attenzione il preprocessore C. Non ci sono regole generiche nell'arte della programmazione, come "non dovresti mai usarlo" o "devi sempre usarlo". Tutte le regole non sono altro che linee guida.


3

Non è mai appropriato usare #define in questo modo. Nel tuo caso, potresti farlo:

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;
}

2

No.

Per le macro che devono essere utilizzate nel codice, una buona linea guida per testare l'adeguatezza è circondarne l'espansione con parentesi (per espressioni) o parentesi graffe (per codice) e vedere se sarà ancora compilato:

// 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;

Le macro utilizzate nelle dichiarazioni (come nell'esempio nella risposta di Andrew Shepherd) possono cavarsela con un insieme più libero di regole purché non turbino il contesto circostante (es. Il passaggio tra publice private).


1

È una cosa ragionevolmente valida da fare in un programma "C" puro.

È inutile e confuso in un programma C ++.

Esistono molti modi per evitare la digitazione ripetitiva del codice in C ++. Dall'utilizzo delle funzionalità fornite dal tuo IDE (anche con vi un semplice " %s/ pspace /<< " " <</g" risparmierebbe tanto digitando e produrrebbe comunque un codice leggibile standard). È possibile definire un metodo privato per implementare questo o per casi più complessi il modello C ++ sarebbe più semplice e pulito.


2
No, non è una cosa ragionevolmente valida da fare in puro C. Singoli valori o espressioni indipendenti complete che si basano solo sui parametri macro, sì, e per quest'ultima una funzione può persino essere una scelta migliore. Un costrutto così cotto come nell'esempio, assolutamente no.
Sicuro il

@secure - Sono d'accordo che non è una buona idea nel caso dell'esempio fornito. Ma dati i modelli di mancanza ecc. Ci sono usi validi per le macro "#DEFINE" in C.
James Anderson,

1

In C ++ questo può essere risolto con un sovraccarico dell'operatore. O anche qualcosa di semplice come una funzione variadica:

lineWithSpaces(word1, word2, word3, ..., wordn)è sia semplice che ti fa risparmiare di pSpacesnuovo e ancora.

Quindi, mentre nel tuo caso potrebbe non sembrare un grosso problema, c'è una soluzione che è più semplice e più robusta.

In generale, ci sono alcuni casi in cui l'uso di una macro è significativamente più breve senza introdurre l'offuscamento, e soprattutto c'è una soluzione sufficientemente breve che utilizza le funzioni del linguaggio reale (le macro sono più di una semplice sostituzione di stringhe).


0

C'è qualche idea sul fatto che usare #define per definire linee complete di codice per semplificare la programmazione sia una buona o una cattiva pratica di programmazione?

Sì, è molto male. Ho anche visto persone fare questo:

#define R return

per salvare la digitazione (cosa stai cercando di ottenere).

Tale codice appartiene solo in posti come questo .


-1

Le macro sono cattive e dovrebbero essere utilizzate solo quando è necessario. Ci sono alcuni casi in cui le macro sono applicabili (principalmente il debug). Ma in C ++ nella maggior parte dei casi puoi usare invece funzioni inline .


2
Non c'è nulla di intrinsecamente malvagio in nessuna tecnica di programmazione. Tutti gli strumenti e i metodi possibili possono essere utilizzati, purché tu sappia cosa stai facendo. Si applica al famigerato goto, a tutti i possibili macrosistemi, ecc.
Logica SK

1
Questa è esattamente la definizione del male in questo caso: "qualcosa che dovresti evitare la maggior parte del tempo, ma non qualcosa che dovresti evitare tutto il tempo". È spiegato sul collegamento che il male sta indicando.
sakisk,

2
Ritengo controproducente marchiare qualsiasi cosa come "malvagia", "potenzialmente dannosa" o addirittura "sospettosa". Non mi piace l'idea di "cattiva pratica" e "odore di codice". Ogni sviluppatore deve decidere in ogni caso specifico, quale pratica è dannosa. L'etichettatura è una pratica dannosa : le persone tendono a non pensare più se qualcosa è già stato etichettato dagli altri.
SK-logic,

-2

No, non puoi utilizzare le macro per salvare la digitazione .

Tuttavia, è consentito, anche richiesto di usarli per separare parte non modificabile del codice da quelle modificabili e ridurre la ridondanza. Per quest'ultimo devi pensare a alternative e scegliere la macro solo se gli strumenti migliori non funzionano. (Per la macro pratica è abbastanza alla fine della linea, quindi averlo implica l'ultima risorsa ...)

Per ridurre la digitazione, la maggior parte degli editor ha macro, anche frammenti di codice intelligenti.


"No, non puoi utilizzare le macro per salvare la digitazione ." - Ottimo lavoro, non ascolterò il tuo ordine qui! Se ho una pila di oggetti che devo dichiarare / definire / mappare / switch/ etc, puoi scommettere che userò le macro per evitare di impazzire - e aumentare la leggibilità. Usare le macro per salvare la digitazione su parole chiave, controllare il flusso e cose simili è stupido, ma dire che nessuno può usarle per salvare i tasti premuti in contesti validi è ugualmente essenziale.
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.