Temos este código que, quando simplificado, fica assim:
public class Room
{
public Client Client { get; set; }
public long ClientId
{
get
{
return Client == null ? 0 : Client.Id;
}
}
}
public class Client
{
public long Id { get; set; }
}
Agora temos três pontos de vista.
1) Este é um bom código porque a Client
propriedade deve sempre ser configurada (ou seja, não nula), para Client == null
que nunca ocorra e o valor do Id 0
denote um ID falso de qualquer maneira (essa é a opinião do autor do código ;-))
2) Você não pode confiar no chamador para saber que 0
é um valor falso para Id
e quando a Client
propriedade sempre deve ser definida, você deve lançar um exception
no get
quando a Client
propriedade for nula
3) Quando a Client
propriedade sempre deve ser definida, basta retornar Client.Id
e deixar o código lançar uma NullRef
exceção quando a Client
propriedade for nula.
Qual destes está mais correto? Ou existe uma quarta possibilidade?
fonte
Respostas:
Parece que você deve limitar o número de estados em que sua
Room
classe pode estar.O fato de você estar perguntando sobre o que fazer quando
Client
é nulo é uma dica de queRoom
o espaço de estado é muito grande.Para simplificar, não permitiria que a
Client
propriedade de nenhumaRoom
instância fosse nula. Isso significa que o código dentroRoom
pode assumir com segurança queClient
nunca é nulo.Se por algum motivo no futuro
Client
torna-senull
resistir ao desejo de apoiar esse estado. Fazer isso aumentará sua complexidade de manutenção.Em vez disso, permita que o código falhe e falhe rapidamente. Afinal, este não é um estado suportado. Se o aplicativo entrar nesse estado, você já cruzou uma linha sem retorno. A única coisa razoável a fazer é fechar o aplicativo.
Isso pode acontecer (naturalmente) como resultado de uma exceção de referência nula não tratada.
fonte
null
valor por umNullObject
(ou melhorNullClient
) que ainda funcionaria com o código existente e permitir definir o comportamento para um cliente inexistente, se necessário. A questão é se o caso "sem cliente" faz parte da lógica de negócios ou não.Apenas algumas considerações:
a) Por que existe um getter específico para o ClientId quando já existe um getter público para a própria instância do Client? Não vejo por que as informações do ClientId precisam
long
ser gravadas em pedra na assinatura do Room.b) Em relação à segunda opinião, você pode introduzir uma constante
Invalid_Client_Id
.c) Em relação à opinião um e três (e sendo meu ponto principal): Uma sala deve sempre ter um cliente? Talvez seja apenas semântica, mas isso não parece certo. Talvez seja mais apropriado ter classes completamente separadas para Room e Client e outra classe que as una. Talvez nomeação, reserva, ocupação? (Isso depende do que você está realmente fazendo.) E nessa classe, você pode impor as restrições "deve ter uma sala" e "deve ter um cliente".
fonte
Não concordo com as três opiniões. Se
Client
nunca pode sernull
, nem torne possível que sejanull
!Client
no construtorArgumentNullException
no construtor.Portanto, seu código seria algo como:
Isso não tem relação com o seu código, mas você provavelmente usaria uma versão imutável de
Room
makingtheClient
readonly
, e se o cliente mudar, criando uma nova sala. Isso melhorará a segurança do encadeamento do seu código, além da segurança nula dos outros aspectos da minha resposta. Veja esta discussão sobre mutável vs imutávelfonte
ArgumentNullException
s?NullReferenceException
, é lançado pela .Net VM "quando houver uma tentativa de desreferenciar uma referência de objeto nulo" . Você deve lançar umArgumentNullException
, que é lançado "quando uma referência nula (Nada no Visual Basic) é passada para um método que não o aceita como argumento válido".readonly
). Eu teria acabado de editar, mas senti que um comentário serviria para explicar melhor o porquê (para futuros leitores). Se você ainda não tivesse dado essa resposta, eu daria exatamente a mesma solução. Imutabilidade também é uma boa ideia, mas nem sempre é tão fácil quanto se espera.As segunda e terceira opções devem ser evitadas - o atendente não deve bater no chamador com uma exceção sobre a qual eles não têm controle.
Você deve decidir se o Cliente pode ser nulo. Nesse caso, você deve fornecer uma maneira para um chamador verificar se é nulo antes de acessá-lo (por exemplo, propriedade booleana ClientIsNull).
Se você decidir que o Cliente nunca pode ser nulo, torne-o um parâmetro obrigatório para o construtor e lance a exceção se um nulo for passado.
Finalmente, a primeira opção também contém um cheiro de código. Você deve deixar o Cliente lidar com sua própria propriedade de ID. Parece um exagero codificar um getter em uma classe de contêiner que simplesmente chama um getter na classe contida. Apenas exponha o cliente como uma propriedade (caso contrário, você duplicará tudo o que o cliente já oferece ).
Se o Cliente puder ser nulo, você pelo menos dará responsabilidade ao chamador:
fonte
Se uma propriedade nula do cliente for um estado suportado, considere usar um NullObject .
Mas, provavelmente, esse é um estado excepcional, portanto, você deve tornar impossível (ou não muito conveniente) acabar com um valor nulo
Client
:Se não for suportado, no entanto, não perca tempo com soluções meio cozidas. Nesse caso:
Você 'resolveu' a NullReferenceException aqui, mas a um grande custo! Isso forçaria todos os chamadores de
Room.ClientId
a verificarem 0:Bugs estranhos podem surgir com outros desenvolvedores (ou você mesmo!) Esquecendo de verificar o valor de retorno "ErrorCode" em algum momento.
Jogue pelo seguro e falhe rápido. Permita que a NullReferenceException seja lançada e "graciosamente" a pegue em algum lugar mais alto na pilha de chamadas, em vez de permitir que o chamador estrague ainda mais as coisas ...
Em outra nota, se você deseja expor o
Client
e tambémClientId
pensar sobre o TellDontAsk . Se você pedir demais aos objetos, em vez de dizer a eles o que deseja alcançar, poderá acabar acoplando tudo, dificultando as mudanças mais tarde.fonte
NotSupportedException
está sendo usado de maneira incorreta; você deve usar umArgumentNullException
.A partir do c # 6.0, agora lançado, você deve fazer isso,
Criar uma propriedade de
Client
toRoom
é uma violação óbvia do encapsulamento e do princípio DRY.Se você precisar acessar o
Id
de umClient
de um,Room
você pode fazer,Observe o uso do operador condicional nulo ,
clientId
será umlong?
, seClient
énull
,clientId
seránull
, caso contrárioclientId
terá o valor deClient.Id
.fonte
clientid
é um longo anulável, então?var clientId = room.Client.Id
será seguro elong
.