Nous avons une couche de données qui enveloppe Linq To SQL. Dans cette couche de données, nous avons cette méthode (simplifiée)
int InsertReport(Report report)
{
db.Reports.InsertOnSubmit(report);
db.SubmitChanges();
return report.ID;
}
Lors de la soumission des modifications, l'ID du rapport est mis à jour avec la valeur dans la base de données que nous renvoyons ensuite.
Du côté appelant, cela ressemble à ceci (simplifié)
var report = new Report();
DataLayer.InsertReport(report);
// Do something with report.ID
En regardant le code, l'ID a été défini dans la fonction InsertReport comme une sorte d'effet secondaire, puis nous ignorons la valeur de retour.
Ma question est, devrais-je compter sur l'effet secondaire et faire quelque chose comme ça à la place.
void InsertReport(Report report)
{
db.Reports.InsertOnSubmit(report);
db.SubmitChanges();
}
ou devons-nous l'empêcher
int InsertReport(Report report)
{
var newReport = report.Clone();
db.Reports.InsertOnSubmit(newReport);
db.SubmitChanges();
return newReport.ID;
}
peut-être même
Report InsertReport(Report report)
{
var newReport = report.Clone();
db.Reports.InsertOnSubmit(newReport);
db.SubmitChanges();
return newReport;
}
Cette question a été soulevée lorsque nous avons créé un test unitaire et constaté qu'il n'était pas vraiment clair que la propriété ID des paramètres du rapport était mise à jour et que pour se moquer du comportement des effets secondaires, une odeur de code si vous voulez.
la source
Réponses:
Oui, c'est OK et assez courant. Cependant, cela peut ne pas être évident, comme vous l'avez découvert.
En général, les méthodes de type persistance ont tendance à renvoyer l'instance mise à jour de l'objet. C'est:
Oui, vous retournez le même objet que celui que vous avez transmis, mais cela rend l'API plus claire. Il n'y a pas besoin du clone - s'il y a quelque chose qui causera de la confusion si, comme dans votre code d'origine, l'appelant continue à utiliser l'objet qu'il a transmis.
Une autre option consiste à utiliser un DTO
De cette façon, l'API est très évidente, et l'appelant ne peut pas essayer accidentellement d'utiliser l'objet passé / modifié. Selon ce que fait votre code, cela peut cependant être un peu pénible.
la source
OMI, c'est un cas rare où l'effet secondaire du changement est souhaitable - parce que votre entité de rapport a un ID, il pourrait déjà être supposé avoir les préoccupations d'un DTO, et il y a sans doute l' obligation ORM de s'assurer que le rapport en mémoire l'entité est synchronisée avec l'objet de représentation de la base de données.
la source
Le problème est que sans documentation, ce n'est pas clair du tout ce que fait la méthode et surtout pourquoi retourne-t-elle un entier.
La solution la plus simple serait d'utiliser un nom différent pour votre méthode. Quelque chose comme:
Pourtant, cela n'est pas assez ambigu: si, comme en C #, l'instance de l'
report
objet est passée par référence, il serait difficile de savoir si l'report
objet d' origine a été modifié, ou si la méthode l'a cloné et modifié uniquement le clone. Si vous choisissez de modifier l'objet d'origine, il serait préférable de nommer la méthode:Une solution plus compliquée (et peut-être moins optimale) consiste à refactoriser fortement le code. Qu'en est-il de:
la source
Les programmeurs s'attendent généralement à ce que seules les méthodes d'instance d'un objet puissent changer son état. En d'autres termes, je ne suis pas surpris de
report.insert()
modifier l'ID du rapport et c'est facile à vérifier. Il n'est pas facile de se demander pour chaque méthode de l'application entière si elle change l'identifiant du rapport ou non.Je dirais également que peut-être
ID
ne devrait même pas appartenir duReport
tout. Comme il ne contient pas un identifiant valide depuis si longtemps, vous avez vraiment deux objets différents avant et après l'insertion, avec un comportement différent. L'objet "avant" peut être inséré, mais il ne peut pas être récupéré, mis à jour ou supprimé. L'objet "après" est exactement le contraire. L'un a un identifiant et l'autre pas. La façon dont ils sont affichés peut être différente. Les listes dans lesquelles ils apparaissent peuvent être différentes. Les autorisations utilisateur associées peuvent être différentes. Ce sont tous les deux des «rapports» au sens anglais du terme, mais ils sont très différents.D'un autre côté, votre code peut être assez simple pour qu'un seul objet suffise, mais c'est quelque chose à considérer si votre code est parsemé de
if (validId) {...} else {...}
.la source
Non ce n'est pas OK! Il convient pour une procédure de modifier un paramètre uniquement dans les langages procéduraux, où il n'y a pas d'autre moyen; dans les langages POO, appelez la méthode de changement sur l'objet, dans ce cas sur le rapport (quelque chose comme report.generateNewId ()).
Dans ce cas, votre méthode fait 2 choses, donc casse SRP: elle insère un enregistrement dans la base de données et elle génère un nouvel identifiant. L'appelant n'est pas en mesure de savoir que votre méthode génère également un nouvel identifiant car il est simplement appelé insertRecord ().
la source
db.Reports.InsertOnSubmit(report)
appelle la méthode de changement sur l'objet?