码农戏码

新生代农民工的自我修养

0%

《程序员软技能》中,提到过一种思维,要把自己当成一家企业,而就职公司是我们的客户。

既然如此,我想需要考虑的最重要的事情应该是我给客户提供了什么产品,产品是什么价格?

对于程序员来讲,提供的最根本产品自然是代码,我们现在需要考虑的事就是代码的价格,平均到基本单位,就是每一行代码值多少钱?

当下市场,先考虑一下代码语言种类:

使用java语言写的一行代码

使用go语言写的一行代码

使用python语言写一行代码

亦或写一行sql

甚至调试一个AI模型参数

这些代码它们的价格肯定是不一样的。想到的第一个因素估计是写的人不一样。都写相同的语言代码,什么在决定价格?自然是写代码程序员的水平了。如同时期研究生相对本科生自然值钱些。

即使都是出于同一人之手。那么是什么决定了价格?是因为牛逼吗?

PHP是最好的语言?从高维度讲sql man与AI调试师没什么不同,那决定价格的最根本因素是什么?

在市场上,决定价格的最重要因素是需求

现在写一行VB语言会比java语言值钱吗?不是VB语言不好,终究是因为市场需求。当更先进更高阶的技术出现,更能满足市场需求时,价格自然上涨。这也是为什么苦逼程序员必须随着技术更迭不停地学习。甚至要预判技术趋势,提前投入精力学习。才能保障自己写的代码价值不被贬值。

除了市场需求因素,程序员水平,还有什么因素呢?我们还需要考虑哪些问题?

把需求换种说法,就是写的代码是否有用?

从关注程序员自身来讲,可以考虑再深入点:

1、我写的代码对别人有没有用呢?

写个hello world,或者写了个微信,似乎都有用

2、我写的代码对别人有用的话,有多大用处呢?如何做到最有用?

hello world作用可以作教程,对小白编程基础培训,微信可以通讯、支付、摇一摇,似乎用处都不小。因此它们的作用不能从程序员侧考虑,而得从客户侧考虑。

3、我写的代码对别人有用的话,对多少人有用?如何做到对更多人有用?

hello world面对的人群是刚要入门编程人员,而微信是全民应用;要想对更多人有用,全民编程似乎不太可能

4、我写的代码对别人有用的话,在多长时间段对别人有用呢?如何做到让这个时段更长些

除了上面的问题,还需要从客户侧考虑,不能只是埋头写一行行的代码,还得考虑客户的需求,这样又需要考虑一些问题:

1、他们真正的需求是什么?最需要的是什么?

需要程序员?需要35岁以下的程序员?

2、我是那个能满足他们需求的人吗?

在优化人员时,优化名单上会出现你的名字吗?

3、如果我能,我有没有可能成为必需?

4、如果我不能,我怎样才能?

5、有必要一定由我去满足他们的需求吗?

这么多的问题,总结成一句话,行动指南:挑最需要的事情做,自然就成了最被需要的人。


常听些大佬讲,要有商业sense,而一切商业模式的根本,怎么赚钱,赚什么钱,赚多少钱。

在现如今充满物质喧嚣的大环境中,总包、副业刚需、内卷这些词时时充斥我们时,更应该考虑下商业底层逻辑。

我想作为程序员,“我的一行代码值多少钱?”,这个问题是最基本的商业sense。

这些年中台、微服务都是技术浪潮中的弄潮儿。两者的命运似乎是所有技术新词的缩影:先谈,再建,后拆,最后平静。

如中台,开始时聊什么都得带上中台,战略层喜欢谈,执行层也喜欢谈,再后面跟随一线大厂纷纷搭建自己的中台,然后就是反思,拆除中台,最后平静看待中台。

中台可以说已经经历完整的生命周期,而微服务周期也差不多,但对于“拆掉”,两者的声势与目标却不太相同。

《中台是什么》中提出,“效能下限”与“创新上限”就像翘翘板,产生了哑铃效应,而中台则是追求效能的极致,同时却也降低了创新上限

建中台是为了效能,拆中台是为了创新。

以阿里为代表的大厂对拆中台真是高举高打,但看看微服务,可没哪个大厂高喊要拆掉微服务,可见他们俩还是有本质差别的。

更神奇的是,不管是拆微服务还是拆掉微服务,本质需求却是一致的:提升效能

为什么都是提升效能,从两种行为分别阐述一下

为什么拆分微服务

首先一起回顾一下,为什么要拆分微服务?对于这个问题不得不再向前回退,回到单体架构时代

在微服务架构出现之前,单体架构是永恒,天经地义的。以致于“单体架构”一词都没人提出。

项目起初,单体架构无疑是最佳选择,不仅易于开发、易于测试、易于部署,且由于系统中各个功能、模块、方法的调用都是进程通信,性能是最高的。

甚至在项目从小型发展为中型时,也没有那么不堪。虽然是单体架构,但内部结构并非“铁板一块”,在业务量级可承载范围内,也有一定程度的扩展性。

从两个视角观察扩展性

在纵向角度,绝没有一个项目完全是个“大泥球”形态,至少都会以分层架构风格对代码进行纵向层次划分。

在横向角度,单体架构也支持以功能、技术等维度划分,拆分成各个模块,以便代码重用和管理,甚至提取出各种形体组件,如jar

那拆微服务解决了哪些效能问题?

第一程序效能

在于应用程序的某个方面给基础设施带来了过重负担,这反过来又很可能会导致糟糕的用户体验。

如,图像处理需要大量CPU,如果CPU负载变得非常高,这将会导致应用其他处理资源的饿死现象。影响系统延迟,甚至影响系统可用性。

也就是说应用程序中局部缺陷会造成全局问题。局部间没有隔离能力,一旦出现内存泄漏、线程爆炸、阻塞、死循环等问题,将影响整个程序。不仅导致单个功能不可用,甚至整个程序的效能都降至为零。

从程序维护性来说,所有代码都在同一进程,无法做到单独停止、更新、升级某一部分代码。只能制定专门停机更新计划,整体做灰度发布,A/B测试。

第二团队效能

与应用的关系不大,但关系到如何组织团队。在应用程序的特定部分,投入工作的人越多,开发和部署就会越慢,而且越容易出错。

如,抢占持续部署同一服务,出现排队现象,意味着本可以交付产品的工程师只能坐等轮到他们进行部署。出事“紧急事件”时,多个团队代码都需要回滚。

综上所述,简单总结一下,单体架构并不是一无是处,项目起始阶段依然是最佳选择;

当对应用程序性能要求超过单机能力,以及软件的开发人员规模明显超过了“2 Pizza Team”时,
不管是程序效能还是团队效能都已经达到瓶颈,此时可以通过微服务架构来解决这些问题。

微服务怎么解决效能问题?

对于程序效能,在单体架构时代,想要整体系统的可靠性,我们只能努力让每一个模块,每一行代码都可靠,以达到整体系统的最终可靠性。

然而常常事与愿违,战术层面再优秀,也难以弥补战略层面的缺陷。在构建大规模系统时,人们的观念也从“尽量不出错”向正视“出错是必然”转变,在此前提下,历史悠久的单体架构终被挑战。

微服务把独立的单体架构内部依赖组件,由“编译时期”推迟到了“运行时期”,对时间维度的解耦,带来了运行时动态扩展、服务间技术异构、服务独立交付等好处。

也就解决了上面所述的局部间没有隔离能力造成的全局性故障,导致整体程序效能降至为零的情况。也实现了局部维护的能力,提升系统整体可维护性,保障系统整体可靠性与可用性。

对于团队效能,系统不再是一块整体,团队更加独立地工作,独立地部署,从而发布更多产品。

尤其在康威定律的指导下,划分组织边界以及服务职责范围,让组织之间更高效默契的沟通以及相互配合提升整体效益。

为什么拆掉微服务

微服务的确带来了很大的收益,不管在系统扩展性,还是在组织扩展性,都促使商业最大限度的规模化。

然业务不是永远增长的,随着业务增长乏力,收益萎缩,需要探索新商业机会,原先高成长的业务团队规模也慢慢收缩,之前的服务系统也慢慢沦为遗留系统。

从原先增长期的每个系统0.5人,到维护期每个人10个系统,服务与团队与康威定律极度不匹配。

简而言之,在高增长期康威定律带来的所有收益,随着时间的推移,业务收缩,都变成了“遗留”团队的负债。

所以需要拆掉微服务,改变服务边界以匹配团队边界,平稳回归康威定律。当然也不是彻底拆掉所有微服务回归单体架构。重点是重新调整职责范围,拆分成符合团队的服务边界。

