如何写出整洁的函数

1,224
原文链接: mp.weixin.qq.com

Talk is cheap show me the code! 我想这句话充分表达了代码的重要性,而我们大部分的代码就是函数,整洁的函数优雅、高效,让人赏心悦目!它能够很容易的被修改、应该讲述事实,不引人猜测。为了写出整洁的函数,码农们一直在努力着、探索着、实践着,在这篇文章中,笔者结合自己多年的工作经验和其他大牛的一些文章,总结出了一些原则、模式,供大家参考与实践!


0. 简洁函数的重要性


越简单的东西越容易理解,函数也是一样



写出简洁的函数一定是非常困难的,凡是写出那种一大堆让人读不懂的复杂函数的一定不是一个好的coder.


我们每次写代码要调用自己或别人写的函数的时候,是要看看具体的实现的,而如果该部分实现写的非常的晦涩难懂,那么在一定程度上也会影响我们的开发效率。

那么如何让写出的函数简洁呢?下面分别从以下几个方面介绍以下


  1. 取名字


命名无处不在,包名、类名、方法名、参数名、变量名等等。


好名字胜过千言万语!


名字要名副其实


要可以理解,不能模糊,要能表达具体的含义,如:


int i ; // 工作日


变量i什么也没说明,如果换成workDays是不是更好些?


避免误导


避免使用可能会引起误会的命名,如:


int pi


大家看到第一眼都以为是圆周率,但是实际上它是partIndex的缩写。

误导的另一种形式是相近的拼写。如:小写字母I和1,大写字母O和0。应当避免使用。


要有一定的区分度


避免使用a1,a2,......,aN这个样的命名,没有任何区分度。

user,userInfo,userData这几个命名同样是废话,也没有任何的区分度。


能读得懂的命名


一定是人类能够识别的语言,鸟语是看不懂的!

如:要表达一个交易时间的变量,可以命名为tradeymdhms,也可以命名为tradeDate。显然,人们更容易记住后者。(这也说明学好英语也是蛮重要的)


尽量用动词


方法名应是动词或动词短语,如:


void driveCar()


每个概念一个单词


给每个行为概念一个词,并一以贯之!这样当再次看到这个词的时候,就能大致明白是什么意思了。


如插入数据库记录,又是insert,又是save,又是put,这样很让人迷惑,所以用一个。


避免双关语


同一个单词,不同的概念,就是双关了!如:


  • 插入数据库记录 insert 

  •  list中插入数据 insert 


这个地方insert双关了,应该换一个,如:list中用append


优先技术性命名,其次为业务性命名


技术性命名是指技术领域内的名称,如:queue、pool等 

因为只有程序员才会读你写的代码(机器除外)! 

因此,在命名的时候,应该首先以技术性相关的词语来命名,如usersQueue等,其次才是业务性的命名 


 语境


 变量或命名应该有语境,这样才能自我说明。 

 如:name,单独放在这里没有什么意义,如果是userName,就有了意义,因为有user这个前缀作为语境。 

 不过,通常来说,不要添加没用的语境。比如user.userName,这里就显的很多余。 


一些原则 


 我们写的函数比写的类还要多,写好函数非常的重要,如何写好函数呢?


 最重要原则:


短小


其实我也不知道为什么要短小,只是写过看过了那么多的代码,函数就应该短小!

 那么到底多短小算短小呢?也没有一个完美的答案,但有一个标准: 

 

函数中代码块的缩进层级不该多于一层或两层。


只做一件事情


 每个函数只做一件事情。 

 一件事情不等于就是一个函数调用,处在同一个抽象层级上的就可以。 

 多了的,应该分解为更小的函数。 

 

同一抽象层级


一个函数的函数体应该只包含同一抽象层级的代码。


理解起来比较难,下面举个例子啊

   

    public void transfer(Account from,Account to,Money m){

        withdraw(from,m);

        deposit(to,m);

    }


    public void withdraw(Account account , Money m){

        account.withdraw(m);

    }


    public void deposit(Account account , Money m){

        account.deposit(m);

    }


其中withdraw和deposit处于同一抽象层,因为它们都只是定义了一个概念:支取、存入。 

而下面这样写,就不是一个抽象层级。

   public void transfer(Account from,Account to,Money m){

        withdraw(from,m);

        account.deposit(m);

    }


    public void withdraw(Account account , Money m){

        account.withdraw(m);

    }


望大家自行体会!


switch语句


 应该避免使用之,因为它首先违反了单一权责原则(SRP),因为有好几个要修改他的理由。 

 也违反了开放闭合原则(OCP),每当添加新类型时,就必须修改之!

 那怎么避免呢?可以使用抽象工厂来 尽量避免 它的发生。当然还有一些其他的方法,如:反射等。 


取个好名字


 函数越短小、功能越集中、就越便于取个好名字。 

 另外,别害怕长名字,要比短而令人费解的名称好很多。 别害怕花时间取名字,你会发现你不是在取名字,而是在整理思路! 

 名称要与抽象层级相符合! 

 要注意参数的顺序,最好参数也是名字的一部分,例如:


       transfer(from,to,withMoney)


封装条件、避免使用否定性条件


尽量把if,while等逻辑判断的条件封装到一个函数中。 

 且这个函数尽量是肯定式的,否定式的要不肯定式的难明白一些。 

 

魔术数字 


 单独出现的数字、字母等,让人无法理解,如: 

 

if(status==1)


这个“1”是什么意思?

 可以使用常量或枚举值的方式改造它,如: 

 

if(status==ACTIVE)


尽量不要返回NULL


 Null是很麻烦的,每次都要进行判断,导致了大量的恶心的代码。 

 尽量返回空对象,或抛出异常来解决。 


 函数参数 


 参数越少越好,最多不应该超过三个,不过还得具体问题具体分析。 

 参数越多,参数与参数之间的逻辑关系就越多,单元测试的组合就越多,越难以测试。 

 参数多的时候,可以将之封装为类。当然要把相同概念或问题域的封装在一起。 

 使用多个函数,要比使用一个函数多个参数要好一些!不过,这也通常说明,该函数不只做了一件事。 


 flag参数 


 flag是指一些标记,通常表示函数不只做一件事情,这个应该避免使用。 

 如:


transfer(Boolean useWX) 


这个函数其实做了两件事,一个是通过微信转账,另一个是不通过微信转账。应该把其一分为二,即:transfer(),transferUseWX() 。


 尽量没有副作用 


 应该是可以被重复调用的,而且尽量没有副作用。 

 尽量是幂等的。如转账,不会因为调用第二次而转了两次。 

 如果有副作用,在方法名中应该表达出来。如:createOrRetrunUser 

 

查询与命令相分离 


一个函数要么是查询的,即:不修改任何数据!,要么就是命令的,即:做业务,修改数据!


 二者不可兼得


错误处理


 错误处理就是一件事,应该是一个整体,而不是分散到各处的地方。 

 比如:在java等语言中,catch中的代码库就应该是一个函数! 

 

别重复自己(DRY) 


 重复很危险,他让你的思路变得混乱,修改起来麻烦,代码中散发着坏味道。 


 总结


 编写程序就是在讲故事,就是在把各个函数干净利落的拼装在一起,为此,应该: 


 取个好名字 

 短小的函数 

 抽象 

 DRY 

 DRY 

 DRY