iOS:你会如何设计一个框架,给别人使用!

         所谓定位就是回答几个问题,我出于什么目的要写一个框架,个人这个框架是干什么的,有什么特性适用于什么场景,个人这个框架的用户对象是谁,他们会怎么使用,框架由谁维护未来怎么发展等等前端

  1. 若是你打算写框架,那么确定内心已经有一个初步的定位,好比它是一个缓存框架、Web MVC框架、IOC框架、ORM/数据访问框架、RPC框架或是一个用于Web开发的全栈式框架。
  2. 是 否要重复造轮子?除非是练手项目,通常咱们是有了解决不了问题的时候才会考虑不使用既有的成熟的框架而重复造轮子的,这个时候须要列出新框架主要但愿解决 什么问题。有关是否应该重复造轮子的话题讨论了不少,个人建议是在把问题列清后进行简单的研究看看是否能够经过扩展示有的框架来解决这个问题。通常而言大 部分红熟的框架都有必定的扩展和内部组件的替换能力,能够解决大部分技术问题,但在以下状况下咱们可能不得不本身去写一个框架,好比即便经过扩展也没法满 足技术需求、安全缘由、须要更高的生产力、须要让框架和公司内部的流程更好地进行适配、开源的普适框架没法知足性能需求、二次开发的成本高于从新开发的成 本等等。
  3. 主打轻量级?轻量级是不少人打算本身写一个新框架的缘由,但咱们要明白,大部分项目在一开始的时候其实都是轻量级的,随着框架 的用户愈来愈多,它一定须要知足各类奇怪的需求,在通过了无数次迭代以后,框架的主线流程就会多不少扩展点、检测点,这样框架势必变得愈来愈重(从框架的 入口到框架的工做结束的方法调用层次愈来愈多,势必框架也就愈来愈慢),若是你打算把框架定位于一个轻量级的框架的话,那么在从此的迭代过程当中须要进行一 些权衡,在心中有坚决的轻量级的理念的同时不断作性能测试来确保框架的轻量,不然随着时间的发展框架可能会愈来愈重进而偏离了开始的定位。
  4. 特性?若是你打算写一个框架,而且只有轻量级这一个理由的话,你或许应该再为本身的框架想一些新特性,就像作一个产品同样,若是找不出两个以上的亮点,那么这个产品不太可能成功,好比你的新框架能够是一个零配置的框架,能够是一个前端开发也能用的后端框架。
  5. 其它?通常来讲框架是给程序员使用的,咱们要考虑框架使用的频度是怎么样的,这可能决定的框架的性能需求和稳定性需求。还有,须要考虑框架未来怎么发展,是但愿走开源路线仍是商业路线。固然,这些问题也能够留到框架有一个大体的结构后再去考虑。

咱们来为本文模拟一个场景,假设咱们以为现有的Spring MVC等框架开发起来效率有点低,打算重复造轮子,对于新框架的定位是一个给Java程序员使用的轻量级的、零配置的、易用的、易扩展的Web MVC框架。程序员

 

调研正则表达式

虽然到这里你已经决定去写一个框架了,可是在着手写以前仍是至少建议评估一下市面上的相似(成熟)框架。须要作的是通读这些框架的文档以及阅读一些源码,这么作有几个目的:数据库

  1. 经过分析现有框架的功能,能够制定出一个新框架要实现的功能列表。
  2. 经过分析现有框架的问题,总结出新框架须要避免的东西和改善的地方。
  3. 经过阅读现有框架的源码,帮助本身理清框架的主线流程为整体设计作铺垫(后面整体设计部分会更多谈到)。
  4. 若是能充分理解现有的框架,那么你就是站在巨人的肩膀上写框架,不然极可能就是在井底造轮子。

新 开发一个框架的好处是没有兼容历史版本的包袱,可是责任也一样重大,由于若是对于一开始的定位或设计工做没有作好的话,未来若是要对格局进行改变就会有巨 大的向前兼容的包袱(除非你的框架没有在任何正式项目中使用),兼容意味着框架可能会愈来愈重,可能会愈来愈难看,阅读至少一到两个开源实现,作好充分的 调研工做能够使你避免犯大错。编程

假设咱们评估了一些主流框架后已经很明确,咱们的MVC框架是一个Java平台的、基于Servlet的轻量级的Web MVC框架,主要的理念是约定优于配置,高内聚大于低耦合,提供主流Web MVC框架的大部分功能,而且易用方面有所创新,新特性体包括:json

  1. 起手零配置,整体上约定因为配置,即便须要扩展配置也支持经过代码和配置文件两种方式进行配置。
  2. 除了Servlet以外不依赖其它类库,支持经过插件方式和诸如Spring等框架进行整合。
  3. 更优化的项目结构,不须要按照传统的Java Web项目结构那样来分离代码和WEB-INF,视图能够和代码在一块儿,阅读代码更便利。
  4. 拦截器和框架自己更紧密,提供Action、Controller和Global三个级别的"拦截器"(或者说过滤器)。
  5. 丰富的Action的返回值,返回的能够是视图、能够是重定向、能够是文件、能够是字符串、能够是Json数据,能够是Javascript代码等等。
  6. 支持针对测试环境自动生成测试的视图模型数据,以便前端和后端能够同时开发项目。
  7. 支持在开发的时候自动生成路由信息、模型绑定、异常处理等配置的信息页面和调试页面,方便开发和调试。

提供一套通用的控件模版,使得,而且支持多种模版引擎,好比Jsp、Velocity、Freemarker、Mustache等等。后端

嗯,看上去挺诱人的,这是一个不错的开端,若是你要写的框架本身都不以为想用的话,那么别人就更不会有兴趣来尝试使用你的框架了。设计模式

 

解决难点缓存

之 因此把解决难点放在开搞以前是由于,若是实现这个框架的某些特性,甚至说实现这个框架的主流程有一些核心问题难以解决,那么就要考虑对框架的特性进行调 整,甚至取消框架的开发计划了。有的时候咱们在用A平台的时候发现一个很好用的框架,但愿把这个框架移植到B平台,这个想法是好的,但之因此在这之前这么 多年没有人这么干过是由于这个平台的限制压根不可能实现这样的东西。好比咱们要实现一个MVC框架,势必须要依赖平台提供的反射特性,若是你的语言平台压 根就没有运行时反射这个功能,那么这就是一个很是难以解决的难点。又好比咱们在某个平台实现一个相似于.NET平台Linq2Sql的数据访问框架,但如 果这个目标平台的开发语言并不像C#那样提供了类型推断、匿名类型、Lambda表达式、扩展方法的话那么因为语法的限制你写出来的框架在使用的时候是无 法像.NET平台Linq2Sql那样优雅的,这就违背了实现框架的主要目的,实现新的框架也就变得意义不大了。安全

对于咱们要实现的MVC框 架貌似不存在什么根本性的没法解决的问题,毕竟在Java平台已经有不少能够参考的例子了。若是框架的实现整体上没什么问题的话,就须要逐一评估框架的这 些新特性是否能够解决。建议对于每个难点特性作一个原型项目来证实可行,以避免在框架实现到一半的时候发现有没法解决的问题就比较尴尬了。

分析一下,貌似咱们要实现的这8大特性只有第1点要研究一下,看看如何免配置经过让代码方式让咱们的Web MVC框架能够和Servlet进行整合,若是没法实现的话,咱们可能就须要把第1点特性从零配置改成一分钟快速配置了。

 

开搞

首先须要给本身框架取一个名字,取名要考虑到易读、易写、易记,也须要尽可能避免和市面上其它产品的名字重复,还有就是最好不要起一个侮辱其它同类框架的名字以避免引发公愤。

若是未来打算把项目搞大的话,能够提早注册一下项目的相关域名,毕竟如今域名也便宜,避免到时候项目名和域名差距很大,或项目的.com或.org域名对应了一个什么不太和谐的网站这就尴尬了。

而后就是找一个地方来托管本身的代码,若是一开始不但愿公开代码的话,最好除了本地源代码仓库还有一个异地的仓库以避免磁盘损坏致使抱憾终身,固然若是不怕出丑的话也能够在起步的时候就使用Github等网站来托管本身的代码。

 

整体设计

对 于整体设计个人建议是一开始不必定须要写什么设计文档画什么类图,由于可能一开始的时候没法造成这么具体的概念,咱们能够直接从代码开始作第一步。框架的 使用者通常而言仍是开发人员,抛开框架的内在的实现不说,框架的API设计的好坏取决于两个方面。对于普通开发人员而言就是使用层面的API是否易于使 用,拿咱们的MVC框架举例来讲:

最基本的,搭建一个HelloWorld项目,声明一个Controller和Action,配置一个路由规则让Get方法的请求能够解析到这个Action,能够输出HelloWorld文字,怎么实现?

若是要实现从Cookie以及表单中获取相关数据绑定到Action的参数里面,怎么实现?

若是要配置一个Action在调用前须要判断权限,在调用后须要记录日志,怎么实现?

咱们这里说的API,它不必定全都是方法调用的API,广义上来讲咱们认为框架提供的接入层的使用均可以认为是API,因此上面的一些功能均可以认为是MVC框架的API。

框架除了提供基本的功能,还要提供必定程度的扩展功能,使得一些复杂的项目可以在某些方面对框架进行加强以适应各类需求,好比:

  1. 个人Action是否能够返回图片验证码?
  2. 个人Action的参数绑定是否能够从Memcached中获取数据?
  3. 若是出现异常,可否在开发的时候显示具体的错误信息,在正式环境显示友好的错误页面而且记录错误信息到数据库?

一 般而言若是要实现这样的功能就须要本身实现框架公开的一些类或接口,而后把本身的实现"注册"到框架中,让框架能够在某个时候去使用这些新的实现。这就需 要框架的设计者来考虑应该以怎么样的友好形式公开出去哪些内容,使得之后的扩展实如今自由度以及最少实现上的平衡,同时要兼顾外来的实现不破坏框架已有的 结构。

要想清楚这些不是一件容易的事情,因此在框架的设计阶段彻底能够使用从上到下的方式进行设计。也就是不去考虑框架怎么实现,而是以一 个使用者的身份来写一个框架的示例网站,API怎么简单怎么舒服就怎么设计,只从使用者的角度来考虑问题。对于相关用到的类,直接写一个空的类(能用接口 的尽可能用接口,你的目的只是经过编译而不是能运行起来),让程序能够经过编译就能够了。你能够从框架的普通使用开始写这样一个示例网站,而后再写各类扩展 应用,在此期间你可能会用到框架内部的20个类,这些类就是框架的接入类,在你的示例网站经过编译的那刹那,其实你已经实现了框架的接入层的设计。

这里值得一说的是API的设计蕴含了很是多的学问以及经验,要在目标平台设计一套合理易用的API首先须要对目标平台足够了解,每个平台都有一些约定俗成的规范,若是设计的API能符合这些规范那么开发人员会更容易接受这个框架,此外还有一些建议:

  1. 之 因此咱们把API的设计先行,而不是让框架的设计先行是由于这样咱们更容易设计出好用的API,做为框架的实现者,咱们每每会进行一些妥协,咱们可能会为 了在框架内部DRY而设计出一套丑陋的API让框架的使用者去作一些重复的工做;咱们也可能会由于想让框架变得更松耦合强迫框架的使用者去使用到框架的一 些内部API去初始化框架的组件。若是框架不是易用的,那么框架的内部设计的再合理又有什么意义?
  2. 尽可能少暴露一些框架内部的类名吧,对 于框架的使用者来讲,你的框架对他一点都不熟悉,若是要上手你的框架须要学习一到两个类尚可接受,若是要使用到十几个类会头晕脑胀的,即便你的框架有很是 多的功能以及配置,能够考虑提供一个入口类,好比建立一个ConfigCenter类做为入口,让使用者能够仅仅探索这个类即可对框架进行全部的配置。
  3. 一 个好的框架是可让使用者少犯错误的,框架的设计者务必要考虑到,框架的使用者没有这个业务来按照框架的最佳实践来作,因此在设计API的时候,若是你希 望API的使用者必定要按照某个方式来作的话,能够考虑设置一个简便的重载来加载默认的最合理的使用方式而不是要求使用者来为你的方法初始一些什么依赖, 同时也能够在API内部作一些检测,若是发现开发人员可能会犯错进行一些提示或抛出异常。好的框架无需过多的文档,它能够在开发人员用的时候告知它哪里错 了,最佳实践是什么,即使他们真的错了也能以默认的更合理的方式来弥补这个错误。
  4. 建议全部的API都有一套统一的规范,好比入口都叫XXXCenter或XXXManager,而不是叫XXXCenter、YYYManager和 ZZZService。API每每须要进行迭代和改良的,在首个版本中把好名字用掉也不必定是一个好办法,最好仍是给本身的框架各类API的名字留一点余 地,这样之后万一须要升级换代不至于太牵强。

下一步工做就是把项目中那些空的类按照功能进行划分。目的很简单,就是让你的框架 的100个类或接口可以按照功能进行拆分和归类,这样别人一打开你的框架就能够立刻知道你的框架分为哪几个主要部分,而不是在100个类中晕眩;还有由于 一旦在你的框架有使用者后你再要为API相关的那些类调整包就比困难了,即便你在建立框架的时候以为个人框架就那么十几个类无需进行过多的分类,可是在将 来框架变大又发现当初设计的不合理,没法进行结构调整就会变得很痛苦。所以这个工做仍是至关重要的,对于大多数框架来讲,能够有几种切蛋糕的方式:

  1. 分 层。我以为框架和应用程序同样,也须要进行分层。传统的应用程序咱们分为表现层、逻辑层和数据访问层,相似的对于不少框架也能够进行横向的层次划分。要分 层的缘由是咱们的框架要处理的问题是基于多层抽象的,就像若是没有OSI七层模型,要让一个HTTP应用去直接处理网络信号是不合理的也是不利于重用的。 举一个例子,若是咱们要写一个基于Socket的RPC的框架,咱们须要处理方法的代理以及序列化,以及序列化数据的传输,这彻底是两个层面的问题,前者 偏向于应用层,后者偏向于网络层,咱们彻底有理由把咱们的框架分为两个层面的项目(至少是两个包),rpc.core和rpc.socket,前者不关心 网络实现来处理全部RPC的功能,后者不关心RPC来处理全部的Socket功能,在未来即便咱们要淘汰咱们的RPC的协议了,咱们也能够重用 rpc.socket项目,由于它和RPC的实现没有任何关系,它关注的只是socket层面的东西。
  2. 横切。刚才说的分层是横向的分 割,横切是纵向的分割(横切是跨多个模块的意思,不是横向来切的意思)。其实横切关注点就是诸如日志、配置、缓存、AOP、IOC等通用的功能,对于这部 分功能,咱们不该该把他们和真正的业务逻辑混淆在一块儿。对于应用类项目是这样,对于框架类项目也是这样,若是某一部分的代码量很是大,彻底有理由为它分出 一个单独的包。对于RPC项目,咱们可能就会把客户端和服务端通信的消息放在common包内,把配置的处理单独放在config包内。
  3. 功能。也就是要实现一个框架主要解决的问题点,好比对于上面提到的RPC框架的core部分,能够想到的是咱们主要解决是客户端如何找到服务端,如何把进 行方法调用以及把方法的调用信息传给目标服务端,服务端如何接受到这样的信息根据配置在本地实例化对象调用方法后把结果返回客户端三大问题,那么咱们可能 会把项目分为routing、client、server等几个包。

若是是一个RPC框架,大概是这样的结构:

你知道如何写一个框架吗?详细步骤大放送

对于咱们的Web MVC框架,举例以下:

  1. 咱们能够有一个mvc.core项目,细分以下的包:
  2. common:公共的一组件,下面的各模块都会用到
  3. config:配置模块,解决框架的配置问题
  4. startup:启动模块,解决框架和Servlet如何进行整合的问题
  5. plugin:插件模块,插件机制的实现,提供IPlugin的抽象实现
  6. routing:路由模块,解决请求路径的解析问题,提供了IRoute的抽象实现和基本实现
  7. controller:控制器模块,解决的是如何产生控制器
  8. model:视图模型模块,解决的是如何绑定方法的参数
  9. action:action模块,解决的是如何调用方法以及方法返回的结果,提供了IActionResult的抽象实现和基本实现
  10. view:视图模块,解决的是各类视图引擎和框架的适配
  11. filter:过滤器模块,解决是执行Action,返回IActionResult先后的AOP功能,提供了IFilter的抽象实现以及基本实现
  12. 咱们能够再建立一个mvc.extension项目,细分以下的包:
  13. filters:一些IFilter的实现
  14. results:一些IActionResult的实现
  15. routes:一些IRoute的实现
  16. plugins:一些IPlugin的实现

这里咱们以IXXX来描述一个抽象,能够是接口也能够是抽象类,在具体实现的时候根据需求再来肯定。

你知道如何写一个框架吗?详细步骤大放送

这 种结构的划分方式彻底吻合上面说的切蛋糕方式,能够看到除了横切部分和分层部分,做为一个Web MVC框架,它核心的组件就是routing、model、view、controller、action(固然,对于有些MVC框架它没有route部 分,route部分是交由Web框架实现的)。

若是咱们在这个时候还没法肯定框架的模块划分的话,问题也不大,咱们能够在后续的搭建龙骨的步骤中随着更多的类的创建,继续理清和肯定模块的划分。

通过了设计的步骤,咱们应该内心对下面的问题有一个初步的规划了:

  • 咱们的框架以什么形式来提供如何优雅的API?
  • 咱们的框架包含哪些模块,模块大概的做用是什么?

 

搭建龙骨 

在 通过了初步的设计以后,咱们能够考虑为框架搭建一套龙骨,一套抽象的层次关系。也就是用抽象类、接口或空的类实现框架,能够经过编译,让框架撑起来,就像 造房子搭建房子的钢筋混凝土结构(添砖加瓦是后面的事情,咱们先要有一个结构)。对于开发应用程序来讲,其实没有什么撑起来一说,由于应用程序中不少模块 都是并行的,它可能并无一个主结构,主流程,而对于框架来讲,它每每是一个高度面向对象的,高度抽象的一套程序,搭建龙骨也就是搭建一套抽象层。这么说 可能有点抽象,咱们仍是来想一下若是要作一个Web MVC框架,须要怎么为上面说的几个核心模块进行抽象(咱们也来体会一下框架中一些类的命名,这里咱们为了更清晰,为全部接口都命名为IXXX,这点不太 符合Java的命名规范):

  1. routing MVC的入口是路由
  2. 每个路由都是IRoute表明了不一样的路由实现,它也提供一个getRouteResult()方法来返回RouteResult对象
  3. 咱们实现一个框架自带的DefaultRoute,使得路由支持配置,支持默认值,支持正则表达式,支持约束等等
  4. 咱们须要有一个Routes类来管理全部的路由IRoute,提供一个findRoute()方法来返回RouteResult对象,天然咱们这边调用的就是IRoute的getRouteResult()方法,返回能匹配到的结果
  5. RouteResult对象就是匹配的路由信息,包含了路由解析后的全部数据
  6. controller 路由下来是控制器
  7. 咱们有IControllerFactory来建立Controller,提供createController()方法来返回IController
  8. IController表明控制器,提供一个execute()方法来执行控制器
  9. 咱们实现一个框架自带的DefaultControllerFactory来以约定因为配置的方式根据约定规则以及路由数据RouteResult来找到IController并建立它
  10. 我 们为IController提供一个抽象实现,AbstractController,要求全部MVC框架的使用者建立的控制器须要继承 AbstractController,在这个抽象实现中咱们能够编写一些便捷的API以便开发人员使用,好比view()方法、file()方法、 redirect()方法、json()方法、js()方法等等
  11. action 找到了控制器后就是来找要执行的方法了
  12. 咱们有IActionResult来表明Action返回的结果,提供一个execute()方法来执行这个结果
  13. 咱们的框架须要实现一些自带的IActionResult,好比ContentResult、ViewResult、FileResult、JsonResult、RedirectResult来对应AbstractController的一些便捷方法
  14. 再来定义一个IActionInvoker来执行Action,提供一个invokeAction()方法
  15. 咱们须要实现一个DefaultActionInvoker以默认的方式进行方法的调用,也就是找到方法的一些IFilter按照必定的顺序执行他们,最后使用反射进行方法的调用获得上面说的IActionResult并执行它的execute()方法
  16. filter 咱们的框架很重要的一点就是便捷的过滤器
  17. 刚才提到了IFilter,表明的是一个过滤器,咱们提供IActionFilter对方法的执行先后进行过滤,提供IResultFilter对IActionResult执行先后进行过滤
  18. 咱们的IActionInvoker怎么找到须要执行的IFilter呢,咱们须要定义一个IFilterProvider来提供过滤器,它提供一个getFilters()方法来提供全部的IFilter的实例
  19. 我 们的框架能够实现一些自带的IFilterProvider,好比AnnotationFilterProvider经过扫描Action或 Controller上的注解来获取须要执行的过滤器信息;好比咱们还能够实现GlobalFilterProvider,开发人员能够直接经过配置或代 码方式告知框架应用于全局的IFilter
  20. 既然咱们实现了多个IFilterProvider,咱们天然须要有一个类来管理这些IFilterProvider,咱们实现一个FilterProviders类并提供getFilters()方法(这和咱们的Routes类来管理IRoute是相似的,命名统一)
  21. view 各类IActionResult中最特殊最复杂的就是ViewResult,咱们须要有一个单独的包来处理ViewResult的逻辑
  22. 咱们须要有IViewEngine来表明一个模版引擎,提供一个getViewEngineResult()方法返回ViewEngineResult
  23. ViewEngineResult包含视图引擎寻找视图的结果信息,里面包含IView和寻找的一些路径等
  24. IView天然表明的是一个视图,提供render()方法(或者为了统一也能够叫作execute)来渲染视图
  25. 我 们的框架能够实现常见的一些模版引擎,好比FreemarkerViewEngine、VelocityViewEngine 等,VelocityViewEngine返回的ViewEngineResult天然包含的是一个实现IView的VelocityView,不会返回 其它引擎的IView
  26. 一样的,咱们是否是须要一个ViewEngines来管理全部的IViewEngine呢,一样也是实现findViewEngine()方法
  27. common 这里能够放一些项目中各个模块都要用到的一些东西
  28. 比 如各类context,context表明的是执行某个任务须要的环境信息,这里咱们能够定义HttpContext、 ControllerContext、ActionContext和ViewContext,后者继承前者,随着MVC处理流程的进行,View执行时的 上下文相比Action执行时的上下文信息确定是多了视图的信息,其它同理,之因此把这个信息放在common里面而不是放在各个模块本身的包内是由于这 样更清晰,能够一目了然各类对象的执行上下文有一个立体的概念
  29. 好比各类helper或utility

