"Senhor, eu vim com uma defesa de dragão." Ele não tem mais medo de nós! É acionado pelo bater das asas de um dragão e aciona uma sirene alta para que todos possam ouvir o dragão se aproximando.
"Essa defesa faz mais alguma coisa?"
"Não, por quê?" Seremos avisados!
"Sim ... Comido pelas sirenes uivantes ... E ainda assim ... nos lembra quando planejamos interrupções? ...
Descrição do problema
Este método não pretende o conceito de tratamento de erros em projetos complexos e complexos. Pelo contrário, é um exemplo do que pode ser feito com meios mínimos.
Uma boa norma é assumir que nenhum assert () deve ser acionado durante a execução do programa. E se pelo menos um assert () funcionou ao testar o aplicativo, você precisará enviar esse erro ao desenvolvedor. Mas, e se o aplicativo não for totalmente testado? E assert () funcionará para o cliente? Enviar erro ao desenvolvedor? Abortar um programa? Na realidade, esta será a versão de lançamento do aplicativo e o assert padrão () será simplesmente desativado. A questão também surge com a contradição interna do sistema: deve haver muito assert () para facilitar a detecção de erros, mas assert () deve ser menor para interromper o usuário e seu trabalho com o aplicativo menos. Gostaria especialmente de "cair" se quantas pessoas usam o aplicativo dependem da estabilidade do trabalho e se assert () era essencialmente insignificante (exigindo correção, mas permitindo, por exemplo, continuar trabalhando com êxito).
Tais considerações levam à necessidade de refinar assert () c / c ++. E defina suas próprias macros que estendem a funcionalidade do padrão assert () - mas adicionando manipulação mínima de erros. Que tais macros sejam.
VERIFY_EXIT (Condição);
VERIFY_RETURN (Condição, ReturnValue);
VERIFY_THROW (Condição, Exceção);
VERIFY_DO (Condição) {/ * falha no bloco * /};(Essas macros também podem ser chamadas de maneira diferente. Por exemplo, VERIFY_OR_EXIT (), VERIFY_OR_RETURN (), VERIFY_OR_THROW (), VERIFY_OR_DO (). Ou vice-versa em uma versão mais curta.)
Essas macros, em primeiro lugar, têm uma implementação para a versão de depuração da compilação e a versão de lançamento. Isso permite que eles tenham comportamento na versão de lançamento do programa. I.e. execute ações não apenas durante o teste, mas também com o usuário.
Descrição da macro
(A descrição das macros é aproximada, seu outro design também é possível.)
1)
VERIFY_EXIT (condição);Ele verifica a condição e, se for falso, chama o assert padrão () (versão de depuração) e também sai da função atual (versões de depuração e lançamento).
2)
VERIFY_RETURN (Condição, ReturnValue);Ele verifica a condição e, se for falso, chama o assert padrão () (versão de depuração) e também sai da função atual retornando
ReturnValue (versões de depuração e lançamento).
3)
VERIFY_THROW (Condição, Exceção);Verifica a condição e, se for falsa, chama o assert padrão () (versão de depuração) e também lança uma
exceção (versões de depuração e lançamento).
4)
VERIFY_DO (Condição) {/ * falha no bloco * /};Verifica a condição e, se for falsa, chama o assert padrão () (versão de depuração) e também executa um
bloco de falha ou uma operação imediatamente após a macro (versões de depuração e lançamento).
Para todas as macros, é importante:
- Em todos os casos, a Condição deve ser verdadeira para "passar" a macro e falsa para ativar o caminho do tratamento mínimo de erros.
- Cada uma das macros implementa algum método mínimo de tratamento de erros. Isso é necessário para implementar o comportamento em caso de erros que não foram detectados durante o teste, mas ocorreram no usuário. Dependendo da implementação, você pode informar o desenvolvedor sobre um erro que ocorreu no cliente, mas também cada implementação fornece uma maneira mínima de se recuperar de um erro.
Padrões de uso de macro
Obviamente, o mais interessante é que os super-homens de entropia (heróis da redução de erros em programas) são o uso dessas macros.
1) Condições pré e pós.
O primeiro caso de uso é pré e pós condições. Deixe-me lembrá-lo de que as condições pré verificam o estado do programa (argumentos de entrada, estado do objeto, variáveis usadas) quanto à conformidade com os requisitos necessários do fragmento de código executado. As condições pós (são menos comuns nos programas) têm como objetivo verificar se alcançamos o resultado desejado e se o estado dos objetos permanece válido para o fragmento de código atual.
O uso das macros propostas é direto - atribuímos cada verificação em uma macro separada. Selecionamos macros com base em qual tratamento de erro precisamos. (VERIFY_EXIT () - erro ao processar com a saída desta função, VERIFY_RETURN () - erro ao processar com o retorno de um valor, VERRIFY_THROW () - erro ao processar com exceção, etc.)
Você também pode adicionar ou usar a macro VERIFY (), que não fará nenhum tratamento de erro. Isso pode ser útil, por exemplo, em condições pós no final de uma função.
Essas macros são completamente auto-suficientes se você usar os princípios do código puro e alocar funções suficientes para implementar ações atômicas. Cada função pode verificar o estado de um objeto, argumentos de entrada etc. para executar sua ação atômica.
2) Semântica de uma transação.
Além disso, essas macros podem ser usadas para implementar código com semântica de transações. Essa semântica é entendida como: 1) preparação gradual para uma operação com verificação dos resultados de cada uma das etapas da preparação; 2) a execução da ação somente se todas as etapas da preparação foram bem-sucedidas; 3) recusa em cumprir, se algumas condições não forem atendidas no estágio de preparação (com possível reversão do cumprimento).
3) Projetando o código, levando em consideração uma possível extensão.
Isso é especialmente verdadeiro para bibliotecas e códigos gerais, que podem ser desenvolvidos inicialmente no contexto de uma única condição de execução e mais tarde podem começar a ser usados com outras condições (comece a ser usado de maneira diferente). Nesse caso, essas macros podem descrever os "limites" da funcionalidade do código. Determine o que foi inicialmente visto como um erro e o que foi bem-sucedido. (Essa abordagem está próxima das condições clássicas pré-pós.) É claro que escrevo as “bordas” entre aspas, porque esses limites podem ser revisados, mas é importante determinar (ou melhor, repassar aos futuros desenvolvedores) o conhecimento dos limites aceitáveis do design de código.
Implementação de macro
Suponho que a maioria dos desenvolvedores de nível médio não terá problemas para implementar essas macros. Mas se você precisar de informações, dedicarei alguns pontos importantes.
As macros devem ser representáveis como uma única instrução. O que pode ser feito com fazer {} construções (falsas) ou similares. Por exemplo, assim:
#define VERFY_EXIT(cond) \ do{bool _= (bool)(cond); assert(_); if(!_) {return;}} while(false) \
Então você pode escrever o seguinte código:
if(a > 0) VERIFY_EXIT(a%2==0);
Obviamente, essa é apenas uma das possibilidades de implementação. Você pode implementar macros de outras maneiras.
PS Batalha de sucesso com entropia, super-homens!