假设你的Android app是一个犯罪现场!

在Karumi,iOS和Android应用的技术审核已经成为日常工作必不可少的一部分。这项工作看起来容易,然而在实际进行审核的时候,我们会发现有不少实现细节要注意。我们来回顾一下这个文档里面我们认为最重要的审核条目。这些审核条目按照技术方向被分成几种不同类别。

版本控制系统:

术语参考

工程师是否使用版本控制,使用哪种系统,通过哪种过程来向我们呈现软件开发过程。

  • 你有没有一个正确设置的*ignore忽略文件,使得IDE的元文件和多余的东西不出现在版本控制系统中?

  • 第三方库文件是不是通过版本控制系统放到同一个仓库中,而不是被设置成一个外部依赖?

  • 你有没有撰写足够准确而且描述清晰的提交信息?

  • 提交的大小是否合理?

  • 同一个提交的所有文件是否都是和同一个问题或者特性相关连?

  • 你在使用分支时有没有使用”特性分支“或者”git-flow”这样的分支策略?

  • 分支命名是否足够描述清晰?

  • 是否在合并到主分支之前使用拉取请求(pull request)或者代码审核(code review)系统?

    • 审核拉取请求(pull request)的时候,有没有相关可以参考的参考规则?

    • 每个PR平均有多少评论?

    • 每个PR有多少人审核?

    • 合并PR需要达到多少个同意?

    • 谁来负责关闭被合并的分支?

  • 有没有用发布分支(release branch)来准备每次版本发布?

  • 测试过程持续多久?

  • 正式发布之前需要引进多少个问题修复到发布候选版本中?

  • 能否签出和用于构建线上应用的完全一样的代码?

  • 去年你一共发布了多少个紧急修复(hotfixes)?

  • 合并分支到主分支或者开发分支之前你是否会压缩提交?

  • 主分支或者开发分支是否随时可以发布?

构建工具:

能够在任何开发者机器,任何像持续集成系统这样的外部系统上复制构建过程非常重要。

  • 项目使用了多少库?

  • 项目是否被分成了多个模块?

  • 项目模块是否通过Maven或者Gradle来提供依赖还是使用本地Jar文件?

  • 项目是否接近了dex方法数量的红线?还是已经超过了红线?

  • 有没有使用不需要的库?

  • 有没有使用multidex?

  • 外部依赖是否保持了最新状态?

  • 你是否遵守了每个第三方库的授权证明?

  • 项目是否使用了任何过时或者被遗弃/停止维护的第三方库?

  • 最低SDK是否是项目描述中所要求的?

  • 目标SDK是否是当前最新?

  • 是否启用并且正确配置了proguard或者任何其他混淆工具?

  • keystore证书和Google Play Store证书是否被存放在安全的地方?

  • 应用证书和签名是否被存放在安全的地方?

  • 应用有没有正确使用构建类型?

  • 应用是否正确使用特色类型?

  • 发布构建类型是否正确配置?

  • 是否开启了备份选项?

  • lint是否启用而且成功通过?

  • 是否配置了静态分析工具而且成功通过?

  • 是否配置了风格检查而且成功通过

  • 是否正确配置应用id和版本号,版本编码?

  • 对于id,你是否使用某种版本结构或者策略?

  • 有没有配置持续集成工具?

  • 发布过程是否自动化?

Android资源使用:

在Android世界中有很多不同的设备,每种都有不同的屏幕大小,功能等等特征。你需要额外小心还要使用Android工具来根据用户的设备给他们提供最佳的体验。

  • 对于各种屏幕密度,特色或者构建类别是否有缺失的资源?

  • 应用是否支持产品描述要求的所有屏幕密度?

  • 应用是否使用位图,字体或者矢量资源?

  • 是否有缺失的翻译?

  • 翻译过程是否自动化?

  • 默认的翻译是什么语言?

  • 应用是否使用自定义字体?

  • 应用是否使用存放在String资源文件中的配置变量?

  • 变量命名的规则和资源命名的规则是否一致?

  • 硬件相关的配置参数是否正确设置?

  • 是否支持平板设备?

Android布局用法:

就像我们之前说到的,市面上有很多不同屏幕,不同像素密度的各种Android设备。正确使用Android布局非常重要。

  • 应用布局的层数是否造成了性能问题?

  • 是否使用了主题和风格?

  • 是否通过使用“include”标签来复用布局?

  • 在布局实现中有没有使用正确的视图组(view group)?

  • 布局有没有为不同屏幕大小适配?

  • 布局和控件命名规则是否一致?

  • 实现列表的时候使用的是ListView还是RecyclerView控件?

  • 是否正确使用了Android Support库?

