“敏捷”与“瀑布”,在中国还真相遇过,只不过不是你认为的方式

最近敏捷圈发起一阵讨论:敏捷、瀑布,在中国存在过吗,不禁思考在中国敏捷与瀑布,真没相遇过吗? 2000年前后,CMM思想流入中国,01年《程序员》杂志开始以《CMM布道中国》为专题进行一系列讲述。从这时起CMM在中国如火如荼的发展,IPD-CMMI一度成为众多企业的学习对象。在这股思潮下,软件开发以瀑布V模型为主,该流程要求软件发现缺陷后,需向上回溯到最早缺陷引入点,随后从缺陷引入点开始进行一系列后续变更。举个例子在测试阶段发现程序缺陷,通过分析后得知是需求文档描述有误,那么需要先变更需求文档,接着变更概要设计,详细设计,测试用例,代码修改,单元测试,代码评审,最后该功能重新进入测试阶段。 这个流程里需求分析,开发,测试人员通常属于各自职能部门,由于职能竖井(KPI,部门墙等),软件开发过程中经常需要开会协调。但只要时间充足,上线软件倒也质量尚可。但问题就出在时间充足这个假设前提,当时中国IT企业大多是以倒排期和版本火车方式进行项目计划。团队成员在同一时间会投入到多个软件版本中,时间紧,任务重,同时白天大部分时间又用于开会和扯皮,晚上才能安静的进行软件开发,长此以往团队陷入软件泥潭,疲惫不堪。 这时团队往往采取流程裁剪这样的骚操作,概要设计,详细设计,测试用例,单元测试,代码评审等非直接编码活动统统拆剪掉。剩下哪些活动呢?总得有需求文档吧,不然团队用什么开发,最主要的编码活动不能裁剪掉,就这两个活动就可以交付软件了,可团队TL心里没底啊,测试环节也留下。于是乎V模型只剩下“需求-开发-测试”三个活动。如果企业流程不能随意裁剪,那么你仔细观察上线前一天团队在做什么,就会发现所有团队成员都在补写文档,至于这些文档和当前上线代码有什么关系,没人知道。你问这些团队这是什么开发方式,他们会说这是裁剪后的“瀑布流程”。 2008年前后,敏捷思潮开始在企业发芽,《硝烟中的Scrum和XP》和《持续集成》成为敏捷支持者的床头书,迭代增量开发,快速反馈成为敏捷的重要指导思想,上一活动的问题,尽量在下一活动反馈中找到并修复。自动化测试成为保证质量,快速反馈,快速发布的重要标志。团队实在受不了每夜写代码,开始纷纷上马敏捷开发。可是采用敏捷开发,之前的问题就解决了吗? 敏捷宣言第二句“工作的软件高于详尽的文档”,但团队选择性无视左边,专注到右边,你看敏捷不用写任何文档了,一阵欢呼。于是敏捷3355会议开起,需求澄清会议一开就是2,3天,会议室里面坐满黑压压一片,产品经理卖力念着需求文档,团队里三层,外三层各自玩手机。​总算结束,团队开始拆卡片,随后进入迭代开发,后期进入迭代测试。你看这个敏捷开发不就是换了个名词的“敏捷3355+裁剪后的瀑布流程”。久而久之团队开始抱怨,以前瀑布流程的时候可没那么多会议,敏捷开发咋那么烦琐,要不我们也把迭代演示,迭代回顾,代码评审,自动化测试,小批量交付等裁剪了,最后敏捷开发成为迭代进行的“需求-开发-测试”三个活动。你问这些团队这是什么开发方式,他们会说这是“敏捷开发”。 你看中国企业采取了”瀑布流程“、”敏捷开发“,怎么最后都是一个样子?这里原因很多,有人认为是企业倒排期文化,有人认为是需求变化太快,但有一个很重要的问题,团队里面真的是合格程序员吗?真的理解计算机科学吗?以下一些问题作为参考: 哪些算法,语言设计,操作系统,分布式计算,运用了fork-join思想? Java为什么放弃绿色线程,现在为什么又要开始支持虚拟线程? 设计模式,面向对象编程是灵丹妙药吗? 你写的代码会覆盖必要的单元测试吗? 抽象能力是计算机基础,编程语言,框架,系统,硬件进行大量的抽象,尽量隐藏细节,但并不意味着你可以不理解计算机基础,就到处调用框架,运用大量并发技术。深入团队编码,会发现并不是团队不愿意写单元测试,而是团队写的代码根本无法测试,更不用说从未写过单元测试。 选人的时候,从未考虑候选人计算机基础能力,编码内功,只要这个人能尽快上手项目就行,这个语言,那个框架玩的飞起来。你仔细观察这些团队,会发现他们的流程再次升级,变成“需求-拷贝、粘贴-人肉点点点”。中国IT发展了20,30年,系统升级了,语言升级了,框架升级了,工具也升级了,可团队能力升级了吗?

January 3, 2023

敏捷本质

敏捷宣言诞生至今已20多年,在众多挑战瀑布的方法中,它完美的完成了这次反叛,成为主流的方式。敏捷最早作为软件行业的开发方式,现在逐渐渗透到其他行业。 随着敏捷方法流行,越来越多的概念,方法论,实践被加了进来,“敏捷”开始变得臃肿,很多和敏捷八杆子打不着的方法也号称“敏捷”。人们开始在各种概念中迷失,敏捷的本质被逐渐掩盖。 敏捷首要解决的问题是小团队开发,两个披萨可以喂饱的团队(6~12个人),如下图敏捷的核心实践,都是针对小团队的一系列活动: 我认为整个敏捷开发活动最重要的是BDD和TDD两个实践,再辅以过程透明化,快速反馈与持续改进。 为什么这两个实践如此重要,整个软件开发活动中我们要解决的三个核心问题是: 如何确保充分理解了业务需求 如果确保技术上能实现该需求 如何确保开发出来的功能满足业务需求 BDD通过与业务达成一致的实例化需求,来确保我们理解了业务需求,如果我们不能写出验收用例,证明我们还没理解业务需求。验收测试用例用来衡量我们是否真正理解业务需求。 同时我们把验收用例自动化,通过自动化验收测试,来确保我们开发出来的功能是满足业务需求的。 TDD通过自动化测试,来确保我们技术上是可行的,如果不能写出自动化测试,就无法确保在当前技术框架下可以完成该功能开发。 这两个实践使我们的开发活动工程化,只有通过工程化,才能确保我们的需求透明化,开发透明化,验收透明化,才能得到快速反馈,最后才能根据反馈持续改进。 关于规模化敏捷,人类历史上是不缺乏大规模的组织活动(金字塔,巴拿马运河,登陆月球,原子弹等等等),敏捷是通过首要解决小团队问题,然后把大规模团队划分为小团队,来达到规模化效果。但如果不先解决小团队问题,规模化敏捷就会流于形式。举个例子,规模化敏捷的核心问题就是团队间协作,版本火车中投入最大,耗时最长就是整个版本开发活动,多个团队要在估算的时间完成某功能联调活动,如果团队不具备TDD能力,那么团队的任务估算无法保证,导致该功能无法按时联调,团队间产生等待,连锁反应到迭代末团队继续追赶任务,无法投入到下迭代需求分析活动中,影响一直持续到后续迭代。 最后关于敏捷价值观,在眼花缭乱的各种方法中,以及在我们教练辅导中,是否还秉承着Kent Beck提到最核心的敏捷价值观:勇气,沟通,反馈,简单。

August 14, 2022

泛型编程浅入浅出(C++, Java, Go)

今年3月随着Go 1.18版发布,引入了一个重大的语言特性:泛型编程。这个特性在发布前引起了一定的争议。崇尚少即是多的一边认为这个特性不是很必要,应该谨慎引入。另一边则认为这是语言必不可少的特性。最后Go的泛型还是如期而至 泛型编程在其他编程语言也遇到了不同的问题,比如在Java 1.0时是没有引入泛型编程支持,到了Java 1.5的时候引入了泛型,但由于引入时间过晚,有大量的标准库和第三方库无法支持,Java选择了妥协采用间接的类型擦除方式来实现泛型编程,导致泛型的使用复杂度增加,同时场景受限。而C++比较明智,一开始就支持泛型,所以在C++ STL标准库里面大量的算法都是采用泛型实现,整个语言体系中泛型占据了核心位置 学习一个语言的特性可以参考其他语言同样的特性,这样就可以了解这个语言特性实现是否设计合理,是否优雅,存在哪些局限性 语言版本: C++11 Java 11 Go 1.18 1. 泛型函数 泛型函数用来支持当一个算法用在多种数据类型上,为了避免重复的定义函数,而用泛型函数来支持 两个数据进行取小操作 //C++ template <class T> T min(T a, T b){ return a < b ? a : b; } //Java不支持 //Go func Min[T int|float64](a, b T) T { if a < b { return a } else { return b } } 2. 泛型函数-显示特化 当泛型函数用来支持一个算法用在多种数据类型上,但在某个类型数据上泛型函数的实现不适合该类型,那么我们可以显示特化该类型的实现算法 /C++独有 template<> string min<string>(string a, string b){ return a.size() < b....

July 3, 2022

断食·轻断食

最近因为未控制饮食,以及家附近游泳池关闭,导致体重持续上升,已经到了自己难以忍受的状态,148.5。 一直知道有一种可能比较健康的生活方式,轻断食,但一直不知道怎么实践,通过搜索发现有部记录片《进食 断食 长寿》,介绍了轻断食的理念。决定先尝试一下正常断食,计划断食4天半,每天吃一些蔬菜汤,维生素,卡路里在200左右,完成后采用5+2轻断食法维持健康。 4月开始 第一天 23号晚上暴饮暴食后,不再进食,24号白天中午,未感觉任何饥饿感,可能是头天晚上吃太多了,出去溜达溜达,中午也没午觉,下午继续办公,办公后进行骑行锻炼1.5小时。晚上回来感觉头有点晕,吃了半碗饭+一小份莴笋烧兔子,补充了一颗维生素,肚子咕咕叫,后面整体正常。晚上睡觉质量变差,睡眠变浅。网上查询是因为身体激素变化的原因,断食期间睡眠会出现没有以前好的状况。 第二天 25号早晨体重145.5,没有饥饿感,工作到中午一样没有多少睡意,中午出去溜达一圈。下午工作到16点时,困意来袭,小眯了一会儿。进行骑行锻炼1.7小时,感觉力气没有昨天多。晚上自制蔬菜+西红柿汤,大概卡路里在200以内,补充一颗维生素。 肚子继续咕咕叫,并伴有轻微腹泻,持续时间一整夜。晚上睡眠还是变浅,醒来几次。 第三天 26日早晨体重143.5斤。无头晕感觉,但觉得力气不足。喝水,补充一颗维生素。腹泻状态暂时消失。今天工作比较忙,晚上回家感觉心跳加快,有些不适,怕影响健康,停止断食,开始正常晚餐。 后续开展5+2轻断食+运动,5.9号测量体重为142斤,正常开始降低体重。 7月期间 加大运动量,体重降到130以下,身体并无不适 9~10月期间 由于疫情运动量减小,加强了断食间隔,断食日整天不吃。体重维持到126左右。但出现营养不良型脱发。在10月下旬恢复正常饮食,脱发继续。 11月初脱发减少恢复正常。考虑轻断食太重可能影响健康。开始恢复正常饮食,同时加强运动消耗。体重126左右。

April 23, 2022

使用WebFlux进行响应式编程(using webflux in reactive programming)

上一篇文章《理解响应式编程(reactive programming)》我们谈到响应式编程四个核心概念: 发布者(The Publisher): 发布者就是数据的生产者,这个是为系统生产数据的组件,这里的服务B就是一个发布者,他收到服务A的请求后就开始生产数据。 订阅者(The Subscriber): 订阅者订阅发布者生产的数据。这里服务A就是订阅者,他订阅来自服务B的数据。 订阅过程(The Subscription):订阅过程是一份服务之间的合约(contract),它被用于订阅者获取数据,或者取消订阅。 处理者(The Processor):处理者是一个响应式实体,他能够消费发布者的数据,并进行再加工,然后发布自己的数据,上面的例子并未体现这层逻辑。举个例子,排序处理者,他可以作为订阅者获取发布者的随机序数据,然后进行排序,然后作为发布者生产出排序后的数据。 一个典型的响应式交互如下: 订阅者和发布者签订订阅契约(Publisher.subscribe),一旦契约签订完成,订阅者向发布者请求数据(Subscription.request),发布者准备好数据后传输数据给订阅者(调用订阅者onNext),订阅者再次请求新数据(request),直到发布者告诉订阅者数据已经发送完成(onComplete),本次契约完成。 Reactive REST Appplication 现在我们使用WebFlux开始构建一个简单的响应式应用. 使用一个简单的领域模型-Employee 使用RestController返回发布者生产的数据 使用WebClient构建订阅者获取发布者数据 WebFlux Maven依赖如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> 发布者(Publisher) WebFlux底层使用Project Reactor,所以我们这里使用Project Reactor的命名来介绍。 发布者有两个类型: Mono类型表示生产0个或者至多1个数据 Flux类型表示生产多个数据 示例代码分为三层分别是: controller-获取REST请求,并返回订阅数据 model-包含Employee领域模型 repository-构建内存数据库(采用HashMap) controller: 可以看到方法返回类型为Mono或者Flux,表示生产者会生产对应类型个数的数据 @RestController @RequestMapping("/employees") public class EmployeeController { private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class); private final EmployeeRepository employeeRepository; public EmployeeController(EmployeeRepository employeeRepository) { this.employeeRepository = employeeRepository; } @GetMapping("/{id}") private Mono<Employee> getEmployeeById(@PathVariable String id) { logger....

March 21, 2022