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 >>

35岁过后做什么呢?

中年危机

35 岁后程序员就要失业了。

程序员的中年危机说法从我入行的时候就有了,而且还是互联网行业方兴未艾的 7 - 8 年前。

即便如此我还是义无反顾的投入到了程序员大军,虽然当时也有进入体制的机会。选择程序员的原因其实很简单,我希望能从网络上赚到钱,然后找一个小城市生活。

虽然这个理想还没有实现,但是已经被被同学嘲笑过很多遍。高中的时候我就告诉同学,等我赚到 10W 能买套房子的时候,我就拉一根网线回农村,一边写代码一边种地。

回过头来一看,除了医生、教师和公务员之外,哪一个行业没有中年危机呢。

一份特别的 35 岁计划

我给自己的制定了一个 35 岁后被软件行业淘汰后的打算。听起来很蠢,但是又具有理想主义。

我想成为一名英语老师,而且是回到出生的小镇的英语老师。如果被软件公司淘汰后,在回到小镇上,通过网络应该能接到一些小活儿,混口饭吃应该没有问题。

如果我英语还行的话,就能远程的接到一些海外的任务,通过汇率的放大效应,不需要特别辛苦就可以维持一个简单的生活。

我是一个喜欢阅读的人,在我们的镇上甚至没有一家书店,只有卖教辅的文具店。

甚至没有一个阅览室、没有图书馆,只有几家破烂的网吧和台球室。

如果回到小镇上,寻找到一处便宜的场地或许很容易,然后在网上的二手书店采购数千本图书,其实也就是那些文艺青年去趟西藏旅游的费用。如果能得到政府的支持就更好了,没有也无所谓。

我想把这个图小型的图书馆或者叫做阅览室免费开放给全镇的孩子们,也包括我以后的孩子。当慢慢有人知晓这个地方时,我就来教授孩子们英语口语,然后把书籍和英语的世界带给闭塞的西部乡村小镇,并维持这个“图书馆”的运转。

我从小英语很差,因为没有一个真正会说英语的老师,也没有一个不大不小的图书馆,令人悲伤的是 20 年后那个地方依然没有。

焦虑是怎么来的

我曾经有一段时间非常焦虑,不过好在只有一小段时间。

那段时间我在从事咨询和培训工作,偶尔回归到一个开发项目之后,发现自己的手非常生疏,甚至对一些编程中的常用语法都忘记了。

那段时间非常焦虑,疯狂的学习,复习之前的知识,过了大概 2 个月,慢慢的感觉又找回来了。

编程,对于一个软件工程师来说,应该是每日必须练习的,一点都不能丢下。

当我的双手在键盘上能写出自己满意的代码后,焦虑感消失了,我知道不管怎么样,都能找到一家公司能混口饭吃。

焦虑的来源是多方面的。

一些是对未来的害怕。程序员的高溢价很难持续维持,总有一天是会跌回去的,当人习惯了往上走的时候,难以承担往下走。害怕自己没有足够的实力,面对未来的成长。因为软件工作的变化过于迅速,所以这种压力无处不在。其实相比之下,压力更大的是做销售、培训、市场和运营的,程序员的工作至少还能掌握手上,而其他行业的工作都是靠天吃饭。

一些对自己的不满足。对自己的不满足往往都是和别人对比出来的,比如同学、同事和朋友。觉得自己没有什么成就,工作多年也没有什么自己的作品,非常的颓废。对于这一点 ,我们需要知道的是,人的能力是有边界的。无论鸡汤喝的再多,寻求能力边界之外的事情,都会很痛苦,何不做好自己的能力边界的事情呢,让能力强的人去做更多的事情。

看着自己碗里的,盯着别人的锅里的,忘记把自己的饭吃好。程序员就是一个为别人做软件的手艺人,何必天天操心其他老板的生意呢,做软件也是一门生意而已嘛。

最后一些是消费的升级和自我膨胀。程序员是比其他行业多赚了那么一点钱,但是其实也不多。如果程序员被套上了“中产阶级”的圈套,然后进行所谓的消费升级,消费着红酒、咖啡和每年几次的旅游。由俭入奢易,由奢入俭难,消费升级了,就很难降下来。不膨胀,存好钱,过好原本的日子,“手里有粮,心中不慌”。

程序员的降级论

2012年的时候,阅读到一篇文章,是 meditic《降级论》。其中心思想是,如果程序员或者说其他高智力密集性的人,往技术含量更高的领域进军并不一定有优势,而且会混的灰头土脸。

如果转变思路,程序员的逻辑思维能力、分析能力和学习能力在其他领域发展,会得到意想不到的效果。

在 IT 行业里面处处是精英,这些人都是现在说的天龙人,进外企、考CPA、做咨询、云计算各种高大上的领域,用上了各种牛逼的思维。一步一步升级后才发现,身边到处都是精英,自己一点优势没有,还是拿着打工的钱。

反而是小学同学,毕业就在某个作坊跑腿,然后有了自己的作坊,慢慢的那个行业混的风生水起。在高级圈子里混久了,除了焦虑、秃头、挫折,几乎没有什么收获,还不如把高级圈子里面的高级思维带到其他领域去。

程序员如果去培训班教教数学那应该也是很厉害,如果开个水果店,利用自己的写程序能力,说不定也能运转的高效。在追求升级的过程中,会做的很累,因为你在每一次升级都是进入这个级别中的最低线。meditic 的比喻是,升级为大学生的中学生,实际上并不是大学生,而是刚刚完成升学的中学生,他的能力还只是中学生,是无法和大学生竞争的。

如果人生一直在升级,那么就没有时间和精力做点自己该做的事情。不要一直升级,而是应该找到自己的合适的级别,并做到最好。

查理芒格说,“你永远无法赚到超出自己认知以外的钱”。对我们而言,如果一直在升级,就一直无法去把这个级别的事情做到最好。

为什么不尝试降级呢?

more >>

作为 Tech Lead 应该操心什么?

认清 Tech Lead 的位置

对于软件工程师做技术经理是一件好的事情,可以从另外一个更高的角度观察自己现在正在做的事情。

成长为技术经理并不是一件很值得得意的事情,我接触到的技术经理压力都非常大,所以很多人还是愿意做一个纯粹的开发。软件开发行业很其他行业有一个不同的地方就是,会出现 Tech Lead 的角色没人愿意做的现象。

那么 Tech Lead 所处的位置是什么呢?

实现领导的开发目标。软件工程师的职责就是为老板构建可用的软件,至于老板拿这个软件在市场上赚钱和赔钱是市场和老板需要关心的事情。Tech Lead 能够把控产品经理的需求,并对老板给出估算,这事儿靠谱还是不靠谱,需要多久的时间。对老板来说,他不关心具体的技术实现和细节,Tech Lead 能帮他拿捏好,这需求能做还是不能做,能做的话最终能保证出活儿。对老板来说,靠谱是最重要的。

安排好项目的进展情况。如果没有项目经理专门带队做软件研发,Tech Lead 也需要关心项目管理的事情。项目管理并不是发布任务,设定目标,保证目标的达成这么简单。要需要考虑技术选型、技术决策和移除项目的困难点和阻塞点。

关心好团队的成员。 开发人员往往除了工作挣钱之外还有一些自我成长的诉求,所以还需要关心人员的成长,以及帮他们搭舞台唱戏。Tech Lead 不仅仅是给他们分配任务的人,还一定能引导他们的兴趣,尽可能帮他们找到资源,分配给他们感兴趣的任务。毕竟成就他人就是成就自己。

所以在 Thoughtworks 把 Tech Lead 这个角色赋予了四种能力:

  • Expert:是这个项目和产品的核心专家,必须保证有足够的业务知识和技术知识,能把握项目上的一些关键问题
  • Manager:能拆分任务,讲活儿分给其他同事做,并且管理项目的进展
  • Coach: 具有发展他人、团队的能力,能给其他团队成员提供指导和帮助
  • Leader:知道如何用正确的方式达成目标,激励人,知道团队的业务和技术目标

从这四个方面来看,操心的事情不少,后面部分整理了一下作为 Tech Lead 经常要操行的事情。

应该操心什么事情?

技术选型和决策

  • 能根据业务需求做出合理的技术选型
  • 技术选型能满足未来一段时间的业务和架构演进目标
  • 技术选型需要考虑成本
  • 技术选型需要考虑团队人员素质和背景

软件架构和搭架子

  • 服务设计和领域划分
  • 数据库设计
  • 分层架构和包结构的设计
  • 错误码规范和定义
  • API 规划
  • 一些常见操作的例子供开发人员参考
  • 日志和监控设计

