Nossa base de código é antiga e novos programadores, como eu, aprendem rapidamente a fazê-lo da maneira que é feita em prol da uniformidade. Pensando que precisamos começar em algum lugar, decidi refatorar uma classe de detentores de dados como tal:
- Removemos os métodos setter e criamos todos os campos
final
(entendo "final
é bom" axiomaticamente). Os setters foram usados apenas no construtor, como se vê, então isso não teve efeitos colaterais. - Introduziu uma classe Builder
A classe Builder era necessária porque o construtor (que é o que levou à refatoração em primeiro lugar) abrange cerca de três linhas de código. Tem muitos parâmetros.
Por sorte, um colega de equipe meu estava trabalhando em outro módulo e precisou dos levantadores, porque os valores que ele exigiu ficaram disponíveis em diferentes pontos do fluxo. Assim, o código ficou assim:
public void foo(Bar bar){
//do stuff
bar.setA(stuff);
//do more stuff
bar.setB(moreStuff);
}
Argumentei que ele deveria usar o construtor em vez disso, porque livrar-se dos levantadores permite que os campos permaneçam imutáveis (eles já me ouviram falar sobre imutabilidade antes) e também porque os construtores permitem que a criação de objetos seja transacional. Esbocei o seguinte pseudocódigo:
public void foo(Bar bar){
try{
bar.setA(a);
//enter exception-throwing stuff
bar.setB(b);
}catch(){}
}
Se essa exceção for acionada, bar
haverá dados corrompidos, o que teria sido evitado com um construtor:
public Bar foo(){
Builder builder=new Builder();
try{
builder.setA(a);
//dangerous stuff;
builder.setB(b);
//more dangerous stuff
builder.setC(c);
return builder.build();
}catch(){}
return null;
}
Meus colegas de equipe replicaram que a exceção em questão nunca será acionada, o que é justo o suficiente para essa área específica de código, mas acredito que esteja faltando a floresta para a árvore.
O compromisso foi reverter para a solução antiga, ou seja, usar um construtor sem parâmetros e definir tudo com os configuradores, conforme necessário. A lógica era que essa solução segue o princípio do KISS, que a minha viola.
Sou novo nesta empresa (há menos de 6 meses) e tenho plena consciência de que perdi esta. As perguntas que tenho são:
- Existe outro argumento para o uso de construtores em vez da "maneira antiga"?
- A mudança que proponho vale mesmo a pena?
mas realmente,
- Você tem alguma dica para apresentar melhor esses argumentos ao defender algo novo?
setA
?Respostas:
Foi aí que deu errado - você fez uma grande alteração na arquitetura da base de código, de forma que ela se tornou muito diferente do esperado. Você surpreendeu seus colegas. Surpresa só é boa se envolver uma festa, o princípio da menor surpresa é ouro (mesmo que envolva festas, então eu saberia usar cueca limpa!)
Agora, se você achava que essa mudança valia a pena, seria muito melhor esboçar o pseudocódigo que mostrava a mudança e apresentá-la aos seus colegas (ou pelo menos quem tem o papel mais próximo do arquiteto) e ver o que eles pensavam antes de fazer qualquer coisa. Do jeito que está, você ficou desonesto, pensou que toda a base de código era sua para você brincar como quisesse. Um jogador da equipe, não um jogador da equipe!
Eu posso estar sendo um pouco duro com você, mas eu estive no lugar oposto: verifique um código e pense "WTF aconteceu aqui ?!", apenas para encontrar o novo cara (quase sempre o novo cara) decidiu mudar um monte de coisas baseadas no que ele acha que o "caminho certo" deveria ser. Parafusos também com a história do SCM, que é outro grande problema.
fonte
Conforme descrito, não vejo o que faz o código exigir setters. Provavelmente não sei o suficiente sobre o caso de uso, mas por que não esperar até que todos os parâmetros sejam conhecidos antes de instanciar o objeto?
Como alternativa, se a situação exigir setters: ofereça uma maneira de congelar seu código para que os setters gerem um erro de asserção (também conhecido como erro de programador) se você tentar modificar os campos depois que o objeto estiver pronto.
Eu acho que remover os levantadores e usar final é a maneira "correta" de fazê-lo, além de reforçar a imutabilidade, mas é realmente com o que você deveria gastar seu tempo?
Dicas gerais
Velho = ruim, novo = bom, do meu jeito, do seu jeito ... esse tipo de discussão não é construtivo.
Atualmente, a maneira como seu objeto é usado segue alguma regra implícita. Isso faz parte da especificação não escrita do seu código e sua equipe pode pensar que não há muito risco de outras partes do código utilizá-lo incorretamente. O que você deseja fazer aqui é tornar a regra mais explícita com um Construtor e verificações em tempo de compilação.
De alguma forma, a refatoração de código está ligada à teoria Broken Window : você acha certo corrigir qualquer problema como o vê (ou anote algumas reuniões posteriores de "melhoria da qualidade") para que o código sempre pareça agradável de se trabalhar, é seguro e limpo. Você e sua equipe não têm a mesma idéia sobre o que é "bom o suficiente". O que você vê como uma oportunidade de contribuir e melhorar o código, com boa fé, provavelmente é visto de maneira diferente da equipe existente.
A propósito, não se deve presumir que sua equipe esteja necessariamente sofrendo de inércia, maus hábitos, falta de educação, ... o pior que você pode fazer é projetar uma imagem de um sabe-tudo que existe para mostrar a eles como codificar. Por exemplo, imutabilidade não é algo novo , provavelmente seus colegas já leram sobre isso, mas apenas porque esse assunto está se tornando popular em blogs não é suficiente para convencê-los a gastar tempo alterando seu código.
Afinal, existem inúmeras maneiras de melhorar uma base de código existente, mas custa tempo e dinheiro. Eu poderia olhar para o seu código, chamá-lo de "antigo" e encontrar coisas para detalhar. Por exemplo: nenhuma especificação formal, afirmações insuficientes, talvez comentários ou testes insuficientes, cobertura de código insuficiente, algoritmo não ideal, uso excessivo de memória, não uso do "melhor" padrão de design possível em cada caso, etc. ad nauseam . Mas para a maioria dos pontos que eu gostaria de mencionar, você pensaria: tudo bem, meu código funciona, não há necessidade de gastar mais tempo nele.
Talvez sua equipe pense que o que você sugere já passou do ponto de diminuir os retornos. Mesmo se você tivesse encontrado uma instância real de um bug devido ao tipo de exemplo que você mostra (com exceção), eles provavelmente apenas o corrigiam uma vez e não queriam refatorar o código.
Então a pergunta é sobre como gastar seu tempo e energia, considerando que eles são limitados ? Existem mudanças contínuas no software, mas geralmente sua ideia começa com uma pontuação negativa até que você possa fornecer bons argumentos. Mesmo se você estiver certo sobre o benefício, não é um argumento suficiente por si só, porque ele terminará na cesta "É bom ter um dia ... ".
Quando você apresenta seus argumentos, precisa fazer algumas verificações de "sanidade": você está tentando vender a ideia ou a si mesmo? E se alguém a apresentasse à equipe? Você encontraria algumas falhas em seu raciocínio? Você está tentando aplicar algumas práticas recomendadas gerais ou levou em consideração as especificidades do seu código?
Então, lembre-se de não aparecer como se estivesse tentando consertar os "erros" anteriores do seu time. Isso ajuda a mostrar que você não é sua ideia, mas pode receber feedback razoavelmente. Quanto mais você fizer isso, mais suas sugestões terão a oportunidade de serem seguidas.
fonte
Você não
Se o seu construtor tiver 3 linhas de parâmetros, é muito grande e muito complexo. Nenhum padrão de design corrigirá isso.
Se você possui uma classe cujos dados são disponibilizados em momentos diferentes, devem ser dois objetos diferentes ou seu consumidor deve agregar os componentes e depois construir o objeto. Eles não precisam de um construtor para fazer isso.
fonte
String
outros primitivos. Não há lógica em lugar algum, nem mesmo verifica se hánull
etc. Portanto, é apenas o tamanho, não a complexidade em si. Eu pensei que fazer algo assimbuilder.addA(a).addB(b); /*...*/ return builder.addC(c).build();
seria muito mais fácil para os olhos.Você parece mais focado em estar certo do que em trabalhar bem com os outros. Pior ainda, você reconhece que o que está fazendo é uma luta pelo poder e, no entanto, deixa de pensar em como as coisas se sentirão para as pessoas cuja experiência e autoridade você está desafiando.
Inverta isso.
Concentre-se em trabalhar com outras pessoas. Enquadre seus pensamentos como sugestões e idéias. Faça com que ELES conversem sobre SUAS idéias antes de fazer perguntas para fazê-los pensar sobre a sua. Evite confrontos diretos e aceite externamente que continuar fazendo as coisas da maneira que o grupo (por enquanto) é mais produtivo do que travar uma batalha árdua para mudar o grupo. (Embora traga as coisas de volta.) Faça com que seja um prazer trabalhar com você.
Faça isso de forma consistente e você deverá criar menos conflitos e encontrar mais idéias adotadas por outras pessoas. Todos sofremos da ilusão de que o argumento lógico perfeito irá impressionar os outros e vencer o dia. E se não funcionar dessa maneira, achamos que deveria .
Mas isso está em conflito direto com a realidade. A maioria dos argumentos é perdida antes que o primeiro argumento lógico seja apresentado. Mesmo entre pessoas supostamente lógicas, a psicologia supera a razão. Convencer as pessoas é superar as barreiras psicológicas para ser ouvido adequadamente, e não ter uma lógica perfeita.
fonte
Frame your thoughts as suggestions and ideas. Get THEM to talk through THEIR ideas before asking questions to make them think about yours
+1 para isso, no entanto"Quando você tem um novo martelo, tudo parecerá um prego". - foi o que pensei quando li sua pergunta.
Se eu fosse seu colega, perguntaria "por que não inicializar o objeto inteiro no construtor?" Afinal, é isso que é a funcionalidade. Por que usar o padrão do construtor (ou qualquer outro design)?
É difícil distinguir desse pequeno trecho de código. Talvez seja - você e seus colegas precisam avaliar.
Sim, aprenda mais sobre o tópico antes de discuti-lo. Arme-se com bons argumentos e razões antes de entrar na discussão.
fonte
Eu estive na situação em que fui recrutado por minhas habilidades. Quando cheguei a ver o código, bem ... era mais do que horrível. Ao tentar limpá-lo, alguém que está lá por mais tempo sempre suprime as alterações, porque não vê os benefícios. Parece algo que você está descrevendo. Acabou comigo deixando essa posição e agora posso evoluir muito melhor em uma empresa que tem uma prática melhor.
Eu não acho que você deva apenas atuar junto com os outros membros da equipe porque eles estão lá há mais tempo. Se você vir os membros de sua equipe usando práticas inadequadas nos projetos em que trabalha, é uma responsabilidade que você precise apontar como está. Caso contrário, você não é um recurso muito valioso. Se você apontar essas coisas repetidamente, mas ninguém estiver ouvindo, peça à gerência uma substituição ou troca de trabalho. É muito importante que você não se adapte às práticas ruins.
Para a pergunta real
Por que usar objetos imutáveis? Porque você sabe com o que está lidando.
Digamos que você tenha uma conexão db transmitida que permita alterar o host por meio de um setter, então você não sabe qual é a conexão. Sendo imutável, então você sabe.
Diga, no entanto, que você possui uma estrutura de dados que pode ser recuperada da persistência e depois persistida com novos dados; então, faz sentido que essa estrutura não seja imutável. É a mesma entidade, mas os valores podem mudar. Em vez disso, use objetos de valor imutável como argumentos.
O que você está focando na pergunta é, no entanto, um padrão de construtor para se livrar das dependências no construtor. Eu digo um grande NÃO gordo para isso. A instância é obviamente complexa já. Não tente esconder, conserte!
Tl; dr
A remoção dos levantadores pode estar correta, dependendo da classe. O padrão do construtor não está resolvendo o problema, apenas ocultando-o. (A sujeira debaixo do tapete ainda existe, mesmo que você não possa vê-lo)
fonte
Embora todas as outras respostas sejam muito úteis (mais úteis do que as minhas), há uma propriedade dos construtores que você ainda não mencionou: ao transformar a criação de um objeto em um processo, você pode impor restrições lógicas complexas.
Você pode exigir, por exemplo, que determinados campos sejam definidos juntos ou em uma ordem específica. Você pode até retornar uma implementação diferente do objeto de destino, dependendo dessas decisões.
Este é um exemplo simplório que não faz muito sentido, mas demonstra as três propriedades: o caractere é sempre definido primeiro, os limites do intervalo são sempre definidos e validados juntos, e você escolhe sua implementação no momento da criação.
É fácil ver como isso pode levar a um código de usuário mais legível e confiável, mas como você também pode ver, há uma desvantagem: muito e muito código de construtor (e até deixei de fora os construtores para reduzir os trechos). Então você tenta calcular o trade-off, fazendo perguntas como:
Seus colegas simplesmente decidiram que a troca não seria benéfica. Você deve discutir as razões de maneira aberta e honesta, de preferência sem recorrer a princípios abstratos, mas concentrando-se nas implicações práticas dessa ou daquela solução.
fonte
Parece que essa classe é realmente mais de uma classe, reunida na mesma definição de arquivo / classe. Se for um DTO, deve ser possível instancia-lo de uma só vez e ser imutável. Se você não usar todos os seus campos / propriedades em qualquer transação, é quase certo que ele está violando o princípio de responsabilidade única. Pode ser que dividi-lo em classes DTO menores, mais coesas e imutáveis, possa ajudar. Considere a possibilidade de ler o Código Limpo, se você ainda não o tiver, para poder articular melhor os problemas e os benefícios de fazer uma alteração.
Eu não acho que você possa projetar código nesse nível pelo comitê, a menos que você esteja literalmente programando mob (não parece que você faz parte desse tipo de equipe), então acho que não há problema em fazer uma alteração com base no seu melhor julgamento e, em seguida, leve-o ao queixo, se você achar que está errado.
No entanto, desde que você seja capaz de articular os possíveis problemas com o código, ele ainda poderá debater que uma solução é necessária , mesmo que a sua não seja a aceita. As pessoas são livres para oferecer alternativas.
" Opiniões fortes, fracamente defendidas " é um bom princípio - você segue em frente com confiança com base no que sabe, mas se encontrar alguma informação nova que contrarie isso, seja modesta e renuncie e entre em uma discussão sobre o que a solução correta deve estar. A única resposta errada é deixá-lo piorar.
fonte