Estou tentando convencer o líder da minha equipe a permitir o uso de exceções em C ++ em vez de retornar um bool isSuccessful
ou uma enumeração com o código de erro. No entanto, não posso contrariar essa crítica dele.
Considere esta biblioteca:
class OpenFileException() : public std::runtime_error {
}
void B();
void C();
/** Does blah and blah. */
void B() {
// The developer of B() either forgot to handle C()'s exception or
// chooses not to handle it and let it go up the stack.
C();
};
/** Does blah blah.
*
* @raise OpenFileException When we failed to open the file. */
void C() {
throw new OpenFileException();
};
Considere um desenvolvedor chamando a
B()
função Ele verifica sua documentação e vê que ela não retorna exceções, para não tentar pegar nada. Esse código pode travar o programa em produção.Considere um desenvolvedor chamando a
C()
função Ele não verifica a documentação, portanto não captura nenhuma exceção. A chamada é insegura e pode travar o programa em produção.
Mas se verificarmos erros desta maneira:
void old_C(myenum &return_code);
Um desenvolvedor que use essa função será avisado pelo compilador se ele não fornecer esse argumento e ele diria "Aha, isso retorna um código de erro que devo verificar".
Como posso usar exceções com segurança, para que haja algum tipo de contrato?
fonte
Either
/Result
mônada para retornar o erro de uma forma combináveis tipo seguroRespostas:
Esta é uma crítica legítima às exceções. Eles geralmente são menos visíveis do que o tratamento simples de erros, como retornar um código. E não há uma maneira fácil de fazer cumprir um "contrato". Parte do objetivo é permitir que você permita que as exceções sejam capturadas em um nível mais alto (se você precisar capturar todas as exceções em todos os níveis, qual a diferença de retornar um código de erro?). E isso significa que seu código pode ser chamado por algum outro código que não lida adequadamente com ele.
Exceções têm desvantagens; você precisa argumentar com base no custo-benefício.
Achei esses dois artigos úteis: A necessidade de exceções e Tudo errado com exceções. Além disso, esta postagem do blog oferece opiniões de muitos especialistas sobre exceções, com foco em C ++ . Embora a opinião de especialistas pareça inclinar-se a favor de exceções, está longe de ser um consenso claro.
Quanto a convencer o líder da sua equipe, talvez essa não seja a batalha certa a escolher. Especialmente não com código legado. Conforme observado no segundo link acima:
Adicionar um pouco de código que usa exceções a um projeto que principalmente não é provavelmente não será uma melhoria. Não usar exceções em códigos bem escritos está longe de ser um problema catastrófico; pode não ser um problema, dependendo do aplicativo e de qual especialista você pergunta. Você tem que escolher suas batalhas.
Provavelmente, esse não é um argumento no qual eu gastaria esforços - pelo menos até que um novo projeto seja iniciado. E mesmo se você tiver um novo projeto, ele será usado ou usado por algum código legado?
fonte
Existem literalmente livros inteiros escritos sobre esse assunto, então qualquer resposta será, na melhor das hipóteses, um resumo. Aqui estão alguns dos pontos importantes que acho que valem a pena destacar, com base na sua pergunta. Não é uma lista exaustiva.
Exceções não devem ser capturadas em todo o lugar.
Contanto que exista um manipulador de exceção geral no loop principal - dependendo do tipo de aplicativo (servidor web, serviço local, utilitário de linha de comando ...) - você geralmente possui todos os manipuladores de exceção necessários.
No meu código, existem apenas algumas instruções catch - se houver - fora do loop principal. E essa parece ser a abordagem comum no C ++ moderno.
Exceções e códigos de retorno não são mutuamente exclusivos.
Você não deve fazer disso uma abordagem do tipo tudo ou nada. Exceções devem ser usadas para situações excepcionais. Coisas como "Arquivo de configuração não encontrado", "Disco cheio" ou qualquer outra coisa que não possa ser manipulada localmente.
Falhas comuns, como verificar se um nome de arquivo fornecido pelo usuário é válido, não são um caso de uso para exceções; Use um valor de retorno nesses casos.
Como você vê nos exemplos acima, "arquivo não encontrado" pode ser uma exceção ou um código de retorno, dependendo do caso de uso: "faz parte da instalação" versus "o usuário pode fazer um erro de digitação".
Portanto, não há regra absoluta. Uma diretriz aproximada é: se puder ser tratada localmente, faça com que seja um valor de retorno; se você não puder lidar com isso localmente, lance uma exceção.
A verificação estática de exceções não é útil.
Como as exceções não devem ser tratadas localmente de qualquer maneira, geralmente não é importante quais exceções podem ser lançadas. A única informação útil é se alguma exceção pode ser lançada.
O Java possui verificação estática, mas geralmente é considerado um experimento com falha e a maioria das linguagens, já que - principalmente o C # - não tem esse tipo de verificação estática. Esta é uma boa leitura sobre os motivos pelos quais o C # não o possui.
Por esses motivos, o C ++ substituiu o seu
throw(exceptionA, exceptionB)
em favor denoexcept(true)
. O padrão é que uma função pode ser lançada, então os programadores devem esperar isso, a menos que a documentação prometa explicitamente o contrário.Escrever código de exceção seguro não tem nada a ver com escrever manipuladores de exceção.
Prefiro dizer que escrever código de exceção seguro é tudo sobre como evitar escrever manipuladores de exceção!
A maioria das práticas recomendadas pretende reduzir o número de manipuladores de exceção. Escrever um código uma vez e invocá-lo automaticamente - por exemplo, através do RAII - resulta em menos erros do que copiar e colar o mesmo código em todo o lugar.
fonte
IOException
s também . A divisão marcada é arbitrária e inútil.Os programadores de C ++ não procuram especificações de exceção. Eles procuram garantias de exceção.
Suponha que um pedaço de código tenha lançado uma exceção. Que suposições o programador pode fazer que ainda serão válidas? Da maneira como o código é escrito, o que o código garante após uma exceção?
Ou é possível que um determinado pedaço de código possa garantir que nunca será lançado (ou seja, nada menos que o processo do SO ser encerrado)?
A palavra "retroceder" ocorre frequentemente em discussões sobre exceções. Ser capaz de reverter para um estado válido (explicitamente documentado) é um exemplo de garantia de exceção. Se não houver garantia de exceção, um programa deve ser encerrado no local porque nem sequer é garantido que qualquer código executado posteriormente funcione conforme o esperado - por exemplo, se a memória estiver corrompida, qualquer operação adicional será um comportamento tecnicamente indefinido.
Várias técnicas de programação C ++ promovem garantias de exceção. O RAII (gerenciamento de recursos baseado em escopo) fornece um mecanismo para executar o código de limpeza e garantir que os recursos sejam liberados em casos normais e casos excepcionais. Fazer uma cópia dos dados antes de realizar modificações nos objetos permite restaurar o estado desse objeto se a operação falhar. E assim por diante.
As respostas para esta pergunta do StackOverflow dão uma idéia dos grandes comprimentos que os programadores de C ++ compreendem todos os possíveis modos de falha que podem acontecer ao seu código e tentam proteger a validade do estado do programa, apesar das falhas. A análise linha a linha do código C ++ se torna um hábito.
Ao desenvolver em C ++ (para uso em produção), não se pode dar ao luxo de encobrir os detalhes. Além disso, o blob binário (de código não aberto) é a desgraça dos programadores de C ++. Se eu precisar chamar algum blob binário e o blob falhar, a engenharia reversa é o que um programador C ++ faria a seguir.
Referência: http://en.cppreference.com/w/cpp/language/exceptions#Exception_safety - consulte em Segurança de exceção.
O C ++ teve uma falha na tentativa de implementar especificações de exceção. Análises posteriores em outros idiomas afirmam que as especificações de exceção simplesmente não são práticas.
Por que é uma tentativa fracassada: para aplicá-lo estritamente, ele deve fazer parte do sistema de tipos. Mas não é. O compilador não verifica a especificação de exceção.
Por que o C ++ escolheu isso e por que as experiências de outras linguagens (Java) provam que a especificação de exceção é discutível: À medida que se modifica a implementação de uma função (por exemplo, ele precisa fazer uma chamada para uma função diferente que pode gerar um novo tipo de exceção), uma aplicação estrita de especificação de exceção significa que você também deve atualizar essa especificação. Isso se propaga - você pode ter que atualizar as especificações de exceção para dezenas ou centenas de funções para uma simples alteração. As coisas pioram para as classes base abstratas (o equivalente em C ++ das interfaces). Se a especificação de exceção for imposta nas interfaces, as implementações de interfaces não poderão chamar funções que geram tipos diferentes de exceções.
Referência: http://www.gotw.ca/publications/mill22.htm
Começando com C ++ 17, o
[[nodiscard]]
atributo pode ser usado em valores de retorno de função (consulte: https://stackoverflow.com/questions/39327028/can-ac-function-be-declared-such-that-the-return-value não pode ser ignorado ).Então, se eu fizer uma alteração no código e isso introduzir um novo tipo de condição de falha (ou seja, um novo tipo de exceção), será uma mudança de quebra? Deveria ter obrigado o chamador a atualizar o código ou pelo menos ser avisado sobre a alteração?
Se você aceitar os argumentos de que os programadores de C ++ procuram garantias de exceção em vez de especificações de exceção, a resposta é que, se o novo tipo de condição de falha não quebrar nenhuma das exceções, garantir que o código promete anteriormente, não será uma mudança de quebra.
fonte
std::exception
) e sua própria exceção base serão lançadas. Mas quando aplicada a um projeto inteiro, significa simplesmente que cada função terá a mesma especificação: ruído.catch
se baseia no tipo de objeto de exceção, quando em muitos casos o que importa não é tanto a causa direta da exceção, mas o que ela implica sobre o estado do sistema. Infelizmente, o tipo de exceção geralmente não diz nada sobre se causou a interrupção do código de maneira disruptiva de um trecho de código de uma maneira que deixou um objeto em um estado parcialmente atualizado (e, portanto, inválido).É totalmente seguro. Ele não precisa capturar exceções em todos os lugares, ele pode apenas tentar / capturar em um lugar onde ele possa realmente fazer algo útil sobre isso. Não é seguro se ele permitir que ele vaze de um thread, mas normalmente não é difícil impedir que isso aconteça.
fonte
C()
e, consequentemente, não protege contra o código após a chamada ser ignorada. Se esse código for necessário para garantir que alguma invariância permaneça verdadeira, essa garantia será prejudicada na primeira exceção que surgirC()
. Não existe software perfeitamente seguro para exceções e, certamente, não onde as exceções nunca foram esperadas.C()
é um grande erro. O padrão em C ++ é que qualquer função pode ser lançada - requer um explícitonoexcept(true)
para informar o compilador de outra forma. Na ausência desta promessa, um programador deve assumir que uma função pode ser lançada.C()
funções quebradas na presença de exceções. Isso é realmente uma coisa muito imprudente, está fadado a levar a uma tonelada de problemas.Se você estiver construindo um sistema crítico, considere seguir o conselho do líder da equipe e não use exceções. Esta é a regra 208 da AV nos padrões de codificação C ++ do veículo aéreo de combate conjunto da Lockheed Martin . Por outro lado, as diretrizes MISRA C ++ possuem regras muito específicas sobre quando exceções podem e não podem ser usadas se você estiver criando um sistema de software compatível com MISRA.
Se você estiver construindo sistemas críticos, é provável que também esteja executando ferramentas de análise estática. Muitas ferramentas de análise estática alertam se você não verificar o valor de retorno de um método, tornando casos de manipulação de erros ausentes prontamente aparentes. Que eu saiba, suporte de ferramenta semelhante para detectar o tratamento adequado de exceções não é tão forte.
Por fim, eu argumentaria que o design por contrato e programação defensiva, juntamente com a análise estática, é mais seguro para sistemas críticos de software do que exceções.
fonte