干货!高效编码之谋篇布局

作者 | SUNNY

企鹅杏仁架构师

高效编码不是高速编码,而是在相同或更短的时间内交付质量更高、更易维护的代码。

首先简单介绍一下,高效编码的三个步骤:

编码的步骤

1、设计组件和接口:设计组件和组件之间交互的接口。

2、编写骨架代码:编写组件和接口的胶水代码,将组件搭建和组装起来。

3、编写实现代码:编写实现接口功能的具体代码。

闲话少叙,接下来,让我们通过一个案例来演示从设计到编码全过程。

案例

案例:对网关的路由表进行分组。

背景:由于历史原因公司上海部和成都部分别建设了一个网关系统,现在需要将两地的网关合二为一,由于网关非常重要,必须要保证迁移过程的安全可靠,同时还要满足日常需求(高速上换轮胎),我们决定使用分组分批迁移的方案。由于上海网关没有路由分组功能,实施起来人工成本太高,所以需要实现对路由表进行分组的功能。

要求:

(1)不修改路由数据模型(不增加分组字段)。

(2)控制时间,必须快速的完成开发和测试(小于 8 小时)。

1 、设计组件和接口

设计组件和组件之间交互的接口,如下图:

组件说明:

(1)RouteView 路由视图组件:渲染路由表页面。

(2)RouteController 路由控制器组件:查询路由表数据,通过视图组件渲染路由表。

(3)RouteCache 路由缓存组件:从缓存中高效读取路由表。

(4)RedisService 缓存服务组件:提供 Redis 通用缓存读写服务。

(5)hashOperations 哈希表操作器组件:提供 Redis 哈希表读写服务。

注:a)网关管理面板使用 MVC + Thymeleaf 实现。b)网关路由表使用 Redis 做为“持久层”。

组件之间的依赖关系:

(1)控制器主线:RouteController 依赖视图 RouteView 和路由数据缓存组件 RouteCache。

(2)视图支线:Routeview 依赖路由数据 routes。

(3)数据支线:RouteCache 依赖 RedisService,RedisService 依赖 hashOperations。

注:如果依赖关系特别复杂,可以借助思维导图来梳理。

组件与组件之间交互的接口:

通过梳理组件之间的依赖和交互关系,我们能快速准确的定义组件之间交互的接口:

(1)RouteController # routes(group):路由控制器 # 分组展示路由表。

(2)RouteCache # getRoutes(group) :路由缓存 # 获取指定分组的路由表。

(3)RedisService # scanHash(prefix) :缓存服务 # 获取哈希表键中有指定前缀的子哈希表。

(4)hashOperations # scan(...) :哈希表操作器 # 扫描哈希表。

组件接口设计基本原则:

(1)在对的层次上做对的抽象:

组件层次功能
RouteControllerMVC 中的控制器协调 Model 数据与 View 视图的交互,控制哪些数据在哪个视图上进行展示
RoutesViewMVC 中的视图实现数据展示
RoutesVOMVC 中的模型定义在视图中展示的数据模型
RouteCache数据访问层从缓存中高效读取路由表(业务相关)
RedisService缓存服务层提供 Redis 通用缓存读写接口(与具体业务解耦)
hashOperations哈希表操作器提供 Redis 哈希表读写接口

(2)每个接口只做一件事:

接口组件职责
routes (group)RouteController查询并展示某分组的路由表
getRoutes (group)RouteCache从缓存中查询某分组的路由表
scanHash (prefix)RedisService扫描 Redis 哈希表键中有指定前缀的子哈希表
scan (...)hashOperations扫描 Redis 哈希表(通用方法)

2、 编写骨架代码

通过第 1 步的组件和接口设计,我们能快速的编写各个组件的骨架代码:

(1)RouteController # routes (group):查询并展示某分组的路由表

public Mono<String> routes (final Model model,
                            @QueryParam("group") String group) {
  // TODO: 1.1 按分组获取路由表:routeCache.getRouteMap(group): Map<routeId, routeDefinition>
  // TODO: 1.2 将路由缓存模型转换为路由视图模型:routeVoTransfer.from(RouteDefinition): RouteVO
  // TODO: 1.3 构造路由表视图模型:buildRoutesVo(routeVoList): RoutesVO
  // TODO: 1.4 渲染视图
}

(2)RouteCache # getRoutes (group):从缓存中查询某分组的路由表

public Map<String, RouteDefinition> getRoutes (String group) {
  // TODO: 2.1 扫描有带有分组前缀的缓存字典:redisService.scanHash(KEY, group)
  // TODO: 2.2 反序列化缓存路由表:routeDefinitionTransfer.from(RouteJson): RouteDefinition
}

(3)RedisService # scanHash:扫描 Redis 哈希表键中有指定前缀的子哈希表

public Map<String, Object> scanHash (String key, String prefix, long limit) {
  // TODO: 3.1 构造 scanOptions
  // TODO: 3.2 扫描哈希表:hashOperations.scan(key, scanOptions)
}

3 、编写实现代码

代码实现逻辑不是本文讨论的重点,所以此处省略具体的实现代码。

高效的奥秘

整个编码过程实际花费的时间,比我预估的时间至少缩短了 50%,预估 8 小时其实不到 4 小时就完成了。

其中的奥秘其实很简单:

(1)理顺思路:利用系统结构化思维推演、理顺了整个流程的思路

先理顺全局思路,那么在进入局部的时候,只需要专注于局部的设计。

(2)分而治之,各个击破

对系统进行整体分析,把系统分解为组件和接口,本质上是把一个大问题切分成了一个个小问题,每次只解决一个小问题,难度变小,效率也会变高。

(3)纸上谈兵:最小化试错成本

相比于一开始就编写代码并在编码的过程中修正设计,在设计稿中修改设计的成本要低得多,而且很多错误,在系统设计阶段就能够避免,因此先设计可以少走很多弯路。

(4)持久的编码上下文:无惧打断

被打断是编程最大的挑战,切换工作上下文的损耗太大了!人脑无法像电脑那样快速的切换线程上下文,况且我们也不可能在打断时存档上下文,所以切换回来时,我们要先回到断点,然后重组上下文,接着思考下一步要怎么做,进而才能接着往下做。

相对的,提前做设计,不仅是理清思路,选对方向,更重要的是它持久化了编码上下文,有了固化的设计稿,无论如何频繁的打断,我们都能快速找到断点、切回上下文、迅速进入编码状态。

后记

这套方法论不仅能够提高编码的效率,同时也能锻炼系统设计能力、文档编写能力。

设计方法就像三体世界中的一片二向箔,它能够对复杂的编码问题进行降维打击;二向箔看起来平平无奇,实则蕴含着无穷的力量。

全文完


以下文章您可能也会感兴趣:

我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值