Temos uma camada de dados que envolve o Linq To SQL. Nesta camada de dados, temos este método (simplificado)
int InsertReport(Report report)
{
db.Reports.InsertOnSubmit(report);
db.SubmitChanges();
return report.ID;
}
Ao enviar as alterações, o ID do relatório é atualizado com o valor no banco de dados que retornamos.
Do lado da chamada, fica assim (simplificado)
var report = new Report();
DataLayer.InsertReport(report);
// Do something with report.ID
Observando o código, o ID foi definido dentro da função InsertReport como um tipo de efeito colateral, e então estamos ignorando o valor de retorno.
Minha pergunta é: devo confiar no efeito colateral e fazer algo assim em seu lugar.
void InsertReport(Report report)
{
db.Reports.InsertOnSubmit(report);
db.SubmitChanges();
}
ou devemos impedi-lo
int InsertReport(Report report)
{
var newReport = report.Clone();
db.Reports.InsertOnSubmit(newReport);
db.SubmitChanges();
return newReport.ID;
}
talvez até
Report InsertReport(Report report)
{
var newReport = report.Clone();
db.Reports.InsertOnSubmit(newReport);
db.SubmitChanges();
return newReport;
}
Essa questão foi levantada quando criamos um teste de unidade e descobrimos que não está realmente claro que a propriedade ID dos parâmetros do relatório é atualizada e que, para zombar do comportamento do efeito colateral, parecia errado, um cheiro de código, se você quiser.
fonte
Respostas:
Sim, tudo bem e bastante comum. Pode não ser óbvio, como você descobriu.
Em geral, costumo ter métodos do tipo persistência retornando a instância atualizada do objeto. Isso é:
Sim, você está retornando o mesmo objeto que passou, mas isso torna a API mais clara. Não há necessidade do clone - se alguma coisa causar confusão, se, como no código original, o chamador continuar usando o objeto pelo qual passou.
Outra opção é usar um DTO
Dessa forma, a API é muito óbvia e o chamador não pode tentar acidentalmente usar o objeto passado / modificado. Dependendo do que o seu código está fazendo, pode ser um pouco trabalhoso.
fonte
Na IMO, esse é um caso raro em que o efeito colateral da mudança é desejável - porque sua entidade de Relatório possui um ID, já é possível supor que ela tenha as preocupações de um DTO e, sem dúvida, existe a obrigação do ORM de garantir que o Relatório na memória A entidade é mantida em sincronia com o objeto de representação do banco de dados.
fonte
O problema é que, sem a documentação, não está claro o que o método está fazendo e, principalmente, por que ele está retornando um número inteiro.
A solução mais fácil seria usar um nome diferente para o seu método. Algo como:
Ainda assim, isso não é suficientemente ambíguo: se, como em C #, a instância do
report
objeto for passada por referência, seria difícil saber se oreport
objeto original foi modificado ou se o método o clonou e modificou apenas o clone. Se você optar por modificar o objeto original, seria melhor nomear o método:Uma solução mais complicada (e talvez menos otimizada) é refatorar fortemente o código. A respeito:
fonte
Geralmente, os programadores esperam que apenas os métodos de instância de um objeto possam alterar seu estado. Em outras palavras, não fico surpreso se
report.insert()
alterar o ID do relatório e for fácil verificar. Não é fácil pensar em todos os métodos em todo o aplicativo se ele altera o ID do relatório ou não.Eu também argumentaria que talvez
ID
nem devesse pertencerReport
. Como ele não contém um ID válido por tanto tempo, você realmente tem dois objetos diferentes antes e depois da inserção, com comportamento diferente. O objeto "antes" pode ser inserido, mas não pode ser recuperado, atualizado ou excluído. O objeto "depois" é exatamente o oposto. Um tem um ID e o outro não. A maneira como eles são exibidos pode ser diferente. As listas em que aparecem podem ser diferentes. Permissões de usuário associadas podem ser diferentes. Ambos são "relatórios" no sentido inglês da palavra, mas são muito diferentes.Por outro lado, seu código pode ser simples o suficiente para que um objeto seja suficiente, mas é algo a considerar se seu código estiver apimentado
if (validId) {...} else {...}
.fonte
Não, não está tudo bem! É adequado para um procedimento modificar um parâmetro apenas em linguagens procedurais, onde não há outra maneira; em idiomas OOP, chame o método de alteração no objeto, neste caso no relatório (algo como report.generateNewId ()).
Nesse caso, seu método faz duas coisas, portanto quebra o SRP: ele insere um registro no banco de dados e gera um novo ID. O chamador não pode saber que seu método também gera um novo ID, pois é chamado simplesmente insertRecord ().
fonte
db.Reports.InsertOnSubmit(report)
chama o método de alteração no objeto?