题图:Photo by Kace Rodriguez on Unsplash
本文原创发布于微信公众号“洛奇看世界”。
前段时间记忆承载公众号推送过一篇文章,里面有个视频说史玉柱当年失败的时候,在央视上节目被人嘲讽教育的故事。
[video(video-qYpXYvxN-1652359270105)(史玉柱失败在央视被人嘲讽)(url-undefined)(image-https://img-blog.csdnimg.cn/editor-video.png)(史玉柱失败在央视被人嘲讽)]
视频地址如下:https://v.qq.com/x/page/g0391ljxrb4.html
看完有一种墙倒众人推,破鼓万人捶的感觉。转到朋友圈的时候顺便问了一句,如果史玉柱后来没有成功翻身,后人再看这视频又会怎样?有个小伙伴说,看问题角度不一样,现在是上帝视角。
无比认同。
所谓上帝视角,就是第三人称视角,置身事外,以一种旁观者的角度来观察。
假设我们就在当初的现场,前面坐着一个创业失败者,周围的人都不断发表者自己的见解,你一言我一语,在这种氛围之下,自己难免也会和其它人一样,对史玉柱的失败,对巨人的失败指手画脚,评头论足。
而旁观者,看这个问题,由于在当事者之外,所以可以更冷静、更理性地分析情况。
吃瓜如此,看代码也一样,站在旁观的角度,读代码会更容易。
我刚工作的时候看代码,拿到代码的第一件事就是找到main函数,然后开始在代码间游走,逐行阅读,觉得阅读了每一行代码,就读懂了程序。
这个方法在以前是没错的,因为学生时代写的程序,基本上只有几个文件,更多时候只有一个文件,从main函数开始,到return结束,整个程序就完事了。
实际项目中,文件少则几百,多则成千上万,main函数可能有很多个……或者没有main函数。所以,拿到代码就一头扎进去,很可能就迷失在代码里了。
防止迷失,最好的办法是跳出具体的代码,以一个旁观者的角度,观察代码的组织结构,将代码和文件当做一个个对象,从俯视的角度来查看代码。
重要的事情说三遍:
不要一头扎入到具体的代码实现中,一行一行看代码!
不要一头扎入到具体的代码视线中,一行一行看代码!
不要一头扎入到具体的代码视线中,一行一行看代码!
跳出代码,从俯视的角度来看,就像上帝视角,说起来很虚,该如何落实?
为了做到上帝视角,根源上需要对代码有层次感。
什么是代码层次呢?
自上而下,一个大的功能由若干个小功能组合而成,而这些小的功能又由更小的功能组成,如此往下,直到最小的功能不能再分。外面的大功能里面包含若干小功能,这就构成了层次。
写代码的时候,通常自下而上,最底层的功能就是单个语句,然后一个语句一个语句组合,完成某个大一些的操作,于是构成了函数(一个函数做一件事)。然后将一个个函数存放到文件中(单个文件通常包含对一个数据的一组操作),再将多个文件组成模块实现某项功能,最后多个功能形成一个完整的应用。
代码都有哪些层次呢?
根据上面写代码的表述,我们将代码自下而上分为函数层次(文件里面的某几行代码),文件层次(单个文件),模块层次(文件夹)和应用层次(代码包)。
1. 函数层次
函数层次上,对应一个函数(文件的若干行代码),实现了一个具体操作,通常是某个基本操作。例如数据的插入:
- 基于数组的实现,需要在内部移动插入位置以及后面的元素,腾出空位将数据放进去;
- 基于链表的实现,需要在内部修改插入位置前后的指针,将其和插入的数据绑定;
这就好像一家银行某个业务,例如取钱。具体操作,就要看这个负责取钱的工作人员所做的事情,例如先检查用户资料,然后查询账户,从账户中划扣资金,准备现金,交付现金给取钱人。这就是具体的实现。
2. 文件层次
文件层次,一般对应于一个代码文件(或一个头文件+一个代码文件),一般是对单个数据的抽象,包含对数据本身结构的定义和对数据操作的定义。
例如实现串口设备的文件,包括串口具体的数据结构,以及一组对串口的操作,像串口初始化,打开串口,关闭串口,向串口写入数据,从串口读取数据,查询串口状态等。
在文件的层次上,只需要关心文件所定义的对象都提供了哪些操作,至于这些操作是如何实现的,我们不需要去了解。只有在需要进一步查看代码实现的时候才去查看具体的函数内容。
所以,在文件层次上,只需要浏览代码,进行泛读就可以了,不需要逐句精读。所谓泛读,就是那种鼠标滚轮上下滚动,在函数名的地方稍微停顿一下,只看函数名和函数参数,不看实现的做法。如果是C/C++代码,在这一步上,就是去阅读头文件,看下头文件中公开的函数。
再拿银行打比方,银行大厅的若干服务窗口,每个窗口完成不同的业务,有的负责存取款,有的负责修改资料,有的负责理财业务,有的负责公积金,所有这些柜台的功能加起来,就是一个银行业务的集合。想了解一个银行都有什么功能,不需要了解窗口里面的人都做什么,每一步是如何做的,只需要知道这个银行有几个服务窗口,每个窗口负责什么业务就好了。
当然,也不只是关心函数名和参数,如果头文件有详细的注释,通常也建议看一看。
3. 模块层次
模块一般对应于代码中的一个文件夹,里面包含多个文件,从几个到几十个上百个不等。从实现上看,一个模块内包含多个对象,这些对象功能不一样,但对象间互相协作,完成一个宏观的功能。
回到银行的例子,个人业务部的所有人在一个大的办公室工作(相当于文件夹),因此一个部门就相当于一个模块。整个办公室包含多个小隔间(每个小隔间相当于一个文件),小隔间之间协作共同完成部门的功能。落实到具体,小隔间就好像模块下的一个个文件,有的负责存取款业务(文件1),有的负责安全审核业务(文件2),有的负责用户回访业务(文件3)。
4. 应用层次
应用层面就是你从总体的角度来对待应用程序。多个宏观的模块协作构成一个完整的应用。
例如,银行的公司业务部和个人业务部,再加上投资部,风险审核部和后勤服务部这多个模块就组成了银行的整个应用。
应该如何看各个层次的代码呢?
回到代码阅读,明白了各个层次,根据你阅读代码的具体目标,进入到相应层次阅读就可以了,切记不要什么事情都直接到最底层去看代码实现。
文件层面,例如想看链表都有什么功能,那就到链表头文件看下都有哪些公开的接口。如果在代码文件中,那就只浏览那些声明为非static的函数。
模块层面,例如想看一个模块是如何工作的,那就看下模块对应的文件夹下都有哪些文件(主要是看定义了哪些对象),这些文件定义的数据对象之间是什么关系?再看下这个模块对外提供了哪些接口即可,不需要关注那些没有对外的接口。
应用层面,例如想看下应用都包含了哪些模块,大多数时候解包应用看下都包含哪些文件夹就好了。或者想看整个应用提供了哪些功能?此时最好的办法不是去阅读底层代码实现,而是去相关目录下看关于应用整体介绍的文档。
解释了一大堆,简而言之:
-
只有当你关心某个操作具体实现的时候,才需要跳到具体的函数阅读代码,如果这个操作有多个子操作构成,那就依次去阅读子操作的函数。
-
如果你想知道某个对象都有哪些操作,到文件层面,转到定义对象的相关文件,看看定义了哪些公开的函数;
-
如果想知道某个模块提供了哪些功能,到模块层面,跳转到模块的相关文件,看看都有什么公开的接口,看看关于模块说明的文档;
-
如果想知道应用是怎么跑起来的,都有哪些功能,这是应用层面的问题,那就转到应用的文档,看下应用执行的步骤和关于应用功能的描述,不需要去看代码。
把自己当做旁观的第三者,不要第一时间就逐行读代码,先明白自己要阅读到代码的哪一层,然后转到相应层面去浏览文档,看注释,泛读代码就好。只有你希望了解具体的底层操作时,才需要认真地逐行反复阅读。
啰啰嗦嗦说了一大篇,简单总结就是:
将代码抽象成不同层次的对象,根据要研究的内容,在目标所在的层次,用上帝视角,采用浏览而不是逐行精读的方式,会更有利于读懂代码。不要一头扎入到具体的代码实现中,一行一行看代码!
相关文章
代码阅读
- 读代码,没有文档怎么办?
- 读代码,如何精准定位?
- 读代码,没有头绪怎么办?
- 读代码,多用用这种方式
调试
- 程序查错:一种排查问题的通用方法
- 程序查错:一类常见错误的处理思路
- 程序查错,第一件事要做什么?
8. 其它
洛奇自己维护了一个公众号“洛奇看世界”,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号: