目录
背景:大型重写项目的一部分
核心问题:人工智能仍然需要领域专家
第一步:将 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
如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。