前言

做开发也有了一段时间,好的代码和烂的代码也看了很多,所以想记录下来平日里面自己开发的一些规范,方便后续其他人阅读学习或借鉴。之前有读过《代码整洁之道》,感觉受益匪浅,里面讲的很多内容都是日常会犯的一些小毛病,而且书中教了大家该如何让代码看起来更舒服,细节决定了更多事情,如果一个人写代码的风格换来换去,只会让这一片代码受到污染,即使是小的空格和空行,也需要多加注意。先记录下来,慢慢更新吧!

或许是强迫症还是洁癖使然,让我对代码规范比较敏感
例如这里是否多个空行,那里是否少个空格
有问过其他同事,他们貌似对这些并无所谓,觉得能开发出来就好

开门见山

1. 空格

所有特殊符号前后都应有空格,最常见的可以直接使用 idea 自带的代码整理,自动帮忙规范。

规定:

  1. 定义变量的等于号前后都需要有空格
  2. 循环或者IF这种特殊词与括号都需要有空格来隔离。

坏示例

String name="example";
int age= 1;
int age =1;

if(A ==B)
if(A<B)

好示例

String name = "example";
int age = 1;

if (A == B)

2. 类开始和结束的空行

有很多人喜欢类开始的时候,直接开始写代码,在结束的时候又莫名奇妙多出来很多空行,亦或是在分割方法的时候没有空行,或者多个空行,这样都会让代码显得很不规范。

规定:

  1. 在新的类写代码时,应先空一行,再开始编写代码
  2. 在类结束时,应该与上一个最近的方法紧贴,没有空行
  3. 每个方法之间只且需要一个空行来隔离
  4. 不同类型的变量应该用空行来隔离
  5. 变量与方法之间应该用空行来隔离
  6. 如果出现 List 类型的变量,应该以 xxxList 为命名,取消类似英文复数的定义,比如pages等,这种很容易让人引起歧义
  7. 如果出现 Map 类型的变量,应该以 xxxMap 为命名

坏示例

public class ApiRequestTotal extends BaseBasicDashboardCalculate {
    private static final String NAME = "example";
    private String name;

    private String type;
    @Override
    public Map<String, Object> getConditions() {
        Map<String, Object> map = new HashMap<>();
        map.put("finalDecisionCode", "Accept");
        return map;
    }
    @Override
    public Map<String, Object> getNestedConditions() {
        return new HashMap<>();
    }


    @Override
    public BasicDashboardMetricTypeEnum getMetricType() {
        return BasicDashboardMetricTypeEnum.API_REQUEST_TOTAL;
    }

}

好示例

public class ApiRequestTotal extends BaseBasicDashboardCalculate {

    private static final String NAME = "example";

    private String name;
    private String displayName;

    @Override
    public Map<String, Object> getConditions() {
        Map<String, Object> map = new HashMap<>();
        map.put("finalDecisionCode", "Accept");
        return map;
    }

    @Override
    public Map<String, Object> getNestedConditions() {
        return new HashMap<>();
    }

    @Override
    public BasicDashboardMetricTypeEnum getMetricType() {
        return BasicDashboardMetricTypeEnum.API_REQUEST_TOTAL;
    }
}

3. 属性和变量的定义

这块的定义其实是很糟糕的,经常看到很多代码,打开一个类的时候,迎头一棒全是定义,导致要一直往下翻找代码,且定义的很不规范,一样类型的内容应该遵守从上到下的结构。

规定:

  1. 顺序规范应遵从:public 常量 -> private 常量 -> private 属性
  2. 同一种类型的内容应该放在同一块去定义,且没有空行分割
  3. 不同类型的内容应该有空行进行分割
  4. 常量定义的名字都应该遵从全部大写,且使用下划线进行分割

坏示例

@Resource
private BasicDashboardCalculateExecutor calculateExecutor;
public static final String CACHE_HOUR_KEY = "BasicDashboardWarningTask_Hour";

@Resource
private CacheService cacheService;

private static final String CACHE_NOTICE_KEY = "BasicDashboardWarningTask_Notice";

private static final List<String> ENABLE_METRICS = Stream.of(
        BasicDashboardMetricTypeEnum.SCAN_TOTAL,
        BasicDashboardMetricTypeEnum.SCAN_DENY_RATIO,
        BasicDashboardMetricTypeEnum.SCAN_REVIEW_RATIO,
        BasicDashboardMetricTypeEnum.API_REQUEST_TOTAL
).map(BasicDashboardMetricTypeEnum::name).collect(Collectors.toList());

@Resource
private WarningConfigCache cache;

好示例

public static final String CACHE_HOUR_KEY = "BasicDashboardWarningTask_Hour";

private static final String CACHE_NOTICE_KEY = "BasicDashboardWarningTask_Notice";
private static final List<String> ENABLE_METRICS = Stream.of(
        BasicDashboardMetricTypeEnum.SCAN_TOTAL,
        BasicDashboardMetricTypeEnum.SCAN_DENY_RATIO,
        BasicDashboardMetricTypeEnum.SCAN_REVIEW_RATIO,
        BasicDashboardMetricTypeEnum.API_REQUEST_TOTAL
).map(BasicDashboardMetricTypeEnum::name).collect(Collectors.toList());

@Resource
private CacheService cacheService;
@Resource
private WarningConfigCache cache;
@Resource
private BasicDashboardCalculateExecutor calculateExecutor;

4. 注释的使用

注释真的是一个很重要的东西,在写这一段的时候我有在思考,为什么注释五花八门,很多人都写不好,注释的意义到底是什么?我认为注释的意义在于让别人看懂这段东西的含义,但是现在注释存在着很多问题,可能他会影响本身代码的整洁性,亦或是存留在这里多年没有被人更新,最后导致最新的这块代码早已和之前写的注释互不相关,让人揪心。在好的代码中,应该通过命名来告诉他人这个是做什么的,在名字中即可提现具体的含义,注释不该被大量定义、解释重复的内容,这会造成大量的代码污染。

规定:

  1. 不得在行尾添加注释,不管什么注释都应该另起一行,尤其是 // 这种注释
  2. // 注释开始后,应该有一个空格,然后再继续写注释,双斜杠与文字之间不能紧连着
  3. 注释也属于一块代码或者一个方法的整体,不能算作空行,例如方法之间要有空行,如果要在方法上写注释,额外起一行
  4. 注释应该频繁更新,不要造成注释与代码含义不同的情况,在更新代码的同时去更新注释
  5. 注释不该去解释重复的内容,例如private String name;,上边就不需要写注释了,经常有同事在上边还要写一个 名字 意思的注释,感觉很奇怪
  6. 尽可能减少注释,注释本身就不该被大量编写,好的代码在命名上即可体现出它所要干的事情
  7. 在编写 TODO 时,应保持以下结构 // TODO | 存储至数据库

坏示例

private String displayName; //展示名 无意义的注释、不该在行尾、没有空格分割
/**
 * 展示名 无意义的注释
*/
private String displayName;

好示例

/**
 * every hour/day, for example: time is 2023/x/x xx:00:00 00.00.00
 * while storage all metric value in mysql when after aggregation
 */
@Scheduled(cron = "0 0 * * * ?")
public void hourTask() {
    ...
}

// TODO | async consumer trigger

5. 代码中的空行

代码中的空行是指在某一个方法中空行的使用,在代码编写习惯里面,经常会有人从上到下写完一个空行都没有,我的评价是连贯,太xx的连贯了,条理清晰,逻辑通畅,全文从上到下,我是没办法维护哈哈哈。这种代码看上去就像是一篇没有逗号和句号的文章一样,让人喘不上来气,也看不懂他究竟要做什么。更有甚者如果这个方法超过了100行将是地狱难度,所以应该在某个部分完成后,适当的使用空行来作为分割线,告诉后来人这里的代码告一段落,下一段要干什么事情。

规定:

  1. 方法应适当减少行数,尽可能保持在20行以内为最佳
  2. 多重复杂的代码应该对其进行拆分,在一个部分完成后,使用空行来分割
  3. 空行应只有一行,不要为了分割多搞好几个空行
  4. 在方法开始后直接写代码,不要有空行;代码写完后,也不要有空行
  5. 在最终即将要return什么内容的时候,上边需要有一个空行来承接(前提是之前有很多的大量代码逻辑)
  6. 在某个if或者某个循环结束时,可以考虑在其后边增添一个空行

坏示例

public Response<String> execute(ConsoleListAddCmd cmd){
    StringBuffer rosterDefineName = new StringBuffer();
    StringBuffer rosterDefineCode = new StringBuffer();
    rosterDefineCode.append("console");
    rosterDefineName.append("CONSOLE");
    if(cmd.getListType()==1){
        rosterDefineCode.append("_black");
        rosterDefineName.append("黑名单");
    }
    if(cmd.getListType()==3){
        rosterDefineCode.append("_white");
        rosterDefineName.append("白名单");
    }
    //appMap 所有渠道
    List<Map<String, Object>> list = commonService.getAPPs();
    Map<String,String> appMap = new HashedMap();
    for (Map<String, Object> map : list) {
        appMap.put(map.get("name").toString(),map.get("dName").toString());
    }
    appMap.put("all","全部渠道");
    rosterDefineCode.append("_"+cmd.getApp()).append("_"+cmd.getDataType());
    rosterDefineName.append(appMap.get(cmd.getApp())+cmd.getDataType().toUpperCase());
    List<RosterDefineDO> rosterDefineDOS = rosterDefineRepository.selectValidAll();
    List<RosterDefineDO> collect = rosterDefineDOS.stream().filter(p -> p.getCode().equals(rosterDefineCode.toString())).collect(Collectors.toList());
    Response addRosterDefineRes = new Response();
    ...
}

好示例

protected void sendMessage(List<String> messages) {
    List<WarningUser> warningUserList = cache.getWarningUserList();
    StringBuilder stringBuilder = new StringBuilder();

    AtomicInteger times = new AtomicInteger(1);
    messages.forEach(message -> {
        stringBuilder.append(times.get())
                .append(". ")
                .append(message)
                .append("\n");
        times.getAndAdd(1);
    });

    Map<WarningTypeEnum, List<WarningUser>> collect = warningUserList.stream()
            .collect(Collectors.groupingBy(warningUser -> WarningTypeEnum.valueOf(warningUser.getWarningType())));

    String result = stringBuilder.toString();
    if (StringUtils.isNotEmpty(result)) {
        mailWarningSender.execute(result, collect.get(mailWarningSender.getWarningType()));
        dingWarningSender.execute(result, collect.get(dingWarningSender.getWarningType()));
    }
}

6. 注解的使用

在类上的注解,经常使人感觉难受,每个人都有不同定义的顺序,有时候经常出现乱塞,乱使用的情况,导致影响整体美感。应从下到上根据关联关系的重要性进行排序,使其让它更有意义。

规定:

  1. 注解的使用应保持从下到上的关联重要性排序
  2. 如果不确定关联重要性可根据注解长度进行排序
  3. 不要定义没有意义的注解,或定义不使用的注解

注: 第二条额外解释,例如在 SpringBoot 项目中,定义某个 Service 时,@Service 注解应该离类最近,其次是其他的条件,然后是日志

坏示例

@EnableScheduling
@SpringBootApplication
@Import(cn.hutool.extra.spring.SpringUtil.class)
public class StartApplication {
    // @SpringBottApplication 应该与类最近
}

好示例

@Slf4j
@EnableScheduling
@MapperScan(basePackages = "com.domcer.infrastructure.mapper")
@SpringBootApplication(scanBasePackages = {"com.domcer"})
public class AthenaApplication {
}

@RequestMapping("/api/alert/config")
@RestController
public class AlertConfigController {
}

@Slf4j
@ConditionalOnClass(RedisOperations.class)
@Service
public class RiskEventStatServiceImpl implements RiskEventStatService {
}

7. 代码复杂度规范

复杂度在 sonar 中一直是个比较有意思的内容,它又名为 complex,在 sonar 中规范一个方法中复杂度不可大于 15 以上。而计算方式并非是统计用到了多少个 IF 或者 FOR循环,当你在使用两个 IF 嵌套起来时,他们复杂度计算方式为 1+2=3,所以在越深的层级里面,它所代表的复杂度权重就越高。应极力避免复杂度的出现,在公司修复标准产品线时,曾看见过约 200 复杂度的长方法,这种代码阅读、维护都需要消耗大量的成本,得不偿失。尽量拆解成各类小方法,使其具有唯一性,一个方法只敢一件事情,让方法共用,也可以让代码看着更简洁。

规定:

  1. 在同一个方法中,复杂度不可超过 15 以上
  2. 在同一个方法中,最多仅允许出现 3 层深度结构
  3. 在同一个方法中,请尽量减少 else 单独出现
  4. 在同一个方法中,请尽可能保证代码行数在 25 以内

坏示例

public static void main(String[] args) {
    if (true) {
        // do sth.
        if (true) {
            // do sth.
            if (true) {
                // do sth.
            } else if (true) {
                // do sth.
            }
        } else if (true) {
            // do sth.
        } else {
            // do sth.
        }
        else {
            // do sth.
        }
    } else {
        // do sth.
    }
}

好示例

public static void main(String[] args) {
    if (true) {
        // do sth.
        return;
    }
    // do sth.
}

8. 方法的规范

有看过之前很多代码,很隐晦又难懂,好的方法应该如同变量一样,在名字上就能体现出它要做的事情,而且应该尽职尽责,何为尽职尽责?它应只做这一件事情,且只做这件事情,并把这件事情完整的做完,这才叫尽职尽责。往往有很多人抽离方法的时候,总是到一半就结束了,然后再通过其他代码去加工,显得特别奇怪。方法名也应该尽可能的表达出你要做的事情,不要使用简写等,一个好的方法让人一看就懂,不需要再点进去看具体写了什么代码,让阅读更加迅速。并且,在方法的入参上应该尽可能的减少,在 sonar 中,方法入参最大规定为 7 个,但是我认为,应该尽可能保持在 4 个左右为最佳,尽可能减少入参所带来不必要的成本。

规定:

  1. 方法应尽职尽责,只做一件事,且只做这件事,并做完这件事
  2. 方法名在定义上应保证可阅读,不管名字多长,只要完整的表达出即可
  3. 方法的入参应尽可能保证在 4 以下,以避免后续的维护成本
  4. 方法中的代码应该短小精悍,避免大量的糟糕代码出现,抽出可隔离的代码

坏示例

public void test(A, B, C, D, E, F, G) {
}

好示例

public void storageTest(A, B) {
    // do sth.
}

public void addCoins(Long coins) {
    this.coins += coins;
}

9. stream流使用规范

10. 内部变量名字规范

all, list

后话

代码风格只是为了让代码看的更舒服,方便后续阅读,也可以让自己有一种风格,让别人看见代码就知道是谁写的。一个人的风格可以永久的保持下去,不管是到未来还是以后,都是对一些细节的较真。如果一个人写代码没有任何的风格,随便定义或修改代码,只会让代码越来越糟糕。代码的开发往往是小成本,在后期的更新维护才需要大量的成本投入。所以我这个人比较神经,包括但不限于:

  1. 经常看其他人代码
  2. 经常看自己已经写过的代码
  3. 经常优化自己的代码
  4. 经常翻找是否有冗余的内容(例如最上方多余的import或者变量定义)
  5. 经常在想如何更简洁

所以,尽可能保持自己的代码简洁吧!让自己和别人看见你的代码时,都会感觉舒爽!

未完结

坏示例

好示例

最后修改:2024 年 03 月 08 日
如果觉得我的文章对你有用,请随意赞赏