质量属性II

Posted ruangongyouxi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了质量属性II相关的知识,希望对你有一定的参考价值。

代码层实现六种质量属性战术——《信息领域热词分析》

2020-03-01

首先对可修改性战术进行分析。

可修改性战术的目标是控制实现、测试和部署变更的时间和成本。

我们可以把修改性战术根据其目标进行分组。

一组和修改性战术的目标是减少由某个变更直接影响的模块的数量。我们把这组可修改性战术称为“局部化修改”;另一组可修改性战术的目标是限制对局部化的模块的修改。我们使用这组战术来“防止连锁反应”;这两组战术之间的差别是有直接受变更影响的模块(那些调整其责任来完成变更的模块)和间接受变更影响的模块(那些其责任保持不变,但必须改变其实现来适应直接受彫响的模块)。第三组战术的目标是控制部署时间和成本。我们把这组战术叫做“延迟绑定时间”。

首先是“局部化修改”。

尽管在一组变更所影响的模块的数量和实现这些变更的成本之间不一定有一个准确的关系,但把修改限制在一小组模块内般会降低成本。这组战术的目标是在设计期间为模块分配责任,以把预期的变更限制在一定的范围内。我们标识了如下几个战术:

维持语义的一致性。语义的一致性指的是模块中责任之间的关系。目标是确保所有这些责任都能够协同工作,不需要过多地依赖其他模块。该目标是通过选择具有语义一致性的责任来实现的。耦合和内聚指标是度量语义一致性的尝试,但它们遗漏了变更的上下文。相反,应该根据一组预期的变更来度量语义一致性。其中的一个子战术就是“抽象通用服务”。通过专门的模块提供通用服务通常被视作支持重用。这是正确的,但抽象通用服务也支持可修改性。如果己经抽象出了通用服务,那么,对这些通用服务的修改只需要进行一次,而不需要在使用这些服务的每个模块中都进行修改。此外,对使用这些服务的模块的修改不会影响其他的用户。因此,该战术不仅支持局部化修改,而且还能够防止连锁反应。抽象通用服务的示例就是应用框架的使用和其他中间件软件的使用。

下面从数据库连接代码编写的层面对这一战术进行实践。

DBUtil.java

package hotWords.util;

import java.sql.*;

public class DBUtil {

    

    public  static  Connection getConnection() throws ClassNotFoundException, SQLException {

        String JDBC_DRIVER = "com.mysql.jdbc.Driver";  

        String DB_URL = "jdbc:mysql://localhost:3306/hotwords?useUnicode=true&characterEncoding=UTF-8&useSSL=false";

        String USER = "root";

        String PASS = "";

        @SuppressWarnings("unused")

        Connection conn = null;

        Class.forName(JDBC_DRIVER);

        System.out.println("连接数据库...");

        return conn = DriverManager.getConnection(DB_URL,USER,PASS);

    }

    public static void close(Connection connection ) {

        try {

            if (connection != null) {

                connection.close();

            }

            

        } catch (SQLException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }

    public static void close(PreparedStatement preparedStatement ) {

        try {

            if (preparedStatement != null) {

                preparedStatement.close();

            }

            

        } catch (SQLException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }

    public static void close(ResultSet resultSet ) {

        try {

            if (resultSet != null) {

                resultSet.close();

            }

            

        } catch (SQLException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }    

}

将数据库连接的操作这一通用服务存放到一个文件里面,其他模块可以直接调用,修改时(修改数据库连接用户名密码等)只需修改这一个文件,其余内容不用修改。

泛化该模块。使一个模块更通用能够使它根据输入计算更广泛的功能。可以把该输入看作是为该模块定义了一种语言,这可能会如同使常数成为输入参数一样简单;也可能会如同把该模块实现为解释程序,并使输入参数成为解释程序的语言中的程序一样复杂。模块越通用,越有可能通过调整语言而非修改模块来进行请求的变更。

下面从数据库数据读取代码编写的层面对这一战术进行实践。

LoadDaoImpl.java

package hotWords.dao;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.List;

import hotWords.bean.Explain;

import hotWords.bean.News;

import hotWords.bean.Relation;

import hotWords.bean.Title;

import hotWords.util.DBUtil;

public class LoadDaoImpl {

    public List<Explain> loadhot() throws ClassNotFoundException, SQLException {

        Connection connection=DBUtil.getConnection();

        String sql="select * from keywords";

        PreparedStatement preparedStatement=null;

        ResultSet resultSet=null;

        Explain explain=null;

        List<Explain> explains=new ArrayList<>();

        int flag=1;

        try {

            preparedStatement=connection.prepareStatement(sql);

            resultSet = preparedStatement.executeQuery();

            while(resultSet.next()) {

                if(resultSet.getInt("num")<300)

                    continue;

                explain=new Explain();

                explain.setWords(resultSet.getString("word"));

                explain.setNum(resultSet.getInt("num"));

                explain.setExp(resultSet.getString("exp"));

                explain.setId(flag);

                explains.add(explain);

                flag++;

            }

        }catch (Exception e) {

            // TODO: handle exception

            e.printStackTrace();

        }finally {

            DBUtil.close(resultSet);

            DBUtil.close(preparedStatement);

            DBUtil.close(connection);

        }

        return explains;

    }

    

    public List<Explain> load() throws ClassNotFoundException, SQLException {

        Connection connection=DBUtil.getConnection();

        String sql="select * from keywords";

        PreparedStatement preparedStatement=null;

        ResultSet resultSet=null;

        Explain explain=null;

        List<Explain> explains=new ArrayList<>();

        int flag=1;

        try {

            preparedStatement=connection.prepareStatement(sql);

            resultSet = preparedStatement.executeQuery();

            while(resultSet.next()) {

                explain=new Explain();

                explain.setWords(resultSet.getString("word"));

                explain.setNum(resultSet.getInt("num"));

                explain.setExp(resultSet.getString("exp"));

                explain.setId(flag);

                explains.add(explain);

                flag++;

            }

        }catch (Exception e) {

            // TODO: handle exception

            e.printStackTrace();

        }finally {

            DBUtil.close(resultSet);

            DBUtil.close(preparedStatement);

            DBUtil.close(connection);

        }

        return explains;

    }

    

    public Explain loadmore(String wordname) throws ClassNotFoundException, SQLException {

        Connection connection=DBUtil.getConnection();

        String sql="select * from keywords where word = "+"‘"+wordname+"‘";

        PreparedStatement preparedStatement=null;

        ResultSet resultSet=null;

        Explain explain=null;

        try {

            preparedStatement=connection.prepareStatement(sql);

            resultSet = preparedStatement.executeQuery();

            while(resultSet.next()) {

                explain=new Explain();

                explain.setWords(resultSet.getString("word"));

                explain.setExp(resultSet.getString("exp"));

            }

        }catch (Exception e) {

            // TODO: handle exception

            e.printStackTrace();

        }finally {

            DBUtil.close(resultSet);

            DBUtil.close(preparedStatement);

            DBUtil.close(connection);

        }

        return explain;

    }

    @SuppressWarnings("null")

    public List<News> loadnews(String wordname) throws ClassNotFoundException, SQLException {

        Connection connection=DBUtil.getConnection();

        String sql="select * from words where word = "+"‘"+wordname+"‘";

        PreparedStatement preparedStatement=null;

        ResultSet resultSet=null;

        List<News> news=new ArrayList<>();

        News n=null;

        try {

            preparedStatement=connection.prepareStatement(sql);

            resultSet = preparedStatement.executeQuery();

            while(resultSet.next()) {

                n=new News();

                n.setTitle(resultSet.getString("title"));

                n.setLink(resultSet.getString("link"));

                news.add(n);

            }

        }catch (Exception e) {

            // TODO: handle exception

            e.printStackTrace();

        }finally {

            DBUtil.close(resultSet);

            DBUtil.close(preparedStatement);

            DBUtil.close(connection);

        }

        return news;

    }

    public List<Relation> loadrelation(Title title) throws ClassNotFoundException, SQLException {

        Connection connection=DBUtil.getConnection();

        String sql="select * from words where title = "+"‘"+title.getTitle()+"‘";

        PreparedStatement preparedStatement=null;

        ResultSet resultSet=null;

        List<Relation> relations=new ArrayList<>();

        Relation relation=null;

        try {

            preparedStatement=connection.prepareStatement(sql);

            resultSet = preparedStatement.executeQuery();

            while(resultSet.next()) {

                relation=new Relation();

                relation.setTitle(resultSet.getString("title"));

                relation.setWord(resultSet.getString("word"));

                relations.add(relation);

            }

        }catch (Exception e) {

            // TODO: handle exception

            e.printStackTrace();

        }finally {

            DBUtil.close(resultSet);

            DBUtil.close(preparedStatement);

            DBUtil.close(connection);

        }

        return relations;

    }

    public List<Title> loadtitle() throws ClassNotFoundException, SQLException {

        Connection connection=DBUtil.getConnection();

        String sql="select * from tit";

        PreparedStatement preparedStatement=null;

        ResultSet resultSet=null;

        List<Title> titles=new ArrayList<>();

        Title title=null;

        try {

            preparedStatement=connection.prepareStatement(sql);

            resultSet = preparedStatement.executeQuery();

            while(resultSet.next()) {

                if(Integer.parseInt(resultSet.getString("num"))<14)

                    continue;

                title=new Title();

                title.setTitle(resultSet.getString("title"));

                titles.add(title);

            }

        }catch (Exception e) {

            // TODO: handle exception

            e.printStackTrace();

        }finally {

            DBUtil.close(resultSet);

            DBUtil.close(preparedStatement);

            DBUtil.close(connection);

        }

        return titles;

    }

}

关于数据加载并且处理后存放的方法都在这一个文件里面,只要导入这个文件,热词系统中所有关于数据调用的方法都可以使用。

“防止连锁反应”

修改所产生的一个连锁反应就是需要改变该修改并没有直接影响到的模块。例如,如果改变了模块A以完成某个特定的修改,那么必须改变模块B,这仅仅是因为改变了模块A。必须修改模块B,在某种意义上来说,是因为它依赖于模块A。

请注意,在我们所讨论的战术中,没有任何一个战术一定能够防止语义变更的连锁反应。

信息隐藏。信息隐藏就是把某个实体(一个系统或系统的某个分解)的责任分解为更小的部分,并选择使哪些信息成为公有的,哪些信息成为私有的。可以通过指定的接口获得公有责任。信息隐藏的目的是将变更隔离在一个模块内,防止变更扩散到其他模块。这是防止变更扩散的最早的技术。它与“预期期望的变更有很大关系”,因为它使用那些变更作为分解的基础。

下面展示一个“导出word文档”编码的实践。

PrintWord.java

package hotWords.dao;

import java.io.IOException;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.sql.SQLException;

import hotWords.bean.Explain;

public class PrintWord {

public void word() throws ClassNotFoundException, SQLException {

        LoadDaoImpl loadDaoImpl=new LoadDaoImpl();

        List<Explain> explains=null;    

        explains=loadDaoImpl.load();

        String title = "热词解释文档";

        Map<String,Object> map = new HashMap<String,Object>();

        map.put("title", title);

        List<Map<String,String>> excelMapList = new ArrayList<Map<String,String>>();

        Map<String,String> excelMapTemp = null;

        for (Explain explain :explains) {

            if(explain.getExp().equals(""))

            {

                explain.setExp("未在百科中爬取到此信息。");

            }

            excelMapTemp = new HashMap<String,String>();

            excelMapTemp.put("excel.no1", explain.getWords());

            excelMapTemp.put("excel.no2", explain.getExp());

            excelMapList.add(excelMapTemp);

        }

        

        //模板存放位置

        String demoTemplate = "C:/study/eclipse_work/WordsHot/WebContent/file/in.docx";

        //生成文档存放位置

        String targetPath = "C:/study/eclipse_work/WordsHot/WebContent/file/out.doc";

        

        //初始化导出

        WordExport export = new WordExport(demoTemplate);

        try {

            export.init();

        } catch (IOException e) {

            e.printStackTrace();

        }

        try {

            export.export(map);

            export.export(excelMapList,0);

            export.generate(targetPath);

        } catch (Exception e) {

            e.printStackTrace();

        }

        

    }

}

在导出word文档这一功能里,把所有的导出文档处理都隔离在了“WordExport.java”里面,通过“toWord.java”对里面的方法进行调用。在修改文档导出方法时绝对不会影响到其他功能。

“推迟绑定时间”。

到目前为止已经讨论的两个战术分类经过了精心设计,以使实现修改所要求改变的模块的数量最少。我们的可修改性场景包括通过减少需要修改的模块的数量不能满足的两个元素——部署时间以及允许非开发人员进行修改。推迟绑定时间支持这两个场景,但需要提供额外的基础结构来支持后期绑定。暂时没有这两个场景。

前端部分,我将总的html文件和它所调用的jsp文件分开存放以方便管理。当需要增加新的功能页面时,在jsp文件夹里增加相应的文件并且在index.html中增加调用的代码即可。

后台数据处理部分,我将实体类、数据库连接、数据库数据处理三部分进行了划分。数据的获取与处理在dao层中执行,在其中会对util中的数据库连接方法以及bean中的实体类进行调用实现数据的读取、处理、存放。

一个系统的可用性主要是体现在这个系统的系统服务不中断运行时间占实际运行时间的比例,系统的伸缩性则是指在不改变系统软硬件设计,仅仅通过新增服务器的情况下,就能提升系统的处理能力,而系统的可扩展性是指该系统适应变化的能力。

例如在数据库的连接中,会加入如下代码。

try {

rs.close();

} catch (SQLException e) {

e.printStackTrace();

}

通过这样的抛出异常机制,在数据库的连接中可以诊断异常,并且抛出。

性能最重要的就是时间和空间的合理划分。响应时间越短对于空间的要求往往也会非常高,不能顾此失彼,一个高性能的系统应该是两者都能兼顾到。

在编码的层面有如下实践。

资源需求:

1.减少一个事件流所用的资源

1.1:尽量避免随意使用静态变量。多使用局部变量

1.2:尽量使用基本数据类型代替对象。基本类型数据产生和处理都在栈中处理,对象是在堆中产生实例。

String str=“hello”;

String str =new String("hello");

1.3:使用移位代替乘除运算,左乘右除.<<,>>

1.4:尽量避免使用二维数组

1.5:频繁调用结构简单的函数设置为内联函数

2.减少处理事件的数量。

控制采样频率。

3.控制资源的使用

超时任务的返回设定。

应用:病毒动态演示时当某一天异常超时时,跳过渲染下一天地图。

jdk1.5自带的并发库中Future类。

Future类中重要方法包括get()和cancel()。get()获取数据对象,如果数据没有加载,就会阻塞直到取到数据,而 cancel()是取消数据加载。另外一个get(timeout)操作,表示如果在timeout时间内没有取到就失败返回,而不再阻塞。

安全性对于网站来说有与抵抗攻击有关的战术、与检测攻击有关的战术以及从攻击中恢复有关的战术。

从用户信息的安全性进行实践。

对原项目修改采用spring security来作为SpringBoot的身份验证和权限授予的插件。

<dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-security</artifactId>

</dependency>

软件可测试性是指通过测试(通常是基于运行的测试)揭示软件缺陷的容易程度。软件的开发和测试是并行的,这就需要测试人员尽快找出软件的关键bug并且快速的修复他,这就需要程序员提高软件的可测试性,让软件在测试阶段更加方便明了的被测试,而不是结构混乱,难以测试,难以维护。

可测试性战术的目标是允许在完成软件开发的一个增量后,轻松地对软件进行测试。

在记录回放这一操作进行实践展示。

public static Connection getConn () {

        Connection conn = null;

        

        try {

            Class.forName("com.mysql.jdbc.Driver");

            conn = DriverManager.getConnection(db_url, db_user, db_pass);

        } catch (Exception e) {

            e.printStackTrace();

        }

        

        return conn;

    }

java中的撤销操作实践如下。

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.undo.*;

 

class UndoDemo extends JFrame {

 

    static JTextArea text = new JTextArea();

    static JPanel pnl = new JPanel();

    static JButton unbtn = new JButton("撤销");

    static JButton rebtn = new JButton("恢复");

    static UndoManager undomg = new UndoManager();

 

    UndoDemo() {

 

        super("撤销、恢复功能实例");

        setVisible(true);

        setSize(400,300);

        setLocationRelativeTo(null);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setLayout(new BorderLayout(5,5));

 

        pnl.setLayout(new FlowLayout(5));

        pnl.add(unbtn);

        pnl.add(rebtn);

        add(pnl,BorderLayout.NORTH);

        add(text,BorderLayout.CENTER);

 

        text.getDocument().addUndoableEditListener(undomg);

 

        unbtn.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent evt) {

                if(undomg.canUndo()) {

                    undomg.undo();

                } else {

                    JOptionPane.showMessageDialog(null,"无法撤销","警告",JOptionPane.WARNING_MESSAGE);

                }

            }

        });

 

        rebtn.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent evt) {

                if(undomg.canRedo()) {

                    undomg.redo();

                } else {

                    JOptionPane.showMessageDialog(null,"无法恢复","警告",JOptionPane.WARNING_MESSAGE);

                }

            }

        });

    }

 

    public static void main(String[] args) {

        new UndoDemo();

    }

}

易用性关注的是对用户来说完成某个期望任务的容易程度和系统所提供的用户支持的种类。

易用性(usability)是一种以使用者为中心的设计概念,易用性设计的重点在于让产品的设计能够符合使用者的习惯与需求。

以因特网网站的设计为例,希望让使用者在浏览的过程中不会产生压力或感到挫折,并能让使用者在使用网站功能时,能用最少的努力发挥最大的效能。

在网站的使用过程中比较人性化的操作是,登陆了一次后的一段时间内,可以免登录再次访问。具体的实现如下所示。

self.driver = webdriver.Chrome()

#下面这个不能省,就是必须先让浏览器打开一个网页,至于打开哪个网页不重要,不然会报错:unable to set cookie

self.driver.get(‘https://t.bilibili.com/‘)  

 

cookies = [{‘domain‘: ‘.bilibili.com‘, ‘expiry‘: 1577689702.126078, ‘httpOnly‘: False, ‘name‘: ‘sid‘, ‘path‘: ‘/‘, ‘secure‘: False, ‘value‘: ‘hrbxcwgk‘},{‘domain‘: ‘.t.bilibili.com‘, ‘httpOnly‘: False, ‘name‘: ‘Hm_lpvt_8a6e55dbd2870f0f5bc9194cddf32a02‘, ‘path‘: ‘/‘, ‘secure‘: False, ‘value‘: ‘1546160927‘}]

 

for cookie in cookies:

    self.driver.add_cookie(cookie)  #循环添加cookie

self.driver.get(‘https://t.bilibili.com/‘)

self.driver.maximize_window()

time.sleep(3)

质量属性亦被称为非功能性需求和跨职能约束,我将其理解为,质量属性与系统具体功能无关,一个系统质量属性会约束多个系统功能。软件架构被定义为:有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计。系统的各个重要组成部分及其关系构成了系统的架构,这些组成部分可以是具体的功能模块,也可以是非功能设计与决策,他们相互关系组成一个整体,共同构成了软件系统的架构。一般来说,除了当前的系统功能需求外,软件架构设计过程中还需要关注可用性、可修改性、性能、安全性、可测试性、易用性等六个方面的质量属性,并且还需要平衡这六个方面的关系以实现需求和架构目标,也可以通过观察这些质量属性来衡量一个软件架构设计的优劣,判断其是否满足期望。

 

以上是关于质量属性II的主要内容,如果未能解决你的问题,请参考以下文章

software construction第一章第二节 软件开发的质量属性

软件质量属性简述

几类系统需要关注的质量属性

基于框架的应用系统的质量属性

质量属性的六个常见属性场景分析

淘宝网描绘质量属性六个常见属性场景