1

I am trying to work on finding the following metrics

New Work - totally new code which does not replace other code.

Churn - code that is rewritten or deleted after being written

Help Others - where developer helps on someone else work

While I was working on churn and new work metrics, I cam to know about legacy refactor which says it's a code that updates or edits old code.

I am confused in between churn and legacy refactor. To me it seems like the same thing.

For calculating churn the code(taking help from truegitcodechurn) is below

def get_commits_by_author(self, author, after=None, before=None, format='%h'):
    cmd = f'git log --author="{author}" --format="{format}" --no-abbrev '
    cmd += f'--before="{before}" --after="{after}" '
    cmd += f'{self.no_merges} {get_log_range("develop")} --reverse'
    command = [f'{cmd}']
    result = get_proc_out(command).split('\n')
    self.commits_by_author = result
    return self.commits_by_author

def get_churn_per_author(self, author, before, after, exdirs=[]):
    commits = get_commits_by_author(self, author, before, after, format="%h")
    files = {}
    contribution = 0
    churn = 0
    contributors = author
    for commit in commits:
        [files, contribution, churn] = get_loc(
            commit, files, contribution, churn, exdirs
        )
    if len(contributors.strip()) == 0:
        contributors = set(
            get_commits_by_author(self, author, before, after, "%an")
        ).__str__()
        contributors = contributors.replace("{", "").replace("}", "").replace("'", "")
    return contributors, contribution, churn


def get_loc(commit, files, contribution, churn, exdirs):
    cmd = f"git show --format= --unified=0 --no-prefix {commit}"
    if len(exdirs) > 1:
        cmd += " -- ."
        for exdir in exdirs:
            cmd += f' ":!{exdir}"'
    print("cmd", cmd)
    command = [f"{cmd}"]
    results = get_proc_out(command).splitlines()
    file = ""
    loc_changes = ""
    for result in results:
        new_file = is_new_file(result, file)
        if file != new_file:
            file = new_file
            if file not in files:
                files[file] = {}
        else:
            # @@ -1,5 +1,4 @@ = -1,5 +1,4
            new_loc_changes = is_loc_change(result, loc_changes)
            if loc_changes != new_loc_changes:
                loc_changes = new_loc_changes
                locc = get_loc_change(loc_changes)
                # locc = {2: 0} or {8: 0, 9: 1}
                for loc in locc:
                    # files[file] = {2: 0, 8: 0, 9: 1}
                    if loc in files[file]:
                        files[file][loc] += locc[loc]
                        churn += abs(locc[loc])
                    else:
                        files[file][loc] = locc[loc]
                        contribution += abs(locc[loc])
            else:
                continue
    return [files, contribution, churn]


def get_loc_change(loc_changes):
    # removals
    # -1,5 +1,4 = -1,5
    left = loc_changes[: loc_changes.find(" ")]
    left_dec = 0
    # 2
    if left.find(",") > 0:
        # 2
        comma = left.find(",")
        # 5
        left_dec = int(left[comma + 1 :])
        # 1
        left = int(left[1:comma])
    else:
        left = int(left[1:])
        left_dec = 1

    # additions
    # +1,4
    right = loc_changes[loc_changes.find(" ") + 1 :]
    right_dec = 0
    if right.find(",") > 0:
        comma = right.find(",")
        right_dec = int(right[comma + 1 :])
        right = int(right[1:comma])
    else:
        right = int(right[1:])
        right_dec = 1

    if left == right:
        return {left: (right_dec - left_dec)}
    else:
        return {left: left_dec, right: right_dec}


def is_loc_change(result, loc_changes):
    # search for loc changes (@@ ) and update loc_changes variable
    # @@ -1,5 +1,4 @@
    if result.startswith("@@"):
        # loc_change = result[2+1: ] -> -1,5 +1,4 @@
        loc_change = result[result.find(" ") + 1 :]
        # loc_change = loc_change[:9] -> -1,5 +1,4
        loc_change = loc_change[: loc_change.find(" @@")]
        return loc_change
    else:
        return loc_changes


def is_new_file(result, file):
    # search for destination file (+++ ) and update file variable
    if result.startswith("+++"):
        return result[result.rfind(" ") + 1 :]
    else:
        return file

Firstly, I want to understand what is the difference between them, and if legacy refactor is different than churn, then what should I consider to separate churn and legacy refactor result from the above code so that it can be shown clearly that this is churn and this is legacy refactor.

2
  • Is it okay to ask for a reason on why I am getting negative vote?
    – Serenity
    Jun 14, 2021 at 9:32
  • 2
    Don't overthink these downvotes, your question is fine. We have some community members here who downvote regularly for pretty unclear reasons. In this case, the question got also a close vote "needs-details-or-clarity", but the voter seems to believe you can read their mind, since they did not tell what kind of details they think is missing. Take my excuses for them.
    – Doc Brown
    Jun 14, 2021 at 11:05

1 Answer 1

4

Your definitions of churn and legacy refactoring are not completely the same, nor are they completely different. Churn, as you describe it, could apply to refactoring old code, as well as rewriting old code because requirements have changed. To understand the difference between those terms, you must first understand legacy refactoring.

To be honest, I've never encountered the term "legacy refactoring." Most of us just call it refactoring. The purpose of refactoring code is too improve the architecture or performance without changing the output of that code. I combine "improve architecture" with "improve maintainability" and "improve testability," because if you are not improving maintainability or testability, then your architecture is not better.

Now compare that to your definition of churn:

code that is rewritten or deleted after being written

This does not specify if the code was rewritten or deleted because requirements changed, or because the code was refactored. The word "churn" in this context is less specific, and includes more reasons to change code than the term "refactoring."

I could see how churn and refactoring could be useful as separate metrics, if you further refine the meaning of churn to separate it from refactoring:

Churn: Code that is rewritten or deleted after being written in order to change behavior.

After redefining the meaning of churn, you identify parts of your code base that change frequently due to changes in requirements. This indicates that the problems you are trying to solve are not well understood, or that part of the business model/process is in a state of evolution. Neither thing is bad. It is common to start out with a simple solution to a poorly understood problem, and then iteratively build on that as you discover more. Agile Development is a software development methodology centered on this concept.

Refactoring is also a good metric because it could lead to more churn. Once the architecture is improved, that code is easier to change when requirements change. You might find that refactoring code is a necessary precursor to churn.

As for how useful those metrics are? I'm not sure. Churn could be associated with new features and product improvements that generate more income. Refactoring, since it does not change any behavior, could be seen as a waste of time and money. The challenge is connecting those two metrics. Refactoring leads to churn, which leads to more features to market at a faster pace. If you do not attempt to connect the two, then management will not see the value of refactoring.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.