读代码,多用用这种方式

题图: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. 其它

洛奇自己维护了一个公众号“洛奇看世界”,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:

公众号

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洛奇看世界

一分也是爱~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值