Szhangbiao's blog

记录一些让自己可以回忆的东西

0%

个人技术心得分享

最近项目间隙时间有些长,就主动发起一次内部技术分享,主要从模块化,简洁代码,高质量开发,技术焦虑等几个方面做了一些思考,其中部分内容没有写的那么详细,需要在现场分享的时候做一些引申,从业这么多年一直在闷头做项目,偶有感悟也随着项目的忙碌遗忘到九霄云外,这次也算是一个自我总结的好机会。

问题点

  • 新框架和技术落地的比较理想的时间节点
  • 当开发达到一定熟练度后,还能有什么追求
  • 除了完成需求之外,我们还能追求一点别的什么东西
  • 当应用达到性能瓶颈的时候,我们优先做哪些事
  • 为什么说技术只是工具,哪些能力是比较重要的
  • 一个项目周期迭代完,所有痛点都解决了吗,是否有更好的解决方案

模块化

模块化编程是一种软件设计技术,它强调将程序的功能分离为独立的、可互换的模块,使每个模块都包含执行所需功能的一个方面所必需的一切。

为什么要做模块化:

从工程化的手段上去解决

  • 模块代码复用与抽象问题
  • 编译速度问题(AS 和代码规模)
  • 日益增长的业务代码和版本迭代速度问题

模块化有哪些好处:

  • 模块内的代码抽象与封装,代码重用、控制代码的可访问性等 (考虑模块间的数据交互)
  • 可使得项目架构比较清晰,模块(功能或业务)划分明确,便于团队成员维护
  • 将复杂问题拆解成多个小的、简单问题,采用分而治之的思想把复杂的问题简单化
  • 在一定程度上提高代码库的可维护性和项目整体质量

怎么做模块化:

以云青来应用市场的模块划分为例并结合个人的项目经历来演示下在模块化实践上的演变

image

  • Android开发处于野蛮生长的时代,单一模块,开发模式主要是MVC
  • 开发工具从EclipseAndroid Studio的过渡,异步编程也从AsyncTaskThread + HandlerRxJava转变
  • Android团队的成员只有一个人,项目的规模偏中等

image

  • 受到简洁代码架构分层的思想的影响,对项目在工程化方面进行模块分层的改造
  • 简单的划分为基础模块业务模块壳模块基础模块提供接口或者类直接供业务模块使用,壳模块则作为App的入口
  • MVC阶段:这个阶段相当于简版的MVVM,在Activity里写页面的业务逻辑,这也是后期阻碍版本迭代的原因之一
  • MVVM阶段:jetpack组件的初次尝试,没有很好的发挥ViewModelLiveData组合的作用,也感受到了LiveData的一些缺点
  • Android团队规模也始终保持在1-2个人

image

  • 更加细致地做模块划分然后为模块中的类添加单元测试,使用Dagger2框架来解决模块间通信问题
  • 注重代码质量与规范,git代码提交和PR代码合并等开发流程上的规范
  • Android团队人员增加到3-5个人

模块间的通信:

image

  • 模块之间的通信场景一般有两种,数据交互与页面跳转
  • 模块之间通信一般需要引入中介模块,有向下抽象和控制反转两种方式
  • 向下抽象是在基础依赖模块中定义模块通信接口和回调,对应模块实现相应接口和回调,另外基础依赖模块中还需要提供接口实现类的注册与接口查找功能的管理类
  • 控制反转则是对应模块定义接口与回调,在App 模块做所有的接口实现与回调,App 模块来组装各个业务模块的业务逻辑

image

  • 使用version.gradleMaven BOMcatalog等来统一各模块中依赖库的版本号
  • 使用Gradle插件来共享build.gradle中的构建逻辑和支持单一模块独立编译运行

更多需求:

注意点:

  • 没有必要为了模块化而模块化,在项目比较简单的情况下模块变多反而增加了维护成本
  • 模块的划分要注意划分的粒度,粒度太粗不能发挥模块化的优势,粒度太细则增加编译时间
  • 对于模块划分比较模糊的地带,不必纠结能够自圆其说就行,大多是凭借个人感觉

简洁代码

引言

作为一个开发,大多数的时间是跟代码打交道,有人说做程序开发就是写Bug和解Bug的这样一个循环的过程,仔细想想也确实是这样,在日常的版本迭代过程中,我们好像开启了一个循环一样,不断地重复写代码->改代码->验证大致这样一个过程,由于业务代码的不断膨胀和开发时间的紧迫,就致使开发人员在技术实现上往往选择最快速的方案,长此以往下来随着技术债不断的积累,后续迭代就会出现开发效率越来越低(阅读代码的时间长于开发时间),一些小的需求改动就会牵扯到大范围的代码改动,从而增加了这些改动引发的隐性Bug的担忧。

业务急速增长:

快速的迭代 -> 代码量快速增长 -> 代码越来越臃肿 -> 陷入代码陷阱

image

技术债:

  • 开发时间短
  • 在没有完全理解需求的情况下就动手写代码
  • 一个迭代内需求频繁变动

代码在迭代的过程中是会逐渐劣化的,模块化在一定程度上可以缓解这种情况。

为了避免陷入越来越深的代码陷阱中,需要在开发流程上做出一些改变,比如在新功能的开发前做一些代码设计,对已有的臃肿的代码进行代码重构等手段,从而实现代码与代码之间那种干净清爽的分割效果,这种效果也称为简洁优雅的代码

代码设计

代码设计强调的是在开发前把对当前新功能或业务的理解进行梳理与思考,最终体现到后续开发的封装与抽象上,开发过程中随着对需求理解的进一步加深,持续的对当前的设计进行改进。
通用场景下的代码设计 - Java 的23设计模式,以及设计模式的六大原则,有时候我们在代码实现的时候并不需要严格的遵从六大设计原则以及现有的设计模式,更像是我们在封装抽象后实现的某一设计模式的变种。
开发模式 - Android开发领域比较经典的开发模式(MVC、MVP 和 MVVM),应用层的开发内容占比也是遵从二八原则,基础的业务功能开发占了很大一部分比例,因此使用开发模式(模版代码)可以提高项目的容错率

image

性能优化工作通常发生在系统运行稳定之后,针对出现的性能问题再进行优化,但是在手机性能过剩的时代,性能优化也不是必须的,在进行日常开发时遵从一些最佳的开发实践已经能开发出质量很高的应用了。

代码重构

误区:代码重构是针对整个应用的重做,包括架构、技术选型、业务逻辑和UI都重新来一遍。
重构:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

重构的主要目的就是应对未来需求的改动,不管是可读性、可测试性、可维护性、可扩展性、设计良好或者代码质量高等等这些都是在未来需求改动时方便自己或者其他人在理解现有代码的基础上做出修改。

可读性高的代码需不需要写注释?虽然说通过阅读代码能知道代码实现的意图,但是在业务开发上总有一些不能用代码表现的点,再加上如果是多人合作的项目,团队成员之间的水平高低不一,这种情况下项目文档和注释就很有必要了。

下面是代码设计与重构在开发周期中的应用

image

预期收益

简洁优雅的代码带来的不仅仅是节约了开发时间

  • 维护成本低:对于一个长期需要维护的项目,它的维护成本肯定是高于开发成本,那这个项目代码的可读性和修改的难易程度就会在项目的迭代在开发效率、Bug定位和修复方面有很大的影响。
  • 提高性能:高质量的代码一般都会遵循最佳实践,意味着在技术实现上会采用更高效的算法、更少的冗余操作和合理的资源使用。
  • 心理健康:在优质的项目上工作可以提高心情的愉悦度和工作的积极性,所有流程执行起来都比较顺风顺水,主打开开心心工作,与之相反的情况则是比较容易陷入技术纠结与自我怀疑当中。
  • 价值实现:良好的技术氛围、技术实现的满足感、工作的成就感,项目的价值提升带来的正向的反馈。

如何开始

跟掌握一份新技能过程差不多,整体流程上就是学习 -> 模仿实践 -> 创新

  • 学习:文档、博客、书籍或源码
  • 实践:主动在新项目中应用或者有机会参与到代码质量比较优秀的项目中来
  • 创新:在有了实践的基础上做一些微创新,比如写的框架提高了开发效率,减少了应用交付的时间等等。

