码农戏码

新生代农民工的自我修养


  • 首页

  • 归档

  • 标签

  • 关于

  • 在线工具

  • 搜索

康威定律与逆康威定律

发表于 2021-10-17
字数统计: 1.9k 字数 | 阅读时长 ≈ 6 分钟

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

康威定律

先回顾一下什么是康威定律: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发起,逻辑层处理,数据库存储,组织结构与架构结构是匹配的。这就是康威定律的威力。

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

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

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

建模没必要

发表于 2021-09-23
字数统计: 2.9k 字数 | 阅读时长 ≈ 11 分钟

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类会越来越臃肿,发展成超大类,充满坏味道,可我们这次也没有违反什么原则?难道达到了原生面向对象的能力极限?

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

莫道君行早

发表于 2021-09-14
字数统计: 990 字数 | 阅读时长 ≈ 3 分钟

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

中台是什么

发表于 2021-09-04
字数统计: 1.8k 字数 | 阅读时长 ≈ 6 分钟

中台,在过去两年是个流量顶级词汇,谈什么都得带上中台,干什么都得扯点中台,不说中台,那绝对不是个合格的技术人

但经过了谈中台、建中台、拆中台,潮起潮落,不管是看好,还是贬低;可以看出在技术大词的浪潮里,不管是应激者怕掉队,还是投机者想上位,真正懂它的人不多,或者大多都还停留在以以往经验来判定的新事物

在历史遗忘之际,我来重温一下它

起源

在中台的发展进程中,首先得回到它的起源,至少有两个版本:

一是正史,马云一行,参观了芬兰一家游戏公司supercell,大受震撼,回来就提出了“中台战略”

一是野史,张勇的挟中台以令诸侯

从这两史中至少可以看出一些东西:

  1. 中台是由企业掌舵者提出以及使用的,它不是一个技术人员提出的,甚至说是CTO级别提出的,提出的起源与技术占不上边
  2. 中台的战略地位,不管是愿景实现还是战略落地,都发挥着巨大作用

所以很多技术人不解的地方,为什么谈到中台,需要谈企业战略,需要企业级组织变更,而不仅仅在于研发内部,不是使用的技术多牛,而是因为中台本身就有战略属性

因此在讨论中台时,需要从业务环境,组织结构,人力构成和技术架构各方面统筹考虑,抛开这些单从技术角度,是没有全局视角衡量中台的优劣,也是没有意义的,所以中台得结合企业本身综合情况,而不是技术迁移就能完成的

定义

现在提到中台,一连串的词汇会涌现出现,比如共享、复用、积木化

那么什么才叫中台呢?至少指出者没有给出定义,但它有特性,就是在高速发展环境之下,企业需要具备相应的响应速度去支撑企业运营的需求,而依靠小而灵活的前台团队,频繁而低成本的试错是一种应对此商业环境具有竞争力的模式

“小而灵活”是关键:小意味着人员少,成本低;灵活意味着对外快速响应市场,对内流程敏捷,快速失败

而能支撑这种小而灵活前台团队的系统就称为中台,这是从中台的作用来描述它,经过这几年的发展,有各式各样的定义

我比较认同王健老师的定义:企业级能力复用平台

企业级:

定义了中台的范围。它不是单业务级,是从企业全局出发,考虑多条业务线;一个企业也不是只有一个中台,可以有多个中台。也就是企业与中台的关系是多对多的

企业级这表明了中台不单是技术问题,而是上升到企业架构的问题

能力:

定义了中台主要承载的对象。能力的抽象解释了为什么有那么多种类的中台,也解释了为什么每家中台都是不一样的,因为每家企业的核心能力是不一样的

复用:

定义了中台的核心价值,建设中台的过程就是推倒烟囱系统的过程,也是去重复用的过程;“去重”讲的更多是向后看,是技术驱动;“复用”讲的更多是向前看,是业务驱动和用户驱动的

中台需要从“去重”到“复用”的视角转变

“复用”是中台更加关注的目标

“可复用性”和“易复用性”是衡量中台建设好坏的重要指标

“业务响应力”和“业务满意度”是考核中台建设进度的重要标准

平台:

定义了中台的主要形式。区别于传统的应用系统拼凑的方式,通过对于更细粒度能力的识别与平台化沉淀,实现企业能力的柔性复用,更好地支撑前台业务

种类

自从中台概念流行,各个词都与中台组词了,研发中台、技术中台、组织中台、业务中台…只要把以前谈的词语带上中台,就是高大上的

经过过去几年的喧嚣,沉寂。人们对中台品种达成了一定的共识:业务数据双中台

网易副总裁汪源曾在网易云创峰会上提到:“所有中台都是业务中台”。从中台起源出发,的确,中台就是为业务,为企业更好地以更低成本、更高质量、更快响应速度售出产品、换取利润服务的

而数据中台,更多的是大数据时代到来,大势所趋,业务中台是产生数据,数据中台是做数据二次加工,并将结果再服务于业务,为业务进行数据和智能的赋能

创新

这两年,拆中台的声音呼啸而起。尤其以当年带起中台的阿里等一系列巨头,都在拆。人们又开始跟风中台不行了

戏称,我们作业才抄到一半,你说写错了

为什么要拆呢?想那盒马不就是中台成功的典范,但在犀牛制造却提出不拿中台一针一线了,自己从零开始

这其实就是任何软件平台的特性

平台的能力越丰富,上层应用可以利用的越多,去完成某类功能的成本就越低,因而平台能力通常被看作效能下限

应用利用平台能力获得效能,是通过放弃一部分自主性获取,而低自主性就影响创新的可能,所以应用自主度被看作创新上限

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

对于像巨头在中台已经沉淀多年,有了相当应对当前市场的能力,但想要争取更多的市场份额,创新需求日益剧增,尤其需要颠覆式创新

因此,别人拆的时候,你能拆吗?建中台需要综合考虑,拆中台同样需要考虑

总结

中台曾经的顶级流量热词,不管当初的是应激怕掉队,还是投机想上位,浪潮退去之时,我们才能静下心来思考它是什么,它能干什么

虽然现在已经冷却,但威力不减,提升企业竞争力一把好手,它的出发点不是技术基建,而是寻找更好的组织结构和技术架构,以支持业务的快速增长和发展

耦合必然性

发表于 2021-08-15
字数统计: 1.7k 字数 | 阅读时长 ≈ 6 分钟

最近学到一个词“耦合创伤应激障碍”,讲的是程序员对耦合条件反射式恐惧,对于这个新词,我再重新理解一篇

对于一名程序员,从入行开始,就听到前辈们对“高内聚低耦合”的谆谆教诲,所以对于低耦合的意识深入骨髓。知行合一,看看是怎么践行的,打开任何一个项目工程,可以看到,每一个service都有一个interface和impl,代码看起来整整齐齐,所有变化点都考虑到了,但其实没有降低问题复杂度,只是自己看着舒服

在《SOLID总结》中提到过面向接口编程中接口到底是什么含义,并不是所有实现类都得需要一个接口,才是面向接口编程

而现在实践中对实现依赖心理恐惧,成了一种行业通病,见不得对实现的依赖,这是典型的耦合创伤应激障碍,像“猴子实验”

五只猴子被关进笼子里,笼子一角挂着一串香蕉,如果有猴子试图摘取香蕉,就会被开水泼到。猴子们吃了几次苦头之后,就再也不想摘香蕉了。
此时用一只新猴子替换老猴子,新猴子看到有香蕉刚想去摘,就被老猴子们拉住一顿暴打。新猴子挨了几次打之后,也不再去摘香蕉了。
此时再换进一只新猴子,它也看到香蕉想去摘,也被老猴子们一顿暴打,下手最狠的恰恰是那一只没被开水烫到过的。
最后老猴子们都被换干净了,仍然没有猴子去碰那串香蕉,因为它们知道——碰香蕉意味着被打,而为什么会被打,没有猴子知道。

当参与一个新项目,不再创建interface时,肯定会变成那只被打的“猴子”

然而现实并不是这样的,真的加个interface就减少耦合了吗?耦合少得了吗?比如,需要使用支付宝或微信支付,那么这就是业务需求,与支付宝和微信就必然会耦合,才能达到业务要求。不管怎么组织代码结构,是明显直接调用,还是隐晦地抛出支付事件,终将需要调用支付宝微信的支付接口;再比如现在很多应用需要推送消息,短信、邮件亦或微信,那么与支付类似,不管如何,必将调用第三方接口才能实现这些功能,这也就是耦合的必然性

代码大致是这样的:

先来一个接口:

1
2
3
4
5
6
package com.zhuxingsheng.infrastructure.port

public interface AlipayService {

public PayResult pay(AliInfo aliInfo,decimal payAmount);
}

再来对接口的具体实现,调用支付宝SDK

1
2
3
4
5
6
7
8
package com.zhuxingsheng.infrastructure.apapter
public class AlipayServiceImpl implements AlipayService {

public PayResult pay(AliInfo aliInfo,decimal payAmount) {
AlipaySdk.pay();
return result;
}
}

微信支付代码结构类似,对于这些代码味道是不是相当熟悉,有service必有interface和impl,但看看接口的意义在哪儿?

机智的你,肯定发现这样不对,我们需要的应该是在线支付能力,而支付宝支付或微信支付只是具体的实现而已,也就是与支付宝和微信耦合其实不是必然的,必然的是在线支付能力

这儿其实有两种演变过程:

第一种:先实现了支付宝支付功能,当再实现微信支付时,此时发现要抽象出在线支付接口,策略模式

第二种:从业务需求用户故事:作为用户需要完成订单线上支付,完成订单的全流程

第一种从技术入手,而第二种从最原始的业务入手,这两种演变虽然第二种方是大道,可技术人却喜欢第一种,这也就回到篇首所说,代码看起来整整齐齐,却没有简化问题,甚至只因为技术风格的强统一,带来了业务语义的更加隐晦

技术其实是解决业务的工具,需要从业务本源着手,挖掘业务背后隐藏的业务能力,构建出对应的技术模型,达到模型即代码的统一,也就知道了必然性耦合,怎么降低耦合

所以再看上面的接口,package com.zhuxingsheng.infrastructure.port,是在基础设施层,只是技术上单纯的一个接口,在《DDD》专栏中,提到过的接口在领域层,实现在基础设施层,因为maven模块循环依赖或者DIP的需要,所以必需要把接口放在领域层,从业务角度分析,线上支付能力是构建模型的必要能力,是领域模型必不可少的部分,需要在线支付能力,也是业务必然性耦合的体现,应该在领域层

可从代码形式上看,你是不是觉得不管是对业务的深刻理解,还是单纯技术抽象,不都是一个接口吗?无非是叫接口还是叫业务能力而已吗?

再看一个接口

1
2
3
4
5
6
package com.zhuxingsheng.infrastructure.port

public interface UserService {

public User getUser(long userId);
}

从命名就知道,获取用户信息,不管是业务系统自身处理用户信息,还是从用户中心式外部服务获取用户信息,这是整个系统的基本能力,而不会再需要去抽象一个深奥的业务能力,当业务需求故事阐述用户下订单业务行为时,业务方已然抽象了整个用户体系,只有研究用户上下文子域问题时,才去深入用户领域模型,但从当前业务上下文看,业务系统与这些基础服务是深度绑定,并在系统架构初始已经确定了整体架构

因此在业务系统中去定义一个userservice接口,是没啥意义的,除非系统大动,否则是不会变动的

延伸一下,此时领域层如何依赖基础设施层呢?怎么DIP呢?有没有丝毫感受到分层有问题呢。对此下会分解

总结一下,我们需要正确看待耦合必然性,不是从技术实现的角度去硬生抽象,而需要从业务角度,挖掘出业务真正耦合的能力,坦然接受这样的耦合,清晰化表达业务语义

应对复杂性

发表于 2021-07-25
字数统计: 565 字数 | 阅读时长 ≈ 2 分钟

资本家主要目标是赚钱、赚很多很多的钱;他们给提出的要求是降本增效

那么作为架构师,目标是什么呢?

在《整洁架构》书中作者写到架构的主要目的是支持系统的生命周期。良好的架构使系统易于理解,易于开发,易于维护和易于部署。
最终目标是最小化系统的寿命成本并最大化程序员的生产力

大多数程序员心里觉得应该是展示最牛B的技术才对,可现实却只是资本家的工具而已,是不是有些惊讶

软件的核心是它为用户解决领域相关问题的能力,保持业务价值的持续交付

可在软件行业,交付能力的持续性是相当有挑战性的,也许前期交付很快,但慢慢交付就很慢,质量也会下降,甚至哪怕一次小小的改动都要经历很久,更可怕的是无法交付,为什么呢?

在之前的相关文章中也提过,有两张图:

《架构师》中提到软件需求并不只是功能需求:

软件复杂度并不仅仅是业务复杂度:

在一起起看似快速交付背后,不合理的设计或者实现积累了过多的技术债,造成无法交付

所以架构师最重要的事就是解决软件中的复杂性

在软件项目中,任何方法论如果最终不能落在“减少代码复杂度”,都是有待商榷的

软件架构设计的实质,是让系统能够更快地响应外界业务变化,并且使得系统能够持续演进

架构设计的主要目的是为了解决软件复杂度带来的问题

《DDD应对复杂》中也提到复杂的来源,对于软件复杂性以及应对方案,特定总结画了一幅图

API如何设计

发表于 2021-07-18
字数统计: 947 字数 | 阅读时长 ≈ 3 分钟

在之前《应对变化》中提到模块之间合的策略:缩小依赖范围,API是两个模块间唯一的联结点

怎么才是一个好的API设计呢?最近项目中正好碰到一件关于一个API引起的相爱相恨的事件

数据来源于外部系统,外部系统通过回调把数据传输过来,内部系统通过系统A进行接受,接受完之后,转发给系统B

接受回调api大概是:

1
systemA.callback(long userId,Object data);

整体两个参数,一个userId表示这个数据是谁的、一个data表示数据本身

对于系统B来讲,他的业务要求,这数据必须要知道用户名字,必须在数据上打标签,所以跟系统A商量,要不你把username也随便转给我吧

系统A一想,那也是两秒的事,因为本身在接受数据时也得对userId校验,取个username也不麻烦,不废话了,你要就给你

因此系统B接受数据api设计成:

1
systemB.receive(long userId,String username,Object data);

一切都是行云流水,大家都很happy,如期发布上线

爱情总是在转角处遇到,上线完,QA同学一顿操作,却发现系统B没有如期显示出数据,系统B觉得是系统A没传来数据;咋办呢?心虚的系统A只能查查日志

1
log:systemB return error,username is empty

原来原来是因为这个用户的username是空,系统B拒绝接受了,怎么username会为空呢?username怎么能为空呢?

找到用户系统,用户系统解释了,一个用户在注册时并不一定有username,有username,email,usercode三个值中的任何一个值就可以了

这时该怎么办呢?相爱相杀时刻到了

系统B:要不你把这三个值都传给我?

系统A:我还得再改下代码,测试后发版本,要不你自己从用户系统取吧

系统B:传一个可以,怎么三个就不可以了,不都一样吗?

系统A:太麻烦了,你自己取了,想怎么控制就怎么控制

系统B:你是不爱我了

系统A:你怎么就不理解我呢


温习一下一个好的API设计要求:

缩小依赖范围,就是要精简API;API要稳定得站在需求角度,而不是how角度

  1. API包含尽可能小的知识。因为任何一项知识的变化都会导致双方变化
  2. API也要高内聚,不应强迫API的客户依赖不需要的东西
  3. 站在what角度;而不是how,怎么技术实现的角度

上面示例的问题就在系统B接受数据api:

1
systemB.receive(long userId,String username,Object data);

关照上面的要求:

问题一:API中包含的知识有重复:userid,username

问题二:客户端也就是systemA并不需要username,但被强迫要知晓并要按规则赋值

问题三:站在设术实现角度,api中增加参数username,而不是需求角度

总结

示例虽小,日常工作中常常碰到这类问题,如果这个例子上线成功,每个人都觉得这是一次成功的交付,但回头复盘,发现了很多理论缺乏,惯性思维使然造成的不合理,难维护,难扩展的设计

由此看出,日常的CRUD并不是没有技术含量,而是我们有没有深刻认知

应对变化

发表于 2021-07-11
字数统计: 3.3k 字数 | 阅读时长 ≈ 11 分钟

之前对SOLID做了一个总结 《SOLID》总结

这些原则是前辈们经过无数实践提炼出来的,百炼成刚,那是不是成了放之四海皆准的道理呢?某种程度上讲,还真就是准的,常被人耳提面命写的代码要遵守这些原则,想想code review时,是不是代码常常对比这些原则,被人指出没有遵循哪个原则

总结篇中画了这幅图,SOLID也的确是我们达到高内聚低耦合很重要的手段

1
2
3
4
5
6
7
8
9
10
//读取配置文件和计算
class Computer{
public int add() throws NumberFormatException, IOException {
File file = new File("D:/data.txt");
BufferedReader br = new BufferedReader(new FileReader(file));
int a = Integer.valueOf(br.readLine());
int b = Integer.valueOf(br.readLine());
return a+b;
}
}

这是一段被用来演示SRP的反例,从示例代码中看出,这个方法职责的确不单一,引起它变化的至少有两个地方:一是数据不一定从配置文件读取、二是计算方式可能会变

在code review时,不管是自己还是别人,的确让人觉得不够完美

因此,我们会花一番功夫,来让方法达到SOLID的要求,可如果此方法从系统上线运行几个月,甚至几年都无需变动,那我们花费的这些时间也只是自我感动,毕竟我们最终目标是给客户交付带来价值的系统,以最快的速度给公司带来效益

这其实是成本的问题,没错,程序员要有技术追求,但也得考虑成本

可总不能为了成本,忽略一切吧,那怎么处理呢,我们要达到“高内聚、低耦合”,SOLID是重要路径,但又不能不计成本地进行SOLID,更不能为了SOLID而SOLID

所以不能走两个极端,既不能坐视不管,也不能一味求全,在这两层之间应该还有一片灰色地带


这灰色地带是什么样的?怎么做才能不去穷举变化疲惫应对,而当真正变化来临时又能轻松应对?大佬提供了思路,不能以这些原则去应对软件变化,应该以无法为有法,以无限为有限。以实际需求变化来帮助我们识别变化,管理变化。这思路就是袁英杰提出的正交设计,有四大策略

四大策略

策略一:消除重复

重复代码,不管接手老项目还是住持新项目,都特别重视重复代码的比率,为什么呢?

  1. 重复代码增加维护成本,变动同一个逻辑会遗漏修改
  2. 重复代码说明团队沟通不畅,团员间没有交流或者没有必要的code review

这只是实践带来的观察,那么从理论角度说说消除重复的重要性

“重复”极度违背高内聚、低耦合原则,从而会大幅提升软件的长期维护成本;而我们所求的高内聚是指关联紧密的事物放在一起,两段完全相同的代码关联最为紧密,重复就意味着低内聚

更糟糕的是,本质重复的代码,都在表达同一项知识。如果他们表达(即依赖)的知识发生了变化,这些重复代码都需要修改,因而重复代码也意味着高耦合

重复意味着耦合

当完全重复的代码进行消除,会让系统更加高内聚、低耦合

小到代码块,大到模块也一样,如果两个模块之间部分重复,那么这两个模块都至少存在两个变化原因或两重职责;站在系统的角度看,它们之间有重复部分,也有差异部分,那这两个模块都存在两个变化原因

对于这一类型重复,比较典型的情况有两种:实现重复和客户重复

实现型重复

客户型重复

这个策略非常明确,极具操作性,消除重复后,明显提高系统内聚性,降低耦合性,在消除重复过程中,也提高了系统的可重用性,而且对于客户重复,还提高了扩展性

策略二:分离不同的变化方向

对于策略一使用时机,可以随时进行,重复也容易判定。除重复代码外,另一个驱动系统朝向高内聚方向演进的信号是:我们经常需要因为同一类原因,修改某个模块。而这个模块的其它部分却保持不变

分离不同变化方向,目标在于提高内聚度。因为多个变化方向,意味着一个模块存在多重职责。将不同的变化方向进行分离,也意味着各个变化职责的单一化

分离变化方向

对于变化方向分离,也得到了另外一个我们追求的目标:可扩展性

策略二相对策略一,最重要的就是时机,不然就会回到我们文章开头时的窘境:早了,过度设计;晚了,则被再次愚弄

当你发现需求导致一个变化方向出现时,将其从原有的设计中分离出去。此时时机刚刚好,不早不晚;Uncle Bob也曾给出答案:被第一颗子弹击中时,也就是当第一个变化方向出现时

这个世界里,本质上只存在三个数字:0,1,和N。

0意味着当一个需求还没有出现时,我们就不应该在系统中编写一行针对它的代码。

1意味着某种需求已经出现,我们只需要使用最简单的手段来实现它,无需考虑任何变化。

N则意味着,需求开始在某个方向开始变化,其次数可能是2,3,…N。但不管最终的次数是多少,你总应该在由1变为2时就需要解决此方向的变化。随后,无论最终N为何值,你都可以稳坐钓鱼台,通过一个个扩展来满足需求

如果我们足够细心,会发现策略消除重复和分离不同变化方向是两个高度相似和关联的策略:

它们都是关注于如何对原有模块进行拆分,以提高系统的内聚性。(虽然同时也往往伴随着耦合度的降低,但这些耦合度的降低都发生在别处,并未触及该如何定义API以降低客户与API之间耦合度)。

