Est-ce une utilisation appropriée de #define pour faciliter la saisie de code répété?

17

Existe-t-il une opinion quant à savoir si l'utilisation de #define pour définir des lignes complètes de code pour simplifier le codage est une bonne ou une mauvaise pratique de programmation? Par exemple, si j'avais besoin d'imprimer un tas de mots ensemble, je serais ennuyé de taper

<< " " <<

Pour insérer un espace entre les mots dans une instruction cout. Je pourrais juste faire

#define pSpace << " " <<

et tapez

cout << word1 pSpace word2 << endl;

Pour moi, cela n'ajoute ni ne soustrait à la clarté du code et rend la saisie légèrement plus facile. Il y a d'autres cas où je peux penser où taper sera beaucoup plus facile, généralement pour le débogage.

Des réflexions à ce sujet?

EDIT: Merci pour toutes les bonnes réponses! Cette question m'est venue juste après avoir fait beaucoup de frappe répétitive, mais je n'ai jamais pensé qu'il y aurait d'autres macros moins déroutantes à utiliser. Pour ceux qui ne veulent pas lire toutes les réponses, la meilleure alternative est d'utiliser les macros de votre IDE pour réduire la frappe répétitive.

gsingh2011
la source
74
C'est clair pour vous parce que vous l'avez inventé. Pour tout le monde, c'est juste obscurci. Au début, cela ressemble à une erreur de syntaxe. Quand il compile, je pense ce que le diable et puis trouve que vous avez une macro qui n'est pas en toutes lettres. À mon avis, cela rend le code horrible à maintenir, je le rejetterais certainement s'il venait à être révisé et je ne pense pas que vous en trouverez beaucoup qui l'accepteraient. Et vous enregistrez 3 caractères !!!!!!!!!
Martin York
11
Lorsque vous ne pouvez pas raisonnablement prendre en compte la répétitivité à l'aide de fonctions ou autre, la meilleure approche est d'apprendre ce que votre éditeur ou IDE peut faire pour vous aider. Les macros d'éditeur de texte ou les raccourcis clavier "d'extrait" peuvent vous éviter de taper autant sans nuire à la lisibilité.
Steve314
2
J'ai déjà fait cela (avec de plus gros morceaux de passe-partout), mais ma pratique consistait à écrire le code, puis à exécuter le préprocesseur et à remplacer le fichier d'origine par la sortie du préprocesseur. M'a sauvé la frappe, m'a épargné (et d'autres) les tracas d'entretien.
TMN
9
Vous avez enregistré 3 personnages et les avez échangés contre des déclarations déroutantes. Très bon exemple de mauvaise macro, à mon humble avis: o)
MaR
7
De nombreux éditeurs ont une fonctionnalité avancée pour exactement ce scénario, il est appelé "Copier et coller"
Chris Burt-Brown

Réponses:

111

L'écriture de code est simple. La lecture du code est difficile.

Vous écrivez du code une fois. Il vit depuis des années, les gens le lisent cent fois.

Optimiser le code pour la lecture, pas pour l'écriture.

tdammers
la source
11
Je suis d'accord à 100%. (En fait, j'allais écrire cette réponse moi-même.) Le code est écrit une fois, mais peut être lu des dizaines, des centaines, voire des milliers de fois, peut-être par dizaines, par centaines, voire par des milliers de développeurs. Le temps qu'il faut pour écrire du code est totalement hors de propos, la seule chose qui compte est le temps de le lire et de le comprendre.
sbi
2
Le préprocesseur peut et doit être utilisé pour optimiser le code de lecture et de maintenance.
SK-logic
2
Et même si c'est juste vous qui lisez le code dans un ou deux ans: vous oublierez ces choses vous-même en faisant d'autres choses entre les deux.
johannes
2
"Codez toujours comme si le gars qui finit par maintenir votre code sera un psychopathe violent qui sait où vous vivez." - (Martin Golding)
Dylan Yaga
@Dylan - sinon, après quelques mois maintenant que le code, il va vous trouver - (Me)
Steve314
28

Personnellement, je déteste ça. Il y a plusieurs raisons pour lesquelles je décourage les gens de cette technique:

  1. Au moment de la compilation, vos modifications de code réelles peuvent être importantes. Le gars suivant arrive et inclut même une parenthèse fermante dans son #define ou un appel de fonction. Ce qui est écrit à un certain point de code est loin de ce qu'il y aura après le prétraitement.

  2. C'est illisible. Il peut être clair pour vous .. pour l'instant .. si c'est juste cette seule définition. Si cela devient une habitude, vous vous retrouverez bientôt avec des dizaines de # définitions et vous commencerez à perdre la trace vous-même. Mais le pire de tout, personne d'autre ne pourra comprendre ce word1 pSpace word2que signifie exactement (sans rechercher la #define).

  3. Cela peut devenir un problème pour les outils externes. Disons que vous vous retrouvez en quelque sorte avec un #define qui comprend un crochet de fermeture, mais pas de crochet d'ouverture. Tout peut bien fonctionner, mais les éditeurs et autres outils peuvent voir quelque chose comme function(withSomeCoolDefine;plutôt particulier (c'est-à-dire qu'ils rapporteront des erreurs et ainsi de suite). (Exemple similaire: un appel de fonction à l'intérieur d'un define - vos outils d'analyse pourront-ils trouver cet appel?)

  4. L'entretien devient beaucoup plus difficile. Vous avez tous ceux à définir en plus des problèmes habituels que la maintenance entraîne. En plus du point ci-dessus, la prise en charge des outils de refactorisation peut également être affectée négativement.

Franc
la source
4
Je me suis finalement banni de presque toutes les macros à cause des tracas d'essayer de les gérer directement dans Doxygen. Cela fait maintenant quelques années, et dans l'ensemble, je pense que la lisibilité s'est beaucoup améliorée - que j'utilise Doxygen ou non.
Steve314
16

Ma pensée principale à ce sujet est que je n'utilise jamais "rendre la frappe plus facile" en règle générale lors de l'écriture de code.

Ma règle principale lors de l'écriture de code est de le rendre facilement lisible. La raison derrière cela est simplement que le code est lu un ordre de grandeur plus de fois qu'il est écrit. En tant que tel, le temps que vous perdez à l' écrire avec soin, de manière ordonnée et correctement disposée est en fait investi dans la poursuite de la lecture et la compréhension beaucoup plus rapidement.

En tant que tel, le #define que vous utilisez rompt simplement la manière habituelle d'alternance <<et d' autres choses . Il enfreint la règle de la moindre surprise et n'est pas à mon humble avis une bonne chose.

Didier Trosset
la source
1
+1: "Le code est lu un ordre de grandeur plus de fois qu'il est écrit" !!!!
Giorgio
14

Cette question donne un exemple clair de la façon dont vous pouvez mal utiliser les macros. Pour voir d'autres exemples (et se divertir) voir cette question .

Cela dit, je vais donner des exemples concrets de ce que je considère comme une bonne incorporation des macros.

Le premier exemple apparaît dans CppUnit , qui est un framework de tests unitaires. Comme tout autre framework de test standard, vous créez une classe de test, puis vous devez en quelque sorte spécifier les méthodes à exécuter dans le cadre du 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();
}

Comme vous pouvez le voir, la classe a un bloc de macros comme premier élément. Si j'ai ajouté une nouvelle méthode testSubtraction, il est évident ce que vous devez faire pour l'inclure dans le test.

Ces blocs de macro s'étendent à quelque chose comme ceci:

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

Lequel préférez-vous lire et conserver?

Un autre exemple est dans le cadre Microsoft MFC, où vous mappez des fonctions à des messages:

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

Alors, quelles sont les choses qui distinguent les "bonnes macros" des horribles sortes de mal?

  • Ils effectuent une tâche qui ne peut être simplifiée autrement. L'écriture d'une macro pour déterminer un maximum entre deux éléments est incorrecte, car vous pouvez obtenir la même chose à l'aide d'une méthode de modèle. Mais il existe des tâches complexes (par exemple, mappage des codes de message aux fonctions membres) que le langage C ++ ne gère tout simplement pas avec élégance.

  • Ils ont un usage formel extrêmement strict. Dans ces deux exemples, les blocs de macro sont annoncés en démarrant et en terminant les macros, et les macros intermédiaires n'apparaissent que dans ces blocs. Vous avez un C ++ normal, vous vous excusez brièvement avec un bloc de macros, puis vous revenez à la normale. Dans les exemples de "macros diaboliques", les macros sont dispersées dans le code et le malheureux lecteur n'a aucun moyen de savoir quand les règles C ++ s'appliquent et quand elles ne le sont pas.

Andrew Shepherd
la source
5

Il vaudra certainement mieux que vous régliez votre éditeur IDE / texte préféré pour insérer des extraits de code que vous trouvez fastidieux à retaper encore et encore. Et mieux est le terme "poli" pour comparaison. En fait, je ne peux penser à aucun cas similaire lorsque le prétraitement bat les macros de l'éditeur. Eh bien, peut-être un - lorsque, pour des raisons mystérieuses et malheureuses, vous utilisez constamment différents outils de codage. Mais ce n'est pas une justification :)

Cela peut également être une meilleure solution pour des scénarios plus complexes, lorsque le prétraitement de texte peut faire des choses beaucoup plus illisibles et compliquées (pensez à l'entrée paramétrée).

shabunc
la source
2
+1. En effet: laissez l'éditeur faire le travail pour vous. Vous obtiendrez le meilleur des deux mondes si, par exemple, vous faites une abréviation de << " " <<.
unperson325680
-1 pour "Cela peut aussi être une meilleure solution pour des scénarios plus complexes, lorsque le prétraitement de texte peut faire quelque chose de beaucoup plus illisible et compliqué (pensez à la saisie paramétrisée)" - Si c'est aussi complexe, faites-en une méthode, même alors, faites une méthode pour cela. par exemple, ce mal que j'ai récemment trouvé dans le code ..... #define printError (x) {put (x); return x}
mattnz
@mattnz, je voulais dire des constructions de boucles, des constructions if / else, des modèles pour la création de comparateurs et ainsi de suite - ce genre de choses. Dans les IDE, ce type d'entrée paramétrée vous aide non seulement à taper rapidement quelques lignes de code, mais aussi à parcourir rapidement les paramètres. Personne n'essaie de rivaliser avec les méthodes. méthode est méthode)))
shabunc
4

Les autres ont déjà expliqué pourquoi vous ne devriez pas le faire. Votre exemple ne mérite évidemment pas d'être implémenté avec une macro. Mais, il existe un large éventail de cas où vous avez utiliser des macros pour des raisons de lisibilité.

Le projet Clang est un exemple notoire d'application judicieuse d'une telle technique : voyez comment les .deffichiers y sont utilisés. Avec les macros et #includevous pouvez fournir une définition unique, parfois entièrement déclarative pour une collection de choses similaires qui seront déroulées dans les déclarations de types, les casedéclarations le cas échéant, les initialiseurs par défaut, etc. Cela augmente considérablement la maintenabilité: vous n'oublierez jamais d'ajouter de nouveaux casepar exemple, partout où vous en avez ajouté une nouvelle enum.

Ainsi, comme avec tout autre outil puissant, vous devez utiliser le préprocesseur C avec précaution. Il n'y a pas de règles génériques dans l'art de la programmation, comme "vous ne devriez jamais utiliser ceci" ou "vous devez toujours utiliser cela". Toutes les règles ne sont que des directives.

SK-logic
la source
3

Il n'est jamais approprié d'utiliser #defines comme ça. Dans votre cas, vous pouvez faire ceci:

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
la source
2

Non.

Pour les macros destinées à être utilisées dans le code, une bonne ligne directrice pour tester la pertinence est d'entourer son expansion de parenthèses (pour les expressions) ou d'accolades (pour le code) et de voir si elle sera toujours compilée:

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

Les macros utilisées dans les déclarations (comme l'exemple dans la réponse d'Andrew Shepherd) peuvent s'en tirer avec un ensemble de règles plus lâches tant qu'elles ne bouleversent pas le contexte environnant (par exemple, basculer entre publicet private).

Blrfl
la source
1

C'est une chose raisonnablement valable à faire dans un programme "C" pur.

C'est inutile et déroutant dans un programme C ++.

Il existe de nombreuses façons d'éviter la saisie répétitive de code en C ++. En utilisant les fonctionnalités fournies par votre IDE (même avec vi, un simple " %s/ pspace /<< " " <</g" économiserait autant de frappe et produirait toujours du code lisible standard). Vous pouvez définir une méthode privée pour l'implémenter ou pour des cas plus complexes, le modèle C ++ serait plus propre et plus simple.

