静态代码分析(SAST)入门:什么是SAST、常见工具、在CI/CD中集成SAST

聊到代码质量,我第一个想到的就是静态代码分析。说白了,SAST 就是在不运行代码的情况下,直接扫描你的源代码,找出潜在的问题。你想想看,代码还没跑起来,就能发现一堆坑,这多省事。

我记得刚入行那会儿,团队里有个老哥,每次提交代码都特别自信。结果上线后,空指针异常、SQL 注入漏洞轮着来。后来我们上了 SAST,他的代码在 CI 阶段就被拦下来了。嗯,从那以后,他再也不敢裸写代码了。

什么是 SAST?

SAST 全称是 Static Application Security Testing,中文叫静态应用安全测试。它像是一个严格的代码审查员,逐行扫描你的代码,检查有没有安全漏洞、代码坏味道、不符合规范的地方。

它的工作原理其实不复杂:

  • 先把你的代码解析成抽象语法树(AST)
  • 然后根据预定义的规则集,在树上做模式匹配
  • 最后把匹配到的问题汇总成报告

我在项目中遇到过最典型的例子,就是有人把数据库密码硬编码在代码里。SAST 一扫描,直接标红。这种问题如果等到上线再发现,后果你懂的。

核心要点:SAST 是在开发阶段就介入的,越早发现问题,修复成本越低。这跟体检一个道理,小病不治,大病难医。

常见工具:SonarQube、ESLint、Pylint

工具这东西,选对了事半功倍。我这些年用过不少,挑三个最常用的说说。

SonarQube

SonarQube 算是 SAST 领域的老大哥了。它支持 30 多种语言,从 Java、Python 到 JavaScript 都能管。我最喜欢它的质量门(Quality Gate)功能——代码质量不达标,直接不让合并。

它的工作流程大概是这样的:

  1. 开发者提交代码
  2. SonarQube 扫描分析
  3. 生成报告,给出质量评分
  4. 如果评分低于阈值,CI 流程直接失败

我曾经在一个遗留项目上用过 SonarQube,第一次扫描结果惨不忍睹——技术债务累计了 300 多天。我们花了两个月逐步修复,最后质量评分从 D 提到了 A。那种成就感,真的爽。

ESLint

如果你写 JavaScript 或 TypeScript,ESLint 几乎是标配。它不光检查语法错误,还能统一代码风格。团队里有人喜欢用分号,有人不喜欢?ESLint 直接帮你搞定。

一个简单的配置示例:

// .eslintrc.json
{
  "rules": {
    "no-unused-vars": "error",
    "no-console": "warn",
    "eqeqeq": ["error", "always"]
  }
}

我个人习惯把 ESLint 配置得严格一点。比如 no-unused-vars 设成 error,未使用的变量直接报错。这样能逼着开发者清理无用代码,减少维护成本。

小技巧:ESLint 可以配合 Prettier 一起用。ESLint 管逻辑问题,Prettier 管格式问题,各司其职。

Pylint

Python 项目的话,Pylint 是首选。它检查的东西很细,从命名规范到代码复杂度,都能覆盖到。

举个例子:

# 不好的写法
def f(x, y):
    if x > 0:
        return x + y
    else:
        return x - y

# Pylint 会提示:函数名太短,不符合命名规范
# 建议改成:def calculate_result(x, y):

我记得有一次,团队里新来的同事写了个函数,嵌套了 8 层 if-else。Pylint 直接给了个复杂度警告。我们重构后,代码可读性提升了一大截。

工具 适用语言 主要功能 我的评价
SonarQube 多语言(30+) 质量门、技术债务管理 企业级首选,功能全面
ESLint JavaScript/TypeScript 代码规范、错误检查 前端必备,配置灵活
Pylint Python 命名规范、复杂度检查 Python 项目标配

在 CI/CD 中集成 SAST

光有工具不行,得把它集成到流水线里,才能发挥最大价值。我见过太多团队,SAST 工具装好了,但没人用,最后成了摆设。

集成 SAST 的核心思路就一句话:让扫描成为 CI/CD 流程的必经关卡

以 GitLab CI 为例,一个简单的集成配置:

# .gitlab-ci.yml
stages:
  - test

sast:
  stage: test
  script:
    - sonar-scanner
  only:
    - merge_requests
  allow_failure: false

这里的关键点是 allow_failure: false。意思是 SAST 扫描不通过,流水线直接失败。这样能确保每个合并请求都经过质量检查。

避坑指南:我曾经把 SAST 扫描放在代码提交后立即执行,结果发现扫描时间太长,开发者等得心烦。后来我改成在合并请求阶段触发,既保证了质量,又不影响日常开发节奏。

集成时要注意几个点:

  • 扫描时机:建议在合并请求阶段触发,而不是每次提交都跑
  • 阈值设置:刚开始别设太严,先收集数据,再逐步收紧
  • 结果反馈:扫描结果要能直接关联到代码行,方便开发者定位问题

我现在的做法是,在 CI 流水线里加一个 SAST 阶段,扫描结果直接显示在合并请求的评论区。开发者打开 MR,就能看到哪些代码有问题,哪里需要改。这样效率高很多。

总结一下:SAST 不是银弹,但它能帮你拦住 80% 的常见问题。配合 CI/CD 使用,效果翻倍。别等到上线了才发现问题,那时候改起来就痛苦了。

好了,这一章就聊到这儿。下一章我们讲讲动态分析,那又是另一个有意思的话题了。