精华内容
下载资源
问答
  • 如何写好代码?

    2020-08-04 20:05:50
    凌云时刻 · 技术导读:为赶项目进度欠下一堆技术债怎么办?业务逻辑复杂,如何处理比较好?相似的功能要不要copy修改一下复用?怎么代码注释?作者 | Guodong Chen来源 | ...

    凌云时刻 · 技术

    导读:项目进度欠下一堆技术债怎么办?业务逻辑复杂,如何处理比较好?相似的功能要不要copy修改一下复用?怎么写代码注释

    作者 | Guodong Chen

    来源 | 阿里技术

    前言

    写了多年的代码,始终觉得写出干净优雅的代码并不是一件容易的事情。

    按10000小时刻意训练的定理,假设每天8小时,一个月20天,一年12个月,大概也需要5年左右的时间成为大师。其实我们每天的工作中真正用于写代码的时间不可能有8个小时,并且很多时候是在完成任务,在业务压力很大的时候,可能想要达到的目标是如何尽快的使得功能work起来,代码是否干净优雅非常可能没有能放在第一优先级上,而是怎么快怎么来

    在这样的情况下是非常容易欠下技术债的,时间长了,这样的代码基本上无法维护,只能推倒重来,这个成本是非常高的。欠债要还,只是迟早的问题,并且等到要还的时候还要赔上额外的不菲的利息。还债的有可能是自己,也有可能是后来的继任者,但都是团队在还债。所以从团队的角度来看,写好代码是一件非常有必要的事情。如何写出干净优雅的代码是个很困难的课题,我没有找到万能的solution,更多的是一些trade off,可以稍微讨论一下。

    代码是写给人看的,

    还是写给机器看的?

    在大部分的情况下我会认为代码是写给人看的。虽然代码最后的执行者是机器,但是实际上代码更多的时候是给人看的。

    我们来看看一段代码的生命周期:开发 -> 单元测试 -> Code Review -> 功能测试 -> 性能测试 -> 上线 -> 运维、Bug修复 -> 测试上线 -> 退休下线。开发到上线的时间也许是几周或者几个月,但是线上运维、bug修复的周期可以是几年。

    在这几年的时间里面,几乎不可能还是原来的作者在维护了。继任者如何能理解之前的代码逻辑是极其关键的,如果不能维护,只能自己重新做一套。所以在项目中我们经常能见到的情况就是,看到了前任的代码,都觉得这是什么垃圾,写的乱七八糟,还是我自己重写一遍吧。

    就算是在开发的过程中,需要别人来Code  Review,如果他们都看不懂这个代码,怎么来做Review呢。还有你也不希望在休假的时候,因为其他人看不懂你的代码,只好打电话求助你。这个我印象极其深刻,记得我在工作不久的时候,一次回到了老家休假中,突然同事打电话来了,出现了一个问题,问我该如何解决,当时电话还要收漫游费的,非常贵,但是我还不得不支持直到耗光我的电话费。

    所以代码主要还是写给人看的,是我们的交流的途径。那些非常好的开源的项目虽然有文档,但是更多的我们其实还是看他的源码,如果开源项目里面的代码写的很难读,这个项目也基本上不会火。因为代码是我们开发人员交流的基本途径,甚至可能口头讨论不清楚的事情,我们可以通过代码来说清楚。代码的可读性我觉得是第一位的。

    各个公司估计都有自己的代码规范,遵循相关的规范保持代码风格的统一是第一步(推荐谷歌代码规范[1]和微软代码规范[2])。规范一般都包括了如何进行变量、类、函数的命名,函数要尽量短并且保持原子性,不要做多件事情,类的基本设计的原则等等。另外一个建议是可以多参考学习一下开源项目中的代码。

    KISS: 

    Keep it simple and stupid

    一般大脑工作记忆的容量就是5-9个,如果事情过多或者过于复杂,对于大部分人来说是无法直接理解和处理的。通常我们需要一些辅助手段来处理复杂的问题,比如做笔记、画图,有点类似于在内存不够用的情况下我们借用了外存。

    学CS的同学都知道,外存的访问速度肯定不如内存访问速度。另外一般来说在逻辑复杂的情况下出错的可能要远大于在简单的情况下,在复杂的情况下,代码的分支可能有很多,我们是否能够对每种情况都考虑到位,这些都有困难。为了使得代码更加可靠,并且容易理解,最好的办法还是保持代码的简单,在处理一个问题的时候尽量使用简单的逻辑,不要有过多的变量。

    但是现实的问题并不会总是那么简单,那么如何来处理复杂的问题呢?与其借用外存,我更加倾向于对复杂的问题进行分层抽象。

    网络的通信是一个非常复杂的事情,中间使用的设备可以有无数种(手机,各种IOT设备,台式机,laptop,路由器,交换机...), OSI协议对各层做了抽象,每一层需要处理的情况就都大大地简化了。

    通过对复杂问题的分解、抽象,那么我们在每个层次上要解决处理的问题就简化了。其实也类似于算法中的divide-and-conquer, 复杂的问题,要先拆解掉变成小的问题,从而来简化解决的方法。

    KISS还有另外一层含义,“如无必要,勿增实体” (奥卡姆剃刀原理)。

    CS中有一句 "All problems in computer science can be solved by another level of indirection", 为了系统的扩展性,支持将来的一些可能存在的变化,我们经常会引入一层间接层,或者增加中间的interface。

    在做这些决定的时候,我们要多考虑一下是否真的有必要。增加额外的一层给我们的好处就是易于扩展,但是同时也增加了复杂度,使得系统变得更加不可理解。对于代码来说,很可能是我这里调用了一个API,不知道实际的触发在哪里,对于理解和调试都可能增加困难。

    KISS本身就是一个trade off,要把复杂的问题通过抽象和分拆来简单化,但是是否需要为了保留变化做更多的indirection的抽象,这些都是需要仔细考虑的。

    DRY: 

    Don't repeat yourself

    为了快速地实现一个功能,知道之前有类似的,把代码copy过来修改一下就用,可能是最快的方法。但是copy代码经常是很多问题和bug的根源。有一类问题就是copy过来的代码包含了一些其他的逻辑,可能并不是这部分需要的,所以可能有冗余甚至一些额外的风险。

    另外一类问题就是在维护的时候,我们其实不知道修复了一个地方之后,还有多少其他的地方还需要修复。在我过去的项目中就出现过这样的问题,有个问题明明之前做了修复,过几天另外一个客户又提了类似的问题出现的另外的路径上。相同的逻辑要尽量只出现在一个地方,这样有问题的时候也就可以一次性地修复。这也是一种抽象,对于相同的逻辑,抽象到一个类或者一个函数中去,这样也有利于代码的可读性。

    是否要写注释?

    个人的观点是大部分的代码尽量不要注释。代码本身就是一种交流语言,并且一般来说编程语言比我们日常使用的口语更加的精确。在保持代码逻辑简单的情况下,使用良好的命名规范,代码本身就很清晰并且可能读起来就已经是一篇良好的文章。

    特别是OO的语言的话,本身object(名词)加operation(一般用动词)就已经可以说明是在做什么了重复一下把这个操作的名词放入注释并不会增加代码的可读性。并且在后续的维护中,会出现修改了代码,却并不修改注释的情况出现。在我做的很多Code Review中我都看到过这样的情况。尽量把代码写的可以理解,而不是通过注释来理解。

    当然我并不是反对所有的注释,在公开的API上是需要注释的,应该列出API的前置和后置条件,解释该如何使用这个API,这样也可以用于自动产品API的文档。在一些特殊优化逻辑和负责算法的地方加上这些逻辑和算法的解释还是非常有必要的。

    一次做对

    不要相信以后会Refactoring

    通常来说在代码中写上TODO,等着以后再来refactoring或者改进,基本上就不会再有以后了。我们可以去我们的代码库里面搜索一下TODO,看看有多少,并且有多少是多少年前的,我相信这个结果会让你很惊讶(欢迎大家留言分享你查找之后的结果)。

    尽量一次就做对,不要相信以后还会回来把代码refactoring好。人都是有惰性的,一旦完成了当前的事情,move on之后再回来处理这些概率就非常小了,除非下次真的需要修改这些代码。如果说不会再回来,那么这个TODO也没有什么意义。如果真的需要,就不要留下这个问题。

    我见过有的人留下了一个TODO,throw了一个not implemented的exception,然后几天之后其他同学把这个代码带上线了,直接挂掉的情况。尽量不要TODO, 一次做好。

    是否要写单元测试?

    个人的观点是必须,除非你只是做prototype或者快速迭代扔掉的代码。

    Unit tests are typically automated tests written and run by software developers to ensure that a p of an application (known as the "unit") meets its design and behaves as intended. In procedural programming, a unit could be an entire module, but it is more commonly an individual function or procedure. In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method. 

     From Wikipedia

    单元测试是为了保证我们写出的代码确实是我们想要表达的逻辑。当我们的代码被集成到大项目中的时候,之后的集成测试、功能测试甚至e2e的测试,都不可能覆盖到每一行的代码了。如果单元测试做的不够,其实就是在代码里面留下一些自己都不知道的黑洞,哪天调用方改了一些东西,走到了一个不常用的分支可能挂掉了。

    我之前带的项目中就出现过类似的情况,代码已经上线几年了,有一次稍微改了一下调用方的参数,觉得是个小改动,但是上线就挂了,就是因为遇到了之前根本没有人测试过的分支。单元测试就是要保证我们自己写的代码是按照我们希望的逻辑实现的,需要尽量的做到比较高的覆盖,确保我们自己的代码里面没有留下什么黑洞。关于测试,我想单独开一篇讨论,所以就先简单聊到这里。

    要写好代码确实是已经非常不容易的事情,需要考虑正确性、可读性、鲁棒性、可测试性、可以扩展性、可以移植性、性能。前面讨论的只是个人觉得比较重要的入门的一些点,想要写好代码需要经过刻意地考虑和练习才能真正达到目标!

    END

    往期精彩文章回顾

    如何解决大规模高性能存储可靠性问题?

    掌门教育微服务体系 Solar(中)

    演过电影的无人驾驶卡车是如何炼成的?

    全部满分!阿里云函数计算通过可信云21项测试

    如何应对容器和云原生时代的安全挑战?

    融合阿里云,牛客助您找到心仪好工作

    掌门教育微服务体系 Solar

    阿里云上新了!

    我们该不该在Rust上做点投资?

    云原生时代,谁是容器的最终归宿?


    长按扫描二维码关注凌云时刻

    每日收获前沿技术与科技洞见

    展开全文
  • 您从 如何有效整理信息(谷臻小简·AI导读版)(本书累计销售超过50万册!只用一册白菜价的笔记本,一网打尽生活中易遗漏的宝贵信息。) 导出的笔记本内容已附至此邮件。 奥野宣之 江西人民出版社出版,后浪出品, ...


    您从 如何有效整理信息(谷臻小简·AI导读版)(本书累计销售超过50万册!只用一册白菜价的笔记本,一网打尽生活中易遗漏的宝贵信息。) 导出的笔记本内容已附至此邮件。

    奥野宣之

    江西人民出版社出版,后浪出品, 2019. Kindle edition.

    这次并不是读的原版,读的是,谷臻小简的简化版本,谷臻小简是按比例浓缩图书精华的人工智能。
    将其中的一些内容摘录于此:

    第一章 一册笔记本建构的知识生产体系

    标注(黄色) - 位置 40
    要相信自己的直觉,把有趣的、炫酷的、美丽的、可爱的、想要的、想尝试的、能让工作生活更方便的事物和想去的地点不断记录和粘贴在笔记本上。

    第二章 一元化笔记法的三条规则

    • 规则1:一元化
    • 规则2:时序化
    • 规则3:索引化

    第三章 高校记录信息的书写和粘贴法

    • 写上时间
    • 加上分隔线
    • 附上标题
    • 空一行书写,便于加工
    • 在书脊或者切口处写上序号

    第四章 孕育创意的生活日志

    标注(黄色) - 位置 172
    消除选择带来的压力 2.顺便启动思考 3.促进深入写作
    标注(黄色) - 位置 175
    生活日志不仅能在知识生产中发挥作用,还能使做记录的人变得幸福、快乐。
    标注(黄色) - 位置 181
    日常的举一反三和积累对提炼关键要素而言是必不可少的。
    标注(黄色) - 位置 184
    为了能发现意外的组合,我们往往需要尽一切可能把所有触动内心的信息不断记录在笔记本上。
    标注(黄色) - 位置 187
    增加的都是你自己的体验和思考。

    第五章 帮助活用素材的知识生产技巧

    标注(黄色) - 位置 197
    我《创意的生成》一书中提到的获取创意的过程,以我自己的方法整合成以下 5个步骤。步骤 1:收集——积累常识与作为研究课题的知识步骤 2:咀嚼——阅读、回想、深入思考积累的信息步骤 3:发酵——脱离课题,在潜意识层面思考步骤 4:顿悟——创意的诞生,突然之间惊呼“明白了!”“找到了!”步骤 5:具体化——将创意化为成果

    第六章 方便自有参考的笔记索引化

    • 制作目录,写好笔记本的序号,页面,可以作为excel
    • 加上标签
    • 在切口处涂色
    展开全文
  • 简介:面对零售通如此复杂的业务场景,如何在架构和代码层面进行应对,是一个新课题。...结合实际的业务场景,Frank 沉淀了一套“如何写复杂业务代码”的方法论,在此分享给大家,相信同样的方法论可以复制...

    简介: 面对零售通如此复杂的业务场景,如何在架构和代码层面进行应对,是一个新课题。

     

    image

    阿里妹导读:张建飞是阿里巴巴高级技术专家,一直在致力于应用架构和代码复杂度的治理。最近,他在看零售通商品域的代码。面对零售通如此复杂的业务场景,如何在架构和代码层面进行应对,是一个新课题。结合实际的业务场景,Frank 沉淀了一套“如何写复杂业务代码”的方法论,在此分享给大家,相信同样的方法论可以复制到大部分复杂业务场景。

    一个复杂业务的处理过程

    业务背景

    简单的介绍下业务背景,零售通是给线下小店供货的B2B模式,我们希望通过数字化重构传统供应链渠道,提升供应链效率,为新零售助力。阿里在中间是一个平台角色,提供的是Bsbc中的service的功能。

     

    image

    商品力是零售通的核心所在,一个商品在零售通的生命周期如下图所示:

     

    image

    在上图中红框标识的是一个运营操作的“上架”动作,这是非常关键的业务操作。上架之后,商品就能在零售通上面对小店进行销售了。因为上架操作非常关键,所以也是商品域中最复杂的业务之一,涉及很多的数据校验和关联操作。
    针对上架,一个简化的业务流程如下所示:

     

    image

    过程分解

    像这么复杂的业务,我想应该没有人会写在一个service方法中吧。一个类解决不了,那就分治吧。

    说实话,能想到分而治之的工程师,已经做的不错了,至少比没有分治思维要好很多。我也见过复杂程度相当的业务,连分解都没有,就是一堆方法和类的堆砌。

    不过,这里存在一个问题:即很多同学过度的依赖工具或是辅助手段来实现分解。比如在我们的商品域中,类似的分解手段至少有3套以上,有自制的流程引擎,有依赖于数据库配置的流程处理:

     

    image

    本质上来讲,这些辅助手段做的都是一个pipeline的处理流程,没有其它。因此,我建议此处最好保持KISS(Keep It Simple and Stupid),即最好是什么工具都不要用,次之是用一个极简的Pipeline模式,最差是使用像流程引擎这样的重方法。

    除非你的应用有极强的流程可视化和编排的诉求,否则我非常不推荐使用流程引擎等工具。第一,它会引入额外的复杂度,特别是那些需要持久化状态的流程引擎;第二,它会割裂代码,导致阅读代码的不顺畅。大胆断言一下,全天下估计80%对流程引擎的使用都是得不偿失的。

    回到商品上架的问题,这里问题核心是工具吗?是设计模式带来的代码灵活性吗?显然不是,问题的核心应该是如何分解问题和抽象问题,知道金字塔原理的应该知道,此处,我们可以使用结构化分解将问题解构成一个有层级的金字塔结构:

     

    image

    按照这种分解写的代码,就像一本书,目录和内容清晰明了。

    以商品上架为例,程序的入口是一个上架命令(OnSaleCommand), 它由三个阶段(Phase)组成。

    @Command
    public class OnSaleNormalItemCmdExe {
    
        @Resource
        private OnSaleContextInitPhase onSaleContextInitPhase;
        @Resource
        private OnSaleDataCheckPhase onSaleDataCheckPhase;
        @Resource
        private OnSaleProcessPhase onSaleProcessPhase;
    
        @Override
        public Response execute(OnSaleNormalItemCmd cmd) {
    
            OnSaleContext onSaleContext = init(cmd);
    
            checkData(onSaleContext);
    
            process(onSaleContext);
    
            return Response.buildSuccess();
        }
    
        private OnSaleContext init(OnSaleNormalItemCmd cmd) {
            return onSaleContextInitPhase.init(cmd);
        }
    
        private void checkData(OnSaleContext onSaleContext) {
            onSaleDataCheckPhase.check(onSaleContext);
        }
    
        private void process(OnSaleContext onSaleContext) {
            onSaleProcessPhase.process(onSaleContext);
        }
    }

    每个Phase又可以拆解成多个步骤(Step),以OnSaleProcessPhase为例,它是由一系列Step组成的:

    @Phase
    public class OnSaleProcessPhase {
    
        @Resource
        private PublishOfferStep publishOfferStep;
        @Resource
        private BackOfferBindStep backOfferBindStep;
        //省略其它step
    
        public void process(OnSaleContext onSaleContext){
            SupplierItem supplierItem = onSaleContext.getSupplierItem();
    
            // 生成OfferGroupNo
            generateOfferGroupNo(supplierItem);
    
           // 发布商品
            publishOffer(supplierItem);
    
            // 前后端库存绑定 backoffer域
            bindBackOfferStock(supplierItem);
    
            // 同步库存路由 backoffer域
            syncStockRoute(supplierItem);
    
            // 设置虚拟商品拓展字段
            setVirtualProductExtension(supplierItem);
    
            // 发货保障打标 offer域
            markSendProtection(supplierItem);
    
            // 记录变更内容ChangeDetail
            recordChangeDetail(supplierItem);
    
            // 同步供货价到BackOffer
            syncSupplyPriceToBackOffer(supplierItem);
    
            // 如果是组合商品打标,写扩展信息
            setCombineProductExtension(supplierItem);
    
            // 去售罄标
            removeSellOutTag(offerId);
    
            // 发送领域事件
            fireDomainEvent(supplierItem);
    
            // 关闭关联的待办事项
            closeIssues(supplierItem);
        }
    }

    看到了吗,这就是商品上架这个复杂业务的业务流程。需要流程引擎吗?不需要,需要设计模式支撑吗?也不需要。对于这种业务流程的表达,简单朴素的组合方法模式(Composed Method)是再合适不过的了。

    因此,在做过程分解的时候,我建议工程师不要把太多精力放在工具上,放在设计模式带来的灵活性上。而是应该多花时间在对问题分析,结构化分解,最后通过合理的抽象,形成合适的阶段(Phase)和步骤(Step)上。

     

    image

    过程分解后的两个问题

    的确,使用过程分解之后的代码,已经比以前的代码更清晰、更容易维护了。不过,还有两个问题值得我们去关注一下:

    领域知识被割裂肢解

    什么叫被肢解?因为我们到目前为止做的都是过程化拆解,导致没有一个聚合领域知识的地方。每个Use Case的代码只关心自己的处理流程,知识没有沉淀。

    相同的业务逻辑会在多个Use Case中被重复实现,导致代码重复度高,即使有复用,最多也就是抽取一个util,代码对业务语义的表达能力很弱,从而影响代码的可读性和可理解性。

    代码的业务表达能力缺失

    试想下,在过程式的代码中,所做的事情无外乎就是取数据--做计算--存数据,在这种情况下,要如何通过代码显性化的表达我们的业务呢?说实话,很难做到,因为我们缺失了模型,以及模型之间的关系。脱离模型的业务表达,是缺少韵律和灵魂的。

    举个例子,在上架过程中,有一个校验是检查库存的,其中对于组合品(CombineBackOffer)其库存的处理会和普通品不一样。原来的代码是这么写的:

    boolean isCombineProduct = supplierItem.getSign().isCombProductQuote();
    
    // supplier.usc warehouse needn't check
    if (WarehouseTypeEnum.isAliWarehouse(supplierItem.getWarehouseType())) {
    // quote warehosue check
    if (CollectionUtil.isEmpty(supplierItem.getWarehouseIdList()) && !isCombineProduct) {
        throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,不能发布Offer,请联系仓配运营人员,建立品仓关系!");
    }
    // inventory amount check
    Long sellableAmount = 0L;
    if (!isCombineProduct) {
        sellableAmount = normalBiz.acquireSellableAmount(supplierItem.getBackOfferId(), supplierItem.getWarehouseIdList());
    } else {
        //组套商品
        OfferModel backOffer = backOfferQueryService.getBackOffer(supplierItem.getBackOfferId());
        if (backOffer != null) {
            sellableAmount = backOffer.getOffer().getTradeModel().getTradeCondition().getAmountOnSale();
        }
    }
    if (sellableAmount < 1) {
        throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,实仓库存必须大于0才能发布,请确认已补货.\r[id:" + supplierItem.getId() + "]");
    }
    }

    然而,如果我们在系统中引入领域模型之后,其代码会简化为如下:

        return;
    }
    
    if (backOffer.isNonInWarehouse()){
        throw new BizException("亲,不能发布Offer,请联系仓配运营人员,建立品仓关系!");
    }
    
    if (backOffer.getStockAmount() < 1){
        throw new BizException("亲,实仓库存必须大于0才能发布,请确认已补货.\r[id:" + backOffer.getSupplierItem().getCspuCode() + "]");
    }

    有没有发现,使用模型的表达要清晰易懂很多,而且也不需要做关于组合品的判断了,因为我们在系统中引入了更加贴近现实的对象模型(CombineBackOffer继承BackOffer),通过对象的多态可以消除我们代码中的大部分的if-else。

     

    image

    过程分解+对象模型

    通过上面的案例,我们可以看到有过程分解要好于没有分解,过程分解+对象模型要好于仅仅是过程分解。对于商品上架这个case,如果采用过程分解+对象模型的方式,最终我们会得到一个如下的系统结构:

     

    image

    写复杂业务的方法论

    通过上面案例的讲解,我想说,我已经交代了复杂业务代码要怎么写:即自上而下的结构化分解+自下而上的面向对象分析。

    接下来,让我们把上面的案例进行进一步的提炼,形成一个可落地的方法论,从而可以泛化到更多的复杂业务场景。

    上下结合

    所谓上下结合,是指我们要结合自上而下的过程分解和自下而上的对象建模,螺旋式的构建我们的应用系统。这是一个动态的过程,两个步骤可以交替进行、也可以同时进行。

    这两个步骤是相辅相成的,上面的分析可以帮助我们更好的理清模型之间的关系,而下面的模型表达可以提升我们代码的复用度和业务语义表达能力。

    其过程如下图所示:

     

    image

    使用这种上下结合的方式,我们就有可能在面对任何复杂的业务场景,都能写出干净整洁、易维护的代码。

    能力下沉

    一般来说实践DDD有两个过程:

    套概念阶段:了解了一些DDD的概念,然后在代码中“使用”Aggregation Root,Bounded Context,Repository等等这些概念。更进一步,也会使用一定的分层策略。然而这种做法一般对复杂度的治理并没有多大作用。

    融会贯通阶段:术语已经不再重要,理解DDD的本质是统一语言、边界划分和面向对象分析的方法。

    大体上而言,我大概是在1.7的阶段,因为有一个问题一直在困扰我,就是哪些能力应该放在Domain层,是不是按照传统的做法,将所有的业务都收拢到Domain上,这样做合理吗?说实话,这个问题我一直没有想清楚。

    因为在现实业务中,很多的功能都是用例特有的(Use case specific)的,如果“盲目”的使用Domain收拢业务并不见得能带来多大的益处。相反,这种收拢会导致Domain层的膨胀过厚,不够纯粹,反而会影响复用性和表达能力。

    鉴于此,我最近的思考是我们应该采用能力下沉的策略。

    所谓的能力下沉,是指我们不强求一次就能设计出Domain的能力,也不需要强制要求把所有的业务功能都放到Domain层,而是采用实用主义的态度,即只对那些需要在多个场景中需要被复用的能力进行抽象下沉,而不需要复用的,就暂时放在App层的Use Case里就好了。

    注:Use Case是《架构整洁之道》里面的术语,简单理解就是响应一个Request的处理过程。

    通过实践,我发现这种循序渐进的能力下沉策略,应该是一种更符合实际、更敏捷的方法。因为我们承认模型不是一次性设计出来的,而是迭代演化出来的。

    下沉的过程如下图所示,假设两个use case中,我们发现uc1的step3和uc2的step1有类似的功能,我们就可以考虑让其下沉到Domain层,从而增加代码的复用性。

     

    image

    指导下沉有两个关键指标:

    • 复用性
    • 内聚性

    复用性是告诉我们When(什么时候该下沉了),即有重复代码的时候。内聚性是告诉我们How(要下沉到哪里),功能有没有内聚到恰当的实体上,有没有放到合适的层次上(因为Domain层的能力也是有两个层次的,一个是Domain Service这是相对比较粗的粒度,另一个是Domain的Model这个是最细粒度的复用)。

    比如,在我们的商品域,经常需要判断一个商品是不是最小单位,是不是中包商品。像这种能力就非常有必要直接挂载在Model上。

    public class CSPU {
        private String code;
        private String baseCode;
        //省略其它属性
    
        /**
         * 单品是否为最小单位。
         *
         */
        public boolean isMinimumUnit(){
            return StringUtils.equals(code, baseCode);
        }
    
        /**
         * 针对中包的特殊处理
         *
         */
        public boolean isMidPackage(){
            return StringUtils.equals(code, midPackageCode);
        }
    }

    之前,因为老系统中没有领域模型,没有CSPU这个实体。你会发现像判断单品是否为最小单位的逻辑是以StringUtils.equals(code, baseCode)的形式散落在代码的各个角落。这种代码的可理解性是可想而知的,至少我在第一眼看到这个代码的时候,是完全不知道什么意思。

    业务技术要怎么做

    写到这里,我想顺便回答一下很多业务技术同学的困惑,也是我之前的困惑:即业务技术到底是在做业务,还是做技术?业务技术的技术性体现在哪里?

    通过上面的案例,我们可以看到业务所面临的复杂性并不亚于底层技术,要想写好业务代码也不是一件容易的事情。业务技术和底层技术人员唯一的区别是他们所面临的问题域不一样。

    业务技术面对的问题域变化更多、面对的人更加庞杂。而底层技术面对的问题域更加稳定、但对技术的要求更加深。比如,如果你需要去开发Pandora,你就要对Classloader有更加深入的了解才行。

    但是,不管是业务技术还是底层技术人员,有一些思维和能力都是共通的。比如,分解问题的能力,抽象思维,结构化思维等等。

     

    image

    用我的话说就是:“做不好业务开发的,也做不好技术底层开发,反之亦然。业务开发一点都不简单,只是我们很多人把它做“简单”了。

    因此,如果从变化的角度来看,业务技术的难度一点不逊色于底层技术,其面临的挑战甚至更大。因此,我想对广大的从事业务技术开发的同学说:沉下心来,夯实自己的基础技术能力、OO能力、建模能力... 不断提升抽象思维、结构化思维、思辨思维... 持续学习精进,写好代码。我们可以在业务技术岗做的很”技术“!。

    后记

    这篇文章是我最近思考的一些总结,大部分思想是继承自我原来写的COLA架构,该架构已经开源,目前在集团内外都有比较广泛的使用。

    这一篇主要是在COLA的基础上,针对复杂业务场景,做了进一步的架构落地。个人感觉可以作为COLA的最佳实践来使用。

    另外,本文讨论的问题之大和篇幅之短是不成正比的。原因是我假定你已经了解了一些DDD和应用架构的基础知识。如果觉得在理解上有困难,我建议可以先看下《领域驱动设计》和《架构整洁之道》这两本书。

    如果没有那么多时间,也可以快速浏览下我之前的两篇文章应用架构之道 和 领域建模去知晓一下我之前的思想脉络。

    原文发布时间为:2019-08-14
    本文作者:从码农到工匠

    展开全文
  • 程序员如何写工作日志?

    千次阅读 2018-11-06 09:09:42
    导读: 程序员开发时间越久,跟文档打交道的时间越长,需要记忆的东西就越多。每到周总结的时候,已经忘记本周做了哪些工作,为了防止这种情况,强烈建议养成日志习惯!工作日志的内容不限,可参考下面的模板 ...

    导读:

    程序员开发时间越久,跟文档打交道的时间越长,需要记忆的东西就越多。每到写周总结的时候,已经忘记本周做了哪些工作,为了防止这种情况,强烈建议养成日志习惯!工作日志的内容不限,可参考下面的模板

    • 计划 当天的工作计划 ,具体到可以完成的一个很小的任务
    • 学习 工作中接触的新内容,需要学习的地方,记录下来,及时充电
    • 工作 实际完成的工作内容,周报主要参照内容
    • 其他 包括会议,考勤,搬位置等其他特殊情况的记录

    计划

    • XXX周例会(上午9:30-10:30)

    • 统筹昨天完成情况和今天的计划工作(10:30-11:00)

    • 具体工作A(11:00-12:00)

    • 具体工作B(13:00-15:00)

    • 具体工作C(15:00-18:00) 

     

    学习

    • 学习spring cloud框架第一章

    • mysql性能优化之——索引优化

    • hadoop深入学习第二章

    工作 

    • 完成工作A

    • 完成工作B

    • 工作C未完成(由于开会)

    其他

    • 周例会,主题:讨论sass供应链系统原型UI会议15:00-17:00

    通过以上几步,相信一天的主要工作了然于心,写周报的效率也提高了很多。希望大家能够坚持下去!

    展开全文
  • 滴滴国际化项目 Android 端演进 ...如何Python一个安卓APP,附源码! 吊炸天!74款APP完整源码! 经历的某度的一场面试 来自CSDN:http://geek.csdn.net/news/detail/129998 滴滴国
  • 点击上方蓝色链接,关注并“设为星标”Java干货,每天及时推送阿里大佬分享的一篇很不错的文章,推荐收藏!导读明代王阳明先生在《传习录》谈为学之道时说:私欲日生,如地上尘,...
  • 导读如何写一手好代码,本文值得大伙一读哦。前言作为公司代码委员会 golang 分会的理事,我 Review 了很多代码,看了很多别人的 review 评论。发现不少同学 code r...
  • 正文:13923字阅读时间:35分钟 建议先马后看导读:学单片机的大概最先、最常写的通信程序应该就是串口程序了,但是如何写出一个健壮且高效的串口接收程序呢?接下来鱼鹰将根据多年的...
  • 那么如何才能出健壮的代码?阿里文娱技术专家长统将从防御式编程、如何正确使用异常和 DRY 原则等三个方面,并结合代码实例,分享自己的看法心得,希望对同学们有所启发。 你不可能出完美的软件。因为它不曾...
  • 如何写IT招聘

    2008-01-28 12:22:00
    导读: 最近看到cnblogs上的不少兄弟创业了,我个人还是很支持他们的,相信大家作了身份转换之后,是不是觉得有些不习惯,原来是人家面你,现在是你面人家,而且还要自己决定招聘些什么人,是否值得招聘这些人。...
  • 导读 团队中的每个人都会用不同的视角来’审视‘你的”作品“,那么我们如何拿出一份像艺术品一样的项目代码,然后赢得得同事们的赞许呢?作者/琼虎(安增平)编辑/hjy00前言在加入了拥有较...
  • 【翻文+导读】《Writing a C Compiler》从0开始C编译器 前言 据说,这是学习做编译器必读优秀文章,所以就做一下翻译以及导读,顺便自己也学习一下。作者Nora Sandler 也很用心,涉及一些如何从0开发编译器的实施...
  • CSDN首页> 软件研发 【观点】如何写出无法维护的代码 ...摘要:酷壳网的陈皓写了很多优秀的文章,这篇《如何写出无法维护的代码》相信一定能触动大家的兴奋点,让你受益匪浅。 导读:酷壳
  • 导读最近经常看到很多 JavaScript手写代码的文章总结,里面提供了很多 JavaScriptApi的手写实现。里面的题目实现大多类似,而且说实话很多代码在我看来是非...
  • 导读 背景 痛点在哪? 为什么要接口文档? API规范 接口工具 总结 背景 随着业务的发展,支撑组的项目也是越来越多。同时,从整个支撑组项目架构体系(含运维和运营体系),我们对系统业务水平拆分,垂直分层,让...
  • 大家可以关注我个人公众号,所有分享内容,会在公众号第一时间推送,且阅读排版更好。 公众号里有很多SAP相关...这一篇,我们就简单聊聊一般情况下,如何写功能开发说明书。 功能开发说明书,是业务顾问和ABAP..
  • 一直想买本关于操作系统的书,听不少人都说学操作系统要从linux学起,也看了好几...无意中在网上发现了这本《自己动手操作系统》,看到第一眼就有买的冲动,又在baidu上搜了下简介,于是读了这本书的导读,一下子被吸
  • 导读:命令和查询责任分离(CQRS)是指将数据存储的读取和更新操作分开的一种模式。实施 CQRS 据称可以提高性能、可扩展性和安全性。迁移到 CQRS 模式所创造的灵活性,使系统能够随着...
  • 一、文章内容吸引潜在的用户:没有人会对阅读非常沮丧的软文广告文章感兴趣,文章以及第一款的标题,通常首段是最重要的,有导读作用,一般都是2-3句话,不要太长,你的文章的第一段看了你的文章,你需要与他说话,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,110
精华内容 444
关键字:

如何写导读