【翻译】单体优先的架构策略

本来打算写一篇类似的文章,不过看到有同事有转发马丁·福勒的一篇博客,结合自己的项目经历,感同身受,就直接把这篇文章翻译过来了。

当我听说有团队在使用微服务架构时候,我注意到了一些规律:

  1. 几乎所有成功应用微服务的系统都来自于一个过大单体项目拆分而来。
  2. 几乎所有我听到过一开始就选择使用微服务架构的系统,并从 0 构建,最终的结果都有一系列严重的麻烦。

这些规律在我同事中产生了长期的讨论:你不应该你在一个新的项目一开始就采用微服务架构,即使你确认你的应用在未来因为业务演进变得无比巨大。

img

微服务是一种有用的架构,但是即使这种架构的拥趸也不得不承认,使用这种架构需要付出额外的成本,这意味者只有更复杂的系统值得使用这种架构。这种代价本质是管理这些服务所带来的基本开销。所以这就是单体优先架构策略的依据,架构师应该在构建新的系统时候先使用单体做整体设计,即使你认为后期可能使用微服务更有价值。

第一个原因是经典的 Yagni 原则 (译者注:"You Aren't Gonna Need It" 的缩写,意在避免过度设计)。当你开始一个新的应用程序时,您如何确定这个应用的特性是用户有价值的?一个成功的软件可能背后的设计很糟糕,很难拓展,但是总比一个设计卓越但是没有价值的系统好。正如我们现在所认识到的,通常判断一个软件和想法是否有用的最好方法是构建一个简单的版本,看看它的跑起来效果如何。在第一个阶段,您需要优先考虑速度(以及反馈的周期时间),因此微服务的成本是一个累赘,应该被避免。

从微服务开始的第二个问题是,它们只有在服务之间建立良好、稳定的边界时才能正常工作——这本质上是厘清正确的边界上下文的任务。因为任何服务之间的功能重构都比在一个庞然大物的单体中要困难得多。不幸的是,即使是在熟悉的领域工作的经验丰富的架构师也很难在一开始就确定边界。通过先构建一个单体应用,你可以在微服务设计刷上一层糖浆之前(译者注:比喻微服务的一些额外设施),找出正确的边界是什么。它还为你提供了缓冲时间来开发细粒度服务所需的基础设施。

据我所知,践行单一优先策略有不同的方式。比如,仔细的从逻辑上设计为一个类似单体系统的整体,注意内部的模块化设计,包括 API 边界和数据的存储方式。做好这一点,再来开发微服务的系统。然而,如果我听过很多这样的成功案例,我会觉得这种方法更合适,实际很难做到。

一种更常见的方法是从一个庞然大物开始,然后逐渐从边缘剥离微服务。这种方法可能会在微服务体系结构的核心留下一块巨大的庞然大物,但是大多数新开发都是在微服务中进行的,只是这一块庞然大物是相对不变的。

另一种常见的方法是先开发一个单体系统,再用一套新的微服务系统完全替换单体结构。很少会有人认同这种办法,然而建造作为献祭架构的单体系统是有好处的(译者注:一种设计方法,先做简单的原型系统,再用新的系统代替,其实就是重写)。不要害怕建造一个你会丢弃的庞然大物,特别是如果一个庞然大物可以让你很快地进入市场。

还有,我遇到的其他方法是从几个粗粒度的服务开始,这些服务要比你预期的要大。使用这些粗粒度的服务来习惯处理多个服务,同时享受这样一个事实:粗粒度减少了你必须不得已进行服务间重构的数量。然后,随着边界稳定下来,就会分解为更细粒度的服务。

虽然我接触的大多数人倾向于“单体优先”的策略,但这不是绝对的。相反的观点认为,从微服务开始构建新的系统可以让你适应在微服务环境中开发的节奏。要以足够模块化的方式构建单体系统,以便将其轻松分解为微服务,需要花费很多甚至太多的编程规则。从微服务开始,你可以让每个人从一开始就习惯在独立的小团队中进行开发,并且在需要的时候,通过服务边界将团队分开可以更容易地扩大开发工作。对于那些更有可能尽早获得足够稳定边界的系统来说,这尤其可行。尽管证据不多,但我觉得你不应该从微服务开始,除非你有在团队中构建微服务系统的大量经验。

我觉得我还没有足够的例子来明确如何决定是否使用“单体优先”策略。毕竟,这是微服务的早期阶段(译者注:这篇文章发布在2015),可供学习的业界案例相对较少。因此,任何人在这些问题上的建议都需要保持开放性,无论观点的提出者如何信誓旦旦。

文章来源

more >>

高效工作的策略 2.0

现代人对自己的压榨达到了极致,对工作效率的诉求越来越高。

不同的理念、方法、概念层出不穷,番茄钟、清单、早起、费曼学习法、心流等等。我读过各种时间管理的书,这些方法都试过。

效果是不错,但是太累,精力消耗严重,难以持续发展。

慢慢我意识到,人一天能做的事情极其有限,但是却极其贪婪用各个方法把自己的精力快速耗尽。其实真正能提高效率的方法是管理好自己的精力,让有限的精力不浪费就很不错了。

一个明显的感觉就是,当一个人精力不足时候,无论用啥方法,都很难集中注意力。

慢慢的我的关注点变为如何休息、注意力管理和精力管理。

1. 如何休息

休息是提高效率的基础,就像存储子弹,如果不好好休息,就无法好好工作。

睡觉。这个是休息中最重要的一件事,我一直睡眠不好,于是寻找了一些关于改善睡眠的方法。

总结下来的经验是,睡眠方法需要学习,且因人而异。一定不能相信所谓成功人士每天 4 小时的睡眠,大部分人一般需要 7 个小时左右,但具体时间和个人的状态有关。早睡和健康状态好的时候,我只需要 6 个小时睡眠时间加一点午睡即可,但是一旦生病或者工作压力大就需要 8 个小时左右的睡眠时间。

另外 R90 睡眠法经过实践检验,通过 90 分钟的睡眠周期,确实有用,状态好的时候可以不用闹钟自然醒来。

《睡眠革命》这本书介绍了一种新的睡眠方法。每个人的睡眠呈周期性的规律,一个周期内分为浅睡、深睡、眼动睡等阶段,一个完整的周期在 90 分钟左右,完成一个周期后趋向于相对清醒(这也是做梦的原理)。如果你起床时并不在周期结束之时,早晨起床时会感觉整个人都是蒙的,因为你的睡眠周期被打断。

人一般需要 4 - 5 个周期。如果你晚上 11:30 睡觉,4 个周期就是第二天 5:30 需要醒来,5 个周期就是 7:00,几天时间适应后可以赶在闹钟前自动醒来。一般工作日需要多一点睡眠,使用 5 个周期,周末用 4 个周期即可。

放松。 另外一种休息的方式就是坐在沙发上啥也不做,啥也不想。有人会说冥想是一种恢复精力的方法,其实有点不对,冥想的注意点很多,不得要领的人搞一套下来反而更累。

简单的方式其实就是没有方法的放松即可。找一个舒服的姿势坐在沙发上,盘腿或者怎么样都行。除了工作的事情,可以任其心猿意马。杂念宜疏不宜堵,一些冥想和灵修的书会用各种方式控制杂念,其实有点走偏。

唯一需要注意的就是呼吸,细长、稳定的呼吸方式确实可以提高休息效率,《庄子》的听息法是一种最简单的方法,就是听自己的呼吸声,然后降低呼吸的声音。

有人总结了松、通、空三个字可以概括放松休息的方法。

喝水。人需要喝水,浓茶、可乐都不太能代替水。多喝水的好处是可以起来上厕所,避免久坐。

早上经过八个小时的睡眠,身体已经缺水了,起床第一时间就是喝水。

运动。 我不擅长运动,也没怎么去过健身房。运动可以激活人身体状态,不过也需要适度。加完班、缺乏睡眠的状态下不适合运动,会发生猝死等危险,而且人的精力会进一步透支。

众所周知,运动分为有氧和无氧。有一段时间我按照朋友推荐的方法,每天做一些超过我日常能力的运动,那段时间明显发现身体无法应付锻炼和日常工作。

工作日可以做一些无氧运动,换换脑子,休息日出去徒步进行有氧运动,比较合适。

2. 如何获得注意力

效率的关键是获得注意力,教员曾说 “对于人,伤其十指不如断其一指,对于敌,击溃其十个师不如歼灭其一个师。” 对于工作的事情,宁可做完一件事,不要 10 件事做一点。

目标感。 把时间、精力集中在重要的事情上,不然做再多的事情都是浪费。非常羡慕有一些人的目标感极强,能十年如一日的坚持,回报也是极大的。

目标感分为:战略目标和战术目标。战略目标就是几年内的大目标,战术目标可以是具体到每周、每天的目标。

战略目标具有很大的不确定性,和外部环境息息相关,可以随时调整。

战术目标则需要集中,执行力强的人对战术目标要求很严格。战术目标不要定的太大,因为人需要一种完成任务后的满足感,虽然大的目标带来的满足感强但是很难到达。一个小的目标让人获得快速的满足感,比如一篇文章、阅读一本书,这种满足感可以快速获得,也能坚持。

清单。高效的人都是清单控。每个人基本都会使用清单安排工作,不过使用清单的方法不同。

有很多清单软件,我自己用过很多,也给自己做了一个简单的清单应用。这些软件不见得好用,我还是比较喜欢直接使用文本列表来记录即可。每天写在一个 markdown 笔记本中就完全足够了。

可以把清单分为两类:主要清单和火炉清单。

  • 主要清单:就是当天必须完成的工作,尽量少,避免完不成带来的挫败感。

  • 火炉清单:不是必须做的,但做了更好的清单,如果当天完不成也没有关系。

使用两个清单把目标的任务放到主要清单,高优先级,不重要的事情放入火炉清单,低优先级。现实是完成主要清单后,就可以休息,完成火炉清单的任何一项都可以带来满足感。清单完成后,不要再放入新的东西。

番茄钟。 有一本书叫做 《番茄工作法》, 作者描述的一种工作方式,虽然是一本书,其实一句话就能概括 “工作以 25 分钟为周期,然后休息5分钟,周期内排除干扰,尽力集中注意力。” 因为作者使用厨房的番茄形状的定时器,于是起名番茄工作法。

番茄工作法基本已经进入大多数人基因了,我在 Chrome 浏览器安装了插件,只需要点一下就可以开始计时,基本上一开始工作就使用番茄工作法。

番茄工作法让我形成了条件反射,这个插件在时间到后会有闹钟响起,结合 Mac 多桌面个沉浸式的工作方式,开始后使用多桌面屏蔽无用的消息,时间到后闹钟响起回到主桌面,回复微信消息之类的。

番茄工作法还有一个好处,就是可以统计真实需要的时间,便于安排清单。有时候我觉的一个小时能完成的文章,实际需要 6 个番茄时间(3h)。人总是高估自己的能力,然后排入过多的事情,带来挫败感。

单线程。 我是一个绝对的单线程,做一件事情的时候腾不出来功夫做其他事情。所以像开餐馆这类繁杂的事情不适合我,编程尤其需要单线程,所以刚好可以混口饭吃。

一次只做一件事情,是一个违反直觉的事情,但是实际上是效率最高的方式。

计算机曾经有两种通信方式:串口和并口,串口的速率大大高于并口,有意思的是,串口使用的线路成本反而更低。

对计算机而言,原因在于某些场景(计算密集)下上下文切换带来的成本很高,对于某些场景(IO 密集)多线程更适合。

对人来说,需要持续思考的工作一次做一件事最好,需要等待外部条件的(比如厨师)可以同时做。

3. 如何管理精力

如何保持精力,对持续高效率工作有重要意义。

不要加班。 加班和晚睡带来的坏处极大,尤其是熬夜加班。

如果晚上 11 点还在工作,基本无法实现早起,即使还是 11: 30 开始睡觉。人每天的精力是基本固定的,如果前一天高强度工作,就会影响第二天的工作。除了紧急的事情外,算下来基本不划算。

10:00 - 12:00 的时间用在了工作上基本第二天做不到 7 点起床,晚上花掉的时间需要第二天双倍的返还。所以没有紧急的事情时,10 点停止工作是非常重要的一件事。

自我激励。人需要满足感,有时候需要可以设计满足感。用不好听的话来说,就是年薪和日结的区别。

不过需要客观认识的是,普通人都做不到延迟满足,不能接受回报周期过长的事情。

比如学英语、读书,都是回报周期极长的事情。设计奖励回路,读完一本书可以玩游戏 1 小时,可以让精力得到恢复。

有度。 休息有度、工作有度、睡眠有度,所有的事情也都需要张弛有度。

假期睡眠过久,自我感觉并不好,打乱生活节奏,其实不利于人的状态恢复。休息的时间过长,随着边际递减效应对精力恢复就没有意义了。持续工作过久,效率会大大减低。

避免碎片化时间。 有一些时间管理的书籍会建议利用碎片时间工作和学习,实际体验不好。无法专注的时间完全可以用来休息,不适合学习和工作。

碎片化的时间可以刷知乎、微博,玩游戏都可以,不过需要注意上面那条有度的原则,离开碎片化的时间就需要及时停止。

慢性疾病。容易忽略影响人精力的另一个重要因素,就是慢性疾病。

慢性疾病带给人的影响是潜移默化的,有时候不明显。有一些全身性的疾病之间会有一些莫名奇妙的关联。我之前体质差,容易感冒和溃疡,我以为是缺乏维生素,以及和个人体质有关系,后来发现不是。因为我有一颗龋齿,后来去修复牙齿,华西的医生说我应该早点来,不然会引发容易感冒和周身的疾病,因为坏牙会导致口腔细菌造成咽喉反复感染,炎症反应又会造成全身酸痛。

处理牙齿后,身体莫名变好很多。

4. 一些陷阱

有一些畅销书写了很多鸡汤,可能有害,需要警惕。

强调意志的作用。 其实普通人意志不强,执行力强的人很多是被一些客观因素推着走,也许房贷比个人意志更能激励人早起。

当一个人发现意志不起作用时,就会带来无休无止的焦虑和无力感。

承认自己的能力有限,学会和自己和解才能保持良好的心态,持续不断的成长。

把时间排满。 生活需要节奏感,人的工作和生活都需要留白,当清单中的任务完成后,看看电视剧,玩游戏本来就没有任何问题。一味强调工作,反而不能把工作做好。

不把时间排满,留一些时间胡思乱想也挺好的。

和自己对抗。 除了一些心理受到过创伤的人外,大多数人的心理问题和情绪问题都是和自己对抗带来的。

注意力不集中,是身体告诉你精力已经耗散光了,你需要休息。

感到疲惫,是身体告诉你出现问题,需要调整。

人的身体状态不同、人记忆力不同、思维能力不同,以及外部环境都是客观存在的。一些书中用各种理论表达人的智力是一样的、记忆力差不多,暗示你工作做得不好是不够努力是一种典型的 PUA 行为。久而久之,会带来不自信、焦虑和强迫症行为,也会有反强迫症行为,进一步带来身心伤害。

参考文章

more >>

读毛选:实践论和矛盾论的现实意义

春节前因为手臂酸痛,假期就没有再碰电脑,趁此机会把之前的购买的毛泽东选集第一卷再读了一遍。

毛选第一卷有许多著名的文章,比如《湖南农民运动考察报告》《星星之火可以燎原》等,其中压轴的就是《实践论》和《矛盾论》的两篇文章。毛选第一卷的重要意义在于作者从哲学的高度来分析当时中国的各种问题,实践论和矛盾论是毛泽东思想的精华。

毛选是哲学,是方法论和认识论的统一。

毛泽东思想继承于马克思辩证唯物主义,新中国是柏拉图在《理想国》中描绘的 “哲学家建立的国家”。毛泽东思想从哲学的角度可以作为各个行业的工作方法的指导,尤其是分析复杂问题的场景下尤为有用。

这篇文章总结了实践论和矛盾论的一些现实意义,尤其是作为软件工程师的工作。

1. 实事求是

如果了解近代史的话,能感受到毛泽东思想来源于我党不同时期惨痛的教训。

博古和王明在中央苏区第五次反“围剿”中,因盲目听从共产国际军事顾问李德的指挥。照搬苏联的革命经验,坚持走阵地战、夺取城市的策略,反对毛泽东指出的建立农村根据地、在运动中消灭敌人的方针。

然而我国红军毕竟和苏联的客观条件差距甚大,军事能力上还非常弱小,在当时的环境下使红军伤亡惨重,造成第五次反围剿的失败。

红军不得不被迫进行战略转移,开始长征。

教条主义给红军带来了巨大的损失,逐渐的,实事求是成为毛泽东思想中最重要的原则之一。《实践论》就是用马克思主义的认识论来反对本本主义、教条主义和经验主义。警惕熟读马克思主义的 “行家” 带来的错误思想。

马克思主义的认识论指出了,事物是客观的、变化的以及不同事物的内在是不同的。用他国的经验解决我国的问题,用过去的经验解决现在的问题,用主观的意志去改变客观的事实,这都是徒劳的。

在技术领域里,中台、微服务、GraphQL 等新的技术概念的兴起,或许对于一个大的公司是适合的,但对于创业公司未必合适。对于互联网行业有积极价值的技术,对于传统行业未必合适。

计算机软件是客观的,是唯物的,实事求是也应该是软件公司在部署工作中最重要的原则之一。

2. 实践和理论都重要

其实实践和理论的关系无需多说,古今中外的哲学家都有大量的论述。

