0x1、杂谈
① 为什么要重构上传库?
我司APP在上传图片/视频前,需要进行一系列处理,最后才上传,如:
- 图片:判断路径是否存在 → 通过Exif信息判断是否需要旋转 → 判断是否需要压缩 → 获取MD5 → 如果开启秒传查询是否有秒传记录,有直接返回 → 没有才上传 → 上传完成对应状态更新;
- 视频:判断路径是否存在 → 判断是否需要压缩 → 要压缩的话压缩 → 获取MD5 → 获取视频第一帧 → 判断是否需要压缩 → 要压缩的话再获取一次视频MD5 → 同样是秒传验证 → 上传完视频传第一帧图片
有些业务场景的处理更复杂,Talk is cheap,show you the code,出现这样的代码很正常(局部):
网络异常,图片无法展示
|
得益于rx链式调用,上述代码是已经简化后的一版了,可以想象没rx前的就更混乱了,对写的人来说负担,对看的人来说也是,重构势在必行...
② 不重构的话有其他低成本一点的方案没?
答:有,在上面代码的基础上优化,把flatMap抽取为单独的功能函数,按流程调用,当然,也不算太优雅。最优雅的应该是上Kotlin协程,写几个挂起函数,同步方式写异步代码。当然,问题也有:一丢丢的学习成本 和 不能在Java里用。
0x2、需求拆解
原始需求
写个图片上传库,给你一个本地图片路径、一个上传接口,完成图片上传。
小白视角
简单,库都不用谢,直接写个UploadPicUtils工具类,定义一个上传方法就好了
光速敲完代码:
object UploadPicUtils { fun uploadPic(picPath: String, serverUrl: String) { val pic = File(picPath) if(pic.exists()) { // 执行网络上传操作(如调用OkHttp直接传) // 利用rx或EventBus通知上传结果,给出成功、失败反馈 } } } // 上传图片处调用: UploadPicUtil.uploadPic("本地图片路径", "上传接口")
看着挺简单的,但 唯一不变的是变化,需求往往是反复无常的~
- 因为公司不舍得买图片加水印服务,所以客户端传图片前本地要加下水印;
- BUG:有用户用自己手机拍照,上传后的图片却旋转了,上传前要检查下,歪了的要摆正;
- BUG:有用户反馈上传图片太慢,一排查图片太大,服务器顶不住,上传前要做下图片压缩;
- 某些图片尺寸有规定(X*Y),尺寸不对的不能上传;
- 秒传功能,md5一样的文件传过就不要传了,直接返回地址;
- 支持同时上传多张图片;
- 现在不止传图片了,还有传视频、音频、文件、文件夹等场景...
然后代码就变成上面这样的结果,写的人看了沉默,接盘的人看了流泪。
所以,在拿到原始需求时,不要上来就肝代码,而是 对需求进行拆解、分析、假设和思考。
- 真的只上传图片吗?后面会不会要传其他东西,如音视频?
- 要做图片有效性校验吗?如:存不存在,大小是否为0,文件格式为图片类型等;
- 库需要对上传图片做什么特别的处理吗?如打水印、翻转、压缩、切割、尺寸校验等;
- 是否要支持多张图片同时上传,最多多少张同时传;
- 是否要支持秒传;
- 不同手机系统版本或设备文件获取API兼容;
- 上传接口地址是不是一直变化的,是否需要鉴权,是否有特定上传配置;
- 上传任务在后台异步进行,还是前台同步堵塞,上传中途能否取消;
- 上传任务中断了(杀掉APP)是否需要保留进度,下次打开APP重新传;
- 上传失败是否需要重试,最多重试次数多少;
- 上传任务是否还有优先级等;
当然,不要想着一下子就给出完美设计方案,完整的功能实现,受限于设计者的架构经验和有限的排期,先给出一个粗糙的、基础可用的方案,有一个迭代的基础,再慢慢进行优化,最小化可行产品。