袋鼠云代码检查服务,揭秘高质量代码背后的秘密

打印 上一主题 下一主题

主题 688|帖子 688|积分 2064

质量是产品的生命线,代码检查是软件开发过程中至关重要的一环,它可以帮助我们发现并纠正潜在的错误,提高软件质量,降低维护成本。
袋鼠云产品中也存在这个问题,由于离线数据开发人员 SQL 水平不一,导致代码书写混乱、SQL 代码运行问题较多。本文将介绍在离线产品中如何利用 SQL 检查规则规范化 SQL 代码,对代码书写问题进行拦截,便于统一管理,用于预防引入需要治理的问题。
通过本文的介绍,我们希望您能够认识到代码检查的重要性,并了解如何通过最佳实践来提高代码质量和开发效率。
何时进行代码规则检查?

SQL 任务在离线产品界面开发完成之后,点击运行的按钮,会先经过代码规则检查,如果代码规则不满足则会提示到用户具体的原因。

数据资产模块内置了 5 种代码检查规则,用户可以根据需要选择性开启。

开启后在离线项目管理中可以选择使用的代码规则检查项、生效范围和 SQL 任务类型。

在离线 SQL 任务中去运行一条 SQL 前会根据选择的规则先进行代码检查,如果代码检查不通过则会反馈给用户,用户可以根据实际需要判断要不要执行该 SQL。

在数据资产的代码检查时间中可以看到已经触发的检查历史以及相应的统计数据。

如何实现代码检查规则?

在 CodeCheck 包下定义了通用的代码规则检查的接口。
  1. public interface ICheck {
  2. Result codeCheck(String checkContent, String defaultDb, Integer dataSourceType, Long tenantId, SqlParseInfo sqlParseInfo);
  3. CodeCheckType getCheckType();
  4. }
复制代码
分区表查询必须带分区规则为例,会先调用 SQLParser 组件进行 SQL 解析,SQLParseInfo 即为 SQL 解析结果,检查时会先判断 SQL 语句是不是查询语句,如果是则判断查询的表是不是分区表,再判断是否有查询条件,最后判断查询条件中是否包含分区字段来判断是否检查通过。
  1. public class CodeCheckImplType01 extends AbstractCheck {
  2. private static final Logger LOGGER = LoggerFactory.getLogger(CodeCheckImplType01.class);
  3. @Autowired
  4. private DataTableColumnThirdService dataTableColumnThirdService;
  5. @Autowired
  6. private DataTableThirdService dataTableThirdService;
  7. @Override
  8. public Result codeCheck(String checkContent, String defaultDb, Integer dataSourceType, Long tenantId, SqlParseInfo sqlParseInfo) {
  9. if (!isQuery(sqlParseInfo.getSqlType())) {
  10. return Result.buildSuccessResult();
  11. }
  12. try {
  13. MetadataSearchParam searchParam = new MetadataSearchParam();
  14. searchParam.setDbName(sqlParseInfo.getMainTable().getDb());
  15. searchParam.setTableName(sqlParseInfo.getMainTable().getName());
  16. searchParam.setDataSourceType(dataSourceType);
  17. searchParam.setTenantId(tenantId);
  18. List<TableDTO> tableDTOS = dataTableThirdService.tableList(searchParam);
  19. // 获取表信息
  20. for (TableDTO tableDTO : tableDTOS) {
  21. List<DataTableColumn> tableColumns = dataTableColumnThirdService.listColumnByTableIds(Lists.newArrayList(tableDTO.getTableId()));
  22. if (CollectionUtils.isEmpty(tableColumns)) {
  23. continue;
  24. }
  25. List<String> partitionColumnNameList = tableColumns.stream()
  26. .filter(Objects::nonNull)
  27. .filter(t -> HavePartitionEnum.have_partition.getPartitionValue().equals(t.getIsPartition()))
  28. .map(DataTableColumn::getColumnName)
  29. .collect(Collectors.toList());
  30. // 非分区表直接返回
  31. if (CollectionUtils.isEmpty(partitionColumnNameList)) {
  32. continue;
  33. }
  34. if (CollectionUtils.isEmpty(sqlParseInfo.getColumnIdentifierList())) {
  35. // 没有查询条件则校验失败
  36. return Result.buildFailedResult(String.format(getCheckType().getCheckResultFormat(), searchParam.getTableName()));
  37. }
  38. List<String> columnList = sqlParseInfo.getColumnIdentifierList().stream()
  39. .filter(c -> StringUtils.equals(c.getDb(), searchParam.getSchemaName()) && StringUtils.equals(c.getTable(), searchParam.getTableName()))
  40. .map(ColumnIdentifier::getColumn).collect(Collectors.toList());
  41. boolean disjoint = Collections.disjoint(partitionColumnNameList, columnList);
  42. if (disjoint) {
  43. return Result.buildFailedResult(String.format(getCheckType().getCheckResultFormat(), searchParam.getTableName()));
  44. }
  45. }
  46. } catch (Exception e) {
  47. // 异常情况先通过
  48. LOGGER.error("code check error, check content: {}, defaultDb: {}, checkType: {}", checkContent, defaultDb, getCheckType().name());
  49. }
  50. return Result.buildSuccessResult();
  51. }
  52. @Override
  53. public CodeCheckType getCheckType() {
  54. return CodeCheckType.TYPE_01;
  55. }
  56. }
复制代码
如何自定义代码检查规则?

如果内置的代码检查规则不满足客户的使用场景,客户可以通过上传 jar 的方式自定义代码检查规则。

自定义代码检查规则使用 SPI 机制加载用户上传的自定义 jar,并在代码检测时调用 CodeCheck 方法,在资源关闭时调用 close 方法,用户需要将配置文件说明中的 jar 依赖自己的项目中。具体如下:
● 创建一个类实现接口
创建一个类实现接口 com.dtstack.assets.spi.codecheck.ICodeCheckClient 并实现 CodeCheck 和 close 方法,书写相关逻辑代码,如果校验通过需要将 CheckResult 对象中 success 设置为 true,失败时设置 success 字段为 false 并设置校验不通过的理由。
  1. package com.dtstack.assets.spi.codecheck;
  2. import java.util.Map;
  3. public interface ICodeCheckClient {
  4. /**
  5. * 代码检查
  6. *
  7. * @param checkContent 检查内容
  8. * @param extMap 扩展配置
  9. * @return 检查结果
  10. */
  11. CheckResult codeCheck(String checkContent, Map<String, Object> extMap);
  12. /**
  13. * 释放资源, 调用时需要关闭所使用的资源
  14. */
  15. void close();
  16. }
复制代码
· 入参字段解释
– checkContent 为单条 SQL 信息
– extMap 会设置一些平台的属性,包含任务名称、任务类型等
· 出参字段解释
– success 为是否校验通过,必须设置
– checkResult 为校验结果,校验不通过时不能为空
  1. package com.dtstack.demo;
  2. import com.dtstack.assets.spi.codecheck.CheckResult;
  3. import java.util.Map;
  4. public class CodeCheckImpl implements com.dtstack.assets.spi.codecheck.ICodeCheckClient{
  5. @Override
  6. public CheckResult codeCheck(String checkContent, Map<String, Object> extMap) {
  7. // 代码检查相关逻辑
  8. CheckResult checkResult = new CheckResult();
  9. checkResult.setSuccess(false);
  10. checkResult.setCheckResult("校验不通过的理由");
  11. return checkResult;
  12. }
  13. @Override
  14. public void close() {
  15. // 关闭相关资源
  16. }
  17. }
复制代码
● 在 resource 目录下创建 META-INF/services 目录

● 在 META-INF/services 目录下创建文件
文件名称为 com.dtstack.assets.spi.codecheck.ICodeCheckClient ,文件内容为实现 ICodeCheckClient 接口类的权限定类名。

文件名称和内容示例:

● 打包当前工程并在数据资产页面注册代码校验规则
不符合条件的 jar 会给出提示。


如何加载自定义代码规则对应的 jar ?

我们会为上传的每个规则对应的 jar 初始化一个唯一的自定义 classloader,该 classloader 继承 URLClassLoader 并保证子类加载器优先加载。

在第一次调用时进行加载并缓存对应的 client。

在用户重新上传或者编辑规则后清除旧的 classloader 和加载的 client 并释放资源。

《数栈产品白皮书》:https://www.dtstack.com/resources/1004?src=szsm
《数据治理行业实践白皮书》下载地址:https://www.dtstack.com/resources/1001?src=szsm
想了解或咨询更多有关袋鼠云大数据产品、行业解决方案、客户案例的朋友,浏览袋鼠云官网:https://www.dtstack.com/?src=szbky
同时,欢迎对大数据开源项目有兴趣的同学加入「袋鼠云开源框架钉钉技术qun」,交流最新开源技术信息,qun号码:30537511,项目地址:https://github.com/DTStack

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

慢吞云雾缓吐愁

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表