18

从编程思想到软件开发和设计能力培养(201016)

 3 years ago
source link: http://blog.sina.com.cn/s/blog_493a84550102z9kh.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在和开发团队沟通的时候,经常会谈到开发人员要逐步提升软件设计能力,而实际上我们整个软件编码和设计的过程往往并没有严格拆分,那么软件编码和设计究竟有哪些区别?或者说如何从软件编码阶段逐步提升自己的软件能力能力?

在前面我专门谈过软件架构设计方面的文章,因此今天重点主要是谈软件设计和编码,整个内容主要从如下几个方面来谈。

 1.从软件开发编程思想说起
 2.基于一个业务场景谈软件编码和设计
 3.从编码到设计,从设计到架构

从软件编程思想说起

在前面的文章里面有不少谈到通用性思维,分析和解决问题思维,SOA思想,面向对象思维等方面的文章。实际上可以看到里面很多内容是互通的,因此今天谈下编程思维。

编程的过程是对现实世界的抽象

谈编程不可避免的要谈到编程语言,而编程语言之所以出现,其最终的目的仍然是提供一种抽象方法来解决现实中的问题,问题本身的复杂程度往往取决于抽象的种类和质量。从汇编语言的出现解决了最初的抽象,而类似c或fortran语言出现则可以看做是对汇编语言的进一步抽象。这一步抽象的完成其实是很重要的一 个进步,既我们在解决问题的时候不再需要关系复杂的机器模型或机器码,而是可以更多的关注问题和解决方案本身。

算法+数据结构

在这个阶段,从编程本身来说最核心的还是算法和数据结构。这也是任何程序最重要的两个基本要素。既把问题域本身涉及到的数据映射到合适的数据结构,把通过 程序解决问题的过程映射为具体的算法逻辑。

那么编程实际的难点在哪?

不是算法本身或数据结构本身,而是 当你拿到问题域的时候知道如何理解和分解问题,并将其映射到最适合的算法或数据结构上 。这个映射其实本身不是程序解决的问题,还是人脑在思维,程序本身仅仅是在实现自动化的过程。

那么程序在算法实现过程中最基本的是什么?

我们看不同的程序片段可以看到的还是if/else,或者for/while,然后才是数据或数据类型定义。而 前者即写任何一个程序中最重要的控制逻辑。那么编程里难的实际上不是控制语句本身,而是在把问题域分解后知道如何理解判断逻辑,如何将问题域中重复的东西抽象为循环,如何从问题域中抽象出数据结构。一个人编程能力本身的好坏,或者说编程思维能力,重点其实是体现在这种映射能力,也可以称这种映射能力为数学 建模能力。

举个例子来说,如果一个问题你已经知道了可以映射到构建二叉树,然后通过遍历的方式来解决了,那么可以说然后一个掌握了语言语法的人都可以写出程序来。那么实际编程思维或能力的强弱则在于前面谈到的映射和建模。

面向对象思想是最容易理解的现实抽象

面向对象思想和面向对象编程语言的出现,可以说也是编程思维本身的第二次重大提升。既原有的编程语言可以看到我们关注更多的已经是抽象后的解决方案,而面向对象的编程语言则首先关注的是通过对象和类,通过继承,通过接口定义等首先对现实世界进行很好的抽象描述,其次才是如何去解决问题。现实世界中所有的一 切都是对象,而面向对象语言中的类本身就是对现实世界中对象的很好的抽象。

对于面向对象的核心特征谈的比较多的是封装,继承和多态。这些可能比较偏技术词汇,那么再简单点来说面向对象编程思维其核心则是找到问题域中的对象,将其抽象为类,识别类应该有的属性和方法特征,同时去理清类和类之间的关联和交互关系,将问题本身的解决过程映射到类和类之间的方法交互上。

如果从这个意义上 来说,好像也不是很复杂,那么实际面向对象编程的难点实际在为了保持代码足够的健壮性,可维护性,可扩展性而做出的各种抽象,包括接口的提取和组合,控制或逻辑类的增加等,这些本质已经转换到技术域类本身。

复用和自动化思想

除了上面谈到的,再谈下其它的一些内容。编程里面有一个重要的思想即是复用,从最简单的函数,到模版库,类库,再到更上层的公共组件等,都在体现复用的思 想,而复用本身的目的则主要是提升开发效率,提升可维护性和代码的可读性等。复用可以理解为编程过程中的编程思想更加恰当。

编程的思想是自动化,不要简单的理解为编程语言能够帮助你解决建模和映射的难题,编程的自动化更多的还是体现在机器可以自动化的进行大量计算和运算,而这 个运算是通过我们的程序进行的。程序中体现的一个重点我更喜欢把它理解为循环,从抽象中去发现和发掘一种可自动化的循环,这种循环的处理正是程序的强项。 任何人都应该有这种自动化的编程思维,即懒人思维,重复的事情一定不要自己手工重复完成。

基于一个简单业务实现谈编码

在谈这部分之前首先还是要继续推荐下《代码大全》这本书,该书在很多公司都会被推荐为新人入职后的必读书,即使在互联网和敏捷开发环境下,该书对于新人仍然有仔细阅读和体会的必要,要明白架构和设计思维的基础仍然是编码思维,面向对象思维的基础仍然是代码本身的逻辑和结构,如果不能写出高质量的代码,那么架构和面向对象思想很多内容往往难以真正落地。

下面结合一个最简单的业务功能场景来讲解,即采购订单新增这个业务功能和场景。

核心对象和依赖对象分析

拿到这个功能需求后当然是首先要详细阅读具体的业务功能需求,在阅读完后第一需要思考的不是业务流程,不是数据库表,而是首先应该思考该功能对应的核心领域对象究竟是什么?以这个功能为例,可以很明显的看到核心对象是采购订单,这是一个有明确业务含义的对象,可以看到后续很多的业务操作和方法都将围绕采购订单这个对象展开。

核心主对象明确后再分析该功能需要依赖的附属对象,比如:

  • 供应商:基础主数据对象,依赖原因是需要在创建采购订单的时候选择供应商
  • 采购类型:数据字典类对象,依赖原因同样需要在创建订单的时候选择
  • 订购商品:基础主数据对象,一张订单选择购买的一个或多个商品和数量信息

了解开发框架的分层和运行机制

该问题做了基本的思考后,再回答我们具体使用的语言和开发框架,比如基于标准的java ssh框架来开发该功能,要明白我们一般选择了某种框架后基本的分层思路就已经确定了。

当我们拿到一个现成的框架后,在基于这种框架做完练习后首先要思考的就是这种框架本身的运行机制是如何的?各层之间的调用逻辑和分工是如何的?各层有哪些约束和边界,在业务实现过程中涉及到的业务规则,逻辑和数据处理究竟应该放在哪层去实现。

这些问题必须在编码中搞清楚,而不是依葫芦画瓢而不知其所以然。

识别和定义核心类和方法

对于我们使用的分层框架基本运行机制和逻辑了解清楚后,基于拿到业务功能需求要开始思考的就不是框架和分层的问题了,而是我们应该在每个分层中设计哪些类?每个类应该有哪些核心方法?类和类之间如何衔接和实现内部调用。单表功能做多了最容易犯错的地方就是完全的数据库表式思维模式,即任何一个数据库表都会有对应的展现页面,控制类,service类和dao类,完全一对一映射和调用。在这种思维模式下忽略了最前面思考的对象的本质,在领域模型里面一个核心就是我们关心的是有明确业务含义的对象,而不是数据库表。数据库表和dao层只是在最终持久化要做的事情而已。

核心功能实现

基于上面的分析可以看到,对于订单创建的时候供应商信息的获取应该是供应商类完成的职责,对于采购类型可能则是一个数据字典的common类完成的职责。而对于核心的订单类可以看到,我们需要的领域服务或方法很简单,即:

public void savePurchaseOrder(COrderEntity order);

订单应该作为一个核心的领域对象来处理,但是实际在后台操作则涉及到OrderHeader和OrderDetail两张数据库表,任何一张订单的保存都涉及到对两张数据库表的操作。

可以看到如果对应的业务功能没有明确的对订单明细的操作规则和方法,那么在Service层没有必要根据头和明细定义两个Service类,只需要一个OrderService类即可。在该类中实现所有的订单保存前数据准备和逻辑校验。

在这一个步骤想清楚后,即对于订单新增功能可以看到,即在页面和展现层进行新增订单操作,在点击保存按钮的时候应该将页面上的订单数据信息传递到action层进行基本的数据完整性校验,然后再将订单数据以json或已经实例化好的订单实体类传递到逻辑层的savePurchaseOrder方法去处理。而savePurchaseOrder要做的事情就是进行数据准备和转换,再调用Dao层的订单头和订单明细保存方法进行数据保存,并控制好事务处理。