权限用法:

请求正确的权限可以帮助你在用户中建立起信任,也可以让你的应用走的更远,同时可以无缝的和其他服务集成给你的用户带来良好的体验。

  • 所有请求的权限是否真的有必要?

  • 有没有滥用任何权限?

  • 有没有缺失必要的权限?

  • 目标SDK版本是否高于23?危险权限是否通过兼容权限系统(compatibility permissions system)请求?

  • 权限被使用时有没有发出请求?

  • 有没有通过反馈说明需要请求的权限?

安全问题:

  • 作为开发者,我们需要意识到应用的安全性,我们不希望用户数据被泄露或者用户会话被劫持。

  • HTTP客户端是否配置使用HTTPS?

  • HTTP客户端是否配置使用证书绑定,消息是否使用HMAC验证?

  • 应用是否持久化用户敏感信息?存放在哪里了?

  • 应用是否在内置存储外存储信息?

  • 运行一个发布版时,应用是否记录日志跟踪信息?

  • 应用代码有没有混淆?

  • 应用是否对外暴露任何content provider, receiver或者service给其他应用?

  • 发行版中,应用的“debuggable”标志是否被禁用了?

推送通知:

推送通知是一种可以在任何时候保持用户关注相关内容的良好机制,但是这个问题并不像第一眼看起来那么简单。

  • 应用是否使用第三方库来实现推送通知系统?

  • GCM系统是否用来给应用通过推送通知来发送信息还是只是用来给用户显示消息?(这个问题没有理解清楚)

  • 应用收到推送通知后的行为是什么?

  • 如果和推送通知关联的信息并不是我们想要的,这时应用的行为又是什么?

  • 通知是否使用了兼容API来显示给用户?

性能:

性能非常关键。没人希望在自己400-600美金购买的设备上运行一个又烂又慢的应用。性能就是金钱。

  • 应用是否有内存泄露?

  • 开发版构建中是否配置了类似“LeakCanary”这样类似的内存分析工具?

  • 开发版构建中是否配置并启用了Strick Mode?

  • 应用的线程使用情况怎么样?有没有使用async task,intent services或者其他第三方库?

  • 后台线程的数量是否造成了性能问题?

  • 有没有使用任何线程调度策略,还是有需要就创建线程?

  • 有没有随时关注Android的Doze Mode?

  • 应用是否监听了网络状态事件,或者其他操作系统的重复事件?

  • 主线程是否只用来执行用户界面相关的任务?

  • 应用有没有使用任何缓存策略?

  • HTTP客户端有没有配置超时?

  • HTTP客户端有没有配置GZIP压缩?

  • UI能否达到每秒60的帧率?

  • 有没有分配大量内存,或者在UI线程执行耗时炒作的自定义控件?

  • 有没有在低端设备上测试你的应用?

  • 自循环列表控件(recycler view)的滚动有没有卡顿?

  • 图片处理实现使用了第三方库还是用自定义的解决方案?

  • 使用图片的时候是在设备上缩放到屏幕大小还是下载和屏幕大小相关联的图片?

  • 内存使用是否合理?

  • 有没有正确使用“static”修饰符?

  • 有没有图片管理相关的功能一次同时处理一张以上的图片的情况?

  • 状态跟踪系统是不是运行在一个正确配置了优先级的后台线程上?

  • 代码是否在发行版构建中做了优化?

Java包结构:

  • 好的包结构可以让代码更好扩展

  • 包是根据功能还是概念来分割的?比如,登录(Login)相对与用户(User)。

  • 在包内部有没有使用java的可见性修饰符来隐藏实现细节?

  • 有没有包游离在根包之外?

  • 测试目录和源代码目录是否遵循了同样的包结构?

  • 功能需求是否用同样的包结构来组织?

  • 有没有任何放在不正确包里面的类?

  • 有没有遵循一定的命名规范,这样包命名就可以比较一致?

  • 根包的名字是否和公司名字相关?

代码风格:

风格一致的代码基础库可以让工程师更容易阅读代码。工程师通常读的代码远多于写的代码,所以这个概念很重要。

  • 风格是否保持一致?

  • 是否使用匈牙利命名法?

  • 有没有配置并通过代码风格检查工具?

  • 代码是否遵循java的代码风格?

  • 使用tab还是空格?

  • 类名是否正确?

  • 使用“I”作为接口前缀还是使用“Impl”作为后缀?

  • 变量名是否正确?

  • 字段命名是否正确?

  • 方法命名是否正确?

  • 属性和方法的可见性修饰符是否正确使用了?

  • 代码是否用英语来写?

  • 是否使用javadoc?

  • 是否写注释?

  • 是否使用常量或者枚举量来避免出现重复的字面量?

