敏捷日记 - 1.回到起点

在软件行业摸爬滚很多年,从开发软件采用分布式编译技术也需要一天一夜才能编译完成一套系统,大家都在使用16寸CRT编写代码的时代,到现在一台Mac的性能远超当时的集群性能。当年一本《硝烟中的Scrum和XP》读的津津乐道,学到的新东西立马在团队试用,到后来作为敏捷教练帮助企业组织转型。近几年企业开始“降本增效”风潮,普遍看到的只是“降本”,采用精益敏捷的方式减少浪费提升效率的实在太少。有时候看到明明只需1/2甚至1/3的人,改变做事方式,就能比现在更好,但企业还是倾向于低效堆人,十分感叹。 言归正传,很早作为软件工程师的时候,喜欢在工作外写一些小工具帮助团队自动化一些事情,这个习惯在做教练的时候也继续保持着,估计还是内心喜欢写代码这件很纯粹有趣的事情。以前也想过开发一些面向用户的软件,投向市场,看看能不能掀起一点浪花。也跟曾经的团队聊过,无奈大家都要吃饭,养家,靠爱发电似乎难以实现。大家都知道现在这类软件基本很难有市场,所以这件事情一直搁置着,但内心仍有不甘。 在从事敏捷教练期间听到最多的争论是“敏捷教练不要只当裁判,也要下场踢球”,“既要管杀,也要管埋”。于是教练不仅要传递知识,方法论,也要下场带着团队一起干。虽然这样,但进行软件交付的核心工作还是团队。除了通过帮助团队,自己也想深度体验敏捷,迭代,持续交付能做到什么程度,所以内心有个想法,自己分别扮演PO,Team,Scrum Master,从一个想法开始,到实现一个软件并推向市场。在这个过程中会对产品经理,开发,测试,运维,运营等角色在敏捷活动中怎样高效的协作有更深入的理解。记录这个过程中发生的事情,分享给大家,希望能有一些帮助。 可以预见执行过程中的难点,一个是能不能完全实现这些想法,中间肯定会遇到各种困难需要去解决,第二个是主要是时间投入的保证,最近公司开始重视提效,希望能有所帮助,估计会有些忙,家里还有小朋友需要陪伴,考验怎样合理安排时间。好了暂时想到这些。 初步想法如下: 迭代周期为一周,在迭代内既要完成当前迭代的开发,也要完成下迭代用户故事的准备 采用用户故事地图进行版本规划 采用看板进行每日任务跟踪 每周进行回顾 借用TDD思维进行开发,但不完全遵循TDD的步骤 那么下周开始<迭代0-迭代准备阶段>: 准备完成商业模式画布,用户建模,用户故事地图,迭代1用户故事准备,准备决策看板,任务看板 Week 1: Sprint 0 - 迭代准备

February 9, 2025

现代语言怎么支持Parallel和Concurrent

Parallel和Concurrent 我们先来回顾一下概念: Parallel是指同时执行多个任务或计算.通常在多核或多处理器的硬件上并行执行多个操作,每个操作独立运行,并且是同步的(并行的任务在同一时刻发生).并行计算的目标是通过同时使用多个计算资源来加速程序的执行. Concurrent是指系统在同一时间段内处理多个任务的能力.并发并不意味着任务是同时执行的,而是任务在某一时刻轮流执行. 举个例子就更容易理解: 你有一个奶茶店,制作奶茶需要三个步骤,分别是收银,调制奶茶配料和用设备制作奶茶.假设现在有两个角色,A负责收银,B负责调制奶茶配料和用设备制作奶茶.整个步骤是A->B.作为店主你很快发现A收银很快,B调制配料和制作奶茶很慢造成了瓶颈,这是你选择解决方案是再雇佣了另外一个人和B一起负责调制奶茶配料和用设备制作奶茶,现在整个步骤是A->B1 or B2.在这个场景中增加B2就是我们说的Parallel.当然你还可以用另一个方案解决,把原步骤调制奶茶配料和用设备制作奶茶拆分为两个步骤,分别让B负责调制奶茶配料,再雇佣一个人去负责用设备制作奶茶,步骤变为A->B->C,在这个场景中增加C就是增加了Concurrent. 现代语言的并发模型 现代语言支持并发模型一般会在操作系统线程和应用程序之间增加一层,在这层中使用预先分配的线程池和调度器来实现应用程序的并发能力.如在Go语言中称为协程(Coroutine). Go语言的并发模型如下: G—Goroutine M—OS thread (stands for machine) P—CPU core (stands for processor) 默认情况每一个CPU核心(P)里面会有一个线程(M),每个线程会不断切换去运行协程(G). Swift语言的并发模型,同样也是在操作系统线程和应用程序之间增加了一层.稍有不同的是他通过把代码划分为逻辑单元称为局部任务(Partial Tasks),通过调度器把局部任务分配到线程池不同的线程运行来实现并发能力. 现代语言中支持并发的语言特性 那么在这些语言中怎么支持并发的呢?这里我们举例三个场景: 场景1: 增加Parallel,就是我们前面说的第一个方案增加另一个人和B一起负责调制奶茶配料和用设备制作奶茶 Go实现 func taskA() int { time.Sleep(2 * time.Second) return 5 } func parallelExample() int { c := make(chan int) go func() { c <- taskA() }() go func() { c <- taskA() }() r1 := <-c r2 := <-c return r1 + r2 } Swift实现 ...

January 2, 2025

SwiftUI @State、@Observable、@Binding、@Bindable实现原理

@State 用途 struct 1.私有的View State,使用当前View维护状态生命周期 2.保持struct不可变性的同时修改内部变量值 class 1.私有的View State,使用当前View维护状态生命周期 使用方式 struct Counter: View { @State private var value = 0 var body: some View { Button("Increment: \(value)") { value += 1 } } } 内部实现 struct Counter: View { private var _value = State(initialValue: 0) private var value: Int { get { _value.wrappedValue } nonmutating set { _value.wrappedValue = newValue } } var body: some View { Button("Increment: \(value)") { value += 1 } } } SwiftUI给value state在render tree里面分配内存并赋予initialValue:0,并建立链接使value指向这个内存值 Counter’s body依赖这个内存值,一旦内存值变化会重新构建Counter’s body. ...

November 29, 2024

编译器是否帮我们做的过多?

近段时间编程语言开始往安全方面进行发展,类型安全,内存安全,并发安全等,如新兴的Rust语言,Swift 6.0都在安全性上发力,并都在编译器方面下功夫,添加了诸多规则,当你和编译器战斗通过后,理论上你的代码具备某种程度的安全性。 这类安全性同时也影响着语言的发展,比如在面向接口编程中Swift语言里面对于接口还增加了some和any两个关键字,一时让我疑惑不解,其他语言没有这类概念为啥这个语言需要,他想要解决了什么问题?然后又引入两个类型概念Opaque Types和Boxed Protocol Types。 官方文档描述如下: “You can think of an opaque type like being the reverse of a generic type. … An opaque type lets the function implementation pick the type for the value it returns in a way that’s abstracted away from the code that calls the function. … An opaque type refers to one specific type, although the caller of the function isn’t able to see which type” ...

November 19, 2024

程序语言中内存管理-DO NOT STOP THE WORLD

类似枪械有手动步枪-M1903春田,半自动步枪-M1,自动步枪-M16之分,程序语言的内存管理也有手动、半自动和全自动三种方式。手动内存管理的代表语言:C、C++,半自动内存管理的代码语言:Modern C++,Swift,Rust,全自动的代表语言:Java,C#,Go,Python。本文通过不同程序语言来描述三种内存管理方式,以及如何应对循环引用的典型场景。 手动内存管理: 为了实现代码的极致效率以及灵活性C和C++语言采用了手动内存管理方式,通过自己编写代码实现堆上的内存分配与回收。 //C++代码示例 export class Manual { public: Manual(int s) :elem{ new double[s] }, sz{ s } {} ~Manual() { delete[] elem; } private: double* elem; int sz; }; 半自动内存管理: 内存泄漏是手动内存管理遇到最大的挑战,在复杂的场景下可能出现分配的内存没有释放,导致程序在特殊的情况下内存耗尽停止工作。为了保证内存安全,新一代语言为了不牺牲效率采用编译时期来分析内存的分配与释放,典型的方式是自动引用计数(Automatic Reference Counting ) //swift代码示例 class ARC { } func usingARC() { var ref1: ARC? = ARC() var ref2: ARC? = ref1 ref1 = nil ref2 = nil //ref1、ref2 已释放 } //Modern C++ export class SmartPointers { public: //represents unique ownership (its destructor destroys its object) void uniquePtr() { std::unique_ptr<X> sp{ new X }; std::unique_ptr<X> sp3 = std::make_unique<X>(); std::unique_ptr<X> sp2 = std::move(sp); } //represents shared ownership (the last shared pointer¡¯s destructor destroys the object) void sharedPtr() { std::shared_ptr<X> sp = std::make_shared<X>(); std::shared_ptr<X> sp2 = sp; sp.reset(); sp2.reset(); } //A pointer to an object owned by a shared_ptr void weakPtr() { std::shared_ptr<X> sharedPtr = std::make_shared<X>(); // Creating a weak pointer from the shared pointer std::weak_ptr<X> weakPtr = sharedPtr; // Using the weak pointer to access the object if (auto ptr = weakPtr.lock()) { } else { std::cout << "Weak pointer is expired." << std::endl; } // Resetting the shared pointer sharedPtr.reset(); // Using the weak pointer again after resetting the shared pointer if (auto ptr = weakPtr.lock()) { } else { std::cout << "Weak pointer is expired." << std::endl; } } }; 全自动内存管理: 同样保证内存安全,同时减少内存管理心智负担,还有一种全自动内存管理-垃圾回收机制(GC)。但这种方式引入了臭名昭著的STOP THE WORLD。 ...

March 26, 2024