您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 2浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

通过自动代码重写减少技术债务

寒冰屋 发布时间:2022-08-11 22:19:30 ,浏览量:2

目录

技术债务的起源

编译器理论基础

重写和.NET编译器平台简介(Roslyn)

实用的C#代码重写

下一步

技术债务的起源

在深入研究技术细节之前,让我们回顾一下什么是技术债务、它的来源以及它如何影响开发过程。为简单起见,我们将研究两个软件项目开发场景,每个场景都以不同的方式导致技术债务。

在第一个场景中,一群开发人员只为这个项目而聚集,按时完成项目,要求越来越高,最后期限也很紧迫。当项目接近尾声时,开发人员别无选择,只能偷工减料以求生存,技术债务可能会出现并增加。

在紧迫的条件下,可能无法在代码质量上花费足够的时间。开发人员可以通过更频繁地手动或使用代码生成器复制和粘贴代码片段来更快地完成功能。

该项目上线了,但多个代码重复掩盖了程序逻辑并隐藏了依赖关系,从而阻止了开发人员有效地进行更改。最终,开发人员在添加新功能后会花费越来越多的时间进行调试。在经历了一系列成功的项目阶段后,管理人员对开发团队无法按时实施后续里程碑感到惊讶。

每个人都指责,但真正的问题是代码质量已经低于有效项目可维护性的关键标志。补偿低代码质量的成本越来越高,类似于不断增长的“技术债务”的利息支付。

一个由专门开发人员组成的小型团队致力于第二个项目。它运行多年,有时甚至数十年。在相对轻松的环境中工作,开发人员需要时间来正确设计代码以获得最佳结构和可维护性。系统设计达到或超过了多年前的标准,调试通常没有问题,因为开发人员对代码了如指掌。

但是,从技术上讲,该项目及时冻结了。它在整个生命周期内接受一些小的技术升级,但主要升级通常需要完全重写。技术债务从不同的角度发展:补偿旧技术的成本不断增加,例如寻找和培训熟练的员工、获得供应商支持,以及最终由于技术限制而无法实施新要求。

这两种情况都显示出不断增加的“技术”费用,这些费用不会直接增加任何价值,只是偿还不断增长的债务。这种虚拟债务被称为“技术债务”。

在第一种情况下实现更好的员工保留、外包或让开发人员有更多时间思考,并在第二种情况下经常向团队添加新的技术人员,会有帮助吗?管理专业人士辩论和分析这些举措的利弊。

是否有适用于这两个项目的“神奇药丸”?每个场景都是独一无二的,没有一个解决方案适用于所有人。你需要问一个问题——尽管存在技术问题,但是否有实质性价值?如果你启动一个替代项目,花在开发和会议上的时间会特别多吗?部分逻辑没有包含在文档中吗?许多人的生产力是否严重依赖于有问题的代码?

如果答案是肯定的,那么在尝试其他选项之前,值得考虑代码转换。如果仔细计划,代码转换有可能减少技术债务。更好地称为“重构”或“重写”,您可以手动执行少量代码。对于大量生产线,人工改造的成本变得可比或超过更换项目的成本。

有大型项目的解决方案吗?自动代码转换虽然已经存在了几十年,但并不广为人知。大多数计算机语言研究人员都熟悉,自动代码转换是位于计算机科学核心的真正宝石。发现这颗宝石是本文的重点。

编译器理论基础

编译和自动代码转换是同一进程的不同名称。计算机语言编译是程序员众所周知的过程,实际上是将一种计算机语言转换为另一种计算机语言。最初设计用于两种语言之间的直接转换,如汇编到机器代码,现代编译技术越来越多地使用第三种“中间”语言。众所周知的示例包括C#到Microsoft中间语言(MSIL)到机器代码和TypeScript到JavaScript到机器代码。

Compiler Theory提供构建块来设计任何计算机语言转换,包括将借助“复制粘贴”创建的代码转换为可重用代码的可维护逻辑。另一个例子是将“旧技术”代码重写为现代语言和平台,同时遵循当今架构所需的最佳编码实践。

听起来难以置信,但这些任务之前已经解决了。从编译器理论的角度来看,这些只不过是“编译器优化”,因为后者构成了以更有效、更优化的方式重写代码元素,同时保留它们的行为。

