Quando você deve e não deve usar a palavra-chave 'nova'?

15

Eu assisti a uma apresentação do Google Tech Talk sobre testes de unidade , ministrada por Misko Hevery, e ele disse para evitar o uso da newpalavra - chave no código de lógica de negócios.

Eu escrevi um programa e acabei usando a newpalavra - chave aqui e ali, mas eles eram principalmente para instanciar objetos que contêm dados (ou seja, eles não tinham nenhuma função ou método).

Eu estou pensando, eu fiz algo errado quando usei a nova palavra-chave para o meu programa. E onde podemos quebrar essa 'regra'?

Sal
fonte
2
Você pode adicionar uma tag relacionada à linguagem de programação? O novo existe em muitas linguagens (C ++, Java, Ruby, para citar algumas) e possui semânticas diferentes.
sakisk

Respostas:

16

Isso é mais orientação do que regra rígida e rápida.

Ao usar "novo" no seu código de produção, você está acoplando sua classe aos colaboradores. Se alguém quiser usar outro colaborador, por exemplo, algum tipo de colaborador simulado para teste de unidade, não poderá - porque o colaborador é criado na sua lógica de negócios.

Obviamente, alguém precisa criar esses novos objetos, mas geralmente é melhor deixar para um destes dois lugares: uma estrutura de injeção de dependência como Spring, ou então a classe que instancia sua classe de lógica de negócios, injetada pelo construtor.

Claro, você pode levar isso longe demais. Se você deseja retornar um novo ArrayList, provavelmente está bem - especialmente se esta for uma lista imutável.

A principal pergunta que você deve se fazer é "é a principal responsabilidade desse pedaço de código criar objetos desse tipo ou é apenas um detalhe de implementação que eu poderia razoavelmente mover em outro lugar?"

Bill Michell
fonte
1
Bem, se você quer que seja uma lista imutável, você deve usar Collections.unmodifiableListou algo assim. Mas eu sei o que dizer :)
MatrixFrog
Sim, mas você precisa de alguma forma para criar a lista original que você, em seguida, converter a ser unmodifiable ...
Bill Michell
5

O cerne desta questão está no final: estou me perguntando, eu fiz algo errado quando usei a nova palavra-chave para o meu programa. E onde podemos quebrar essa 'regra'?

Se você é capaz de escrever testes de unidade eficazes para o seu código, não fez nada de errado. Se o seu uso newtornou difícil ou impossível testar o seu código de unidade, você deve reavaliar o uso do novo. Você pode estender essa análise para interagir com outras classes, mas a capacidade de escrever testes de unidade sólidos geralmente é um proxy suficientemente bom.

ObscureRobot
fonte
5

Em resumo, sempre que você usa "new", você está acoplando fortemente a classe que contém esse código ao objeto que está sendo criado; para instanciar um desses objetos, a classe que está instanciando deve saber sobre a classe concreta que está sendo instanciada. Portanto, ao usar "novo", considere se a classe em que você está inserindo a instanciação é um local "bom" para residir esse conhecimento e você está disposto a fazer alterações nessa área se a forma do o objeto instanciado mudaria.

O acoplamento apertado, que é um objeto com conhecimento de outra classe concreta, nem sempre deve ser evitado; em algum nível, alguma coisa, EM ALGUM LUGAR, precisa saber como criar esse objeto, mesmo que todo o resto lide com o objeto, recebendo uma cópia dele de outro lugar. No entanto, quando a classe que está sendo criada é alterada, qualquer classe que conhece a implementação concreta dessa classe deve ser atualizada para lidar corretamente com as alterações dessa classe.

A pergunta que você deve sempre fazer é: "Fazer com que essa classe saiba como criar essa outra classe se tornará uma responsabilidade ao manter o aplicativo?" As duas principais metodologias de projeto (SOLID e GRASP) geralmente respondem "sim" por motivos sutilmente diferentes. No entanto, são apenas metodologias e ambas têm a extrema limitação de que não foram formuladas com base no conhecimento do seu programa exclusivo. Como tal, eles podem apenas errar por precaução e assumir que qualquer ponto de acoplamento apertado EVENTUALMENTE causará um problema relacionado a alterações em um ou em ambos os lados deste ponto. Você deve tomar uma decisão final sabendo três coisas; a melhor prática teórica (que consiste em acoplar tudo porque tudo pode mudar); o custo da implementação das melhores práticas teóricas (que podem incluir várias novas camadas de abstração que facilitarão um tipo de mudança enquanto dificultam outro); e a probabilidade do mundo real de que o tipo de mudança que você está antecipando seja necessário.