班固的 “百闻不如一见。”强调实践的作用。

狄德罗在反对形而上学时说“仅仅一个理论上的证明,也比五十件事实更能打动我。”强调理论的作用。

王阳明的”格物致知“强调理论来自于实践,”知行合一“ 强调即使知道理论也必须实践才能改造事物。

毛泽东思想认为一般有两种世界观:

  1. 强调实践,凭经验的主观认识论,以过去的经历和体验认识事物。
  2. 强调理论,通过辩证的方法发现事物的性质、规律,并作出预测和推定。

两种认识论会带来不同的结果:强调实践会出现只要是 ”老祖宗“ 的法子就是好的,是不能被修改的,造成经验主义和机会主义;强调理论会带来无休无止的辩论,走向形而上,比如用”金木水火土“讨论那种颜色的梨子更甜,实际只需要尝一口就好了。

毛泽东思想一针见血的指出,我们对事物的认识一开始是朴素的,出现最符合直觉的理论,然后在实践中发现事情不是那么简单,于是修正理论使之进步。

因此, 实践和理论是在循环前进的。中国革命就是在不断失败中前进的,马克思主义也不断在中国化。

对于软件工程而言,计算机科学建立在离散数学等理论基础之上,如果没有理论基础做出的技术方案只能是凭自己臆想,不具有可持续性。一些没有受过计算机科学训练的 ”富有经验的“ 的老程序员,做出的方案,有时候强行走向数学中被证明的错误方向。

一些大的公司往往对员工要求苛刻,不仅需要大量工作经验的人,也要求这些工程师有计算机科学基础,于是招聘介绍中会有计算机专业二本以上这类看似奇怪的要求。

3. 矛盾是分析问题的理论工具

《矛盾论》是毛选的精华,也是最重要的难理解的文章之一。政治书对矛盾这个概念给了一个简洁的定义:即对立统一。不过这一定义也是让大多数人在了解《矛盾论》的过程中最大的绊脚石。

其实矛盾在我们生活中无处不在,一个鲜明的例子就是:看似完美的方案和设计背后会付出对应的成本,换句话说就算吃口软饭,也的服人管呢。

有一个笑话说的是,有人找工作要的是 ”位高权重责任轻,钱多事少离家近“,这里面出现了多个矛盾。在同一个系统中(论域),对于一个普通人来说,不引入外部其他优势条件的情况下,位居高位就需要承担巨大的责任和压力。

对于投资来说,获得高收益就必须承担风险;在分布式系统中,强调分区之间的可用性,一致性就很难保证。对于团队管理来说,自由和民主就会降低效率和执行力。

通俗来说,矛盾,是指一个事物内部属性或一个系统内部组成部分的对立关系。

矛盾,不是一个新的概念,其实在老子的朴素唯物史观已经说明了问题。”故有无相生,难易相成,长短相形,高下相倾,音声相和,前后相随。“

要使用好矛盾论,需要注意一些矛盾的特点。

矛盾是普遍的。人们对事物的认识加深,继而产生概念,对概念又分化出属性,这些属性在概念中出现了对立,矛盾就无处不在了。 有一些矛盾并不明显,有一些矛盾显而易见。矛盾不能用世俗道德的好坏来评价,比如战争是人们不希望的,但是战争的发起却最终能带来和平。事物正是由这些矛盾才会变化,运动和发展,否则就不会再变化。比如汽车加速运动,是发动机推力和地面摩擦力矛盾不平衡的表现;死去动物的腐败,是崩溃的免疫系统和微生物的侵袭的矛盾不平衡的表现。

矛盾和冲突是不同的。 矛盾存在不一定会造成冲突,毛泽东思想中叫做斗争,在日常的语境下冲突或对抗更为准确。存在矛盾的两种事物,有两种状态:冲突和平衡。一颗炮弹,密闭空间和爆炸性气体是一对内部矛盾事物。在没有外部条件(引信)的触发下,处于平衡状态,当引信触发后变为冲突。到达临界点后爆炸(量变到质变),密闭空间被打破,重新平衡。

认识到矛盾和冲突的关系,可以认识到矛盾是如何发展的。

矛盾是不断变化的。 这个世界唯一不变的就是变化。矛盾也是不断变化的,一个系统中主要矛盾可以变为次要矛盾。矛盾和冲突也在不断变化,冲突和平衡一直在反复变化。

外部条件也在不断变化,导致触发的条件变化了。抗日的爆发,让国内阶级矛盾变为不那么重要,中日民族矛盾变为主要矛盾,在历史的表现就是第二次国共合作的实现,统一的抗日战线形成等等。

找到主要矛盾是问题分析的关键。主要矛盾是影响某一事物发展进程的关键矛盾。有时候显而易见的矛盾不一定是主要矛盾,主要矛盾往往需要仔细分析才能浮出水面。土地革命战争时期,社会的主要矛盾看似是国共两党的矛盾,实际上是代表中国人民利益的共产党和代表大地主大资产阶级利益的国民党反动派的矛盾。所以应该丢弃意识形态的不同,团结一切可以团结的力量。

矛盾论不能解决所有问题。矛盾论是认识论,能作为解决问题的指导方向,但是某些问题无法被客观的改变也是客观存在的。人们曲解了黑格尔的”存在即合理“ ,这里的合理是指的客观,而非道德意义上的”合理“。犯罪分子存在是合理的,犯罪分子被惩处也是合理的。某些事物的消失是历史的必然,比如奴隶制度被封建制度取代,继而被资本主义取代,历史的车轮不能被任何人阻挡。但是,世界在微观上又是偶然的,微观上的偶然又会影响宏观的必然,也是一种矛盾。

在应用矛盾论分析问题时需要注意在同一系统中分析问题,才能保证问题分析可靠性。当需要改造事物时,识别到外部条件就可能破局关键,从而在正确的方向上施加影响。

4. 调查是应用矛盾的实践工具

认识了矛盾这个基本的哲学工具后,如何落地使用呢?

在毛选中没有提到如何使用矛盾论的具体方法,不过在毛选第一卷前面的文章中,以及毛选第三、五卷都有反复提到 ”调查“ 的重要性。

在《反对本本主义》 中作者给出了调查的方法,以及说明了调查是解决问题的重要手段;《湖南农民运动考察报告》是作者给出的一个详细的示例;在《实践论》中也提到了如何使用调查来认识事物和改造事物。

一切结论都要产生在调查结尾,而不是开头。

调查的过程就是人们对事物认识加深的过程,也是解决问题的过程。在没有调查的情况下,无论用那种决策分析工具,都陷入了形而上学和机会主义的陷阱。成功学,本质就是一种形而上学。成功人士的成功法则往往无法复制,即使可以复制也无法创造出同样的历史背景、客观条件和机遇。

通过调查才能分析出当前的客观条件。作者也总结了调查的技术:

  1. 要使用讨论的方法调查。只凭一个人讲他成功的经验,是片面的,处于利害关系可能把实际情况夸大。需要从不同人中获取信息加以修正。
  2. 准备调查时,需要分析被调查对象的选择,根据分类来分层选取。这个和自然科学的等价划分的抽样方法类似。
  3. 调查前需要设定纲目。也就是需要设计调查方案,比如目标、方法和输出方式等。
  4. 要亲自出马和自行记录。这个是说给当时的干部的,对我们来说调查一定要到现场。
  5. 要深入。在注意调查的全面性时也需要取几个重点,深入开展,才能发现之前没有设想到的问题。

在做一些技术方案和开发团队的咨询工作时,调查决定了是否能命中客户的问题,以及在调查中找到影响客户问题的主要矛盾,才能进行一些正确的行动。

5. 总结

因此我们可以看出毛泽东思想的脉络是:以客观为原则,使用矛盾为分析问题的切入点,使用调查寻找具体的矛盾,尤其是主要矛盾,最后再结合外部条件的变化,改造世界。

毛泽东思想,是对马克思唯物主义辩证法认识论和方法论的总结。

在使用毛泽东思想时,应该像毛泽东思想说的那样,观察客观的外部环境和具体情况,不应该盲从,实事求是的分析问题和解决问题。

more >>

DDD 概念的解释和理解

DDD 的概念来源于 2004 年 Eric Evans 的书籍《领域驱动设计》,这本书中有大量的软件工程的概念,阅读比较困难,鉴于翻译的原因,容易望文生义。

其实 DDD 的这些概念不是 Eric 自己发明的,而是在之前就有,《领域驱动设计》这本书总结了这些概念,并发扬光大。在理解这些概念是一定需要结合实践,以及当下的环境适当的采用。

《领域驱动设计》并不需要多高的计算机科学知识,但是对软件设计的经验要求较多,尤其是业务复杂的应用软件。下面是 DDD 相关概念的总结。

1. DDD 通用类概念

DDD 是 Domain-Driven Design 的缩写。其主要的思想是,我们在设计软件时,先从业务出发,理解真实的业务含义,将业务中的一些概念吸收到软件建模中来,避免造出“大而无用”软件。也避免软件设计没有内在联系,否则一团散沙,无法继续演进。

领域(Domain)是软件要解决的业务问题和诉求。通俗来说,领域就是业务需求。不过这些业务有一些内在逻辑需要分析,存在一些专业性,比如财务、CRM、OA、电商等不同领域。计算机只是自动化的处理领域问题,领域知识可能不是开发人员擅长的。

领域模型(Model)是业务概念在程序中的一种表达方式。面向对象的编程语言使用类作为业务概念的承载体,也可以用 UML 可视化的表达模型。模型是用来设计和理解整个软件结构,切记不要事无巨细的模型。在模型思维中,模型是简单的,能反应业务概念即可。

通用语言(Ubiquitous language)是指在软件设计中,业务人员和开发人员需要使用无歧义的统一语言来对话。这些语言包括对概念的统一理解和定义,以及业务人员参与到软件建模中,否则业务的变化会造成软件巨大的变化。

模型驱动的设计和面向对象,是计算机对现实世界的映射。理解现实世界需要用到哲学的工具,比如概念的内涵和外延、本体和客体,因此 DDD 会有一点哲学的意味,需要适应。

2. 建模概念

实体(Entity)是领域模型面向对象语言的具体体现,一般是一个类。领域模型可以是一个广义的概念,建模的结果和中间过程其实都可以看做模型。

值对象 (Value Ojbect)是一种特殊的实体,但一般认为没有自己的状态和生命周期。实体可以使用 ID 标识,但是值对象是用属性标识,任何属性的变化都视为新的对象。比如一个银行账户,可以由 ID 唯一标识,币种和余额可以被修改但是还是同一个账户;交易单中的金额由币种和数值组成,无论修改哪一个属性,金额都不再是原来的金额。

聚合 (Aggregate)是一组生命周期一致的实体集合,表达统一的业务意义。 聚合的意义在于让业务统一、一致,在面向对象中有非常重要价值。

聚合根( Aggregate Root)是聚合中最核心的对象,是聚合的领航员。要管理聚合必须使用一个聚合根,然后使用聚合根来实现发现、持久化聚合的作用。聚合中有且只有一个聚合根,一个实体可以独立作为一个聚合。

界限上下文( Bounded context) 有统一业务目标和概念的聚合组成的集合。大型项目都会存在多种模型,这些模型在不同的上下文中,可能有相似业务概念,但是他们的业务目标完全不一样。识别上下文可以从概念的二义性着手,比如商品的概念在物流、交易、支付含义完全不一样,但具有不同内涵和外延,实际上他们处在不同上下文。

界限上下文可以用于微服务划分、避免模型的不正确复用带来的问题。

3. 程序架构概念

服务(Service)是领域模型的操作者,承载领域的业务规则。 模型用于承载数据和系统状态,业务规则的事情就交给服务,让逻辑更为清晰明了。

模块(Module)同领域的类或者对象组成的集合。 模块的划分没有固定的模式,如果不是特别复杂的业务逻辑可以使用上下文划分模块,次一级的包使用对象类型组织即可。

工厂(Factory)是以构建模型为职责的类或方法。工厂可以把不同的业务参数转化为领域模型,在简单的业务逻辑中可以不使用工厂,实际工作中工厂不是一个具体的概念,为了简化我们可以把静态工厂方法放到 DOT 或者其他类中。

仓库(Repository)是以持久化领域模型为职责的类。 仓库的目的是屏蔽业务逻辑和持久化基础设施的差异,由于大部分项目使用关系数据库作为存储基础设施,仓库层大多被 ORM 代为实现,如果不使用 ORM,需要自己实现仓库。

规格 (Specfication) 是一些特殊的业务规则,一般表现为计算结果是真或者假的函数。规格可以用于业务逻辑的校验、查询条件,一般来说规格是可选的,不过对于复杂的业务抽象出规格可以复用这些业务逻辑。

4. 分层设计概念

DDD 在落地时可以使用四层构架,这四层有不同的意义,必要时候可以进行裁剪。

用户界面层,向用户显示信息,处理用户输入。 对于服务端应用而言,用户界面不是指 UI,而是指接入的协议,HTTP、websocket、LDAP 等协议都属于用户界面层。

应用层,组织业务场景,编排业务,隔离场景对领域层的差异。应用层最大的用处是处理业务场景,对应面向对象的思想就是 “关注点分离”。如果是一个用户社区应用,用户面、作者面、后台管理多个面实际是多个应用,而共享一套领域代码,可以做到既能关注点分离,又能复用核心业务逻辑的目的。

领域层,实现具体的业务逻辑、规则。 实际处理业务的地方,领域层需要对应用层提供无差别的服务和能力。比如用户注册,用户可以通过邮箱自己注册,也可以由管理员在后台应用添加。注册的逻辑需要由领域层完成,但是处理用户输入参数的部分需要由应用层屏蔽。

基础设施层,提供具体的技术实现,比如存储,基础设施对业务保持透明。 如果无法对领域层提供透明的能力,说明这部分代码不适合放入基础设施层。比如,在使用 JPA 时,Repository 的 Interface 定义,不应该认为是基础设施,而 ORM 对这些 Interface 的实现和具体的 SQL 处理则可以认为是基础设施层的代码。

在使用框架时,需要弄清楚分层不是绝对的。比如,Spring MVC 的 controller,实际不是用户界面层,而是应用层,因为接入协议的已经被框架处理了。

more >>

敏捷日历

敏捷团队使用迭代方式演进,一般是两周一个迭代一次。也就是说两周就要规划、发布一次,那么如何规划这种团队的各种活动呢?

参考当前团队的运作方式,我整理了一个日历,包含了当前迭代的活动、为下个迭代的准备工作和回顾上个迭代的活动。

这些活动来自于一些经典的敏捷实践(scrum),但对于时间的选择,来自于我们团队根据大量实践,仔细斟酌,在下文中也会说明这样安排的原因。

如果团队能坚持使用固定的敏捷日历,团队养成规律和取得默契后,团队协作会得到较好的提高。

1. 敏捷日历

image-20210205231125414

2. 活动解释

活动分为了三类:

  • 当前迭代活动
  • 准备下个迭代活动
  • 总结上个迭代活动

每日站会:15 - 30分钟。用于团队同步每个人的工作,需要更新前日的工作、遇到的问题和当日的计划。

迭代计划会议:一般叫做 IPM,用于和团队规划这个迭代的工作事项,需要全员参与。PM 或者 BA 发起或者组织,时间一般在迭代的第一天站会后,是迭代中最重要的会议之一。

TL 分配开发任务:计划会议后,团队知晓当前迭代的任务和目标。TL 需要总览任务,引导相关性强的工作有某一个人领取,达到效率最大化,并同步整体的技术方案,避免工作冲突。

上线:安排在周一晚的目的是,前一个迭代的末尾有足够的时间测试和修复问题,避免在周五发布是为了周末出现问题,人员已经离岗,需要加班才能处理。

每日 Code Review:30 - 60 分钟,团队一起查看代码,用于发现问题、同步一些技术上下文。

Retro 回顾: 可以安排在迭代后的任何时间,计划在周三是因为周三已经进入迭代状态,会议较少。

第一周技术例会:占用 Codere View 时间,进行技术例会,需要梳理技术债、工作规范和注意事项、一些技术问题。

第二周技术例会:占用 Codere View 时间,需要讨论 UAT 注意事项、梳理迭代中的 bug、配置清单、版本号、上线负责人等事项。

上 UAT:上线负责人需要拉取分支、创建 git tag、发布版本到 UAT等工作。周四、周五留给 QA 回归和 bug 修复,未完成的业务特性需要剥离出版本。

下个迭代的技术方案:没有 bug 需要修复的人员开始设计下个迭代的技术方案,比如 API、数据库设计等,并和团队确认。

全员 Bug Bash:QA 组织团队以用户视角回归测试,发现问题。

工作量估算: BA 需要确认下个迭代的工作量,由于这个时候需求和技术方案比较明确,比较容易得到工作量的估算值。BA 和 PM 需要根据估算结果,排入合适的业务工作和技术工作。

more >>

如何可视化的表达 DDD 的模型?

做完 DDD 的建模工作,或者事件风暴工作坊后,往往会有朋友会问,如何方便的表示模型和维护模型呢?

使用自己定义的图块,太过于随意;使用 UML 又显得繁琐,又有大量的人唱衰 UML。

我曾陷入无休无止的对工具的选择中,最终意识到克制和取舍才是问题的答案 —— 没有终极的解决方案。

模型,是用来表达和理解复杂的软件业务的,简洁明了意味着信息的丢失,追求准确和细致,必然需要面对繁复和难以维护的问题。

1. 如何取舍模型中的信息?

我想用 DDD 模型指导我的开发工作:数据库设计、代码分包、Restful API 设计等工作。

模型中的上下文会在单体中设计包,而聚合会和 Service 的组织有关系,于是我需要关心模型的上下文、模型的关系。

而属性不是我关心的,而且会干扰对模型的理解。

UML 是一个大的工具箱,如果我们想明白我们需要什么样信息,去 UML 找对应的工具即可。

想看清系统业务结构基本元素需要:

  • 上下文。可以使用 namespace 来表达,表示相对隔离的概念空间。
  • 模型。可以使用类图来表达,代表业务中可被操作的实体。
  • 关系。由于模型都是被动操作对象(业务客体),业务模型可以只使用组合即可,避免使用 UML 中的 6 种关系,弄得复杂。
  • 聚合。聚合代表一组强关联和具有一致生命周期的一组模型,可以使用 UML package 来表达。
  • 实体值对象。实体、值对象和聚合根本质是模型的不同形态,在编写代码时需要注意,在 UML 中可以不表达出来。

有一些信息会对模型的视图干扰,不推荐表达出来:

  • 属性。过于细节,不利于模型的维护,会让视图过大而看不清。
  • 工作坊的中间产物。比如事件、角色等信息,如果需要表达,可以使用其他图形来表达,否则含混不清。

2. PlantUML 简介

代码的维护和持续演进比“完美”的初始设计更为重要。

将模型文件放入代码仓库,随代码维护,并每个迭代演进,有非常重要的意义。但是需要让图形能用代码表示,做到 Diagram As Code,便于版本管理。

PlantUML 是一个用代码驱动绘图的 UML 工具,就这一点就足以够让其他的 UML 工具相形见绌。

PlantUML 使用 Java 编写, 可以通过命令行、Jar 包、插件等多种途径使用,不过一般常见的使用方式有两种:

  1. 使用在线绘图工具。用于临时和同事讨论问题,也可以把绘制好的内容通过链接发送给其他人。
  2. 使用 IDE 插件。直接将 UML 模型在 IDE 中渲染为图形。

使用在线绘图工具

使用官网的在线绘图工具 http://www.plantuml.com/plantuml/uml

编写 UML 表达式后点击 submit 即可生成 UML,并带有一个带有绘图信息的链接。

使用 IDE 插件

如果你使用的 IntelliJ IDEA,可以在 Preferences 菜单下的 Plugins 搜索 PlantUML integration,安装重启 IDE 即可。

image-20210204232444997

在项目中添加后缀为 puml 的文本文件即可使用。

3. 使用示例

这里使用一个互联网内容社区应用(UCG)作为示例,做了一些简化,设计好的模型大致为:

  • 用户上下文,包含用户聚合、角色聚合。
  • 内容上下文,包含专题、分类、文章聚合。

可以如下表示上下文、聚合和实体的关系,得到一个非常简洁的模型图。

@startuml
namespace UserContext {
    package User <<Aggregate>> {
        User  --> UserRole
        User --> UserAttribute
        User --> UserPreference
    }
namespace UserContext {
    package Role <<Aggregate>> {
        class Role
    }

namespace ContentContext{
    package Category <<Aggregate>> {
        Category  --> CategoryTag
    }
namespace ContentContext{
    package Topic <<Aggregate>> {
        Topic  --> TopicArticle
    }
namespace ContentContext{
    package Article <<Aggregate>> {
        Article --> ArticleMeta
    }
@enduml

渲染好的效果如下:

PlantUML diagram

可以清晰的看明白模型、聚合,以及他们所处上下文的关系,也可以在代码仓库中被很好的管理。

参考文档可以做出更多特性的图表来,比如增加模型之间的关系数量,是一对多还是多对多。

@startuml
namespace UserContext {
    package User <<Aggregate>> {
        User "1" --> "*" UserRole
        User --> UserAttribute
        User --> UserPreference
    }
@enduml

PlantUML diagram

more >>

DDD落地,如何持久化聚合根

1. 理解聚合根

聚合是一组始终需要保持一致的业务对象。因此,我们作为一个整体保存和更新聚合,以确保业务逻辑的一致性。

聚合根是 DDD 中最为重要的概念,即使你不使用 DDD 编写代码也需要理解这一重要的概念 —— 部分对象的生命周期可以看做一个整体,从而简化编程。

一般来说,我们需要对聚合内的对象使用 ACID 特性的事务。

最简单的例子就是订单和订单项目,订单项目更新必须伴随订单的更新,否则就会有总价不一致之类的问题。订单项目需要跟随订单的生命周期,我们把订单叫做聚合根,它就像一个导航员一样

class Order {    
    private Collection<OrderItem> orderItems;   
    private int totalPrice;   
}

class OrderItem {
    private String productId;   
    private int price;   
    private int count; 
}

Order 的 totalPrice 必须是 OrderItem 的 price 之和,还要考虑折扣等其他问题,总之对象的改变都需要整体更新。

理想中最好的方式就是把聚合根整体持久化,不过问题并没那么简单。

2. 聚合持久化问题

如果你使用 MySQL 等关系型数据库,集合的持久化是一个比较麻烦的事情

  1. 关系的映射不好处理,层级比较深的对象不好转换。
  2. 将数据转换为聚合时会有 n+1 的问题,不好使用关系数据库的联表特性。
  3. 全量的数据更新数据库的事务较大,性能低下。
  4. 其他问题

聚合的持久化是 DDD 美好愿景落地的最大拦路虎,这些问题有部分可以被解决而有部分必须取舍。

聚合的持久化到关系数据库的问题,本质是计算机科学的模型问题

聚合持久化是面向对象模型和关系模型的转换,这也是为什么 MongoDB 没有这个问题,但也用不了关系数据库的特性和能力。

面向对象模型关心的是业务能力承载,关系模型关心的是数据的一致性、低冗余。描述关系模型的理论基础是范式理论,越低的范式就越容易转换到对象模型。

理论指导实践,再来分析这几个问题:

“关系的映射不好处理” 如果我们不使用多对多关系,数据设计到第三范式,可以将关系网退化到一颗树。

image-20210120230700098
△ 网状的关系
image-20210120230820713
△ 树状的关系

"将数据转换为聚合时会有 n+1 的问题" 使用了聚合就不好使用集合的能力,列表查询可以使用读模型,直接获取结果集,也可以利用聚合对缓存的优势使用缓存减轻 n+1 问题。

"全量的数据更新数据库的事务较大" 设计小聚合,这是业务一致性的代价,基本无法避免,但是对于一般应用来说,写和更新对数据库的频率并不高。使用读写分离即可解决这个问题。

3. 自己实现一个 Repository 层

如果你在使用 Mybatis 或者使用原生的 SQL 来编写程序,你可以自己抽象一个 Repository 层,这层只提供给聚合根使用,所有的对象都需要使用聚合根来完成持久化。

一种方式是,使用 Mybatis Mapper,对 Mapper 再次封装。

class OrderRepository {
    private OrderMapper orderMapper;
    private OrderItemMapper orderItemMapper;

    public Order get(String orderId) {
        Order order = orderMapper.findById(orderId);
        order.setOrderItems(orderItemMapper.findAllByOrderId(orderId))
        return order;
    }
}

这种做法有一个小点问题,领域对象 Order 中有 orderItems 这个属性,但是数据库中不可能有 Items,一些开发者会认为这里的 Order 和通常数据库使用的 OrderEntity 不是一类对象,于是进行繁琐的类型转换。

类型转换和多余的一层抽象,加大了工作量。

如果使用 Mybatis,其实更好的方式是直接使用 Mapper 作为 Repository 层,并在 XML 中使用动态 SQL 实现上述代码。

还有一个问题是,一对多的关系,发生了移除操作怎么处理呢?

比较简单的方式是直接删除,再存入新的数组即可,也可以实现对象的对比,有选择的实现删除和增加。

完成了这些,恭喜你,得到了一个完整的 ORM,例如 Hibernate 。

4. 使用 Spring Data JPA

所以我们可以使用 JPA 的级联更新实现聚合根的持久化。

大家在实际操作中发现,JPA 并不好用。

其实这不是 JPA 的问题,是因为 JPA 做的太多了,JPA 不仅有各种状态转换,还有多对多关系。

如果保持克制就可以使用 JPA 实现 DDD,尝试遵守下面的规则:

  1. 不要使用 @ManyToMany 特性
  2. 只给聚合根配置 Repository 对象。
  3. 避免造成网状的关系
  4. 读写分离。关联等复杂查询,读写分离查询不要给 JPA 做,JPA 只做单个对象的查询

在这些基本的规则下可以使用 @OneToMany 的 cascade 属性来自动保存、更新聚合。

class Order {    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private String id;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Collection<OrderItem> orderItems;   
    private int totalPrice;   
}

class OrderItem {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private String id;
    private String productId;   
    private int price;   
    private int count; 
}

OneToMany 中的 cascade 有不同的属性,如果需要让更新、删除都有效可以设置为 ALL。

5. 使用 Spring Dat JDBC

Mybatis 就是一个 SQL 模板引擎,而 JPA 做的太多,有没有一个适中的 ORM 来持久化聚合呢?

Spring Data JDBC 就是人们设计出来持久化聚合,从名字来看他不是 JDBC,而是使用 JDBC 实现了部分 JPA 的规范,让你可以继续使用 Spring Data 的编程习惯。

Spring Dat JDBC 的一些特点:

  • 没有 Hibernate 中 session 的概念,没有对象的各种状态
  • 没有懒加载,保持对象的完整性
  • 除了 SPring Data 的基本功能,保持简单,只有保存方法、事务、审计注解、简单的查询方法等。
  • 可以搭配 JOOQ 或 Mybatis 实现复杂的查询能力。

Spring Dat JDBC 的使用方式和 JPA 几乎没有区别,就不浪费时间贴代码了。

如果你使用 Spring Boot,可以直接使用 spring-boot-starter-data-jdbc 完成配置:

spring-boot-starter-data-jdbc

不过需要注意的是,Spring Data JDBC 的逻辑:

  1. 如果聚合根是一个新的对象,Spring Data JDBC 会递归保存所有的关联对象。
  2. 如果聚合根是一个旧的对象,Spring Data JDBC 会删除除了聚合根之外旧的对象再插入,聚合根会被更新。因为没有之前对象的状态,这是一种不得不做的事情。也可以按照自己策略覆盖相关方法。

6. 使用 Domain Service 变通处理

正是因为和 ORM 一起时候会有各种限制,而抽象一个 Repository 层会带来大的成本,所以有一种变通的方法。

这种方法不使用充血模型、也不让 Repository 来保证聚合的一致性,而是使用领域服务来实现相关逻辑,但会被批评为 DDD lite 或不是 “纯正的 DDD”。

这种编程范式有如下规则:

  • 按照 DDD 四层模型,Application Service 和 Domain Service 分开,Application Service 负责业务编排,不是必须的一层,可以由 UI 层兼任。
  • 一个聚合使用 DomainService 来保持业务的一致性,一个聚合只有一个 Domain Service。Domain Service 内使用 ORM 的各种持久化技术。
  • 除了 Domain Service 不允许其他地方之间使用 ORM 更新数据。

当不被充血模型困住的时候,问题变得更清晰。

DDD 只是手段不是目的,对一般业务系统而言,充血模型不是必要的,我们的目的是让编码和业务清晰。

这里引入两个概念:

  1. 业务主体。操作领域模型的拟人化对象,用来承载业务规则,也就是 Domain Service,比如订单聚合可以由一个服务来管理,保证业务的一致性。我们可以命名为:OrderManager.
  2. 业务客体。聚合和领域对象,用来承载业务属性和数据。这些对象需要有状态和自己的生命周期,比如 Order、OrderItem。

回归到原始的编程哲学:

程序 = 数据结构 + 算法

业务主体负责业务规则(算法),业务客体负责业务属性和数据(数据结构),那么用不用 DDD 都能让代码清晰、明白和容易处理了。

more >>

如何做好敏捷技术预研(Spike)?

在敏捷开发中,有时会出现下面的一些问题:

  1. 迭代前工作量不好估计。
  2. 敏捷开发造成技术方案破碎,无整体规划。
  3. 迭代中方案变化,交付风险大。

在敏捷实践中,往往会有 Spike 这个实践,可以在一定程度解决这些问题。Spike 的目的是收集信息和寻找一个问题的答案,而不是交付具体产品。

我一直找不到一个好的中文词汇,有些书籍翻译为“探针”,通俗的来说就是研发人员正式开始启动一个大的技术方案前。提前“摸一下”,摸的好不好直接决定了后面的工作是否能很好的开展。

在我国的五代战斗机 J20 首飞 10 年的新闻中学到了一个新词 —— “技术预研”,其实能很好的描述 Spike 这实践,技术预研的价值在软件工程中同样明显。

技术预研的价值

工作量估算。工作量估算不准的原因是信息不够,技术预研可以提供这些信息,提前知道有哪些坑。工作量的评估对业务、产品规划极其重要,尤其是多个团队工作在一个项目上时。
人员能力培养。有一些团队技术预研工作往往被技术 leader 默认做了,这种做法有利有弊。不好的地方是团队成员得不到锻炼,只能实现一些预研好的内容,造成团队做方案的能力低。同时,也会消耗技术 leader 的工作时间,无关注其他工作。提前设计预研让团队来做,让技术 leader 评审即可。

输出技术方案。技术预研可以提前输出技术方案,避免在迭代开始后再进行设计工作,如果多个人在一类工作上,没要技术方案会各自为政。敏捷开发让这种情况放大,减低开发效率。

减低风险和减少浪费。一些技术方案可能客观上就不能实现,如果放入迭代交付可能在花费了时间分析后,结论是无法实现,造成浪费和交付风险。有可能因为这一个任务影响其他被依赖的工作内容。

预研工作规划

“生产一代、试制一代、预研一代、探索一代” ,这是我国航空工业尤其是军用飞机的发展计划。 J20 在 2011 年于成都黄田坝军用机场首飞 距今 10 年,但 1997 就开始立项,且比在之前就开始规划了。

结合敏捷工作方式,我们可以用类似的思路规划技术预研工作。

这里个“代”可以粗暴的设定为一个迭代,也就是 2 周,让策略更好落地实施。对于敏捷工作方式,如果进入迭代才开始验证技术方案的可行性、给出方案等工作,会给任务的拆分带来麻烦。

规划一代。提前两个迭代规划,这时候业务需要给出粗的业务目标,如果业务明显不合理,技术 leader 可以第一时间提出建议。这个阶段需要业务方准备相对大粒度的业务需求(非常细也不现实),计划好需要技术预研的工作内容。这个阶段的参与人为业务需求方和技术 leader 即可,技术 leader 可以通过经验得出能否实现,是否有价值预研,但是不知道细节和工作量。

技术预研一代。提前一个迭代预研,给出技术方案,并给业务方反馈。这个阶段就是预研的阶段,技术 leader 得到需要预研的工作后,可以通过兴趣让团队成员领取。这个阶段的预研工作通过空余的时间,通过兴趣驱动,需要设点一个时间范围(Time Box),截止时间一般在下个迭代的计划工作之前。技术阶段需要给出结果,这个结果可能是肯定的,也可能是否定的,还有可能需修改业务需求。总之,业务方可以拿到这个结论进行下个迭代的计划。

实现一代 。如果预研结果表明方案可行,进入迭代开始交付,开始前需要技术 leader 组织团队评审,并进一步得到更为细致的方案,比如:数据库设计、API 设计、数据迁移方案等。预研工作和实现工作最好是同一个人,如果无法做到,可以在实现过程中由预研工作的人指导。

技术预研的输出

技术预研往往可以看出一个开发者的专业性,不好的输出在人的脑子中,只要一个影子,而好的方案需要考虑到方方面面。

下面有一个清单供输出技术预研结果时使用,推荐使用 Markdown 文档记录。

demo 原型。原型的意义在于可以快速实现,没有当前项目的包袱,比较灵活。原型法是一个比较好的工程方法,业务原型有 Axure 线框图,技术原型则需要做一些小的 demo 去验证可行性。比如支付、人脸识别等,集成到业务代码中需要的成本和 demo 完全不同。
落地方案。需要考虑如何将原型落地到业务代码中,需要设计数据库、API、流程图等。还有落地的成本在和现有的逻辑结合,以及老数据的迁移和兼容问题。有一些技术方案还需要增加特性开关,考虑方案失败如何回退。

工作量。 需要给出具体的工作量,用于迭代排期。
风险点。在预研时,原型毕竟不能考虑所有的情形,可以给出一些风险点,便于团队决策,如果有备选方案就更好了。

任务拆分。 用于在实施中如何分布实现,好的任务拆分也可以工作量估算更准确,风险更小。

more >>

如何做笔记?

笔记的作用是让知识放心丢下,放空自己,但是又能及时的捡起来,减少自己的心智负担。一份好的笔记可以让知识简化、压缩,一目了然,然后在一段时间后在需要的情况下能轻易的捡起来,做到能放能收的程度,起到整理学习内容的作用。

最优秀的笔记可以做到将一本书压缩成一张纸或者几页纸,从而轻易的回忆、联想出全部内容。压缩的过程需要将书本的知识转换成自己的语言,只有自己的语言才能记住。压缩成一页纸是理想情况,一些专业书籍往往内容非常多,可以一个章节压缩成一页纸。但是一些畅销书有很多废话,只能提炼出非常少的东西,一页纸都用不上。

自己语言的标准是能轻易复述一遍,不是背诵,能用自己的话把这套知识讲通,说明完全理解了这套知识。这就是费曼学习法的精髓,费曼学习法并不是一定要将自己的学到的东西做成 PPT 讲授给别人,而是说需要将知识内化、提炼、复述,讲给别人只是检验复述能力的一种手段。

从这个角度看待笔记的话,做笔记的过程比最终的笔记更有价值。做笔记就是在复述压缩内化。阅读别人做好的笔记毫无意义,有时候会因为信息被裁剪弄得一头雾水。得到 APP 中拆书的本质就是分享笔记,所以这些笔记看起来学到了知识,其实只是感觉自己学习到了。

好的笔记有几个要求:

  1. 用自己的语言来描述知识,而不是照着书本的内容抄写。复述是背诵的前提,不是自己的语言很难记忆。
  2. 用线条串联知识点,形成知识网。
  3. 压缩信息,结合自己的上下文保留关键信息。
  4. 最好用图形的方式做笔记。我喜欢用格子背景的 keynote 来记笔记。

笔记可以做很多事情。除了整理学习内容之外还可以记录自己的想法。每天将自己的想法记录下来,每个人都有一些好的想法迸发出来,养成记录想法的习惯可以收获很多。一些作家就时常带上小本子和笔,有灵感时就记录下来。元末陶宗仪外出时有灵感就写在树叶子上,汇集成《南村辍耕录》。

通过把想法写下来可以大大降低自己的思维负担,更适合深度思考。每天写想法的时候可以深入多挖一点,多问一些问题,多问几个为什么?从问题驱动,深度思考。比如:

  • 为什么书店摆出来的书和图书馆不一样,说明了什么?
  • 我们能否从中得到启发?
  • 从中能得到什么价值?

从这些问题中可以得到,书店是面向大众消费者人群的。为了赚钱,选择的都是消费者喜爱的书籍,从而可以了解到大众想要的是什么。根据观察,现在书店往往不再有专业书籍,更多的成功学、简单的心理学等更多的畅销书。在书店看书的时候,需要注意这些书专业性并不强,更多的是科普作用。

通过这些想法加上深度思考,这样可以拓展出一篇小文章,这种短文作为自己的心得总结还是很有价值。不过需要注意不要花费太多的时间,丢失自己当前的工作目标。不要过于在乎这些小文章的连贯性,很多看似没有连贯的东西,时间久了就会连贯起来。这就是量变产生质变。

记笔记还可以提高学习力。笔记是学习中很重要的一环,并不是简单记录下知识点,而是对知识再加工。最好不要边学边记,如果是课堂上这样做会丢失学习的注意力,如果是看书学习,会难以同时兼顾阅读和记录。

记笔记前应该先大量获取知识作为输入,如果是课堂就记录一些关键词,如果是看书就用记号笔做标记。完成输入后,理解、联系、类比这些知识,并用自己的语言整理成笔记。尽可能压缩这些信息,便于记忆。

完成笔记后,在必要时回顾它加强记忆,比较好的方式可以是在睡前通过类似闪卡的方式记忆。在记忆时就能体会图形化笔记的好处了,通过图形化和线条的联系,可以在大脑中通过空间位置检索知识点。比如,我在学习《八段锦》时做了一页纸的笔记,将八段锦的八个动作和人体的空间位置联系起来,很容易的记住了八段锦的八个动作。

所以,好的笔记可以帮助整理学习内容、记录日常想法、提高学习力的作用。用图形法、压缩信息的方法记录笔记可以结构化的表示知识点,从而帮助记忆和降低心智负担。

more >>

敏捷过程中的软件持续建模

敏捷方法追求单件工作流和响应变化,需求按照故事卡拆分,容易造成业务模型、架构设计碎片化。

在按照敏捷方法运作的团队工作时,比较麻烦的事情是怎么继续坚持软件建模的这些实践。BA (业务分析师)们按照迭代把分析好的用户故事给到开发,开发会按照情况认领任务并开始开发。

因为每个用户故事都是单独计算工作量,有单独的生命周期,所以使用用户故事来描述业务需求,会造成业务需求的整体性缺失。

但是,软件开发的本质是知识性工作,如果没有整体性设计的过程,软件架构会很快腐化和混乱。

软件建模,在软件开发过程中指的是数据库设计、UML 图例的更新、架构的调整和领域对象的设计。建模的专业性,往往决定了一个项目的成败。建模清晰的开发团队,效率和开发成本都会小很多。

软甲项目很小时,不太能体会到建模的优势,当业务复杂到几十上百个数据库表、几百个用例、7-8 个服务时,没有有效的建模,软件项目将无法继续。

因此,必须想办法既要敏捷迭代式的开发,也要持续维护模型的完整和一致性。

解决敏捷项目中整体性设计的问题有几种运作方法:

  1. 将用户故事拆解粒度变大
  2. 固定的人认领批量的用户故事
  3. 在迭代期间增加软件设计和持续建模的过程
  4. 增加专门的软件建模任务卡,并排在每个迭代前

1、2 两种方法可以看做一体,是对当前的敏捷过程调整,对敏捷的一些实践进行裁剪。固定的人批量的认领一些任务,由一个人来负责整体性设计。

3、4 两种方法是我从一种叫做 RUP 的软件开发方法中受到的启发,对敏捷过程进行改进。每个迭代都应该进行模型的修正,并持续建模。

软件建模,也需要迭代更新,这就是持续建模。

持续建模方法

其实 UML、ER 图等常用软件建模的方法使用起来都比较简单,也很容易学会。但是实际开发过程中很少看到有团队再认真的画 UML 相关图形来设计对象,也基本上很难看到大规模使用 E-R 图的情况。

原因很简单,这些图形难以随着代码和业务的变更而同步更新。如果一个软件模型不能正确的表达业务逻辑和真实的架构情况,团队就很难有动力去更新它了。软件建模大多数都发生在项目启动的时候,以及大规模重构的时候,难以持续整个软件周期。

背后的逻辑分为技术、开发过程两个方面:

  1. 从管理上,敏捷方法中每个迭代周期没有一个过程留给去做架构调整、更新模型这些事。
  2. 从技术上,模型的更新成本比较高,修改代码时需要记得同步更新模型,尤其是一些图形的方式更新就更麻烦了。

简单来说,模型的创建很简单,但是保持和代码同步更新很麻烦。这也是很多代码生成器难以发展的根本原因,使用代码生成器会造成一旦手动修改部分生成的代码,就无法再使用代码生成器了。

RUP 方法

先说从开发过程上解决每个迭代模型更新和架构调整的问题。

RUP(Rational Unified Process),即 Rational 统一过程。它是一种比较重的开发方法,并提供了一整套软件工程相关的文档、规范,以及相关的工具。

正是因为 RUP 比较重,需要每个团队的项目经理根据自己的情况进行裁剪,所以在竞争中不如敏捷方法。我们不必学习 RUP,只需要提取出 RUP 中有价值的的方法和实践到敏捷开发中。

RUP 将开发过程划分为了初始阶段、细化阶段、构造阶段和交付阶段 4 个阶段。RUP 按照迭代的方式运行,商业建模、需求、分析和设计、实现、测试、部署 6 个步骤,每个迭代都有这 6 个步骤。

RUP 和敏捷的不同是,敏捷强调单个用户故事的流动(WIP),RUP 可以理解为每个迭代内实际上还是瀑布的方式在运作。

其中:

  • 商业建模和需求的过程为 BA 或者产品经理输出原型图、用户故事、业务用例等
  • 分析和设计步骤为开发人员进行系统设计的过程,前期迭代为初始话系统设计,后期的迭代为修正系统设计的过程。系统设计的输出为 E-R 图、UML 中的类图等整体性设计。

RUP 的几个步骤都发生在同一个迭代。敏捷的一些实践,实际上是穿插完成的。

比如以两周一个迭代为例,前一个迭代的第二周,BA 们应该开始为下一个迭代的工作进行业务分析工作,完善功能的细节和约束条件。迭代的末尾 2 - 3 天应该进入封包整体回归和修复 bug 阶段,可以开始下个迭代的系统设计和模型修正。

img

代码化的 UML 工具

再从技术的角度来说,如何持续维护模型。

表达模型的工具很多,Microsoft Visio、draw.io、starUML 都是比较好的选择。但是,持续建模要求每个迭代都对模型进行修正,因此模型最好能通过版本管理工具管理。

在调研各种绘图工具后,能通过代码化的方式表达 UML 图形的工具不多,其中有 markdown 的 UML 拓展、plantuml。实际使用的情况来看,比较建议使用 plantuml。

plantuml 可以绘制基本的时序图、类图等,以及架构图、E-R 图等非 UML 规范的图形。在实际工作中,我们绘制的图形中,有 5 种比较常用于表达软件模型:

  • 用例图
  • 类图
  • 时序图
  • E-R 图
  • 架构图

用例图、时序图在分析业务逻辑时使用,架构图、E-R 图在做整体性设计时候使用,类图则往往用于具体实现阶段。从维护成本的考虑,保持架构图和 E-R 图持续维护,其他图例在必要时使用即可。

使用 plantuml 非常简单,只需要安装 Java,并现在一个 jar 文件就可以将文本的 UML 代码转换成图形,还可以借助一些工具进行代码生成。 除时序图和活动图以外的图,还需要需要安装 Graphviz 依赖。

以 Mac 为例,安装 graphviz:

brew install graphviz

下载 planuml :

wget https://nchc.dl.sourceforge.net/project/plantuml/plantuml.jar

用官网的例子绘制一个时序图,将下列文本保存为 sequence.txt。

@startuml
用户 -> 认证中心: 登录操作
认证中心 -> 缓存: 存放(key=token+ip,value=token)token

用户 <- 认证中心 : 认证成功返回token
用户 -> 认证中心: 下次访问头部携带token认证
认证中心 <- 缓存: key=token+ip获取token
其他服务 <- 认证中心: 存在且校验成功则跳转到用户请求的其他服务
其他服务 -> 用户: 信息
@enduml

然后用 java 执行 plantuml.jar 即可运行,并得到序列图。

java -jar plantuml.jar sequence.txt

总结

为了解决敏捷项目中,业务模型和架构碎片化的问题,可以通过开发过程和技术两方面进行优化。

过程方面,可以在敏捷过程中加入建模的环节,实际上在一些大的公司类似于方案评审环节。技术方面,可以尝试改进模型的维护方式,竟可能的将模型简化,并通过代码化的方式持续演进。

参考文章

more >>

必须教给孩子的几种思维模型

写给以后的小孩,也写给现在的自己。

清单思维

提高效率最简单的思维,列一个清单并完成它。这个思维已经成为很多人的默认选项,拆任务、贴便签,然后逐个突破。

比较有意思的是,网上还有清单思维的课程,和很厚的书籍,其实大可不必。很多培训都只是为了参训的人感觉学到了东西,其实过段时间就忘了,很多操作方法过重反而不利于习惯的培养。

列清单很简单,可以无处不在,最根本的好处是让清单把脑袋中的内容腾下来,放空大脑的同时,也可以防止遗忘。写在清单上 ,不用随时惦记这,把精力都留给要做的事情。

一场会议,一次培训,甚至做一个菜,或者写一篇文章都可以使用清单。

很多职场人士都无法离开清单,可以使用滴答清单、便利贴或者在笔记中列一个清单都可以。我也做了一个简单的网页,可以存储清单,打开就可以使用。http://todo.printf.cn/

模型思维

世界是复杂的,要想理解一件事情必须要进行简化,简化后找到和自己知识背景有联系的事物进行类比就能理解。

抽象的概念经过简化,并找到一个经历过的事物进行类比,才能得到理解,这就是人类认知的基本逻辑。

如果给幼儿园的孩子讲解 -1 * -1 = 1?

用双重否定等于肯定,孩子是理解不了的。一个方法是 -1 代表的是向后转,-1 * -1 意思是向后转,再向后转,最后面向了哪里呢?

这就是类比,类比不是准确,但是可以让人理解抽象的概念。

用手指学习数数,手指就是模型,而数字只是人们创造出来的概念。

数学老师让我们理解什么是集合,集合很好理解,就是一堆事物的整体。那么什么是空集呢?空集就是爸妈给你了一个钱包,但是拿走了所有的钱。

辩证思维

为什么所有人都觉得《老子》非常牛逼,非常有道理?

《老子》中充满了朴素的辩证思维,高下、前后、美丑,随处可见的辩证思维让这本书闪闪发光。“有无相生,难易相成,长短相形,高下相倾,音声相和,前后相随。” 这类的描述非常多,也非常具体。

老子的辩证思维是一种朴素辩证法,辩证唯物主义又进了一步,毛主席的《矛盾论》就是其中的代表。

辩证思维,可以让人从不同的角度思考问题。比如有人给你推销保险,会给你讲所有有关保险的优点,根据辩证思维,有好处就有坏处,它们是客观存在的。

通过辩证法,可以减少很多上当受骗的机会。

逻辑思维

逻辑思维不是罗振宇的《逻辑思维》,而是指哲学的逻辑思维。

我们无论是认识世界还是和人辩论,都需要遵守一些思维规律,掌握了这些思维规律,就可以让自己的逻辑更为严密。

现代逻辑思维有三大规律:

  1. 同一律。同一段论述中,概念的内涵和外延不能发生变化,否则就是偷换概念。比如 A: 学生可以同时学习数学、语文。 B: 不对,人不可以同时学习两门课,比如你不可以同时看两本书。这里的“同时”概念发生了变化,无法继续讨论了。
  2. 矛盾律。在同一段论述中,不能出现两个相互矛盾的说法,比如“我有一个任何矛也无法刺穿的盾,还有一个可以刺穿任何盾的矛。”
  3. 排中律。排中律指同一个思维过程中,两个思想不能同假,必有一真。比如老妇人拜菩萨,求天不要下雨,不然买扇子的女儿没有生意,一会儿又求天要下雨,不然另外一个卖伞的女儿也没有生意。

掌握逻辑学三大规律,可以避免一些诡辩,可以跟正确的理解道理。

目标思维

人有时候无法专注,并不是想偷懒,而是缺少目标思维。

用目标思维随时提醒自己当前工作的意义,避免写着文章搜索材料的时候,进入八卦专区灌水去了。

目标思维的建立是通过各种问题,比如 5w1H 方法。

  • why(为什么)
  • What(是什么)
  • Where(在哪儿)
  • Who(谁)
  • When(什么时候)
  • 1H是指:How(如何)

通过 5w1H 方法,可以让自己做的事情更有意义,具体,而有焦点。

同理心思维

同理心思维是人与人相处的思维,通俗来说就是同情的能力。

比如说出某句话会对对方造成反应?

某个行为对方会产生如何的感受?

产生同理心思维的方法是“精神分裂”,想象自己是另外一个人 B,旁观自己的处境 A。作为 B 去考察 A 在某个场景下的行为,以及对方的额感受,就可以获得同理心。同理心分为个体、群体同理心。

同理心思维不是为了一味的照顾、迁就别人的感受,而是应该作为一个旁观者 B,如果利用同理心影响、感知对方,并在人群中做出适当的社交行为 。

more >>

35岁过后做什么呢(二)?

去做了种植牙齿的手术,回来后除了肉疼,还有心疼。华西的教授技术完全没话说,唯一一点不好就是贵。

为了打发牙疼的时间把 《35岁过后做什么呢?》中没有说完的东西再写一点。

医院的见闻

给我治疗的人是一个教授,因为之前在小的牙科医院吃过亏,即使价格比较贵也选择了华西口腔。

华西口腔可以说是世界上前列的口腔医院,国内大部分的口腔医院创始的过程都和华西口腔有关,华西口腔真的是博士遍地走,研究生不如狗。

在我完成治疗后,楼下在自助收费机器旁边帮助患者操作自助机的志愿者都是川大的研究生。

在拍摄牙片 CBCT 时,看到了墙上关于放射科的介绍,研究生学历,3-4个头衔一样也是每天重复的拍摄 CT,被患者骂。

在收费窗口,收费员是一个 40 岁左右的中年人,不耐烦的叫我把处方递给他,从他的神情来看,对 35 岁后的生活并不满意。

牙医的工作模式是围绕着主任医师为中心,以助理医师、护士、规培生为主要组成的团队工作的。受人尊重的当然是主任医师了,华西口腔种植科的主任医师只有十多个。

曾经遇到过一个华西口腔的博士,给我吐槽华西口腔的工作压力相当大,可以类比于做技术的在华为,做咨询的在麦肯锡。如果在华西口腔不能持续上升,就会被淘汰,机会留给后面的博士生。

按照现在的说法,内卷的厉害。

焦虑来自不恰当的类比

我们的焦虑来自于比恰当的对比。

虽然 IT 行业不敢膨胀到和医疗行业做比较,但是看看医疗行业的情况。我们只看到了主任医师这种级别的人,每年拿到很多越来越吃香。在 IT 行业中能与之对比的是计算机学院的教授和老师,拿着国家的科研经费,做着自己的校企合作项目,一样越老越吃香。

顶级的和顶级的类比。

一般的和一般的类比。

对于普通的程序员而言,能类比的其实只是私立医院的医护团队中的一员。

对于主程序而言,能类比的其实只是私立医院的某个科室主任医师。

对于软件公司的财务人员来说,类比的就是医院的收费员,甚至拿的更多。

对于软件公司的测试人员来说,和护士、CT医生也差不多。

我们有时候犯得错误是,盯到的只是,容易看到的一面。主任医师很亮眼,助理、护士和收费员,谁会多注意呢。

more >>