zoukankan      html  css  js  c++  java
  • 怎样高速读懂别人的项目

    作为入行未深的0基础程序猿。由于工作须要接手了别人的项目做优化。才发现已入苦海,由于可能原作者比較清楚程序的架构以及每行代码的含义所以非常多地方没有进行凝视。程序中一些变量以及定义都是使用的缩写。所以弄懂这些变量的含义就已经让自己烦躁不安,再加上源代码体系较大程序中也嵌入了非常多外部的库每每打开源代码看上一会儿就感觉头都要炸了,简直是毫无头绪,多看上几眼就会认为十分的烦躁。

    只是还好,可能真的是要逼自己一把才会成长。

    当然也没有办法,这但是领导分配的任务啊,总不能拿钱不干活吧。在哪里都说只是去啊。再说假设能搞定自己也能从中学到非常多东西。那就可劲干吧。终于还是完毕了优化任务,并且比计划要提前了三分之中的一个。

    总结来说,作为0基础程序猿(我自己就是O(∩_∩)O哈哈~)。切勿急躁!

    。!感觉越耐不住性子成长的过程就会越缓慢,不会的就上网查资料,或者问同事在论坛上发帖子求助。总能找到解决方式的。

    非常感谢我们的项目负责人,在我工作的这一段时间真是帮助非常大非常大,我自己心里已经将他视为师傅,从C语言開始手把手教我,尽管如今仅仅是我有问题再去请教他,但他已经教会了我学习的方法,我认为这才是最重要的。授之以鱼不如授之以渔嘛!

    不废话了,以下是查询读别人源码的时候看到的文章(原文链接已失效)认为受益匪浅就做了摘录,总的来说就是先理清框架。然后就是一步一步深入分析。最总要的是切勿急躁!

    阅读他人的程式码( 1 )

    读懂程式码,使心法皆为我所用 

    程式码是别人写的,仅仅有原作者才真的了解程式码的用途及涵义。很多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其它人所写的程式码。可是。与其抗拒接收别人的程式码,不如彻底了解相关的语言和惯例,当成是培养自我实力的基石。

    对大多数的程式人来说,撰写程式码也许是令人开心的一件事情,但我相信,有很多其它人视阅读他人所写成的程式码为畏途。很多人宁可自己又一次写过一遍程式码,也不愿意接收别人的程式码。进而修正错误。维护它们,甚至加强功能。 

    这当中的关键到底在何处呢?若是一语道破,事实上也非常easy。程式码是别人写的。仅仅有原作者才真的了解程式码的用途及涵义。很多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其它人所写的程式码。这是来自于人类内心深处对于陌生事物的原始恐惧。 

    读懂别人写的程式码,让你收获满满 

    只是。基于很多现实的原因,程式人时常受迫要去接收别人的程式码。比如。同事离职了,必须接手他遗留下来的工作,也有可能你是刚进部门的菜鸟,而同事经验值够了。升级了。风水轮流转,一代菜鸟换菜鸟。甚至。你的公司所承接的专案,必须接手或是整合客户前一个厂商所遗留下来的系统。你们手上仅仅有那套系统的原始码(运气好时。还有数量不等的文件) 。 

    诸如此类的故事,事实上时常在程式人身边或身上持续上演着。很多程式人都将接手他人的程式码,当做一件悲慘的事情。每一个人都不想接手别人所撰写的程式码,由于不想花时间去探索,宁可将生产力花在产生新的程式码,而不是耗费在了解这些程式码上。 

    非常遗憾的是。上述的情况对程式人来说非常难避免。

    我们总是必须碰触到其它人所写成的程式码,甚至必须了解它,加以改动。对于这项需求。在现今开放原始码的风气如此盛行的今日,正如之前的“程式设计2.0 ”文中所提到的,你能够透过开放原始码学习到新的技术,学习到高手的架构设计,大幅提高学习的效率及效果。你甚至能够直接自开放原始码专案中抽取,提炼出自己所需的程式码,站在巨人的肩膀上。直接由彼端获得所需的生产力。从这个观点来看,读懂别人所写的程式码,就不再仅仅是从负面观点的“被迫接收” ,而是极具正面价值的“汲取养份。 ” 

    先了解系统架构与行为模式,再细读 

    倘若撰写程式码是程式人的重要技艺之中的一个,那么读懂别人的程式码。接着加以改动,也势必是还有一个重要的技艺。 

    假设你不能熟悉这项工作。不仅在遭逢你所不愿面对的局面时。无法解决眼前接手他人程式码的难题,更重要的是。当你看着眼前现成的程式码。却不知怎样从中撷取自己所需,导致最后仅仅能入宝山空手回,望之兴叹。

     

    接触他人的程式码,大致上可以分为三种程度:一,了解,二,改动。扩充,三,抽取,提炼。了解别人的程式码是最基础的工作。倘若不能了解自己要处理的程式码,就甭论改动或扩充,更不可能去芜存菁。从中萃取出自己所需。回收再利用别人所撰写的程式码。虽说是“阅读” 。但程式码并不像文章或小说一样,透过这样的做法,便可以获得一定程度的了解。

    阅读文章或小说时。差点儿都是循序地阅读,你仅仅消翻开第一页,一行行阅读下去就可以。可是,有很多程式人在试着阅读其它人的程式码时。却往往有不知怎样读起的困难。 

    也许找到系统的第一页(也就是程式码运行的启始点)并不难,可是复杂度高的系统,有时十分庞大。有时千头万绪。 

    从程式码的启始点開始读起,一来要循序读全然部的程式码旷日费时,二来透过这样的方式来了解系统,非常难在脑中构建出系统的面貌。进而了解到系统真正的行为。所以,阅读程式码的重点,不在于读完每一行程式码,而是在于有效率地透过探索及阅读,从而了解系统的架构及行为模式。以便在你须要了解不论什么片段的细节实作时,可以非常快在脑上对映到详细的程式码位置。直到那一刻,才是细读的时机。 

    熟悉沟通语言与惯例用语 

    不论怎样。有些主要的准备。是阅读他人程式码时必需要有的。 

    首先。你最好得了解程式码写成的程式语言。想要读懂法文写成的小说。总不能连法文都不懂吧。

    有些情况则非常特殊。我们尽管不懂该程式码撰写所用的语言,可是由于现代语言的高阶化,并且流行的程式语言多半都是血统相近。所以即使不那么熟悉,有时也可勉力为之。 

    除了认识所用语言之外,再来就是要先确认程式码所用的命名惯例(命名惯例) 。了解命名惯例非常重要。不同的程式人或开发团队,差异可能非常大。

     
    这命名惯例涵盖的范围通常包含了变数的名称,函式的名称。类别(假设是物件导向的话)的名称。原始码档案。甚至是专案建构文件夹的名称。

    倘若使用了像设计模式之类的方法,这些名称更有一些详细的表述方式。 

    命名惯例有点像是程式人在程式语言之上。另行建构的一组沟通行话。

    程式人会透过共通约束,遵守的命名惯例,来表达一些较高阶的概念。比如,有名的匈牙利式命名法,便将变数名称以属性,型别,说明合并在一起描写叙述。

    对程式人来说。这样的方式可以提供更丰富的资讯,以了解该变数的作用及性质。 

    对程式码阅读来说。熟悉这个做法之所以重要,是由于当你了解整个系统所採用的惯例时。你便能试着以他们所共同操用的语汇来进行理解。

    倘若,不能了解其所用的惯例,那么这些额外提供的资讯,就无法为你所用。像以设计模式写成的程式码,相同处处充满着模式的名称。诸如:工厂,门面。代理等等。以这些名称指涉的类别。也直接透过名称,表达了它们自身的作用。对于懂得这命名惯例的读者来说。不须要深入探索,也能非常快捕捉到这些类别的意义。

     

    当你拿到一套必须阅读的程式码时,最好先取得命名惯例的说明文件。然而,并非每套程式码都附有此类的说明文件。

    还有一个方式。就是自己到程式码中。大略浏览一遍,有经验的程式人能够轻易发掘出该系统所用的命名惯例。

     

    常见的命名方式不脱那几类,这时候经验就非常重要。倘若你知道的惯例越多,就越能轻易识别他人所用的惯例。假设运气非常糟。程式码所用的惯例是前所未见的。那么你也得花点时间归纳,凭自己的力量找出这程式码命名上的规则。

     

    掌握程式码撰写者的心态与习惯 

    大多数的程式码,基本上都依循一致的命名惯例。只是运气更差的时候,一套系统中可能会充斥着多套命名惯例。这有可能是由于开发团队由多组人马所构成。每组人马都有不同的文化,而在专案开发管理又没有管控得宜所造成。最糟的情况。程式码全然没有明显的惯例可言,这时候阅读的难度就更高了。 

    想要阅读程式码。得先试着体会程式码作者的“心” 。想要这么做,就得多了解对方所使用的语言。以及惯常运用的语汇。

    在下一回中。我们将继续探讨阅读程式码的相关议题。 

    阅读他人的程式码( 2 )

    摸清架构,便可轻松掌握全貌

    在本文中,我们的重点放在:要了解一个系统,最好是採取由上至下的方式。先试着捕捉系统架构性的观念,不要过早钻进细节,由于那通常对于你了解全貌。没有多大的帮助。阅读程式码不须要从第一行读起,我们的目的并非在于读遍每一段程式码。

    基于很多原因,程式人须要阅读其它人所写成的程式码。而对程式设计2.0时代的程式人来说,最正面的价值在于,能读懂别人程式的人,才有能力从中萃取自己所需的程式,借以提高生产力。

     

    阅读程式码的目的。在于了解全貌而非细节 

    想要读懂别人程式码的根本基础。便是了解对方所用的程式语言及命名惯例。

    有了这个基础之后,才算是具备了主要的阅读能力。

    正如我之前提到的─ ─想要读懂法文写成的小说,总不能连法文都不懂吧。

    阅读程式码和阅读文学作品。都须要了解撰写所用的语言及作者习用的语汇。 

    但我们在阅读文学作品一般是採循序的方式,也就是从第一页開始。一行一行地读下去,依循作者为你铺陈的步调。逐渐进到他为你准备好的世界里。阅读程式码却大大不同。我们非常少从第一行開始读起。由于除非它是非常easy的单运行绪程式,否则非常少这么做。由于要是这么做。就非常难了解整个系统的全貌。是的。我们这边提到了一个重点,阅读程式码的目的在于了解系统的全貌,而不是在于仅仅是为了地毯式的读遍每一段程式码。 

    就拿物件导向程式语言所写成的系统来说,整个系统被拆解,分析成为一个个独立的类别。阅读个别类别的程式码,也许能够明确每项类别物件个别的行为。

    但对于各类别物件之间怎样交互影响。怎样协同工作。又非常easy陷入盲人摸象的困境。这是由于各类别的程式码,仅仅描写叙述个别物件的行为,而片段的阅读就仅仅能造就片面的认识。 

    由上而下厘清架构后,便可轻易理解组成关系 

    假设你想要跳脱困境,不想浪费大量时间阅读程式码。却始终仅仅能捕捉到对系统片段认识,就必须转换到还有一种观点来看待系统。

    从个别的类别行为着手,是由下至上(自下而上)的方法;在阅读程式码时。却应该先採由上至下(自上而下)的方式。

    对程式码的阅读来说,由上至下意谓着,你得先了解整个系统架构。

     

    系统的架构是整个系统的骨干,支柱。

    它表现出系统最突出的特征。知道系统架构到底属于那一种类型。通常大大故意于了解系统的个别组成之间的静态及动态关系。

    有些系统由于所用的技术或框架的关系,决定了最上层的架构。

    比如,採用的Java Servlet的/ JSP的技术的应用系统。最外层的架构便是以J2EE的(或起码的J2EE中的Web容器)为根本。 

    使用的Java Servlet的/ JSP的技术时,决定了某些组成之间的关系。

    比如, Web容器根据web.xml中的内容加载全部的Servlets 。听众,以及过滤器。每当语境发生事件(比如初始化)时,它便会通知监听类别。

    每当它收到来自client的请求时,便会依循设定的全部过滤器链,让每一个过滤器都有机会检查并处理此一请求,最后再将请求导至用来处理该请求的Servlet的。 

    当我们明确某个系统採用这种架构时,便能够非常easy地知道各个组成之间的关系。即使我们还不知道到底有多少Servlets 。但我们会知道,每当收到一个请求时。总是会有个相相应的server来处理它。当想要关注某个请求怎样处理时。我应该去找出这个请求相应的server。 

    了解架构。必需要加上层次感 

    相同的,以爪哇写成的网页应用程式中,或许会应用诸如Struts的之类的的MVC框架。以及像Hibernate的这种资料存取框架。它们都能够视为最基本的架构下的较次级架构。

    而各个应用系统,甚至有可能在Struts的及休眠之下,建立自有的更次级的架构。 

    也就是说。当我们谈到“架构”这种观念时,必需要有层次感。而不论是那一层级的架构,都会定义出各自的角色。以及角色间的关系。对阅读者来说,相较于直接切入最细微的单一角色行为,不如了解某个特定的架构中,到底存在多少角色。以及这些角色之间的互动模式,比較可以帮助我们了解整个系统的运作方式。 

    这是一个非常重要的关键,当你试着进到最细节处之前,应该先试着找出參与的角色,及他们之间的关系。比如,对事件驱动式的架构而言,有3个非常重要的角色。

    一个是事件处理的分派器(事件调度) ,一个是事件产生者(事件发生器) ,还有一个则是事件处理器(事件处理程序) 。 

    事件产生器产生事件,并送至事件分派器,而事件分派器负责找出各事件相相应的事件处理器,而且转交该事件,并命令事件处理器加以处理。

    像的图形用户界面的Windows应用程式。便是採用事件驱动式的架构。 

    当你知道此类的应用程式皆为事件驱动式的架构时,你便能够进一步得知,在这种架构下会有3种基本的角色。尽管或许还不清楚整个系统中,到底会须要处理多少事件的类型,但对你而言。已经建立了对系统全貌最概观的认识。 

    尽管你还不清楚全部的细节。但诸如确切会有那些事件类型之类的资讯。在此刻还不重要─ ─不要忘了,我们採取的是由上而下的方式。要先摸清楚主建筑结构,至于壁纸的花色怎么处理,那是到了尾声时才会做的事。

     

    探索架构的第一件事:找出系统怎样初始化 

    有经验的程式人。对于时常被运用的架构都非常熟悉。经常仅仅须要瞧上几眼。就能明确一个系统所用的架构。自然就行直接联想到当中会存在的角色,以及角色间的关系。然而,并非每一个系统所用的架构,都是大众所熟悉,或是一眼可以望穿的。

    这时候,你须要探索。目标相同要放在界定当中的角色。以及角色间的静态,动态关系。 

    不论某个系统所採用的架构是否为大部分人所熟知的,在试着探索一个系统的长相时,我们应该找出来几个答案,了解在它所用的架构下,下列这件事是怎样被完毕的:一,系统怎样初始化,二。与这个系统相接的其它系统(或使用者)有那些。而相接的介面又是什么;三,系统怎样反应各种事件,四,系统怎样处理各种异常及错误。 

    系统怎样初始化是非常重要的一件事,由于初始化是为了接下来的全部事物而做的准备。从初始化的方式。内容,能知道系统做了什么准备,对于系统会有什么行为展现,也就能得窥一二了。之所以要了解与系统相接的其它系统(或使用者),为的是要界定出系统的边界。其它的系统可能会提供输入给我们所探索的系统,也可能接收来自这系统的输出,了解这边界所在。才干确定系统的外观。 

    而系统所反应的事件类型。以及怎样反应,基本上就代表着系统本身的主要行为模式。最后,我们必须了解系统处理异常及错误的方式,这相同也是系统的重要行为,但easy被忽略。之前。我们提到必须先具备一个系统的语言基础。才可以进一步加以阅读。而在本文中。我们的重点放在:要了解一个系统,最好是採取由上至下的方式。先试着捕捉系统架构性的观念,不要过早钻进细节,由于那通常对于你了解全貌,没有多大的帮助。

    阅读他人的程式码( 3 ) 

    优质工具在手,读懂程式非难事

    系统的复杂度往往超过人脑的负荷。阅读程式码的时候,你会须要很多其它工具提供协助。使用好的整合式开发环境( IDE )的或文字编辑器,就能提供最主要的帮助。

    阅读程式码的动作,能够是非常原始的。利用最简单的文字编辑器。逐一开启原始码。然后凭借着一己的组织能力,在不同的程式码间跳跃,拼凑出脑中想要构建的图像。 
    只是,系统的复杂度往往超过人脑的负荷。

    阅读程式码的时候,你会须要很多其它工具提供协助。使用好的整合式开发环境( IDE )的或文字编辑器,就能提供最主要的帮助。

     

    善用文字编辑器或IDE中。加速解读程式码 

    很多文字编辑器提供了常见程式语言的语法及keyword标示功能。这对于阅读来说,绝对可以起非常大的作用。有些文字编辑器(比如我经常使用的编辑器及偶而使用的记事本+ + ),甚至可以自己主动列出某个原始档中全部定义的函式清单,更同意你直接从清单中选择函式。直接跳跃到该函式的定义位置。

    这对于阅读程式码的人来说,就提供了极佳的便利性。 

    由于在阅读程式码时,最常做的事。就是随着程式中的某个控制流,将阅读的重心,从某个函式移至它所呼叫的还有一个函式。

    所以对程式人来说。阅读程式码时最常做的事之中的一个就是:找出某个函式位在那一个原始档里,接着找到该函式所在的位置。

     

    好的的IDE可以提供的协助就很多其它了。

    有些可以自己主动呈现一些额外的资讯,最实用的莫过于函式的原型宣告了。比如,有些的IDE支援当游标停留在某函式名称上一段时间后,它会以提示的方式显示该函式的原型宣告。 

    对阅读程式码的人来说,在看到程式码中呼叫到某个函式时。能够直接利用这种支援,立即取得和这个函式有关的原型资讯,立即就能知道呼叫该函式所传入的各个引数的意义,而不必等到将该函式的定义位置找出后,才干明确这件事。 

    grep按(读者:推荐来源透视)是一个基本而极为实用的工具 

    除了选用好的文字编辑器或的IDE 之外,另一个基本。但却极为实用的工具,它就是grep按。熟悉的Unix作业系统的程式人,对grep按这个公用程式多半都不陌生。

    grep按最大的用途,在于它同意我们搜寻某个文件夹(包含递回进入全部子文件夹)中全部指定档案,是否有符合指定条件(常数字串或正规表示式)档案。 

    倘若有的话,则能帮你指出所在的位置。

    这在阅读程式码时的作用极大。当我们随着阅读的脚步,遇上了不论什么一个不认识,但自觉得重要的类别,函式,资料结构定义或变数,我们就得找出它到底位在这茫茫程式码海中的何处,才干将这个图块从未知变为已知。 
    grep按之所以好用,就是在于当我们发现某个未知的事物时,能够轻易地利用它找出这个未知的事物到底位在何方。

    此外,虽说grep按是Unix系统的标准公用程式之中的一个。可是像视窗这样子的平台,也有各种类型的grep按程式。对于在视窗环境工作的程式人来说,能够自行选用认为称手的工具。 

    gtags可建立索引,让搜寻更有效率 

    grep按尽管好用,可是仍然有一些不足之处。第一个缺点在于它并不会为所搜寻的原始码档案索引。每当你搜寻时,它都会逐一地找出全部的档案,而且读取当中的全部内容。过滤出满足指定条件的档案。当专案的原始码数量太大时,就会产生搜寻效率不高的问题。

     

    第二个缺点是它仅仅是一个单纯的文字档搜寻工具,本身并不会剖析原始码所相应的语言语法。当我们仅仅想针对“函式”名称进行搜寻时。它有可能将注解中含有该名称的原始码,也一并找了出来。 

    针对grep按的缺点。打算阅读他人程式码的程式人。能够考虑使用像是gtags这样子的工具。 gtags是源码的GNU全局标记系统,它不仅仅搜寻文字层次,并且由于具备了各种语言的语法剖析器,所以在搜寻时,能够仅仅针对和语言有关的元素。比如类别名称。函式名称等。 

    并且,它能针对原始码的内容进行索引,这意谓一旦建好索引之后,每次搜寻的动作,都毋需又一次读取全部原始码的内容并逐一搜寻。

    仅仅须要以现成的索引结构为基础。就可以有效率的寻找关键段落。 

    gtags提供了基于命令列的程式,让你指定原始码所在的文件夹运行建立索引的动作。它同一时候也提供程式让你得如同操作grep按一般。针对索引结构进行搜寻及检索。它提供了很多实用的检索方式,比如找出专案中定义某个资料结构的档案及定义所在的行号。或者是找出专案中全部引用某资料结构的档案,以及引用处的行号。

     

    这么一来,你就能够轻易地针对阅读程式码时的需求予以检索。相较于grep按所能提供的支援, gtags这种工具。简直是强大很多。 

    再搭配htags制作的HTML文件,更是如虎添翼 

    另一个绝对须要一提的工具。

    这个叫做htags的工具,可以帮你将已制作完毕的索引结构,制作成为一组相互參考的的HTML文件。

    基本上,利用这种的HTML文件阅读程式码,比起单纯地直接阅读原始码,来得更有结构。原因是阅读程式码时,这种的HTML文件,已经为你建立起在各个原始码档案片段间跳跃的链结。 

    htags工具首先为你找出全部定义的Main ( )函式的档案。而且列出所在的函式。

    找出的Main ( )函式,时常是阅读程式码的第一步,由于主要( )函式是程式的主要入口点,全部的动作皆由此启动。它是一切事物的源头。

     
    凭借htags制作的的HTML文件,你能够轻易地点击超连结,直接进到的Main ( )函式所在的程式码片段。

     


    当我们检视上述原始码时。发现av_register_all ( )是个陌生,无法了解的事物,而想要搞懂它到底是什么,能够再继续点击这个函式。这真是太方便了!

    阅读至此,你会猛然发现, gtags仿佛就是为了阅读程式码而专门量身打造的利器。 

    阅读他人的程式码( 4 )

    望文生义,进而推敲组件的作用

    先建立系统的架构性认识,然后透过名称及命名惯例,就能够猜測出各组件的作用。比如:当AOL的Winamp尝试着初始化一个插件时。它会呼叫这个结构中的初始化函式,以便让每一个插件程式有机会初始化自己。

    当AOL的Winamp打算结束自己或结束某个插件的运行时,便会呼叫退出函式。

    在阅读程式码的细节之前。我们应先试着捕捉系统的运作情境。

    在採取由上至下的方式时,系统性的架构是最顶端的层次,而系统的运作情境,则是在它之下的还有一个层次。



    好的说明文件难求,拼凑故事的能力非常重要 

    有些系统提供良善的说明文件,或许还利用UML的充分描写叙述系统的运作情境。

    那么对于阅读者来说,从系统的分析及设计文件着手。便是高速了解系统运作情境的一个途径。


    可是,并非每一个软体专案都伴随着良好的系统文件。而很多极具价值的开放原始码专案。也时常不具备此类的文件。对此,阅读者必须尝试自行捕捉,并适度地记录捕捉到的运作情境。 

    我喜欢将系统的运作情境。比拟成系统会上演的故事情节。

    在阅读细节性质的程式码前。先知道系统到底会发生那些故事,是必备的基本功课。

    你能够利用熟悉或者自己发明的表示工具,描写叙述你所找到的情境。甚至能够仅仅利用简单的列表。直接将它们列出。仅仅要能够达到记录的目的,对程式码阅读来说。都能够提供帮助。

    或者,你也能够利用基于UML中的类别图。合作图,循序图之类的表示方法。做出更具体的描写叙述。 
    当你可以列出系统可能会有的情境,表示你对系统所具备的功能。以及在各种情况下的反应,都具备概括性的认识。

    以此为基础。便可在不论什么须要的时候。钻进细节处深入了解。

     

    探索架构的第一步─ ─找到程式的入口 

    在之前。我们在一个开发专案中。以前须要将系统所得到的的MP3音讯档。放至iPod的这个极受欢迎的播放设备中。

     

    尽管iPod的本身也能够做为可移动式的储存设备,但并非单纯地将MP3播放档案放到中的iPod ,就能够让苹果的播放器认得这个档案,甚至能够加以播放。

     
    这是由于苹果利用一个特殊的档案结构( iTunes的数据库) ,记录播放器中可供播放的乐曲,播放清单以及乐曲资讯(比如专辑名称,乐曲长度,演唱者等) 。

    为了了解而且试着反复使用既有的程式码,我们找到了一个AOL的Winamp的iPod的外挂程式(插件) 。 

    AOL的Winamp是个人电脑上极受欢迎的播放软体。而我们找到的外挂程式。能让的软件直接显示连接至电脑的的iPod中的歌曲资讯,而且同意的软件直接播放。 

    我们追踪与阅读这个外挂程式的思路及过程例如以下。首先,我们要先了解外挂程式的系统架构。非常明显的,大概浏览过原始码后,我们注意到它依循着AOL的Winamp为插件程式所制定的规范,也就是说。它是实作成的Windows上的DLL的。而且透过一个叫做winampGetMediaLibraryPlugin的DLL的函式,提供一个名为winampMediaLibraryPlugin的结构。 
    当我们不清楚系统的架构到底为何时。我们会试着探索,而第一步。便是找到程式的入口。怎样找到呢?这会依程式的性质不同而有所区别。 
    对一个本身就是可独立运行的程式来说,我们会找启动程式的主要函式。比如对的C / C + +来说就是主要( ) 。而对爪哇来说,便是静无效的main ( ) 。在找到入口后。再逐一追踪,摸索出系统的架构。

     
    但有时,我们所欲阅读的程式码是类别库或函式库,它仅仅是用来提供多个类别或函式供用户端程式(客户程序)使用。本身并不具单一入口,此类的程式码具有多重的入口─ ─每一个同意用户端程式呼叫的函式或类别,都是它可能的入口。

     

    比如。对AOL的Winamp的 iPod的插件来说,它是一个动态链接库形式的函式库。所以当我们想了解它的架构时。必需要先找出它对外提供的函式。而对的Windows的DLL来说,对外提供的函式,皆会以dllexport这个keyword来修饰。所以,不论是利用grep按或gtags之类的工具,我们能够非常快从原始码中。找到它仅仅有一个DLL的函式(这对我们而言,真是一个好消息) ,而这个函式便是上述的winampGetMediaLibraryPlugin 。

     

    系统多会採用同样的架构处理插件程式 

    假设经验不够的话,或许无法直接猜出这个函式的作用。 
    只是,假设你是个有经验的程式人,多半能从函式所回传的结构。猜出这个函式实际的用途。而其实。当你已经知道它是一个插件程式时。就应该要明确,它可能採用的,就是很多系统都採用的同样架构处理插件程式。 

    当一个系统採用所谓插件形式的架构时。它通常不会知道它的插件到底会怎么实作,实作什么功能。

    它仅仅会规范插件程式须要满足某个特定介面。当系统初始化时,全部的插件都能够依循同样的方式。向系统注冊,合法宣示自己的存在。

     

    尽管系统并不确切知道插件会有什么行为展现。可是由于它制定了一个标准的介面,所以系统仍然可以预期每一个插件可以处理的动作类型。这些动作详细上怎么运行。对系统来说并不重要。

    这也正是物件导向程式设计中的“多型”观念。

     

    随着实务经验,归纳常见的架构模式 

    我想表达的重点,是当你“涉世越深”之后,所接触的架构越多,就越能触类旁通。

    仅仅须要瞧上几眼,就能明确系统所用的架构,自然就行直接联想到当中可能存在的角色,以及角色间的关系。

     

    像上述的插件程式手法,时常可以在很多同意“外挂”程式码的系统中看到。

    所以,有经验的阅读者,多半可以马上反应,知道像这种系统的软件,应该是让每一个插件程式。都写成DLL的函式库。 

    而每一个插件的DLL的函式库中。都必须提供winampGetMediaLibraryPlugin ( )这个函式(假设你熟悉的Windows的程式设计,你会知道这是利用载入()和GetProcAddress ( )来达成的一种多型手法) 。假设你熟悉设计模式,你更会知道这是简单工厂方法这个设计模式的运用。 
    winampGetMediaLibraryPlugin ( )所回传的winampMediaLibraryPlugin结构,正好就描写叙述了每一个AOL的Winamp插件的实作内容。 

    善用名称可加速了解 

    利用gtags这个工具,我们马上发现,这个插件它所定义的初始化,退出, PluginMessageProc这三个名称,都是函式名称。

    这暗示在多型的作用下。它们都是在某些时间点,会由AOL的Winamp核心本体呼叫的函式。 

    名称及命名惯例是非常重要的。看到“ 初始化” 。我们会知道它的作用多半是进行初始化的动作,而“退出”大概就是结束时处理函式,而PluginMessageProc多半就是各种讯息的处理常式(过程一般是程序的简写,所以PluginMessageProc意指插件讯息程序)了。 

    “望文生义”非常重要,我们看到函式的名称。就能够猜想到它所代表的作用,比如:当AOL的Winamp尝试着初始化一个插件时。它会呼叫这个结构中的初始化函式。以便让每一个插件程式有机会初始化自己;当AOL的Winamp打算结束自己或结束某个插件的运行时。便会呼叫退出函式。当AOL的Winamp要和插件程式沟通时,它会发送各种不同的讯息至插件。而插件程式必须对此做出回应。 

    我们甚至不须要检视这几个函式的内容。就能够做出猜測,而这种如果。其实也是正确的。

    阅读他人的程式码( 5 ) 

    找到程式入口,再由上而下抽丝剥茧

    依据须要决定展开的层数。或展开特定节点。并记录树状结构,然后适度忽略不须要了解的细节─这是一个非常重要的态度。由于你不会一次就须要全部的细节,阅读都是有目的的,每次的阅读或许都在探索程式中不同的区域。

    探索系统架构的第一步。就是找到程式的入口点。

    找到入口点后,多半採取由上而下(自上而下)的方式,由最外层的结构。一层一层逐渐探索越来越多的细节。 
    我们的开发团队曾针对AOL的Winamp的iPod的插件进行阅读及探索,不仅找到入口点,也找出,并理解它最根本的基础架构。从这个入口点,能够往下再展开一层,分别找到三个重要的组成及其意义: 
    ●init ( ) :初始化动作 
    ●退出( ) :终止化动作 
    ● PluginMessageProc ( ) :以讯息的方式处理程式所必须处理的各种事件

    展开的同一时候。随手记录树状结构 

    当我们从一个入口点找到三个分支后。能够顺着每一个分支再展开一层,所以分别继续阅读的init ,退出,以及PluginMessageProc的内容。并试着再展开一层。阅读的同一时候。你能够在文件里试着记录展开的树状结构。 
    ●的init ( ) :初始化动作 
         ● itunesdb_init_cc ( ) :建立存取iTunes的数据库的同步物件 
         ●初始化资料结构 
         ●初始化的GUI元素 
         ●加载设定 
         ●建立日志档 
         ● autoDetectIpod ( ) :侦測的iPod插入的运行绪 
         ●退出( ) :终止化动作 
         ● itunesdb_del_cc ( ) :终止存取iTunes的数据库的同步物件 
         ●关闭日志档 
         ●终止化图形用户界面元素 
         ● PluginMessageProc ( ) :以讯息的方式处理程式所必须面临的各种事件
         ●运行所连接之苹果的MessageProc ( ) 

    这部分必需要留意几个重点。首先。应该一边阅读,一边记录文件。由于人的记忆力通常有限,对于陌生的事物更是easy遗忘,因此边阅读边记录,是非常好的辅助。 
    再者。由于我们採取由上而下的方式,从一个点再分支出去成为多个点。因此。通常也会以树状的方式记录。除此之外,每次仅仅试着往下探索一层。从的init ( )来看你便会明确。 

    下面试着摘要的init ( )的内容: 
    诠释的init ( ) ( 
    itunesdb_init_cc ( ) ; 
    currentiPod =空; 
    苹果=新C_ItemList ; 
    ...略 
    conf_file = (字符* ) SendMessage 
    ( plugin.hwndWinampParent 。 WM_WA_IPC , 0 。 IPC_GETINIFILE ) ; 
    m_treeview = GetDlgItem ( plugin.hwnd LibraryParent 。 0x3fd ) ; 
    / /这个数字实际上是魔术: ) 
    ...略 
    g_detectAll = GetPrivateProfileInt ( “ ml_ipod ” 。 “ detectAll ” , 0 。 conf_file ) !

    = 0 ; 
    ...略 
    g_log = GetPrivateProfileInt ( “ ml_ipod ” 。 “日志” , 0 , conf_file ) 。 = 0 ; 
    ...略 
    g_logfile =打开( g_logfilepath 。有“ A ” ) ; 
    ...略 
    autoDetectIpod ( ) ; 
    返回0 ; 
     

    由于我们仅仅试着多探索一层,而目的是希望发掘出下一层的子动作。

    所以在的init ( )中看到像“ itunesdb_init_cc ( ) ; ”这种函式呼叫动作时,我们知道它是在初始化( )之下的一个独立子动作。所以能够直接将它列入。可是当看到例如以下的程式行: 
    currentiPod =空; 
    苹果=新C_ItemList ; 

    我们并不会将它视为的init ( )下的一个独立的子动作。由于好几行程式。才构成一个具有独立抽象意义的子动作。比如以上这两行构成了一个独立的抽象意义。也就是初始化所需的资料结构。

     

    理论上。原来的程式撰写者。有可能撰写一个叫做init_data_structure ()的函式。包括这两行程式码。这样做可读性更高,然而基于种种理由。原作者并没有这么做。身为阅读者。必须自行解读,将这几行合并成单一个子动作。并赋予它一个独立的意义─ ─初始化资料结构。 

    无法望文生义的函式,先试着预看一层 

    对于某些不明作用的函式叫用。不是望其文便能生其义的。

    当我们看到“ itunesdb_init_cc ( ) ”这个名称时。我们也许能从“ itunesdb_init ”的字眼意识到这个函式和苹果所採用的的iTunes数据库的初始化有关,但“循环”却实在令人费解。为了理解这一层某个子动作的真实意义,有时免不了要往前多看一层。 

    原来它是用来初始化同步化机制用的物件。作用在于这程式一定是用了某个内部的资料结构来储存的iTunes数据库。而这资料结构有可能被多运行绪存取,所以必须以同步物件(此处是视窗的临界区)加以保护。 

    所以说,当我们试着以树状的方式,逐一展开每一个动作的子动作时,有时必须多看一层,才干真正了解子动作的意义。由于有了这种动作,我们能够在展开树状结构中,为 itunesdb_init_cc ()附上补充说明:建立存取iTunes的数据库的同步物件。

    这么一来。当我们在检视自己所写下的树状结构时,就能轻易一目了然的理解每一个子动作的真正作用。 

    依据须要了解的粒度,决定展开的层数 

    我们到底须要展开多少层呢?这个问题和阅读程式码时所需的“粒度(粒度) ”有关。假设我们仅仅是须要概括性的了解,那么或许展开两层或三层,就行对程式有基础的认识。倘若须要更深入的了解。就会须要展开很多其它的层次才行。

     

    有时候,你并非一视同仁地针对每一个动作,都展开到同样深度的层次。或许。你会基于特殊的需求,专门针对特定的动作展开至深层。比如。我们阅读AOL的Winamp的iPod插件的程式文件夹,事实上是想从中了解到底应该怎样存取的iPod上的iTunes的数据库,使我们可以将MP3播放歌曲或播放清单加至此数据库中,并于的iPod中播放。 

    当我们层层探索与分解之后,找到了parseIpodDb ( ) ,从函式名称推断它是我们想要的。

    由于它代表的正是剖析iPod的数据库,正是我们此次阅读的重点,也就达成阅读这程式码的目的。 

    我们强调一种不同的做法:在阅读程式码时,多半採取由上而下的方式。而本文建议了一种记录阅读的方式,就是试着记录探索追踪时层层展开的树状结构。

    你能够视自己须要,了解的深入程度。再决定要展开的层数。

    你更能够根据特殊的须要。仅仅展开某个特定的节点,以探索特定的细目。

     

    适度地忽略不须要了解的细节,是一个非常重要的态度,由于你不会一次就须要全部的细节,阅读都是有目的的。每次的阅读或许都在探索程式中不同的区域;而每次探索时。你都能够增补树状结构中的某个子结构。渐渐地,你就会对这个程式更加的了解。


    阅读他人的程式码( 6 ) 

    阅读的乐趣:透过程式码认识作者

    即便每一个人的写作模式多半受到他人的影响。程式人通常还是会融合多种风格,而成为自己独有的特色,假设你知道作者程式设计的偏好,阅读他的程式码就更得心应手。

    阅读程式码时。多半会採取由上而下,抽丝剥茧的方式。透过记录层层展开的树状结构。程式人能够逐步地建立起对系统的架构观,并且能够按照须要的粒度(粒度) 。决定展开的层次及精致程度。 

    建立架构观点的认识是最重要的事情。尽管这一系列的文章前提为“阅读他人的程式码” 。但我们真正想做的工作。并不在于彻底地详读每一行程式码的细节,而是想要透过重点式的程式码“摘读” ,达到对系统所需程度的了解。每一个人在阅读程式码的动机不尽同样,须要了解的程度也就有深浅的分别。仅仅有极为少数的情况下,你才会须要细读每一行程式码。 

    阅读程式码是新时代程式人必备的重要技能 

    这一系列的文章至此已近尾声。回想曾探讨的主题。我们首先研究了阅读程式码的动机。

    尤其在开放原始码的风气如此之盛的情况下,妥善利用开放原始码所提供的资源。不仅可以更快学习到新的技术,同一时候在原始码版权合适时,还可以直接利用现成的程式码。大幅地提高开发阶段的生产力。

    所以,阅读程式码俨然成为了新时代程式人必备的重要技能之中的一个。 
    接着。我们提到了阅读程式码前的必要准备。包含了对程式语言。命名惯例的了解等等。

    在此之后,我们反覆提起了“由上而下”的阅读方向的重要性。

     
    由上而下的阅读方式,是由于我们重视架构更胜于细节。

    从最外层的架构逐一向内探索。每往内探索一层,我们了解系统的粒度就添加了一个等级。

    当你识别出系统所用的架构时。便可以轻易了解在这个架构下会有的角色,以及它们之间的动态及静态的关系。如此一来,很多资讯便不言可喻,毋需额外花费力气。便可以高速理解。 

    好的名称可以摘要性地点出实体的作用 

    追踪原始码时,固然能够用本来的方式,利用编辑器开启所需的档案,然后利用编辑器提供的机制阅读。可是倘若能够善用工具,阅读程式码的效率及品质都能大大提升。在本系列文章中。我们介绍了一些工具。也许你还能够在坊间找到其它更实用的工具。 
    我在这一系列的文章中,实际带着大家阅读。追踪了一个名为ml_pod的开放原始码专案。

    它是一个AOL的Winamp的iPod的外挂程式。在追踪的过程中,我们试着印证这一系列文中所提到的观念及方法。我们採用逐渐开展的树状结构来记录追踪的过程,并借以建立起对系统的概观认识。 

    就原始码的阅读来说。之前的讨论涉及了工具面及技巧面。但另一些主题不在这两个范畴之内。比如。善用名称赋予你的提示。名称做为隐喻(隐喻)的作用非常大,好的名称可以摘要性地点出实体的作用,比如我们看到autoDetectIpod ( ) ,自然而然可以想像它的作用在于自己主动(自己主动)侦測(检測)的iPod的存在。 

    我们在展开树状结构时,有时候须要预看一层,有时却不须要这么做。便可得到印证。程式人都会有惯用的名称以及组合名称的方法。倘若能够从名称上理解,便毋需钻进细节,能够省去相当多的时间。比如,当我们看到parseIpodDb ( )时。便能够轻易了解它是剖析(解析)的iPod的资料库( DB )的。因此便不须要马上钻进parseIpodDb ( )中查看底细。 

    虽然如此,是否能理解程式人命名的用意。和自身的经验以及是否了解原作者的文化背景,是息息相关的。

     

    命名本身就是一种文化产物。不同的程式人文化,就会衍生出不同的命名文化。当你自己的经验丰富,看过及接触过的程式码也多时,对于名称的感受及联想的能力自然会有不同。 

    这样的感受和联想的能力,到底应该怎样精进,非常难详细描写叙述。就我个人的经验。多观察不同命名体系的差异,而且尝试归纳彼此之间的异同。有助于更快地提升对名称的感受及联想力。 

    转换立场,理解作者的思考方式 

    除了工具及技巧之外。 “想要阅读程式码,得先试着阅读写这个程式码的程式人的心。 ”这句话说来十分抽象。也许也令人难以理解。

     

    当你在阅读一段程式码时,也许能够试着转换自己的立场。从旁观者的角度转换成为写作者的心态,揣摩原作者的心理及处境。当你试着设身处地站在他的立场,透过他的思考方式来阅读,追踪他所写下的程式码,将会感觉更加流畅。 

    很多软体专案。都不是由单一程式人所独力完毕。

    因此。在这种专案中。便有可能呈现多种不同的风格。 

    很多专案会由架构师决定主体的架构及运作,有既定实施的命名惯例,及程式设计须要遵守方针。在多人开发的模式下。越是好的软体专案,越看不出某程式码片段到底是由谁所写下的。 

    只是,有些开放原始码的专案,往往又整合了其它开放原始码的专案。有的时候,也非常难求风格的统一,便会出现混杂的情况。好比之前提到的ml_pod专案,由于程式码中混合了不同的来源,而呈现风格不一致的情况。 

    我在阅读非自己所写的程式码时,会观察原作者写作的习惯。借以相应到脑中所记忆的多种写作模型。

    在阅读的过程中。读完几行程式码,我会试着猜想原作者在写下这段程式码时的心境。

    他写下这段程式码的用意是什么?为什么他会採取这种写法?顺着原作者的思考理路阅读,自己的思考才干更贴近对方写作当时的想法。 

    当你短暂化身为原作者时,才干更轻易的理解他所写下的程式码。 
    假设你能知道原作者的背景。程式设计时的偏好,阅读他的程式码,就更能得心应手了。 

    从程式码着手认识作者独有的风格,进而见贤思齐 

    我在阅读别人写下的程式码时。我会试着猜想。原作者到底是属于那一种“流派”呢?每一个人都有自己独特的写作模式,即便每一个人的写作模式多半受到他人的影响─ ─不论是书籍的作者,学习过程中的指导者,或一同參与专案的同侪。但每一个程式人一般会融合多种风格。而成为自己独有的风格。 

    物件导向的基本教义派,总是会以他心中认为最优雅的物件导向方式来撰写程式。

    而阅读惯用。善用设计模式的程式人所写下的程式码时,不难推想出他会在各种常见的应用情境下,套用哪些模式。

     

    有些时候,在阅读之初。你并不知道原作者的习性跟喜好,甚至你也不知道他的功力。可是,在阅读之后,你会慢慢地从一个程式人所写下的程式码,開始认识他。 

    你也许会在阅读他人的程式码时,发现令人拍案叫绝的技巧或设计。你也有可能在阅读的同一时候。发现原作者所留下的缺失或写作时的缺点,而暗自警惕于心。

    这也算是阅读他人程式码时的一项乐趣。 

    当你从视阅读他人的程式码为畏途,转变成为能够从中获取乐趣的时候,我想。你又进到了还有一个境地。

  • 相关阅读:
    rsync使用
    文件系统、mkdir、touch、nano、cp笔记
    man/ls/clock/date/echo笔记
    Python之路,Day2
    Python之路,Day1
    自动化部署nginx负载均衡及监控短信报警
    NO.11天作业
    Tiny C Compiler简介-wiki
    stm32中使用cubemx配置freertos的信号量大小
    c99的新功能
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/7297516.html
Copyright © 2011-2022 走看看