您的位置:首页 > 其它

嵌入式软件的重构思想

2014-12-22 13:21 253 查看

一. 概述:

有人把《重构——改善既有代码的设计》和《设计模式》并列为“JAVA行业的圣经”,这实在并不为过,其实,还可以称为面向对象软件开发的圣经,本人对面向对象略知一二,多年都是在嵌入式领域,采用结构化设计开发软件,但是,通过培训和自学习《重构——改善既有代码的设计》这本书,对软件的开发有了更深的理解,甚至有茅塞顿开的感觉,怪不得我们的软件质量提不高,怪不得我们的软件难以维护,怪不得我们的单元测试开展不起来,怪不得我们的模块化工作作不好。

同时,虽然开发模式不一样,但是,软件的核心思想是一样的,所以我决心把这本书的内容重新整理,移植,使之应用到嵌入式,结构化软件开发,让它同样服务于我们这些暂时还走不出结构化设计领域的开发人员。面向对象和结构化毕竟还是不同,面向对象侧重封装,继承,多态性,结构化设计对于封装还有一些表现,但是对于继承和多态性就实在是不一样了,但是即使这样,很多重要的思想却是共同的,模块化,可测试性,可维护性,可扩展性,软件重构的这些功能,也是所有软件都应该具备的特点,实际上,对于封装,结构化软件也是有的,对用户隐藏信息是所有软件都关注的,功能模块封装的体现,继承的体现在嵌入式软件可以说是另一种表达,暂且把对于库函数的重新组合看作继承吧,尽管两者确实不同。多态性在结构化里的确没有应用,但是对于不同类型指针的传递和应用却也是或多或少了有了多态性的影子。面向对象设计的CLASS,很多时候,都可以看成结构化设计的模块,对应一定的源文件和头文件。

无论如何,重构对于增强软件的可测试性,可维护性,可扩展性,模块化是毋庸置疑的。里面是本人根据经验总结的如何在嵌入式软件开发领域应用软件重构,借鉴了《重构》大部分思想,作了少量改动,同时也增加了自己的一些浅薄见识,难免有偏激,理解错误的地方,欢迎大家指正。提高软件开发水平,改善软件质量,应该是我们共同的努力得目标。我为自己在从事软件开发的过程中能认识到这些而庆幸。别告诉我你写的那些行代码就是软件,当看完这些文档后,你可能不再敢自称或者自傲为软件工程师了,不过别沮丧,我也是这样。能看到这一点,说明我们还有机会。

二. 重构的定义

重构到底是什么,它能够帮到我们什么,说的那样玄妙,其实正如《设计模式》的使用,提炼,总结一样,尽管我们未曾意识到,但是,实际上,我们大多数人也都是或多或少的作过重构这些事情,只是没有总结,提炼作成一种思想,一种标准来指导我们,可惜了它的作用了,系统化的重构还只停留在不同经验的个人领域。每个人按照自己的理解也在进行着自己的重构,现在则要提炼出来,让它称为一种标准,一种可以不断扩充,不断维护的准则。让菜鸟也能从大师那里直接取得经验。前提是,菜鸟愿意承认自己代码的不足,并且认识到重构的重要性,实际上,往往是菜鸟才自以为是,不肯承认自己的缺陷,真正的大师,是更愿意向别人学习的。经验的分享和交流是重构能不断前进的推动力。

重构的定义:对软件内部结构的一种调整,目的是在不改变软件之可察性前提下,提高其可理解性,降低其修改成文。这些暨成的定义也许现在还不理解,没关系,只要知道重构的作用是增强软件的可测试性,可维护性,可扩展性就可以了。看看你的软件,是否具备这些特点——这些软件质量的关键属性。描述的更清楚些,不是我总结的:
1. 改进软件设计;
2. 使软件更易被理解;
3. 帮助你找到BUG;
4. 帮助你提高编程速度;

还不明白,好吧,继续我们的重构行程,我保证没等走完一半你就已经明白并且认可重构了。文中很多地方提到菜鸟,甚至对中国的软件开发现状的描述,实在不是针对别人,而是对自己的讥讽,曾经觉得还不错的自己,才是真正的井底之蛙。

三. 坏味道

