Ponto V!

Home C/C++ Conceitos Básicos Tratamento de erros
Vinícius Godoy de Mendonça
Tratamento de errosImprimir
Escrito por Vinícius Godoy de Mendonça

Nem sempre é possível prever tudo o que pode ocorrer num programa. Um usuário pode tentar abrir um arquivo do qual não tem permissão, um programador pode fornecer um parâmetro inválido a um método da nossa API. Precisamos sinalizar esse tipo de ocorrência de alguma forma, e é aí que entra o papel dos tratamentos de erro. Nesse artigo, iremos estudar diferentes formas de resolver o problema: códigos de erro e o poderoso mecanismo de exceções do C++.

O problema

Um programa geralmente precisa realizar uma série de tarefas, mas nem sempre, é possível realizá-las com sucesso. Por exemplo, considere a implementação de um jogo que irá carregar o savegame de um jogador. O código que implementa a carga do jogo dentro da engine, poderia ser assim:

void readSave(const std::string& filename) 
{ std::fstream saveGame; saveGame->open(filename, fstream::out); load(saveGame); }

Em algum outro lugar de nosso game, teríamos um código de interface gráfica para solicitar ao jogador que save carregar:

void askForSaveFile() 
{ std::string filename; std::cout << "Entre no o nome do arquivo que quer carregar"; std::cout << endl; cin >> filename; readSave(filename); }

O que aconteceria nesse código, caso o arquivo não existisse? Ou então, caso o usuário não tivesse permissão de leitura no arquivo? Ou ainda, o que fazer se o arquivo tiver sido corrompido, e não for possível ler todos os seus dados?

Códigos de erro

A solução adotada por programadores C era a de retornar códigos de erro. Ao invés de declarar a função como void poderíamos declara-la como int, e então retornar um código indicando sucesso (por exemplo 0) e códigos diferentes indicando erros. Por exemplo, poderíamos definir no arquivo erros.h a seguinte constante:

const int FILE_NOT_FOUND = 1;

Vamos ajustar nossa função para gerar esse código de erro?

int readSave(const std::string& filename) 
{ std::fstream saveGame; saveGame->open(filename, fstream::out); if (saveGame.fail()) return FILE_NOT_FOUND; load(saveGame); return 0; }

E como ficaria a interface gráfica?

void askForSaveFile() 
{ std::string filename; std::cout << "Entre no o nome do arquivo que quer carregar"; std::cout << endl; cin >> filename; int err = readSave(filename); switch (err)
{ case FILE_NOT_FOUND: std::cout << "Arquivo não encontrado."; std::cout << std::endl; break; } }

Esse código já sinaliza caso problemas ocorram. Entretanto, o que aconteceria se, ao invés do arquivo não existir, tivéssemos um problema para ler o arquivo? Quem daria o erro não é a função readSave, mas a função load, que está dentro de readSave. Como você faria para sinalizar esse erro ao jogador? Uma forma prática seria retornar o código de erro da função load, na função readSave:

int readSave(const std::string& filename) 
{ std::fstream saveGame; saveGame->open(filename, fstream::out); if (saveGame.fail()) return FILE_NOT_FOUND; return load(saveGame); }

Entretanto, o que ocorreria se esse código tivesse várias linhas? Provavelmente, em pouco tempo, estaríamos cheios de ifs e com um código difícil de ler. Por exemplo, vamos considerar uma versão um pouco mais completa do método readSave, que também carrega na engine o cabeçalho do save (header), os recursos do jogo (assets) e informações sobre o jogador (player):

 
int readSave(const std::string& filename) 
{
    std::fstream saveGame;
    saveGame->open(filename, fstream::out);
    if (saveGame.fail()) return FILE_NOT_FOUND;
    int err = readHeader(saveGame);
    if (err) return err;
    err = readCharInfo(saveGame, player);
    if (err) return err;
    return loadAssets(saveGame, assets);
}

Note que o código ficou extremamente poluído. Os motivos para isso são claros:

  1. É necessário intercalar cada linha de cada função com um teste de código de erro;
  2. As funções deixam de retornar parâmetros relevantes, para só retornar códigos de erro;
  3. Como consequência do item anterior, os valores de retorno passam a ser fornecido por referência, o que torna a lista de argumentos da função mais confusa.

Entretanto, há outros problemas menos óbvios com essa abordagem:

  1. Se os códigos de erro não forem explicitamente testados pelo programador, o programa continuará executando num estado inválido;
  2. Se não houver uma unificação na codificação de erro, pode ocorrer de erros diferentes terem o mesmo código. Isso é especialmente problemático quando você está operando com várias bibliotecas, de fabricantes diferentes, pois não há convenção multi-fabricante.
  3. Os códigos de erro são pouco descritivos e uma vez atribuídos, são difíceis de modificar.

Esse pequeno exemplo serve para mostrar algumas características importantes que um bom mecanismo de prevenção de erros deve contemplar:

  • Funções precisam notificar erros;
  • Erros podem ocorrer a qualquer momento;
  • Nem sempre a função A que chama uma função B é a responsável por tratar o erro, o que nos força a repassar o erro para quem chamou A.

    A abordagem do C acima é suficiente para cobrir tudo isso? Sim, mas é pouco prática. Não é à toa que muitos programadores recorrer ao uso de macros e outros artifícios para reduzir o trabalho.

    Exceções

    Um dos elementos mais importantes incluídos no C++ é o conceito de exceções (exceptions). Elas fornecem um mecanismo que permite que um programa trate erros, eliminando boa parte das desvantagens que os códigos de erro possuem.

    Para usar o mecanismo de exceções, colocamos o código que pode dar erro num bloco try. Caso um erro ocorra, podemos capturá-lo numa outra porção, chamada de catch:

    void askForSaveFile() 
    {
        std::string filename;
        std::cout << “Entre no o nome do arquivo que quer carregar”;
        std::cout << endl;
        cin >> filename;
        try 
        {
            readSave(filename);              
        } 
        catch (int error) 
        {
            switch (error) 
            {
               case FILE_NOT_FOUND:
                   std::cout << "Arquivo não encontrado."                
                   break;
               default:
                   std::cout << "Um erro ocorreu ao ler o arquivo!"   
                   break;
            }
            std::cout << std::endl;
        }
    }

O método que pode disparar o erro, o faz através do comando throw.

void readSave(const std::string& filename) 
{
     std::fstream saveGame;
     saveGame->open(filename, fstream::out);
     if (saveGame.fail()) throw FILE_NOT_FOUND;
     readHeader(saveGame);
     player = readCharInfo(saveGame);
     assets = loadAssets(saveGame);
}

Quando um erro ocorre e a linha do throw é executada, a função é imediatamente abandonada, e o erro é disparado para a função que a chamou.

Se essa função possuir um bloco catch, esse erro é capturado, e o bloco catch é acionado. Mas, o que acontece se a função não possuir esse bloco? Nesse caso, o código dela também será abandonado, e a exceção subirá para a função que a chamou. Esse processo continua, até que a exceção seja capturada em algum momento, ou o método main seja abandonado, encerrando o programa.

Observe o código da função readSave depois que colocamos o comando throw. Se as funções readCharInfo ou loadAssets dispararem uma exceção, o que ocorre? Observe que a função readSave não possui um bloco try. Portanto, o código de readSave será abandonado e o erro cairá na função askForSaveFile.

A primeira vantagem que podemos observar nesse mecanismo é que o erro é retornado sem a necessidade de usar o valor de retorno da função. Isso nos deixa livre para retornar valores realmente relevantes.

A segunda, é que tiramos do programador a necessidade de repassar adiante códigos de erro, ou de testar por sua existência. Note como isso não só deixou o código bem mais legível, como ao mesmo tempo impedirá o programa de continuar executando caso um erro ocorra e não seja tratado. Ou seja, nada mais de ifs linha-a-linha, nada mais de valores de retorno sendo passado como argumentos por referência na função.

Exceções como classes

O mecanismo que vimos até agora já está interessante. Porém, ele fica ainda melhor. Ao invés de disparar um número inteiro, o mecanismo permite que disparemos objetos. As vantagens disso são as seguintes:

  • Os erros podem ser agrupados hierarquicamente, através da herança de classes;
  • Classes podem ser mais descritivas: prover informações adicionais sobre o erro, como um texto, a linha que ocorreu, etc.
  • A herança de classe pode ser estendida por usuários de sua biblioteca.

Vejamos um exemplo. Vamos começar criando uma classe para descrever os erros de arquivo:

class FileException 
{
   std::string description;
   FileException(const std::string& _description) 
      : description(_description) 
   {
   }

   const std::string getDescription() const
   { 
       return description; 
   }
};
Ela poderia ter uma subclasse, para alguns tipos diferentes de exceção de erro:
class FileNotFoundException : public FileException {};
class PermissionDeniedException : public FileException {};
class CorruptedFileException : public FileException {};

Como modificaríamos nosso código, para disparar essas exceções?

void readSave(const std::string& filename) 
{
     std::fstream saveGame;
     saveGame->open(filename, fstream::out);
     if (saveGame.fail()) 
        throw FileNotFoundException("Arquivo não encontrado");
     readHeader(saveGame);
     player = readCharInfo(saveGame);
     assets = loadAssets(saveGame);
}

E como ficaria o catch na função askForSaveFile?

void askForSaveFile() 
{
    std::string filename;
    std::cout << "Entre no o nome do arquivo que quer carregar";
    std::cout << endl;
    cin >> filename;
    try 
    {
        readSave(filename);              
    } 
    catch (FileException &e) 
    {
        std::cout << e.getDescription() << std::endl;
    } 
    catch (...) 
    {
        std::cout << "Um erro ocorreu ao ler o arquivo!" << std::endl;   
    }
}

Note que nossa função captura qualquer FileException. Como a função readSave dispara FileNotFoundException e essa função é filha de FileException, essa exceção será capturada. E o que significa catch (...)? Ele substitui o default do switch anterior, pois captura qualquer exception não capturada anteriormente.

Repare também que agora substituímos nossa mensagem de erro pela mensagem dentro da exceção. Assim, demos a responsabilidade de escrever uma mensagem descritiva do erro para o programador que o gera, não mais para quem detecta a exceção. Reflita um pouco a respeito. Essa escolha foi realmente inteligente?

Exceções padrão

Você pode estar pensando. “Poxa, como ficou prática a classe de exceções! Eu poderia criar uma classe básica, que sempre tivesse uma mensagem dentro.”

