Estou tendo uma discussão com meu colega de trabalho sobre quanto trabalho um construtor pode fazer. Eu tenho uma classe B que internamente requer outro objeto A. O objeto A é um dos poucos membros que a classe B precisa fazer seu trabalho. Todos os seus métodos públicos dependem do objeto interno A. As informações sobre o objeto A são armazenadas no banco de dados, então eu tento validar e obtê-lo procurando-o no banco de dados no construtor. Meu colega de trabalho apontou que o construtor não deve fazer muito trabalho além de capturar os parâmetros do construtor. Como todos os métodos públicos falhariam de qualquer maneira, se o objeto A não fosse encontrado usando as entradas do construtor, argumentei que, em vez de permitir que uma instância seja criada e depois falhe, é melhor lançar o construtor mais cedo.
O que os outros pensam? Eu estou usando c # se isso faz alguma diferença.
Leitura Existe alguma razão para fazer todo o trabalho de um objeto em um construtor? Gostaria de saber que a busca do objeto A, indo ao DB, faz parte de "qualquer outra inicialização necessária para tornar o objeto pronto para uso" porque, se o usuário passasse valores incorretos para o construtor, eu não seria capaz de usar nenhum de seus métodos públicos.
Os construtores devem instanciar os campos de um objeto e fazer qualquer outra inicialização necessária para tornar o objeto pronto para uso. Isso geralmente significa que os construtores são pequenos, mas há cenários em que isso seria uma quantidade substancial de trabalho.
fonte
Respostas:
Sua pergunta é composta de duas partes completamente separadas:
Devo lançar exceção do construtor, ou devo ter método falhar?
Esta é claramente a aplicação do princípio Fail-fast . Fazer com que o construtor falhe é muito mais fácil de depurar do que descobrir por que o método está falhando. Por exemplo, você pode obter a instância já criada a partir de outra parte do código e obter erros ao chamar métodos. É óbvio que o objeto foi criado errado? Não.
Quanto ao problema "encerre a chamada em try / catch". Exceções devem ser excepcionais . Se você souber que algum código lançará uma exceção, você não quebrará o código em try / catch, mas validará os parâmetros desse código antes de executar o código que pode lançar uma exceção. Exceções são apenas uma forma de garantir que o sistema não entre em estado inválido. Se você sabe que alguns parâmetros de entrada podem levar a um estado inválido, verifique se esses parâmetros nunca acontecem. Dessa forma, você só precisa tentar / capturar em locais onde é possível lidar logicamente com a exceção, que geralmente fica nos limites do sistema.
Posso acessar "outras partes do sistema, como DB" do construtor.
Eu acho que isso vai contra o princípio de menor espanto . Poucas pessoas esperariam que o construtor acesse um banco de dados. Então não, você não deve fazer isso.
fonte
Ugh.
Um construtor deve fazer o mínimo possível - o problema é que o comportamento de exceção nos construtores é estranho. Muito poucos programadores sabem qual é o comportamento adequado (incluindo cenários de herança) e forçar seus usuários a tentar / capturar cada instanciação é ... doloroso, na melhor das hipóteses.
Há duas partes nisso, no construtor e no uso do construtor. É constrangedor no construtor porque você não pode fazer muita coisa quando ocorre uma exceção. Você não pode retornar um tipo diferente. Você não pode retornar nulo. Basicamente, você pode repetir a exceção, retornar um objeto quebrado (mau construtor) ou (na melhor das hipóteses) substituir alguma parte interna por um padrão sadio. Usar o construtor é estranho, porque suas instanciações (e as instâncias de todos os tipos derivados!) Podem ser lançadas. Então você não pode usá-los nos inicializadores de membros. Você acaba usando exceções para que a lógica veja "minha criação teve sucesso?", Além da legibilidade (e fragilidade) causada pela tentativa / captura em todos os lugares.
Se o seu construtor estiver fazendo coisas não triviais, ou coisas que possam razoavelmente falhar, considere um
Create
método estático (com um construtor não público). É muito mais utilizável, mais fácil de depurar e ainda oferece uma instância totalmente construída quando bem-sucedida.fonte
Connection
pode um objetoCreateCommand
para você, para que você saiba que o comando está conectado adequadamente.O equilíbrio entre complexidade do construtor e do método é realmente uma questão de discussão sobre o bom estilo. Muitas vezes,
Class() {}
é usado um construtor vazio , com um métodoInit(..) {..}
para coisas mais complicadas. Entre os argumentos a serem levados em consideração:Class x = new Class(); x.Init(..);
várias vezes, parcialmente nos testes de unidade. Não é tão bom, porém, claro para a leitura. Às vezes, eu apenas os coloco em uma linha de código.fonte
init
método é que lidar com falhas é muito mais fácil. Em particular, o valor de retorno doinit
método pode indicar se a inicialização foi bem-sucedida e o método pode garantir que o objeto esteja sempre em bom estado.