寻找源代码中的缺陷点

865 阅读4分钟

绪论

我们经常面临着源代码经常中断的问题。或者那些对自己或其上游模块的变化非常敏感的模块。而随着新的团队成员的加入,并最终不得不提交这些相同模块的变化,这就要求有一些方法来跟踪这些代码的碎片,而不是靠人的记忆。同时,找到源代码中的这些点,可以更好地了解哪些地方可能需要重构或重新思考实现的策略。 很明显,我们认为预测错误的发生需要将多个参数输入一个大的神经网络来计算这些发生的概率是什么。事实证明,我们可以用更少、更简单的指标来做这件事。

解决方案

Rehman等人指出,你所需要的只是在源代码中所做的修改的历史或缓存,以及一些标签来标记哪些修改是为了修复错误。将这些修改映射到文件或代码片断中,就能找到需要返工或仔细的同行评审的地方。正如谷歌的工程师们在这里提到的,将其与时间衰减功能结合起来,这将降低一段代码的优先级,因为面对其他代码中浮现的新错误,在这段代码中完成的错误修复变得更老。

我们发现Jorge Niedbalski很好地实现了这一功能,但它有一个bug,我们为此提出了一个拉动请求。尽管如此,这个模块工作得很好,所以我们分叉了它,并为我们的使用做了一些修改。

这里的想法是阅读一个给定仓库的git logs ,并在其中搜索标有特定关键词的提交。这与版本库中的修改频率和最近的修改情况一起考虑,可以看出哪一段代码是热点。我们使用vcstools库来操作和处理版本控制系统的存储库。

我们首先检查所提供的路径,检查它是否是一个有效的vcs仓库:

def get_current_vcs(path):
    if path is None:
        path = '.'
    for vcs_type in vcs_abstraction.get_registered_vcs_types():
        vcs = vcs_abstraction.get_vcs(vcs_type)
        if vcs.static_detect_presence(path):
            return vcs(path)
    raise Exception("Did not find a valid VCS repository")

然后我们从版本库的特定分支获取变化集:

def get_changesets(days_ago):
        current_branch = vcs.get_current_version_label()

        if current_branch != branch:
            vcs._do_checkout(branch)

        for log in vcs.get_log():
            (date, message, id) = (log['date'], log['message'],
                                   log['id'])

            commit_date = date.replace(tzinfo=None)

            if commit_date >= days_ago and \
               description_regex.search(message):
                yield((message, commit_date, vcs.get_affected_files(id)))

在这里,我们还检查变化集是否来自指定时间段内的提交,以及提交信息是否有特定的关键字。我们使用下面的重词来匹配任何带有错误修复关键词的提交信息:

"^.*([B|b]ug)s?|([f|F]ix(es|ed)?|[c|C]lose(s|d)?)|(([Q|q][F|f])-\d?).*$"

一旦我们有了来自指定分支和所需时间段内的修复提交,我们还可以继续排除一些可能不是我们感兴趣的计算错误热点的文件。这将包括像package.json、requirements.txt、yarn.lock或Readme文件等依赖文件。这是由一个布尔参数驱动的:

def remove_excluded_files(fixes):
    exclusion_regex = re.compile(
        r".*(README|package\.json|yarn\.lock|test[s]*).*$")
    for fix in fixes:
        exclusion_files = [file for file in fix[2]
                           if exclusion_regex.search(file)]
        for file in exclusion_files:
            fix[2].remove(file)
    return fixes

然后,我们为所采购的变更集中的每个文件计算热点系数:

def get_code_hotspots(options):
    commits = get_fix_commits(options.branch, options.days, options.path)

    if options.fileExclusions:
        commits = remove_excluded_files(commits)

    if not commits:
        print(
            '''Did not find commits matching search criteria\n'''
            f'''for repo at: {options.path} branch: {options.branch}''')
        return None

    print_summary(options.path, options.branch, len(commits), options.days)

    (last_message, last_date, last_files) = commits[-1]
    current_dt = datetime.datetime.now()

    print(f'\nFixes\n{("-" * 80)}')

    hotspots = {}

    for message, date, files in commits:
        this_commit_diff = time_diff(current_dt, date)
        last_commit_diff = time_diff(current_dt, last_date)

        factor = this_commit_diff / last_commit_diff

        factor = 1 - factor

        for filename in files:
            if filename not in hotspots:
                hotspots[filename] = 0
            try:
                hotspot_factor = 1/(1+math.exp((-12 * factor) + 12))
            except:
                pass

            hotspots[filename] += hotspot_factor

        print(f'      -{message}')

    sorted_hotspots = sorted(hotspots, key=hotspots.get, reverse=True)

    print(f'\nHotspots\n{("-" * 80)}')
    for k in sorted_hotspots[:options.limit]:
        yield (hotspots[k], k)

与此同时,我们使用argparse模块在命令行中添加了以下参数。

支持的参数

可选参数参数描述使用实例
-h, --help显示描述用法的帮助对话框bughotspots --help
--天数在历史上考虑提交的天数来计算错误系数,默认值。30bughotspots --days 60
--limit显示文件热点结果的最大数量,默认值:10bughotspots --limit 100
--branch使用特定的分支,默认值:'master'。bughotspots --branch feature-branch
--bugsFile使用一个包含bug列表的文件来搜索提交的内容,默认为搜索提到QF问题的提交内容bughotspots --bugsFile bugs.csv
--paths提供要搜索的版本库路径,默认为在当前目录下搜索bughotspots --paths .../folder1 .../folder2
--fileExclusions在计算bug热点时不考虑依赖文件(package.json、requirements.txt)、README和测试中的变化,默认值为false。bughotspots --路径 .../folder1 .../folder2

后果

更进一步,我们可以通过这个添加功能,以markdown甚至更美化的HTML报告来生成报告。如果保留了历史趋势,那么可以使用Bokeh这样的可视化库来绘制趋势。