基于该逻辑思路我们就基本可以写出各个分层的核心方法和方法实现,实现最基本的订单保存功能。在整个过程中我们始终围绕订单这个核心对象展开。逻辑层的savePurchaseOrder这个方法是核心,即从展现层准备好订单实例数据,从该层到Dao层是实现数据最终的持久化和事务控制。

扩展业务规则实现

对于业务规则可以分为两类规则,即一类规则是数据参考完整性规则,比如数据类型,数据的长度,2个数据属性间简单控制逻辑(比如订单类型为A时候,发运地址必须输入等);还有一类即较为复杂的数据处理规则或需要调用后台数据库实现的控制逻辑(订单总金额>1000的时候用户信用等级必须>B级,在订单保存前还需要再实时检查商品库存信息是否足够等)。 对于两类规则的基本原则就是第一类可以在action层实现,而第二类数据则需要在业务逻辑层来实现。

对于逻辑层的savePurchaseOrder方法最终应该相当简单,即首先进行业务规则教育,在通过后再调用订单保存方法进行订单保存操作。类似如下:

//采购订单保存方法
public void savePurchaseOrder(COrderEntity order)
 {
     //对订单相关的业务规则进行逐个校验
     if !validBusinessRule1(order)  return;
     if !validBusinessRule2(order)  return;
    
     //校验通过后调用订单保存方法
   saveOrderInfo(order);
 }

在该实现中可以看到首先进行了子方法的拆分,保持订单保存方法本身的简洁和代码可读性。其次需要考虑对于拆分的处理规则的方法是否需要拆分到单独的业务规则类里面。

这里要看情况来处理,即对于业务规则本身有比较高的复用性时候最好拆分为单独的业务规则类来处理(比如在订单分发或订单拆分业务功能中仍然需要使用同样的业务规则,那么规则单独拆分到类是有必要的),如果功能确实够简单也可以考虑不拆分。

详细的编码实现

以上都思考完成后基本的框架和设计实现逻辑就都清楚了,下一步则过渡到具体的编码实现环节。基于任何语言的编码其核心都是算法和数据结构,而在这两者里面最基本的又是变量和数据类型,程序控制逻辑(判断和循环等),变量定义的规范性,数据类型选择的合理性,控制逻辑代码的清晰度都将直接影响到编码本身的健壮性和可读性,这些最基本的内容才是写出高质量代码的关键,也是类似《代码大全》书籍所一直强调和内容。

代码本身具备足够的自解释性,源代码就是设计核心就是我们的命名,方法的拆分,控制逻辑要清晰,代码具备足够的可读性往往则不再需要过多的注释。子方法的拆分是另外一个重点,对于子方法的拆分不仅仅是考虑到方法的复用性问题,有时候一两行代码也需要拆分,其核心原因包括两个,一个是拆分后增加了代码的可读性,其次是增加代码的可扩展性,即后续拆分的子方法往往存在规则或逻辑变更和扩展的可能等。

任何一段代码本身的结构化和逻辑化是面向对象编程的基础,而结构化后最直观的体现就是代码的可读性和可维护性。软件质量的衡量不仅仅是简单功能的实现,而是在各种非常规场景下的边界和异常处理能力,因此代码本身的健壮性是另外一个重点。任何在编码阶段的工作都必须时刻关注这两个重点,才可能不断提升最基本的编程思维和编码能力。

从开发到设计

在日常团队沟通中,经常会和开发人员谈到设计思维,如何从开发能力转变到具备基本的设计能力,今天基于这点做一些展开的说明。

在开发过程中应该有的几个关键意识

复用意识: 在软件设计中有一个重点就是复用,因此在你日常的编码过程中就需要有复用意识,你可以自己考虑下你平时编码过程中是否经常存在大量的粘贴拷贝动作,如果是的话那就是复用意识很差。即使一个最简单的字符转换,我们也要有复用的意识,从最简单的公共方法和函数,到可复用的组件和类。尽量去减少你程序里面的大量重复代码。

抽象意识: 抽象意识是你平时在代码编码中另外一个关键意识,即能否从不同的东西中找到共性的内容,抽象出共性的基础类或接口,抽象意识一方面是增加了你代码的可扩展性,这也是最基本的设计模式中谈到的意识。你做好了抽象,你会发现你代码更加容易扩展,你程序里面大量难懂的if ese语句就越少。同时抽象意识是复用的进一步深化,很多东西经过抽象你会发现共性出来了,可以做复用出来了。