Não foi só você quem pensou assim, o pessoal do C++ também. Existe na STL uma série de exceções padrão. Para utiliza-las basta incluir o cabeçalho <stdexcept> em seu código. Recomenda-se que suas exceções derivem de std::logic_failure ou uma de suas filhas. Veja a árvore de exceções e o significado das mais comuns:

Exceções da biblioteca padrão

  • exception: É a mãe de todas as exceptions. Uma alternativa melhor que o catch (...) pois você ainda poderá obter o erro da exception.
  • bad_alloc: Disparada automaticamente pelo comando new quando não há memória suficiente.
  • bad_cast: Disparada pelo dynamic_cast caso a conversão de tipos que você está tentando for impossível
  • runtime_error: Exceções que denotam erros de programação comuns, que não poderiam ser previstos inicialmente pelo programador. Tal como, no meio da avaliação de uma função, um cálculo ter o limite superior de uma variável estourada (std::overflow_error).
  • logic_failure: São exceções que violam condições no programa, ou que denotam erros de programação. Por exemplo, numa função de fatorial, passar valores negativos como parâmetro. Geralmente, suas exceções serão sempre filhas de logic_error. Suas filhas são:
    • domain_error: Indica que um erro de domínio foi cometido. Ou seja, um valor incorreto foi passado para uma função matemática (por exemplo, na função de raiz quadrada, foi passado um número negativo).
    • invalid_argument: Indica o valor de um dos parâmetros recebidos pela função é inválido. Por exemplo, o usuário passou um ponteiro nulo onde deveria passar um objeto.
    • length_error: Indica que não é possível alterar o tamanho de uma estrutura para o valor indicado. Por exemplo, o usuário tentou dar um resize para um tamanho maior que o máximo, ou negativo.
    • out_of_range: Indica que um parâmetro foi passado fora do intervalo permitido. Por exemplo, valores menores que 10 ou maiores que 100 numa função que pergunta ao usuário sua idade.

Todas as exceções padrão possuem uma descrição. Ao invés do método getDescription(), como criamos, o método what() é o responsável por retornar a mensagem de erro.

Uma das vantagens de usar uma das exceções padrão como base é que há boas chances de que o programador que usar seu código tenha fornecido algum try...catch para tratá-las. Isso é especialmente valioso se você estiver desenvolvendo sua própria lib ou engine.

Relançando exceções

Muitas vezes, queremos fazer algum tratamento devido a um erro, mas que não necessariamente o resolve. Por exemplo, considere nossa função readSave:

void readSave(const std::string& filename) 
{
     std::fstream saveGame;
     saveGame->open(filename, fstream::out);
     if (saveGame.fail()) 
        throw FileNotFoundException(“Arquivo não encontrado.”);
     readHeader(saveGame);
     player = readCharInfo(saveGame);
     assets = loadAssets(saveGame);
}

Poderia ser interessante registrar a ocorrência desse erro em algum arquivo, que pudesse ser analisado caso algum jogador desesperado ligue reclamando que seu save não abre mais. Para isso, bastaria capturar as exceções disparadas e fazer esse registro. Entretanto, isso não elimina o fato não ter sido possível ler o arquivo e, portanto, ainda deveríamos sinalizar essa ocorrência para a função askForSaveFile().

Podemos então capturar a exceção, fazer os devidos registros, e redisparar a exceção, como mostrado abaixo:

void readSave(const std::string& filename) 
{
     std::fstream saveGame;
     saveGame->open(filename, fstream::out);
     if (saveGame.fail()) 
        throw FileNotFoundException("Arquivo não encontrado.");
     try 
     {
        readHeader(saveGame);
        player = readCharInfo(saveGame);
        assets = loadAssets(saveGame);
     } 
     catch (FileException &e) 
     {
         logFileProblem(e);
         throw;
     }
}

Aqui vemos mais um uso para a palavra chave throw. A de relançar a exceção que foi pega por um catch. Logicamente, seria possível relançar outra exceção, diferente da capturada. Veremos em futuros artigos que muitas vezes isso é até mesmo recomendável.

Restringindo exceções (obsoleto)

A palavra chave throw também pode ser usada para restringir quais exceções um determinado método dispara. Na teoria, bastaria usa-la dessa forma:

void askForSaveFile() throw (FileException, int)

O que aconteceria se uma exceção diferente dessas duas fosse disparada no interior do método? O C++ chamaria uma função especial, chamada de unexpected_handler. Essa função pode ser configurada pelo programador com o método set_unexpected e por padrão, simplesmente chama o método terminate e encerra o programa.

Outra opção é incluir na lista de argumentos a exceção std::bad_exception:

void askForSaveFile() throw (FileException, int, std::bad_exception)

Nesse caso, se uma exception não estiver na lista, ela será automaticamente convertida numa bad_exception.

Infelizmente, a cláusula throw não funcionou como os projetistas da linguagem esperavam. Recomenda-se que essa palavra não seja usada. Uma discussão completa das razões disso, escrita pelo Herbert Sutter, pode ser encontradas nesse link.

Na nova especificação a palavra noexcept foi criada, que indica que um método nunca dispara uma exceção. Veremos mais detalhes desse assunto em artigos futuros.

Finalizando

Nesse artigo, procuramos descrever os mecanismos básicos de tratamento de erros: os códigos de erro e exceções. Logicamente, há muito o que se falar sobre ambos os assuntos: desde detalhes de performance, implementação em outras linguagens ou até mesmo escrever programas que sejam corretos, mesmo que uma exceção dispare a qualquer momento.

