WebKit模块化分析

模块化

      软件的首要技术使命是管理复杂度(Complexity)。这是<<代码大全>>中的一个标题。软件本质性困难的根源都在于复杂性。Dijkstra指出没有谁的大脑能容得下一计算机程序。正如社会进步催生社会分工一样,软件行业也自然而然地发展出来了模块化方法,将整个系统分解为多个子系统来降低问题的复杂度,分而治之。它有两个主要的目的:

   1. 分工  (角色与责任)

   2. 信息隐藏 (协作)

      分工可以更容易实现并行开发,带来开发效率的提升。分工还可以隔离变化,使得软件应对变化的能力增强。而信息隐藏则降低了对程序员的要求,能够更好地掌握模块内的复杂度。

 

      另一种将模块化视为系列决策组合的看法,只关注到了模块化过程的形式,而忽视了模块化的目的和目标。决策固然是贯穿整个开发过程,但并不是专属于模块化。还是应当强调模块化背后的目的以及使用的方法,关注其产出的结果:模块+接口。

 

      模块是分工的结果,接口则由协作定义。

 

     为了强化模块的协作,上世纪80年代引入了契约式编程(也称为Design by Contract,或Contract Programming)的概念,要求清楚地定义功能的前置条件和输出结果。正像是商业活动中使用合同(contract)来约束和规范商业协作。

      凡事过犹不及,模块化的粒度多大才合适?在<<Unix编程艺术>>中第四章有专门一个章节进行探讨,可以作为一个参考。模块化并不能完全解决耦合和复用的问题,所以90年代末又有了将模块中分散着的各个相似的功能集中起来的设计实践,面向方面的编程(AOP, Aspect Oriented Programming)。其实也是为了将分散的逻辑抽取出来,模块化。

 

      这些都是对模块化的完善。

面向对象编程同模块化

      面向对象编程其实是一种能更好地支持模块化的编程方法,可以更好的达到了模块化的两个目标。在系统级别的模块划分上,并没引入什么特别的贡献。但在系统模块的内部,却较以前面向过程的开法更方便地实现了逻辑上的模块,更为重要的是它在信息隐藏上的突破。

     举例来说,以前在某个模块(组件)内部,以面向过程来写代码时,很容易产生出复杂的函数关系和无法约束数据访问的问题。而面向对象却使用不同的类或接口很好的在内部又建立区不同的逻辑模块来。不同的类对象之间的访问和存取都受到各自实现的约束,真正做到了信息隐藏。

 

     虽然有时面向对象的编程语言也被发展的过于庞大,其本身的复杂度也越来越高,特别是如果过于专注于分层而引入了更多的细节,反而增加了复杂度。语言本身是个工具,如何使用好这个工具最终取决于设计者。它既可以用来雕琢天使,也可以用来创造魔鬼。

  

模块化的设计方法

      如何进行模块化设计?要么从上到下(Top-Down),要么从下向上(Down-Up)。从上到下时常常导致领域问题和技术细节考虑不足,而从下至上的设计方法却有时却让整合出了问题。所以还需要有设计验证的工作和迭代设计的流程。

     从上至下,是从整个系统的层面入手,切分子模块。可以按业务功能分(垂直切分),如ERP分为财务、人事、制造/生产管理、采购、销售、资材管理(仓库)等。也可以按逻辑功能分(水平切分),如UI模块、业务逻辑模块、数据持久层、报表管理模块等。

 

     为了实现并行开发,模块间的接口必须被清楚明确的定义出来,包括接口函数和公共数据结构。关于接口的设计,也有很多计论。比如下文:

     设计Qt风格的C++API

     如何评估模块化的效果呢?Eric.Raymond推荐至少可以从两方面考察:紧凑性和正交性。紧凑性可以用来评估模块化的划分是否合适,而正交性则是关注在模块间的协作水平。为了提高紧凑性,就要减少方案中的特例和边界情况,尽量使用统一明确的规则来组织系统。

WebKit的模块化

    模块化的概念绝大多数人都知道,正如前面所说的,结果决定于设计者,而不是工具。向好的设计学习,是掌握模块化的好方法。

 

     后面将对目前使用最广的浏览器内核WebKit的模块化设计进行分析。

 

     Eric.Raymond在<<Unix编程艺术>>中讨论模块化时,多次提到浏览器设计,足可见浏览器设计的代表性。

 

     首先做个简要说明。我们常说的WebKit是浏览器内核,开发者只要在它上面加一个界面层就可以开发出一个浏览器。它的功能包括对网页的加载、解析、显示以及处理用户的交互。随着网页功能越来越复杂多样,浏览器内核的功能也是日益强大,其内部实现也是相当庞大。

 

     根据对Safari使用的WebKit进行统计(使用的是cloc.pl),其中有13354个源文件,共计1645695行代码,不含注释等非代码行的内容。面对这样的代码,还要不断升级来满足各种标准和网页的变化,以及用户的需求变化,如果没有良好的模块化,是根本无法完成的。

 

     面对庞大复杂的浏览器软件,对于浏览器框架首要任务是区隔出核心功能及非核心功能,什么是变化的,什么是相对稳定的。像许多大型的开源项目一样,WebKit的开发也深受Unix的开发文化的影响。于是"只将一件事做好"的格言就决定了WebKit不会去做浏览器,只是浏览器的核心引擎。展示网页内容就是WebKit的核心功能。

 

     关于浏览器设计,有一个简洁的概念模型:

     (来源: <<A Reference Architecture for Web Browsers>>,Alan Grosskurth& Michael W. Godfrey)

 

     这个模型展示出来浏览器设计的主要要素。其中用户界面面向的是用户交互,渲染引擎则面向对各种页面标准的支持,而中间的浏览器引擎处理用户的操作,并回馈网页上的交互,是用户界面与渲染引擎交互的桥梁。数据持久层则是用于存储网页会使用的如Cookies和缓存一类的数据。在最下面一层,则是为渲染引擎提供支持的功能模块。

 

     虽然WebKit并没有完全按这样来切分,但其思想是相似的。WebKit将上图中的绿色部分构成了WebKit的主要部分,形成自己的结构:

 

     UI部分可以自由定制,通过WebKit2调用WebCore的功能。WebCore可以通过封装的接口方便地选择JSC或V8做为它的JavaScript解释器。在整个项目中所使用到的公共数据结构和一些与平台无关的辅助性的功能都被放到了WTF中。

 

     我在下面主要探讨WebKit模块化的要点:

          1. 封装核心功能,与用户界面隔离。

          2. 提取公共的基础代码和数据结构,封装成库,供其它模块使用。

          3. 使用插件机制支持扩展需求。

          4. 为跨平台提供良好的支持。

          5. 持续改进。

 

应对变化 - 封装核心功能,与用户界面隔离

      变化无处不在,但基本上是万变不离其踪。软件中的变化可以归纳为用户需求变化和技术环境变化等。在技术上来说,应用软件中的变化体现在两个层面,一个是外部展现的变化,如界面操作、事件响应等,另一种则是运行策略上的变化,在不同的配置或环境下执行的路径不同。对于浏览器软件还有一项重要的环境引起的变化:标准的变化。比如现在正在发展的HTML5、CSS3等。

 

     WebKit2的应对之道就是分离原则:接口与引擎分离,策略与机制分离。(以下内容参考了WebKit官网的介绍:WebKit2 - High Level Document)

 

     首先在接口与引擎的分离上,WebKit2除了提供一组C的API外,还将用户进程与内核进程分隔开来,如下图所示。上面的UI Process包含了UI和WebKit2的UIProcess层,下面的WebProcess则包括了WebKit内核模块和WebKit2的WebProcess层。这样做可以带来更好的健壮性、安全性和性能。因为如此UI与内核的耦合降低了,提高了正交性,大大降低了系统的复杂度。

    

    (*源自WebKit官网)

 

     为了应对在外部显示的变化及运行策略上的变化,WebKit2提供的是非阻塞式的API,有丰富的回调机制。包括:

  • 消息通知式的客户端回调(Notification style client callbacks)。在适当的时机通知客户端但并给它机会去执行什么干预操作,仅仅是通知而已。
  • 策略式的客户端回调(Policy style clients callbacks)。允许由客户端决定某个行为。
  • 策略配置(Policy settings) 让在客户端就可以预先设定一些策略选项。如一些预设文本之类的内容。
  • 可注入的代码(Injected code)。允许在Web Process中加载并执行特定的代码。实现在Web Process中的InjectedBundle模块。

 

建构基础 - 提取公共的基础代码和数据结构,封装成库

     核心业务代码与非核心业务代码的分离,正是面向方面编程(AOP)的主要思想。在不运用AOP框架的情况下,也可以达到一定效果。关键在于识别并抽取出公共的数据结构及操作。

    如智能指针和内存分配器。WebKit中提供了丰富的智能指针操作来简化程序员的负担,而它们都被封装到了WTF中。

 

为了优化内存分配的性能,特别是小内存的分配和跨线程的安全性,WebKit使用了TCMaclloc来作为系统缺省内存分配器的替代者。使用宏定义就可以在它们之间切换。

     这些功能无论在WebCore,还是WebKit2都会被使用到。而它们也并不是核心业务,抽取出来,降低了核心业务部分的复杂度,好处不言自明。

 

 

灵活健壮 - 支持标准的插件机制

      在云计算快速发展的时代,浏览器也正朝着平台化的方向发展,不再只是一个单纯的浏览器。在其上可以构建出许许多多的基于网页的应用,被称为WebApp。通过Chrome的应用商店就可以窥见一斑(国内的360和猎豹因为使用的正是相同的内核,也就可以很容易共享这些应用)。

 

      Netscape为当时的Netscape Navigator定义了一套插件接口 (NAPI) ,现在由Mozilla维护,并被多个主流浏览器所采用。

 

     NAPI实现了在网页中插件一些特别的元素,并可以和其它DOM元素一样通过JavaScript Binding由用户脚本控制。比如常见的Adobe Flash播放插件,和PDF阅读插件。甚至可以和系统应用程序交互。

 

     每个插件由一个特定的MIME Type来指定,由浏览器内核在解析时根据需要加载对应的插件。WebKit实现的模块图如下:

    对于插件开发者而言,只要遵循NAPI的规范,就可以实现跨浏览器的支持。Plugin Process同时将插件的独立于内核之外。在需要时调用插件的服务就可以了。

 

    插件在模块化上的作用非常明显,宿主在需要动态调用或组合各个插件就可以实现强大的功能,而且保证他们各自的核心功能不受影响。比如著名的GIMP,它的图像处理函数几乎都是以插件的形式存在的。还有多媒体播放器,解码器也是以插件的形式组织的。插件机制的核心就是接口的定义,而协作决定接口。只有构造出简洁有效的协作方式,才能保证插件的优势。

 

      以下是NAPI相关的参考:

           1. NAPI Plug-in Basics

           2. Chromium - Plugin Architecture

 

