#分析程序的方法   在“借鉴“阶段的时候,需要掌握一些分析开源程序的方法。我总结了几条分析开源程序的方法,供大家参考。 ##先看文档了解程序功能   很多人可能习惯拿到代码就开始看,这时候对程序整体一点不了解,是十分痛苦的,我们大脑对整个程序都是陌生的,很容易触发缘脑的阻碍机制。我们要先说服缘脑, 先大概了解程序的功能,如果有文档的话,先看文档,这样先了解功能后再去看源码,对大脑来说就不会那么陌生,减少缘脑的阻碍。   有些开源程序还在文档中讲了程序的编程思想、架构原理的。如果我们不看文档光想通过看代码看出编程思想是比较困难的,先看文档了解编程思想再去看代码就比较容易理解。   每种编程语言都有将注释生成文档的工具,比如PHP有phpdoc, Java有JavaDoc , iOS有AppleDoc。大家需要了解这些工具,有时候虽然程序没有稳定,但程序注释符合一定规范,可以用这些工具把注释生成一份文档。 ##断点调试   我们的程序运行速度是非常快的,几毫秒就执行完了, 根本看不清楚执行过程, 有没有一种方法,能放慢程序的运行过程,让我们看清楚程序执行的每一步?这样就方便我们分析程序执行的每个过程。   断点调试就能做到这样的效果,   程序每执行一行代码都会暂停,在编辑器中显示出执行这行代码时各个变量的值、调用栈等信息(如图1-15)。等我们点击下一步按钮,程序才会执行下一行代码。这样我们可以一步一步分析程序。 ![](https://box.kancloud.cn/2016-01-12_5695156127350.png) 图1-15 netbeans和xdebug结合调试   PHP做断点调试需要xdebug扩展结合一个IDE编辑器(如netbeans,phpstorm等编辑器)。其他编程语言也有相应的断点调试方法,大家可以在搜索引擎搜索对应工具进行学习。 ##内置函数   很多内置的函数也有利于分析程序代码,我以PHP为例,列举几个用内置函数分析程序代码的方法。 * 用debug_backtrace看调用栈   调用栈能显示出程序的调用过程,比如一个程序先执行了A函数,A函数中又调用了B函数 ,B函数中又调用了C函数, 那么C函数的调用栈显示出来就是 A->B->C 的执行顺序。比如执行下面的示例代码 ``` <?php function a(){ b('hello'); } function b($arg){ c(); } function c(){ var_dump(debug_backtrace()); } a(); ```   我们在c函数中, 用debug_backtrace 获得调用栈并用var_dump输出。debug_backtrace函数返回值是一个数组,数组中记录了每一步调用信息,包括文件、行数、执行的函数名、函数传参参数。这个调用栈要从下往上看:先看数组最后一个元素,它是第一步程序执行过程;倒数第二个元素为第二步程序执行过程。运行结果如下: ``` array(3) { [0]=> array(4) { ["file"]=> string(27) "/Users/luofei/test/test.php" ["line"]=> int(6) ["function"]=> string(1) "c" ["args"]=> array(0) { } } [1]=> array(4) { ["file"]=> string(27) "/Users/luofei/test/test.php" ["line"]=> int(3) ["function"]=> string(1) "b" ["args"]=> array(1) { [0]=> &string(5) "hello" } } [2]=> array(4) { ["file"]=> string(27) "/Users/luofei/test/test.php" ["line"]=> int(13) ["function"]=> string(1) "a" ["args"]=> array(0) { } } } ```   debug_backtrace获得调用栈非常详细,包括每个传参的值都显示出来了。但有时候我们不需要这么详细,可以使用 debug_print_backtrace函数打出一个简单的调用栈。这个函数自己有输出行为,不需要用var_dump打印。 将上面示例代码 `var_dump(debug_backtrace())` 改为 `debug_print_backtrace()` , 再运行程序,我们得到如下结果: ``` #0 c() called at [/Users/luofei/test/test.php:6] #1 b(hello) called at [/Users/luofei/test/test.php:3] #2 a() called at [/Users/luofei/test/test.php:13] ```   调用栈帮助我们快速分析程序的执行流程,比如我们在分析一些开源的MVC框架时, 很想知道核心代码是哪儿调用Controller的, 这时候我们就可以在Controller中 用debug_backtrace 打印出调用栈来分析,比我们一行一行的找代码快很多。 * 用get_included_files看加载了哪些文件   很多开源程序都有一些共通之处,比如一般都有配置文件,数据库DB类等。 如果一个刚拿到一个陌生的开源程序,我们想快速找到它的配置文件,可以用get_included_files 显示出程序加载了哪些文件,然后根据文件名可以快速找到配置文件的位置,配置文件的文件名一般都叫“config”。 比如我们打印出了ThinkPHP5加载的所有文件,从这个文件列表中我们能开始发现ThinkPHP5的项目配置文件地址应该为`/thinkphp5/application/config.php` ``` array(20) { [0]=> string(20) "/thinkphp5/index.php" [1]=> string(29) "/thinkphp5/thinkphp/start.php" [2]=> string(28) "/thinkphp5/thinkphp/base.php" [3]=> string(44) "/thinkphp5/thinkphp/library/think/loader.php" [4]=> string(43) "/thinkphp5/thinkphp/library/think/error.php" [5]=> string(35) "/thinkphp5/thinkphp/mode/common.php" [6]=> string(44) "/thinkphp5/thinkphp/library/think/config.php" [7]=> string(34) "/thinkphp5/thinkphp/convention.php" [8]=> string(41) "/thinkphp5/thinkphp/library/think/app.php" [9]=> string(33) "/thinkphp5/application/config.php" [10]=> string(35) "/thinkphp5/application/database.php" [11]=> string(32) "/thinkphp5/application/route.php" [12]=> string(41) "/thinkphp5/thinkphp/library/think/log.php" [13]=> string(53) "/thinkphp5/thinkphp/library/think/log/driver/file.php" [14]=> string(43) "/thinkphp5/thinkphp/library/think/cache.php" [15]=> string(55) "/thinkphp5/thinkphp/library/think/cache/driver/file.php" [16]=> string(42) "/thinkphp5/thinkphp/library/think/lang.php" [17]=> string(45) "/thinkphp5/thinkphp/library/think/session.php" [18]=> string(49) "/thinkphp5/application/index/controller/index.php" [19]=> string(46) "/thinkphp5/thinkphp/library/think/response.php" } ``` * 变量的输出方法   输出变量时,echo函数会有一些问题, echo调试时如果变量是一个空字符串,看不见输出的内容,经常会误以为是程序没有执行到调试的地方。还有,用echo如果是要输出的变量是对象或数组只会打印出变量的类型,不知道变量的内部结构。   用var_dump调试不会有这些问题, 如果是输出空字符串,var_dump也会有显示:`string(0) ""` ,不会让人误以为程序没有执行。出数组或对象的时候,var_dump也能输出对象的内部结构。所以建议大家用var_dump调试而不用echo。   有时候不能直接输出调试信息,比如在线上环境调试时,如果输出调试信息,正式使用产品的用户也能看见了。这时候你可以会把调试信息写到日志文件中。   写日志文件时不要用覆盖的方式,程序执行了很多次但最能看见最后一次结果,追加的方式能看见每一次的执行结果。PHP设置文件写文件的方式:file_put_contents 设置第三个参数为FILE_APPEND。   如果写入文件是一个对象或数组,我们要用var_export,将变量导出再写入日志文件,否则无法看见变量的内部结构。 下面代码演示如何将一个数组以追加的方式写入文件。 ``` $arr=[1,2,3,4]; file_put_contents('/tmp/log.txt',var_export($arr,true),true); ``` ##SocketLog   像上面说的不能直接用var_dump输出调试信息的情况,以前需要写日志文件来调试,有了SocketLog比用日志文件更方便。它可以把调试信息实时的打印到浏览器控制台,可以打印字符串、对象、数组等各种变量类型,可以灵活定义打印字符串的样式,可以打印调用栈,还方便分析开源程序,有助于我们二次开发开源产品。   github地址:http://github.com/SocketLog ,我们按官方文档安装好SocketLog,然后运行官方的例子可以看见简单的效果。   示例代码: ``` slog('msg','log'); //一般日志 slog('msg','error'); //错误日志 slog('msg','info'); //信息日志 slog('msg','warn'); //警告日志 slog('msg','trace');// 输入日志同时会打出调用栈 slog('msg','alert');//将日志以alert方式弹出 slog('msg','log','color:red;font-size:20px;');//自定义日志的样式,第三个参数为css样式 ```   用浏览器查看的效果如下: ![](https://box.kancloud.cn/2016-01-12_5695156177738.png) 图1-16 SocketLog打印日志的效果   如图1-16,我们并没有把调试信息打印到网站的正文,而是打印到了chrome浏览器的控制台中,还可以输出不同样式的日志。需要打开chrome浏览器的控制器才能看见日志,window下可以按F12打开, Mac下同时按下“⌘+alt+i” 可以打开控制台。   SocketLog调试的原理是什么呢? 如图1-17 ![](https://box.kancloud.cn/2016-01-12_569515618854c.png) 图1-17 SocketLog运行原理   当PHP程序无法直接把调试信息输出到浏览器时,我们借助了WebSocket ,搭建一个WebSocket服务,PHP将日志传送给WebSocket, WebSocket再将日志发送给chrome浏览器。所以要使用SocketLog,需要启动websocket服务器同时浏览器需要安装一个接收日志的插件。   我们还可以把程序执行的所有SQL语句打印出来,从而有助于我们分析开源程序,我以OneThink的程序为例为大家做说明 ![](https://box.kancloud.cn/2016-01-12_569515619ecaf.png) 图1-18 用SocketLog分析OneThink程序 如图1-18,我们用SocketLog打出OneThink的SQL语句后,当我们访问每个页面,都知道了这个页面执行了哪些SQL语句,并且点开每条SQL语句能显示出执行SQL语句的调用栈,这很方便我们找到自己想要的代码。 假设我们在做OneThink的二次开发,想在自己新增的程序里面也读取OneThink的文章,读取文章这种操作肯定OneThink已经封成函数了,我们如何能快速找到这个函数?如上图所示,只需要访问一下文章详情页,然后看哪条SQL语句像是在读取文章。```SELECT `id`,`parse`,`content`,`template`,`bookmark` FROM `onethink_document_article` WHERE ( `id` = 1 ) LIMIT 1 ,``` 这条SQL很像是在读取文章,我们点开这条SQL语句的调用栈,很快就会发现 DocummentModel::detail方法就是我们想找的代码, 这比一行一行的去找代码快多了。   很多开源程序都做了对数据库操作的封装,一般叫着Db类,程序对数据库进行操作都要调用这个类,我们只要找到这个Db类,加上SocketLog调试,将SQL打印出来,就能到达上图显示效果。以OneThink为了,需要修改ThinkPHP/Library/Think/Db.class.php文件的debug方法加上代码 `slog($this->queryStr,$this->_linkID);` debug方法是每次数据库操作都会执行的方法, $this->queryStr 就是这次数据库操作的SQL语句, slog的第二个参数$this->_linkID传递的是数据库对象,当第二个参数为数据库对象是, SocketLog会对SQL语言性能进行分析并打出调用栈。   SocketLog是我以前发现调试API十分麻烦时所开发的工具,程序员本来就是有创造性的,不要忍受自己觉得麻烦的地方,你可以自己开发工具解决问题,你可以做到自动化。SocketLog为我们团队带来很多便利,我们团队现在如果没有SocketLog都快感觉不能工作了。   SocketLog还能做微信调试,如图1-19 ![](https://box.kancloud.cn/2016-01-12_56951561c80b5.png) 图1-19 用SoketLog调试微信   我们在开发微信公众号的时候,接口出错时微信上面只会提示“该公众号暂时无法提问服务,请稍候再试”, 然后并不知道出错原因,这给开发带来很多麻烦,而我们将微信API配上SocketLog后,可以把调试信息和程序报错打印到浏览器的控制台上。如上图所示,我们很快知道API出错是因为程序报错`Call to undefined function...` 调用了一个不存在的函数。 要做微信调试, SocketLog需要设置force_client_id 这个配置项,从而将调试信息打到指定的浏览器,具体如何使用大家可再参考一下官方文档。 ##整理思维   再做开源程序分析时,会看很大量的代码,如果光是靠脑力记,会很累,越记越乱。如何把脑袋里烦乱的思绪理顺? 这时候可以用思维导图工具xmind,如图1-20是用Xmind整理的ThinkPHP5的执行流程。 ![](https://box.kancloud.cn/2016-01-12_569515622ef9d.png) 图1-20 用Xmind整理ThinkPHP执行流程   Xmind的每一个分支都是可以拖动的, 我们先把杂乱内容统统列到xmind上, 然后再拖动进行归类,从而理清思绪。   另外,在整理程序类和类之间关系时,可以画UML图形(如图1-21) ![](https://box.kancloud.cn/2016-01-12_569515625e64d.png) 图1-21 用UML图形分析类和类的关系 UML图形能表示出类与类是继承、组合还是聚合等关系,可以使用staruml等软件要画UML图形,如果你之前对UML不了解,建议再网上找更多相关的资料来学习一下。