此时再回头看微服务概念时,当初纠结的“微”到底是多大的问题,已经完全不重要。微服务只是相对单体架构(Monolithic)的称呼,“微”不代表任何实际的内容。

我们需要更多的关注“合适大小”,服务经过了恰当地设计,以满足其需求:它负责“合适数量”的功能。而且,至于什么是“合适”并不是一个静态的概念,它取决于团队,即团队的技能集、组织的状态、投资回报率(return-on-investment,ROI)的计算、持有成本以及该服务运行的时间点。

简单总结一下,拆掉微服务,相对程序效能,更多的关注点在团队效能,服务边界匹配团队边界。其次,在整合团队,回归康威定律的过程中,业务流量也是在减少的,程序效能问题也再像扩张时期那么显著。

总结

一切技术都得服务于业务,而业务形态决定了技术形态。

没有完美的业务,也必然没有完美的技术,只有两者相匹配时,才能相得益彰。

不管是建,还是拆。都是适时的选择。架构只有顺应环境才能生存,最大化业务价值。

参考

拆掉微服务

之前总结了一篇文章《单服务器高性能》,主要整理了两方面的知识:

一是socket以及IO常识

二是单机高性能模式

你会发现IO知识一般不会单独出现,常会与socket,linux底层相关知识结合出现,所以在学习IO时,总会有很多的背景知识,不然会很吃力。或者不明就理。

这是为什么呢?

与socket关联,应用角度看,是因为发生IO时,数据来源与目的地除了磁盘就是网络了,而网络必谈socket;其次从OS角度看,linux思想就是一切皆是文件,socket也是文件的一种。

与linux关联,上面写了linux一切皆是文件,IO操作对象都是文件;其次IO操作得涉及内核,IO的一切优化都需要得到OS的支持。

所以你会发现,谈到I/O multiplexing、mmap、Zero-copy这些提升IO性能的技术时,都需要OS层面出手,否则很难有大的提升。

其中I/O multiplexing与Reactor经常被搞混,甚至被认为是同一种东西。

我再尝试总结一下I/O Multiplexing的前世今生以及与reactor的关联:

I/O multiplexing

multiplexing概念多用于通信领域,详细可看Multiplexing – Definition – Types of Multiplexing: FDM, WDM, TDM,截取两张图片表示multiplexing技术前后的对比:

在没有multiplexing时,每次通信,一个通道上只能传输一个信号,浪费了带宽

当有了multiplexing技术后,通过设备多路复用器multiplexer(MUX),一个通道可以传输多个信号,带宽最大化利用。在接受端,通过信号分离器demultiplexer(DEMUX),再把多个信号分离开。

通过上面的两张图可以看出,multiplexing技术里面有两个设备比较关键,一是MUX负责多路复用,另一个是DEMUX负责多路分发。

理解了multiplexing,再看看I/O

简要回顾一下:

从最原始的BIO,PPC模式进化到TPC,通过线程池有效控制线程扩张,但线程上下文切换也带来了性能损耗依然不能小觑。关键是IO处理没有丝毫改进。

从图中可以看出,之前block的依然block,之前system call的依然发生,对于成千上万的连接高并发,怎么达到高性能?

怎么办呢?

这一次还是kernal体现了大爱无私的胸怀,在提供了blocking read方法与non-blocking read方法后,又体贴地想出了两种方法

1、不要每次都来问了,一次性打包问吧。把所有的system call整合在一起,一次性打包给system kernal,结合上面的multiplexing技术,I/O multiplexing由此而来。

2、不要总跑来问我了,有情况我通知你吧。对,这就是著名的Hollywood Principle

根据这两个办法,打造出了最终的完美体epoll。当然在这个完美体前还有select、poll两个被丢弃的废品。至于这三者区别,可详看《单服务器高性能》


Reactor pattern

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.

更详细的介绍可看Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events

看一下reactor pattern里面包含的部件:

1.event:
IO event like write, read, timeout and signal.

2.Event Source (ES):
file descriptor (fd) in linux and Socket or Handle in Windows. Program adds the event that it cares on the given event.

3.event_demultiplexer (ED):
select() and epoll() provided by the OS. The program firstly add the event source (fd) and the related events to the ED. When event comes, ED eill notify that the events on one or more EVs are ready where program can process the events in nonblock way. The same ED way like select(), poll() and epoll() are used in Libevent where they are encapsulated by eventop struct.

4.event_handler_interface and event_handler_imp (EH):
EH has a serial of interfaces and each interface refers to a different event.Reactor will invoke certain interface when some certain event fets ready. EH usually binds a valid ES.
In libevent, it is event struct.

5.reactor:
It is the event management. It uses ED internally to add and remove event. When event is ready, invoke the callback function on the event. It is actually event_base struct in libevent.

可以看到在reactor pattern中,有个reactor部件。而还有个部件event_demultiplexer是由多路复用技术支持。

所以在谈到reactor时,是reactor模式,还是reactor部件?也没必要咬文嚼字了,反正他们是一家。

不仅如此,还有一股编程思潮:响应式宣言里面也有reactor

头晕,反正他们都是一家族的。

总结

一堆名词堆一起,看是又不是,是不是剪不断,理还乱。

简单讲:

结合I/O与multiplexing,由此得来I/O multiplexing。

Reactor模式中包含了reactor部件及依赖了多路复用技术。而多路复用技术的底层epoll又吸取了reactor思想。

道生一,一生二。阴阳之道,存乎万物之间。

不管在做系统分析,还是系统设计时,我们大概率都会提到领域模型这个词,奇妙的是虽然大家都在谈论领域模型,但每个人心中都有一份对领域模型的认知。

套用DDD,我们需要统一语言,首先需要对“领域模型”有一个统一认知。达成共识。

你可以暂时挂起大脑进程,想想:“领域模型是什么?怎么描述?”

世事万物都在变化中发展,就如同“手机”,十年前和现在,人们对它的认知也是不一样的。所以我们一起回顾一下最原始的“领域模型”是什么,你是否记起大明湖畔的领域模型。

“领域模型”最早流行于OOA中,简单回顾一下OOA/D

OOA/D

分析:强调的是对问题和需求的调查研究,而不是解决方案。例如,如果需要一个新的在线交易系统,那么,应该如何使用它?它应该具有哪些功能?

设计:强调的是满足需求的概念上的解决方案,而不是实现。例如,对数据库方案和软件对象的描述。设计思想通常排斥底层或“显而易见”的细节。最终设计可以实现,而实现则表达了真实和完整的设计。

++分析和设计可以概括为:做正确的事和正确地做事++

OOA:强调在问题领域内发现和描述对象(或概念)。例如,在航班信息系统里包含飞机、航班和飞行员等概念。

OOD:强调的是定义软件对象以及它们如何协作以实现需求。例如,软件对象Plane可以有tailNumber(飞机唯一标识)和getFightHistory方法(飞行过的航班)

领域模型

领域模型是OOA中最重要的和经典的模型。

定义

领域模型是对领域内的概念类或现实世界中对象的可视化表。也称为概念模型、领域对象模型和分析对象模型。

不是描述软件类、软件架构领域层或有职责软件对象的组图。

Why

为什么需要领域模型?去掉修饰语,为什么需要模型,这在DDD系列文章中已经解释:模型是对业务复杂度的简化和提炼。帮助我们更好地理解业务。

同理领域模型能够使我们理解关键概念和业务知识。

我们在设计和实现时,软件类名称也大多源于领域模型的名称,以使对象具有源于领域的信息和职责。

这样可以降低我们思维与OO建模之间的表示差异

How

如何创建领域模型?

  1. 寻找概念类
  2. 将其绘制为UML类图中的类
  3. 添加关联和属性

寻找概念类

根据领域模型定义,需要先找到概念类。

概念类是思想、事物或对象。可以从其符号、内涵和外延考虑。

符号:表示概念类的词语或图形

内涵:概念类的定义

外延:概念类所适用的一组示例

考虑购买交易事件的概念类。

可以使用符号Sale对其命名。

Sale的内涵陈述为“表示购买交易的事件,并且具有日期和时间”

Sale的外延是所有销售的例子,或者说是世界上所有销售实例的集合

描述类图

领域模型描述的信息可以采用纯文本方式表示。

但是在可视化语言中更容易理解这些术语,特别是它们之间的关系,因为我们的思维更擅长理解形象的元素和线条连接。

在应用UML时,领域模型被描述为一组没有定义操作的类图。

关联

关联是类之间的关系,表示有意义和值得关注的连接。

关联能够满足当前所开发场景的信息需求,并且有助于理解领域。

关联被表示为类之间的连线,并冠以首字母大写的关联名称。

关联末端可以包含多重性表达式,用于指明类的实例之间的数量关系。

关联本质上是双向的,方向箭头只是为了方便阅读,默认是从左往右。

总结

没有所谓唯一正确的领域模型。所有模型都是对我们试图要理解的领域的近似。

领域模型主要是特定群体中用于理解和沟通的工具。

有效的领域模型捕获了当前需求语境下本质抽象和理解 领域所需要的信息,并且可以帮助理解领域的概念、术语和关系。


到此,已经完成追忆大明湖畔的领域模型,也是OO风云初起时代领域模型的含义。

如果你想更多的回忆,可以去看看以往OO方便书籍,本文内容大多来自《UML和模式应用》。

现今,领域模型演义出更多新的含义,如Martin Fowler提出的充血模型,以及DDD中的领域模型。

在与别人交流时,我们得听声听音,是否在同一频道。

那一年有个程序员在github上撕心裂肺痛首疾呼

程序员呐喊

这句呐喊,喊出了多少程序员内心的苦楚,行业更新迭代太快,而年龄在不断增长,精力在下降,尤其现如今的内卷文化,怎么才能不掉队,保持竞争力?似乎唯有保持学习能力一条路。

我们是怎么学习的呢?学习的效果如何?需要去对学习有一定的元认知能力。

李笑来老师有个专栏,叫学习学习再学习。意思是讲先把学习这件事学习会了,再去学习其他知识。

我也一直在反思自己的学习能力,学习效果与速度,所以也一直在学习怎么学习,怎么学习才有效果,脱离低级学习效能。毕竟学习本身不仅对自身有帮助,也是家中无矿普通大众唯一可以传承给后代的财富,知识时代,缺什么也不能缺少学习能力。

功夫

我们学习的动机是什么?

一开始我们靠兴趣,但是兴趣多变;然后我们追新知,发现新知进化得比我们学习的速度还快;之后我们回身去读经典,却发现经典一辈子也读不完;于是我们开始寻求底层逻辑。

然而底层逻辑是高度抽象的,人类对抽象的认知都是非常困难的,幸运的是所有对抽象的认知都是源于对大量具体事物认知的抽象。

我们一般都不是骨骼清奇的天才,大多数时候只能渐修顿悟。就算是真经放在眼前,也没有识货的能力。

举个切身经历的案例,在我刚入职场时,那时我还很痴迷于阅读源码,一天我灵机一动,学生时代就倒腾了几年的web开发,都是跑在web container中的,为何不看看tomcat源码呢,下班看,上班干完手上活也看。一天领导问我在干吗,我兴致勃勃地说在看tomcat源码,写得真精妙。

领导把我叫到一边,语重心长地说,学习源码是不错,但对我们当前工作有直接帮助吗?我们开发游戏,用的是TCP协议,使用的是mina、netty网络框架,如果现在项目中要准备使用web了,再看也不迟。就算你现在看了,以后我们真用上,其他人去学习,你也不一定比他们提前多少。

“切,又忽悠我多干活,压榨”。这是我当时第一反应。你觉得这是真经吗?

领导的这番教导,我也是过了很久才慢慢醒悟。是什么底层认知逻辑,且看文末总结。

因此我们还是得踏踏实实地积累基础知识,同时,需要勤于思考,学会抽象,学会抓本质。

花功夫,事上修。否则读遍经书也枉然。

如何学习

学习一样技能,需要经过三个过程

1、编码:知道并理解,形成一些潜在的心理表征

2、巩固:强化心理表征,通过遗忘,再考试,再练习不断地被挑战

3、检索:学以致用

细化一下这个过程

大概会经过这几个步骤:获取、理解、拓展、纠错、应用

获取

不再像过去贫瘠时代,获取知识本身的难度,现今是如何面对信息大爆炸时代,过滤出精品。

获取知识的两个要点:

1、速度

2、质量

对于速度,有几个方法:

首先让自己变成一个文字型学习者,现在有视频、音频等多媒体信息,而文字是最快速的获取方式。回想微信聊天,是看文字快还是语音快?

其次学习一些快速阅读的技能

1、指读法:对的,你没看错,用手指指着文字阅读。

2、练习阅读法:刻意练习,比如手指移动更快些,又快又能最大吸收信息

3、积极阅读法:也就是带着问题阅读,或者读完问自己几个问题,这一节主要观点是什么;怎么能记住主要观点;观点拓展以及应用

比如最近有篇转来转去的文章《我在美团的八年》,作者总结了几个原则,第一次看觉得作者讲得真不错,可当在聊天群中再次看到被人转发时,只是想这文章我看过,作者写得不错。但具体内容可能都忘光了。这时不妨回忆下,作者说了哪个原则?最认同哪个?能再加减点别的原则。再阅读比对一下。

而对于质量,是要去学习第一手资料学习原理并且及时更新,尤其像程序员,不然看到的知识可能是错的。

比如,我两年前总结的一篇JVM参数文章:《百万QPS系统JVM参数》,如果你现在看到直接照本全收,估计有些参数有效,有些无效,整体效果你会失望,但也别说我乱写,因为JVM在发展,参数也在更新,而且也是在我当时的系统背景下得出的结论,我的认知也可能是不完整的。

但你掌握了JVM调优原理,并结合最新的JVM规范因地制宜,是没问题的,质量也是有保障的。

再比如你正在看的这篇文章,也是很多手信息后的产物。不是说就不要看了,至少可以激发你的思考,进而去拓展学习。

质量和速度兼并的学习技能是联机学习,我们要做知识的路由器

通过交流交换思想,再深入思考,整合归纳。相比个人学习,不管是速度还是质量都会有更大的提升。

为什么要去大厂,不就是能更方便地遇到更牛B的人,快速吸收他们的最新思考成果。

拓展

一味地获取内容和信息,并不一定能达到想要的效果。启发思考才是学习的目的,所以反思是学习真正发生的时候,这样才能去改变我们的行为和思维。

拓展就是思考与反思阶段,只有拓展得好,才会将学到的知道举一反三、触类旁通。也只有这样,后期我们才能应用好知识。

拓展有三种方式:

1、深度拓展

不仅仅理解一个结论就结束,还得挖掘知识从何而来?结论来自何处?一个发现是如何做出来的?结论之前的试验是怎么做的?怎么想起来做的?

一句话,我们要知其然还得知其所以然,多问why,以及how。

2、横向拓展

与知识周围建立联系。

知识不会孤立地存在,与此类似的结论还有哪些?是哪些地方类似?不同的地方在哪里?同一时期还有哪些其他的发现,同一个发现者还有哪些发现,在同一个领域里还有哪些发现?

3、纵向拓展

知识都遵循一定的模式,同样的模式在其他知识中也会出现,能将一个公式与一个自然事件相联系吗?

纵向拓展是有相当难度的。也是最有创造性的学习方式。

比如达芬奇看到鸟在天上飞,他就琢磨鸟在天上飞和鱼在水里游到底有什么共同之处,为什么鱼在水里游那个敏捷的程度明明有水做阻力,但看起来比鸟还快。为什么?后来他就慢慢搞出了流体力学。

这三种方式似乎有些难以理解,可以类比在《架构与架构师》中提到的架构师技术能力包含的知识深度、宽度、广度。

再如最近我写的《大明湖畔的领域模型》,领域模型因OOA被提出,让我们寻找现实中的概念类,万物皆对象,映射到具体软件实现类。而同时期Martin Fowler也提出了Domain Model,却是对OO升华,要充血模型,不要贫血模型。再到现如今大家热议DDD中的领域模型,Eric推崇的分析模型与实现模型统一融合。

一个概念被各方位拓展后,原始的软件方法论被突破,各种全新软件方法论被创造。

高效学习

赚钱只能赚到认知范围内的钱,不然凭运气赚取的钱也会被实力亏掉;同样,学习也是如此。只是知道如何学习,无法内化这些认知,学习行为的效率依然低下。

如何能高效学习,我们需要再一次反思自己对学习的元认知。

错觉

我们的元认知非常容易出现偏差,常会产生两个误区:

1、不知道自己学习中的薄弱之处,不知道要在哪里花更多精力才能提高自己的知识水平

2、爱使用那些会让自己错误地认为掌握了知识的学习方法,也就是拼命记笔记、拼命画下划线、拼命地用荧光笔、拼命地反复阅读

怎么解决这两个误区?巩固与检索。

学习越轻松,效果越不好,看来起来非常勤奋,不停地背书,一遍一遍地背,拿笔一遍一遍地画,甚至是一遍一遍地抄,看起来很勤奋耗费大量的时间,但他的学习过程是很轻松的,没有做到有挑战的事情。

通过检索、考试,不断地挑战来巩固记忆。

比如要跳槽了,怎么去准备面试呢?拿出浩瀚如烟的资料去慢慢复习吗?或者找一堆面试题来刷吗?

这些都很低效,不如自己玩一把角色扮演,扮演下面试官,给自己出一份面试题,出题需要检索知识点,解题也需要挑战记忆。

再按知识模块与各种资料比对自己的知识掌握度,查漏补缺,事半功倍。

问题树

认知心理学认为,有三个前提要求时,学习效率最高:

1、有目标导向,俗称带着问题学习

2、有即时反馈,为什么游戏好玩?

3、最近发展区,当前水平与通过学习获得的潜力之间的差异叫最近发展区

那么怎么样的学习方式才会同时拥有这几个前提呢?使用问题树的思维方式替代知识树的思维方式。

知识树的思路,是典型的专业知识细分的学习路径。工业时代分工高度稳定,每个领域都相对独立、发展缓慢,一个人有机会学完一个细分领域的所有知识。沿着一棵长成的大树向上爬,这种学习路径效率最高。

但在一个高度变化、多领域跨界的时代,完成任何任务都需要调取多领域的知识,全部靠自己学习显然来不及。

当我们想学习某一知识时,常列个计划,搜集资料,罗列书单、阅读清单,可涉及面很广,这样难免有很大的随机时,书单虽全虽好,最后没有动力去读,读了也没有实践的动力。

而以问题为切入点,问题使目标更清晰,不会在知识树里迷路;动力也更强,一个问题解锁后,会带来更多、更大、更有趣的问题。

时代是水流,答案是河岸,而问题是船只。

在水流不快的时代,可以在河岸上慢慢走,也许跟得上水流;但在知识爆炸、洪流时代,只有登上船只,才能保持和时代同步。守在岸上,只能被远远抛下,望洋兴叹。

不要学习

相对现如今提出的终身学习者,是不是有些反人类。

不要学习,指的是在没有明确自己究竟想达到什么目的,就去不停地“学习”,实在是对宝贵时间资源的浪费。而且学习得多,未必收获得就好。

打个比方,学习就好比整个食物经过咀嚼、消化、吸收的过程,它不是表面看起来“吃”的动作。人们不可能永远吃个不停,所以学习行为不是越多越好。

学习需要挑选吃的食物(获取信息)、咀嚼(明白阶段)、消化(理解阶段)、吸收(应用阶段)。

犹如ThoughtWorks中国区CTO徐昊在他的专栏《业务建模》中所说,技术没有反哺过我的生活,反而我的人生给养了在技术上的洞见。吃喝玩乐并不耽误事,因为学习是一种状态(being),而不是一种行为(doing)。在学习的状态中,吃喝玩乐都可以让我成为更好的程序员。

找到自己的目标,不要人云亦云,刻意宣传终身学习更多时刻是商家制造的焦虑感收缴人们的智商税。

总结

世人都知道认知能力重要性时,我们更需要去认知一下如何更好地获得认知的能力。

“学习”本身不是一件容易的事,在我们长期实践“学习”的空隙,需要对“学习”保留一份反思,学习学习,加深对学习元认知的认识。

不需要刻意终身学习,更不必因终身学习而焦虑,它就是我们的血液。是这个时代赋予我们的第二呼吸。

学习行为像吃饭,学习状态像呼吸。不要为了学习而学习,贪图勤奋的假象带来的快感。

回到篇首,提到我的切身经历,问题的本质是认知效率:认知收益与时间精力之比,牛人真正的秘诀是在最精华的资源上,以高很多倍的认知资源来学习。要事第一。

当然在认知水平不够时,也不要吝啬自己的时间。毕竟我们已经与牛人有了差距,花功夫才能得到真功夫。

最后的最后,如果你对学习主题也感兴趣,可以延伸阅读《刻意练习》、《认知天性》、《如何高效学习》、《人是如何学习的》、《卡片笔记法》、《跃迁》。

《软件学报》在2021年第32卷第9期刊登了一篇论文:《领域驱动设计模式的收益与挑战:系统综述》。这篇论文是学术界在这一领域开山之作。

论文是对2003~2019年之间发表的相关文献进行识别、筛选、汇总和分析。

揭示DDDP的应用情况,即哪些DDDP被应用到了软件开发中,以及其所带来的收益、挑战及相应的缓解挑战方法。

对于长期在DDD中折腾的人,值得一读。

一是可以看看学术界的研究成果

二是对比自己实践,是否在康庄大道上

如果你对学术性论文不感兴趣,可以仔细阅读这篇文章,我对论文做些简单摘抄,对比一下自己实践感悟

基本介绍

论文先对DDD的一些概念、特性作了介绍

定义:

DDD是一种软件设计中应该遵循的思维方式,其目标在于加快具有复杂需求的软件项目的研发速度

理论:

通过构建领域模型来解决软件开发的复杂性问题。因为在大多数软件项目中,最困难的往往是解决业务领域复杂性,而非技术复杂性

特征:

设计与开发的绑定,DDD强调软件设计概念必须在开发过程中得以成功实现

DDDP:

DDD方法中,将一组设计实践、技术和原则合并为标准模式,即所谓的领域驱动设计模式(domain driven design pattern,简称DDDP)

基本原则

  • Evans强调设计概念必须在代码中成功实现,否则它们将会变成抽象的讨论。DDD通过引入模型驱动设计建模范式及其构造块,弥补模型与可运行软件之间的差距
  • DDD提倡迭代开发,领域驱动设计与具体实现过程间紧密关系,使得DDD比其他软件设计方法更具实用性
  • DDD追求领域模型需要依靠头脑风暴的创造性和对领域的深入了解,因此在实践过程中,开发人员与领域专家之间需要展开紧密协作
  • DDD是一种软件设计方法,而任何设计出来的领域模型都应该与架构无关。也就是除了领域模型,在软件开发过程中仍然需要架构设计,比如微服务架构或六边形架构

DDD设计模式概览

DDD由Evans作为一种大型模式语言引入,其由一组相互关联的模式组成。

模式语言提供了讨论问题的交流术语,它定义了特定场景、特定问题的解决方案,其主要目的是帮助开发者解决在设计和编程中遇到的通用问题。

模式语言在软件工程中被广泛地应用,比如设计模式、企业架构模式等。

DDD与DDDP的关系,正如同OOD与面向对象设计模式的关系。

对于DDD,最基本的模式是通用语言,它是一种供不同涉众(如开发人员和领域专家)共同使用的语言,主要用来辅助领域建模,,

一种通用语言只适用于单个限界上下文,后者作为一个电焊工的模型边界来维护模型的完整性。

根据Vernon的观点,除了通用语言之外的DDDP,要么属于战略设计模式,要么属于战术设计模式。

战略设计模式

旨在应对具有多个领域模型的大型软件开发项目的复杂性,其中,每个领域模型都属于单独的限界上下文。

限界上下文以外的其他战略设计模式关注如何管理不同限界上下文之间的关系

比如上下文映射负责描述不同领域模型间的通信,而职责分层则站在更高的抽象层级来组织不同领域模型间的概念依赖关系

战术设计模式

负责根据通用语言对单个限界上下文进行领域建模,并结合面向对象原则绑定领域建模和编码实现。包括实体、值对象、聚合、服务、资源库等。

实体和值对象用于对具有不同领域特征的领域对象进行建模;

聚合将一组领域对象绑定为一个整体,以控制事务;

服务则充当领域模型接口,具有无状态特点;

资源库用于封装领域对象的数据库访问操作


感悟

对于基础信息的介绍,可以说基本是符合当前DDD的理论知识,只是表述方式有所差异。但鉴于每个人认知差距,可能抓住的重点信息不同,从我个人认知角度,我来点评一下:

首先是定义,论文指出DDD是一种思维方式

这是种很新颖的定义。在DDD系列文章中,我们也去追寻过DDD定义。但这种定义是第一次听说。

软件设计中最困难的是什么?

论文指出是解决业务领域复杂性,而非技术复杂性。

这个应该会随着软件复杂性越来越高,人们的认知会越来越深。现在技术人员还是过于看重技术,甚至对技术驱动业务深信不疑。现实是怎么样?现在工具人的流行说法已经足以说明,驱动业务是少见的,而跟随业务是常态,成就业务已经了不得

为什么瀑布模型不适合软件行业,就因为隐藏的业务知识太多。不可能一次性就把所有业务知识提取出来,而且在软件开发进程中,业务可能还在变化中发展。

借助敏捷中迭代式开发,一次次与领域专家交流的深入,才能慢慢挖掘出隐藏在业务知识中的复杂性。

而DDD正是通过模型去捕获领域知识,对领域复杂性进行简化和提炼。

所以整合起来看,DDD是一种迭代改进试错法,还不知道什么时候该停止,甚至停止的时候你也不知道得到的到底是垃圾还是宝藏。

设计与开发绑定

这一点,是相当重要。是DDD与别的分析方法重要区别。

为什么DDD如此流行,可能跟很多技术大词一样,正如之前在中台文章所说,可能是应激者怕掉队,也可能是投机者想上位,很多人还是把它当成一种分析方法。

如果只是一种分析法,我们之前使用的ER分析,OOA,都已经够用了。

对于现流行说的DDD帮助微服务划分,那只是DDD中界限上下文这一个点,而且服务划分也不能只考虑领域界限,还有其它因素。

因此需要多思考,为什么需要DDD,DDD到底有什么独到之处?

我个人认为理由在Evans著作中提到的知识消化。学习者过多的精力放在了战术部分,什么是实体、领域服务、repository等等这些元素上。

在编程实践中,我们希望实实在在的用上DDD元素,才表示我们是在进行DDD,而其实没有DDD,对OO的深刻理解,充血模型是自然而然的。

然而,如果没有DDD战略部分,只有DDD战术元素,那只是DDD编码风格,而编码风格,没有高低之下,贫贱之分。

何为知识消化?我特此画张图:

业务方与技术方沟通业务需求,在交流中,对部分业务知识达到共识,这部分交流语言被标准化为统一语言;

在不断地交流中,对于双方达成共识部分,凝炼成到模型中,对模型review,以确定描述了业务实际情况,发现缺乏或不适当概念时,进行修正,指取新的统一语言;

当模型确认后,以模型为样本,使用代码实现它;

上面是正向流程,技术方在重构代码时,也会同步修改模型,进来提取出新的统一语言,反哺业务方。

通过正逆流程,达到修改统一语言就是修改模型,修改模型也就是修改代码;修改代码也是修改模型,修改模型也相继修改统一语言。

这样打破了知识壁垒,给予业务方与技术方一种更好的协同方式。这就是DDD的精髓。让分析模型与实现模型融合了,不再像以前的方法一样分裂。


研究结果

如上图所示,应用DDDP的相关活动主要分为4类:领域分析、领域设计、领域模型实现和普适性活动。

领域分析:与领域专家一起探索领域知识的过程。经过领域,将得到初始的领域模型;

领域设计:指将模型分成不同部分(每个部分对应着独立的限界上下文),然后扩展和细化每个限界上下文的过程,以此为开发实现做准备

领域模型实现:将模型转换为可运行代码,这一过程还通过检查模型为模型设计提供反馈

普适性活动:在应用DDDP时,可能在领域分析,领域设计,领域模型实现这3个阶段都会发生的模切活动,比如构建和更新通用语言

应用情况

在基础研究集合中,出现频次到达3次以上的DDDP,以及对应的描述和提及这些模式的研究文献

这些模式出现的频次并不平衡,战术设计模式被提及的频次明显高于战略设计,表明开发人员更容易注意到领域驱动设计的战术设计模式。

总体而言,目前只有31.1%的DDDP在基础研究中得到探讨,这也表明当前学术界对DDDP的研究存在不足。

应用DDDP的收益

下图展示了应用DDDP所带来的收益在领域分析、领域模型实现以及普适性活动中的体现情况

对每个阶段带来的收益细节详述一下

领域设计

应用DDDP收益在于使各个领域之间依赖关系更加明确,上下文映射和职责分层用于组织系统的不同部分:

前者表示不同限界上下文之间的关系,每个上下文表示一个特定的领域;

后者根据领域对象职责,将它们组织成具有清晰依赖关系的层次结构

帮助开发人员更加深入地了解系统,降低认知复杂性,有助于分析系统架构

领域模型实现

应用DDDP有助于领域模型的落地实现。

普适性活动

可以提升软件架构的质量属性,如可维护性、可扩展性、可重用性和可测试性等

应用DDDP的挑战

下图列出了在领域分析、领域设计、领域模型实现和普适性活动中,应用DDDP可能面临的挑战以及相应的缓解方法


感悟

可以看出论文对DDDP应用的收益和挑战阐述得很详细。在现实实践中,挑战远比论文中提到的还要多。我觉得主要是两方面:

一是对理论研究的不够深入,没有系统性学习

二是过于关注战术问题,不深入战略部分而不知所以然,又对OO的理解不深入使其难以落地,从而抱怨DDD

对于第一条

1、从个人学习者角度,只能自己多看看市面上已经出版的书籍,碎片化时间系统性学习,而不是随便看看几篇文章,就以为理解DDD了,现在DDD就像个筐,什么东西都在往里装,就算是Evans开山之作,也有很多时代局限性;

2、从团队角度讲,不要奢求团队每个人都去理解DDD,毕竟它的本身门槛就高,作为TL,需要自身内化DDD,制定规范,团队执行就可以,向有兴趣的同学传输背后理论。

对于第二条

3、除了需要系统性学习外,要明白DDD精髓与DDD编码风格是两码事,在实践DDD过程中,也带有个人经验特征,不仅要有自我理论作为背后支撑,也要认识到不是使用了战术元素,我们就是DDD了,而是要有DDD的知识消化过程。

总结

软件工程中是“没有银弹”的,任何理论方法都会存在一定的局限性和缺点,DDDP也是如此。因此应用各种DDDP带来的局限或者挑战,仍需要未来进一步探索和反思。

也希望我的感悟能帮助到你,如果有兴趣可以再看看论文原稿。对于高阶程序员来讲,困难的不是能不能干出来,而是怎么干才舒坦。

最近又看了几本关于架构的书籍,不禁回到原点:架构是什么?架构师职责是什么?

架构

《架构与架构师2》中引用了1995年David Garlan和Dewayne Perry给出的定义:

系统的组件结构,组件的相互关系,以及管控组件设计和长期演进的原则和指导方针

十几年前,软件架构师只处理架构中的纯技术问题,像上面定义中的组件,可能是类,是包。而现在架构师承担着大量、宽泛的责任,并且范围还在不断扩大。尤其云时代,IT基础设施包括网络、数据中心、计算基础设施、存储,以及其他子系统都得考虑

贴一张思维导图来说明软件架构涵盖的范围

从图中可以看出,架构师的职责包含技术能力、软技能、运营意识及其他很多方面

所以定义架构不是件轻松的事,Martin Fowler也都拒绝尝试对架构做出定义,退而引用名言:

“架构是那些重要的东西…………无论它具体是什么”
—- Ralph Johnson

所以在行业内共识:对软件架构本身并没有一个好的定义。

虽然架构很难定义,但我们总得尝试着描述它,分解它,进而更好地运用它指导软件开发。如果说软件世界更迭速度过快,组件化定义显得太陈旧,那我们需要一种与时俱进的思考软件架构的方式

Mark Richards与Neal Ford展示了这样的思考方式

如图中所示:软件架构包含系统的结构、系统必须支持的架构特征、架构决策以及设计原则

系统结构

实现该系统的一种或多种架构风格(比如微服务、分层和微内核)

仅仅描述结构并不能完整地诠释架构,还需要了解架构特征、架构决策和设计原则

架构特征

架构特征定义了系统的成功标准,这些标准往往与系统的功能正交

《架构与架构师》中,指出应用系统需要考虑两方面内容:一是功能性需求,二是非功能性需求。

但从语言角度来看,将一个东西命名为非功能会带来负面影响:如何说服团队充分注意“非功能性”的东西

另一个流行术语是质量属性,但它暗示的是事后质量评估而不是设计。

架构特征描述了对架构以及整个系统的成功至关重要的关注点,同时又不影响其重要性。

架构特征满足三个标准:

  1. 明确非领域设计的某个注意事项
  2. 影响设计的某些结构项
  3. 是否对应用的成功至关重要

构架决策

架构决策定义了一组关于如何构建系统的规则,构成了系统约束,并指导团队哪些可以做,哪些不可以做

比如在一个分层架构中,架构师可能会规定只有业务层和服务层可以访问数据库,限制表现层直接调用数据库。

设计原则

设计原则与架构决策的不同之处在于,设计原则是指导原则,而不是必须遵守的规则。

在微服务架构中,开发团队应该使用服务间的异步消息传递来提升性能。

架构定律

虽然架构范围已经大到难以置信,但统一元素仍然存在。

架构第一定律:

软件架构中的一切都是在做权衡

当架构师若认为自己发现了不需要做权衡的东西,很有可能他们只是还没有发现需要舍弃的东西而已

通过结合架构的原则、特征等,我们对软件架构的定义超越了软件结构脚手架。架构不仅仅是各种要素的组合,还体现了架构第二定律:

原因比方法更重要

架构师面对不了解的系统时,可以探明这个架构是如何工作的,但会很难解释某个选择背后的原因。

因此架构师需要去详细记录架构决策以及背后权衡的逻辑。

架构师

在之前的两篇文章中指出架构师必须要有屠龙刀还得有绣花针,需要技术+业务+管理三条腿。

总之一句话,架构师是最牛的人。可一个团队不能人人都是架构师,况且还有资深工程师,技术专家。作为架构师与他们的区别是什么呢?能力模型有什么不同呢?

决定一个人的强弱,是他的认知水平。对应技术人员就是对技术的认知。架构师的认知有四个阶段:

愚昧之巅(不知道自己不知道)、绝望之谷(知道自己不知道)、开悟之坡(知道自己知道)和持续平衡的高原(不知道自己知道),这也是架构师的认知逐步提升的过程。

如果人们对开发工程师的期望是把功能需求开发完成,那对架构师的期望就多样了

一、制定架构决策

架构师需要制定架构决策和设计原则,以指导团队、部门或者整个企业进行技术决策

二、持续分析架构

架构师需要持续分析架构和当前技术环境,然后给出改进建议。从整体上分析技术和问题域的变化,以确定架构的稳健性

三、掌握最新趋势

开发人员必须时刻关注技术更新,从而保证与这些技术与时俱进。对架构师来说,掌握最新的技术和行业趋势更为关键

四、确保决策被遵守

架构师需要确保架构决策和设计原则被遵守

五、丰富的经历和经验

架构师需要涉猎各种各样的技术、框架、平台和环境

六、具备业务领域知识

一个称职的软件架构师不仅要了解技术,还要了解问题背后的业务领域。没有业务领域知识,就无法理解业务的问题、目标和需求,也就不可能设计出有效的架构

七、具备人际交往能力

架构师需要具备出色的人际交往能力,其中包括团队合作、引导和领导力

八、了解并驾驭政治

架构师所做的几乎每个决策都会受到挑战。由于成本或工作量(时间)的增加,架构性决策将受到产品负责人、项目经理和业务利益相关者的挑战


针对以上八点,以及技术+业务+管理三项技术人普实能力,可以更简洁地概述架构师自身定制的三条腿:技能+影响力+领导力

1.技能是实践架构的基础。它需要知识以及应用知识的能力

2.影响力用来衡量架构师在项目中应用技能后给项目或公司带来多大的效益

3.领导力确保了架构实践的状态能稳步向前推进,同时培养更多的架构师

能力模型

论能力模型,与开发人员之间对技术方向的侧重有所不同。开发人员必须拥有很深的技术深度,但软件架构师必须具有非常广的技术广度才能像架构师般思考,并以架构的角度看待事物。

以知识金字塔展示世界上所有技术知识的类别,事实证明,技术人员应重视的信息类型随职业阶段的不同而不同

“已知”指代技术人员日常工作用到的技术、框架、编程语言和工具

“已知的未知”指代技术人员稍微了解或听说过,但没有掌握的技术

“未知的未知”是金字塔中面积最大的部分,指代能够完美解决技术人员面临的问题的技术、工具、框架和编程语言,但是技术人员甚至都不知道它们的存在

开发人员的早期职业生涯专注于扩展金字塔的顶端,积累经验和专业知识。金字塔顶端的知识需要投入时间才能保持专业。最终,金字塔顶的大小就是个人的技术深度。

而随着开发人员过渡到架构师角色,知识的性质也发生变化。架构师的价值很大一部分是广泛地理解技术,并且知道如何利用技术解决特定的问题。对架构师来说,最重要的部分是金字塔的顶部和中间部分

中间部分与底部交汇处的长度代表了架构师的技术广度

作为架构师,技术广度比技术深度更重要。因为架构师的职责就是根据功能做出与技术限制相匹配的决策,所以广泛了解了解各种解决方案是非常有价值的

架构师需要“博而不专”,牺牲技术深度来提高技术广度,虽然技术人都想在多个领域保持专业深度,但结果往往事与愿违,甚至无一成功。

对于能力模型为啥有区别,简单总结一下:

广度决定能找到的方法+深度决定选择方法的正确性+经验决定找到正确方法的速度

平衡编码

虽然架构师的能力模型与开发工程师有所不同,但还是需要保持动手编写代码,并保持一定水平的技术深度。

正好之前所说,不仅要有屠龙术,还要会瓷器活。

因此架构师面临的困难任务之一是如何在动手编码和软件架构之间取得平衡。

如果参与过多的编码工作,可能会陷入瓶颈陷阱。也就是当架构师在项目的关键部分(通常是基础框架代码)中拥有代码所有权并成为团队的瓶颈时,就会发生瓶颈陷阱。

避免瓶颈陷阱方法之一是将关键路径和框架代码委托给开发团队其他人员,然后着重于实现业务功能(一个服务),并且在1~3个迭代中完成。

如何保持编码能力和一定水平的技术深度呢?

一、频繁进行概念验证(POC),这种做法不仅要求架构师编写源代码,还要求架构师能够通过考虑细节来帮助验证架构决策

二、处理一些技术债或架构相关的故事问题,使开发团队腾出精力来处理关键的功能性的用户故事;或者在迭代中修复bug,能使架构师识别出存在于代码甚至架构中的问题和弱点

三、创建简单的命令行工具和分析器进行自动化来帮助开发团队完成日常任务

四、进行频繁的代码审查,通过代码审查能确保代码符合架构规则,并在团队中进行指导和辅导

总结

架构定义是件不容易的事,软件发展日异月新,架构的范围也日益扩大,进一步加据了定义架构的难度。但无论如何,我们还是有必要通过结构化思维去分析架构,进化古老的组件化定义,从架构结构、架构决策、架构特征以及设计原则四方面描述架构,继而明确架构师的职责,区别与开发工工程师的能力模型,加强“技能+影响力+领导力”三条腿能力成长,更好地服务架构。

康威定律随着微服务架构兴起的步伐慢慢复苏,重新进入人们的视线,但他的威力远远不仅限于简单的指导如何拆分微服务,不管是整个团队的战力,还是架构方案能否顺利落地都起着重要的作用。

康威定律

先回顾一下什么是康威定律:1968年,计算机系统研究院的梅尔康威在Datamation杂志上发表了一篇论文How Do Committees Invent?

这篇论文中有一句话被总结为康威定律:“设计系统的组织由于受到约束,这些设计往往是组织内部沟通结构的副本。”

下面先通过一次切身经历来阐述定律如何发挥威力,以及如何通过逆康威定律得到我们想要的架构方案

起初我带领一支团队负责一个业务,先称它为APP1,经过一段时间,老板找我谈话,说:“APP1在你的带领下,运行得不错,应该承担更大的责任,后面APP2团队也由你负责”。

经过一段时间的迭代,APP2需要一个配置服务,支撑差异化运营

APP2架构师根据最新业务需求,提出了给APP2增加一个配置服务,对于APP2来讲,架构师都无需赘述,此架构方案无疑是合理的

但从整体看APP1已经有配置服务

此时就有了两个方案:

  1. 按架构师规划,APP2构建新的配置服务
  2. 增强APP1的配置服务,让它同时支撑APP1和APP2

怎么决择呢?

从架构角度,方案一似乎更有优势,独立,两个APP之间也不会相互干扰,原先的配置服务也无需改动,相对去改造一个旧系统,构建新系统负担还小一些

但从组织结构讲,组织效能角度也更高效,大局出发,也不需要两个相似的配置服务,组织结构与架构结构也更有同态性

此时,康威定律就发挥了至关重要的作用:“如果系统的架构和组织结构不一致,那么组织结构将成为赢家”


当我在计划着进一步整合两个团队时,事情发生了变化,老板又找我谈话了,“经过这段时间,发现你带两个团队有些吃力,这样吧,以后你就只负责APP2团队”

随着业务的继续开展,发现了个问题,当APP2团队需求需要变更配置服务时,为难了

APP1使用配置服务深度和广度都高于APP2,所以在划分时,配置服务归于APP1了,之前都是同一个大团队,资源协调很简单,内部沟通很容易

此时怎么办?

原先团队内部的沟通,需要跨团队沟通了,再简单的一次变更,都需要提前沟通,协调排期,制约了高效迭代交付能力

所以APP2团队不得不剥离APP1的配置服务,另起炉灶,回到当初架构师的方案一

这其实还是康威定律发挥着威力:组织内部的沟通路径(无论是否和正式汇报线一致)严重制约了组织所能设计的解决方案的类型

逆康威定律

这是ThoughtWorks技术总监James Lewis突发奇想地提出的,组织要根据他们想得到的软件架构来设计团队结构,而不是期望团队盲从一个被设计好的团队结构

其核心观点在于,将软件架构看作一个独立的概念,认为它可以被孤立设计,然后交由任何团队来实现,这从根本上就是错误的

我们把上面的示例,顺序倒置过来,就是逆康威定律。

我详细阐述下:

刚开始,APP1和APP2是两个独立完整的团队,都有各自的配置服务,也就是

虽然他们功能相似,但由于在两个团队里面,与组织结构和沟通路径都是匹配的

从公司全局架构看,发现配置服务只需要一个就够了,推倒烟囱式架构,整合资源,配置服务中台化,这也是近些年各公司崇拜的中台文化

怎么办呢?简单啊,提取共性,抽象整合呗。

现实没那么轻松,如果两大APP团队,是支撑公司盈利的两大业务,营收压力也很大,基本上整合是句空话,看看有多少公司的BU都是各自为战,烟囱式系统一个比一个强悍,谁能动得了?

此时怎么办?整合组织结构,让两个团队组合成更大的团队,拥有独立的架构团队,团队内部自己就会催生出整合的力量

再看一个示例,假设有四个独立团队,每个都由前后端开发工程师组成,他们分别负责系统的不同部分,然后对DBA提出数据库变更请求。

如果这四支团队独立的前端和后端开工程师推动了UI和应用层的前后端分离,而有一个共享的DBA团队,那么很可能会带来这样一个单一的共享数据库。

因为康威定律的同态力会强烈地牵引软件架构“自然而然”地体现出当前的组织设计和沟通路径。

当我们在使用微服务架构时,每个独立服务都需要有属于自己的数据存储。

通过应用逆康威定律,可以在各个独立的客户端应用和API开发团队里面增加一名数据库开发人员,那架构结构自然就体现出来了。

总结

想想架构风格千千万万,为什么分层架构却是最流行的,也是最容易实践成功的,因为有独立的前端团队,后端团队,基础服务团队,对于业务数据流向,正好也是从UI发起,逻辑层处理,数据库存储,组织结构与架构结构是匹配的。这就是康威定律的威力。

组织结构和团队间真实的沟通路径会直接影响所构建系统的架构。如果有四个小组合作开发一个编译器,那么你将得到一款具有四个步骤的编辑器。对于一家软件产品公司来说,组织结构预示着产品架构。

过去很多组织结构调整的潜在目标都是为了减少员工或者围绕管理者和领导者的权势建立山头。可对于一家软件公司,势必慎重,必须要考虑架构,更可以应用逆康威定律:设计团队满足理想的软件架构

简而言之,在设计软件架构或进行组织结构调整时,将康威定律纳入考虑因素之中,就能够受益于兼顾软件架构和团队设计的同态力。

Eric在DDD第一章节就介绍了模型,可见模型的作用不言而喻,说DDD是一种模型驱动设计方法,绝对没有问题

那是不是我们在拿到业务需求时,就急呼呼的跟业务方来一起构造模型呢?毕竟模型是万事之首嘛

《DDD开篇》提过DDD是一种基于面向对象的设计方法,我们既然已经有了面向对象,而且OOAD也很强大,为什么还需要DDD呢?

要想弄清楚这两个问题,首先我们需要拿个示例来仔细比对一下

OOP小示例

《面向对象是什么》一文中提到的游戏小示例

有个游戏,基本规则就是玩家装备武器去攻击怪物

  • 玩家(Player)可以是战士(Fighter)、法师(Mage)、龙骑(Dragoon)
  • 怪物(Monster)可以是兽人(Orc)、精灵(Elf)、龙(Dragon),怪物有血量
  • 武器(Weapon)可以是剑(Sword)、法杖(Staff),武器有攻击力
  • 玩家可以装备一个武器,武器攻击可以是物理类型(0),火(1),冰(2)等,武器类型决定伤害类型

作为一名受过OO熏陶的程序员,借助OO的继承特性把类结构设计成:

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class Player {
Weapon weapon
}
public class Fighter extends Player {}
public class Mage extends Player {}
public class Dragoon extends Player {}

public abstract class Weapon {
int damage;
int damageType; // 0 - physical, 1 - fire, 2 - ice etc.
}
public Sword extends Weapon {}
public Staff extends Weapon {}

攻击规则如下:

  • 兽人对物理攻击伤害减半
  • 精灵对魔法攻击伤害减半
  • 龙对物理和魔法攻击免疫,除非玩家是龙骑,则伤害加倍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Player {
public void attack(Monster monster) {
monster.receiveDamageBy(weapon, this);
}
}

public class Monster {
public void receiveDamageBy(Weapon weapon, Player player) {
this.health -= weapon.getDamage(); // 基础规则
}
}

public class Orc extends Monster {
@Override
public void receiveDamageBy(Weapon weapon, Player player) {
if (weapon.getDamageType() == 0) {
this.setHealth(this.getHealth() - weapon.getDamage() / 2); // Orc的物理防御规则
} else {
super.receiveDamageBy(weapon, player);
}
}
}

public class Dragon extends Monster {
@Override
public void receiveDamageBy(Weapon weapon, Player player) {
if (player instanceof Dragoon) {
this.setHealth(this.getHealth() - weapon.getDamage() * 2); // 龙骑伤害规则
}
// else no damage, 龙免疫力规则
}
}

如果此时,要增加一个武器类型:狙击枪,能够无视一切防御,此时需要修改

  1. Weapon,扩展狙击枪Gun
  2. Player和所有子类(是否能装备某个武器)
  3. Monster和所有子类(伤害计算逻辑)

除了伤害逻辑有各种规则,还有装备武器也会有各种规则

比如,战士只能装备剑,法师只能装备法杖,但他们都可以装备匕首

再比如,当我们有不同的对象,但又有相同或类似的行为时,OOP会不可避免的导致代码的重复

在这个例子里,如果我们去增加一个“可移动”的行为,需要在Player和Monster类中都增加类似的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Player {
int x;
int y;
void move(int targetX, int targetY) {
// logic
}
}

public abstract class Monster {
int x;
int y;
void move(int targetX, int targetY) {
// logic
}
}

一个可能的解法是有个通用的父类:

1
2
3
4
5
6
7
8
9
10
public abstract class Movable {
int x;
int y;
void move(int targetX, int targetY) {
// logic
}
}

public abstract class Player extends Movable;
public abstract class Monster extends Movable;

但如果再增加一个跳跃能力Jumpable呢?一个跑步能力Runnable呢?如果Player可以Move和Jump,Monster可以Move和Run,怎么处理继承关系?要知道Java(以及绝大部分语言)是不支持多父类继承的,所以只能通过重复代码来实现


原生OOP力不从心

从OO角度看待,逻辑简单,代码也算过得去,也基本符合充血模型需要的数据与行为结合性要求

但如果业务比较复杂,未来会有大量的业务规则变更时,简单的OOP代码会在后期变成复杂的一团浆糊,逻辑分散在各地,缺少全局视角,各种规则的叠加会触发bug。

在这个小示例中,可以看到新增加一次规则几乎重写很多类,改造成本相当高,这还写得不够OO吗?

总体而言,上面的代码没有处理好这三个问题:

  • 业务规则的归属到底是对象的“行为”还是独立的”规则对象“?
  • 业务规则之间的关系如何处理?
  • 通用“行为”应该如何复用和维护?

DDD应对

示例和单纯使用面向对象的问题已经很明晰了,DDD如何应对呢?

当然,可以申辩

虽然示例代码已经很OO,但却没有遵守OO原则SOLID,至少没有达到OCP目标

尤其开始就掉进OOP的陷阱,使用继承来实现看似是继承关系的逻辑,没有遵循组合优先于继承的原则

尤其没有提取出业务规则,并理清业务规则的归属,不应该与实体对象混合

建模

示例本身很简单,如果我们建模,大概是这样:

但很怪,模型则偏重于数据角度,描述了在不同业务维度下,数据将会如何改变,以及如何支撑对应的计算与统计,也就是说模型上看,会有实体以及实体间的关系,隐藏了业务维度,可以我们这个模型上却包含了动词,来描述业务行为

当然这个模型可以再充实一下,比如把业务规则标识上去,这也说明了传统模型的缺点,如果你对其他模型感兴趣,请关注我,后期会详情介绍模型系列文章

我们回到有问题的本质原点,为什么要建模呢,为了抽象复杂业务逻辑,降低理解业务的成本,挖掘更多的业务隐藏知识

可上面的示例太清楚了,一览无余。一句话可以概述出整个业务需求:

玩家使用武器攻击怪物,对怪物造成伤害,直至怪物死亡

把规则加进去:

玩家按规则使用武器按规则攻击怪物,对怪物、玩家、武器造成一定规则的影响(怪物受到伤害,玩家可能会有反弹伤害,武器持久属性会下降直到武器消失),直至怪物死亡

这其实是任何一款ARGP游戏的核心业务

软件开发的核心难度在于处理隐藏在业务知识中的复杂度,模型就是对这种复杂度的简化与精练,DDD改进版还使用事件风暴方式挖掘业务知识,而像这种业务知识没有隐藏的简明型业务系统,我们已经把核心问题描述得很清楚,无需再去知识消化,事件风暴,为了DDD而DDD,所以建模价值不高,甚至毫无必要

DDD应对

在上面的申辩中,我们已经发现了并不是OO不行,而是使用OO方式不对,虽说要把OO原则深入骨髓,可有没有一种方法能直接上升一层次,就像我们在使用面向过程语言时,也要有面向对象思维,实践没那么容易,直接使用面向对象语言,会让我们更容易使用面向对象思维,领略OO精髓

DDD正好就是这样一种方法,基于OO的升华,主要看看领域层的规范

实体,充血的实体

这一点与原生OO一样,数据与行为相结合

1
2
3
4
5
6
7
8
9
public class Player {
private String name;
private long health;
private WeaponId weaponId;

public void equip(Weapon weapon) {
// ...
}
}
  • 任何实体的行为只能直接影响到本实体(和其子实体)
  • 因为 Weapon 是实体类,但是Weapon能独立存在,Player不是聚合根,所以Player只能保存WeaponId,而不能直接指向Weapon
  • 实体需要依赖其他服务时,也不能直接依赖,使用Double Dispatch
1
2
3
4
5
6
7
8
9
10
public class Player {

public void equip(Weapon weapon, EquipmentService equipmentService) {
if (equipmentService.canEquip(this, weapon)) {
this.weaponId = weapon.getId();
} else {
throw new IllegalArgumentException("Cannot Equip: " + weapon);
}
}
}

领域服务(Domain Service)

单对象

这种领域对象主要面向的是单个实体对象的变更,但涉及到多个领域对象或外部依赖的一些规则

跨对象领域服务

当一个行为会直接修改多个实体时,不能再通过单一实体的方法作处理,而必须直接使用领域服务的方法来做操作。

在这里,领域服务更多的起到了跨对象事务的作用,确保多个实体的变更之间是有一致性的

不能学习实体的Double Dispatch

1
2
3
4
5
public class Player {
void attack(Monster, CombatService) {
CombatService.performAttack(this, Monster); // ❌,不要这么写,会导致副作用
}
}

这个原则也映射了“任何实体的行为只能直接影响到本实体(和其子实体)”的原则,即Player.attack会直接影响到Monster,但这个调用Monster又没有感知

通用组件型

像Movalbe、Runnable通用能力,提供组件化的行为,但本身又不直接绑死在一种实体类上

策略对象(Domain Policy)

Policy或者Strategy设计模式是一个通用的设计模式,但是在DDD架构中会经常出现,其核心就是封装领域规则。

一个Policy是一个无状态的单例对象,通常需要至少2个方法:canApply 和 一个业务方法。其中,canApply方法用来判断一个Policy是否适用于当前的上下文,如果适用则调用方会去触发业务方法。通常,为了降低一个Policy的可测试性和复杂度,Policy不应该直接操作对象,而是通过返回计算后的值,在Domain Service里对对象进行操作。

总结

DDD是一种模型驱动设计方法,但使用DDD也并不是一定要按固定方式方法一步步执行,建模是为了对复杂问题的简化和精炼,挖掘隐藏的业务知识。

如果能通过简明方式就能把业务核心问题描述清楚,比其他一切手段都有用,也都重要。那我们就没必要再去为了DDD而DDD,去进行事件风暴,知识消化慢迭代方式

本文中虽然提取了一些DDD领域层规范直接升华OO,但你有没有注意到一个问题,Player如果拥有很多能力,比如Moveable,Runnable,Jumpable,Fireable,那这个实体如何实现?

首先我们肯定会面向接口编程,提取出interface Moveable,interface Runnable,interface Jumpable,interface Fireable,可Player呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Player implements Moveable,Jumpable,Fireable {

void move(int targetX, int targetY) {
// logic
}

void jump() {
// logic
}

void fire() {
// logic
}

}

可以想象,随着能力越来越强大,player类会越来越臃肿,发展成超大类,充满坏味道,可我们这次也没有违反什么原则?难道达到了原生面向对象的能力极限?

如果你有好的想法,欢迎留言交流。如果你觉得文章有帮助,多转发,点击右下角『看一看』

前一段时间,脑子短路了,出现点思维障碍,突然特别想推广一下公众号,增长一下粉丝数量。

所以呢,就去各大技术网站,同步一些文章,想引流到公众号,现在写作技术网站真不少,也可能因为各个能写的技术人都要打造自己的个人品牌,从这些大型平台上搬迁到个人网站了,造成了一些流量流失,所以现在这些平台也在大力推广,吸引作者回归平台,比如搞更文活动,有赏写文,用户体检也上去了,可以很方便地从别的平台把文章搬迁过来

时间巧得很,7月底去看了各大平台,发现掘金在8月搞更文活动,连续更文多少天,就有相应的奖品。虽然奖品不是很值钱,但我借此机会,把文章搬过去,即能引些流量,又能得到奖品,两全其美。要得到最大奖项的要求是连续31天,心里想,这应该很难有多少人做到,每天写一篇,真是神人,除非像我这种人,已经有一大堆文章,只是手动搬迁一下

虽然就每天机械地选篇文章复制过去,但坚持了一周,还是有些烦,一是每天都有这么一件事在心里挂着,二是越来越发现意义不大,不应该把时间浪费在此,哪怕只有2分钟。 同时也发现能挑选的系列文章不多,好文章也不多,几年的积累,战不过一个月的考验,悲哉

一整月,终于过去了,平台发布了中奖名单,我根据人数,做了个统计

横轴是更文天数,纵轴是人数

个人感觉应该是金字塔结构,结果是头尾两波人数量差不多,中间人数反而比较少,这有些违背常识,难道跟我类似的人很多?

连续更文31天的人,如此之多,单单是为了这些奖品,我是不太相信,更多的应该是内驱力的驱动。莫道君行早,更有早行人。这个世界勤奋的人远超出想象,这估计也是为什么各行各业都充斥着内卷的气息

这也说明在当下时代,相对过去想要成功,不仅需要付出更多的勤奋,还需要有智慧的勤奋,也就是战术勤奋,战略更不能不努力。需要更加剖析自我,找出个人特色,有的放矢。

努力决定下限,运气决定上限;除了单单个人努力,在这样一个时代,还需要其他因素,而且因他因素的占比会越来越重,也越来越多元。

在此环境下,我们首先需要提高自己的底层认知水平

最近听好几位大佬提到一本书《跃迁》,摘抄几句:

获得百倍收益的关键,并不是百倍努力。每个时代的高手都在利用社会和科技的底层逻辑撬动自己,实现跨越式的成长。

长江商学院的校训是“取势、明道、优术”,个人方法论被放到了第三位,更重要的是把握趋势(取势)、理解系统运行之道(明道)

没有一个人是仅凭努力、天赋、机遇而获得巨大成功的,跃迁式成功都是利用了更底层规律,激发了个体的跨越式成长。

暂时还在消化中,有些认识值得多看几遍,希望你也有所收获