基本思路:

每一行代码都有作者, 错误的代码更应该有作者。
本文的思路是, 通过火线静态代码扫描程序扫描后的 json 报告, 直接定位到人,也就是错误代码的引入者。
责任到人,可以更好地做到错误代码发现及时, 修复更及时。

准备条件:

  1. 火线静态代码扫描程序
    1). 下载火线 jar 包 http://magic.360.cn/index.html
    2). 编写配置文件 RedLineConfigurationNoLog.xml http://magic.360.cn/user.html#args

  2. 代码扫描服务器 - Linux CentOS 6 or 7
    1). 安装 JDK
    2). 准备预扫描项目的 git 目录,例如:/data/git/test
    3). 发布 WEB 服务的文件夹, 用于发布火线报告 HTML 版, 例如: /data/webtest/fireline
    3). jq 用于解析火线 json 报告的内容,
    CentOS 7:yum install -y jq,
    CentOS 6:需要自行下载安装,https://stedolan.github.io/jq/

脚本执行步骤:

一、火线静态代码扫描

  1. git pull 更新扫描服务器上的代码
  2. 执行火线静态代码扫描命令
  3. 发布火线扫描报告 html

二、定位错误代码作者

  1. 检查火线扫描报告 json 文件内 block 和 error 条目的数量
  2. 根据错误代码的文件名和出错行数查找错误代码作者

脚本内容(/data/fireline/fireline.sh):

#! /bin/sh
# Author : binwei.liu
# Usage: 2017-09-02 火线静态代码扫描以及定位错误代码作者

#########################################
# 一、火线静态代码扫描
#########################################
# 1. git pull 更新扫描服务器上的代码
# 2. 执行火线静态代码扫描命令
# 3. 发布火线扫描报告html
#########################################
# 二、定位错误代码作者
#########################################
# 4. 检查火线扫描报告json文件内block和error条目的数量
# 5. 根据错误代码的文件名和出错行数查找错误代码作者
#########################################

PS4='+[$LINENO]'
RUN_TIME=$( date +'%Y%m%dT%H%M%S' )

# 设置扫描项目名称
# FIRELINE_PROJ_NAME=${1}
FIRELINE_PROJ_NAME='test'
# 设置已经准备好的项目git目录
# FIRELINE_SCAN_SRC_DIR=${2}
FIRELINE_SCAN_SRC_DIR='/data/git/test'

# 火线报告临时存放文件夹
FIRELINE_REPORT_SAVE_DIR="/tmp/report_${FIRELINE_PROJ_NAME}_${RUN_TIME}"
# 火线报告文件名
FIRELINE_REPORT_FILE_NAME="index_${FIRELINE_PROJ_NAME}"
# 火线报告提交人名称
FIRELINE_USER='binwei.liu'
# 火线程序jar包
FIRELINE_JAR='/data/fireline/fireline.jar'
# 火线程序配置文件路径
FIRELINE_CONFIG='/data/fireline/RedLineConfigurationNoLog.xml'
# 扫描服务器上,web服务发布的文件夹
PUBLIC_REPORT_DIR='/data/webtest/fireline'
# 火线报告内的json文件
FIRELINE_REPORT_JSON_FILE="/data/webtest/fireline/${FIRELINE_REPORT_FILE_NAME}.json"
# web报告发布后的查看地址
PUBLIC_REPORT_URL="http://www.17test.net/fireline/${FIRELINE_REPORT_FILE_NAME}.html"
# json报告里包含block条目的数量
JSON_BLOCK_ITEM_COUNT=0
# json报告里包含error条目的数量
JSON_ERROR_ITEM_COUNT=0

# 日志屏幕显示或者记录到指定文件
function log () {
    # echo -e `date +'%Y-%m-%d %H:%M:%S'` "${*}" | tee -a ${LOG_FILE}
    # echo -e "${*}" | tee -a ${LOG_FILE}
    echo -e "${*}"
}

function init () {
    log "##################################################"
    log "报告链接: ${PUBLIC_REPORT_URL}"
    log "规则文档: http://magic.360.cn/document.html"
    log "##################################################"

    # 建立报告临时存放文件夹
    log "info: create new tmp report folder ${FIRELINE_REPORT_SAVE_DIR}"
    mkdir -p ${FIRELINE_REPORT_SAVE_DIR}
}

function finalize () {
    log "${2}"
    exit ${1}
}

function update_local_code () {
    log "info: update the local code to the latest."

    cd ${FIRELINE_SCAN_SRC_DIR}
    git reset --hard origin/master && \
    git gc && \
    git pull && \
    git submodule init && \
    git submodule update && \
    git submodule foreach git checkout master && \
    git submodule foreach git pull

    if [[ $? -ne 0 ]]; then
        finalize 1 "error: update the local code failed."
    fi
}

function fireline_scan () {
    log "info: fireline starts to scan the project of <${FIRELINE_PROJ_NAME}>"
    java -jar ${FIRELINE_JAR} \
        -Df ile.encoding=UTF-8 \
        scanSrcDir=${FIRELINE_SCAN_SRC_DIR} \
        reportSaveDir=${FIRELINE_REPORT_SAVE_DIR} \
        reportFileName=${FIRELINE_REPORT_FILE_NAME} \
        proj_name=${FIRELINE_PROJ_NAME} \
        user=${FIRELINE_USER} \
        config=${FIRELINE_CONFIG} > /dev/null 2&>1

    if [[ $? -ne 0 ]]; then
        finalize 1 "error: fireline scans project <${FIRELINE_PROJ_NAME}> failed."
    else
        log "info: fireline scans completely."  
    fi
}

function public_report_html () {
    log "info: public the fireline report."

    cp -rf ${FIRELINE_REPORT_SAVE_DIR}/* ${PUBLIC_REPORT_DIR}/

    if [[ $? -ne 0 ]]; then
        log "error: public the fireline report failed."
    fi
}

function find_author_of_error_code () {
    local _beginline=${1}
    local _endline=${2}
    local _filepath=${3}

    log "author:"
    cd ${FIRELINE_SCAN_SRC_DIR}
    git blame -L ${_beginline},${_endline} ${_filepath}

    if [[ $? -ne 0 ]]; then
        finalize 1 "error: git blame -L ${_beginline},${_endline} ${_filepath} failed."
    fi
}

function get_item_count_by_level () {
    local _level=${1}

    jq ".${_level} | length" ${FIRELINE_REPORT_JSON_FILE}

    if [[ $? -ne 0 ]]; then
        finalize 1 "error: get the count of <${_level}> level failed."
    fi
}

function get_element_value_from_item () {
    local _level=${1}
    local _no=${2}
    local _key=${3}

    jq -r ".${_level}[] | select(.no == \"${_no}\") | .${_key}" ${FIRELINE_REPORT_JSON_FILE}

    if [[ $? -ne 0 ]]; then
        finalize 1 "error: get the key <level:${_level}, no:${2}, key:${_key}> failed."
    fi
}

function get_json_item_count () {
    JSON_BLOCK_ITEM_COUNT=$( get_item_count_by_level 'block' )
    JSON_ERROR_ITEM_COUNT=$( get_item_count_by_level 'error' )
}

function print_items_details () {
    local _level=${1}
    local _count=${2}

    log "info: show the items of <${_level}>"

    for ((i=1; i<=${_count}; i++)); do
        _no=${i}
        _classname=$( get_element_value_from_item ${_level} ${_no} 'classname' )
        _filepath=$( get_element_value_from_item ${_level} ${_no} 'filepath' )
        _rulename=$( get_element_value_from_item ${_level} ${_no} 'rulename' )
        _rule_description=$( get_element_value_from_item ${_level} ${_no} 'ruleDescription' )
        _docurl=$( get_element_value_from_item ${_level} ${_no} 'docurl' )
        _beginline=$( get_element_value_from_item ${_level} ${_no} 'beginline' )
        _endline=$( get_element_value_from_item ${_level} ${_no} 'endline' )

        log "---- ${_no} ----"
        log "classname: ${_classname}"
        log "filepath: ${_filepath}"
        log "rulename: ${_rulename}"
        log "ruleDescription: ${_rule_description}"
        log "docurl: ${_docurl}"
        log "beginline: ${_beginline}"
        log "endline: ${_endline}"

        find_author_of_error_code ${_beginline} ${_endline} ${_filepath}
    done
}

function main () {
    # 初始化
    init

    # 1. git pull 更新扫描服务器上的代码
    update_local_code

    # 2. 执行火线静态代码扫描命令
    fireline_scan

    # 3. 发布火线扫描报告html
    public_report_html

    # 4. 检查火线扫描报告json文件内block和error条目的数量
    get_json_item_count

    # 5. 根据错误代码的文件名和出错行数查找错误代码作者
    if [[ ${JSON_BLOCK_ITEM_COUNT}>0 ]]; then
        log "fail: there are some code issues about <block> level."
        print_items_details 'block'${JSON_BLOCK_ITEM_COUNT}
    else
        log "info: not found the code issues about <block> level."
    fi

    if [[ ${JSON_ERROR_ITEM_COUNT}>0 ]]; then
        log "fail: there are some code issues about <error> level."
        print_items_details 'error' ${JSON_ERROR_ITEM_COUNT}
    else
        log "info: not found the code issues about <error> level."
    fi
}

main

脚本执行结果:

[root@VM-TEST-96 fireline]# bash /data/fireline/fireline.sh 
##################################################
报告链接: http://www.17test.net/fireline/index_test.html
规则文档: http://magic.360.cn/document.html
##################################################
info: create new tmp report folder /tmp/report_test_20170904T201126
info: update the local code to the latest.
HEAD is now at e096a44 edit errot01 02 03
Counting objects: 15, done.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (15/15), done.
Total 15 (delta 2), reused 15 (delta 2)
Already up-to-date.
info: fireline starts to scan the project of <test>
info: fireline scans completely.
info: public the fireline report.
info: not found the code issues about <block> level.
fail: there are some code issues about <error> level.
info: show the items of <error>
---- 1 ----
classname: TestFireline
filepath: /data/git/test/src/test/TestFireline.java
rulename: 错位的空判断
ruleDescription: 这里的空检查是放错位置的。如果变量为空你将得到一个空指针异常。可能因为检查是无用的或者是不正确的。如:if (!string.equals("") && string!=null){}。
docurl: http://magic.360.cn/document.html#CodeStyle-11
beginline: 53
endline: 53
author:
013d7a2d (binwei.liu 2017-09-04 15:39:29 +0800 53)              if (a.equals(baz) && a != null) {}
---- 2 ----
classname: TestFireline
filepath: /data/git/test/src/test/TestFireline.java
rulename: 破坏空判断
ruleDescription: 如果自身抛出空指针异常空检查就会遭到破坏,比如你使用 代替 &&,反之亦然,如(应是&&):if (string!=null  !string.equals("")){}。
docurl: http://magic.360.cn/document.html#CodeStyle-13
beginline: 60
endline: 62
author:
e096a440 (binwei.liu 2017-09-04 16:16:25 +0800 60)         if (string!=null || !string.equals("")) {
e096a440 (binwei.liu 2017-09-04 16:16:25 +0800 61)              return string;
e096a440 (binwei.liu 2017-09-04 16:16:25 +0800 62)         }
---- 3 ----
classname: TestFireline
filepath: /data/git/test/src/test/TestFireline.java
rulename: 破坏空判断
ruleDescription: 如果自身抛出空指针异常空检查就会遭到破坏,比如你使用 代替 &&,反之亦然,如(应是&&):if (string!=null  !string.equals("")){}。
docurl: http://magic.360.cn/document.html#CodeStyle-13
beginline: 65
endline: 67
author:
e096a440 (binwei.liu 2017-09-04 16:16:25 +0800 65)         if (string==null && string.equals("")) {
e096a440 (binwei.liu 2017-09-04 16:16:25 +0800 66)              return string;
e096a440 (binwei.liu 2017-09-04 16:16:25 +0800 67)         }
[root@VM-TEST-96 fireline]# 

火线 json 报告内容(/data/webtest/fireline/index_test.json):

{
    "block": [],
    "error": [
        {
            "no": "1",
            "classname": "TestFireline",
            "filepath": "/data/git/test/src/test/TestFireline.java",
            "rulename": "错位的空判断",
            "ruleDescription": "这里的空检查是放错位置的。如果变量为空你将得到一个空指针异常。可能因为检查是无用的或者是不正确的。如:if (!string.equals(\"\") && string!=null){}。",
            "docurl": "http://magic.360.cn/document.html#CodeStyle-11",
            "beginline": "53",
            "endline": "53"
        },
        {
            "no": "2",
            "classname": "TestFireline",
            "filepath": "/data/git/test/src/test/TestFireline.java",
            "rulename": "破坏空判断",
            "ruleDescription": "如果自身抛出空指针异常空检查就会遭到破坏,比如你使用 代替 &&,反之亦然,如(应是&&):if (string!=null  !string.equals(\"\")){}。",
            "docurl": "http://magic.360.cn/document.html#CodeStyle-13",
            "beginline": "60",
            "endline": "62"
        },
        {
            "no": "3",
            "classname": "TestFireline",
            "filepath": "/data/git/test/src/test/TestFireline.java",
            "rulename": "破坏空判断",
            "ruleDescription": "如果自身抛出空指针异常空检查就会遭到破坏,比如你使用 代替 &&,反之亦然,如(应是&&):if (string!=null  !string.equals(\"\")){}。",
            "docurl": "http://magic.360.cn/document.html#CodeStyle-13",
            "beginline": "65",
            "endline": "67"
        }
    ]
}

鸣谢: @oggboy (丁老九), 完善了火线 json 报告,使代码扫描和定位责任人两部分结合起来, 实现完整的自动化。


↙↙↙阅读原文可查看相关链接,并与作者交流