Algumas diretrizes gerais:

  • Evite acoplamentos rígidos entre bibliotecas de códigos compiladas. A interface entre DLLs (ou um EXE e suas DLLs) é o principal local em que o acoplamento rígido apresentará uma desvantagem. Se você alterar uma classe A na DLL X e a classe B no EXE principal souber sobre a classe A, será necessário recompilar e liberar os dois binários. Dentro de um único binário, o acoplamento mais apertado é geralmente mais permitido, porque todo o binário deve ser reconstruído para qualquer alteração. Às vezes, a necessidade de reconstruir vários binários é inevitável, mas você deve estruturar seu código para evitá-lo sempre que possível, especialmente em situações em que a largura de banda é muito alta (como implantar aplicativos móveis; implantar uma nova DLL em uma atualização é muito mais barato do que empurrar o programa inteiro).

  • Evite acoplamentos estreitos entre os principais "centros lógicos" do seu programa. Você pode pensar em um programa bem estruturado como consistindo em fatias horizontais e verticais. As fatias horizontais podem ser camadas de aplicativos tradicionais, como interface do usuário, controlador, domínio, DAO, dados; fatias verticais podem ser definidas para janelas ou visualizações individuais ou para "histórias de usuários" individuais (como a criação de um novo registro de algum tipo básico). Ao fazer uma chamada que se move para cima, para baixo, esquerda ou direita em um sistema bem estruturado, você geralmente deve abstrair a chamada. Por exemplo, quando a validação precisa recuperar dados, ela não deve ter acesso ao banco de dados diretamente, mas deve fazer uma chamada para uma interface para recuperação de dados, que é apoiada pelo objeto real que sabe como fazer isso. Quando algum controle da interface do usuário precisa executar lógica avançada envolvendo outra janela, deve abstrair o disparo dessa lógica por meio de um evento e / ou retorno de chamada; ele não precisa saber o que será feito como resultado, permitindo alterar o que será feito sem alterar o controle que o aciona.

  • De qualquer forma, considere o quão fácil ou difícil será uma alteração e qual a probabilidade da referida alteração. Se um objeto que você está criando é usado apenas em um local e você não prevê essa alteração, o acoplamento rígido geralmente é mais permitido e pode até ser superior nessa situação a um acoplamento solto. O acoplamento flexível requer abstração, que é uma camada extra que impede a alteração de objetos dependentes quando a implementação de uma dependência deve ser alterada. No entanto, se a própria interface precisar alterar (adicionar uma nova chamada de método ou adicionar um parâmetro a uma chamada de método existente), uma interface realmente aumentará a quantidade de trabalho necessário para fazer a alteração. Você deve avaliar a probabilidade de diferentes tipos de mudança impactarem o design,

KeithS
fonte
3

Objetos que mantêm apenas dados ou DTOs (objetos de transferência de dados) são bons, criam esses o dia inteiro. Onde você deseja evitar newé nas classes que executam ações, você deseja que as classes consumidoras programem na interface , onde elas podem invocar essa lógica e consumi-la, mas não serão responsáveis ​​por criar as instâncias das classes que contêm a lógica. . As classes que executam ações ou lógicas são dependências. A palestra de Misko é voltada para você injetando essas dependências e permitindo que outra entidade seja responsável por realmente criá-las.

Anthony Pegram
fonte
2

Outros já mencionaram esse ponto, mas queriam citar isso do Clean Code do tio Bob (Bob Martin), porque poderia facilitar a compreensão do conceito:

"Um mecanismo poderoso para separar a construção do uso é a Injeção de Dependência (DI), a aplicação da Inversão de Controle (IoC) no gerenciamento de dependências. A Inversão de Controle move responsabilidades secundárias de um objeto para outros objetos dedicados ao objetivo, apoiando assim o princípio da responsabilidade única.No contexto do gerenciamento de dependências, um objeto não deve se responsabilizar pela instanciação de dependências.Em vez disso, deve passar essa responsabilidade para outro mecanismo "autoritário", invertendo o controle. Como a instalação é uma preocupação global, mecanismo autoritário será geralmente a rotina "principal" ou um contêiner para fins especiais ".

Eu acho que é sempre uma boa ideia seguir esta regra. Outros mencionaram o mais importante da IMO -> separa sua classe de suas dependências (colaboradores). A segunda razão é que isso torna suas aulas menores e mais tersas, mais fáceis de entender e até reutilizar em outros contextos.

c_maker
fonte