断食·轻断食

最近因为未控制饮食,以及家附近游泳池关闭,导致体重持续上升,已经到了自己难以忍受的状态,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.debug("getEmplyeeById controller get called"); return employeeRepository.findEmployeeById(id); } @GetMapping private Flux<Employee> getAllEmployees() { logger.debug("getAllEmployees controller get called"); return employeeRepository.findAllEmployees(); } @PostMapping("/update") private Mono<Employee> updateEmployee(@RequestBody Employee employee) { return employeeRepository.updateEmployee(employee); } } model: 很简单的领域模型Employee包含id和name两个字段 ...

March 21, 2022

理解响应式编程(reactive programming)

过去十年互联网用户数呈指数级增长,各类网络服务访问数量也随之持续增长。为了应对持续增长的访问需求,各种技术被重新赋予了新的活力。微服务,DDD,响应式编程等技术被重新改进用于应对以上问题。本文着重讨论响应式编程背后的原理,帮助读者理解并应用于实际的开发中。 响应式宣言 说到响应式编程首先要引入一个概念响应式系统(Reactvie Systems)。回到2013年Jonas Boner领导的开发团队提出了响应式宣言,其中定义了响应式系统一系列核心原则.主要描述了该系统应具备灵活性,松耦合以及可扩展性.原则描述了响应式系统的基础特性: 可响应性:一个响应式系统应提供快速和一致的响应时间,以及一致的服务质量 可复原性:一个响应式系统在随机失败的情况下,通过复制和隔离能力保持响应 可伸缩性:一个响应式系统在不可预测的负载下,通过经济的可扩展性保持响应 消息驱动:系统组件之间应通过异步消息机制进行通信 响应式编程要解决什么问题 在非响应式同步调用的系统中两个服务是怎么调用的呢?假设我们有A,B,2个服务,当服务A调用服务B后(request),服务B开始处理收到的请求,这个时候服务A的线程会被阻塞住(idle),等待服务B处理完成后返回响应给服务A(respond),这时服务A线程被唤起继续处理接下来的逻辑。 这是我们常用的同步调用方式,他最大的问题是会使资源经常处于idle状态,没有充分利用我们的资源,对资源浪费很大。例如服务A调用服务B的线程在调用后就被阻塞住,不能做其他的事情,直到服务B响应为止。我们知道系统的线程要占用CPU周期,内存等硬件资源,并且极其有限。 那么响应式编程的目标就是解决资源浪费问题,最高效地使用资源。这里我们看到服务A线程调用服务B后,并未等待服务B处理完成,便开始处理其他逻辑,所以服务B的单个线程可以不断调用服务B。那么服务B也可以不间断的收到大量的请求进行处理,这里A,B两个服务都高效的利用了资源。 响应式编程背后的原理 这怎么实现呢?当服务A调用服务B的时候,AB服务之间建立了一个订阅通道,通道建立好以后,服务A线程就去处理其他逻辑,等服务B处理完请求准备好数据后,便会通知服务A数据已经准备好了,这个时候服务A会有一个独立的线程池去获取服务B的数据,这样就实现了同步调用/异步响应的调用方式。 这里有几个响应式编程重要的核心概念: 发布者(The Publisher): 发布者就是数据的生产者,这个是为系统生产数据的组件,这里的服务B就是一个发布者,他收到服务A的请求后就开始生产数据。 订阅者(The Subscriber): 订阅者订阅发布者生产的数据。这里服务A就是订阅者,他订阅来自服务B的数据。 订阅过程(The Subscription):订阅过程是一份服务之间的合约(contract),它被用于订阅者获取数据,或者取消订阅。 处理者(The Processor):处理者是一个响应式实体,他能够消费发布者的数据,并进行再加工,然后发布自己的数据,上面的例子并未体现这层逻辑。举个例子,排序处理者,他可以作为订阅者获取发布者的随机序数据,然后进行排序,然后作为发布者生产出排序后的数据。 背压机制(Backpressure Mechanism) 前面说到响应式编程包含发布者生产数据,订阅者订阅数据,很自然想到当发布者生产数据的速度和订阅者消费数据的速度不匹配的问题,特别是快于订阅者能消费数据的速度,这时系统就是出现问题,订阅者就会被过量的数据淹没。 响应式编程是为了高效的利用系统资源,总不能把系统服务打垮了吧。这时我们在设计系统的时候会引入背压机制来控制发布者和订阅者之间的平衡。 一般我们通过三种策略来调控速率: 控制发布者发送数据的速率(推荐) 订阅者使用缓存来存储暂时无法处理的数据 订阅者丢弃所有无法处理的数据 为了达到系统的高效运行,通过背压机制,使发布者和订阅者速率达到平衡,也就是根据消费能力来按需生产和发送数据,生产多少,就消费多少,用前面的A,B服务的例子来说明: 服务A给服务B发送一个request请求1个数据,服务B的生产完成1个数据的时候,就通知服务A(onNext)有个数据就绪了,服务A就以同样的速率处理数据,这样服务B生产速率就被服务A限制,A,B两个服务就工作在同样的速率,这样系统效率达到最佳。 在这里讲完了响应式编程的核心原理,下一篇《使用WebFlux进行响应式编程》会继续深入讨论响应式编程在代码中的实现。

March 20, 2022

代码抽象性与依赖性

我们常说代码除了满足功能需求以外,还应该满足以后的可读性,可测性,可扩展性,可维护性等。我们常常看到两种类型的团队,一类团队软件开发流程里面只有开发+测试,常常处于加班状态,不断的赶新功能上线。另一类团队有完善的软件开发过程,迭代开发,同行评审,单元测试,自动化测试等。主观觉得第二类团队的代码质量应该比较高,可我们对这两类团队的代码到底怎么样,除了一般常用的sonar,findbugs等静态扫描工具的数据,就没太多的了解。除非深入学习业务逻辑,并剖析源代码。 代码的抽象性,决定了代码以后是否容易扩展,抽象性高的代码易于通过继承的方式进行扩展,抽象性低的代码更容易出现复制-粘贴的扩展方式。代码被别的类依赖多,导致代码不容易变化,反之代码可变性就很高。如果从这两个方面考虑代码的设计,就提供了更多的维度了解团队代码的健康度。 我们以模块为基本单位,统计整个系统每个模块的抽象性和依赖性: 代码抽象性 Nc:模块内类的数量 Na:模块内抽象类和接口的数量 A:代码抽象性 A = Na / Nc 那么这个值的区间是[0,1] 代码依赖易变性 Fan-in:进入的依赖。模块外有多少个类依赖于该模块内部类。 Fan-out:出去的依赖。模块内有多少个类依赖于模块外部的类。 代码依赖易变性: I = Fan-out/(Fan-in + Fan-out) 值区间[0,1] 举个例子: 上图Fan-in=3, Fan-out=1 模块依赖易变性 I=1/(3+1) 如果我们把代码抽象性和依赖性放入到二维坐标轴,x坐标是代码依赖易变性,Y坐标是代码的抽象性。然后在图里面最高抽象性(左上角)到最高抽象易变性(右下角)划一条对角线,这条线叫主道线。 我们在上图可以看到三个区域1.痛苦区 2.无用区 3.主道线区 处在痛苦区的模块里面类的抽象性不足,同时极度的稳定,依赖易变性低。这种模块可扩展性不足因为抽象性低,同时被多个模块依赖,不能轻易改变。如果要扩展这个模块的功能,就会产生大量的复制-粘贴重复代码。有一个例外就是基础工具模块,这类模块的特征就是处于极度稳定,并不需要抽象性,所以这类工具模块处于这个区间是合理的。 处在无用区的模块里面类的抽象性很高,但没有类去使用所以依赖易变性也很高。这个区间的模块包含大量的抽象类和接口,但没有外部模块使用它,所以变成了无用的模块。 结论是我们期望一个系统大部分的模块都处于主道线区,不要偏离主道线太远。如果一个模块的二维坐标到主道线的距离过远比如达到0.5,那么这个模块值得打开深入分析里面的类的抽象性与依赖性是否合理。 这里我写了一个工具可以分析java代码的抽象性与依赖性供参考: GitHub

August 10, 2021

研发大猜想

经历了太多乱七八糟的产品和项目,听到了很多有趣的猜想和假设,我把他称之为"研发大猜想" 猜想1:这个系统架构和代码太烂了,我们重新做个2.0系统吧,前面的问题就都解决了 感觉好像新做的2.0代码就不会和1.0系统一样烂了,结果就是和以前一样前期赶需求,后期集中测试,什么设计,什么内建质量都是太浪费时间,最后搞了大半年搞出了跟1.0一模一样的代码烂泥。怎么办喃,还可以3.0嘛。 “如果不改变做事的方式,永远都只能做出一样的系统” 猜想2:因为业务要的太急,所以我们没有时间写单元测试,没有时间做代码评审 听着很有道理,就好像不那么忙的时候,他们就会写单元测试了。实际上他们从来没写过单元测试,也永远不会去写。因为从思想上就没有理解什么是合格的代码。一个合格的程序员交付给测试人员的代码应该是很难再测出问题,测试人员花大量的努力都无法验伪的程序。这个才是一个刚刚合格的程序,因为这方面仅仅是功能无问题,还要涉及设计的合理性,抽象性,耦合性,代码可测性等代码结构事宜。 “一个合格的程序员,应该有这样的品质:自己写的代码,应该在功能上很难验伪,在设计上保持代码健康。” 猜想3:业务两周就要一个版本,连回归测试的时间就不够,我们干脆把版本周期加长,然后搞几个前后并行的版本,这样人处于最忙状态,研发就最快了 在集成测试阶段开发人员有空闲,所以下个并行版本要进入到开发阶段。最好在第三个并行版本进入到需求分析阶段,这样大家就看起来都很忙了。下面的人保持最忙的状态,才显得上面的人员指挥恰当。 结果就是搞需求的时候,还要同时搞开发,搞缺陷,搞开发的时候还要同时搞上版本缺陷修复,每个事情都没做好。结果是需求没有搞清楚,开发设计一团乱,缺陷一大堆,就这样往复循环。但每个人都很忙,真的很忙。 “好好体验一下,做好每件事情,大脑处于专注流的状态” 猜想4:一个功能需求写了很多复杂逻辑,各种场景覆盖,产品经理的价值体现就在能把事情考虑周全 写产品需求是很枯燥的事情,不是某个领域熟悉的人,却想写出解决这个领域问题的产品需求,还要能读懂使用者最终怎么用这个产品,同时还想产品功能面面俱到。遇到这样的产品经理,研发团队是痛苦的,平白的做了大量的复杂业务逻辑。最后用户认为功能难用,不能解决问题,推翻重做,产品经理美其名曰“我在试错”。这样的产品经理从来考虑不清楚,这些功能的价值是什么,能解决什么问题。拍脑袋写大量的文档是他们最喜欢的事情,为什么呢?因为不用思考。 殊不知就算你熟悉这个领域,也不该一开始设计复杂的功能逻辑。简单即是美,往往是简单的功能才能解决用户问题。 “优秀的产品经理真的很稀有” 猜想5:项目会有紧急需求,遇到紧急需求的项目要加人,我的项目总是缺人,要多补充人,就能把事情完成 人能解决所有的问题,人多我就什么都能搞。不了解沟通的耗费,不了解优秀程序员和劣质能写hello world 人的区别,不了解往进行中项目加人的影响。只想通过加人解决问题。问题真的是人不够吗? 你见过谁说我的项目人太多了,需求太少的事情吗? “问题的本质真的很重要” 猜想6:项目大部分是倒排期,不合理 按照估算排期就是正确的?怎么证明按照估算排期就是正确的?我倒是觉得估算排期证伪比较容易。看看历史数据估算和实际偏差有多大就知道了。 “所以动态规划就很重要了”,实时的适应变化,这个迭代需求多了,减少点, 需求少了,增加点。要满足某个倒排期的市场需求,去找寻mvp。 猜想7: 我们团队没有产品经理,团队无法明确需求,需求经常做偏 程序员要做好两件事情,搞明白要做什么,正确的做出来。有了产品经理,就指望这个人把做什么搞明白,再告诉你。做的东西用户不满意就是产品经理的问题。你再不用了解用户遇到什么问题,也不用思考怎样能解决用户的问题。没有产品经理,你就什么都不会了。 你都不想理解问题,就开始写代码. “难道真的是不要把爱好变成工作?”

July 27, 2021