函数是实现程序功能的最基本单元,每一个程序都是由最基本的函数组成的。编写函数是提高程序代码质量最关键的一步。本文将从函数命名、代码分布和技巧入手,讨论如何编写一个可读性强、易维护、易测试的函数。命名从命名开始,这是提高可读性的第一步。如何命名变量和函数一直是开发者心中的痛点之一,对于母语不是英语的我们来说更是难上加难。先说说函数命名的一些感想:采用统一的命名规则在讲如何给函数起一个准确优雅的名字之前,首先最重要的是要有一个统一的命名规则。这是提高代码可读性的最基本准则。帕斯卡命名法和驼峰命名法是目前比较流行的两种命名规则。不同语言采用的规则可能不同,但要记住一件事:保持团队和个人风格的一致。1.Pascal的命名法Pascal的命名法很简单:当多个词组成一个名字时,每个词的首字母大写。例如:publicvoidSendMessage();publicvoidCalculatePrice();在C#中,这种命名常用于类、属性、函数等。在JS中,构造函数也推荐使用这种命名方式。2.驼峰命名法驼峰命名法与帕斯卡命名法非常相似。多个单词组成一个名字时,第一个单词全部小写,后面单词的首字母大写。例如:varsendMessage=unction(){};varcalculatePrice=function(){};驼峰命名一般用于字段、局部变量、函数参数等,在JS中,函数也常用这种命名方式。采用哪种命名规则并不是绝对的,最重要的是要遵守团队约定和语言规范。尽可能完整地描述函数所做的一切。有些开发人员可能会觉得短函数名可能比长函数名看起来更简洁、更舒服。但一般来说,函数名越短,所描述的意思就越抽象。函数用户对函数的第一印象是函数名,进而了解函数的作用。我们应该尽可能描述功能所做的一切,以防止用户忽略或误解而导致潜在的错误。比如,假设我们做一个添加评论的功能,添加后返回评论总数,如何命名更合适呢?//没有完整描述的函数名varcount=functionaddComment(){}//完整的函数名varcount=functionaddCommentAndReturnCount(){};这只是一个简单的例子,实际开发中可能会遇到更复杂的情况,单一职责原则是我们在开发功能时必须遵循的原则,但有时当功能的单一职责无法实现时,请记住函数名称应尽可能描述一切。当你不能命名一个函数时,你应该分析这个函数的写法是否科学,可以做些什么来优化它。对于母语不是英语的开发人员来说,使用准确的描述性动词应该很困难。想要提高这个能力,最重要的还是要提高词汇量,多看优秀的代码,积累经验。以下是我自己的一些想法和看法:1、不要用太抽象、太宽泛的词。很多开发者会使用比较宽泛的动词来命名函数。最典型的例子就是get这个词。在我们平时的开发中,经常会通过各种方式来获取数据,但是每种方式都使用get有点过于抽象了。具体如何命名需要具体分析:(1)简单返回数据Person.prototype.getFullName=function(){returnthis.firstName=this.lastName;}(2)从远程获取数据varfetchPersons=function(){...$.ajax({})}(3)从本地存储加载数据varloadPersons=function(){};(4)通过计算获取数据varcalculateTotal=function(){};(5)从数组中查找数据varfindSth=function(arr){};(6)生成或从一些数据中获取varcreateSth=function(data){};varbuildSth=function(data){};varparseSth=function(data){};这是一个简单的例子,我们在正常开发中遇到的情况肯定会复杂很多。关键是靠单词的积累。阅读更多优秀的源代码。下面是一些常用的反义词。可以参考add/removeincrement/decrementopen/closebegin/endinsert/deleteshow/hidecreate/destorylock/unlocksource/targetfirst/lastmin/maxstar/stopget/putnext/previousup/downget/setold/new制定命名规则也很重要不同的项目和需求,尤其是在团队合作中,不同的项目和需求可能会导致不同的命名约定。例如,我们通常采用的命名规则是动宾结构,即动词在名词之前。但是有一些项目,比如数据接口之类的项目,有的团队会使用先名后动词的形式,例如:publicstaticProduct[]ProductsGet(){};publicstaticProduct[]ProductsDel(){};publicstaticCustomer[]CustomerDel(){};publicstaticCustomer[]CustomerDel(){};这样做的好处是,看到前面的名词,比如ProductsGet,就可以很快知道这是一个产品相关的数据接口。当然,这不是绝对的。关键是团队必须共同制定并遵守同一套命名规则。函数参数函数使用者在调用函数时必须严格遵守函数定义的参数,这对函数的易用性和可测试性至关重要。下面我从几个方面谈谈如何优化函数参数的一些想法。参数个数毫无疑问,函数参数越多,函数的易用性越差,因为用户需要严格按照参数列表顺序输入参数。如果参数输入错误,将导致意想不到的结果。但是,函数参数越少越好吗?让我们看看下面的例子:varcount=0;varunitPrice=1.5;......varcalculatePrice=function(){returncount*unitPrice;}在这个例子中,我们使用函数calculatePrice来计算价格,函数不接收任何参数,直接通过两个全局变量unitPrice和count进行计算。这个函数的定义对于用户来说非常方便,不需要输入任何参数就可以直接调用。但是这里可能存在潜在的bug:全局变量可能在其他地方被修改为其他值,难以进行单元测试等等。因此,该函数可以传入数量和价格信息:varcalculatePrice=function(count,unitPrice){returncount*unitPrice;}这样函数的使用者在使用时需要传入参数进行调用,避免了可能全局变量存在的问题。另外减少了耦合,提高了可测试性,测试时不需要依赖全局变量。当然,在保证函数不依赖全局变量和可测试性的情况下,函数参数越少越好。《代码大全》建议将函数的参数限制在7以下,可以作为我们的参考。有时,我们不可避免地需要使用10个以上的函数。在这种情况下,我们可以考虑将类似的参数构造成一个类。让我们来看一个典型的例子。相信大家一定都做过这样一个功能,列表过滤,里面涉及到过滤、排序、分页等各种条件,如果把参数一一列举出来会很长,比如:varfilterHotel=function(city,checkIn,checkOut,price,star,position,wifi,meal,sort,pageIndex){}这个是过滤酒店的函数,参数是城市,入住退房时间,价格,***,位置,是否有wifi,是否有早餐,排序,页码等,实际情况可能会更多。在参数数量如此之多的情况下,我们可以考虑将一些类似的参数提取到类中:价格,星级,位置,wifi,膳食){this.price=price;this.star=star;this.position=position;this.wifi=wifi;this.meal=meal;}varfilterHotel=function(datePlce,hotelFeature,排序,页面索引){};多个参数被提取到对象中。虽然对象数量变多了,但是函数参数更清晰,调用起来也更方便。尽量不要使用bool类型作为参数有时候,我们会写使用bool作为参数的case,比如:vargetProduct=function(finished){if(finished){}else{}}//callgetProduct(true);如果没有注释,用户看到这段代码:getProduct(true),他一定不知道true是什么意思,还得去查函数定义才能明白这个函数是怎么用的。也就是说这个功能还不够清晰,应该考虑优化一下。通常有两种优化方式:(1)把函数拆分成两个,分成getFinishedProduct和getUnfinishedProduct两个函数(2)把bool转换成有意义的枚举getProduct(ProductStatus)如果输入参数被修改了,就不要修改输入参数在函数中,这很可能会导致潜在的bug,而用户并不知道调用函数后函数参数会被修改。输入参数的正确使用方法是只传入函数调用的参数。如果修改不可避免,请务必在评论中提及。尽量不要使用输出参数。使用输出参数意味着这个函数做了不止一件事,用户在使用时可能会感到困惑。正确的做法应该是分解函数,让函数只做一件事。编写函数体函数体是实现函数功能的全部逻辑,是一个函数中最关键的部分。说一下个人对函数代码编写的一些感想。把相关的操作放在一起有时候,我们会在一个函数中进行一系列的操作来完成一个功能,比如:=getMealPrice(mealCount);returnroomPrice+mealPrice;}此代码计算房价和早餐价格,然后将它们加在一起返回总价。乍看这段代码没有问题,但是我们分析代码的时候,首先分别获取房间数和早餐数,然后通过房间数和早餐数计算出两者的价格早餐。本例中房间数和计算房价的代码分散在两处,早餐价格的计算也分散在两处。也就是说这两部分相关的代码是散落各处的,所以在阅读代码的时候逻辑会略显不通,代码组织的也不够好。我们应该把相关的语句和操作放在一起,这样也有利于重构代码。我们修改如下:varcalculateTotalPrice=function(){varroomCount=getRoomCount();varroomPrice=getRoomPrice(roomCount);varmealCount=getMealCount();varmealPrice=getMealPrice(mealCount);returnroomPrice+mealPrice;}我们把相关操作放在一起,这样使代码看起来更干净,更容易重构。尽量减少代码嵌套我们平时写if、switch或者for语句,肯定写了多层if或者for语句嵌套。如果代码中的嵌套超过3层,将难以阅读。这非常困难。尽量避免多层代码嵌套,最好不要超过2层。下面说说我常用的一些减少嵌套的技巧或者方法。if语句嵌套问题多层if语句嵌套是很常见的。有什么减少嵌套的好方法吗?1.尽快终止函数或返回数据。如果函数在某个条件下可以直接终止,则应将此条件放在首位。让我们看看下面的例子。如果(条件1){如果(条件2){如果(条件3){}else{返回;}}else{返回;}}else{return;}这段代码中,if语句嵌套了3层,看起来很复杂现在,我们可以把第一面的return提取到最前面。如果(!条件1){返回;}if(!condition2){return;}if(!condition3){return;}//doSth在这段代码中,我们将condition1等于false的语句抽取出来,直接终止函数。将多级嵌套的if语句重构为只有一层if语句,代码更清晰。注意:一般我们在写if语句的时候,会先写条件为真,这样更符合我们的思维习惯。如果是多层嵌套的情况,优先考虑减少if语句的嵌套2.不适用if语句或switch语句条件语句一般是不可避免的。有时候,我们需要判断很多条件,写很多if-Elseif语句,嵌套的话就更麻烦了。如果哪天增加了新的需求,我们还得加一条if分支语句,不仅修改起来麻烦,而且容易出错。《代码大全》提出的表驱动方式可以有效解决if语句带来的问题。让我们看下面的例子:="case4"){return4;}这段代码依次判断四种情况。如果再增加一个case,我们就需要再增加一个if分支,这可能会带来潜在的问题。如何优化这部分代码呢?我们可以使用Map或Dictionary将每个案例与相应的值对应起来。varmap={"case1":1,"case2":2,"case3":3,"case4":4}returnmap[条件];经过地图优化后,整个代码不仅更简洁,而且更方便,不易修改错误。当然,很多时候我们的条件判断语句并不是那么简单,可能会涉及到复杂的逻辑运算。详细介绍可以参考《代码大全》的第18章。3、将内嵌套抽取出来作为调用多层嵌套的函数,我们也可以将内嵌套抽取成一个新的函数,再调用该函数,这样代码更清晰。for循环嵌套优化for循环嵌套比if嵌套复杂,读起来会比较麻烦。这里有几点需要注意:1.for循环最多只能嵌套两层2.提取内循环到新函数3.多层循环时,不要简单地将索引变量命名为i,j,k等,容易造成混淆。提取复杂的逻辑需要有特定的含义。有的时候在语义上,我们会写出一些比较复杂的逻辑,读代码的人看到后可能不知道该怎么做。这个时候应该把这段复杂的逻辑代码提取出来。if(age>18&&gender=="man"){//doSth}这段代码的意思是如果年龄大于18,并且是男性,就可以doSth,但是还是不够明确,可以提取出来varcanDoSth=function(age,gender){returnage>18&&gender=="man";}.......if(canDoSth(age,gender)){//doSth}虽然多了一个函数,但是代码更清晰,更语义化。小结本文从函数命名、函数参数和函数代码编写三个方面来谈谈如何写一个函数。文中提到了很多具体情况。当然,日常编码中肯定会有比较复杂的情况,可能是我暂时没有想到。我简单总结了几点:1.准确命名变量和函数2.不要有重复逻辑的代码3.一个函数的行数不要超过20行。这里的20行只是一个近似值,不一定是这个数字4.减少嵌套。我相信每个人都会在这方面有很多经验。欢迎交流,共同提高代码质量。
