曾经有一群人,将自己的想法变成一个个小孔,打在纸带上,他们把这纸带叫做Program——程序。将它塞入被称为Computer的机器中,然后恭敬的等待机器重新吐出一段纸带,拿着欢喜地去了。这群人被称为Programmer——伟大而光荣的程序员。
后来,两个叫斯蒂夫的人搞出了个叫Apple的小家伙,PC——个人电脑开始燃起星星之火。但是这些小东西需要被称作Software——软件的摸不着的东东才能使用。所以,有人开始专门编写它们。其中一叫做比尔 盖茨的人,认为Software可以独立,而且有知识产权,不能随意复制,必须付$。所以,他建立了个叫Micosoft的公司,搞来了DOS,又自产了Windows,拉上一个叫安迪格罗夫的偏执狂,乘着蓝色巨人的东风,开创了软件工业。从此,那些编写Software的人,被称为Software Engineer——软件工程师,他们开发的软件又称为Software Product——软件产品。
然后,不断有人凭借Software,改变世界,名利双收。最近的一个叫扎克伯格——一个80后,用一个叫“脸谱”的东东,改变了人们的社交方式。
我们也免不了有这样的梦想…但是,一个软件要能挣来$|¥,首先要满足客户需求。要大卖,还得满足客户都没想到的需求。扎克伯格们都是市场+工程的复合大牛,而且今天,单靠一人可以取得一时之成功,但想长久,非得依靠团队的力量,通过系统性的工作才能基业长青。所以,凡人,还是先立足当下,或可它图。
软件工程师的日常工作通常称为研发,可用下面的简单研发模型表示:
研究的哲学说法是:探索客观世界,发现其运行规律(详细的可看维基百科),被发现且被验证的规律便成为了人类的知识。研究一般分为两类:
1.基础研究:以获得新知识为目标,不关心其是否有实际应用价值。
2.应用研究:以解决实际为目标开展的探索活动。其结果一般以技术论文形式发表。
基础研究,特别是国内,都由大学、科研院所进行。大企业为保持竞争优势,则建立研究院开展应用研究。中小企业,则开发人员就是研究人员,所以统称研发。
开发人员的研究,不同于专职研究人员,主要探索的是他人已知但个人未知的世界,通过不断的搜集、学习,积累个人的知识,拓展知识边界,从而能顺利完成开发工作。如果能拓展到产品、市场等领域,可以看看那儿有钉子。而有些极具科学精神和智慧的同学,触碰到了所处领域的前沿,那么可以试试专职的研究员。
开发的目的是生产产品、工具解决实际问题,满足大众的需要,丰富大众的精神和物质生活。像Web2.0理念下的各种产品,极大的丰富了我们的生活。开发任务由需求驱动,工程师接到一系列需求(文档记载,详略不一,甚至有口述的),联系已经学到的知识,开始设计,实现。期间很可能发觉缺乏某些知识,如对某个技术不熟悉甚至不掌握,必须要先通过研究,习得新知识,才能继续开发活动。如何运用已有知识,写出合格的程序,就看各人的智慧了,有赖于99%的汗水+1%的灵感,当然1%灵感是决定性的!而灵感来自于,通过坚持不懈的研究对客观世界的观察得来感悟。机会只垂青有准备的人,没有锤子,即使看到钉子也只能扎手。练得千斤力,方能开顽石。
作为研发人员,能不断的创建、创造,享受带给他人改变第一重的幸福。同时研发人员又是悲哀的,因为你得学习、学习再学习,不断的研究,积累知识,历练智慧,才有可能享受哪种幸福。否则……
设计真是件奇妙的事情,能造就璀璨的明珠,也能带来一堆万年不去核废料;能让人享受释放智慧的乐趣,也能品尝挫败的沮丧。Why?
设计的过程
工程角度,设计是一个过程,包含三种不同层次的活动:架构设计,概要设计和详细设计。三者由全局到局部,依次展开,逐渐深入细节,最终完成一个技术解决方案,给出可行的如何实现需求的答案。此三者的一般性过程如下:
架构设计
目标:定位全局,确定技术方案的方向、后续技术活动策略。
输入:需求文档。敏捷过程常常是是一组用户故事。
输出:架构设计文档。敏捷过程称之为应用全局视图Application Overview。
角色:架构师
任务:
1.分析需求,识别关键功能、质量需求和约束
2.分析目标系统为达成以上目的,需要哪些流程,流程中有多少环节,各环节涉及哪些角色,这些角色的职责及他们之间的协作关系。
3.将角色按逻辑分类抽象,变成子系统。角色的职责即是子系统的接口,角色间的协作关系即为各子系统间的关系。
4.各子系统内部,根据职责拆分模块,确定它们的交互方式。
5.针对关键功能,确定完成这些功能的流程,在时序上由各子系统,模块协同工作的顺序。
6.考虑各子系统的部署方式。
7.将各子系统、模块转换成实现语言的元素。如对于.net,规划各子系统有哪些namespace,namespace如何分配到assembly,assembly之间的依赖关系。再将assembly分配到project中。期间需要注意避免循环依赖,明确接口(作为接口的assembly和project)。
8.规划project在SCM中的组织结构。
9.根据质量需求确定全局性的技术策略(方案):如线程控制,错误处理,数据存储等。
10.分析其余需求,验证当前方案是否能支持,根据发现的问题做必要调整。
概要设计
目标:在具体实现语言层面上展开工作,确定每个project的主要类及交互过程和总体算法。
输入:架构设计文档和需求。
输出:概要设计文档。
角色:高级程序员
任务:
1.设计提供暴露服务的接口的具体实现类,补充所需的相类,通常可按界面(接口)、活动和存储三方面考虑。进一步落实对项目外接口的依赖关系和位置。
2.设计算法实现接口暴露的相关功能。
详细设计
目标:确定概设提出的每个方法的具体实现。
输入:概要设计
输出:伪代码。
角色:程序员
任务:用伪代码描述每个方法。
这里看到的是一个按需求->系统->子系统->模块->类->方法,自顶向下逐步求精的过程。
设计的技术
技术角度,设计是一系列方法和工具,通过应用这些方法和工具给出一个可行解决方案的描述。常用方法有:
OO:面向对象方法。其目的是统一问题的描述与解决方法,通过一致的抽象提高开发效率。核心思想是,将问题空间映射到计算机模型上,在计算机中建立一个同我们日常感知世界相同的模型,解决问题。实践中,通过类描述问题空间的概念,通过类的消息描述这些概念的交互形成一个模型,再将模型落实到OOP的语言中,如C++,C#,Java等。
AOP:面向方面的方法。期望将主要业务同这些业务中散落的支撑功能(服务)如日志,权限等分离。结合OOP应用时,通过横切点定义跨多个类的支持服务,由方面(特殊类)实现这些服务,再通过切入点连接方面与横切点,达到运行时自动提供服务的目的。
DDD:领域驱动的设计。采用传统OO方法时,一般的需求分析结果是用自然语言描述的与OOD存在脱节,容易生造出问题域中不存在的概念,建立与实际需求不一致的模型。因此,从需求分析起,首先为问题域即领域建模,完全不考虑如何设计、实现,用客户能理解的方法仅描述问题现状。常用的建模方法是OOA,模型可用UML表达,也可以用类似框图联系,总是以能让用户明白这就是问题的描述,方便沟通为原则。
在不断使用这些方法的过程中,一些大师们发现了某些经常重复的解决方案,便对它们进行了提炼和总结,以便后来者这能利用这些经验,提高工作效率和质量,少走弯路。这些经验有:架构风格,架构模式,设计模式和反模式。
架构风格
架构层次的,根据系统的架构呈现出的总体特点进行架构分类的方式。主要的风格有:
1.客户-服务器:系统分为客户与服务端两部分,客户端发送请求,服务端执行并响应。
2.分层(级)架构:系统按关注点水平分层,每一层为上层提供一个抽象。近一步,可将层分布到不同计算机上。
3.面向对象:系统分成单独的可重用的对象,每个对象包含数据及处理它们的行为。
4.基于消息(事件):系统各部分通过发送和接收约定格式的消息工作,无需关心实际的收发者。
5.面向服务(SOA):系统通过约定的契约暴露功能,并根据这些契约工作。
6.基于组件:系统分解为逻辑的可重用位置透明的组件,通过明确定义的通信接口工作。
7.管道-过滤器:由管道连接过滤器一组,处理流经的数据。
8.微内核:分离最小功能核心和可扩展部分。
它们在最高层次根据问题的类型给出了一般的解决方向。如:
1.通信问题:基于消息、管道-过滤器
2.部署问题:客户/服务器、分层架构
3.重用与可扩展:OO、基于组件、SOA
但是,不同风格并不是互相排斥的,相反,一个实际系统通常同时呈现出多种风格。如一个分布系统,功能可通过语言无关的契约暴露,用OOP实现这些契约,实现对象又被组织成一个个组件,每个组件定义了彼此的通信接口,而通信又可是基于消息的,组件本身运行在一个支持插件的容器中,可随时添加新组件,提供新服务。这里表现出了SOA、OO、Component、Messaging和Mico-Kernel多种风格。
架构模式
一系列可重用的架构设计方案,每个方案在满足适用场景的前提下,解决一种或一类问题。经典的POSA给出了一些常用的模式分类:
1.服务访问和配置:包装器(Wrapper Facade)、组件配置器(Component Configurator)、截取器(Interrceptor)、扩展接口(Extension Interface)
2.事件处理:反应器(Reactor)、主动器(Proactor)、异步完成标记(ACT)、接收-连接器(Acceptor-Connector)
3.同步:界定枷锁(Scoped Locking)、策略化加锁(Strategized Locking)、线程安全接口(Thread-Safe Interface)、双检查加锁优化(Double-Checked Locking Optimization)
4.并发:主动对象(Active Object)、监视器对象(Monitor Object)、半同步/半异步(Half-Sync/Half-Asynce)、领导者/追随者(Leader/Followers)、线程特定存储器(Thread-Specific Storage)
5.资源获取:查找(Lookup)、懒加载(Lazy Acquistion)、预加载(Eager Acquistion)、分步加载(Partial Acquisition)
6.资源生命周期:缓存(Caching)、池(Pooling)、协调器(Coordinator)、资源生命周期管理(Resource Lifecycle Manager)
7.资源释放:租约(Leasing)、清除者(Evictor)
此类模式给出了全局性问题的一般处理方案,大都是关于子系统、模块及相互之间关系的粗粒度的描述。
设计模式与反模式
设计模式指OO的设计模式,是可反复使用的代码经验总结。通过GoF经典的《设计模式》广为人知。GoF将它们分类为:
1.创建型:简单工厂(Simple Factory)、工厂方法(Factory Method)、抽象工厂(Abstract Factory)、创建者(Builder)、原型(Prototype)、单例(Singleton)
2.结构型:外观(Facade)、适配器(Adapter)、代理(Proxy)、装饰(Decorator)、桥接(Bridge)、组合(Composite)、享元(Flyweight)
3.行为型:模板方法(Template Method)观察者(Observer)、状态(State)、策略(Strategy)、职责链(Chain of Responsibility)、访问者(Visitor)、调停者(Mediator)、备忘录(Memento)、迭代器(Iterator)、解释器(Interpreter)
它们在代码层面给出解决上述三类问题的一般做法及使用场景。
设计模式如红日般普照大地,光芒万丈,导致做OO的言必称设计模式,不用上几个都不好意思拿去见人。免不了被乱用、误用,明明需要避光保存的,偏偏加个LED增加照明,还曰节能、低碳。所以不得不需要反模式来拨乱反正。反模式说明了,当在错误的时间,错误的地点,使用了错误设计模式后,出现的严重后果,提醒人们过犹不及。
设计的人员
今天,从人员角度,设计是一系列扮演不同角色的人员的协作,他们通过某种过程,应用某些技术,相互配合,共同完成一个解决方案。一个采用传统设计过程的大型系统涉及的角色通常有:
1.架构师:一个人或者一个团队,负责将系统分解成子系统和模块,去顶它们之间的关系(开发期、运行期)并制定相关的技术决策,如部署、开发、性能等。
2.高级程序员(设计师):负责完成一个或多个子系统、模块的概要设计。
3.程序员:负责详细设计。
4.项目经理:负责整个活动的任务协调,并根据架构安排开发任务。
其中,架构师是核心,其工作成果是后续管理和实现的基石。有什么样的架构,便会有什么样的开发组织结构。如分层架构,必然会存在界面、业务、持久化及公共模块的开发职责分配,由不同人(团队)完成不同层的工作。
现代软件,因为规模和复杂性,再也无法由个人独立完成所有工作,必须依靠协作。协作首先需要分工,明确各工种的职责,个人依照职责行事。分工之后便有了工作的先后次序,不同次序的串联需要一定规则,便形成了一些过程规范,大家依照规范协同。所以,才有了那么多的软件工程方法论,开发才有了架构师,设计师和程序员的细分。不能简单的认为架构师>设计师>程序员,他们主要的区别在于工作范围的广度和深度的侧重点不同。架构师更广,程序员工作的更深入。
为了使用一致的思维考虑问题与问题的解决方法,诞生了OOP。为了分离业务与支持服务,让不同的人在不同的时间和地方分别解决不同问题,诞生了AOP。为了便于开发人员与用户达成待解决问题的一致认识,诞生了DDD。
设计时无论采用何种种过程、技术和人员组织方式,根本目的只有一个:给出关于需求的技术解决方案。
实际工作中还会碰到一个严重的问题,常常发现,要解决的问题,并不是地上的石头,静静的躺在那儿,等你照剑谱挥剑的。经常是,哦,我要砍的不是这块,是那块,甚至不是碎石而是需要劈柴,一身武艺无处使。不由怒从心头,不时问候需求人员或者客户,干嘛不一次说清楚,写的仔细点。害我改这改那儿的。对此,Brooks在《设计原本》说:设计的本质是帮助客户发现他们想要的需求。
设计的本质!
根本上,解决方案和问题是共同变化的,甚至会相互影响。现实中,需求阶段给出的需求,往往是初始需求,随设计过程的推进,它会奇妙的发生一些变化:
1.需求描述更精确了:随着设计深入,发现原来的描述存在模糊的方,需要更精确才能做出设计决策。
2.需求描述错了:设计着,突然,卡住了,一交流,发现,哦原来这不是用户要的,他们要那样的,其实很简单。
3.出现新需求:会发现之前不曾注意的需求,会加入系统性的需求,如缓存等。
设计必须能适应这些变化,有些需要通过技术方法,柔性的容纳新变化,将变化点抽象成接口,隔离变化,新需求只要设计成新的实现了即可。有些则只能通过总体过程来适应,如敏捷过程的高迭代,分批交付,在每个迭代间能响应变化。企图一次就做出美妙的设计是不现实的,设计必须具备响应变化的能力。因此,设计人员需要: