Solucionado (ver solução)
Solucionado
(ver solução)
2
respostas

Trows / exception

Ao mudar minha classe Exception de extends RuntimeException para apenas extends Exception, na classe que eventualmente eu lanço a Exception(throw), obrigatoriamente eu tenho que avisar a classe que possivelmente eu possa lançar essa exceção, utilizando assim o Trows na classe referente. Caso eu não utilize o Try/Catch para tratar dessa exceção, todas as classes relacionadas, até mesmo a main do qual eu não lanço a exceção, vai ser obrigatório usar o Throws para avisar do possível uso desse recurso? Porque ao realizar a atividade e eu não tratar da exceção, o eclipse apontou erros, e solicitou a colocação do Trows nas classes das quais estão relacionadas. Isso é devido eu não ter segurado a exceção e ela vazou até a main? E caso seja isso, o quão ruim essa pratica é para um profissional?

2 respostas
solução!

Fala Rodrigo, tudo bem ?

Esse assunto é bem interessante. A ideia geral do throws é mais para dar liberdade para o programador escolher onde quer tratar (try/catch) o erro.

Se tentássemos estabelecer uma regrinha sobre esses mecanismos poderíamos fazer mais ou menos assim.

  1. Toda exception filha de (ou a própria) RuntimeException, tem seu lançamento (throw) e seu tratamento (try/catch) livre de qualquer checagem em tempo de compilação. Em geral, tratam de casos mais simples, onde é possível o desenvolvedor corrigir via código o comportamento que está quebrando o fluxo normal da app. Exemplo: NullPointerException, ArrayIndexOutOfBoundsException, etc.

  2. Qualquer exception, não filha de RuntimeException (checked), tem seu comportamento checado pelo compilador. Em geral, tratam de casos mais críticos. Exemplo: SQLException, IOException (onde estamos saindo dos limites da JVM, dependendo de serviços externos, que nem sempre vão ter comportamento esperado pela aplicação). Nesses casos toda lançamento de exceção desse tipo gera uma checagem do compilador, a fim de proteger a JVM dessa possível falha grave. A checagem consiste em:

2.1. Verificar se o programador que lançou essa exception mais grave está prevendo a recuperação da falha via try {} catch(){}. Onde a punição pelo não tratamento (ausência de try/catch) gera um erro de compilação.

O problema é que a compilação tem como escopo apenas a própria classe que está sendo compilada, o que pode causar problemas. Imagine um cenário onde nosso código lança uma exception do tipo checada (checked exception)...

class Conta {

    void saca(double valor) {
        if(valor > this.saldo) {
            throw new SaldoInsuficienteException(); // que é filha direta de Exception
        }
        this.saldo -= valor;
    }
}

// Esse código não compila
// O compilador não está vendo o problema ser tratado nesta classe

Só poderíamos nos recuperar da falha adicionando try/catch, no próprio local (dado que em outra classe, o compilador não levaria em conta). Sendo assim teríamos um código estranho:

class Conta {

    void saca(double valor) {
        try {
            if(valor > this.saldo) {
                throw new SaldoInsuficienteException(); // que é filha direta de Exception                          }
            }
            this.saldo -= valor;
        } catch(SaldoInsuficienteException e) {
            // Essa seria a única forma de fazer a checagem compilar
            // Porque vou lançar e recuperar no mesmo ponto ??? Não faz nenhum sentido
        }
    }
}

O código acima mostra o problema, se quisermos lançar uma exceção prevendo sua recuperação em outro ponto do sistema o compilador não vai deixar. Por isso criaram outro recurso, que serve como lenitivo para esse primeiro.

Ao invés de a checagem apenas pensar em verificar a recuperação, ela faz uma segunda verificação como fallback, verificar se estamos notificando os usuários desta operação (potencialmente lesiva a JVM) que podemos lançar uma exception (deste tipo mais crítico).

2.2 A segunda checagem verifica se usamos a clausula throws na assinatura do método, deixando claro a quem o invoca do possível perigo. Assim podemos lançar com tranquilidade e recuperar em outro ponto ( assim como fazíamos com qualquer outra exceção de runtime =) )

Exemplo:

class Conta {

    void saca(double valor) throws SaldoInsuficienteException { // avisando aqui, deixando claro na API do método/classe que pode dar problema
        if(valor > this.saldo) {
            throw new SaldoInsuficienteException(); // que é filha direta de Exception                          }
        }
        this.saldo -= valor;
    }
}

e agora quem usa:

class TestaConta {
    main(...) {
        Conta conta = new Conta();
        conta.deposita(500);

        // ...
        conta.saca(600); 
        caixa.emiteDinheiro(600);
    }
 }

Agora, como o método avisou que podia dar problema o código da classe teste acima não compila ao fazer o saque, dado que sabendo do problema, não podemos neste ponto ser negligentes. Seremos obrigados novamente a pensar em recuperar a falha no local (que seria a melhor solução)...

class TestaConta {
    main(...) {
        Conta conta = new Conta();
        conta.deposita(500);

        // ...
        try {
            conta.saca(600); 
            caixa.emiteDinheiro(600);
        } catch(SaldoInsuficienteException e) {
            System.out.println("Saldo insuficiente! =/");
        }
    }
 }

.. ou delegar pra cima novamente usando throws. Mas aqui vale ressaltar justamente o que você observou, se o método main avisar com throws, estamos deixando vazar essa exception que deveria ser tratada, o que configura uma má prática. Esse segundo mecanismo continua disponível, porque, caso quem utilize o saca() não fosse o código do main você poderia querer continuar delegando para cima para tratar em outro local. (em resumo, para não engessar o desenvolvedor, obrigando tratamento sempre no ponto que logo usa o método).

Espero ter sido claro e ter ajudado na compreensão. Abraço!

Esclarecedor! Muito obrigado! Sucesso amigo!