一般程序开发完成后就进入了繁琐无趣的后期维护阶段,请不要以为一个不停更新的项目后期维护是一件轻松的事情,它会暴露出开发过程中的所有硬伤,不规范的写法、混乱的逻辑结构、高耦合导致地牵一发而动全身。虽然开发内容实际上减少了,但人力成本反而更高。
要提高这方面效率有很多技巧,本文介绍的内容只是起点–如何快速找到项目中需要修改的代码。
一般出现问题首先看到得是表现部分,例如对话框,关系到一些具体逻辑或某个服务端请求,即使不是很清晰的部分也一定有临近的区域。根据表现找到其对应代码,我将其称为定位。
搜索关键字:泛用但低效
搜索关键字是广泛使用的方法。例如,你在节目上看到某个图片,找到图片标志,在所有代码中搜索图片标志,一定可以找到调用这个图片的代码。再如,屏幕中显示的文本,也能找到对应的语言包标识,找到相关代码。然而,这种做法效率很低,因为你要找到标识的具体拼写,搜索项目代码查找关键字也需要时间。所以下面主要介绍如何不借助搜索直接找到目标代码。
包结构
类一般可以从两个维度分类,一个维度是结构类别,如模型、视图、控制器,甚至工具类、组件,另外一个维度是业务类别,如商店、人物、战斗等各种不同模块。
目录里的文件只有一个根,类似单继承,所以你只能用一个分类做为大类别。一般情况都是用结构类别做为大类别,因为结构类别一般是固定不变地,而业务类别可能会经常变化。例如下图所示情况:
这样做是为了避免大量类混杂在一起,只有分到不同目录才能彻底解决这个问题 (目录可折叠,必要时子目录或文件命名可重复)。开发模块时,只需展开关心的目录,避免其他文件干扰。
文件命名
推荐文件名根据结构类别做前缀,如视图以 UI 开头、后台请求以 Rpc 开头等。如果没有前缀,命名时很容易遇到重复的情况。再以业务类型设定第二个前缀,使得没有目录时,按字母排序时同一系统中的类被排到一起,当然也可以防止重名。
恰当的命名会在利用代码提示引入类时提供便利,而且在打开类(ctrl+shift+T)对话框里也比较容易找到需要的类。当然,主要还是在查看包资源时,列表会比较整齐,方便找到特定文件。
以上是为了帮助你在知道类的功能和类别却不确定具体命名时使用的,你可能不记得具体名字,但应该可以判断出它所在的包,但仅仅这样是不够的。
逻辑分离是前提
逻辑要按一定规则分开到不同类中,否则你的查找目标本身就不存在。
MVC 是实现逻辑分离的方法之一。根据 MVC 思想将类分开,你就会很清楚知道,与用户交互、显示有关的类是在视图中,与数据格式转换、获取特定数据 (诸如获得图标实例)、判断 (诸如 isPropTask() 之类)、修改数据逻辑 (诸如修改经验值触发升级) 是在模型中,而制定特定服务器请求、设置模型数据、引起数个视图更新的代码一定是在服务器请求类的 result 中。这样就能确定目标位置,即使不能确定具体某个类,也能界定到某个包范围内。
当然这是需要事先约定的,但只要开发者理解并遵守这个约定,就可以做到不依赖搜索关键字也能立即找到需要修改的代码。毫无疑问这种做法是值得的。
从视图着手
视图是最容易找到的部分,然后根据交互事件模拟用户操作过程,通过调用关系一层层查找,最后就能找到需要的部分。
无论你的代码结构如何,这种方法都是通用可行的。如果你的目标就是视图,需要修改的是诸如布局、颜色、数据填充逻辑,这样做就可以了。但如果你的目标不是视图,这并不是最好的办法,因为毕竟需要从视图一层层中转,会多几个步骤而不是直接找到,这自然影响效率。
模型
模型不只是数据。
很多人不明白为什么要在数据之外套一层模型。代码放哪里比较好要以可复用性做为标准,放在模型里的逻辑应该是和数据密切交流的,最基本的是数据序列化和反序列化。这些内容不少人认为应该写在请求完成函数里,而实际应该写在模型中,因为同一个模型可能会由不同请求生成,它们传入的数据格式是一样的,只有在模型中解析才能重复利用这段功能代码。
此外模型还需要负责和数据相关的逻辑,以某个 RPG 游戏为例:
- 人物属性变化:包括金钱变化(钱不够会失败并提示充值),经验变化(可能需要升级),还有数据更新后对应视图更新;
- 道具管理:增加 / 删除道具,获得道具数量,判断道具满;
- 计算:诸如保存地图模型可以提供计算 A* 的方法;
上述逻辑是针对游戏玩家的,只会存在一个模型,数据模型是固定的,所以无论放在哪里只要集中都容易查找,但显然放在模型里最好理解,而且这样即使出现多角色需求,需要修改的地方也很少。
模型通常也会提供一些简单的数据转换方法,例如:
- 获得图标,类似的方法有获取格式化文本、获得 ToolTip 信息等;
- 校验,一般一组条件的与 或关系或者大于 等于 小于判断,也可能有循环遍历数据进行统计等复杂表达式;
如果按照约定编码,将这些逻辑存放在模型中,这样就不能通过视图快速找到代码,但模型数量少、逻辑少,会比放在视图里找快很多。你也可以用查找引用方法找到调用模型方法的代码片段,以确定这个逻辑的入口,方便逆向追踪。这样只需修改一处,所有相关部分都会发生变化。
命令(Command/Action)
所谓命令,其实很大一部分都是用来请求服务端数据的,设计中有 result 方法可以在返回结果后处理一些事情,例如整理数据格式、设置模型、调用视图更新方法。虽然很多人将这些功能发明放在视图中,但显然放在 Command 里重用率更高且更易于定位。
服务端返回的数据会经常变换,虽然具体解析是在模型中完成,但返回的数据常常是多个数据拼凑在一起,可能是数组或者一些简单数据类型,这部分可以有 Command 处理,做特殊解析并给相关模型赋值。由于这部分需要与服务端沟通,是容易出错的地方,放在 Command 里容易找到,修改时也可以省去不少麻烦。
命令除了给模型赋值,也还会有一些触发操作。如果确定某个逻辑是请求返回一定执行的也可以放在这里。例如更新视图、触发附加逻辑、首次购买某物品的弹窗。这部分常发生变化,放在 Command 执行也易于调整。当然 Command 也可以与服务端请求无关,但道理是一样的。
总而言之,原则是尽可能不要将代码放在视图,而是放在联系更紧且数量少、代码少的一方,这样就能更快找到修改代码位置。Command 一般用来调用模型和视图方法,是其他逻辑的入口,自己只有少量代码。
常量类
配置类数据只可能存在于三个地方:服务端,本地配置文件,常量类。
对于服务端数据配置,客户端只单纯接受,本地配置文件一般都保持经常更新的大量数据,所以零散数据配置通常都存在常量类里,例如等级上限、各级经验分配、特定功能花费。
很多开发者都喜欢偷懒,例如一个功能需要花费 5 元开启,开发者直接在代码里写 5。这样看起来算不算神秘数字先不谈,要知道,这种数据即使说绝对不会变而它未来变化的几率也会超过 30%。所以常量是必须有的,哪怕数值是 1,也应该写成常量,因为未来可能变成 2,在代码里保留数字始终会有隐患。如果你设置为常量,对于这类内容就可以直接找到常量修改,而不用关心其他部分,也很容易找到使用这个常量的代码片段。如果没有常量的话,就只能借助和这个数值相关的内容引导并借助关键字搜索以确保修改无遗漏了。
此外,有多版本时配置可能会变化,如果硬编码写死在程序中,需求会很难实现。
其他
这里介绍是以通用 MVC 为例,因为熟悉的人较多,容易理解。游戏中除用户界面外,结构会更复杂,虽然也有类似模型视图这样的概念,但层次需要分得更细致。其实这些分离方法都是约定的,既然要分离,就要更合理、更易于理解、更具有可复用性。高效修改首先要容易找到问题症结点,要达到这个目的,就要求代码结构整理。良好的结构也可以让约定更加简洁,易于记忆和理解。
上述这些内容,框架并不能帮到你,因为框架大多是限制你分成几部分,而没有也无法限定这几部分具体是什么内容。因此你需要刻意地约定,而这个刻意地行为,对于减少修改维护时的人力成本,比框架重要百倍。
评论