QCon SF 2024:重构大型、顽固的代码库。

Stripe 的 Sorbet 技术主管Jake Zimmerman和Stripe 的 Ruby 基础设施工程师Getty Ritter在 2024 年QCon 旧金山会议上介绍了重构顽固的遗留代码库

Zimmerman 在演讲开始时列举了根据 2017 年公司调查得出的一些关于顽固代码库的常见抱怨。为了使重构达到满意的状态,Zimmerman 认为集中重构的最佳方式是让一个团队以专注于专业知识、激励自动化和更高的完成概率的方式推动重构。

集中式迁移需要两件事:利用代码库,使解决问题的小力量可以对系统行为产生重大影响;以及一种“棘轮”渐进式进展的方法。使用术语“棘轮”意味着一种只能沿一个方向转动的机械齿轮。考虑到这一点,Zimmerman 引入了Sorbet,这是 Ruby 的类型检查器。他坚持认为:

要重构一个庞大而顽固的代码库,您需要有一定的杠杆作用并选择好的棘轮。

制作冰糕是解决所有这些投诉的关键,并引入了解决这些问题的杠杆点。

棘轮的使用是通过# typed位于每个 Ruby 文件顶部的注释来实现的。它的作用就像一个棘轮,因为很容易“向上一点”。此结构的有效使用是:

  • # typed: false用于语法和常量
  • # typed: true用于方法中的推理
  • # typed: strict用于每个需要签名的方法

true虽然这看起来很简单,但齐默尔曼警告说,和 的使用顺序false可能会产生意想不到的后果。他用了下面的例子:

<span style="background-color:#f5f2f0"><span style="color:#000000"><code class="language-java">
# typed<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">:</span></span> <span style="color:#990055">true</span>
#         <span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">^</span></span> start with <span style="color:#990055">false</span>

<span style="color:#0077aa">class</span> KnownParent
    def <span style="color:#dd4a68">method_on_parent</span><span style="color:#999999">(</span>x<span style="color:#999999">)</span><span style="color:#999999">;</span> end
end

<span style="color:#0077aa">class</span> MyClass <span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59"><</span></span> UnknownParent
    def example
        self<span style="color:#999999">.</span><span style="color:#dd4a68">method_on_parent</span><span style="color:#999999">(</span><span style="color:#999999">)</span>
    end
end
    </code></span></span>

最初使用 时# typed: true,常量和对方法的UnknownParent调用被突出显示。这可能会令人困惑,因为该方法是在类中定义的。method_on_parent()MyClassmethod_on_parent()KnownParent

切换到 后# typed: false,只有UnknownParent常量会突出显示,因为它不存在。现在可以通过将常量更改为 来轻松解决该问题KnownParent

现在,返回,仅突出显示对方法的# typed: true调用,以提醒开发人员该方法调用需要参数。method_on_parent()MyClass

结果,开发人员的满意度得到了提高,因为: 大量的大型、顽固的代码库被重构;他们使用冰糕有一定的杠杆作用;他们选择了好的棘轮。

Ritter 然后讨论了如何使 Ruby 单体更加模块化以及为什么模块化很重要。他使用包含个人身份信息的简单记录器应用程序作为示例。

<span style="background-color:#f5f2f0"><span style="color:#000000"><code class="language-java">
# a toy logger
<span style="color:#0077aa">class</span> Logger
    def <span style="color:#dd4a68">log</span><span style="color:#999999">(</span>message<span style="color:#999999">,</span> <span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">*</span></span><span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">*</span></span>storytime<span style="color:#999999">)</span>
        payload <span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">=</span></span> storytime<span style="color:#999999">.</span>map <span style="color:#0077aa">do</span> <span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">|</span></span>k<span style="color:#999999">,</span> v<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">|</span></span>
            <span style="color:#669900">"#{k}=#{v.inspect}"</span>
        end<span style="color:#999999">.</span><span style="color:#dd4a68">join</span><span style="color:#999999">(</span><span style="color:#669900">" "</span><span style="color:#999999">)</span>

        @output<span style="color:#999999">.</span><span style="color:#dd4a68">puts</span><span style="color:#999999">(</span><span style="color:#669900">"#{Time.now.to_i}: #{message} #{payload}"</span><span style="color:#999999">)</span>
    end
end

# elsewhere
logger<span style="color:#999999">.</span><span style="color:#dd4a68">log</span><span style="color:#999999">(</span><span style="color:#669900">"Attempting operation"</span><span style="color:#999999">,</span> op<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">:</span></span> my_op<span style="color:#999999">,</span> merchant<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">:</span></span> m<span style="color:#999999">)</span>
# <span style="color:#990055">1730756308</span><span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">:</span></span> Attempting operation op<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">=</span></span><span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">:</span></span>update merchant<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">=</span></span>#<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59"><</span></span>Merchant id<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">=</span></span><span style="color:#990055">22</span> secret<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">=</span></span><span style="color:#669900">"hunter2"</span><span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">></span></span>
    </code></span></span>

然而,尽管编写的代码是善意的,但该解决方案可能会创建混乱的代码。

<span style="background-color:#f5f2f0"><span style="color:#000000"><code class="language-java">
# <span style="color:#999999">.</span><span style="color:#999999">.</span><span style="color:#999999">.</span>
payload <span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">=</span></span> storytime<span style="color:#999999">.</span>map <span style="color:#0077aa">do</span> <span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">|</span></span>k<span style="color:#999999">,</span> v<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">|</span></span>
    <span style="color:#0077aa">if</span> v<span style="color:#999999">.</span>is_a<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">?</span></span><span style="color:#999999">(</span>Merchant<span style="color:#999999">)</span> # <span style="color:#0077aa">if</span> we're logging a merchant<span style="color:#999999">.</span><span style="color:#999999">.</span><span style="color:#999999">.</span>
        <span style="color:#669900">"#{k}=Merchant(id=#{v.id}, ...)"</span> # redact most fields
    <span style="color:#0077aa">else</span>
        <span style="color:#669900">"#{k}=#{v.inspect}"</span> # other objects can be logged as<span style="background-color:rgba(255, 255, 255, 0.5)"><span style="color:#a67f59">-</span></span>is
    end
end<span style="color:#999999">.</span><span style="color:#dd4a68">join</span><span style="color:#999999">(</span><span style="color:#669900">" "</span><span style="color:#999999">)</span>
# <span style="color:#999999">.</span><span style="color:#999999">.</span><span style="color:#999999">.</span>
    </code></span></span>

Ritter 讨论了两个杠杆点:打包,这是 Sorbet 所固有的,但不足以解决模块化问题:以及分层,“基本原则是层中的任何元素仅依赖于同一层中的其他元素或元素”正如埃里克·埃文斯所定义的,向上的通信必须通过某种间接机制

Ritter 提供了许多分层的代码示例,使用 Zimmerman 之前描述的棘轮,以及优秀棘轮的属性。

这一切怎么会崩溃呢?沃尔特·J·萨维奇 (Walter J. Savitch)转述了在计算机科学会议上无意中听到的一句话,他说:

从理论上讲,理论和实践没有区别。实践中是有的。

里特表示,工具一开始并不总是完美的,并建议不要急于启动项目。一个团队可以重构一个更大、更顽固的代码库。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值