浅谈软件开发项目的质量控制

一、引言

  J.M.Juran认为质量控制是一个常规的过程,通过它度量实际的质量性能并与标准比较,当出现差异时采取行动。由此,DonaldReifer 给出软件质量控制的定义:软件质量控制是一系列验证活动,在软件开发过程的任何一点进行评估开发的产品是否在技术上符合该阶段制定的规约。

  二、软件缺陷分析

  在IEEE 1983 of IEEES tandard729 中对软件缺陷下了一个标准的定义:从产品内部看,软件缺陷是软件产品开发或维护过程中所存在的错误、毛病等各种问题;从外部看,软件缺陷是系统所需要实现的某种功能的失效或违背。

  软件缺陷是一个更广的概念,而软件错误(error)属于缺陷的一种———内部缺陷,往往是软件本身的问题,如程序的算法错误、语法错误或数据计算不正确、数据溢出等。软件错误往往导致系统某项功能的失效,或成为系统使用的故障。软件的故障、失效是指软件所提供给用户的功能或服务,不能达到用户的要求或没有达到事先设计的指标,在功能使用时中断,最后的结果或得到的结果是不正确的。

  软件缺陷的产生主要是由软件产品的特点和开发过程决定的,如软件的需求经常不够明确,而且需求变化频繁,开发人员不太了解软件需求,不清楚应该 “做什么”和“不做什么”,常常做不合需求的事情,产生的问题最多。同时,软件竞争非常激烈,技术日新月异,使用新的技术也容易产生问题。

  从软件自身特点、团队工作和项目管理等多个方面进一步分析,就比较容易确定造成软件缺陷的一些原因细节,归纳如下:

  (一)软件自身特点造成的问题。

  需求不清晰,导致设计目标偏离客户的需求,从而引起功能或产品特性上的缺陷。系统结构非常复杂,而又无法设计成一个很好的层次结构或组件结构, 结果导致意想不到的问题或系统维护、扩充上的困难;即使设计成良好的面向对象的系统,由于对象、类太多,很难完成对各种对象、类相互作用的组合测试,而隐藏着一些参数传递、方法调用、对象状态变化等方面问题。

  新技术的采用,可能涉及技术或系统兼容的问题,事先没有考虑到。

  对程序逻辑路径或数据范围的边界考虑不够周全,容易在边界条件出错或超过系统运行环境的复杂度。

  系统运行环境的复杂,不仅用户使用的计算机环境千变万化,包括用户的各种操作方式或各种不同的输入数据,容易引起一些特定用户环境下的问题;在系统实际应用中,数据量很大,可能会引起强度或负载问题。

  对一些实时应用系统,要进行精心设计和技术处理,保证精确的时间同步,否则容易引起时间上不协调,或不一致性所带来的问题。

  没有考虑系统崩溃后系统的自我恢复或数据的异地备份等问题,从而存在系统安全性、可靠性的隐患。

  由于通信端口多、存取和加密手段的矛盾性等,会造成系统的安全性或适用型等问题。

  (二)软件项目管理的问题。

  缺乏质量文化,不重视质量计划,对质量、资源、任务、成本等的平衡性把握不好,容易挤掉需求分析、评审、测试等时间,遗留的缺陷会比较多。系统分析时对客户的需求不是十分清楚,或者和用户的沟通存在一些困难。开发周期短,需求分析、设计、编程、测试等各项工作不能完全按照定义好的流程来。开发流程不够完善,存在太多的随机性和缺乏严谨的内审或评审机制,容易产生问题。文档不完善、风险估计不足等。

  (三)团队工作的问题。

  不同阶段的开发人员相互理解不一致,软件设计人员对需求分析结果的理解偏差,编程人员对系统设计规格说明书中某些内容重视不够,或存在着误解。设计或编程上的一些假定或依赖性,没有得到充分的沟通。项目组成员技术水平参差不齐,新员工较多,或培训不够等原因也容易引起问题。

  软件缺陷是由很多原因造成的,但如果把这些缺陷按整个软件开发周期的结果———软件产品(市场需求文档、规格说明书、系统设计文档、程序代码、测试用例等) 归类起来,统计结果发现,规格说明书是软件缺陷出现最多的地方。       

软件产品规格说明书是软件缺陷存在最多的地方,主要原因如下:

  用户一般是非计算机专业人员,软件开发人员和用户的沟通存在较大困难,对要开发的产品功能理解不一致。

  由于软件产品还没有设计、开发,完全靠想象去描述系统的实现结果,所以有些特性还不够清晰。

  用户的需求总是在不断变化的,容易引起前后文、上下文的矛盾和需求描述的不一致。

  需求分析没有得到足够重视。在规格说明书设计和写作上投人的人力、时间不足。排在产品规格说明书之后的是设计,编程排在第三位。而许多人印象中,软件测试主要是找程序代码中的错误,从分析看,这是一个误区。

  如果从软件开发各个阶段所能发现的软件缺陷分布来看,也主要集中在需求分析、系统设计阶段,代码阶段的错误要比前两个阶段少。

  三、分析及应对措施

  (一)定义合适的项目过程。

  软件过程是指开发和维护软件产品的活动、技术和实践的集合。在以计算机网络为基础的现代社会信息化背景下,过程管理作为现代企业管理的先进思想和有效工具,随着外部环境与组织模式的变化而变化。因此,作为一个好的软件项目过程,必须针对企业和项目的实际情况,确定软件项目运作流程,定义软件功能及相关性能,明确各阶段的进入条件和退出条件,进行有效的过程控制与管理,在提高软件开发的效率和项目的成功率的基础上进一步保证所开发软件的质量。

  (二)明确项目需求。

  对于任何软件项目过程而言,需求不仅是一个不可避免的环节,也是软件开发的基础。往往用户需求明确、变更少的项目的成功率就高,而那些用户需求混乱、变更频繁的项目几乎从一开始就注定了失败的命运。但是,在现实生活中,用户需求总是在开发进入中后期时,因为各种不同的原因而发生变化。这就给软件项目过程实施带来不确定因素。在涂装项目中,由于前期需求不明确以及随意变更需求,导致项目组在开发阶段不停的返工,进而造成代码质量低下,测试拖期等一系列问题。因此,在项目实施过程中,为了保证软件开发的顺利进行和最后交付的产品质量,应该对项目需求变更进行管理

  1、需求说明书要描述明确、详尽。由于与用户沟通的需求人员并不是最后的开发人员,所以有可能导致开发人员对需求说明书的理解与用户真正的意图会产生一定的偏差。另外,当项目在进行到开发(编码)阶段时,由于记忆的缺失,对当初所作的需求说明书的理解也会产生偏差。

  2、要对需求变更进行管理。通常需求分析完成后项目就进入开发阶段,用户可能会因为市场或策略的变化而提出需求变更的要求。此时,若是合理变更则有利于项目实施,但有时所作的变更可能会影响项目整体的设计和开发,造成项目进度的延期。对于这一情况,项目组应该积极与用户沟通,制订需求变更说明书,在双方都认可的情况下方可实施。

  3、在项目开发过程中要尽早明确用户需求,有些内容一时无法确定则应该暂缓该部分的开发,尽量降低因需求变更而带来的风险。

  (三)代码走查。

  软件质量在很大程度上依赖于代码质量。在实际环境中对于同一项目而言,由于项目组成员的编程能力、习惯、风格、对需求的理解和个性的不同,所开发的代码质量也不尽相同。再加上一些难以预测的人为因素,由此带来的隐患将严重影响代码质量,最终造成软件质量低下,使得用户无法正常使用并为以后的维护带来更大的工作量和难度。

  在软件开发过程中可以根据需要引进代码走查。每周在规定的时间内,轮流让程序员讲解其所开发代码的主要部分。这项措施一方面可以从侧面促使程序员本人注意所开发代码的质量,另一方面在走查过程中可以获得他人的意见进一步改善代码效率,使开发成员共享项目实施过程中问题解决的思路和方法,使得软件质量更有保障。

  (四)进行正式的测试,并形成制度测试就是对软件产品的检验。

  项目测试分集成测试和系统测试,主要进行功能测试、健壮性测试、性能-效率测试、用户界面测试、安全性测试、压力测试、可靠性测试、安装/反安装测试等活动。测试过程通常在模拟环境中进行。要尽可能覆盖整改项目过程,从最初的需求到部署阶段,都应该制订详细的计划并编制相应的文档,如测试计划、测试用例文档、测试报告等。通过测试活动,尽可能早得发现每个阶段中软件存在的缺陷,以方便后续阶段的实施。总之,一切测试应该符合用户需求。

帮助了解前后端分离的项目,如何解决登录问题

方案一:使用token

  1. 前端把accountpassword,提交到服务端的登录api
  2. 服务端验证正确后,生成一个token,并把tokenuserId,存在缓存里(推荐redis数据库),然后把token返回给前端。
  3. 前端每次的请求头中带token,这样就能够轻松的实现

方案二:使用cookie

  1. client发送username和password到server
  2. server验证成功后, 写cookie到client,然后返回ok的json, 其中cookie的key要存储在redis中,value就是用户信息, 并且要设置key的超时时间,如:60分钟
  3. client收到ok后, 进行相应的业务操作, 以后每次请求server都会自动带上cookie, 不用你写代码
  4. server端的filter(你肯定用filter来实现)中会每次验证传过来的cookie的key在redis中是否存在, 有就代表登录成功过可以操作, 没有就返回错误标识注意: 在登录成功后, 每次调用服务器接口时候, 都要为redis的key进行续期,如60分钟
  5. 当redis的key超过60分钟, 自己会删除这个key, 那么再次请求server时, 就会收到需要登录的返回值
  6. 当用户主动退出系统的时候, 也要在server中删除redis的key

浅谈接口测试get与post

 接口的概念从IT的角度出发,主要是子模块或者子系统间交互并相互作用的部分。从形式上来看各种应用程序的API(最著名的Windows 系统的API),硬件的驱动程序,数据库系统的访问接口,再到后来的Webservice接口,http rest接口。虽然接口的形式各有不同,但是从测试角度来说,需要测试的内容大致是相同的,功能,性能,安全。
  我们常说的api就是接口的意思,现在常用的web项目,app项目的接口都是基于http请求的,有些系统内部之间调用的接口一般不需要我们测试,这些很多是基于jar包那种类型的接口,只了解到这就差不多了。
  接口类型常见的有get,post,put…类型。get类型的接口一般是指获取信息的接口,比如列表查询的功能,点击查询按钮就调用一个get接口,然后把信息返回出来。就是指把内容从服务器拉下来。
  post类型一般是提交表单的功能,比如注册、上传、发布帖子之类的就是post接口。就是指把内容推到服务器上去。
  接口测试的策略:接口测试属于功能测试,也可以看做是需要了解部分代码的灰盒测试。测试流程是:1测试接口文档。2.根据接口文档编写测试用例(用例编写方法完全可以按照黑盒测试的用例编写规则来编写,如:边界值、正交表等等设计方法)。3.执行测试,查看接口返回的接口数据是否正确,主要检查返回的接口是否和接口文档中定义的一样,还有要检查返回的数据是否和数据库中的保持一致。
  eg1.https://api.douban.com/v2/book/search?q=”, 这是一个豆瓣的查询书籍信息的开放的api,是一个get型接口。q=”,单引号里就是查询的参数,这里给参数为《百年孤独》,然后在浏览器中访问下面地址:https://api.douban.com/v2/book/search?q=’百年孤独’, 返回的就是接口信息。返回信息如下:
1
  内容很多,是一个json字符串。可以把所有东西复制下来,用在线解析的站点:json.cn这个网站中解析出来就很好看了。如图:
2
  右边就是解析出来的格式,就是一个json字符串中嵌套了一个名叫books的数组。我们测试的时候就是根据接口文档,查看返回的这些数据是否是我们预期,判断这些数据是否是预期一般还需要了解项目的数据库,然后根据条件查询数据库,看接口返回的数据和数据库中查出来的是否一致。
  eg2.get型的接口可以直接通过浏览器访问,参数就带在地址的后面以‘?’连接。但是post的就不行了,要用专门的工具来测试,常用的推荐jmeter和soapUI。
下面介绍简单的首页Jmeter进行简单的post接口测试方法。
  1. 进行打开jmeter工具的,然后右键测试计划-threah-线程组进行添加线程组。
3
  1. 然后进行右键线程组-》逻辑控制器-》http请求的选项,添加http请求。
4
  1. 然后使用抓包的工具进行对http协议post协议上抓包,可以抓到的IP的地址和端口号
5
  1. 然后在jmeter中的服务器名称中添加ip地址,在端口号中进行添加端口号。
6
  1. 然后进入抓包工具overview中可以看到的接口的连接路径位置,
7
  1. 把链接路径填写到jmeter中的路径位置当中。
8
  1. 现在就是获取到的body的内容,在stream中进行可以获取到时body内容,把这个内容复制。
9
  1. 选中body data中进行粘贴到当前的输入框中。这样就设置完成了,下面就查看结果树来执行结果。
10

Orika-Java Bean 映射框架

Orika

今天主要介绍一款Java Bean 映射框架—Orika

在此之前,先抛出几个问题,以便加深对orika的理解

  • Q1:什么是Java Bean 映射框架?
    • A:简单地说,就是从一个Bean A映射(转换)成另一个Bean B。举个粟子,实际开发场景中,很多时候都用到VO、BO、PO、DTO等POJO,我们需将其中一个POJO转换成另一个POJO。
  • Q2:如果要从一个Bean转换成另一个Bean,使用b.setXXX(a.getXXX()),手动转换不可以吗?
    • A:当然可以,但可以试想一下,当Bean的属性非常多(十几二十个)的时候,手动get set 不旦麻烦,还容易出错,浪费时间,纯体力劳动。
  • Q3:我听说过BeanUtils.copyProperties(a,b)也可以实现Bean属性的拷贝,它跟Orika有什么区别?
    • A:无论spring的BeanUtils.copyProperties还是apache.commons的BeanUtils.copyProperties,其底层实现机制都是运行时反射,其性能可想而知。在映射场景不频繁的场景或许还可以用一用,但是在一个频繁转换的场景,这样的性能损失,是不能接受的。

Orika是什么?

Orika是一个Java Bean映射框架,它将数据从一个对象递归拷贝到另一个对象。在开发多层应用程序时可能非常有用。其目的在于简化一个对象到另一个对象之间的映射过程。

Orika能干什么?

Orika能提供如下的功能:

  1. 映射复杂和深层结构的对象
  2. 无论是“Flatten”(扁平的)或“Expand”(深层次的)对象,都可以将嵌套属性映射到顶级属性,反之亦然。
  3. 随时快速地创建映射器,并使用自定义来控制部分或全部映射
  4. 处理代理或增强对象(如Hibernate或各种模拟框架)
  5. 通过一个配置,就可以实现双向映射
  6. 可以将任意POJO属性映射到List、Arrays、Map

Orika实现原理

Orika的设计思路就是预先通过javaassist把Java Bean之间的映射关系一次性生成目标拷贝方法代码。这样就可以避免在Bean映射环节一次次的读取映射规则。这就是Orika性能提升根本的原因。因此,理论上以生成的目标Java代码来运行映射是拷贝模式所能取到性能最大值的方法

注:Orika是我做组卷服务时候新学的一个框架,当时发现BeanUtils.copyProperties太慢了,根本不能忍。偶然的机会发现了这么一个东西,这真是刚想瞌睡就有人送上枕头。但是网上关于这个框架的资料非常少,没什么太实用的资料。可能原因有俩,一是知道的人少,二是它太简单易用了,根本就不需要详细地了解它,简单地配置后就能使用。

在此附上一个不知道几年前的参考资料:http://wangbt5191-hotmail-com.iteye.com/blog/1632444

时间有限,今天只做简单的理论介绍,下次将给出实战性的代码。

                                                                                                                           周雄

                                                                                                           搞技术,我们是认真的

浅谈对“分布式事务”的理解

  • 什么是数据库“事务”?

一句话简述一组sql操作,要么都成功执行,要么都不执行。

  • “事务”存在的意义是什么?

保证数据一致性。
eg:A转账给B 100块钱,A余额减少100 并且B余额增加100。确保不出现 A减少100 B没增加 或 A没减少 B增加的情况。


~~~~~~~~~~上面讲的啰嗦了一些基础知识 下面步入正题~~~~~~~~~~


  • 什么是“分布式事务”?

一组sql操作,分别写在不同的系统中,甚至这些sql在操作不同的数据库,在这种情况下,要么都执行成功,要么都不执行。

  • 举例描述当前问题

eg:机构开教师账号。
这个动作在系统中分为两步。
步骤1:将教师账号的业务数据写入DB。
步骤2:将开账号这个动作以及消耗爱豆数记录到流水表中,并减少机构爱豆余额。

目前步骤1由业务系统实现,步骤2由财务系统实现,是两个微服务,这时想使用事务并不是简单的一个@Transactional就能实现了。

  • 数据一致性处理方案

1.强一致性:

a.业务整合:
将开账号业务与财务扣费逻辑写在一个服务中,这也是老系统的实现逻辑,虽然保证了数据一致性 也规避了分布式事务,但把多个相关性不大的业务放在同一个系统中,业务职责不清晰,不利于维护。(极不推荐)

b.使用分布式事务 3次握手方案:
(1)询问服务方是否可执行 得到回应(yes/no)。
(2)服务方回应可执行后,发起预备执行申请,得到预备申请是否成功(true/false)。
(3)服务方回应申请成功后,发起提交申请,得到提交申请是否成功(true/false)。

3次握手
优点:保证数据强一致性,执行成功后立即查询,即可获得正确数据。

缺点:这种实现方式会造成代码量庞大,耦合性高。而且非常有局限性,因为有很多的业务是无法很简单的实现回滚的,如果串行的服务很多,回滚的成本实在太高。

2.终一致性:

MQ消息队列

(由于本人对此方案了解较少,并且远没有达到对网上的技术“一看就懂”的地步,所以此方案的原理下周再续。)

技术与方案参考地址:

http://www.cnblogs.com/dinglang/p/5679542.html

http://blog.csdn.net/bluishglc/article/details/7612811

粗谈Patton的《软件测试》

Ron Patton的《软件测试》是一本经典的软件测试方面的书籍,作者是一位有着多年软件测试经验的测试工程师,他深入浅出的讲了许多他对软件测试的见解与知识。虽然这是一本十多年前出版的老书,许多技术已经被更替,但是他对于测试的讲解还是令我受益匪浅,我总结了一些可能对大家有用的部分。
首先我们了解一下Patton对软件缺陷做的定义,他认为符合下面5个规则的才能叫软件缺陷:
1.软件未实现产品说明书要求的功能。
2.软件出现了产品说明书指明不应该出现的错误。
3.软件实现了产品说明书未达到的功能。
4.软件未事先产品说明书虽未明确提及但应该实现的目标。
5.软件测试员认为软件难以理解、不易使用、运行速度缓慢,或者最终用户认为不好。
那么为什么会出现软件缺陷
1.首先可能是产品说明书不够全面,经常更改,或者整个开发小组没有很好的沟通。为软件做计划是极其重要的,这一步没做好就可能会产生上述第4、第5类的缺陷。
2.然后可能是设计,这是程序员规划软件的过程,这个过程好比是建筑师规划蓝图的过程,这个过程产生缺陷的原因跟产品说明书是一样的–随意,易变,沟通不足
3.最后是编码错误,通常代码错误可以归咎于软件的复杂性、文档不足、进度压力或者普通的低级错误。
软件测试的目的就是找到软件的缺陷,但是书中却说软件是不可能完美的,软件测试也不仅仅是技术的问题。
1.完全测试是不可能的。主要有以下4个原因:
a.输入量太大
b.输出结果太多
c.软件执行路径太多
d.软件说明书是主观的,而使用者是客观的
2.软件测试是有风险的行为。
软件测试不可能测试所有的状况,不完全测试的地方又有可能存在软件缺陷。软件终归是要发布的,所以测试需要停止,但如果过早的停下来,就还有一部分没有测试到。那么选择一些关键的部分测试就是冒险的行为。测试员就要学会一个关键的思想:如何把巨大规模的测试减少到可控制的范围,以及针对风险做明确的抉择。
3.测试无法显示潜伏的软件缺陷。
就像除虫公司来家里杀虫不能把房子里所有的虫子杀完–总有些虫子藏在墙里没有找到。软件测试也没办法找到一些隐藏起来的缺陷。
4.找到的缺陷越多,就说明软件缺陷越多
很多时候中找到一个缺陷,就会接二连三地找到更多。其原因是:
a.程序员也有心情不好的时候,烦躁的人容易犯一些自己无法发现的小错误。一个缺陷的出现很可能表明附近还有更多的缺陷。
b.程序员容易犯同样的错误,每个人都有自己的习惯,在没有发现这个习惯性错误之前,程序员可能已经造成好几个一样的缺陷。
c.某些缺陷很可能是冰山一角,一个找不到原因的、不明显的错误很可能后面隐藏了一个巨大的缺陷。
5.杀虫剂怪事
就像虫子会对杀虫剂产生抗体一样,测试员用同样的方法测程序也会漏过新的错误,所以测试员要不断编写新的、不同的测试程序。
6.并非所有软件缺陷都要修复,有许多原因会使一个团队放弃修复某些缺陷:
a.没有足够的时间,产品必须要马上上线,但还有一些缺陷没有修复。
b.不算真正的软件缺陷。测试可能错误的把某些缺陷当作功能看待,而没有上报。
c.修复风险太大。当软件本身脆弱复杂时或者产品发布迫在眉睫,修复一个缺陷很肯能导致新一个甚至更多个缺陷,而这个缺陷又不会造成太大的危害。那么放弃修复这个缺陷无疑是明智的选择。
7.产品说明书从没有最终版
IT行业竞争太大,更新太快,所以我们的产品也在不断的更新,测试也要不断的测试新的功能并且保证以前的功能没有发生新的缺陷。
8.软件测试员在团队中不受欢迎
测试的目标就是寻找程序员的错误,不断的否定同事成果的人总是不受欢迎的。那么就需要我们做到以下几点:
a.早点找出缺陷,尽早的找出缺陷,使其产生的影响尽量减小。
b.控制情绪,诚然,测试员喜欢自己的工作,找出缺陷时非常大兴奋,但是也不能兴冲冲的去找同事:“你的程序出bug了”,更不能到处宣扬,他肯定会不高兴的。
c.不要总是报告坏消息,当同事做出一个厉害的功能时,应该毫不吝啬的夸奖。
希望我总结的一些内容能对大家有一点帮助。

Failed to save registry store file, cause: Can not lock the registry cache file错误日志解决

[] 2017-04-14 09:59:38,015 [DubboSaveRegistryCache-thread-1] WARN [com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry] – [DUBBO] Failed to save registry store file, cause: Can not lock the registry cache file /root/.dubbo/dubbo-registry-zookeeper.aixuexi.com.cache, ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties, dubbo version: 2.8.3, current host: 101.201.78.226
java.io.IOException: Can not lock the registry cache file /root/.dubbo/dubbo-registry-zookeeper.aixuexi.com.cache, ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties
at com.alibaba.dubbo.registry.support.AbstractRegistry.doSaveProperties(AbstractRegistry.java:193)
at com.alibaba.dubbo.registry.support.AbstractRegistry$SaveProperties.run(AbstractRegistry.java:150)

出现这个的原因是服务向ZK注册的同时,会缓存Consumer的列表,写入user.home/.dubbo/dubbo-registry-” + url.getHost() + “.cache 这个文件,当在同一个机器上启动多个Provider的时候,就会出现文件锁争用的问题,报上面这个错误。

解决办法:

既然是由于竞争文件锁导致的,那么让服务模块各自缓存自己的cache文件就可以避免这样的问题了。

具体做法是:在provider的xml配置文件中加入 file=”${catalina.home}/dubbo-registry/dubbo-registry.properties” ,如下:

<dubbo:registry address=”${zookeeper_registry}” transporter=”curator” file=”${catalina.home}/dubbo-registry/dubbo-registry.properties”/>

浅谈java设计模式

一、设计模式的分类

总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:

 

 

二、设计模式的六大原则

1、开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

5、迪米特法则(最少知道原则)(Demeter Principle)

为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

原则是尽量使用合成/聚合的方式,而不是使用继承。

 

个人见解:在开发一些通用型工具时,或者一些特殊的业务场景,用一些设计模式会大大提高可扩展性和高可用性,通过复用已经公认的设计,我能够在解决问题时取得先发优势,而且避免重蹈前人覆辙。我可以从学习他人的经验中获益,用不着为那些总是会重复出现的问题再次设计解决方案了。设计模式还为我们提供了观察问题、设计过程和面向对象的更高层次的视角,这将使我们从“过早处理细节”的桎梏中解放出来。大多数设计模式还能使软件更容易修改和维护。其原因在于,它们都是久经考验的解决方案。所以,它们的结构都是经过长期发展形成的,比新构思的解决方案更善于应对变化。而且,这些模式所用代码往往更易于理解,从而使代码更易维护。

java内存区域介绍

1、什么是JDK、JRE、JVM

JVM(Java Virtual Machine):主要功能就把字节码解释成具体平台上的机器指令执行,目的是实现跨平台,一次编译,到处运行。
JRE(Java Runtime Environment):支持Java程序运行的标准环境,Java SE API + JVM。
JDK(Java Development Kit):java程序设计语言 + java虚拟机 + java API类库。
 lip_image002

2、Java内存区域

Jvm在执行java程序的过程中会把它所管理的内存划分成若干个不同的数据区域,他们有各自的用途和生命周期,如下图
ip_image004

程序计数器

它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变计算器的值里获取下一条要执行的指令的,分支、循环、跳转、异常处理等等都是依赖计数器来完成的。Java虚拟机规范中唯一没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈

每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。
ip_image006 ip_image008
  1. 局部变量表:是一组局部变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java文件编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量
  1. 操作数栈:JVM底层字节码指令集是基于栈类型的,所有的操作码都是对操作数栈上的数据进行操作,对于每一个方法的调用,JVM会建立一个操作数栈,以供计算使用。和局部变量一样。操作数栈的最大深度也是编译的时候写入到方法表的code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long、double。
  1. 动态连接:每个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。
  1. 返回地址:当一个方法被执行后,有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法方式称为正常完成出口(Normal Method Invocation Completion)。另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的调用都产生任何返回值的。     无论采用何种方式退出,在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用都栈帧的操作数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等
  1. 附加信息:虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与高度相关的信息,这部分信息完全取决于具体的虚拟机实现。在实际开发中,一般会把动态连接,方法返回地址与其它附加信息全部归为一类,称为栈帧信息。
JAVA虚拟机规范中,对这个区域规定了两种异常状况:1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;2、如果虚拟机可动态扩展,如果扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常。
如递归方法层级过多就会出现StackOverflowError

本地方法栈

本地方法栈( Native Method Stack) 与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如 Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和 OutOfMemoryError 异常。

Java堆

对于大多数应用来说,Java堆( Java Heap)是Java 虚拟机所管理的内存中最大的一块。 Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java 虚拟机规范中的描述 是:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、 标量替换优化技术将会导致一些微妙的变化发生, 所有的对象都分配在堆上也渐渐变得不是那么“ 绝对”了。
Java 堆 是 垃圾 收集 器 管理 的 主要 区域, 因此 很多 时候 也 被 称做“ GC 堆” 从内 存 回收 的 角度 来看, 由于现在收集器基本都采用分代收算法, 所以 Java 堆 中 还可以细分为:新生代和老年代;再细致一点的有Eden空间、 From Survivor空间、 To Survivor 空间等。从内存分配的角度来看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区( Thread Local Allocation Buffer, TLAB)。不过无论如何划分,都与存放内容无关,无论 哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快 地分配内存。
根据 Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是 连续的即可,就像我们的磁盘空间一样。 在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的( 通过- Xmx 和- Xms 控制)。 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区

方法区( Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时将抛出 OutOfMemoryError 异常。
运行时常量池( Runtime Constant Pool)是方法区的一部分。 Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外,还有一项信息是常量池( Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量 池中存放。运行期间也可能将新的常量放入池中,如 String 类的intern() 方法。

直接内存

直接 内存( Direct Memory) 并不是 虚拟 机 运行时 数据区 的 一部分, 也不 是 Java 虚拟 机 规范 中 定义 的 内存 区域。在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 类, 引入 了 一种 基于 通道( Channel) 与 缓冲区( Buffer) 的 I/ O 方式, 它可 以 使用 Native 函数 库 直接 分配 堆 外 内存, 然后 通过 一个 存储 在 Java 堆 中的 DirectByteBuffer 对象 作为 这块 内存 的 引用 进行 操作。 这样 能在 一些 场景 中 显著 提高 性能, 因为 避免 了 在 Java 堆 和 Native 堆 中 来回 复制 数据。
本机 直接 内存 的 分配 不会 受到 Java 堆 大小 的 限制, 但是,既然 是 内存, 肯定 还是 会受 到 本机 总 内存( 包括 RAM 以及 SWAP 区 或者 分页 文件) 大小 以及 处理器 寻址 空间 的 限制。也可能 导致 OutOfMemoryError 异常 出现。