在 Visual Studio 2026 中使用 GitHub Copilot 进行遗留代码重写
2026/8/7 8:30:34 网站建设 项目流程

目录

背景:大型重写项目的一部分

核心问题:人工智能仍然需要领域专家

第一步:将 RandomNumbers 类引入解决方案

使用 Copilot 来识别实际使用情况

重写类,只保留必需的方法

真正的问题在于:GetUniqueRandomNumbers

Copilot关于异步和性能的建议

架构转变:从领域到基础设施

当你再也看不懂自己的代码时

当人工智能真的帮不上忙的时候

尝试不同的模型:Grok

代码库的当前状态

使用 Copilot 创建集成测试

最终要点

参考


如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

人工智能辅助开发工具通常被宣传为生产力提升工具,可以帮助我们理解、重构甚至重写复杂的代码库。但是,当代码老旧、编写糟糕且文档不完善时,它们的实际表现如何呢?

在本文中,我将通过一个实际示例,演示如何在 Visual Studio 2026 中使用 GitHub Copilot 重写遗留 C#/.NET 应用程序的一部分——具体来说,是位于系统核心的随机数生成器。

这并非一个精心设计的演示,而是对人工智能遇到混乱的遗留代码时会发生什么情况的实际展现。

背景:大型重写项目的一部分

这段视频是我即将推出的系列视频的一部分,在这个系列中,我将重新审视多年前我开发的一个应用程序。在之前的几集中,我解释了该系统的总体目标和重写策略。

本节重点在于重写旧版应用程序使用的 RandomNumbers 组件。

这里的主要目标不是为了风格而重构,而是为了理解意图、简化行为,并做出在原始代码中从未明确表达过的架构决策。

核心问题:人工智能仍然需要领域专家

这段重写代码提供了一个实际示例,展示了如何使用 Copilot 来处理编写不佳的代码。

这项练习最重要的结论很简单:

进行重大重写时,开发人员仍然需要对应用程序实际要实现的目标有深入的领域知识。

人工智能可以提供帮助,但它无法取代理解。

在常见的现实场景中,这一点尤其如此:

  • 原开发人员已离开公司。
  • 几乎没有相关文件。
  • 这段代码“能运行”,但仅仅是因为存在一些未记录在案的假设。
  • 有些快速技巧只有原作者才能理解。

当遗留代码既老旧又编写糟糕时,事情很快就会变得非常棘手。

第一步:将 RandomNumbers 类引入解决方案

这个RandomNumbers类是系统的核心组成部分,但在最初的设计中,它位于一个独立的外部库中。第一个实际步骤是将这个类直接引入到解决方案中,以便更轻松地进行审查、测试和重写。

查看原始实现代码后,立刻就能看出这段代码有问题:

  • 该课程力求具有多种用途
  • 它被设计成可以在假想的未来应用程序中重复使用。
  • 它包含许多从未真正使用过的方法。

修订版的目标是将课程精简到实际需要的程度。

namespace Lottron2000.Infra.Utilities;

public interface IRandomNumberGenerator
{
#region Multiple Items
List<int> GetRandomNumbers(int noOfRandomItems, int min, int max);
Task<List<int>> GetRandomNumbersAsync(int noOfRandomItems, int min, int max, CancellationToken ct = default);

List<int> GetUniqueRandomNumbers(int noOfRandomItems, int min, int max);
Task<List<int>> GetUniqueRandomNumbersAsync(int noOfRandomItems, int min, int max, CancellationToken ct = default);
#endregion

#region Single Item
int GetRandomNumber(int min, int max);
Task<int> GetRandomNumberAsync(int min, int max, CancellationToken ct = default);

int GetUniqueRandomNumber(int min, int max);
Task<int> GetUniqueRandomNumberAsync(int min, int max, CancellationToken ct = default);
#endregion
}

使用 Copilot 来识别实际使用情况

Copilot 可以帮助解答一个简单但重要的问题:

实际采用的方法有哪些?

在这种情况下,类中只有两个方法RandomNumbers被调用过。

即使是这样基本的洞察,Copilot 也需要几分钟才能计算出来——单个问题大约需要 3 到 5 分钟。在实际的改写过程中,你可能会遇到 20 到 30 个类似的问题,每个问题都需要时间进行分析。

这凸显了一个令人不安的事实:

有时候,除了手动理解糟糕的代码之外,没有其他捷径可走。

人工智能有所帮助,但它并不能消除认知成本。

重写类,只保留必需的方法

Copilot 的第一个具体任务是重写该类,只保留两个被调用的方法。

这种方法效果还不错。新类已在新解决方案中创建,快速检查后确认只保留了所需的方法。

然而,事情一旦办妥,更深层次的问题就开始浮出水面。

namespace Lottron2000.Infra.Utilities;


public class RandomNumbersGenerator : IRandomNumberGenerator
{

private static readonly ThreadLocal<Random> _threadLocalRandom = new ThreadLocal<Random>(() => new Random(unchecked(Environment.TickCount * 31 + Guid.NewGuid().GetHashCode())));

private Random Random => _threadLocalRandom.Value;

public RandomNumbersGenerator()
{ }


public List<int> GetRandomNumbers(int noOfRandomItems, int min, int max)
{ }


public Task<List<int>> GetRandomNumbersAsync(int noOfRandomItems, int min, int max, CancellationToken ct = default)
{
return Task.Run(() => GetRandomNumbers(noOfRandomItems, min, max), ct);
}


public List<int> GetUniqueRandomNumbers(int noOfRandomItems, int min, int max)
{
return GenerateUniqueRandomNumbersInternal(noOfRandomItems, min, max, CancellationToken.None);
}


public Task<List<int>> GetUniqueRandomNumbersAsync(int noOfRandomItems, int min, int max, CancellationToken ct = default)
{
return Task.Run(() => GenerateUniqueRandomNumbersInternal(noOfRandomItems, min, max, ct), ct);
}

private List<int> GenerateUniqueRandomNumbersInternal(int noOfRandomItems, int min, int max, CancellationToken ct)
{ }

private void Shuffle<T>(IList<T> list, CancellationToken ct)
{ }

#region Single Item Methods


public int GetRandomNumber(int min, int max)
{ }


public Task<int> GetRandomNumberAsync(int min, int max, CancellationToken ct = default)
{
return Task.Run(() => GetRandomNumber(min, max), ct);
}


public int GetUniqueRandomNumber(int min, int max)
{
var result = GenerateUniqueRandomNumbersInternal(1, min, max, CancellationToken.None);
return result.First();
}


public Task<int> GetUniqueRandomNumberAsync(int min, int max, CancellationToken ct = default)
{
return Task.Run(() => GetUniqueRandomNumber(min, max), ct);
}

#endregion
}

真正的问题在于:GetUniqueRandomNumbers

剩余的方法之一GetUniqueRandomNumbers需要仔细审查。

该方法的目的是:

  • 生成随机数
  • 将先前生成的值存储在内存中
  • 确保不退回重复件

几个问题立刻显现出来:

  • 执行过程并非确定性的
  • 生成 1,000 个不同的数字很容易,但生成 1,000,000 个则要昂贵得多。
  • 性能和资源使用量会不可预测地变化
  • 由于集合共享,线程安全是一个值得关注的问题。
  • 该方法受 CPU 限制,而非 I/O 限制。

这引出了一个重要的设计问题:

这个方法应该采用异步方式吗?

Copilot关于异步和性能的建议

Copilot的指导其实相当可靠:

  • 随机数生成受 CPU 限制
  • 请勿将其async/await用于纯 CPU 工作——它只会增加开销。
  • 如果大型请求可能会阻塞调用线程,则提供卸载选项(例如,Task.Run)。
  • 支持取消和进度报告
  • 对于小规模工作负载,始终提供快速同步路径。

因此,行为发生了RandomNumbers显著变化:

  • 它现在公开了异步/卸载方法
  • 大型操作可能需要几秒钟。
  • CancellationToken安全需要A

此时,该类已不再符合纯领域对象的定义。

架构转变:从领域到基础设施

