从技术到逻辑,技术逻辑分离实践

6 篇文章 0 订阅
4 篇文章 0 订阅

4月中旬,到上海听了一场大型分享会,GopherChina,从中收益颇多。


Golang经常被喷的一个缘由是没有泛型,比如Java中有类似ArrayList<T>这样的泛型,编程时可以有任何类型的ArrayList,减少代码重复度,使用起来非常方便。


360郭老师(为表敬意,以下提到讲师,都称为X老师)提出:


程序设计中有一个词叫“封装”,所谓封装,是隐藏内部实现,对外提供简单方便接口的一种编程方式。加一个中间件,则可以打通“技术”和“逻辑”之间的路。

举个例子,鲁迅先生在《孔乙己》中提出一个问题,“茴”有几种写法。闲来无事,我们可以拿这种问题来做秀,刷一下自己的存在感。但是如果在写文章过程中,我每个“茴”字写法的都不一样,但确实又都是“茴”字,这种时候,读者会有种被“打断”的感觉。即,脑子一直跟着文章思路逻辑走,但突然跳出一个特殊写法的字,读文章的人头脑中的弦就会被打断,转而大脑需要反应这个字的意思。

同样,业务逻辑过程中,最好是不要出现复杂技术。否则会导致看代码的人,要先把之前理的逻辑“压栈”,转而分析技术代码,分析透后,再弹出之前逻辑,进行整合。所有需要分析的地方,肯定是要花时间的。如果业务逻辑和复杂技术之间有一层封装,用于连接业务逻辑和复杂技术,那么在分析业务逻辑时,可以快速理清头绪,细节技术实现可以在分析完业务逻辑之后,选择性地跳转到封装里面去看。这其实也是逻辑和技术的“解藕”。

如郭老师所说,这个“间接的中间层”便是常用的“封装”(注:中间层是,但不止是封装,这里是为了更方便理解,计算机技术中,很多思想和原则都是可大可小)。回想我司业务代码,也只有极少数的第三方库可以直接参与业务逻辑。多数第三方库,或是被公共库封装,或是被各自服务特定封装。


接下来,我们可以试着从技术到逻辑,沿着“技术”—>“封装”—>“业务”的思路,简单的分析一下该过程。


我们先假想一种需求:我们要缓存用户在线状态,数据不用落地,掉线则清理掉内存数据。

要明确的一点是,所有技术都是为了实现逻辑,这里一切最简化说。我们可以想到这个问题其实很简单,无非是把用户状态放进缓存中而已。例如用Redis,用SET、GET、DEL命令即可。当用户连接完成后,保存用户连接状态,当连接断开时,删除用户连接状态。

既然要讲具体实现,我们现在要抛弃已有组件,一切自己来造轮子。


首先,我们要实现一个类似于Redis的缓存组件,大致上key-value结构就够用了。一提到key-value组合,大家自然而然地想到Hash,是的,很多语言内置了Hash,不过真可惜,我们连Hash都要造了(不是为了增加篇幅,忽悠大家吧?没错,就是这样:)。

那么,现在的过程应该是:写一个Hash实现,利用Hash实现造个key-value缓存,利用缓存实现用户在线逻辑。


有些语言中Hash是用平衡二叉树实现的,我们这里可以做借鉴,这里用SizeBalancedTree(SBT,戏称傻逼树)做吧。


Rob Pike老师说,接口越大,抽象越弱鸡。SBT是基于比较的平衡二叉树,那我们要的数据只要能比较大小就满足条件了,于是写出如下代码:


是的,我们只要能比较大小的能力,不需要其他,鲁棒性原则告诉我们:

在Go语言里面,它应该被解读为:

那么我们SBT结点需要哪些信息呢?

字段不多,够用便好。

接下来的问题是,我们需要哪些操作呢?SET、GET、DEL,对应的SBT操作为Insert、Find、Delete:


相信有些人是知道这个问题的:如果Node不存在,也会删除一个Node,所以我们需要用Find去判断一下这个Node存在与否。

如上,返回nil表示不存在。

为外部调用方便,这里简单封装一下:


好了,算法有了,现在需要在SBT之上建立缓存机制。用key-value模式是够的,那:

用key-value对存储(这里注意内部数据可信任原则,在业务逻辑中用断言一定要双返回值判断),并实现了SBT对数据的要求,能比较大小,但它不需要被导出,外部调用者不应该参与内部存储实现。

缓存的结构则比较简单:

对缓存进行GET、SET操作还是很简单的:


咦?为啥会有ErrNil和ErrType错误?好吧,我其实也想存别的类型,装一把逼的。接下来是DEL操作:

相信看到这里大家一定发现问题了:类似于Python中的GIL,有一把全局的大锁,B站毛老师在他写的开源项目Goim中有一种解决方案:利用Hash做离散,将数据几乎平均地分布到多个缓存中,降低全局大锁的影响。于是有了:


卧槽,这名字屌啊,"NewX"(牛叉)。Cityhash离散cache的那块代码借鉴(抄袭)了毛老师(侵删)的。

单纯的这样说,大家觉得没啥用,那就事实来说话吧,先看一组对比数据:

对比显示,当用Hash把数据分散到不同的缓存中后,程序不用再等全局锁,把电脑CPU跑满不是梦。当两个程序速度相差不大时,我们要看哪个更能压榨电脑的硬件及性能。明显,牛叉胜出。


最后到业务逻辑层,我们需要把缓存简单封装一下,用于判断用户是否在线:


最后,再加上一段业务逻辑伪代码,就算是大功告成吧:



—— —— —— —— —— 装逼的分割线 —— —— —— —— ——


在这次实践中,我们体会了技术与逻辑的分离,这样做更好的让读代码的人更关心某一方面,比如,看逻辑时不必关心技术实现,看技术实现时不用总想着业务逻辑。

这要求我们把代码的职责划分清楚,保证每个模块的代码职责是单一的,相互之间是组合的,互不干扰的,它们之间通过封装间接的中间层进行交互。这样做的好处是,当底层技术变化时,上层逻辑不感知,代码的改动量是极小的。

同样,泛型编程虽然方便,但也要适度使用,尽量少地和逻辑粘和,逻辑独立,技术细节也独立。在Go语言里面,多数需要泛型编程的,比如类似Java里面的ArrayList或HashMap之类,语言内建了slice和map;另外如果实在特殊的,一般需要添加一个中间层,屏蔽底层细节。

代码协作与我们协同工作一样,各司其职,办好份内之事,相互独立,又相互组合,形成一个有机的整体。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值