函数是实现程序功能的最基本单位,每个程序都是由一个个最基本的函数构成的。写好一个函数是提升程序代码质量最关键的一步。本文就函数的编写,从函数命名,代码分布,技巧等方面入手,谈谈如何写好一个可读性高、易维护,易测试的函数。ajax
命名
首先从命名提及,命名是提升可读性的第一步。如何为变量和函数命名一直是开发者心中的痛点之一,对于母语非英语的咱们来讲,更是难上加难。下面我来讲说如何为函数命名的一些想法和感觉:数组
采用统一的命名规则
在谈及如何为函数取一个准确而优雅的名字以前,首先最重要的是要有统一的命名规则。这是提升代码可读性的最基础的准则。
帕斯卡命名法和驼峰命名法是目前比较流行的两种规则,不一样语言采用的规则可能不同,可是要记住一点:保持团队和我的风格一致。
一、帕斯卡命名法
帕斯卡命名法简单地说就是:多个单词组成一个名称时,每一个单词的首字母大写。好比:ide
1 public void SendMessage (); 2 public void CalculatePrice ();
在C#中,这种命名法经常使用于类、属性,函数等等,在JS中,构造函数也推荐采用这种方式命名。函数
二、驼峰命名法
驼峰命名法和帕斯卡命名法很相似,多个单词组成一个名称时,第一个单词所有小写,后面单词首字母大写。好比:post
1 var sendMessage = function () {}; 2 var calculatePrice = function () {};
驼峰命名法通常用于字段、局部变量、函数参数等等。,在JS中,函数也经常使用此方法命名。单元测试
采用哪一种命名规则并不绝对,最重要的是要遵照团队约定,语言规范。测试
尽量完整地描述函数所作的全部事情
有的开发者可能以为相较于长函数名来讲,短函数名看起来可能更简洁,看起来也更舒服。可是一般来讲,函数名称越短其描述的意思越抽象。函数使用者对函数的第一印象就是函数名称,进而了解函数的功能,咱们应该尽量地描述到函数所作的全部事情,防止使用者不知道或误解形成潜在的错误。
举个例子,假设咱们作一个添加评论的功能,添加完毕后并返回评论总数量,如何命名比较合适呢?fetch
1 // 描述不够完整的函数名 2 var count = function addComment() {}; 3 4 // 描述完整的函数名 5 var count = function addCommentAndReturnCount() {};
这只是简单的一个例子,实际开发中可能会遇到得更多复杂的状况,单一职责原则是咱们开发函数要遵照的准则,可是有时候没法作到函数单一职责时,请记得函数名应该尽量地描述全部事情。当你没法命名一个函数时,应该分析一下,这个函数的编写是否科学,有什么办法能够去优化它。优化
采用准确的描述动词
这一点对母语非英语的开发者来讲应该是比较难的一点,想要提升这方面的能力,最主要的仍是要提升词汇量,多阅读优秀代码积累经验。
这里简单说说我本身的一些感想和见解:
一、不要采用太抽象普遍的单词
不少开发人员会采用一个比较宽泛的动词来为函数命名,最典型的一个例子就是get这个单词。咱们平时开发中常常会经过各类不一样的方式拿到数据,可是每一种方式都用get就有点太抽象了。具体如何命名,要具体分析:
(1)简单的返回数据ui
1 Person.prototype.getFullName = function() { 2 return this.firstName = this.lastName; 3 }
(2)从远程获取数据
1 var fetchPersons = function () { 2 ... 3 $.ajax({ 4 }) 5 }
(3)从本地存储加载数据
1 var loadPersons = function () {};
(4)经过计算获取数据
1 var calculateTotal = function () {};
(5)从数组中查找数据
1 var findSth = function (arr) {};
(6)从一些数据生成或获得
1 var createSth = function (data) {}; 2 var buildSth = function (data) {}; 3 var parseSth = function(data) {};
这是一个简单的例子,咱们平时开发中遇到的状况确定会复杂得多,关键仍是靠单词的积累,多阅读优秀源码
下面是整理的一些经常使用的对仗词,你们能够参考使用
1 add/remove increment/decrement open/close 2 begin/end insert/delete show/hide 3 create/destory lock/unlock source/target 4 first/last min/max star/stop 5 get/put next/previous up/down 6 get/set old/new
根据不一样项目和需求制定好命名规则
这一点也是很重要的,尤为是在团队合做中,不一样的项目和需求可能致使的不一样的命名规则。
好比咱们一般采用的命名规则是动宾结构,也就是动词在前,名词灾后。可是有一些项目,好比数据接口等项目中,有的团队会采用名字在前,动词在后的形式,例如:
1 public static Product[] ProductsGet(){}; 2 public static Product[] ProductsDel(){}; 3 public static Customer[] CustomerDel(){}; 4 public static Customer[] CustomerDel(){};
这种的好处是看到前面的名词,好比ProductsGet,就能很快的知道这是产品相关的数据接口。
固然这个并非绝对的,关键仍是要团队共同制定和遵照同一套命名规则。
函数参数
函数使用者在调用函数时,必须严格遵照函数定义的参数,这对函数的易用性,可测试性等方面都是相当重要的。下面我从几个方面来谈谈关于如何优化好函数参数的一些想法。
参数数量
毫无疑问,函数参数越多,函数的易用性就越差,由于使用者须要严格眼中参数列表依次输入参数,若是某个参数输错,将致使不可意料的结果。
可是,函数参数就必定越少越好吗?咱们来看看下面的例子:
1 var count = 0; 2 var unitPrice = 1.5; 3 .... 4 ... 5 var calculatePrice = function () { 6 return count * unitPrice; 7 }
在这个例子中,咱们经过calculatePrice这个函数来计算价格,函数不接收任何参数,直接经过两个全局变量unitPrice和count进行计算。这种函数的定义对使用者来讲很是方便,直接调用便可,不用输入任何参数。可是这里可能会有潜在的bug:全局变量可能在其余地方被修改为其余值了,难以进行单元测试等等问题。因此,这个函数能够传入数量和价格信息:
1 var calculatePrice = function(count, unitPrice) { 2 return count * unitPrice; 3 }
这种方式下,函数使用者在使用时,要传入参数进行调用,避免了全局变量可能存在的问题。另外也下降了耦合,提升了可测试性,在测试的时候就没必要依赖于全局变量。
固然,在保证函数不依赖于全局变量和测试性的状况下,函数参数仍是越少越好。《代码大全》中提出将函数的参数限制在7个之内,这个能够做为咱们的参考。
有的时候,咱们不可避免地要使用超过10个以上函数,在这中状况下,咱们能够考虑将相似的参数构形成一个类,咱们来看看一个典型的例子。
我相信你们平时必定作过这样的功能,列表筛选,其中涉及到各类条件的筛选,排序,分页等等功能,若是将参数一个一个地列出来一定会很长,例如:
1 var filterHotel = function (city, checkIn, checkOut, price, star, position, wifi, meal, sort, pageIndex) {}
这是一个筛选酒店的函数,其中的参数分别是城市,入住和退房时间,价格,星级,位置,是否有wifi,是否有早餐,排序,页码等等,实际的状况可能会更多。在这种参数特别多的状况下,咱们能够考虑将一些类似的参数提取成类出来:
1 function DatePlace (city, checkIn, checkOut){ 2 this.city = city; 3 this.checkIn = checkIn; 4 this.checkOut = checkOut 5 } 6 7 function HotelFeature (price, star, position, wifi, meal){ 8 this.price = price; 9 this.star = star; 10 this.position = position; 11 this.wifi = wifi; 12 this.meal = meal; 13 } 14 15 var filterHotel = function (datePlce, hotelFeature, sort, pageIndex) {};
将多个参数提取成对象了,虽然对象数量增多了,可是函数参数更清晰了,调用起来也更方便了。
尽可能不要使用bool类型做为参数
有的时候,咱们会写出使用bool做为参数的状况,好比:
1 var getProduct = function(finished) { 2 if(finished){ 3 } 4 else{ 5 } 6 } 7 8 // 调用 9 getProduct(true);
若是没有注释,使用者看到这样的代码:getProduct(true),他确定搞不清楚true是表明什么意思,还要去查看函数定义才能明白这个函数是如何使用的。这就意味着这个函数不够清晰,就应该考虑去优化它。一般有两种方式去优化它:
(1)将函数一分为二,分红两个函数getFinishedProduct和getUnFinishedProduct
(2)将bool转换成有意义的枚举getProduct(ProductStatus)
不要修改输入参数
若是输入参数在函数内被修改了,颇有可能形成潜在的bug,并且使用者不知道调用函数后竟然会修改函数参数。
正确使用输入参数的作法应该是只传入参数用于函数调用。
若是不可避免地要修改,必定要在注释中说明。
尽可能不要使用输出参数
使用输出参数说明这个函数不仅作了一件事情,并且使用者使用的时候可能还会感到困惑。正确的方式应该是分解函数,让函数只作一件事。
编写函数体
函数体就是实现函数功能的整个逻辑,是一个函数最关键的地方。下面我谈谈关于函数代码编写的一些我的想法。
相关操做放在一块儿
有的时候,咱们会在一个函数内进行一系列的操做来完成一个功能,好比:
1 var calculateTotalPrice = function() { 2 var roomCount = getRoomCount(); 3 var mealCount = getMealCount(); 4 5 var roomPrice = getRoomPrice(roomCount); 6 var mealPrice = getMealPrice(mealCount); 7 8 return roomPrice + mealPrice; 9 }
这段代码计算了房间价格和早餐价格,而后将二者相加返回总价格。
这段代码乍一看,没有什么问题,可是咱们分析代码,咱们先是分别获取了房间数量和早餐数量,而后再经过房间数量和早餐数量分别计算二者的价格。这种状况下,房间数量和计算房间价格的代码分散在了两个位置,早餐价格的计算也是分散到了两个位置。也就是两部分相关的代码分散在了各处,这样阅读起代码来逻辑会略显不通,代码组织不够好。咱们应该让相关的语句和操做放在一块儿,也有利于重构代码。咱们修改以下:
1 var calculateTotalPrice = function() { 2 var roomCount = getRoomCount(); 3 var roomPrice = getRoomPrice(roomCount); 4 5 var mealCount = getMealCount(); 6 var mealPrice = getMealPrice(mealCount); 7 8 return roomPrice + mealPrice; 9 }
咱们将相关的操做放在一块儿,这样代码看起来更清晰了,并且也更容易重构了。
尽可能减小代码嵌套
咱们平时写if,switch或for语句是常有的事儿,也必定写过多层if或for语句嵌套的状况,若是代码里的嵌套超过3层,阅读起来就会很是困难了。咱们应该尽可能避免代码嵌套多层,最好不要超过2层。下面我来讲说我平时一些减小嵌套的技巧或方法。
if语句嵌套的问题
多层if语句嵌套是常有的事情,有什么好的方法能够减小嵌套呢?
一、尽早终止函数或返回数据
若是符合某个条件下能够直接终止函数,则应该将这个条件放在第一位。咱们来看看下面的例子。
1 if(condition1) { 2 if(condition2){ 3 if(condition3){ 4 } 5 else{ 6 return; 7 } 8 } 9 else{ 10 return; 11 } 12 } 13 else { 14 return; 15 }
这段代码中if语句嵌套了3层,看起来已经很复杂了,咱们能够将最后面的return提取到最前面去。
1 if(!condition1){ 2 return; 3 } 4 if(!condition2){ 5 return; 6 } 7 if(!condition3){ 8 return; 9 } 10 //doSth
这段代码中,咱们把condition1等于false的语句提取到前面,直接终止函数,将多层嵌套的if语句重构成只有一层if语句,代码也更清晰了。
注意:通常状况下,咱们写if语句会将条件为true的状况写在前面,这也比较符合咱们的思惟习惯。若是是多层嵌套的状况,应该优先减小if语句的嵌套
二、不适用if语句或switch语句
条件语句通常来讲是不可避免的,有的时候,咱们要判断不少条件就会写不少if-elseif语句,嵌套的话,就更加麻烦了。若是有一天增长了新需求,咱们就要去增长一个if分支语句,这样不只修改起来麻烦,并且容易出错。《代码大全》提出的表驱动法能够有效地解决if语句带来的问题。咱们来看下面这个例子:
1 if(condition == “case1”){ 2 return 1; 3 } 4 elseif(condition == “case2”){ 5 return 2; 6 } 7 elseif(condition == “case3”){ 8 return 3; 9 } 10 elseif(condition == “case4”){ 11 return 4; 12 }
这段代码分别依次判断了四种状况,若是再增长一种状况,咱们就要再新增一个if分支,这样就可能形成潜在的问题,如何去优化这段代码呢?咱们能够采用一个Map或Dictionary来将每一种状况和相应值一一对应。
1 var map = { 2 "case1":1, 3 "case2":2, 4 "case3":3, 5 "case4":4 6 } 7 return map[condition];
经过map优化后,整个代码不只更加简洁,修改起来也更方便并且不易出错了。
固然,不少时候咱们的条件判断语句并非这么简单的,可能会涉及到复杂的逻辑运算,你们能够查看《代码大全》第18章,其中有详细的介绍。
三、提取内层嵌套为一个函数进行调用
多层嵌套的时候,咱们还能够将内层嵌套提取到一个新的函数中,而后调用该函数,这样代码也就更清晰了。
for循环嵌套优化
for循环嵌套相比于if嵌套来讲更加复杂,阅读起来会更麻烦,下面说说几点要注意的东西:
一、最多只能两层for循环嵌套
二、提取内层循环到新函数中
三、多层循环时,不要简单地位索引变量命名为i,j,k等,容易形成混淆,要有具体的意思
提取复杂逻辑,语义化
有的时候,咱们会写出一些比较复杂的逻辑,阅读代码的人看到后可能搞不清楚要作什么,这个时候,就应该提取出这段复杂的逻辑代码。
1 if (age > 18 && gender == "man") { 2 //doSth 3 }
这段代码表示当年龄大于18而且是男性的话,能够doSth,可是仍是不够清晰,能够将其提取出来
1 var canDoSth = function (age, gender){ 2 return age > 18 && gender == "man"; 3 } 4 ... 5 ... 6 ... 7 if(canDoSth(age, gender)){ 8 //doSth 9 }
虽然说多了一个函数,可是代码更加清晰和语义化了。
总结
本文从函数命名,函数参数和函数的代码编写三个方面谈了关于如何编写好一个函数的感觉和想法。文中提到了不少具体的状况,固然平常编码中确定会遇到更多复杂的状况可能我暂时没有想到。我简单的概括了几点:
一、准确地对变量、函数命名
二、不要有重复逻辑的代码
三、函数的行数不要超过20行,这里的20行只是个大概,并不必定是这个数字
四、减小嵌套
我相信你们必定会不少关于这方面的经验,欢迎进行交流,共同提升代码质量。