一、技术架构设计
1.1 整体架构
前端层(Vue3)
├── 代码编辑器
├── 提示面板
└── 交互控制
AI 能力层
├── 代码补全(Claude API)
├── 代码解释(Claude API)
└── 测试生成(Claude API)
代码分析层
├── 语法分析
├── 上下文提取
└── 依赖检测
1.2 核心功能模块
- 代码补全:智能预测和生成代码片段
- 代码解释:自然语言解释代码逻辑
- 单元测试生成:自动生成测试用例
二、代码补全实现
2.1 技术实现原理
核心思路
- 监听用户代码编辑行为
- 提取上下文(当前文件、光标位置、已有代码)
- 调用 AI 预测下一步代码
- 以浮层形式展示补全建议
- 用户可一键接受或继续编辑
关键技术点
- 上下文提取:前后 20 行代码 + 函数签名 + 导入语句
- 触发时机:特殊字符(. / ( { 等)或停顿 1 秒
- 补全策略:单行补全 / 多行补全 / 整个函数生成
2.2 完整代码实现
<template>
<div class="code-completion-container">
<h2>AI 代码补全助手</h2>
<div class="editor-section">
<div class="editor-header">
<span class="file-name">main.js</span>
<div class="editor-controls">
<button @click="clearCode" class="btn-clear">清空</button>
<button @click="runCode" class="btn-run">运行代码</button>
</div>
</div>
<div class="editor-wrapper">
<textarea
ref="codeEditor"
v-model="code"
@input="handleCodeChange"
@keydown="handleKeyDown"
@click="updateCursorPosition"
class="code-textarea"
spellcheck="false"
placeholder="开始输入代码,AI 会智能补全..."
></textarea>
<!-- 行号 -->
<div class="line-numbers">
<div
v-for="n in lineCount"
:key="n"
class="line-number"
>
{{ n }}
</div>
</div>
<!-- 补全建议浮层 -->
<div
v-if="showCompletion && completion"
class="completion-popup"
:style="completionPosition"
>
<div class="completion-header">
<span class="completion-icon">💡</span>
<span class="completion-label">AI 建议</span>
<span class="completion-shortcut">Tab 接受 / Esc 关闭</span>
</div>
<pre class="completion-code">{{ completion }}</pre>
<div class="completion-footer">
<button @click="acceptCompletion" class="btn-accept">接受</button>
<button @click="closeCompletion" class="btn-reject">忽略</button>
</div>
</div>
</div>
<!-- 状态栏 -->
<div class="editor-footer">
<span class="footer-item">行: {{ currentLine }}</span>
<span class="footer-item">列: {{ currentColumn }}</span>
<span class="footer-item">字符: {{ code.length }}</span>
<span v-if="isGenerating" class="footer-status">
AI 生成中...
</span>
</div>
</div>
<!-- AI 生成历史 -->
<div v-if="completionHistory.length > 0" class="history-section">
<h3>补全历史</h3>
<div
v-for="(item, index) in completionHistory"
:key="index"
class="history-item"
>
<div class="history-header">
<span class="history-time">{{ item.time }}</span>
<span class="history-status" :class="item.accepted ? 'accepted' : 'rejected'">
{{ item.accepted ? '已接受' : '已拒绝' }}
</span>
</div>
<div class="history-context">触发上下文: {{ item.context }}</div>
<pre class="history-code">{{ item.completion }}</pre>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, nextTick } from 'vue'
const codeEditor = ref(null)
const code = ref('')
const completion = ref('')
const showCompletion = ref(false)
const isGenerating = ref(false)
const currentLine = ref(1)
const currentColumn = ref(1)
const completionPosition = ref({ top: '0px', left: '0px' })
const completionHistory = ref([])
let completionTimer = null
// 计算行数
const lineCount = computed(() => {
return code.value.split('\n').length
})
// 处理代码变化
const handleCodeChange = () => {
updateCursorPosition()
// 防抖处理
clearTimeout(completionTimer)
completionTimer = setTimeout(() => {
triggerCompletion()
}, 1000) // 停顿 1 秒后触发补全
}
// 处理键盘事件
const handleKeyDown = (event) => {
// Tab 键接受补全
if (event.key === 'Tab' && showCompletion.value) {
event.preventDefault()
acceptCompletion()
return
}
// Esc 键关闭补全
if (event.key === 'Escape' && showCompletion.value) {
event.preventDefault()
closeCompletion()
return
}
// 特殊字符触发补全
const triggerChars = ['.', '(', '{', '[', ' ']
if (triggerChars.includes(event.key)) {
clearTimeout(completionTimer)
completionTimer = setTimeout(() => {
triggerCompletion()
}, 300)
}
}
// 更新光标位置
const updateCursorPosition = () => {
const textarea = codeEditor.value
if (!textarea) return
const cursorPos = textarea.selectionStart
const textBeforeCursor = code.value.substring(0, cursorPos)
const lines = textBeforeCursor.split('\n')
currentLine.value = lines.length
currentColumn.value = lines[lines.length - 1].length + 1
// 更新补全浮层位置
updateCompletionPosition()
}
// 更新补全浮层位置
const updateCompletionPosition = () => {
const textarea = codeEditor.value
if (!textarea) return
const rect = textarea.getBoundingClientRect()
const lineHeight = 20
const charWidth = 8.4
completionPosition.value = {
top: `${(currentLine.value - 1) * lineHeight + 40}px`,
left: `${currentColumn.value * charWidth + 50}px`
}
}
// 触发代码补全
const triggerCompletion = async () => {
if (isGenerating.value || !code.value.trim()) return
isGenerating.value = true
showCompletion.value = false
try {
// 提取上下文
const context = extractContext()
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'claude-sonnet-4-20250514',
max_tokens: 1000,
messages: [{
role: 'user',
content: `你是一个代码补全助手。根据用户当前的代码上下文,预测他接下来可能要写的代码。
当前代码:
\`\`\`javascript
${context.before}█${context.after}
\`\`\`
光标位置:█
请补全光标位置之后的代码。要求:
1. 只返回要补全的代码,不要重复已有代码
2. 代码要符合上下文逻辑
3. 保持代码风格一致
4. 如果是函数调用,补全参数;如果是对象访问,补全属性
5. 补全内容控制在 5-10 行
只返回代码,不要任何解释。不要用 \`\`\` 包裹。`
}]
})
})
const data = await response.json()
const suggestedCode = data.content[0].text.trim()
// 清理可能的代码块标记
completion.value = suggestedCode
.replace(/^```(?:javascript|js)?/gm, '')
.replace(/```$/gm, '')
.trim()
if (completion.value) {
showCompletion.value = true
updateCompletionPosition()
}
} catch (error) {
console.error('代码补全失败:', error)
} finally {
isGenerating.value = false
}
}
// 提取代码上下文
const extractContext = () => {
const textarea = codeEditor.value
const cursorPos = textarea.selectionStart
const before = code.value.substring(0, cursorPos)
const after = code.value.substring(cursorPos)
// 提取光标前后各 10 行
const beforeLines = before.split('\n').slice(-10).join('\n')
const afterLines = after.split('\n').slice(0, 10).join('\n')
return {
before: beforeLines,
after: afterLines,
cursor: cursorPos
}
}
// 接受补全
const acceptCompletion = () => {
const textarea = codeEditor.value
const cursorPos = textarea.selectionStart
const before = code.value.substring(0, cursorPos)
const after = code.value.substring(cursorPos)
code.value = before + completion.value + after
// 记录到历史
completionHistory.value.unshift({
time: new Date().toLocaleTimeString(),
context: before.split('\n').slice(-2).join('\n'),
completion: completion.value,
accepted: true
})
closeCompletion()
// 移动光标到补全内容之后
nextTick(() => {
const newCursorPos = cursorPos + completion.value.length
textarea.setSelectionRange(newCursorPos, newCursorPos)
textarea.focus()
})
}
// 关闭补全
const closeCompletion = () => {
if (completion.value && showCompletion.value) {
// 记录拒绝的补全
completionHistory.value.unshift({
time: new Date().toLocaleTimeString(),
context: extractContext().before.split('\n').slice(-2).join('\n'),
completion: completion.value,
accepted: false
})
}
showCompletion.value = false
completion.value = ''
}
// 清空代码
const clearCode = () => {
if (confirm('确定要清空代码吗?')) {
code.value = ''
closeCompletion()
}
}
// 运行代码
const runCode = () => {
try {
eval(code.value)
alert('代码执行成功!')
} catch (error) {
alert('代码执行错误:\n' + error.message)
}
}
// 初始化示例代码
code.value = `// 示例:定义一个计算器类
class Calculator {
constructor() {
this.result = 0
}
// 试试输入 add 然后停顿,AI 会帮你补全
`
</script>
<style scoped>
.code-completion-container {
max-width: 1200px;
margin: 0 auto;
padding: 30px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
h2, h3 {
color: #2c3e50;
margin-bottom: 20px;
}
.editor-section {
background: #1e1e1e;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
margin-bottom: 30px;
}
.editor-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
background: #2d2d30;
border-bottom: 1px solid #3e3e42;
}
.file-name {
color: #cccccc;
font-size: 14px;
font-weight: 500;
}
.editor-controls {
display: flex;
gap: 10px;
}
.btn-clear,
.btn-run {
padding: 6px 16px;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-clear {
background: #3e3e42;
color: #cccccc;
}
.btn-clear:hover {
background: #4e4e52;
}
.btn-run {
background: #0e639c;
color: white;
}
.btn-run:hover {
background: #1177bb;
}
.editor-wrapper {
position: relative;
display: flex;
}
.line-numbers {
padding: 16px 0;
background: #1e1e1e;
border-right: 1px solid #3e3e42;
user-select: none;
}
.line-number {
height: 20px;
line-height: 20px;
padding: 0 16px;
color: #858585;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
text-align: right;
}
.code-textarea {
flex: 1;
min-height: 400px;
padding: 16px;
background: #1e1e1e;
color: #d4d4d4;
border: none;
outline: none;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 14px;
line-height: 20px;
resize: none;
tab-size: 2;
}
.code-textarea::placeholder {
color: #6a9955;
}
.completion-popup {
position: absolute;
background: #252526;
border: 1px solid #3e3e42;
border-radius: 8px;
padding: 12px;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
z-index: 1000;
animation: fadeIn 0.2s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.completion-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #3e3e42;
}
.completion-icon {
font-size: 16px;
}
.completion-label {
color: #4ec9b0;
font-size: 13px;
font-weight: 600;
}
.completion-shortcut {
margin-left: auto;
color: #858585;
font-size: 11px;
}
.completion-code {
color: #d4d4d4;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
line-height: 18px;
margin: 0;
padding: 8px;
background: #1e1e1e;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
}
.completion-footer {
display: flex;
gap: 8px;
margin-top: 10px;
padding-top: 8px;
border-top: 1px solid #3e3e42;
}
.btn-accept,
.btn-reject {
flex: 1;
padding: 6px 12px;
border: none;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-accept {
background: #0e639c;
color: white;
}
.btn-accept:hover {
background: #1177bb;
}
.btn-reject {
background: #3e3e42;
color: #cccccc;
}
.btn-reject:hover {
background: #4e4e52;
}
.editor-footer {
display: flex;
gap: 20px;
padding: 8px 20px;
background: #007acc;
color: white;
font-size: 12px;
}
.footer-item {
color: white;
}
.footer-status {
margin-left: auto;
color: #ffffff;
font-weight: 500;
}
.history-section {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.history-item {
padding: 16px;
margin-bottom: 16px;
background: #f5f7fa;
border-radius: 8px;
border-left: 3px solid #409eff;
}
.history-item:last-child {
margin-bottom: 0;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.history-time {
color: #909399;
font-size: 12px;
}
.history-status {
padding: 3px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
}
.history-status.accepted {
background: #e1f3d8;
color: #67c23a;
}
.history-status.rejected {
background: #fde2e2;
color: #f56c6c;
}
.history-context {
color: #606266;
font-size: 13px;
margin-bottom: 8px;
font-style: italic;
}
.history-code {
color: #303133;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
line-height: 18px;
margin: 0;
padding: 10px;
background: white;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
}
</style>
三、代码解释实现
3.1 技术实现原理
核心思路
- 用户选中代码片段
- 点击"解释代码"按钮
- AI 分析代码逻辑
- 用自然语言解释代码功能、参数、返回值等
- 展示解释结果
解释维度
- 整体功能:这段代码做什么
- 逻辑流程:代码执行步骤
- 关键变量:重要变量的作用
- 潜在问题:可能的 bug 或优化点
3.2 完整代码实现
<template>
<div class="code-explanation-container">
<h2>AI 代码解释助手</h2>
<div class="explanation-layout">
<!-- 左侧:代码输入 -->
<div class="code-panel">
<div class="panel-header">
<span>输入代码</span>
<button @click="loadExample" class="btn-example">加载示例</button>
</div>
<textarea
v-model="codeToExplain"
placeholder="粘贴你想要理解的代码..."
class="code-input"
rows="20"
></textarea>
<button
@click="explainCode"
class="btn-explain"
:disabled="explaining || !codeToExplain.trim()"
>
{{ explaining ? '分析中...' : '解释这段代码' }}
</button>
</div>
<!-- 右侧:解释结果 -->
<div class="explanation-panel">
<div class="panel-header">
<span>AI 解释</span>
<button
v-if="explanation"
@click="copyExplanation"
class="btn-copy"
>
复制解释
</button>
</div>
<div v-if="!explanation && !explaining" class="empty-state">
<div class="empty-icon">📖</div>
<div class="empty-text">在左侧输入代码,点击"解释"按钮</div>
</div>
<div v-if="explaining" class="loading-state">
<div class="loader"></div>
<div class="loading-text">AI 正在分析代码...</div>
</div>
<div v-if="explanation" class="explanation-content">
<!-- 整体功能 -->
<div class="section">
<div class="section-title">整体功能</div>
<div class="section-content">{{ explanation.overview }}</div>
</div>
<!-- 详细解释 -->
<div class="section">
<div class="section-title">详细解释</div>
<div
v-for="(step, index) in explanation.steps"
:key="index"
class="step-item"
>
<div class="step-number">{{ index + 1 }}</div>
<div class="step-content">
<div class="step-code">{{ step.code }}</div>
<div class="step-desc">{{ step.description }}</div>
</div>
</div>
</div>
<!-- 关键概念 -->
<div v-if="explanation.concepts" class="section">
<div class="section-title">关键概念</div>
<div class="concepts-list">
<div
v-for="(concept, index) in explanation.concepts"
:key="index"
class="concept-item"
>
<div class="concept-name">{{ concept.name }}</div>
<div class="concept-desc">{{ concept.description }}</div>
</div>
</div>
</div>
<!-- 注意事项 -->
<div v-if="explanation.notes" class="section">
<div class="section-title">注意事项</div>
<div class="notes-list">
<div
v-for="(note, index) in explanation.notes"
:key="index"
class="note-item"
>
<span class="note-icon">⚠️</span>
<span class="note-text">{{ note }}</span>
</div>
</div>
</div>
<!-- 改进建议 -->
<div v-if="explanation.improvements" class="section">
<div class="section-title">改进建议</div>
<div class="improvements-list">
<div
v-for="(improvement, index) in explanation.improvements"
:key="index"
class="improvement-item"
>
<div class="improvement-title">{{ improvement.title }}</div>
<div class="improvement-desc">{{ improvement.description }}</div>
<pre v-if="improvement.code" class="improvement-code">{{ improvement.code }}</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const codeToExplain = ref('')
const explanation = ref(null)
const explaining = ref(false)
// 加载示例代码
const loadExample = () => {
codeToExplain.value = `function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
const handleSearch = debounce((query) => {
console.log('搜索:', query);
}, 500);`
}
// 解释代码
const explainCode = async () => {
explaining.value = true
explanation.value = null
try {
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'claude-sonnet-4-20250514',
max_tokens: 2500,
messages: [{
role: 'user',
content: `请详细解释这段代码:
\`\`\`javascript
${codeToExplain.value}
\`\`\`
请按以下结构返回 JSON:
{
"overview": "这段代码的整体功能(1-2句话)",
"steps": [
{
"code": "代码片段",
"description": "这个步骤做什么"
}
],
"concepts": [
{
"name": "概念名称",
"description": "概念解释"
}
],
"notes": [
"注意事项1",
"注意事项2"
],
"improvements": [
{
"title": "改进建议标题",
"description": "建议详情",
"code": "改进后的代码(可选)"
}
]
}
要求:
1. 解释要通俗易懂,适合初学者
2. 逐行或逐段解释代码逻辑
3. 指出关键概念和技术点
4. 如果有潜在问题或改进空间,要指出
只返回JSON,不要其他内容。`
}]
})
})
const data = await response.json()
const content = data.content[0].text
const jsonMatch = content.match(/\{[\s\S]*\}/)
if (jsonMatch) {
explanation.value = JSON.parse(jsonMatch[0])
}
} catch (error) {
console.error('代码解释失败:', error)
alert('解释失败,请重试')
} finally {
explaining.value = false
}
}
// 复制解释
const copyExplanation = () => {
let text = `整体功能:\n${explanation.value.overview}\n\n`
text += `详细解释:\n`
explanation.value.steps.forEach((step, i) => {
text += `${i + 1}. ${step.code}\n ${step.description}\n\n`
})
if (explanation.value.concepts) {
text += `关键概念:\n`
explanation.value.concepts.forEach(c => {
text += `- ${c.name}: ${c.description}\n`
})
text += '\n'
}
if (explanation.value.notes) {
text += `注意事项:\n`
explanation.value.notes.forEach(n => {
text += `- ${n}\n`
})
}
navigator.clipboard.writeText(text)
alert('已复制到剪贴板!')
}
</script>
<style scoped>
.code-explanation-container {
max-width: 1400px;
margin: 0 auto;
padding: 30px;
}
h2 {
text-align: center;
color: #2c3e50;
font-size: 32px;
margin-bottom: 40px;
}
.explanation-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.code-panel,
.explanation-panel {
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
overflow: hidden;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: #f5f7fa;
border-bottom: 1px solid #e4e7ed;
font-weight: 600;
color: #303133;
}
.btn-example,
.btn-copy {
padding: 6px 16px;
background: #409eff;
color: white;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-example:hover,
.btn-copy:hover {
background: #66b1ff;
}
.code-input {
width: 100%;
padding: 20px;
border: none;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 14px;
line-height: 1.6;
resize: vertical;
box-sizing: border-box;
}
.btn-explain {
width: 100%;
padding: 16px;
background: #67c23a;
color: white;
border: none;
border-top: 1px solid #e4e7ed;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-explain:hover:not(:disabled) {
background: #85ce61;
}
.btn-explain:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.empty-state,
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
padding: 40px;
text-align: center;
}
.empty-icon {
font-size: 64px;
margin-bottom: 20px;
}
.empty-text {
color: #909399;
font-size: 15px;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #409eff;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 20px;
color: #606266;
font-size: 15px;
}
.explanation-content {
padding: 24px;
max-height: 800px;
overflow-y: auto;
}
.section {
margin-bottom: 30px;
}
.section:last-child {
margin-bottom: 0;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #409eff;
}
.section-content {
color: #606266;
font-size: 15px;
line-height: 1.8;
}
.step-item {
display: flex;
gap: 12px;
margin-bottom: 20px;
padding: 16px;
background: #f5f7fa;
border-radius: 8px;
}
.step-number {
flex-shrink: 0;
width: 28px;
height: 28px;
line-height: 28px;
text-align: center;
background: #409eff;
color: white;
border-radius: 50%;
font-weight: 600;
font-size: 14px;
}
.step-content {
flex: 1;
}
.step-code {
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
color: #303133;
background: white;
padding: 8px 12px;
border-radius: 4px;
margin-bottom: 8px;
border-left: 3px solid #409eff;
}
.step-desc {
color: #606266;
font-size: 14px;
line-height: 1.6;
}
.concepts-list,
.notes-list,
.improvements-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.concept-item {
padding: 14px;
background: #ecf5ff;
border-left: 3px solid #409eff;
border-radius: 4px;
}
.concept-name {
font-weight: 600;
color: #409eff;
margin-bottom: 6px;
font-size: 14px;
}
.concept-desc {
color: #606266;
font-size: 14px;
line-height: 1.6;
}
.note-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 12px;
background: #fff7e6;
border-left: 3px solid #ff9800;
border-radius: 4px;
}
.note-icon {
font-size: 18px;
flex-shrink: 0;
}
.note-text {
color: #606266;
font-size: 14px;
line-height: 1.6;
}
.improvement-item {
padding: 16px;
background: #f0f9ff;
border-left: 3px solid #67c23a;
border-radius: 4px;
}
.improvement-title {
font-weight: 600;
color: #67c23a;
margin-bottom: 8px;
font-size: 15px;
}
.improvement-desc {
color: #606266;
font-size: 14px;
line-height: 1.6;
margin-bottom: 10px;
}
.improvement-code {
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
line-height: 1.6;
color: #303133;
background: white;
padding: 12px;
border-radius: 4px;
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
@media (max-width: 1024px) {
.explanation-layout {
grid-template-columns: 1fr;
}
}
</style>
四、单元测试生成实现
4.1 技术实现原理
核心思路
- 分析函数/类的输入输出
- 识别边界条件和异常情况
- 生成测试用例覆盖各种场景
- 使用主流测试框架(Jest/Vitest)
测试用例生成维度
- 正常情况:常规输入输出
- 边界条件:空值、极值、特殊字符
- 异常情况:错误输入、异常处理
- 性能测试:大数据量、并发
4.2 完整代码实现
<template>
<div class="test-generation-container">
<h2>AI 单元测试生成</h2>
<div class="generator-layout">
<!-- 左侧:代码输入 -->
<div class="code-section">
<div class="section-header">
<span>待测试代码</span>
<select v-model="testFramework" class="framework-select">
<option value="jest">Jest</option>
<option value="vitest">Vitest</option>
<option value="mocha">Mocha</option>
</select>
</div>
<textarea
v-model="sourceCode"
placeholder="粘贴你的函数或类代码..."
class="code-input"
rows="15"
></textarea>
<div class="options-panel">
<label class="option-item">
<input type="checkbox" v-model="options.includeEdgeCases" />
<span>包含边界条件测试</span>
</label>
<label class="option-item">
<input type="checkbox" v-model="options.includeErrorCases" />
<span>包含异常情况测试</span>
</label>
<label class="option-item">
<input type="checkbox" v-model="options.includeMocks" />
<span>生成 Mock 示例</span>
</label>
</div>
<button
@click="generateTests"
class="btn-generate"
:disabled="generating || !sourceCode.trim()"
>
{{ generating ? '生成中...' : '生成测试用例' }}
</button>
</div>
<!-- 右侧:生成的测试代码 -->
<div class="test-section">
<div class="section-header">
<span>生成的测试代码</span>
<button
v-if="generatedTests"
@click="copyTests"
class="btn-copy"
>
复制代码
</button>
</div>
<div v-if="!generatedTests && !generating" class="empty-state">
<div class="empty-icon">🧪</div>
<div class="empty-text">输入代码后点击"生成测试用例"</div>
</div>
<div v-if="generating" class="loading-state">
<div class="loader"></div>
<div class="loading-text">AI 正在分析代码并生成测试...</div>
</div>
<div v-if="generatedTests" class="test-result">
<!-- 测试统计 -->
<div class="test-stats">
<div class="stat-item">
<span class="stat-label">测试用例数:</span>
<span class="stat-value">{{ generatedTests.testCount }}</span>
</div>
<div class="stat-item">
<span class="stat-label">覆盖率预估:</span>
<span class="stat-value">{{ generatedTests.coverage }}%</span>
</div>
<div class="stat-item">
<span class="stat-label">框架:</span>
<span class="stat-value">{{ testFramework }}</span>
</div>
</div>
<!-- 测试代码 -->
<pre class="test-code">{{ generatedTests.code }}</pre>
<!-- 测试说明 -->
<div v-if="generatedTests.explanation" class="test-explanation">
<div class="explanation-title">测试说明</div>
<div class="explanation-content">{{ generatedTests.explanation }}</div>
</div>
<!-- 运行指南 -->
<div class="run-guide">
<div class="guide-title">如何运行测试</div>
<pre class="guide-commands">{{ generatedTests.runCommands }}</pre>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const sourceCode = ref(`function calculateTotal(items) {
return items.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
}`)
const testFramework = ref('jest')
const generatedTests = ref(null)
const generating = ref(false)
const options = reactive({
includeEdgeCases: true,
includeErrorCases: true,
includeMocks: false
})
// 生成测试用例
const generateTests = async () => {
generating.value = true
generatedTests.value = null
try {
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'claude-sonnet-4-20250514',
max_tokens: 3000,
messages: [{
role: 'user',
content: `请为以下代码生成单元测试:
\`\`\`javascript
${sourceCode.value}
\`\`\`
测试框架:${testFramework.value}
测试要求:
${options.includeEdgeCases ? '- 包含边界条件测试\n' : ''}${options.includeErrorCases ? '- 包含异常情况测试\n' : ''}${options.includeMocks ? '- 包含 Mock 示例\n' : ''}
请返回 JSON 格式:
{
"code": "完整的测试代码",
"testCount": 测试用例数量,
"coverage": 预估覆盖率(0-100),
"explanation": "测试用例说明(每个用例测试什么)",
"runCommands": "运行测试的命令(多行字符串)"
}
要求:
1. 测试用例要全面,覆盖正常、边界、异常情况
2. 测试描述要清晰明确
3. 包含必要的 setup 和 teardown
4. 遵循测试框架的最佳实践
只返回JSON,不要其他内容。`
}]
})
})
const data = await response.json()
const content = data.content[0].text
const jsonMatch = content.match(/\{[\s\S]*\}/)
if (jsonMatch) {
generatedTests.value = JSON.parse(jsonMatch[0])
}
} catch (error) {
console.error('测试生成失败:', error)
alert('生成失败,请重试')
} finally {
generating.value = false
}
}
// 复制测试代码
const copyTests = () => {
navigator.clipboard.writeText(generatedTests.value.code)
alert('测试代码已复制到剪贴板!')
}
</script>
<style scoped>
.test-generation-container {
max-width: 1400px;
margin: 0 auto;
padding: 30px;
}
h2 {
text-align: center;
color: #2c3e50;
font-size: 32px;
margin-bottom: 40px;
}
.generator-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.code-section,
.test-section {
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
overflow: hidden;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: #f5f7fa;
border-bottom: 1px solid #e4e7ed;
font-weight: 600;
color: #303133;
}
.framework-select {
padding: 6px 12px;
border: 1px solid #dcdfe6;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
}
.code-input {
width: 100%;
padding: 20px;
border: none;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 14px;
line-height: 1.6;
resize: vertical;
box-sizing: border-box;
}
.options-panel {
padding: 16px 20px;
background: #f5f7fa;
border-top: 1px solid #e4e7ed;
border-bottom: 1px solid #e4e7ed;
display: flex;
flex-direction: column;
gap: 10px;
}
.option-item {
display: flex;
align-items: center;
gap: 8px;
color: #606266;
font-size: 14px;
cursor: pointer;
}
.option-item input[type="checkbox"] {
cursor: pointer;
}
.btn-generate {
width: 100%;
padding: 16px;
background: #67c23a;
color: white;
border: none;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-generate:hover:not(:disabled) {
background: #85ce61;
}
.btn-generate:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-copy {
padding: 6px 16px;
background: #409eff;
color: white;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-copy:hover {
background: #66b1ff;
}
.empty-state,
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
padding: 40px;
text-align: center;
}
.empty-icon {
font-size: 64px;
margin-bottom: 20px;
}
.empty-text {
color: #909399;
font-size: 15px;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #67c23a;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 20px;
color: #606266;
font-size: 15px;
}
.test-result {
padding: 24px;
}
.test-stats {
display: flex;
gap: 30px;
padding: 16px;
background: #f0f9ff;
border-radius: 8px;
margin-bottom: 20px;
}
.stat-item {
display: flex;
align-items: center;
gap: 6px;
}
.stat-label {
color: #606266;
font-size: 14px;
}
.stat-value {
color: #409eff;
font-weight: 600;
font-size: 15px;
}
.test-code {
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
line-height: 1.6;
color: #303133;
background: #1e1e1e;
color: #d4d4d4;
padding: 20px;
border-radius: 8px;
margin: 0 0 20px 0;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 500px;
overflow-y: auto;
}
.test-explanation,
.run-guide {
padding: 16px;
border-radius: 8px;
margin-bottom: 16px;
}
.test-explanation {
background: #ecf5ff;
border-left: 3px solid #409eff;
}
.run-guide {
background: #e1f3d8;
border-left: 3px solid #67c23a;
}
.explanation-title,
.guide-title {
font-weight: 600;
color: #303133;
margin-bottom: 10px;
font-size: 15px;
}
.explanation-content {
color: #606266;
font-size: 14px;
line-height: 1.8;
white-space: pre-wrap;
}
.guide-commands {
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
line-height: 1.6;
color: #303133;
background: white;
padding: 12px;
border-radius: 4px;
margin: 0;
white-space: pre-wrap;
}
@media (max-width: 1024px) {
.generator-layout {
grid-template-columns: 1fr;
}
}
</style>
五、简历撰写指南
5.1 项目经验描述模板
项目名称: AI 代码助手系统
项目时间: 2024.08 - 2024.12
项目描述: 开发基于 AI 的代码辅助工具,集成代码补全、代码解释和单元测试生成功能。通过 Claude API 理解代码上下文,为开发者提供智能编码建议,显著提升开发效率和代码质量。
核心职责
- 实现智能代码补全功能,基于上下文预测下一步代码,开发效率提升 40%
- 开发代码解释功能,将复杂代码转为通俗易懂的自然语言,降低代码理解成本 60%
- 实现单元测试自动生成,覆盖正常、边界、异常多种情况,测试覆盖率提升至 85%
- 优化 AI 调用策略,通过上下文提取和智能触发机制,降低 API 成本 50%
技术栈: Vue3 Composition API、Claude API、代码解析、AST 分析、防抖节流
项目成果
- 团队 50+ 开发者使用,平均编码效率提升 35%
- 减少 Code Review 时间 40%,代码质量明显提升
- 新人上手时间从 2 周缩短至 3 天
- 获得团队年度最佳工具奖
5.2 SOP 标准回答话术
面试官:介绍一下你做的 AI 代码助手项目
回答话术: "好的。这个项目是我开发的一个 AI 驱动的代码辅助工具,主要解决开发过程中的三个痛点:写代码慢、看代码难、测试用例难写。
先说背景。我们团队经常接手一些复杂的老项目,新人看代码很吃力,而且手写测试用例很费时间。另外开发时有些重复性的代码片段,手写效率低还容易出错。
针对这些问题,我做了三个核心功能:
第一个是智能代码补全。用户输入代码时,系统会分析上下文,预测接下来要写什么。实现上,我会提取光标前后各 10 行代码,加上文件顶部的导入语句和函数签名,一起发给 AI。AI 理解了上下文后生成补全建议。我设置了两种触发方式:一是用户输入特殊字符像 . ( { 这些,二是停顿 1 秒。建议会以浮层形式展示,用户按 Tab 接受,Esc 拒绝。这个功能让写代码效率提升了 40%。
第二个是代码解释。开发经常遇到看不懂的代码,我做了一个工具,用户粘贴代码,AI 会用自然语言解释。不是简单的翻译,而是从整体功能、执行流程、关键概念、注意事项等多个维度解释。还会指出潜在问题和改进建议。这对新人特别有用,理解代码的时间减少了 60%。
第三个是单元测试生成。手写测试用例很费时间,我让 AI 自动分析函数逻辑,生成测试用例。会覆盖正常情况、边界条件、异常处理等场景。还支持选择测试框架,生成的代码可以直接运行。实测团队的测试覆盖率从 50% 提升到了 85%。
技术难点主要是两个:一是上下文提取,要找到最相关的代码给 AI,既不能太少导致理解不准,也不能太多浪费 token。我的方案是提取光标前后各 10 行加关键定义。二是如何优化 AI 调用,我用了防抖策略和智能触发,把成本降低了 50%。
最终效果很好,现在团队 50 多人在用,平均编码效率提升 35%,新人上手时间从 2 周缩短到 3 天。"
面试官:代码补全的上下文提取为什么选择前后各 10 行?
回答话术: "这是个很好的问题。前后 10 行其实是我经过实验得出的一个平衡值。
最开始我提取的是前后 20 行,发现两个问题:
- Token 消耗太大,一次补全要用 500-800 token
- 上下文太多反而干扰 AI,它可能关注不到真正相关的部分
后来改成前后 5 行,发现上下文不够,AI 理解不准。比如用户在写一个函数内部的逻辑,但函数签名在 5 行之外,AI 就不知道参数类型和返回值,补全效果很差。
最后定在前后 10 行,再额外加上:
- 文件顶部的 import 语句:知道有哪些库可用
- 当前函数的完整定义:理解函数上下文
- 相关的类型定义:如果是 TypeScript
这样控制在 300-400 token,既能让 AI 充分理解,又不会浪费太多成本。
另外,如果遇到特殊情况,比如用户在文件开头,我会多提取后面的内容;如果在文件结尾,会多提取前面的。保证 AI 始终有足够的上下文。
这个优化让补全准确率从 65% 提升到了 88%,同时 API 成本降低了 40%。"
面试官:如何保证生成的单元测试是高质量的?
回答话术: "保证测试质量我主要从三个方面入手:
第一是测试用例的全面性。我在 Prompt 里明确要求 AI 生成三类测试:
- 正常情况:常规输入输出
- 边界条件:空值、null、undefined、极值、空数组等
- 异常情况:错误参数、类型不匹配、异常抛出
比如测试一个数组求和函数,不只是测 [1,2,3],还会测空数组、包含负数、包含非数字等情况。
第二是测试描述的清晰性。每个测试用例的描述要明确说明"在什么情况下,应该得到什么结果"。这样后续维护时能快速理解测试意图。我会在 Prompt 里强调这一点。
第三是可运行性。生成的代码要能直接跑起来,不需要手动修改。我会:
- 指定测试框架和版本
- 包含必要的 import
- 如果需要 Mock,也生成相应代码
- 提供运行命令
另外,我还做了一个验证机制:生成测试后,会让 AI 自我审查,看有没有遗漏的场景。如果覆盖率预估低于 80%,会提示用户手动补充。
实测效果:生成的测试用例可用率达 92%,团队的测试覆盖率从 50% 提升到 85%,而且 Code Review 发现的 bug 减少了 40%。"
5.3 难点与亮点分析
难点1:上下文窗口的动态调整
问题描述: 不同场景需要的上下文量不同,固定提取可能不够准确或浪费 token。
解决方案
- 基础策略:光标前后各 10 行
- 智能扩展:
- 如果在函数内,自动包含函数签名
- 如果有 import,包含相关导入
- 如果是类方法,包含类定义
- 动态压缩:如果上下文超长,优先保留:
- 最近的代码(离光标近)
- 函数/类定义
- 导入语句
技术实现
const extractSmartContext = (code, cursorPos) => {
const lines = code.split('\n')
const currentLineNum = code.substring(0, cursorPos).split('\n').length
let context = {
before: lines.slice(Math.max(0, currentLineNum - 10), currentLineNum),
after: lines.slice(currentLineNum, currentLineNum + 10)
}
// 查找并包含函数定义
const funcDef = findEnclosingFunction(lines, currentLineNum)
if (funcDef) context.funcDef = funcDef
// 包含 import 语句
const imports = lines.filter(line => line.trim().startsWith('import'))
context.imports = imports
return context
}
难点2:补全建议的触发时机
问题描述: 过于频繁触发会打扰用户,太少又错过补全机会,需要找到平衡点。
解决方案
- 多种触发方式:
- 特殊字符:. ( { [ 等立即触发
- 停顿触发:用户停止输入 1 秒后触发
- 手动触发:用户按快捷键(Ctrl+Space)
- 智能过滤:
- 输入少于 2 个字符不触发
- 在字符串/注释内不触发
- 连续触发间隔至少 500ms
- 用户行为学习:
- 记录用户接受/拒绝补全的频率
- 动态调整触发策略
难点3:测试用例的覆盖度保证
问题描述: 如何确保生成的测试用例能覆盖各种场景,不遗漏关键情况。
解决方案
- 结构化 Prompt:明确列出必须包含的测试类型
- 代码分析:
- 识别函数的参数类型
- 提取返回值类型
- 检测异常处理逻辑
- 二次验证:
- AI 生成后自我审查
- 计算预估覆盖率
- 提示遗漏的场景
亮点1:渐进式补全策略
不是一次性生成大段代码,而是根据用户输入逐步补全:
- 输入函数名 → 补全函数签名
- 输入参数 → 补全函数体框架
- 输入具体逻辑 → 补全细节代码
这种渐进式方式让用户保持对代码的掌控感。
亮点2:代码解释的多维度分析
不只是简单翻译,而是从多个角度解释:
- 整体功能:这段代码做什么
- 执行流程:代码如何运行
- 关键概念:用到的技术点
- 注意事项:潜在的问题
- 改进建议:如何优化
亮点3:测试生成的框架适配
支持多种测试框架,生成的代码符合各框架的最佳实践:
- Jest:使用 describe/it/expect
- Vitest:支持 Vite 生态
- Mocha:配合 Chai 断言库
5.4 避免 AI 化的表达技巧
❌ AI 化表达: "利用先进的大语言模型和深度学习算法,构建了一个智能化的代码辅助系统,实现了代码补全、解释和测试生成的全面自动化。"
✅ 自然表达: "开发了一个 AI 代码助手,能根据你正在写的代码预测下一步,还能把复杂代码解释成通俗的话,自动生成测试用例。就像有个老司机在旁边指导你写代码。"
关键差异
- 用比喻而非技术术语
- 强调用户体验而非技术实现
- 用具体场景而非抽象功能
- 口语化而非书面语
5.5 完整简历示例
【AI 代码助手系统】2024.08 - 2024.12
项目背景:
团队开发效率有提升空间,新人看代码困难,手写测试用例费时,需要智能工具辅助开发。
我的工作:
1. 智能代码补全功能开发
- 实现基于上下文的代码预测,提取光标前后 10 行 + 函数定义 + 导入语句
- 设计双重触发机制:特殊字符立即触发 + 停顿 1 秒触发
- 通过防抖和智能缓存,将 API 调用成本降低 50%
- 编码效率提升 40%,补全准确率达 88%
2. 代码解释功能实现
- 开发多维度代码分析:整体功能、执行流程、关键概念、改进建议
- 实现自然语言转换,将技术代码转为通俗易懂的描述
- 代码理解时间减少 60%,新人上手周期从 2 周缩短至 3 天
3. 单元测试自动生成
- 实现测试用例智能生成,覆盖正常、边界、异常多种情况
- 支持 Jest/Vitest/Mocha 三种框架,生成代码可直接运行
- 测试覆盖率从 50% 提升至 85%,可用率达 92%
- Code Review 发现的 bug 减少 40%
4. 性能优化与用户体验
- 优化上下文提取算法,平衡准确性和成本,token 使用量降低 40%
- 实现补全建议的智能过滤,避免过度打扰用户
- 设计渐进式补全策略,保持用户对代码的掌控感
技术栈:Vue3、Claude API、AST 解析、防抖节流、代码分析
项目成果:
- 团队 50+ 开发者使用,平均编码效率提升 35%
- 新人培训周期缩短 77%(从 2 周到 3 天)
- Code Review 时间减少 40%,代码质量明显提升
- 获得团队年度最佳工具奖,被推广至其他部门
六、运行说明
6.1 环境准备
npm create vite@latest code-assistant -- --template vue
cd code-assistant
npm install
6.2 运行代码
- 将三个功能的代码分别复制到
src/App.vue - 运行
npm run dev - 浏览器访问
http://localhost:5173
6.3 功能演示
- 代码补全:在编辑器输入代码,停顿或输入特殊字符触发补全
- 代码解释:粘贴复杂代码,查看 AI 的多维度解释
- 测试生成:输入函数代码,自动生成全面的测试用例
核心要点
- 上下文提取要精准,平衡准确性和成本
- 触发时机要智能,避免过度打扰用户
- 测试用例要全面,覆盖各种边界情况
- 简历表达要具体、自然、有数据支撑
- 面试回答要有深度思考和解决方案