本期视频发布于 2022 年。文中的技术状态、个人经历与观点均为当时情况。
一段从清理无用代码开始的技术分享,为什么最后会讲到绘画和持续学习?在 2022 年 7 月发布的 T Chat 第 6 期中,戴铭给出了一个连贯的回答:围绕同一个问题不断改进解法,会迫使人进入新的知识领域;把理解画出来、讲给别人听,又会暴露自己还没有想清楚的地方。
第一段录播约 33 分钟,从大型移动工程的优化需求,走到 LLVM 中间表示、Pass 和开发反馈速度。第二段约 57 分钟,讨论他在快手的工作、此前的经历、绘画与编程的结合、团队选择,以及对后续技术方向的兴趣。
技术分享:为什么从编译过程寻找优化空间
长期演进的应用,需要重新审视留下来的代码
戴铭先提出两个问题:怎样优化积累多年的移动工程,怎样在工程越来越大时保持开发效率。旧工程可能留下不再需要的功能和代码,而客户端的交付方式决定了这些内容会随安装包来到用户设备上。因此,清理工作不仅影响代码维护,也关系到包体积及用户设备上的资源使用。
他选择从无用代码切入,并逐步比较不同观察位置能够提供什么信息。分享的主线是:先看编译产物,再看源码结构,接着观察实际运行,最后考虑能否在编译过程中定制自己需要的数据。
编译产物里的引用关系,并不等于完整的运行行为
第一种路径从产物入手。戴铭介绍,开发者可以结合链接结果与 Objective-C 相关的引用信息,比较有哪些方法、哪些方法在产物中被引用,以寻找可能不再使用的部分。
但 Objective-C 存在运行时调用,方法是否被执行不能只靠一类静态引用判断。通过选择子、字符串或框架回调触发的行为,未必能被简单的集合相减可靠捕捉。因此,这类方法更适合产生待检查的候选项,不能把所有没有被某种规则识别到的调用都直接删掉。
这也是分享中第一个重要的边界:工具从某个位置没有看到使用证据,与程序永远不会用到它,是两件不同的事。
回到源码,理解调用是怎样写出来的
为补充产物侧的信息,戴铭转向编译前端。他以 Clang 及其处理的语法结构说明,分析工具可以遍历节点,识别特定调用形式,再读取相应参数。与只看最终产物相比,这样能利用源码中保留的更多表达信息。
他用 performSelector 一类调用方式,以及事件、定时器等相关场景解释需求。重点是根据不同调用机制设计对应规则,而不是期待一种扫描方式覆盖所有动态行为。源码层面的分析仍然属于静态观察,它能帮助缩小范围,但不能替代运行验证。
从“代码可能调用”走到“运行中观察到了什么”
即使代码里写着调用关系,真实用户也可能没有走到对应路径。戴铭因此继续讨论动态观察:在页面上增加埋点,能够知道页面是否使用,但粒度较粗;观察类的初始化,也不容易区分一个大类中究竟哪些方法被执行。
代码覆盖率提供了更细的观察方式。他介绍通过构建选项生成运行数据,再用配套工具生成报告。这样的结果可以帮助了解测试或一段运行期间覆盖了哪些代码,但也会带来采集、构建和运行成本。选择观察粒度,必须结合要回答的问题。
对清理代码而言,这里还有一个不能省略的限制:某段代码在一次测试或一批用户样本里没有被执行,不足以单独证明它可以安全删除。样本、功能入口与低频场景仍需另行确认。
插桩的重点,是选择需要的观察位置
随后,戴铭把话题带到 fuzzing 所使用的覆盖反馈及相关插桩能力,介绍了在函数、基本块或控制流边等位置加入记录逻辑的思路,并概览相关工具路线。他真正关心的,是能否用更合适的粒度观察函数执行,而不必承担与目标无关的全部记录工作。
这里需要区分两类概念。Clang 的源码覆盖率和 SanitizerCoverage 是不同机制;后者可以在函数、基本块或边级别插入对自定义函数的调用。不能把所有代码覆盖率工具统称为由 fuzzing 实现,也不能将它们的开销一概而论。Clang 源码覆盖率文档;SanitizerCoverage 文档
分享通过回调说明,程序执行到相应位置时,开发者可以记录所需信息。戴铭同时提出了自己的工程顾虑:现有方式在其场景下的编译成本和定制空间仍不理想,如果只需要少量特定记录,能否进一步减少插入的工作?这个问题引出了自定义 LLVM Pass。
LLVM IR 提供了源码与机器码之间的工作层
在讲 Pass 之前,戴铭先解释 LLVM IR,即编译流程中的一种中间表示。它让开发者有机会在程序已经离开原始源码、但尚未完成目标代码生成的阶段分析或改变程序。
他按模块、函数、基本块与指令展开这一结构:模块中包含函数和全局信息;函数的主体组织为基本块;基本块内排列具体指令,并通过控制流与其他块连接。理解这些关系,才能知道分析或插桩应该放在哪里。
分享也用 Objective-C 运行时帮助听众理解,高级语言特征在更低层会有相应的实现形式。不过,不能把 IR 想成完全保留了所有源码语义的抽象语法树,也不应将其描述为不受目标平台约束的机器码。本文保留的是编译层次和结构关系,具体表示应以所用工具链的语言参考为准。LLVM IR 参考
Pass 怎样进入编译流程
戴铭接着介绍 Pass:把某项分析或转换组织成可接入编译流程的处理步骤。分析可以收集程序信息,转换则可能改变 IR,例如加入记录函数的调用。他谈到 C++ 实现、C 接口及语言封装的选择,也说明开发者需要关注接口和版本条件。
Pass 并非彼此毫无关系。它们可以组成执行流程,并通过管理机制协调分析结果、执行顺序及相关行为。分享介绍了 Pass Manager、运行入口与前后回调等概念,帮助听众理解一个处理步骤怎样被调用,而不是要求业务代码直接承担这些控制工作。
官方文档也区分分析 Pass 与转换 Pass,并说明插件加载及管理接口。回顾中不复制录播里的类名拼写和构建参数作为通用命令;需要实践时,应对应使用的 LLVM 版本核对。LLVM Pass 编写文档
一个记录函数,怎样被放进目标程序
示例回到最初的需求:想知道某些函数在运行时有没有执行。戴铭展示的思路是,先在模块中取得准备调用的记录函数,再遍历相应函数与基本块,在选定的位置插入调用指令。记录逻辑由此进入目标程序,而不必要求业务开发者逐处手写日志。
这使插桩范围、位置和内容有了进一步定制的可能,也将实现责任移到了构建工具一侧。示例中的指令规模说明了精简的方向,但不应把演示中的数字写成所有工程都能达到的固定开销。
他随后谈到如何把自定义处理接入 Xcode 构建环境,包括使用相应工具链及选项。实际工程仍需要验证生成结果、调试方式及运行表现;一次演示不足以证明它适合所有线上应用。
缩短反馈周期:从代码注入到 IR 执行的探索
技术分享后半段转向开发提效。理想体验是修改代码后尽快看到结果,减少等待整个工程重新构建的时间。戴铭先比较脚本执行与原生开发中的代码注入思路,并用工具示例解释一种流程:监听文件变化,编译变更部分,加载新的代码,再让运行中的应用使用更新后的实现。
这段讨论涉及动态库、符号替换及相关运行机制。其重点是解释反馈为什么能够变快,同时提醒听众,这类工具有适用范围、接入要求和实现限制。开发期能够使用的方式,并不能直接推导出应用发布或线上更新同样可行。
接下来,他提出从 IR 层继续探索的想法:如果需要加入辅助行为,可以尝试在中间阶段完成,减少对业务源码的侵入;再研究怎样解释执行相应的中间表示。他借 IR 的结构与静态单赋值形式说明研究这一层的吸引力,并介绍生成、连接和读取 bitcode 后组织执行的过程。
这是当时的技术探索方向,录播也没有将所有边界展开。本文保留其解决问题的思路,不把它写成已经验证的通用热更新方案,更不据此作出 App Store 审核承诺。
成长对谈:同一个问题,也可以不断打开新领域
工作方向与长期收益
第二段先回应观众的问题。面对学习 Objective-C 还是 Swift 的提问,戴铭在当时倾向于 Swift;面对自建工具链能否用于上架的问题,他没有给出审核一定通过的保证,而是区分线下分析、开发验证与实际提交的构建路径。这两段回答都应保留其时间与适用范围。
介绍在快手的工作时,他列出几个方向:iPad 适配、在已有工程中引入 Swift,以及编译相关的长期研究。适配看起来像一项界面任务,真正落入历史工程后,却会遇到旧布局方式、窗口管理和代码组织等困难;刚进入团队时,还要同时熟悉工程与协作者。
谈到 Swift,他重视语言及工具帮助开发者更早发现一部分错误的价值,也关心表达方式和长期维护。这是他解释为什么投入新语言的理由,并不意味着采用一种语言就能消除全部内存或运行时问题。他将自己的角色概括为继续做好技术专家,推动能够长期产生收益的工作。
不同经历留下的知识,会在后来重新连接
主持人随后请他回顾成长经历。戴铭从大学期间接项目讲起:面对实际需求,学习服务端、脚本和客户端相关技术,先尝试做出来,再逐渐理解背后的概念。他也讲到创业公司中同时承担开发、产品、设计与管理,以及后来不同工作环境中的经历。
他并没有把这段过程包装成一条事先规划好的直线。有些阶段能接触很多事情,却缺少大规模业务带来的深层技术问题。进入用户规模更大的互联网项目后,反馈和故障变得更密集,对工程能力的要求随之提高,他也因此感受到更强的学习压力和成长。
早期看似分散的经历仍有作用:服务端、网页、脚本和客户端的知识,在后来会找到新的连接点。主持人从中提炼出成长不必过早定型的看法;戴铭则继续用具体经历说明,问题和实践怎样改变自己的理解。
走过弯路之后,还要继续改进解法
谈到对成长帮助最大的事情,戴铭给出三条相互关联的经验:从弯路中学习,围绕一个问题不断迭代方案,以及记录与分享。
他以早期做复杂列表为例。不同单元格的状态在滚动过程中出现问题,自己反复尝试后才逐渐理解相关机制。正是实际困难,使继续研究原理变得有动力。他所说的弯路,是对既有经历的理解,并不是主张在可以借助资料时刻意重复错误。
但单靠经历困难仍不够。包体积优化就是一个可以持续深挖的问题:一种方法已经被使用后,如果还想取得新的改进,就需要转向新的观察位置,从产物分析到编译前端,再到链接和 IR 插桩。问题看似相同,追求更好解法的过程却扩展了自己的知识范围。
讲不清楚,有时是还没有理解清楚
为了继续研究,戴铭会查资料、读书,并把有用内容记录下来。分享则是下一轮检验:同一个主题讲过多次,仍然会发现某处说不清。原因可能不仅是表达技巧,也可能是对问题的理解还不够完整。
他回忆参加早期技术交流、研究新的技术思路并向团队分享的经历。通过多次组织和解释,原本模糊的关系逐渐变得清楚。听众也不必很多,两三个人提出的问题,就可能暴露讲者没有意识到的细节。图里颜色代表什么、步骤为什么这样安排,都能成为继续推敲的入口。
关于学习渠道,他介绍自己把资料、链接、书单、播客与开发手册汇集进一个 macOS 工具的尝试,也提到整理开发知识图谱。对他而言,这些材料既服务分享,也帮助日常查阅,不需要假装所有语法和接口都已记住。
另一个方法是关注自己认可的开发者,观察他们正在研究什么、怎样解决问题。这样的关注提供线索,再结合自己的需求进入具体资料,而不是用追随别人替代实践。
绘画怎样成为技术表达的一部分
进入绘画话题后,戴铭先谈取舍。他把较多时间留给技术和绘画,也就减少了其他活动;临近已经承诺的分享时,还会暂时收拢输入,将精力集中在完成内容上。这是他的阶段性工作方式,并非适合所有人的固定时间管理规则。
编程与绘画真正连接起来,来自一次解释自动布局问题的需要。他回忆,为了说明线上问题及相应机制,自己查阅了大量资料,直到进一步理解原理后,仍觉得只靠口头描述难以让人跟上。于是,他把关系画在纸上,再在白板上一边画一边讲。
画图让过程逐步展开,听众可以跟着提问,他也能立即回应。之后,他更愿意用图形解释技术。绘画还帮助他形成形象记忆,将概念与可见的结构连接起来。随着练习增加,构思仍然重要,但动手绘制不再像初学时那样每一步都需要重新摸索。
团队文化,从交流与实际工作中感受
谈到快手的团队氛围,戴铭用自己加入前去做分享的经历举例:参与者多,提问也多,让他感受到同事对技术的兴趣。加入之后,他也参与团队技术内容的整理与分享,并谈到启动优化、性能工具和开源等工作。
这些例子呈现的是他在当时所观察到的团队,不代表对整个公司作统一评价。更具体的判断依据是,技术问题是否有人愿意认真讨论,实践能否沉淀为可共享的材料,以及团队是否愿意投入长期建设。
招聘问答中,他区分了校招与有经验候选人的观察重点。对校招,他重视学习能力,并结合带新人的经验说明其影响;对有工作经历的人,基础与技术能力之外,还要考虑协作方式和团队是否合适。录播中也提到了当时的招聘信息,这些时效性信息不在此延用。
主持人进一步追问选择工作时看什么,戴铭补充了对公司发展方向的认同。技术兴趣、团队相处方式和业务方向共同影响选择,不能只根据一个抽象的公司标签判断自己是否适合。
困惑不会因为擅长某件事就消失
当话题转向困难时,戴铭选了一个持续多年的绘画问题:为什么掌握了一些线条、颜色、光影和结构知识,作品仍然不够好看?他尝试过从不同要素寻找答案,也不断观察自己喜欢的作品,想理解作者为什么这样安排。
他坦言,投入过深也会影响自己的生活节奏;这不是对透支身体的鼓励。真正值得保留的是他面对问题的方式:没有因为暂时解释不了就停止观察,也没有把某一条技巧当成能解决全部问题的公式。
后来,阅读中关于秩序与混乱关系的一种理解,使他重新连接了此前的经验。他认为,如果没有前面那些尝试,同样一句话未必会给自己带来如此具体的体会。主持人由此总结,较平稳的心态和向有经验的人学习,都能帮助人在困惑中继续推进。这是一段个人学习经验,不是对美学问题的唯一答案。
对未来的兴趣,仍围绕工具与底层能力展开
最后,戴铭介绍当时关注的方向。他谈到一个本地发布网站与订阅内容的工具,以及 IPFS 等去中心化技术带来的想象空间。这里反映的是他对客户端能力扩展的兴趣;访谈中的未来判断不能当成服务端必然消失、内容自动永久可用或无需维护的技术结论。
另一个更贴近日常工程的方向,是编译、IDE 和文本编辑器。他希望分析结果能够更自然地回到开发者正在使用的工具里,减少在不同平台之间切换的割裂感。前半场的编译分析与开发提效,也在这里回到了完整的开发体验。
他还提到对游戏模拟器和虚拟机实现的兴趣:旧软件怎样在新环境中运行,本身就能引出许多值得研究的系统问题。谈到职业方向,他当时仍愿意围绕苹果生态继续投入,因为语言、工具、客户端产品与自己的兴趣存在交集;同时也给未来出现更合适的技术环境留下空间。
主持人最后提醒,这里的客户端应作更广义的理解,不只是编写某一种应用界面,也包括工具、执行环境及系统能力。本文将这一讨论保留为当时对探索方向的建议,不把它扩展成任何岗位前景的保证。

