长江后浪推前浪,前浪死在沙滩上。
这句话太适合形容IT行业了, 时髦的 技术/软件/框架 可能隔几年就被抛弃取代。
逼得咱程序员得不停地学习,停下来就意味着被淘汰。
大多数人聪慧程度一般,在完成一万小时定律之前,只能做技术的跟随者和使用者。
然而技术都有一定的学习曲线,好不容易掌握了还没耍几下,又要投入到下一波技术革新的浪潮中。
说白了就是熟悉的东西不搞了,要啃一堆堆新的代码,周而复始。
换组,换部门,换公司都会遇到上面的问题。
很快30岁就来了,似乎离“专家”还很遥远,您可能纳闷时间都去哪了?
如果您也有过这种烦恼,恭喜您成为本文重点阅读对象。否则请立刻关闭网页,无需往下浪费时间!
================================= 分割线 ================================
作为一个Developer,喜怒哀乐都来自一个抽象的东西: Source Code, 能搞得定它的就是大神!
Linus 大神说过 “Read the Fucking Source Code“, 但他没有具体指出用哪种方式,哪种工具来read?
总结我之前的read方式:
咨询同事,基本上也是用这套方式来read code,耗时很长。
实际开发过程中并没有这么充裕的时间来read code。
================================= 分割线 ================================
列举一个工作中常见的场景:
上面第2 ,第6步是耗时最长,最痛苦的。相信每个Developer都用身体会过。
若能快速跳过这两步,即快速理解代码,无疑是向大神靠近了一大步。
我相信大神都是有套路(方法)的,以不变应万变。
软件行业的万变在于有N个开发语言(C,JAVA,Python,..), N个开发平台 (Linux, Windows, Android, …),N个开发框架,N个 此处省略XX字 。。。
大神的不变在于找到一种通用的方式快速理解Source Code: (我个人观点)
工欲善其事,必先利其器。掌握 Source Code Editor, 达到如火纯青的地步, 有两种神器可选:
Vim:编辑器之神
Emacs:神的编辑器 (听名字应该选用Emacs ^_^)
原因之一:它们适应万变,稍作配置即可支持另一种新开发环境。
原因之二:它们有无敌的扩展性,世界上没有一款软件能满足所有人的需求,每个人都是独一无二的,所以有独一无二的需求,可自行编写插件来实现。
原因之三:它们本身就是出色的软件,研究它,理解它,驾驭它,随心所欲地切换,思想流畅地从指尖流出,将生产力释放到极限,这是一种信仰。
相信最理解代码的是机器(CPU),最终运行的都是二进制,从程序运行的角度理解代码。
可以变相理解为让机器来read code,“监视”程序运行的每一步,将详细过程dump出来,再将这些数据转换成文档,告诉你代码长啥样。
Source Code 是源, 在机器上运行的二进制是尾, 抓住一头一尾。
此乃太极混元状态,一头一尾相系,一手一脚相合。此处省略XX字。。。好吧,我扯远了。
本文重点讨论第2点。
================================= 分割线 ================================
上面抛玉引砖这么多,进入重点环节。
本人声明: 如下观点如有雷同,纯属英雄所见略同。 ^_^
这里我选择调试器入手,原因在于:
而每种编程语言在每个平台上都有一套工具链,能帮住理解代码的就是工具链中的调试器。
这里我不用调试器来分析bug,而是找到一种方式来自动化控制调试器,让它将程序在运行时”走过的路程”都dump出来,
dump出来的数据越多好,越全越好。
回想下我们read code的过程中,不就是在脑子里生成这些”数据”么?再把这些数据总结成文档 + 流程图。
思考下:
再思考另外几个问题:
在比如你独自一人写了一个上万行代码的软件,过了一阵子再回想这些代码,每个细节都还清楚么?头脑里是不是只剩下代码大概的”运行过程图”?
是你更清楚代码还是正在运行这个软件的机器更清楚?
如果每个Developer都对代码了如指掌,运行的程序好比数学证明一样完美可靠,那还需要跑测试case干嘛?
如果程序有bug,就说明更懂代码的机器在告诉你: 你写的代码有问题,不是吗?
如果自己写的代码都不能了如指掌,更何况要理解别人写的呢?
OK,回到用调试器dump数据,这些数据比较原始,可读性/理解性不强,可以写个小程序稍微排版下,自动生成最终你想要的那张图或文档。
这个因人而异,反正就是你理解代码后头脑里存放的代码影相。 例如:流程图 / UML实例图 / 代码运行路线等等。
有了这些原始数据 + 排版后的数据,上面例举的工作场景里的痛点,就能迎刃而解。
例如:
4万行的strace源码,完全不用看代码就可以得到下图左边部分,运行过程一目了然。
对着排版后程序执行路线,稍微对应参照下源码,例如 ptrace_restart 执行的是 ptrace 系统调用,很快就能理解源码主要脉络。
详见: strace 源码分析 (快速分析源码,半小时搞懂)
某老版本的linux kernel启动过程,不用看kernel源码。修改QEMU代码,将QEMU当成调试器,dump kernel运行状态,写个小程序排版下数据,输出路线图
详见: linux kernel启动 (自动化调试输出实例图)
================================= 分割线 ================================
下面举一个例子来实现我的“理论”。
例如想搞懂wget这个软件的代码实现:
运行结果见下图(1),左边部分,得到wget运行时执行到的所有函数及调用栈。
下图(2): 写个程序parse gdb.log, 得到可读性很好的程序运行路线。
下图(3): 在Emacs中折叠部分非重要函数调用过程,高亮显示重点函数,我相信一眼即可理解wget大致代码原理。
就算我死磕代码,花大量时间研究得很透彻,我相信过了一个多月,头脑里剩下保存的差不多也就图(3)显示的调用过程。
而用这种方法,整个过程不到5分钟。
如果要我在wget的代码上增加个新功能,我就顺着这些调用过程,在恰当的位置加些函数实现。
当然除了显示图(3)的样子外,还可以显示成类似UML格式的,只要有了原始的raw格式数据,即可将它们转成画图工具的输入数据,生成适合自己理解的图。
我收集的辅助画图工具:
================================= 分割线 ================================
对于其它语言同样需要找到能控制调试器的方法,以Java为例:
可以写个程序得到java class文件的所有方法,打上断点,并控制输入操作达到自动化
jdb详细命令见: https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/jdb.html
我之前的一篇blog中分析 Android ArrayAdapter 界面创建流程,里面的调用栈就是用这种方法合成的,根据调用栈即可画出方便理解的流程图:
================================= 结尾 ==================================
用这种方法理解大多数用户态(应用)程序基本上没多大问题。
我遇到的不大适用的地方:
那如何用这种方式理解底层代码呢,例如操作系统?
因为底层的CPU架构有很多种,可以借助QEMU,QEMU虚拟了各种平台的CPU指令,可以把QEMU当成调试器,就很容易“追踪”底层的代码了。
再以大神的另一句话结尾吧。
“You can use a kernel debugger if you want to, and I won’t give you the cold shoulder because you have “sullied” yourself.” – Linus Torvalds
貌似大神比较鄙视使用调试器,OK 此方法姑且算是一种奇技淫巧吧,蝌蝌 ~