news 2026/9/8 19:31:42

VS Code Agents 窗口移动端 Diff 编辑器设计:单文件与多文件虚拟化审查视图的实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VS Code Agents 窗口移动端 Diff 编辑器设计:单文件与多文件虚拟化审查视图的实现解析

VS Code Agents 窗口移动端 Diff 编辑器设计:单文件与多文件虚拟化审查视图的实现解析

【免费下载链接】vscodeVisual Studio Code项目地址: https://gitcode.com/GitHub_Trending/vscode6/vscode

导读

本文围绕仓库中的设计文档 MOBILE_DIFF_EDITORS.md,完整解析 VS Code(当前仓库)中 Agents 会话窗口为手机端提供的两类 Diff 审查界面:MobileDiffView(单文件统一 Diff 全屏浮层)与MobileMultiDiffView(多文件虚拟化统一 Diff 浮层)。你会掌握:移动端为何不复用桌面并排 Diff、轻量 diff 数据载荷的结构、双层虚拟化与原生 sticky 表头如何协作、按需懒加载与预取优先级策略,以及 Monaco tokenization 与正则回退高亮机制的取舍,最终能够据此理解或复现一套面向触屏的长列表 Diff 审查架构。

核心思想一句话概括:移动端 Diff 审查采用"全屏原生浮层 + 连续滚动审查面"而非桌面式窗格;单文件视图渲染一个统一 Diff,多文件视图把变更文件排布在同一个可滚动区间中,用"文件级 + 正文级"两层虚拟化控制挂载与渲染成本。


为什么移动端需要独立的 Diff 界面

设计文档(Why 一节)给出的结论是:手机需要与桌面等价的能力,但不能使用与桌面等价的呈现方式。约束来自四个方面:

  • 视口宽度:桌面端 side-by-side(左右并排)Diff 对手机视口过宽,压缩后可读性极差;因此移动端统一采用单栏 unified diff(统一 Diff),以+/-与行号着色区分增删。
  • 辅助视图不可用:桌面辅助区(auxiliary view)在手机布局中被禁用(参见 mobileLayout.ts、mobileAuxiliaryBarPart.ts 等手机布局部件),无法像桌面那样把多文件 Diff 放在侧边窗格里。
  • 触屏交互范式:触屏审查需要全屏审查面、可吸顶(sticky)的上下文信息、简洁的返回导航与始终可见的控件(如返回键、上一个/下一个文件按钮)。
  • 成本控制:大型 Agent 会话可能改动大量文件,若在用户尚未打开/滚动到某文件时就急切地为所有文件做读取、diff、tokenize,会造成可观的浪费;需要"按需 + 就近"的延迟计算。

也就是说,这套设计的本质是在保留桌面 Diff 审查能力的同时,把"全量"成本换成"可见即可得"的按需成本


总体设计:两张浮层、一份轻量数据载荷

两个移动 Diff 审查面

src/vs/sessions/browser/parts/mobile/contributions/目录下的实现共提供两类全屏浮层(设计文档 Current Design 一节):

视图类 / 文件定位
MobileDiffViewmobileDiffView.ts单文件unified diff 浮层;可附带兄弟文件列表做上一个/下一个导航,无兄弟时退化为单文件纯查看
MobileMultiDiffViewmobileMultiDiffView.ts多文件unified diff 浮层;带每文件表头、可折叠正文、按可见区间懒加载的连续虚拟滚动

两视图都复用同一套mobile-overlay-*浮层外壳(返回键 + 标题区 + 可滚动正文,样式见 mobileOverlayViews.css):构造时append到 workbench 容器,点返回即dispose,并通过onDidDispose事件让持有方清空浮层槽位,维持"`undefined <=> 无浮层打开"的不变式。

轻量 diff 数据载荷 IFileDiffViewData

两类视图共享同一个极小的 diff 载荷(设计文档原样给出的接口):

interface IFileDiffViewData { readonly originalURI: URI | undefined; readonly modifiedURI: URI | undefined; readonly identical: boolean; readonly added: number; readonly removed: number; }

源码定义位于 mobileDiffView.ts#L78-L94,其注释把语义说得更细:

  • originalURIundefined表示该文件是Agent 新增(newly added)文件,无旧内容,Diff 针对空原文渲染为全新增行;
  • modifiedURIundefined表示文件被Agent 删除,Diff 全部由来自originalURI的删除行构成;
  • 两者都有则为修改文件;identical: true表示无实际差异(no-op)。

