软件质量保证与测试PPT课件第8章高质量编程

上传人:桔**** 文档编号:592467388 上传时间:2024-09-20 格式:PPT 页数:49 大小:488.50KB
返回 下载 相关 举报
软件质量保证与测试PPT课件第8章高质量编程_第1页
第1页 / 共49页
软件质量保证与测试PPT课件第8章高质量编程_第2页
第2页 / 共49页
软件质量保证与测试PPT课件第8章高质量编程_第3页
第3页 / 共49页
软件质量保证与测试PPT课件第8章高质量编程_第4页
第4页 / 共49页
软件质量保证与测试PPT课件第8章高质量编程_第5页
第5页 / 共49页
点击查看更多>>
资源描述

《软件质量保证与测试PPT课件第8章高质量编程》由会员分享,可在线阅读,更多相关《软件质量保证与测试PPT课件第8章高质量编程(49页珍藏版)》请在金锄头文库上搜索。

1、软件质量保证与测试软件质量保证与测试1第8章 高质量编程 2内容提要8.1代码风格8.1.1程序的书写格式8.1.2Windows程序命名规则8.1.3共性规则8.1.4表达式和基本语句8.2函数设计规则8.2.1函数外部特性的注释规则8.2.2参数规则8.2.3返回值的规则8.2.4函数内部的实现规则8.3提高程序质量的技术8.3.1内存管理规则8.3.2面向对象的设计规则8.4代码审查8.4.1代码审查的主要工作8.4.2代码审查的流程8.4.3Java代码审查的常见错误8.5小结38.1 代码风格统一编程风格的意义很大,是一个优秀而且职业化的开发团队所必需的素质。增加开发过程代码的强壮性

2、、可读性、易维护性。减少有经验和无经验开发人员编程所需的脑力工作,为软件的良好维护性打下好的基础。通过人为以及自动的方式对最终软件应用质量标准,使新的开发人员快速适应项目氛围。支持项目资源的复用:允许开发人员从一个项目区域移动到另一个,而不需要重新适应新的子项目团队的氛围。48.1.1程序的书写格式版本的声明格式/Copyright 2011,北京侏罗纪公司XX部/All rights reserved./文件名称:filename.h/文件标识:见配置管理计划书/摘 要:简要描述本文件的内容/ 当前版本:2.1/作 者:输入作者名字/完成日期:2011年3月20日/取代版本:2.0/原作者

3、:输入原作者名字/完成日期:2011年2月10日/5头文件的书写格式 头文件必须包含下列内容:头文件开头处的版权和版本声明。预处理块。函数和类结构声明等。/ 版权和版本声明见示例1-1。#ifndef GRAPHICS_H /防止graphics.h被重复引用#define GRAPHICS_H#include /引用标准库的头文件#include “myheader.h” /引用非标准库的头文件void Function1(); /全局函数声明class Box /类结构声明;#endif6定义文件的书写格式 定义文件的书写格式:必须包含三部分内容:定义文件开头处的版权和版本声明;对一些头文

4、件的引用;程序的实现体(包括数据和代码)。/ 版权和版本声明见示例1-1,此处省略。#include “graphics.h”/引用头文件/全局函数的实现体void Function1()/ 类成员函数的实现体void Draw()7空行的使用 /空行/空行void Function1()/空行/空行void Function2()/空行/空行void Function3()/空行While(condition)statement1;/空行if(condition)statement2;elsestatement3;/ 空行statement4; 8 Void Func1(int x,int

5、y,int z); /良好的风格void Func1(int x,int y,int z); /不良的风格if (year=2000) /良好的风格if(year=2000) /不良的风格if (a=b)&(c=b&c=d) /不良的风格for (i=0;i10;i+) /良好的风格for(i=0;i10;i+) /不良的风格for (I=0;I10;i+) / 过多的空格x = a b ? a : b; /良好的风格x=aFunction(); / 不要写成b - Function();代码行内的空格代码行内的空格 98.1.2Windows程序命名规则匈牙利命名法是一种编程时的命名规范。基

6、本原则是:变量名属性类型对象描述,其中每一对象的名称都要求有明确含义,可以取对象名字全称或名字的一部分。命名要基于容易记忆容易理解的原则。保证名字的连贯性是非常重要的。举例来说,表单的名称为form,那么在匈牙利命名法中可以简写为frm,则当表单变量名称为Switchboard时,变量全称应该为frmSwitchboard。这样可以很容易从变量名看出Switchboard是一个表单,同样,如果此变量类型为标签,那么就应命名成lblSwitchboard。可以看出,匈牙利命名法非常便于记忆,而且使变量名非常清晰易懂,这样,增强了代码的可读性,方便各程序员之间相互交流代码。据说这种命名法是一位叫C

7、harles Simonyi的匈牙利程序员发明的,后来他在微软呆了几年,于是 这种命名法就通过微软的各种产品和文档资料向世界传播开了。现在,大部分程序员不管自己使用什么软件进行开发,或多或少都使用了这种命名法。10常用的数据类型前缀 前缀前缀类型类型例子例子bBOOLbIsParentby,byteBYTEbyFlag,byteFlagchcharchTextfn函数变量fnCallbackhHANDLE(句柄)hWndiintiValuenintnValueuunsigned intuFlagdwDWORDdwDatap指针pBuffersz,str字符串szBufferlpstr,lpsz

8、LPSTRlpstrMessagewWORDwDatax,y坐标xPos,yPosm_类成员变量m_bFlag,m_nValg_全局变量g_bFlag,g_nMsg11常用的控件名前缀 前缀前缀控件类型控件类型frm,wnd窗口cmd,btn按钮cmb,combo下拉式列表框txt文本输入框lbl标签grdGrid,网格scr滚动条lst列表框frame框架128.1.3共性规则 提示1较短的单词可通过去掉“元音”形成缩写;较长的单词可取单词的头几个字母形成缩写;一些单词有大家公认的缩写。示例:如下单词的缩写能够被大家基本认可。temp 可缩写为 tmp ;flag 可缩写为 flg ;sta

9、tistic 可缩写为 stat ;increment 可缩写为 inc ;message 可缩写为 msg ;提示2 应该在源文件的开始之处,对文件中所使用的缩写或约定,特别是特殊的缩写,进行必要的注释说明。标识符最好采用英文单词或其组合,便于记忆和阅读,可望文知意,不必进行“解码”。不能使用汉语拼音来命名。程序中的英文单词一般不会太复杂,用词应当准确。例如不要把CurrentValue写成NowValue。138.1.4表达式和基本语句1. 表达式与复合表达式示例如下表达式,考虑不周就可能出问题,也较难理解。* stat_poi + += 1;* + stat_poi += 1;应分别改为

10、如下。*stat_poi += 1;stat_poi+; / 此二语句功能相当于“ * stat_poi + += 1; ”和+ stat_poi;*stat_poi += 1; / 此二语句功能相当于“ * + stat_poi += 1; ”14if语句 假设布尔变量名字为flag,它与零值比较的标准if语句如下:if (flag)/ 表示flag为真if (!flag)/ 表示flag为假其它的用法都属于不良风格,例如:if (flag = TRUE)if (flag = 1 )if (flag = FALSE)if (flag = 0)/我觉得应该采用if(flag = TRUE)来表

11、示,赋值用flag = TRUE;/因为不同操作系统的TRUE和FALSE不一样,如WINDOWS里TRUE是1而有些系统/TRUE是0 cyj应当将整型变量用“=”或“!=”直接与0比较。假设整型变量的名字为value,它与零值比较的标准if语句如下:if (value = 0)if (value != 0)不可模仿布尔变量的风格而写成if (value)/ 会让人误解value是布尔变量if (!value)不可将浮点变量用“=”或“!=”与任何数字比较。15循环语句的效率 For(row=0; row100; row+)for( col=0; col5; col+ )sum=sum+ar

12、owcol;for (col=0; col5; col+ )for (row=0; row100; row+) sum=sum+arowcol;16建议【建议】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。例如,下面代码: for (I=0; iN; I+)if (condition) DoSomething();Else DoOtherthing();if (condition)for (i=0; iN; i+) DoSomething();else for (i=0; iN; i+) DoOtherthing();17建议【建议】for语句的循环控制变量的取值采

13、用“半开半闭区间”写法。例如for (x=0; xN; x+)for (x=0; x=N-1; x+)18C+类中的常量 不能在类声明中初始化const数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道SIZE的值是什么。class A const int SIZE = 100; / 错误,企图在类声明中初始化const数据成员int arraySIZE;/ 错误,未知的SIZE;const数据成员的初始化只能在类构造函数的初始化表中进行,例如class A A(int size);/ 构造函数 const int SIZE ; ;A:A(int size) : SIZE(siz

14、e)/ 构造函数的初始化表 A a(100);/ 对象a的SIZE值为100A b(200);/ 对象b的SIZE值为200198.2 函数设计规则函数是C+/C程序的基本功能单元,其重要性不言而喻。函数设计的细微缺点很容易导致该函数被错用,所以光使函数的功能正确是不够的。函数接口的两个要素是参数和返回值。C+ 语言中,函数的参数和返回值的传递方式有三种:值传递(pass by value)、指针传递(pass by pointer)、引用传递(pass by reference)。208.2.1函数外部特性的注释规则函数外部特性注释必须在函数体上部采用中文说明,标准格式如下:/输入参数:/参

15、数1:(指出参数的物理意义、量纲、取值范围等信息)/参数N:/函数返回:/ (指出返回值的物理意义、量纲、取值范围等信息)/功能描述:/注意事项:/218.2.2参数规则建议对仅作为输入的参数,尽量使用const修饰符。如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。参数缺省值只能出现在函数的声明中,而不能出现在定义体中。例如:void Foo(int x=0,int y=0);/ 正确,缺省值出现在函数的声明中void Foo(int x=0,int y=0)/ 错误,缺省值出现在函数的定义体中如果函数有多个参数,

16、参数只能从后向前挨个儿缺省,否则将导致函数调用语句怪模怪样。例如:void Foo(int x,int y=0,int z=0);/正确void Foo(int x=0,int y,int z=0);/错误【建议】避免函数有太多的参数,参数个数尽量控制在5个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。【建议】尽量不要使用类型和数目不确定的参数。C标准库函数printf是采用不确定参数的典型代表,其原型为:int printf(const chat *format,argument);这种风格的函数在编译时丧失了严格的类型安全检查。【建议】非调度函数应减少或防止控制参数,尽量只使用数据

17、参数。该规则可降低代码的控制耦合。228.2.3返回值的规则【建议】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。【建议】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。【建议】函数要尽量只有一个返回点。不能返回引用和指针到局部对象。说明 离开函数作用域时会销毁局部对象;使用销毁了的对象会造成灾难。不可返回由 new 初始化,之后又已解除引用的指针。说明 由于支持链式表达式,造成返回对象不能删除,导致内存泄漏。238.2.4函数内部的实现规则提示在同一项目组应明确规定对接口函

18、数参数的合法性检查应由函数的调用者负责还是由接口函数本身负责,缺省是由函数调用者负责。说明:对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。(C+代码)使 operator= 检查自赋值。说明 执行检查有两点重要的理由:首先,派生类对象的赋值涉及到调用每一个基类(在继承层次结构中位于此类的上方)的赋值操作符,跳过这些操作符就可以节省很多运行时间。其次,在复制“rvalue

19、”对象前,赋值涉及到解构“lvalue”对象。在自赋值时,rvalue 对象在赋值前就已销毁了,因此赋值的结果是不确定的。248.3 提高程序质量的技术8.3.1内存管理规则Bill Gates在1981年曾经说过:640K ought to be enough for everybody。程序员们经常编写内存管理程序,往往提心吊胆。同时,由于个人编程的习惯性缺陷,导致同类问题重复出现,如果不想触雷,唯一的解决办法就是发现所有潜伏的地雷并且排除它们。25内存分配方式按照内存分配的位置不同,内存分配方式有三种:从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存

20、在。例如全局变量,static变量。在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。26常见的错误情况 内存分配未成功,却使用了它。内存分配虽然成功,但是尚未初始化就引用它。内存分配成功并且已经初始化,但操作越过了内存的边界。忘记了释放内存,造成内存泄露。释放了内存却继续

21、使用它。程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。27注意事项 不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。动态内

22、存的申请与释放必须配对,防止内存泄漏。用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。用delete释放对象数组时,留意不要丢了符号。检查程序中内存申请/释放操作的成对性,防止内存泄漏。28指针与数组的区别 char a = “hello”;a0 = X;cout a endl;char *p = “world”; / 注意p指向常量字符串p0 = X; / 编译器不能发现该错误cout p endl;29内存中指针参数的传递 vo

23、id GetMemory(char *p,int num)p=(char*)malloc(sizeof(char)*num);void Test(void)char *str = NULL;GetMemory(str,100); / str 仍然为 NULLstrcpy(str,hello); / 运行错误30示例用指向指针的指针申请动态内存 如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见示例。void GetMemory2(char *p,int num) *p = (char *)malloc(sizeof(char) * num);void Test2(void) c

24、har *str = NULL; GetMemory2(&str,100); / 注意参数是 &str,而不是str strcpy(str,hello); cout str endl; free(str); 318.3.2面向对象的设计规则 比较面向对象程序设计和面向过程程序设计,还可以得到面向对象程序设计的其他优点:数据抽象的概念可以在保持外部接口不变的情况下改变内部实现,从而减少甚至避免对外界的干扰;通过继承大幅减少冗余的代码,并可以方便地扩展现有代码,提高编码效率,也减低了出错概率,降低软件维护的难度;结合面向对象分析、面向对象设计,允许将问题域中的对象直接映射到程序中,减少软件开发过程

25、中中间环节的转换过程;通过对对象的辨别、划分可以将软件系统分割为若干相对为独立的部分,在一定程度上更便于控制软件复杂度;以对象为中心的设计可以帮助开发人员从静态(属性)和动态(方法)两个方面把握问题,从而更好地实现系统;通过对象的聚合、联合可以在保证封装与抽象的原则下实现对象在内在结构以及外在功能上的扩充,从而实现对象由低到高的升级。32开-闭原则(Open-Closed Principle,OCP) 开-闭原则的定义及优点实现“开-闭”原则对可变性的封装原则33里氏代换原则(Liskov Substitution Principle,LSP) 里氏代换原则的定义定义:如果对每一个类型为T1的

26、对象O1,都有类型为T2 的对象O2,使得以T1定义的所有程序P在所有的对象O1都代换为O2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。即,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类。而且它觉察不出基类对象和子类对象的区别。也就是说,在软件里面,把基类都替换成它的子类,程序的行为没有变化。反过来的代换不成立,如果一个软件实体使用的是一个子类的话,那么它不一定适用于基类。任何基类可以出现的地方,子类一定可以出现。基于契约的设计、抽象出公共部分作为抽象基类的设计。里氏代换原则与“开-闭”原则的关系实现“开-闭”原则的关键步骤是抽象化。基类与子类之间的继承关系就是抽象化

27、的体现。因此里氏代换原则是对实现抽象化的具体步骤的规范。违反里氏代换原则意味着违反了“开-闭”原则,反之未必。34依赖倒转原则(dependence inversion principle,DIP) 接口实例/* Interface IManeuverable provides the specification for a maneuverable vehicle */public interface IManeuverable public void left(); public void right(); public void forward(); public void revers

28、e(); public void climb(); public void dive(); public void setspeed(double speed); public double getspeed(); public class car implements IManeuverablepublic class Board implements IManeuverablepublic class Submarine implements IManeuverable35讨论依赖倒转原则关系我们决定耦合的程度的依据何在呢?怎样将大系统拆分成小系统怎样做到依赖倒转?依赖倒转原则的优缺点36

29、合成/聚合复用原则(CARP) 概念合成和聚合合成/聚合的优缺点继承来进行复用的优缺点37迪米特法则(Law of Demeter,LoD) 概述狭义的迪米特法则狭义的迪米特法则的缺点广义的迪米特法则广义迪米特法则在类的设计上的体现38接口隔离原则(interface separate principle,ISP) 概念如何实现接口隔离原则不应该强迫用户依赖于他们不用的方法。利用委托分离接口。利用多继承分离接口。39一些基本的设计模式 Abstract Factory:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。Adapter:将一个类的接口转换成客户希望的另外一个接口

30、。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。Bridge:将抽象部分与它的实现部分分离,使它们都可以独立地变化。Builder:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。Chain of Responsibility:为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。Command:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。40Composite:将对象组合成树形结构以表

31、示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。Decorator:动态地给一个对象添加一些额外的职责。就扩展功能而言,它比生成子类方式更为灵活。Facade:为子系统中的一组接口提供一个一致的界面,F a c a d e模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。Factory Method:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。Flyweight:运用共享技术有效地支持大量细粒度的对象。Interpreter:给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释

32、器使用该表示来解释语言中的句子。41Iterator:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。Mediator:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。Memento:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。Observer:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。Prototype:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创

33、建新的对象。Proxy:为其他对象提供一个代理以控制对这个对象的访问。Singleton:保证一个类仅有一个实例,并提供一个访问它的全局访问点。42State:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。Strategy:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。Template Method:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。Visitor:表示一个作用于某对象结构中的各元素的操作。它使

34、你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。438.4 代码审查8.4.1代码审查的主要工作代码审查的主要工作:发现代码中的bug;从代码的易维护性、可扩展性角度考察代码的质量,提出修改建议。是否符合java开发规范和代码审核检查表代码编写者,代码审核者共同对代码的质量承担责任。这样才能保证Code Review不是走过场,其中代码编写者承担主要责任,代码审核者承担次要责任。448.4.2代码审查的流程重要性重要性激激活活级别检查项重要重要20命名规则是否与所采用的规范保持一致?重要重要50has/can/is前缀的函数是否返回布尔型?重要重要10注释是否较清晰且必要?重要重要

35、Y50函数是否已经有文档注释?(功能、输入、返回及其他其他可选)声明、空白、声明、空白、缩进20每行是否只声明了一个变量?(特别是那些可能出错的类型)重要重要40变量是否已经在定义的同时初始化?语句句/功能分布功能分布/规模模4520if/if-else/if-else if-else/do-while/switch-case语句的格式是否符合规范?重要重要40单个函数是否执行了单个功能并与其命名相符?Y20操作符和 操作符的应用是否复合规范?规模模重要重要Y40常数变量是否声明为final?重要重要70对数组的访问是否是安全的?(合法的index取值为0,MAX_SIZE-1)。重要重要20

36、是否确认没有同名变量局部重复定义问题?重要重要Y20所有判断是否都使用了(常量=变量)的形式?重要重要80是否每个if-else if-else语句都有最后一个else以确保处理了全集?重要重要80是否每个switch-case语句都有最后一个default以确保处理了全集?重要重要40XML标记书写是否完整,字符串的拼写是否正确?重要重要40对浮点数值的相等判断是否是恰当的?(严禁使用=直接判断)46可靠性(函数)可靠性(函数)重要重要Y60入口对象是否都被进行了判断不为空?重要重要Y60入口数据的合法范围是否都被进行了判断?(尤其是数组)重要重要Y20是否对有异常抛出的方法都执行了try.

37、catch保护?重要重要Y80是否函数的所有分支都有返回值?重要重要60是否确保函数返回CORBA对象的任何一个属性都不能为null?重要重要60是否对方法返回值对象做了null检查,该返回值定义时是否被初始化?重要重要60是否对同步对象的遍历访问做了代码同步?重要重要80是否确认在对Map对象使用迭代遍历过程中没有做增减元素操作?20原子操作代码异常中断,使用的相关外部变量是否恢复先前状态?重要重要100函数对错误的处理是恰当的?47可可维护性性重要重要100实现代码中是否消除了直接常量?(用于计数起点的简单常数例外)20是否有冗余判断语句?(如:if (b) return true; else return false;)488.4.3Java代码审查的常见错误 多次拷贝字符串没有克隆返回的对象自编代码来拷贝数组检查new 操作的结果是否为null在catch 块中作清除工作增加不必要的catch 块498.5 小结本章内容均围绕如何产生高质量的代码,从而减轻后续测试压力和降低修改的成本。介绍了与代码风格相关的程序的版式,windows的命名规则,并列出了一些共性规则,供读者参考。在编程方面着重阐述了内存管理的技巧和面向对象编程的规则以及设计模式的应用。代码审查方面介绍了Java编程中的注意事项。

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 医学/心理学 > 基础医学

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号