站内搜索

搜索

手游源码-游戏源码-棋牌源码资源网-亲测源码-游戏搭建-破解游戏-网站源码-qq技术

100金币/天 购买

这堂培训:如何提高代码的可读性反复依然不明其意?

12

主题

23

帖子

2

金币

绿钻会员

Rank: 3Rank: 3

积分
243
发表于 2022-1-8 19:00:19 | 显示全部楼层 |阅读模式
今天的培训课程是关于什么的?我不谈论它,我不谈论它,我不谈论 Ext,我不谈论任何特定的技术。让我们抛开任何具体的技术,谈谈如何提高代码质量。如何提高代码质量,相信不仅是这里大家的烦恼,也是所有软件项目的烦恼。如何提高代码的质量,我想我们首先要了解什么是高质量的代码。 优质代码的三要素 我们根据三个要素评估高质量代码:可读性、可维护性和可更改性。我们的代码必须满足这三个要素的要求才能被认为是高质量的代码。 1. 可读 提到可读性似乎有点陈词滥调,但令人沮丧的是,尽管一遍又一遍地强调可读性,但我们的代码在可读性方面仍然做得很好,非常糟糕。由于工作需要,经常需要阅读别人的代码,维护别人设计的模块。每当看到一大段代码密密麻麻,没有任何注释的时候,我常常会感慨万千,深刻体会到这项工作的重要性。由于分工的需要,我们写的代码难免需要别人去阅读和维护。而对于许多程序员来说,他们很少阅读和维护其他人的代码。正因为如此,他们很少关注代码的可读性,也缺乏如何提高代码可读性的第一手经验。有时即使对代码进行了注释,但注释语言往往晦涩难懂,以至于读者反复思考后仍不清楚其含义。针对以上问题,我给大家以下建议: 1)不要写大块代码 如果你有读过别人代码的经历,当你看到一大段别人写的代码,而且没有太多注释,你怎么看,是不是很“嗡嗡”?各种函数纠缠在一个方法中,各种变量来回调用。我相信没有人会认为它是高质量的代码,但它却经常出现在我们编写的程序中。如果你回头看你现在写的代码,你会发现几百行代码只需要一点点编写一个复杂的函数。一些更好的方法是分割。整理了一大段代码后,分成功能相对独立的,每段前面写了注释。这种写法确实比之前杂乱无章的大段代码好很多,但是在功能独立性、可复用性、可维护性等方面还是差强人意。从另一个更专业的评价标准来看,并没有做到低耦合高内聚。我给大家的建议是,将这些相对独立的段落封装成一个又一个的函数。 在他们的经典著作中,许多大师鼓励我们在编写代码时养成不断重构的习惯。在编写代码的过程中,我们经常要编写一些复杂的函数,这些函数最初都是写在一个类的函数中。随着功能的逐步扩展,我们开始对复杂的功能进行总结和整理,一个又一个独立的功能进行梳理。这些独立功能具有与其他功能通信的输入和输出数据。当我们分析到这一点时,我们自然会将这些函数与原函数分离,形成一个又一个独立的函数供原函数调用。在编写这些函数时,我们应该仔细考虑,给它们一个释义名称,并为它们写注释(稍后会详细介绍)。另一个需要考虑的问题是这些功能应该去哪里。这些功能可以放在原来的类中,也可以放在其他具有相应职责的类中,应遵循“职责驱动设计”的原则(后面也会详细介绍)。 下面是我写的一个类,它从 XML 文件中读取数据并生成一个工厂。这个类的程序最重要的部分就是初始化工厂,可以概括为三个部分:尝试以各种方式读取文件,以DOM的方式解析XML数据流,生成工厂。并将这些函数汇总封装在不同的函数中,我取了一个释义名称,并为它写了注释: Java 代码

    /**
  * 初始化工厂。根据路径读取XML文件,将XML文件中的数据装载到工厂中
  * @param path XML的路径
  */
public void initFactory(String path){
    if(findOnlyOneFileByClassPath(path)){return;}
    if(findResourcesByUrl(path)){return;}
    if(findResourcesByFile(path)){return;}
    this.paths = new String[]{path};
}
/**
* 初始化工厂。根据路径列表依次读取XML文件,将XML文件中的数据装载到工厂中
* @param paths 路径列表
*/
public void initFactory(String[] paths){
    for(int i=0; i);
    }
    this.paths = paths;
}
/**
* 重新初始化工厂,初始化所需的参数,为上一次初始化工厂所用的参数。
*/
public void reloadFactory(){
initFactory(this.paths);
}
/**
* 采用ClassLoader的方式试图查找一个文件,并调用readXmlStream()进行解析
* @param path XML文件的路径
* @return 是否成功
*/
protected boolean findOnlyOneFileByClassPath(String path){
    boolean success = false;
    try {
        Resource resource = new ClassPathResource(path, this.getClass());
        resource.setFilter(this.getFilter());
        InputStream is = resource.getInputStream();
        if(is==null){return false;}
        readXmlStream(is);
        success = true;
    } catch (SAXException e) {
        log.debug("Error when findOnlyOneFileByClassPath:"+path,e);
   } catch (IOException e) {
        log.debug("Error when findOnlyOneFileByClassPath:"+path,e);
    } catch (ParserConfigurationException e) {
        log.debug("Error when findOnlyOneFileByClassPath:"+path,e);
    }
    return success;
}
/**
* 采用URL的方式试图查找一个目录中的所有XML文件,并调用readXmlStream()进行解析
* @param path XML文件的路径
* @return 是否成功
*/
protected boolean findResourcesByUrl(String path){
    boolean success = false;
    try {
        ResourcePath resourcePath = new PathMatchResource(path, this.getClass());
        resourcePath.setFilter(this.getFilter());
        Resource[] loaders = resourcePath.getResources();
        for(int i=0; i.getInputStream();
            if(is!=null){
                readXmlStream(is);
                success = true;
            }
        }
    } catch (SAXException e) {
        log.debug("Error when findResourcesByUrl:"+path,e);
    } catch (IOException e) {
       log.debug("Error when findResourcesByUrl:"+path,e);
    } catch (ParserConfigurationException e) {
        log.debug("Error when findResourcesByUrl:"+path,e);
    }
    return success;
}
/**
* 用File的方式试图查找文件,并调用readXmlStream()解析
* @param path XML文件的路径
* @return 是否成功
*/
protected boolean findResourcesByFile(String path){
    boolean success = false;
    FileResource loader = new FileResource(new File(path));
    loader.setFilter(this.getFilter());
    try {
        Resource[] loaders = loader.getResources();
        if(loaders==null){return false;}
    for(int i=0; i.getInputStream();
        if(is!=null){
            readXmlStream(is);
            success = true;
        }
    }
} catch (IOException e) {
    log.debug("Error when findResourcesByFile:"+path,e);
} catch (SAXException e) {
    log.debug("Error when findResourcesByFile:"+path,e);
} catch (ParserConfigurationException e) {
    log.debug("Error when findResourcesByFile:"+path,e);
}
  return success;
}
/**
* 读取并解析一个XML的文件输入流,以Element的形式获取XML的根,
* 然后调用buildFactory(Element)构建工厂
* @param inputStream 文件输入流
* @throws SAXException
* @throws IOException
* @throws ParserConfigurationException
*/
protected void readXmlStream(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException{
    if(inputStream==null){
        throw new ParserConfigurationException("Cann't parse source because of InputStream is null!");
    }
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(this.isValidating());
    factory.setNamespaceAware(this.isNamespaceAware());
    DocumentBuilder build = factory.newDocumentBuilder();
    Document doc = build.parse(new InputSource(inputStream));
    Element root = doc.getDocumentElement();
    buildFactory(root);
}
/**
* 用从一个XML的文件中读取的数据构建工厂
* @param root 从一个XML的文件中读取的数据的根
*/
protected abstract void buildFactory(Element root);  

在编写代码的过程中,通常有两种不同的方式。一种是从下往上写,即按照顺序,每次划分一个函数,必须先写完函数,然后再返回主程序继续写。一些更有经验的程序员使用另一种自上而下的方法。他们在写程序的时候,每一个被划分的程序可以暂时只写一个空程序,不实现具体的功能。主程序完成后,将其所有子程序一一实现。使用这种写法可以让复杂的程序有更好的规划,避免只见树木不见森林的弊病。 一大段代码是多少代码,每个人都有自己的理解。我写代码,每到15-20行,我就开始思考是否需要重构代码。同样,一个类也不应该有太多的功能。当功能达到一定程度时,应考虑分为多个类;一个包不应该有太多的类... 2)定义名称和注释 我们在给变量、函数、属性、类和包命名的时候,要慎重考虑,让名字更符合对应的函数。我们常说,在设计一个系统的时候,一个或多个系统分析师应该规划整个系统的包、类以及相关的功能和属性,但是这在正常的项目中是很难做到的。更多的是由程序员来命名它们。但是,在项目开始时,应该有一个命名项目的约定。比如我的项目中规定新建记录以new或add开头,更新记录以edit或mod开头,删除以del开头,查询以find或query开头。最乱用的就是get,所以我规定get开头的函数只用于获取类属性。 每个项目组都在不断的强调注释,但是还是有很多代码没有任何注释。为什么?因为每个项目在开发过程中往往都很紧张。在紧张的代码开发过程中,注释往往会逐渐被忽略。使用开发工具的代码编写模板可以解决这个问题。 以我们常用的例子,在菜单“>>>>Java>>代码风格>>代码>>”中,可以很方便地修改。
“Files”代表我们每次新建文件(可能是类或者接口)时写的注释,我通常设置为: Java 代码

    /*
* created on ${date}
*/  

“Types”代表我们新创建的接口或类之前的注解,我一般设置为: Java 代码

    /**
*
* @author ${user}
*/  

第一行是空行,是你写类的注释。如果使用“责任驱动设计”,这里首先要说明的是类的责任。如有需要,可以编写该类的一些重要方法及其用法、该类的属性及其中文含义等。 ${user} 代表您登录时使用的用户名。如果这个用户名不是你的名字,你可以硬写成你自己的名字。 其他我通常保留默认值。通过以上设置,当您创建类或接口时,系统会自动为您编写注解,您可以在此基础上进行修改,大大提高了注解编写的效率。 同时,如果在代码中添加函数,可以通过Alt+Shift+J快捷键快速根据模板添加注释。 写代码的时候,如果你写的是接口或者抽象类,我也建议你在@后面加上@see注解,列出接口或者抽象类的所有实现类,因为读者是在读,更很难找到接口或抽象类的实现类。 Java 代码

    /**
* 抽象的单表数组查询实现类,仅用于单表查询
* @author 范钢
* @see com.htxx.support.query.DefaultArrayQuery
* @see com.htxx.support.query.DwrQuery
*/
public abstract class ArrayQuery implements ISingleQuery {
...  

2.可维护性 软件的可维护性有几个含义。第一个含义是可以适应软件部署和使用的各种情况。从这个角度来看,它对我们软件的要求是代码不能被硬编码。 1)代码不能硬编码 我看到我的同事在 C 盘的固定目录中指定了一个要被系统读取的日志文件。如果系统部署没有这个目录和这个文件,就会出错。如果他将此决策路径下的目录更改为相对路径,或者可以通过属性文件进行修改,代码将被实时编写。一般来说,我的设计中需要用到日志文件、属性文件、配置文件,通常有以下几种方式:把文件和类放在同一目录下,使用.()来读取;把文件放在目录下面,使用File的相对路径来读取;使用 web.xml 或其他属性文件来指定读取路径。 我也看到另外一家公司的软件需求,部署时必须在C:/bea目录下,换到别的目录下就不能正常使用了。这样的设置往往会给软件部署带来很多麻烦。如果服务器在这个目录下没有多余的空间,或者有其他软件,那就很麻烦了。 2)预测可能的变化 另外,在设计时,如果在配置文件中放置一些关键参数,可以为软件的部署和使用带来更大的灵活性。要做到这一点,需要我们对软件设计有更多的认识,同时考虑到软件应用程序可能发生的变化。比如我在设计财务软件的时候,就考虑到了制作一些文档的前提条件。不同的公司可能有不同的要求。一些公司可能有更严格的要求和一些更宽松的要求。考虑到这种可能发生的变化,我将前置条件设计为可配置的,这样可以方便部署人员在实际部署中进行灵活的变化。但是,对于这样的配置,必要的注解是非常有必要的。 软件可维护性的另一层意味着软件旨在促进未来的变化。这一层含义与软件的可变性不谋而合。一切软件设计理论的发展都是从软件可变性的要求逐步发展起来的,这已成为软件设计理论的核心。 3.可变性 我前面提到过软件可变性是所有软件理论的核心,那么什么是软件可变性呢?根据当前的软件理论,客户对软件的需求一直在变化。软件设计完成后,为响应客户需求变化而修改代码的成本就是软件设计的可变性。由于软件设计合理,修改成本越低,软件的可变性越好,即代码设计质量越高。在一个非常理想的状态下,无论客户的需求如何变化,只要适当的修改,软件就可以适应。但这被称为理想状态,因为客户需求差异很大。如果客户的需求发生很大变化,再好的设计也无法应付,甚至需要重新开发。但是,随着客户需求的适当变化,合理的设计可以最大限度地降低变更成本,并延续我们设计的软件的生命力。 1)通过改进代码重用提高可维护性 我曾经遇到过这样的事情。因为我要维护的一个系统的应用范围扩大了,它需要改变一个组织层级的计算策略。如果本项目采用通用方法计算水平,这样的修改过于简单,只需修改通用方法即可。然而,事实却不同。器官级别的计算代码分散在整个项目中,有的甚至写进了那些复杂的SQL语句中。在这种情况下,修改这样的需求就等于遍历项目代码。这样的示例显示了代码重用在项目中的重要性,然而,不幸的是,代码在我们所有的项目中都不能很好地重用。代码复用的原理很简单,但具体操作很复杂,不仅需要很好的代码规划,还需要不断的代码重构。 对整个系统进行整体分析和合理规划,可以从根本上保证代码复用。通过对用例模型、领域模型、分析模型的逐步分析,最后通过正向工程,系统分析师生成系统需要设计的各种类及其各自的属性和方法。通过这种方法,可以将函数合理地划分到这个类中,并且可以很好地保证代码的复用。 虽然采用上述方法很好,但技术难度大,需要资深的系统分析员。并不是所有的项目都能被普遍采用,尤其是时间有限的项目。通过开发人员在设计过程中进行重构可能更实用。开发人员在开发一段代码时,发现功能与之前开发的功能相同或部分相同。这时候开发者可以对之前开发的功能进行重构,提取出一般可以使用的代码,并进行相应的改造,使其在各个地方都通用,好用。 一些成功的项目组会指定一个专门管理通用代码的人,负责收集和整理项目组每个成员编写的通用代码。负责人也应该有一定的代码编写能力,因为特殊代码升级为通用代码,或者之前使用了通用代码的某个功能,由于业务变化,对这个通用代码的变更需求均对此负责人们对容量提出了很高的要求。 虽然后一种方式很实用,但是有点拼凑,不能有效地规划项目代码整体。因为这两种方法各有利弊,所以应该在一个项目中一起使用。 2)使用设计模式提高可变性 对于初学者来说,软件设计理论常常让人觉得晦涩难懂。提高软件质量的一种快速简便的方法是使用设计模式。这里所说的设计模式不仅是指经典的32种模式,还包括前人总结出来的更广泛的我们可以使用的设计模式。 一个。如果...否则... 这个不知道叫什么名字,哪位大师最先总结的,出现在《UML与模式应用》中,也出现在《敏捷软件开发》中。它是这样描述的:当你发现你必须设计这样的代码时:“if...else...”,你应该认为你的代码应该被重构。我们先来看看这类代码的特点。 Java 代码

    if(var.equals("A")){ doA(); }
else if(var.equals("B")){ doB(); }
else if(var.equals("C")){ doC(); }
else{ doD(); }  

这样的代码很常见很普通,我们都写过。但正是这种共性隐藏了我们从未注意到的问题。问题是,如果有一天这个选项不再只是 A、B、C,而是添加了一个新选项怎么办?你可能会说,没关系,我只是更改代码。然而,这种情况并非如此。大型软件的开发和维护有一个原则。尽量不要每次更改都修改原始代码。如果我们重构,就可以保证原代码不被修改,只添加新代码才能应对选项的增加,增加了这段代码的可维护性和可更改性,提高了代码的质量。那么,我们应该怎么做呢? 深入分析后你会发现有对应关系,即A对应doA(),B对应doB()。 . 如果是 doA()、doB()、doC()。 . 与原始代码解耦解决了这个问题。如何解耦?设计一个接口 X 及其实现 A、B 和 C。 每个类都包含一个方法 doX() 并将 doA() 的代码放在 A.doX() 中,将 doB() 的代码放在 B.doX() 中。 . 经过上面的重构,代码还是一样的代码,但是效果完全不同了。我们只需要写: Java 代码

    X x = factory.getBean(var); x.doX();  

这样就可以实现以上功能了。我们看到这里有一家工厂,所有的A,B,C..。 并对应它们的key,写在配置文件中。如果出现新的选项,可以通过修改配置文件无限制的添加。 这种模式虽然有效地提高了代码的质量,但不能被滥用。没必要用 if...else... 由于使用了工厂,在一定程度上增加了代码的复杂度,所以只能在选项比较多的情况下使用,并且增加选项的可能性是高的。另外,要使用这种模式,你可以通过扩展我在附件中提供的抽象类 e 来快速构建一个工厂。如果你的项目放在或其他可配置的框架中,你也可以快速搭建工厂。设计一个 Map 静态属性,使 V 成为这些 A、B、C。 工厂成立。 b.策略模式 也许您已经阅读过有关策略模型(model)的内容,但并没有太深刻的印象。一个简单的例子可以让你快速理解。如果是员工制,员工分为临时工和正式工,不同地方对应的行为是不一样的。在设计它们时,必须设计一个抽象员工类,并设计两个继承类: 和 。这样,临时工和正式工各自的行为就可以通过下钻类型在不同的地方表现出来。在另一个系统中,员工被分为销售人员、技术人员、经理,并且在不同的地方表现不同。同样,我们设计了一个抽象的员工类,并设计了几个继承类:销售员、技术员和经理。现在,我们要把这两个系统合并,也就是说,在新的系统中,员工分为临时工和正式工,同时又分为销售人员、技术人员、管理人员。这次怎么设计? 如果我们要使用以前的设计,我们将不得不设计很多继承类:Sales Temp、Sales 、 Temp、 。 . 这样的设计,随着划分的类型,以及每种类型选项的增加,呈现出笛卡尔式增长。通过以上系统的设计,我们不得不发现,以往学习的继承设计遇到了挑战。 解决继承问题的最好方法是使用策略模式。在这个应用中,之所以将员工分为临时工和正式工,只是因为他们的一些行为不同,比如工资的计算方法不同。如果我们在设计中不将员工类划分为临时工类和正式工类,而只有员工类,只需在类中添加“工资支付策略”即可。当我们创建一个员工对象时,根据员工的类型,将“工资政策”设置为“临时工政策”或“普通工人政策”。在计算工资时,我们只需要调用策略类中的“计算工资”方法即可。 ,其行为的表现,与临时工和正式工的设计是一样的。销售人员战略、技术人员战略、管理人员战略都可以采用相同的设计。一种常见的设计是我们将某个影响较大或选项较少的属性设计为继承类,而将其他属性设计为策略类,可以很好地解决上述问题。
使用策略模式,您还可以使代码保持活力,因为您可以无限制地添加策略。但是,要使用策略模式代码培训,您还需要设计一个工厂——策略工厂。在上面的例子中,您需要设计一个工资政策工厂,并将工厂中的“临时工”映射到“临时劳动政策”,将“正式员工”映射到“正常劳动政策”。 c。适配器模式 我的笔记本电脑是香港产的,插头和我们平时的插座不一样,所以我出差的时候必须带一个适配器去不同的地方使用插座。这是适配器模式最经典的描述。当我们设计的系统想要和其他系统交互,或者我们设计的模块想要和其他模块交互时,交互可能是调用一个接口或者交换一段数据,而接收者经常因为发送者的变化而频繁变化到协议。这种变化可能是接收方来源的变化,例如原来的系统A现在是系统B;也可能是接收器本身的代码变化,比如原来的接口现在增加了一个参数。因为发送方的修改往往会导致接收方代码的不稳定,即频繁修改,给接收方的维护带来困难。 当遇到这样的问题时,有经验的程序员会立即想到使用适配器模式。在设计时,我们的接口是根据某种协议编写的,并且保持不变。然后,在与真实交易对手交互时,在前面部分设计一个适配器类。一旦对方协议发生变化,我可以更换适配器将新协议转换为原协议,问题就解决了。适配器模式应该包含一个接口及其实现类。接口应该包含一个被系统调用的方法,它的实现类是与系统A接口的适配器和与系统B接口的适配器。 .
我曾经需要与项目中的另一个系统交互。起初,该系统以数据集的形式为我提供了数据。我写了一个适配器来接收数据集;后来它被更改为 XML 数据流。 ,我写了另一个接收 XML 的适配器。虽然数据提供给我的方式不同,但是经过适配器转换后的输出数据是一样的。通过配置 in ,我可以灵活切换使用哪个适配器。 d。模板模式 32种经典模式中的模板模式对开发者的代码规划能力提出了更高的要求。它要求开发者具备将自己开发的所有代码关联和抽象的能力,从不同的From模块和不同的功能,抽象出一个流程相对一致的通用流程,最终形成一个模板。例如,读取 XML 并形成工厂是许多模块使用的常用功能。尽管它们不同,但总体流程是相同的:读取 XML 文件、解析 XML 数据流、形成工厂。因为这个特性,他们可以使用一个通用的模板,那么模板模式是什么? 模板模式(Model)通常有一个抽象类。在这个抽象类中,通常有一个main函数按一定的顺序调用其他函数。其他功能往往是这个连续过程中的步骤,比如上面例子中读取XML文件、解析XML数据流、组建工厂的步骤。由于这是一个抽象类,这些步骤函数可以是抽象函数。抽象类只定义了整个流程的执行顺序,以及一些常用的步骤(如读取XML文件和解析XML数据流),而其他更个别的步骤则由其继承的类自己完成(如上例)中的“形成工厂”,因为每个工厂都不一样,所以由各自继承的类来决定它的工厂如何形成)。
每个继承的类都可以根据自己的需要通过重载的方式重新定义每个step函数。但是模板模式要求主函数不能重载,所以正则模板模式的主函数应该是final的(虽然我们经常不这么写)。此外,模板模式还允许您定义此步骤,其中一些步骤是可选的。对于可选步骤,我们通常将它们称为“钩子”。写的时候不是抽象类中的抽象函数,而是什么都不写的空函数。写继承类的时候,如果需要这一步,就重载这个函数,否则什么都不写,然后就当什么都不执行了。 从上面对模板模式的描述可以发现,模板模式可以大大提高代码的复用程度。 以上常见的设计模式可以帮助我们快速提高代码质量。再次强调,设计模式并不是什么高级的东西,相反,它是初学者快速提高的捷径。但是,如果提高代码复用是提高代码质量的初级阶段代码培训,那么使用设计模式只能是提高代码质量的中间阶段。那么,什么是高阶?我认为是那些分析设计理论,更具体地说,是责任驱动设计和领域驱动设计。 3)责任驱动设计和领域驱动设计 正如我前面提到的,当我们尝试编写一些复杂的函数时,我们将函数分解为相对独立的函数。但是这些功能应该分配给哪个类呢?即系统中的所有类都应该具备哪些功能?或者应该表现出什么样的行为?答案就在这里:重在责任,按责任分配行为。我们在分析系统时,首先根据客户需求分析用例,然后根据用例绘制领域模式和分析模型,整个系统的主类就形成了。通过上述分析形成的类往往对应于现实世界中的对象。因此,软件世界中的这些类也具有对应于现实世界对象的职责,以及这些职责中的行为。 责任驱动设计(Drive, RDD)是Craig在他的经典著作《UML与模式应用》中提出的。责任驱动设计的核心思想是,我们在分析和设计一个系统时,要以责任为中心,按责任分配行为。这个想法首先要求我们设计的软件世界中的所有对象都应该尽可能地与现实世界保持一致,他称之为“低表示方差”。低表示差异,一方面提高了代码的可读性,另一方面,当业务发生变化时,能够根据实际情况快速响应变化。 Craig 提出了 GRASP 设计模式,同时提出了责任驱动设计理论来丰富这一理论。在 GRASP 设计模式中,我认为低耦合、高内聚、信息专家模式是最有用的。 在克雷格的责任驱动设计几年后,另一位大师提出了领域驱动设计。领域驱动设计(Drive,DDD)是由埃里克·埃文斯在他的同名著作《领域驱动设计》中提出的。在以往的设计理论中,领域模型是从用例模型到分析模型的中间模型,即从需求分析到软件开发的中间模型。这样的中间模型既不是需求阶段的重要产品,也不是开发阶段的标准,只是作为参考,甚至给人的印象是有些多余。但是,Evans 将其放在领域驱动设计中极其重要的位置。根据领域驱动设计理论,在需求分析阶段,需求分析师利用领域模型与客户进行沟通;在设计和开发阶段,开发人员使用领域模型来指导设计和开发;在运维和二次开发阶段,维护和二次开发开发人员使用领域模型来了解和熟悉系统,并指导他们进行维护和二次开发。总之,在整个软件开发生命周期中,领域模型已经成为核心内容。 领域驱动设计继承自责任驱动设计。在领域驱动设计中,重点仍然是低表示差异和职责分配。但是如何实现低表示差异呢?如何完成职责分配?领域驱动设计给了我们完美的答案,那就是构建领域模型。领域驱动设计改变了我们的设计方式。在需求分析阶段,用例模型不再是这个阶段的核心,而是领域模型的建立。在开发和二次开发阶段,开发者不再埋头于程序堆开始编程,而是先对领域模型进行详细分析。领域驱动设计强调持续的细化,使领域模型不再是分析完成后就扔到一边忽略的图纸。而是在不断理解业务的基础上不断修改和完善领域模型,从而推动我们代码的细化。改变。领域驱动设计不再强调我们在软件开发过程中要做的工作,而是放眼长远,强调在长期持续升级一套软件的过程中我们应该做的工作一段的时间。 In my , is the level of code . At that time, using - was quite a huge , it all our past , and we had to and step by step with our feet on the . - With the of the and the of , the scope of is also . In the past, a only a small of a , but now it has been to an , an , and an chain. In the past, when we a set of , there was only a small of . When it was used for a , we it and a new set. Now, with the of users on , it is to say that we a set of for , but to in a set of , so that the life cycle of this set of lasts for years and . It is the is under such that the of our code, the to and the we , has a key in , and we help but . Key of code : low , high is a of the , , and an and other . : 1。 B is a of A, or A an of B (this a by A that has B in its ). 2。 A calls a of B. 3。 A or a of B. 4。 A is an of B. If an is too on other , once the it on does not exist or , the will no , or will have to be . , will the and of the code. , more , is a of the and of the of in a . An has high if it has , and there is no work other than the tasks those , then the has high , it has low . is like a bossy , it only does what is its of , and other to it to . High- code our code to low and high . , this is so and vague, how can it be ? gurus have us many ways, one of which is Craig's - . - (Drive, RDD) is by Craig in his book "UML and ". To , we first need to "low ". Low means The we is a of the real world, so there is an the world and the real world. When we are , are the rules and of real-world from . If we link the world with the real world in the of and , our will the most laws of more . Such a is "low ".
Using "low " for , what in the real world are to () in the world; what kind of the in the real world have, the in the world have What kind of ; in the real world, the due to its , is in the world as the owned by the . Low makes and of and for ; makes code more and for to ; more , when or 时,设计者只需要遵循事物本来的面貌去思考和修改软件,使软件更加易于变更和扩展。 角色、职责、协作 理解了“低表示差异”,现在我们来看看我们应当如何运用职责驱动设计进行分析和设计。首先,我们通过与客户的沟通和对业务需求的了解,从中提取 出现实世界中的关键事物以及相互之间的关系。这个过程我们通常通过建立领域模型来完成。领域模型建立起来以后,通过诸如 Rose这样的设计软件的正向工程,生成了我们在软件系统中最初始的软件类。这些软件类,由于每个都扮演着现实世界中的一个具体的角色,因而赋予了各自的 职责。前面我已经提到,如果你的系统采用职责驱动设计的思想进行设计开发,作为一个好的习惯,你应当在每一个软件类的注释首行,清楚地描述该软件类的职 责。 当我们完成了系统中软件类的制订,分配好了各自的职责,我们就应该开始根据软件需求,编写各个软件类的功能。在前面我给大家提出了一个建议,就 是不要在一个函数中编写大段的代码。编写大段的代码,通常会降低代码的内聚度,因为这些代码中将包含不是该软件类应当完成的工作。作为一个有经验的开发人 员,在编写一个功能时,首先应当对功能进行分解。一段稍微复杂的功能,通常都可以被分解成一个个相对独立的步骤。步骤与步骤之间存在着交互,那就是数据的 输入输出。通过以上的分解,每一个步骤将形成一个独立的函数,并且使用一个可以表明这个步骤意图的释义函数名。接下来,我们应当考虑的,就是应当将这些函 数交给谁。它们有可能交给原软件类,也有可能交给其它软件类,其分配的原则是什么呢?答案是否清楚,那就是职责。每个软件类代表现实世界的一个事物,或者 说一个角色。在现实世界中这个任务应当由谁来完成,那么在软件世界中,这个函数就应当分配给相应的那个软件类。 通过以上步骤的分解,一个功能就分配给了多个软件类,相互协作地完成这个功能。这样的分析和设计,其代码一定是高内聚的和高可读性的。同时,当 需求发生变更的时候,设计者通过对现实世界的理解,可以非常轻松地找到那个需要修改的软件类,而不会影响其它类,因而也就变得易维护、易变更和低耦合了。 说了这么多,举一个实例也许更能帮助理解。拿一个员工工资系统来说吧。当人力资源在发放一个月工资的时候,以及离职的员工肯定不能再发放工资 了。在系统设计的期初,开发人员商量好,在员工信息中设定一个“离职标志”字段。编写工资发放的开发人员通过查询,将“离职标志”为false的员工查询 出来,并为他们计算和发放工资。但是,随着这个系统的不断使用,编写员工管理的开发人员发现,“离职标志”字段已经不能满足客户的需求,因而将“离职标 志”字段废弃,并增加了一个“离职时间”字段来管理离职的员工。然而,编写工资发放的开发人员并不知道这样的变更,依然使用着“离职标志”字段。显然,这 样的结果就是,软件系统开始对离职员工发放工资了。仔细分析这个问题的原因,我们不难发现,确认员工是否离职天外神坛,并不是“发放工资”软件类应当完成的工作, 而应当是“员工管理”软件类应当完成的。如果将“获取非离职员工”的任务交给“员工管理”软件类,而“发放工资”软件类仅仅只是去调用,那么离职功能由 “离职标志”字段改为了“离职时间”字段,其实就与“发放工资”软件类毫无关系。而作为“员工管理”的开发人员,一旦发生这样的变更,他当然知道去修改自 己相应的“获取非离职员工”函数,这样就不会发生以上问题。通过这样一个实例,也许你能够理解“职责驱动设计”的精要与作用了吧。 职责分配与信息专家 通过以上对职责驱动设计的讲述,我们不难发现,职责驱动设计的精要就是职责分配。但是,在纷繁复杂的软件设计中,如何进行职责分配常常令我们迷惑。幸运的是,大师清楚地认识到了这一点。在他的著作中,信息专家模式为我们提供了帮助。 信息专家模式(又称为专家模式)告诉我们,在分析设计中,应当将职责分配给软件系统中的这样一个软件类,它拥有实现这个职责所必须的信息。我们称这个软件类,叫“信息专家”。用更加简短的话说,就是将职责分配给信息专家。 为什么我们要将职责分配给信息专家呢?我们用上面的例子来说明吧。当“发放工资”软件类需要获取非离职员工时,“员工管理”软件类就是“获取非 离职员工”任务的信息专家,因为它掌握着所有员工的信息。假设我们不将“获取非离职员工”的任务交给“员工管理”软件类,而是另一个软件类X,那么,为了 获取员工信息,软件类X不得不访问“员工管理”软件类,从而使“发放工资”与X耦合,X又与“员工管理”耦合。这样的设计,不如直接将“获取非离职员工” 的任务交给“员工管理”软件类,使得“发放工资”仅仅与“员工管理”耦合,从而有效地降低了系统的整体耦合度。 总之,采用“职责驱动设计”的思路,为我们提高软件开发质量、可读性、可维护性,以及保持软件的持续发展,提供了一个广阔的空间。
【天外神坛】免责声明及帮助
1.重要:如果遇到隐藏内容回复后显示为代码状态,直接刷新一下页面即可解决此问题。
2.本文部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责。
3.若您需要商业运营或用于其他商业活动,请您购买正版授权并合法使用。
4.如果本站有侵犯、不妥之处的资源,请在网站右边客服联系我们。将会第一时间解决!
5.本站所有内容均由互联网收集整理、网友上传,仅供大家参考、学习,不存在任何商业目的与商业用途。
6.本站提供的所有资源仅供参考学习使用,版权归原著所有,禁止下载本站资源参与商业和非法行为,请在24小时之内自行删除!
回复

使用道具 举报

14

主题

1万

帖子

-225

金币

论坛元老

Rank: 8Rank: 8

积分
17402
发表于 2023-2-10 16:26:06 | 显示全部楼层
因为太胖跑起来阻力大很快就来了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

老群被封加此新群不迷路。
上个主题 下个主题 快速回复 返回列表 客服中心 搜索 QQ加群
上个主题 下个主题 快速回复 返回列表 客服中心 搜索 QQ加群

QQ|Archiver|小黑屋|天外神坛

湘ICP备2021015333号

Powered by 天外神坛 X3.4 © 2020-2022 天外神坛