静态分析代码质量开发工具【免费下载链接】error-proneCatch common Java mistakes as compile-time errors项目地址https://gitcode.com/gh_mirrors/er/error-prone点击查看免费下载本文围绕 Error Prone 项目中的UseBindsBugChecker 展开讲解它如何识别并自动重写那些“仅原样返回单个参数”的Provides/Produces方法将其转换为更声明式、运行时开销更小的Binds方法。读完本文你将掌握该检查器的触发规则、自动修复逻辑、边界情况与使用前提并能据此在 Dagger 2 / Dagger Producers 项目中安全地执行此类重构。一、背景为什么需要一个专门的检查器在 Dagger 2 引入Binds之前将一个绑定委托给另一个绑定delegation的唯一手段是编写一个“形式大于内容”的Provides或Produces方法方法接收一个参数然后原样把它return出去。文档 UseBinds.md 明确指出A Provides or Produces method that returns its single parameter has long been Daggers only mechanism for delegating a binding.这类方法本身是“概念上极其简单的操作”a conceptually simple operation但由于委托逻辑需要通过一个用户自定义的方法体来实现Dagger 在实现层面不得不为其付出与其简单性不相称的额外开销。Binds正是为此而生它提供了一种声明式declarative的委托方式让 Dagger 可以直接在代码生成阶段完成绑定转发从而把实现开销降到最低。因此Error Prone 的结论非常明确Binds should always be preferred over Provides or Produces for delegation.UseBinds检查器就是把这条工程规范固化到编译器里的产物。二、检查器的定位与基本信息该检查器位于core/src/main/java/com/google/errorprone/bugpatterns/inject/dagger/UseBinds.java其核心元信息如下检查器名称UseBinds类型MethodTreeMatcher即针对方法树节点做匹配严重级别WARNING编译期告警不阻断构建摘要信息Binds is a more efficient and declarative mechanism for delegating a binding.从源码可以看出它属于com.google.errorprone.bugpatterns.inject.dagger包与AndroidInjectionBeforeSuper、ProvidesNull、ScopeOnModule等同属 Dagger 相关的一组检查器专门服务于依赖注入场景。三、问题的具体形态两种写法对比原文档给出了最经典的一对对照示例。改造前的Provides写法Provides static Heater provideHeater(ElectricHeater heater) { return heater; }等价且被推荐的Binds写法Binds abstract Heater bindHeater(ElectricHeater impl);两种写法在语义上完全等价——Heater的绑定被委托给ElectricHeater的绑定但Binds版本去掉了方法体直接声明“用ElectricHeater的绑定来满足Heater的绑定”交由 Dagger 注解处理器在生成代码时直接完成转发避免了运行时通过用户方法调用的额外间接层。四、触发规则什么样的方法会被报告UseBinds并不是见到所有Provides都报警它的匹配逻辑非常克制只针对“可以用Binds表达”的委托方法。从 UseBinds.java 的实现看判定分为两层。4.1 SIMPLE_METHOD结构上的“简单委托”判据一个方法必须同时满足以下条件才会被认定是一个可转换为Binds的简单委托方法有且仅有一个参数parameters.size() 1方法体非空Provides/Produces抽象方法本就不是本检查器的目标方法体恰好只有一条语句这条语句必须是return语句return的表达式符号与唯一参数符号完全相同通过ASTHelpers.getSymbol比对符号而非简单字符串比较因此能正确处理参数别名、类型转换等场景。用正则语言概括它匹配的就是形如T f(T x) { return x; }的“透传”方法。测试 UseBindsTest.java 中的notABindsMethod用例验证了反例一个Random provideRandom() { return new Random(); }无参数、方法体内构造新对象的方法不会被改写。4.2 CAN_BE_A_BINDS_METHOD必须是绑定方法在此基础上组合条件CAN_BE_A_BINDS_METHOD allOf(isBindingMethod(), SIMPLE_METHOD)要求该方法必须携带Provides或Produces注解。isBindingMethod()的定义位于 DaggerAnnotations.javaprivate static final MatcherTree BINDING_METHOD anyOf(hasAnnotation(PROVIDES_CLASS_NAME), hasAnnotation(PRODUCES_CLASS_NAME));其中PROVIDES_CLASS_NAME dagger.Provides、PRODUCES_CLASS_NAME dagger.producers.Produces。也就是说该检查器同时覆盖经典 Dagger 模块与 Dagger Producers 模块两种场景。4.3 必须位于 Dagger 2 模块中Binds是 Dagger 2 才有的机制因此检查器在matchMethod中会首先确认方法所在的类是一个Dagger 2 模块if (!IS_DAGGER_2_MODULE.matches(enclosingClass, state)) { return NO_MATCH; }IS_DAGGER_2_MODULE在 Util.java 中定义它匹配标注了dagger.Module且没有Dagger 1 风格参数injects、staticInjections、overrides、addsTo、complete、library的类标注了dagger.producers.ProducerModule的类。这意味着 Dagger 1 模块即使存在透传的Provides方法也不会被报告因为在那里改写为Binds无从谈起。五、自动修复SuggestedFix的两条路径一旦命中检查器并非只输出告警而是通过SuggestedFix提供可一键应用的修复。修复策略取决于模块类自身的形态见 UseBinds.java5.1 路径一委托给新模块当前未实现如果满足以下任一条件检查器会走fixByDelegating()分支模块类带有extends父类子句enclosingClass.getExtendsClause() ! null模块内存在实例方法非static、非abstract的兄弟方法且该兄弟方法本身不是可转换为Binds的简单委托方法。原因很直观Binds方法必须是abstract的若模块内还有需要实例调用或实例状态的方法整个类就不能简单地被改造成抽象类。此时源码中该分支返回NO_MATCH并留有一个 TODOfixByDelegating注释中写着“add a suggested fix by which we make a nested abstract module”即暂时只报告不修复等待未来通过生成一个可被 include 的嵌套抽象模块来实现改写。5.2 路径二就地改造方法默认修复当上述障碍都不存在时检查器走fixByModifyingMethod()生成一个复合修复包含三个动作添加import dagger.Binds;将方法转换为Binds方法convertMethodToBinds把具体模块类改造为抽象类makeConcreteClassAbstract。5.2.1 方法体转换convertMethodToBinds的核心逻辑UseBinds.java在修饰符列表最前面插入Binds保留方法上原有的其他注解通过state.getSourceForNode(annotation)原样取回源码文本处理多绑定multibinding注解的迁移如果原Provides(type ...)/Produces(type ...)带有type参数则按下表替换成对应的多绑定注解原type参数值替换后的注解对应类名SETIntoSetdagger.multibindings.IntoSetSET_VALUESElementsIntoSetdagger.multibindings.ElementsIntoSetMAPIntoMapdagger.multibindings.IntoMap测试用例intoSetMethod正好验证了这一行为Provides IntoSet static Random provideRandom(SecureRandom impl)被改写成Binds IntoSet abstract Random provideRandom(SecureRandom impl)。调整修饰符移除static与final若所在类不是接口interface则补上abstract接口中的方法本身隐式抽象无需显式添加替换方法体把整个方法体替换为分号;形成抽象方法声明。5.2.2 类的抽象化Binds方法必须位于抽象类或接口中因此makeConcreteClassAbstractUtil.java会若类原本是final移除final并添加abstract若原本已是抽象类或本身就是接口则不做任何改动no-op特殊兜底如果原类是final且只有默认构造器改造成抽象类后 Dagger 将无法实例化它因此修复会额外注入一个私有空构造器并附上// no instances注释防止外部实例化。5.3 修复后的效果对照测试 UseBindsTest.java 用BugCheckerRefactoringTestHelper精确验证了输入/输出以staticProvidesMethod用例为例// 输入in/Test.java Module class Test { Provides static Random provideRandom(SecureRandom impl) { return impl; } } // 输出out/Test.java import dagger.Binds; Module abstract class Test { Binds abstract Random provideRandom(SecureRandom impl); }注意输出中自动补充了import dagger.Binds;且类由class变为abstract class。六、边界情况哪些代码不会被修改综合源码与测试以下情况检查器保持原样、不做任何修改expectUnchanged不是绑定方法没有Provides/Produces注解的方法不是简单委托无参数、多个参数、方法体多余一条语句、return的不是参数本身如notABindsMethod用例位于 Dagger 1 模块模块注解带 Dagger 1 参数或根本不是模块模块有父类extends子句存在模块内存在无法抽象的实例方法测试instanceProvidesMethodWithInstanceSibling验证了这一点——当模块中还有一个创建new SecureRandom()的实例Provides方法时整个模块无法抽象化因此不做修改模块内已有可被改写的同类方法instanceProvidesMethodWithStaticSibling显示若兄弟方法本身是静态方法可保留则仍可安全改写目标方法。七、使用前提与运行方式在集成UseBinds检查器时需要注意以下前提仅适用于 Dagger 2 / Dagger ProducersBinds在 Dagger 1 中不存在检查器已通过IS_DAGGER_2_MODULE做了显式防护以 WARNING 级别告警默认不阻断编译适合渐进式引入待代码库清理完毕后可通过 Error Prone 的严重级别配置升级为ERROR修复依赖模块类可抽象化若模块内含实例方法或继承关系复杂当前版本只报告不修复需人工评估是否值得拆出嵌套抽象模块源码 TODO 中已注明此方向运行方式UseBinds是 Error Prone 内置检查器随核心模块一起分发。编译期通过标准 Error Prone 插件加载后构建日志中会出现摘要信息Binds is a more efficient and declarative mechanism for delegating a binding配合-XepPatchChecks:UseBinds之类的补丁模式patch mode可以自动应用上述修复。八、小结UseBinds是一个典型的“规范驱动型”静态检查器它将一条 Dagger 官方推荐的最佳实践委托优先使用Binds编码为可判定、可修复的编译器规则。其实现体现出三个值得借鉴的设计精确的形态匹配通过符号级比较而非文本比较识别“透传方法”误报率低安全的自动修复不仅改写方法本身还联动处理import、类抽象化、多绑定注解迁移与私有构造器兜底保证修复后代码可编译谨慎的保守策略凡涉及无法安全抽象化的情况父类、实例方法兄弟一律退化为“只报告不修复”宁可保守也不产出破坏性修改。对使用 Dagger 2 的团队而言把UseBinds纳入编译期检查可以直接把这类“写法正确但低效”的委托代码扼杀在提交之前。其检查逻辑、修复算法与全部测试用例均可在本仓库 UseBinds.java、UseBindsTest.java 以及配套的 DaggerAnnotations.java 与 Util.java 中深入研读。赞分享静态分析代码质量开发工具【免费下载链接】error-proneCatch common Java mistakes as compile-time errors项目地址https://gitcode.com/gh_mirrors/er/error-prone点击查看免费下载相关推荐Error Prone 检查器 EmptySetMultibindingContributions用 Multibinds 取代返回空集合的 Provides 方法Error Prone 检查器 EmptySetMultibindingContributions用 Multibinds 取代返回空集合的 Provid静态分析代码质量开发工具Error Prone 的 ProvidesMethodOutsideOfModule把 Guice Provides 方法限定在 Module 内Error Prone 的 ProvidesMethodOutsideOfModule把 Guice Provides 方法限定在 Module 内 本篇文静态分析代码质量开发工具Error Prone 检查器详解BindingToUnqualifiedCommonType 与 Guice 简单类型绑定的 Qualifier 规范Error Prone 检查器详解BindingToUnqualifiedCommonType 与 Guice 简单类型绑定的 Qualifier 规范 本篇静态分析代码质量开发工具上一篇rtl_433 硬件兼容性指南RTL-SDR 与 SoapySDR 设备选型、配置与实战下一篇intellij-community 中 Jewel Markdown 渲染管线深度解析两阶段解析渲染模型、GFM 扩展与编辑器预览定制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?