企业如何开发优秀的App

近五年来我呆过三家公司,在小公司、中型和大型公司分别呆过一年、三年、一年(现在还在职),说实话,这三家公司都给了不同的启发。一直都想写些文章来纪念我所经历的这几年,其实给我冲击最大的是第二家公司,原本想一直呆在那儿的。但很多时候并不是我们一个基层开发人员所能够左右的,在这里我把这些年的感悟梳理成文,以备大家参考。
在第一家公司时,当时我刚刚本科毕业,懵懵懂懂地就进入了一家iOS创业公司,当时的人员配置相当简单:开发、设计、财务、IT配置人员。大家可以从这份名单中可以看出,没有产品经理,没有测试,没有运营人员,没有QA,没有...是的,当时我们所做的就是按世面上流行的App,山寨并模仿,然后上架,然后就没有然后了...
而这家公司后来引入了项目经理,并做了一些其它的努力和尝试,但最终的结局还是失败了。究其原因,我认为是人员的配置不齐,导致很多问题没有人去管理,存在很多漏洞,例如程序存在很多BUG,例如crash,内存泄漏等等。当时的领导期待大家像个人开发者一样,但是如果是像个人开发者一样,为何要成立公司??

在我所呆的第二家公司,其实人员配置还是齐的,有产品经理、测试、PM、QA、运营人员。但还是失败了,因为当时公司业务的原因,大量扩招了很多人,而且公司在研发的力度上始终不够、招的人员水平相对一般,开发模式一片混乱,最终导致了项目的失败,从而导致整个开发团队的解散。
这里我所强调的其实是专业的人员配置,除了岗位的齐全以外,还是强调专业性。如果不能够做到BAT这样拔尖人才的扎堆,也应该做到高中低搭配。要有一些技术、流程、管理上面过硬的的人员,把技术、流程、质量与进度提上来。
由于本人是一个开发人员,我想主要强调一下开发与测试这两块,如何开发一个优秀的App.


确保App的稳定

1、尽可能少的crash

如何有效的降低crash呢,首先,我们要做好crash的统计与分析。目前世面上有很多crash的统计工具,如CrashlyticsHockeyapp友盟Bugly 等。这儿有一篇crash收集框架的blog.
当然你也可以自己收集crash信息,然后自己手动翻译。
有了crash统计工具就够了吗,显然不能完全解决所有的crash。因为还有很多crash堆栈有可能是系统的,无法直接找出crash的原因,这个时候我们可能要引入另一个强大的系统:日志分析

2、日志分析

足够的日志内容,可以帮助我们快速地定位用户的操作路径,以及当时所发生的内容。例如问题最多的网络请求与回包,我们可以在组包、与回包的地方打印Event级别的日志。而在一些非关键内容上面打印Info级别的日志。
至于如何打印日志,大家可以看一下DDLog日志的打印.日志打印应该做到以下几点:

1)日志级别

Event,Info,Debug等等。因为打印日志会有文件IO的处理,大量打印会影响性能,及大量耗电。所以在App发布后建议只保留Event级别的日志,而在开发和灰度时把其它日志都打开。以定位crash,以及其它错误信息。

2)日志分段上报

因为用户长时间使用后,日志会积累过多,面对如此大量的数据,会增加开发人员定位问题的难度,所以能够分时上报日志也是必要的。

3)日志上报内容

至少要包括时间、文件名称、函数,以及自定义数据等相关内容。

4)日志上报的时机

可以通过后台给APP下发日志上报的请求,让用户把规定时间内的日志上报服务器,以便开发人员定位问题。

3、自动化测试

有了crash收集、日志打印以后,我们还需要更多的稳定测试,提早发现crash.目前也已经有一些开源的自动化测试工具,例如淘宝的自动化测试工具athrun.当然还有其它许多的例子,大家可以自行google搜索。
自动化测试目前主要是解决crash的发现。但是里面仍然有一些坑可能需要大家注意,例如跳转第三方应用会如何回来,以及字符串的输入与验证。目前还无法进行UI界面的验证。

4、更多尝试

有了以上三点,我们程序的crash就能降到一个比较低的值了,但是仍然无法解决所有的crash.这时我们可能需要更多的尝试,比如活动追踪,内存快照等。内存快照指的是在程序可能crash的地方跟踪一些关键信息,等到程序crash的时候再把跟踪到的信息打印到crash文件中。方便开发人员定位或者减小crash发生的地方。
总结:crash的收集与定位,是一个很需要耐心的工作,测试人员只能发现一些易于复现的crash。而其它一些crash需要我们海量的用户自己去使用才有可能发现。这个时候一套完整的crash的收集与分析的框架就能够起到非常好的作用。


流畅的App体验

instrument工具

这个工具不仅要求开发人员会使用,同时测试同学也要用到,所以给测试人员配备一台mac机也是合理的。我们使用它来做什么呢,主要是两件事:1、内存泄漏扫描。2、FPS检测。2、性能测试。
关于instrument工具的使用,在这里就不展开讲了,不然篇幅又太长了,大家可以自行google如何使用。

数据上报

我们除了产品经理所要求的一些pv操作上报以外,开发人员也可以上报一些非常重要的信息

1)FPS数据上报

我们可以监听一些主路径界面的FPS的情况,把每次侦测到的FPS传到数据上报中心,关于数据上报中心的选择,大家可以使用第三方的,如果数据敏感的话也可以公司内部统计。例如友盟,Google Analytics等等。
我们如何计算页面的FPS呢?
我们可以引入#import <mach/mach_time.h>在scrollDelegate代理方法的scrollViewWillBeginDeceleratingscrollViewDidEndDragging两处分别记录一个绝对时间mach_absolute_time()
然后在scrollViewDidScroll中累加界面执行的次数,用累加的次数除以绝对时间差就可以获得一个比较可信的FPS了。当然整个过程大家可以下去再优化。

2)函数耗时统计

与上面的FPS统计有点类似,我们可以在函数的入口记录一个时间,在函数的出口再记录一个时间。用这两都的时间差记录整个函数的耗时。当然事情往往会比这个更加复杂一些,因为大多数情况都存在函数的嵌套调用。这个时候需要我们在各个关键节点中记录时间戳。

3)线程死锁检测上报

为了能够获得流畅的APP体验,我们会大量使用线程,而在线程的调度过程中,存在线程同步的问题。而为了保证线程的同步,我们可以采用的方法大多是线程加锁,或者使用GCD中提供的dispatch_barrier_async或者串行执行的线程对列等。
如果我们使用了线程锁,就有可能造成死锁或者活锁。而死锁的危害在于程序的卡死,而活锁则会造成程序的卡顿与耗电。但如果我们能够侦测到死锁或者活锁,并把该线程的相关调度信息进行上报,那么我们的程序肯定又会上一个新台阶。
如何侦测死锁呢?一个比较好的思路当然是在获得锁的时候记录一个时间信息。然后再在释放锁的时候消除这个记录。同时我们用一个线程去检测那些锁的信息,如果锁住超过了一定时限(例如3秒),我们就记录一下。因为这里可能存在死锁。
然而事情往往没有那么简单。因为在iOS开发中的GCD等方法的线程调度是由其内部实现的,我们很难直接取得入锁与解锁的信息。这个时候我们可以尝试用runtime的机制hook GCD、performSelector等的实现,如果一个线程执行时间过久,就有可能出现了死锁。这里的话,需要大家多多尝试并通过大量测试验证。
最近在网上发现了一个检测卡顿的示例代码,大家有兴趣的话可以好好研究一下。


组件化开发

把大量使用的功能提炼出来,形成相当独立的模块,例如网络组件、相册选取、Toast等等。在组件化开发的过程中我们要结合自己的业务,组件成可扩展、低耦合的功能模块,在开发的过程我们可以做一些单元测试,以提供稳定的组件功能。

单元测试

测试驱动开发,需要我们对一些组件的或者比较复杂的模块进行单元测试,例如XCTest


安全

一个值得依赖的程序必须是安全可靠的,尽管苹果严苛的上架管理与程序的泥沙盒运行机制,已经在很多层面保护了我们运行的程序,但仍然有很多内容是我们所需要关注的,尤其在我们使用银行、账号等敏感信息时更是如此,在iOS中我们有哪些安全手段呢?

HTTPS

HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,就是普通的HTTP数据。
HTTPS比HTTP多了一个数字证书验证与加密的过程,因而网络传输速度上面往往会慢一些,但如果数据是敏感内容时,这种付出是值得的。而且现在的网络传输速率也越来越快了。这儿有一blog介绍了如何在iOS中开发HTTPS。

加密

对于敏感数据,或者一些特殊的使用场景,我们往往需要对数据进行加密。例如我们的账号密码、身份校验。一般而言会对密码进行MD5或者SHA加密。而身份校验则往往是后台下发的一个token.例如Push Notification中苹果后台下发的token.
另外下载到本地的文件,很容易被第三方的工具提取出来,例如iTool工具,如果我们要保护数据的话,加密是一个好的选择,我们通常使用的是AES(对称加密),当然有些App可能会使用其它加密算法,例如Tea加密。不太建议大家使用3DES加密,其安全和性能都不如AES.非对称加密的话,我们可以使用RSA加密。
另外,我们可以通过sqlite3_rekey、sqlite3_key来对数据库进行加解密。

设备锁与密码

安全的另一个有效措施是添加设备锁,当用户在不同的设备上进行登录时,需要进行短信验证码验证才能登录,这样,即使密码被盗,其仍然无法登录。这样就保证了我们的账号。
设备锁的关键之处在于获取设备的唯一标识,由于苹果封杀了mac地址的唯一性,当然苹果关上一扇门的时候总会打开另一扇门。我们可以通过keyChain在不同程序之间共享数据,通过自定义的keyChain数据,我们可以有效地识别唯一的设备。
另外一个弱一点的保护是手势密码,当程序退出或者切换到后台时,就需要我们进行手势密码的验证,当我们的手机丢了,或者借给别人使用时,就不用担心数据泄漏了。

敏感API的混淆

由于程序安装包大小的压力、以及过于严格的App Store审核,我们在程序中可能会使用插件化开发功能模块,例如大家所熟悉的Lua patch,还是现在开源的JSPatch.这些脚本语言利用objective-c的runtime特性,动态地添加了objective-c的一些类和方法。
而这些脚本语言在下发到App时需要进行加密或者预编译处理,避免明文下发。
另外我们也可以对敏感的API函数进行混淆编译,念莤这篇文章有讲到如何进行Objective-C代码混淆


减小安装包

随着arm64的推广,程序代码段的大小基本上是翻了一倍,在安装包变大的情况下,如何尽量地减少安装包也是一个非常重要的一环。当然那些小型App可以忽略。

1)图片压缩

其实我们知道,一张高质量的png图片其实是非常大的,通过压缩工具压缩以后可以减少50%甚至90%以上的大小。图片压缩的工具在网上可以下载,例如图片压缩,当然如果你有更好的可以给我留言。

2)扫描废弃类、函数以及图片

通过脚本或者自己编写代码,来扫描工程中已经不再使用的类和方法,如果工程项目大,扫描时间可能会比较长。同时对于扫描出来的代码要进行仔细辨别,避免误删。不知道大家能否在google上面找到一些好的扫描工具,有的话也可以留言分享给我。

3)插件化

通过Web化、Lua、JSPatch等手段把Native代码插件化,以减少安装包大小,并优化版本发布。做到更加灵活、可配置地升级或者维护某些功能模块。

4)加强代码规范,尽量避免重复代码

减少重复的代码、字符串、尽可能短的类、函数名称,这些都可以为程序瘦身,但有些瘦身的方法并不推荐,例如把程序由ARC转为MRC,这样做虽然可以减小安装包,但可能会带来程序的不稳定性。这样的牺牲是不值得的。
不使用Swift与Objective-C混编.因为Swift2.0以后,在iOS9中才把Swift集成到了系统的FrameWork中,在这之前,使用Swift会大大增加程序安装包的大小。


用户反馈

多元化的用户反馈也是我们需要处理的一个问题,反馈的渠道有很多,我们可以在App内部添加反馈入口,也可以在App Store收集问题,甚至我们可以通过微博等其它渠道进行收集。
把收集到的问题进行分类、并提交bug清单到开发Leader,记录并跟踪问题的解决。
除了用户反馈,企业内部也可以多做一些反馈,例如我们可以拉一个QQ或者微信群,进行问题的反馈与讨论。让问题暴露地更早。

2015-08-01 23:20386