一、前言
大语言模型(如 GPT-4)驱动的智能编码助手已经在软件开发领域显示出巨大的潜力和应用前景,未来的智能编码助手将不仅仅是开发者的辅助工具,更有可能成为开发流程中不可或缺的一部分。
很多公司以及云厂商都在布局智能编码助手领域,像我们公司自研了 ChatCode,商汤科技的小浣熊,阿里云的通义灵码等。这次我们来体验下商汤科技的代码小浣熊。
二、代码小浣熊
下面是商汤科技的小浣熊家族产品矩阵,目前支持代码小浣熊和办公小浣熊,老周体验的是代码小浣熊。
注册完以后进代码小浣熊,可以看到代码小浣熊提供了代码生成 &补全、代码翻译、代码重构、代码纠错、代码问答、测试用例生成六大核心能力。
2.1 代码生成 &补全
笔者这里以 pulsar broker 的策略接口为例定义了接口,让代码小浣熊为我生成另外一种策略。
可以看出代码小浣熊给我实现了一个基于资源使用和集群拓扑的负载均衡策略。
2.2 代码翻译
下面直接让代码小浣熊给我翻译下 LeastResourceUsageWithWeight 这个类,并分点陈述。
代码翻译如下:
代码定义了一个名为 LeastResourceUsageWithWeight 的类,该类实现了 BrokerSelectionStrategy 接口,用于根据资源的使用情况和权重选择一个合适的 broker。
类中定义了两个线程局部变量,分别用于存储最佳 broker 列表和无负载数据的 broker 集合。
实现了一个私有方法 getMaxResourceUsageWithWeight,该方法计算并返回指定 broker 的最大资源使用率,考虑了历史负载和短期负载数据。
实现了 select 方法,该方法用于从给定的候选 broker 集合中选择一个合适的 broker,并将该集合作为参数传递给该方法。
在 select 方法中,首先清除了最佳 broker 列表和无负载数据 broker 集合。
遍历候选 broker 集合,获取每个 broker 的负载数据,并计算其资源使用率。
计算所有候选 broker 的平均资源使用率,并与给定的阈值进行比较,以确定是否将所有 broker 都视为超载。
遍历候选 broker 集合,将未超载且资源使用率较低的 broker 添加到最佳 broker 列表中。
如果最佳 broker 列表为空,则将所有候选 broker 添加到最佳 broker 列表中。
随机选择最佳 broker 列表中的一个 broker,并将其名称作为结果返回。
最后,select 方法返回一个包含选定的 broker 名称的 Optional 对象。
可以的我的熊,以后直接用来梳理源码了,不用自己生硬调试理解作者的意思了,有了代码小浣熊,分析源码的效率直线上升。
2.3 代码纠错
我看这段代码有异步又有线程安全类,感觉有并发问题,所以让代码小浣熊帮我找下可能存在的问题。
public CompletableFuture<Optional<LookupResult>> findRedirectLookupResultAsync() {
String currentLMClassName = pulsar.getConfiguration().getLoadManagerClassName();
boolean debug = ExtensibleLoadManagerImpl.debug(pulsar.getConfiguration(), log);
return getAvailableBrokerLookupDataAsync().thenApply(lookupDataMap -> {
if (lookupDataMap.isEmpty()) {
String errorMsg = "No available broker found.";
log.warn(errorMsg);
throw new IllegalStateException(errorMsg);
}
AtomicReference<ServiceLookupData> latestServiceLookupData = new AtomicReference<>();
AtomicLong lastStartTimestamp = new AtomicLong(0L);
lookupDataMap.forEach((key, value) -> {
if (lastStartTimestamp.get() <= value.getStartTimestamp()) {
lastStartTimestamp.set(value.getStartTimestamp());
latestServiceLookupData.set(value);
}
});
if (latestServiceLookupData.get() == null) {
String errorMsg = "No latest service lookup data found.";
log.warn(errorMsg);
throw new IllegalStateException(errorMsg);
}
if (Objects.equals(latestServiceLookupData.get().getLoadManagerClassName(), currentLMClassName)) {
if (debug) {
log.info("No need to redirect, current load manager class name: {}",
currentLMClassName);
}
return Optional.empty();
}
var serviceLookupDataObj = latestServiceLookupData.get();
var candidateBrokers = new ArrayList<ServiceLookupData>();
lookupDataMap.forEach((key, value) -> {
if (Objects.equals(value.getLoadManagerClassName(), serviceLookupDataObj.getLoadManagerClassName())) {
candidateBrokers.add(value);
}
});
var selectedBroker = candidateBrokers.get((int) (Math.random() * candidateBrokers.size()));
return Optional.of(new LookupResult(selectedBroker.getWebServiceUrl(),
selectedBroker.getWebServiceUrlTls(),
selectedBroker.getPulsarServiceUrl(),
selectedBroker.getPulsarServiceUrlTls(),
true));
});
}
复制代码
代码小浣熊开始纠错了:
纠错如下:
线程安全问题: 使用 AtomicReference 和 AtomicLong 确保了单个线程的操作是原子的,但在并发环境中,多个线程可能会同时访问 lookupDataMap。为了确保线程安全,应该考虑在操作 lookupDataMap 时使用外部锁或同步块。
随机选择的问题: 当选择具有相同 loadManagerClassName 的 broker 时,代码随机选择一个 broker。这可能会导致负载不均衡,因为某些 broker 可能会被频繁选择,而其他 broker 则可能不会被选择。
异常处理: 如果 lookupDataMap 为空或不存在最新的 serviceLookupData,代码会抛出一个异常。这种情况下,应该考虑提供更详细的错误信息,或者考虑其他的错误恢复策略。
代码重复: Objects.equals(latestServiceLookupData.get().getLoadManagerClassName(), currentLMClassName)在代码中重复了两次。为了提高代码的可读性和可维护性,应该考虑将其提取到一个单独的方法中。
日志级别: 使用 log.warn 输出警告日志,但在某些情况下,这可能不足以表示问题的严重性。应该考虑在关键错误情况下使用 log.error。
配置类名的问题: 如果 pulsar.getConfiguration().getLoadManagerClassName()返回 null,则 Objects.equals 方法可能会抛出空指针异常。应该考虑对此进行适当的检查。
返回类型: 方法返回 CompletableFuture<Optional<LookupResult>>,但根据代码逻辑,当 lookupDataMap 为空或不存在最新的 serviceLookupData 时,方法将返回 Optional.empty()。这可能会导致调用者在处理结果时遇到问题,因为他们可能期望 LookupResult 的存在。
日志级别可能代码层面不需要那么重的异常出去;配置类名的问题,源码是放在后面这个参数,根据 Objects 源码来看不会有 NPE;返回类型 Optional<LookupResult>,为空的话返回 Optional.empty()没啥问题;
除了这三个我不太认同的话,其它几个我还是比较认同的。所以说目前阶段还是人与 AI Copilot 协同阶段,让 AI 来辅助你,以提高工作效率,而不是一味的用 AI 给出的建议。
2.4 代码重构
既然让代码小浣熊纠错代码了,那我们让它帮我们重构下吧,我们来看下重构后的代码:
public CompletableFuture<Optional<LookupResult>> findRedirectLookupResultAsync() {
String currentLMClassName = pulsar.getConfiguration().getLoadManagerClassName();
boolean debug = ExtensibleLoadManagerImpl.debug(pulsar.getConfiguration(), log);
return getAvailableBrokerLookupDataAsync().thenApply(lookupDataMap -> {
if (lookupDataMap.isEmpty()) {
String errorMsg = "No available broker found.";
log.warn(errorMsg);
throw new IllegalStateException(errorMsg);
}
return Optional.of(findLookupResult(lookupDataMap, currentLMClassName, debug));
});
}
private LookupResult findLookupResult(Map<String, ServiceLookupData> lookupDataMap, String currentLMClassName, boolean debug) {
AtomicReference<ServiceLookupData> latestServiceLookupData = new AtomicReference<>();
AtomicLong lastStartTimestamp = new AtomicLong(0L);
lookupDataMap.forEach((key, value) -> {
if (lastStartTimestamp.get() <= value.getStartTimestamp()) {
lastStartTimestamp.set(value.getStartTimestamp());
latestServiceLookupData.set(value);
}
});
if (latestServiceLookupData.get() == null) {
String errorMsg = "No latest service lookup data found.";
log.warn(errorMsg);
throw new IllegalStateException(errorMsg);
}
if (Objects.equals(latestServiceLookupData.get().getLoadManagerClassName(), currentLMClassName)) {
if (debug) {
log.info("No need to redirect, current load manager class name: {}", currentLMClassName);
}
return null; // Return null when no redirection is needed
}
var serviceLookupDataObj = latestServiceLookupData.get();
var candidateBrokers = new ArrayList<ServiceLookupData>();
lookupDataMap.forEach((key, value) -> {
if (Objects.equals(value.getLoadManagerClassName(), serviceLookupDataObj.getLoadManagerClassName())) {
candidateBrokers.add(value);
}
});
var selectedBroker = candidateBrokers.get((int) (Math.random() * candidateBrokers.size()));
return new LookupResult(selectedBroker.getWebServiceUrl(),
selectedBroker.getWebServiceUrlTls(),
selectedBroker.getPulsarServiceUrl(),
selectedBroker.getPulsarServiceUrlTls(),
true);
}
复制代码
小结:
额,我的熊啊,上面代码纠错你头头是道,现在让你自己重构下,你就不你会了?给我的感觉像是赵括的纸上谈兵啊,线程安全问题、随机选择问题这种复杂点的都没给我重构,熊给我的感觉就是抽取了下代码,让它分层了下,自己前面说的配置类名的问题,按照你是对的话,也是没处理,只是分了层级透传了下参数而已。
在有创造性的代码编写上,不仅仅代码小浣熊,其它的编码智能助手也是同样的差强人意一些,还有很大提升空间。
2.5 代码问答
找我的小熊问下 CompletableFuture 代码最佳实践,这个没啥太大问题,基于大模型应该有的能力。
2.6 测试用例生成
这个也挺有用的,程序员很不喜欢写单测,那脏活累活给我们的代码小浣熊去做吧~
三、总结
使用编码智能助手(不限于代码小浣熊)的感想是,目前阶段在编写不需要很强创造力的代码方面表现非常出色。重复性高、规则明确的代码生成,自动化测试,代码格式化和错误检查等任务都可以借助智能助手高效完成。然而,在需要深度理解和创造力的复杂项目中,智能助手的表现还无法完全取代人类程序员的能力。
未来几年,人与智能体的协同编码将成为主流。智能助手可以帮助程序员提高工作效率,减少重复劳动,让程序员能够将更多时间和精力投入到更具创造力和挑战性的工作中,如系统架构设计、创新功能开发和复杂问题解决等。
程序员们应积极拥抱这一趋势,与智能体协同办公。通过利用智能助手的优势,程序员可以提升自己的工作效率,掌握更多的创新技能,从而在不断变化的技术环境中保持竞争力。总之,智能助手是程序员的有力助手,而不是替代者。我们应与之合作,共同推动技术进步与创新。
评论