AI结对编程实战演练

随着AI辅助编程工具的普及,开发者与AI结对工作正从概念走向日常。这种新模式并非简单的代码生成,而是深度融合了人类的设计思维、领域知识与AI的快速检索、模式识别及代码生成能力,形成一种高效的协同编程范式。它重塑了问题拆解、方案探索和代码实现的工作流,将开发者从重复劳动中解放出来,更聚焦于架构设计、逻辑复杂性和创造性工作。

核心模式:从指令到对话的演进

传统的AI代码补全多为单次提示与响应。而结对编程的核心在于建立持续的、上下文丰富的对话流。开发者像与一位资深同事并肩而坐,通过多轮交互,逐步澄清需求、探讨方案、迭代代码。

示例:构建一个可排序、过滤的用户数据表格
开发者可能这样开始对话:

开发者:我需要一个Vue 3组件,展示一个用户列表。数据是一个数组,每个用户对象有id, name, email, role字段。需要表头点击排序,以及一个根据nameemail搜索的输入框。

AI助手:好的。我将为您创建一个使用Vue 3 Composition API的组件。我们会用到refcomputed来处理响应式数据和排序逻辑。先搭建基础结构。

vue 复制代码
<template>
  <div>
    <div class="controls">
      <input
        v-model="searchQuery"
        placeholder="搜索姓名或邮箱..."
        @input="handleSearch"
      />
    </div>
    <table>
      <thead>
        <tr>
          <th @click="changeSort('name')">姓名 {{ sortIcon('name') }}</th>
          <th @click="changeSort('email')">邮箱 {{ sortIcon('email') }}</th>
          <th @click="changeSort('role')">角色 {{ sortIcon('role') }}</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in sortedAndFilteredUsers" :key="user.id">
          <td>{{ user.name }}</td>
          <td>{{ user.email }}</td>
          <td>{{ user.role }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const props = defineProps({
  users: {
    type: Array,
    required: true,
    default: () => []
  }
})

const searchQuery = ref('')
const sortKey = ref('name')
const sortOrder = ref('asc') // 'asc' 或 'desc'

// 处理排序
const changeSort = (key) => {
  if (sortKey.value === key) {
    sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
  } else {
    sortKey.value = key
    sortOrder.value = 'asc'
  }
}

const sortIcon = (key) => {
  if (sortKey.value !== key) return '↕️'
  return sortOrder.value === 'asc' ? '↑' : '↓'
}

// 计算排序和过滤后的用户列表
const sortedAndFilteredUsers = computed(() => {
  let filtered = props.users
  if (searchQuery.value) {
    const query = searchQuery.value.toLowerCase()
    filtered = filtered.filter(user => 
      user.name.toLowerCase().includes(query) || 
      user.email.toLowerCase().includes(query)
    )
  }
  
  // 排序
  return [...filtered].sort((a, b) => {
    let aVal = a[sortKey.value]
    let bVal = b[sortKey.value]
    if (typeof aVal === 'string') {
      aVal = aVal.toLowerCase()
      bVal = bVal.toLowerCase()
    }
    if (aVal < bVal) return sortOrder.value === 'asc' ? -1 : 1
    if (aVal > bVal) return sortOrder.value === 'asc' ? 1 : -1
    return 0
  })
})
</script>

开发者:功能基本正确,但样式太简陋了。请为表格和输入框添加一些基础CSS,让它们看起来更美观。同时,当搜索无结果时,显示“未找到匹配的用户”。

AI助手:已添加样式和空状态提示。我使用了简单的边框、间距和悬停效果,并添加了no-results的提示。

vue 复制代码
<template>
  <div class="user-table-container">
    <div class="controls">
      <input
        v-model="searchQuery"
        placeholder="搜索姓名或邮箱..."
        class="search-input"
        @input="handleSearch"
      />
    </div>
    <div v-if="sortedAndFilteredUsers.length === 0" class="no-results">
      未找到匹配的用户。
    </div>
    <table v-else class="user-table">
      <!-- ... thead 部分保持不变 ... -->
      <tbody>
        <tr v-for="user in sortedAndFilteredUsers" :key="user.id">
          <!-- ... td 部分保持不变 ... -->
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup>
// ... 脚本部分保持不变 ...
</script>

<style scoped>
.user-table-container {
  font-family: sans-serif;
  padding: 20px;
}
.controls {
  margin-bottom: 20px;
}
.search-input {
  padding: 10px 15px;
  border: 1px solid #ccc;
  border-radius: 6px;
  width: 300px;
  font-size: 16px;
}
.search-input:focus {
  outline: none;
  border-color: #007bff;
  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.user-table {
  width: 100%;
  border-collapse: collapse;
}
.user-table th,
.user-table td {
  border: 1px solid #ddd;
  padding: 12px 15px;
  text-align: left;
}
.user-table th {
  background-color: #f8f9fa;
  cursor: pointer;
  user-select: none;
}
.user-table th:hover {
  background-color: #e9ecef;
}
.user-table tbody tr:hover {
  background-color: #f5f5f5;
}
.no-results {
  padding: 30px;
  text-align: center;
  color: #6c757d;
  font-style: italic;
}
</style>

通过这样多轮的、聚焦于具体改进点的对话,组件从功能到体验被逐步完善。开发者始终掌控着方向和最终决策权。

实战场景:复杂逻辑的协同拆解与实现

面对复杂业务逻辑,结对编程的优势更加明显。AI可以帮助快速生成基础代码框架、提供算法思路、甚至发现边缘情况。

场景:实现一个具有撤销/重做功能的草图绘制工具
开发者描述核心需求后,AI可以快速搭建基于Canvas的初始结构,并引入命令模式来管理状态历史。

javascript 复制代码
// 来自AI助手的初步建议:核心状态与命令管理器
class DrawingApp {
  constructor(canvasElement) {
    this.canvas = canvasElement
    this.ctx = canvasElement.getContext('2d')
    this.history = []
    this.currentIndex = -1
    this.isDrawing = false
    this.lastX = 0
    this.lastY = 0
    this.currentTool = 'pen'
    this.currentColor = '#000000'
    this.lineWidth = 2

    this.setupEventListeners()
  }

  // 命令抽象
  executeCommand(command) {
    // 如果当前不是最新状态,则丢弃后面的历史
    if (this.currentIndex < this.history.length - 1) {
      this.history = this.history.slice(0, this.currentIndex + 1)
    }
    this.history.push(command)
    this.currentIndex++
    command.execute(this.ctx)
  }

  undo() {
    if (this.currentIndex < 0) return
    // 清空画布,重绘到上一步
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
    for (let i = 0; i < this.currentIndex; i++) {
      this.history[i].execute(this.ctx)
    }
    this.currentIndex--
  }

  redo() {
    if (this.currentIndex >= this.history.length - 1) return
    this.currentIndex++
    this.history[this.currentIndex].execute(this.ctx)
  }

  // ... 事件监听和绘图方法由开发者与AI共同填充
}

// 一个具体的“绘制线段”命令类
class DrawLineCommand {
  constructor(startX, startY, endX, endY, color, width) {
    this.startX = startX
    this.startY = startY
    this.endX = endX
    this.endY = endY
    this.color = color
    this.width = width
  }

  execute(ctx) {
    ctx.beginPath()
    ctx.moveTo(this.startX, this.startY)
    ctx.lineTo(this.endX, this.endY)
    ctx.strokeStyle = this.color
    ctx.lineWidth = this.width
    ctx.lineCap = 'round'
    ctx.stroke()
  }
}

开发者:命令模式的设计很好。现在请帮我补全setupEventListeners方法,实现鼠标绘图的基本逻辑,并将每个笔画作为命令存入历史。

AI助手:已添加鼠标事件处理。在mouseup时,将本次绘制的线段生成命令并执行。

javascript 复制代码
class DrawingApp {
  // ... 构造函数和其他方法 ...

  setupEventListeners() {
    this.canvas.addEventListener('mousedown', (e) => {
      this.isDrawing = true;
      [this.lastX, this.lastY] = [e.offsetX, e.offsetY]
    })

    this.canvas.addEventListener('mousemove', (e) => {
      if (!this.isDrawing) return
      const currentX = e.offsetX
      const currentY = e.offsetY

      // 立即绘制视觉反馈
      this.ctx.beginPath()
      this.ctx.moveTo(this.lastX, this.lastY)
      this.ctx.lineTo(currentX, currentY)
      this.ctx.strokeStyle = this.currentColor
      this.ctx.lineWidth = this.lineWidth
      this.ctx.lineCap = 'round'
      this.ctx.stroke()

      // 创建命令并执行
      const command = new DrawLineCommand(
        this.lastX, this.lastY,
        currentX, currentY,
        this.currentColor,
        this.lineWidth
      )
      this.executeCommand(command)

      // 更新最后坐标
      [this.lastX, this.lastY] = [currentX, currentY]
    })

    this.canvas.addEventListener('mouseup', () => {
      this.isDrawing = false
    })
    this.canvas.addEventListener('mouseout', () => {
      this.isDrawing = false
    })
  }

  // ... undo, redo 等方法 ...
}

在此过程中,开发者可以随时提出新需求或修改方向,例如:“如何支持不同画笔形状?”或“撤销历史能否持久化到本地存储?”,AI能基于现有代码上下文提供连贯的修改建议。

技能提升:AI作为实时导师与知识库

对于开发者不熟悉的新API、库或设计模式,AI能提供即时的解释和示例,加速学习曲线。

示例:学习使用Intersection Observer API实现懒加载
开发者可以直接询问:

开发者:我想用原生JavaScript实现图片懒加载,但不想用滚动事件监听,听说有个Intersection Observer,能给我一个简单的实现示例吗?

AI助手:当然。Intersection Observer API可以高效地观察元素与视口的交叉状态。这是一个基础实现:

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <style>
    .lazy-image {
      width: 100%;
      height: 300px;
      background-color: #eee;
      margin: 20px 0;
      display: block;
    }
  </style>
</head>
<body>
  <div style="height: 1000px;">滚动下方...</div>
  <img class="lazy-image" data-src="https://picsum.photos/800/300?random=1" alt="">
  <img class="lazy-image" data-src="https://picsum.photos/800/300?random=2" alt="">
  <!-- 更多图片 -->

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      const lazyImages = document.querySelectorAll('.lazy-image[data-src]')

      // 创建观察器实例
      const imageObserver = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            const img = entry.target
            // 将 data-src 的值赋给 src
            img.src = img.dataset.src
            img.classList.remove('lazy-image')
            // 停止观察该图片
            observer.unobserve(img)
            console.log(`图片加载: ${img.src}`)
          }
        })
      }, {
        // 可选配置:在视口下方100px时开始加载
        rootMargin: '0px 0px 100px 0px'
      })

      // 开始观察每个图片元素
      lazyImages.forEach(img => imageObserver.observe(img))
    })
  </script>
</body>
</html>

AI助手:关键点在于:1. 将真实URL放在data-src属性;2. 当元素进入视口(isIntersectingtrue)时,将data-src赋给src触发加载;3. 使用rootMargin可以提前加载。这比监听滚动事件性能好得多。

开发者可以立即在此基础上进行修改和实验,例如添加加载占位符、处理加载错误等,从而在实践中快速掌握该API。

工作流整合:将AI深度嵌入开发环节

有效的AI结对编程不仅仅是聊天窗口,而是将AI能力无缝集成到IDE、代码审查、调试和重构等各个环节。

  • 在编码时:AI根据当前文件上下文、项目技术栈,提供更精准的代码补全、函数建议和下一行预测。
  • 在代码审查时:AI可以自动扫描提交的代码,不仅检查语法错误,还能提示潜在的性能问题、安全漏洞、可访问性缺失,并给出符合项目规范的修改建议。
  • 在调试时:开发者将错误信息或异常行为描述给AI,AI可以分析堆栈跟踪,推测可能的原因,并建议添加特定的日志或断点来定位问题。
  • 在重构时:开发者可以指令AI:“将这个巨型函数拆分成几个更小、职责单一的函数”,或“将这段重复的代码提取成一个可复用的工具函数”,AI能理解代码语义并执行重构。

