目录
1.引言
2.将复杂的代码模块化
3.避免函数的参数过多
4.移除函数中的 flag 参数
5.移除嵌套过深的代码
6.学会使用解释性变量
1.引言
本节介绍一些实用的编程技巧。编程技巧比较琐碎、比较多。在本节中,作者仅列出了一些个人认为非常实用的编程技巧,更多的技巧需要读者在实践中慢慢积累。
2.将复杂的代码模块化
在编写代码时,我们要有模块化思维,善于将大块的复杂的代码封装成类或函数,让阅读代码的人不会迷失在代码的细节中,这样能极大地提高代码的可读性。我们结合示例代码进行说明。
//重构前的代码
public void invest(long userId, long financialProductId){
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.DATE(calendar.get(Calendar.DATE)+1));
if(calendar.get(Calendar.DAY OF MONTH)==1){
return;
}
...
}
//原构后的代码:封装成isLastDayofMonth()函数之后,逻辑更加清晰
public void invest(long userId, long fnancialProductId){
if(isLastDayofMonth(new Date())){
return;
}
public boolean isLastDayOfMonth(Date date){
Calendar calendar=Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.DATE, (calendar.get(Calendar.DATE)+ 1));
if(calendar.get(Calendar.DAY_OF_MONTH)==1){
return true;
}
return false,
}
}
在重构前,imvest()函数中的关于时间处理的代码比较难理解。重构之后,我们将其抽象成isLastDayOfMonth函数,从该函数的命名,我们就能清晰地了解它的功能:判断某天是不是当月的最后一天。
3.避免函数的参数过多
如果函数的参数过多,那么我们在阅读或使用该函数时都会感到不方便。函数包含多少参数才算过多呢?当然,这也没有固定标准。根据作者的经验,函数的参数一般超过5个就算过多了,因为函数参数超过5个之后,在调用函数时,调用语句容易超出一行代码的长度,要将其分为两行甚至多行,导致代码的可读性降低。除此之外,参数过多也增加了传递出错风险。
如果导致函数的参数过多的原因是函数的职责不单一,那么我们可以通过将这个函数拆分成多个函数的方式来减少参数。示例代码如下。
public User getUser(String id,String username, String telephone, String email,String udid,
String uuid);//拆分成多个函数
public User getUserById(String id);
public User getUserByUsername(String username);
public User getUserByTelephone(String telephone);
public User getUserByEmail(String email);
public User getUserByUdid(String udid);
public User getUserByUuid(string uuid);
针对函数参数过多的问题,我们还可以通过将参数封装为对象的方式来解决。这种处理式不仅可以减少参数的个数,还能提高函数的兼容性。在向函数中添加新的参数时,只需要问对象中添加成员变量,不需要改变函数定义,原来的调用代码不需要修改。示例代码如下。
public void postBlog(String title, String summary, String keywords, String content
String category,long authorId);//将参数封装成对象
public class Blog{
private String title;
private String summary;
private String keywords;
private Strint content;
private String category;
private long authorId;
}
public void postBlog(Blog blog);
4.移除函数中的 flag 参数
我们不应该在函数中使用布尔类型的 fag(标识)参数来控制内部逻辑(hag为me时执行一个代码逻辑,fag为alse 时执行另一个代码逻辑),这违背单一职贵原则和接口隔离原则。我们建议将包含 flag参数的函数拆分成两个函数。示例代码如下,其中,isVip是 flag参数。
public void buyCourse(long userId, long courseId, boolean isVip) ;
//将其拆分成两个函数
public void buyCourse(long userId, long courseId);
public void buyCourseForVip(long userId, long courseId);
不过,如果函数是私有(private)函数,其影响范围有限,或者拆分之后的两个函数经常同时被调用,那么我们可以考虑保留 flag 参数。示例代码如下。
//拆分成两个函数之后的调用方式
boolean isVip=false;
...
if(isVip){
buyCourseForVip(userId,courseId)
}else (
buyCourse(userId,courseId);
}
//保留flag参数调用方式,代码更加简洁
boolean isVip = false;
...
buyCourse(userId,courseId,isVip);
实际上,在函数中,除使用布尔类型的 flag参数来控制内部逻辑以外,还有人喜欢使用参数是否为 null 来控制内部逻辑。对于后一种情况,我们也应该将这个函数拆分成多个函数。拆分之后的函数的职责明确。示例代码如下,其中,selectTransactions()函数根据参数startDate、endDate 是否为 null,执行不同的代码逻辑。
public list<Transaction> selectTransactions(Long userId, Date startDate, Date endDate){
if(startDate != null && endDate != null){
//查询两个时间之间的交易
}
if(startDate != null && endDate == null){
//查询startDate之后的所有交易
}
if(startDate ==null && endDate != null){
//查询endDate之前的所有交易
}
if (startDate == null && endDate == null){
//查询所有的交易
}
}
public List<Transaction> selectTransactionsBetween(Long userId, Date startDate, Date endDate) {
//拆分成多个公共(public)函数,代码变得清晰、易用
return selectTransactions(userId, startDate, endDate);
}
Public list<Transaction> selectTransactionsStartWith(Long userId, Date startDate)
return selectTransactions(userId, startDate, null);
}
Public list<Transaction> selectTransactionsEndWith(Long userld, Date endDate){
return selectTransactions(userId,null,endDate);
}
public List<Transaction> selectAllTransactions(Long userId){
return selectTransactions(userId,null,null);
}
private list<Transaction> selectTransactions(Long userId, Date startDate, Date endDate){
...
}
5.移除嵌套过深的代码
代码嵌套过深往往是因为if-else、switch-case和for 循环过度嵌套。作者建议嵌套最好不超过两层,如果嵌套超过两层,就要想办法减少嵌套层数。嵌套过深导致代码语句多次缩进大量代码语句超过一行的长度而被分成两行或多行,影响代码的可读性。
针对嵌套过深的问题,作者总结了下列4种常见的处理思路。
1)去掉几余的 if、else 语句,示例代码如下:
//示例一
public double caculateTotalAmount(List<Order> orders){
if(orders == null || orders.isEmpty()){
return 0.0;
}else{ //if内部使用return,因此,此处的else可以去掉
double amount = 0.0;
for(Order order:orders){
if(order != null){
amount += (order.getCount()*order.getPrice());
}
}
return amount;
}
//示例二
Public List<String> matchStrings(List<string> strList, String substr){
List<String> matchedStrings = new ArrayList<>();
if(strList != null && substr != null){
for (String str : strList){
if(str != nu11){//此处的if可以与下一行的if语句合并for(String str:strList)
if(str.contains(substr)){
matchedStrings.add(str);
}
}
}
}
return matchedStrings;
}
2)使用 continue、break和retum 关键字提前退出嵌套,示例代码如下。
//重构前的代码
public List<String> matchStrings(List<String> strList, String substr){
List<String> matchedStrings = new List<>();
if(strList != null && substr != null){
for(String str : strList){
if(str != null && str.contains(substr)){
matchedStrings.add(str);
...
}
}
}
return matchedStrings;
}
//重构后的代码:使用continue提前退出嵌套
public List<string> matchstrings(List<string> strlist,String substr){
List<String> matchedStrings = new ArrayList<>();
if(strList !=null && substr !=null){
for(String str:strList){
if(str==null || !str.contains(substr)){
continue;
}
matchedStrings.add(str);
...
}
return matchedStrings;
}
3)通过调整执行顺序来减少嵌套层数,示例代码如下。
//重构前的代码
public list<String> matchStrings(List<String> strlist,String substr){
List<String> matchedstrings = new ArrayList<>();
if(strList != null && substr != null){
for (String str:strlist){
if(str!=null){
if(str.contains(substr)){
matchedStrings.add(str);
}
}
}
return matchedStrings;
}
//重构后的代码:先执行判断是否为空逻辑,再执行正常逻辑
public list<string> matchstrings(List<string> strlist,String substr){
if(strList != nu11 && substr != nu1l){//先判断是否为空
return Collections.emptyList();
}
List<String> matchedStrings = new ArrayList<>();
for(String str:strList){
if(str!=null){
if(str.contains(substr)){
matchedStrings.add(str);
}
}
}
return matchedstrings;
}
4)我们可以将部分嵌套代码封装成函数,以减少嵌套层数,示例代码如下。
//重构前的代码
public List<string> appendSalts(List<string> passwords){
if(passwords == null || passwords.isEmpty()){
return Collections.emptylist();
}
List<String> passwordsWithSalt = new rrayList<>();
for(String password:passwords){
if(password==null){
continue;
}
if(password.length()<8){
...
}else {
...
}
}
return passwordswithSalt;
}
//重构后的代码:将部分代码封装为函数
public List<String> appendSalts(List<String> passwords){
if(passwords == null || passwords.isEmpty()){
return Collections.emptyiist();
}
List<String> pasowordanithSalt = new ArrayList<>();
for(String password :Passwords){
if(password == null){
continue;
}
passwordsWithSolt.add(appendSalt(password));
}
return passwordsWithSolt;
}
private String appendSalt(String password) {
String passwordWithSalt = password;
if(paasword.length()<8){
...
}else{
...
}
return passwordwithSalt;
}
6.学会使用解释性变量
解释性变量可以提高代码的可读性,也可以减少不必要的注释。常用的解释性变量有以下两种。
1)使用常量取代魔法数字,示例代码如下。
public double CalculatecircularArea(double radius) {
return (3.1415)*radius*radius;
}
//常量替代魔法数字
public static final Double PI = 3.1415;
public double CalculateCircularArea(double radius){
return PI * radius * radius;
}
2)使用解释性变量来解释复杂表达式,示例代码如下。
if (date.after(SUMMER_START) && date.before(SUMMER_END)){
...
}else {
...
}
//引入解释性变量后,代码更易被人理解
boolean isSummer = date.after(SUMMER START)&&date.before(SUMMER END);
if(isSummer){
...
}else {
...
}