在看完一篇博客或者读完一本书的后并没有感觉到有所提升,只是增加了理论上的知识,只有在具体实践或者经历类似的项目才能有一些深刻的体会,学习的知识或经历会对你产生一些潜移默化的影响,帮助你在后续的编程时形成一些良好的习惯,就比如说我在看重构相关的书籍之前并不知道“代码设计是你对业务逻辑的抽象和封装,重构是你在实现代码时追求高可读性的过程”,我仅仅是按照自己的习惯在做事罢了。

团队协作:有的公司都有一套机制避免技术债的持续积累,比如团队共识、编程规范和代码提交规范等,使用单元测试和自动化测试、代码扫描工具、Code ReviewCI/CD,这些工具或者方法可以提升项目代码质量的下限。

反面案例

背景:创业项目,功能多、时间紧和人员有限(人员中途替换),Android端优先级低
接手时现状:

  • 一个人接手三个风格各异的代码,模块间代码耦合严重,代码逻辑混乱
  • 所有代码都在app模块下,项目结构组织混乱,也没有代码分层的概念
  • 项目复杂度以及代码量都已达到一定量级,后期迭代时间紧张,没有时间去重构

后续遇到的问题:

  • 尝试对某些模块进行重构,但都因为耦合严重,做了很多无用功,还因此耽误了正常的开发时间
  • 版本的迭代过程中经常出现解决一个问题引发更多问题的现象,一度陷入自我怀疑的困境当中
  • 前期技术选型问题导致后期技术栈难以升级,某一时间段内陷入技术纠结当中

项目经验:

  • 项目重构是团队自上而下的事情,没有领导层面的支持,尽量不要有任何改进代码的想法,代码能跑起来就不要去动它
  • 针对时间紧任务重的项目,为了产品的交付有时是可以暂时把技术细节忽略,但是后期有时间一定拾起来,不然项目后期维护很困难
  • 为了项目的稳定性,尽量不要有人员变动,如果无法避免就要求开发人员做好注释与项目文档

高质量开发

性能问题是一个应用在业务规模或者用户体量达到一定量级的时候必然出现的问题,较轻的症状则是启动时间长、页面打开慢、页面数据加载慢和长时间使用手机发烫等,严重的像崩溃、卡死、白屏等,解决性能问题需要从崩溃、内存、卡顿、启动、安装包大小等方面来进行优化,作为开发我们也能从解决这些问题的过程中获得成长。

image

内存优化

内存优化的主要思想就是用时申请,及时回收,这个是我们在做架构设计的时候时刻注意的点

  • 设备分级:低端机上关闭复杂动画、使用565格式图片、使用更小的缓存内存以及在OnTrimMemory回调时清理缓存内存,减少应用启动进程数、常驻进程和有节制的进行保活,推出针对性的轻量版应用
  • Bitmap优化:统一图片库,收拢Bitmap.createBitmapBitmapFactory相关接口,大图片、重复图片和图片总内存等监控。
  • 内存泄漏:常规的造成内存泄漏的方式有单例使用泛滥、资源或者对象没有及时回收、Handler或线程使用不当等,这些问题可以通过优秀的架构设计避免,也可以使用LeakCanaryProfiler或者MAT等工具分析内存使用情况。

启动优化

应用启动优化主要针对冷启动,启动过程主要有Launcher点击应用图标 -> 加载空白Window -> 创建进程 -> 创建Application -> 启动线程 -> 创建入口Activity -> 显示首屏页面这个整体的过程

  • 闪屏优化:使用一张和闪屏页一样的图片设置为主题背景
  • 业务梳理与优化:启动过程中精简业务逻辑、移除或懒加载部分模块和三方SDK延迟或者异步初始化,精简首屏页面的复杂度等
  • 线程优化:统一线程池,根据机器性能控制线程数量以减少CPU调度带来的波动,避免出现高优先级的线程等待低优先级线程的情况
  • 进阶优化:I/O优化、类重排、资源文件重排和类加载过程中去除verify过程
  • 黑科技:保活、插件化和热修复

网络优化

网络优化主要从速度、弱网和安全方面优化,此外还要考虑网络请求造成的耗电和流量问题,一个完整的网络请求包含DNS解析、创建连接、发送/接收数据和关闭连接。

  • DNS优化:使用IPIP池直连,避免DNS解析
  • 连接复用:OkHttp的连接复用机制,避免重复创建连接
  • 传输过程:Gzip压缩、Protocol Buffer和数据加密等
  • 业务层面:接口设计和数据缓存来降低请求频次、弱网情况下接口请求重试与分优先级和监听网络状态不同情况采用不同的请求策略等。

UI渲染优化

UI开发是我们做基础业务开发的最重要的部分,也是需求变动最频繁的部分,同样的UI优化页也是卡顿优化的一个重要的子集

  • 布局优化:使用viewstubmerge标签减少布局嵌套,避免重复的背景设置防止过度绘制,优先使用高性能的ViewGroup比如ConstraintLayout
  • 创建优化:使用代码来代替xml创建布局,使用View缓存池来达到跨ActivityFragment的复用,异步创建等
  • 进阶手段:Facebook开源的UI渲染框架Litho和使用skia引擎接管系统渲染的flutter

安装包优化

安装包大小对应用的性能也有一定的影响,比如应用的安装时间、运行时的内存占用和安装后的ROM空间占用等

  • 只使用一套设计图(xhdpi - 720p),使用lint定期清理无用的代码和资源
  • 使用图片压缩工具对项目内的大图进行有损压缩,一些非透明大图使用jpg格式
  • 使用网络图片,使用shapeselector代替一些纯色或渐变的图片,使用.9 图、矢量图或webp等格式的图片
  • 删除x86armable-v7下的so库或删除无用的语言资源
  • 混淆代码:添加proguard rules配置并开启minifyEnabledshrinkResources设置,使用AndResGuard对资源进行压缩与混淆
  • 支持插件化

性能优化的主要解决思路还是要从架构监控两方面入手,打造良好的架构需要一定的项目积累,监控则需要在出现具体的性能问题时使用对应的性能分析工具去处理,这样的话性能优化的处理就有发现问题前发现问题后两种场景,所以在日常开发中我们可以先从遵循最佳实践开始。

技术焦虑

现在Android的开发体系现在已经十分成熟了,最新的开发技术有MVVM开发模式,基于JetpackAAC架构,Kotlin协程Compose等等并且这一套技术栈已经存续了相当长的时间,因为旧项目的维护或者工作环境的影响,很多时候没有新框架落地的条件,这让我们在面对新生框架和技术不断迭代的大环境下,对于新技术的无法落地到项目中而感到焦虑,时常处于一种“还没用过“的不安之中。

正确的应对技术焦虑的思路是:没有条件落地,但是可以去尝试理解这个新框架或技术的本质是什么,从而缓解对未知的恐惧。真正厉害的是造轮子的人而不是使用轮子的人,我们可以广泛的学习新技术,但是切勿陷入对技术的狂热,没有需求和场景的情况下,盲目的技术深入大多数是在浪费时间,不如把时间花在真正需要提升的地方。

开发并不只是写代码,而是一个综合的能力,是需要时间慢慢去养成和踩坑来得到

开发层面:

  1. 沟通的能力
  2. 独立解决问题的能力
  • 快速学习
  • 在(ctrl+c 和 ctrl+v)的基础上一定的创新的能力
  1. 好奇心
  2. 英语

行业沉淀:

  1. 业务需求快速落地需要长期的经验积累
  2. 工作流程的改进,更好的进行团队协作
  3. 每做完一个项目,一些用着不顺手或者有瑕疵的地方都是可以提高的部分

工作效率:

习惯的培养、保持专注、提前规划、大任务拆分(看得到的进度)
工作内容大部分场景是重复且朴素的,思考能不能通过AI来提高这块工作内容的效率
人不是机器,要给自己留一些思考与总结的时间

工作之外:

身体健康、家庭、个人交际或爱好,培养一些习惯等

推荐

  • 付费课:极客时间 - Android 高手开发课
  • 书籍:重构 - 改善既有代码的设计

参考

模块化文章:

图表制作: