
以编程方式重构红宝石。
红宝石(4), devtools(2)当您加入新团队时,您将弄清楚的第一件事之一是他们的首选编码风格。他们可能有一个像乐趣曲目碑或者Flake8.将样式参数委托给您至尊课程的计算机。有时,您会发现更改存储库的编码风格或在具有不同样式选择的另一个代码库中合并的原因。
在一定的范围内,你可能只是用手修理东西,但对于跨越数千个档案的项目,没有多少咖啡因可以掩盖疼痛。
Linting只是一个更广泛类别的问题的一个例子:你如何重构大的codebases?虽然一个共同的答案是根本不是在规模的重构中,但这往往会导致码条随着时间的推移而迅速降级。它可以更好!
大多数语言都带有图书馆来代表源代码表达式,然后,您可以以新的方式修改以生成修改的源代码。对于Ruby,这两个图书馆就是这样:
Ruby_Parser.这解析了Ruby文件并输出其表达式那
Ruby2Ruby.将s表达式转换为Ruby代码。
让我们试试看。(GitHub上提供完整的源代码。)
想象一下我们有一种方法Inc.
用于需要两个参数,但大多数调用递增1
,所以我们补充说1
作为第二个参数的默认值。现在我们想重写所有电话Inc.
仅在与默认值不同,只能通过第二个值。
所以我们可以想象一些看起来这样的代码:
def intr(x,i = 1)x + i端incl(5,100)incr(3,1)Inc(10,1)Inc(17,17)
我们想要重写如下:
def intr(x,i = 1)x + i端incl(5,100)Inc(3)Inc(10)Inc(10)Inc(17,17)
我们代码的有趣部分将在改写
函数,所以首先让我们写下脚手架,这个功能将生活在内,然后赶紧努力。
脚手架很短,需要一些图书馆,解析斯丁到S表达式,将这些S表达式重写为Ruby代码,然后输出到stdout.
。
要求'ruby_parser'需要'ruby2ruby'def重写(expr)expr结束解析= rubymarser.new.parse(argf.read)puts ruby2ruby.new.process重写(解析)
假设您在命名的文件中有上面的示例输入Refactor.Input.
你命名这个文件refactor.rb.
,然后您可以使用它:
Ruby Refactor.rb这实际上非常酷,因为我们正在进行一些代码,解析它,然后重组它,但真的有趣的部分是下一个:修改它!
Astute Reader将注意到输出版本有一些无关括号。我撇去了,因为它是等效的Ruby代码,但它有点烦人,也许一个精明的读者将提出基于非正则表达式的解决方案。
这
改写
函数被调用Ruby_Parser.
在顶级S表达式上,您可以从中递归探索所有程序的S表达式。要探索个体表达式的结构一点,请考虑输入:Incl(3,1)由Ruby对象表示,其结构是:
S(:致电,无,:Inc,S(:Lit,1),S(:Lit,2))按顺序,这些值是:
:称呼
是那种S表达(其他一些常见种类:堵塞
那:Lasgn.
和:Defn.
)用于调用函数,第二个价值,
零
,不包含任何有趣的东西:称呼
虽然它为其他种类做了,
:Inc.
是调用函数的名称,剩余值是传递给调用函数的参数。
提醒我们自己的原始问题声明:我们可以删除对呼叫的第二个参数吗?
:Inc.
如果它们指定与默认参数相同的值,则函数?是的,我们现在知道足以写这个功能:def重写(expr)如果expr.is_a?sexp如果expr [0] ==:call && expr [2] ==:incr && expr.size == 5 && expr [4] [0] ==:lit && expr [4] [1] == 1#删除第二个参数expro.pop()结束#下降到儿童expr.each {| x |重写(x)}结束EXPR结束这里有三个有趣的部分:
- 我们应该只重写S表达的对象!
- 如果我们打电话
Inc.
第二个参数是新的默认参数,a点亮
价值1
,然后我们应该删除它。- 递归地下降到每个S表达的内容中。否则你只能看到顶级
:堵塞
S表达非常无聊。踩回来,我觉得这很棒!我们现在正在编程重写代码。我们可以使用它来维持甚至在不做大量手动劳动的情况下保持大的CodeBases。
让我们再试一次,做一些更雄心勃勃的事情。想象一下,您聘请了一系列掌握了我们一直写作Python风格的团队的Python程序员
为了
循环而不是学习红宝石的每个
习语,我们希望重写它们使用每个
。您的输入可能是如下:
def count(lst)i = 0在lst i + = 1结束时而且你想要这个输出:
def count(lst)i = 0 lst.each {| ELE |我= i + 1}结束在我们的重写函数中拍摄另一个刺激,这是一个误会:
def重写(expr)如果expr.is_a?性别如果expr [0] ==:对于lst = expr [1] param = expr [2] func = expr [3] expr.clear expr [0] =:iter expr [1] = sexp.new(:call,LST,:每个)expr [2] = sexp.new(:args,param [1])expr [3] = func结束#下降进入儿童expr.each {| x |重写(x)}结束EXPR结束有点乱,但也是一旦你开始使用这种技术播放,你就可以做些什么漂亮的演示。例如,如果重构的复杂性,您可以想象只能这样做
为了
循环足够低。接下来是什么?
这些是非常自创作的例子,但我认为足以让你开始梦想这种技术可以用作你的工作应用,特别是如果你的工作涉及将大型码码迁移到新的实现。谷歌使用ClangMR的大规模自动重构是一个有趣的案例研究,在巨大的规模下做到这一点源代码重新焕发不重构是对这个主题的另一个探索。
最重要的是,我认为这是一个很好的提醒,以避免陷入“我只是通过它”的思维方式来实现大型迁徙,我认为可以成为贵公司整体吞吐量的极限。