算是读书笔记吧
极客时间--设计模式之美
命名
命名的一个原则就是以能准确达意为目标,长短主要取决于给谁看。
学会换位思考,假设自己不熟悉这块代码,从代码阅读者的角度去考量命名是否足够直观。
Github
或者有两个网站可以推荐:
CODELF可以搜索到很多Git上的代码命名
Free App可以搜索到很多App以及Web国际化翻译
长度
- 默认的、大家都比较熟知的词,可以用缩写
- 作用于小的,比如临时变量用短命名
- 作用域比较大的,比如全局变量、类名用长的命名
这里的短,并不是让你用a1、a2这种无法辨认的名字
而是比如用sec 表示 second、str 表示 string、num 表示 number、doc 表示 document这样的缩写。
利用上下文简化命名
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);
命名可读
选用的单词。最优先的是能大家看懂、实在不行最起码要能读出来
命名可搜索
比如数组是用array,还是list。插入是用insertXXX,还是addXXX。
这个更多的依赖统一规约,能减少很多不必要的麻烦
特殊前缀
抽象类、接口、常量的前缀在项目里能够统一
注释
类
类的注释要写得尽可能全面、详细。
让人一眼就能看明白这个类的大致作用和实现思路,而不需要进去查阅大量的代码
public函数/接口声明
如果他会给其他业务方使用,写上注释吧。是个好习惯
普通函数声明
通常我们可以通过良好的命名替代注释
但是对于一些特殊细节,比如参数和错误的特殊描述,需要进行注释
函数内部
函数内部的通常也是通过良好的命名以及解释性变量让代码自解释
但是对于内部比较复杂的函数,总结性注释可以很好的对函数进行拆分,使逻辑更清晰
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;
}
注释的维护成本
注释本身有一定的维护成本,所以并非越多越好。撰写精炼的注释,也是一门艺术
函数、类的大小
大小这个东西很主观,就像对于“放盐少许”中的“少许”,即便是大厨也很难告诉你一个特别具体的量值。
函数代码行数,不要超过一个显示屏的垂直高度
一般来讲大概是50
超过一屏之后,在阅读代码的时候,为了串联前后的代码逻辑,就可能需要频繁地上下滚动屏幕,阅读体验不好不说,还容易出错
类的大小,以不影响阅读为准
当一个类的代码读起来让你感觉头大了,实现某个功能时不知道该用哪个函数了,想用哪个函数翻半天都找不到了,只用到一个小功能要引入整个类(类中包含很多无关此功能实现的函数)的时候,这就说明类的行数过多了。
善用空行分割单元块
对于比较长的函数,如果
- 逻辑上可以分为几个独立的代码块
- 不方便将这些独立的代码块抽取成小函数的情况下:
通过添加空行的方式,让这些不同模块的代码之间,界限更加明确。
一些具体的函数编写技巧
这些技巧,可以显著的提升同一份代码的质量
将代码拆分成更小的单元块
要有模块化和抽象思维,善于将大块的复杂逻辑提炼成类或者函数,屏蔽掉细节
让阅读代码的人不至于迷失在细节中,这样能极大地提高代码的可读性,聚焦于业务
// 重构前的代码
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;
}
避免函数参数过多
函数包含 3、4 个参数的时候还是能接受的
再多,就需要重构代码进行优化
通常我们会将参数封装成对象
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);
不过,这样在使用的时候,不同环境下的参数定义可能随着迭代就不那么明确了,有利有弊吧。
迭代时,bool值传入方法作为隔离依据
这个情况对于很多开发人员都很常见,尤其在修改迭代他人代码的时候。
这种打补丁的方式,随着补丁的增多,对项目也是毁灭性的。
void configXXXX() {
//基本操作...
}
//迭代后
void configXXXX(isModeA , isModeB) {
//基本操作...
if (isModeA) {
//特殊操作...
}
//基本操作...
if (isModeB) {
//特殊操作
}
//基本操作...
if (isModeA && !isModeB) {
//特殊操作...
} else if (isModeA){
//特殊操作...
}
//基本操作...
}
函数设计要职责单一
不要设计一个大而全的函数,而在函数的内部做一大堆的盘点。
最起码,具体的功能实现函数不要耦合其他职责的逻辑
比如字符串的校验时,电话、用户名、邮箱三者的实现逻辑(将来)很可能是不同的,要分开定义。
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);
移除过深的嵌套层次
通常是if-else、switch-case、for 循环过度嵌套导致
我们可以最开始按照执行顺去去编写多层代码,功能开发完毕函数稳定之后,再回过头来着手移除
- 去掉多余的 else 语句
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;
}
}
- 去掉多余的if
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;
}
- 提前退出嵌套
// 重构前的代码
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;
}
// 重构后的代码:使用return和continue提前退出
public List<String> matchStrings(List<String> strList,String substr) {
List<String> matchedStrings = new ArrayList<>();
if (strList == null || substr == null){
return matchedStrings;
}
for (String str : strList) {
if (str == null || !str.contains(substr)) {
continue;
}
matchedStrings.add(str);
// 此处还有10行代码...
}
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 {
// ...
}
- 多个复合条件的bool,最好加上注释
// ((团购 && 非折扣 && 有销售价格) || 有团购列表)
boolean showGroupBuyButton = ((isGroupbuy && !isSale && (price>0)) || groupBuyList.count)