客户与开发人员的合作伙伴关系

An excellent software product results from a well-executed design based on excellent requirements. Excellent requirements result from effective collaboration between developers and customers (in particular, actual users)—a partnership. A collaborative effort can work only when all parties involved know what they need to be successful and when they understand and respect what their collaborators need to be successful. As project pressures rise, it’s easy to forget that all stakeholders share a common objective: to build a product that provides adequate business value and rewards to all stakeholders. The business analyst typically is the point person who has to forge this collaborative partnership.


TABLE 2-1 Requirements Bill of Rights for Software Customers

You have the right to

  1. Expect BAs to speak your language.
  2. Expect BAs to learn about your business and your objectives.
  3. Expect BAs to record requirements in an appropriate form.
  4. Receive explanations of requirements practices and deliverables.
  5. Change your requirements.
  6. Expect an environment of mutual respect.
  7. Hear ideas and alternatives for your requirements and for their solution.
  8. Describe characteristics that will make the product easy to use.
  9. Hear about ways to adjust requirements to accelerate development through reuse.
  10. Receive a system that meets your functional needs and quality expectations.

TABLE 2-2 Requirements Bill of Responsibilities for Software Customers

You have the responsibility to

  1. Educate BAs and developers about your business.
  2. Dedicate the time that it takes to provide and clarify requirements.
  3. Be specific and precise when providing input about requirements.
  4. Make timely decisions about requirements when asked.
  5. Respect a developer’s assessment of the cost and feasibility of requirements.
  6. Set realistic requirement priorities in collaboration with developers.
  7. Review requirements and evaluate prototypes.
  8. Establish acceptance criteria.
  9. Promptly communicate changes to the requirements.
  10. Respect the requirements development process.

客户与开发人员的合作伙伴关系

要想开发出优秀的软件产品,必须以优质需求为基础精心制定计划。而高质量的需求则源自开发人员和客户之间良好的沟通与合作,即所谓的合作伙伴关系。然而很多时候开发人员与客户之间却是一种对立的关系。项目经理如果只考虑自己的进度而不考虑用户提出的需求,就会造成矛盾。这样的局面对谁都没有好处。

只有当参与各方都了解自己获得成功的条件,并且理解和尊重合作者的成功条件时,合作才能取得成功。然而,随着项目承受的压力不断增大,涉众各方很容易忘记大家的目标其实是一致的:开发出具有较高商业价值的成功的软件产品,给所有涉众带来回报。

软件客户的权利法案(见表2.1)列出了10项权利。在项目需求工程的实施过程中,客户可以理直气壮地向需求分析员和开发人员提出这些要求。客户每拥有一项权利都意味着软件开发人员和需求分析员承担一项对应的义务。 软件客户的义务法案(见表2.2)则列出了需求过程中客户对需求分析员和开发人员承担的10项义务。也许有人更愿意将其视为开发人员的权利法案。


表2.1 软件客户的权利法案

客户有权利

  1. 要求需求分析员使用客户的语吉
  2. 要求需求分析员熟悉客户的业务,了解客户对系统的目标
  3. 要求需求分析员把需求收集过程中客户提供的信息组织成书面的软件需求规格说明
  4. 要求需求分析员解释需求过程生成的所有工作结果
  5. 要求需求分析员和开发人员尊重客户,始终以合作和专业的态度与客户进行互动
  6. 要求需求分析员和开发人员为需求和产品实现提供思路和备用方案
  7. 要求开发人员实现能让产品使用起来更容易、更有趣的特性
  8. 调整需求,便于重用已有的软件组件
  9. 在提出需求变更时,获得对变更的成本、影响及二者权衡关系的真实评估
  10. 获得满足功能和质量要求的系统,这些要求必须事先告知开发人员并征得其同意

表2.2 软件客户的义务法案

软件客户有义务

  1. 为需求分析员和开发人员讲解业务并定义业务术语
  2. 提供需求,阐明需求,通过与开发人员的交互将需求充实完善
  3. 对系统需求的描述必须详细、准确
  4. 需要时,及时对需求做出决断
  5. 尊重开发人员对需求成本和可行性的评估
  6. 与开发人员协作,为功能需求、系统特性和用例设置优先级
  7. 审阅需求文档,评估原型
  8. 发现需要变更需求时,及时与开发人员沟通
  9. 按照开发组织的变更控制过程提出需求变更
  10. 尊重需求分析员在需求工程中使用的过程

作为项目计划的一部分,客户和所有参与开发的人员都应该仔细阅读这两张表,并达成致看法。忙碌的客户也许不愿意参与需求工程(这是对第二项责任的逃避)。但是我们知道,缺少用户的参与会使生成不符合需求的产品的风险大为增加。需求开发的每一位参与者都应该了解并承担自己的义务。如有疑感应该进行协商,使每个人都清楚各自应负的责任。这样能够减少以后因一方不愿意或不能提供对方所需而产生的矛盾。

注意不要指望项目涉众天生知道如何合作进行需求开发。必须花时间讨论如何最有效地进行协作。

软件客户的权利法案

权利之一:要求需求分析员使用客户的语言

需求的讨论必须以客户的业务需求和业务工作为中心,使用客户的业务用语。客户可以通过词汇表向需求分析员提供业务术语。这样在与需求分析员交谈时,客户就不用为那些计算机行话感到头痛了。

权利之二:要求需求分析员理解客户的业务和目标

通过与客户交流获得需求,需求分析员能够更充分地理解客户的业务以及如何让产品适合业务需求。这将帮助开发人员开发出满足客户要求的系统。客户可以邀请开发人员和需求分析员参观他和同事的工作。如果正在开发的系统是用来替代现有系统的,开发人员应该像客户那样实际操作现有系统,从而了解现有系统如何适合客户的工作流程,知道哪些地方可以改进。

权利之三:要求需求分析员编写软件需求规格说明

需求分析员对来自不同客户的信息进行整理,把用例同业务需求、业务规则、功能需求、质量目标、对解决方案的建议等内容区分开来。需求分析最终应提交出一份软件需求规格说明(SRS)。SRS 是开发人员与客户就目标产品的功能、质量和约束达成的协议。应选择客户容易理解的方式组织和编写SRS. 客户可以通过审阅规格说明和其他形式的需求说明来确保它们准确并完整地表达了自己的需求。

权利之四:听取对需求工作成果的解释

需求分析员也许会使用不同的示意图来配合SRS 文本对需求进行描述。第11章中介绍了这样儿种分析模型。这些对需求的不同表达方式很有用,因为有时图解能更清晰地表述出系统某些方面的行为,例如工作流程。这些图解看来也许比较陌生,却不难理解。客户可以要求需求分析员解释每张图(或其他形式的需求开发的工作成果)要表达什么,图中符号的含义,以及如何检查图解是否有错。

权利之五:得到需求分析员和开发人员的尊重

如果客户和开发人员不能相互理解,讨论需求将是一件令人沮丧的事。合作能够帮助双方认清对方面临的问题、参与需求开发过程时,客户有权要求需求分析员和软件人员尊重他们的想法,并且珍惜他们为项目成功所付出的时间。同样,客户也应该尊重开发团队中每-位成员,他们都在为项目成功这共同目标而共同努力。

权利之六:听取开发人员对于需求及如何实现需求的想法和备用方案

需求分析员应该了解客户现有的系统为何不能很好地满足他们的业务流程需要,从而保证新的系统能够更高效满足新需要。精通业务的需求分析员时常能够对客户的业务流程提出改进建议。富于创造力的需求分析员还能提出客户未曾想到的功能,从而增加新软件的价值。

权利之七:描述使产品易于使用的特性

客户可以要求需求分析员留意用户功能需求之外的软件特性。这些特性,即所谓 的质量属性,可使软件更易于使用,更受欢迎,从而可让用户更快捷地完成任务,用 户常常要求产品“用户友好”、“稳定”、“高效”。这些术语过于主观,对开发人 员帮助不大。因而需求分析员应该让客户列出具体的特性以实现这些要求。

权利之八:为实现重用而对需求做出调整

需求常常是灵活的。需求分析员也许知道有现成的软件组件大致符合客户描述的部分需求。需求分析员应该把这种情况告诉用户,让他们选择是否对需求做出修改,以便开发人员能够重用已有的软件。如果客户能够调整需求,实现合理的重用,就可以节约时间和金钱。如果想在产品中集成一些现有的商业软件,需求就必须具备- 定的灵活性,因为已有的组件很少能完全符合客户的需求。

权利之九:获得对变更成本的真实估算

如果知道还有开销更小的方案,客户会作出不同的选择。每当一项需求变更被提出时,都必须对它的成本和影响做出评估,这样才能做出合理的商业决策,决定是否批准该变更。客户有权要求开发人员对成本、影响和二者的权衡关系做出评估。开发人员不能因为自己不愿实现某一变更而有意夸大其成本。

权利之十:得到满足功能和质量需求的系统

这是大家都希望项目达到的圆满结果。但有两个前提:客户将开发正确产品需要的所有信息明确告知了开发人员:开发人员也让客户清楚了所有的选择和约束。客户一定要说明他们的所有假设和期望:否则,开发人员很可能无法满足客户的需求。

软件客户的义务法案

义务之一: 为需求人员和开发人员讲解业务

开发小组依靠客户为他们讲解客户的业务概念和术语。讲解业务的目的不是要把业务分析员培养成该领域的专家,而是帮他们理解客户的问题和目标。不能要求业务分析员掌握客户的业务中细微和深层的内容。客户认为肯定应该掌握的知识,需求分析员可能并不知道。如果不说明有关这类知识可能会在随后的过程中导致问题。

义务之二:花时间提供并阐明需求

手客户都是大忙人, 而参与需求开发的客户往往又是其中最忙的人。尽管如此,他们还是有义务投入时间去参与产品开发过程、自由讨论、会谈以及其他需求获取活动。有时分析员可能自认为已经理解了客户的观点,后来才发现还需要进一 步的说明。对开发和改进需求的这种迭代方法要有耐心。因为选代体现了人类交流的复杂性,是软件项目成功的关键。客户要容忍那些看似愚意的问题:优秀的需求分析员会选择那些能让用户开口的问题进行提问。

义务之三:对需求的说明必须具体和准确

客户给出的需求常常是含糊和混乱的,这是因为准确说明细节既乏味又费时。但是,开发过程的某时刻, 必须有人来解决歧 义和不准确的问题,这时客户是解决问题的最佳人选。否则,就只有指望开发人员能做出正确的猜测。

在SRS中暂时使用待定(TBD)标志是个好方法。TBD标志表示还需要进一步的研究、分析以及更多信息。不过,TBD有时也用于表示某项需求难以解决,没人愿意去处理它。客户应尽量把每项需求的意图阐述清楚,以便需求分析员可以在SRS中将其准确表达出来。如果无法准确描述,客户应该同意采用能达到所需准确度的方法。这些方法通常需要用到原型技术。使用原型技术,客户和开发人员可采用增量和迭代方法合作进行需求定义。

义务之四:及时做出决定

正如承包商在建房时所做的那样,需求分析员会要求客户作出很多选择和决定。这些决定包括:解决来自多个客户的需求间不致的问题, 在相互矛盾 的质量属性间作出选择,以及评估信息的准确性。相关的客户必须及时做出决定,否则开发者常常会因为得不到用户的决定而没有信心继续开发,从而由于等待答复造成进度的拖延”

义务之五:尊重开发人员对成本和可行性的评估

货所有软件功能的实现都需要成本。开发人员最有资格来估算这些成本,尽管他们中很多人并非熟练的评估员。客户需要的某些特性可能在技术上不可行,或者实现的成本过于高昂。有些需求可能提出了在操作环境中不可能达到的性能,或者要求访问不在系统内的数据。开发人员可能会带来关于成本和可行性的坏消息,但客户还是应该尊重他们的判断。

有时客户可以重写需求,让需求可行,成本更低。例如,要求某一动作“瞬间”发生是不可能的,但更具体的时间需求(“在50毫秒内”)则是可以实现的。

责任之六:为需求设置优先级

很少有项目能有足够的时间和资源来实现所有想要的功能。需求分析的一项重要内容就是确定哪些功能是必需的,哪些是有用的,哪些对客户是可有可无的。对于设置优先级,客户应该起主导作用,因为开发人员无法确定某项需求对客户究竞多重要。开发人员将提供关于每项需求的成本和风险的信息,帮助确定最终的优先级。客户确定了需求的优先级后,开发人员可以据此在合适的时间内,以最低的成本创造出最大的价值。

利用给定的时间和有限的资源,开发人员究竟能实现多少客户提出的功能?关于这点客户要尊 重开发小组的判断。谁都不愿意听 到自己的要求不能在项目中得到满足的结论,但事实如此。项目的决策者将不得不在下列各项之间作出选择:根据优先级缩小项目范围,延长开发周期,投入更多的人力和财力,或者在质量要求上作出让步。

义务之七:审阅需求文档,评估原型

第15章将指出需求审阅是最有价值的保证软件质量的活动之一。让客户参与审阅是评估需求是否具备完整性、正确性和必要性这些所需特性的惟一方法。审阅活动也使客户代表有机会向需求分析员提供反馈,指出其工作是否符合项目的要求。如果客户代表对文档中的需求是否准确没有把握,他们应尽早通知相关负责人,并提出改进建议。

仅仅通过阅读需求规格说明,很难想象出系统是如何运行的。为了更好地理解需求并找出满足需求的最佳方法,开发人员常常会为目标产品构造原型(prototype)。客户对这些初步的、不完整的、试验性的实现的反馈可为开发人员提供极具价值的信息。必须认识到原型不是实用的产品,不能强迫开发人员提交原型,不能把它当成具有完整功能的系统。

义务之八:将需求变更及时告知开发人员

不断地变更需求会严重威胁开发小组按进度交付高质量产品的能力。变更是难免的,但一项变更在开发周期中提出的时间越晚,它造成的不良影响就越大。变更会导致代价高昂的返工。如果在软件开发已走上正轨后才提出新的功能,项目进度就会被延误。客户一旦意识到需要更改需求,就应马上通知与其合作的需求分析员。

义务之九:遵循开发组织的变更过程

为了将变更的负面影响降至最低,客户就必须遵循项目中定义的变更控制过程。这样做可以保证变更请求不会被遗漏,并保证对每项提出的变更的影响能够得到分析,以及以一致的标准对所有被提出的变更进行判断。因此,负责业务的涉众就能够做出明智的业务决定,把合适的变更结合到项目中。

义务之十:尊重需求分析员使用的需求工程方法

收集和验证需求是软件开发过程中一个最大的难题。需求分析员使用的各种方法都有其理论基础。虽然需求活动也许会让你感到麻烦,但花在理解需求上的时间始终是一种很值得的投入。如果客户能够理解并尊重需求分析员用于需求开发的方法,整个需求过程就会变得更轻松。客户可以要求需求分析员解释为什么需要某些信息,为什么要让自己参加这些与需求相关的活动。

关于“签字”

客户和开发人员之间合作伙伴关系的核心是就产品的需求达成致。很多组织把在需求文档上签字作为客户认可需求的标志。需求批准过程的所有参与者都应该明白签字意味着什么,否则会出现很多问题。问题之一是客户 代表把在需求文档上签字视作毫无意义的仪式:“他们给我张纸, 纸上的条横线下印着我的名字。 我在那上面签了名,要不然开发人员就不肯开始编码。”这种态度导致了后来的矛盾冲突:当用户提出更改需求被拒绝或对交付的产品不满时,他会说:“不错, 我是在需求上签了字,可我根本没时间把它们从头到尾读遍。我相信你们这帮家伙,可你们却让我失望了。”

另一个关于签字的问题是开发经理把签字作为冻结需求的方法。每次客户提出更改需求,他就指着SRS提出反对:“可你已经在这些需求上签了字, 我们正照着它们开发。如果有其他的需求,你该早点儿说。

这两种态度都忽略了一个事实:不可能在项目初期就能明确所有的需求,需求肯定要随时间的推移而发生变化。批准需求是终结需求开发过程的正确方式,但是,参与者必须对签字的准确含义达成致的理解。

注意不要把签字当成武器,应该把它作为项目的一个里程碑。对于签字之前应进行哪些活动,以及签字对将来变更的影响,各方应形成明确致的理解。

签字不仅仅是仪式,更重要的是建立需求协议的基线,即某一时刻需求的瞬态图。需求规格说明书的签字的潜台词应该是:“ 我同意这份文档反映了我们此刻对项目需求的最佳理解,它描述的系统能够满足我们的需求。进一步的变更将 在此基线的基础上,依照项目定义的变更过程进行。我知道变更被批准后,我们可能要重新协商对项目成本、资源和进度的约定。”需求分析员定义好基线后,应该将需求置于变更控制之下。这样,开发小组就能在必要时以种可控制的方式对项目范围做出修改。这方式包括分析每项变更对进度等项目成功因素造成的影响。

项目开发过程中, 当需求中的疏漏被发现,或市场和业务需求发生变化时,客户和开发人员之间容易产生摩擦。对需求基线的一致理解则有助于减少这种摩擦。客户可能会担心一旦批准SRS,他们就不能再提出修改,因此就拖延对需求的批准,从而导致分析工作陷入瘫痪这种可怕的困境。设置基线是很有意义的,它能给所有主要的涉众带来信心:

用明确的协议来结束 前期的需求开发活动,能够帮助客户和开发人员形成合作伙伴关系,携手走上项目成功之路。

参考

微服务:Spring Cloud Actuator

1
2
3
4
5
6
7
8
9
10
11
12
spring.management.security.enabled=true
spring.security.basic.enabled=true
spring.security.user.name=admin
spring.security.user.password=admin

management.endpoints.web.base-path=/
management.endpoints.web.exposure.include=*

info.app.name=@project.name@
info.app.description=@project.description@
info.app.version=@project.version@
info.app.spring-boot-version=@project.parent.version@

参考

微服务:Sleuth

微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。

参考

微服务:zuul统一异常处理

我们详细介绍了Spring Cloud Zuul中自己实现的一些核心过滤器,以及这些过滤器在请求生命周期中的不同作用。我们会发现在这些核心过滤器中并没有实现error阶段的过滤器。那么这些过滤器可以用来做什么呢?接下来,本文将介绍如何利用error过滤器来实现统一的异常处理。

过滤器中抛出异常的问题

首先,我们可以来看看默认情况下,过滤器中抛出异常Spring Cloud Zuul会发生什么现象。我们创建一个pre类型的过滤器,并在该过滤器的run方法实现中抛出一个异常。比如下面的实现,在run方法中调用的doSomething方法将抛出RuntimeException异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.baoguoding.apigateway;

import com.netflix.zuul.ZuulFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ThrowExceptionFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(ThrowExceptionFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        log.info("This is a pre filter, it will throw a RuntimeException");
        doSomething();
        return null;
    }

    private void doSomething() {
        throw new RuntimeException("Exist some errors...");
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.baoguoding.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;


@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayApplication {

	@Bean
	public AccessFilter accessFilter() {
		return new AccessFilter();
	}

	@Bean
	public ThrowExceptionFilter throwExceptionFilter() {
		return new ThrowExceptionFilter();
	}

	public static void main(String[] args) {
		SpringApplication.run(ApiGatewayApplication.class, args);
	}

}

运行网关程序并访问某个路由请求,此时我们会发现:在API网关服务的控制台中输出了ThrowExceptionFilter的过滤逻辑中的日志信息,但是并没有输出任何异常信息,同时发起的请求也没有获得任何响应结果。为什么会出现这样的情况呢?我们又该如何在过滤器中处理异常呢?

解决方案一:严格的try-catch处理

运行网关程序并访问某个路由请求,此时我们会发现:在API网关服务的控制台中输出了ThrowExceptionFilter的过滤逻辑中的日志信息,但是并没有输出任何异常信息,同时发起的请求也没有获得任何响应结果。为什么会出现这样的情况呢?我们又该如何在过滤器中处理异常呢?

回想一下,我们在上一节中介绍的所有核心过滤器,是否还记得有一个post过滤器SendErrorFilter是用来处理异常信息的?根据正常的处理流程,该过滤器会处理异常信息,那么这里没有出现任何异常信息说明很有可能就是这个过滤器没有被执行。所以,我们不妨来详细看看SendErrorFilter的shouldFilter函数:

1
2
3
4
5
6
7
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        // only forward to errorPath if it hasn't been forwarded to already
        return ctx.containsKey("error.status_code")
                && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
    }

可以看到该方法的返回值中有一个重要的判断依据ctx.containsKey(“error.status_code”),也就是说请求上下文中必须有error.status_code参数,我们实现的ThrowExceptionFilter中并没有设置这个参数,所以自然不会进入SendErrorFilter过滤器的处理逻辑。那么我们要如何用这个参数呢?我们可以看一下route类型的几个过滤器,由于这些过滤器会对外发起请求,所以肯定会有异常需要处理,比如spring-cloud-netflix-core-1.1.4.RELEASE.jar中的org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter的run方法实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        this.helper.addIgnoredHeaders();
        try {
            RibbonCommandContext commandContext = buildCommandContext(context);
            ClientHttpResponse response = forward(commandContext);
            setResponse(response);
            return response;
        }
        catch (ZuulException ex) {
            context.set(ERROR_STATUS_CODE, ex.nStatusCode);
            context.set("error.message", ex.errorCause);
            context.set("error.exception", ex);
        }
        catch (Exception ex) {
            context.set("error.status_code",
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            context.set("error.exception", ex);
        }
        return null;
    }

可以看到,整个发起请求的逻辑都采用了try-catch块处理。在catch异常的处理逻辑中并没有做任何输出操作,而是往请求上下文中添加一些error相关的参数,主要有下面三个参数:

其中,error.status_code参数就是SendErrorFilter过滤器用来判断是否需要执行的重要参数。分析到这里,实现异常处理的大致思路就开始明朗了,我们可以参考RibbonRoutingFilter的实现对ThrowExceptionFilter的run方法做一些异常处理的改造,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
    @Override
    public Object run() {
        log.info("This is a pre filter, it will throw a RuntimeException");
        RequestContext ctx = RequestContext.getCurrentContext();
        try {
            doSomething();
        } catch (Exception e) {
            ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            ctx.set("error.exception", e);
        }
        return null;
    }

参考

近期文章

相关页面