改进软件,最重要的是要知道改进哪里,Kent给了我们很生动的描述,“坏味道”,当我们察看代码的时候,每个人的嗅觉是不一样的,嗅觉灵敏的一下子就可以察觉到那些坏味道的存在,这个嗅觉能力是需要培养的,这就是资深工程师和入门者之间的差别。看看这些坏味道你能闻出来几种。这里注意,函数和数据都和面向对象基本一致,只是原有的class可以改变成模块的思想,对象之间可以看作模块之间。
1. 重复的代码:如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们合而为一,程序将变得更好。
2. 过长函数:程序愈长愈难理解。更应该积极的分解函数,需要注释的地方提炼出去。条件式和循环常常也是提炼的信号。
3. 过大结构:裁减到舒服的使用。按功能结构划分。
4. 过多参数列表:集成结构
5. 发散式变化:每种变化引发多种修改,文件功能过于繁杂,应该分解出来。
6. 散弹修改:发生事件,多个文件需要修改,功能相似的函数集中文件管理;
7. 依恋情结:
8. 数据泥团:数据,函数集中管理,修改成结构。
9. 基本类别数据组合,不要用数组表示不同类型的数据,而是使用结构;
10. Switch的情况?(是否适用)
11. 过小结构,过多单一全局变量,合并成结构进行统一管理。
12. 未来性的结构,未来性的文件,未来性的函数。 
13. 过度耦合,关系不清晰,高内聚,低耦合
14. 变量,函数的命名,
15. 对于不给用户看到的信息,封装set/get接口;
16. 拒绝全局变量,采用获得接口来实现
17. 用代码代替注释,让所有注释变得多余。

测试体系:

编写良好的测试程序,可以极大的提高编程速度,构造自测体系。自动化是必然的。和源代码一起提交的测试代码。MOZILLA包含了大量的单元测试代码。代码和测试代码同时编写,测试代码具有自动检测性。自测的可监控性。一旦发现臭虫,则首先编写单元测试,哈哈,我以前都是先维护监控信息。花合理时间找到大多数BUG,好过花所有时间找到所有BUG。

重构的实现细节:

本章按照大体分类来详细描述重构的实现,分别为

重构名称:

小结:

动机:

好处:

问题:

实用性:

(一)重新组织你的函数:

主要是对函数的处理,适合重构函数。

1. 提炼函数:

1) 小结:有一段代码可以被组织在一起独立出来,2)将这段代码放进一个独立函数中,3)
并让函数名4)称解释该函数的用途。

5) 动机:对付过长的函数,6)对付重复7)
代码; 

8) 好处:复9)
用机会大,10) 容易理解(代替注释),11)容易覆写;

12) 问题:局部变量,13)

14) 实用性:非常大,15)最好的重构方法。

2. 内联函数

1) 小结:一个函数,2)
本体应该与其名3) 称同4)样清楚易懂。带函数调用点插入函数本体,5)
然后移除该函数。

6) 动机:代码和函数同7)样清晰,8)
没有必要分开;许多杂乱的函数,9)可以都移回到调用函数,10)
然后重新提炼出新的函数; 

11) 好处:减少委托,12)去掉无用得间接层,13)


14) 问题:效率,15)

16) 实用性:用的不17)多,18)
需要高层次理解;

3. 内联变量:

1) 小结:有一个临时变量,2)只被一个简单表达式赋值一次,而3)
它妨碍了其他重构方法。将所有对改变量的应用动作,4)替换为对它赋值得那个表达式本身。

5) 动机:妨碍其他重构,6)降低效率; 

7) 好处: 

8) 问题:,9)

10) 实用性:用处不11)大,12)
根据情况决定;

4. 用查询代替临时变量:

1) 小结:以临时变量保存某一表达式的运算结果,2)将这个表达式提炼到一个独立函数,3)
将这个临时变量得所有被引用点替换为对新函数的调用,4)新函数可倍5)
其他函数使用。

6) 动机:代码清晰,7)
可以用于提炼函数; 

8) 好处:复9)
用机会大,10) 容易理解(代替注释),11)容易覆写;

12) 问题:性能受影响,13)

14) 实用性:有时不15)好用;

5. 引入解释变量:

1) 小结:有一个复2)
杂的表达式,3) 将该复4)杂表达式的结果放进一个临时变量,5)
以此变量名6)称来解释表达式用途。

7) 动机:表达式复8)
杂难懂,9) 良好的变量命名10); 

11) 好处:容易理解,12)解释表达式;

13) 问题:很多时候用提炼函数更好,14)我们倾向于用变量

15) 实用性:全力提倡;

6. 分解临时变量:

1) 小结:有一个临时变量被赋值超过一次,2)不3)
是循环变量,4)也不5)
是一个集用临时变量,6)针对每次赋值,7)
创造一个独立的,8)对应的临时变量。

9) 动机:每个变量承担单一的责任; 

10) 好处:清晰,11)
容易理解,12) 不13)
容易出错;

14) 问题:效率,15)
定义麻烦;

16) 实用性:推荐使用;集用临时变量除外

7. 删除对参数的赋值:

1) 小结:代码对一个参数进行赋值动作,2)在函数入口以一个临时变量取代该参数的位置 。

3) 动机:只对需要修改的参数赋值,4)然后返回临时变量; 

5) 好处:代码清晰,6)
容易处理异常返回的情况;

7) 问题:小函数不8)
会出现过多处理。

9) 实用性:必须采用。

8. 替换算法:

1) 小结:把某个算法替换为另一个更清晰的算法,2)将函数本体替换为另一个算法。

3) 动机:替换整个算法,而4)不5)
是修改。

6) 好处:效率,7)
清晰;

8) 问题:大函数首先变小,9)然后替换,10)

11) 实用性:一般,12)根据情况选用。

(二)对象之间搬移特性:

适用在各个文件内的组织,明确变量,函数的位置,责任,解决软件的耦合性,如是驱动,还是单板相关,还是cpu,操作系统相关。在嵌入式软件中,函数一定要模块化,功能化,明确是驱动程序,还是单板相关,还是应用相关等,从而分别创建不同的文件结构。面向对象的类,我们可以相应的认为就是文件,就是功能模块的一个体现。

9. 搬移函数:

1) 小结:某个函数与另一个模块之间的关系更密切,2)调用后者,3)
或者被后者调用,4)或者从逻辑关系,5)
功能上讲,6)更适合另一个模块。

7) 动机:模块的功能过大,8)耦合太多、; 

9) 好处:功能稳定,10)减少耦合;

11) 问题:,12)

13) 实用性:比较大,14)如获取单板硬件版本号算在哪个模块。

10. 搬移变量

1) 小结:配合搬移函数,2)搬移文件内静态变量,3)
或者重新定位文件内静态变量,4)可以删除原有文件的定义,5)
在新的文件内重新定义。

6) 动机:变量对于各个文件,7)模块的适用性不同8)
; 

9) 好处:清晰,10)
容易维护;

11) 问题:,12)

13) 实用性:用的不14)多,15)
根据情况调整;

11. 提炼模块:

1) 小结:如果一个文件包含多个模块的功能,2)则重新建立文件,3)
把相应的函数和变量从原有文件搬移到新的文件。

4) 动机:文件职能应该清晰,5)处理明确的职责。长时间的积累让文件过大,6)
职责过多。可以根据模块功能来划分,7)如果调用接口单一,8)
注意模块之间是否需要隐藏信息,9)是否把这个模块暴露出去。

10) 好处: 功能明确,11)容易理解,12)
模块化好,13)

14) 问题: 文件增多,15)工程管理成本增加。

16) 实用性:根据情况决定,17)经验为主,18)
比较重要;

12. 模块内联:

1) 小结:某个模块没有做太多事情,2)并且服3)
务于其它模块,4)则可以把这个模块搬移到那个模块,5)
可以减少一个文件,6)方便维护。动机:代码清晰,7)
可以用于提炼函数; 

8) 好处:模块功能少,9)或者完全服10)
务于其它模块;

11) 问题:增大文件,12)不13)
好查找。 

14) 实用性:根据经验决定,15)判断是否留外部接口,16)
主要通过定义内部函数还是外部函数来实现;

13. 隐藏调用关系,14.删除中间调用:

1) 小结:当模块不2)
想为外界提供接口或者不3) 需要被中间层调用的时候,4)可以采用这种方法。

5) 动机:隐藏关系,6)
或者去除中间调用; 

7) 好处:模块化,8)
效率;

9) 问题: 

10) 实用性:在c中体现不11)明显;

15. 引入外加函数:

1) 小结:一个已经提交或者没有源代码的模块,2)需要增加一个额外函数,3)
建立一个新的函数,4)并且把相应模块的结构传递进来。

5) 动机:增加模块的功能;已有稳定的模块或者库文件没有源代码,而6)需要增加新的功能

7) 好处:不8)
要破坏成熟的模块,9) 或者无法获得源代码;

10) 问题:效率,11)
定义麻烦;

12) 实用性:对于模块化的实施,13)或者对已有模块的扩展,14)
这个函数放的位置不15)是很重要。

16. 引入本地扩展:

1) 小结:所使用得模块需要封装,2)或者修改很多函数,3)
则建立一个新的模块文件,4)实现相应的函数。

5) 动机:原有模块没有这样多的函数要求,6)或者只有库,7)
没有源代码,8)或者需要对模块进行整体得封装。

9) 好处:扩展原有模块功能,10)封装模块,11)
解决没有源代码的问题;

12) 问题:大函数首先变小,13)然后替换,14)

15) 实用性:一般,16)根据情况选用。

(三)重新组织数据

          主要是对数据的处理,封装,可理解性,常数的替换,对象之间的关联,

17. 封装值域:

1) 小结:直接访问一个值域,2)会增加与值域的耦合,3)
为这个值域建立设值/取值函数,4)并且只以这些函数来访问值域。

5) 动机:可以通过覆写一个函数改变获取数据的途径,6)支持更灵活的数据管理方式。

7) 好处: 封装数据,8)模块化用,9)
容易修改,10)减少耦合。

11) 问题:函数增加,12)不13)
必所有的值域都这样封装。

14) 实用性:模块化对外不15)留接口,16)
模块内部基本可以不17)用这种方式;

18. 以模块代替数据:

1) 小结:一些数据的处理,2)逐渐变得复3)
杂,4)可以抽取出一个模块; 

5) 动机:简单数据,6)
不7) 断变得复8)
杂,9) 根据情况,10)
抽取成模块。

11) 好处:模块功能集中,12)方便数据管理;

13) 问题:增大文件,14)粒度。 

15) 实用性:随着系统的复16)杂,17)
分解出一些模块出来;

19. 用引用代替实体:

1) 小结:如果对一个对象有多个地方引用,2)为了方便管理,3)
不4)要出现多次重复5)
修改,6)则把实体修改为引用。

7) 动机:开始引用少,8)并且内容部多,9)
后来逐渐增加内容,10)多处引用,11)
; 

12) 好处:方便管理,13)不14)
要手动保持同15)步;

16) 问题: 

17) 实用性:结构化设,18)驱动设计等用的很多;

20. 用实体代替引用:

1) 小结:引用对象很小而2)且不3)
可变,4)不5)
易管理。将它变成一个实体。

6) 动机:避免关联关系,7)不8)
用考虑同9)步,10)
对象不11)可变。

12) 好处:容易管理;

13) 问题:;

14) 实用性:在结构化设计影响不15)大。

21. 用结构代替数组:

1) 小结:数组的各个元素各自代表不同2)的东西,3)
可以用结构来代替,4)结构的成员来描述数组的元素。

5) 动机:数组描述了不同6)类型的成员,7)
容易出错。

8) 好处:管理方便,9)
可读性强;

10) 问题:;,11)

12) 实用性:碰到这种情况,13)一定要替换,14)
用数组是入门练习的做法。

22. 改变结构之间的单向关系为双向:

1) 小结:两个结构都需要对方特性,2)但是其间只有一条单向连接,3)
添加一个反向指4)针。

5) 动机:单向指6)
针无法保证获得对方的关系。

7) 好处: 封装数据,8)模块化用,9)
容易修改,10)减少耦合。

11) 问题:函数增加,12)不13)
必所有的值域都这样封装。

14) 实用性:模块化对外不15)留接口,16)
模块内部基本可以不17)用这种方式;

23. 改变结构之间的双向关系为单向:

1) 小结:两个结构之间有双向连接,2)现在不3)
再需要另一个方向,4)去除不5)
必要的连接; 

6) 动机:维护双向连接增加复7)杂度,8)
增加了耦合,9)。

10) 好处:降低复11)
杂度,12) 方便管理;

13) 问题:。 