需求评审

  • 需求是否合理,业务逻辑上是否自洽
  • 交互设计的性价比
  • 设计的一致性,和项目现有的逻辑是否一致
  • 是否会影响安全
  • 是否会造成性能的大幅度降低

基础设施和运维把控

  • devops 基础设施,例如流水线、制品库、跳板机等
  • 建立运维手册,例如定期拨测和数据备份
  • 定期防灾演练
  • 定期检查系统健康状态,密匙、证书、域名有效期
  • 制定上线流程和规范

非功能性需求

  • 关注需求的性能指标
  • 关注兼容性需求
  • 关注容量和弹性伸缩的需求

代码质量

  • 配置静态代码扫描,例如 checkstyle、find bugs
  • 设计团队代码规范
  • 进行代码评审

安全

  • 安全规范,例如密匙的要求
  • 安全建模
  • 定期的安全扫描工具异常处理
  • 预防技术方案中的潜在安全问题,比如数据和隐私泄露

团队

  • 制定团队协作策略,例如分支策略
  • 制定版本策略
  • 制定团队契约,例如 codereview 时间和主持人、技术例会
  • 对团队新人进行 onboarding 和维护团队 onboarding 文档
  • 人员梯队建设、管理人员流动的任务交接
  • 团队矛盾和冲突处理

知识共享

  • 制定团队技术分享机制
  • 制定文档存放契约
  • 更新系统架构图、部署图和数据库模型等项目架构材料

项目管理

  • 工时估算和迭代计划
  • 技术债的评估和分析
  • 项目风险评估
  • 跟踪第三方依赖的时间点
  • 收集项目反馈

more >>

模型学习法

通过模型学习

我不是一个善于学习的人,我的大脑发展并不均衡:喜欢把一些问题载入大脑慢慢思索,但是记忆力非常差。

用计算机来类比的话,我是那种 CPU 尚可但是硬盘极差,以至于很长一段时间内成绩都不是很好,但是遇到喜欢的科目成绩有特别好。于是在读书生涯中成绩总是起起伏伏。

学习,不仅仅是读书时候的事,工作以后大部分人都必须成为一个终生学习者,否则就会被当前的岗位淘汰。我们应该见过各种各样的学习方法,这篇文章不会成为一种新的学习方法,而是总结前人的经验。

在读书和工作后,我使用过各种各样的学习方法,包括但不限于:

  • 思维导图
  • 费曼学习法
  • 联想法学习英语
  • 写作驱动法

这些方法都非常有效,但不得不承认人的学习能力和智力水平从客观上是有一定差距的,有时候别人可以通过一节课学会微积分的基本方法,而我却需要整整一年去理解和消化微积分到底是什么回事。

智力不够,方法来凑。

硬件不够,软件来凑。

于是我了解了各种各样的学习方法,通过寻找方法来弥补理解能力、记忆方面的不足。慢慢的我了解到,这些学习方法只是一种形式,其背后都是在建立一种让人容易理解的模型。

回忆一下我们的幼儿园是怎么学习算数的?老师会让我们用手指来代替数字,并进行计算。一个中学老师把联想、比喻使用的越好,对学生来说学习效果越好。我们天生难以理解抽象的概念,因为抽象的内容是人定义出来的,是建立在他人的知识背景之上的。对从来没有社会经历的高中生来说,没有为资本家工作过的经历,难以理解什么是“资本主义对人的异化”。在无法理解的情况下,就只能死记硬背,记忆力不好的人处于学习的劣势。

联想和比喻是我们理解事物最重要的方法之一,我们研究的对象是需要学习的知识,被用来联想和比喻的事物就是模型。

模型是什么样的?

在我细致的观察中发现,模型有一些有趣的特点。

模型是多样的。能帮助人理解知识的东西都是模型。教学条件好的地方老师会有各种各样的教具,地理课上老师会拿来地球仪,化学课老师会拿来分子模型,生物学老师会拿来人体模型。因为这些模型和教具,在理解一些知识的时候变得更加容易。但是给了我们一个印象,只有摸得着的东西,用塑料、木材等实物制作而成的才是模型。

这限制了我们的思维。中学时,我特别喜欢物理,为了理解物理学的一些知识,偶然间我在网上找到了一些关于物理学公式的 gif 动图,很多难以理解的东西变得容易理解了。

模型生活中无处不在,只要能拿来联想和比喻的东西都可以作为模型,或纸上、或电脑里。实际上数学、物理公式也是数学、物理的模型,而数学、物理则是宇宙的模型。

模型是简单的。因为知识和概念是复杂、难以理解的,因此我们需要通过类比、比喻来理解,如果我们找了一个模型,本身也很复杂和难以理解,可以说这个比喻并不恰当,或者这个模型并不成功。

模型的简单性是帮助理解的基础,所思维导图做得过于复杂还不如不做,简单性随之而来的是,模型都是错的。模型只能帮助理解知识,但是不能代替知识。

六祖慧能说过:“真理是与文字无关的,真理好像天上的明月,而文字只是指月的手指,手指可以指出明月的所在,但手指并不是明月”。对我们学习来说,模型是手指,而知识是天上的月亮。

什么可以当做模型?

书的目录就是一种模型。

我们把思维导图转换成文字版本就会发现,它和书的目录没有什么区别,只不过突出的重点不同。

对于工作后的人来说,大部分学习都是在电脑上完成的,用来承载知识模型的途径很多。

  1. 思维导图
  2. PPT、keynote 或者一些在线画图工具
  3. markdown 的清单

如果不使用电脑,小抄是一种很好的表达模型的方法。我在一些考试的时候发现,小抄不是为了作弊,而是为了让自己对知识有一个全局的印象,并记住关键知识点,以及知识点之间的练习。

最巧妙的记录小抄的方法是使用一张 A4 纸(只能用一张,不能多),把尽可能关键的信息写在这个纸上。如果写不下,说明这个模型太过于复杂,换一张纸重新整理知识点,直到必要的知识点能在一张 A4 纸上呈现才算完成。

操作方法

模型法学习的关键在于知识的压缩和形象化。压缩是为了提炼出最重要的信息,形象化是为了能真正理解知识。

金岳霖先生在《知识论》中提到过很多处理知识的方法,简单来说,学习就是把凌乱的信息整理成有序的知识,并加以实践和应用的过程。

所以模型学习法有几个过程:

  1. 输入
  2. 压缩、理解、内化
  3. 建立模型
  4. 应用

输入

学习首先得有信息来源,不然理解和内化无法发生,即使能发生也是自己空想。最简单的输入过程就是阅读,除了阅读外和别人交谈、问问题都可以。

刚开始学习一个新东西的时候,需要大量的泛读,即使部分内容无法理解也可以跳过,先积累和寻找大量语料库。我的阅读速度一直都很快,也坚持阅读了大量的材料,有时候会发现前面无法理解的东西,读到后面就能理解了,所以不能因为部分无法理解就停滞不前,因为学习是一个整体性的过程。

个人建议在刚开始泛读时候不要过于在乎笔记,这样会让自己的思维被打断,可以用笔做一些标记或者折叠相应的页码。

压缩、理解、内化

当大量的速读和泛读之后,在脑海中可能会建立起来一个对知识的模糊轮廓。这个时候,需要尝试理解学习的知识到底是个什么?这个时候需要寻找能类比的事物,建立联系。比如 DNA 双螺旋结构的分子式非常像两条缠绕在一起的蛇;波尔的原子模型,可以类比为太阳系的行星结构,它的形象和能级结构非常相似。

理解这些知识后,尝试按照上面的说法,做出思维导图或者写在一张 A4 纸上,我一般喜欢压缩到一页 keynote 中,超过一页的内容就缺乏全局观和简单性。

建立模型

压缩后的知识点可以作为模型的草稿,如果没有什么问题其实就可以当做模型了。不过为了让模型起到帮助记忆和理解的作用,可以通过连线建立起元素之间的关系,画出概念之间的关系,这有点概念图的味道。

删减掉无关重要的的信息,尽可能的让模型保持简单。

应用

除了应试的用途外,如果我们学习到的知识不是为了拿来应用,那么知识也没有什么用处。

应用和实践是让知识记忆的更牢固,“纸上得来终觉浅,绝知此事要躬行”。并且我们建立的模型在实际使用过程中会遇到这样或者那样的问题,当模型并不能很好指导实际应用的时候,我们需要进行修正,并重新投入实践。

应用中获得的反馈也可以作为第一个步骤的输入,而且这个输入更为准确和重要。

写在后面

这篇文章相当于也是我对学习方法建立的一个模型,通过写作来检验这个模型发现还是有效的,所以可以将学习过程总结为:输入、内化、建模和应用。

当然,如果这篇文章对你有用的话,你也可以建立一个自己的学习模型。

more >>