Psalm 死代码检测:UnusedClass 问题的原理、触发条件与 `@api` 修复实践

发布时间:2026/10/13 2:19:49

Psalm 死代码检测:UnusedClass 问题的原理、触发条件与 `@api` 修复实践 开发工具代码质量质量保障【免费下载链接】psalmA PHP static analysis tool for finding errors and security vulnerabilities in PHP applications项目地址https://gitcode.com/gh_mirrors/ps/psalm点击查看免费下载本指南围绕 Psalm 的UnusedClass问题展开讲解它在何种检测模式下被报告、底层基于代码引用图CodeUseGraph的死代码分析原理以及正确区分「真实公共 API」与「死代码」并选用api/psalm-api注解修复的完整实战方案。读完本文你将能准确解读UnusedClass报告理解为什么psalm-suppress无法替代api并能在自己的项目中正确配置死代码检测。一、UnusedClass 是什么UnusedClass是 Psalm 在开启死代码检测--find-dead-code后发出的一种问题报告当 Psalm 在整个被分析代码库中找不到某个类的任何使用痕迹时就会报告该类「从未被使用」。以原文档给出的最小示例为例?php final class A {} final class B {} $a new A();在--find-dead-code模式下Psalm 会发现类A被new A()实例化、属于「被使用」而类B在整个代码库中没有任何引用于是对B报告UnusedClass。这里类B就是典型的死代码。从源码看该问题的定义位于 src/Psalm/Issue/UnusedClass.php它继承自ClassIssue其ERROR_LEVEL为-2、SHORTCODE为75。ERROR_LEVEL为负值意味着该问题默认不参与常规的错误级别报告只有在显式开启死代码检测时才会被产出这与「UnusedClass仅在--find-dead-code下出现」的文档描述完全吻合。二、如何开启死代码检测UnusedClass属于 Psalm 的Unused*问题族必须在死代码检测开启时才会被报告。开启方式有两种2.1 命令行参数在运行 psalm 时追加--find-dead-code./psalm --find-dead-code从 src/Psalm/Internal/Cli/Psalm.php 的参数解析逻辑可以确认--find-dead-code会被转换为--find-unused-codeauto若显式传入--find-dead-codealways则转换为--find-unused-codealways--find-unused-code同样支持auto/always两种取值传入always时无论代码是否被条件性引用都会执行查找。其中auto模式只检查确实可以被分析的范围而always模式会强制执行更彻底的死代码扫描。实际使用中日常 CI 通常采用--find-dead-code即auto。2.2 配置文件也可以在psalm.xml中通过findUnusedCode属性全局开启psalm findUnusedCodetrue 对应的 XML Schema 定义在 config.xsd该属性类型为xs:boolean、默认值为true。需要说明的是配置层面的findUnusedCode最终同样会走shouldFindUnusedCode()的auto分支见 Psalm.php因此开启后即可看到UnusedClass报告。三、底层原理Psalm 如何判断一个类「未被使用」理解UnusedClass的关键在于理解 Psalm 的死代码检测算法。Psalm 并非简单地逐个类搜索引用而是构建了一张代码使用关系有向图然后从「根节点」出发做可达性分析。3.1 代码使用图CodeUseGraph核心实现位于 src/Psalm/Internal/Codebase/CodeUseGraph.php其类注释明确说明了设计每个代码元素类、函数、方法、属性、类常量都是一个图节点节点以kind member字符串标识每条边A - B的含义是「如果 A 是存活的那么 B 被 A 使用」「是否被使用」通过从一组根节点出发的可达性搜索来判定。根节点包括公共 API、顶层文件代码、自由函数Psalm 永不将自由函数报告为未使用、以及项目之外的代码由于采用可达性分析只被其他未使用代码引用的代码包括互相引用的死代码环也会被正确判定为未使用。也就是说Psalm 报告UnusedClass的依据不是「有没有字符串形式的引用」而是「该类是否可以从根节点真正活着的代码出发被触达」。3.2 报告位置与判定条件UnusedClass的具体报告逻辑位于 src/Psalm/Internal/Codebase/ClassLikes.php。在开启$find_unused_code后Psalm 遍历项目目录内的所有类并执行这样的判断if ($code_use_graph-isUsed($class_node) ($classlike_storage-public_api || $code_use_graph-isReferenced($class_node)) ) { // 类被使用继续检查其方法、属性的引用 $this-checkMethodReferences($classlike_storage, $methods); $this-checkPropertyReferences($classlike_storage); } else { // 类未被使用报告 UnusedClass IssueBuffer::maybeAdd( new UnusedClass( Class . $classlike_storage-name . is never used, $classlike_storage-location, $classlike_storage-name, ), $classlike_storage-suppressed_issues, ); }从这段源码可以提炼出两个关键判定因素图可达性类节点是否从根节点出发被「使用」isUsed或「引用」isReferenced公共 API 标记类的存储信息中public_api标志是否为真。只有当「既未在图中被触达又未被标记为公共 API」时Psalm 才会生成UnusedClass。public_api标志正是由api/psalm-api注解驱动的详见下文第四节。类存储结构中的该字段定义于 src/Psalm/Storage/ClassLikeStorage.php。四、如何修复只有两条正确路径当UnusedClass或其他Unused*问题被报告时Psalm 文档明确指出只有两条正确的处理路径见 supported_annotations.md该类确实属于公共 API—— 它会被被分析代码库之外的代码消费例如库的入口类仅由框架、容器DI 容器或插件加载器实例化的类。对于这类类应使用api或等价写法psalm-api标注。注意该注解只应用于真正的公共面不能为了消除告警而滥用。该类是死代码—— 它不存在任何外部消费者。此时应删除该类以及所有仅通过它才能触达的代码方法、属性、依赖它的其他类等因为一旦删除入口这些代码同样是不可达的死代码。4.1 正确示例标记公共入口类?php /** * 这个类被框架通过反射实例化代码库内没有直接引用。 * * psalm-api */ final class A {} final class B {} $a new A();在加上psalm-api之后类A虽然代码库内没有任何new A()但会被视为公共 API 而不再报告UnusedClass类B仍会继续被报告因为它既无引用、也无注解。4.2api与psalm-api的解析api与psalm-api是等价的两种写法。从源码看Psalm 在解析类级 docblock 时对二者一视同仁src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php$info-public_api isset($parsed_docblock-tags[psalm-api]) || isset($parsed_docblock-tags[api]);随后该标记被写入类存储的public_api字段见 ClassLikeNodeScanner.php从而在 ClassLikes.php 的判定中让类直接通过「公共 API」这一分支、免于报告。方法与属性的api解析逻辑与之类似FunctionLikeDocblockParser.php。五、api的完整使用规则原文档引用了 supported_annotations.md 中的api/psalm-api完整指南核心规则如下。5.1 注解位置优先标注整个类对于被报告为未使用的方法或属性api可以放在该成员上也可以放在整个类上。但几乎总是应该选择标注整个类一个被外部消费的成员必然属于一个被外部消费的类一个类级别的注解即可覆盖其全部公共 API类本身、所有公共方法、公共属性。反之如果一个类没有api标记、而类内某个成员被报告为未使用那么该成员几乎总是真正的死代码 —— 正确做法是删除它而不是单独给这个成员加注解。只有一种少见情况适合单独标注成员类本身不是公共 API但其中某一个成员确实是公共 API例如类供内部使用、但某个回调/钩子方法被外部调用。5.2 外部接口实现自动视为公共表面一个特殊且重要的规则实现了项目外部定义的接口或继承项目外部基类的类会被自动视为公共表面。原因在于这类类可以通过外部的类型被实例化和调用Psalm 无法在项目内找到其全部消费方因此其覆写方法会被视为「已使用」无需显式api见 supported_annotations.md。5.3 不要在api位置使用psalm-suppresspsalm-suppress UnusedClass或其他Unused*抑制注解不能替代api。原因有二抑制注解只静默报告并不会把类标记为「已使用」由于类未被标记为使用它引用的其他符号仍然可能继续被报告为未使用导致问题不断向下游蔓延。在 supported_annotations.md 中对此有明确告诫。换句话说抑制是「掩盖症状」api是「声明事实」只有后者才能让整个引用链正确存活。六、实战建议与判断清单综合原文档、注解指南与源码实现处理一条UnusedClass报告时可以按以下清单决策情况判定处理方式类被框架/容器/插件加载器通过反射或魔法方式实例化真实公共 API加api/psalm-api标注整个类类是库的对外入口、被项目外部代码使用真实公共 API加api/psalm-api类实现了项目外部的接口/继承外部基类自动视为公共表面无需注解确认无其他问题即可类仅被项目内部代码使用但内部引用也不存在死代码删除该类及其仅可达的关联代码想消除告警但类既非公共 API 也非外部使用死代码不要用psalm-suppress应删除值得强调的是死代码检测的价值不仅在于清理无用类更在于防止公共 API 面被误删。一个没有api标记的库入口类在--find-dead-code下会被报告为UnusedClass如果开发者不加区分地删除就会破坏库的对外契约。因此为库的入口类和框架钩子类主动加上api是 Psalm 死代码检测实践中的关键习惯。Psalm 仓库自身的插件测试也大量使用这一模式来声明「由反射实例化的插件类」——例如 tests/Config/Plugin/Hook/AfterAnalysis.php 中的psalm-suppress UnusedClass registered as a plugin via test config, instantiated by reflection注释即为通过抑制注解处理插件类未使用报告的现实案例这也反向印证了当类确实由反射加载、但又不想在代码库内声明其为公共 API 时抑制是测试场景下的临时手段。七、相关参考问题清单索引docs/running_psalm/issues.mdapi/psalm-api完整指南docs/annotating_code/supported_annotations.md命令行参数详解docs/running_psalm/command_line_usage.md错误级别与问题配置docs/running_psalm/error_levels.md配置文件说明docs/running_psalm/configuration.md问题定义源码src/Psalm/Issue/UnusedClass.php死代码判定实现src/Psalm/Internal/Codebase/ClassLikes.php代码使用图实现src/Psalm/Internal/Codebase/CodeUseGraph.phpapi注解解析src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php赞分享开发工具代码质量质量保障【免费下载链接】psalmA PHP static analysis tool for finding errors and security vulnerabilities in PHP applications项目地址https://gitcode.com/gh_mirrors/ps/psalm点击查看免费下载相关推荐Psalm ParadoxicalCondition 问题详解悖论条件的检测原理与修复实践Psalm ParadoxicalCondition 问题详解悖论条件的检测原理与修复实践 本文围绕 Psalm 官方文档 docs/running_psal开发工具代码质量质量保障Psalm 死代码分析PossiblyUnusedMethod 的触发原理、api 正确修复与误报边界Psalm 死代码分析PossiblyUnusedMethod 的触发原理、 api 正确修复与误报边界 导读 PossiblyUnusedMethod 是开发工具代码质量质量保障Psalm 纯函数检查中的 ImpureVariable 问题原理、触发场景与修复实践Psalm 纯函数检查中的 ImpureVariable 问题原理、触发场景与修复实践 ImpureVariable 是 Psalm 在分析纯函数 psa开发工具代码质量质量保障上一篇BiliDownloader一键解锁B站视频离线收藏神器下一篇5大核心功能解锁OpenVINO™ AI插件为Audacity带来智能音频处理新体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/13 2:19:49

用Excel制作标准波士顿矩阵图:散点图与误差线实战教程

Excel波士顿矩阵图一直是商业分析里的高频需求,几乎每份战略汇报PPT里都会出现一张。但真正能用手头Excel做出来的人,说实话不多——大多数教程要么只讲原理不讲操作,要么上来就让你装各种插件,最离谱的是直接让你画个四宫格填颜色…

2026/10/13 2:14:48

LeetCode 19:删除链表倒数第 N 个节点,双指针与虚拟头节点详解

LeetBook 上这道删除链表倒数第 N 个结点的题,我前前后后教过不少人和自己重写过好几遍,看着简单,其实特别容易在边界条件上翻车。写这篇东西的目的很纯粹:把这道题的前因后果、两种主流解法、还有我踩过的坑全部摊开来聊一遍&…

2026/10/13 3:29:54

MFC TCP通信实战:选型、拆包重连与避坑指南

简介:基于MFC的TCP通信项目源码包,面向Windows平台下希望掌握网络编程的C开发者,以客户端/服务器模型为蓝本,聚焦于使用CSocket类完成可靠连接、文件分块发送与接收,并引入多线程处理并发通信。压缩包共含36个文件&…

2026/10/13 3:29:53

用Flask搭建个人博客:CMS全流程实战与避坑指南

简介:一份基于Flask框架搭建个人博客的入门级项目源码,面向正在学习Python Web开发、希望快速上手轻量级CMS的初学者,可作为课程设计或个人练手的完整参考,既能巩固基础知识,也能作为小型项目的起点。项目围绕博客文章…

2026/10/13 3:29:53

HP Gen8装2008找不到磁盘?B120i/B320i驱动加载实战指南

简介:面向HP ProLiant Gen8服务器维护场景,这套驱动专门解决B120i与B320i动态智能阵列控制器在Windows Server 2008 R2/X64下无法识别磁盘、安装系统找不到存储设备等问题,同时适配DL388e gen8等B320i机型,并对应hpsa2驱动模块&am…

2026/10/13 3:29:53

MT4 Manager API开发实战:连接模型、业务落地与避坑指南

简介:MT4 Manager API 是专为外汇应用程序开发设计的 MetaTrader 4 经理管理接口,面向 MT4 二次开发人员与金融软件工程师,用于账户管理、订单监控、交易数据分析等后端集成场景。整包含 128 个文件,以 46 个 h 头文件、43 个 cpp…

2026/10/13 3:29:53

platform-tools 28.0.1部署实战:adb调试与fastboot刷机避坑指南

简介:这是面向 Android 开发与测试人员的 ADB 平台工具包,版本 r28.0.1,适用于 Windows 环境。借助其中的 ADB 命令行工具,开发者可完成设备连接与管理、APK 安装卸载、文件上传下载、日志抓取、shell 访问及多设备并行调试&#…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/13 0:04:41

基于Triton部署YOLO目标检测:从训练脚本到线上服务

简介:这份资源面向希望将目标检测模型落地到生产环境的深度学习开发者与算法工程师,聚焦于用 Triton Inference Server 部署 YOLOv9 的完整实战方案,解决从训练权重到线上推理服务之间的工程化难题。压缩包共 16 个文件,约 886KB&…

2026/10/13 0:04:41

手把手教你云端部署Qwen-Image-2.1:从GPU选型到在线文生图服务

Qwen-Image-2.1 这个开源的图像生成模型,最近在内容创作和 AI 应用圈子里热度相当高。它属于通义 Qwen 系列里的视觉生成分支,让我眼前一亮的是它对中文长文本的理解能力——以前用那些老方案生成带中文文字的图,十个里九个是“乱码印刷体”&…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