从概念上讲,这个类现在的行为更像是:

  • 数据库调用
  • 文件系统操作
  • 一项资源密集型服务

因此,架构设计决定RandomNumbers从领域项目转移到基础设施项目。

完成这项工作后,class就变成了:

  • 瘦身
  • 更易读
  • 更专注于单一职责

当你再也看不懂自己的代码时

接下来是 LotteryNumbersGenerator 类。

这门课看起来至关重要——但即使作为原作者,我也完全摸不着头脑。

我无法自信地回答:

  • 每种方法的作用是什么?
  • 哪些方法至关重要
  • 哪些是多余的或过时的?

经过近两个小时的调查,意图仍然不明。代码中包含太多职责,导致难以判断其正确性。

这里一个重要的区别就显得至关重要了:

代码重写不是重构。

重构保留了现有行为。重写则需要对代码意图有透彻的理解。

否则,你可能会以更简洁的形式延续过去的错误。

当人工智能真的帮不上忙的时候

我尝试使用 Copilot(ChatGPT 5 mini)来讲解课程。

正如预期的那样,它生成了大量文本——但未必能提供有用的信息。我仍然无法判断移除某些方法是否会破坏系统的其他部分。

在这种情况下,Copilot 确实帮不上忙——这也很正常。问题出在代码本身。

尝试不同的模型:Grok

出于好奇,我尝试了另一种模型:Grok(代码)。

我请它解释一个具体的概念:WinningNumberPermutation(中奖号码排列)。

这一次,解释清晰明了,切实可行。

简而言之,排列逻辑表示的是一组预先计算的、有界的有效彩票号码,而不是在彩票环境中没有意义的无界随机值。

这种解释实际上就变成了一种规范。有了这种理解,我就可以自信地重写这个类,并删除旧的冗余代码。

有趣的是,这一见解来自一个尚未被广泛讨论的模型——而且目前也不需要高级会员资格。

代码库的当前状态

现阶段,建筑外观与原貌已大相径庭:

  • 一个专注于随机数生成的基准 RandomNumbers 工具。
  • 彩票专用逻辑已迁移到更高级别的服务中
  • 数据访问被隔离在存储库之后
  • 项目间职责划分清晰明确

最初的实现方式将所有内容都塞进了一个类和一个项目中。看起来简洁明了,但实际上代码混乱不堪。

新版本虽然更冗长,但却更容易理解。

使用 Copilot 创建集成测试

接下来,我要求 Copilot 为更新后的生成器创建集成测试RandomNumbers

关键限制因素:

  • 仅进行集成测试(不使用模拟测试)
  • 新项目02_Integration
  • 每个公共方法对应一个测试类
  • 命名规则:_Should

Copilot 总体上取得了成功,但也暴露出了一些局限性:

  • 它使用旧版 .NET Framework 创建了测试项目。
  • 它没有向实用程序类添加项目引用
  • 我忘记指定要测试的确切类名了。

测试本身通过了,而且很简单——但这强化了一个重要的教训。

最终要点

1. 人工智能不能取代理解,尤其是在处理遗留代码时。

2. 不同的模型提供非常不同的价值。高级摘要不足以进行架构重写。

3. 及时准确地说明很重要。对框架版本、引用或约定的假设将会失败。

4. 重写需要意图,而不是清理。将重构后的冗余代码带过来就违背了重写的目的。

人工智能是一个强大的助手——但正确性、架构和意图的责任仍然在于开发者。

如果你正在进行遗留代码的重写工作,或者尝试使用人工智能辅助开发,你的体验很可能非常相似。这些工具固然令人印象深刻,但它们并不能免除我们的思考。

参考

【第一集——GitHub Copilot AI 代码审查】( https://youtu.be/P26t5EVz70U )

【第二集——创建.NET项目和解决方案结构】( https://youtu.be/Vf0yULOHY3I )

【第三集——旧代码重写:随机数生成器】( https://youtu.be/6DuaW9VjQa8 )

如不能打开上面视频参考网址,可下载观看:https://download.csdn.net/download/hefeng_aspnet/93232242

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询