示例:在IDE中利用AI快速生成工具函数
在编写工具函数时,可以直接在注释中描述需求。

javascript 复制代码
// 我想要一个函数,深度比较两个对象是否相等,考虑嵌套对象和数组。
// 请用原生JavaScript实现。

// AI生成的代码:
function deepEqual(obj1, obj2) {
  // 处理基本类型和引用相同的情况
  if (obj1 === obj2) return true
  if (obj1 == null || obj2 == null) return false
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false

  // 处理数组
  if (Array.isArray(obj1) && Array.isArray(obj2)) {
    if (obj1.length !== obj2.length) return false
    for (let i = 0; i < obj1.length; i++) {
      if (!deepEqual(obj1[i], obj2[i])) return false
    }
    return true
  }
  // 处理对象
  const keys1 = Object.keys(obj1)
  const keys2 = Object.keys(obj2)
  if (keys1.length !== keys2.length) return false
  for (const key of keys1) {
    if (!keys2.includes(key)) return false
    if (!deepEqual(obj1[key], obj2[key])) return false
  }
  return true
}

// 测试用例
console.log(deepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } })) // true
console.log(deepEqual([1, [2, 3]], [1, [2, 3]])) // true
console.log(deepEqual({ a: 1 }, { a: 1, b: 2 })) // false

挑战与最佳实践

尽管前景广阔,但AI结对编程也面临挑战,需要建立正确的工作方式:

  1. 保持批判性思维:AI生成的代码可能存在逻辑错误、安全漏洞或性能问题。开发者必须仔细审查,不能盲目接受。始终将AI视为助手而非权威。
  2. 提供清晰、具体的上下文:AI的表现极度依赖输入质量。在提问时,应包含技术栈、业务背景、约束条件(如性能要求、浏览器兼容性)等详细信息。
  3. 迭代与精炼:很少有一次生成就完美的代码。应将过程视为迭代:生成 -> 审查 -> 提出改进要求 -> 再生成。例如,“这个函数缺少错误处理”,“这个组件需要支持移动端触摸事件”。
  4. 所有权与理解:最终代码必须被开发者完全理解和掌控。对于AI生成的复杂逻辑,要求其添加注释,或自己手动重写关键部分以加深理解。
  5. 安全与合规:切勿将敏感数据(如API密钥、用户信息、专有算法)输入到公共AI模型中。确保生成的代码符合公司的安全标准和开源许可证要求。
  6. 聚焦创造性工作:将重复性、模式化的编码任务委托给AI,让自己更专注于系统架构、复杂业务逻辑、用户体验优化和创造性问题解决。

这种协同关系正在不断进化。随着模型对代码上下文理解能力的加深,以及IDE插件的更加智能化,AI结对编程将越来越像与一个不知疲倦、知识渊博的伙伴共同工作,它能够加速开发流程,激发解决方案,并帮助团队将更多精力投入到构建真正创新和有价值的软件产品中。