写优雅的代码

问题

    自古有个难题,到底代码写成什么样会是好的代码,这个问题千人千面,一万个人写一行代码可能写出一万种样子。

    语义到底是啥,重不重要,代码里的语义怎么体现。

    使用面向对象的语言,写的是面向对象的代码。这句话大家都会说,但是理解的人可能不多,经常写出来就是面向过程的代码还浑然不知(或者知道但是无所谓,能跑就行)。还有一个问题是代码的逻辑是没法消除的,只能转移,面向对象写的逻辑在对象里,面向过程的逻辑在哪里呢,这个界限不容易区分。

1.语义 & 面向对象

            什么叫语义,简单来说:见文识义。可以很大,可以很小。大到一个大的项目,一个模块,小到一个类,一个方法甚至一个变量。大家通常对项目,模块能有比较好的语义,看到项目名就能知道是干啥的,但是到小了可能就并不清楚。

            我们在看代码或者写代码的时候经常能看到一个方法里面就不用调用其他的代码,一个方法100行,觉得别扭但是也说不出来为什么,难以查看,难以复用,难以结构性测试。其实这就是面向过程的代码。

            举个例子:我们在项目中,经常使用到redis。redis是什么呢?是一个中间件,不能代表具体的业务操作。在使用过程中我们会直接使用redisServer.addXXX,redisTemplate.putXXX。然后其他人来看的时候会需要先翻出这个key是什么,在哪里使用的,才能知道这个redis的key是起到什么作用。这暴露了的问题就是,redis只是一个中间件,不加场景的使用只会给人带来不便,又因为大家的水平参差不齐,业务复杂,很有可能使用出错。

            我们可以在redisServcer的中间件层和业务层之间嵌套具有语义的一层。比如说新加一个单位缓存层,之后业务层的查询调用全部通过该层。该层暴露的方法包括了所有对于特定业务模块的操作,相较于以前的方式有非常大的不同,见文识义,即使新人来了也能看懂业务,也能知道如何修改。除了语义之外还可以对同一业务的操作进行收口,提高服用性、隔离变化、使代码可测试性变强。

            对于以上的对中间件抽象业务的一层我称之为业务网关层,可以是缓存网关,消息网关,搜索网关,统计网关。除了语义化之后还可以隔离变化,单独测试。

            话说回来,如何做到语义化/面向对象呢?

                1.时序图分辨动作:我们可以想象一下一个方法的时序图,一个柱子到另一个柱子之间的连线,柱子自己到自己的连线等都代表了一个操作,这个操作就是需要单独拿出来成 方法/对象 的。

                2.隔离说出来别扭的部分:你在当前类里面声明了一个方法,和你预设该类要干的事不相符。比如说:审批这个操作的审批通过/拒绝之后要发送消息,审批是一个方法,你在审批这个方法结束之后调用消息服务发出消息。“发消息”这个操作放在“审批”里就是不合理的,因为作用审批类我就只需要审批就好了,发消息不算是我需要做的。常用的做法是发出一个事件,有一个中间层接收该事件,然后再做操作。把“审批”和“发消息”隔离起来。

                3.适时舍弃“大而全”:大而全的命名在小的项目里面是ok的,在大型项目里是不可取。在这里我想说的是没有必要教条主义,每个操作都要精细化,比如在一个小型项目里有一个任务类,里面聚合了查询,修改,审批,但是可能就200行代码,清晰明了,这个类叫TaskService无可厚非。但是随着项目越来越大,查询、审批都变得复杂起来,这个时候就可以将之分拆。TaskQueryService、TaskOperationService、TaskApproveService。不需要一开始就教条主义、未雨绸缪,但是随着项目演进这是必须重构的(只针对该点而言)。

                4.抽象可以单独说出“作用”的代码块:这点大家可能有遇到过,但是也不知道如何总结,其实这就是面向过程和面向对象的区别。比如一个添加用户信息的操作,拿到了一个大对象,对象里面有个人信息,组织架构信息,地址信息。

                    常见的写法像:

UserInfo userInfo = new UserInfo();

userInfo.setxxx(allInfo.getxxx);

...

userInfoOperation.add(userInfo);

AddressInfo addressInfo = new AddressInfo();                        

addressInfo.setxxx(allInfo.getxxx);

...

addressInfoOperation.add(addressInfo);

                   我觉得这很面向过程,一段一段的堆代码,没有组织性,第一步怎样,第二步怎样,都堆在一个方法里。偏上层的方法应该是组织性代码,而不是具体细节代码,面向对象的方式:

UserInfo userInfo = this.userService.parseUserInfoFromAllInfo(allInfo);

this.userInfoOperation.add(userInfo);                                            

AddressInfo addressInfo = this.addressService.parseAddressInfoFromAllInfo(allInfo);

this.userInfoOperation.add(userInfo);

                    大家在这里可能感觉不到好处,因为就这么简单的赋值而已,但是一旦用户信息/地址信息需要通过一些特出处理,比如调用第三方,比如请求到实体对象的字段适配这些复杂的操作就可以被抽象出来,隔离了变化。

                    再上层一点来说,这个方法其实是添加用户信息。用户信息包括多个子信息,方法要做的是对每个子信息模块的方法调用,而不是做子信息模块的逻辑。

                    大家也不要纠结上面伪代码的调用依赖,我只是为了体现这个面向对象的思想而已。

2.是否一定要DDD?

            DDD全称领域驱动设计。大家可能都有听过,实体、值对象、服务、界限上下文、南向网关、北向网关、事件风暴、三色建模、充血模型、贫血模型,市面上也有一些DDD的框架,阿里云有提供DDD的代码脚手架。所以晦涩难懂,难以落地成了DDD的实现痛点。大家不知道这些概念都是为了解决什么而产生的。

            真正接触到DDD还是在前公司的时候,那个时候我在直播小组,当时的代码就是逻辑堆成山,各种服务调用,状态机逻辑复杂,产品提出一个需求马上要开工,一个迭代完成是正常的速度。那个时候我们就想需要对代码进行重构,因为集团当时有几个框架,也有一些成功的case,所以在那个时候我们就开始学习、调研。因为一些原因我没能参与到项目重构,但是我对DDD也有一些见解,在这里分享给大家。

            DDD最难的部分在哪里呢?其实是对业务领域的区分(呼应了领域驱动设计),充血模型是一种领域思想的体现。如果领域划分得当,充血模型是顺理成章的。

            1).将所有的业务专家(产品,以前的项目开发者,测试)将他们关注的行为都列出来。

                比如 

                         主管(角色)在任务的审批阶段进行审批,通知对应的经理。

                拆分上面这句话:

                    名词:主管,任务,审批阶段,经理

                    行为:审批,通知

                这里我们可以将该操作涉及的领域划分出来。

                        1.角色域 

                        2.任务域

                            2.1.任务状态

                        4.审批服务

                        5.消息服务

                将所有专家的需求列出来,组合重合的 域/服务,这就可以构建我们的领域了。

            2).不同领域的技术选型,中间件选型和交互方式(存储,队列,推送。。。)

            3).写代码,逻辑集中于domain,服务层只做组织调用,这是代码层次的精髓。

        下面我进行灵魂拷问,用了DDD又怎么样呢?其实给我最大的感觉就是,模块划分清晰,代码极度内聚,复用性极强。可能我替换了消息中间件,我只要修改一个类就完了。

        按照我的理解这其实就是DDD就是让你写面向对象的代码。跟上一节没有本质的区别,它规范了领域的划分,规范了写代码的格式,分包方式和调用方式。是写面向对象的解决方案。

        说到这里大家可能也就知道我想说什么了,没有必要说一定按照DDD的流程一步步来,我们需要学习的是它的思想,它是怎么做到领域拆分,如何集中逻辑,定义服务间交互方式,服务如何复用。

        在现代大型互联网项目中很难有时间去完全按照DDD的形式实现功能,一般是在项目跑了一段时间后进行重构,而且这种重构难以量化价值,老板不认可。在每次迭代中利用DDD的领域划分思想,拆分新的领域,按照面向对象的形式写代码即可。纠结于不是纯正的领域驱动模型过程可能就有些形而上了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 1、谈谈对http协议的认识流程:1.域名解析域名解析检查顺序为:浏览器自身DNS缓存---》OS自身的DNS缓存...
    Zzmi阅读 692评论 0 0
  • 优雅的代码:符合规范,代码合理、易于阅读和维护。 一、备注 1.文档注释: 简单描述当前js文件作用。是页面js逻...
    一代码农1970阅读 1,086评论 0 4
  • 工作一年,维护工程项目的同时一直写CURD,最近学习DDD,结合之前自己写的开源项目,深思我们这种CURD的编程方...
    zerouwar阅读 293评论 0 1
  • GitChat课程《领域驱动设计--战略篇》笔记,课程作者张逸 一.遵循DDD思想的代码模型 考虑1)层与模块之间...
    莫小归阅读 2,470评论 1 1
  • 1、多条件判断 Bad Good 将condition写作正则存到Map里,利用数组循环的特性,符合正则条件的逻辑...
    风之化身呀阅读 879评论 0 1