兼容并蓄 - WebKit的跨平台方案

      跨平台是许多大型软件要解决的问题,其前提条件也是提炼出核心业务逻辑,保持它的一致性。而跨平台真正要变的是各种接口,对于浏览器来说主要是网络接口、图形接口、多媒体解等。

 

     WebKit的解决方案主要使用适配器模式。主要思想还是提取公共的业务逻辑封装在一个接口中,再由各个平台视需要实现不同的部分。

 

     以Image类为例,它的一个函数与平台相关,于是类的实现被放在了三个文件中Image.cpp, ImageMac.mm和ImageWin.cpp中。Image.cpp中实现了公共的部分,而ImageMac.mm中实现了Mac OS版本,而ImageWin.cpp则实现了Windows版本。如下图所示:

    

        *WebKit将所有与平台相关的代码,放在platform目录,结构清楚。

 

     还有另一种实现方式,如多媒体元素的播放控件。首先是MediaPlayer提供了一部分公共的逻辑,对于与平台相关实现的部分,定义了一个MediaPlayerPrivateInterface,各平台继承自这个接口实现各自的逻辑。

     

 

重构 - 不断追求卓越

 

     好的设计很难一步到位,一定需要不断的演化。WebKit的演化也体现在它的代码上。无论<<程序员修炼之道>>作者推荐的"Don't repeat yourself"和Unix中SPOT原则,都是在强调不要有重复的逻辑实现。

 

     比如前面提到的Plugin Process,最初是放在Web Process中实现的,后来来演化到和UI Process、Web Process使用共同的独立进程的做法。这个过程可能很多的决策,考虑不同的需求、复杂度控制等等同题。

 

WebKit的开发者也会面临这样的问题,比如下面这段注释就清楚表明了作者将残余的重复列为一个优化项:

    // FIXME: There is much code here that is duplicated in WebProcessMainMac.mm, 

    // we should add a shared base class where we can put everything.

      而且提到应当使用一个新的共享基类来完成这个工作,这也是模块化工作的一部分:分工,以降低复杂度。

 

总结

     最后再以<<Unix编程艺术>>中提炼出的KISS原则来做个总结。模块化设计利用模块化在上层得出一个简洁清晰的逻辑关系,使得在每个层级的复杂度都是可控的,将不必要的复杂性隐藏起来。Keep It Simple, Stupid!

 

转载请注明出处:http://blog.csdn.net/horkychen

参考: 温煜 <<软件架构设计>>

时间: 2024-08-03 23:08:56

WebKit模块化分析的相关文章

[WebKit]WebCore之页面加载的设计与实现(二)

从上次学习WebKit加载已经过去了大半年了,终于又有时间理一次加载流程.期望逐步完善细节,最后能有一个系统的总结. 首先可以这样理解WebKit的加载逻辑,涉及三个主要的组件, 其中HTTP stack为各个平台下使用的HTTP协议模块,WebCore Loaders则依据页面加载及解析过程对加载不同HTML Element的控制,Loading Controller则具体实现了资源加载的行为控制: 虽然从WebKit整体结构上这样理解不太严谨的,但单纯站在加载的角度来看,却有利于理解加载流程

[WebKit]WebKit2多进程机制的解析

在<<WebKit模块化分析>>中说到WebKit2中的多进程模型.多进程模型已经是浏览器的基本架构要素,下面展开分析一下WebKit2中的多进程模型. 协作决定接口,确立责任分工后,对于模块或系统间最重要的事莫过于接口定义,而且是有着简洁明确的定义.对于WebKit2中三个进程中的交互也是相当频繁和多样,如果使用传统的查表法对应解析执行,就会面临巨大的维护成本.WebKit2使用了Encoder和Decoder的概念的很好地将消息解析的工作放到各个功能上.于是提供的公共接口主要关

[WebKit]WebKit2 API解析

