整洁代码
一、命名
以业务为导向命名[operateMaxSaleQtyLogs] > 以技术命名[operateMaxSaleQtyLogList] > 随意命名 [logList]
1.1 变量
Rule 1. 【推荐】变量名称最好:名词、短语、形容词
Rule 2. 【推荐】不要在变量中包含多余的信息
badCase:User userWithNameAndAge = newUser()
Rule 3. 【推荐】避免误导
生成的单号,不要使用数字0、1和字母o、O、l、L
Rule 4. 【推荐】做有意义的区分:避免废话
product和productData、productInfo意思无区别
customer和customerData没区别
name和nameStr没区别
1.2 函数
Rule 1. 【推荐】函数名称动词 + 名词
Rule 2. 【推荐】函数长度
最好能20行以内
造成长函数的原因:
- 把多个业务处理流程放在一个函数里实现;
- 把不同层面的细节放到一个函数里实现。
Rule 3. 【推荐】只做一件事
每个函数一个抽象层级(锁库-较高抽象、查询算法或查档期-中间抽象、最大售卖量转换为字符串-低抽象)
反例:一个方法里,前20行代码在进行很复杂的基本价格计算,然后调用一个折扣计算函数,再调用一个赠品计算函数。
此时可将前20行也封装成一个价格计算函数,使整个方法在同一抽象层级上。
Rule 4. 【推荐】向下规则
每个函数后面,都紧跟着位于下一抽象层级的函数
public void test(){
//xxx
a();
//yyy
}
private void a() {
//
b();
//
}
private void b() {
//
}
Rule 5. 【推荐】参数个数尽量小于3个
1)如果多个参数同属于一个对象,直接传递对象。
例外: 你不希望依赖整个对象,传播了类之间的依赖性。
2)将多个参数合并为一个新创建的逻辑对象。
例外: 多个参数之间毫无逻辑关联。
3)将函数拆分成多个函数,让每个函数所需的参数减少。
- 尤其是查询数据库时,将常用的查询参数封装为model。在构造查询条件的时候,if判空model中属性是否为null,不为null,则criteria
Rule 6. 【推荐】减少记忆参数顺序的负担
AssertEquals(expected,actual)==》 assertExpectedEqualsActual(expected,actual)
Rule 7. 【推荐】尽量减少重复的代码,抽取方法
超过5行以上重复的代码,都可以考虑抽取公用的方法。
Rule 8.【推荐】需要进行参数校验
执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,代价更大。
class A {
void hello(List list);
void hello(ArrayList arrayList);
}
List arrayList = new ArrayList();
a.hello(arrayList);//调用的是hello(List list),因为arrayList的定义类型是List
Rule 9.【推荐】不要返回null
如果你打算在方法中返回null,不如抛出异常,或者返回特例对象(Collections。emptylist()等)
Rule 10.【推荐】入参不要传递null
入参为null,也很容易npe
Rule 11.【推荐】先整体后细节
大部分人阅读代码的习惯是,先看此方法整体1、2、3做什么,然后再看1中具体细节
- 重构前的代码
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;
}
Rule 12.【推荐】创建
create、getInstance、newInstance、valueOf、of、as
1.3 类
Rule 1. 【推荐】命名使用名词、名词短语叠
避免使用动词(Manager、Processor)
Rule 2. 【推荐】字段未分组避免大类的产生、
可以将一个大类n多个字段,按照商品基本信息、档期信息、库存信息、其它信息等几个模块分组。
Rule 3. 【推荐】高内聚
类应该只有少数实体变量、类中方法操作的变量越多,类就越内聚。
大函数 -> 小函数(使用了大函数的4个变量)-> 将4个变量提升为类的实体变量 -> 丧失内聚(新增了4个只是为了少量函数而存在的实体变量)
-> 将这些想要共享某些实体变量的函数 和 实体,抽离为一个新的小类,这样就完成了大函数 到 小函数的拆分,同时类也符合高内聚
二、格式
Rule 1. 【推荐】加减空格,乘除不加空格
int result = temp - 2*a*b ;
三、条件语句
Rule 1. 【推荐】少用if-else方式,多用哨兵语句式以减少嵌套层次
if (condition) {
...
return obj;
}
// 接着写else的业务逻辑代码;
Rule 2.【推荐】减少使用取反的逻辑
不使用取反的逻辑,有利于快速理解。且大部分情况,取反逻辑存在对应的正向逻辑写法。
Rule 3.【推荐】能用while循环实现的代码,就不用do-while循环
四、对象和数据结构
Rule 1.【推荐】面向对象编程
eg1: 计算图形的面积
-
面向对象编程
// 正方形 @Data public class Square { private Double side; } // 圆形 @Data public class Circle { private Double r; } // 计算类 public class Calculate { public double area(Object shape) { if (shape instanceof Square) { Square square = (Square) shape; Double side = square.getSide(); return side * side; } if (shape instanceof Circle) { Circle circle = (Circle) shape; Double r = circle.getR(); return Math.PI * r * r; } throw new NoSuchElementException(); } }
当新增三角形这种数据结构时,需要:添加三角形类、需要在计算类中新增if判断条件,违背了开闭原则
-
面向对象编程
// 接口定义方法:计算面积 public interface Shape { /** * 计算图形的面积 * @return 面积 */ public double area(); } // 具体的正方形 public class Square implements Shape{ private Double side; @Override public double area() { return side * side; } } // 具体的圆形 public class Circle implements Shape{ private Double r; @Override public double area() { return Math.PI * r * r; } }
当新增三角形这种数据结构时,需要:仅仅需要添加三角形实现类,实现接口重写area方法即可,符合开闭原则
eg2:计算不同等级用户对应的图书价格
-
面向对象编程
// 获取微信读书,不同书对于不同等级用户的价格 public double getWeiXinReadBookPrice(final User user, final Book book) { double price = book.getPrice(); switch (user.getLevel()) { case UserLevel.SILVER: //银 return price * 0.9; case UserLevel.GOLD: // 金 return price * 0.8; default: return price; } } // 获取Kindle,不同书对于不同等级用户的价格 public double getKindleBookPrice(final User user, final Book book) { double price = book.getPrice(); switch (user.getLevel()) { case UserLevel.SILVER: //银 return price * 0.95; case UserLevel.GOLD: // 金 return price * 0.85; default: return price; } }
如果新增用户的Level:Plantinum铂金用户,则对应的书的具体价格又不一样,两个方法都要新增case,违背了开闭原则
-
面向对象编程
// 定义用户等级接口 interface UserLevel { double getWeiXinReadBookPrice(Book book); double getKindleBookPrice(Book book); } // 白银用户,使用微信读书 和 Kindle读书,对应的书价 class SilverUserLevel implements UserLevel { @Override public double getWeiXinReadBookPrice(final Book book) { return book.getPrice() * 0.9; } @Override public double getKindleBookPrice(final Book book) { return epub.getPrice() * 0.85; } } // 黄金用户,使用微信读书 和 Kindle读书,对应的书价 class GoldUserLevel implements UserLevel { @Override public double getWeiXinReadBookPrice(final Book book) { return book.getPrice() * 0.8; } @Override public double getKindleBookPrice(final Book book) { return epub.getPrice() * 0.85; } } // 调用的时候,就不要使用Switch public double getWeiXinReadBookPrice(final User user, final Book book) { UserLevel level = user.getUserLevel() return level.getBookPrice(book); }
如果新增用户的Level:Plantinum铂金用户,则直接新增铂金类,实现接口,重写两个方法即可,其它地方均无需改动,遵循开闭原则