continue reading hover preload topbar hover preload widget hover preload

把复杂的事情简单化

Categories: 敏捷软件开发  |   Comments(1)

       有个人要在客厅里钉一幅画,请邻居来帮忙,画已经在墙上扶好,正准备砸钉子,这个邻居却说:“这样不好,最好钉两块木板,把画挂上面。” 他找来锯子还没有锯两三下,又说:“不行,这锯子,得磨一磨。”于是,他丢下锯子去拿锉刀。锉刀拿来了,他又发现在使用锉刀之前,必须得给锉刀安个把柄。为此,他拿起斧头到屋外的一个灌木丛里去寻找小树。就在要砍树时,他又发现生满老锈的斧头实在是不能用,必须得磨一下……当这个邻居为磨斧头找不到磨石,又去买锯子而忙乎时,画早已钉在了墙上。


      有些事情原来并不复杂,可是由于人的思维惯性最终在寻求解决方案的过程中忘记了根本目标,最终里目标越来越远。

       当然不是说传统的软件过程是离软件开发的终极目标——创建满足客户需求的可运行的应用程序越来越远了,但是很多软件公司在实施软件过程管理的过程中过于强调文档、过程步骤的作用,以至于许多项目管理人员都把精力放在了监督程序员编写众多中看不中用的文档、报告上面了。这样是不是违反了引入软件工程的初衷呢?

      让我们看看敏捷软件开发的宣言吧:


  • 个体和交互                 胜过              过程和工具

  • 可以工作的软件         胜过              面面俱到的文档

  • 客户合作                    胜过               合同谈判

  • 响应变化                    胜过               遵循计划

      强调这些原则并不是说过程和工具、文档、合同、计划毫无用处,而仅仅是为了警醒世人软件开发的根本目标之所在。


      再强调一点,强调前者不是要忽略后者,仅仅说明前者的重要性胜于后者,在进行软件过程敏捷化改造的过程中应该保证前者目标的实现,后者应该是在前者能够被保证遵循的过程上补充的。

敏捷设计原则

Categories: 敏捷软件开发  |   No Comments

      “在按照我的理解方式审查了软件开发的生命周期后,我得出一个结论:实际上满足工程设计标准的唯一软件文档,就是源代码清单。”



                                                                            ——Jack Reeves

       Reeves认为软件系统的源代码是它的主要设计文档。用来描绘源代码的图示只是设计的附属物而不是设计本身。
       软件设计是一个抽象的概念。它和程序的概括形状(Shape)、结构以及每一个模块、类和方法的详细形状和结构有关。可以使用许多不同的媒介去描绘它,但是它最终体现为源代码。最后,源代码就是设计。

      拙劣设计的症状



  • 僵化性(Rigidity):设计难以改变。很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的其他改动。

  • 脆弱性(Fragility):设计易于遭到破坏。对系统的改动会导致系统中和改动的地方在概念上无关的许多地方发现问题。

  • 牢固性(Immobility):设计难以重用。很难解开系统的纠结,使之成为一些可在其他系统中重用的组件。

  • 粘滞性(Viscosity):难以做正确的事情。做正确的事情比做错误的事情要困难。

  • 不必要的复杂性(Needless Complexity):过分设计。设计中包含有不具有任何直接好处的基础结构。

  • 不必要的重复性(Needlsee Repetition):滥用复制/粘贴。设计中包含有重复的结构,而该重复的对象本可以使用单一的抽象进行统一。

  • 晦涩性(Opacity):很难阅读、理解。没有很好的表现出意图。

      面向对象设计原则



  • 单一职责原则(The Single Responsibility Principle,简称SRP)

  • 开放-封闭原则(The Open-Close Principle,简称OCP)

  • Liskov替换原则(The Liskov Subsititution Principle,简称LSP)

  • 依赖倒置原则(The Dependency Inversion Principle,简称DIP)

  • 接口隔离原则(The Interface Segregation Interface,简称ISP)


 单一职责原则(SRP)
     就一个类而言,应该仅有一个引起它变化的原因。
     在SRP中,我们把职责定义为“变化的原因”。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的原则。
     应用FACADE或PROXY模式对设计进行重构可以帮助分离类的职责。

开放—封闭原则(OCP)
    软件实体(类、模块、函数等等)应该是可以扩展的,但是不可修改的。

    遵循开放—封闭原则设计的模块具有两个主要的特征,他们是:
   1、“对于扩展是开放的”(Open for extension)。
        这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。换句话说,我们可以改变模块的功能。
   2、“对于更改是封闭的”(Closed for modification)。
    对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者Java的“.jar”文件,都无需改动。

    这两个特征好像是互相矛盾的。扩展模块的行为的通常方式就是修改该模块的源代码。不允许修改的模块常常被认为是具有固定的行为。

   

    在面向对象的语言中,可以创建出固定却能够描述一组任意个可能行为的抽象体。这个抽象体就是抽象基类。而这一组任意个可能的行为则可以表现为可能的派生类。

    模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,所以它对于更改是可以关闭的。同时,通过这个抽象体派生,也可以扩展此模块的行为。

  

   实际上使用抽象接口比使用抽象类通常具有更好的效果。
  
   在许多方面,OCP是面向对象设计的核心所在。遵循这个原则可带来面向对象技术所声称的巨大好处(灵活性、可重用性以及可维护性)。然而,对于应用程序的每个部分都肆意地进行抽象并不是一个好主意。应该仅仅对程序中呈现出频繁变化的那部分作出抽象。拒绝不成熟的抽象和抽象本身一样重要。

 

Liskov简单替换原则(LSP)
       OCP背后的主要机制是抽象(abstraction)和多态(polymorphisim),支持抽象和多态的关键机制之一是继承。采用Liskov简单替换原则将保证系统使用了最佳的继承层次,正确使用继承不违反OCP。

      子类型(subtype)必须能够替换掉他们的基类型(base type)
    “这里需要如下替换性质:若对每个类型S的对象s1,都存在一个类型T的对象s2,使得在所有针对T编写的程序P中,用s1替换s2后,程序P行为功能不变,则S是T的子类型。”


——Barbara Liskov


       LSP让我们得出一个非常重要的结论:一个模型,如果孤立的看,并不具有真正意义上的有效性。模型的有效性只能通过它的客户程序来表现。

      在考虑一个特定设计是否恰当时,不能完全孤立地来看这个解决方案。必须要根据该设计的使用者作出的合理假设来审视它。

       1、基于契约设计

       为了使“合理假设”明确化,更好支持LSP,可以采用基于契约设计(Design Contract,简称DBC)方法。
        基于DBC方法,派生类的前置条件和后置条件规则是:
       在重新声明的派生类中的例程(routine)时,只能使用相等或者更弱的前置条件来替换原始的前置条件,只能使用相等或者更强的后置条件来替换原始的后置条件。

       2、不易察觉的违反LSP的设计
       a、 派生类的退化函数
       派生类的某些函数退化(变得没有用处),Base的使用者不知道不能调用f,会导致替换违规。在派生类中存在退化函数并不总是表示违反了LSP,但是当存在这种情况时,应该引起注意。
      b、从派生类抛出异常
      如果在派生类的方法中添加了其基类不会抛出的异常。如果基类的使用者不期望这些异常,那么把他们添加到派生类的方法中就可以能会导致不可替换性。

      LSP是保证OCP的重要原则。

 

依赖倒置原则(DIP)
     高层模块不应该依赖于底层模块。二者都应该依赖于抽象。
     抽象不应该依赖于细节,细节应该依赖于抽象。

 

      1、本质上倒置的应该是接口所有权。
      低层模块实现了在高层模块中声明并被高层模块调用的接口。

      2、依赖于抽象
      程序中所有的依赖关系都应该终止于抽象类或接口。
      任何变量都不应该持有一个具体类的指针和引用。
      任何类都不应该从具体类派生。
      任何方法都不应该覆写它的任何基类中已经实现的方法。

 

接口隔离原则(ISP)
      不应该强迫客户依赖于它们不使用的方法。

       一个对象的客户不是必须通过该对象的接口去访问它,也可以通过委托或者该对象的基类去访问它。

      如何使用ISP
      1、对客户进行分组
      常常可以根据客户调用的服务方法来对客户进行分组。这种分组方法使得可以为每组而不是每个客户创建分离的接口。

      2、改变接口
       在维护面相对象的程序时,常常会改变现有的类和组件的接口。为了减少这些改变的影响。可以通过为现有的对象增加新接口的方法来缓解,而不是去改变现有的接口。

胖类(fat class)会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求该类进行一个改动时,会影响到其他所有的客户程序。因此,客户程序应该依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定客户程序的接口,可以实现这个目标。

 


敏捷软件开发的思考

Categories: 敏捷软件开发  |   No Comments

        2003度Jolt大奖的获奖图书又有关于敏捷软件开发的——《 敏捷软件开发工具——精益开发方法 》,看来老外对敏捷的方法越来越推崇了。


      与以往的软件项目管理方法变革不同,这次在国内似乎引起的争论远超过以往。

      很多人认为传统重型的方法应该用于大型项目,而新的敏捷的方法应该只能适用于几个人的小项目。暂且不讨论目前国内到底能有多少需要近百个人年的开发项目。就纯粹从对敏捷方法的理解上就有很多误区,敏捷的方法不仅仅适用于小型项目同样适用于大型项目。容易引起误会的可能是XP,许多人一提起敏捷方法就会马上联想到XP,不要文档,代码就是设计等等。需要解释的是XP并不等同于敏捷软件开发,只是众多敏捷软件开发方法中的一种。

      “敏捷”在管理领域本来就不是什么新名词,敏捷制造、精益生产理论早就是现代企业管理理论的核心思想了。这次从传统管理领域引入到了软件项目管理领域,应该视作管理思想发展的必然。

       这次项目管理方法体系的变革跟上一次面向对象软件工程的变革有着本质不同。OOSE与传统面向过程的软件工程方法相比,更多的是在分析、设计、代码一系列方法发生了改变,整个软件工程管理思想体系没有太大改变,改变更多的是体现在具体文档和代码表达方式上。

      “敏捷软件开发”引起的变革不仅仅是几个实践方法的改变。而“敏捷”软件开发的根本在于管理指导思想发生了改变。      

    “敏捷”软件开发并不在于重新创立新的方法体系,相反,以往的UML、Design By Contact等方法依然适用。“敏捷”思想的精髓在于以人为本,充分分析过程以消除传统软件工程方法中的浪费、低效率行为,如只编写必要的文档,这样做旨在提高整个团队的警惕性,消除侥幸心理,每个人都会对外界的变化产生迅速的反应,项目管理人员也能掌握真正的项目进展情况(对比以往的开发经验,很多项目经理仅仅看程序员编写的报告,对延期、实际产品质量缺乏了解,一出问题往往无法处理)。

       所以,我们在引入“敏捷软件开发”应该仔细考虑其思想精髓,重要的是对比敏捷的原则、方法检查我们现有的过程管理方法中有哪些无用低效的环节,从而进行改进。改进应该是持续的,不能照搬国外的方法和经验,我们的环境不同,我们要把握根本的“敏捷原则”——消除、减少不必要的、低效的工作环节和工件产品。