14) 实用性:结构化设计,15)很少出现这种情况。

24. 用宏定义代替魔法数:

1) 小结:数值有特殊含义,2)定义宏来代替它。

3) 动机:难懂,4)
维护麻烦,5) 容易出错; 

6) 好处:提高可读性,7)容易维护;

8) 问题: 

9) 实用性:一定要采用,10)方便修改,11)
消灭一切12)魔法数;

25. 传递参数和返回参数:

1) 小结:如果参数传递进去,2)不3)
允许改变,4)则定义const,5)
如果返回是数组,6)不希7)
望被改变,8)则返回副本。

9) 动机:避免参数被修改,10)传递的如果是指11)
针,12)更要小心,13)
返回也一样,14)如果是实体,15)
并且不16)想被修改,17)
则建立一个副本变。

18) 好处:不19)
容易出错;

20) 问题:;

21) 实用性:在结构化设计中要广泛采用。

四.简化条件表达式

将复杂的条件逻辑分成若干小块。使得切换逻辑和操作细节分开

26. 分解表达式:

1) 小结:将一个复2)
杂的表达式从if,then,else三个段落分别提炼出三个函数,。

3) 动机:降低复4)
杂度。

5) 好处:容易维护,6)
代码清晰,7) 可读性高;

8) 问题:,9)

10) 实用性:根据情况使用,11)有一定好处。

27. 合并条件式:

1) 小结:有一系列条件测试,2)得到相同3)
得结果,4)可以将这些测试合并成为一个条件式,5)
并将这个条件式提炼成为一个独立函数。

6) 动机:多个相同7)
结果的判断条件。

8) 好处:代码清晰,9)
可读性好;

10) 问题:;,11)

12) 实用性:看情况修改。

28. 合并重复29.
的条件片断:

1) 小结:在条件式的每个分支上有着相同2)的一段代码,3)
将这段重复4)代码搬移到条件式之外。

5) 动机:不同6)
条件含有相同7) 代码,8)可以清晰描述变化。

9) 好处: 容易维护,10)不11)
容易出错。

12) 问题:注意代码出现的位置。

13) 实用性:很重要,14)一定采纳;

30. 删除控制标31.
记:

1) 小结:在一系列表达式中,2)某个变量带有控制标3)
记,4)用break,return语句取代控制标5)
记; 

6) 动机:控制标7)
记带来的麻烦大于它带来的好处。

8) 好处:代码清晰;

9) 问题:。 

10) 实用性:一定采纳,11)不12)
要使用控制标13)记。

32. 用卫语句代替嵌套条件式:

1) 小结:复2)
杂的条件逻辑,3) 使人难以看清正常的执行路径,4)使用卫语句表现所有特殊的情况。

5) 动机:对于非正常条件的处理,6); 

7) 好处:逻辑清楚;

8) 问题: 没搞懂确切9)的实现,10)
违背单入口,11)但出口

12) 实用性:可以采纳,13)注意使用方法,14)
可以考虑增加返回结果;

33. 引入断言:

1) 小结:一段代码需要对程序状态做出某种假设,2)以断言明确表现这种假设。如果根据类型进行相应的函数执行,3)
可以采用函数数组来解决。

4) 动机:交流河调试的辅助作用。

5) 好处:方便交流,6)
容易定位;

7) 问题:不8)
是非常清楚在C中的用法;
9) 实用性:vxWorks的各种信号量处理。

 五.简化函数调用

       函数式代码最重要的组成单位,函数的信息隐藏,参数个数,查询和修改分离,函数命名等,

34. 重新命名35.
函数

1) 小结:函数的名2)
称未能揭示函数的用途,3) 修改函数名4)称完成。

5) 动机:复6)
杂处理分解成小函数,7) 通过一个好的命名8)来增强可读性,9)
通过注释来命名10)函数,11)
采用动宾式,12)代码首先是为人写的,13)
其次才是为计算机写的,14)

15) 好处:容易维护;

16) 问题:不17)
断提高经验和能力;,18)

19) 实用性:非常重要

36. 添加参数:

1) 小结:某个函数需要从调用端得到更多信息,2)为此函数添加一个对象参数,3)
让该对象带进函数所需信息。

4) 动机:很常用,5)
需要给函数提供信息,6) 尽量用其他方法,7)添加必要的参数。