这样五种状态(新增/删除/修改/无变化/加载失败占位)仅凭两条 URI + 两个统计数就能表达。设计上刻意不在vs/sessions/browser层 import 桌面的 multi-diff workbench 类型(分层约束),IFileDiffViewData就定义在mobileDiffView.ts内,由 mobileChangesView.ts#L22 与 mobileMultiDiffView.ts#L22 直接复用。

MobileChangesView(会话变更列表,mobileChangesView.ts#L114)是这两类 Diff 视图的"入口":它把活跃会话的变更实时渲染成一行行的触控目标,每行展示文件图标、文件名、相对目录、A/M/D 变更类型药丸与+N -N计数(rowToDiffData负责把行模型转成 diff 载荷,见 mobileChangesView.ts#L88-L96);点击某行时,会把整个兄弟列表连同被点击行索引一并传给 Diff 浮层(siblings参数),从而支持"上一个/下一个文件"导航。入口命令sessions.mobile.openChangesView定义于 mobileChangesView.ts#L32,由手机标题栏部件在统计徽标上被点击时触发(mobileTitlebarPart.ts#L305-L316)。


单文件视图 MobileDiffView 的工作方式

MobileDiffView通过命令sessions.mobile.openDiffView(mobileDiffView.ts#L72)打开,参数为IMobileDiffViewData

export interface IMobileDiffViewData { readonly diff: IFileDiffViewData; readonly siblings?: readonly IFileDiffViewData[]; readonly index?: number; }

为兼容旧调用方,命令也接受不带siblings的裸IFileDiffViewData,此时视图退化为无前后导航的单文件查看。构造时会把siblings归一化为非空数组(无兄弟则视自身为单元素列表),再以index(或indexOf兜底)确定起始文件,见 mobileDiffView.ts#L157-L165。

头部导航与左右滑动手势

  • 头部左侧是返回按钮;中部标题区显示文件名与带主题强调色的+N -N计数(复用变更列表的mobile-changes-row-added/-removed样式类)。
  • 仅当siblings.length > 1时右侧才出现prev/nextchevron 与i / n位置指示(mobileDiffView.ts#L188-L209),越界时按钮置灰并同步aria-disabled
  • 还支持水平滑动切换相邻文件:attachSwipeNavigation监听pointerdown/pointerup(mobileDiffView.ts#L222-L274),要求水平位移明显占优(absDx <= absDy * 1.5则视为竖向滚动放行)、且达到视口宽 30% 或速度 0.5px/ms 才触发;左滑切下一文件、右滑切上一文件。手势挂在滚动容器上以保证竖向滚动不被误吞。

数据读取与 diff 计算

renderBodyForCurrent每次导航都会先renderGeneration++,再读取当前文件的原始/修改文本(mobileDiffView.ts#L357-L364):

  • 文本读取走ITextFileService.read(resource, { acceptTextOnly: true })originalURI/modifiedURI缺失的一侧按空串处理,随后用computeUnifiedDiff(original, modified)计算统一 diff。
  • 读取前先渲染一行Loading…占位,无差异或读取异常则显示对应空态文案。

computeUnifiedDiff的实现(mobileDiffView.ts#L735-L826,与 mobileDiffHelpers.ts 内的同名导出同构)值得细看:

const result = linesDiffComputers.getDefault().computeDiff(origLines, modLines, { ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false, });
  • 底层行 diff 直接复用编辑器内核linesDiffComputers.getDefault(),与桌面 Diff 编辑器同源,仓库不维护第二套 diff 算法(注释原文:"no in-tree diff algorithm to maintain")。
  • 计算后把变更合并成 hunks:相邻变更若间隔不超过2 * CONTEXT_LINESCONTEXT_LINES = 3,即 6 行)就并入同一 hunk,避免视觉碎片化;每个 hunk 保留前导 3 行与尾部 3 行 context,hunk 头按@@ -origLeading,origCount +modLeading,modCount @@生成。
  • 输出的行模型IDiffLine三类:context/added/removedlineNum为 1 基行号(context/removed 取原文件行号,added 取修改后行号)。

语法高亮:Monaco tokenization 优先,正则回退兜底

这是移动端比较特殊的一处:Agents 窗口(sessions workbench)默认不加载内置语言扩展,因此即便ILanguageService.guessLanguageIdByFilepathOrFirstLine命中,多数语言也没有已激活的 TextMate grammar。渲染链路做了两级高亮(mobileDiffView.ts#L384-L393):

  1. 语言识别resolveMobileDiffLanguageId先用ILanguageService猜测;结果非unknown即返回,否则落入硬编码的EXTENSION_LANGUAGE_MAP(mobileDiffView.ts#L34-L61),覆盖.js/.ts/.py/.java/.c/.cpp/.cs/.go/.rs/.rb/.php/.html/.css/.json/.md/.sh/.yaml/.xml/.sql/.swift/.kt/.r/.lua/.dart等约 30 种常见扩展名,全部失败才退回plaintext。语言 ID 与 VS Code 内置扩展package.json中声明的贡献一致。
  2. tokenizationtokenizeFileLines调用tokenizeToString整段文本做一次性分词(保证跨行状态——开字符串、模板字面量、块注释——正确传播),剥掉<div class="monaco-tokenized-source">包装后按<br/>切回"每行一份 HTML",再逐行查找 token 片段按统一 diff 行号还原到增删行上。同时注入由TokenizationRegistry.getColorMap()生成的<style>generateTokensCSSForColorMap)让mtkN类着色。
  3. 正则回退hasMultipleTokenClasses检测若所有非空行都只含mtk1(默认前景色),说明 grammar 并未真正生效;此时改用轻量正则分词器regexTokenizeLine,按语言家族(js/python/css/html/json/shell/generic,见 mobileDiffView.ts#L573-L583)识别注释(///* */#)、模板字面量、字符串、数字与关键字,产出mobile-diff-tok-comment/string/keyword/number等 CSS 类 span。这些类在 mobileOverlayViews.css 中按.vs.hc-black.hc-light主题类选择器下的 CSS 变量着色,使主题适配留在样式表而非 JS 中。

异步一致性:generation 计数器

由于文本读取与分词都是异步的,单文件视图用**每体渲染代次(generation)**保护旧结果:renderBodyForCurrent每次导航都自增renderGenerationloadAndRender在每次await返回后检查this.disposed || generation !== this.renderGeneration,不匹配即丢弃结果,防止"用户已切走/已关闭视图"时迟到数据写入过期容器。

配色层面,mobileDiffColors.ts 以副作用方式向全局颜色注册表注册agentsMobileDiff.addedForegroundagentsMobileDiff.modifiedForegroundagentsMobileDiff.deletedForeground三个主题 token(含 dark/light/hcDark/hcLight 默认值),默认值刻意对齐 git 扩展色板,使移动 Diff 观感与 VS Code 其余部分一致——因为 Agents 窗口未加载workbench/contrib/scm,桌面变更视图依赖的gitDecoration.*token 在此不可用。


多文件视图 MobileMultiDiffView 的工作方式

MobileMultiDiffView把若干文件放进同一个虚拟滚动范围,连续滚动即可审完全部变更。构造入参 IMobileMultiDiffViewData 为:

export interface IMobileMultiDiffViewData { readonly diffs: readonly IFileDiffViewData[]; /** Index of the file to scroll to initially. */ readonly initialIndex?: number; /** Optional async diff computation override, used by test/demo hosts that can compute diffs off the UI thread. */ readonly computeDiff?: (originalText: string, modifiedText: string) => Promise<readonly IDiffHunk[]>; }

其中computeDiff是测试/演示宿主注入的异步钩子:Vite 移动端 multi-diff 演示页用它把 diff 计算放到Worker线程,模拟 VS Code 真实的 worker 支撑的 diff 环境(见设计文档 How It Works 一节);正常路径下会退化为本地computeUnifiedDiff调用(mobileMultiDiffView.ts#L724)。

数据读取与增量装配流水线

整个懒加载链路(loadFileContent)是文档所述流程的源码级落点:

  1. identical文件或未挂载即折叠的文件直接进入empty状态,渲染 "No changes in this file.",不产生任何 IO。
  2. resolveMobileDiffLanguageId识别语言(mobileDiffHelpers.ts#L54-L69)。
  3. 并行读取两个 URI 的文本:优先ITextFileService.read(..., { acceptTextOnly: true }),失败时回退IFileService.readFile(这正是设计文档所说"I 用 ITextFileService 读取、多文件视图以 IFileService 兜底"的出处,见 readTextContent)。
  4. 计算 hunks(本地computeUnifiedDiff或宿主computeDiff)。
  5. 整文件分词两个版本并检测是否产出真实 token;失败则用正则回退(复用 mobileDiffHelpers.ts 中导出的tokenizeFileLineshasMultipleTokenClassesregexTokenizeLines)。
  6. 把 hunks展平为确定性 body entries:每个 hunk 头一条hunk条目(高hunkHeaderHeight),hunk 内每行一条line条目(高rowHeight),逐条累加出绝对top与总体bodyHeight,并统计maxLineCharacterCount用于横向内容最小宽度(createBodyEntries)。
  7. 提交renderData并把该文件状态切到loaded,触发虚拟布局重排。

由此"读文件 → diff → tokenize → 挂载"四步都是随着虚拟化条目进入可视范围而增量发生的,而非打开浮层时全部执行。

每文件持久状态与加载优先级

每个文件对应一份IMobileMultiDiffFileState(mobileMultiDiffView.ts#L80-L102),其设计要点:

  • 持久状态不随卸载丢弃:折叠态collapsed、当前loadStateidle | loading | loaded | empty | error)、已计算的 hunk/行计数、渲染代次与滚动位置都保留在 state 上,文件段落滚出视口被卸载后再滚回时可基于缓存数据快速重建。
  • 虚拟高度来自已知统计:未加载时用轻量统计占位——初始估计estimatedHunkCountidentical或增删为 0 时为 0,否则为 1)与estimatedRowCount = added + removed;加载后替换为真实hunkCount/rowCount,见虚拟布局算法 computeMobileMultiDiffItemHeight。
  • 并发与预取限额(mobileMultiDiffView.ts#L35-L38):
const MAX_CONCURRENT_FILE_LOADS = 2; // 同时进行中的"可见"加载数 const MAX_CONCURRENT_PREFETCH_LOADS = 1; // 同时进行中的预取加载数 const MIN_PREFETCH_DISTANCE = 2400; // 预取最小距离(px) const PREFETCH_VIEWPORT_MULTIPLIER = 4; // 预取半径 = 视口高 × 4

loadVisibleFiles(mobileMultiDiffView.ts#L549-L593)按当前布局在"未加载且未折叠"的文件中挑离视口最近的一个以visible种类加载;prefetchNearFile(mobileMultiDiffView.ts#L595-L648)只在本批可见文件全部不再空闲时才在距视口prefetchDistance预热一个文件的渲染数据(读文本 + diff + tokenize),绝不为其挂载 DOM,且可见加载始终优先于后台工作。文件一旦滚出可视集合,未完成的可见加载会被abandonOffscreenLoads作废并退回idle,把额度让给更需要的文件。

浮层外壳与每文件 UI

  • 顶部固定栏展示返回键与"N file(s)"统计(mobileMultiDiffView.ts#L185-L191);滚动监听以passive: true注册并节流到requestAnimationFrame
  • 每文件段落含:可折叠表头(chevron 既是折叠手柄也承担 ARIAaria-expanded,CLICK 与Tap双绑定,键盘 Enter/空格亦可折叠)、…/父目录/文件名(目录只取末两级以免手机窄屏超宽,见formatDirSegment)、以及+N -N统计 span(identical 时省略)。
  • initialIndex会在首帧用 rAF 把滚动定位到对应文件的虚拟顶部(scrollToInitialIndex),并即时调度可见加载。

Body Virtualization:两层虚拟化如何分工

设计文档强调MobileMultiDiffView两层虚拟化,源码与之一一对应:

外层:文件段虚拟化(virtual 布局 + CSS sticky 表头)

纯计算层位于 mobileMultiDiffVirtualizer.ts:computeMobileMultiDiffVirtualLayout顺序累计每个文件的虚拟高度,求出totalHeight撑起滚动内容,再依据scrollTopviewportHeightoverscanMath.max(viewportHeight, 480),见 mobileMultiDiffView.ts#L496)筛出可见文件段,输出virtualTop/virtualHeight与用于正文内滚的innerOffset

在 updateVirtualLayout 中:

  • 文件段落(mobile-multi-diff-file-section)一律按其虚拟顶部绝对定位section.style.top = item.renderTop + 'px'),文件段在滚动范围内始终锚定,因此表头可以放心使用position: sticky实现吸顶(样式见 mobileMultiDiffView.css)。
  • 设计文档给出明确告诫:不要用 JS 在每帧滚动里"搬运"段来模拟 sticky——合成器快速滚动时这种 JS 钉头会漂移,违背"虚拟化管挂载范围与确定性高度、CSS 管 sticky 行为"的分工。
  • DOM 顺序用ensureFileSectionDomOrder维持文件原始顺序(只在必要时insertBefore微调),避免每次布局重排都重挂整个可见集合。

内层:hunk/行级虚拟化(loaded 文件正文)

文件加载完成后,正文区只渲染可见 body entries 范围 + overscan

  1. renderLoadedFileContent依据外层换算出的bodyScrollTop/bodyViewportHeight求可见区间,再用两个二分查找(findFirstBodyEntryEndingAfterfindFirstBodyEntryStartingAtOrAfter)在条目数组上快速定位startIndex/endIndex(mobileMultiDiffView.ts#L813-L853)。
  2. 行与 hunk 头都做成绝对top定位、固定行高的扁平条目;渲染时复用已挂载行(renderedBodyRows: Map<index, HTMLElement>),只回收范围外元素、插入缺失的连续行段(reconcileBodyEntries+insertBodyEntryRun,mobileMultiDiffView.ts#L889-L952),新增行段用<template>一次性拼 HTML 后再整段插入。
  3. 行高确定性是内层的关键:外层总高只由"每文件表头 + hunk 头数 + 行数 × 固定行高"决定(见 computeMobileMultiDiffVirtualLayout 中renderTop === virtualTop的设计),所以正文加载完成不会导致外层滚动位置跳变。

落地这些设计的固定量纲集中在 VIRTUALIZER_METRICS:

const VIRTUALIZER_METRICS: IMobileMultiDiffVirtualizerMetrics = { fileHeaderHeight: 44, // 每文件 sticky 表头高 hunkHeaderHeight: 26, // @@ 头高 rowHeight: 18, // 单行高 bodyVerticalPadding: 0, // 正文垂直留白 placeholderHeight: 76, // 未加载占位最小高 };

加载中的占位与空白禁忌

设计文档有一条硬约束:懒加载可以推迟工作,但可见的文件正文绝不能空白applyVirtualLayout对未加载/加载中的段落渲染稳定的占位(Loading… 空态或高度占位,高度取Math.max(placeholderHeight, bodyViewportHeight)与段剩余高度的较小者,见 mobileMultiDiffView.ts#L391-L400),占位在原生滚动期间始终可见,直到 loaded 后才切换为真实行渲染。每文件滚动位置(bodyScrollTop)持久在 state 上,滚动回来可直接续读。

当前文档记录的一个遗留打磨项:虚拟段卸载再挂载时,尚无每个文件独立的水平滚动状态保留(design 文档 Body Virtualization 一节原文:preserving horizontal scroll state per file when a virtualized section unmounts and remounts),属于后续可继续完善的体验点。


Rendering Ideas:借鉴 Monaco/编辑器虚拟化的实践清单

设计文档 Rendering Ideas 一节汇总了从 Monaco/编辑器虚拟化借鉴的原则,其中部分已在当前源码中落地(标注"Applied"),本文对照给出代码证据:

已落地(Applied)

  • 复用可见行 DOM 而非整片重建reconcileBodyEntries遍历renderedBodyRows只删除越界行,再插入缺失的连续行段,见 mobileMultiDiffView.ts#L889-L952。
  • 批量构建新可见行insertBodyEntryRun先把一整段行的 HTML join 成字符串,经<template>一次性解析后整批insertBefore,见 mobileMultiDiffView.ts#L921-L952。
  • 已挂载文件段保持 DOM 顺序、不在每次布局重排时重挂ensureFileSectionDomOrder只在顺序错位时才insertBefore,见 mobileMultiDiffView.ts#L372-L377。

开放原则(尚未/按需落地)

  • 近可见文件只预取缓存渲染数据,不预渲染 DOM:数据侧已经实现(prefetchNearFile只加载不挂载),DOM 预渲染保持禁用,避免后台布局抢占主线程。
  • 已加载行用绝对top定位,避免对已加载内容做 transform 驱动滚动:本实现已遵守(行条目全部绝对定位);原因是 sticky 表头依赖稳定的段落定位,若用 transform 平移内容会破坏 sticky 基准。
  • 跨虚拟化卸载/重挂周期保留每文件水平滚动状态:同上文遗留项,尚未完成。

小结:一条可复用的移动端长 Diff 审查架构

把设计文档与源码交叉印证,可以得到一套完整的移动端 Diff 审查架构决策链:

  1. 呈现:手机全屏浮层 + 单栏 unified diff;单文件看局部,多文件把全部变更放进一个连续滚动面。
  2. 数据:用极薄的IFileDiffViewData(双 URI + 统计)贯穿变更列表与 Diff 浮层,避免跨层依赖桌面 multi-diff 类型。
  3. 懒加载:两层虚拟化——外层按需挂载文件段并维持确定性总高,内层只渲染可见 hunk/行;读文本、diff、tokenize 全部随可见性增量发生,并受并发额度(可见 2 路 / 预取 1 路)与视口距离(≥2400px 或视口 4 倍内)约束。
  4. 计算:行 diff 复用linesDiffComputers.getDefault(),diff 选项(maxComputationTimeMs: 1000、关闭 trim/moves)按移动端平衡精度与开销;高亮走tokenizeToString,grammar 缺失时自动降级到按语言家族分派的正则着色。
  5. 一致性:generation 计数器 + 每文件loadRequestId双重防抖,确保导航/关闭后迟到的异步结果不会污染已销毁视图。
  6. 分工:JS 管挂载范围与确定性高度,CSS 管 sticky;已知打磨项为跨卸载周期保留每文件水平滚动状态。

如需深入某处实现细节,推荐从三份源码展开阅读:mobileDiffView.ts(单文件视图与统一 diff 生成)、mobileMultiDiffView.ts(多文件双层虚拟化)、mobileMultiDiffVirtualizer.ts(纯布局计算层)。

【免费下载链接】vscodeVisual Studio Code项目地址: https://gitcode.com/GitHub_Trending/vscode6/vscode

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 19:31:38

【C++ 第二阶段:】智能指针与现代 RAII

C 第二阶段&#xff1a;智能指针与现代 RAII 前言 第一阶段我们手动管理过&#xff1a; FILE* → fopen / fclose char* → new[] / delete[]第二阶段的目标不是“把裸指针换成智能指针”&#xff0c;而是建立清晰的资源所有权模型&#xff1a; 谁拥有资源&#xff1f; 谁负责…

作者头像 李华
网站建设 2026/9/8 19:27:20

应用(客户端)开发、框架开发、驱动/系统开发的关系

最近找工作&#xff0c;好多猎头给我推荐的岗位跟我几乎完全不匹配&#xff0c;有鉴于此&#xff0c;特作此文&#xff0c;以供诸猎头参考。编程语言&#xff1a;打个比方&#xff0c;我想写一本书&#xff0c;可以用中文&#xff0c;也可以用英文&#xff0c;还可以用俄文&…

作者头像 李华
网站建设 2026/9/8 19:24:41

opencode:终端里的AI编程代理,从安装到实战全解析

1. 先把opencode放在正确的位置上&#xff1a;它到底是个什么东西 1.1 从终端里长出来的AI编程代理 第一次听说opencode的时候&#xff0c;我其实有点麻木了&#xff0c;毕竟这两年AI编程工具出来一个接一个&#xff0c;从最早的GitHub Copilot到后来的Cursor&#xff0c;再到…

作者头像 李华
网站建设 2026/9/8 19:23:43

硬件工程师如何一次性做好设计?从原理图到量产的避坑指南

1. 为什么硬件总在返工&#xff1a;根源不在调试能力&#xff0c;而在设计习惯先说一个我印象很深的例子。前年团队里来个新人&#xff0c;做一块电源板&#xff0c;原理图改了四版&#xff0c;PCB投板两次&#xff0c;样板焊完上电发现纹波超标&#xff0c;又来回调了好几天。…

作者头像 李华