Prefácio
Este artigo é uma reação ao artigo: O que acontecerá com o tratamento de erros no C ++ 2a . Depois de cada parágrafo, senti uma coceira, feridas curadas se abriam e começava a sangrar. Talvez eu leve o que está escrito muito perto do meu coração. Mas eu só quero uivar sobre a miopia e o analfabetismo que os programadores de C ++ mostram no século XXI. E nem mesmo no começo.
Vamos começar.
Classificação
Convencionalmente, todas as situações errôneas do programa podem ser divididas em 2 grandes grupos:
- Erros fatais.
- Erros não fatais ou esperados.
Agora vou encontrar a falha. Mas erros fatais - eles também são, em certo sentido, esperados. Esperamos que o deslocamento da memória geralmente leve a uma queda, mas pode não levar a isso. E isso é esperado, não é? Quando uma classificação é introduzida, sempre seria possível verificar sua consistência.
Mas é assim, um erro sutil frequente.
Vamos olhar para os erros fatais.
Divisão por 0 . Gostaria de saber por que esse erro é fatal? Eu adoraria lançar uma exceção neste caso e capturá-la para processamento adicional. Por que ela é fatal? Por que um certo comportamento do meu próprio programa é imposto a mim e não posso influenciá-lo de nenhuma maneira? O C ++ não tem a ver com flexibilidade e com o fato de a linguagem estar voltada para o programador? Embora ...
Cancelamento de referência de ponteiro nulo . Lembro-me imediatamente de Java, há uma NullPointerException
que pode ser manipulada. Há também uma NullPointerException
biblioteca Poco! Então, por que desenvolvedores padrão com a tenacidade de surdos-mudos repetem o mesmo mantra?
Geralmente, por que eu comecei este tópico? É muito importante e apenas revela a compreensão do desenvolvedor sobre o tratamento de erros. Não se trata de tratamento de erros em si, é um prelúdio para uma ação importante. É sempre sobre a confiabilidade do aplicativo, sobre a tolerância a falhas e, às vezes, nos tipos mais raros e mais ameaçados, eu diria até em tipos de programas ameaçados, sobre comportamento transacional e consistente.
Nesse aspecto, todas as disputas sobre a divisão por zero e a exclusão de indicadores parecem uma luta de pássaros por uma migalha de pão. Certamente um processo importante. Mas apenas do ponto de vista dos pássaros.
Voltemos à divisão do fatalismo e sua ausência ... Começarei com uma pergunta simples: se recebi dados incorretos pela rede, isso é um erro fatal?
Resposta simples e correta: depende. É claro que na maioria dos casos isso não é um erro fatal, e todos os dados recebidos pela rede devem ser validados e 4xx retornados se os dados estiverem incorretos. Existem casos em que você precisa travar? E caiu com um uivo selvagem, para que o SMS chegasse, por exemplo. Sim, e não um.
Existem. Posso dar um exemplo da minha área de assunto: um algoritmo de consenso distribuído. Um nó recebe uma resposta que contém um hash de uma cadeia de alterações de outro nó. E esse hash é diferente do local. Isso significa que algo deu errado e continuar a execução adicional é simplesmente perigoso: os dados podem divergir, se ainda não estiverem. Isso acontece quando a disponibilidade de um serviço é menos importante que sua consistência. Nesse caso, precisamos cair, e com um rugido, para que todos ouçam ao redor. I.e. recebemos dados pela rede, eles validaram e caíram. Para nós, esse erro não é mais fatal. Esse erro é esperado? Bem, sim, escrevemos o código com validação. É tolice dizer o contrário. Só que não queremos continuar o programa depois disso. Intervenção manual necessária, a automação não funcionou.
Escolha do fatalismo
A coisa mais óbvia no tratamento de erros é decidir o que é fatal e o que não é. Mas cada programador faz essa pergunta a si mesmo durante toda a atividade de desenvolvimento. Portanto, de alguma forma, responde a essa pergunta. A resposta correta vem da prática por algum motivo.
No entanto, esta é apenas a parte visível do iceberg. Nas profundezas há uma pergunta muito mais monstruosa. Para entender a profundidade total, você precisa definir uma tarefa simples e tentar respondê-la.
Desafio . Faça uma estrutura para alguma coisa.
Tudo é simples. Criamos uma estrutura, por exemplo, de interação de rede. Ou analisando JSON. Ou, na pior das hipóteses, XML. A pergunta surge imediatamente: mas quando ocorre um erro no soquete - é um erro fatal ou não? Parafraseando: devo lançar uma exceção ou retornar um erro? Esta é uma situação excepcional ou não? Ou pode retornar std::optional
? Ou uma freira? (^ 1)
A conclusão paradoxal é que o próprio quadro não pode responder a essa pergunta. Somente o código que o utiliza sabe. É por isso que a excelente biblioteca boost.asio , na minha opinião, usa as duas opções. Dependendo das preferências pessoais do autor do código e da lógica aplicada, um ou outro método de tratamento de erros pode ser escolhido. No começo, fiquei um pouco envergonhado com essa abordagem, mas com o tempo fiquei imbuído da flexibilidade dessa abordagem.
No entanto, isso não é tudo. O pior está por vir. Aqui estamos escrevendo o código do aplicativo, mas parece-nos que ele é aplicado. Para outro código, nível superior, nosso código será a biblioteca. I.e. A divisão no código do aplicativo / biblioteca (estrutura etc.) é pura convenção, que depende do nível de reutilização dos componentes. Você sempre pode estragar algo em cima e o código do aplicativo deixará de ser. E isso imediatamente significa que a escolha do que é válido e do que não é já é decidida pelo código que usa e não é usado.
Se pularmos para o lado, acontece que às vezes nem é possível entender quem usa quem. I.e. o componente A pode usar o componente B e o componente B, componente A (^ 2). I.e. quem determina o que vai acontecer não está claro.
Desembaraçar o emaranhado
Quando você olha para toda essa desgraça, surge imediatamente a pergunta: como viver com ela? O que fazer Que diretrizes você deve escolher, para não se afogar em variedade?
Para fazer isso, é útil olhar em volta e entender como esses problemas são resolvidos em outros lugares. No entanto, é preciso procurar sabiamente: é necessário distinguir "colecionar selos" de soluções completas.
O que é colecionar selos? Este é um termo coletivo, o que significa que trocamos uma meta, mas outra coisa. Por exemplo: tínhamos um objetivo: ligar e nos comunicar com os entes queridos. E nós uma vez, e comprou um brinquedo caro, porque "na moda" e "bonito" (^ 3). Isso é familiar? Você acha que isso não acontece com os programadores? Não se iluda.
O tratamento de erros não é o objetivo. Sempre que falamos sobre tratamento de erros, imediatamente paramos. Porque é uma maneira de alcançar uma meta. E o objetivo inicial é tornar nosso software confiável, simples e compreensível. São esses objetivos que devem ser estabelecidos e sempre respeitados. E o tratamento de erros é besteira que não vale a pena discutir. Eu quero lançar uma exceção - mas para a saúde! Devolveu um erro - bem feito! Quer uma mônada? Parabéns, você criou a ilusão de progresso, mas apenas em sua própria cabeça (^ 4).
Então eu queria escrever como fazê-lo corretamente, mas eu já escrevi. As feridas sararam e pararam de sangrar. Em suma, as dicas são:
- Separe em componentes com limites claros.
- Nas fronteiras, descreva o que e como ele pode voar. É desejável que seja uniforme. Mas muito mais importante é.
- Facilite o tratamento de erros no código que o usará.
- Se algo puder ser processado internamente sem carregar o código do usuário, não o destaque. Quanto menos erros um usuário precisar, melhor.
- Respeite seu usuário, não seja idiota! Escreva interfaces intuitivas com o comportamento esperado para que ele não precise ler comentários e xingar.
O quinto conselho é o mais importante, porque ele combina os quatro primeiros.
PS Na infância, eu sempre fiquei curioso para olhar o formigueiro. Milhares de formigas, todo mundo faz alguma coisa, rasteja sobre seus negócios. O processo está em andamento. Agora eu também estou assistindo com interesse. Também atrás do formigueiro. Onde milhares de pessoas estão fazendo suas pequenas coisas. Posso desejar-lhes boa sorte em seus negócios difíceis!
^ 1: As pessoas gostam de coisas da moda. Quando todo mundo já tocou o suficiente, os programadores de C ++ acordaram e tudo mudou.
^ 2: Isso pode acontecer quando há várias abstrações no componente B que as conecta. Veja Inversão de controle .
^ 3: E no dia seguinte, bam, e a tela falhou.
^ 4: Eu não sou contra mônadas, sou contra aspirantes, tipo, olha, aqui está a mônada, ou seja, monóide na categoria monoidal de endofunitores! Aplausos e acenos de aprovação são ouvidos. E em algum lugar distante, longe, quase inaudível, alguém orgasmo.