接下去就再也不详细阐述model、plugin等模块的内容了。

看到这里,咱们来总结一下,咱们的MVC框架在组织结构上有着高度的统一:

  • 若是xxx自己并没有选择策略,但xxx的建立过程也不是一个new这么简单的,能够由xxxFactory类来提供一个xxx
  • 若是咱们须要用到不少个yyy,那么咱们会有各类yyyProvider(经过getyyy()方法)来提供这些yyy,而且咱们须要有一个yyyProviders来管理这些yyyProvider
  • 若是zzz的选择是有策略性的,会按照须要选择zzz1或zzzN,那么咱们可能会有一个zzzs来管理这些zzz而且(经过findzzz()方法)来提供合适的zzz

同 时咱们框架的相关类的命名也是很是统一的,能够一眼看出这是实现、仍是抽象类仍是接口;是提供程序,是执行结果仍是上下文。固然,在未来的代码实现过程当中 极可能会把不少接口变为抽象类提供一些默认的实现,这并不会影响项目的主结构。咱们会在模式篇对框架经常使用的一些高层设计模式作更多的介绍。

到了这里,咱们的项目里已经有几十个空的(抽象)类、接口了,其中也定义了各类方法能够把各个模块串起来(各类find()方法和execute()方法),能够说整个项目的龙骨已经创建起来了,这种感受很好,由于咱们内心颇有底,咱们只须要在接下去的工做中作两个事情:

  • 实现各类DefaultXXX来走通主流程
  • 实现各类IyyyProvider和Izzz接口来完善支线流程

 

走通主线流程

所谓走通主线流程,就是让这个框架能够以一个HelloWorld形式跑起来,这就须要把几个核心类的核心方法使用最简单的方式进行实现,仍是拿咱们的MVC框架来举例子:

  • 从startup开始,可能须要实现ServletContextListener来动态注册咱们框架的入口Servlet,暂且起名为DispatcherServlet吧,在这个类中咱们须要走一下主线流程
  • 调用Routes.findRoute()得到IRoute
  • 调用IRoute.getRouteResult()来得到RouteResult
  • 使用拿到的RouteResult做为参数调用DefaultControllerFactory.createController()得到IController(其实也是AbstractController)
  • 调用IController.execute()
  • 在 config中建立一个IConfig做为一种配置方式,咱们实现一个DefaultConfig,把各类默认实现注册到框架中去,也就是 DefaultRoute、DefaultControllerFactory、DefaultActionInvoker,而后把各类 IViewEngine加入ViewEngines
  • 而后须要完成相关默认类的实现:
  • 实现Routes.findRoute()
  • 实现DefaultRoute.getRouteResult()
  • 实现DefaultControllerFactory.createController()
  • 实现AbstractController.execute()
  • 实现DefaultActionInvoker.invokeAction()
  • 实现ViewResult.execute()
  • 实现ViewEngines.findViewEngine()
  • 实现VelocityViewEngine.getViewEngineResult()
  • 实现VelocityView.render()

在这一步,咱们并不必定要去触碰filter和model这部分的内容,咱们的主线流程只是解析路由,得到控制器,执行方法,找到视图而后渲染视图。过滤器和视图模型的绑定属于加强型的功能,属于支线流程,不属于主线流程。

虽 然在这里咱们说了一些MVC的实现,但本文的目的不在于教你实现一个MVC框架,因此不用深究每个类的实现细节,这里想说的是,在前面的龙骨搭建完后, 你会发现按照这个龙骨为它加一点肉上去实现主要的流程是瓜熟蒂落的事情,毫无痛苦。在整个实现的过程当中,你能够不断完善common下的一些 context,把方法的调用参数封装到上下文对象中去,不但看起来清楚且符合开闭原则。到这里,咱们应该能够跑起来在设计阶段作的那个示例网站的 HelloWorld功能了。

在这里还想说一点,有些人在实现框架的时候并无搭建龙骨的一步骤,直接以非OOP的方式实现了主线流程,这种方式有如下几个缺点:

不容易作到SRP单一指责原则,你很容易把各类逻辑都集中写在一块儿,好比大量的逻辑直接写到了DispatcherServlet中,辅助一些Service或Helper,整个框架就肥瘦不匀,有些类特别庞大有些类特别小。

不容易作到OCP开闭原则,扩展起来不方便须要修改老的代码,咱们指望的扩展是实现新的类而后让框架感知,而不是直接修改框架的某些代码来加强功能。