另外,如果两个模块有部分代码是重复的,往往意味着不同变化方向。

尽管如此,我们依然需要两个不同的策略。这是因为:变化方向,并不总是以重复代码的形式出现的(其典型症状是散弹式修改,或者if-else、switch-case、模式匹配);尽管其背后往往存在一个以重复代码形式表现的等价形式(这也是为何copy-paste-modify如此流行的原因)。

策略三:缩小依赖范围

前面两个策略解决了软件单元如何划分问题,现在需要关注合的问题:模块之间的粘合点API的定义

  • 首先,客户和实现模块的数量,会对耦合度产生重大的影响。它们数量越多,意味着 API 变更的成本越高,越需要花更大的精力来仔细斟酌。
  • 其次,对于影响面大的API(也意味着耦合度高),需要使用更加弹性的API定义框架,以有利于向前兼容性。

因此缩小依赖范围,就是要精简API

  1. API包含尽可能小的知识。因为任何一项知识的变化都会导致双方变化
  2. API也要高内聚,不应强迫API的客户依赖不需要的东西

策略四:向稳定的方向依赖

虽然缩小依赖范围,但终究还是要有依赖范围,还是必然存在耦合点。降低耦合度已到尽头。

耦合最大的问题在于:耦合点的变化,会导致依赖方跟着变化。这儿意味着如果耦合点不变,那依赖方也不会变化。换句话说,耦合点越稳定,依赖方受耦合变化影响的概率就越低

因此得出第四个策略:向稳定的方向依赖

耦合点也就是API,什么样的API更侧向于稳定?站在What,而不是 How 的角度;即站在需求的角度,而不是实现方式的角度定义API;也就是站在客户的角度,思考用户的本质需要,由此来定义API,而不是站在技术实现的方便程度角度来思考API定义

SOLID

一个好的面向对象设计,自然是符合高内聚,低耦合原则的对象划分和协作方式。

单一职责和开放封闭,更多的在强调类划分时的高内聚;而里氏替换,依赖倒置,接口隔离则更多的强调类与类之间协作接口(即API)定义的低耦合

单一职责,通过对变化原因的识别,将一个承担多重职责的类,不断分割为更小的,只具备单一变化原因的类。而单一变化原因指的是:一个变化,会引起整个类都发生变化。只有关联极其紧密的情况,才会导致这样的局面。因而,单一职责和高内聚某种程度是同义词。

但单一职责原则本身,并没有明确指示我们该如何判定一个类属于单一职责的,以及如何达到单一职责的状态。而策略消除重复,分离不同变化方向,正是让类达到单一职责的策略与途径

开放封闭原则,正是通过将不同变化方向进行分离,从而达到对于已经出现的变化方向,对于修改是封闭的,对于扩展是开放的

里氏替换原则强调的是,一个子类不应该破坏其父类与客户之间的契约。唯有如此,才能保证:客户与其父类所暴露的接口(即API)所产生的依赖关系是稳定的。子类只应该成为隐藏在API背后的某种具体实现方式。

依赖倒置原则则强调:为了让依赖关系是稳定的,不应该由实现侧根据自己的技术实现方式定义接口,然后强迫上层(即客户)依赖这种不稳定的API定义,而是应该站在上层(即客户)的角度去定义API(正所谓依赖倒置)

但是,虽然接口由上层定义,但最终接口的实现却依然由下层完成,因此依赖倒置描述为:上层不依赖下层,下层也不依赖上层,双方共同依赖于抽象。

最后,接口隔离原则强调的是:不应该强迫客户依赖它不需要的东西。显然,这是缩小依赖范围策略在面向对象范式下的产物

总结

尽管理论上讲,任意复杂的系统都可以被放入同一个函数里。但随着软件越来复杂,即便是智商最为发达的程序员也发现,单一过程的复杂度已经超出他的掌控极限。这逼迫人们必须对大问题进行分解,分而治之,这也是必须模块化的原因

模块化主要是两方面:

  1. 软件模块该如何划分?(怎么分)
  2. 模块间API该如何定义?(怎么合)

本文四个策略,前两个指导怎么高内聚,也就是怎么分;后两个指导耦合方式,怎么合

重要的是使用各个策略的使用时机,变化驱动识别变化、重构变化

变化导致的修改有两类:

  • 一个变化导致多处修改(重复);
  • 多个变化导致一处修改(多个变化方向);

由此得到前两个策略:消除重复;分离不同变化方向。

除此之外,我们要努力消除变化发生时不必要的修改,也有两种方式:

  • 不依赖不必要的依赖;
  • 不依赖不稳定的依赖;

这就是后面两个策略:缩小依赖范围,向着稳定的方向依赖。

Reference

变化驱动:正交设计

SOLID总结

发表于 2021-06-08
字数统计: 1.8k 字数 | 阅读时长 ≈ 6 分钟

之前已经把SOLID的每人原则都阐述过一遍,此篇主要是从全局角度复述一下SOLID,对于细节概念再做少许补充

SOLID原则的历史已经很悠久,早在20世纪80年代末期,都已经开始逐渐成型了

通常来讲,想构建一个好的软件系统,应该从写整洁的代码开始做起。毕竟如果建筑的砖头质量不佳,那么架构所能起到的作用也会很有限。反之亦然,如果建筑的架构设计不佳,那么其所用砖头质量再好也没用

SOLID原则的主要作用就是告诉我们如何将数据和函数组织成为类,以及如何将这些类链接起来成为程序,类似于指导我们如何将砖块彻成墙与房间

对照几张前辈们画的图,看图说话

这张图把SOLID的整体关系描述清楚了,不再是把各个原则单独看待

单一职责是所有设计原则的基础,开闭原则是设计的终极目标。

里氏替换原则强调的是子类替换父类后程序运行时的正确性,它用来帮助实现开闭原则。

而接口隔离原则用来帮助实现里氏替换原则,同时它也体现了单一职责。

依赖倒置原则是过程式编程与OO编程的分水岭,同时它也被用来指导接口隔离原则


这些原则每个单独看都是简单的,但他们却是优秀代码的指导思想,不得不常读,常思;犹如设计模式,很多时候你感觉懂了,不过只是懂了介绍模式的示例,并没有真正理解模式

反观这些原则,道理类似

如SRP:是公认最容易理解的原则,却是被违反得最多的设计原则之一;再比如ISP,看着简单,更小和更具体的瘦接口比庞大臃肿的胖接口好,很多时候都没有明白接口的定义

在实现编写代码时,只要是service都会加上一个 service interface,但想想,从项目开启到后期维护,几乎没有一个 service interface 有一个以上的实现,那为什么要加个接口呢?美其名曰面向接口编程,其实是人云亦云,让自己也让别人看着是那么一回事而已

面向接口编程所指的“接口”并非Java语言中的interface类型,而是指面向调用者对外暴露的接口,代表一种交互与协作,是对信息的隐藏和封装,而不是具体的interface类型。即使是普通的java方法仍然满足隐藏细节的原则,如果是public的,就可以认为该方法是“面向接口设计”中的接口,也就是说:不要针对实现细节编程,而是针对接口编程

接口之所以存在,是为了解耦。开发者常常有一个错误的认知,以为是实现类需要接口。其实是消费者需要接口,实现类只是提供服务,因此应该由消费者(客户端)来定义接口。理解了这一点,才能正确地站在消费者的角度定义Role interface,而不是从实现类中提取Header Interface。

对于Role interface 与 header interface , Martin Fowler给出了定义:

A role interface is defined by looking at a specific interaction between suppliers and consumers. A supplier component will usually implement several role interfaces, one for each of these patterns of interaction. This contrasts to a HeaderInterface, where the supplier will only have a single interface

如果你先定义了一个类,然后因为你需要定义接口对其抽象,然后就简单地将这个类的所有公有方法都提取到抽象的接口中,这样设计的接口,被Martin Fowler称为Header Interface,这种接口也正是胖接口的来源,而 Role interface 才是能达到瘦接口目标

想起一位投资前辈说的话,成功就是对简单道理的深刻理解和灵活运用;我们很多时候有种无力感,为什么这么简单的道理都做不好,落地不了呢?其实是没有深刻理解而自以为懂了


Kent Beck对软件设计的定义:软件设计是为了在让软件在长期范围内容易应对变化

为了软件更容易应对变化,就需要符合软件的道:高内聚低耦合

单一职责和开放封闭,更多的在强调类划分时的高内聚;而里氏替换,依赖倒置,接口隔离则更多的强调类与类之间协作接口(即API)定义的低耦合,单独应用SOLID的某一个原则并不能让收益最大化。应该把它作为一个整体来理解和应用,从而更好地指导软件设计。

这个同心圆的原图本来是:

要实现道就得遵循正交设计四原则:

  1. 消除重复
  2. 分离关注点
  3. 缩小依赖范围
  4. 向稳定的方向依赖

「正交设计」的理论、原则、及其方法论出自前ThoughtWorks软件大师「袁英杰」先生。这一块对我来讲很新颖,消化之后再总结


这幅图揭示了模块化设计的全部:首先将一个低内聚的模块首先拆分为多个高内聚的模块;然后再考虑这多个模块之间的API设计,以降低这些高内聚的软件单元之间的耦合度。

除了内聚与耦合之外,上面这幅图还揭示了另外一种关系:正交。具备正交关系的两个模块,可以做到一方的变化不会影响另外一方的变化。换句话说,双方各自独自变化,互不影响。

而这幅图的右侧,正是我们模块化的目标。它描述了永恒的三方关系:客户,API,实现,以及它们之间的关系。这个三方关系图清晰的指出了我们应该关注的内聚性,耦合性,以及正交性都发生在何处

总结:

软件的复杂性已经是世界性难题,但最原始的道是相当简单的,就是要高内聚低耦合,在追求道的过程中,前人总结出了很多原则,这些原则相互协作、相互碰撞,我们需要平衡,取舍,这考验架构师的功力,也要求架构师对这些基本概念有深刻理解

参考:

《正交设计,OO 与 SOLID》

你真的了解SOLID吗?

RoleInterface

我的长处是什么

发表于 2021-05-29
字数统计: 928 字数 | 阅读时长 ≈ 3 分钟

这两天一直在看被誉为“现代管理学之父”与“管理大师中的大师”的彼得·德鲁克(Peter F.Drucker)写的《自我管理》这篇文章

我的长处是什么?这是自我管理8个问题的其中一个,也是第一个

只有当所有工作都从自己的长处着眼,你才能真正做到卓尔不群

以前的人没有什么必要去了解自己的长处,因为一个人的出身就决定了他一生的地位和职业:农民的儿子也会当农民,工匠的女儿会嫁给另一个工匠等。但是,现在人们有了选择。我们需要知己所长,才能知己所属

要发现自己的长处,唯一途径就是回馈分析法(feedback analysis)。每当做出重要决定或采取重要行动时,你都可以事先记录下自己对结果的预期。9到12个月后,再将实际结果与自己的预期比较

比如开始写作,希望一年达到什么效果。跳槽了,一年内达到什么改变?

在九到十二个月之后,拿出之前的预期对比实际效果,看看预期是达成了,超标了,还是偏离了

运用这个简单的方法,就能知道自己的长处,也知道自己正在做的哪些事情不能发挥自己的长处,看到哪些方面能力不是特别强,哪些方面完全不擅长,做不出成绩来

根据回馈分析法的结果,需要采取如下行动:

“施展长处”:

首先专注于你的长处,把自己放到能发挥长处的地方,不要试图去完成自己不在行的领域,要从无能到平庸比从一流到卓越需要付出多得多的努力

“改善长处”:

其次加强你的长处。回馈分析会迅速显示在哪些方面需要改善自己的技能或学习新技能;同时纠正影响工作成效和工作表现的不良习惯

例如,一位企划人员可能发现自己美妙的计划最终落空,原因是他没有把计划贯彻到底。同那些才华横溢的人一样,他也相信好的创意能够移动大山。但是,真正移山的是推土机,创意只不过是为推土机指引方向,让它知道该到何处掘土。这位企划人员必须意识到不是计划做好就大功告成,接下来还得找人执行计划,并向他们解释计划,在付诸行动前须做出及时的调整和修改,最后要决定何时终止计划

“增强长处”:

发现自己的不知道,以免由于恃才傲物而造成的偏见和无知,不知道自己不知道过渡到知道自己不知道,放空自己,虚怀若谷,其实意思是虽然一技之长很重要,但现如今各领域相互融合,边界模糊,更需要综合能力,让自己的一技之长更加有的放矢

回馈分析法不是总结,也不是复盘,是当生活中的重大改变和做出的重大决定,长期坚持使用可以让你找到自己擅长做的事情,并在职场中找到适合自己的定位


1234…13
朱兴生

朱兴生

123 日志
3 分类
48 标签
© 2016 — 2022 朱兴生 | Site words total count: 296.3k
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4
沪ICP备18040647号-1