☰
【设计模式之美】改善代码质量之:代码可读性
2026/11/3 1:15:54 网站建设 项目流程

文章目录

    • 1. 把代码分割成更小的单元块
    • 2. 避免函数参数过多
    • 3. 勿用函数参数来控制逻辑(ing)
    • 4. 函数设计要职责单一
    • 5. 移除过深的嵌套层次
    • 6. 学会使用解释性变量

1. 把代码分割成更小的单元块

大部分人阅读代码的习惯都是,先看整体再看细节。所以,我们要有模块化和抽象思维,善于将大块的复杂逻辑提炼成类或者函数,屏蔽掉细节。

这里我举一个例子来进一步解释一下。代码具体如下所示。

// 重构前的代码:getpublicvoidinvest(longuserId,longfinancialProductId){Calendarcalendar=Calendar.getInstance();calendar.setTime(date);calendar.set(Calendar.DATE,(calendar.get(Calendar.DATE)+1));if(calendar.get(Calendar.DAY_OF_MONTH)==1){return;}//...}// 重构后的代码:提炼函数之后逻辑更加清晰publicvoidinvest(longuserId,longfinancialProductId){if(isLastDayOfMonth(newDate())){return;}//...}publicbooleanisLastDayOfMonth(Datedate){Calendarcalendar=Calendar.getInstance();calendar.setTime(date);calendar.set(Calendar.DATE,(calendar.get(Calendar.DATE)+1));if(calendar.get(Calendar.DAY_OF_MONTH)==1){returntrue;}returnfalse;}

2. 避免函数参数过多

函数包含 3、4 个参数的时候还是能接受的,大于等于 5 个的时候,我们就觉得参数有点过多了,会影响到代码的可读性,使用起来也不方便。针对参数过多的情况,一般有 2 种处理方法。

  • 考虑函数是否职责单一(get),是否能通过拆分成多个函数的方式来减少参数。示例代码如下所示:
publicUsergetUser(Stringusername,Stringtelephone,Stringemail);// 拆分成多个函数publicUsergetUserByUsername(Stringusername);publicUsergetUserByTelephone(Stringtelephone);publicUsergetUserByEmail(Stringemail);
  • 将函数的参数封装成对象。示例代码如下所示
publicvoidpostBlog(Stringtitle,Stringsummary,Stringkeywords,Stringcontent,Stringcategory,longauthorId);// 将参数封装成对象publicclassBlog{privateStringtitle;privateStringsummary;privateStringkeywords;privateStrintcontent;privateStringcategory;privatelongauthorId;}publicvoidpostBlog(Blogblog);

除此之外,如果函数是对外暴露的远程接口,将参数封装成对象,还可以提高接口的兼容性。在往接口中添加新的参数的时候,老的远程接口调用者有可能就不需要修改代码来兼容新的接口了。

3. 勿用函数参数来控制逻辑(ing)

不要在函数中使用布尔类型的标识参数来控制内部逻辑,true 的时候走这块逻辑,false 的时候走另一块逻辑。这明显违背了单一职责原则和接口隔离原则。建议将其拆成两个函数,可读性上也要更好。

publicvoidbuyCourse(longuserId,longcourseId,booleanisVip);// 将其拆分成两个函数publicvoidbuyCourse(longuserId,longcourseId);publicvoidbuyCourseForVip(longuserId,longcourseId);

不过,如果函数是 private 私有函数,影响范围有限,或者拆分之后的两个函数经常同时被调用,我们可以酌情考虑保留标识参数。示例代码如下所示:

// 拆分成两个函数的调用方式booleanisVip=false;//...省略其他逻辑...if(isVip){buyCourseForVip(userId,courseId);}else{buyCourse(userId,courseId);}// 保留标识参数的调用方式更加简洁booleanisVip=false;//...省略其他逻辑...buyCourse(userId,courseId,isVip);

除了布尔类型作为标识参数来控制逻辑的情况外,还有一种“根据参数是否为 null”来控制逻辑的情况。针对这种情况,我们也应该将其拆分成多个函数。拆分之后的函数职责更明确,不容易用错。具体代码示例如下所示:

publicList<Transaction>selectTransactions(LonguserId,DatestartDate,DateendDate){if(startDate!=null&&endDate!=null){// 查询两个时间区间的transactions}if(startDate!=null&&endDate==null){// 查询startDate之后的所有transactions}if(startDate==null&&endDate!=null){// 查询endDate之前的所有transactions}if(startDate==null&&endDate==null){// 查询所有的transactions}}// 拆分成多个public函数,更加清晰、易用publicList<Transaction>selectTransactionsBetween(LonguserId,DatestartDate,DateendDate){returnselectTransactions(userId,startDate,endDate);}publicList<Transaction>selectTransactionsStartWith(LonguserId,DatestartDate){returnselectTransactions(userId,startDate,null);}publicList<Transaction>selectTransactionsEndWith(LonguserId,DateendDate){returnselectTransactions(userId,null,endDate);}publicList<Transaction>selectAllTransactions(LonguserId){returnselectTransactions(userId,null,null);}privateList<Transaction>selectTransactions(LonguserId,DatestartDate,DateendDate){// ...}

4. 函数设计要职责单一

实际上,对于函数的设计来说,更要满足单一职责原则。相对于类和模块,函数的粒度比较小,代码行数少,所以在应用单一职责原则的时候,没有像应用到类或者模块那样模棱两可,能多单一就多单一。

publicbooleancheckUserIfExisting(Stringtelephone,Stringusername,Stringemail){if(!StringUtils.isBlank(telephone)){Useruser=userRepo.selectUserByTelephone(telephone);returnuser!=null;}if(!StringUtils.isBlank(username)){Useruser=userRepo.selectUserByUsername(username);returnuser!=null;}if(!StringUtils.isBlank(email)){Useruser=userRepo.selectUserByEmail(email);returnuser!=null;}returnfalse;}// 拆分成三个函数publicbooleancheckUserIfExistingByTelephone(Stringtelephone);publicbooleancheckUserIfExistingByUsername(Stringusername);publicbooleancheckUserIfExistingByEmail(Stringemail);

思考点:但总体来说,不要将代码逻辑拆分封装的太细,这个度仁者见仁智者见智。

5. 移除过深的嵌套层次

代码嵌套层次过深往往是因为 if-else、switch-case、for 循环过度嵌套导致的。我个人建议,嵌套最好不超过两层,超过两层之后就要思考一下是否可以减少嵌套。
过深的嵌套本身理解起来就比较费劲,除此之外,嵌套过深很容易因为代码多次缩进,导致嵌套内部的语句超过一行的长度而折成两行,影响代码的整洁。

解决嵌套过深的方法也比较成熟,有下面 4 种常见的思路。

  • 去掉多余的 if 或 else 语句。代码示例如下所示:
// 示例一:getpublicdoublecaculateTotalAmount(List<Order>orders){if(orders==null||orders.isEmpty()){return0.0;}else{// 此处的else可以去掉doubleamount=0.0;for(Orderorder:orders){if(order!=null){amount+=(order.getCount()*order.getPrice());}}returnamount;}}// 示例二:getpublicList<String>matchStrings(List<String>strList,Stringsubstr){List<String>matchedStrings=newArrayList<>();if(strList!=null&&substr!=null){for(Stringstr:strList){if(str!=null){// 跟下面的if语句可以合并在一起if(str.contains(substr)){matchedStrings.add(str);}}}}returnmatchedStrings;}
  • 使用编程语言提供的 continue、break、return 关键字,提前退出嵌套。代码示例如下所示:
// 重构前的代码publicList<String>matchStrings(List<String>strList,Stringsubstr){List<String>matchedStrings=newArrayList<>();if(strList!=null&&substr!=null){for(Stringstr:strList){if(str!=null&&str.contains(substr)){matchedStrings.add(str);// 此处还有10行代码...}}}returnmatchedStrings;}// 重构后的代码:使用continue提前退出publicList<String>matchStrings(List<String>strList,Stringsubstr){List<String>matchedStrings=newArrayList<>();if(strList!=null&&substr!=null){for(Stringstr:strList){if(str==null||!str.contains(substr)){continue;}matchedStrings.add(str);// 此处还有10行代码...}}returnmatchedStrings;}
  • 调整执行顺序来减少嵌套。具体的代码示例如下所示:
// 重构前的代码publicList<String>matchStrings(List<String>strList,Stringsubstr){List<String>matchedStrings=newArrayList<>();if(strList!=null&&substr!=null){for(Stringstr:strList){if(str!=null){if(str.contains(substr)){matchedStrings.add(str);}}}}returnmatchedStrings;}// 重构后的代码:先执行判空逻辑,再执行正常逻辑publicList<String>matchStrings(List<String>strList,Stringsubstr){if(strList==null||substr==null){//先判空returnCollections.emptyList();}List<String>matchedStrings=newArrayList<>();for(Stringstr:strList){if(str!=null){if(str.contains(substr)){matchedStrings.add(str);}}}returnmatchedStrings;}

将部分嵌套逻辑封装成函数调用,以此来减少嵌套。具体的代码示例如下所示:

// 重构前的代码publicList<String>appendSalts(List<String>passwords){if(passwords==null||passwords.isEmpty()){returnCollections.emptyList();}List<String>passwordsWithSalt=newArrayList<>();for(Stringpassword:passwords){if(password==null){continue;}if(password.length()<8){// ...}else{// ...}}returnpasswordsWithSalt;}// 重构后的代码:将部分逻辑抽成函数publicList<String>appendSalts(List<String>passwords){if(passwords==null||passwords.isEmpty()){returnCollections.emptyList();}List<String>passwordsWithSalt=newArrayList<>();for(Stringpassword:passwords){if(password==null){continue;}passwordsWithSalt.add(appendSalt(password));}returnpasswordsWithSalt;}privateStringappendSalt(Stringpassword){StringpasswordWithSalt=password;if(password.length()<8){// ...}else{// ...}returnpasswordWithSalt;}

除此之外,常用的还有通过使用多态来替代 if-else、switch-case 条件判断的方法。

6. 学会使用解释性变量

常用的用解释性变量来提高代码的可读性的情况有下面 2 种。常量取代魔法数字。示例代码如下所示:

publicdoubleCalculateCircularArea(doubleradius){return(3.1415)*radius*radius;}// 常量替代魔法数字publicstaticfinalDoublePI=3.1415;publicdoubleCalculateCircularArea(doubleradius){returnPI*radius*radius;}

使用解释性变量来解释复杂表达式。示例代码如下所示:

if(date.after(SUMMER_START)&&date.before(SUMMER_END)){// ...}else{// ...}// 引入解释性变量后逻辑更加清晰booleanisSummer=date.after(SUMMER_START)&&date.before(SUMMER_END);if(isSummer){// ...}else{// ...}

参考:《设计模式之美》-- 王争

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询