移动开发中的Code Smell:本质、类型与修复原则

作者: IT Sectr 发布日期: 2026-05-13 阅读时间: 9 分钟

Code Smell 是代码中的一个表面迹象,预示着应用程序设计或架构中的潜在问题。该术语由 Kent Beck 提出,并由 Martin Fowler 在《Refactoring: Improving the Design of Existing Code》一书中推广。根据 Martin Fowler 的说法,代码坏味不一定意味着错误,但几乎总是表明需要重构以提高可维护性。

要点

  • Code Smell — 代码问题的外部迹象,不是错误,但会妨碍维护和开发
  • 长方法 — 最常见的坏味:做得太多、需要拆分为多个的方法
  • 大类 — 违反单一职责原则、包含不同领域逻辑的类
  • Duplicate code — 重复的代码片段,修改时需要在多处进行更改
  • Feature envy — 使用其他类数据多于自身数据的方法

什么是 Code Smell

Code Smell(代码坏味)— 是对源代码中很可能表明更深层次问题的症状的一种比喻。该术语本身没有正式的定义 — 它是一种基于程序员经验的启发式方法。Martin Fowler 和 Kent Beck 于 1999 年在《Refactoring》一书中首次系统化了 22 种坏味,并且大多数在几十年后仍然具有现实意义。

理解 Code Smell 和错误之间的区别很重要。坏味不是错误:代码可以编译、运行并产生正确的结果。问题在于这种代码难以阅读、修改和测试。随着时间的推移,每次更改的成本都会增加,而对重构正确性的信心则会下降。静态分析工具(SonarQube、Detekt、SwiftLint)可以自动检测许多坏味。

Code Smell 的启发式特性意味着并非每个长方法都需要拆分,也并非每个大类都需要重构。决定由程序员做出,评估上下文:更改频率、模块的关键性、开发计划。经验丰富的工程师会凭直觉闻到坏味 — 代码「闻起来不舒服」,尽管形式上所有规则都得到了遵守。

Code Smell 的主要类型

Fowler 区分了 22 种坏味,它们分为几个类别。对于移动开发,最相关的是结构性坏味、面向对象设计坏味以及与平台限制相关的特定问题。让我们用实际案例中的例子来研究每个组。

结构性坏味

Long Method(长方法)— 移动应用中最常见的坏味。带有注册表单的屏幕通常包含一个 200+ 行的 setupUI 方法,它创建所有 View、设置约束、订阅事件并处理错误。解决方案:按逻辑块拆分为方法 — configureEmailField、configurePasswordField、setupConstraints、bindViewModel。

Large Class(大类)— 负责显示、导航、业务逻辑和网络通信的 Activity 或 ViewController。这样的类违反了单一职责原则,并包含数十个字段和方法。在 Android 中,这通常是一个包含不同屏幕逻辑的 1000+ 行的 Fragment。解决方案:分离 presenter/ViewModel,将网络工作转移到 repository,将导航转移到协调器。

Duplicate Code(代码重复)— 在应用程序的不同部分复制相同的块。典型示例:在目录和收藏夹中显示产品卡片的两个屏幕。如果显示逻辑被复制,在一个地方修复错误不会在另一个地方修复它。解决方案:将通用逻辑转移到可重用组件或扩展中。

面向对象设计坏味

Feature Envy(嫉妒其他类)— 一个类的方法密集地使用另一个类的数据。在 Android 中,这表现为 ViewModel 直接访问 User 模型的字段,而不是调用模型的方法。信号:如果可以将方法移动到其数据被使用的类中 — 请移动它。Switch Statements(条件链)— 检查对象类型的 switch 结构或 if-else 链。应该使用多态性或策略模式代替。

Data Class — 只存储数据但不包含行为的类。数据类(在 Kotlin 中)或结构体(在 Swift 中)本身并不是坏味。当处理这些数据的业务逻辑分散在整个代码库中而不是被封裝时,问题就出现了。Refused Bequest — 继承者不使用父类的大部分方法,并用空实现覆盖它们。不正确继承的标志:用组合替换继承。

移动开发中的坏味

God Activity / God Fragment — 了解一切的 Activity 或 Fragment:生命周期、数据、导航、权限、DI。这是应用程序中维护成本最高的类。解决方案:MVVM、MVI 或 Clean Architecture 等架构模式分担了责任。Giant ViewController — iOS 的类似物,其中 UIViewController 包含屏幕的所有逻辑,通常超过 500 行。

Hardcoded Resources — 直接嵌入代码中的字符串、颜色、尺寸、API URL。在 Android 中,这违反了 R 资源系统的使用,在 iOS 中 — 违反了 NSLocalizedString 和 Asset Catalog。修复:将所有字符串移动到 strings.xml 或 Localizable.strings,URL 移动到配置文件,尺寸移动到 dimens。Leaking Context — 对 Activity 或 ViewController 的引用保留时间超过组件本身的寿命。导致内存泄漏和崩溃。解决方案:弱引用、Jetpack Lifecycle、RxSwift DisposeBag。

坏味出现位置解决方案
Long MethodAndroid/iOSExtract Method、拆分
Large ClassActivity、ViewControllerMVVM、VIPER、Clean Arch
Duplicate Code任何屏幕Shared Component、DRY
Feature EnvyViewModel、PresenterMove Method
Leaking ContextAndroid生命周期感知组件

如何发现 Code Smell

Code review — 检测坏味最可靠的方法。人眼能注意到自动分析器遗漏的不自然结构。如果团队使用典型坏味的检查清单,代码审查的效率会提高。建议在一次会话中检查不超过 200–400 行代码 — 超过此阈值后,注意力会下降,坏味开始被遗漏。

静态分析 自动化了结构性坏味的搜索。对于 Android,标准工具是 Detekt(Kotlin)和 Android Lint,对于 iOS — SwiftLint 和 SonarQube。这些工具可以找到长方法、大类、代码重复和许多其他问题。根据项目配置规则很重要 — 默认配置通常过于严格,或者相反,会遗漏关键的坏味。

代码度量 提供了客观标准:Cyclomatic Complexity(阈值 >10 需要注意)、Lines of Code per Method(阈值 >30)、Depth of Inheritance(>3 — 值得深思)。CodeMetrics(Xcode)和 Gradle Metrics Plugin 等工具构建度量随时间变化的图表。如果方法的复杂度在最近一次提交后从 5 增加到 15 — 这是重构的信号。

kotlin
// 示例:Cyclomatic 复杂度 = 7 的方法(高于阈值 5)
fun processOrder(order: Order) {
    if (order.status == Status.NEW) { /* 10 行 */ }
    else if (order.status == Status.PAID) { /* 15 行 */ }
    else if (order.status == Status.SHIPPED) { /* 20 行 */ }
    else if (order.status == Status.DELIVERED) { /* 8 行 */ }
    else if (order.status == Status.CANCELLED) { /* 5 行 */ }
    else { throw IllegalStateException() }
}

// 修复:用多态性替代 switch
interface OrderHandler {
    fun handle(order: Order)
}

自动 搜索坏味不能取代代码审查:静态分析器只能找到结构性问题,但无法捕获语义坏味(Feature Envy、Inappropriate Intimacy)。自动工具和人工控制的结合提供了最好的结果。配置 CI/CD 流水线,使其在超过复杂度或方法长度阈值时构建失败。

如何修复 Code Smell

重构 — 消除代码坏味的主要方法。Fowler 描述了数十种重构技术,每种技术都适用于特定的坏味。Extract Method — 用于长方法,Extract Class — 用于大类,Move Method — 用于 Feature Envy。以小步骤进行重构很重要,每次更改后要保持代码的功能。

重构前进行测试 — 强制性条件。如果代码没有被单元测试覆盖,重构就会变成结果未知的重写。对于没有测试的遗留代码,使用 Characterisation Tests — 编写记录当前行为的测试,然后进行重构。测试可以确保重构后业务逻辑没有损坏。

循序渐进 — 在移动开发中成功消除坏味的关键。不要试图完全重写 God Activity。首先分离导航层,然后是数据层,接着是显示逻辑。每一步都要伴随提交和运行测试。使用功能开关为部分用户启用重构,并在出现问题时回滚。

  • Extract Method — 将长方法拆分为多个名称清晰的小方法
  • Extract Class — 将相关的字段和方法组分离到单独的类中
  • Replace Conditional with Polymorphism — 用类层次结构替换 switch
  • Introduce Parameter Object — 将一组参数合并到一个对象中
  • Replace Inheritance with Delegation — 用组合替换 extends

IDE 工具 自动化了许多重构技术。Android Studio 和 IntelliJ IDEA 提供内置重构:Extract Method、Extract Interface、Pull Members Up、Encapsulate Fields。Xcode(从版本 14 开始)改进了对 Swift 重构的支持。使用自动重构与手动复制代码相比降低了错误风险。

移动开发中的 Code Smell

移动开发 增加了与平台限制相关的自身特定坏味。在 Android 中,这些是 Context 泄漏、未关闭的 Cursor、Lifecycle 的不正确使用。在 iOS 中 — 通过闭包产生的 retain cycle、与 Auto Layout 的错误使用、巨大的 ViewController。这些坏味不仅会降低可维护性,还会直接影响应用程序的性能和稳定性。

Callback Hell — 使用异步操作的代码的典型坏味。嵌套的回调(callback inside callback)使代码难以阅读和调试。解决方案:协程(Kotlin)、async/await(Swift 5.5+)、RxJava/RxSwift 或 Combine。根据 Google I/O 2023 的数据,从回调风格迁移到协程的项目将错误数量减少了 30%,并加快了对新功能的添加。

Platform Coupling — 业务逻辑与平台组件的紧密耦合。测试这样的逻辑需要启动模拟器,这会减慢反馈循环。修复:Clean Architecture 将代码分为 Domain 层(纯 Kotlin/Swift,无平台依赖)和 Data/UI 层(带有平台依赖)。业务逻辑在没有模拟器的 JVM 上进行测试。

常见问题

Code Smell 和错误是一回事吗?

不是 — Code Smell 不是错误。带有坏味的代码可以正常工作,但难以维护、修改和测试。错误是不正确的行为,坏味是对未来潜在问题的警告。

Martin Fowler 指出多少种坏味?

22 种坏味 在《Refactoring》第二版(2019 年)中。其中包括 Long Method、Large Class、Primitive Obsession、Data Clumps、Switch Statements、Speculative Generality 等。社区为现代范式和平台添加了数十种新的坏味。

哪种工具最擅长发现 Code Smell?

组合 提供了最佳结果:Detekt(Android/Kotlin)、SwiftLint(iOS)、SonarQube(两者)用于自动分析,代码审查用于语义坏味。没有工具能发现 100% 的问题 — 人的经验仍然至关重要。

可以忽略 Code Smell 吗?

可以,如果代码很少更改或在不久的将来会被完全重写。然而,坏味的积累会变成技术债务:每次新的更改都变得更加困难,修复成本呈指数级增长。

SwiftUI 和 Jetpack Compose 有特定的坏味吗?

— 声明式框架创造了新的坏味:巨大的 @State 块、与重复渲染的错误使用、过度重组、未能提取到单独的 View。对于 SwiftUI,典型的坏味是带有数十个 @State 变量的 Massive View。

总结

  • Code Smell — 代码深层问题的表面迹象,不是错误,但会降低可维护性
  • Long Method 和 Large Class — 移动开发中最常见的坏味,需要 Extract Method 和 Extract Class
  • Duplicate Code — 逻辑重复,每次修改都会使工作量加倍
  • Feature Envy 和 Switch Statements — 类之间责任分配不当的标志
  • 特定坏味 — God Activity、Giant ViewController、Leaking Context — 移动平台独有的
  • 重构 没有测试是危险的:首先进行 Characterisation Tests,然后小步提交
  • 静态分析(Detekt、SwiftLint)自动化了搜索,但不能取代代码审查

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读