最近整个互联网圈都有 Vibe Coding 热潮。原因是有 LLM 写代码以后,一方面编程的门槛大大降低了,另一方面编程的速度也越快了(LLM 可以每秒吐几十个 tokens,人哪有这个速度啊),所以早些年有很多想法设想,但是没有时间和精力去做,现在都可以尝试着让 LLM 做一下了。

这篇文章讲的是我使用 C# 写了一个 Windows 相册的项目 lyh543/ham-gallery (opens new window)。我一直想要在 Windows 上也有一个类似于手机上的相册应用,能够提前扫描图片、建好索引和缩略图,打开应用的时候就能快速展示图片,而不是像 Windows Explorer 那样每次打开目录都要重新扫描一遍,尤其是在一个 Camera 目录下的照片数量上千的情况。于是我在 C# 和 Windows 开发零基础的前提下,花了一周时间,实现了 70% 的原型。没有手动编辑一行代码逻辑,主要是在改 README,跑编译 + 运行命令验证功能,最多手动改了一点前端文案。当然,剩下完善的工作还需要一两个月的时间,一两周的时间做出来的东西不太能达到生产可用的状态。

除此之外,我还做了很多的 Vibe Coding 小项目,都是现成的小工具没法满足我的需求,但是一直又懒得自己写,处于凑合用的状态,比如:

  • webdav-docker (opens new window):一个把本地目录当做 Webdav 服务器的 docker 镜像。之前用的是 morrisjobke/webdav (opens new window),但是不支持自定义 docker 内的 uid,导致多多少少有点权限问题。有 Vibe Coding 以后,我就自己写了一个 webdav docker 镜像,支持自定义 uid,解决了权限问题。
  • web-tools (opens new window):微信表情包录屏转 GIF 的前端小工具。核心逻辑是视频解码和抽帧、识别循环节、自动裁剪图像、生成 GIF。逻辑很简单,但是很实用,可以把一些只在微信发布的动图表情包转成动图,在 QQ、飞书和小红书上用。第一版是 Vibe Coding 做的原型。第二版引入了 Webcodecs 和 ffmpeg 解决了录屏解码问题。第三版用 React + Material UI 重写了前端界面,还整合了一些别的前端小工具,作为一个前端工具箱 (opens new window)发布。
  • ip_traffic_monitor_cli (opens new window):一个按 IP 统计服务器出入流量的 CLI,同时提供了 Prometheus 接口,方便我在 Grafana 上监控服务器的流量情况。看看是谁在爬我的服务器!!!

而相册项目比其他小工具更像是一个完整的产品,开发周期也是最长的,这个开发过程非常适合用来总结一下 Vibe Coding 的一些小技巧,以及 LLM 在这个过程中的表现和一些坑。

# 语言的选择

在早期 Vibe Coding 的时候,就有一个明显的现象,越热门的语言,LLM 学习的更多,所以表现会更好。所以 Vibe Coding 更推荐使用热门的语言,比如 Python、TypeScript、Go、Java 这些,能少走很多弯路。

而我选择使用 C# 而不是 TS + Electron,主要还是想要体验 Win11 的 Win UI 3 框架,毕竟做一个相册应用,颜值也很重要。但也因此也踩了很多坑,比如 GPT 5.4 初始化的项目跑不起来、调用 Windows 的 原生 API 解码时闪退、内存泄漏。大家 Vibe Coding 时,还是尽量选择热门语言吧。

# 模型评分

这个项目主力开发在四月初,因此没有测试后来的 GPT 5.5 和 Opus 4.7 还有 Deepseek V4。

当时的测试结果是:

  1. Opus 4.6 确实是最强的,可以用来分析 Sonnet 和 GPT 5.4 都搞不定的疑难杂症。比如在我提示加日志了以后,定位到并解决了使用第三方库进行照片解码触发的内存溢出的问题。
  2. 对于 Sonnet 4.6 和 GPT 5.4,最开始觉得 Sonnet 4.6 比 GPT 5.4 好。GPT 5.4 在初始化项目的时候,写出来的东西连窗口启动不了。当然,选择热门语言的话,这类问题肯定会少很多。但是后来发现 Sonnet 4.6 在实现功能的过程中经常摸鱼(把需要实现的东西 mock 掉,编译通过就说写完了),GPT 5.4 倒是会老老实实干过,不怎么出现这种情况。所以现在也抛弃 Sonnet 了。

对于简单的任务,可以切换成便宜的模型。

# 编辑器 / Agent 评分

强大的 Agent 能够更好地发挥模型的能力。但我更想说的是,模型的提升带来的收益比 Agent 提升带来的收益更为可观。在模型能够执行 shell 命令、模型知道怎么执行 shell 命令以后,不同的 Agent 之间的差距就越来越小了。并且 Agent 架构设计的门槛远比模型设计的门槛低很多了,所以不同 Agent 的更新换代和相互学习的速度也很快。最开始 Claude Code 引入的 subagent 可以节省 token,现在 GitHub Copilot 也有类似的机制了。

Claude Code 一个很强大的 Agent,在内部做了非常多的优化。但是它的 UI 实在不敢恭维,毕竟 TUI 相比于 GUI 显示的信息密度小很多、交互也麻烦很多。

相比之下,我更喜欢 Cursor 和 GitHub Copilot Chat。它们可以一边 review 代码一边对话,UI 上针对多 Session 并发也进行了优化,会在 Session List 显示每个 Session 的状态。而 Claude Code CLI 不支持但单窗口多 Session,只能多开几个窗口来实现多 Session 的效果。

# first prompt

LLM 非常擅长写给 LLM 看的文档,所以非常推荐用它来写 Prompt、Skill 这类东西。

  1. 描述清楚你的需求,然后让它写一个 Prompt。

下面是我让他生成 prompt 的 prompt:

我想要使用 llm 从零开发一个 windows 平台的相册应用,请帮我编写提示词。

1. 平台:Windows,使用 Fluent UI 开发,类似于 Windows 11 自带的相册
2. 页面:需要有至少以下页面:
  a. 相册列表,展示管理的所有相册,支持创建、删除、重命名相册
  b. 照片列表,展示相册中的照片,支持添加、删除
  c. 照片详情,展示照片。支持全屏。支持照片信息展示(拍摄时间、位置、相机型号等)。支持照片简单的编辑(旋转、裁剪),更复杂的编辑可以调用外部接口。支持照片删除(调用系统 API 移动到回收站)
  c. 全部照片列表,展示所有照片。点击某张照片可以进入照片详情页
  d. 设置页面,支持设置扫描相册(多选、递归)、排除文件夹、语言、删除照片是否需要确认、清除片缓存、清除数据库缓存、清除全部数据等
1. 导航栏:有相册列表、全部照片、设置页面。相册列表页面下可以支持固定/取消固定指定的目录到导航栏,方便用户快速访问
2. 多语言支持:需要支持中文、英文。默认为英文,用户可以在设置中切换语言。
3. 亮点功能:
   1. 使用 Fluent UI 的设计语言,界面美观、现代
   2. 对触屏友好(详情页可以通过触屏放大、缩小照片;列表页可以通过触屏调整每行展示的照片数量)
   3. 支持 jpg、png、bmp、heic、heif 等常见图片格式,支持
   4. 相册列表页面支持多种排序方式(名字、日期、创建时间、修改时间、拍摄时间)
   5. 照片列表页面支持多种排序方式(名字、日期、大小、创建时间、修改时间、拍摄时间、原生排序)。原生排序指的是和 Windows Explorer 的排序顺序保持相同
   6. 在相册列表、照片列表、全部照片页面,通过照片缩略图,加快展示照片。如果照片的修改时间未生变化则重新生成缓存
   7. 在相册列表、照片列表、全部照片页面,将照片信息存到数据库中,不是每次都需要扫描全量的照片。Windows 文件管理器的痛点是每次打开目录都需要重新加载所有相册、然后重新排序,在一千张照片的目录下性能非常非常差。这个每次打开应用时,首先展示数据库里的照片信息,然后后台线程根据设置里需要扫描的目录,进行扫描照片目录,如果照片的修改时间未生变化则不更新数据库,只有当照片的修改时间发生变化时才更新数据库。
   8. 在照片详情页浏览某张图片时,预先加载前后的几张照片的全量数据。
   9. 对于超大的照片,先降采样加载,用户放大照片时再加载图片细节,且需要保证前端不卡顿。
   10. 从 Windows Explorer 打开照片时,保证用户可以向前/向后查看图片,且图片顺序和用户在 Windows Explorer 的排序顺序保持相同。可以自动加载所在目录下的照片,并进入照片详情页。

Sonnet 的回答:

提示词已写入 PROMPT.md,共 12 个章节,可直接作为 System Prompt 或需求文档交给 LLM。以下是结构概览:

章节	内容
一、项目概述	目标定位、核心亮点
二、技术栈	WinUI 3 + C# + SQLite + WIC,含每项选型理由
三、项目结构	完整目录树,分层清晰(Models / Data / ViewModels / Views / Controls)
四、数据库 Schema	4 张表 + 索引,含字段注释
五、页面规范	6 个页面(含 MainWindow)的完整功能描述
六、亮点功能详细规范	缩略图缓存、后台扫描、Explorer 激活、导航栏固定、自然排序、触屏手势,含伪代码/代码片段
七、图片格式	HEIC 依赖处理方案
八、多语言规范	resw 路径、Key 命名约定、运行时切换方案
九、性能要求	量化指标(首屏 ≤1s、60fps、UI 线程无阻塞等)
十、错误处理	文件丢失、HEIC 缺扩展、权限不足、DB 迁移
十一、代码规范	async/CancellationToken、日志、单测覆盖范围
十二、交付顺序	12 步渐进式实现路径,保证每步可运行
使用建议:

直接将 PROMPT.md 全文作为 LLM 的 System Prompt 或第一条 User 消息
按「第十二章·交付顺序」逐步推进,每次只要求 LLM 完成一步,避免一次生成过多代码导致质量下降
对于 HEIC 支持,需提醒用户在 Windows 上安装「HEVC 视频扩展」(Microsoft Store 付费,或使用 OEM 预装版)

它写入的 Prompt 我也上传到项目的 GitHub (opens new window) 了。

确认架构没问题了以后,让它一步步实现:

请阅读 PROMPT.md 以后,逐步实现这个项目。

请按「第十二章·交付顺序」逐步推进,每次只需要完成一步,避免一次生成过多代码导致质量下降。

首先请你初始化项目,创建 WinUI 3 项目结构,并搭建基础的 MainWindow 和导航框架。请确保项目可以成功编译和运行,MainWindow 中包含一个基本的导航栏(NavigationView),并且能够切换到一个空白的「相册列表」页面。

# LLM 怎么修 bug

LLM 修 bug 和人类的逻辑不一样。我们平时修 bug 依赖日志,这样可以快速定位问题,把问题定位在二三十行以内。LLM 不一样,它可以一目十行,所以 LLM 会更偏向于读代码、理解代码、找到代码其中的错误。但对于复杂的 bug,有些代码看上去很奇怪、可能会有 bug,实际上是 by design 的,所以它修了几轮以后还是不行,反而把代码改得七零八碎。

  1. 一些难以复现、需要检查代码逻辑的 bug,在 LLM 出现以前一般非常难解决,非常考验程序员功底,且非常费心费力。现在交给 llm 相当好用,它还可以告诉我如何复现。
  2. 对于一些纯算法、不依赖外部数据库、UI 的问题,最好能够提供一个它能复现和验证是否修复的环境,让它能够自己运行命令验证问题。
    • 比如在 HEIC 缩略图生成问题时,我让它自己写了一个命令行工具调用它自己写的生成代码,生成好以后也需要验证图片是否正常(大小正常)。它生成完以后我手动验证没问题,就让它编写一个测试,并把这张 HEIC 图片(为了缩减大小,进行了裁剪)作为测试数据提交到了 Git。
  3. 对于需要人工交互、它自己不好复现的问题,可以先让它尝试读代码修两轮。要是修不好,可以引导它加日志,然后告诉人类怎么操作,给它提供日志,让它查。
  4. 做好版本管理。如果它修了几轮以后还是不行,可能需要回滚到一个干净的版本重新修。
  5. 如果尝试了以上的办法还是不行,就有必要读一下这块的逻辑(可以借助 LLM),或者重新从整体出发,思考一下最合理的逻辑是什么
  • 在修复切换图片时缩放比例不对的 bug 时,sonnet 一直在思考是不是设置尺寸的时机不对导致读取到了空值,需要延迟触发(类似于 react 的 setTimeout(0) 在下一轮渲染时修改数据)。后来我从全局出发重新思考了更符合直觉的加载逻辑,应该是在渲染图片前读取图片尺寸,计算出正确的尺寸值,然后一次设置就生效。按照这个思路,sonnet 老师两轮就改好了。
  • 在图片页实现图片旋转的时候,GPT 5.4 一开始的思路是旋转图片,然后重新加载,但是重新加载图片会闪两次屏幕。GPT 5.4 觉得可能是缓存等问题,反复尝试了几轮都没解决这个问题。后来我发现其实旋转图片的时候不需要重新加载修改后的图片的,只需要在前端做一个旋转的变换就好了。GPT 5.4 一轮就修好了,但是之前就完全没有考虑过这个方案。
  1. 如果在多轮交互以后,它终于修复了问题了,这事还没完,还需要把之前一些无效修复的代码更改给移除掉,不然每个更改都会让代码逻辑更复杂,久而久之就变成屎山了。

# 性能优化和架构重构

4.6 这天,花了一个上午时间解决 HEIC 图片预加载的问题。在 Claude 的辅助下,熟悉了它写的代码,并且提出了重构方案。

  1. 记得主动询问“这样实现有什么问题”,避免 LLM 只会谄媚地赞同你的方案。

对话总结放到 GitHub (opens new window) 上了。

# LLM 在分析疑难杂症上,仍然不是万能的

我在编写图片预加载的时候,经常出现闪退。由于是 App 并发调用 Windows 的 WIC 接口,闪退的时候,App 自身的日志和 Windows 日志非常有限,非常难定位问题。Sonnet 和 Opus 老师帮我分析了很长时间,提出了非常多的 workaround 尝试解决问题,包括把所有需要调用 WIC 的接口改到非 UI 线程、把所有调用 COM 的地方统一加锁,也可能出现闪退,只是闪退的概率降低了。

最后根本解决问题的契机是,某天我意识到引入第三方库解码 HEIC 之前从来没有闪退过,于是把两份代码给 LLM 分析差异。最终发现问题是旧代码将 JPG 解码完全交由 WPF 框架处理,全程未自定义任何解码逻辑;新代码改为自行编写逻辑调用 WIC 实现 JPG 解码,属于半自定义解码——既没有完全自研解码,又直接调用了不支持用户侧并发操作的 WIC 组件,最终引发程序闪退。而 HEIC 解码全程基于 Magick.NET 独立实现,完全不依赖 WIC,因此未出现异常。

所以复杂的问题中,如果人能够引导 LLM,LLM 解决问题的速度会更快。人在这个场景里更像是一种剪枝的角色:如果让 LLM 自己分析问题,方向会非常多,非常浪费时间和 token。如果让人来给 LLM 一个方向(比如打日志前后对比、给一份没有问题的 POC),LLM 在阅读代码、编写代码的效率比人高非常多。

对话历史:20260409-jpg_file_crash_issue.organized.md (opens new window)

# Opus 4.6 老师在打日志、分析日志以后,修掉了内存泄漏的疑难杂症

内存泄漏问题是一个老大难问题。现代语言虽然有垃圾回收机制,大部分时候不需要管理内存,但是在一些特殊的场景下,还是会有内存泄漏的问题,比如在调用 COM 组件和其他 native 库的时候产生的内存泄漏,并且这种内存泄漏比编程语言产生的内存泄漏更难发现和修复(它不遵循语言内存管理和 GC 的逻辑,甚至无法通过语言自带的堆栈内存统计分析到),非常考验人和 LLM 的知识量。

还是在上面 HEIC 解码的时候,即使我已经显式调用了 dispose() 操作、进行了 GC、甚至切出了图片页,内存还是超过 3GB 高居不下、没有任何减少的迹象。

我让 Sonnet 老师改了几版,没有效果。又让 Sonnet 老师打了一些日志,分析日志,Sonnet 老师的结论就是:这些内存是 native COM 组件产生的,不在 C# 代码的管辖范围内,GC 不能主动释放这些内存,所以修不了。由于内存溢出问题的优先级低于新功能的开发,就先搁置一边了。

过了几周,新功能开发的差不多了,我回过头来让 Opus 老师再打日志看看,Opus 老师分析出了几个 native 内存的泄露点,改掉以后,内存占用就回到 40MB 了。

这个例子说明,疑难杂症的修复,不仅需要人来引导 LLM,还需要人或 LLM 有足够丰富的知识,需要足够了解相关的语言和框架。而一个显然的事实是,在热门的知识点上,LLM 的知识量和更新速度都远超人类,所以 LLM 会越来越擅长分析和修复疑难杂症,并且参数越多的模型的效果会越好。只有一种情况,那就是非常冷门、非常新、还没有什么文档的知识点,LLM 的知识量可能还不如人类,这个时候人就非常重要了。

对话历史:20260504-3-copilot_photodetailpage_memory_usage_analys.organized.md (opens new window)

# 感想

  1. 因为能够生成准确率非常高的代码,就能实现自己的想法,但是自己去学习新的代码、框架就要付出很多。所以它其实是简化了非常大的学习曲线。
  2. Vibe Coding 的时候,对实现细节确实了解得不多了。不过领导带实习生也是这样,只要架构对了,下面实现的细节都不会出太大的问题,真有问题(性能问题、安全问题)可以再具体问题具体分析。90% 以上的代码都是不需要优化的代码,这部分代码用 LLM 搞,ROI 非常高。剩下的代码用 LLM 辅助阅读代码,效率也会比手搓的效率更高。
  3. 对于一些纯算法、不依赖外部数据库、UI 的问题,最好能够提供一个它能复现和 Debug 的环境,让它能够自己运行命令验证问题。对于需要人工交互、它自己不好复现的问题,可以先让它尝试读代码修两轮。要是修不好,可以引导它加日志,然后告诉人类怎么操作,给它提供日志,让它查。
  4. 能够编写单元测试的,尽量编写。这是让它自动回归验证功能是否被更改的途径。反正是 LLM 来写,对人没有什么心智负担。
  5. 多 Commit 代码,尽量在每个功能可用且稳定的时候 commit 一次,以免后面 LLM 改炸了,然后 Cursor / GitHub Copilot 自带的 Restore Workspace 也炸了,前面的代码就白生成了。相信 Agent 的版本管理,不如做好 git 的版本管理
  6. LLM 的出现,也不意味着只需要架构师,多年开发经验的工程师没有作用了。LLM 负责提出若干个方案,工程师负责剪枝、排除掉不合理的、优先选择最快最有效的,然后 LLM 继续负责实现。特别奇怪的疑难杂症,LLM 自己搞可能会搞很久,但如果有工程师给予提示和剪枝,LLM 解决问题的效率会高很多。
  7. 从 GPT 3.5 出现,就无法否认一件事实:LLM 的学习速度远超人类。或许上面这些小技巧也会被 LLM 学会并内化,到时候这篇博客里提到的这些小技巧对我们工程师来说就没那么重要了。果然,只要我学的足够慢,我就可以什么都不用学当然,了解这些点背后的思想也很重要,因为各个学科的知识是相通的。这些知识在内化以后,或许在平时工作交流或者日常生活中,就会突然派上用场了呢?