Nos próximos artigos, pretendemos trazer em pauta alguns desses tópicos, afinal, foram eles que amadureceram e permitiram a construção de boa parte das técnicas modernas de desenvolvimento em C++.

Até lá!


Comentários (20)
  • Danny Angelo Carminati Grein  - Sensible Error Handling
    avatar

    Vale a pena ler esses artigos:

    http://altdevblogaday.com/2012/01/22/sensible-error-handling-part-1 /
    http://altdevblogaday.com/2012/02/05/sensible-error-handling-part-2/

    Principalmente sobre Exceptions....

  • Bruno Crivelari Sanches
    avatar

    Eu cheguei a ler essa semana, são bem recentes.

    A discussão de exceções ou não vai ser eterna.

    eu de alguns anos para cá passei a usar em alguns projetos e sinceramente hoje não tenho muito do que reclamar, contanto que, como tudo em C++, exista uma certa disciplina.

    Se for para usar exceções como no ambiente java, onde tudo é uma exceção, estou fora.

    Mas se for algo ponderado, onde fica a cargo do programador decidir o que é e o que não é, dessa forma o trabalho fica tranquilo, principalmente seguindo uma linha semelhante ao do C# (que o vini cita no próximo artigo), onde você tem duas alternativas de método, um que faz throw em caso de erro e outro que retorna um código de erro, que na maioria dos casos é um bool.

    Isso resolve bem a questão de se decidir quando um erro é esperado ou não, em algumas situações é difícil decidir e você com duas opções de método delega essa responsabilidade a quem vai usar a funcionalidade, da mesma forma que no artigo, ele delega ao programador, invocar as funções que checam se algo é valido ou não. Se ele não checar antes, vai ter um abort em caso de falha :).

    No final, o que eu faço nos meus códigos é ter alguns throws e nunca ter um catch, exceção é exceção e não deve ocorrer, sendo assim, num throw, acaba ocorrendo um abort no programa, semelhante ao que o artigo faz, mas no caso dele ele não usa exceção para implementar isso.

    O que mais gostei nesses dois artigos, foi o log de erro dele e aquele contexto de erro, muito prático aquilo e vou por em prática assim que possível.

    T+

  • Danny Angelo Carminati Grein
    avatar

    Sim, a discussão sobre exceptions será eterna.

    Existem casos em que exceptions são excelentes, mas não acredito que nenhum desses seja no domínio de games, pode ser que em software de sistemas seja mais útil.

    O que me incomodou nesse artigo foi a indução à exceptions como a solução ideal, ou seja, no caso de códigos de erro o Vinícius cita vários problemas em utilizar esse método, mas no caso de exceptions ele mostra o mundo ideal e não comenta nenhum problema de uso das exceptions, como se realmente não houvessem.

    Minha sugestão então é trocar o titulo do artigo de "Tratamento de erros" para "Utilizando exceptions em C++ para o tratamento de erros".

    Por esse motivo citei os dois artigos, lá ele mostra os prós e contras das alternativas e mostra o que ele utiliza na prática em projetos reais.

    E concordo com o autor dos artigos, crash often and crash soon.

    http://altdevblogaday.com/2012/01/22/sensible-error-handling-pa rt-1/
    http://altdevblogaday.com/2012/02/05/sensible-error-handling-pa rt-2/

  • Vinícius Godoy de Mendonça
    avatar

    Oi. Eu concordo com o Bruno.

    Esse artigo é introdutório, e na sessão de C++ básico. Seria inadequado e pouco didático inserir aqui uma dicussão filosófica sobre os prós e contras de exceptions, pois confundiria quem está aprendendo.

    No próximo artigo, já dou a dica de criar alternativas ao mecanismo de exceptions (e até tentar evita-las totalmente, quando possível). Mais para frente, pretendo falar de exception safety e gerência de recursos, e aí sim, introduzir a pessoa aos problemas relacionados ao mecanismo de exceções e discutir alternativas.

    Mas é importante ter um amadurecimento do leitor no processo.


    Sobre esse tema, também vale ler a discussão sobre exceptions no Tecnical Report: http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdf


    E, claro, usar mais ou menos exceptions vai do quanto você gostaria de confiar no programador que utiliza seu código.

  • Bruno Crivelari Sanches
    avatar

    Sobre games ou não eu não sei, o maior problema que vejo na questão dos games é a parte de desempenho, que existe aquela discussão sobre isso, mas eu não consigo lembrar de algum benchmark sério sobre isso ou que de números concretos em plataformas modernas.

    O único projeto relativo a jogos que me lembro agora que usa exceções é o Ogre, não vou discutir se é um bom motor ou não, mas é fato que já foi usado em diversos projetos de jogos profissionais, inclusive consoles.

    A análise mais completa que conheço sobre a questão desempenho é: http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdf, item 5.4.

    No final, os custos são:
    * um custo moderado / baixo entrando e saindo de cada bloco try/catch, dependendo de como o compilador gera o código
    * um custo alto em um throw

    Bom, no meu caso, nos meus códigos conta-se nos dedos os blocos try/catch, então acaba não sendo uma preocupação. O mesmo para o throw, que só deve ocorrer em casos excepcionais mesmo, não no timeout de um socket ou com um fileopen, então esse custo não é problema, pois se ocorre um throw, literalmente, a vaca já foi para o brejo :).

    Mas o problema todo é que, se exception tem ou não um custo de desempenho, se vai ou não impactar o seu projeto, somente com um benchmark especifico para seu projeto, não concordo muito em generalizar com "expcetions são lentas".

    No artigo inclusive cita que em muitos casos os custos de if/else para checar erro causam problemas com pipeline e acabam tendo um custo alto também, no caso da exception (com exceção do throw), não existiram desvios.

    A outra questão de exceção ou não, que consigo lembrar agora é a discussão de saber que diabos um método vai dar throw e nesse caso, só vejo duas soluções:
    * seu método como erro retorna um enum e você consulta a declaração do enum para saber todos códigos de erro possíveis
    * Sua documentação diz claramente o que cada método retorna ou faz de throw.

    No artigo que você citou ele propõe o enum, novamente é como eu comentei anteriormente, tudo em C++ acaba precisando de uma certa disciplina e ele esta fazendo uma disciplina de como usar códigos de erro.

    O problema de saber e imaginar todos os possíveis fluxos de uma rotina no caso de um throw, como eu não entupo o código de try/catch, não me preocupo tanto, se ocorreu um throw, acabou mesmo a festa e o que resta a fazer é terminar o programa ou deixar o SO limpar a sujeira.

    No artigo ele cita a criação de interfaces e funções de "query" para checar se uma função vai funcionar ou não, tipo o FileExists antes de fazer um Open. É uma técnica interessante, mas no fundo, cai quase no mesmo problema, que erros podem ocorrer em um Open ??? E se o SO negar acesso? E se entre o FileExists e o Open, alguém remover o arquivo? Como que eu sei para uma determinada função, quais verificações preciso fazer antes de invocar ela para ter certeza que erros não ocorram? Para mim, tão oculto quanto quais exceptions um método lança.

    A unica vantagem é que se as interfaces dele são enxutas, você tem poucas opções de funções para decidir qual (ou quais) deve usar.

    Ele resolve esse problema minimizando os erros e as possibilidades de erros, o que concordo plenamente, mas o mesmo podemos aplicar para uma técnica de exceptions e minimizar as possibilidades.

  • Vinícius Godoy de Mendonça
    avatar

    E ele também não explica o que fazer com essa struct de retorno, caso o método esteja 4 níveis abaixo do método que deveria reportar ou logar a exception. Você tem o método A, que chama B, que chama C, que chama D. D reporta um erro, mas A deve mostrar um erro para o usuário, como fazer isso usando a struct? E o quão porco fica o código de quem repassa o erro adiante?


    Outro ponto abordado ao falar que o erro se perde ao subir pelo mecanismo de exceção, pois uma exception pode ser relançada. Você não tem exatamente o mesmo problema com códigos de erro, quando eles sobem?

    A solução que ele apresenta é a de refazer a interface da engine. Mas o problema em si, não são as exceptions, mas o mau design de uma interface. O problema se repetirá com códigos de erro ou com flags para sinalizar erros, ou com qualquer outro mecanismo, por mais simples que seja.

    E, no fundo, os mecanismos mais simples de controles de erro tem uma boa dose de confiança no programador e no sistema. Você também pode programar assim com exceptions, simplesmente não colocando blocos try, catch e assumindo que o sistema e o programador, em certos momento, farão a coisa certa. Deixe então para um catch (muito) superior logar e crashear o programa.

  • Antonio Arleudo da Costa  - Perfeito
    avatar

    Vini, como sempre seus posts são magníficos, sua didática é muito boa e consegue manter a pessoa lendo o post do começo ao fim com o mesmo interesse, estarei no aguardo dos proximos posts relacionado a exceptions.

  • Danny Angelo Carminati Grein  - re:
    avatar

    Primeiramente, vocês desvirtuaram o assunto. Minha critica é a maneira que o artigo apresenta "as alternativas". Principalmente sendo um artigo introdutório ou C++ básico, onde não se apresenta efetivamente alternativas, prós e contras e sugestões. E sim, induz o leitor a aceitar uma opinião pessoal como verdade sem dar espaço a dúvida.

    Mas, já que temos agora uma discussão sobre mitos, lendas e exceptions....

    Bruno Crivelari Sanches Escreveu:
    No final, os custos são:
    * um custo moderado / baixo entrando e saindo de cada bloco try/catch, dependendo de como o compilador gera o código
    * um custo alto em um throw

    Já que colocaram desempenho no assunto, como o próprio TR indica, não são apenas esses os custos não, vai além! como o uso de RTTI acabando sendo necessário e aumentando significativamente o tamanho de seus objetos.

    Bruno Crivelari Sanches Escreveu:
    Bom, no meu caso, nos meus códigos conta-se nos dedos os blocos try/catch, então acaba não sendo uma preocupação. O mesmo para o throw, que só deve ocorrer em casos excepcionais mesmo, não no timeout de um socket ou com um fileopen, então esse custo não é problema, pois se ocorre um throw, literalmente, a vaca já foi para o brejo .

    Bom, pior ainda, pois no seu caso você esta desnecessariamente inchando TODOS seus objetos (estão pagando por algo que não usam), criando sim um impacto de performance (minimo mas existente) e para não fazer proveito do que teoricamente as exceptions são boas. Crash então, se é somente por isso que usa exceptions.

    Bruno Crivelari Sanches Escreveu:
    Mas o problema todo é que, se exception tem ou não um custo de desempenho, se vai ou não impactar o seu projeto, somente com um benchmark especifico para seu projeto, não concordo muito em generalizar com "expcetions são lentas".

    Quem disse isso? Mas se quiser entrar no mérito, sugiro fazer pesquisar no google sobre opinião em blog de gamedev profissionais.... não entrarei nesse assunto, sei que a maioria aqui aceita uma perda pequena e concordo que é mais uma decisão de design, e continuo considerando uma opção válida para sistemas.

    Bruno Crivelari Sanches Escreveu:
    No artigo inclusive cita que em muitos casos os custos de if/else para checar erro causam problemas com pipeline e acabam tendo um custo alto também, no caso da exception (com exceção do throw), não existiram desvios.

    Perfeito, exatamente isso. Por isso recomenda-se o crash often, crash soon não fique tentando tratar erro.
    Em nenhum momento falei que error code é melhor que exception, falei que o artigo é tendencioso. Ok, é um artigo de nível introdutório? Pior ainda, está apresentando o seu gosto e não as possibilidades e os prós e contras.. e isso só vai criar mais opinião cega sobre o assunto sem avaliar os méritos.

    Bruno Crivelari Sanches Escreveu:
    O problema de saber e imaginar todos os possíveis fluxos de uma rotina no caso de um throw, como eu não entupo o código de try/catch, não me preocupo tanto, se ocorreu um throw, acabou mesmo a festa e o que resta a fazer é terminar o programa ou deixar o SO limpar a sujeira.

    Ok, você é um programador mais experiente e sabe do problema e como se disciplinar para evitá-lo. Mas, o artigo não comenta isso, o artigo não sugere alguma disciplina para evitar esse problema e o artigo nem ao menos considera que esse problema existe.

    Bruno Crivelari Sanches Escreveu:
    No artigo ele cita a criação de interfaces e funções de "query" para checar se uma função vai funcionar ou não, tipo o FileExists antes de fazer um Open. É uma técnica interessante, mas no fundo, cai quase no mesmo problema, que erros podem ocorrer em um Open ??? E se o SO negar acesso? E se entre o FileExists e o Open, alguém remover o arquivo?

    Bom, no próprio artigo ele mostra a diferença entre os tipos de erro, expected, unexpected e warnings. Você deve categorizar seus erros de acordo com a importância deles ao seu projeto. Cada caso é um caso aqui, e o exemplo dele é gamedev. Nessa área a tolerância a certos erros é praticamente zero, não faz sentido tratar esses tipos de casos. Já em caso de sistemas, você pode tratar com mais detalhes alguns tipos de erros.

    Bruno Crivelari Sanches Escreveu:
    Como que eu sei para uma determinada função, quais verificações preciso fazer antes de invocar ela para ter certeza que erros não ocorram? Para mim, tão oculto quanto quais exceptions um método lança.

    Não sei, exceptions é muito mais verbose, exige muito mais código. Exemplo, uma classe de exception para cada possível erro, arquivo não existente, arquivo em uso, sem permissão, etc. No fim é tanta coisa que o risco de alguém fazer uma exception genérica de erro de arquivo e meter junto é muito maior, fora que ele pode fazer catch em classe base para não ficar tratando caso a caso os erros.. try {} catch (Exception e) {} .... você sim aqui está confiando que o cara que vai usar, use da forma que você espere que ele use, e por vez, ele terá ...

  • Vinícius Godoy de Mendonça
    avatar
    "Danny" Escreveu:
    Principalmente sendo um artigo introdutório ou C++ básico, onde não se apresenta efetivamente alternativas, prós e contras e sugestões.

    Quanto ao método. Primeiramente, é importante apresentar o mecanismo, que é o objetivo desse artigo. Considere que o leitor nem sequer entende ainda o que são exceptions, ou mesmo como dispará-las. Mais para frente, apresenta-se prós, contras e sugestões.

    Danny, no artigo eu não apresento "as alternativas". Eu falei apenas de uma única alternativa, e dos problemas que essa alternativa apresentava e que as exceptions foram criadas especificamente para resolver.

    "Danny" Escreveu:
    criando sim um impacto de performance (minimo mas existente)

    Aí eu discordo. A frase "o impacto é mínimo mas existe" é geralmente fruto de preciosismo. Se o impacto não estiver no gargalo de sua aplicação, ou não for visível para o usuário, ele efetivamente não existe. Otimizar isso só fará sua aplicação esperar mais tempo pelo gargalo real.

    Seria melhor perder tempo fazendo profilers e encontrando locais lentos mais significativos em sua aplicação.

    Agora, uma coisa que você falou é fato. Tudo depende da equipe com quem você desenvolve, do que você desenvolve e para quem. Em engines de jogos mais hardcore, ou jogos para console, ou mesmo numa equipe de pessoal bem qualificado, é muito tranquilo substituir exceções por um mecanismo mais simples, e assumir um comportamento mais positivo por parte do usuário, ou do ambiente.

    O fato é que já desenvolvi também muitos sistemas soft real time (como os próprios jogos e sistemas de telecomunicações), com exceptions, e o uso desse mecanismo jamais sinalizou qualquer problema de performance ou consumo exagerado de memória, em nenhum profiler.

    Creio que muito dos desenvolvedores de consoles ainda desprezem o mecanismo por trabalharem em jogos AAA, que realmente exigem o último suspiro do hardware, e também por cultura - já que no passado os hardwares poderiam realmente impor limitações ao mecanismo.

  • Bruno Crivelari Sanches
    avatar
    Citou:
    Já que colocaram desempenho no assunto, como o próprio TR indica, não são apenas esses os custos não, vai além! como o uso de RTTI acabando sendo necessário e aumentando significativamente o tamanho de seus objetos.

    O problema é justamente definir o que é certo ou errado, o que é bom ou problema. O RTTI só vai ter efeito até onde sei nas clausulas do catch, para poder definir quem deve tratar qual exceção.

    Citou:
    Bom, pior ainda, pois no seu caso você esta desnecessariamente inchando TODOS seus objetos (estão pagando por algo que não usam), criando sim um impacto de performance (minimo mas existente) e para não fazer proveito do que teoricamente as exceptions são boas. Crash então, se é somente por isso que usa exceptions.

    Não é o único motivo, mas é para 90% dos casos. Sem falar que simplifica um bocado a vida na hora de depurar, o depurador já lhe informa exatamente onde ocorreu o throw que causou o problema, se o programa se matar, a não ser que ele tenha um mecanismos próprio de dar um stacktrace, fica complicado achar em que ponto ocorreu o erro e com quais parâmetros.

    Citou:
    Quem disse isso? Mas se quiser entrar no mérito, sugiro fazer pesquisar no google sobre opinião em blog de gamedev profissionais.... não entrarei nesse assunto, sei que a maioria aqui aceita uma perda pequena e concordo que é mais uma decisão de design, e continuo considerando uma opção válida para sistemas.

    Eu disse, você levantou que o artigo não cita os contras de exceptions, eu comentei sobre os contras que conheço, pode ser que existam outros e gostaria de conhece-los se souber de mais algum.

    Os que conheço e lembro agora são:
    * Desempenho: que é altamente discutível e não consigo generalizar dizendo que é lentou ou mais rápido que as alternativas
    * Tamanho de código gerado: novamente, depende muito do seu contexto e do seu problema, até mesmo num jogo
    * Não saber o que uma função pode dar de throw: esse é o unico realmente problemático e depende mais de disciplina e forma de trabalhar no código e esse é o único que o C++ não ajuda em nada, podemos dizer que a função é no throw (no velho e novo padrão), mas nem todo mundo implementa, então ficamos a pé aqui, tendo que passar o problema para documentação. Mas o mesmo problema pode ocorrer com códigos de erros e outras formas, a unica solução é disciplinar a forma como isso é feito e nada impede disso se aplicar a exceções.

    Citou:
    Ok, você é um programador mais experiente e sabe do problema e como se disciplinar para evitá-lo. Mas, o artigo não comenta isso, o artigo não sugere alguma disciplina para evitar esse problema e o artigo nem ao menos considera que esse problema existe.

    Já tem um próximo artigo pronto sobre isso, sai na sequencia.

    Citou:
    Bom, no próprio artigo ele mostra a diferença entre os tipos de erro, expected, unexpected e warnings. Você deve categorizar seus erros de acordo com a importância deles ao seu projeto. Cada caso é um caso aqui, e o exemplo dele é gamedev. Nessa área a tolerância a certos erros é praticamente zero, não faz sentido tratar esses tipos de casos. Já em caso de sistemas, você pode tratar com mais detalhes alguns tipos de erros.

    Certos erros não fazem sentido nesse ambiente, mas não quer dizer que não ocorram. Nos artigos citados, provavelmente ele vai abortar a execução, um outro jogo pode dar throw, cada caso é um caso.

    Eu jogo um jogo que ao rodar precisa pedir elevação do nível de usuário para super usuário no Windows por causa de mods e modificações, claro que existem alternativas para isso, mas foi a forma que eles adotaram de resolver.

    Citou:
    Não sei, exceptions é muito mais verbose, exige muito mais código. Exemplo, uma classe de exception para cada possível erro, arquivo não existente, arquivo em uso, sem permissão, etc. No fim é tanta coisa que o risco de alguém fazer uma exception genérica de erro de arquivo e meter junto é muito maior, fora que ele pode fazer catch em classe base para não ficar tratando caso a caso os erros.. try {} catch (Exception e) {} .... você sim aqui está confiando que o cara que vai usar, use da forma que você espere que ele use, e por vez, ele terá ...

    Não sei se entendi bem, concordo quanto ao verbose de ter criar uma exception para cada tipo de erro, mas sinceramente não costumo fazer, costumo usar uma versão mais genérica, tipo "FileOperationException" e nela além de incluir descrição, incluir códigos de erro que possam ser relevantes.

    Cai no mesmo problema de código de erro ou como citam no artigo, de generalizar, a questão é, até onde generalizar. O que é relevante para seu contexto e o que não é?

    Sem falar que a proposta dele de estrutura de erro acaba deixando meio verbose também :).

    ----------------

    Eu concordo em diversos pontos sobre exceptions, o problema, até mesmo para jogos, é definir o que é relevante e o que realmente é problema.

    Em um Unreal engine pode ser que um sistema erros que use exceptions tenha u...

  • Bruno Crivelari Sanches
    avatar

    Continuando...

    Em um Unreal engine pode ser que um sistema erros que use exceptions tenha um custo proibitivo ou que, digamos, 1% de impacto (que pode ser mais ou menos) seja fator relevante para ele se destacar ou falhar miseravelmente.

    Mas ao mesmo tempo, temos milhares de jogos por ai a fora onde desempenho, tamanho de objetos, RTTI são os menores dos problemas e não impactam em nada ou tem um impacto insignificante.

    No final das contas, fico no muro, não digo que é ruim ou bom, mas tem seus prós e contras. Nos próximos artigos esperamos poder elucidar os contras também.

    Alias, falando em exceptions, quando que vou poder invocar o catch o abaixo:

    Código:

    catch(DannySentAnArticleToPontoV &e)
    {
    ....
    }

    :cheer:

    Abraços

  • Danny Angelo Carminati Grein
    avatar

    Hahahahahahahahhaha, não vai funcionar esse catch, não emito exceptions, eu crasho! :P

    Seguinte, arruma esse treco de comentários!!!!!!!!!!!

    1. Ele corta comentarios longos, mas se você clicar em "citar" você consegue ler o comentário inteiro. Não falta um botão de expandir não?
    2. Se você troca de "Responder" para "Citar" ele sai do nivel da thread e vai para a base! Why?!?!
    3. As vezes os dados de contato somem, não entendi quando acontece.. mas postei como anonimo por isso.

  • Bruno Crivelari Sanches
    avatar

    A gente tem que trocar, o problema que é o menos pior que achamos até agora e as versões novas não resolvem algum desses problemas :(.

  • Anônimo  - re:
    avatar
    Vinícius Godoy de Mendonça Escreveu:
    Danny, no artigo eu não apresento "as alternativas". Eu falei apenas de uma única alternativa, e dos problemas que ela apresentava e que as exceptions foram criadas especificamente para resolver.
    Artigo Escreveu:
    Nesse artigo, iremos estudar diferentes formas de resolver o problema: códigos de erro e o poderoso mecanismo de exceções do C++.

    Então eu entendi errado ou o texto está confuso?

    Vinícius Godoy de Mendonça Escreveu:
    "Danny" Escreveu:
    criando sim um impacto de performance (minimo mas existente)


    Aí eu discordo. O impacto é mínimo, que muitas vezes não é existente, ou ser insignificante ao ponto de ser imperceptível, sendo portanto preciosismo se incomodar com ele.

    Seria melhor perder tempo fazendo profilers e encontrando gargalos significativos em sua aplicação.

    [/quote]

    Não entendi, você discorda concordando? O disse:

    "Danny" Escreveu:
    criando sim um impacto de performance (minimo mas existente)

    e você:

    Vinícius Godoy de Mendonça Escreveu:
    O impacto é mínimo, que muitas vezes não é existente, ou ser insignificante ao ponto de ser imperceptível

    Considerando que a parte "que muitas vezes não é existente" é ERRADA, como a própria TR mostra. Você confirmou o que eu disse.

    Em nenhum momento citei que exceptions são lentas, que temos que otimizar exceptions ou não usar porque seu fps vai cair de 60 para 59 (se bem que grandes desenvolvedores realmente precisam desse 1fps extra e fazem de tudo para obtê-lo até mesmo recorrer ao "preciosismo".. ao exemplo clássico do God Of War 3, google it).

  • Danny Angelo Carminati Grein
    avatar
    Vinícius Godoy de Mendonça Escreveu:
    Creio que muito dos desenvolvedores de consoles ainda desprezem o mecanismo por trabalharem em jogos AAA, que realmente exigem o último suspiro do hardware, e também por cultura - já que no passado os hardwares poderiam realmente impor limitações ao mecanismo.

    Não estamos falando de Gameboy nem Atari... isso é fato e ocorre em PS3, iPhone, Android, que não são passadas.

    De qualquer forma, minha opinião é sobre a forma de apresentar como a solução mágica. Não sou contra o uso, sou contra o uso impensado ou fé cega.
    Vocês que estão levando o assunto para uma discussão religiosa.

  • Bruno Crivelari Sanches
    avatar

    Não vejo como uma discussão religiosa, o problema é justamente não ter fatos concretos sobre isso.

    A industria de jogos tem essa tara por desempenho que realmente, é fator de ganhar ou perder mercado em jogos, principalmente AAA, mas fica a questão:

    Todo jogo em PS3, Android, iPhone e demais plataformas onde C++ em jogos seja relevante são CPU bound de forma que o custo de se usar exceptions (existindo ou não, seja lá quanto for) é impactante?

    T+

  • Danny Angelo Carminati Grein
    avatar

    Não. Depende de caso a caso como qualquer outra coisa. Depende de muitas variáveis o que faz ser mais necessário conhecer os prós e contras de todas alternativas e não sair aceitando qualquer ideia pronta.

  • Bruno Crivelari Sanches
    avatar

    No próximo artigo vamos por os poréns e é claro linkar ambos! :)

  • Anônimo
    avatar

    Faço das palavras do Antonio Arleudo da Costa as minhas!
    Parabéns, Vinny!

  • Fabiano Vasconcelos
    avatar

    Faço das palavras do Antonio Arleudo da Costa as minhas!
    Excelente post!
    Parabéns, Vinny! :silly:

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter