BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

Scrum框架及其背后的原则(上)——Scrum 框架的伪代码描述

| 作者 何勉 关注 6 他的粉丝 发布于 2011年4月8日. 估计阅读时间: 24 分钟 | CNUTCon 了解国内外一线大厂50+智能运维最新实践案例。

Scrum是应用最广泛的敏捷开发方法。同时,它的失败率却非常高,其创始人之一Ken Schwaber估计75%尝试Scrum的组织无法获取他们预期的效果 (http://www.agilecollab.com/interview-with-ken-schwaber)。对此,通常的解释是“对Scrum框架的错误应用,和对其原则的错误把握。”Ken Scheaber 在“Scrum Guide”一文中对这两方面都提供了权威的阐述。本文的目的是在此基础上,提供更加明确的操作性的指导和检查工具。本文分成上下两个部分,分别讲述scrum 框架本身和其背后的原则。

第一部分:Scrum 的框架

Scrum并不是一个特定产品开发的流程或技术,而是一个容纳其它流程和技术的框架。作为一个迭代和增量的产品开发框架,Scrum本身十分简单和明确。下面的一段伪代码,是对Scrum框架的完整表述。

void run_scrum() {
    const int 	Sprint_Length = 10;    
    int 		velocity = get_past_performance();  		

    // Scrum 中的三个角色
    Role team, product_owner, ScrumMaster;

    // Scrum 中的制品
    Product_Backlog 	product_backlog;
    Sprint_Backlog 	sprint_backlog;
    Burndown_Chart	sprint_burndown_chart, release_burndown_chart;

    Product_Increment 	product_increment;

    //开始项目的三个准备条件
    setup_team(team);
    define_Definition_of_Done(team, product_owner);
    initial_project(&product_backlog );  //标红的为输出参数,将带回值,下同

    //每一次while 循环为一次迭代
    while (!is_empty(product_backlog)) {
        run_sprint_planning_meeting(product_backlog, velocity, &sprint_backlog);

    	//每一次for循环为一个工作日
        for(num_of_day = 1; num_of_day <=  Sprint_Length; num_of_day ++){
            run_daily_scrum_meeting(&sprint_burndown_chart);
            do_development_activity(sprint_backlog, &product_increment);
        }

        run_sprint_review_meeting(product_backlog, product_increment);
        run_retrospective_meeting();

        update_product_backlog(&product_backlog, &release_burndown_chart);
        update_velocity(&velocity);
    }  
}

这就是Scrum的完整框架,只有这些,很简单,我们下面将逐行解释。

const int 	Sprint_Length = 10;    
int 	velocity = get_past_performance(); 

Scrum是一个迭代开发模型。每一个迭代周期,团队完成一部分产品需求,交付可工作的软件。在Scrum中,迭代被称为sprint (本意为冲刺跑,一般不作翻译),典型的sprint长度是1到4周。值得强调的是,对于同一团队,sprint的长度是固定不变的。

Sprint_Length -- Sprint长度:这里以10个工作日(两周)为例,const常量修饰符,强调了其不可变性。

Velocity -- 团队开发速率:也即每个sprint 团队能完成多少量的用户需求,它是Scrum中计划和承诺的最重要依据。开发速率来源自过去实际产出结果,并在产品开发过程中不断修正。

Role team, product_owner, ScrumMaster;

以上的实体定义是Scrum 中的三个关键角色,在Scrum框架中也仅仅定义了这三个角色。

Team -- 团队:团队负责产品交付,规模一般为 5~9人,Scrum强调团队的多功能(cross functional)和自组织。多功能指的是,团队应该整体具备各个职能所需的技能,如系统,开发和测试技能,同时也具备各个组件的技能如数据库设计、协议开发和UI设计等。团队的多功能是短迭代开发的基础,只有做到这一点,独立的团队才可能在一个sprint 中交付对用户有意义的产品增量。自组织指,在目标清晰的前提下由团队自主决定完成目标的具体方法。

Product Owner – 产品负责人: 多直接称为PO。PO 负责把握产品的方向,包括需求的收集、定义,优先级设定等。PO通过这些活动以及和团队的合作,最终确保产品ROI(return of investment, 投资回报)的最大化。

ScrumMaster: 一般不作中文翻译,ScrumMaster并不直接负责产品交付,它向团队负责,确保团队按照Scrum的框架工作,具备良好的外部和内部工作环境,顺畅地交付产品,并不断的改进,提升交付的效率和质量。与传统意义上的管理者不同,ScrumMaster更多是起服务、协调和引领的作用。如果注意观察会发现,在这段程序中,ScrumMaster 是一个定义,但从未使用的变量,这也正反映了ScrumMaster的协调和支持的作用。

Product_Backlog 	product_backlog;
Sprint_Backlog 		sprint_backlog;
Burndown_Chart		sprint_burndown_chart, release_burndown_chart;

Product_Increment 	product_increment;

上面的结构变量是Scrum 中的核心制品,它们贯穿整个Scrum 操作过程,分别是:

Product backlog -- 产品待办事项:很多时候直接用英文表示,简称PB,是一份用户需求列表。其中的每一项都是一个具体的端到端的用户需求,如“用户可以完成登录”等等。Scrum 产品开发过程就是,通过一系列的sprint,每个sprint完成一部分“产品待办事项”,交付包含这些需求实现的产品增量,直至完成所有“产品待办事项”。Product backlog 的责任人是Product Owner,它在产品开发的初期生成,并在开发过程中不断更新完善。

Sprint backlog -- sprint待办事项:是一个sprint 中要完成的任务列表。其中的项目是为完成特定用户需求而要进行的设计、开发、集成、测试等具体任务,如“为模块A添加外部接口”等。完成sprint backlog中的所有任务,也意味着sprint 开发任务的完成,对应的用户需求可以交付。Sprint backlog,在spirnt 计划会议上生成,在开发过程中,可能会有所调整。

Sprint burndown chart – sprint 燃尽图:以坐标图表示团队在一个sprint中工作进展情况,横坐标是sprint 已进行的实际天数,纵坐标是还剩余的任务需要的时间的总和。它直观的反应了sprint 实际执行与计划的比较,以及团队离sprint 的目标完成的距离。

Release burndown chart – 发布燃尽图:以坐标图表示当前版本的需求完成进展情况,横坐标是已经进行的sprint 的个数,纵坐标是待完成的用户需求的多少。它直观的反应了产品需求的完成情况,以及团队离完成版本完全发布的距离。

Product increment -- 产品增量:Scrum 要求每一个sprint结束都产生用户可用的软件,也被称着“潜在可交付的产品增量”(Potential shippable product increment, PSPI),事实上交付与否还要受用户习惯的约束。能否每个sprint生成满足质量定义的PSPI 是Scrum 执行效果的试金石。

setup_team(Team);
define_Definition_of_Done(team, product_owner);
initial_project(&product_backlog );  

上面代码中的三步操作是Scrum 执行开始的准备条件。

Setup Team – 创建团队:创立和建设适合的团队是Scrum实施的第一步工作,团队应该满足上面定义的Scrum团队的基本属性,并形成自己的目标和愿景,理解Scrum工作模式。

Define Definition of Done – 定义完成标准:完成标准,是指一个用户需求完成应该满足什么样的标准。它是Product Owner 和 团队之间的一个约定,有了这个约定,当团队说,这个需求完成了的时候,Product Owner 将明确知道这意味着什么,比如是否包含了针对这个需求的性能测试,或是否包含相应的用户使用手册等。在实际运用中,由于条件限制,团队在一开始可能无法做到sprint 结果可交付,而会剩余一部分工作在交付前完成,如性能测试等,这一部分工作被称为“undone”的工作。完成标准在特定时间是固定的,但随着团队成熟度提高,团队应逐步扩展自己的完成标准,使其逼近向客户交付的条件,undone的部分越来越少。

Initial Project – 启动项目:项目启动,是项目进入开发阶段前的一系列准备工作,如:项目目标的设定,项目初始需求的定义和澄清,工作量的估计,风险的识别,关键设计决策的产生,开发基础设施的选择和构建等。一般由客户(如果可能),PO,团队以及相关干系人共同参与项目启动。项目启动最重要的是输出一个初始product backlog,它应该包含对其中条目的大小的估计和优先级别的设定。

上面三个操作的结果是,有了合适的Scrum团队,团队和PO之间就需求完成的标准达成一致的定义,并生成了一份初始的product backlog。有了这三个条件便可以正式进入迭代开发了。

While (!is_empty(product_backlog)) {
    run_sprint_planning_meeting(product_backlog, velocity, &sprint_backlog);
    ……

每一次while循环代表一个sprint周期,直至product backlog中所有的需求被开发完成。

Run sprint planning meeting – 进行sprint计划会议: sprint 规划会议规划这个sprint 目标和具体任务安排,它标志着一个sprint 的开始。在sprint计划会议上要依次完成以下的内容:

  1. 团队决定在接下来的的sprint 中要完成的用户需求,如过对需求存在疑问,团队应和PO进行澄清和确认。团队必须按照PO设定的product backlog的优先级别,从高到低选择,如因实现上的依赖关系,要调整需求选择的顺序,也需要和PO进行确认,以确保团队始终工作在最有价值的需求上。关于承诺多少需求,也并非取决于团队或专家的主观判断,而是根据product backlog中对每一个需求的大小估计,以及团队过去的实际开发速率(velocity),承诺相匹配数量的需求。
  2. 针对所选择需求的实现,进行简单和必要的沟通、分析。以确保第三步可以顺利的进行。
  3. 分别将每一个需求,分解成设计、开发和测试等任务。并估计每一个任务所需的工作量(通常以小时计)。

Sprint 计划会议由团队主导,但需要PO的贡献,特别是上面的第一条,需要PO的现场参与。其它条目,即使PO不在场,也应该随时可以提供远程的咨询。

作为sprint 计划会议的结果,团队选择并承诺了接下来sprint 中要完成的product backlog中的用户需求,并且,将每一个需求分解成具体的多个开发任务。这些开发任务的列表被称为sprint backlog,它是sprint 计划会议的最重要输出,接下来的工作,就是完成这些开发任务,交付对应的用户需求。

for(num_of_day = 1; num_of_day <=  Sprint_Length; num_of_day ++){
    run_daily_scrum_meeting();
    update_burndown_chart();
    do_development_activity(sprint_backlog, &product_increment);
}

在这里,每一次for循环代表一个工作日,循环的次数也即sprint 的时间长度。

Run daily scrum meeting – 进行每日Scrum 会议: Scrum 团队每天都会进行一次同步 -- Scrum 会议。Scrum 会议被限定在15分钟之内结束,每一天在同样的时间,同样的地点举行,旨在沟通同步项目开发状态,建立团队对项目的整体认识,并发现项目中的问题。会议上,每一个人都向团队回答三个问题:我昨天做了什么?我今天计划做什么?在前进的道路上有什么障碍? Scrum 会议结束后,要更新sprint燃尽图以反映团队的工作进度,和离sprint目标的距离。

Do development activity – 进行开发工作:Scrum强调团队成员间的紧密协作,原则上任务应该由团队成员主动认领,而非被分配。为此,团队需要形成相应的规则,如:在同一时刻,不应该并行开始过多用户需求开发,这样可以确保团队有明确的工作重点,也可以避免在sprint 结束时所有的需求都只是部分完成,而交付不了任何有价值的软件增量。随着开发进程的不断进展,软件增量得以生成、扩展和验证。

run_sprint_review_meeting(product_backlog, product_increment);
run_retrospective_meeting();

Run sprint review meeting – 进行sprint评审会议:Sprint评审会议又称为sprint演示会议,一个sprint结束,团队构建了包含新的用户需求的产品增量,团队在这个会议上展示过去的一个sprint所构建的产品。sprint评审会议是开放的,应尽可能邀请相关人员参加,ScrumMaster、团队、PO、市场人员、客户、管理人员、维护人员、领域专家以及关联团队等。在会议上团队对照product backlog依次演示刚刚构建的用户需求,获取参与者的反馈,这些反馈将成为未来的产品设计和规划调整的依据,以使产品更好的满足客户的需求,更好的服务于组织的业务目标。

Run retrospective meeting – 进行sprint团队回顾会议:在评审会议之后,团队进行回顾会议,评审会议是对产品的检查和调整,而回顾会议是团队对自身的检查和调整。Scrum 强调通过经验性的过程,逐步检查和调整团队的协作和工作模式,持续改善。回顾会议是Scrum 运行的十分重要的一环,其有效性是Scrum实施成功的保障。除非团队决定邀请额外人员,回顾会议一般只有团队参加,在回顾会议上,团队回顾刚刚过去的一个sprint,团队在哪些方面做得好,应该坚持;哪些方面有待改进,并挖掘其本质原因,定义具体的改进计划,以在下一个sprint去切实实施。为保证回顾会议的有效,组织和团队都应该承诺愿意做出适应性的改变。

update_product_backlog(&product_backlog, &release_burndown_chart);
update_velocity(&velocity); 

Update product backlog -- 更新产品待办事项:sprint 结束,产品待办事项内的一部分需求被交付,应该更新其状态。此外,由于PO和团队获取了更准确和详细的产品信息,product backlog 也应该相应更新,例如在sprint 回顾会议激发了用户对优先级新的认识,在同PO确认后,product backlog 的优先级定义就需要做出相应的调整。更新产品待办事项的同时,发布燃尽图也需要相应更改,以反映最新的需求完成情况。

Update velocity – 更新团队开发速率:前面提到,团队承诺多少需求的依据是团队过去的开发速率。每个sprint结束,团队的参考发速率都需要根据实际情况做修正,这将有助于更好地把握开发节奏,合理地计划未来的工作。

至此,一个sprint 结束,潜在可交付的产品增量得以交付或演示,团队和PO获取了有意义的反馈, product backlog 得到了调整,团队对工作进行了反思并定义了改进方案。这时就可以进入下一个sprint,直至交付整个产品。

总结

Scrum框架为团队敏捷实施定义了一个简单和明确的边界。在边界之内,团队探索和完善相关的管理和技术实践。我们一般建议,开始尝试Scrum的组织最初应严格遵循框架的定义,这时常会引起过于教条和形式主义的争议,团队会提出“应该抓住Scrum的实质,而不是强调形式”。问题是,只有通过严格的基本实践的学习和应用,我们才可能掌握其原则,区分哪些是实质,哪些是可以调整的形式。Ken Schwaber对此的描述是,“对Scrum规则的修改,只有在ScrumMaster 确信团队足够深入的掌握了Scrum的运行原理,有足够的技能和思维来修改规则时才可以进行”。

我们建议,那些准备实施Scrum 或者已经实施但还处于探索中的团队,可以打印出这段伪代码,对照自身的实际操作,任何与这段伪代码不符的地方,团队都应该认真思考其合理性。我们更加建议,团队在实施Scrum的过程中,深入理解和思考Scrum框架背后的价值观和原则,Scrum为什么可以和将怎样改进产品开发,为此我们需要做出哪些改变。对原则的理解和思考,有助于我们对实践的更好掌握,更快的从Scrum中受益。这也将是本文的第二部分要讨论的内容。(待续……)

* 本文部分参考了“Scrum guide”和“Scrum Primer


感谢张凯峰对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

反思调整和总结 by 陈 炼

sprint backlog在过程中的调整如果过大,往往会导致无法在预定的sprint达成目标。有时甚至是导致目标不断发生变化,团队在持续的加班中体会到强烈的挫败感。
此外,迫于项目进度压力,常常会出现scrum团队忽略总结,进而重估团队开发速率也成为水中月镜中花。

伪代码太有才了 by Jun Ran

写得好,赞一个

Re: 伪代码太有才了 by He Mian

我是作者,伪代码其实是一个小伎俩而已,目的是提供一个整体框架,作为索引。 我设想这篇文章的重点在第二部分(下),希望能把背后的原则讲清楚,以让使用者避免Scrum实施的误区。正在写,快了。

scrum说爱你不容易 by xue gang

scrum还是有难度的:(1)组建一个多功能的scrum团队不容易,大概也需要培养两三年时间。(2)制定每个sprint的任务,每个任务的dependence也要解决,对整个sprint任务的pre-study也还是需要时间的。。。问下:每个user story的评估不是很准,大家有啥好方法没?比如找到一个point.设置为1?

Re: scrum说爱你不容易 by He Mian

1. Scrum特别让人误以为很容易,这是问题。关键是要有心理准备和愿意做出改变。组件一个团队要两三年倒也不至于,可能你指的是即使不是Scrum 团队要建设团队的能力,也要这么长时间吧。

2. 任务之间肯定会有dependency,那是要靠团队在日常沟通中自行解决的。Pre-study是需要时间,也应该安排时间,一般建议站上一个sprint 5%~10%的时间,Pre-study是针对 User story 而不是针对任务。

3. User story评估不准原因很多,Scrum的标准答案是Ask the team. 不过还是可以列举一下常见的原因:a.需求就没搞清,或拆分的不很里 b. 对需求的沟通还是不够 c. 用绝对而不是相对估计了 d. 把估计当成了承诺,团队有压力。不过真正的原因还是得和团队一起分析

Re: 伪代码太有才了 by 宋 励奋

伪代码让人眼前一亮, 期待下篇!
小comment: 建议run_scrum() 有return值 :)

Sprint Planning Meeting 伪代码 by 王 辉

昨天参加CSM培训,培训师Jens Ostergaard讲到Sprint Planning Meeting时,用到一段伪代码。
foreach (PBI in ProductBacklog) // PBI means Product Backlog Item
{
PO describe PBI;
Team ask questions;
Testers describe tests;
Team brainstorms tasks;
}
也许此段可以作为run_sprint_planning_meeting的实现。

Re: Sprint Planning Meeting 伪代码 by He Mian

谢谢分享,原来想到伪代码的不只我一个。现在开始出题:
1. 请定义 Product_Backlog 结构类型。

2. 请实现 initial_project 子函数。

3. 在操作上经常出现一个问题,当团队进行Sprint Planning 时,发现对要做的需求还存在很大的不确定,其中一些甚至PO也不清楚,得同客户去确认,这让会议很低效,并浪费了团队很多时间。一个解决方案是,在上一个sprint当中的某个时刻,团队和PO对下一个sprint可能要做的user story进行预先的计划,有时称为pre-planning。问题:请改造源代码时期包含这一过程

Re: Sprint Planning Meeting 伪代码 by He Mian

对不起,敲错字了。最后一句话,时期 -> 使其

“ScrumMaster 是一个定义,但从未使用的变量”,这句话说得太差了。 by Wang Super

ScrumMaster是协调调度,保持项目进度和平衡开发人员压力的至关重要的一个角色。参看如下的一点儿伪代码

ScrumMaster.run_daily_scrum_meeting()
ScrumMaster.run_sprint_review_meeting()
ScrumMaster.run_retrospective_meeting()

if (newRequirementComing()) {
if (product_owner.wantToAddNewFeatureInThisSprint(100)) {
velocity = velocity + 100;
ScrumMaster.balanceBacklogInThisSprint(-100);
}
}

他在project manager或者说team和product_owner沟通中起到重要作用,产品经理不一定懂技术,他只对客户负责,项目经理懂技术,但不懂客户。当新的需求转化成为开发代码的过程中,ScrumMaster的作用就是保证新需求得到满足的同时不会给团队造成太大的压力。

Re: “ScrumMaster 是一个定义,但从未使用的变量”,这句话说得太差了。 by He Mian

可能说的有的极端,但还不至于太差了。但我承认,在缺乏上下文的情况下这句话可能有误导的作用,但那不是我的本意。

在表述上本意是为了强调,Scrum master 是协调而不是管理的作用,团队应该自我管理。Scrum master 的一个重要作用,就是通过自己的协调和辅导能让团队更好的自我管理,并为团队尽量创造好的外部环境,所以他事实上不是伪代码上任何一个步骤的负责人。一句话,Scrum master的责任是deliver 一个优秀的团队。

我同意你的Scrum master 的协调作用,不同意你的scrum master的调度作用。具体的我不同意你,ScrumMaster.run_daily_scrum_meeting(),这些是团队在run 不是scrum 在run,Scrum master 缺席时,也应该能照常进行。

你有可能会认为,如果scrum master连调度都不负责,那他还负责什么?这个建议看一下Michael James的Scrum master checklist( scrummasterchecklist.org/pdf/scrummaster_checkl... )。他有很多很多事情要做。

谢谢你说出你的不同观点,还有什么需要指出,或澄清的可以发信给我 mian.a.he@gmail.com

伪代码太有才了? by 魏 波

描述工作流程都要用到伪代码?这真的是水平高还是水平低啊?

Not bad by Stinebaker Vernon

不错. 用代码代表Scrum似的连老外都可看明白。由于时间关系,我用英文提两个建议。

One key thing missing: prioritization of the backlog. If we're building without prioritizing our product backlog, we're quite possibly waisting our time.

Related, I don't think:

while (!is_empty(product_backlog)){...}

is accurate. Our goal isn't necessarily to complete everything that appears in the product backlog; our goal is to deliver the highest value. I think it would be better to use:

while(product_backlog_item_hasvalue() && time_remaining()) {...}

Even if there are product items in the backlog, if they have no value (or their value is lower than their cost), why would we continue developing?

Also, can we not deliver something on a fixed date, even if there are items (those of the lowest priority) remaining in the backlog? (If there is no schedule/budget, as might be the case on an ideal project, time_remaining(){return 1}; The order of evaluation will end the loop when value is zero or negative.)

My $.02.

还是很不错!

Thanks,
Vernon

Re: Not bad by He Mian

Vernon,

Thanks!
For the first one, I assumed the priotization is included in initial_project(). Actually I explained it in the text behind, the priotization, estimation, release planning and infrastructure setup are all included in initial_project().

For the second one, I fully agree with you. If we get rid of the release concept, then we shall always focus on the Top priority items (let's say 30%) only. And release the SW as needed. This is idea model. while((!is_empty(product_backlog) is only a intermediate model, which compromise too much to the traditional way. I am considering to refactor it.

He Mian

Re: 伪代码太有才了? by He Mian

无所谓水平高低,如果有助理解就是有价值的。 伪代码只是形式,形式为内容服务。

Re: Sprint Planning Meeting 伪代码 by Chen Acan

3. 在操作上经常出现一个问题,当团队进行Sprint Planning 时,发现对要做的需求还存在很大的不确定,其中一些甚至PO也不清楚,得同客户去确认,这让会议很低效,并浪费了团队很多时间。一个解决方案是,在上一个sprint当中的某个时刻,团队和PO对下一个sprint可能要做的user story进行预先的计划,有时称为pre-planning。

Cite from <The SCRUM Primer> version 1.2:

Product Backlog Refinement
One of the lesser known, but valuable, guidelines in Scrum is that five or ten percent of each Sprint must be dedicated by the Team to refining (or “grooming”) the Product Backlog. This includes detailed requirements analysis, splitting large items into smaller ones, estimation of new items, and re-estimation of existing items. Scrum is silent on how this work is done, but a frequently used technique is a focused workshop near the end of the Sprint, so that the Team and Product Owner can dedicate themselves to this work without interruption. For a two week Sprint, five percent of the duration implies that each Sprint there is a half-day Product Backlog Refinement workshop. This refinement activity is not for items selected for the current Sprint; it is for items for the future, most likely in the next one or two Sprints. With this practice, Sprint Planning becomes relatively simple because the Product Owner and Scrum Team start the planning with a clear, well-analyzed and carefully estimated set of items. A sign that this refinement workshop is not being done (or not being done well) is that Sprint Planning involves significant questions, discovery, or confusion and feels incomplete; planning work then often spills over into the Sprint itself, which is typically not desirable.

所以准确的说:应该是“靠近上一个 Sprint 结尾的某个时刻” 而非 “上一个sprint当中的某个时刻”。 有点吹毛求疵 :) 但是符合 Agile 精神...</the>

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

16 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT