本期「MarsCoders 开发者」主角: 张博,浙江大学2024级软件学院硕士,本科期间曾先后在华为、微软、字节跳动飞书团队实习, 是一名拥有多段"大厂"经历的研发同学。如今攻读研究生的他选择 "二进"字节,目前实习于字节跳动 Seed 团队。
2024年9月24日,字节跳动开源走进浙江大学软件学院开展线下活动。张博作为"浙大软院"和"字节实习生"双重身份,走进自己的学院,为老师同学们推广开源社区。当被问到以另一种身份在母校为大家做分享是何心情,他笑着开玩笑说:"老师能不能给我加点分啊!"
张博坦言称,虽然选择去实习的初衷只是完成本科的毕业要求,然而当自己真正沉浸在工作状态中时,才发现能学到很多东西,也能认识到很多专业能力强又有趣的人,便继续了这条"半工半读"的道路。
双线并行的生活,两种状态的自己
"压力"和"松弛" 是张博的人生关键词。
张博认为有压力才会有进步,于是坚持一边读书一边实习;而他又把工作、学习和生活分得很开,该放松的时候就放松,不碰任何工作。为了健身,张博坚持了近两年的私教课,本科期间,他还爱上了骑行,工作之余加入社团,一天绕湖骑几十公里"放松"自己,也经常和女朋友一起打卡美食,四处游玩。
张博所在的字节 Seed 团队为公司大模型研发提供平台,他主要负责数据的离线推理、代码的维护和编写工作。工作中,张博每次写完代码都需要 mock 数据测试,用豆包MarsCode 生成单测带给了张博极大的便利。
"现在大部分人写代码就基本上都离不开 AI 了,能大幅提高程序员的开发效率,未来肯定是离不开 AI 助手的。" 在张博看来,大模型已经渗透进各行各业,程序员可以用它协助解读或生成代码,老师可以用它生成一些测试题目,学生也可以通过大模型快速检索知识。作为一名开发者,张博发现豆包MarsCode 不但能帮忙解读业务代码、生成单测,还可以针对需求提供不同的技术方案,甚至可以针对特定的业务场景通过 MySQL 生成模板进行复用,给研发领域的学习和工作提供了丰富的帮助。
接下来让我们跟随张博以学生视角测试豆包MarsCode 的过程,看看他的感受。
使用案例演示
序章
这次测评主要以一个学生/初学者的视角,测试豆包MarsCode 的代码生成能力、代码解读能力以及单测生成能力。
生成代码
生成的代码如下:
JSON
package main
import "fmt"
// LCS 函数计算两个字符串的最长公共子序列长度
func LCS(str1, str2 string) int {
m := len(str1)
n := len(str2)
// 创建一个二维数组来存储中间结果
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
// 填充二维数组
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if str1[i-1] == str2[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
}
}
}
// 返回最长公共子序列的长度
return dp[m][n]
}
// max 函数返回两个整数中的最大值
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
str1 := "AGGTAB"
str2 := "GXTXAYB"
lcsLength := LCS(str1, str2)
fmt.Printf("The length of the longest common subsequence between '%s' and '%s' is: %d\n", str1, str2, lcsLength)
}
LCS是业务场景中用的相对比较多的代码,可以看到,MarsCode生成的代码还是比较靠谱的,生成的代码算法逻辑是利用动态规划来更新str1前m个字符和str2前n个字符最长的相似子串长度,运行示例也能成功运行。
目前用来生成简单的业务代码效果比较好,但是稍微复杂点的代码比如LRU,效果并不理想
所以总的来说,用MarsCode来生成一些简单的代码是比较好的,但是对于复杂点的代码,虽然MarsCode生成的不准确,但是可以尝试让它生成一个Template然后去修改复杂的业务逻辑,所以MarsCode还是一个很好的提效工具的。
代码解读
既然生成完代码了,我就顺便让豆包MarsCode 帮我解读下它的 LCS 代码。
生成的解释如下:
JSON
// LCS 函数计算两个字符串的最长公共子序列长度
func LCS(str1, str2 string) int {
// 获取字符串 str1 和 str2 的长度
m := len(str1)
n := len(str2)
// 创建一个二维数组来存储中间结果
dp := make([][]int, m+1)
// 初始化二维数组
for i := range dp {
dp[i] = make([]int, n+1)
}
// 填充二维数组
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
// 如果 str1 的第 i 个字符等于 str2 的第 j 个字符
if str1[i-1] == str2[j-1] {
// 则 dp[i][j] 的值为左上角的值加一
dp[i][j] = dp[i-1][j-1] + 1
} else {
// 否则,dp[i][j] 的值为上方和左方的值中的最大值
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
}
}
}
// 返回最长公共子序列的长度
return dp[m][n]
}
可以看到,效果是不错的!豆包MarsCode 给自己的每行关键代码都生成了比较详细的注释。就算是不常写代码的人看到,也能很快速地理解代码的意思,并且解释得很合理。
生成单测
GO
package tcc
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLCS(t *testing.T) {
// 测试用例 1:两个字符串完全相同
str1 := "abc"
str2 := "abc"
expected := 3
actual := LCS(str1, str2)
assert.Equal(t, expected, actual, "Test case 1 failed")
// 测试用例 2:两个字符串没有相同字符
str1 = "abc"
str2 = "def"
expected = 0
actual = LCS(str1, str2)
assert.Equal(t, expected, actual, "Test case 2 failed")
// 测试用例 3:两个字符串有部分相同字符
str1 = "abcd"
str2 = "bcde"
expected = 2
actual = LCS(str1, str2)
assert.Equal(t, expected, actual, "Test case 3 failed")
}
可以看到,生成的单测文件相当完善,符合 go 测试的写法,并且生成了多组测试用例,并且写好了预期的结果等内容,效果相当不错,后续用豆包MarsCode 来生成需要 mock 的单测,效果也非常好,满分。
协助阅读开源项目
众所周知,阅读别人的代码是很艰难的事情,那么用MarsCode来分析代码效果会怎么样呢,下面以Kubernetes源码中的初始化NodePort的代码段为例子,看看生成的效果如何。
Kubernetes 源码:
GO
package tcc
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLCS(t *testing.T) {
// 测试用例 1:两个字符串完全相同
str1 := "abc"
str2 := "abc"
expected := 3
actual := LCS(str1, str2)
assert.Equal(t, expected, actual, "Test case 1 failed")
// 测试用例 2:两个字符串没有相同字符
str1 = "abc"
str2 = "def"
expected = 0
actual = LCS(str1, str2)
assert.Equal(t, expected, actual, "Test case 2 failed")
// 测试用例 3:两个字符串有部分相同字符
str1 = "abcd"
str2 = "bcde"
expected = 2
actual = LCS(str1, str2)
assert.Equal(t, expected, actual, "Test case 3 failed")
}
豆包MarsCode 给出的解释如下:
go
当前代码是一个名为 initNodePorts 的函数,它的目的是为给定的 Kubernetes Service 对象初始化 NodePort。NodePort 是 Kubernetes 中一种将服务暴露给集群外部的方式,它通过在每个节点上打开一个特定的端口来实现。
函数的主要逻辑如下:
1.首先,创建一个名为 svcPortToNodePort 的映射,用于存储服务端口和已分配的 NodePort 之间的对应关系。
2.然后,遍历服务的每个端口。对于每个端口,如果它的 NodePort 已经被设置为 0,并且不需要为该服务分配新的 NodePort(通过 shouldAllocateNodePorts 函数判断),则跳过该端口的处理。
3.如果需要为该端口分配 NodePort,首先检查 svcPortToNodePort 映射中是否已经存在该服务端口对应的 NodePort。如果不存在,则尝试在服务端口列表中查找用户指定的 NodePort(通过 findRequestedNodePort 函数)。
4.如果找到了用户指定的 NodePort,则尝试通过 nodePortOp.Allocate 方法分配该端口。如果分配成功,将该端口设置到服务端口的 NodePort 属性上,并更新svcPortToNodePort 映射。
5.如果没有找到用户指定的 NodePort,或者用户没有指定 NodePort,则通过 nodePortOp.AllocateNext 方法分配一个新的 NodePort,并进行同样的设置和更新操作。
6.在分配 NodePort 的过程中,如果出现错误(例如,端口已经被占用),则根据错误类型返回相应的错误信息。
7.最后,函数返回 nil,表示初始化 NodePort 的过程成功完成。
总结来说,initNodePorts 函数的主要作用是为 Kubernetes Service 对象的端口分配或更新 NodePort,并处理可能出现的错误情况。
可以看到,豆包MarsCode 对大部分的代码逻辑解释准确,上面的代码就是为 SVC 对象初始化NodePots,豆包MarsCode 解释了大部分的初始化处理逻辑,能描述出来对应函数的作用以及处理逻辑。
生成基础语法
对于不熟悉的编程语言,上手可能比较缓慢,说不定能用豆包MarsCode 来生成一些数据结构的基础用法来提升学习效率,下面用 cpp 的 STL 来看看效果。
生成的结果如下:
GO
//vector
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
for (int i = 0; i < vec.size(); i++) {
std::cout << vec[i] << " ";
}
return 0;
}
// list
#include <list>
#include <iostream>
int main() {
std::list<int> lst;
lst.push_back(1);
lst.push_back(2);
lst.push_back(3);
for (auto it = lst.begin(); it!= lst.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
//queue
#include <queue>
#include <iostream>
int main() {
std::queue<int> que;
que.push(1);
que.push(2);
que.push(3);
while (!que.empty()) {
std::cout << que.front() << " ";
que.pop();
}
return 0;
}
//stack
#include <stack>
#include <iostream>
int main() {
std::stack<int> stk;
stk.push(1);
stk.push(2);
stk.push(3);
while (!stk.empty()) {
std::cout << stk.top() << " ";
stk.pop();
}
return 0;
}
//set
#include <set>
#include <iostream>
int main() {
std::set<int> st;
st.insert(1);
st.insert(2);
st.insert(3);
st.insert(2); // 重复元素不会被插入
for (auto it = st.begin(); it!= st.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
//map
#include <map>
#include <iostream>
int main() {
std::map<int, std::string> mp;
mp[1] = "one";
mp[2] = "two";
mp[3] = "three";
mp[2] = "TWO"; // 重复键会更新对应的值
for (auto it = mp.begin(); it!= mp.end(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
return 0;
}
未来,与 AI 的联结
学习、工作、健身、恋爱...
张博在繁忙的日子里逐渐找到自己的节奏,确定追寻的目标。作为一名准程序员,又或是早已成为程序员,豆包MarsCode AI 编码助手已愈发成为他工作中不可或缺的一部分。
"开源 + AI" "编程 + AI 编程助手",又会带来什么样的人生变化呢?让我们一起期待吧!也欢迎你在评论区分享你的项目开发 idea!