Refactoring Programmatically

Added on February 4, 2017 and completed 1 time.

When you join a new team, one of of the first things you'll figure out is their preferred coding style. If you're following best practice, then you've been using a linter like rubocop or flake8 to keep style consistent and avoid style arguments (it turns out computers are better at that kind of argument). Sometimes though, you'll find that coding style shifts for a project, or that you need to incorporate another code base with inconsistent style.

At a certain scale, it's very common for someone to end up fixing style differences by hand, but for projects that span thousands of files, this can get quite painful.

Linting is just one example of a broader category of problems: how do we refactor large codebases? Although a common answer is simply not to refactor at scale, that tends to cause codebases to degrade rapidly over time. Fortunately, we can do better.

Most languages come with libraries to break their source code down into S-expressions, which you can then recombine in new ways and compile into modified source code. For Ruby, you can take advantage of ruby_parser and ruby2ruby to do just that.

As you've probably guessed, the system we're building here will be to rewrite some Ruby code to make two improvements.

For the first, let's say that we add a method "incr" which used to require two parameters, but we realized mostly people used it to increment by 1, so we added 1 as the default value for the second parameter, and we want to rewrite all calls to "incr" to only pass a second value if it is different than the default.

Input
def incr(x, i = 1)
    x + i
end
incr(5, 100)
incr(3, 1)
incr(10, 1)
incr(17, 17)
Output
def incr(x, i = 1)
    x + i
end
incr(5, 100)
incr(3)
incr(10)
incr(17, 17)

As a second optimization, let's say that we have bunch of Python programmers on our team who keep writing Python-style "for" loops instead of learning Ruby's "each" idiom, and that we want to rewrite them to use "each".

Input
def count(lst)
  i = 0
  for ele in lst
    i += 1
  end
end
Output
def count(lst)
  i = 0
  lst.each { |ele| i = (i + 1) }
end

	

Once you have those two examples working, then you'll be ready to take on the full system.

This solution is easy to write manually, but you'll learn a lot more if you write the program that solves it!

You'll need to log in to submit solutions.

Related papers
Name Year Topic Rating
Large-Scale Automated Refactoring Using ClangMR 2013 Refactoring ★★★
Source Code Rejuvenation is not Refactoring 2010 Refactoring ★★★★