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

17

C'è qualche idea se utilizzare #define per definire linee complete di codice per semplificare la codifica è una buona o cattiva pratica di programmazione? Ad esempio, se avessi bisogno di stampare un po 'di parole insieme, mi arrabbieresti a digitare

<< " " <<

Per inserire uno spazio tra le parole in un'istruzione cout. Potrei fare solo

#define pSpace << " " <<

e scrivi

cout << word1 pSpace word2 << endl;

Per me questo non aggiunge né sottrae alla chiarezza del codice e rende la digitazione leggermente più semplice. Ci sono altri casi in cui posso pensare a dove scrivere sarà molto più semplice, solitamente per il debug.

Qualche idea su questo?

EDIT: Grazie per tutte le grandi risposte! Questa domanda mi è venuta dopo aver ripetuto molte digitazioni ripetute, ma non avrei mai pensato che ci sarebbero stati altri, meno confusi macro da usare. Per coloro che non vogliono leggere tutte le risposte, l'alternativa migliore è usare i macro del tuo IDE per ridurre la digitazione ripetitiva.

    
posta gsingh2011 26.10.2011 - 07:39
fonte

13 risposte

110

Scrivere il codice è facile. Leggere il 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.

    
risposta data 26.10.2011 - 08:18
fonte
28

Personalmente, lo detesto. C'è una serie di motivi per cui scoraggiamo le persone da questa tecnica:

  1. In fase di compilazione le modifiche effettive al codice potrebbero essere significative. Il prossimo arriva e include anche una parentesi di chiusura nella sua #define o una chiamata di funzione. Ciò che è stato scritto in un determinato punto del codice è lontano da ciò che ci sarà dopo la pre-elaborazione.

  2. È illeggibile. Potrebbe essere chiaro per te .. per ora ... se è solo questa una definizione. Se diventa un'abitudine, ti ritroverai presto con dozzine di #define e inizierai a perdere la testa. Ma, peggio di tutto, nessun altro sarà in grado di capire cosa significhi esattamente word1 pSpace word2 (senza cercare il #define).

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

  4. La manutenzione diventa molto più difficile. Hai tutti quelli che definiscono oltre ai soliti problemi che la manutenzione porta con sé. Inoltre, con il punto precedente, il supporto degli strumenti per il refactoring può essere influenzato negativamente.

risposta data 26.10.2011 - 08:04
fonte
16

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

La mia regola principale durante la scrittura del codice è renderla facilmente leggibile. La logica alla base di questo è semplicemente che il codice viene letto un ordine di grandezza più volte che è stato scritto. In quanto tale, il tempo in cui ti perdi lo scrivi con attenzione, ordinato, presentato correttamente è in effetti investito nel fare ulteriori letture e nella comprensione molto più velocemente.

In questo modo, il #define che usi semplicemente rompe il solito modo di% alternato<< e altro-roba . Rompe la regola della sorpresa minima, e IMHO non è una buona cosa.

    
risposta data 26.10.2011 - 08:03
fonte
14

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

Detto questo, darò esempi reali di ciò che considero una buona integrazione di macro.

Il primo esempio appare in CppUnit , che è un framework di test unitario. Come qualsiasi altro framework di test standard, crei una classe di test e quindi devi in qualche modo specificare quali metodi devono 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 metodo testSubtraction , è ovvio che cosa devi fare per averlo incluso nell'esecuzione del test.

Questi blocchi macro 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 preferisci leggere e mantenere?

Un altro esempio è nel framework Microsoft MFC, dove 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 "Good Macros" dal tipo orribile e malvagio?

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

  • Hanno un uso formale estremamente rigoroso. In entrambi questi esempi i blocchi macro vengono annunciati con le macro iniziali e finali e le macro intermedie appariranno sempre all'interno di questi blocchi. Hai un normale C ++, 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.

risposta data 26.10.2011 - 12:19
fonte
5

Sarà, con tutti i mezzi, meglio se si sintonizza l'IDE / editor di testo preferito per l'inserimento di frammenti di codice che si trovano stancanti a ridigitare ancora e ancora. E meglio è il termine "educato" per il confronto. In realtà, non riesco a pensare a casi simili quando la pre-elaborazione batte i macro dell'editor. Beh, potrebbe essere uno - quando per ragioni misteriose e infelici si utilizzano costantemente diversi set di strumenti per la codifica. Ma non è una giustificazione:)

Può anche essere una soluzione migliore per scenari più complessi, quando la preelaborazione del testo può rendere la cosa molto più illeggibile e complicata (pensate all'input parametrizzato).

    
risposta data 26.10.2011 - 10:27
fonte
4

Gli altri hanno già spiegato perché non dovresti farlo. Il tuo esempio ovviamente non merita di essere implementato con una macro. Tuttavia, esiste una vasta gamma di casi in cui hanno per usare le macro per motivi di leggibilità.

Un noto esempio di una saggia applicazione di tale tecnica è il progetto Clang : vedi come vengono utilizzati i file .def . Con macro e #include puoi fornire una definizione unica, a volte del tutto dichiarativa per una raccolta di cose simili che verranno srotolate in dichiarazioni di tipi, dichiarazioni case laddove opportuno, inizializzatori di default, ecc. Aumenta significativamente la manutenibilità: hai vinto ' t dimenticare di aggiungere sempre nuove dichiarazioni case ovunque quando, ad esempio, hai aggiunto un nuovo enum .

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

    
risposta data 26.10.2011 - 10:32
fonte
3

Non è mai appropriato usare #definiti del genere. Nel tuo caso, puoi fare questo:

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;
}
    
risposta data 26.10.2011 - 13:43
fonte
2

No.

Per le macro destinate a essere utilizzate nel codice, una buona guida per verificare l'appropriatezza è circondare la sua espansione con parentesi (per espressioni) o parentesi (per il codice) e vedere se sarà ancora compilata:

// 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 ottenere una serie di regole più libere, purché non alterino il contesto circostante (ad esempio, il passaggio da public a private ).

    
risposta data 26.10.2011 - 13:44
fonte
1

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

Non è necessario e confuso in un programma C ++.

Ci sono molti modi per evitare la digitazione ripetitiva del codice in C ++. Dall'uso dei servizi forniti dal tuo IDE (anche con vi un semplice " %s/ pspace /<< " " <</g " farebbe risparmiare tanto scrivere e produrre codice standard leggibile). È possibile definire metodi privati per implementare questo o per casi più complessi. Il modello C ++ sarebbe più pulito e semplice.

    
risposta data 26.10.2011 - 08:44
fonte
1

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

lineWithSpaces(word1, word2, word3, ..., wordn) è semplice e ti consente di salvare pSpaces ancora e ancora.

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

In generale, ci sono pochi casi in cui l'uso di una macro è significativamente più breve senza introdurre l'offuscamento e, per lo più, esiste una soluzione sufficientemente breve che utilizza le funzionalità linguistiche effettive (i macro sono più di una semplice sostituzione di stringa).

    
risposta data 26.10.2011 - 22:55
fonte
0

Is there any view on whether using the #define to define full lines of code for simplifying coding is good or bad programming practice?

Sì, è molto brutto. Ho persino visto persone fare questo:

#define R return

per risparmiare digitazione (cosa stai cercando di ottenere).

Tale codice appartiene solo in luoghi come questo .

    
risposta data 26.10.2011 - 22:23
fonte
-1

Le macro sono evil e dovrebbero essere utilizzate solo quando realmente dovere. Esistono alcuni casi in cui le macro sono applicabili (principalmente il debug). Ma in C ++ nella maggior parte dei casi puoi invece utilizzare le funzioni inline .

    
risposta data 26.10.2011 - 15:45
fonte
-2

No, non ti è permesso usare le macro per salvare digitando .

Tuttavia, è consentito, anche richiesto di usarli per separare parti di codice non modificabili da quelle modificate e ridurre la ridondanza. Per quest'ultimo è necessario pensare alle alternative e scegliere la macro solo se gli strumenti migliori non funzionano. (Per pratica la macro è abbastanza alla fine della riga, quindi averla implica l'ultima risorsa ...)

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

    
risposta data 07.06.2013 - 19:32
fonte

Leggi altre domande sui tag