做一件事 - 这条规则要走多远?

时间:2009-08-27 14:03:52

标签: c# coding-style

所以“清洁代码”一书中有“做一件事”的规则。 但到目前为止我们还有多远。

例如以下陈述:

Settings.Default.BaudRate = baudRate;
Settings.Default.COMPort = port;
Settings.Default.DataBits = dataBits;
Settings.Default.Handshake = handshake;
Settings.Default.Parity = parity;
Settings.Default.ReadTimeout = readTimeout;
Settings.Default.WriteTimeout = writeTimeout;
Settings.Default.CommunicationTimeout = communicationTimeout;
Settings.Default.Save(); 

好的,确定这里有多个声明, 但它确实让我觉得他们只是做了一件事。 保存设置。

我在一个功能中有这个。你真的会接受这个appart吗? 每个设置都有一个方法吗?

你什么时候坚持这个规则?什么时候不遵守?

11 个答案:

答案 0 :(得分:19)

本书的下一部分每个功能的一级抽象,对于回答这个问题大有帮助。所有这些语句都处于相同的抽象级别,因此这个函数已经做了一件事,保存了设置。

答案 1 :(得分:18)

看起来对我来说完全有效。该代码的明显方法名称是SaveSettings,表示该方法完全有一件事。无需担心。

答案 2 :(得分:6)

我将它们全部保存在一个SaveSettings()函数中 - 如果你将它们放在各自的函数中,你仍然需要从另一个函数中调用所有这些函数。

答案 3 :(得分:6)

是的,每个方法都应该只做一个的事情。那是什么一件事?

这取决于您的方法所处的抽象级别。用于保存单个设置的方法(属性)是一个相当低的抽象。然后,下一个更高的抽象将是建议的SaveSettings方法。

在顶部你有一个方法/函数main,它只做一件事:整个程序......

答案 4 :(得分:4)

我假设此规则引用何时将函数划分为多个子函数。

你有正确的想法 - 保存设置是“一件事”,可以在自己的功能。将每个设置放在自己的功能中将是过度的。

我听过的另一个指南可能有助于您理解“一件事”的概念:如果函数长于一两页,可以通过将其内容划分为多个子函数来更好地编写。 / p>

答案 5 :(得分:4)

单独查看代码,很难说。你可以说你的功能做了两件事 - 更新设置然后保存更改。另外,如何填充值,它们是作为函数的参数传递的(最好)还是函数自己获取值(做其他事情)?

我会关注Single Responsibility Principle,其摘要如下:

  

改变课程的理由绝不应有多于一个

并且同样可以应用于方法。在您的示例中,是否有理由更新设置,但不保存它们?

答案 6 :(得分:4)

我没有读过那本书,但是关于这个概念......

“一件事”并不意味着“一行代码”。 “一件事”意味着函数中的所有内容都应该与逻辑相关。

我对一些以前的海报说“saveSettings”是“一件事”是狡辩的。也许这只是粗心的措辞,但我会借此机会指出潜在的陷阱。在你的情况下,它更像是“saveCommunicationSettings”,我认为它很容易适合“一件事”的定义。如果您将“Settings.Default.customerLoyaltyDiscount = ...”添加到该列表中,我会说您可能处于危险的境地,因为您现在正在将通信设置与定价计算设置混合。

在现实生活中,决定什么是合理的凝聚力不是一个公式,而是一个需要运用智慧的判断。计算订单总金额的函数是否应包括销售税计算?可以说这是两件事:订购所有商品的总价并计算销售税。但你也可以争辩说它只有一个:找到订单的总价,无论涉及到什么。在实践中,我经常根据逻辑的复杂性做出决定。如果计算订单总额所需的全部是一个简单的循环,通过所有项目加总价格,然后从表中获取销售税率并乘以,我可能会在一个函数中完成所有操作。如果它还有更多 - 比如我现在正在研究的系统,计算定价涉及库存与定制订单,查找各种可能的折扣,添加保修等等 - 我们真的需要打破它起来。

答案 7 :(得分:2)

在我们的团队中,我们尝试遵循每个功能指南的一个目的。为了帮助开发人员,我们在标准中添加了一个建议,如果函数超过25行,他们会考虑重构。因此,如果您有100行代码设置属性,您可以考虑按类别(如SaveUserSettings,SaveNetworkSettings等)将它们拆分。

最终目标是使代码更具可读性。如果你采用你的方法并将其分成20个调用,每个调用都设置了一个属性,我认为跟踪和支持它会更耗时。

答案 8 :(得分:1)

示例代码可以更好地解决问题,何时使用属性以及何时在构造函数或方法上使用参数来设置对象状态。我知道在.NET framework guidelines中有一个关于此的部分,它讨论了组件基本模式(许多属性和组件内部状态在第一个和最后一个赋值之间可能无效)与另一个模式(许多构造函数)参数和方法参数以及对象内部状态在每行代码执行后都有效。

答案 9 :(得分:0)

如果你想拆分你的方法,我会考虑中断实际保存对象的值的映射。您可以说值的映射是一个单独的抽象级别。所以你会:

public void save(){
    mapSettings();
    Settings.Default.Save();
}

private void mapSettings(){
    Settings.Default.BaudRate = baudRate;
    Settings.Default.COMPort = port;
    Settings.Default.DataBits = dataBits;
    Settings.Default.Handshake = handshake;
    Settings.Default.Parity = parity;
    Settings.Default.ReadTimeout = readTimeout;
    Settings.Default.WriteTimeout = writeTimeout;
    Settings.Default.CommunicationTimeout = communicationTimeout;
}

似乎映射可能会在其他地方重用或者您可能想要重用。此外,如果数字设置增长,则可以按类别进一步细分。

在这种情况下,我不认为这是值得的,我是否这样做取决于我的心情或天空中的云量。但是,从技术上讲,这里有两个层次可以拆分。

答案 10 :(得分:0)

如何知道一个函数是做一件事还是一件事?这取决于抽象的程度。 我试着在两个例子中描述抽象层次(干净的代码簿,第3章) 看到这段代码

public function buildPage() {
   $page = header();
   $page .= body();
   $page .= footer();
   return $page;
}

此函数只执行一项操作,所有子功能都是buildPage短语的一部分

public function sendMail() {
   $user = $this->getUser();
   $this->mailer->content("send this message!")
       ->to($user->email)->send();
}

此功能需要用户发送电子邮件,因此在getUser函数的帮助下,sendMail会设法获取用户的电子邮件。但是sendMail短语中没有描述$ user = getUser() 该功能的最佳实践是

public function sendMail($email,$message) {
   $this->mailer->content($message)->to($email)->send();
}