很难实现DIP依赖倒置原则,即便你依赖的确实是IService但其实就没意义,由于它只有一个实现,只是把他看成帮助类来用罢了。

 

实现各类支线流程

咱们想一下,对于这个MVC框架有哪些没有实现的支线流程?其实无需多思考,由于咱们在搭建龙骨阶段的设计已经给了咱们明确的方向了,咱们只须要把除了主线以外的那些龙骨上也填充一些实体便可,好比:

  1. 实现更多的IRoute,并注册到Routes
  2. 实现更多的IViewEngine,并注册到ViewEngines
  3. 实现必要的IFilterProvider以及FilterProviders,把IFilterProvider注册到FilterProviders
  4. 加强DefaultActionInvoker.invokeAction()方法,在合适的时候调用这些IFilter
  5. 实现更多的IActionResult,而且为AbstractController实现更多的便捷方法来返回这些IActionResult
  6. ……实现更多model模块的内容和plugin模块的内容

实现了这一步后,你会发现整个框架饱满起来了,每个包中再也不是仅有的那些接口和默认实现,并且会有一种OOP的爽快感,爽快感来源于几个方面:

  1. 面对接口编程抽象和多态的放心安心的爽快感
  2. 为抽象类实现具体类享受到父类大量实现的知足的爽快感
  3. 实现了大量的接口和抽象类后充实的爽快感

咱们再来总结一下以前说的那些内容,实现一个框架的第一大步就是:

  1. 设计一套合理的接口
  2. 为框架进行模块划分
  3. 为框架搭建由抽象结构构成的骨架
  4. 在这个骨架的基础上实现一个HelloWorld程序
  5. 为这个骨架的其它部分填充更多实现

经 过这样的一些步骤后能够发现这个框架是很稳固的,很平衡的,很易于扩展的。其实到这里不少人以为框架已经完成了,有血有肉,其实我的以为只能说开发工做实 现了差很少30%,后文会继续说,毕竟直接把这样一个血肉之躯拿出去对外有点吓人,咱们须要为它进行不少包装和完善。

 

单元测试 

在这以前咱们写的框架只能说是一个在最基本的状况下能够使用的框架,做为一个框架咱们没法预测开发人员未来会怎么使用它,因此咱们须要作大量的工做来确保框架不但各类功能都是正确的,并且仍是健壮的。写应用系统的代码,大多数项目是不会去写单元测试的,缘由不少:

  • 项目赶时间,连作一些输入验证都没时间搞,哪里有时间写测试代码。
  • 项目对各项功能的质量要求不高,只要能在标准的操做流程下功能可用便可。
  • 项目基本不会去改或是临时项目,一旦测试经过以后就始终是这样子了,没有迭代。
  • ……

对于框架,偏偏相反,没有配套的单元测试的框架(也就是仅仅使用人工的方式进行测试,好比在main中调用一些方法观察日志或输出,或者运行一下示例项目查看各类功能是否正常,是很是可怕的)缘由以下:

  1. 自动化程度高,回归须要的时间短,甚至能够整合到构建过程当中进行,这是人工测试没法实现的。
  2. 框架必定是有很是多的迭代和重构的, 每一次修改虽然只改了A功能,可是可能会影响到B和C功能,人工测试的话你可能只会验证A是否正常,容易忽略B和C,使用单元测试的话只要全部功能都有覆盖,那么几乎不可能遗漏由于修改致使的潜在问题,并且还能反馈出来由于修改致使的兼容性问题。
  3. 以前说过,一旦框架开放出去,框架的使用者可能会以各类方式在各类环境来使用你的框架,环境不一样会形成不少怪异的边界输入或非法输入,须要使用单元测试对代码进行严格的边界测试,以确保框架能够在严酷的环境下生存。
  4. 单元测试还能帮助咱们改善设计,在写单元测试的时候若是发现目标代码很是难以进行模拟难以构建有效的单元测试,那么说明目标代码可能有强依赖或职责过于复杂,一个被单元测试高度覆盖的框架每每是设计精良的,符合高内聚低耦合的框架。

若是框架的时间需求不是特别紧的话,单元测试的引入能够是走通主线流程的阶段就引入,越早引入框架的成熟度可能就会越高,之后重构返工的机会会越小,框架的可靠性也确定会大幅提升。以前我有写过一个类库项目,并无写单元测试,在项目中使用了这个类库一段时间也没有出现任何问题,后来花了一点时间为类库写了单元测试,出乎我意料以外的是,个人类库提供的全部API中有超过一半是没法经过单元测试的(原觉得这是一个成熟的类库,其实包含了数十个BUG),甚至其中有一个API是在个人项目中使用的。你可能会问,为何在使用这个API的时候没有发生问题而在单元测试的时候发生问题了呢?缘由以前提到过,我是框架的设计者,我在使用类库提供的API的时候是知道使用的最佳实践的,所以我在使用的时候为类库进行了一个特别的设置,这个问题若是不是经过单元测试暴露的话,那么其它人在使用这个类库的时候基本都会遇到一个潜在的BUG。

示范项目

写一个示例项目不只仅是为了给别人参考,并且还可以帮助本身去完善框架,对于示例项目,最好兼顾下面几点:

  1. 是一个具备必定意义的网站或系统,而不是纯粹为了演示特性而演示。这是由于,不少时候只有那些真正的业务逻辑才会暴露出问题,演示特性的时候咱们老是有一些定势思惟会规避不少问题。或者能够提供两个项目,一个纯粹演示特性,一个是示例项目。
  2. 覆盖尽量多的特性或使用难点,在项目的代码中提供一些注释,不少开发人员不喜欢阅读文档,反而喜欢看一下示例项目直接上手(模仿示例项目,或直接拿示例项目中的代码来修改)。
  3. 项目中的代码,特别是涉及到框架使用的代码必定要规范,缘由上面也说了,做为框架的设计者你不会但愿你们复制的代码粘帖的代码一团糟吧。
  4. 若是你的项目针对的不只仅是Web项目,那么示例项目最好提供Web和桌面两个版本,一来你本身容易发现由于环境不一样带来的使用差别,二来能够给予不一样类型项目不一样的最佳实践。

 

完善日志和异常

一个好的框架不但须要设计精良,日志和异常的处理是否到位也是很是重要的标准,这里有一些反例:

  1. 日志的各类级别的使用没有统一的标准,甚至是永远只使用某个级别的日志。
  2. 几乎没有任何的日志,框架的运行彻底是一个黑盒。
  3. 记录的日志多且没有实际含义,只是调试的时候用来观察变量的内容。
  4. 异常类型只使用Exception,不使用更具体化的类型,没有自定义类型。
  5. 异常的消息文本只写"错误"字样,不写清楚具体的问题所在。
  6. 永远只是抛出异常,让异常上升到最外层,交给框架的使用者去处理。
  7. 用异常来控制代码流程,或本应该在方法未达到预期效果的时候使用异常却使用返回值。

其实我的以为,一个框架的主逻辑代码并不必定是最难的,最难的是对一些细节的处理,让框架保持一套规范的统一的日志和异常的使用反而对框架开发者来讲是一个难点,下面是针对记录日志的一些建议:

一、首先要对框架使用的日志级别有一个规范,好比定义:

  1. DEBUG:用于观察程序的运行流程,仅在调试的时候开启
  2. INFO:用于告知程序运行状态或阶段的变化,能够在测试环境开启
  3. WARNING:用于告知程序能够本身恢复的错误或异常,或不影响主线流程执行的错误或问题,能够在正式环境开启
  4. ERROR:用于告知程序没法恢复,主线流程中断,须要开发或运维人员知晓干预的错误或异常,须要在正式环境开启

二、按照上面的级别规范,在须要记录日志的地方记录日志,除了DEBUG级别的日志其它日志不能记录过多,若是框架老是在运行的时候输出几十个WARNNING也容易让使用者忽略真正的问题。

三、日志记录的消息须要是明确的,最好包含一些上下文信息,好比"没法在xxx下找到配置文件xxx.config,框架将采用默认的配置",而不是"加载配置失败!"

下面是一些针对使用异常的建议:

  1. 框架因为配置错误或使用错误或运行错误,不能完成API名字所表示的功能,考虑抛出转化后的异常,让调用者知道发什么了什么状况,同时框架能够创建本身的错误处理机制
  2. 对于能够预料的错误,而且错误类型能够枚举,考虑以返回值的形式告知调用者能够根据不一样的结果来处理后续的逻辑
  3. 对于框架内部功能实现上遇到的调用者无能力解决的错误,若是错误能够重试或不影响返回,能够记录警告或错误日志
  4. 能够为每个模块都陪伴自定义的异常类型,包含相关的上下文信息(好比ViewException能够包含ViewContext),这样出现异常能够很方便知晓是哪一个模块出现问题而且能够获得出现异常时的环境信息
  5. 若是异常跨了实现层次(好比从框架到应用),那么最好进行一下包装转换(好比把文件读取失败的提示改成加载配置文件失败的提示),不然上层人员是不知道怎么处理这些内部问题的,内部问题须要由框架本身来处理
  6. 异常的日志中能够记录和当前操做密切相关的参数信息,好比搜索的路径,视图名等等,有关方法的信息不用过多记录,异常通常都带有调用栈信息
  7. 若是可能的话,出现异常的时候能够分析一下为何会出现这样的问题,在异常信息中给一些解决问题的建议或帮助连接方便使用者排查问题
  8. 异常处理从坏到好的层次是,出现了严重问题的时候:
  9. 使用者什么都不知道,程序的完整性和逻辑获得破坏
  10. 使用者既不知道出现了什么问题也不知道怎么去解决
  11. 使用者能明确知道出现了什么问题,但没法去解决
  12. 使用者不但知道发生了什么,还能经过异常消息的引导快速解决问题

 

完善配置

配置的部分能够留到框架写的差很少了再去写,由于这个时候已经能够想清楚哪些配置是:

  1. 须要公开出去给使用者配置的,而且配置会根据环境不一样而不一样
  2. 须要公开出去给使用者来配置的,配置和部署环境无关
  3. 仅仅须要在框架内供框架开发人员来配置的
  4. 无需是一个配置,只要在代码中集中存储这个设定便可

通常来讲配置有几种方式:

  1. 经过配置文件来配置,好比XML文件、JSON文件或property文件
  2. 经过注解或特性(Annotation/Attribute)方式(对类、方法、参数)进行配置
  3. 经过代码方式进行配置(好比单独的配置类,或实现配置类或调用框架的配置API)

不少框架提供了多种配置方式,好比Spring MVC同时支持上面三种方式的配置,我的以为对配置,咱们仍是应该区别对待,而不是无脑把全部的配置项都同时以上面三种方式提供配置,咱们要考虑高内聚和低耦合原则,对于Web框架来讲,高内聚须要考虑的比低耦合更多,个人建议是对不一样的配置项提供不一样的配置方式:

  1. 若是配置项目是须要让使用者来配置的,特别是和环境相关的,那么最好使用配置方式来配置,好比开放的端口、内存、线程数配置,不过要注意:
  2. 全部配置项目须要有默认值,若是找不到配置使用默认值,若是配置不合理使用默认值(你不会但愿使用你框架的人把框架内部的线程池的min设置为999999,或定时器的间隔设置为0毫秒吧?)
  3. 框架启动的时候检测全部配置,若是不合理给予提示,大多人只会在启动的时候看一下日志,使用的时候根本就无论
  4. 不知道你们对于配置文件的格式倾向于XML呢仍是JSON呢仍是键值对呢?
  5. 对于全部仅在开发时进行的配置,都尽可能不要去使用配置文件,而且让配置尽可能和它所配置的对象靠在一块儿:
  6. 若是是对框架总体性进行的设置扩展类型的配置,那就能够提供代码方式进行配置,好比咱们要实现的MVC框架的各类IRoute、IViewEngine等,最好能够提供IConfig接口让开发人员能够去实现接口,这样他们能够知道有哪些东西能够配置,代码就是文档
  7. 若是是那种对模型、Action进行的配置,好比模型的验证规则、Filter等一概采用注解的方式进行配置
  8. 有的人说使用配置文件进行配置很是灵活,使用代码方式和注解方式来配置不灵活并且可能有侵入性。我以为仍是要权衡对待,个人建议是不要把太多框架内在的东西放在配置文件中,增长使用者的难度(并且不少时候,大多数人只是复制配置为了完成配置而配置,并非为了真正的灵活性而去使用配置文件来配置你的框架,看看网上这么所SSH配置文件的抄来抄去就知道了)。
  9. 最后,我建议不少太内部的东西对于轻量级的应用型框架能够不去提供任何配置选项,只须要在某个常量文件中定义便可,让真正有需求进行二次开发的开发人员去修改,对于一个框架若是一会儿暴露上百个"高级"配置项给使用者,他们会晕眩的。

 

提供状态服务

所谓状态服务就是反映框架内部运做状态的服务,不少开源服务或系统(Nginx、Mongodb等)都提供了相似的模块和功能,做为框架的话我以为也有必要提供一些内部信息(主要是配置、数据统计以及内部资源状态)出来,这样使用你框架的人能够在开发的时候或线上运做的时候了解框架的运做状态,咱们举两个例子,对于一个咱们以前提到的Web MVC框架来讲,能够提供这些信息:

  1. 路由配置
  2. 视图引擎配置
  3. 过滤器配置

对于一个Socket框架来讲,有一些不一样,Socket框架是有状态的,其状态服务提供的信息除了当前生效的配置信息以外,更多的是反映当前框架内部一些资源的状态以及统计数据:

  1. 各类配置(池配置、队列配置、集群配置)
  2. Socket相关的统计数据(总打开、总关闭、每秒收发数据、总收发数据、当前打开等等)
  3. 各类池的当前状态
  4. 各类队列的当前状态

状态服务能够如下面几种形式来提供:

  1. 代码方式,好比若是开发人员实现了IXXXStateAware接口的话,就能够为它的实现类来推送一些信息,也能够直接在框架中设立一个StateCenter来公开框架全部的状态信息
  2. 自动日志方式,好比若是在配置中开启了stateLoggingInterval=60s的选项,咱们的框架就会自动一分钟一次输出日志,显示框架内部的状态
  3. 接口方式,好比开放一个Restful的接口或额外监听一个端口来提供状态服务,方便使用者能够拿原始的数据和其它监控平台进行整合
  4. 内部外部工具方式
  5. 好比咱们能够直接为框架提供一个专门的页面(/_route)来呈现路由的配置(甚至咱们能够在这个页面上让开发人员能够直接输入地址来测试路由的匹配状况,状态服务不必定只能看),这样在开发和测试的时候能够更方便调试
  6. 咱们也能够为框架提供一个专有工具来查看框架的状态信息(固然,这个工具其实可能就是链接框架的某个网络服务来获取数据),这样即便框架在多个机器中使用,咱们可能也只有一个监控工具便可
  7. 若是没有状态服务,那么在运行的时候框架就是一个黑盒,反之若是状态服务足够详细的话,能够方便咱们排查一些功能或性能问题。不过要注意的一点是,状体服务可能会下降框架的性能,咱们可能须要对状态服务也进行一次压测,排除状态服务中损耗性能的地方(有些数据的收集会意想不到得损耗性能)。

 

检查线程安全

框架对多线程环境支持的是否好,是框架质量的一个重要的评估标准,每每能够看到甚至有一些成熟的框架也会有多线程问题。这里涉及几个方面:

1,你没法预料框架的使用者会怎么样去实例化和保存你的API的入口类,若是你的入口类被用成为了一个单例,在并发调用的状况下会不会有单线程问题?

这是一个老话题,以前已经说过不少次,你在设计框架的时候内心若是把一个类定位成了单例的类但却没有提供单例模式,你是没法要求使用者来帮你实现单例的。这其中涉及的不只仅是多线程问题,可能还有性能问题。好比见过某分布式缓存的客户端的CacheClient在文档中要求使用者针对一个缓存集群保持一个CacheClient的单例(由于其中有了链接池),可是用的人仍是每一次都实例化了一个CacheClient出来,几小时后就会产生几万个半死的Socket致使网络奔溃。又见过某类库的入口工厂的代码注释中写了要求使用的人把XXXFactory做为单例来使用(由于其中缓存了大量数据),可是用的人就没有注意到这个注释,每一次都实例化了一个XXXFactory,形成GC的崩溃。因此我以为做为框架的设计者开发人员,最好仍是把框架的最佳实践直接作到API中,使得使用者不可能出错(以前说过一句话,再重复一次,好的框架不会让使用的人犯错)。你可能会说对于CacheClient的例子,不可能作成单例的,由于个人程序可能须要用到多个缓存的集群,换个思路,咱们彻底能够在封装一层,经过一个CacheClientCreator之类的类来管理多个单例的CacheClient。即便在某些极端的状况下,你不能只提供一条路给使用者去走,也须要在框架内作一些检测机制,及时提醒使用者 "咱们发现您这样使用了框架,这可能会产生问题,你本意是否打算那样作呢?"

2,若是你的入口类原本就是单例的,那么你是类中是否持有共享资源,你的API在并发的状况下被调用是否能够确保这些资源的线程安全?在解决多线程问题的时候每每有几个难点:

百密难有一疏,你很难想到这段代码会有人这样去并发调用。好比某init()方法,某config()方法,你老是假设使用者会调用而且仅调用一次,但事实不必定这样,有的时候调用者本身也不清楚个人容器会调用我这段代码多少次。

好吧,解决多线程问题各类烦躁,那就对各类涉及到共享资源的方法所有加锁。对方法进行粗犷(粒度)的锁可能会致使性能急剧降低甚至是死锁问题。

自觉得使用了优雅的无锁代码或并发容器但却达不到目的。咱们每每在大量使用了并发集合心中暗自窃喜解决了多线程问题的同时又达到了极佳的性能,但你觉得这样是解决了线程安全问题但其实根本就没有,咱们不能假设A和B都方法是线程安全的,但对A和B方法调用的整个代码段是线程安全的。

对于多线程问题,我没有好的解决办法,不过下面的几条我以为能够尝试:

须要很是仔细的过一遍代码,把涉及到共享资源的地方,以及相关的方法和类列出来,不要去假设什么,只要API暴露出去了则假设它可能被并发调用。共享资源不必定是静态资源,哪怕资源是非静态的,在并发环境下对相同对象的资源进行操做也可能产生问题。

通常而言对于公开的API,做为框架的设计者咱们须要确保全部的静态方法(或但单例类的实例方法)是线程安全的,对于实例方法咱们能够不这么作(由于性能缘由),可是须要在注释中明确提示使用者方法的非线程安全,若是须要并发调用请自行处理线程安全问题。

能够看看是否有可能让这些资源(字段)变为方法内的局部变量,有的时候咱们并非真正的须要类持有一个字段,只是由于多个方法要使用相同的东西,随手一写罢了。

对于使用频率低的一些方法相关的一些资源没有必要使用并发容器,直接采用粗狂的方式进行资源加锁甚至是方法级别加锁,先确保没有线程安全,若是之后作压测出现性能问题再来解决。

对于使用频率高的一些方法相关的一些资源能够使用并发容器,但须要仔细思考一下代码是否会存在线程安全问题,必要的话为代码设计一些多线程环境的单元测试去验证。

 

性能测试和优化

以前也提到过,你不会预测到你的项目会在怎么样的访问量下使用,咱们不但愿框架和同类的框架相比有明显的性能差距(若是你作的是一个ORM框架或RPC框架,这个工做就是必不可少的),因此在框架基本完成后咱们须要作Benchmark:

  1. 肯定几个测试用例,尽可能覆盖主流程和一些重要扩展
  2. 找几个主流的同类型框架,实现相同的测试用例,实现到时候要单纯一点,尽可能不要再依赖其它外部框架
  3. 为这些框架和本身的框架,使用压力测试工具在相同的环境和平台来跑这些测试用例,使用图表绘制在不一样的压力下的执行时间(以及内存和CPU等主要资源的消耗状况)
  4. 若是出现明显的差距则用性能分析工具进行排查和优化,好比:
  5. 优化框架内的线程安全的实现方式
  6. 为框架内的代码作一些缓存(缓存反射获得的元数据等等)
  7. 减小调用层次
  8. 这些调整可能会打破原来的主线流程或让代码变得难以理解,须要留下相关注释
  9. 不断重压力测试和优化的过程,每次尝试优化5%~20%的性能,虽然越到后来可能会越难,若是发现实在没法优化的话(性能分析工具显示性能的分布已经很均匀了),能够看一下其它框架对于这部分工做实现的代码逻辑

