RSS 2.0 Feed
Design & Architecture
摘要:今天看到朋友wayfarer写的一篇文章,大概是关于protected的“保护性”问题的,看过之后内心有些想法想与大家分享,如果大家不嫌弃,敬请往下看。拍脑壳所想之   ——戏言面向对象说到protected这个词,我不可避免的就会想到一个概念——面向对象。那么什么是面向对象呢?其实我个人认为面向对象这个概念是一直在发展变化的,到了今天,面向对象这个词也许让它叫做面向抽象更加贴切。在刚刚建立面向对象这个概念的时候,大概连创造者对于到底什么是面向对象都不是很清楚。要搞清楚面向对象(编程,或者设计)是什么,也许得看看过去的软件代码都是什么样的。 I.公元前软件开发在最初的十几二十年里面,基本上就是面向过程的。面向过程的核心内容有两项,一个是控制流,另外一个就是数据流。在这一个时期里面,软件界最大的发展估计是数据结构与算法这两个“科目”了,这两者分别对应着两个“流”。在面向过程的软件代码里面,执行主体是过程或者函数。一个过程所代表的就是一个动作,动作的对象(这里还不是面向对象的对象)是一些数据,数据也许通过参数得到,也许通过全局变量得到,还有一些常量或者预定义值。如果我们仔细想一下,就会发现这是一个“动宾”结构的体系,比如说Basic里面比较著名的“Line (x1, y1) -(x2, y2)”,翻译成自然语言就是“画一条(x1,y1)到(x2,y2)的直线”。类似的例子还有很多,比如C语言里面的“printf("%s\r\n", "Hello world!");”。可是主语在哪里? II.创世纪面向过程的代码里面并没有突出一个主语,很多时候这个主语也并非不存在,就像上面的例子里面,主语就是一个屏幕。可是如果我们需要往打印机里面画一条直线呢?(或者打印一个"Hello world"。)在面向过程的代码里面,我们就不得不自己写一个PrintLine的函数。(C语言往文件里面些东西就是fprintf。)如果我们要往远程设备上画一条直线,那还要写一个RemoteLine,如果……不需要我多说,您也会觉得麻烦。围绕着这样一个问题,人们就开始思考:是否能够把主语明确的给写出来?是否能够让我们少做一点重复性的工作?后来就有了面向对象这个东西,在面向对象是一个“主谓宾”结构的世界,绝大多数东西都有一个主语,比如我们所熟悉的“g.DrawLine(pen, pt1, pt2);”,由于我们有了“主语”,我们就可以让不同的东西,用相似的方法做相似的事情。如果光是把g换成h,仅仅解决了“在这个窗口画”与“在那个窗口画”的问题,如果我们希望他能够在其他类型的空间上画,我们还需要容许主语的类型可以不完全相同。但我们要解决的更多问题还是概念相同之处,例如打印机的g和屏幕的g都能够画线,因此有了诸如继承、封装等概念。这就是面向对象的一切了吗? III.改革开放随着面向对象概念的诞生,春风沐浴大地。正如上帝说要有光,于是有了光。上帝说要有毒蛇,于是有了毒蛇,上帝说要有苹果,于是有了苹果,结果亚当和夏娃吃了这个上帝创造出来的苹果受到了上帝的“惩罚”。真不明白,既然上帝不希望亚当和夏娃吃这个苹果,为什么还要创造这么一个东西?其实上帝创造这个苹果当然是不希望他们“吃”这个苹果,创造这个苹果实际上是为了产生浪漫的爱情以及其后千秋万代的动人故事。如果你把这个苹果仅仅看成是吃的,那么接下来你看到的就是痛苦的惩罚。如果你看到的是背后动人的故事,那么浪漫甜蜜等美好之辞就会充满你的大脑。面向对象也一样,他的核心意义并不在于你把东西封装成什么样了,不在于有什么东西被继承出来了,最重要的是他容许我们用抽象的方式来构建一个软件。比如当我们写代码写到:stream.Write(buff, 4, buff.Length - 4);或者hashbuff = hasher.ComputeHash(buff);我们是否需要关心stream到底是什么,hasher用的又是什么算法呢?如果我们由始至终,在做相应的东西的操作都用相同的stream对象和hasher对象,任务是否都应当能够正确完成呢?应该是能够正确完成的,因为这正是我们的期待。如果让我们来设计某一个stream,是否应该从这个角度去考虑如何设计这一个类呢?如果我们定义这个stream变量,是否应该更抽象一点呢?考虑这么一个函数:void DoSomething(FileStream stream, MD5CryptoServiceProvider hasher, byte[] buff) {...}如果写成如下形式将会更加灵活,也更加符合面向对象(面向抽象)的真实含义:void DoSomething(Stream stream, HashAlgorithm hasher, byte[] buff) {...}换句话说,所有的封装、继承、接口等等,实际上是为了提供抽象能力而存在的。如果我们把protected当作保护“某些方法的存在”这个秘密的话,那就大错特错了。保护这些秘密严格说来应该是密码学的职责,而不是面向对象的职责。 IV.回顾历史面向对象的核心是面向抽象,但我们看到,实际发展的过程并非如此。我们在过去有着太多错误的概念了,比如说这个面向对象技术的面向对象,就太容易让我们认为,这项技术的核心就是面向对象。于是很多时候我们写一个“面向对象”的程序充斥的过度的对象,泛滥的继承,以及不知道为什么的封装。并且不少开发者,包括我在内,都曾经认为所谓的面向对象就是把一些要素抽象成对象,进行封装,然后从某个基类派生出万物。好比有一个基类叫做物体,派生出活物与死物,活物派生出细菌病毒植物动物,动物里面有猴鸡狗猪和人,人里面有张三李四王二麻子(还有个娃)。没错,面向对象当然得包括这些,但是这不是全部,更不是根本。根本就是在于我们写某些东西的时候,不需要关心具体的对象是什么,只需要知道至少它应该是一个什么。比如上一节当中的例子,DoSomething只需要知道stream是一个流,而hasher是一个哈希算法提供者就够了。至于具体提供的是什么样的流和哈希算法,则不应当是我们关心的,而是使用我们这段代码的用户所关心的。如此一来,我们就可以在设计这一段我们所关心的功能的时候,不需要考虑过多的、过于具体的、不断变化的问题。仔细想想,我们是否真的已经明白了面向对象的核心所在呢? V.封装保护的是什么面向对象的封装并非保护你的秘密,而是防止被错误使用,是为了明确划分问题的界限。就“保护”这个词而言,更进一步的讲,它并非对使用该对象的用户(下面称为用户)做出使用某个成员的授权,而是对延展该类的设计人员(下面称为设计人员)做出延展问题领域的授权。现在让我们回过头来看一下wayfarer所写的例子: class Base    {        protected void Print()        {            Console.Write("This is protected method in Base Class!");        }    }    class Derived:Base    {        public new void Print()        {            base.Print();        }    } class OtherClass    {                [STAThread]        static void Main(string[] args)        {            Derived d = new Derived();            d.Print();            Console.ReadLine();        }    } 这个例子确实是非常容易迷惑人的,曾经,我也被这样的问题所困扰。在解决这个困扰之前我们首先要弄清楚下面两个问题:protected是什么?new又是什么?protected 很好回答,他表明该成员容许在派生类当中被使用,但不允许使用本类对象的用户代码直接使用。实际上是对设计人员的有限度授权,和对用户的拒绝授权。而new也并非难以回答,比如说:他是为了在没有override的情况下造成一种被重写了的假象。如果您真的这么认为,那就掉入了幻觉的漩涡当中去了。事实上new的作用并非一个trick,让你可以造成各种各样的假象,或者企图绕过某些使用与设计的授权。new的作用仅仅是为了解决一个命名冲突的问题,也就是说new所指定的成员实际上与基类的同名成员毫无干系,只是非常抱歉的跟他重名了只好声明此Print非彼Print。如果您真的企图用new来制造trick假象的话,终究是要撞掉你的门牙的。在我举出“撞掉你的门牙”的例子之前,请容许我首先给出一个正确使用new关键字的场景。不知道各位有没有真正的研究过.NET Framework里面interface呢?如果研究过,对于下面的这个问题应当不是非常难以回答。     public interface IFoo    {        bool Bubble();    }    public class Boo    {        public void Bubble()        {        }    }    public class Foo : Boo, IFoo    {        public bool Bubble()        {        }    } 上面这个代码会在Foo的Bubble函数上面产生一个警告,但是仍然能够编译通过。为什么能够编译通过呢?这个问题留给读者自己琢磨了。解决这个警告的办法有两个:一个是显式实现接口IFoo;可是如果我不希望通过显式的方式来实现该接口,那么就只能够在Foo的Bubble函数前面添加一个new修饰符,告诉编译器我知道他们有冲突,但是我还是希望选择用这种方式来完成它们。这两个Bubble相同的名称给我们一种它们之间有什么联系的错觉,事实上 new bool Bubble() 与 bool new_Bubble() 的含义接近,和Boo里面的void Bubble可以看作毫无关系。如果你觉得有关系的话,那么下面的我将举出一个例子让你碰一鼻子灰。     public class Boo    {        public void Bubble()        {            Console.WriteLine("Boo sheet");        }    }    public class Foo : Boo    {        public new void Bubble()        {            Console.WriteLine("Foo sheet");        }    }    class Program    {        static void Main(string[] args)        {            Foo obj = new Foo();            Test(obj);            Console.ReadLine();        }        static void Test(Boo obj)        {            obj.Bubble();        }    } 你猜你会说Boo sheet呢,还是Foo sheet?为什么会这样也请自个儿思考一下。wayfarer所举的那个例子,看起来确实容易造成困惑,或者会让大家觉得这里有一个暴露protected函数的bug,但事实上并非如此。还记不记得前面我说过了,protected是一个授权问题,而非保密问题,这一个说法应该能部分解决您的困惑。而上面的Boo sheet例子表明,实际上你并没有暴露那个protected函数,因为你仍然无法直接从一个指向Derived实例的Base变量上面寻求到使用Print函数的方法。如果您还记得我前面说过的面向抽象这个概念,也应该意识到,您写的Derived类只是对Base类的补充,而用户一般应该用Base变量来使用您的对象,而不是Derived变量,除非他认为他需要使用Derived提供而Base不提供的功能。如果说我不使用new关键字,而是把Derived的Print函数命名为PrintBase,那是否算是会引起“暴露”基类成员的Bug呢?显然不是。此时如果用Base变量仍然无法访问Print,用Derived变量则仍然可以访问到PrintBase(并最终调用Print)。还记得派生是为了延展问题领域的边界吗?这里将Base的一个受保护函数暴露出来,就是延展了问题领域的边界。设计Base的人认为,Print的功能是对象的内部事务,而Derived的设计人员则认为Print功能应该是外部与内部之间的事务,此时是否仍叫Print已不重要了。(P.S.: 容许派生类使用,却不允许被暴露,这是根本不可能的事情,即使new关键字不存在也一样。而真正能够称之为“暴露”的是反射邦定/反射调用等。可惜我们还是不能够称之为Bug,因为这正是“反射”这个设计所期望的功能,而非不小心造成的有害副作用。)说到这里,我想protected和new的问题应该已经讲完了。还有什么疑惑吗? VI.回到未来回过头来再说说面向抽象,以及面向接口。 前面已经提到了面向抽象了,不知道大家是否有更多的感想。抽象到了头是什么?当然不是什么都没有,不是虚空太极。抽象的本质是描述某个主语能够完成一组什么动作,这些动作构成了什么样的功能。如果我们从这样的一个角度去想,就会发现接口能够很好的完成这样的任务。比如说IList,它表达的是“一个列表”这样的抽象,这个抽象能够提供一组相应的动作,比如"object this[int index];" 能够取出或设置列表当中的第n项内容。只有拥有IList接口所定义的成员,才能够表明这个物体确实能够称之为“一个列表”。“服务”这个词也许能够更加深刻的表达上述的含义:class ArrayList : IList {...}这样的定义表明,ArrayList提供“列表”相关的服务。如果我们在定义变量和参数的时候更多的使用接口,而不是具体的类,那么我们的代码将拥有更大的自由度。这个时候我们关心的事某个对象是否能够提供我所需要的服务,而不关心他到底是什么。在面向对象刚刚开始的时候,我们在这个方面走进了一个误区,就是用多继承来解决上面的这个需求。比如我们可以在C++里面看到Stream派生自IStream和OStream,这么做也能够解决问题,也许同样能够表达“服务”这个含义。但是我觉得这样做还是会引起许多不必要的麻烦,比如同名称冲突等。现代的理论甚至直接告诉我们继承他不是一个好东西,如果能够用引用来代替继承,那就不要继承,如DesignPattern里面的Decorate等。 真正未来的理论是什么,我不知道。但是至少我看到面向接口的设计思想比“面向对象”要更为先进,而目前真正能够这样思考的人远比知道如何封装继承的人要少得多。从这个角度讲,那也算是未来的技术,至少是未来需要普及的技术。(现在COM不就是这样一种思想吗?) ...[阅读全文]

posted @ | Feedback (24) | Filed Under [ .NET 技术内幕 Design & Architecture ]

摘要:最近在看两本书,一本是敏捷软件开发,另外一本是微软团队的成功之路。两本书都是还没有看完一半,但是都有了不少的体会,觉得自己的思路开阔了不少。以前总以为做架构师,或者做项目经理一类型的职业,都需要有非常深入的技术知识。后来发现不是这么一回事,这种高级位置上面的职业需要有一种总体的把握能力。正确的讲,并不是不需要深厚的技术知识作为基础,相反,技术知识非常重要,但是处于不同的层面,就有不同的东西需要关注。在架构层面,或者项目层面上,需要关注的实际上并不是技术,而是诸如组织团队、分配资源、管理项目等等。在这两本书里面我们都可以或多或少的看到背后所关注的“人”的问题,怎么样让人发挥最大的创造力,使团队成功的关键之一。当然大家的创造力必须统一到一个大方向上面,这就是领导者的职责所在。有时候我们会有这么一些经历:上司不赋予你和你的团队以明确的权利和责任,整个项目不关注与用户的沟通,项目进度和想象当中的差别很大,以及团队内部不良情绪在蔓延。如果你和你领导的团队没有明确的权利与责任,那就比较容易做事缩手缩脚,对于一些明显会出问题的地方没有人愿意指出或者不知道该向谁指出,当真正出现问题的时候没有人能够承担,即使有人承担了,那也会有一种替罪羊的感觉。在这么一种环境里面,每个人都会觉得什么时候倒霉的事情就会落到我的头上了。如果拥有了明确的权利和责任,还需要用合适的方式去分配。其实自己最清楚自己的实力,如果让大家坐下来进行商讨,也许会得到更满意的结果。每一个人能做什么不能做什么,不应该决定于他所在的位置或者在公司呆得时间的长短,而应该是专业方面的知识水平。如果你把一些其实你没有能力控制好的事情把握在自己的手上,最后有可能倒霉的是自己。项目最终是否成功,是看你对设计是否满意呢,还是看用户对需求的实现是否满意?明显是后者。你的产品赚不赚钱,看你的用户满不满意,用户则只关心他想要的东西你有没有给他,至于里面的技术细节则是一点都没有兴趣关心,因此开发的时候一定要立足于满足需求这一点。然而过去的软件工程方法,在这方面我看是有缺陷的。里面虽然有需求分析,但是这里的分析一共只进行一次,并且期待着尽量在整个开发阶段封闭需求。之前我的一个Post也说到了,除了需求随着时代的变化会变化之外,客户看到了一个发布之后就可能对以前的需求有了新的了解,也会产生变化。从这个方面来说,封闭需求就是不明智的一件事情。然而封闭需求还会导致一件非常不好的事情,它会让我们轻易的陷入从一开始就把各个细节设计清楚。事实证明,很多时候不要说客户,连我们这些开发人员对于整个东西是什么,而我们却总是对自己的思考能力过于高估,而对想象能力过于低估。最后我们想出来很多很多的功能,但是有好一部分却不不符合需求,此外还会有很多没有用的东西出现。开发的过程需要尽快发布新的原型,让用户去进行实际体验,看看有什么不符合的。其实传统的软件工程学里面也说到,项目越是进行到后面的阶段,修改编码尤其是修改需求变化方面的代码代价就越大,这一句话确实是对的。然而过去的很多方法比如瀑布模型,却企图把需求封闭在一个特定的阶段(需求分析阶段,顶多是到设计阶段),一旦到了编码就不允许对需求甚至设计进行修改。这是一个愚蠢的方法,除非你确实是这一个方面的专家,有着丰富的经验,并且引领着行业的潮流,你说了就算。不幸的是很多时候不是你说了算,而是客户说了算,就算你说你的设计多名优秀,客户说不要那就是不要。封闭需求、设计之后,一旦用户对最终产品不满意,那么实际上还是需要回到需求分析阶段,这个时候代价就非常的大了。所谓的敏捷开发的其中一个目的就是希望让需求变化来的越早越好,减少在开发后期才出现的需求变化。不停地进行迭代,尽量缩短每次发布的周期。在每一次迭代完成之后,让用户去亲身感受一下,可以让你知道这一阶段的设计是否就是客户想要的东西。因此一个项目要成功,和用户进行沟通实际上是很重要的,这种沟通越频密越好。有人就会说了,我的用户在十万八千里以外,不可能经常来我们这里进行体验。如果说请求对方指派代表是不可行的,那么也需要考虑在项目团队内部设立一个专门进行模拟客户的角色,这个人必须熟悉这一个市场。如果没有人熟悉,那么就专门找一个人来熟悉这方面的工作。这一个角色总得由人来担任,就像编码人这个角色总得由人来担任一样。如果我们能够真正的从客户这个角度来考虑问题,我们就会发现我们以前的很多设计方法是有问题的。比如说我们会产生一大堆的文档,从细小到如何对数据进行排序的函数开始设计代码,在设计里面为以后可能出现的需求变化留下“钩子”。为什么说这样做有问题?我们首先看看文档,难道用户需要的是文档吗?需要看看你的FooStory是派生还是引用JokeImplement的设计文档吗?还是TempleteMethod/MVC/Decorate之类的词语在某个文档中出现会让他升官发财?统统都不是!他要的是能够正确运行并且提供他所需要的功能的软件。软件才是中心,不是吗?有人也许会说,UML之类的东西是为了让我们的设计更加清晰。可是这些UML图最终还是要转化为代码的,如果你的代码设计的充分好,就会有比较好的自解释能力,就算什么地方有不太容易理解的地方,在代码里面添加简单的注释基本上都能够解决问题。反过来说,当你的程序因为Bug或者其他原因需要进行调试和修改,你觉得你首先要读的是UML图还是代码呢?代码永远都只有一种解释,但是文档则难免会有二义性存在。思考完这些问题,我得出一个结论:我们应该围着代码转,然后产生文档,而不是围着文档转,然后产生代码。接下来看看设计的方向。因为我们错误的从一开始就把整个需求确定下来,甚至到了毛细血管的程度,所以我们会不自觉地首先实现最为底层的东西。这个惯性很好解释,因为我们需要编译通过。如果class a里面使用了class b的实例,如果首先写class a,肯定无法立即编译通过。如果我们要一次过完成一个可编译的版本,那么从上面开始写,三天三夜不眠不休都不可能完成。所以我们就从class b开始我们的工作。但是这样却需要等你将整个系统的所有部分(至少是主要部分)都完成了之后,才可能发布一个可以给用户演示的版本,需要的时间通常会非常漫长。如果我们反过来从顶层开始往下写,只要我们设法保证每天都能够产生一个可用版本,那么随时都可以拿出来给用户演示。实际上一些很细节的功能用户一开始并不会很清楚,所以我们也不可能很清楚,一些东西也许并不会出现,另外一些东西也许没有考虑进去,还有一些则并不是你原来想象的那样。如果一开始就埋头做哪些细节的功能,很可能最后会发现,有很多东西其实就是在浪费你的青春。至于怎么进行自上往下的设计,留着以后再讲。另一个我们比较容易犯的错误,就是留“钩子”。这个错误我也一直在犯,并且总是忍不住会犯。很简单,你的经验告诉你,需求是会变化的,因此需要考虑为以后的需求变化预先进行设计。这是一个非常愚蠢的想法,当我明白这一点之后我简直是无地自容。前面我们已经看到了,在整个开发的过程中,需求就会不断地变化。这种变化就像一串随机序列,根本就无法把握。你以为它会是那样的,甚至可能客户一开始也那样以为,接下来没几天客户就说不是那样的。如果你能够在一开始就能预测这种变化的方向,你就有能力应用瀑布模型,你就可以封闭需求,你就可以简单的面对客户今天说要这个明天说不要那个的情况。事实上明显不是这样的,需求的更改不可能按照你的设想去进行的。如果说今天你要设计的软件的需求,你都不可能预见,那么你凭什么那么自信的认为你知道明天的需求要变成什么?“即使最后发现需求确实不是向着你想的方向发展,那这些代码顶多是没有用,难道还会有害么?”我认为是有害的,因为它影响了你的重构。很多时候我们解决某一类特定的问题可能会有好多的方法,比如我们可以Templete或者Strategy等等,每一种解决方法都有它的侧重点和副作用。假设我们把Templete用在了这些地方,到最后也许我们的需求变化要求我们使用Strategy,那么要从整个设计里面拔除已有的Templete相关的代码将会是痛苦的。实际上问题可能还会更严重,假如你这个Templete是在一个组件里面,拔除这个Templete很可能会对另外一个使用这个组建的系统产生影响,最后你不得不修改另外几个系统,重新deploy。当然,这个也许是极端的情况,但无论如何它肯定会对你未来的修改或者重构造成障碍。好了,今天只是一个粗略的笔记,详细的内容日后再写,来日方长嘛。本来还想画一些图,让页面更加rich一点,但是已经霸占JGTM的19'一天了,不好意思霸占下去。回到另外一台17'的显示器,就完全没有了画图的激情与欲望了。还是看看书算了……...[阅读全文]

posted @ | Feedback (23) | Filed Under [ Design & Architecture ]

摘要:这个问题我想也许有人的答案都跟我以前的答案差不多,不就是解决实际编码当中所遇到的问题吗?不就是解决怎么设计一个程序的问题吗?确实是这样,但这只是表面现象。如果没有深入的挖掘问题的本质,就可能会错误的是用这些知识。最近在JGTM2004这里工作的时间,我学到了不少东西,有了更高的觉悟,对这个问题的理解就是其中一方面。以前我就没有仔细去想过这个问题,并且站在一个实际做开发的人员的角度来看,自认为这个东西就是为了减轻开发人员的负担,并且为最终产品提供更加优良的特性。其实这个只是一方面,并且就我目前的理解觉得可能还是一个次要的方面。为了阐述这个问题,我不妨举一个具体的例子:某天你接到一个项目,客户说,我们需要一个对树型结构的数据节点进行遍历,同时对这里面的数据进行一个统计,你就开始噼里啪啦的敲键盘编码了。但是过两天客户跟你说了,现在要添加一个新的统计,于是你就开始辛辛苦苦的进行修改。再过两天用户又说了,去掉原来那一个,只要后来增加的部分。等待产品快要发布了,用户一看,说:“哎呀,看来这样还是不好,你还是给我显示原来的统计数据吧。”等到展品发布之后过了一个月,用户打电话找你然你进行维护——当初没有说清楚,实际上要遍历的数据是网络型结构的…… 现实确实就是这样的,事情总是在变。唯一不变的就是一直在变,这个确实是一句名言,甚至就是真理。我想大家也一定认同,问题是作为普通的程序员,可能没有意识到这个变化是从一开始进行开发就出现的,并且在整个产品的生命周期里面,出变化最多的可能是产品发布之前的阶段。而产品一旦发布了,用户通常也能够理解有一些东西改变起来不方便。如果我们从管理高层的角度看问题,可能会更容易解释这个问题。实际上制约一个软件成功的最大要素并不在于技术,而在于两个制高点——“创意”和“市场”。创意我们不谈,我们就说这个市场。你的产品是否买得出去,在于你的产品是否能够适应并满足市场的需求,不能够满足需求的东西显然不可能赚钱。至于你的技术怎么样,用户根本就不关心,甚至你的产品写的很垃圾都好,只要你能够满足用户需求满足市场需求,那就一定赚大钱。那么怎么才能够尽最大的程度满足需求呢?在一个项目的最开始,我们肯定不会非常清楚。如果我们按照自己一知半解的假设去做,那么最终做出来的东西可能就不是用户所需要的,至少是用户所最需要的。所以我们需要用户给我们提出需求,到这里就是传统软件工程学里面的思想。很可惜的是,用户根本就不知道自己需要些什么,而我们对于用户想表达的是什么更是没有头绪。 因此我们只好不停的跟用户接触,不停的把我们现阶段的东西展现给用户,哪怕没有具体实现,哪怕背后的数据都是假的,也要给用户一个Visualize的东西出来看看,这样用户才有可能把需求说明白:噢,这个确实是这样的,那个我不需要,但是我需要的是什么,能不能这么改,这么操作我不习惯,我就要那样的……有了这个你才可能进行更进一步的设计。这里就已经出现了一定的需求变化了——由于原始需求的定义是粗糙的,没有经过验证的,而现在进行了一定的验证之后可能发现原来的想象可能有问题,需要修改。于此同时,你也需要谨记,客户可能会随时反复。只要你的产品没有正式发布,你就没有理由不去响应客户的需求变化。因此你做出来的东西应该能够随时地,方便得在某种程度上面进行修改。简而言之,在设计和开发阶段,需求的变化是最为剧烈的,这种变动的次数可能会很多。怎样有效率的适应这些变化,我觉得可能是设计模式所要解决的一个很重要的问题。我们来假设一下,某个客户一共会更改10次的需求,如果你每次接到需求变化之后第二天就能够把修改过的演示版本提交给用户评议,而我则每次都要花费10天的时间,我们两个人之间的差别将会是多大。事实上市场的变化速度是相当快的,可以说一年前的产品基本上就进入了淘汰序列,如果你还在卖一年以前普遍使用的技术,那就是一件危险的事情——这表明你跟不上时代的节奏,不可能拉到大量的订单。这说明现代IT行业的产品有一个非常重要的一点就是从“创意”到“市场”这两点之间花费的时间越短越好,也就是要求你的开发效率要高。如果你的设计灵活,能够有效地应付变化,那么你就在这一点上面成功了。当然,这只是其中的一个方面。今天关于这方面问题的总结就暂时到这里,以后还有什么想法再贴。   P.s.: 其实我到了北京已经有一个星期左右了,这段时间也不是没有发Post,主要都发在cnblogs上面了。因为我担心我的那些Post对于各位大侠来说简直就是班门弄斧,实在不好意思放在博客堂里面。此外要说一声,JGTM2004的私家机器好爽啊!19'的屏幕确实非同凡响,现在看17'的屏幕就觉得好像在看14'的屏幕一样。好工具就是能够激发人的灵感和激情……...[阅读全文]

posted @ | Feedback (20) | Filed Under [ Design & Architecture ]