会读好源码,才能写出好代码
而且除了经常写代码,还要保持习惯看看别人是怎么写的,这里我只引出一个话题就是如何阅读别人的代码
。
编辑
一个工整的代码就好比欣赏一个漂亮的美女一样让人赏心悦目,百看不厌
,一个乱糟糟的代码就不忍直视,而且还要吐槽他,对于阅码无数的老司机而言,保持良好的编码和阅读习惯
很重要,读别人的代码并不大费力,对于新手而言,我觉得这篇文章对你很有帮助。
保持阅读他人代码的习惯
我个人认为时常保持阅读他人代码的习惯放到第一位,你读的越多,你就越容易读懂,很多设计的思路思想都是通用的(常用的一些设计模式你可以查看往期文章 设计模式---设计模式基本原则),相信当年你们的英语老师教英语的时候一直强调阅读和词汇量。
编辑
当你代码阅读量到达一个层级,就似乎打开了你的任督二脉,你能看懂作者是怎么实现的,从而进一步分析作者为什么要这样设计(你会发现为什么比怎么做更重要),不同的语言都有各自不同经典的源码,所以我很推荐大家看优质开源项目源码,不仅仅是代码写的漂亮,而且开源项目的readme都通俗易懂,帮助读者阅读理解他的思想和用法,另外社区基本上都比较活跃,你在阅读过程中遇到的一些问题几乎都能在github,StackOverflow上能找到
提示:所以阅读开源源码我建议先读说明文档,比如README,在阅读程序的时候往往能够从README文件中找到相应的说明,从而简化了源程序的阅读工作。如果源代码有文档目录,一般为doc或者docs,最好也在阅读源程序之前仔细阅读,因为这些文档同样起了很好的说明注释作用。从makefile文件入手,分析源代码的层次结构,找出哪个是主程序,哪些是函数包。这对于快速把握程序结构有很大帮助。从main函数入手,一步一步往下阅读,遇到可以猜测出意思来的简单的函数,可以跳过。但是一定要注意程序中使用的全局变量,可以把关键的数据结构说明拷贝到一个文本编辑器中以便随时查找。
我在这里推荐一些C++/go、node.js相关的开源项目:
C++语言: libevent,memcache, thrift, rabbitmq, 一款开源的MMOG游戏服务端引擎KBE(我只看了一部分,没坚持下来)
libev是一个开源的事件驱动库,基于epoll,kqueue等OS提供的基础设施。其以高效出名,它可以将IO事件,定时器,和信号统一起来,统一放在事件处理这一套框架下处理。基于Reactor模式,效率较高,并且代码精简(4.15版本8000多行),是学习事件驱动编程的很好的资源。
Memcached是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。memcached用epoll来做事件循环(其实网络事件这块的逻辑大多数框架在设计使用上大同小异,真的是万变不离其宗),多线程的,使用Master-Worker的方式,其中主线程负责接收连接,然后将连接分给各个worker线程,在各个worker线程中完成命令的接收,处理和返回结果。内存分配则使用预先分配,预先分配一大块内存,然后接下来就从内存池中分配,这样可以减少内存的分配次数,提高效率。
Thrift 是一个软件框架(远程过程调用框架),用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引 擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务,thrift 允许你定义一个简单的定义文件中的数据类型和服务接口(类似于protobuf的结构文件)以作为输入文件,编译器生成代码用来方便地生成 RPC 客户端和服务器通信的无缝跨编程语言。著名的 Key-Value 存储服务器 Cassandra 就是使用 Thrift 作为其客户端 API 的。现在很多大型的服务器为了避免重复造轮子就直接使用thrift来做服务器和客户端,方便易用,但是这里也有一些使用上的坑,后边有机会我再分享thrift的专题
KBE 一款开源的 MMOG 游戏服务端引擎, 游戏上层业务逻辑使用Python来写,支持热更, 使用配套客户端插件能够快速与 (Unity3D、UE4、OGRE、HTML5、等等) 结合形成一个完整的客户端。
引擎使用 C++ 编写,开发者无需重复的实现游戏服务端通用的底层技术,这个和现在市面上很多bigworld的游戏服务器架构基本上相差不大,甚至市面上的一些游戏服务器架构也是借鉴了KBE的思想,开发者只需要将精力真正集中到游戏开发层面上来,服务器支持横向扩展。
GO语言: Beego,Gin,Leaf
Gin 是一个非常简约的框架。仅包含最基本的功能和库,使 Gin 成为开发高性能 REST API 的绝佳框架,具备完善的GET、POST、PUT、DELETE快捷方法,也可以方便地进行其他请求的处理,并且当今又流行前后端分离,那么作为后端支撑,Gin更适合眼前需求。
beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用,是一个 resetful 的框架 。支持如下特性:
MVC
REST
智能路由
日志调试
配置管理
模板自动渲染
layout 设计
中间件插入逻辑
方便的 JSON/XML 服务
Leaf 开源游戏服务器框架,注重运行效率 并追求极致的开发效率。Leaf 适用于几乎所有的游戏类型,简洁和易用的接口,尽可能的提升开发的效率稳定性。Leaf 总是尽可能的恢复运行过程中的错误,避免崩溃。多核支持。Leaf 通过模块机制和 leaf/go 尽可能的利用多核资源,同时又尽量避免各种副作用。
node.j:
pomelo/pinus 适用于网页游戏、社交游戏、移动游戏的服务端,框架已经实现了网关,连接器,负载均衡,游戏业务主逻辑,db相关服务的剥离和封装,拥有强大的扩展性和伸缩性,比如网易狼人杀最早使用的是pomelo,后来团队将整个服务器的框架替换成了pinus,几乎是无缝迁移,而且使用这个框架可以使用插件化的思路随意改造使用自己的”轮子“,实现高可用。
eggjs:阿里开源的企业级 Node.js 框架,基于MVC设计思路,也是高度可扩展的插件机制,内置多进程管理,基于KOA开发,一般用于小游戏,或者大数据分析统计之类的服务
如果各位读者有推荐的开源源码,也可以在下方留言或者在公众平台留言私信给我。
建立代码层次和结构
看代码不是看流水账作文,线性地往下看就行了。看代码是为了理解代码,在脑子里建立起源代码背后的层次和结构的映射。
由大到小 ,为此,在开始分析项目之初,就要明确项目都包含了哪些模块(名称空间),类的层次和结构。每个包子项目,子模块大致做什么用。主要的类有哪些,各自的大致职责是什么。主要的类里面,又有哪些主要的方法。
由粗见细,不要一开始就想把所有的细节搞清楚,否则你就会陷入“只见树木,不见森林”的困境。先要理清程序的脉络,知道那个模块是干什么的,那个类是干什么的,他们之间有什么样的联系。然后在一个一个问题深究。其思想就是,大面化下,再大而化小...你要细到什么程度,取决于你的要求及期望。一般我看到模块,类一层就不会看了,除非我对某个算法感兴趣,我也会仔细在研究之。其实这也是面向对象的设计思想,由上至下,而不是由下至上。无论你看到哪一层,你都可以说“我了解这个框架的实现”,只是看到的粒度不同而已。
由上之下,逐步求精。任何的项目,有相当的代码是用来做一些琐碎的,事务性的事情的。再牛逼的代码也是要给业务提供服务和支持的,所以光理解框架主要干什么的,结构是什么样的还不行,你也要去熟悉他的业务流转是怎么走的,所以要高效地理解和把握代码,我们就要把握核心的业务逻辑了。对于代码中的一些主要方法,或者流程,可以梳理出它们的主要步骤,次要的东西可以忽略不管,需要的时候再关注。
调试和日志
检验一个程序员的功力深浅其中一个要素就是看看他的debug调试能力。在阅读代码和分析问题过程中调试和加日志是不可缺少的,断点下在哪里最有可能定位问题所在,但又不浪费时间,记住断点并不是越多越好。什么时候应该用条件断点。碰到一个新的程序,你肯定要在入口Main里面下个断点,这个Main 就会分几个枝出来,然后对你感兴趣的枝再设断点,依次类推。当然,如何用更好的方法调试某个程序.是需要具体问题具体分析的,当然大多数case我们不可能逐一去调试的,毕竟我们分身乏术,对于一些关键的逻辑我们只是想看他走不走,有没有走到,值,状态是否有变化,那么打日志是简单直接的一种方式。
文档化或笔记整理
人脑子不善于记住方法间的进进出出之类的东西,在我们分析这些东西的同时,用一种有效的方式,把分析的结果记录下来,既保存了工作成果,更重要的是,帮助我们更容易进行分析,向深挖的时候,知道现在自己在哪里,向回退的时候,又退得出来,不至于迷路。类图,序列图,都是有用的文档形式,也可以用自己定义的更灵活的图表。好记性不如烂笔头,勤快动脑同时也勤快动手,看代码会容易很多。
- 一边阅读代码一边写注释。这是我用过的最好的方法,对代码理解得更深入,看一些重要代码或者特别难懂的代码时挺有用。更何况,注释也是一种文档嘛。
- 一边阅读代码一边绘制UML。这个方法适用于类之间的关系较复杂和调用层次较深的情况,我一般都是先绘制顺序图,然后为顺序图中的类绘制关系图。
- 通过Debug来跟踪程序的主要执行过程,这样就可以分清主次了,阅读的时候更有针对性。
- 类的快速阅读。先弄清楚它在继承链中的位置,看看它的内部状态,也就是成员变量,一般来说,类的对外接口都是对成员变量的访问、加工、代理等,然后看看它的对外接口,也就是公有成员函数,识别核心的一个或多个函数,这时候你应该可以大概了解这个类的职责或作用了。可能这个类是某个设计模式中的一个组成部分,所以,设计模式的掌握对代码的快速阅读也是很有帮助的。
- 带着问题去阅读。比如想了解android中的消息机制,那么看看Looper、Handler、MessegeQueue这几个类就可以了,其他的不要去看,要不然就跑题了。
下面列几个阅读源码时所处的情景,在特定场景下用哪些方法:
不太熟悉业务逻辑,还不是很清楚它是干啥的,可以用3、5。
代码量很大,有几十万行,甚至百万行,可以用2、3、5。
你无法看见程序的运行过程,比如没有用户界面,也有可能是无法运行的,可以用3、5。
设计复杂,用了大量的设计模式,调用链很深,可以用1、2、3、4、5。
时间有限,没有那么多时间让你看源码,可以用3、5。