Um getter deve lançar uma exceção se seu objeto tiver um estado inválido?

54

Frequentemente me deparo com esse problema, especialmente em Java, mesmo que eu ache que seja um problema geral de POO. Ou seja: levantar uma exceção revela um problema de design.

Suponha que eu tenha uma classe que tenha um campo String name e um String surname .

Em seguida, ele usa esses campos para compor o nome completo de uma pessoa para exibi-la em algum tipo de documento, por exemplo, uma fatura.

public void String name;
public void String surname;

public String getCompleteName() {return name + " " + surname;}

public void displayCompleteNameOnInvoice() {
    String completeName = getCompleteName();
    //do something with it....
}

Agora eu quero reforçar o comportamento da minha classe lançando um erro se o displayCompleteNameOnInvoice for chamado antes de o nome ser atribuído. Parece uma boa ideia, não é?

Eu posso adicionar um código de aumento de exceção ao método getCompleteName . Mas, dessa maneira, estou violando um contrato "implícito" com o usuário da classe; em geral, os getters não devem lançar exceções se seus valores não estiverem definidos. Ok, isso não é um padrão, pois não retorna um único campo, mas do ponto de vista do usuário a distinção pode ser muito sutil para pensar sobre isso.

Ou posso lançar a exceção de dentro do displayCompleteNameOnInvoice . Mas, para fazer isso, eu deveria testar diretamente os campos name ou surname e, assim, violaria a abstração representada por getCompleteName . É responsabilidade desse método verificar e criar o nome completo. Pode até decidir, baseando a decisão em outros dados, que em alguns casos é suficiente o surname .

So a única possibilidade parece mudar a semântica do método getCompleteName para composeCompleteName , que sugere um comportamento mais "ativo" e, com isso, a capacidade de lançar uma exceção.

Esta é a melhor solução de design? Estou sempre procurando o melhor equilíbrio entre simplicidade e correção. Existe uma referência de design para este problema?

    
por AgostinoX 02.11.2014 / 16:00
fonte

6 respostas

149

Não permita que sua turma seja construída sem atribuir um nome.

    
por 02.11.2014 / 16:56
fonte
74

A resposta fornecida pela DeadMG praticamente a une, mas deixe-me dizer de forma um pouco diferente: Evite ter objetos com estado inválido. Um objeto deve ser "inteiro" no contexto da tarefa que cumpre. Ou não deveria existir.

Então, se você quiser fluidez, use algo como o Padrão do construtor . Ou qualquer outra coisa que seja uma reificação separada de um objeto em construção em oposição à "coisa real", onde a última é garantida como tendo estado válido e é a que realmente expõe as operações definidas naquele estado (por exemplo, getCompleteName ).

    
por 02.11.2014 / 18:39
fonte
39

Não tem um objeto com um estado inválido.

O propósito de um construtor ou construtor é definir o estado do objeto para algo consistente e utilizável. Sem essa garantia, os problemas surgem com o estado inicializado incorretamente nos objetos. Esse problema se torna agravado se você estiver lidando com a simultaneidade e algo puder acessar o objeto antes de você terminar de configurá-lo completamente.

Então, a pergunta para esta parte é "por que você está permitindo que o nome ou sobrenome seja nulo?" Se isso não for algo válido na aula para que funcione corretamente, não permita. Tenha um construtor ou construtor que valide corretamente a criação do objeto e, se não estiver certo, eleve o problema nesse ponto. Você também pode usar uma das @NotNull annotations que existe para ajudar a comunicar que "isso não pode ser nulo" e aplicar na codificação e análise.

Com esta garantia, torna-se muito mais fácil raciocinar sobre o código, o que ele faz, e não ter que lançar exceções em lugares estranhos ou colocar cheques excessivos em torno das funções do getter.

Getters que fazem mais.

Há um pouco por aí sobre este assunto. Você tem Quanta lógica em getters e daqui e getters e setters executando lógica adicional no Stack Overflow. Este é um problema que surge de novo e de novo no design de classes.

O núcleo disso vem de:

in general getters aren't supposed to throw exceptions if their values aren't set

E você está certo. Eles não deveriam. Eles devem parecer "burros" para o resto do mundo e fazer o que é esperado. Colocando muita lógica lá leva a questões onde a lei do menor assombro é violada. E vamos encarar isso, você realmente não quer envolver os getters com try catch porque sabemos o quanto amamos fazer isso em geral.

Existem também situações em que você deve usar um método getFoo , como o framework JavaBeans, e quando você tem algo de EL chamando esperando obter um bean (para que <% bar.foo %> chame getFoo() na classe - deixando de lado o 'caso o bean esteja fazendo a composição ou Isso deve ser deixado para a visão? 'porque pode-se facilmente chegar a situações em que um ou outro pode claramente ser a resposta certa)

Perceba também que é possível que um determinado getter seja especificado em uma interface ou que tenha sido parte da API pública exposta anteriormente para a classe que está sendo refatorada (uma versão anterior tinha apenas 'completeName' que foi retornada e uma refatoração quebrou em dois campos).

No final do dia ...

Faça a coisa mais fácil de raciocinar. Você gastará mais tempo mantendo esse código do que gastará planejando (embora quanto menos tempo você gasta projetando, mais tempo você gastará mantendo). A escolha ideal é projetá-lo de tal forma que não leve tanto tempo para manter, mas não fique sentado pensando nisso por dias - a menos que seja realmente uma escolha de design que irá em> tem dias de implicações mais tarde.

Tentando ficar com a pureza semântica do getter sendo private T foo; T getFoo() { return foo; } você terá problemas em algum momento. Às vezes, o código simplesmente não se encaixa nesse modelo e as contorções que alguém passa para tentar mantê-lo assim não fazem sentido ... e, no final das contas, dificultam o design.

Aceite às vezes que os ideais do design não podem ser percebidos da maneira que você deseja no código. Diretrizes são diretrizes - não algemas.

    
por 02.11.2014 / 22:20
fonte
5

Eu não sou um cara de Java, mas isso parece aderir a ambas as restrições que você apresentou.

getCompleteName não lança uma exceção se os nomes não forem inicializados e displayCompleteNameOnInvoice .

public String getCompleteName()
{
    if (name == null || surname == null)
    {
        return null;
    }
    return name + " " + surname;
}

public void displayCompleteNameOnInvoice() 
{
    String completeName = getCompleteName();
    if (completeName == null)
    {
        //throw an exception.
    }
    //do something with it....
}
    
por 02.11.2014 / 17:22
fonte
4

Parece que ninguém está respondendo sua pergunta.
Ao contrário do que as pessoas gostam de acreditar, "evitar objetos inválidos" geralmente não é uma solução prática.

A resposta é:

Sim, deveria.

Exemplo simples: FileStream.Length em C # /. NET , que lança NotSupportedException se o arquivo não for pesquisável.

    
por 04.11.2014 / 08:48
fonte
1

Você tem toda a coisa do nome de verificação de cabeça para baixo.

getCompleteName() não precisa saber quais funções o utilizarão. Portanto, não ter um name ao chamar displayCompleteNameOnInvoice() não é responsabilidade de getCompleteName() .

Mas, name é um pré-requisito claro para displayCompleteNameOnInvoice() . Assim, deve assumir a responsabilidade de verificar o estado (ou deve delegar a responsabilidade de verificar a outro método, se preferir).

    
por 05.11.2014 / 10:43
fonte