封装和扩展

我的以为一个框架若是只是能用那是第一个层次,能很方便的进行扩展或二次开发那是另一个层次,若是咱们龙骨阶段的工做作的足够好,框架是一个立体饱满的框架,那么这部分的工做量就会小不少,不然咱们须要对框架进行很多的重构以即可以达到这个层次。

  1. 咱们须要纵览一下框架的全部类型,看看有哪些类型咱们是打算提供开发人员进行加强、扩展或替换的,对这些类型进行响应的结构调整。
  2. 好比但愿被加强,则须要从继承的角度来考虑
  3. 好比但愿被扩展,则须要从Provider的角度来考虑
  4. 好比但愿被替换,则须要在配置中提供组件的替换
  5. 咱们须要再为这些类型进行精细化的调整:
  6. 检查是否该封闭的封闭了,该开放的开放了
  7. 加强扩展或替换是否会带来反作用
  8. 对于新来的外来类型,接收和使用的时候作足够的检查
  9. 相关日志的完善

 

重构仍是重构

光是重构这个事情其实就能够说一本书了,其实我有一点代码的洁癖,这里列一些我本身写代码的时候注重的地方:

  1. 格式:每次提交代码的时候使用IDE来格式化你的代码和引用(固然,实现可能须要配置IDE为你喜欢的代码风格)
  2. 命名:保持整个类和接口命名统一,各类er,Provider、Creator、Initializer、Invoker、Selector表明的是一件事情,不要使用汉语拼音命名,若是英文不够好的话多查一下字典,有的时候我甚至会由于一个命名去阅读一些源代码看看老外是怎么命名这个对象或这个方法的
  3. 访问控制修饰符:这是一个很是难作好的细节,由于有太多的地方有访问控制修饰符,到底是给予什么级别的修饰符每每又取决于框架的扩展。能够在一开始的时候给尽可能小的权限,在必要的时候慢慢提高,好比对于方法除了必定要给public的地方(好比公共API或实现接口),尽可能都给private,在有继承层次关系的时候去给到protected,对于类能够都给默认包/程序集权限,产生编译错误的时候再去给到public
  4. 属性/getter、setter:对于非POJO类字段的公开也要仔细想一下, 是否有必要有setter,由于一旦外部能够来设置类的某个内部字段,那么不只仅可能改变了类的内部状态,你还要考虑的是怎么处理这种改变,是否是有线程安全问题等等,甚至要考虑是否有必要开放getter,是否应该把类内部的信息公开给外部
  5. 方法:思考每个方法在当前的类中存在是否合理,这是否属于当前类应该作的事情,方法是否作了太多事情太少事情
  6. 参数:须要思考,对于调用每个方法的参数,应该是传给方法,仍是让方法本身去获取;应该传多个参数,仍是封装一个上下文给到方法
  7. 常量:尽可能用枚举或静态字符串来代替框架使用到的一些常量或幻数,须要为常量进行一个分类不能一股脑堆在一个常量类Consts中

除了上面说的一些问题,我以为对于重构,最重要的一句话就是:不要让同一段代码出现两遍,主要围绕这个原则进行重构每每就会解决不少设计问题,要实现这个目标可能须要:

  1. 干差很少活的类使用继承来避免代码重复(提炼超类),使用模版方法来把差别留给子类实现
  2. 构造方法能够层次化调用,主构造方法只要一个就能够了,不要在构造方法中实现太多逻辑
  3. 若是方法的代码有重复能够考虑对方法提取出更小的公共方法来调用(提炼方法),也能够考虑使用Lambda表达式进行更小粒度重复代码的提取(提炼逻辑)
  4. 能够使用IDE或一些代码分析工具来分析重复代码,若是你能想尽一切办法来避免这些重复的话,代码质量能够提升一个层次

其实也不必定是在重构的时候再去处理上面全部的问题,若是在写代码的时候都带着这些意识来写的话那么重构的负担就会小一点(不过写代码思想的负担比较大,须要同时考虑封装问题、优雅问题、日志异常问题、多线程问题等等,因此写一套能用的代码和写一套好的代码其实不是一回事情)。

 

项目文档

若是要别人来使用你的框架,除了示例项目来讲提供和维护一份项目文档是颇有必要的,我建议文档分为这几个部分:

  1. 特性 Features:
  2. 至关于项目的一个宣传手册,让别人能被你项目的亮点所吸引
  3. 每个特性能够是一句话来介绍
  4. 新手入门 Get started:
  5. 介绍框架的基本定位和做用
  6. 从下载开始,经过一步一步的方式让用户了解怎么把框架用起来
  7. 整个文档的阅读时间在10分钟之内
  8. 新手教程 Tutorials:
  9. 提供5~10篇文章站在使用者的角度来介绍项目的主要功能点
  10. 仍是经过一步一步的方式,教你们使用框架完成一个小项目(好比CRUD)
  11. 介绍框架使用的最佳实践
  12. 整个文档的阅读时间在8小时内
  13. 手册 Manual:
  14. 介绍项目的定位和理念
  15. 详细介绍项目的每个功能点,能够站在框架设计者的角度多介绍一些理念
  16. 详细介绍项目的每个配置,以及默认配置和典型配置
  17. 详细介绍项目的每个扩展点和替换点
  18. 文档最好不是带格式的,方便之后适配各类文档生成器和开源网站

 

开源

开源的好处是有不少人能够看到你的代码帮助你改进,你的框架也可能会在更多的复杂环境下使用,框架的发展会较快框架的代码质量也会有很大的提高。

要把框架进行开源,除了上面的各类工做以外可能还有一些额外的工做须要作:

  1. 选择一个合适的License,而且检测本身选择的License与使用到的类库的License是否兼容,在代码头的地方标记上License。
  2. 要确保每个人均可以在本身的环境中能够构建你的代码,尽可能使用Maven等你们熟悉的构建工具来管理依赖和构建。
  3. 选择诸如Github等平台来管理源代码,并以良好的格式上传你的文档,有条件的话对示例子网站进行部署。
  4. 若是你但愿你的代码让更多的人一块儿来参与开发,那么须要制定和公开一些规范,好比风格、命名、提交流程、测试规范、质量要求等等。
  5. 开源后时刻对项目进行关注,对各类反馈和整合请求进行及时的反馈,毕竟开源是让别人来帮你一块儿改进代码,不是单纯让别人来学习你的代码也不是让别人来帮你写代码。 

看到这里你可能相信我一开始的话了吧,框架能够使用到完善能够商用差距仍是很大的,并且还要确保在迭代的过程当中框架不能偏离开始的初衷不能有很大的性能问题出现,任重道远。

相关文章
相关标签/搜索