货拉拉出行Android订单详情重构实践

背景

技术改造的背景是由于业务需求和系统版本不断迭代,整个订单详情页面包含了:订单等待动画,各种场景的弹窗,地图的渲染,订单信息卡片,卡片底部的广告,问卷,以及卡片状态的转换等等 ,导致订单详情页面的代码量逐渐增多,从21年的 500行到至今的4000+行代码。随着时间的推移,原有的代码结构变得复杂且难以维护,可能存在一些问题,如代码冗余、可读性下降等。为了应对这些挑战,团队决定进行技术改造,以提高系统的可维护性、可扩展性。

这种重构涉及多个方面的工作:

  1. 代码优化: 对现有代码进行优化,去除冗余代码,改进算法和逻辑,提高代码执行效率,以确保页面的快速加载和响应。

  2. 业务粒度细化: 将页面拆分为更小的模块或组件,使得代码更具可重用性和可测试性。这样可以简化维护流程,提高团队的工作效率。

  3. 业务可灰度化:

    1. 定义: 业务可灰度化指的是系统能够支持逐步、渐进式地推出新功能或版本,而不会影响整个用户群体。在灰度化发布中,新功能首先向一小部分用户(例如5%或10%)发布,然后逐步扩大发布范围,以确保新功能的稳定性。
    2. 优势: 业务可灰度化有助于降低新功能引入的风险,可以及时发现和解决问题,同时也能够收集用户反馈,进而进行迭代改进。这种方法能够保障系统整体的稳定性,避免一次性的大规模变更可能引发的问题。
  4. 可拆卸性:

    1. 定义: 可拆卸性是指系统中的不同部分能够相对独立地进行开发、部署和更新,而不会对整个系统产生过大的影响。每个模块或组件都是可拆卸的,可以独立添加、删除或替换,而不影响其他部分的正常运行。
    2. 优势: 可拆卸性使得系统更具弹性,可以根据需求进行快速的扩展或缩减。团队能够更容易地维护和更新系统的不同部分,而不必担心整个系统的稳定性。

技术思路

一、业务代码解耦

将整个订单详情的生命周期进行分片复制,确保每个独立的模块都具备同样的能力感知,并且这些模块都能够根据订单状态的变化进行驱动。与此同时,每个模块具备灵活性,可以定制需要感知的订单状态。这意味着系统中的每个模块都能够独立处理订单详情的不同阶段,并在订单状态发生变化时,根据自身定制的需求,触发相应模块的感知机制。

二、订单状态感知

通过订单状态的定制感知,每个模块可以选择性地关注并响应与其业务逻辑相关的订单状态变化,而忽略对其无影响的状态。这种灵活性使得系统更具适应性,各个模块可以根据自身业务需求进行个性化的定制,提高系统的可配置性和定制化程度。同时,确保每个模块都具备相同的能力感知和订单状态的驱动机制,系统能够更为一致地响应用户操作,提高用户体验,并为未来的功能扩展和系统演进提供更为可靠的基础。这种定制化的感知设计有助于满足多样化的业务场景和需求,使系统更加灵活和易于维护。

三、界面ui结构分解

实施方案

一、业务代码解耦

首先,我们对庞大的代码进行解耦。我们的方法是将独立的业务逻辑抽离至一个独立的Strategy类中,该Strategy类拥有与页面生命周期相匹配的功能。这样,在处理需求和生命周期相关的业务时,我们不再需要依赖于activity通知。可以将Strategy视为activity的分身,因此我们设计了这样一个策略体系结构。

1、我们定义了一个Strategy接口,为处理业务提供了入口,并且赋予了感知生命周期的能力。

kotlin 复制代码
interface IStrategy {
    /** 处理事件 */
fun handle(vararg data: Any?)

    /**
* 优先级
*  @return  MainPhase
*/
fun getPhase(): MainPhase
}

/** 具有生命周期的Strategy*/
abstract class ILifeStrategy : IStrategy, DefaultLifecycleObserver {
    override fun onDestroy(owner: LifecycleOwner) {
        super.onDestroy(owner)
        owner.lifecycle.removeObserver(this)
    }
}

2、建立了一系列策略,包括订单启动、订单进行中以及订单结束。

kotlin 复制代码
class OrderStartStrategy(val iOrderDetailDelegate: IOrderDetailDelegate) : ILifeStrategy(){

    override fun handle(vararg data: Any?) {
         //处理一些 初始化 事件

    }

    override fun getPhase(): MainPhase {
        return MainPhase.INIT
}

    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
    }

    override fun onDestroy(owner: LifecycleOwner) {
        super.onDestroy(owner)
    }
}

class OrderProcessStrategy(val iOrderDetailDelegate: IOrderDetailDelegate) : ILifeStrategy(){

    override fun handle(vararg data: Any?) {

    }

    override fun getPhase(): MainPhase {
        return MainPhase.INIT
}
}

class OrderEndStrategy(val iOrderDetailDelegate: IOrderDetailDelegate) : ILifeStrategy(){

    override fun handle(vararg data: Any?) {

    }

    override fun getPhase(): MainPhase {
        return MainPhase.INIT
}
}

3、通过接口,为每个Strategy提供了对activityviewBindingViewModel的访问。

kotlin 复制代码
interface IOrderDetailDelegate {

    fun activity(): NewOrderDetailActivity

    fun binding(): ViewBinding

    fun vm(): ViewModel

}

4、通过Strategy的管理类,成功将所有的策略进行集中管理,进行统一的初始化,并赋予了生命周期的控制。

kotlin 复制代码
class OrderStrategyManager(delegate: IOrderDetailDelegate): AbsStrategyManager() {

    // 保存所有的独立策略
    private val map = ArrayList<IStrategy>()

    init {
        map.add(OrderStartStrategy(delegate))
        map.add(OrderProcessStrategy(delegate))
        map.add(OrderEndStrategy(delegate))
    }

    override fun getStrategy(): ArrayList<IStrategy> {
        return map
    }

}

// 初始化以及赋予感知生命周期能力
class NewOrderDetailActivity:AppCompatActivity(),StrategyManagerOwner,IOrderDetailDelegate {

    private val mStrategyManager by lazy { OrderStrategyManager(this) }

private val viewModel = ViewModelProvider(this).get(OrderDetailViewModel::class.java)

    private lateinit var binding: NewOrderDetailActivityBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.new_order_detail_activity)

       getStrategyManager().addObservers( lifecycle )
getStrategyManager().handleInit()
    }

    override fun getStrategyManager(): AbsStrategyManager {
        return mStrategyManager
    }

    override fun activity(): NewOrderDetailActivity {
        return this
    }

    override fun binding(): ViewBinding {
        return binding
    }

    override fun vm(): ViewModel {
        return viewModel
    }

}

5、拥有可拆卸和可灰度的能力的架构设计,使得对于需要灰度的业务,可以通过添加或卸载Strategy的方式进行灰度发布。这种架构允许通过几乎一行代码的简单操作,就能够解决类似新手引导功能的场景。举例而言,通过在线上配置的方式,可以非常灵活地应对不同需求,实现对新功能的灰度发布。这样的设计不仅降低了灰度发布的复杂性,也提高了系统的灵活性,使得团队能够更迅速地适应不同的业务场景和用户需求。这种灰度发布的能力,使得新功能的引入更加可控,有助于在保障系统稳定性的前提下进行迭代和创新。

csharp 复制代码
class OrderStrategyManager(delegate: IOrderDetailDelegate): AbsStrategyManager() {

    // 保存所有的独立策略
    private val map = ArrayList<IStrategy>()

    init {
        map.add(OrderStartStrategy(delegate))
        map.add(OrderProcessStrategy(delegate))
        map.add(OrderEndStrategy(delegate))
        if (灰度){
map.add(OrderGuideStrategy(delegate))
}
    }

    override fun getStrategy(): ArrayList<IStrategy> {
        return map
    }

}

通过以上架构,我们成功地将订单详情页面的庞大代码分装到各个各司其职的Strategy中。这样的分层设计使得相应的业务代码更具边界性,为后续的维护工作和新团队成员熟悉代码提供了更大的便利性。这种结构的清晰性不仅降低了代码的复杂度,还增强了系统的可维护性和可读性。未来的开发和维护工作将更加高效和容易,同时新团队成员能够更迅速地理解整个系统的组织结构和逻辑。

通过这样的架构模式,我们成功将出行司乘两端的复杂业务进行了细化,实现了对司乘两端的架构风格的统一。这不仅对团队管理起到了积极的作用,同时也为研发人员在相互切换项目开发提供了更为便捷的体验,从而显著提高了研发效率。这种统一的架构风格使得团队更容易协同合作,同时也为未来的系统演进和新功能的添加提供了更加灵活和可维护的基础。

二、订单状态感知

对于通过订单状态驱动的订单详情,因为我们的订单详情页面是实时根据订单信息更新 ui 状态的, 我们在这一基础上引入了观察者模式,使得每个Strategy都能够定制化地感知订单状态的变化,并处理与之相关的订单状态信息。这样的设计进一步提高了系统的灵活性,每个Strategy都可以根据其具体业务需求独立订阅和响应订单状态的变化,从而更有效地实现个性化的业务逻辑。这种观察者模式的引入不仅强化了系统的可扩展性,也为团队提供了更多自定义订单状态处理的机会,使得整个订单详情页面更具定制性和适应性。

1、定义一个订单观察者接口

kotlin 复制代码
interface OrderObserver {
    fun handleOrder(orderInfo: OrderInfo)
    fun targetStatus():List<OrderStatus>
}

2、引入观察者模式后,让需要感知订单状态的Strategy实现OrderObserver接口,并实现targetStatus方法,以提供需要感知的订单状态。这样的设计使得每个Strategy都能够定义其特定的目标订单状态,实现了更为灵活和个性化的状态变化处理。这种接口实现的方式为系统的不同部分提供了一个标准的方式来订阅并响应订单状态的变化,使得整体设计更为模块化和可维护。这种扩展性的增加将有助于团队更好地适应未来业务的变化和扩展。

kotlin 复制代码
class OrderStartStrategy(val iOrderDetailDelegate: IOrderDetailDelegate) : ILifeStrategy() , OrderObserver{

    override fun handle(vararg data: Any?) {
         //处理一些 初始化 事件

    }

    override fun getPhase(): MainPhase {
        return MainPhase.INIT
}

    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
    }

    override fun onDestroy(owner: LifecycleOwner) {
        super.onDestroy(owner)
    }

    override fun handleOrder(orderInfo: OrderInfo) {
        //处理订单相关业务
    }

    override fun targetStatus(): List<OrderStatus> {
        return listOf(OrderStatus.Start)
    }
}

3、不管是通过轮询、推送或主动刷新订单信息,都对每个实现了OrderObserver接口的Strategy发起通知。这确保了所有需要感知订单变化的Strategy都能够及时处理相关逻辑。这种实时的通知机制有效地保障了订单状态的同步性,使得系统中的各个模块都能在订单信息发生变化时快速响应,进而执行相应的业务逻辑。这种设计不仅提高了系统的实时性,同时也保证了订单详情页面的各个组成部分能够准确、迅速地反映订单状态的变动,从而提升了用户体验和系统的整体性能

scss 复制代码
 fun sendOrderInfo(orderInfo: OrderInfo) {
    getStrategyManager().getStrategy().forEach {
if (it is OrderObserver) {
            if (it.targetStatus().contains(orderInfo.orderStatus)
            ) {
                it.handleOrder(orderInfo)
            }
        }
    }
}

实践结果

以上是订单详情改造的核心思想,实际上出行用户端将订单详情拆分出了22个Strategy

我们根据业务复杂度细分出了23个Strategy,将对应逻辑的代码都封装到对应的Strategy中,以下几个例子:

  1. 如处理intent传值的IntentStrategy,主要用于初始化页面相关的intent传值逻辑。
  2. 如处理地图相关业务的MapStrategy,将涉及到的地图相关的业务统一抽取到这个MapStrategy中,统一维护,这样避免地图相关能力过于分散,即在将来需要替换地图 sdk 厂商,也能比较集中快速的处理能力切换。
  3. 如切换订单状态 ui 的UICardChangeStrategy,负责处理根据状态也页卡片ui的切换逻辑,在这个UICardChangeStrategy中,根据handleOrder回调的订单变化,统一处理跟随订单状态变化的ui。

降级方案

在线上环境中,因为任何小的问题都可能对用户体验和系统稳定性产生影响,因此必须具备可靠的降级方案。这意味着系统需要在面临异常或故障时能够迅速而有效地切换到一种更为基础或有限但可靠的状态,以确保关键功能的可用性。

在具体实践中,我们通过短时间内同时灰度新旧两个订单详情页面,一个是主流程业务有降级的方案,二是任何大的改造都应该具备灰度发布的能力,得益我们集成的TheRouter路由框架,我们可以轻松的将跳转到旧的订单详情页面轻易安全的中转到新的页面去

kotlin 复制代码
addPathReplaceInterceptor(object : PathReplaceInterceptor(){
    override fun replace(path: String?): String? {
        return when(path){
            ORDER_DETAIL ->
            {
                if (降级开关){
                    NEW_ORDER_DETAIL
}else{
                    path
                }
            }
            else -> path
        }
    }
})

收益总结

这是一项令人瞩目的工作成果!成功将原有超过4000行的代码通过重构,精简至仅300行,并且在上线后实现了零事故和零回滚,以下是一些可以用来描述这一成就的表达方式:

  1. 高效的重构工作:通过有条理的重构过程,团队成功地将原有的大量代码减少至仅300行,展示了高效的工作方法和对代码优化的深刻理解。
  2. 上线零事故:上线后零事故是一个令人瞩目的成绩。这意味着在重构的过程中,团队充分考虑了潜在的问题,并且在发布阶段成功地避免了系统故障。
  3. 零回滚:不仅没有发生事故,而且零回滚表明新的订单详情系统在上线后表现出色,满足了预期,并且不需要通过回滚来修复任何问题。
  4. 提升可维护性:通过这次成功的重构,不仅仅减少了代码量,还提升了系统的可维护性。这有助于未来更容易地维护、升级和扩展系统。
  5. 团队协作的胜利:这一成就是整个团队协作的胜利。不仅开发人员的技术能力得到展示,而且团队的沟通、规划和执行都表现出色。
  6. 用户体验保障:零事故和零回滚直接有利于用户体验的保障。用户可以在一个稳定和高效的系统中享受订单详情服务,这有助于提升用户满意度。
相关推荐
醉の虾6 分钟前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧15 分钟前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm24 分钟前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
天空中的野鸟25 分钟前
Android音频采集
android·音视频
asleep70137 分钟前
第8章利用CSS制作导航菜单
前端·css
hummhumm41 分钟前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王1 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
疯狂的沙粒1 小时前
对 TypeScript 中高级类型的理解?应该在哪些方面可以更好的使用!
前端·javascript·typescript
小白也想学C2 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程2 小时前
初级数据结构——树
android·java·数据结构