1. 底层基建层面该如何做好代码质量维护?
代码质量维护的范围很广,这件事情要做起来也是千头万绪,但是大的方向是可以明确的,我们可以把代码质量的管理,在基建层面,分为两个方向:
- 静态代码分析
- 单测覆盖率监测
针对所有代码,我们可以提供基本的代码分析,支持在开发中、上线前提供有依据、行业内认可的静态分析指标,可以有效避免一些低级错误,并且在开发过程中达到『指导』一线开发的作用,使团队整体写出更优质、更规范的代码,同时对团队内部有追求的一线开发,带来权威性的代码编写指导意见,在开发中就拦截一部分问题代码,避免流入到Code Review阶段。以上就是代码静态分析的价值。
而单测覆盖率监测是另一个重要的评价指标。这里对是否要做单测、如何做单测不做展开,只讨论该如何设立单测覆盖率的指标。首先,需要了解单测覆盖率的评价标准有哪些(参考Jacoco团队对于覆盖率的定义):
- Instructions (C0 Coverage): 指令覆盖率,java字节码程度的指令覆盖率
- Branches (C1 Coverage): 分支覆盖率,针对if和switch语句,这个指标对分支的覆盖情况进行统计
- Cyclomatic Complexity: 圈复杂度是可以(线性)组合通过某种方法生成所有可能路径的最小路径数。因此,复杂度值可以作为完全覆盖某个软件的单元测试用例数量的指示。
- Lines: 行覆盖率,指的是某一行生成的指令,如果在单测执行过程中被执行过,那么这一行就算被覆盖到了,然后在行维度计算得到的覆盖率指标
- Methods: 方法覆盖率,当至少一条指令已执行时,方法被视为已执行。由于 JaCoCo 在字节码级别工作,因此构造函数和静态初始化程序也被视为方法。
- Classes:当一个类至少有一个方法被执行时,该类就被视为已执行。请注意,JaCoCo 将构造函数和静态初始值设定项视为方法。由于 Java 接口类型可能包含静态初始化程序,因此此类接口也被视为可执行类。
根据内外网的相关文档,我们可以发现:
- 行覆盖率和分支覆盖率是最常使用的指标;
- 分支覆盖率通常比行覆盖率更难提升,更能反映单测覆盖的业务广度。
- 业界不存在一个统一标准,覆盖率越高并不代表代码质量越高,原则上要更加关注未被覆盖的代码的价值,重要的、被反复使用的代码,都应该被覆盖到
- 国内阿里的几篇文章反映出来,阿里内部认为6-70%的行覆盖率是有必要的
这一结论参考了以下来源:- https://mp.weixin.qq.com/s/8JC_vaFOgiJPIH7yfbP25A
- https://mp.weixin.qq.com/s/e5gkhOyZZuLpjVyDUModQQ
- https://mp.weixin.qq.com/s/wzGxqNv58Zig9_Izi3VhDg
- https://mp.weixin.qq.com/s/okwW01oCtUxUya2XG_PugQ
- https://www.baeldung.com/cs/code-coverage
- https://testing.googleblog.com/2020/08/code-coverage-best-practices.html
2. 代码的静态分析工具选择
2023年年初的时候,我们曾经有这样的愿景:
- https://mp.weixin.qq.com/s/8JC_vaFOgiJPIH7yfbP25A
当时,代码静态分析工具暂定为老牌的Java代码静态分析工具FindBugs。
但是事与愿违,FindBugs目前已经处于不维护的状态,不再适合作为我们的第一选择。
之后,我们又选择了CheckStyle工具来进行静态代码分析。它的地址:https://checkstyle.sourceforge.io/
选择分析工具的需求很简单:
- 最好是业界规范,具有权威性
- 可以和常见Ide很好的集成,便于开发时作为参考
- 可以集成到流水线中,对开发的部署产生实质影响,避免不达标的代码流入线上
- 最好是可以提供分级的监控标准,例如可以分为坏味道、警告、bug几个级别
- 最好可以对质量进行评分,动态评估代码库整体的质量分数
CheckStyle基本可以满足条件1-3,他提供了一套简单的配置、分析机制,用于简单的代码质量检测,google代码规范、阿里巴巴代码规范,都有相应的适配配置,可以开箱即用,并且idea\gradle等开发工具也可以和它很好的结合,在开发中、流水线里,获得一致的检测标准。
但是CheckStyle的缺点是:
- 无法应对复杂的检测场景:例如编译后文件的全局分析,多级分析
- 无法灵活的应对所有场景:缺乏分级机制、豁免机制死板
再后来,我们考虑使用baidu内部的bugbye工具。
BugBye工具也可以符合上述条件里的1、3、4、5。
但是致命缺陷是无法和ide进行集成,厂内的idea插件早就处于不可维护的状态了。虽然bugbye团队还在,但是插件早就不维护了,这本身就很奇怪,而且,当咨询bugbye团队时,他们依然还在推荐使用该插件。这种让我对使用厂内轮子的信心大打折扣,一个显然无法使用的轮子,怎么还有勇气让用户去使用,对自己内部工具的维护状态都没有感知么。
为了解决这么一个看似简单的需求,不得已,目光还是得看向开源社区的解决方案。
Sonar映入眼帘。
3. 单测的覆盖率指标维护
Java技术栈里单测覆盖率的计算可以使用Jacoco工具,Jacoco生成覆盖率报告的原理主要基于Java Agent和字节码技术,在编译后的字节码里进行埋点统计,最终得到覆盖率报告。
所有统计覆盖率的工具,运行模式无非就是:
- 提供环境运行代码库的单测,生成Jacoco的单测报告
- 以特定机制获取Jacoco的单测报告,进一步分析、统计、存储项目的覆盖率信息
- 更强大的工具,可以读取git等VCS的信息,提供增量的分析
- 基于历史的报告,提供新增代码的覆盖率分析
获取代码库某一时刻全局的覆盖率信息很容易,难的地方在于如何获取增量的代码覆盖率,这里面有一套复杂的算法,要么工具内维护一个项目状态的演进,要么工具要有能力分析代码库的版本信息。
依照惯例,还是首选厂内方案,但是再次令人失望,厂内覆盖率工具的文档更新频率很低,交互逻辑莫名其妙,与流水线集成的机制相对死板。
例如,我们希望实现『新增代码的覆盖率校验不通过时,禁止部署代码』这一功能,依托厂内工具,是做不到的。
Jacoco提供了覆盖率校验的机制,但是由于Gradle的jacoco task是无状态的,无法获取增量的覆盖率信息,所以也是做不到的。
这一点上,也只有Sonar可以做到。
4. SonarQube的选型依据
依据上述描述,SonarQube具有以下优点:
-
同时具备静态分析、覆盖率监测的能力
-
可以和流水线很好的集合,包括但不限于:gitlab\jenkins\自定义流水线
-
项目保持一定的活跃度,是业界常见的代码质量监测工具
作为静态分析工具,它: -
ide友好,常见ide上都有插件,可以实时分析代码问题
-
提供了多级的监测机制
-
可以灵活豁免,避免误判干扰
作为单测覆盖率监测工具,它:
- 配置简单,容易上手
- 支持代码库的增量覆盖率计算
并且,还有一个重要的亮点,在和流水线结合时,提供了质量阈的概念,用户可以定义一套质量标准,对新入库代码进行校验,如果代码不符合校验要求,则进行拦截,不允许流水线继续执行。
5. 团队内部SonarQube服务的搭建与维护
简而言之,
- 部署一个PostGreSql服务、一个SonarQube服务,PostGreSql作为存储使用。
- 给PostGreSql、SonarQube外挂PVC存储,持久化配置与服务数据。
- 进行一些基础设置:配置中文插件、配置社区的多分支支持插件、设置用户权限
k8s的配置文件如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
labels:
app: postgres
data:
POSTGRES_DB: sonarDB
POSTGRES_USER: postgresadmin
POSTGRES_PASSWORD: ****
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: postgres-pv-claim
labels:
app: postgres
spec:
storageClassName: common-cfs
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-deployment
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: postgres
replicas: 1
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:11.7
imagePullPolicy: "IfNotPresent"
ports:
- containerPort: 5432
envFrom:
- configMapRef:
name: postgres-config
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgredb
volumes:
- name: postgredb
persistentVolumeClaim:
claimName: postgres-pv-claim
---
apiVersion: v1
kind: Service
metadata:
name: postgres-service
labels:
app: postgres
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
protocol: TCP
selector:
app: postgres
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sonarqube-data
spec:
accessModes:
- ReadWriteMany
storageClassName: common-cfs
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sonarqube
labels:
app: sonarqube
spec:
replicas: 1
selector:
matchLabels:
app: sonarqube
template:
metadata:
labels:
app: sonarqube
spec:
initContainers:
- name: init-sysctl
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
containers:
- name: sonarqube
image: sonarqube:lts
ports:
- containerPort: 9000
env:
- name: SONARQUBE_JDBC_USERNAME
value: "postgresadmin"
- name: SONARQUBE_JDBC_PASSWORD
value: "****"
- name: SONARQUBE_JDBC_URL
value: "jdbc:postgresql://postgres-service:5432/sonarDB"
livenessProbe:
httpGet:
path: /sessions/new
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /sessions/new
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
failureThreshold: 6
resources:
limits:
cpu: 2000m
memory: 2048Mi
requests:
cpu: 1000m
memory: 1024Mi
volumeMounts:
- mountPath: /opt/sonarqube/conf
name: data
subPath: conf
- mountPath: /opt/sonarqube/data
name: data
subPath: data
- mountPath: /opt/sonarqube/extensions
name: data
subPath: extensions
imagePullSecrets:
- name: baidubceregistrykey
volumes:
- name: data
persistentVolumeClaim:
claimName: sonarqube-data
---
apiVersion: v1
kind: Service
metadata:
name: sonarqube
labels:
app: sonarqube
spec:
type: ClusterIP
ports:
- name: sonarqube
port: 9000
targetPort: 9000
protocol: TCP
selector:
app: sonarqube
6. 团队内部新项目如何接入SonarQube服务(仅针对Java+Gradle代码)
6.1 服务端配置
进入自己搭建的sonar-qube首页,点击右上角新增项目,
选择手工,设置项目基本信息,通常项目标识=代码库的名字
6.2 流水线设置
可以参考sonarqube自己提供的CICD命令,嵌入进流水线即可。
7. 本地如何使用SonarLint插件
idea插件市场搜索SonarLint安装
设置Settings->tools->SonarLint,设置远端的sonarQube服务地址
设置Settings->tools->SonarLint->project settings绑定远端服务
之后本地就可以看到代码分析结果。
发表回复
要发表评论,您必须先登录。