别给计算机科学学生教面向对象编程

面向对象理念是编程时入手问题的一种人类理解意义上的“具象”,在性能关键的领域(科学计算、图形渲染、大数据处理等)这种“具象”所建立起来的计算模型十有八九不是计算效率的最优解。学校里填鸭“封装”“继承”“多态”是对于计算过程的黑盒化,说严重点是一种“思维束缚性”的误导,因此计算机“科学”系的学生和码农应该透过现象熟谙本质,不被流行概念蒙蔽自己本该树立的计算三观。(如果是非专业人士,想学编程玩,或者带来职场机动性的,可以关掉浏览器了,不用浪费时间看本文。面向对象对编程技能的普及功不可没,放开学吧。)

 

面向数据编程的理念(Data-Orientated Programming)在近年来被提及,之所以说这个概念,很大程度上就是去dis面向对象这个概念。计算的本质是输入和输出,改变状态、读写数据本来就是程序的目的。写的代码最终存在硬盘上,活在内存里,所谓的数据是数据,代码也是一种数据。代码会被某些计算单元处理,或者被某些虚拟机所处理,而虚拟机的的代码也是代码最终被计算单元处理。

 

下面说说面向对象的一些误导:

实施模型(implementation model)和人类心理模型(mental model)的差异。在虚拟世界的建模中,一把椅子,可能是静态的环境的一部分,可能是动态的可以交互(人可以坐,可以搬起),也可以是能够被物理模拟所打碎、掰弯、点燃烧蚀。上述三种类型的椅子,在实施和处理上共性很少。即使能够抽象出一些共同的属性,写成继承的形式是对计算过程的巨大羁绊。计算机的每一级缓存都有有限的cache line,如果能够把要处理的数据集中起来,按一定格式排列或者压缩起来,放在临近位置,那最终在硬件上的运行效率和对此”不管不问”的面向对象模型会有天壤之别。Array of structs 和struct of arrays的例子不胜枚举。前者虽然归类在一个struct很多属性,但是对于alignment,何时何地处理何种数据都是一种“忽视”,在实操时缓存miss掉的概率很高。而如果有意的去排列所要操作的数据,根据数据的依赖关系构建计算管线,那最终的运行起来是处理器非常舒服的flow。

 

计算机科学的学生要学编译原理。编译的前端需要对文本进行分析和转译。当我们在说”继承“的时候,好像默认了一个包围在花括号里叫class的东西,衍生出了新的类型。可是关于其成员数据如何存取在内存里,成了一个不管不问的黑盒。加了prviate的变量,子类看不到就不存在了吗?能够通过offset取到吗?子类如果有个一模一样名字的变量会发生什么?变量的名字对于编译器意味着什么?Virtual table怎么找到你override的函数的?找的过程消耗如何的计算资源?一个class有一百个函数,一个class有三个函数,谁占内存多还是压根没关系?当你在原地写lambda的时候,到底发生了什么?所有的东西都是一个object,会淡化primitive类型的存在感,会忽视内存alignment的意义。会淡化传reference的意义(如果reference永远是以copy的形式当value被传),没有pointer会忽视对地址概念的理解和思考,pointer和reference被编译器翻译成的指令有何不同?面向对象概念所建立起的对程序的感官体验,是对编译原理学习的巨大干扰。编译原理所要解决的问题是本质的人机交互过程。而一些带花活语法的编程语言,复杂的词法语法语义分析把使用者和计算机成功的割裂开来,在一定意义上,它让编程更简单(C#/.NET, Java)。同时这些花活带来的误区需要很大的后天努力才能掰回来,这是一个打破和重建三观的过程。

 

计算机科学是科学,科学是抽象的,抽象是痛苦的。但痛苦的意义在于成就卓越。可读性差的程序,码农多看几遍看文档可以理解。运行慢的程序,计算机跑了十遍也不会自发让它更快。现在技术栈在不断往上加,层与层之间的抽象黑盒化让应用软件可以遍地开花。然而跑得快,跑得多,不代表能跑得远。科学会在文明毁灭后几乎一样的被重新发现,抽象的概念会抽象地被另一个领域在不知道多久之后被重新拾起。长远考虑,面向对象的道理,更像人类去说服自己接近”计算思维“时,给自己编造的一则圣经故事,老少咸宜。

 

posted on 2022-09-19 03:33  安泰  阅读(101)  评论(0编辑  收藏  举报

导航