离线实现:

好的离线体验是我们应用的一个差异化因素。

  • 断网时应用是否可以正常使用?

  • 网速慢时应用的行为是什么?

  • 网络请求被网络中断时应用的行为是什么?

  • 应用的数据更改是否在应用后端连接恢复时马上进行同步?

  • 网络连接有没有设置超时?

  • 有没有配置HTTP缓存策略?

  • 用户会话是否自动重新协商?

架构:

代码角度的应用架构是整个审查规则中让我们对应用有更多洞察的一部分。在应用架构复审的时候我们将关注SOLID相关的概念以及Clean Code原则。

展示层实现:

  • 针对GUI实现,应用有没有相应的模式?MVP和MVVM可能时开发应用时最常见的两种模式。有没有正确实现呢?

  • 展示层实现是否和显示层实现耦合了?

  • 显示层实现和模型层实现是否耦合了?

  • 展示层是否实现了业务需求?

  • 显示层实现是否正确使用了Android SDK里面提供的工具?

  • 是否使用了第三方库来简化显示层实现?

  • 不同特性用activity还是fragment实现?

  • 公共的UI行为是否统一处理了?

  • 有没有使用自定义控件来重用UI代码?

领域实现:

  • 领域层甚至说全部业务逻辑是否有在展示层实现?

  • 领域规则和不同应用的需求是否在主要的业务逻辑主体里面进行描述?

  • 领域层实现是否用了面向对象的原则?

  • 领域层实现是否和Android SDK或者任何第三方库耦合?

  • 领域层是否和展示层耦合?

  • 领域模型是否很少?

  • 是否使用大量的领域模型?

  • 代码是否构建在高内聚低耦合的模块中?

  • 错误处理时用Exception还是其他错误处理机制实现?

  • 不同层次的数据是否进行了映射?

  • 外部设计(比如数据库模型或者JSON解析)是否影响领域模型设计?

  • 开发者有没有滥用继承?

  • 代码是否重复?

  • 有没有使用依赖注入库或者服务发现器?

  • 类或者方法复杂度是否过高?

API客户端实现

  • API客户端实现是否和Android SDK耦合?

  • API客户端是否泄露了HTTP客户端或者网络层实现的相关细节?

  • API客户端是否发送了正确的头?

  • 对应不同的HTTP相应,API客户端的行为是什么?

  • API客户端是否实现了验证机制?

  • 刷新会话的过程是否正确实现了?

  • JSON序列化工具是否支持混淆?

  • API客户端是否和不同API分割开(这里没有完全理解)?

存储实现:

  • 信息存放在哪里?

  • 从存储读出或者写入数据时有没有使用事务?

  • 存储是否安全存放了用户的敏感信息?

  • 数据层是否使用第三方库?

  • 数据层是否泄露了实现细节?

  • 存储的表或者模型是否正确建模了?

  • 发送到存储的请求是否做了优化?

  • 是否在正确的场合使用了Android SDK的持久化API?数据放数据库,选项或者小数据放Shared Preferences,文件存放在磁盘上?

可测试性:

  • 应用是否有测试?

  • 应用是否可测试?

  • 应用是否覆盖不同测试(单元测试,集成测试,端到端测试)?

  • 测试命名是否正确?

  • 测试是否正确划分了范围?

  • 测试中有没有过度描述?

  • 测试执行事件是否合理?

  • 测试覆盖率是否过低?

  • 有没有被忽略的测试?

  • 有没有不可靠的测试?

  • 有没有使用最新的测试框架?

  • 有没有没有断言的测试?

  • 测试是否由实现线上代码的相应开发者编写?

  • 有没有手工QA团队?

  • 有没有QA团队自动化部分测试?

  • 有没有持续集成系统?

  • 有没有使用构造类,工厂类或者母亲类来减少创建测试所需实体的麻烦?

  • 测试断言是否正确编写?

  • 每个测试是否执行了超过一个逻辑断言?

  • 同一个项目是否有不同的测试套件?

  • 测试应用不同部分时,你是否使用了不同的测试方法?

  • 有没有使用monkey runner?

  • 你是否遵循TDD或者BDD方法论?

  • 是否使用java编写测试用例?

根据这个列表的不同主题,我们能够确保应用的质量。我们还考察一些其他的点,但是这个列表包含了最重要的部分。你能给出这些问题的正确回答吗?

由 Pedro Vicente Gómez Sánchez 编写

由 Nick Qi 翻译