函数可以修改参数吗


17

我们有一个包装Linq To SQL的数据层。在此数据层中,我们有此方法(简化)

int InsertReport(Report report)
{
    db.Reports.InsertOnSubmit(report);
    db.SubmitChanges();
    return report.ID; 
}

在提交更改时,将使用数据库中的值更新报告ID,然后我们将其返回。

从主叫方看起来像这样(简化)

var report = new Report();
DataLayer.InsertReport(report);
// Do something with report.ID

查看代码,作为一种副作用,已经在InsertReport函数内部设置了ID,然后我们忽略了返回值。

我的问题是,我应该依靠副作用,而是做类似的事情。

void InsertReport(Report report)
{
    db.Reports.InsertOnSubmit(report);
    db.SubmitChanges();
}

还是应该防止它

int InsertReport(Report report)
{
    var newReport = report.Clone();
    db.Reports.InsertOnSubmit(newReport);
    db.SubmitChanges();
    return newReport.ID; 
}

甚至会

Report InsertReport(Report report)
{
    var newReport = report.Clone();
    db.Reports.InsertOnSubmit(newReport);
    db.SubmitChanges();
    return newReport; 
}

当我们创建一个单元测试时,提出了这个问题,发现它不是很清楚报告参数ID属性是否已更新,并且模拟副作用的行为感觉不对,如果可以的话,会产生代码异味。


2
这就是API文档的用途。

Answers:


16

是的,没关系,而且相当普遍。正如您所发现的,它可能不是很明显。

通常,我倾向于让持久性类型的方法返回对象的更新实例。那是:

Report InsertReport(Report report)
{        
    db.Reports.InsertOnSubmit(report);
    db.SubmitChanges();
    return report; 
}

是的,您返回的对象与传入的对象相同,但是可以使API更清晰。不需要克隆-如果像您的原始代码中那样,如果调用者继续使用传入的对象,则可能引起混乱的任何东西。

另一种选择是使用DTO

Report InsertReport(ReportDTO dto)
{
    var newReport = Report.Create(dto);
    db.Reports.InsertOnSubmit(newReport);
    db.SubmitChanges();
    return newReport; 
}

这样,API非常明显,并且调用者不会意外地尝试使用传入的/修改的对象。但是,根据您的代码在做什么,这可能会有些麻烦。


如果不需要Ira,我将不会返回该对象。那看起来像额外的处理和内存(过度)使用。如果不需要,我会作废或例外。否则,我投票给您的DTO示例。
独立时间:

所有这些答案几乎总结了我们自己的讨论。这似乎是一个没有真正答案的问题。您的陈述“是的,您返回的是与传入的对象相同的对象,但它使API更清晰。” 几乎回答了我们的问题。
约翰·彼得拉克

4

在IMO中,这种情况很少会发生更改的副作用-因为您的报告实体具有ID,因此可以假定它具有DTO的关注,并且可以说ORM 有义务确保内存中的报告实体与数据库表示对象保持同步。


6
+1-在数据库中插入实体后,您希望它具有ID。如果实体在没有它的情况下回来,那将更加令人惊讶。
MattDavey 2012年

2

问题在于,没有文档,根本不清楚该方法在做什么,尤其是为什么它返回整数。

最简单的解决方案是为您的方法使用其他名称。就像是:

int GenerateIdAndInsert(Report report)

不过,这还不够明确:如果像在C#中一样,report通过引用传递对象的实例,则将很难知道原始report对象是否被修改,或者该方法是否克隆了该对象并仅修改了克隆。如果选择修改原始对象,则最好命名该方法:

void ChangeIdAndInsert(Report report)

一个更复杂的(也许不是最优的)解决方案是大量重构代码。关于什么:

using (var transaction = new TransactionScope())
{
    var id = this.Data.GenerateReportId(); // We need to find an available ID...
    this.Data.AddReportWithId(id, report); // ... and use this ID to insert a report.
    transaction.Complete();
}

2

通常,程序员希望只有对象的实例方法才能更改其状态。换句话说,如果report.insert()更改报告的ID,我并不感到惊讶,而且很容易检查。对于整个应用程序中的每个方法都难以怀疑是否更改了报表的ID。

我还要说,也许ID根本不应该属于Report。由于它很长时间没有有效的ID,因此在插入之前和之后,您实际上都有两个不同的对象,它们的行为不同。可以插入“之前”对象,但是无法检索,更新或删除该对象。“之后”对象与之完全相反。一个有一个ID,另一个没有。它们的显示方式可能不同。它们出现的列表可能不同。关联的用户权限可能不同。它们都是英文单词中的“报告”,但是它们有很大的不同。

另一方面,您的代码可能足够简单,以至于一个对象就足够了,但是如果您的代码中使用,则需要考虑一些问题if (validId) {...} else {...}


0

不,这不行!仅在没有其他方法的情况下,才可以使用过程语言修改参数,这适合于过程。在OOP语言中,在对象(在本例中为报表)上调用change方法(类似于report.generateNewId())。

在这种情况下,您的方法会做两件事,因此会破坏SRP:在数据库中插入一条记录并生成一个新的ID。调用者无法知道您的方法还会生成一个新的ID,因为它简称为insertRecord()。


3
Ermm ...如果db.Reports.InsertOnSubmit(report)在对象上调用change方法怎么办?
斯蒂芬·C

没关系,应该避免,但是在这种情况下,LINQ to SQL正在进行参数修改,因此,如果没有环跳跳跃克隆效应(这是它自己对SRP的违反),那么OP不可能避免这种情况。
Telastyn

@StephenC我是说您应该在报表对象上调用该方法;在这种情况下,传递同一对象作为参数没有任何意义。
m3th0dman

@Telastyn我在一般情况下发言;良好做法不能100%得到尊重。在他的特殊情况下,没有人可以从5行代码中推断出最好的方法是……
m3th0dman 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.