在任何给定的类定义中,我都看到了以各种方式排序的方法定义:按字母顺序排列,基于最常用用法的按时间顺序排列,按可见性分组的按字母顺序排列,按顺序将getter和setter组合在一起的按字母顺序排列的等等,当我开始编写新类时,我倾向于只输入所有内容,然后在写完整个课程后重新排序。关于这一点,我有三个问题:
- 顺序重要吗?
- 是否有“最佳”订单?
- 我猜没有,所以不同订购策略的优缺点是什么?
在任何给定的类定义中,我都看到了以各种方式排序的方法定义:按字母顺序排列,基于最常用用法的按时间顺序排列,按可见性分组的按字母顺序排列,按顺序将getter和setter组合在一起的按字母顺序排列的等等,当我开始编写新类时,我倾向于只输入所有内容,然后在写完整个课程后重新排序。关于这一点,我有三个问题:
Answers:
在某些编程语言中,顺序确实很重要,因为在声明它们之前,您无法使用它们。但是,除非如此,对于大多数语言,编译器都没有关系。因此,剩下的就对人类而言至关重要。
我最喜欢的马丁·福勒(Martin Fowler)语录是:Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
因此,我想说的是,班级的顺序应取决于使人类易于理解的因素。
我个人更喜欢鲍勃·马丁(Bob Martin)在Clean Code
书中给出的降压处理。在类顶部的成员变量,然后是构造函数,然后是所有其他方法。然后,您将这些方法与它们在类中的使用方式紧密联系在一起(而不是任意地将所有公共然后私有然后再保护)。他称之为最小化“垂直距离”之类的东西(目前暂时没有这本书)。
编辑:
“垂直距离”的基本思想是,您要避免让人们为了理解它而在源代码中四处跳动。如果事情相关,则它们应该更靠近在一起。无关的事物可能相距更远。
Clean Code的第5章 (精装书,btw)详细介绍了Martin先生如何建议订购代码。他建议阅读代码的工作方式应类似于阅读报纸上的文章:高级细节排在最前面(顶部),而您在阅读时会得到更多细节。他说:“如果一个函数调用另一个函数,则它们应垂直关闭,并且如果可能的话,调用者应位于被调用者之上。” 此外,相关概念应紧密结合在一起。
因此,这是一个人为设计的示例,该示例在很多方面都是不好的(OO设计不佳;从不double
花钱),但说明了这个想法:
public class Employee {
...
public String getEmployeeId() { return employeeId; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public double calculatePaycheck() {
double pay = getSalary() / PAY_PERIODS_PER_YEAR;
if (isEligibleForBonus()) {
pay += calculateBonus();
}
return pay;
}
private double getSalary() { ... }
private boolean isEligibleForBonus() {
return (isFullTimeEmployee() && didCompleteBonusObjectives());
}
public boolean isFullTimeEmployee() { ... }
private boolean didCompleteBonusObjectives() { ... }
private double calculateBonus() { ... }
}
这些方法是有序的,因此它们与调用它们的方法很接近,从顶部开始一直向下工作。如果我们将所有private
方法置于方法之下public
,那么您将不得不做更多的工作来跟踪程序的流程。
getFirstName
并且getLastName
在概念上是相关的(getEmployeeId
可能也是如此),因此它们彼此靠近。我们可以将它们全部向下移动到底部,但是我们不想getFirstName
在顶部和getLastName
底部看到。
希望这给您基本的想法。如果您对这种事情感兴趣,我强烈建议您阅读Clean Code
。
calculateBonus()
之前来isFullTimeEmployee()
和didCompleteBonusObjectives()
?
isFullTimeEmployee
并且didCompleteBonusObjectives
被isEligibleForBonus
它们所使用,因此它们应该垂直靠近它。但是您可能会向上移动calculateBonus
以使其更接近调用位置。不幸的是,当您拥有调用函数的函数时,最终会遇到没有完美排序的问题(例如一个共享函数被多个其他函数调用)。然后由您做出最佳判断。我建议保持较小的类和函数以减轻这些问题。
我通常按关系和使用顺序对方法进行排序。
参加网络课程,我将所有TCP方法分组在一起,然后将所有UDP方法分组在一起。内部TCP我将第一个设置方法设置为第二个,然后发送给定消息,然后关闭tcp套接字为第三个。
显然,并非所有类都适合该模式,但这是我的常规工作流程。
我这样做是为了进行调试,而不是其他任何事情,当我遇到问题并且想跳到该方法时,我不认为它是怎么拼写的,我认为它负责什么并转到该部分。
这种方式对于第三方查看/使用您的代码分组在一起的第三方特别有意义,并且将遵循其使用顺序,因此他们将与您的类一起编写的代码将采用与该类相同的结构。
至于这有关系吗?
为了可读性,当然。
除此之外,仅在某些语言中,除非在上面的调用等中定义了该方法,否则您无法调用该方法。
理想情况下,您的班级很小,没关系。如果您只有十二个方法,并且您的编辑器或IDE支持折叠,那么您就不会有问题,因为整个方法列表都包含12行。
未能做到这一点,最高级别的区别应该是公共与私人。首先列出公共方法:这是人们最需要的方法,因为它们定义了类与代码库其余部分交互的方式。
然后,在每种方法中,按功能对方法进行分组最有意义:一个块中的构造函数和析构函数,另一个块中的getters / setter方法,运算符重载,静态方法以及其他方法。在C ++中,我喜欢operator=
与构造函数保持密切联系,因为它与复制构造函数密切相关,并且还因为我希望能够快速发现默认ctor,复制ctor,operator =和dtor是否全部(或不包括)存在。
仅当您使用的语言要求特定的顺序时。除此之外,订购取决于您。选择一个一致且有意义的系统,并尝试尽可能坚持下去。
1。顺序重要吗?
仅当您使用的语言需要在文件中定义一个比调用它的位置更早的函数时,例如在本示例中:
void funcA()
{
funcB();
}
void funcB()
{
//do something interesting...
}
您会收到错误消息,因为您funcB()
在使用它之前先打电话。我认为这是PL / SQL和C语言中的一个问题,但是您可以具有如下的前向声明:
void funcB();
void funcA()
{
funcB();
}
void funcB()
{
//do something interesting...
}
这是我能想到的唯一情况,如果顺序是“错误的”,那么您甚至将无法编译。
否则,您可以随时根据需要重新排序。您甚至可以编写一种工具为您完成此操作(如果您找不到那里的话)。
2。是否有“最佳”订单?
如果语言/环境没有订购要求,则“最佳”订购是最适合您的订购。对我来说,我喜欢将所有的getter / setters放在一起,通常是在类的开头(但在构造函数/静态初始化器之后),然后是private方法,然后是protected,然后是public。在每个基于作用域的组中,通常没有排序,尽管我尝试按参数数量的顺序将重载方法保持在一起。我还尝试将具有相关功能的方法保持在一起,尽管有时我不得不打破基于作用域的顺序。有时尝试维护基于作用域的排序会破坏按功能分组。而且我的IDE可以给我一个字母轮廓视图,所以也很好。;)
某些语言(例如C#)具有将代码分组在“区域”中的功能,这些区域对编译没有影响,但可以使将相关功能保持在一起,然后使用IDE隐藏/显示它们变得更加容易。正如MainMa指出的那样,有些人认为这是不好的做法。我已经看到过使用这种方式的区域的好坏例子,因此,如果要采用这种方式,请当心。