这里是对上一篇<<WebKit模块化分析>>的进一步展开.先从API层开始. API概览 主要类图 WebKit提供了灵活的回调机制用来支持客户端与内核的交互,在API中有一些Set Client类的函数,Client一般就是用于注册针对某一功能的回调函数. 如向WKContext注册history item处理的回调函数,就会使用下面这个结构(WKContext.h): struct WKContextHistoryClient {     int                

解耦设计手法小结

设计是一个平衡的产物,需要在各个约束条件下(组织目标,业务目标,开发流程,技术能力,学习及维护成本等)不断地进行演进. 我们虽然不提倡做大而全的设计,但会坚持进行基础性设计,以保证我们的设计一直在正确的方向上演进. 设计演进的过程既可以是自上而下的,也可以是自下而上的. 基本设计原则 业界普遍被接受的设计原则不再赘述.这里特别针对基于开源项目的软件,其总体主旋律将是:跟随,扩展,贡献,其中跟随将是一个基本能力,反观深度定制的方式会遭遇越来越多的尴尬.落实在设计上,其最核心的设计原则:隔离自有业务

XSS分析及预防(转)

阅读目录 XSS的种类和特点 XSS预防 总结 XSS(Cross Site Scripting),又称跨站脚本,XSS的重点不在于跨站点,而是在于脚本的执行.在WEB前端应用日益发展的今天,XSS漏洞尤其容易被开发人员忽视,最终可能造成对个人信息的泄漏.如今,仍然没有统一的方式来检测XSS漏洞,但是对于前端开发人员而言,仍是可以在某些细微处避免的,因此本文会结合笔者的学习和经验总结解决和避免的一些方案,并简要从webkit内核分析浏览器内核对于XSS避免所做的努力,了解底层基础设施对预防XSS

思科SAFE蓝图中科院海洋安全解决方案

就在5年前,黑客和病毒还是新鲜词.如果什么地方被黑客攻击,或者出现什么新的病毒,肯定会成为热炒的新闻.可现在,黑客和病毒好象就在我们身边,随时都有爆发的可能.网络安全成了我们每个人心中的隐患,中科院海洋所现在就面临着这样的问题. 中科院海洋研究所成立于1950年,是我国目前规模最大.综合实力最强的海洋科学研究机构,他们在海洋生物.海洋生态.海洋地质等很多领域做了大量的开创性和奠基性的工作.和科研领域的许多研究所一样,海洋所每天都要进行大量的内外信息交流,这些交流主要在网络上进行.在前些年,网络安

《深入理解Android》一第3章 WTF的实现及使用

第3章 WTF的实现及使用本章主要内容分析OwnPtr和RefPtr的实现及使用分析Assert与Android crash dump分析WTF内存管理及容器类分析原子操作介绍Android WebKit的运行结构第2章对WebKit的整体结构做了介绍,从宏观上勾画出了WebKit的轮廓,使读者据此对WebKit有了整体的了解.从本章起读者将与笔者一道拿起"手术刀",精确剖析WebKit的实现细节,对WebKit建立起具体的认识.对于WebKit的分析,起点一定是其C++基础库-WTF

运用大数据思维和手段提升政府治理能力

当今时代,数据已成为国家基础性战略资源,大数据正日益对全球经济运行机制.社会生活方式和国家治理能力产生重要影响.党中央.国务院高度重视大数据发展及创新应用,十八届五中全会明确提出实施国家大数据战略.国务院印发的<促进大数据发展行动纲要>指出,大数据已成为"提升政府治理能力的新途径".这就要求各级政府树立大数据思维,借助大数据手段推动政府管理理念和社会治理模式进步,实现国家治理体系和治理能力现代化. 一.大数据是政府提升治理能力的全新契机 大数据不仅将改变生产方式.生活方式,

JavaScript 模块化及 SeaJs 源码分析

网页的结构越来越复杂,简直可以看做一个简单APP,如果还像以前那样把所有的代码都放到一个文件里面会有一些问题: 全局变量互相影响 JavaScript文件变大,影响加载速度 结构混乱.很难维护 和后端(比如Java)比较就可以看出明显的差距.2009年Ryan Dahl创建了node.js项目,将JavaScript用于服务器编程,这标志"JS模块化编程"正式诞生. 基本原理 模块就是一些功能的集合,那么可以将一个大文件分割成一些小文件,在各个文件中定义不同的功能,然后在HTML中引入