前言
做Java开发的,大多数可能都有看过阿里的Java后台开发手册,里面有关于Java后台开发规范的一些内容,基本覆盖了一些通用、普适的规范,但大多数都讲的比较简洁,本文主要会用更多的案例来对一些规范进行解释,以及结合自己的经验做补充!
其他类型的规范
【Java后台开发规范】— 不简单的命名 【Java后台开发规范】—日志的输出 【Java后台开发规范】—线程与并发 【Java后台开发规范】— 长函数、长参数
圈复杂度
圈复杂度是一种衡量代码复杂度的标准,其数量上表现为独立路径的条数,也可以理解为覆盖所有的可能情况最少使用的测试用例个数,圈复杂度高说明程序代码的判断逻辑复杂,可能质量低,且难于测试和维护。
在sonar规范中,经常有关于圈复杂度的扫描,比如一个方法的圈复杂度不得超过5或者10。
一般来说,代码里每出现一次if、for、while、case、catch等这样的语句,圈复杂度就加1。
很明显,圈复杂度的目的就是为了减少代码里控制语句的滥用,比如像这样:
接下来我们就来看看如何可以减少圈复杂度。
提炼方法
这个和之前提到的长函数类似,如何存在通用的方法可以提炼出来,一些条件控制语句多数也可以提炼出来,总之就是一个大函数变多个小函数。
if、else
一提到if、else的优化必然会想到策略模式,策略模式主要解决的就是在多种算法相似的情况下,使用if、else所带来的高复杂度,不过除了使用策略模式之外,还有一些其他的小技巧,我相信在平时开发过程中无论是有意识还是无意识,应该都有用到过,下面我们就一起来看看。
卫语句
第一种方式就使用卫语句。
这是很正常的逻辑,嵌套了if控制语句
public void test() {
if(条件1成立){
if(条件2成立){
执行xxx逻辑;
}
}
}
优化后,嵌套消失了
public void test() {
if(!条件1成立){
return;
}
if(!条件2成立){
return;
}
执行xxx逻辑;
}
这是一种经典的重构方法:通过卫语句来替代嵌套的条件表达式
减少了嵌套,也就减少了层次的缩进,这样在阅读理解上会更加轻松,减少了代码的复杂度。
去else
public void test() {
if (10 < amount && amount < 20) {
执行xxx逻辑;
} else if (21 < amount && amount < 30) {
执行xxx逻辑;
} else if (31 < amount && amount < 40) {
执行xxx逻辑;
} else {
执行xxx逻辑;
}
}
去除else后
public void test1() {
if (10 < amount && amount < 20) {
执行xxx逻辑;
return;
}
if (21 < amount && amount < 30) {
执行xxx逻辑;
return;
}
if (31 < amount && amount < 40) {
执行xxx逻辑;
return;
}
执行xxx逻辑;
}
这也是一种常见的减少复杂层级的方式。
策略模式
最后我们再来看看策略模式的方法。
下面这段逻辑描述的是,如果memberAttr是VIP,我们就根据用户VIP级别给到不同的折扣。
public BigDecimal test(String memberAttr) {
BigDecimal amount = BigDecimal.valueOf(10d);
if ("VIP".equals(memberAttr)) {
String level = getVipLevel();
if ("1".equals(level)) {
return amount.multiply(BigDecimal.valueOf(0.9));
}
if ("2".equals(level)) {
return amount.multiply(BigDecimal.valueOf(0.8));
}
return amount.multiply(BigDecimal.valueOf(0.7));
}
return amount;
}
这是最简单的实现方式。
卫语句优化
public BigDecimal test(String memberAttr, String userId) {
BigDecimal amount = BigDecimal.valueOf(10d);
if (!"VIP".equals(memberAttr)) {
return amount;
}
String level = getVipLevel(userId);
if ("1".equals(level)) {
amount = amount.multiply(BigDecimal.valueOf(0.9));
} else if ("2".equals(level)) {
amount = amount.multiply(BigDecimal.valueOf(0.8));
} else {
amount = amount.multiply(BigDecimal.valueOf(0.7));
}
return amount;
}
去else
public BigDecimal test(String memberAttr, String userId) {
BigDecimal amount = BigDecimal.valueOf(10d);
if (!"VIP".equals(memberAttr)) {
return amount;
}
String level = getVipLevel(userId);
if ("1".equals(level)) {
return amount.multiply(BigDecimal.valueOf(0.9));
}
if ("2".equals(level)) {
return amount.multiply(BigDecimal.valueOf(0.8));
}
return amount.multiply(BigDecimal.valueOf(0.7));
}
策略模式
public BigDecimal method1(String memberAttr, String userId) {
BigDecimal amount = BigDecimal.valueOf(10d);
if (!"VIP".equals(memberAttr)) {
return amount;
}
String level = getVipLevel(userId);
DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(level);
return discountStrategy.discount(amount);
}
switch
switch其实就是if、else的一种简化写法,在语法上给与了优化,但要注意如果switch的条件出现在过多的业务场景中,那么可能就需要优化了。
刚才的方法,我们可以换成switch写法
public BigDecimal test(String memberAttr, String userId) {
BigDecimal amount = BigDecimal.valueOf(10d);
if (!"VIP".equals(memberAttr)) {
return amount;
}
String level = getVipLevel(userId);
switch (level) {
case "1":
return amount.multiply(BigDecimal.valueOf(0.9));
case "2":
return amount.multiply(BigDecimal.valueOf(0.8));
default:
return amount.multiply(BigDecimal.valueOf(0.7));
}
}
假设现在又出现了一种超级VIP,那么它可能是这样的。
public BigDecimal test(String memberAttr, String userId) {
BigDecimal amount = BigDecimal.valueOf(10d);
if (!"VIP".equals(memberAttr)) {
return amount;
}
String level = getSuperVipLevel(userId);
switch (level) {
case "1":
return amount.multiply(BigDecimal.valueOf(0.8));
case "2":
return amount.multiply(BigDecimal.valueOf(0.7));
default:
return amount.multiply(BigDecimal.valueOf(0.6));
}
}
折扣力度发生了变化,但是也是根据用户级别来区分的,如果再出现一些根据用户级别来进行业务逻辑处理的场景,那我们就应该考虑抽象、多态的方式了,否则一处变动,你需要找到所有使用switch的地方,并修改它。
循环嵌套
for循环嵌套不太好优化,因为可能会和选择的算法有关,比如下面这个经典的求两个数组交集。
第一版,for循环嵌套,复杂度高,性能也差,最不应该采取的方式。
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> intersectionSet = new HashSet<>();
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
if (nums1[i] == nums2[j]) {
intersectionSet.add(nums1[i]);
}
}
}
return intersectionSet.stream().mapToInt(Integer::valueOf).toArray();
}
第二版,借用API,循环嵌套没了,使用retainAll函数实现交集,性能优于第一版。
public static int[] intersection1(int[] nums1, int[] nums2) {
Set<Integer> set1 = new HashSet<>();
Set<Integer> set2 = new HashSet<>();
for(int num : nums1){
set1.add(num);
}
for(int num : nums2){
set2.add(num);
}
set1.retainAll(set2);
return set1.stream().mapToInt(Integer::valueOf).toArray();
}
第三版,利用contains函数,实际上就是利用O(1)时间复杂度的hash函数,性能上要优于第二版,但圈复杂度确实上升了。
public static int[] intersection2(int[] nums1, int[] nums2) {
Set<Integer> set1 = new HashSet<>();
Set<Integer> intersectionSet = new HashSet<>();
for(int num : nums1){
set1.add(num);
}
for(int num : nums2){
if(set1.contains(num)){
intersectionSet.add(num);
}
}
return intersectionSet.stream().mapToInt(Integer::valueOf).toArray();
}
第四版,排序+双指针,这是最优解的算法,不过复杂度大大上升。
public static int[] intersection3(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int p1 = 0, p2 = 0, index = 0;
int[] nums = new int[nums1.length + nums2.length];
while (p1 < nums1.length && p2 < nums2.length) {
if (nums1[p1] == nums2[p2]) {
if (index == 0 || nums1[p1] != nums[index - 1]) {
nums[index++] = nums1[p1];
}
p1++;
p2++;
} else if (nums1[p1] < nums2[p2]) {
p1++;
} else {
p2++;
}
}
return Arrays.copyOfRange(nums, 0, index);
}
其他建议
尽量不要在条件判断中附加其他的业务逻辑,条件判断尽量清晰明了。
if(长的高 && 长的帅 && 有钱){
...
}
boolean 高富帅 = 长的高 && 长的帅 && 有钱
if(高富帅){
...
}
尽量避免反逻辑
if(x < 100){
...
}
if(!x >= 100){
...
}
长行代码学会换行
StringBuilder sb = new StringBuilder();
sb.append("a").append("b").append("c").append("d").append("e");
sb.append("a").append("b")
.append("c")
.append("d")
.append("e");
new User(String userName, String account, String age, String sex, String phone, String email)
new User(String userName, String account, String age
, String sex, String phone, String email)
|