|
简介工程效率工具在软件开发中扮演着至关重要的角色,它能帮助开发团队更快速、更高效地构建、测试和部署应用程序。而对于客户端开发者来说,一款端内的效率工具也是必不可少。一般来说,由于客户端发版的特殊性,发版周期相对较长,无法做到实时上线。另外整个开发过程除了客户端自身的Debug需要,还要与Server或者FE联调,以及UI走查保证设计稿的还原度,最后在验收阶段QA也有在端内快速切换各个环境、用户、deviceToken等诉求,而这些相关功能都与客户端内调试工具息息相关。它不仅帮助我们识别、定位和解决问题,而且也提高了开发效率、产品质量和用户体验。下面让我们来详细介绍下我们客户端内Debugger效率工具在设计和开发过程中遇到的挑战及我们的解决方案。遇到的问题客户端内部效率工具,自2015年APP上线以来就已存在,但伴随着业务的发展,转转APP内部调试工具的不断增加,安卓和iOS两个主要平台间的工具功能以及用户体验差异越来越明显,并且在一定程度上影响了开发和测试效率。历史版本安卓和iOS效率工具展开后截图: 安卓 iOS鉴于此,我们分别对客户端和QA成员进行调研,收集整理之后,发现对客户端开发来说反馈最多的四个问题如下:效率工具扩展成本高:原有分组固定不支持扩展,新增功能时需要手动修改原有工具框架代码。工具面板可复用性低:为了提供一个工具面板,开发人员需要编写冗余的代码,并且每次新增一个工具入口时,都需要复制和粘贴部分代码,并且功能性工具不支持直接与分组类工具入口同一层级。不支持跨组件:现有的工具系统无法跨组件实现。效率工具必须集中开发管理,不方便扩展,增加交互环节不易集成:现有的工具代码散落在主工程内,且大多依赖转转特有业务代码,甚至部分代码直接与业务耦合,不利于其他APP集成。QA同学反馈最多的问题:分组过于笼统:效率工具被分组得过于宽泛,导致每个分组内包含的工具过多,给查找工具带来不便。这使得用户需要花费更多的时间和精力在分组中寻找特定的工具。查找难度大:相同的工具在各端的命名方式不一致,工具的排序方式也不一致。QA同学在不同平台使用相同工具时带来困扰,需要额外的记忆和转换。没有定义出常用工具:经常使用的工具被分散到不同分组下,需要多余查找步骤。交互不一致:各端相同的功能,页面交互等不一致(经常被吐槽)。问题分析针对以上问题,我们决定重新设计和改进效率工具系统,一方面拉齐android、iOS两端能力,提高日常FE、QA使用体验,降低沟通成本,提高效率;另一方面,随着APP通用能力(如统跳、网络库)逐渐补齐,不同APP对应的效率工具能力也需要及时拉齐,提升整个APP端的使用体验、使用效率。现状梳理在整理之前,Android端有测试工具61个,iOS94个,梳理前的效率工具如下:梳理前根据相同特性划分出两端通用的功能,以及各端私有的功能:梳理后统一规范为了使效率工具能够统一交互,并具备可复用、可扩展、易集成和易测试等特点,我们对两端现有工具进行了梳理。经过讨论、分析、再梳理,最终达成以下统一意见:建立全新的效率工具分类分组标准,并在转转APP端优先上线;命名一致;格式统一:开关类统一格式为xxx开关。优先按功能划分,后角色划分;保留端内特有能力;目前已分区通用能力,在改分组内通用能力在上,非通用能力在下。分组统一;命名统一;建立全新的通用的效率工具组件体系,初步按照效率工具框架-通用工具-端专用工具三层结构搭建。同时参考服务器切换工具,保证android、iOS统一的交互体验;支持入口定制;支持搜索(支持拼音);支持顶部展示当前主要信息;完全解耦,无对外依赖;支持分组与非分组类型同级展示(iOS)。构建效率工具框架组件-DebuggerBox;预留通用工具组件扩展能力;效率工具入口迁移;进一步优化效率工具入口、交互体验;修改token;截图上报bug工具新增统跳地址上报,支持发送视频,支持图片编辑;WebView辅助工具新增当前M页开发人员信息;统跳工具支持扫码,历史记录,常用链接;清理缓存统一为一键回到初始化状态(环境不初始化);选择环境的优化,增加对历史用户手动输入的自定义环境内容进行保存。已独立组件的工具保持原有组件;主工程内通用debug组件统一下沉,与主工程解耦;业务组件保留。完成通用工具下沉;一致的交互体验;其他APP接入通用效率工具组件体系;低侵入线上/线下工具包自动集成应用内特有工具扩展公共工具扩展多能力扩展灵活扩展插件化最终整理出的分组如下:其中常用分组在效率工具展示面板中直接展示分组下的工具入口,与其他分组入口同一层级。每个分组下的通用工具与私有分组类型同一层级。确定了规范之后,接下来进入开发环节是项目的关键步骤之一。技术方案我们调研了业内一些成熟的效率工具框架,例如滴滴的DoKit[1],但考虑到转转内现有工具接入成本和后期维护等问题,我们选择了参照之前已有的效率工具面板样式重新开发一个轻量级的工具盒子,之后再往DebuggerBox中注册不同功能的Debug工具。考虑到线上版本携带效率工具的局限性,我们将效率工具体系划分为三个主要部分:DebuggerBox-效率工具面板UI框架DebuggerTools-每一个工具都可作为单独的子组件,它负责管理这些子组件DebuggerTrigger-效率工具的触发器由此,我们设计出的效率工具整体架构如下:三个组件都有明确定义的职责,使团队能够更容易地协作和维护工具,也保持了代码的组织性。在接下来的讲述中,我们着重讲述他们各自的工作原理。DebuggerBox在梳理完现有Debug工具之后,我们根据分组的特征以及交互统一,将DebuggerBox的item类型划分为3类:事件类型、开关类型、分组类型。下面我们先来介绍下DebuggerBox。在设计之前我们先回顾下需求,并将主要功能及特点拆分:与业务解耦支持工具扩展支持搜索顶部支持显示文本信息支持不同类型item处在同一层级支持展开收起接入简单最终根据上述功能特点,以及DebuggerBox在UI和交互上的表现,我们将DebuggerBox划分为两大组成部分:DebuggerBox核心部件悬浮窗部件而在实现上遵循典型的UI层、数据层、逻辑层分层结构,以及对外开放注册Debug工具用的PublicAPI层。由于两端开发语言的差异,实现上可能有所差异,但原理基本类似。框架逻辑示意图如下:其中UI层与平时开发无异就不再展开细说,涉及到类和对应功能:AssistiveTouch-效率工具面板悬浮球,效率工具面板收起时展示;ToolListView-展示效率工具面板的列表视图;ToolListHeaderView-用来在顶部展示自定义描述信息的视图;SearchView-展示搜索结果的View。逻辑层涉及类和对应功能:DataCenter-根据协议层注册的工具名称、icon、事件类型及父分组(可选)等信息生成ListItem对象,并根据父分组信息生成对应的层级结构,最终提供给UI层需要的数据。数据层涉及类和对应功能:ListItem-效率工具面板列表中对应的每一个列表子item数据。首先将每一个item分为事件类型以及分组类型,事件类型的item点击事件又分为普通点击类型以及开关类型。以Swift代码为例,枚举如下:// item类型enum DebuggerBoxItemType { case action = 0 case group = 1}// item 点击类型enum DebuggerBoxActionType { case jump = 0 case `switch` = 1}接下来就是定义工具信息,一个工具在面板中的信息主要包括工具的名称、icon、item类型、action类型、当前的工具id、item为分组类型时子items、当前item收起与展开状态、开关类型的默认开关状态等等,其中工具id用来做分组嵌套时使用。定义如下:class DebuggerBoxListItem { var name: String var icon: String /// 节点类型 var itemType: DebuggerBoxItemType = .action /// 非分组节点时的事件类型 var actionType: DebuggerBoxActionType = .jump /// 节点id, 初始节点id=0默认为一级分组 var itemId: String = "0" /// 分组子节点 lazy var subItems = [DebuggerBoxListItem]() /// 是否展开 var isExpanded = false /// switch 是否打开 var isSwitchOn = false /// event类型的回调 var jumpAction: (() -> Void)? /// switch类型的回调 var switchAction: ((_ isSwitchOn: Bool) -> Void)? /// 名称拼音, 搜索时使用 var namePinyin: String?}PublicAPI层涉及类和对应功能:DebuggerBox-主要提供launch方法,方便在合适的时机加载效率工具,以及配置悬浮球icon、效率工具板配置信息等。DebuggerBoxRegisterProtocol-用来对外开放注册工具入口,为了方便接入,避免外部生成DebuggerBox工具面板数据模型,这里提供了三种类型的工具注册,在使用时只需要关注传入对应数据即可:注册时,如果没有父分组Id,默认会作为一级工具或分组展示在效率工具面板最外层,注册成功后返回toolId。当注册工具为分组时,根据返回的toolId作为父分组id继续注册该分组的子工具。分组类型public func registerGroup(_ name: String, superGroupId: String = nil, icon: String? = nil) -> String普通事件类型public func registerEvent(_ name: String, superGroupId: String, icon: String? = nil, jumpAction() -> Void)? = nil) -> String开关事件类型public func registerSwitch(_ name: String, superGroupId: String = nil, icon: String? = nil, isSwitchOn:Bool = false, switchAction(Bool) -> Void)? = nil) -> StringDebuggerTools有了效率工具面板框架之后,工程内大量的调试工具还需要通过统一管理起来。对于我们内部已有的工具,我们梳理之后,将其进行归类、解耦,我们将其又划分为适合对集团内其他APP使用的通用型工具及转转特有的工具。在使用DebuggerBox注册工具时,由于两端平台的差异,下面分别介绍下两端的实现方案。AndroidAndroid端整体方案:1、通过注解标记ZZKit的调试工具及分组实现类;2、通过gradle编译插件AutoFound,在编译期收集所有的工具及分组实现类;3、不同组件,不同环境的调试工具通过组件依赖方式集成;4、zzkit:entry-trigger功能组件负责初始化,管理效率工具入口和工具集的实例化;Android架构设计iOS在iOS客户端,我们使用CocoaPods来管理组件,适合对外的通用工具我们统一打包成了单独的DebuggerTools组件,DebuggerTools内部的各个工具采用subspec形式进行管理,方便其他APP选择性接入。而与转转APP业务依赖性强的工具,我们也统一将其打包成DebuggerTools_zhuanzhuan组件,各个工具依然采用subspec集成。为了简化工具注册流程,我们提供了一个分组注册父类DebuggerRegister,在集成时可以通过选择继承该类并在类名后添加下划线+排序序号来实现将分组排序,另外也可通过实现父类的registerSubTools方法来注册对应的分组下的子工具。大致思路是在启动后使用runtime获取DebuggerRegister的子类,并在排序后依次调用子类的registerSubTools方法,具体流程如下:示例代码:static func getAllSubclasses(of parentClass: AnyClass) -> [AnyClass] { var result: [AnyClass] = [] // 获取当前加载的所有类的数量 let classCount = objc_getClassList(nil, 0) guard classCount > 0 else { return result } let classes = UnsafeMutablePointer
|
|