目录

代码不规范的典型示例

之前听过一个分享,代码的哲学其实就是处理复杂度问题。

应用开发中,不需要什么奇技淫巧,好的代码往往通俗易懂,副作用更低。差的代码往往会给人造成困惑,引发各种问题

案例分析1

当你看到这样一个函数时,不知道你会怎么想?

public static Activity getCurrentActivity() {
    return ActivityContextInitModule.getActivityContext().getCurrentActivity();
  }

解释一下,这个函数通过我个人觉得很**的方式,用来获取当前的 activity 对象。获取当前 activity 的方式,我觉得从 AMS 大管家里找肯定能找得到,完全没必要自己来一套。当然,这里这个不是重点,忽略实现方式。

这个函数恶心的地方,是我在看另一个灵异的问题时发现的。什么问题呢?就是一个自定义的 snackBar,它本意是要出现在 statusBar 下方,但是同一段代码,时而位置正确,时而遮挡 statusBar,这里的所有代码我都没有改动过,都是公司原有代码,所以这令我百思不得其解。后来才发现,其实这个问题是概率问题,早就存在了!

先来说说这行代码为什么写的不好,然后顺便就解释下为什么会出现时而正确时而错误的情况。

为什么这代码写的不好

  • 函数没有任何注释,没有给予调用者任何额外的注意事项(我还是个新人,这是给新人看的?)

  • 自己实现,不可靠

  • 静态方法,使得所有人所有时机都可以任意调用,容易使得忽略了 Activity 的生命周期

snackBar 位置错误的原因

上面其实也已经说了,其实就是第三点,生命周期的问题。写 snackBar 的这个同事,他直接通过上面的函数获取到 activity 对象,然后 findViewById 获取 view,然后 view.getHeight() 获取偏移量,一通操作行云流水。然而,view.getHeight() 这个函数应届生都知道,它可能会是0。当 view 渲染完成,你获得的当然是正确高度,当 view 渲染未完成,自然是0。这就是为什么时而遮挡时而不遮挡的根本原因。

这个变态的问题最终被我加了一些状态判断,尽可能的处理了一下。总结一下,就是写 getCurrentActivity 和 snackBar 的两个远古同事都犯了很严重的问题,但是我个人觉得还是前者危害更大,他这个静态方法会诱导很多人直接拿到 Activity 进行一些错误操作···

案例分析2

我还遇到了一个诡异的问题,是我在做一个需求的时候,这个需求是做小包。就是比如当前我们的 APP 版本号是 7.0,但是我们想做一个小包版本(体积和功能等缩减),于是我们从一个 5.x 版本的分支 fork 了一份代码,缺又想迁移一些 7.0 的功能。
这里面问题很多,包括 support 包(5.x版本使用)和 androidx 包(7.x版本使用)问题,我本以为这些已经很恶心了,但当我编译通过,运行时 crash 时候,我才知道,摆在明面上的问题那都不是问题。

问题现象

debug 包运行正常,release 包不正常,体现在侧边栏滑不动了。有经验的基本就一眼看穿,基本是编译时候的差异(最常见的: 混淆),再看 log 抛的异常,FieldNotFoundException。

因为代码不熟,而且大项目的编译过程···你们懂的,各种魔改,gradle 脚本里也是毫无注释,不知道他想干啥。

所以只能靠猜。

先通过异常定位到类 android.support.v4.widget.SlidingPaneLayout,乍一看毫无问题。这是 android support 包提供的类,为什么会抛 FieldNotFoundException 的异常。所以一开始我就没往混淆去想。

  • 不会是 support 包有 bug 吧?
    不会吧,如果有为啥 debug 包可以呢?

  • 不会是编译脚本有 configuration 相关的东西吧,里面 exclude 了?
    想了想也不对,那应该是 ClassNotFound 才对啊。且检查脚本没发现问题。

  • 难道 support 包,release 和 debug 用的版本还不一致?有一个版本没有这个成员变量了?
    最后发现也不是

只有混淆了吧!

还真是···然后我们来欣赏一段这个代码

public class XXXXSlidingPaneLayout extends android.support.v4.widget.SlidingPaneLayout {
   public XXXXSlidingPaneLayout() {
     // 此处反射 try-catch
     setField(this, "mSlideOffset", 0.0f);
   }
}

他自定义 view 继承基础控件,还要反射去拿私有变量,release 包混淆之后,自然找不到 mSlideOffset。

不是说这段代码写的差或者怎么样,我觉得这个就是典型的不规范!

  • 为什么继承 support 包控件,还要反射拿私有变量?满足不了需求的话,应该定制,或者看看自己使用是否正确。而不是为了简单实现需求留下这么个雷。

  • 没有任何注释

  • 混淆规则里没有任何说明。这不得混淆文件里解释下?

最后通过修改混淆规则 keep 了 SlidingPaneLayout,解决问题