zoupaper的个人博客分享 http://blog.sciencenet.cn/u/zoupaper

博文

现代C++的定位、设计原则与编程范式

已有 3700 次阅读 2021-8-5 23:11 |系统分类:科研笔记

1   现代C++的定位

当前C++的应用场合已经比较少,至少不是热门的技术。即使是重点大学的计算机专业学生,很多人也不会把精力用于学习C++语言。但是在目前的阶段,我们依然不能说C++已经属于被淘汰的技术,因为C++语言更多成为信息世界的基础设施。

计算机和信息技术为什么可以成为新一次的科技革命?首要原因是计算机技术大大推进了生产力的发展。回看历史,计算机和网络技术是先用于军事,然后用于工业生产,最后再应用于娱乐和消费端。你不能说消费比生产更重要,毕竟只有消费没有生产,人类立刻就要灭亡。但是中国的计算机行业由于发展阶段,以及国际分工等原因,主要只发展消费端和娱乐。对于计算机底层和应用于工业生产,是严重缺失的。

如果软件工程只局限于消费、娱乐,那C++确实不是好的选择。但是如果是做工业软件,C++作为系统性编程语言,是非常合适的。拿我现在的工作来说(国产的实时仿真装置),C++几乎是唯一的选择。

1)首先,有硬实时的要求,这就直接排除pythonJAVA了,Julia语言也达不到硬实时的要求。

2)其次,有大量的科学计算,哪怕是计算效率比竞争对手高10%,那都是了不起的优势,因为确实可以降低用户的成本。在高性能科学计算方面,似乎C语言、C++Fortran都可以胜任。但是从软件工程方面,C++多范式的特点,用的好的话可以提高研发效率。比如说仿真某个装置,几十个型号都需要开发相应的模型,用C或者Fortran怎么行?很快代码就要变成一团乱麻了。Rust也是系统编程语言,但是Rust的优势是减少内存bug,在科学计算方面还未听说Rust也很强的。

3)有半实物仿真的需求,这就要求仿真软件能够无缝对接各种驱动,甚至深入到操作系统内核,而不是仅仅调用操作系统提供的接口。例如,连接FPGA装置需要走PCI总线,还有链路层的网络报文(无法使用TCP/IP协议,因为性能达不到要求)。由于操作系统和驱动都是C语言写的,因此在这方面CC++都符合要求,其它语言都增加了间接的一层封装。

综上,对于工业软件来说,C++是非常合适的,很多时候也是唯一的选择。

2  C++的设计原则

     这部分很多是摘抄网上的说法,加上一些个人的经验,只可以参考,不一定适用所有的场合。

2.1 系统设计的产出物,应该是一个原型程序

   我不止一次看到这样的案例:某个复杂业务系统,几十个人进行设计,设计的似乎完美无缺,无论是上千页的设计文档,还是精美的PPT,都说明这个系统会是尽善尽美的。但是经过开发、工程投运之后,用户却发现这个系统完全不好用,就算用的起来,那也是磕磕绊绊、投入大量的人力去维护。

这说明设计和工程实施出现了脱节。问题可能是设计人员中或许有业务专家、或许有计算机高手,但是没有兼具一线业务与计算机专业知识的人,造成设计想法的难以落地。为了避免架构的脱离实际,我建议设计的产出物,至少是一个可运行的原型程序。这个原型程序可以没有界面和实质性功能,但是至少可以显示业务流程。这也是为后续实质性的研发,打下一个很好的基础。当然设计文档也是需要的,但是没必要对设计文档提出过多的形式化要求。把设计写在黑板上,用手机拍下来,如果研发人员都觉得看得懂、思路清晰,那就可以作为设计文档。关键是解决实际问题,解决问题是第一位的。

2.2 具体编程的注意点

     Don’t Repeat Yourself:当在两个或多个地方发现一些相似代码的时候,我们需要把它们的共性抽象出来形成一个唯一的新方法,并且改变现有地方的代码让它们以一些合适的参数调用这个新的方法。

     最少知识原则:一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。反面教材:有人喜欢用全局变量,写的时候确实很爽,随心所欲,但维护的时候就非常头疼了。

单一职责原则:一个类,只做一件事,并把这件事做好,其只有一个引起它变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而极大地损伤其内聚性和耦合度。把模块间的耦合降到最低,而努力让一个模块做到精益求精。使用多个专门的接口比使用单一的总接口要好。反面教材:有的大型系统经常出现几万行代码甚至更多行的超级大类,这种类很快陷入不可维护的境地。如果作者转岗、辞职,其它同事对于这些代码只能干瞪眼,很多时候只能推倒重来。

开闭原则:模块是可扩展的,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。反面教材:很多程序加一个简单功能都需要修改几十个地方,用户或者领导非常困惑:加的功能太简单了,看样子几分钟就能开发出来,为什么他们说至少需要1个月呢?

关注点分离:问题太过于复杂,要解决问题需要关注的点太多,而程序员的能力是有限的,不能同时关注于问题的各个方面。实现关注点分离的方法主要有两种,一种是标准化(大家都按照标准行事,最后拼装成大系统),另一种是抽象与封装。

依赖倒置原则:高层模块不应该依赖于低层模块的实现,而是依赖于高层抽象。在依赖结构中不允许出现环(循环依赖)。这就需要对软件的层级结构很好的设计,绝不允许出现混乱。有时候临时一个需求,随手就开发出来,但是却破坏了整体架构,久而久之就不可收拾了。

除了上述要点,写大型C++程序,测试也是非常重要的。有的人甚至推荐测试代码要先于业务代码去编写。有的业务,测试代码的行数比业务代码还多。测试驱动有专门的方法论、工具与管理措施,我这里就不展开说了。

3  C++的编程范式

3.1 过程式(C语言范式)

使用C++编程,很难回避与C语言的混合编程,这是因为这两种语言天然接近底层,而操作系统和驱动,以及许多高性能的库,是C语言编写的。C语言本身的范式属于直接实现设计思路,语言与汇编严格对应。编译器很少会做出让你大吃一惊的优化C++编程在很多场合,例如与C语言的混合编程,以及某些特殊需求,是有必要采用C语言范式的。

我有个观点,不知道对不对,就是你用C++如果完全用不到指针,那真的不如转到Java等开发效率高的语言,指针是C语言最显著的标志之一。一旦与驱动、内核打交道,指针就是无论如何也绕不过去的关键概念了。你用python等语言,觉得没指针也好好的,那是因为某种中间层(JVMCPython等)屏蔽了底层,这样也好,提高了很多开发效率。我有时候写高性能的仿真程序,要求时钟控制在微秒级别,很多时候不得不用裸指针,有时候也不得不改写Linux内核程序,虽然看起来这种做法严重违反了现代软件工程的推荐做法。

当然在用户态编程,如果不涉及操作系统内核,那还是有必要用智能指针包装一下的。有人提出操作系统内核其实也可以用智能指针。

3.2 基于对象范式

    基于对象更多的是用类进行封装,但类与类之间没有继承、多态等复杂关系。关于类,大约可以分为两种:1)一种类存在对资源的抽象封装和管理,例如动态内存、文件句柄、socket等,这种类需要仔细安排资源管理,定义或者禁用拷贝构造函数;编写析构函数,这就是著名的RAII(资源获取就是初始化)原则;2)另一种类并没有对资源进行管理的职责,可以直接复制,例如矩阵、字符串等,这种类就没有必要实现深拷贝,没必要写析构函数。

    把类的作用分别搞清楚,就可以解决大多数关于类的业务问题。

3.3 面向对象

   面向对象是很多教科书,特别是设计模式方面的书详细介绍的。优点:大量的经验(设计模式),符合人的直觉,与真实世界对应。缺点:太多的封装,代码一层层套;重构困难;虚函数的运行效率会略有下降;使用虚函数会有二进制兼容性问题(C语言不兼容,C++编译器会自动安排虚函数表及虚函数指针,这是用户无法看见的)

   有的人对面向对象严厉批评,比如陈硕、王垠等人,觉得是“花拳绣腿”;当然也有人,例如罗剑锋、吴咏炜等人持中立的态度。

我觉得,用不用面向对象,需要具体问题具体分析。例如设计模式中的组合模式,用基类加继承是合适的。但一定不能滥用,毕竟使用C++的首要保证高性能,至少不能像JAVA那样滥用面向对象。

顺便提一句,用C语言(包括嵌入式)也可以实现面向对象的特性,只需要把函数指针表处理好就可以。

3.4 泛型/模板

如果说面向对象是运行时候的多态,模板就是编译时候的多态。其中STL对于数据结构和算法的解决,是泛型极其精彩的应用案例。STL的设计与面向对象是毫无关系的,有些初学者可能把C++当成面向对象的语言,其实不是的,C++是多范式语言。

泛型/模板、编译期编程,还可以用“模板元”的方式,这部分抽象程度太高,至今我也不会,就略过不谈。

3.5 函数式

  C++11引入的lambda 表达式,使C++逐渐具备函数式风格。函数式可以理解为更高抽象层次的泛型,它可以把函数打包成变量,然后就可以实现“函数的函数”。在许多场合,函数式可以极其简化程序,例如设计模式中的行为模式,可以用函数式的风格,避免使用继承。对于STL的有些应用,使用函数风格,可以降低循环的出现次数。

  函数式编程期望函数的行为像数学上的函数,要点在于:

1.会影响函数结果的只是函数的参数,没有对环境的依赖;

2.返回的结果就是函数执行的唯一后果,不产生其他影响;

3.函数就像普通的对象一样被传递、使用和返回;

4.代码为说明式而非命令式,可读性高。

但是,走到极端也是不行的,不能为函数式而函数式、滥用函数式。我觉得,函数式可以是其它范式的一种补充。



https://blog.sciencenet.cn/blog-3316223-1298524.html

上一篇:哲学与电力系统
下一篇:工业系统中的日志
收藏 IP: 112.2.80.*| 热度|

2 黄仁勇 杨正瓴

该博文允许注册用户评论 请点击登录 评论 (0 个评论)

数据加载中...

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2024-10-12 11:14

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部