public class User { private String userName; private String userPassword; private String userAvatarUrl; //... }
在这里我们可以简化变量的命名,因为在调用的时候可以借助对象+属性的名称来明确表达意思:
User user = new User(); user.getName(); // 借助user对象这个上下文
另外也函数参数也可以借助函数名上下文来简化命名:
public void uploadUserAvatarImageToAliyun(String userAvatarImageUri); //利用上下文简化为: public void uploadUserAvatarImageToAliyun(String imageUri);
命名要可读可搜索:
get
相关的方法都用get
,避免自立规范,又去用query
或者list
之类的方法来表达同样的意思,实现同类型的函数。 大家都是selectXXX
,自己就避免使用queryXXX
;大家是addXXX
你就别用insertXXX
。如何命名接口和抽象类
IUserSerbice
,实现类是UserService
;另一种不加前缀,但是实现类加Impl在后面,接口是UserService
,实现类是UserServiceImpl
。注释到底该写什么:做什么、为什么、怎么做。
```java public boolean isValidPasword(String password) { // check if password is null or empty if (StringUtils.isBlank(password)) { return false; }
// check if the length of password is between 4 and 64 int length = password.length(); if (length < 4 || length > 64) { return false; }
// check if password contains only a~z,0~9,dot for (int i = 0; i < length; ++i) { char c = password.charAt(i); if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.')) { return false; } } return true; } ```
注释愈多越好?:凡事有个度。太少可能介绍不明白,太多可能又意味着代码本身可读性差,而且过多注释又影响代码可读性。如果代码一改忘记改注释还会造成疑惑。
一般类和函数要写注释,而且写的可能全面、详细,而函数内部注释要相对少一些,一般都是靠好的命名、提炼函数、解释性变量、总结性注释提高代码可读性。
类、函数多大才合适:就像炒菜说盐少许,这个少许就很难量化。
一行代码最长多长合适?: Google Java Style Code 建议是100个字符,但是实际情况团队自定,一般不超过一个显示屏宽度最好。但是也不能太小导致长语句被折成两行。
善于用空行分割单元块: 在不方便将独立的逻辑代码块拆分成多个函数的情况下为了让逻辑清晰,不仅可以用注释,还可以用空行分割。另外,在成员变量和函数之间、静态成员与普通成员变量之间、函数之间甚至是各个成员变量之间,都可以利用空行,让代码更有结构性和清晰有条理。
四格缩进还是两格缩进?: 内部统一,个人喜好。别用tab,因为tab在不同的ide下有的是四格有的是两格有的是空格缩进有的使用一个tab缩进都会造成混乱。
大括号另起一行?:个人喜好,团队统一。
类中成员的排列顺序。
// 重构前的代码 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; } //... } // 重构后的代码:提炼函数之后逻辑更加清晰 public void invest(long userId, long financialProductId) { 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; }
这个也可以靠到SRP上,单一责任原则,
invest
函数中尽量不包含一个日期处理的实现。
public User getUser(String username, String telephone, String email); // 拆分成多个函数 public User getUserByUsername(String username); public User getUserByTelephone(String telephone); public User getUserByEmail(String email);
2. 将函数的参数封装成对象,例如
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);
这样做还能提高接口的兼容性。如果这个函数是对外暴露的远程接口,当修改参数的时候,只需要该对象就好了,远程调用可能就不需要修改代码来兼容。
不要在函数中使用布尔类型的标识参数来控制内部逻辑,true走这个逻辑,false走另一个。这明显违背单一职责原则和接口隔离原则。可以拆成两个函数,可读性更好。
public void buyCourse(long userId, long courseId, boolean isVip); // 将其拆分成两个函数 public void buyCourse(long userId, long courseId); public void buyCourseForVip(long userId, long courseId);
不过如果函数是private函数,影响范围有限,或者拆分之后他们还是会被经常同时使用,就可以也不用拆开。
// 拆分成两个函数的调用方式 boolean isVip = false; //...省略其他逻辑... if (isVip) { buyCourseForVip(userId, courseId); } else { buyCourse(userId, courseId); } // 保留标识参数的调用方式更加简洁 boolean isVip = false; //...省略其他逻辑... buyCourse(userId, courseId, isVip);
除了使用布尔类型,还有一种是使用是否null来判断走哪块逻辑。这时候也要拆分比较好。职责更明确。
public List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) { 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函数,更加清晰、易用 public List<Transaction> selectTransactionsBetween(Long userId, Date startDate, Date endDate) { return selectTransactions(userId, startDate, endDate); } public List<Transaction> selectTransactionsStartWith(Long userId, Date startDate) { return selectTransactions(userId, startDate, null); } public List<Transaction> selectTransactionsEndWith(Long userId, 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) { // ... }
public boolean checkUserIfExisting(String telephone, String username, String email) { if (!StringUtils.isBlank(telephone)) { User user = userRepo.selectUserByTelephone(telephone); return user != null; } if (!StringUtils.isBlank(username)) { User user = userRepo.selectUserByUsername(username); return user != null; } if (!StringUtils.isBlank(email)) { User user = userRepo.selectUserByEmail(email); return user != null; } return false; } // 拆分成三个函数 public boolean checkUserIfExistingByTelephone(String telephone); public boolean checkUserIfExistingByUsername(String username); public boolean checkUserIfExistingByEmail(String email);
// 示例一 public double caculateTotalAmount(List<Order> orders) { if (orders == null || orders.isEmpty()) { return 0.0; } else { // 此处的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 != null) { // 跟下面的if语句可以合并在一起 if (str.contains(substr)) { matchedStrings.add(str); } } } } return matchedStrings; }
* 使用continue,break,return提前退出嵌套。
// 重构前的代码 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)) { matchedStrings.add(str); // 此处还有10行代码... } } } 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); // 此处还有10行代码... } } return matchedStrings; }
* 调整执行顺序来减少嵌套
// 重构前的代码 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 == null || substr == null) { //先判空 return Collections.emptyList(); } List<String> matchedStrings = new ArrayList<>(); for (String str : strList) { if (str != null) { if (str.contains(substr)) { matchedStrings.add(str); } } } return matchedStrings; }
* 将部分嵌套逻辑封装为函数调用,减少嵌套
// 重构前的代码 public List<String> appendSalts(List<String> passwords) { if (passwords == null || passwords.isEmpty()) { return Collections.emptyList(); } List<String> passwordsWithSalt = new ArrayList<>(); 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.emptyList(); } List<String> passwordsWithSalt = new ArrayList<>(); for (String password : passwords) { if (password == null) { continue; } passwordsWithSalt.add(appendSalt(password)); } return passwordsWithSalt; } private String appendSalt(String password) { String passwordWithSalt = password; if (password.length() < 8) { // ... } else { // ... } return passwordWithSalt; }
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; }
* 使用解释性变量来解释复杂表达式
if (date.after(SUMMER_START) && date.before(SUMMER_END)) { // ... } else { // ... } // 引入解释性变量后逻辑更加清晰 boolean isSummer = date.after(SUMMER_START)&&date.before(SUMMER_END); if (isSummer) { // ... } else { // ... }