8) 好处: 获得信息。

9) 问题:参数台多没好处。

10) 实用性:根据情况添加,11)不12)
要乱添加;

37. 删除参数:

1) 小结:函数本体不2)
再需要某个函数,3) 将该参数去除; 

4) 动机: 过多参数没有好处,5)尽可能去掉。

6) 好处:增加函数的可读性,7)和可维护性;

8) 问题:增大文件,9)
粒度。 

10) 实用性:依靠经验来做;

38. 将查询函数和修改函数分离:

1) 小结:某个函数既返回对象状态值,2)又修改对象状态,3)
建立两个不同4)的函数,5)
一个负责查询,6)一个负责修改。

7) 动机:注意函数调用的副作用,8)分离会让函数功能更明确,9)
更容易维护; 

10) 好处:方便管理,11)不12)
要手动保持同13)步;

14) 问题: 

15) 实用性:结构化设,16)驱动设计等用的很多;

39. 函数参数取代多个函数:

1) 小结:函数完全取决于参数值而2)采取不同3)
的反应,4)对于参数的每一个可能性,5)
建立一个独立函数。

6) 动机:接口清晰。

7) 好处:容易管理,8)
容易理解,9) 减少维护成本;

10) 问题:;

11) 实用性:根据情况使用。

40. 结构代替多个参数:

1) 小结:对于多个参数的调用,2)或者结构内的多个参数调用,3)
采用传递结构的方法。

4) 动机:代码清晰,5)
增加可扩展性。

6) 好处:;

7) 问题:可能参数太少,8)没有必要;,9)

10) 实用性:根据情况选择。

41. 删除设置函数:

1) 小结:模块中的某个全局变量,2)初始化设置后不3)
再发生变化,4)则删除设值函数。

5) 动机:不希6)
望变量被改变,7) 则删除设值函数。

8) 好处: 减少歧义,9)避免被后来的维护人员错误调用,10)

11) 问题:。

12) 实用性:很有用;

42. 隐藏某个函数:

1) 小结:模块中的函数不希2)望被其他模块调用,3)
比希4)望对用户可见,5)
则设置成static; 

6) 动机:尽量降低模块的可见性,7)只留下必要的对外接口。

8) 好处:方便隐藏信息,9);

10) 问题:。 

11) 实用性:多多实用。隐藏信息是很好的编程思想,12)尽可能降低所有函数的可见度。

43. 用异常取代错误码

1) 小结:某个函数返回一个特定的代码,2)用以表示某种错误情况。

3) 动机:用异常代替出错情况,4)???; 

5) 好处:提高可读性,6)容易维护;

7) 问题: 

8) 实用性:一定要采用,9)方便修改,10)
消灭一切11)魔法数;

44. 以测试取代异常:
1) 小结:对于没必要的异常,2)用测试取代异常。

3) 动机:异常不4)
能成为条件检查的替代品。

5) 好处:乱用异常没有好处;

6) 问题:;
7) 实用性:不

8) 要乱用异常。

六.处理概括关系

主要用在各个模块之间的封装和函数调用关系,注意这里和面向对象差别很大。

45. 值域上移:

1) 小结:两个模块具有相同2)变量,而3)
提供统一接口,4)则可以搬移到最外层调用处。例如多种交换网芯片

5) 动机:去除多余代码。

6) 好处:方便管理;

7) 问题:,8)

9) 实用性:根据情况使用,10)有一定好处。

46. 函数上移:

1) 小结:有些函数,2)
在各个模块中有完全相同3) 的结果,4)将这个函数移至公共模块。

5) 动机:重复6)
的工作容易制造错误。

7) 好处:代码清晰,8)
可读性好;

9) 问题:;,10)

11) 实用性:看情况修改。

47. 函数下移:

1) 小结:公共模块的函数在各个子模块的表现不2)一样,3)
需要单独保存在各个模块。

4) 动机:根据情况,5)
将函数下移。

6) 好处:。

7) 问题:。

8) 实用性:根据情况实用;

48. 提炼模块:

1) 小结:将多个模块共有的属性提炼出来。作为单独模块; 

2) 动机:方便扩展。

3) 好处:方便管理,4)
例如uc/os-ii,5) vxWorks的信号量处理;

6) 问题:。 

7) 实用性:根据情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