第三章 函数
-
自顶向下阅读代码
-
如果单纯使用switch语句,每次增加新case时,需要在原来代码的基础上增加新的case。这违反了单一权责原则(因为有很多修改它的理由),以及开放闭合原则(每当添加新类型时,就必须修改原来的函数)。
-
解决方法是将switch语句埋藏到抽象工厂底下(由下面的代码片段一变成再下面的代码片段二)。
public Money calculatePay(Employee e) throws InvalidEmployeeType { switch (e.type) { case COMMISSIONED: return calculateCommissionedPay(e); case HOURLY: return calculateHourlyPay(e); case SALARIED: return calculateSalariedPay(e); default: throw new InvalidEmployeeType(e.type); } }
public abstract class Employee { public abstract boolean isPayday(); public abstract Money calculatePay(); public abstract void deliverPay(Money pat); } public interface EmployeeFactory { public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType; } public class EmployeeFactoryImpl implements EmployeeFactory { public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType { switch (r.type) { case COMMISSIONED: return new CommissionedEmployee(r); case HOURLY: return new HourlyEmployee(r); case SALARIED: retuen new SalariedEmployee(r); default: throw new InvalidEmployeeType(r.type); } } }
-
-
别害怕长名称,局域描述性的长名称,要比短而令人费解的名称好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数起个能说明其
功能
的名称。 -
命名方式要保持一致,使用与模块一脉相承的短语、名词和动词给函数起名。
-
函数参数(输入参数)。
-
最理想的参数数量是0,其次是1,再就是2,应尽量避免3个以上的参数。
-
向函数传入某个参数有两种普遍的类型。
-
问关于参数的问题,eg
boolean fileExists("MyFile")
-
操作该参数,把其转换为某种东西再输出eg
InputStream fileOpen("MyFile")
-
事件,在这种形式中,有输入参数而无输出参数。程序将函数看做一个事件,使用该参数能修改系统状态。eg
void passwordAttempFailedNtimes(int attempts)
,请小心使用这种形式,应该让读者了解这是一个事件,并谨慎的选用名称和上下文。 -
不推荐向函数传输boolen类型的输入参数,因为这样做,方法签名会变得复杂,且相当于大声宣布此函数不只是做一件事情,即为true时做一件事情,为false时做另外一件事情。可以将该函数拆分为两个无参函数,在参数为true时的无参函数起一个名字,为false时无参函数起另一个名字。
-
有的时候双函数刚刚好(比如标示一个坐标),而有的时候应该尽量将双函数拆分为单参数函数。
-
如果有些函数需要2个、3个或者3个以上的参数,就说明其中一些参数该封装为类了。
Circle makeCirle(double x, double y, double radius); Circle makeCirle(Point center, double radius)
-
动词与关键词。给函数起个好名字,能较好的解释函数的意图,以及参数的顺序和意图。对于单参数函数,应该形成一种非常良好的动词/名词对形式。例如,write(name),更好的命名应该是writeFiled(name),这告诉我们,name是一个filed。或者也可以把参数的名称编码为函数名,例如把assertEquals改为assertExpectedEqualsActual(expected, equals),这用便可大大减轻记忆参数顺序的负担。
-
函数参数(输出参数)。普遍而言,应该避免使用输出参数。输出参数是指:一个函数的输入参数,你修改了它的内部状态,但是却啥也不提示。eg
appendFooter(s)
,只看这个调用可能会比较迷惑,当看了函数的全部签名后egpublic void appendFooter(StringBuffer report)
后,才能明白。在面向对象编程出现之前,可以接受输出参数,但是在面向对象之后,对输出参数的需求就已经消失了,因为this也有输出函数的意味。换言之,你可以使用report.appendFooter()
-
-
-
分隔指令与询问。函数要么做一件事情,要么问一件事情,但是二者不可兼得。函数应该修改某对象的状态,或者返回该对象的有关信息。如果两样都干,就会导致混乱。例如下面的函数,它会判断属性是否存在之后,再为其设置value。
public boolean set(String attribute, String value)
更好的做法是,将其拆分为两个函数
if (attributeExists("UserName")) setAttribute("UserName", "unclebob");
-
使用异常替代返回错误码。从指令式函数返回错误码略微违反了指令与询问分隔的规则,因为它鼓励了在if语句判断中把指令当做表达式使用,虽然其不会引起动词、形容词混淆,但是会导致更深层次的嵌套结构。当返回错误码时,它要求调用者立刻处理错误。但是如果使用异常替代错误码,错误处理代码就能从主路径中分离出来,从而得到简化。
-
抽离try/catch代码块。
-
一般错误码会在一个类或者枚举中定义,但是这样的类就是一个依赖磁铁。其它很多类都得导入和使用它,当这个类重新编译时,所有其它的类都需要重新编译和构建。使用异常代替错误码,新异常就可以从异常类中派生出来,从而无需编译或者重新部署。