James Anderson
la source
2
Non, ce n'est pas une chose raisonnablement valable à faire en C. pur. Des valeurs uniques ou des expressions indépendantes complètes qui ne dépendent que des paramètres de macro, oui, et pour ces derniers, une fonction peut même être un meilleur choix. Une construction à moitié cuite comme dans l'exemple, pas du tout.
Sécurisé
@secure - Je suis d'accord que ce n'est pas une bonne idée dans le cas de l'exemple donné. Mais étant donné le manque de modèles, etc., il existe des utilisations valides pour les macros "#DEFINE" dans C.
James Anderson
1

En C ++, cela peut être résolu par une surcharge de l'opérateur. Ou même quelque chose d'aussi simple qu'une fonction variadique:

lineWithSpaces(word1, word2, word3, ..., wordn)est à la fois simple et vous évite de taper pSpacesencore et encore.

Donc, même si dans votre cas cela ne semble pas être un gros problème, il existe une solution plus simple et plus robuste.

En général, il y a peu de cas où l'utilisation d'une macro est beaucoup plus courte sans introduire d'obscurcissement, et la plupart du temps, il existe une solution suffisamment courte utilisant les fonctionnalités du langage réel (les macros étant davantage une simple substitution de chaîne).

back2dos
la source
0

Existe-t-il une opinion quant à savoir si l'utilisation de #define pour définir des lignes complètes de code pour simplifier le codage est une bonne ou une mauvaise pratique de programmation?

Oui, c'est très mauvais. J'ai même vu des gens faire ça:

#define R return

pour enregistrer la saisie (ce que vous essayez de réaliser).

Un tel code n'appartient qu'à des endroits comme celui-ci .

BЈовић
la source
-1

Les macros sont mauvaises et ne doivent être utilisées que lorsque vous en avez vraiment besoin. Il existe quelques cas où les macros sont applicables (principalement le débogage). Mais en C ++ dans la plupart des cas, vous pouvez utiliser des fonctions en ligne à la place.

sakisk
la source
2
Il n'y a rien de intrinsèquement mauvais dans une technique de programmation. Tous les outils et méthodes possibles peuvent être utilisés, tant que vous savez ce que vous faites. Cela s'applique aux infâmes goto, à tous les systèmes de macro possibles, etc.
SK-logic
1
C'est exactement la définition du mal dans ce cas: "quelque chose que vous devez éviter la plupart du temps, mais pas quelque chose que vous devez éviter tout le temps". Il est expliqué sur le lien que le mal pointe.
sakisk
2
Je pense qu'il est contre-productif de qualifier quoi que ce soit de "mal", "potentiellement nuisible" ou même "suspect". Je n'aime pas la notion de "mauvaise pratique" et "odeur de code". Chaque développeur doit décider dans chaque cas spécifique, quelle pratique est nuisible. L'étiquetage est une pratique néfaste - les gens ont tendance à ne pas penser plus loin si quelque chose a déjà été étiqueté par les autres.
SK-logic
-2

Non, vous n'êtes pas autorisé à utiliser des macros pour enregistrer la saisie .

Cependant, vous êtes autorisé, voire obligé à les utiliser pour séparer la partie non changeante du code des éléments changeants et réduire la redondance. Pour ce dernier, vous devez penser à des alternatives et choisir une macro uniquement si de meilleurs outils ne fonctionnent pas. (Pour la pratique, la macro est tout à fait en fin de ligne, donc l'avoir implique en dernier recours ...)

Pour réduire la saisie, la plupart des éditeurs ont des macros, même des extraits de code intelligents.

Balog Pal
la source
"Non, vous n'êtes pas autorisé à utiliser des macros pour enregistrer la saisie ." - Bon travail je ne vais pas écouter ta commande ici! Si j'ai une pile d'objets que je dois déclarer / définir / mapper / switch/ etc, vous pouvez parier que je vais utiliser des macros pour éviter de devenir fou - et augmenter la lisibilité. L'utilisation de macros pour enregistrer la saisie sur des mots clés, contrôler le flux, etc. est stupide - mais dire que personne ne peut les utiliser pour enregistrer des frappes dans des contextes valides est tout aussi stupide.
underscore_d