为了设计和实现一个专门的编译器来对负债项目执行这些“优化”,我们需要用编译器理论的基础知识武装自己。改造项目的任务可能变得异常复杂。

编译器本质上是一个解析器。虽然您可以通过多种方式开发解析器,但并非所有方式都能产生一种编译器解决方案,该解决方案在不断实现对新转换的支持的同时保持可维护性。换句话说,如果你从一开始就没有遵循某些规则,那么缺少地基就会限制建筑物的高度。

因此,我们将从Edsger Dijkstra在为Algol创建第一个编译器时奠定的科学基础开始。Algol是第一种不容易解析的语言。当Dijkstra创建Algol编译器时,它为不受解析器限制的计算机语言进化铺平了道路,这导致了Pascal、Basic、Fortran及其各自编译器基于相同原则的开发。今天,Dijkstra的方法已成为计算机语言编译器的标准,并且已经找到了计算机辅助人类语音识别的方法。它在任何类型的转换任务中都很有用。

简单的想法是好的,编译器理论也不例外。主要思想是将复杂的任务分解为更简单的任务,通过将编译分为两个阶段——解析和代码生成——每个阶段都包含多个步骤来实现。

解析阶段的第一步是将字符流转换为标记流。第二步将令牌流转换为对象树。可选的扩充或“数据挖掘”第三步收集编码统计信息和模式,以优化性能和提高目标代码的可维护性。本文以微型“专家系统”的形式展示了使用人工智能(AI)的事实发现。

在第二阶段,代码生成,优化和丰富的对象模型要么直接用于代码发射,要么转换为适合现有目标语言代码生成器的另一个对象树。

重写和.NET编译器平台简介(Roslyn)

代码重写或转换使用更好的技术用改进的语法元素替换某些语法元素。这可以从本文中说明的现场重写到完全重写,将旧技术源代码转换为现代技术源代码。在任何一种情况下,识别转换源和目标语法都很重要。换句话说,什么必须转化为什么。

对于技术重写,您必须仔细选择目标架构和平台,或者有时通过扩展其中一个现代平台来创建它们,以等效且紧凑地支持源平台中可用的功能。您通常还必须创建一个完整的源平台语法解析器和对象建模器。源对象覆盖范围可能需要扩展到源代码之外并涵盖额外的元数据,例如存储和用户界面。

对于现场重写,根据源语言,您可能不需要创建编译器。例如,在转换C#时,重写器可以构建在现有的.NET编译器平台上。

为此,请打开Visual Studio 2019安装程序并安装“Visual Studio扩展开发”配置文件,并选择可选功能“.NET编译器平台SDK”。

C#语法解析器将源代码的字符转换为标记和对象,并返回“语法树”。您可以在键入语法时在Visual Studio的窗口中显示对象化的源代码:

如果将光标放在代码中后“语法可视化器”窗口仍然空白,请修改源代码中的任何字符并撤消修改以触发刷新。树中的每个节点都代表语法中的一个物理或逻辑元素。

.NET编译器平台(昵称Roslyn)还提供了一个语法生成器。只需在树中的节点上调用“.ToString()”方法即可返回用C#拼写的那个节点。在任意字符串和语法节点之间转换以及反之亦然的能力简化了重写,因为新语法通常包含旧语法的元素。

实用的C#代码重写

首先,从GitHub克隆源代码存储库。

在这个重写示例中,原始代码包含半重复逻辑,通过复制和粘贴创建并就地修改以满足需求。程序员的意图是从数据库中检索一个列表,然后在代码中使用该列表之前,对该列表应用有限的一组和多种后处理操作。

性能指标表明操作消耗大量时间。但是,由于操作集在代码位置之间有所不同,因此缓存结果并不容易。第一个位置应用操作一和二,而第二个位置应用操作二和三。我们假设操作顺序无关紧要:

public static void ShowExample()
{
    var list = GetListFromSomewhere("HELLO");
    Operation1(list);
    Operation2(list);
    // process list
    // ...

    var list2 = GetListFromSomewhere("BONJOUR" + ObtainSuffix("PARIS")):
    Operation2(list);
    Operation3(list);
    // process list
    // ...
}

我们可以改进代码的几个方面。如果将各种需要的操作作为参数传递给从数据库中检索列表的例程,则可以通过缓存后处理结果来提高性能。通过从过程式编程转变为声明式编程来提高代码的可维护性。后者是通过用枚举处理选项替换过程调用的逻辑来实现的。

第一步是将表示原始源代码的字符串转换为对象树:

   public static void Main(string[] args)
    {
        var tree = CSharpSyntaxTree.ParseText(@"
public class Sample
[
    public void ShowExample()
    {
        {
            var list = GetListFromSomeWhere(""HELLO"");
            Operation1(list);
            Operation2(list);
            // process list
            // ...

接下来,为了提供优化,重写器可能需要访问前后节点中的信息,或从树中收集的其他信息。由于重写器按顺序提供节点,因此它通过从代码中收集意图事实来提前执行代码分析。简而言之,意图的事实是程序员真正想做的事情。它是代码背后的“更高真理”,用于用更好的等价物可靠地替换原始代码。

收集意图事实受益于统计方法和人工智能。在此示例中,我们使用了一个小型“专家系统”,这是一种早期的AI形式,由语言集成查询(LINQ)组成,然后扫描语法树中的周围节点:

// analyze tree and detect facts of intention
var orinalTreeRoot = tree.GetRoot();
var detectedFacts = CodeAnalyzer.GatherFacts(originalTreeRoot);

该类CodeAnalyzer通过使用LINQ搜索语法树来检测转换的候选节点。它专注于“GetListFromSomewhere”的调用并检查以下兄弟节点以确定应用了哪些操作。输出是事实列表,例如“节点XYZ检索到具有给定关键字的列表并应用了某某操作”。请注意,其GetListFromSomewhere中的“关键字”不一定是字符串,而是返回字符串的任何语法表达式。在不知道Roslyn对象模型的情况下,您通常可以通过打开“快速观察”并复制和粘贴表达式来获得以下GatherFacts语法:

class CodeAnalyzer
{
    public static List GatherFacts(SyntaxNode syntaxRoot)
    {
        var keyNodes = syntaxRoot.DescendantNodes().OfType()
            .Where(m => ((IdentifierNameSyntax)
                           ((InvocationExpressionSyntax)m.Declaration.Variables[0].Initializer.Value)
                           .Expression).Identifier.Value.ToString() == "GetListFromSomewhere").ToArray();
        var detectedFacts = new List();

一旦检测到事实,它们将用于填充节点替换字典。该字典包含原始树中标识为“事实”的一组旧节点的新重写节点。

// prepare node rewrites and removals
Dictionary nodeRewrites = new Dictionary();
foreach (var intention in detectedFacts)
{
    // replace key node with rewritten node
    nodeRewrites.Add(intention.KeyNode, intention.GetRewrittenNode());
    foreach (var nodeToRemove in intention.OtherNodes)
    {
        // remove other nodes linked to fact of intention
        nodeRewrites.Add(nodeToRemove, null);
    }
}

通过调用ToFullString()转换树的根节点上的方法来生成最终代码。Roslyn语法树是不可变的:它们不能直接修改。一种特殊的方法适应重写,它包括实现一个继承自 CSharpSyntaxRewriter的类(SimpleNodeRewriter)。语法树节点重写器访问树中的所有节点。在节点上时,它可以选择不修改节点或创建并返回替换节点:

var rewriter = new SimpleRewriter(nodeRewrites);
var transformedTreeRoot = rewriter.Visit(orinalTreeRoot);
Console.WriteLine(transformedTreeRoot.ToFullString());

控制台显示重写的源代码:

下一步

我们已经研究了技术债务的原因以及它为什么会成为一个问题,并研究了一些解决方案,包括基于编译器理论重写代码。使用这些方法,您可以更改自己的代码,帮助减少技术债务,以便您可以专注于未来的产品增强。

要了解有关减少技术债务的更多信息,请查看以下资源:

  • 维基百科上的调车场算法
  • 重构是技术债务的良药——但前提是你接受了它
  • 基于语素匹配的稀缺资源语言文本标记化
  • 编译器设计 - 词法分析 vue教程
  • 维基百科专家系统

本文最初以访客提交的形式出现在ContentLab上。

https://www.codeproject.com/Articles/5329268/Reducing-Technical-Debt-with-Automatic-Code-Rewrit

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.2370s