流程化意识: 举个例子来说你准备明天去海边度假,那么你的第一思路是做好计划,出行准备,前往目的地,海边度假,返回。而不是第一先去想到去加油,再想到海边要游泳。我们做任何事情最先想的就是分几个阶段,分几个步骤来做,而不是先想到具体的一个操作。写代码也是一样,想清楚究竟分几个关键步骤来实现,而不是一开始就去想某个细节操作,一个功能实现,你是一下就体现出1000行代码?还是5个操作方法步骤段,每段200行代码容易读?你有流程化意识,反倒设计里面去就是用例实现,活动图或序列图,你没有这个意识,那么你简单的序列图也画不好。

当你拿到一个复杂功能的时候,你在想什么?

设计,和你建造房子画的设计图一个道理,房子还没有建好,但是应该如何建,张什么样子就清楚了。再回来说我们生产一个东西,我们就先画好产品的设计图或结构图,但是这个不足以我们把产品生产出来,我们出来设计产品结构和组成外,我们还得先讲清楚产品如何生产,而这个对应到生产过程中的工艺流程。

所以回到软件开发里面,任何一个设计就是包括两个方面的内容:

  • 其一是最终交付的功能的结构,底层的对象和模型是如何的?
  • 其二就是基于底层对象模型如何最终形成完成的产品功能?

第1个对应到我们的逻辑模型,数据库设计等;而第2个对应到我们的用例分析和实现,或者类似活动图,状态图,序列图等动态实现等。

那么,当我们拿到一个复杂的业务功能的时候,我们基本的思路究竟应该是如何的?

首先通过完整的业务需求分析,找到具体的业务用例(业务功能点),把业务功能点本身的业务,流程描述清楚。 在这个描述和定义过程中,先找到核心的业务对象,从业务对象,从业务对象识别出具体的数据对象,从数据对象抽象具体的对象类,或者定义具体的数据库表对象。简单来说就是,你要先通过业务的分析把关键的对象识别出来,这些对象最终再转成你底层的数据库设计,逻辑层的核心对象或实体类。

其次你就需要考虑底层的对象或模型如何协同和交互,最终实现你需要的业务功能。 对于一个最简单的表单增删改查,你会发现这个过程很简单,就是录入数据存储到数据库,或者从从数据库查询出数据展现到界面表格里面,再复杂点的可能涉及到主从或多层表结构而已。

那么这理解究竟是哪里复杂了?或者说复杂的业务功能究竟是哪里复杂了?

这里并不是底层数据库表结构和关联关系复杂了多少,而是处理数据入库,查询数据出库的逻辑变复杂了。正是由于这个原因,你在分析任何一个业务功能实现的时候,不仅仅搞清楚简单的表单数据流,更加重要的是识别和抽象出关键的业务规则和逻辑。

这一点如何做?

你需要先考虑的就是要有一个分层思维, 即前端实现和底层逻辑分层开,不是1对1绑定的 ,在拆分开后你就会考虑你的逻辑层应该如何规划类,有哪些实体类,有哪些专门的处理业务逻辑的类,有哪些进行业务逻辑组合等问题。

理解了这点,任何一个复杂的前端操作,本质就变成了:

前端操作 = N个DAO操作+N个逻辑方法操作的集成。

理解到这里,你会发现和我文章里面大量提到的SOA服务组装和编排思路完全一样,只是这个组装和编排不是通过服务设计器完成的,而是通过你的类似组合类或Facade类来完成的而已。

高级开发和设计,从技术流到问题域驱动

在日常接触中,我们经常会遇到一些开发人员,一说到主流的redis, spring cloud, rabbitmq等等都用过,都玩过,也感觉用的比较熟,但是在我们定义里面仍然是高级开发。是否具备设计能力重点仍然是前面讲的 真正面对业务需求和功能时候的抽象能力,同时将问题域转换为最合适的对象和模型的能力。 而对于上面各种主流开源技术仅仅是我们解决问题的工具。

我们开发一个系统,比如我们选择了spring cloud来做微服务架构,那首先要回答的就是面对当前业务域和问题,为什么要采用spring cloud架构,还是你认为当前大家都用这个架构所以我们也要用。

其次,你懂了spring cloud和各个组件的用法仅仅是熟悉了一种工具和技术平台,你要真正能将业务需求拆分为合适的微服务模块,识别和定义关键接口服务,抽象出共性组件,包括针对不同的非功能性需求,选择最合适的缓存,消息中间件等各种组件来解决问题,才是最关键的设计能力。

软件领域,一个好的设计一定是最合适+兼具扩展的设计,而不是采用最先进技术的设计。

开发人员的架构设计能力培养

对于软件架构,架构思维,在我前面的博客文章里面已经多次谈到,实际上对于一个开发人员来讲,如果要真正成长为一个名出色的架构师是相当困难的。一个优秀的架构师可以说是同时具备了业务加技术,宏观加微观,抽象加实现多方面能力的整合。

架构师是否需要经过长期的编码锻炼?

这个答案是肯定的,而是做好是不同框架模型,不同业务领域的编码锻炼,同时在进行开发和编码过程中还要时刻有复用,抽象等设计思想。架构师有一个重要能力就是抽象能力,如果你没有经过大量的实践积累,你是很难真正培养出抽象和建模能力的。量的积累最终都是为了质的飞跃,而对于知识经验,要想达到一定的深度,就必须积累足够的广度。

一个架构师必须 同时具备业务和技术两个方面的能力 ,或者说具备对业务需求和业务场景的理解力,如果对业务都不理解,你如何保证你最终的设计能够满足需求,保证你的模型是匹配业务,同时模型有能够具备足够的扩展性响应业务的变化。对业务理解后,是用当前最合适的技术来解决需求和业务问题,而不是用最新,最难的技术。而对于架构,往往经常犯错的地方就是沉迷到技术里面,而不管技术和业务的匹配性。

架构师要做的工作就是 抽象和建模

首先是对现实业务能够抽象,先抽象为业务模型 ,这往往是我们常说的系统分析员要做的事情,而这个事情现在真正能做的人却是越来越少。

其次就是基于业务模型考虑如何形成技术架构模型 ,这个模型本身也包括了业务+技术两个方面的内容,比如我们说的核心用例,逻辑模型等。最终建完的模型需要有效的衔接业务和技术实现两个方面。

架构师建立完成的模型有两个层面的验证 ,一个是纯理论验证,一个是等到后续开发实现完成后运行期验证 。好的架构师往往就是理论验证通过的模型,就一定能确保最终实现后也能够验证通过。为什么能够保证这点?其核心原因就是你当前采用的理论不是简单的都来源于书本,而是通过你自己大量的实践积累出来的知识经验,也就是我常说的你用的理论是经过你自己实证的,而不是简单的书本照抄过来的。

好的架构师不是简单的分解,更加重要的是确保分解完成的东西能够集成回去。

分解是最基础的,架构的重点就是要对复杂问题进行分而治之 ,同时保证分解后的各个部分还能够高内聚,松耦合,最终又集成为一个完整的整体。分解核心是定义问题,因此架构首先仍然需要理解清楚需求。

集成是配合分解完成的动作,最终分解完成的各个组件或子系统 ,通过合适的接口设计,最终还能够集成为一个完整的整体,分解仅仅是加速开发和降低问题复杂度,如果分解后的内容无法集成在一起,那么分解就没有任何意义。分解+集成可以理解为架构最核心的思考方式和方法。

再回来看看架构能力如何锻炼和提升?

要提升架构能力,基础还是要提高设计能力 ,而设计能力的提升完全可以从编码能力开始,同时在编码过程中多去考虑编码本身的健壮性,安全,可复用性,性能等非功能性需求。在这个阶段就可以逐步锻炼抽象复用,算法结构等基础核心能力。设计能力提升一个重要表现就是你没有进行编码,但是整个业务功能如何实现你自己已经想清楚了如何做?应该拆分为哪些类,抽象哪些公共函数,选择什么样的算法等。

架构能力提升的第二点就是业务能力提升 ,需要具备对业务需求和业务流程的分析能力,通过对业务的分析能够抽象关键的业务模型和业务用例,业务建模完成后你才可能进行系统建模。如果是做企业信息化领域,对于供应链,生产,财务,工程项目等核心的业务线条都是可以去自己学习和了解的内容。

架构能力提升第三点仍然是思维能力的提升 ,对于架构思维本身仍然是类似系统思维,结构化思维,编程思维等诸多思维模式的一个合集。这里面包括了抽象,复用,分层,分解和集成,归纳和演绎,模式匹配,迭代,扩展,模拟等多种思维能力的锻炼。只有这些思维能力提升了,你的架构能力才可能更上一个层次。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK