É um uso apropriado de #define para facilitar a digitação de códigos repetidos?

17

Existe alguma opinião sobre se usar o #define para definir linhas completas de código para simplificar a codificação é uma boa ou má prática de programação? Por exemplo, se eu precisasse imprimir um monte de palavras, ficaria irritado digitando

<< " " <<

Para inserir um espaço entre as palavras em uma instrução cout. Eu poderia fazer

#define pSpace << " " <<

e tipo

cout << word1 pSpace word2 << endl;

Para mim, isso não adiciona ou subtrai a clareza do código e facilita a digitação. Há outros casos em que consigo pensar em que a digitação será muito mais fácil, geralmente para depuração.

Alguma idéia sobre isso?

EDIT: Obrigado por todas as ótimas respostas! Essa pergunta surgiu depois de muita digitação repetitiva, mas nunca pensei que houvesse outras macros menos confusas para usar. Para aqueles que não desejam ler todas as respostas, a melhor alternativa é usar as macros do seu IDE para reduzir a digitação repetitiva.

gsingh2011
fonte
74
Está claro para você porque você a inventou. Para todos os outros, apenas ofuscou. A princípio, parece um erro de sintaxe. Quando compila eu pensaria o que diabos e depois achava que você tem uma macro que não está em todas as letras maiúsculas. Na minha opinião, isso torna o código horrível de manter. Eu definitivamente o rejeitaria se viesse para a revisão do código e não espero que você encontre muitos que o aceitem. E você está salvando 3 caracteres !!!!!!!!!
Martin York
11
Onde você não pode razoavelmente levar em consideração a repetitividade usando funções ou qualquer outra coisa, a melhor abordagem é aprender o que seu editor ou IDE pode fazer para ajudá-lo. Macros do editor de texto ou teclas de atalho "snippet" podem evitar que você digite muito sem prejudicar a legibilidade.
Steve314
2
Eu fiz isso antes (com pedaços maiores de clichê), mas minha prática era escrever o código, executar o pré-processador e substituir o arquivo original pela saída do pré-processador. Poupou-me a digitação, poupou-me (e outros) do aborrecimento da manutenção.
TMN
9
Você salvou 3 caracteres e os trocou por declarações confusas. Muito bom exemplo de macro incorreta, imho: o)
MaR
7
Muitos editores têm um recurso avançado para exatamente este cenário, é chamado de "copiar e colar"
Chris Burt-Brown

Respostas:

111

Escrever código é fácil. Ler código é difícil.

Você escreve o código uma vez. Ele vive há anos, as pessoas leem centenas de vezes.

Otimize o código para leitura, não para escrita.

tdammers
fonte
11
Eu concordo 100%. (De fato, eu mesmo escreveria essa resposta.) O código é escrito uma vez, mas pode ser lido dezenas, centenas ou mesmo milhares de vezes, talvez dezenas, centenas ou mesmo milhares de desenvolvedores. O tempo que leva para escrever o código é totalmente irrelevante, a única coisa que importa é o tempo para lê-lo e entendê-lo.
S26
2
O pré-processador pode e deve ser usado para otimizar o código de leitura e manutenção.
SK-logic
2
E mesmo que seja apenas você lendo o código em um ou dois anos: você mesmo esquecerá essas coisas enquanto faz outras.
Johannes
2
"Sempre codifique como se o cara que acabasse mantendo o seu código fosse um psicopata violento que sabe onde você mora." - (Martin Golding)
Dylan Yaga 26/10
@Dylan - caso contrário, depois de alguns meses mantendo esse código, ele o encontrará - (Eu)
Steve314 27/10/11
28

Pessoalmente, eu detesto isso. Há várias razões pelas quais desencorajo as pessoas dessa técnica:

  1. No tempo de compilação, as alterações reais do código podem ser significativas. O próximo cara aparece e até inclui um colchete em seu #define ou uma chamada de função. O que está escrito em um determinado ponto do código está longe do que estará disponível após o pré-processamento.

  2. É ilegível. Pode ficar claro para você .. por enquanto .. se é apenas essa definição. Se isso se tornar um hábito, você acabará em breve com dezenas de #defines e começará a perder o controle. Mas o pior de tudo é que ninguém mais será capaz de entender o que word1 pSpace word2exatamente significa (sem consultar o #define).

  3. Pode se tornar um problema para ferramentas externas. Digamos que você de alguma forma termine com um #define que inclui um colchete de fechamento, mas sem colchete de abertura. Tudo pode funcionar bem, mas editores e outras ferramentas podem parecer algo function(withSomeCoolDefine;bastante peculiar (ou seja, eles relatam erros e outros enfeites). (Exemplo semelhante: uma chamada de função dentro de um define - suas ferramentas de análise poderão encontrar essa chamada?)

  4. A manutenção se torna muito mais difícil. Você define tudo isso além dos problemas usuais que a manutenção traz consigo. Além do ponto acima, o suporte da ferramenta para refatoração também pode ser afetado negativamente.

Frank
fonte
4
Finalmente me bani de quase todas as macros por causa dos aborrecimentos de tentar lidar com elas diretamente no Doxygen. Já faz alguns anos e, no geral, acho que a legibilidade melhorou bastante - independentemente de eu estar usando Doxygen ou não.
Steve314
16

Meu principal pensamento sobre isso é que eu nunca uso "facilitar a digitação" como regra ao escrever código.

Minha regra principal ao escrever um código é torná-lo facilmente legível. A lógica por trás disso é simplesmente que o código é lido uma ordem de magnitude mais vezes do que é escrito. Dessa forma, o tempo que você perde escrevendo-o com cuidado, em ordem e com disposição correta, é de fato investido em fazer mais leituras e entender muito mais rapidamente.

Dessa forma, o #define que você usa simplesmente quebra a maneira usual de alternância <<e outras coisas . Ele quebra a regra da menor surpresa e não é uma coisa boa.

Didier Trosset
fonte
1
+1: "O código é lido uma ordem de grandeza mais vezes do que está escrito" !!!!
Giorgio
14

Esta pergunta fornece um exemplo claro de como você pode usar mal as macros. Para ver outros exemplos (e se divertir), veja esta pergunta .

Dito isso, darei exemplos do mundo real do que considero uma boa incorporação de macros.

O primeiro exemplo aparece no CppUnit , que é uma estrutura de teste de unidade. Como qualquer outra estrutura de teste padrão, você cria uma classe de teste e precisa especificar, de alguma forma, quais métodos devem ser executados como parte do teste.

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

Como você pode ver, a classe possui um bloco de macros como seu primeiro elemento. Se eu adicionei um novo método testSubtraction, é óbvio o que você precisa fazer para incluí-lo no teste.

Esses blocos de macro se expandem para algo assim:

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

Qual você prefere ler e manter?

Outro exemplo está na estrutura do Microsoft MFC, onde você mapeia funções para mensagens:

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

Então, quais são as coisas que distinguem "Boas Macros" do tipo maligno horrível?

  • Eles executam uma tarefa que não pode ser simplificada de nenhuma outra maneira. Escrever uma macro para determinar o máximo entre dois elementos está errado, porque você pode obter o mesmo usando um método de modelo. Mas existem algumas tarefas complexas (por exemplo, mapear códigos de mensagens para funções-membro) com as quais a linguagem C ++ simplesmente não lida com elegância.

  • Eles têm um uso formal extremamente rigoroso. Nos dois exemplos, os blocos de macro são anunciados iniciando e finalizando macros, e as macros entre elas só aparecerão dentro desses blocos. Você tem C ++ normal, se desculpa brevemente com um bloco de macros e volta ao normal novamente. Nos exemplos de "macros malignas", as macros estão espalhadas por todo o código e o infeliz leitor não tem como saber quando as regras do C ++ se aplicam e quando não.

Andrew Shepherd
fonte
5

Será, de todo modo, melhor se você ajustar seu editor de texto / IDE favorito para inserir trechos de código que você achar cansativo redigitar novamente. E melhor é o termo "educado" para comparação. Na verdade, não consigo pensar em nenhum caso semelhante ao pré-processar as macros do editor. Bem, pode ser um - quando, por algumas razões misteriosas e infelizes, você estiver constantemente usando diferentes conjuntos de ferramentas para codificação. Mas não é uma justificativa :)

Também pode ser uma solução melhor para cenários mais complexos, quando o pré-processamento de texto pode fazer coisas muito mais ilegíveis e complicadas (pense em entradas parametrizadas).

shabunc
fonte
2
+1. De fato: deixe o editor fazer o trabalho por você. Você obterá o melhor dos dois mundos se, por exemplo, fizer uma abreviação de << " " <<.
usar o seguinte
-1 para "Também pode ser uma solução melhor para cenários mais complexos, quando o pré-processamento de texto pode fazer coisas muito mais ilegíveis e complicadas (pense em entradas parametrizadas)" - Se é tão complexo, crie um método para isso, mesmo assim, faça um método para isso. eg Esse mal Eu encontrei recentemente em código ..... #define PrintError (x) {puts (x); retorno x}
mattnz
@mattnz, eu quis dizer construções de loop, construções if / else, modelos para criação de comparadores e assim por diante - esse tipo de coisa. Nos IDEs, esse tipo de entrada parametrizada ajuda você não apenas a digitar rapidamente poucas linhas de código, mas também a iterar rapidamente através de parâmetros. Ninguém tenta competir com os métodos. método é método)))
shabunc 27/10
4

Os outros já explicaram por que você não deve fazê-lo. Seu exemplo obviamente não merece ser implementado com uma macro. Mas, há uma grande variedade de casos em que você tem usar macros para facilitar a leitura.

Um exemplo notório de uma aplicação inteligente dessa técnica é o projeto Clang : veja como os .defarquivos são usados ​​lá. Com as macros, #includeé possível fornecer uma definição única, às vezes inteiramente declarativa, para uma coleção de coisas semelhantes que serão desenroladas em declarações de tipos, caseinstruções sempre que apropriado, inicializadores padrão, etc. Aumenta significativamente a capacidade de manutenção: você nunca esquecerá de adicionar novos caseinstruções em todos os lugares quando você adicionou uma nova enum, por exemplo.

Portanto, como em qualquer outra ferramenta poderosa, você deve usar o pré-processador C com cuidado. Não existem regras genéricas na arte da programação, como "você nunca deve usar isso" ou "você sempre deve usar isso". Todas as regras nada mais são do que diretrizes.

SK-logic
fonte
3

Nunca é apropriado usar #defines assim. No seu caso, você pode fazer isso:

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
fonte
2

Não.

Para macros destinadas a serem usadas em código, uma boa diretriz para testar a adequação é cercar sua expansão entre parênteses (para expressões) ou chaves (para código) e ver se ainda será compilado:

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

As macros usadas nas declarações (como o exemplo na resposta de Andrew Shepherd) podem se livrar de um conjunto de regras mais frouxas, desde que não perturbem o contexto circundante (por exemplo, alternando entre publice private).

Blrfl
fonte
1

É uma coisa razoavelmente válida para se fazer em um programa "C" puro.

É desnecessário e confuso em um programa C ++.

Existem várias maneiras de evitar a digitação repetitiva de código em C ++. Ao usar os recursos fornecidos pelo seu IDE (mesmo com o vi, um simples " %s/ pspace /<< " " <</g" economizaria tanta digitação e ainda produziria código legível padrão). Você pode definir métodos particulares para implementar esse ou, para casos mais complexos, o modelo C ++ seria mais limpo e simples.

James Anderson
fonte
2
Não, não é uma coisa razoavelmente válida para fazer em C. puro. Valores únicos ou expressões independentes completas que dependem apenas dos parâmetros macro, sim, e para o último uma função pode até ser uma escolha melhor. Tal construção meio cozida como no exemplo, de jeito nenhum.
Secure
@secure - Concordo que não é uma boa ideia no caso do exemplo dado. Mas, dada a falta de modelos etc. existem usos válidos para macros "#define" em C.
James Anderson
1

Em C ++, isso pode ser resolvido com sobrecarga do operador. Ou mesmo algo tão simples quanto uma função variável:

lineWithSpaces(word1, word2, word3, ..., wordn)é simples e economiza a digitação pSpacesrepetidamente.

Portanto, embora no seu caso possa não parecer grande coisa, há uma solução mais simples e robusta.

Em geral, há poucos casos em que o uso de uma macro é significativamente mais curto sem a ofuscação e, principalmente, há uma solução suficientemente curta usando os recursos reais da linguagem (as macros são mais uma mera substituição de cadeia de caracteres).

back2dos
fonte
0

Existe alguma opinião sobre se usar o #define para definir linhas completas de código para simplificar a codificação é uma boa ou má prática de programação?

Sim, é muito ruim. Eu já vi pessoas fazendo isso:

#define R return

para salvar a digitação (o que você está tentando alcançar).

Esse código pertence apenas a lugares como este .

BЈовић
fonte
-1

Macros são más e devem ser usadas somente quando você realmente precisa. Existem alguns casos em que as macros são aplicáveis ​​(principalmente depuração). Mas em C ++, na maioria dos casos, você pode usar funções embutidas .

sakisk
fonte
2
Não há nada intrinsecamente mau em nenhuma técnica de programação. Todas as ferramentas e métodos possíveis podem ser usados, desde que você saiba o que está fazendo. Isso se aplica ao infame goto, a todos os possíveis sistemas macro, etc. #
SK-logic
1
Essa é exatamente a definição de mal neste caso: "algo que você deve evitar na maioria das vezes, mas não algo que deve evitar o tempo todo". É explicado no link que o mal está apontando.
Sakisk 26/10/11
2
Eu acredito que é contraproducente marcar algo como "mau", "potencialmente prejudicial" ou até "suspeito". Não gosto da noção de "más práticas" e "cheiro de código". Todo desenvolvedor precisa decidir em cada caso específico, qual prática é prejudicial. A rotulagem é uma prática prejudicial - as pessoas tendem a não pensar mais se algo já foi rotulado pelos outros.
SK-logic
-2

Não, você não tem permissão para usar macros para salvar a digitação .

No entanto, você tem permissão, mesmo que seja necessário usá-los para separar parte não alterável do código das alterações e reduzir a redundância. Para o último, você deve pensar em alternativas e escolher macro somente se ferramentas melhores não funcionarem. (Para a prática, a macro está no final da linha, portanto, tê-la implica em último recurso ...)

Para reduzir a digitação, a maioria dos editores possui macros, até mesmo trechos de código inteligentes.

Balog Pal
fonte
"Não, você não tem permissão para usar macros para salvar a digitação ." - Bom trabalho, não vou ouvir seu pedido aqui! Se eu tenho uma pilha de objetos que preciso declarar / definir / mapear / switch/ etc, você pode apostar que vou usar macros para evitar enlouquecer - e aumentar a legibilidade. Usar macros para economizar digitação em palavras-chave, controlar o fluxo e coisas do gênero é estúpido - mas dizer que ninguém pode usá-las para salvar pressionamentos de tecla em contextos válidos é igualmente estúpido.
underscore_d