《代码整洁之道》总结二之整洁的函数

来源:互联网 发布:雅思作文怎么准备 知乎 编辑:程序博客网 时间:2024/05/16 13:47

函数是所有程序中的第一组代码。


1、短小

函数的第一规则是短小。第二条规则是还要更短小。每个函数都一目了然,每个函数都只说一件事,而且每个函数都依次序把你带到下一个函数。这就是函数应达到的短小的程度。


代码块和缩进

if语句、else语句、while语句等,其中的代码块应该只有一行。函数的缩进层级不该多于一层或两层。


2、只做一件事

函数应该做一件事。做好这件事。只做这一件事。要判断函数是否不止做了一件事,就是看是否能再拆分出一个函数,该函数不仅只是单纯地重新诠释其实现。



3、每个函数一个抽象层级

要确保函数只做一件事,函数中的语句都要在同一抽象层级上。函数中混杂不同抽象层级,往往让人迷惑。读者无法判断某个表达式是基础概念还是细节。一旦细节与基础概念混杂,更多的细节就会在函数中纠结起来。


自顶向下读代码:向下规则

代码拥有自顶向下的阅读顺序。每个函数后面都跟着位于下一抽象层级的函数,这样在查看函数列表时,就能循抽象层级向下阅读了。


4、switch语句

写出短小的switch语句很难。即便是只有两种条件的switch语句也要比单个代码块或函数大得多。写出只做一件事的switch语句也很难。switch天生要做N件事。但是又无法避开switch语句,但是可以确保每个switch都埋藏在较低的抽象层级。可以通过多态来实现。
public Monkey calculatePay(Empoyee e) throws InvalidEmployeeType{    switch(e.type){        case COMMISSIONED:            return calculateCommissionedPay(e);        case HOURLY:            return calculateCommissionedPayHourlypay(e);        case SALARIED:            return calculateSalariedPay(e);        default:            throw new InvalidEmployeeType(e.type);    }} 

上述函数的问题:

一、太长,当出现新的雇员类型事,还会更长。

二、做了不止一件事。

三、违反了单一权责原则,因为有好几个修改它的理由。

四、违反了闭合原则,每当添加新类型时,就必须修改。

五、最麻烦的是可能是到处都有类似结构的函数。例如可能会有isPayDay(Employee e ,Date date),或 deliveryPay(Employee e ,Money pay)等等,他们的结构都有同样的问题。

该问题的解决方案是将switch语句埋到抽象工厂底下,不让任何人看到。该工厂使用switch语句为Employee的派生物创建适当的实体,而不同的函数,如calculatePay、isPayday和deliverPay等,则藉由Employee接口多态地接受派遣。

public abstract class Employee{    public abstract boolean isPayday();    public abstract Money calculatePay();    public abstract void deliverPay(Money pay);}---------------------------------------------------------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:                return SalariedEmployee(r);            default:                throw new InvalidEmployeeType(r.type);         }    }}

5、使用描述性的名称

别害怕长名称。长而具有描述性的名称,要比短而另人费解的名称好。

命名方式要保持一致。使用与模块名一脉相承的短语、名称和动词给函数命名。


6、函数参数

最理想的的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够特殊的理由才能用三个以上参数(多参数函数)--------------所以无论如何也不要这么做。

如果函数看起来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了。


7、无副作用

在函数checkPassword中调用了Session.initialize()。副作用就在于调用了initialize()方法。chekPassword函数,顾名思义,就是用来检查密码的。该名称并未暗示它会初始化该次会话。所以,当某个误信了函数名的调用者想要检查用户有效性时,就得冒抹除现有会话数据的风险。


8、分隔指令与询问

函数要么做什么事,要么回答什么事,但二者不可兼得。


9、使用异常替代饭后错误码

从指令式函数饭后错误码轻微违反了指令与询问分隔的规则。它鼓励了在if语句判断中把指令当做表达式使用。

if(deletePage(page) == E_OK)

这不会引起动词/形容词混淆,但却导致更深层次的嵌套结构。当返回错误码时,要求调用者立刻处理错误。


抽离try/catch代码块

try/catch代码块丑陋不堪。它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把try和catch代码块的主体部分抽离出来,另外形成函数。

public void delete(Page Page){    try{        deletePageAndAllReferences(page);    }catch(Exception e){        logError(e);    }}private void deletePageAndAllReferences(Page page) throws Exception{    deletePage(page);    registry.deleteReference(page.name);    configKeys.deleteKey(page.name.makeKey);}private void logError(Exception e){    logger.log(e.getMessage());}

上述中,logError函数只与错误处理有关,很容易理解然后就忽略掉。deletePageAndAllReference函数只与完全删除一个page有关。错误处理可以忽略掉。有了这样美妙的区隔,代码就更易于理解和修改了。

函数应该只做一件事。错误处理就是一件事。因此,处理错误的函数不该做其它的事。使用异常代替错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。


10、别重复自己

重复可能是软件中一切邪恶的根源。许多原则与实践规则都是为控制欲消除重复而创建。


11、结构化编程

只要函数保持短小,偶尔出现的return、break或continue语句没有坏处,甚至比单入单出原则更有表达力。另一方面,goto只在大函数中才有道理,所以应该尽量避免使用。


12、如何写出这样的函数

写函数时,一开始都冗长而复杂。然后打磨这些代码,分解函数、修改名称、消除重复。缩短和重新安置方法。有时还拆散类。同时保持测试通过。



大师级的程序员把系统当做故事来讲,而不是当作程序来写。






0 0