Nilenso Blog 09月30日
软件开发生命周期以代码建模
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文探讨了如何将软件开发生命周期以代码为单位进行建模。通过将工作分解为可管理的小部分,确保每个部分对客户有价值并提供进展感。文章强调了优先级规划、团队协作和上下文收集的重要性,并提出了如何验证和交付完成的单元。最终,文章主张通过有效的单元工作来衡量开发者的整体生产力,并讨论了如何通过重构核心抽象来提高效率。

🔍 单元工作应被视为软件开发的基本抽象,围绕它构建状态机和流程,由产品经理、工程师、设计师等执行,并根据团队需求定制。一个良好的单元工作是有效性和适应性的关键,错误的抽象会导致复杂性指数级增长。

📈 将工作分解为小部分(如产品或功能需求)是关键,这些小部分应具有客户价值并提供进展感。这种“切片”方法有助于尽早验证业务价值假设,并允许独立处理技术任务或重构,只要它们满足价值和进展的基本需求。

📊 规划阶段涉及优先级排序,通过估算实现工作量和业务价值来决定哪些切片首先交付。大切片可能需要进一步分解,而小切片可能需要组合,以确保独立性和最大价值。团队协作要求切片尽可能独立,以避免相互干扰。

🧩 在单元的生命周期中,上下文不断收集,包括其提供的价值、验证方法、实现需求、缺失信息、已知和未知问题、参与者及其遇到的问题,以及测试和发布前的错误。将所有这些上下文集中在一个地方有助于无缝继续工作。

📝 单元工作完成后,需要定义可接受的验收标准,并通过自动化检查来验证它们。关键在于确保单元在用户手中交付(在生产环境中),这可能需要特征标志来控制。当单元被服务并满足所有标准时,它才被认为是完成的。

📊 测量开发者生产力时,应关注交付客户重视的价值,而不是代码生成百分比。有效的单元工作可以成为衡量生产力的有效手段,因为它鼓励优先级排序、消除不必要的工作和快速验证。AI助手也需要明确的小工作部分,因此也能从定义良好的单元工作中受益。

🔄 如果我们的核心抽象(单元工作)存在问题,整个系统就会变得复杂且难以维护。因此,定期重构单元工作就像处理软件中的泄漏抽象一样,对于保持系统的可维护性和效率至关重要。

What if we were to model a typical software development lifecycle in code?

The unit of work would be the fundamental abstraction. We’d build state machines and workflows around it, carrying it from specification to deployment through activities performed by product managers, engineers, designers, and others. The process could be customised to each team’s needs, with all the bells and whistles. But fundamentally, its effectiveness and adaptability depends on how good this central abstraction is.

Get this abstraction wrong, and complexity scales exponentially. All the processes built around it inherit the dysfunction. Planning becomes chaotic, progress becomes opaque, and coordination becomes an expensive mess.

We deal with leaky abstractions by periodically refactoring them, so why not do the same thing with the unit of work? What makes a good unit of work? Let’s walk through these familiar activities and observe the properties that emerge.

Breaking it down

We typically start with product or feature requirements. We don’t usually take on a full feature in one shot, it’s “too big”. Especially if it’s complex enough to need some technical design and specification written along with it. We break it down into small parts that are more approachable for solving, and also give us a steady sense of progress.

Now the product requirement is actually a hypothesis for creating business value, and we need to validate the hypothesis as early as possible. So, the small parts need to be valuable to the customer.

In other words, we need the unit of work to be a slice of the cake, not a layer.

Of course, bug fixes and refactors aren’t providing value in the same way, and that’s okay. Sometimes there are technical tasks that are best left independent. That’s okay too. No need to be dogmatic as long as the broad needs of value and sense of progress are being met.

Planning

Before starting work, we want to prioritise, because it saves a lot of time. We want to ship the most valuable slices first, and perhaps discard some low priority slices. But we can’t prioritise without weighing the business value against the implementation effort. All slices aren’t the same size, so we estimate the implementation effort first.

Then, some large slices can have low product value, so we would want to break them into even smaller slices to prioritise parts we care about most. Some other large slices can’t be sliced further meaningfully, and that’s okay. Some smaller slices can’t be engineered independently, so we build the larger slice anyway. The unit needs to be negotiable.

And since we’re doing this as a team, we’ll want to ensure that the slices are as independent as possible, so that we can each do our part without waiting, and we don’t step on each other’s toes.

Gathering context

A unit can be specified today, picked up for execution next month, blocked by another task, and then deprioritised into the backlog. Over its life, it gathers context about various things:

Keeping these pieces of context collected in a single place helps in picking it up from where it was left off. When discussing, implementing, or tracking, it’s useful to have the same artifact in front of us.

Solving

Knowing exactly what we’re solving for is very helpful, so we can build just enough software™️. No more, no less. So we need to define the acceptance criteria that we can all agree on.

Then, solve until we meet them.

It’s good to automate checking whether they meet the acceptance criteria, because we’re going to be doing that an awful lot while solving.

Verifying

Confidence usually doesn’t require checking every possible case, only the key ones that capture most of the impact. Yes, we checked this slice at every step of the way, but it is useful to inspect it one last time before serving.

When is a unit considered done? When the slice has been served. When it’s in the hands of the user, in production, potentially behind a feature flag.

And that’s it. To manage the life cycle of software development, we manage the unit of work. Some would say we need to INVEST in good units of work. And some of you might rightly recognise that it looks like a User Story. But as long as the described properties and affordances for its users exist, it should make for a decent unit of work regardless of what we call it.


Does your unit of work need refactoring?

We’re fairly aware of the penalties of leaky abstractions in software. The incidental complexity of getting our primary real world abstractions wrong, grows exponentially with each layer of software built over it, until the whole system is slow, sludgy slop that’s difficult to work with. We can hack it here and there, and celebrate minor wins, but the big wins were lost in the ignored opportunities to refactor that central abstraction.

If we apply the same thought process to software development, we’ll see that our core abstraction, the unit of work, might need refactoring.

Big gains in developer productivity in this economic weather are important. Organisations that use DORA measure deploy or commit frequencies might find them valuable in some dimensions, but they’re not a measure of productivity in terms of outcomes for the customer. I love these last lines in Kent Beck’s writing about measuring developer productivity:

Be suspicious of anyone claiming to measure developer productivity. Ask who is asking & why. Ask them what unit they are measuring & how those units are connected to profit.

I am 100% pro-accountability. Weekly delivery of customer-appreciated value is the best accountability, the most aligned, the least distorting.

And I think a unit of work as defined above could be used to measure productivity holistically. Prioritising by value, eliminating unnecessary work, and validating quickly then become obvious, and measurable ways to increase productivity.

Productivity gains through use of AI assistants is also popularly reported and benchmarked in terms of % of code generated, but that’s not a very valuable dimension for measurement. If the benchmarks for AI productivity revolved around units of work valuable to the customer, then we’d be talking true productivity gains. AI assistants also need small, well specified slices of work, and hence, will also benefit from a well defined unit of work. My colleague Atharva has written a wonderful blog post about that in detail.

Yeah, this article is mostly about rehashing a two-decade-old pitch for some common sense agile. But I hope it has been worth your time.

Annexes

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

软件开发 单元工作 生命周期 敏捷开发 生产力 价值驱动开发
相关文章