代码里的苟且
点击上方蓝色字体,选择“设为星标”
优质文章,及时送达
周末看到了去年做平台化方案调研的笔记,又重新看了下。里面的一些方案在当时看已经很好的满足了业务上的需求,但是里面的一些方案细节并不是很完美,就又在白板上画了画,看看有没有更好的方案。结果画了两个小时,方案细节越来越多,需要兜底的地方也愈来越多。
简单说下为什么陷入了死循环了:
因为我们是set部署,就需要将流量和数据闭环到set内,为进一步提升性能,就需要将一些set外的数据,或set内自产自销的数据放到set内,同时考虑到资源利用率问题,并没有大规模采用关系数据库,而是引入k-v集群,k-v集群很大程度上存在数据完整性的风险,所以需要做兜底方案解决db和k-v系统的数据一致性问题,在数据一致性上当时的方案是增量+时间窗口内兜底方式实现的,但是需要引入几个中间件搞,这样就有又涉及到部署的问题和资源的问题了。
另一个问题是有库存方案引起的,如果业务上对于库存数据是强一致要求的,别无他法,简单可依赖的也就是分库分表了。如果库存不敏感可以将库存打散多段进行控制,我们当时的方案是引入每日库存数量,每日库存必须小于等于总库存数量,一旦每日库存被拦截到,就不会对总库存产生压力,由于set部署,每日库存可以闭环到一个set内,不存在跨机房跨地域调用,所以风险也只有k-v系统主从延迟带来的问题了,由于对于库存不敏感,所以影响上还好,再引入兜底任务处理预期外的数据,同时加上监控整体上当时没有风险。
上面的方案已经做了很大的让步,就是系统的可用性和性能完全交由了中间件。
后来在朋友圈发了:
然后《从零学架构》专栏作者华哥留言,去看“架构设计三原则”,哈哈,结论就是合适最重要。又想到孙玄大神说过“架构是取舍的结果,业务,人力,资源,场景的取舍”。
回想起我们掌握的很多设计方案论,都是基于一种理想预设,一种诗和远方的理想,比如设计模式,比如DDD,比如SOA,比如微服务。我们会说,可以这样那样的设计,这种更像是一种站在终点回望过去的一种总结,所以又回到那句话“架构这不是设计出来的,是演进出来的”,没人能在起点就预期到未来的架构走向和扩展点。
另一个代码的苟且体现在具体的编码过程中,我们经常去聊框架,聊模式,这是一种粗的聊法,代表了我们不去谈细节,这种谈法很简单,因为无需陷入细节的泥沼,就跟婚姻一样,我们谈白头偕老,长长久久,转过头就要面对柴米油盐。
我们会通过制定一些编码规范和研发流程去尽量让大家做的事情像一个人一样,减少太大的差异。但是研发工作毕竟属于一种创作工种,我们有那么多的角度去给自己落地代码做包装。我遵循了“依赖倒置”,我遵循了“职能单一”,我遵循了“封装多态”,我的是“约定大于配置”,看似很有规则的一些思想杂糅在一起就变成了一个乡村集市,叫卖声此起彼伏,喧嚣而呱噪。
那有没有好的方式去解决呢?我们可以想一个例子,去朋友家做客,如果他的屋子收拾的干干净净,你会很注意你的每一个动作,生怕弄脏了家具。反过来,如果比较脏乱差,你约束的思想也就少了,这个是破窗效应。
所以我的想法是新项目开始的时候,尽量将系统设计的带有美感和约束,让大家在里面写代码时“有所约束”,这种约束来源于“老子不往好了写,容易被鄙视啊”。
我还见过一些“代码的坏味道”是来源于一种“伪文艺青年的长发”,就是很多系统负责人用一些看似“政治正确”的软件思想约束大家,其实这种约束没有起到任何作用,反而让整个系统代码“越来越臭”。
比如为了体现“复用原则”,让将一些代码块必须复用到其他的逻辑中,而完全没有关心这个被复用代码底层是否存在大量问题。底层有跨机房调用,又存在数据一致性风险,代码逻辑可读性差,一堆if else难以debug,稍微改了里面的一个功能根本难以确定是否对于存量需有有何影响,同时开发成本高,回归压力大,给RD和QA造成了强大的心理负担。
我经常和大家提的一点建议是,你的代码是否可以随意的被丢弃,“丢弃”某种程度体现了你代码的“优雅”,“有一种爱叫做放手”,是否可以通过一个简单的开关就可以把这个新功能干掉,而完全不影响原有逻辑呢?
为什么要考虑代码可以随意的丢弃呢?因为pm的很多需求往往是一种“实验”,只是为了某种可能,不代表结果,有可能下周就推翻了自己上周的一个需求,这种很常见。
商业模式本身就是试错,我们要接受这个现实,但是我们也要为这个现实做好准备,所以我们要分层,要做更硬性的隔离,如果为了某一个“暂时”的需求,对于核心逻辑大动干戈,将整个需求的代码片段像胶水一样贴在系统中的各个角落,将来想要下掉这个功能,成本巨大,系统到处是“小广告留下的牛皮癣”。
我见过一些代码为了体现隔离,建了十多个包,如果按照领域方式可能还好,结果是各种service,proxy,handler,domain,充血模型,贫血模型都用上了,往往造成了开发人员不知道这段代码逻辑应该放到哪里,结果其实还是一个大的service层,逻辑调用存在大量Z字形调用,一个逻辑贯穿7,8个类,十几个方法。如果是中间件系统这样还好,但是业务系统这么搞,最后就是没人敢动了。好的隔离应该是更硬性一些,分模块吧,要不就物理部署。
昨天看了阿杜写的分享也是我比较认可的方案:
在老系统增加新功能,第一原则是不要破坏现有功能,如何做到呢?搭建旁路,提供开关,充分测试。
我们目前对于网关的重构,我认为就是一次AOP的思想,技术的归技术,业务的归业务,网关系统流量通过AOP引流走,不走核心功能,流量需要做流控,鉴权,验签,路由,做解析,有可能就被重定向走了。进入业务逻辑层,根据具体的业务领域模块做路由。
回头想想做平台化调研时,讨论最多的是抽象核心路径,定义扩展点,规则编排,这种理想化,粗粒度的想法反而简单了,不简单的是如何面对日常编码时的苟且。
开关,旁路,AOP,拆分,路由,都是比较简单可依赖的思想,只有那些经得起沉淀的业务逻辑才值得被抽象和沉淀,而大部分PM需求产生的代码,你可以实现随时丢弃吗?