Usando #ifdef para alternar entre diferentes tipos de comportamento durante o desenvolvimento

28

É uma boa prática usar o #ifdef durante o desenvolvimento para alternar entre diferentes tipos de comportamento? Por exemplo, eu quero mudar o comportamento do código existente, tenho várias idéias de como mudar o comportamento e é necessário alternar entre diferentes implementações para testar e comparar diferentes abordagens. Normalmente, as alterações no código são complexas e influenciam métodos diferentes em arquivos diferentes.

Eu costumo introduzir vários identificadores e fazer algo assim

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

void bar()
{
    doSomething3();
#ifndef APPROACH3
    doSomething4();
#endif
    doSomething5();
#ifdef APPROACH2
    bar_approach2();
#endif
}

int main()
{
    foo();
    bar();
    return 0;
}

Isso permite alternar entre diferentes abordagens rapidamente e fazer tudo em apenas uma cópia do código-fonte. É uma boa abordagem para o desenvolvimento ou existe uma prática melhor?

Yury Shkliaryk
fonte
2
Como você está falando sobre desenvolvimento, acredito que você deve fazer o que achar fácil para alternar e experimentar diferentes implementações. É mais como preferências pessoais durante o desenvolvimento, não como uma prática recomendada para resolver um problema específico.
Emerson Cardoso
11
Eu recomendo usar o padrão de estratégia ou o bom polimorfismo, pois isso ajuda a manter um único ponto de plug-in para o comportamento comutável.
pmf
4
Lembre-se de que alguns IDEs não avaliam nada em #ifdefblocos se o bloco estiver desativado. Encontramos casos em que o código pode facilmente ficar obsoleto e não compilar se você não criar rotineiramente todos os caminhos.
Berin Loritsch
Veja esta resposta que dei a outra pergunta. Estabelece algumas maneiras de tornar muito #ifdefsmenos complicado.
user1118321

Respostas:

9

Eu preferiria usar ramificações de controle de versão para este caso de uso. Isso permite que você diferencie as implementações, mantenha um histórico separado para cada uma e, quando tiver tomado sua decisão e precisar remover uma das versões, você apenas descartará esse ramo em vez de passar por uma edição propensa a erros.

Karl Bielefeldt
fonte
gité especialmente especialista nesse tipo de coisa. Talvez não seja o caso svn, hgou outros, mas ainda pode ser feito.
twalberg
Esse foi o meu pensamento inicial também. "Quer mexer com algo diferente?" git branch!
precisa saber é o seguinte
42

Quando você está segurando um martelo, tudo parece um prego. É tentador quando você sabe como #ifdeffunciona usá-lo como uma forma de obter um comportamento personalizado no seu programa. Eu sei porque cometi o mesmo erro.

Eu herdei um programa legado escrito em MFC C ++ que já era usado #ifdefpara definir valores específicos da plataforma. Isso significava que eu poderia compilar meu programa para ser usado em uma plataforma de 32 bits ou de 64 bits simplesmente definindo (ou em alguns casos não definindo) valores de macro específicos.

Surgiu o problema de que eu precisava escrever um comportamento personalizado para um cliente. Eu poderia ter criado uma filial e criado uma base de código separada para o cliente, mas isso teria feito um inferno na manutenção. Eu também poderia ter definido valores de configuração para serem lidos pelo programa na inicialização e usado esses valores para determinar o comportamento, mas precisaria criar configurações personalizadas para adicionar os valores de configuração adequados aos arquivos de configuração de cada cliente.

Fiquei tentado e cedi. Escrevi #ifdefseções no meu código para diferenciar os vários comportamentos. Não se engane, não foi nada demais no início. Foram feitas pequenas alterações de comportamento que me permitiram redistribuir versões do programa para nossos clientes e não preciso ter mais de uma versão da base de código.

Com o tempo isso se tornou o inferno manutenção de qualquer maneira porque o programa não se comportou de forma consistente em toda a linha. Se eu quisesse testar uma versão do programa, precisava necessariamente saber quem era o cliente. O código, embora eu tentasse reduzi-lo a um ou dois arquivos de cabeçalho, estava muito confuso e a abordagem de correção rápida #ifdeffornecida forneceu que essas soluções se espalhassem pelo programa como um câncer maligno.

Desde então, aprendi minha lição, e você também deveria. Use-o se for absolutamente necessário e use-o estritamente para alterações na plataforma. A melhor maneira de abordar as diferenças de comportamento entre programas (e, portanto, clientes) é alterar apenas a configuração carregada na inicialização. O programa permanece consistente e torna-se mais fácil de ler e de depurar.

Neil
fonte
e as versões de depuração, como "se depurar, defina a variável x ..." isso parece útil para coisas como log, mas também pode mudar totalmente o funcionamento do programa quando a depuração está ativada e quando não está .
whn
8
@ SNB eu pensei sobre isso. Eu ainda prefiro poder alterar um arquivo de configuração e torná-lo registrado com mais detalhes. Caso contrário, algo dará errado com o programa em produção e você não poderá depurá-lo sem substituir completamente o executável. Mesmo em situações ideais, isso é menos do que desejável. ;)
Neil
Ah, sim, seria muito mais ideal não ter que recompilar para depurar, eu não pensei nisso!
whn
9
Para um exemplo extremo do que você está descrevendo, veja o segundo parágrafo da subposição "O Problema da Manutenção" neste artigo sobre o motivo pelo qual a MS chegou ao ponto em que eles tiveram que reescrever a maior parte de seu tempo de execução C do zero alguns anos atrás . blogs.msdn.microsoft.com/vcblog/2014/06/10/…
Dan Neely
2
@snb A maioria das bibliotecas de log supõe um mecanismo de nível de log. Se você deseja que certas informações sejam registradas durante a depuração, registre-as com um nível baixo de log ("Debug" ou "Verbose" geralmente). Em seguida, o aplicativo possui um parâmetro de configuração que indica qual nível registrar. Portanto, a resposta ainda está configurada para esse problema. Isso também tem o enorme benefício de poder ativar esse baixo nível de log em um ambiente do cliente .
jpmc26
21

Temporariamente, não há nada errado com o que você está fazendo (por exemplo, antes do check-in): é uma ótima maneira de testar diferentes combinações de técnicas ou ignorar uma seção do código (embora isso fale de problemas por si só).

Mas uma palavra de advertência: não mantenha #ifdef branches , é um pouco mais frustrante do que perder meu tempo lendo a mesma coisa implementada de quatro maneiras diferentes, apenas para descobrir qual eu deveria estar lendo .

Ler sobre um #ifdef exige esforço, pois você precisa se lembrar de ignorá-lo! Não faça isso mais difícil do que absolutamente deve ser.

Use #ifdefs com a menor moderação possível. Geralmente, há maneiras de fazer isso em seu ambiente de desenvolvimento para diferenças permanentes , como compilações de depuração / versão ou arquiteturas diferentes.

Eu escrevi recursos de biblioteca que dependiam das versões incluídas da biblioteca, que exigiam #ifdef splits. Portanto, às vezes, pode ser o único caminho, ou o mais fácil, mas mesmo assim você deve ficar chateado por mantê-los.

Erdrik Ironrose
fonte
1

Usar #ifdefs como esse dificulta a leitura do código.

Portanto, não, não use #ifdefs assim.

Pode haver toneladas de argumentos sobre por que não usar ifdefs, para mim este é o suficiente.

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

Pode fazer muitas coisas:

void foo()
{
    doSomething1();
    doSomething2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
}

void foo()
{
    doSomething1();
    doSomething2();
    foo_approach2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
    foo_approach2();
}

Tudo dependendo de quais abordagens são definidas ou não. O que ele faz não é absolutamente claro à primeira vista.

Pieter B
fonte