自己动手写框架 - v1.0


前言前言 本书介绍本书介绍 旨在提高读者的学习效率,增强其项目实战能力。采用完整的框架开发案例,为读者讲解了开发商业软件的必备 知识,帮组读者汲取框架开发的思想,帮助开发者高屋建瓴地打造适合自己的开发框架。## 适用人群适合开源爱 好者,Java开发者、项目经理和架构师阅读。## 学习前提熟悉Java开发和Maven、Git的使用,有兴趣研究Tin y框架,对开源项目具有浓厚的兴趣。> 致谢致谢 学习社区:http://bbs.tinygroup.org> 源码地址:https://git.oschi na.net/tinyframework/tiny> 开源学习QQ群:228977971 更新日期更新日期 更新内容更新内容 2015-06-17 自己动手写框架(连载中) 目录目录 前言前言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 第 1 章第 1 章 缘起缘起 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 第 2 章第 2 章 用200行的DBF解析器来展示良好架构设计用200行的DBF解析器来展示良好架构设计 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 第 3 章第 3 章 业务流程引擎设计业务流程引擎设计 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1515 第 4 章第 4 章 分布式锁的简单实现分布式锁的简单实现 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2222 第 5 章第 5 章 生态圈的建立生态圈的建立 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2828 第 6 章第 6 章 量身定制规则引擎,适应多变业务场景量身定制规则引擎,适应多变业务场景 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3030 第 7 章第 7 章 关于框架体系与战术的思考关于框架体系与战术的思考 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4545 第 8 章第 8 章 高屋建瓴,理念先行高屋建瓴,理念先行 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4848 第 9 章第 9 章 理想的开源框架与设计原则理想的开源框架与设计原则 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5151 第 10 章第 10 章 Web界面快速开发实践Web界面快速开发实践 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5656 第 11 章第 11 章 软件开发杂谈软件开发杂谈 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7878 第 12 章第 12 章 框架2.0的设计梳理框架2.0的设计梳理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8585 第 13 章第 13 章 开源与中小型软件公司的未来趋势开源与中小型软件公司的未来趋势 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9494 第 14 章第 14 章 教计算机程序解数学题教计算机程序解数学题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9898 第 15 章第 15 章 借船下海还是造船下海借船下海还是造船下海. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104104 第 16 章第 16 章 缓存相关代码的演变缓存相关代码的演变 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107107 第 17 章第 17 章 与屈原对话及开源精神与屈原对话及开源精神. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114114 11 缘起缘起 自己动手写框架?第一次接触这本书的读者可能会有一些惊讶。是的,这就是写本书的缘由。 缘由缘由 从毕业以来,我一直从事软件开发及软件管理,做过大量的项目与产品,但是还是喜欢平台软件及软件管理方面 的工作。多年的业务开发及平台构建的过程中,踩了许许多多的坑,也积累了许许多多的经验。 和很多的朋友一 样,在没有开发框架之前,可能我们只有一些落在纸面上的编码规范,或者说是开发约定。但不管怎么说,这些 规范和约定对于我们软件开发过程还是有相当的指导与规范作用的。再后来,随着时间的推移,我们的软件代码 写得越来越多,它们中的一部分具有相当的通用性,可能就会变成一个公共库;它们其中的一部分,在整体的运 行机制来说是统一的,可以抽取成公共的部分,但是有一部分又会业务性非常强,需要在实际做业务的时候进行 扩展。 于是,这部分就会演变成框架!不过,这个过程往往是困难重重。 我们也深知这个道理,依靠一个全新框架做所 有的事情,可谓筚路蓝缕。第一是从时间精力上不可能的;第二是没有足够的能力与水平做所有的事情;第 三,普适性与专业性方面也不能做到良好的平衡;第四,技术的发展变化是那么快,不能快速适应变化,就只能 被淘汰。因此,从框架设计初始,首先应该是从体系化方面考虑,构建一个生态圈,由所有志同道合的程序员来 来共同加入生态圈,最终做到越用越强,越用越好用的效果!同时,框架的学习成本必须非常低,这样才可以让 使用者更容易上手,避免由于学习难度大而导致的学习曲线太陡、太长。 我们也知道,构建一个框架,明显不仅仅是掌握某种语言的编程技术所能完成的:这需要充分考虑架构、开 发、测试、打包、发布、部署、维护等各个环节的各个参与者的需求及期望;这需要对现有各种框架的优点、缺 点有充分的了解,需要对自己框架的理念、原则、目标、策略有清晰的定位与认识;这需要有敏锐的嗅觉,可以 感受得到某些坏味道,而这种嗅觉就需要经年的积累。所以,好的框架应该是“品”出来的! 正是基于这样的愿景,我们已经走过了近5个年头,实际上在真正开始之前已经做了诸多的准备工作,更多的是在 整个软件架构中蕴含了构建者多年软件从业所积累的丰富经验,以及通过踩下的无数的坑所积累起来的经验。开 始的是一个人的坚守,现在已经形成了一个紧密团结且互补性强的团队,团队成员每个人都有自己的特色,但是 都有一个共同的特点,那就是:对软件事业的热爱。 因此,这本书就是帮助你,一步一步实现一个基于J2EE技术的应用开发框架! 认识框架认识框架 通过这本书,我们将搭建一个基于J2EE技术的应用开发框架,可以用来开发各种Java Application和Web Appli cation。就像一个襁褓中的新生婴儿,我们也给框架取了一个名字——Tiny。权且当作一个小名吧。 第 1 章 缘起 | 4 1.Tiny名称的来历1.Tiny名称的来历 取名Tiny是取其微不足道,微小之意。我们认为,一个J2EE开发框架是非常复杂的,只有把框架分解成非常细 小、可控的部分,并且对每个细小、可控的部分都有一个最优解或相对最优解,那么整个方案也就可以非常不错 的落地。 Tiny Framework是作者在多年框架构建经验积累上的呕心沥血之作,是作者在软件实践中吃了无数的苦,受了 无数的累之后,立足于解决软件的开发、测试、发布、实施全生命周期存在的多种“疑难杂症”所开发的软件开 发平台。往小了说,可以用于编写一个HelloWorld,再大些可用于快速搭建一个网站,往大了说,可以用于构建 一个7*24小时永不停止服务的互联网应用。构建者对软件框架的一个重要理解就是Think big, start small, scale fast。所以,Tiny框架没有什么是不可以进行扩展的,也没有什么是不可以被替换的。所以,只要你是J2EE领域 的参与者,Tiny框架都值得你一看、一试、一用。 2.Tiny框架的构建策略2.Tiny框架的构建策略 Think big, start small, scale fast。想法要宏伟,但是要从小的开始,同时可以快速的进行扩展。Tiny框架的构 建者认为:如果想法不够宏伟,那么就会局限于细节;如果一开始就铺非常大的摊子,将会失去控制;如果不能 快速扩展,就无法满足应用需要。 3.Tiny框架的应用定位3.Tiny框架的应用定位 基于WEB的展现平台主要包括展现层及控制层,可以让开发人员方便快速的实现Web层的开发,同时要避免现 有方案的缺点,同时要有自己非常突出的优点。基于SOA的服务开发平台可以方便的开发平台无关的服务,同时 有水平扩展,应用集群方面要有良好的支持,可以满足7*24的运维要求。 4.Tiny框架的目标定位4.Tiny框架的目标定位 Tiny框架的构建者认为再好的框架也有过时的时候,再优秀的架构师也有视野盲区,再牛的写手也无法实现所有 的部分。所以Tiny框架的构建者认为:只有建立一个良好的生态圈,让广大开发者使用者都在生态圈在找到自己 的位置,才是有生命的模式。 5.兼容性测试5.兼容性测试 理论上说支持各种支持JDK1.6以上的32位,64位操作系统,但是限于时间及环境准备,实际测试过程当中没有 全部测试过。 第 1 章 缘起 | 5 6.引用的开源框架和开源协议6.引用的开源框架和开源协议 框架引用的开源框架有:VelocityVelocity官方网站,Lucene,MavenMaven;扩展引用的开源框架有:JQu eryJQuery,HibernateHibernate,iBatisiBatis。 Tiny框架有两个部分的内容,一部分采用开源方式发布,开源发布的部分采用GPLv3开源协议,详细参见英文文 本,也可以参看GNU通用公共许可协议获取中文的一些解释;另一部分没有开源,采用商业授权方式进行发 布。Tiny框框架之所以选择了GPL协议,主要出发点是说:如果你喜欢Tiny框架,那么可以通过Fork的方式贡献 自己的代码,也可以加入Tiny组织一起来构建Tiny框架;也在自己的项目或产品中可以免费使用Tiny框架。但是 不允许把Tiny框架整体或其中的一部分作为自己的闭源框架进行发布。这也就意味着,如果你要扩展Tiny框架或 者修改Tiny框架为自己的框架,则你自己框架也是需要开源并提供源代码的。 7.编码规范7.编码规范 Tiny的编码规范,如下:采用Eclipse的Ctrl+Shift+F,格式化出来的格式,即基本的缩进格式所有类名都采首字 母大写的驼峰规则所有方法名,变量名都采用首字母小写的驼峰规则所有常量名都定义为用“_”连接的全大写单 词方法的圈复杂度3个以下为标准值,5-7个为可接受值,8个以上要加备注说明其必要性除了极少数情况外(数据 下标,集合或数组长度等),所有的常量都应该定义为常量除非缩写是业界标准说法,否则所有变量名及常量 名,都用全单词拼写类名全部用名字为主体,前面可以加形容性或限定词方法名全部用形容性或动词为主体方法 行数,一般在20行以内,20-40行属于可接受范围,40+以上要有备注说明其必要性类名及接口方法必须有备注 说明,实现类中的方法可以不加。非接口实现类的公有方法必须有备注说明不允许出现Main方法,除非确实需要 从命令行运行异常必须扔出或日志,如果抓住不作处理,需要有明确注释说明日志记录必须闭环(有开始,有结 束)利用sonar进行检测,RCI(规则遵从指数)达到90%以上。 第 1 章 缘起 | 6 22 用200行的DBF解析器来展示良好架构设计用200行的DBF解析器来展示良好架构设计 由于工作关系,需要工作当中,需要读取DBF文件,找了一些DBF读取开源软件,要么是太过庞大,动不动就上 万行,要么是功能有问题,编码,长度,总之是没有找到一个非常爽的。在万般无奈之下,我老人家怒从心头 起,恶向胆边生,决定自己写一下。结果只用了不到300行代码就搞定了,当然搞定不是唯一目标,还要优雅简 洁的搞定,亲们跟随我的脚步一起感受一下简洁的设计与实现吧。 在开始编码之前,先介绍一下DBF,这个DBF可是个老东西,在DOS时代就已经出现,并且风骚了相当一段时 间,后来随着大型数据库的应用,它逐步没落,但是由于其简洁易用的特点,还是应用在大量的数据交换当 中。但是其发展过程中,也形成了许多种版本,不同版本的结构不一样,也就决定 了其解析程序也是不一样的。 今天我只实现了Foxbase/DBaseIII的解析,但是也为扩展各种其它版本做好了准备。 接口设计 上面一共就两个类,一个接口,Field和Header就是两个简单的POJO类,分别定义了文件头及字段相关的信 息。 Reader接口是DBF文件读取的接口,主要定义了获取文件类型,编码,字段以及记录移动相关的方法。 代 码实现 首先实现Reader的抽象类 public abstract class DbfReader implements Reader { protected String encode = "GBK"; private FileChannel fileChannel; protected Header header; protected List fields; private boolean recordRemoved; int position = 0; static Map readerMap = new HashMap(); 第 2 章 用200行的DBF解析器来展示良好架构设计 | 8 static { addReader(3, FoxproDBase3Reader.class); } public static void addReader(int type, Class clazz) { readerMap.put(type, clazz); } public static void addReader(int type, String className) throws ClassNotFoundException { readerMap.put(type, Class.forName(className)); } public byte getType() { return 3; } public String getEncode() { return encode; } public Header getHeader() { return header; } public List getFields() { return fields; } public boolean isRecordRemoved() { return recordRemoved; } public static Reader parse(String dbfFile, String encode) throws IOException, IllegalAccessException, InstantiationException { return parse(new File(dbfFile), encode); } public static Reader parse(String dbfFile) throws IOException, IllegalAccessException, InstantiationException { return parse(new File(dbfFile), "GBK"); } public static Reader parse(File dbfFile) throws IOException, IllegalAccessException, InstantiationException { return parse(dbfFile, "GBK"); } 第 2 章 用200行的DBF解析器来展示良好架构设计 | 9 public static Reader parse(File dbfFile, String encode) throws IOException, IllegalAccessException, InstantiationException { RandomAccessFile aFile = new RandomAccessFile(dbfFile, "r"); FileChannel fileChannel = aFile.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1); fileChannel.read(byteBuffer); byte type = byteBuffer.array()[0]; Class readerClass = readerMap.get((int) type); if (readerClass == null) { fileChannel.close(); throw new IOException("不支持的文件类型[" + type + "]。"); } DbfReader reader = (DbfReader) readerClass.newInstance(); reader.setFileChannel(fileChannel); reader.readHeader(); reader.readFields(); return reader; } public void setFileChannel(FileChannel fileChannel) { this.fileChannel = fileChannel; } protected abstract void readFields() throws IOException; public void moveBeforeFirst() throws IOException { position = 0; fileChannel.position(header.getHeaderLength()); } /** * @param position 从1开始 * @throws java.io.IOException */ public void absolute(int position) throws IOException { checkPosition(position); this.position = position; fileChannel.position(header.getHeaderLength() + (position - 1) * header.getRecordLength()); } private void checkPosition(int position) throws IOException { if (position >= header.getRecordCount()) { throw new IOException("期望记录行数为" + (this.position + 1) + ",超过实际记录行数:" + header.getRecordCount() + "。"); } } 第 2 章 用200行的DBF解析器来展示良好架构设计 | 10 protected abstract Field readField() throws IOException; protected abstract void readHeader() throws IOException; private void skipHeaderTerminator() throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocate(1); readByteBuffer(byteBuffer); } public void close() throws IOException { fileChannel.close(); } public void next() throws IOException { checkPosition(position); ByteBuffer byteBuffer = ByteBuffer.allocate(1); readByteBuffer(byteBuffer); this.recordRemoved = (byteBuffer.array()[0] == '*'); for (Field field : fields) { read(field); } position++; } public boolean hasNext() { return position < header.getRecordCount(); } private void read(Field field) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(field.getLength()); readByteBuffer(buffer); field.setStringValue(new String(buffer.array(), encode).trim()); field.setBuffer(buffer); } protected void readByteBuffer(ByteBuffer byteBuffer) throws IOException { fileChannel.read(byteBuffer); } } 这个类是最大的一个类,值得注意的是几个静态方法: addReader和parse, addReader用于增加新的类型的 Reader,parse用于解析文件。 第 2 章 用200行的DBF解析器来展示良好架构设计 | 11 parse的执行过程是首先读取第一个字节,判断是否有对应的解析实现类,如果有,就有对应的解析实现类去解 析,如果没有,则抛出错误声明不支持。 下面写实现类就简单了,下面是FoxproDBase3的解析器: public class FoxproDBase3Reader extends DbfReader { protected void readFields() throws IOException { fields = new ArrayList(); for (int i = 0; i < (header.getHeaderLength() - 32 - 1) / 32; i++) { fields.add(readField()); } } public byte getType() { return 3; } protected Field readField() throws IOException { Field field = new Field(); ByteBuffer byteBuffer = ByteBuffer.allocate(32); readByteBuffer(byteBuffer); byte[] bytes = byteBuffer.array(); field.setName(new String(bytes, 0, 11, encode).trim().split("\0")[0]); field.setType((char) bytes[11]); field.setDisplacement(Util.getUnsignedInt(bytes, 12, 4)); field.setLength(Util.getUnsignedInt(bytes, 16, 1)); field.setDecimal(Util.getUnsignedInt(bytes, 17, 1)); field.setFlag(bytes[18]); return field; } protected void readHeader() throws IOException { header = new Header(); ByteBuffer byteBuffer = ByteBuffer.allocate(31); readByteBuffer(byteBuffer); byte[] bytes = byteBuffer.array(); header.setLastUpdate((Util.getUnsignedInt(bytes, 0, 1) + 1900) * 10000 + Util.getUnsignedInt(bytes, 1, 1) * 100 + Util.getUnsignedInt(bytes, 2, 1)); header.setRecordCount(Util.getUnsignedInt(bytes, 3, 4)); header.setHeaderLength(Util.getUnsignedInt(bytes, 7, 2)); header.setRecordLength(Util.getUnsignedInt(bytes, 9, 2)); } } 测试用例 第 2 章 用200行的DBF解析器来展示良好架构设计 | 12 public class DbfReaderTest { static String[] files = {"BESTIMATE20140401", "BHDQUOTE20140401"}; public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException { for (String file : files) { printFile(file); } } public static void printFile(String fileName) throws IOException, InstantiationException, IllegalAccessException { Reader dbfReader = DbfReader.parse("E:\\20140401\\" + fileName + ".DBF"); for (Field field : dbfReader.getFields()) { System.out.printf("name:%s %s(%d,%d)\n", field.getName(), field.getType(), field.getLength(), field.getDecimal()); } System.out.println(); for (int i = 0; i < dbfReader.getHeader().getRecordCount(); i++) { dbfReader.next(); for (Field field : dbfReader.getFields()) { System.out.printf("%" + field.getLength() + "s", field.getStringValue()); } System.out.println(); } dbfReader.close(); } } 可以看到最后的使用也是非常简洁的。 代码统计 总共的代码行数是282行,去掉import和接口声明之类的,真正干活的代码大概就200行了: 总结总结上面不仅展示了如何实现DBF文件的解析,同时还展示了如何在现在面临的需求与未来的扩展进行合理均衡 的设计方式。 比如:要实现另外一个标准的DBF文件支持,只要类似上面FoxproDBase3Reader类一样,简单实现之后,再 调用DbfParser.addReader(xxxReader); 第 2 章 用200行的DBF解析器来展示良好架构设计 | 13 好的设计需要即避免过度设计,搞得太复杂,同时也要对未来的变化与扩展做适当考虑,避免新的需求来的时候 需要这里动动,那里改改导致结构上的调整与变化,同时要注意遵守DRY原则,可以这样说如果程序中有必要的 大量的重复,就说明一定存在结构设计上的问题。 第 2 章 用200行的DBF解析器来展示良好架构设计 | 14 33 业务流程引擎设计业务流程引擎设计 一般的时候,我们都采用编程式开发,编程式开发的好处非常明显:直接、高效、自由,当然其缺点也是有 的,与其优点刚好相对,因为直接,所以有些变化都要进行代码上的修改;因为高效,所以一旦出问题,导致的 结果也比较严重,因为自由,所以带来的修改风险也比较大。 这也就是许多大的公司都在进行流程化开发的重要 原因之一,比如:上海普元,Livebos, Justep,还有许许多多知名不知名的公司都有类似的流程化开发引擎存 在,通过流程化开发,增强代码的复用性,降低软件开发成本及测试成本,提升软件的可维护性及降低维护成 本。 在设计Tiny框架时,我们也考虑了自己的方案,主要包括以下几个方面的问题: a.组件扩充的便捷性a.组件扩充的便捷性 组件的扩充的便捷性是指,流程其实玩的就是组件,如果组件扩充起来非常困难,会直接影响到流程引擎的可用 性。所以Tiny框架的流程引擎的组件结构非常之简单,仅有一个接口方法;流程组件的注册与加载也是非常重要 的,如果在扩充流程组件的时候,需要复杂的注册或配置过程,这个时候流程扩充的便捷性也会大大降低。Tiny 框架采用了引用即注册的方案,只要把流程组件放入系统运行环境之间,就完成了流程组件的注册,即可以在流 程中使用,便得流程组件的扩充的便捷性大大提高。 b.流程的面向对象特性支持b.流程的面向对象特性支持 流程的面向特性支持是指在Tiny框架中流程是具有面向对象的特性的。流程可以进行继承,这样带来一个好处就 是多个流程中重复的部分,可以定义在一个父流程中,然后子流程只要继承父流程,即可;流程节点是可以被覆 盖的,也就是说,在父流程中可以定义一个空节点,但是流程中定义了流转关系,但是流程节点的实现留在子流 程中实现; c.流程的易编辑性c.流程的易编辑性 流程的编辑必须方便、容易,有专门的流程编辑工具更好,没有的时候,使用普通的Xml编辑器也可以方便的进 行编辑。 d.流程的可重入性d.流程的可重入性 一般的流程引擎都是不可重入的,也就是只能从开始执行,执行到结束结点之后完成。Tiny流程引擎支持流程重 入,也就是说,不一定是从开始结点执行,可以从任意一个结点执行。这个机制为程序的逻辑提供了非常大的自 由度,可以利用此特性容易的构建页面流引擎或工作流引擎。即使是业务流程引擎,也会由此获得更大的自由 度。 第 3 章 业务流程引擎设计 | 16 由于支持流程的可重入性,在本流程处理当中,不仅可以在当前流程中进行切换与转接,还可以流转到其他流程 的节点当中,这在业务处理及页面处理,流程处理方面都提供了极大的使得,但是这也是一个双刃剑,在提供了 这么灵活的功能的同时,也会导致业务流程看起来比较复杂,因此,控制方面最好由架构师或核心开发人员来编 写,普通开发人员只开发具体的业务点即可。 呵呵,说了这么多,大家理解起来可能还是比较抽象,那就来个例子看看: < /flow> HelloWorldComponent的源码如下: public class HelloWorldComponent implements ComponentInterface { String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void execute(Context context) { context.put("result", String.format("Hello, %s", name)); } } 可以看出,所有组件必须实现ComponentInterface 接口 从其实现逻辑可以看出,它就是把“Hello, ”加上输入 的名字,放在了环境变量的result当中。 下面看看执行结果: a.按默认开始结点开始执行 Context context = new ContextImpl(); flowExecutor.execute("1000", context); assertEquals("Hello, world", context.get("result")); b.从指定节点开始执行 第 3 章 业务流程引擎设计 | 17 Context context = new ContextImpl(); flowExecutor.execute("1000","begin", context); assertEquals("Hello, world", context.get("result")); 可以看到确实是执行并返回了结果,但是它的执行机理是怎么样的呢?? 实际上,上面的流程是一个简化的流 程,就是说Tiny流程引擎的有些参数不输入,也可以按照约定正确的执行,实际上写得完整的话,例子是下面这 个样子的: some thing.... < /flow> 其中flow节点的属性含义为: • id,唯一确定一个流程 • privateContext,如果是true,则在流程单独申请一个context,否则共用调用者的context,这样可以有效 避免环境变量冲突问题 • extend-flow-id,继承的流程id,这个继承id是一个非常强大的功能,后面详细介绍 • version版本号,同一id的流程可以存在多个版本,访问时,如果不指定版本则默认采用最新版本 • name,title仅用于说明其英文,中文名称,易于理解而已。 • default-node-id表示,默认执行节点,即如果一个组件执行完毕,其项值没有指定下一处理节点则执行默 认节点 • begin-node-id,开始节点 • end-node-id,结束节点 如果不指定,则begin-node-id默认为begin,end-node-id默认为end • node节点:id必须指定,在一个流程当中id必须唯一。 • component节点 第 3 章 业务流程引擎设计 | 18 • class-name用于指定组织实现类名 • properties是组件的属性列表 • property中的name与value是组件的属性的值,value,这里传入的是个字符串,但是实际当中可以处理中 可以非常灵活,后面再介绍。 • next-nodes,是指根据执行结果进行后续处理的规则。 • next-node,具体的一条规则,component-result,匹配项,支持正则表达式,节点中的组件执行结果进 行匹配,匹配成功则执行此规则中的下一节点。 • exception-type是异常的类名称,如果出现异常且与这里定义的类型匹配,则执行此规则中的下一节点。 上面说到继承,流程继承实现起来是非常简单的,只要在extend-flow-id属性中指定即可。 继承不支持多继承,即流程只能继承自一个流程,但是可以支持多层继承,即 a>b>c>d..... 实际开发过程中,不要把继承搞得太复杂,这样会把程序逻辑搞得更难理解的。 继承实际会起到什么作用呢? 首先,会继承一些属性,另外会把节点信息继承过来。 简单来说就是:两者都有,当前流程说了算,当前没 有,父流程说了算。 继承应用到什么场景呢?? 继承应用于业务处理的模式非常相似,只有中间处理环境不同的时候。 比如: A B C D ---O--- -D -C -B -A 类型的业务处理流程,只有O不同,其他处理模式完全相同,此时采用继承方式都非常舒服了,只要定义父流 程,在子流程中只用定义O一个流程节点即可。以后要统一进行流程调整,只要在父流程中进行调整就可以了。 比如:flow aa定义为 第 3 章 业务流程引擎设计 | 19 < /flow> flow bb定义为 < nodes> < node id="hello"> < component class-name="org.tinygroup.flow.HelloWorldComponent"> < properties> < property name="name" value="world" /> < /properties> < /component> < /node> < /nodes> < /flow> 则流程bb也可以顺利执行,且执行结果是Hello, world 非常重要的一个亮点就是属性赋值。 属性赋值是否好用,决定了框架的易用性。 可以支持常量赋值"1"表示数字常量 aa 表示字符串常量可以支持,环境变量赋值 比如:xx表示从环境变量取xx键值的对象 可以支持属性赋值 比如:xx.abc表示取环境变量xx的属性abc 比如:xx.abc.def表示取环境变量xx的属性abc的属性def 可以支持组合赋值 比如:${in:aa.abc.def}-${in:bb.cc.dd} 表示把环境aa中的属性abc的属性def中间加"-"再加上环境变量bb中的cc的属性的dd属性 其中属性的层次不受限制。 另外,取值方式,也支持自行扩展: 比如:可以用${in:xmlkey.aa}也取在环境中xmlkey对应的xml节点的aa属性 所以,只有想不到的,没有做不到的。 第 3 章 业务流程引擎设计 | 20 应用开发与部署方式,比较典型的有B/S与B/A/S,C/A/S等。对于B/A/S和C/A/S方式,因为A与B和C是分离部 署的,所以,所有的内容都需要是通过Context进行传递的。 如果是通过分离式部署,那么就需要通过网络来传递请求环境数据。 如果是想通过B/S环境来构建系统,此时就会期望通过HTTP处理线程来同布调用流程处理结果。 同时,有时流程处理的数据可能是在Request,RequestAttribute,Session,Cookie中,如果把这些数据COPY 到环境当中去,其实是有较大的性能消耗的。 本流程引擎即支持通过服务方式调用,也可以通过短路方式进行调用。 虽然我们推荐使用B/A/S体系架构,但是不能否认,目前我们的许多产品还是在B/S架构下运行的。 但是好在,这个对于流程引擎来说,他并不直接访问Request和Session,Cookie等内容,所以,即使是集成在 一起部署,也不妨碍进行分离式部署,依然可以保证服务的无状态特性,前提就是需要实现一个Context的接 口。 小结:小结: Tiny的流程引擎,提供了相当强悍的功能及扩展性,上面只说了一部分,有些也没有完全说清楚,实际上,还提 供了包含EL表达式等许多高级功能,对于期望进行流程式编排开发来说,有相当好的支持。 从后期效果来看,在 Tiny框架中,业务流程编排及页面流程编排都是基于此引擎构建,应用效果非常良好。 第 3 章 业务流程引擎设计 | 21 44 分布式锁的简单实现分布式锁的简单实现 分布式锁在分布式应用当中是要经常用到的,主要是解决分布式资源访问冲突的问题。 一开始考虑采用Reentra ntLock来实现,但是实际上去实现的时候,是有问题的,ReentrantLock的lock和unlock要求必须是在同一线 程进行,而分布式应用中,lock和unlock是两次不相关的请求,因此肯定不是同一线程,因此导致无法使用Ree ntrantLock。 接下来就考虑采用自己做个状态来进行锁状态的记录,结果发现总是死锁,仔细一看代码,能不锁死么。 public synchronized void lock(){ while(lock){ Thread.sleep(1); } lock=true; ... } public synchronized void unlock(){ lock=false; ... } 第一个请求要求获得锁,好么,给他个锁定状态,然后他拿着锁去干活了。 这个时候,第二个请求也要求锁,OK,他在lock中等待解锁。 第一个干完活了,过来还锁了,这个时候悲催了,因为,他进不了unlock方法了。 可能有人会问,为什么采用while,而不是采用wait...notify?这个问题留一下,看看有人能给出来不? 总之,上面的方安案流产了。 同样,不把synchronized 放在方法上,直接放在方法里放个同步对象可以不??道理是一样的,也会发生上面 一样的死锁。 到此为止前途一片黑暗。 @沈学良 同学的http://my.oschina.net/shenxueliang/blog/135865写了一个用zk做的同布锁,感觉还是比较 复杂的且存疑。自己做不出来吧,又不死心。 再来看看Lock的接口,想了一下,不遵守Lock的接口了。编写了下面的接口。 public interface DistributedLock extends RemoteObject { long lock() throws RemoteException, TimeoutException; long tryLock(long time, TimeUnit unit) throws RemoteException, TimeoutException; 第 4 章 分布式锁的简单实现 | 23 void unlock(long token) throws RemoteException; } 呵呵,眼尖的同学可能已经发现不同了。 lock方法增加了个long返回值,tryLock方法,返回的也不是boolean,也是long,unlock方法多了一个long参 数型参数,呵呵,技巧就在这里了。 public class DistributedLockImpl extends UnicastRemoteObject implements DistributedLock { /** * 超时单位 */ private TimeUnit lockTimeoutUnit = TimeUnit.SECONDS; /** * 锁的令牌 */ private volatile long token = 0; /** * 同步对象 */ byte[] lock = new byte[0]; /** * 默认永不超时 */ long lockTimeout = 60 * 60;//默认超时3600秒 long beginLockTime;//获取令牌时间,单位毫秒 public DistributedLockImpl() throws RemoteException { super(); } /** * @param lockTimeout 锁超时时间,如果加锁的对象不解锁,超时之后自动解锁 * @param lockTimeoutUnit * @throws RemoteException */ public DistributedLockImpl(long lockTimeout, TimeUnit lockTimeoutUnit) throws RemoteException { super(); this.lockTimeout = lockTimeout; this.lockTimeoutUnit = this.lockTimeoutUnit; } public long lock() throws TimeoutException { 第 4 章 分布式锁的简单实现 | 24 return tryLock(0, TimeUnit.MILLISECONDS); } private boolean isLockTimeout() { if (lockTimeout <= 0) { return false; } return (System.currentTimeMillis() - beginLockTime) < lockTimeoutUnit.toMillis(lockTimeout); } private long getToken() { beginLockTime = System.currentTimeMillis(); token = System.nanoTime(); return token; } public long tryLock(long time, TimeUnit unit) throws TimeoutException { synchronized (lock) { long startTime = System.nanoTime(); while (token != 0 && isLockTimeout()) { try { if (time > 0) { long endTime = System.nanoTime(); if (endTime - startTime >= unit.toMillis(time)) { throw new TimeoutException(); } } Thread.sleep(1); } catch (InterruptedException e) { //DO Noting } } return getToken(); } } public void unlock(long token) { if (this.token != 0 && token == this.token) { this.token = 0; } else { throw new RuntimeException("令牌" + token + "无效."); } } } 下面对代码进行一下讲解。 上面的代码提供了,永远等待的获取锁的lock方法和如果在指定的时间获取锁失败就获得超时异常的tryLock方 法,另外还有一个unlock方法。 第 4 章 分布式锁的简单实现 | 25 技术的关键点实际上就是在token上,上面的实现,有一个基本的假设,就是两次远程调用之间的时间不可能在1 纳秒之内完成。因此,每次锁的操作都会返回一个长整型的令牌,就是当时执行时间的纳秒数。下次解锁必须用 获得的令牌进行解锁,才可以成功。如此,解锁就不用添加同步操作了,从而解决掉上面死锁的问题。 实际上,没有令牌也是可以的,但是那样就会导致a获取了锁,但是b执行unlock也会成功解锁,是不安全的,而 加入令牌,就可以保证只有加锁者才可以解锁。 下面是测试代码: public class TestDLock { public static void main(String[] args) throws Exception { RmiServer rmiServer = new LocalRmiServer(); DistributedLockImpl distributedLock = new DistributedLockImpl(); rmiServer.registerRemoteObject("lock1", distributedLock); MultiThreadProcessor processor = new MultiThreadProcessor("aa"); for (int i = 0; i < 8; i++) { processor.addProcessor(new RunLock("aa" + i)); } long s = System.currentTimeMillis(); processor.start(); long e = System.currentTimeMillis(); System.out.println(e - s); rmiServer.unexportObject(distributedLock); } } class RunLock extends AbstractProcessor { public RunLock(String name) { super(name); } @Override protected void action() throws Exception { try { RmiServer client = new RemoteRmiServer(); DistributedLock lock = client.getRemoteObject("lock1"); for (int i = 0; i < 1000; i++) { long token = lock.lock(); lock.unlock(token); } System.out.println("end-" + Thread.currentThread().getId()); } catch (RemoteException e) { e.printStackTrace(); } 第 4 章 分布式锁的简单实现 | 26 } } 运行情况: 1 -0 [main] INFO - 线程组运行开始,线程数8... 2 -3 [aa-aa0] INFO - 线程运行开始... 3 -3 [aa-aa1] INFO - 线程运行开始... 4 -3 [aa-aa2] INFO - 线程运行开始... 5 -3 [aa-aa3] INFO - 线程运行开始... 6 -3 [aa-aa4] INFO - 线程运行开始... 7 -4 [aa-aa5] INFO - 线程运行开始... 8 -4 [aa-aa6] INFO - 线程运行开始... 9 -8 [aa-aa7] INFO - 线程运行开始... 10 end-19 11 -9050 [aa-aa3] INFO - 线程运行结束 12 end-17 13 -9052 [aa-aa1] INFO - 线程运行结束 14 end-20 15 -9056 [aa-aa4] INFO - 线程运行结束 16 end-16 17 -9058 [aa-aa0] INFO - 线程运行结束 18 end-21 19 -9059 [aa-aa5] INFO - 线程运行结束 20 end-26 21 -9063 [aa-aa7] INFO - 线程运行结束 22 end-18 23 -9064 [aa-aa2] INFO - 线程运行结束 24 end-22 25 -9065 [aa-aa6] INFO - 线程运行结束 26 -9066 [main] INFO - 线程组运行结束, 用时:9065ms 27 9069 也就是9069ms中执行了8000次锁定及解锁操作。 小结:小结: 上面的分布式锁实现方案,综合考虑了实现简单,锁安全,锁超时等因素。实际测试,大概900到1000次获取锁 和释放锁操作每秒,可以满足大多数应用要求。 第 4 章 分布式锁的简单实现 | 27 55 生态圈的建立生态圈的建立 曾经有人提出过一个看似天方夜谭的设想,在我们生活的地球上再造一个“迷你地球”,探求人类在这个现 代“南泥湾”之中自给自足,以及未来在月球或火星上建立生存空间的可能性。美国得克萨斯州的石油大王爱德 华·巴斯为此憧憬不已。 既然是自己动手写框架,我这里想借鉴一下生物圈(Biosphere)这个概念来描述一下。生物圈是指地球上所有 生态系统的统合整体,是地球的一个外层圈,其范围大约为海平面上下垂直约10公里。它包括地球上有生命存在 和由生命过程变化和转变的空气、陆地、岩石圈和水。从地质学的广义角度上来看生物圈是结合所有生物以及它 们之间的关系的全球性的生态系统,包括生物与岩石圈、水圈和空气的相互作用。 从1984年到1991年,巴斯个人出资2亿美元,在美国亚利桑那州图森市以北的沙漠中建起了“生物圈2号”。生 物圈2号占地13000平方米,仿佛一个巨大的温室,雨林、沙漠、草原和海洋应有尽有。“生物圈1号”是我们生 活的地球,顾名思义,生物圈2号就是一个“迷你地球”。这不就是我们所憧憬的框架吗? 一个好的框架,实质上就是一个生态圈。为什么同样做论坛,discuz能够带来运营圈子的繁荣,而普通的论坛很 难在社区经营的理念上有所超越。这就是框架生态圈。 作为框架的构建者,我们也深知这个道理,依靠框架做所有的事情,第一是从时间精力上不可能的;第二是没有 足够的能力与水平做所有的事情;第三,普适性与专业性方面也不能做到良好的平衡;第四,技术的发展变化是 那么快,不能快速适应变化,就只能被淘汰。因此,从框架设计初始,我们就没有想着做一个多么完善、全面的 平台,更多的是从体系化方面考虑,构建一个生态圈,由所有喜欢我们的框架,支持这个框架的程序员来来共同 加入生态圈,最终做到越用越强,越用越好用的效果! 生态圈有公有生态圈和私有生态圈的概念。公有生态圈就是在整个互联网下的框架生态圈,而私有生态圈就是企 业或组织内部构建的生态圏。作为我们自己动手写的框架,Tiny生态圈由若干核心接口组成,这些接口,有的有 实现,有的没有实现(需要后续进行扩展)。 我们所打造的生态圈,实现Tiny各种扩展只要利用Tiny模板工程创建自己的工程,然后进行简单的实现即可。使 用时只要在自己的工程中通过Maven引用或Jar包引用即可。强烈推荐采用Maven作为依赖及工程组织管理。通 过构建公司级Tiny生态圈,可以方便的在公司组进行全面的复用与资产积累,当然也可以复用整个互联网上Tiny 生态圈中的资源,只不过是在Pom文件中增加一个Maven依赖即可。Tiny生态圈的组成Tiny生态圈由生产者和 消费者两部分组成,生产者生产Tiny组件或模块,消费者则使用这些组件或模块。生产者可以提供开源或非开源 的组件,而消费者免费使用开源软件付费使用商业组件,并通过提供反馈促进组件完善来反过来支持生产者。最 终达成一个互利互惠的良性循环。 第 5 章 生态圈的建立 | 29 66 量身定制规则引擎,适应多变业务场景量身定制规则引擎,适应多变业务场景 规则引擎适合于做业务规则频繁变化的场景,我们的业务在应用过程中,也经常要处理大量的业务规则,当 然,也希望能有一套规则引擎来支撑,这样是再好不过的。 对一些常用的商业规则引擎做一下了解,感觉非常不 错,但是太贵了。看一些开源的引擎吧,也不错,但是感觉相对于我们自己这么简单的需求,太复杂了。 于是就想着自己做个,试试看能不能解决了自己的这些简单的业务规则频繁变化的业务场景,嗯嗯,脑子里大概 过了一下电影,感觉路是通的,主要有如下业务需求: • 业务规则执行器需要支持多种,也应该支持业务人员自行扩展,原因是我自己设计的业务规则再完美,也不 可能完美的适应所有人的胃口,所以这个默认可以有支持的,但是一定是可以扩展的 • 业务规则要支持优先级,也就是说有的业务规则先执行,有的业务规则后执行 • 业务规则允许排他规则,也就是说只要执行到排他规则,就可以马上结束 • 业务规则可以允许重复执行,这样才可以方便的进行循环处理 • 在规则引擎中,可以方便的使用Spring中的业务对象 于是就可以开始设计了: 规则执行器接口规则执行器接口 由于业务规则执行器需要支持扩展,当然需要设计一个接口了: /** * 规则执行器,可以有多种实现 */ public interface RuleExecutor { /** * 返回执行器类型 * * @return */ String getType(); /** * 执行规则,并把结果放到上下文上 * * @param context * @return 返回条件是否成立 */ boolean execute(Context context, T rule); } 第 6 章 量身定制规则引擎,适应多变业务场景 | 31 一共就两方法,getType用来返回规则执行器的类型,以确定它是解决哪种类型的规则的。 execute方法用来执 行规则,执行的结果是一个布尔值,表示此条规则是否有执行。 规则引擎接口规则引擎接口 接下来就是设计规则引擎的接口了: public interface RuleEngine { /** * 对指定上下文执行指定类型的规则 * * @param context * @param ruleSetName */ void execute(Context context, String ruleSetName); /** * 添加一组规则 * * @param ruleSet */ void addRules(RuleSet ruleSet); /** * 删除一组规则 * * @param ruleSet */ void removeRules(RuleSet ruleSet); /** * 添加规则执行器列表 * * @param ruleExecutors */ void addRuleExecutors(List ruleExecutors); /** * 添加一个规则执行器 * * @param ruleExecutor */ void addRuleExecutor(RuleExecutor ruleExecutor); 第 6 章 量身定制规则引擎,适应多变业务场景 | 32 /** * 删除规则执行器列表 * * @param ruleExecutors */ void removeRuleExecutors(List ruleExecutors); /** * 删除一个规则执行器 * * @param ruleExecutor */ void removeRuleExecutor(RuleExecutor ruleExecutor); /** * 设置一批规则执行器 * @param ruleExecutors */ void setRuleExecutors(List ruleExecutors); } 如上面的代码一样,还是非常简单的。 execute用来执行一个规则集,其它的方法就是对规则集和规则执行器的 管理,只要看一遍就一清二楚了。 规则集对象规则集对象 @XStreamAlias("rule-set") public class RuleSet { /** * 同种名称的规则集会自动合并 */ @XStreamAsAttribute private String name; @XStreamImplicit private List rules; public String getName() { return name; } public void setName(String name) { 第 6 章 量身定制规则引擎,适应多变业务场景 | 33 this.name = name; } public List getRules() { if(rules==null){ rules = new ArrayList(); } return rules; } public void setRules(List rules) { this.rules = rules; } } 规则集就两属性,一个属性是规则集的名称,另外一个属性就是一组规则。规则集的名称用来表示一组相关的业 务规则。 规则抽象类规则抽象类 根据上面的业务需求,抽象类Rule的结构如下: 第 6 章 量身定制规则引擎,适应多变业务场景 | 34 它里面只有基本的几个属性:优先级,标识,是否排他,是否允许重复,描述,标题,类型,有效性。 说好的业 务规则呢,怎么描述? 由于不同的规则执行器,它可以支持的规则也不一样,因此这里的规则抽象类只有基本的一些属性,怎么样描述 规则由其子类决定。 MVEL方式的规则及其执行器 Mvel规则 /** * 采用MVEL表达式作为条件实现 * @author yancheng11334 * */ @XStreamAlias("mvel-rule") public class MvelRule extends Rule{ //匹配条件 private String condition; //后续操作 private String action; public String getCondition() { return condition; } public void setCondition(String condition) { this.condition = condition; } public String getAction() { return action; } public void setAction(String action) { this.action = action; } public String getType(){ return "mvel"; } public String toString() { return "MvelRule [condition=" + condition + ", action=" + action + ", type=" + getType() + ", id=" + getId() + ", priority="+ getPriority() +", multipleTimes="+isMultipleTimes()+",exclusive="+isExclusive()+"]"; } 第 6 章 量身定制规则引擎,适应多变业务场景 | 35 /** * 验证mvel规则的合法性 */ public boolean isVaild() { if(StringUtil.isEmpty(getCondition())){ throw new RuntimeException(String.format("规则[%s]的匹配条件为空", getId())); } if(StringUtil.isEmpty(getAction())){ throw new RuntimeException(String.format("规则[%s]的后续操作为空", getId())); } return true; } } 上面表示,这个规则的类型都是mvel,这个规则包含了两个属性:condition和action,condition表示条件,只 有条件执行结果为真的时候,才执行action中的处理。 Mvel规则执行器Mvel规则执行器 public class MvelRuleExecutor implements RuleExecutor{ private EL el; public EL getEl() { return el; } public void setEl(EL el) { this.el = el; } public String getType() { return "mvel"; } public boolean execute(Context context, MvelRule rule) { try{ if(executeCondition(rule.getCondition(),context)){ executeAction(rule.getAction(),context); return true; }else{ return false; } 第 6 章 量身定制规则引擎,适应多变业务场景 | 36 }catch(RuntimeException e){ throw e; }catch(Exception e){ throw new RuntimeException("Mvel规则引擎执行器发生异常:",e); } } /** * 判断条件是否匹配 * @param condition * @param context * @return */ protected boolean executeCondition(String condition,Context context){ try{ MvelContext mvelContext=null; if(context instanceof MvelContext){ mvelContext=(MvelContext) context; }else{ mvelContext=new MvelContext(context); } return (Boolean)el.execute(condition, mvelContext); }catch(Exception e){ throw new RuntimeException(String.format("条件[%s]匹配发生异常:", condition),e); } } /** * 执行条件匹配后的操作 * @param action * @param context */ protected void executeAction(String action,Context context) { try { MvelContext mvelContext = null; if (context instanceof MvelContext) { mvelContext = (MvelContext) context; } else { mvelContext = new MvelContext(context); } el.execute(action, mvelContext); } catch (Exception e) { throw new RuntimeException(String.format("后续操作[%s]执行发生异常:", action), e); } 第 6 章 量身定制规则引擎,适应多变业务场景 | 37 } } execute方法的意思是:如果执行条件表达式且返回真,那么就执行action中的处理,并返回true,否则就返回f alse。 呵呵,这个逻辑也太简单了。对,tiny框架的一大特点就是用非常简单的逻辑来实现相对复杂的处理。 Mvel上下文Mvel上下文 前面讲到,要方便的在表达式中调用Spring中托管的对象,这个的实现就要从上下文上作文章了: public T get(String name) { if(context.exist(name)){ return (T)context.get(name); }else{ //必须保存到上下文,否则每次返回不一定是同一个对象(scope可能是属性) T t = (T)beanContainer.getBean(name); context.put(name, t); return t; } } 主要的逻辑在上面,也就是说:如果上下文中有对像,那么就从上下文中取;如果没有,那么就从Spring容器中 取。呵呵,这么高大上的功能,实现起来也这么简单。 规则引擎实现类规则引擎实现类 到上面为止,相关的准备工作都就绪了,规则引擎的实现类也可以现身了。其实这个类不贴吧,看文章的同学们 一定说我藏着掖着,但是贴出来吧,真的没有啥技术含量: public class RuleEngineDefault implements RuleEngine { private Map> ruleSetMap = new ConcurrentHashMap>(); private List ruleExecutors = null; private Map ruleExecutorMap = new ConcurrentHashMap(); protected static Logger logger = LoggerFactory .getLogger(RuleEngineDefault.class); public void execute(Context context, String ruleSetName) { List ruleSet = ruleSetMap.get(ruleSetName); if (ruleSet != null) { 第 6 章 量身定制规则引擎,适应多变业务场景 | 38 Vector newSet = new Vector(ruleSet); processRuleSet(context, newSet); } } private void processRuleSet(Context context, Vector newSet) { //如果没有后续规则,则退出 if (newSet.size() == 0) { return; } Rule rule = newSet.get(0); RuleExecutor ruleExecutor = ruleExecutorMap.get(rule.getType()); if (ruleExecutor != null) { boolean executed = ruleExecutor.execute(context, rule); if (executed) { //如果 if (rule.isExclusive()) { //如果条件成立,则是独占条件,则直接返回 return; } else if (!rule.isMultipleTimes()) { //如果不是可重复执行的规则,则删除之 newSet.remove(0); } } else { //如果不匹配,则删除之 newSet.remove(0); } } else { throw new RuntimeException("找不到对应" + rule.getType() + "的执行器"); } processRuleSet(context, newSet); } public void addRules(RuleSet ruleSet) { List rules = ruleSetMap.get(ruleSet.getName()); if (rules == null) { rules = new Vector(); ruleSetMap.put(ruleSet.getName(), rules); } //检查规则 for(Rule rule:ruleSet.getRules()){ if(rule.isVaild()){ rules.add(rule); }else{ logger.logMessage(LogLevel.ERROR, String.format("规则[%s]检查无效.", rule.getId())); 第 6 章 量身定制规则引擎,适应多变业务场景 | 39 } rule.isVaild(); } Collections.sort(rules); } public void removeRules(RuleSet ruleSet) { List rules = ruleSetMap.get(ruleSet.getName()); if (rules != null) { rules.removeAll(ruleSet.getRules()); } } public void setRuleExecutors(List ruleExecutors) { this.ruleExecutors = ruleExecutors; for (RuleExecutor ruleExecutor : ruleExecutors) { ruleExecutorMap.put(ruleExecutor.getType(), ruleExecutor); } } public void addRuleExecutor(RuleExecutor ruleExecutor) { if (ruleExecutors == null) { ruleExecutors = new ArrayList(); } ruleExecutors.add(ruleExecutor); ruleExecutorMap.put(ruleExecutor.getType(), ruleExecutor); } public void addRuleExecutors(List ruleExecutors) { if(ruleExecutors!=null){ for(RuleExecutor ruleExecutor:ruleExecutors){ addRuleExecutor(ruleExecutor); } } } public void removeRuleExecutors(List ruleExecutors) { if(ruleExecutors!=null){ for(RuleExecutor ruleExecutor:ruleExecutors){ removeRuleExecutor(ruleExecutor); } } } 第 6 章 量身定制规则引擎,适应多变业务场景 | 40 public void removeRuleExecutor(RuleExecutor ruleExecutor) { if (ruleExecutors == null) { ruleExecutors = new ArrayList(); } ruleExecutors.remove(ruleExecutor); ruleExecutorMap.remove(ruleExecutor.getType()); } } 一大堆维护规则和规则执行器的代码就不讲了,关键的几个讲下: public void execute(Context context, String ruleSetName) { List ruleSet = ruleSetMap.get(ruleSetName); if (ruleSet != null) { Vector newSet = new Vector(ruleSet); processRuleSet(context, newSet); } } 查找规则集,如果能找到就执行规则集,否则啥也不干。 private void processRuleSet(Context context, Vector newSet) { //如果没有后续规则,则退出 if (newSet.size() == 0) { return; } Rule rule = newSet.get(0); RuleExecutor ruleExecutor = ruleExecutorMap.get(rule.getType()); if (ruleExecutor != null) { boolean executed = ruleExecutor.execute(context, rule); if (executed) { //如果 if (rule.isExclusive()) { //如果条件成立,则是独占条件,则直接返回 return; } else if (!rule.isMultipleTimes()) { //如果不是可重复执行的规则,则删除之 newSet.remove(0); } } else { //如果不匹配,则删除之 newSet.remove(0); } } else { 第 6 章 量身定制规则引擎,适应多变业务场景 | 41 throw new RuntimeException("找不到对应" + rule.getType() + "的执行器"); } processRuleSet(context, newSet); } 执行规则集的逻辑是: 如果规则集合中没有规则了,表示规则集已经执行完毕,直接返回。否则获取优先级最高 的规则,首先检查是否有对象的规则执行器,如果没有,则抛异常。如果有就开始执行。 如果执行返回true,说 明此规则被成功执行,则判断其是否是排他规则,如果是,则返回;否则检查是否是可重复执行规则,如果是则 返回继续执行,否则把此条规则删除,继续执行下一条规则。 示例示例 这里假定做一个计算个人所得税的规则实例 定义规则定义规则 3500 && salary<=5000]]> 5000 && salary<=8000]]> 8000 && salary<=12500]]> 12500 && salary<=38500]]> 38500 && salary<=58500]]> 第 6 章 量身定制规则引擎,适应多变业务场景 | 42 58500 && salary<=83500]]> 83500]]> 编写TestCase public void testFeeRule(){ Context context = new MvelContext(); context.put("fee", 0.0); context.put("salary", 1000); ruleEngine.execute(context, "feerule"); assertEquals(0, context.get("fee")); context.put("salary", 4000); ruleEngine.execute(context, "feerule"); assertEquals(15.0, context.get("fee")); context.put("salary", 7000); ruleEngine.execute(context, "feerule"); assertEquals(245.0, context.get("fee")); context.put("salary", 21000); ruleEngine.execute(context, "feerule"); assertEquals(3370.0, context.get("fee")); context.put("salary", 40005); ruleEngine.execute(context, "feerule"); assertEquals(8196.50, context.get("fee")); context.put("salary", 70005); ruleEngine.execute(context, "feerule"); assertEquals(17771.75, context.get("fee")); context.put("salary", 100000); ruleEngine.execute(context, "feerule"); assertEquals(29920.00, context.get("fee")); 第 6 章 量身定制规则引擎,适应多变业务场景 | 43 } 看到这里的时候,我唯一的想法是:啥时我才可以一个月缴3万块的税呀。 总结 呵呵,按照Tiny惯例,传上代码 统计数据: 至此,一个简单的规则引擎就实现了,总共代码行数不包含注释为:462行。可以较好的适应各种简单的业务逻 辑频繁变化的业务场景。 第 6 章 量身定制规则引擎,适应多变业务场景 | 44 77 关于框架体系与战术的思考关于框架体系与战术的思考 什么是框架? 这个问题实际上许多“做框架”的人也不明白。 框架和库的本质不同在于: • 框架考虑的是机制的复用,而库主要考虑的是代码的复用 • 框架考虑的是在机制不变的情况下进行扩展,而库则基本不考虑扩展方面的问题 • 框架本身是不完整的,在大多数的情况下它自己是干不了啥事情的,而库自身是完整的,可以解决某个领域 的问题。 • 框架是活的,通过不断的扩展与衍生,它就更加强大,而库而是死的,发布时是怎样,就是怎样。 当然,关于这两货之间的比较,还有许多个角度,但我个人觉得本质是我上面举的这些。 设计的时候应该考虑哪些问题?设计的时候应该考虑哪些问题?这个问题的答案,如果用一句话来解符号,那答案就是:要仔细考虑使用这个框 架的人感受,要考虑如何让使用者感觉爽的问题。当然如果是三天两天说不清楚的方式,那就得从方法论,问题 领域,设计原则等待进行阐述了。 怎样才是一个及格的框架?怎样才是一个及格的框架?这个问题,用一句话解释,就是在满足上一个问题的情况下不违背基本设计原则,那 么就可以算是一个及格的框架。如果用三天两天说明,那就要把所有的基本设计原则拿出来,一个个讲讲清 楚,一个个说说明白。 如果满足了第一个条件,使用者是满意的,从使用来说是不错的;如果满足了第二个条件来说,从设计及实现来 说是不错的。如果两个都满足了那说明,最起码是可用的及格的框架,当然也可能得分更多。 开源框架的体系与战术开源框架的体系与战术 顺便,今天还谈谈框架的体系与战术性角度,一家之言,说得不对还请海涵。 从军事上来说,战术性的战斗就是具体的一块无关紧要的战斗,胜就胜了,败就败了,局部互有长短,但是就全 局来说没有本质的影响;而体系性的则是影响双方势力对比的重要关键点,是个死去活来的问题,是西风压倒东 风还是东风压倒西风的问题。 做框架也是有同样的问题的,如果框架只能解决一个局部部分,或者说具体问题,那么就是战术性框架;比如一 个Xml解析器,一个网络爬虫框架等等,都是解决具体问题的,因此不管做得怎么到位,怎么好,都是一个战术 性的问题。 那么我再拿人们常说SSH框架来说,很明显Hibernate和Struts是属于战术性框架的。但是Spring则明显不 同,表面上Spring本身只是提供了一系列的机制和体系,但是它本身并不做具体的某个方面的问题,他解决IO C,AOP之类的比较虚的问题,但是正因为如此,它占据了整个开源框架的核心位置,许许多多的别的框架都是非 常容易被替换及剔除的,但是唯一的要剔除Spring就比较困难--或者说,剔除Spring,就需要找一个比Spring 更好的方案来替代,没有找到之前,就很难真正剔除它。 第 7 章 关于框架体系与战术的思考 | 46 偶也看了OSC上一些同学的框架的源代码,从解决具体的问题来说,技巧、慎密性、前卫性都不错,但是看来看 去,感觉还是在战术性问题上打转转,也就是解决具体问题方面做得非常不错,但是在体系性方面就比较弱了。 在此,我也把自己对框架(Framework)的理解和大家交流,我的理解Framework一定是Framework框架者构建 的结构性、体系性、机制性的部分,而让使用者提供实际的、业务的、具体的实现的部分;当然Framework构建 者也可以提供一些实际的、业务的、具体的实现的部分,但是这只可以作为默认的、基本的实现,它在大多数的 情况下都是够用的,但是在特殊情况下是可以被拿掉的,是可以被替换的。 也就是说,如果你达到上面要求,你才可以说是一个框架(Framework),否则,你只是个Library,只是一个代 码库,不能称之为一个框架。 可能有的同学们又说了,你呼扯海说了半天,你现在设计的框架怎么就是有体系性的了??这正是我下面要解释 的问题: 1. 通过大量的体系性上思考,它不仅立足于解决开发问题,还考虑集成、发布、维护方面的问题。 2. 构建了许多子框架,比如:流程框架,插件框架、UI框架等等,这些框架只有体系和机制及规范方面的内 容,本身是不提供具体功能的,但是业务开发人员可以基于其之上进行扩展,来达成各种目标。 3. 在在实现方面大都考虑有侵入性及无侵入性,也就是说如果你可以接受侵入性,那你就做起来更方便;如果 你不接受侵入性,那么也可以使用Tiny框架的许多功能。 4. 在集成方面下的功夫是最大的,可以方便实现自组装,也就是扔进去不用管的模式。 5. 在模块化方面也是投入大量的力量,所有的资源都可以打入Jar包,不必修改web.xml就可以进行各种Web 模板的加载等待。 6. 战略性目标是构建一个生态圈,做UI的,做逻辑的,做业务的能够做自己擅长的事情,通力协作。 所以,正因为Tiny框架做了许多体系性的工作,可能不能直接实现某个功能,但是它的作为体系在开发、协 作、维护、支持各个阶段。 当然,我们正在设计的框架中也包含了大量的解决实际问题的库和框架,同时也不拒绝各种开源框架的集成与使 用。 因此,大的开发框架是个体系性的工程。所以,做开源框架之前,先要定位准确,是做战术性的还是体系性的框 架,这样只做自己擅长,可控的事情,才得心应手,轻松愉快,同时又可以获得最大回报。 第 7 章 关于框架体系与战术的思考 | 47 88 高屋建瓴,理念先行高屋建瓴,理念先行 《史记·高祖本纪》:“地势便利,其以下兵于诸侯,譬犹居高屋之上建瓴水也。”这里用到了高屋建瓴这个 词。意思是把瓶子里的水从高层顶上倾倒。比喻居高临下,不可阻遏的形势。现指对事物把握全面,了解透 彻。此典故于汉高祖刘邦欲杀功臣韩信,大夫田肯进言到"陛下牢牢地控制着三秦(关中),陛下利用这雄险的地 势,来控制、驾御诸侯,就如从高高的屋脊上把水从瓶子里倒下去。”以此来表彰韩信的功劳,于是,刘邦赦免 了韩信,只是将他降为淮阴侯。 同样,设计企业框架,也要对事物把握全面,了解透彻。我们设计框架的时候,考虑了以下理念。 1. 使用灵活1. 使用灵活 可以整个使用它,也可以只用它的一个或几个部分。一个完整的框架可能需要有许许多多个部分组成,但是对于 实际应用的用户来说,它可能只需要其中的一部分功能。构架一定要有这种能力,可以由使用者进行点菜式,使 用,避免只要用一点点功能,就要引入许许多多的内容。 2. 保持核心的稳定性2. 保持核心的稳定性 我们所设计的框架,是立足于在需要稳定、安全要求非常高的应用环境中使用的,因此其稳定性就是框架构建者 首要思考目标,核心部分只使用经过充验证及广泛应用的第三方包。在构建过程中,曾经复用了某饱受赞誉基金 会下的一个2.0版的开源包,结果在应用过程中出现了严重的内存泄露问题,最后不得已花费了非常大的工作量才 清理干净,这也更让我们对选择第三方包的审慎。 3. 学习成本低、上手容易3. 学习成本低、上手容易 框架的学习成本必须非常低,这样才可以让使用者更容易上手,避免由于学习难度大而导致的学习曲线太陡、太 长。经过许多次的实践,我们总结出来:有基础的JAVA开发人员经过半天的培训,就可以完全学会基于Tiny框 架进行开发,经过1天到两天的实践就会变成熟手。对框架进行扩展或基于框架扩展接口进行扩展,则需要3天左 右的培训,就可以完全掌握,经过一周的实践就会变成熟手。 4. 文档一体性4. 文档一体性 做一个好的软件,好的文档是必不可少的。而做软件过程中非常挠头的就是文档了,文档写得早了,后期变化过 程中又不对文件进行不断的调整,工作量非常大,稍不注意,又会导致文档与实际不对应;文档写得晚些吧,又 可能会流于形式。Tiny框架在构建之初就深刻的考虑这个问题。这个问题主要涉及几个软件的参与者:Tiny框架 扩展者:Tiny框架很多的时候都是一个体系而已,更多的内容需要后期进行扩展,这些扩展的内容的文档如何编 写,是需要考虑的问题。Tiny框架使用者:Tiny框架的使用者,主要使用Tiny框架来进行业务开发,他在开发过 第 8 章 高屋建瓴,理念先行 | 49 程中需要用于Tiny框架扩展者扩展的组件,他们期望看到这些组件的最新文档,同时又不希望看到许多与自己无 关的内容,同时在开发过程中,又不想到许多地方去查找这些文档,最后是在自己的开发环境就可以随时看到。T iny组件库管理者:当TIny框架扩展者把这些扩展组件添加到组件库之后,也希望能有一种方式,方便的展现这些 组件,方便Tiny框架的使用者选用。基于Tiny框架开发的业务系统:这些系统中有许多对外的服务,而服务需要 有文档说明,Tiny框架提供了这些服务文档的生成功能,这样就可以保证服务与服务文档的一致性。所以TIny框 架的构建者,在各种组件中都有相关文档描述的元数据,这样,不管是在工具还是在管理台,都可以方便的查 阅、导出这些文档信息。 5. 方便的外延性5. 方便的外延性 Tiny框架构建者也深深知道,自己不可能解决所有问题,所以,Tiny框架除了最小的核心之外所有的部分都是可 选的、可替换的。Tiny框架对于第三方包的使用也完全持开放态度,这个可以完全由使用者自行选择,不做任何 限制 6. 现有资产的可复用性6. 现有资产的可复用性 由于考虑到企业应用的场景,Tiny框架构建者也知道,不可能所有的项目都是从零开始的,一定有一些项目是在 现有基础上进行重新开发或者重构的,不管是哪种情况都需要把其中的一部分或者大部分复用起来,以充分降低 成本、利用企业现有资产。 7. 资产的可积累性7. 资产的可积累性 只有易于知识积累,才可以真正做到越用越强。Tiny框架正是基于上述观点,才提出一构建Tiny生态圈的概 念。不管是私有生态圈还是共有生态圈,都是体现一种众人拾柴火焰高的思维,一起来扩展、一起来复用。当然T iny框架也意识到光有口号是不行的,一定要有规范和机制进行保障,所以在Tiny框架中,许多地方都涉及到规 范、元数据等内容,以实现资产可积累、复用的目标。由于考虑到企业应用的场景,Tiny框架构建者也知道,不 可能所有的项目都是从零开始的,一定有一些项目是在现有基础上进行重新开发或者重构的,不管是哪种情况都 需要把其中的一部分或者大部分复用起来,以充分降低成本、利用企业现有资产。 第 8 章 高屋建瓴,理念先行 | 50 99 理想的开源框架与设计原则理想的开源框架与设计原则 理想的开源框架理想的开源框架 • 她应该是小的、简单的,满足Simple Is Beautiful • 她应该是成长性好的,随着不断的扩展,她可以越来越丰满 • 她应该是有良好工具支持的,为什么要花时间做工具可以完成的事情呢? • 她应该是自组装的,也就是尽可能的脱离配置,而是用一种依赖即可用,取消依赖即消失的全自动处理模式 • 她应该是模块化的,所有的内容都可以被打入jar包而作为一个整体进行发布,并且能支持热部署的,可以开 着车儿换轮胎的 • 她应该是支持水平部署的,想加服务器就加,想减服务器就减 • 她应该是有良好知识积累体系的,使得使用Tiny框架的人们越用越强,越用越爽 • 她应该是便于企业降低开发成本的,便于技术经理控制开发进度的,便于开发人员快速上手的 • 她应该是避免重复劳动的,所有软件参与者都不应该做重复的事情 • 她应该是自管理的,最好不要让程序员配置这个配置那个 • 她应该是让人有种"众里寻他千百度,蓦然回首,那人却在,灯火阑珊处”的开发框架 按照这个目标去设计按照这个目标去设计 • 虽然整体体量比较大,但是它的每个模块都分得非常小,因此非常容易掌握 • 它的各种组件都可以方便的进行扩展,通过扩展可以不断的提升系统的处理能力 • 它的工具已经非常强大,而且它还是变得更加强大。 • 不管是管理台还是过滤器、Servlet,不管是流程组件还是UI组件,还是UI组件包等等都是可以自组装的 • Web工程只是个集合,除了配置文件和Pom依赖,不应该有其它东西 • 支持水平扩展,同时可以支持7*24小时运行 • 开始团队由金字塔向哑铃型转变,高低水平者各司其职 • 绝大多数情况下,要做的只是依赖,而不需进行配置 第 9 章 理想的开源框架与设计原则 | 52 这样去打造框架——设计原则梳理这样去打造框架——设计原则梳理 1. COC原则1. COC原则 Tiny框架在设计时充分考虑此原则,凡是可以通过一定的约定来大大减少配置或开发量的,一般都会采用。所以 在Tiny框架的扩展、开发、配置过程中,会经常发现一些“潜规则”,如果利用好这些“潜规则”,会起起事半 功倍的效果。无标签评论用户图标: cskang蔡少康发表:Convention over Configuration(CoC)–惯例优于 配置原则简单点说,就是将一些公认的配置方式和信息作为内部缺省的规则来使用。例如,Hibernate的映射文 件,如果约定字段名和类属性一致的话,基本上就可以不要这个配置文件了。你的应用只需要指定不convention 的信息即可,从而减少了大量convention而又不得不花时间和精力啰里啰嗦的东西,配置文件很多时候相当的影 响开发效率。 Rails 中很少有配置文件(但不是没有,数据库连接就是一个配置文件),Rails 的fans号称期开发效率是 java 开发的 10倍,估计就是这个原因。Maven也使用了CoC原则,当你执行mvn -compile命令的时候,不需要指 源文件放在什么地方,而编译以后的class文件放置在什么地方也没有指定,这就是CoC原则。 2. DRY“不要重复自己”原则2. DRY“不要重复自己”原则 Tiny框架的构建者对于做重复的事情一向是深恶痛绝的,因此非常不原意开发人员在基于Tiny框架进行开发时出 现重复的内容。为此Tiny框架在设计上做了大量的工作,来避免程序员做违反DRY原则的内容,所以在Tiny框架 中,所有的工作内容都是做一次就足够。无标签评论用户图标:cskang蔡少康发表: DRY——Don't RepeatYourself Principle,直译为“不要重复自己”原则^_^ DRY简而言之,就是不要写重复的代码。原则本身很简单,但是,对于OOAD来说,有着非常重大的意义。 DRY利用的方法就是抽象:把共同的事物抽象出来,把代码抽取到一个地方去。这样就可以避免写重复的代码。 举一个DRY的典型例子,如果在一个类构造的时候,需要进行成员的初始化,在进行了某些操作以后,同样要进 行初始化,那么就可以把“初始化”抽象出来,做成一个方法Initial(),在构造和需要用到的地方调用它。 虽然,抽取重复代码是利用DRY的一个好的开端,但DRY的实质是,一个需求,用一个部分来完成。当你试图避 免重复代码的时候,实际上,你做的应该是用一段代码来完成一个需求。 为什么要用DRY原则?DRY会给代码维护带来很大的好处。以类的初始化为例,假设类修改了,增加、减少或是 修改了成员,如果不写Initial(),那么你可能至少要修改两处,而且,修改之处也可能出现不一致,维护成本大大 增加。而写了Initial()方法,那么只要集中修改Initial()就行了。 第 9 章 理想的开源框架与设计原则 | 53 既然DRY是关于“一个方法,实现一个需求”的,那么,是不是可以把DRY应用到需求分析中?呵呵,答案是肯 定的,而且,个人认为,这是个非常好的主意。多个重复的需求可能导致多个重复或者相近的类,最后导致重复 代码。所以DRY绝不仅仅对代码适用,它是一个广泛适用的原则。 3. SUBTRACTION减法原则3. SUBTRACTION减法原则 减法原则是我们自己提出的,意思就是给程序员做减法。一般来说越到底层的程序员,工作时间越短、技能越 弱、经验越少。但是实际工作当中,你会发现越到底层的程序员要做的事情越多,要用的技能也越多。Tiny构建 者认为,这也是现在程序员工作效率低、质量差的重要原因。因此我们认为应该给程序员做减法,越到底层的程 序员做的事情要越单一、越简单。 4. 下级服从上级原则4. 下级服从上级原则 可能有人在笑了,这个也算原则?确实,它就是原则,只不过原来下级服务上级是挂在嘴上的,而Tiny框架则从 框架层级做了限制,使得下级必须服务上级。这两点主要体现在流程及页面实现中,上级经理可以对下级完成的 工作内容进行强制性要求实现,不同的是流程是采用显式继承的方式,而页面是隐式继承的方式 5. 单一原则5. 单一原则 几乎每个熟悉Tiny框架的人可能会问同一个问题,那就是:为什么Tiny分了这么多的工程?Tiny框架的构建者也 深深的知道这样会增加相当的集成工作,但是这样做的好处是可以把单一原则进行强制性的约束,使得一个模块 只解决单一模块应该解决的问题,从而避免不同的问题放在一起解决所导致的胡子眉毛缕不清的问题,同时也避 免了不恰当的依赖及模板引用。 6. 模块化与自动组装原则6. 模块化与自动组装原则 一般的软件开发,整个集成过程都需要人员小心翼翼,要配这个配那个,稍不小心就会有这种那样问题的出现Tin y框架构建者也常常知道这一点给集成人员带来的困难及给软件开发测试带来的巨大的工作量,因此在整个Tiny框 架的构建过程中,都非常注重集成过程的自动组装,要求做到扔进去不用管,由框架自动集成。Tiny框架构建者 深深知道,模块化对于软件开发过程中开发、高度、集成、发布、维护过程中所起的作用及节省或花费的巨大成 本。因此提出了业务单元(Business Unit)的概念,使得与模块相关的所有内容都可以放在一起。 第 9 章 理想的开源框架与设计原则 | 54 7. 集中配置原则7. 集中配置原则 在Tiny构建者多年的工作过程中,被配置的问题所常常困扰,在开发期有许多问题是由于配置不当引起,在测试 在发布及升级过程中,也有相当多的问题是由于配置不当引起的。在Tiny框架我们对配置做了大量的工作,一个 是COC方式,如果不配,则采用系统默认的值;一个是集中原则:把需要人工需要配置的内容都集中起来统一配 置;一个是对于不需要人工干预的配置,那就集成在Jar包中,作为发布者发布项的一部分。 第 9 章 理想的开源框架与设计原则 | 55 1010 Web界面快速开发实践Web界面快速开发实践 下面是一些常用的链接,供大家使用: GIT地址:https://git.oschina.net/tinyframework/tiny 问题报告:https://git.oschina.net/tinyframework/tiny/issues 更多内容,请看本人博客,不一样的内容,一样的精彩! 在展示过程的同时,会把相关的知识做一个充分的介绍 。 一、寻找网站模板一、寻找网站模板 要做网站,不能没有模板,自己不会做网页设计,咋办?问谷歌找百度呗,找了一阵,看到下面这个模板不 错,就它了。 http://www.tooplate.com/zip_files/2042_the_block.zip 相仔细了解这篇文章的同学,建议把原板的下载下来,对比着看,会更有感觉。 二、开工制作二、开工制作 1.编写default.layout布局文件1.编写default.layout布局文件 位置:/default.layout 演示网站-${pageTitle} ${pageContent} 这个是标准布局了,直接贴过来,唯一要改的就是标题处加了“演示网站-”开头。 第 10 章 Web界面快速开发实践 | 57 里面引入的js和css是TinyUI引擎所独有的,主要处理JS,CSS顺序处理、合并、打包等相关,由于这个是框架内 部实现的部分,这里只是使用就不展开来叙述了。 ${pageContent} 这个标记了渲染替换的位置,一个layout文档中必须有且只能有一个(如果没有,你会发现不管 怎么写页面,内容都是布局的内容;如果有多个,你会发现页面中的内容会加多次)。 2.编写UI组件定义文件2.编写UI组件定义文件 位置:/demosite.ui.xml /css/jquery.lightbox-0.5.css,/css/slider_style.css,/css/tooplate_style.css /js/jquery.js,/js/jquery.lightbox-0.5.js,/js/swfobject.js 这里主要是定义用到的js和css,由于只是一个网站,就简单处理,只写一个了,实际应用当中,要根据用途和职 能不同定义为许多个UI组件包。 3.编写通用宏定义3.编写通用宏定义 位置:/common.component #macro a(title url) ${title} #end #macro img(url alt) ${alt} #end 第 10 章 Web界面快速开发实践 | 58 #macro link(url)#if(url.startsWith("/"))${TINY_CONTEXT_PATH}#end${url}#end #macro pageTitle(title page) #!set(pageTitle=title) #!set(activePage=page) #end 这面主要定义了4个宏,a是超连接的,img是图片的,link是把链接地址转换成绝对地址,pageTitle用于定义页 面的标题和当前页是哪个页面的。 4.编写业务宏4.编写业务宏 位置:/demosite.component #macro header()
#bodyContent
#end #macro siteTitle(title)) #end #macro menu() 第 10 章 Web界面快速开发实践 | 59
    #bodyContent
#end #macro menuItem(url title page)
  • ${title}
  • #end #macro tooplateWrapper())
    #bodyContent
    #end #macro tooplateMiddleSub()
    #bodyContent
    #end 第 10 章 Web界面快速开发实践 | 60 #macro tooplateMiddle()
    #bodyContent
    #end #macro flashGridSlider() #end #macro tooplateContent())
    #bodyContent
    #end #macro tooplateFooter()) #end #macro box220(class)
    #bodyContent
    第 10 章 Web界面快速开发实践 | 62 #end #macro box330(class)
    #bodyContent
    #end #macro gallery() #end #macro galleryBox(largeImage smallImage title alt info class) #end #macro postBos(comment title imageUrl imageAlt postedInList commentCount)

    ${title}

    #img(imageUrl imageAlt)

    Posted in #for(postedIn in postedInList) ${postedIn} #end

    #bodyContent ${commentCount} comments More
    #end 业务宏的定义,就是根据情况从美工制作的页面文件中抽取一些具有共性的内容,定义成一个宏,这样以后使用的时候,就只要使用有意义的宏而不是原来一堆一堆的Html标记。 5.定义演示网站布局文件 分析一下几个页面,都有页头,页脚,菜单,因此可以把这些共性的文件放在演示网站布局文件中: 位置:/page/default.layout [java] view plaincopyprint?在CODE上查看代码片派生到我的代码片 第 10 章 Web界面快速开发实践 | 64 #@tooplateWrapper() #@header() #siteTitle("The Block") #@menu() #menuItem("index.page" "Home" "Home") #menuItem("about.page" "About Us" "About") #menuItem("blog.page" "Blog" "Blog") #menuItem("gallery.page" "Gallery" "Gallery") #menuItem("contact.page" "Contact" "Contact") #end #end ${pageContent} #@tooplateFooter() Copyright © 2048 Company Name - Design: tooplate #end #end 第 10 章 Web界面快速开发实践 | 65 上面代码的函义是: 在整个页面中,上面放置Header,下面旋转Footer,中间放置内容 最后两段js,是说:对某指定选择器中的图片用lightbox去进行处理。 这里就把所有的通用的部分都抽取到布局文件中了。 6.定义相关页面6.定义相关页面 首页: #pageTitle("Home" "Home") #@tooplateMiddle() #flashGridSlider() #end #@tooplateContent()

    Welcome To The Block!

    #img("/images/tooplate_image_01.jpg" "image")

    The Block is a free website template from Tooplate. Credit goes to Free Photos for photos used in this template.

    Aliquam et augue et odio luctus posuere sit amet et nisi. Maecenas convallis, est sit amet convallis consectetur, elementum lacus, ut fermentum elit sem. Duis eu elit tortor, sed condimentum nulla. Phasellus varius posuere adipiscing. Mauris sodales dictum ullamcorper. Validate XHTML and CSS.

    #@box220()

    pharetra id turpisLorem Ipsum Dolor

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porta adi Piscing libero, eget elem ntum lectus varius sed.

    more #end #@box220()

    semper nisl ac nibhDonec Tincidunt Varius

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum eu mauris id neque porttitor.

    第 10 章 Web界面快速开发实践 | 67 more #end #@box220("rmb")

    consect adipiscing elitEtiam Gravida Sagittis

    Cras eu egestas sem. Aenean mollis feugiat massa, eget pharetra nunc interdum non. Etiam euismod sem ac sem tincidunt adipiscin.

    more #end #end 由于这个里面的数据是具体写的,因此就没有再进行抽象,如果这些数据是从数据库来的,那可以再进行一下抽 象,就可以用for循环直接搞定了。 关于: #pageTitle("About" "About") #@tooplateMiddleSub()

    About Our Company

    Sed tempus nunc dolor, sit amet aliquet ligula. Ut euismod lectus vel ligula euismod id porttitor tortor placerat. Aenean tincidunt magna sit amet turpis auctor sagittis. Phasellus aliquet augue nec elit lacinia et faucibus massa scelerisque.

    #end #@tooplateContent()

    Our Company Objectives

    第 10 章 Web界面快速开发实践 | 68 #img("/images/tooplate_image_01.jpg" "image")

    Morbi congue lorem sit amet odio iaculis tincidunt. Donec nibh, molestie nec pellentesque non, in diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra.

    Aliquam et augue et odio luctus posuere sit amet et nisi. Maecenas, est sit amet convallis consectetur, lacus ligula elementum lacus, ut fermentum elit sem et nisi. Duis eu elit tortor, sed condimentum nulla. Phasellus varius posuere adipiscing. Mauris sodales ullamcorper. Validate XHTML and CSS.

    #@box330("float_l")

    what our customers sayTestimonial

    Fusce nec felis id lacus sollicitudin vulputate. Sed vitae elit at diam vestibulum ullamcorper et nec quam. Aenean eit ut luctus sit amet, elementum quis enim. Proin tincidunt, arcu id pellentesque accumsan, neque dolor imperdiet ligula, quis viverra tellus nulla a odio. Curabitur vitae enim risus, at placerat turpis. Mauris feugiat suscipit tempus fringilla, felis in velit.

    Harry - Senior Webmaster
    more #end #@box330("float_r")

    what we doServices

    Lorem ipsum dolor sit amet, consectetur adipiscing elit eget elementum lectus varius sed.

    • Morbi quis lorem risus
    • Suspendisse cursus
    • Donec at viverra
    • Proin eget purus ante
    • 第 10 章 Web界面快速开发实践 | 69
    more #end #end 日志: #pageTitle("Blog" "Blog") #@tooplateMiddleSub()

    Our Blog Posts

    Vestibulum eleifend consequat laoreet. Pellentesque vel orci sapien. Duis lacus ipsum, pretium ut accumsan in, tempor nec mauris. Aenean accumsan placerat elit, sit amet faucibus ante commodo a. In et neque nibh, ac tristique dui.

    #end #@tooplateContent() #@postBos("20" "Lorem ipsum dolor sit amet" "/images/tooplate_image_02.jpg" "Image 02" ["Templates","Freebie"] "148")

    Vestibulum adipiscing tempus elit eu condimentum. Fusce at mi felis. Etiam sed velit nibh. Nunc bibendum justo elementum auctor. Donec at magna eu neque. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia.

    #end 第 10 章 Web界面快速开发实践 | 70 #@postBos("17" "Etiam gravida sagittis lacus" "/images/tooplate_image_03.jpg" "Image 03" ["CSS Templates","Web Design"] "128")

    Ellentesque vitae velit eu lectus rhoncus tincidunt. Phasellus dictum dignissim sapien et dapibus. Sed egestas consequat mauris, orci tincidunt sit amet. Donec pharetra porta ultrices. Sed sit amet lectus libero, at porttitor odio. Validate XHTML and CSS.

    #end #@postBos("10" "Aenean vitae velit eget" "/images/tooplate_image_04.jpg" "Image 04" ["Illustrations","Graphics"] "208")

    Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin gravida ornare mauris ac lobortis. Praesent elit neque, lacinia eget interdum eu. Phasellus posuere nisl et odio egestas ac tristique justo ultrices.

    #end #end 画廊: #pageTitle("Gallery" "Gallery") #@tooplateMiddleSub()

    Our Gallery

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porta adipiscing libero, eget elementum lectus varius sed. Aliquam metus urna, dignissim quis posuere at, posuere eget mauris. Vestibulum laoreet sodales tellus 第 10 章 Web界面快速开发实践 | 71 nec mollis. Validate XHTML and CSS.

    #end #@tooplateContent() #@gallery() #galleryBox("/images/gallery/image_01_l.jpg" "/images/gallery/image_01_s.jpg" "Nunc et tellus id risus ultrices" "Image 01" "Nunc et tellus id risus ultrices") #galleryBox("/images/gallery/image_02_l.jpg" "/images/gallery/image_02_s.jpg" "Nunc et tellus id risus ultrices" "Image 02" "Nunc et tellus id risus ultrices") #galleryBox("/images/gallery/image_03_l.jpg" "/images/gallery/image_03_s.jpg" "Nunc et tellus id risus ultrices" "Image 03" "Nunc et tellus id risus ultrices" "gb_rmb") #galleryBox("/images/gallery/image_04_l.jpg" "/images/gallery/image_04_s.jpg" "Nunc et tellus id risus ultrices" "Image 04" "Nunc et tellus id risus ultrices") #galleryBox("/images/gallery/image_05_l.jpg" "/images/gallery/image_05_s.jpg" "Nunc et tellus id risus ultrices" "Image 05" "Nunc et tellus id risus ultrices") #galleryBox("/images/gallery/image_06_l.jpg" "/images/gallery/image_06_s.jpg" "Nunc et tellus id risus ultrices" "Image 06" "Nunc et tellus id risus ultrices" "gb_rmb") #end #end 三、运行三、运行 首页 关于 第 10 章 Web界面快速开发实践 | 72 日志 第 10 章 Web界面快速开发实践 | 73 第 10 章 Web界面快速开发实践 | 74 画廊 联系我们 第 10 章 Web界面快速开发实践 | 75 四、总结四、总结 上面的完整示例展现了采用Tiny框架开发页面的完整过程,下面总结一下需要说明的一些内容: 第 10 章 Web界面快速开发实践 | 76 • 上面展示采用的模板引擎是TinyTemplate,当然也支持Velocity,但是推荐使用TinyTemplate,因为执行 速度更快、功能更强、更容易使用 • 布局支持多重嵌套,上面的示例中有两层布局,根上的解决js、css引入,标题,网站图标等部分的内容,/p age/目录中的解决网站的整体结构部分的内容,随着网站的复杂,可以做更多层的布局,使得很多页面共用 的部分都放在布局文件中 • xxx.ui.xml定义了UI组件包的内容及其依赖关系,UI引擎会自动根据ui组件包的定义对js及css进行引入、整 合、压缩。 • 整个页面只引入一个css和一个js文件,避免引入文件数太多导致的性能下降,同时提供了压缩,提升网络传 输效率(这个例子中的文件都已压缩,因此压缩率不高)。 • page文件是用来编写展示内容的页面,在显示.page文件时,有两种方式,一种是.pagelet方式,一种是.p age方式,区别在于用.page方式访问时,会渲染布局,而pagelet方式不会渲染布局,适合于Ajax方式使 用。 • 整个网站在重构完成之后,没有一段内容是重复的,真正做到Tiny框架所说的DRY原则。 • 所有对上层布局文件的修改都会对所有下层页面产生影响,真正做到Tiny框架所说的下级服从上级原则。 • 越到底层的开发人员接触的越少,真正的页面编写文件,只需要从控制层转过来的数据再利用宏去显示内容 即可,可以避免接触js,css,html等相关内容。(这一点在示例中还没有做到,毕竟示例是一个静态网站),真 正做到Tiny框架所说的减法原则,越到下面会的技能越少。 • 实际上框架也支持某个页面不服从上层布局的限制,但是我们不推荐这么做,因此这里没有展示这种用法。 采用Tiny框架制作前台,需要考虑好如下角色的协作: • 美工:用于进行界面设计,页面切分 • UI组件包开发工程师:根据功能特性,把具有不同功能特性的js,css,image等放在一个jar包中 ,并编写对应 的xxx.ui.xml文件,并设定好依赖关系,如果需要还需要编写公共的宏文件,用于方便别人使用,并隔离功 能与具体的实现,使得后续的开发工程师尽量少的接触css,js。 • 业务组件开发工程师:根据功能特性,把页面中的一些比较业务化的,封装成业务组件,最终暴露的接口是 一个名字及一些数据的输入,使得最终的界面开发工程师尽可能少的接触原生的html。 • 界面开发工程师:不关心界面展现的具体技术,利用UI组件包及业务组件开发工程师开发的组件,再加上控 制层传过来的数据来编写最终的展现页面。 通过上面的分工,使得不同的开发人员关注于不同的技术细节,从而最大化的提升最终界面开发工程师的开发效 率,同时因为有了一定的封装性,可以使得底层的变化不致于影响上层开发人员的工作成果。 第 10 章 Web界面快速开发实践 | 77 1111 软件开发杂谈软件开发杂谈 杂谈之一:技术只是成功的一点点基础条件,真正还是得靠做人杂谈之一:技术只是成功的一点点基础条件,真正还是得靠做人 话说,有位lianzi同学,水平不错,思想超前,签约阿里现在在百度实习,以前因为喷我的贴又没有啥理由,因此 告诉他离我远一点,但是最近他又回到我群里了,一直伸个大拇指,我说啥他都是大拇指,觉得怪怪的,总不是 那么个感觉,终于憋了一段时间,又恢复了正常的沟通方式,聊天实录: 【传说】杭州-悠然 18:31:13 lianzi本色终于出来了。 【传说】杭州-悠然 18:31:30 我学得这样才是你自己,你天天伸个大拇指,我都觉得不像你了。 【活跃】lianzi(756215798) 18:32:17 哈哈哈,还好,还好 【传说】杭州-悠然 18:32:52 活个本性挺好的,有时碰一下大家也理解的。 碰完了继续哥儿俩好不就可以了。 【活跃】lianzi(756215798) 18:37:22 是的 杂谈之二:让谁“爽”的问题杂谈之二:让谁“爽”的问题 看产品经理的ppt,里面有下面的一段话: 做“产品”,不外乎“要想自己爽,先让别人爽”。 永远站在用户的角度考虑问题。 细节、细节、还是细节。 根据实际情况排定优先级比确定功能更重要。 深以为然,在做Tiny框架中,框架组做Eclipse插件的同学其中做一个功能是执行器,他的方案是:开个首选 项,然后由开发人员在里面配置啥种类型的文件由哪个类去执行。于是我问,如果有好多个执行器,开发人员不 就配死了?于是他做了个功能扩展,增加一个批量导入功能,可以批量导致了。于是我问如果有100个项目,100 个开发人员,有100种 执行器,不同的项目需要的执行器又不一样,是不是就得配100次配置文件,然后花大量的 成本去分发这个配置文件,还得让程序员花大量的时间去导入这个配置文件??关键是随着项目的不断变化,用 的执行器是可变的,那么上面的这个过程就得不断进行,还涉及到一个版本维护的问题,比如有的人导入了新 的,而有的人还是旧的。这样综合起来得投多少人力物力和管理成本? 我给的方案是:在开发执行器时配置一个 执行器xml定义文件,然后工程去扫描当前项目中的执行器xml定义文件,于是工具开发人员只开发一次,每个执 行器开发人员只配置一次,真正的使用者,啥也不用管,随时都是最新可用,0工作量。 两个方案对比,工具开发者工作量小了,执行器开者工作量大了可以忽略的一点点,最终使用者,节省了大量的 工作量,关键是不会让他们觉得使用麻烦,且不会出错。 第 11 章 软件开发杂谈 | 79 杂谈之三:让程序抛错还是让程序“正确”执行?杂谈之三:让程序抛错还是让程序“正确”执行? 龙振东同学,一直在使用TinyDbRouter,也发现了里面的一些BUG,也提了许多的改进建议。由于他是把代码 拉到本地在本地改的,我建议他直接fork我们的代码,并在修改之后pull request给我们,这样,对两方都有好 处。 其中涉及到一个问题,他在QQ上问我如何处置:有些非标准SQL,SQL解析器不支持,他建议(实际上他 前期就是这么做的)在出现不支持的SQL异常的时候,改由读写分离方式去执行。 由于当时在开车,是在电话里 和他沟通的,因此就没有聊天实录了,我直接敲字敲上来吧。 悠然:由于出现了SQL解析异常,说明这个时候SQL是不标准的,有可能是适用于读写分离,有可能适用于分库分表,你不管采用哪一种方式进行处理,总有一种情况是用“错误”的方式去执行的,这样就会导致出现非用户期望的结果--而且这个时候,用户得到了看起来正常的结果--因为没有异常和错误发生,但是实际上结果是不正确的。这种处理结果比抛异常直接告诉他不支持这个功能严重得多得多,会直接害死你、害死你的老板、害死你的客户。 所以,请直接抛异常,而不是改成前面的处置方式。如果这个SQL对你非常重要,那唯一正确的办法是扩展SQL解析器,使之支持。你觉得怎么样? 龙振东:然。 他很快就完成并提交给我,下面是沟通实录: 【传说】杭州-悠然 20:10:03 以后就直接在我们工程上改吧。 这样就可以一起共享了。 今天我给你电话里讲的原则,在工作中一定注意了。 否则你给捅大搂子的要:) 【话唠】龙振东(593038106) 20:11:37 一些有争议的地方我都会先提出来讨论 【传说】杭州-悠然 20:11:54 嗯嗯,我给你讲个故事吧。 我们这边有个非常牛X的人。 看到另外一个人写的程序有个问题:就直接反编译然后改了就弄上去了,结果问题确实没有了。 他也不和别人说这个事儿,结果后来升级的时候一搞,这个修改丢失了。 结果出了非常大的乱子。 又有一次,他又和另外一个程序做对接,结果他想获得人家内部的数据。 【冒泡】杭州-cwl(150326161) 20:14:07 我感觉说的是我。。。 【传说】杭州-悠然 20:14:16 人家里面的数据是private的,他改改访问控制,然后就访问到了人家的private数据。 然后他得意的爽得不行。 【话唠】龙振东(593038106) 20:14:51 后来呢 【传说】杭州-悠然 20:14:55 结果过了一段时间,又他妈的出大问题了,原来人家把private的对象改名了。 还有一次,他又是修改访问设置,访问了人家的私有方法,这次啥也没改,结果又他妈的不行了。 死活无法跑,结果这牛叉人物到现场,跑北京搞了好几天,终于查清了,原来是在Oracle JDK可以突破安全访问私有方法,但是在AIX下的IBM JDK突破不了了。 第 11 章 软件开发杂谈 | 80 所以:千万不要耍小聪明,会吃大亏的。 在计算机领域一定要严谨,要按常规的正常途径来解决问题。 杂谈之四:再论缓冲相关代码的演变杂谈之四:再论缓冲相关代码的演变 本人写过一篇关于缓冲方面的文章,可以通过点击上面的链接去查阅。有许多人回复,有些人觉得不错,有的人 觉得不好,各说各有理。 其实计算机领域当中,解决一个问题,可以有N种方案,有时他们的差别非常小,这个 时候就需要仔细斟酌了。 这不,周末大家又问了: 【活跃】lianzi(756215798) 10:03:18 早,现在不知道看什么技术文章啦,昨晚睡觉前看了篇悠然和hasor的博客 就是那篇缓存重构调优的 逻辑是很清楚的,至于最优解,鬼知道 【传说】杭州-悠然 10:05:45 下面不是被喷了么:) 【活跃】lianzi(756215798) 10:07:19 好吧,那最后的优化方案是什么呢?实际效果如何呢? 【传说】杭州-悠然 10:07:40 实际,我们用得感觉还不错。 maven插件? 【传说】杭州-悠然 10:07:58 关键是避免程序员参与缓冲方面的事情。 由于通过Maven插件动态嵌入代码,因此性能方面也非常有保障。 【活跃】lianzi(756215798) 10:08:52 这个说法,我觉得,要看场景 如果小弟不怎么给力是好事,但是程序员在调试代码的时候怎么办?至少内置个http server 有个缓存的dashboard 你觉得呢? 【传说】杭州-悠然 10:09:46 其实你可以理解成一种AOP处理。 程序调试代码,就是全部从真实数据库取数据呀。 又没有任何影响,用Maven命令处理过,只是通过缓冲加速了而已。 【活跃】lianzi(756215798) 10:10:57 我的意思是这样子的,缓存应不应该对程序员透明 【传说】杭州-悠然 10:11:28 我们的选择是透明。 【活跃】lianzi(756215798) 10:11:39 认同 【传说】杭州-悠然 10:11:48 文章中说了诸多好处,尤其是一种缓冲方案换成另外一种缓冲方案的时候。 关键是避免程序员参与缓冲方面的事情。 【活跃】lianzi(756215798) 10:12:02 是,逻辑清晰 【传说】杭州-悠然 10:12:07 选择透明的方式,只要架构师或高程完成就好了,原有代码不用做修改。 第 11 章 软件开发杂谈 | 81 【活跃】lianzi(756215798) 10:12:19 我们老大也经常骂我,说要把故事讲完整 【传说】杭州-悠然 10:12:25 上次我们从MC->Redis,那代码改得,都吐血了。 【活跃】lianzi(756215798) 10:12:53 没做抽象层? 【传说】杭州-悠然 10:13:01 如果再从Redis->另外的方案,不是又要吐血了? 【潜水】上海-云卷江南(25269626) 10:13:08 改个实现类不就行了 【传说】杭州-悠然 10:13:31 如果你做了抽象层,使用的就一定是KV的。 如果要深层次使用,就麻烦了,有的支持有的不支持。 但是技术肯定是双刃剑,有好处也有坏处。 【活跃】lianzi(756215798) 10:14:53 这个我理解 【传说】杭州-悠然 10:15:06 如何发挥好处,避免坏处了。 即使是搞了抽象层,我在文中也写了,到处是处理缓冲逻辑的代码,也是不好的。 【潜水】上海-云卷江南(25269626) 10:15:43 简单用 【传说】杭州-悠然 10:15:53 所以,比较好的办法就是采用面向切面的方式进行处理。 【活跃】lianzi(756215798) 10:17:25 这个我认可,一开始设计的就有问题 这里,又是我经常说的一个话,好软件是“品”出来的,当一个问题有N种解决方案的时候,就要把各种方案仔 细品味。 杂谈之五:新人心态的问题杂谈之五:新人心态的问题 【活跃】lianzi(756215798) 10:20:14 每每看到oscer说刚毕业的学生会什么的时候,我都在思考,应该多向前辈学习,但心里总有点不爽 哈哈,也许是初生牛犊不怕虎吧 【传说】杭州-悠然 10:23:25 不知我的故事,有没有给你讲过。 我刚毕业的时候,第一个网名起的是叫高手来着。 【活跃】lianzi(756215798) 10:23:48 没呢,然后呢 【传说】杭州-悠然 10:24:03 当时心态估计和你差不多,总觉得你毕业多几年有个啥用,我照样比你强。 后来过了一段时间,默默改成:学习中的高手 又过了一段时间,默默改成:学习中的低手 到现在,哥已经不敢说哪一块是NB的了,觉得啥也了解不够深入。 第 11 章 软件开发杂谈 | 82 【潜水】上海-云卷江南(25269626) 10:25:15 我也毕业不久 【活跃】lianzi(756215798) 10:25:23 悠然,我觉得我还算虚心好学 【传说】杭州-悠然 10:25:43 嗯嗯,不错的苗子。 【潜水】上海-云卷江南(25269626) 10:25:44 事实就是很多人经验根本和能力没什么正相关 【传说】杭州-悠然 10:26:18 你要知道,在战场中打得猛的,打得准的,都已经死掉了。 【潜水】上海-云卷江南(25269626) 10:26:34 越来越谦虚是因为自己无知,而不是队友强大 【传说】杭州-悠然 10:26:57 偶尔有几个活下来的,那就英雄了。 活下来的,都已经不再标榜自己打得准,躲得好,只是说:运气好一点而已。 所以,年轻人么,适当藏一下锋芒是有益于发展的。 【活跃】lianzi(756215798) 10:28:48 悠然说的很对啊 【传说】杭州-悠然 10:29:23 你看看所有获奖感言当中,没有哪一个说:因为我NB所以,我才... 而感谢这个,感谢那个,感谢边边角角的人物。 【活跃】lianzi(756215798) 10:29:39 浸染了奋斗的泪泉,腮边了牺牲的血雨 【传说】杭州-悠然 10:30:04 一个用来展示自己的感恩之心,二来是因为这些人的成功不一定主要是边边角角人的功劳。 但是如果让他们不爽了,他们一个小小的“失误”就可以废了你的大好前程。 你再NB,做的东西,也不可能一点瑕疵也没有。 【潜水】上海-云卷江南(25269626) 10:31:18 好高深的样子 【传说】杭州-悠然 10:31:19 当你有一点瑕疵,就会被人攻击致死。 【活跃】lianzi(756215798) 10:31:22 山水有相逢,悠然的却是管理者的心态,悠然悠然啊 【传说】杭州-悠然 10:31:32 我再给你讲一个例子。 有一个以前阿里的架构师,水平,那是一个高。 用他的话来说:除了看我感觉顺眼点,其他没有一个他会看在眼里的。 【活跃】lianzi(756215798) 10:32:55 很高的评价,这个人有问题,我觉得 【传说】杭州-悠然 10:33:06 但是因为锋芒太盛,被剥得人无一个,枪无一条,完了还让人家说他水平太差。 所以,别标榜自己水平多好,能力多强,扎扎实实做事,老老实实做人才是正点。 第 11 章 软件开发杂谈 | 83 杂谈之六:工作年限与水平的关系杂谈之六:工作年限与水平的关系 【活跃】lianzi(756215798) 10:37:47 这个我觉得和经验关系更大 【吐槽】上海 浩子(120195645) 10:38:49 什么事情,都应该与实际进行权衡 【传说】杭州-悠然 10:39:02 所以比较拽的架构师,可以把工作并行起来。 就是说大家各做做的,到时可以组到一起来,又不费什么工作量。 【活跃】lianzi(756215798) 10:39:29 为什么呢?因为踩过的坑比较多是吧双击查看原图 【传说】杭州-悠然 10:39:36 当然,这个层次就有点高了。 【活跃】lianzi(756215798) 10:43:05 这个我认,现在还是学习阶段,多踩坑吧 【传说】杭州-悠然 10:43:49 当然,这里的经验,不等同于工作年限。 但是同样努力用心的两个人,工作三年和工作一年,差别还是非常大的。 【活跃】长沙-Sept() 10:45:13 体系是自底向上构建出来是 最终的表现 始终受到基础影响 基础构造决定啦 最终的极限与瓶颈 【活跃】lianzi(756215798) 10:46:12 恩恩,说得好 【传说】杭州-悠然 10:46:16 所以,我有个说法,就是工作3,5年说做一个多么先进的框架还是为时尚早的,当然试验性的没有问题。 【活跃】长沙-Sept() 10:46:22 @lianzi 基础构造的限制 可以说已经决定 结果最高极限 了 【传说】杭州-悠然 10:47:11 因为,你局部的实践能力和技术的应用能力应该是有的,但是整体宏观视野肯定是有不足的。 这个你去看人家的框架,也只是看得外部,内在的一些因果关系,根本不清楚的,有一定理解也是不完备的。 用Sept的话来说,你的起点决定了你的终点。 你期望说后面再去补充,这个成本是非常高的。 就好象你想盖个大楼,前面没太想好,就直接上手盖,期望中间进行不断修正就可以盖出大楼来。 但是到最后的时候,发现根本没有办法进行调整好让它转向正确的方向。 细细品味! 第 11 章 软件开发杂谈 | 84 1212 框架2.0的设计梳理框架2.0的设计梳理 前面从各个角度,讲了大概11篇了。言归正传,这里讲解一个完整的框架设计实例。这里不是一个空白的描前面从各个角度,讲了大概11篇了。言归正传,这里讲解一个完整的框架设计实例。这里不是一个空白的描 述,而是基于V1.0之后的建构。因此,整个设计过程,也会尽量少走一些弯路。一起来看看吧!述,而是基于V1.0之后的建构。因此,整个设计过程,也会尽量少走一些弯路。一起来看看吧! 方法论方法论 方法论决定了可以达到的高度 方法论,就是人们认识世界、改造世界的根本方法。 它是人们用什么样的方式、方法来观察事物和处理问题。概括地说,世界观主要解决世界“是什么”的问题,方 法论主要解决“怎么办”的问题。 方法论是一种以解决问题为目标的体系或系统,通常涉及对问题阶段、任务、工具、方法技巧的论述。方法论会 对一系列具体的方法进行分析研究、系统总结并最终提出较为一般性的原则。 方法论也是一个哲学概念。人们关于“世界是什么、怎么样”的根本观点是世界观。用这种观点作指导去认识世 界和改造世界,就成了方法论。 方法论是普遍适用于各门具体社会科学并起指导作用的范畴、原则、理论、方法 和手段的总和。 Tiny框架有着完整的方法论基础,在方法论的基础上构建了完整的框架构建、扩展、利用体系。 设计理念设计理念 设计理念决定了设计的目标 使用灵活:可以整个使用它,也可以只用它的一个或几个部分。Tiny构建者认为,一个完整的框架可能需要有许 许多多个部分组成,但是对于实际应用的用户来说,它可能只需要其中的一部分功能。构架一定要有这种能 力,可以由使用者进行点菜式,使用,避免只要用一点点功能,就要引入许许多多的内容。 学习成本低、上手容易:框架的学习成本必须非常低,这样才可以让使用者更容易上手,避免由于学习难度大而导 致的学习曲线太陡、太长。 保持核心的稳定性:Tiny框架是立足于在需要稳定、安全要求非常高的应用环境中使用的,因此其稳定性就是框架 构建者首要思考目标,核心部分只使用经过充验证及广泛应用的第三方包。 资产的可积累性:只有易于知识积累,才可以真正做到越用越强。 设计原则设计原则 设计原则解决目标冲突时的解决策略 第 12 章 框架2.0的设计梳理 | 86 约定优于配置原则-COC 不要重复你自己原则-DRY 减法原则 :减法原则是我们自己提出的,意思就是给程序员做减法。 模块化原则 :模块化对于软件开发过程中开 发、高度、集成、发布、维护过程中所起的作用及节省或花费的巨大成本。因此提出了Business Unit的概 念,使得与模块相关的所有内容都可以放在一起。 自动组装原则 :在整个Tiny框架的构建过程中,都非常注重集成过程的自动组装,要求做到扔进去不用管,由框架 自动集成。 下级服从上级原则 :Tiny框架则从框架层级做了限制,使得下级必须服务上级。 单一原则 :通过单一原则进行强制性的约束,使得一个模块只解决单一模块应该解决的问题,从而避免不同的问题 放在一起解决所导致的胡子眉毛缕不清的问题,同时也避免了不恰当的依赖及模板引用。 集中配置原则 :在Tiny 框架我们对配置做了大量的工作,一个是COC方式,如果不配,则采用系统默认的值;一个是集中原则:把需要 人工需要配置的内容都集中起来统一配置;一个是对于不需要人工干预的配置,那就集成在Jar包中,作为发布者 发布项的一部分。 生态圈生态圈 生态圈决定了是否可以持续性发展 只有形成完整的开源生态圈,开源才能生存、发展。 只有输出没有输入的模式不可能得到持续发展。 Tiny开源生态圈,包含了Tiny框架、Tiny开源组件、Tiny商业组件、Tiny技术支持、Tiny咨询、Tiny培训等,具 有较强市场竞争力和可持续发展的体系,展现了一种新的软件产业发展模式。 不管您处在生态圈的哪个位置,您都会有所付出有所收获,这正是生态圈的意义及可持续发展的动力所在。 模块化模块化 模块化能力决定了业务模块治理的优劣 Tiny框架在模块化方面进行了深入的研究和实践。 Tiny业务开发过程中的任何内容都可以放入Jar包当中去,包含Java类,静态资源,JSP等等,所以一个业务模 块是不是被工程引用,只要引入对应的Jar包,就可以引入此业务模块;只要把某个业务模块的Jar包移除就可以 彻底移除此业务模块。 Tiny框架的业务单元具有非常好的独立性、替换性和通用性。 第 12 章 框架2.0的设计梳理 | 87 热部署热部署 热部署可有效帮助提升系统的可用性 所谓热部署,就是在应用正在运行的时候软件升级,却不需要重新启动应 用。 常见的热部署方案有OSGI等框架,但是这些方案具有侵入性大,开发调试困难,使用方式固定等弊端。 Tiny框架的Bundle与普通的Jar工程没有本质区别,仅多了一个配置文件。所以,它即可以作为普通的Jar包使用 也可以作为热部署的Bundle来进行使用。 流程引擎流程引擎 流程引擎提供了非编程性开发的能力 所谓流程引擎,就是对通过流程化的方式来进行业务、页面、工作流程的编 排支持的开发框架。 Tiny框架提供了业务流、页面流、工作流(正在实现中)等三种流程编排引擎,可以方便的进行业务流、页面 流、工作流领域的开发。 强大的可视化流程设计工具,可以便捷的进行流程设计。 第 12 章 框架2.0的设计梳理 | 88 WEB展现WEB展现 WEB展现是互联网应用中重中之重 Web应用开发是J2EE领域的重要问题领域 Tiny框架提供了强大的WEB层的扩展和UI组件支持,对于WEB静态资源放入Jar包,CSS合并压缩、JS合并压缩 等方面都有良好支持。对于不同角色的开发者之也可以进行良好角色划分,使得开发过程更加高效有序。 领先的模板引擎提供的一些独特特性使得可以更便捷的进行展现层开发。 第 12 章 框架2.0的设计梳理 | 89 组件库组件库 面向组件的开发是效率与质量的保证 组件化编程的关键目的是为了将程序模块化,使各个模块之间可以单独开 发,单独测试。组件的提取、管理与利用是面向组件开发的关键。 Tiny框架中,组件无处不在,有些组件框架中已经默认嵌入,有些组件就需要自己手工纳入。当然,一些愿分享 组件的同学也可以发布自己的组件让别人使用。 组件化有助于推动企业资产的积累与高水平开发人员工作成果的复用,这个在现在这个讲究协作的竞争体系中尤 为重要。 1 ├── org.tinygroup.jquery必须使用的jquery包 2 ├── org.tinygroup.publicComponentTinyUi特有资源(基础-必须依赖基于bootstrap) 3 ├── /webapp/compatibility(特殊)compatibility针对各浏览器兼容性特有资源 4 ├── org.tinygroup.bootstrap使用bootstrap最新v3.3.4 5 ├── org.tinygroup.gridSystems栅格系统(布局) 6 ├── org.tinygroup.compose排版(一般都能用到) 7 ├── org.tinygroup.code代码 8 ├── org.tinygroup.table表格 9 ├── org.tinygroup.form表单 10 ├── org.tinygroup.button按钮 11 ├── org.tinygroup.picture图片 12 ├── org.tinygroup.icon图标 13 ├── org.tinygroup.animation动画 第 12 章 框架2.0的设计梳理 | 90 14 ├── org.tinygroup.buttonGroup按钮组 15 ├── org.tinygroup.navigation导航 16 ├── org.tinygroup.dropDown下拉菜单 17 ├── org.tinygroup.unslider幻灯片 18 ├── org.tinygroup.tab选项卡 19 ├── org.tinygroup.labelBadge便签与标号 20 ├── org.tinygroup.thumbnails缩略图 21 ├── org.tinygroup.alert警告 22 ├── org.tinygroup.progress进度条 23 ├── org.tinygroup.modal弹出框 24 ├── org.tinygroup.customerService客服 25 ├── org.tinygroup.toTop返回顶部 26 ├── org.tinygroup.example案例 27 ├── org.tinygroup.rolling滚动 28 ├── org.tinygroup.search搜索 29 ├── org.tinygroup.ad广告 30 ├── org.tinygroup.tags标签 31 ├── org.tinygroup.fold折叠 32 ├── org.tinygroup.maskBar遮罩 33 ├── org.tinygroup.comment评论列表 34 ├── org.tinygroup.syntaxhighlighter代码高亮 35 ├── org.tinygroup.dataTablesDataTables数据表格 36 ├── org.tinygroup.mmGridmmGrid数据表格 37 ├── org.tinygroup.superBoxsuperBox图片列表 38 ├── org.tinygroup.zoomPiczoomPic图片列表 39 ├── org.tinygroup.Smart-navigation导航集合 40 ├── org.tinygroup.Smart-treeview树形菜单 41 ├── org.tinygroup.highChartshighCharts图表 42 ├── org.tinygroup.voteCharts投票图表 43 ├── org.tinygroup.pieCharts饼状图比例分布图 44 ├── org.tinygroup.bootstrapDate日期控件 45 ├── org.tinygroup.uedTipsUED提示 46 ├── org.tinygroup.loginRegistered登录注册 47 ├── org.tinygroup.trumbowygTrumbowyg编辑器 48 ├── org.tinygroup.UEditorUEditor编辑器 49 ├── org.tinygroup.fullCalendarfullCalendar日程表 50 ├── org.tinygroup.emailTemplate邮件模版 51 ├── org.tinygroup.error404错误404 52 ├── org.tinygroup.error500错误500 53 ├── org.tinygroup.searchPage搜索页 54 ├── org.tinygroup.interfaceElements界面元素 55 ├── org.tinygroup.forum论坛页 56 ├── org.tinygroup.timeline时间轴 57 ├── org.tinygroup.iconTab图标控制选项卡 58 ├── org.tinygroup.userComment用户评论 第 12 章 框架2.0的设计梳理 | 91 59 ├── org.tinygroup.tabShowTabs展示 60 ├── org.tinygroup.viewsControl视图控制 开发工具开发工具 开发工具是软件工程方法的延伸 不用Tiny开发工具你可以完成所有基于Tiny框架的开发工作。 用了Tiny开发工具你可以更快的完成基于Tiny框架的开发工作,效率提升5倍不是想象。 这,就是开发工具的意义。 模板语言编辑器 流程编辑器 第 12 章 框架2.0的设计梳理 | 92 可视化界面编辑器 还有许多不再一一贴图。 第 12 章 框架2.0的设计梳理 | 93 1313 开源与中小型软件公司的未来趋势开源与中小型软件公司的未来趋势 在我的周边朋友身边就发生过这样的事情: 故事1:故事1: A君在北京从事Java开发好多年了,萌发了创业的念头,想组建了一个开发团队想大干一场。但是慢慢发现,构 建一个有战斗力的团队真不容易。后来技术团队的组建初步有了起色,但是技术路线却非常难成一致意见。折腾 来折腾去,把有点上道的技术人员都折腾得跳槽了。费了巨高的成本搞了一个架构师,就是利用SSH框架搭建了 一个开发环境,数据量小,业务初期还是不错的,但是当业务快速增长的时候,运行速度就无法满足需要了。是 重新来过还是在SSH的基础上继续折腾,非常难以抉择! 故事2:故事2: Jenny从英国回青岛大半年了,很喜欢青岛这座海滨城市,空气很好,周围的一草一木也很亲切,这也是当初为 什么回来的原因之一。不过,Jenny一直在为产品管理的事情焦头烂额。除了开发成本高,软件层次一直比较 低。产品团队管理之外,还要考虑技能水平、分工、角色岗位、薪酬、性格特点等等,各方面都耗费了大量的时 间。由于缺乏大规模的压力测试,花了大量时间做出来的产品,却不能适应海量数据下的大并发访问。想找一些 高手吧,在软件业本来就不发达的二线城市又不太现实;离开二级城市转到一级城市发展吧,又承担不起高额的 运营成本与人力资源成本,路在何方真的是个问题! 故事3:故事3: 经过大半年的筹备,阿灿的技术团队终于组建起来了。不过,人员流动始终是个挥之不去的话题。换了人,技术 方案就要换,随之而来的维护问题也是让人焦头烂额!阿灿沮丧了,他怎么也想不明白,为什么新来的人就不愿 意继续在老人留下来的系统上进行维护和再开发呢?如果才能建得起来铁大的营盘? 类似的故事,不胜枚举。总结一下问题出在如下几个方面: 1. 人员培养速度缓慢1. 人员培养速度缓慢 从人力资源团队的角度来讲,更多考虑人员的职业道德是否符合企业文化和价值观。而软件开发项目经理更多考 虑的是,新成员是否能够为软件开发项目做出贡献,并符合项目文化和价值观。如果你的软件开发团队都不是自 己亲手组建起来的,你又如何能够保证软件开发团队能够按你期望的模式运作?应聘人员通过各种认证,仅仅代 表具备了基本的知识体系和理论基础。但任何认证都无法真正体现出每个人的学习能力和应用知识的能力,而这 两点恰恰是软件开发项目最关心的技能。尤其是,要成为一个优秀的软件架构师,往往需要具备10年以上的软件 开发经验,入门的门槛是相当高的,尤其是在互联网产品愈发重要的当下,一个软件架构师往往需要掌握多项技 能,他所需要的知识面会很广,需要过程更需要时间的学习和磨练。 第 13 章 开源与中小型软件公司的未来趋势 | 95 2. 人员开发效率低下2. 人员开发效率低下 一个产品往往需要多个部门的合作,各部门沟通的有效性直接会影响到产品的质量和产品的进度。如果技术开发 人员没有良好的交流沟通能力,可能会严重阻碍项目的推动。尤其是小型的团队开发中,缺乏沟通往往会导致成 员对任务内容、要求和职责理解有误,导致开发效率低下,甚至引发成员间的矛盾。开发人员如果不能清楚地表 达工作计划、遇到的困难、需要什么支持,会导致项目负责人无法及时掌握项目进展情况,并进行合理分配资 源。在工作中时常会发生别人(通常是上级)征询意见时不知如何表达,但被分配具体工作后又觉得自己未被尊 重,从而产生挫折感。技术开人员大多思维能力强于交流沟通能力,性格也大多趋向于内向,喜欢做事多于说 话。如何提高自身沟通交流能力是摆在很多开发人员面前的一大难题。 3. 技术架构不统一没有延续性3. 技术架构不统一没有延续性 作为设计师,需要保证产品功能的现实,产品功能的可持续性,产品的稳定性及产品的可用性等。产品的这些需 求都依赖于架构师对产品技术的规划。架构师在接到商业需求之后,最主要的工作就是将其转化为技术需求。这 个过程的完成与架构师抽象思维的能力密不可分。好比说Tiny框架这个项目,主架构师第一个闪过的念头多半就 是:这个系统必须具有长期的统一性。而负责每一个Tiny框架功能的架构师,又需要对这些部分进行进一步的抽 象化。这些往往是中小企业团队所不具备的。由于缺乏优秀的架构师,导致团队在产品的现实规划上没有自己明 确的目标和具体的可行性实施方案,缺乏统一的延续性,导致后期难以满足产品在升级、改版方面的需要。 4. 性能不能满足运营要求4. 性能不能满足运营要求 整天只知道大谈“云计算,SaaS”这些东西的团队,注定开发不出优秀的产品。这种毛病在新的开发团队非常常 见,因为这可以忽悠更多的客户。不过,新的技术虽好,程序员接受和再培训还需要时间,还要考虑到系统的兼 容性问题。因此,夸夸其谈的名词专家,往往死得更惨。尤其是出现大并发的数据考验时,做出来的产品往往会 难以满足运营要求。 5. 构建开发框架成本太高无法承受5. 构建开发框架成本太高无法承受 时下各种软件系统发展越来越复杂,尤其是框架软件,其涉及的问题以及知识面太多。当网站变大后,不可避免 的需要拆分应用进行服务化,以提高开发效率,调优性能,节省关键竞争资源等。当服务越来越多时,服务的UR L地址信息就会爆炸式增长,配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。当进一步发 展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用 第 13 章 开源与中小型软件公司的未来趋势 | 96 的架构关系。接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时 候该加机器?等等……在遇到这些问题时,开发成本往往会正比例飞速增长,甚至让中小企业团队无法支撑。 想要减少开发工作量或是缩短时间,降低成本,使用框架便是一个很好的选择。尤其是,使用质量好且有延续性 的开源框架正在成为主流! 1. 节省团队时间和精力1. 节省团队时间和精力 框架节省了我们不少的时间,并且让扩展变得更容易。由于拥有完整的生态体系,以及活跃的社区氛围,使得团 队战斗力更强!由于框架强制使用公共的约定,因此它能有效地解决一些共有的问题。框架能够让工作更连 贯,也能避免我们写一大堆自定义模块来实现某些性能,我们所需要做的,就是将这些共用模块放在框架中实 现。以Tiny框架为例,一年的开发就需要提交数千个Commits,解决了无数个疑难杂症。此外,从文档维护的角 度来看,一年900多页的文档内容,也能够帮助开发团队解决许多难题。 2. 技术支撑更有保障2. 技术支撑更有保障 优秀的开源框架,通常具有高内聚、低耦合、高质量的代码,专职的团队,可以保持项目持续不断的前进。还是 以Tiny框架为例,Tiny主工程共有621个Issues,里面有需求,和改进,有BUG。由于有良好知识积累体系,使 得使用Tiny框架的人们越用越强,越用越爽!相当于有一个强大的后援团队在为你的项目服务。这些优点,不胜 枚举。当A君在青岛的海边悠闲地喝着咖啡时,完全不用担心客户的跟踪电话了。 3. 成本更低,附加值更高3. 成本更低,附加值更高 在优秀的开源框架体系里,由于顶层设计避免了重复劳动,所有软件参与者都会避免做重复的事情。尤其是对于 个体或小型企业,很明确,光是SSH/I已经不足让你的方案看起来高大上,也不足以支持业务数据量比较大的时 候的应用场景,也不足以支撑居高不下的软件开发实施成本。在优秀的开源框架开发团队里,整个团队配置往往 更加合理,高低水平者各司其职,使得运营成本更低、附加值也更高。以Tiny为例,正在构建的Tiny生态圈,上 百个UI组件及流程组件已经足够你日常使用,还会有更多被不断加入,这些完全就是超值服务了! 总之,使用质量好有延续性的开源框架,基于开源框架做应用是未来中小型软件公司的发展趋势,你将获得更加 超值的回报! 第 13 章 开源与中小型软件公司的未来趋势 | 97 1414 教计算机程序解数学题教计算机程序解数学题 周末,看关于专家系统方面的书,其中有关于规则方面的内容,忽然就想,能不能模仿人的学习方式来提升计算 机程序的计算能力呢? 试想,一个小孩子,他一开始什么也不会,首先,你要告诉他什么是数字,然后告诉他什么是加、减;然后告诉 他什么是乘、除,还要告诉他有乘、除要先计算乘除,然后又引入了括号说,有括号永远要先计算括号。如 此,随着告诉他的技能越多,他的解题能力也就越强。 于是就想着试验一下。 第一步,教计算机学习什么是数字。第一步,教计算机学习什么是数字。 下面的正则表达式,就是告诉“孩子”,数字就是前面可能有“-”号,当然也可能没有,接下来连续的数字 0-9,组成的数字,后面可能还会有小数点开始加一堆0-9的数字,当然没有也没有关系。如此,它就算懂得认 数字了。 public final class MathNumber { private MathNumber() { } public static String numberPattern = "[-]?[0-9]+([.][0-9]*)?"; public static Pattern pattern = Pattern.compile(numberPattern); public static Matcher match(String string) { Matcher match = pattern.matcher(string); if (match.find()) { return match; } throw new RuntimeException(string + " is not a number."); } } 第二步就是告诉“孩子”,计算数学题的过程。第二步就是告诉“孩子”,计算数学题的过程。 如果两边有空格就忽略它,然后呢,看看是不是已经是一个数字了,如果已经是一个数字,那说明就算出结果 了。如果不是,就从最高优先级找起,如果找就就计算。如果找不到,说明这个式子有问题,不是一个合法的数 学式子。 public static String eval(String string) { string = string.trim(); while (!isMathNumber(string)) {// 同一优先级的哪个先找到算哪个 System.out.println("求解算式:" + string); boolean found = false; for (MathInterface math : mathList) { Matcher matcher = math.match(string); if (matcher.find()) { 第 14 章 教计算机程序解数学题 | 99 String exp = matcher.group(); String sig = ""; if (exp.charAt(0) == '-' && matcher.start() != 0) {// 如果不是第一个数字,-号只能当运算符 sig = "+"; } System.out.println("发现算式:" + exp); String evalResult = math.eval(exp); string = string.substring(0, matcher.start()) + sig + evalResult + string.substring(matcher.end()); System.out.println(exp + "计算结果为:" + evalResult + ",代回原式"); found = true; break; } } if (!found) { throw new RuntimeException(string + " 不是合法的数学表达式"); } } return string; } 从现在开始,这孩子已经会解题思路了,不过他还是啥也不懂,他还不知道啥是加,减、乘、除啥的,没有办 法,孩子笨,只要多教他了。 下面就教他如何计算,加、减、乘、除、余、括号、指数。 addMathExpression(new Add()); addMathExpression(new Subtract()); addMathExpression(new Multiply()); addMathExpression(new Devide()); addMathExpression(new Minus()); addMathExpression(new Factorial()); addMathExpression(new Remainder()); addMathExpression(new Bracket()); addMathExpression(new Power()); Collections.sort(mathList, new MathComparator()); 由于大同小异,就里就只贴出来加法和括号的实现方式。 加法实现,它的优先级是1,它是由两个数字中间加一个“+”号构成,数字和加号前面的空格没用,不用管 它。计算的时候呢,就是用加的方式把两个数字加起来,这一点计算机比人强,呵呵,告诉他怎么加永远不会错 的。而且理解起加减乘除先天有优势。 第 14 章 教计算机程序解数学题 | 100 public class Add implements MathInterface { static String plusPattern = BLANK + MathNumber.numberPattern + BLANK + "[+]{1}" + BLANK + MathNumber.numberPattern + BLANK; static Pattern pattern = Pattern.compile(plusPattern); static Pattern plus = Pattern.compile(BLANK + "\\+"); @Override public Matcher match(String string) { return pattern.matcher(string); } @Override public int priority() { return 1; } @Override public String eval(String expression) { Matcher a = MathNumber.pattern.matcher(expression); if (a.find()) { expression = expression.substring(a.end()); } Matcher p = plus.matcher(expression); if (p.find()) { expression = expression.substring(p.end()); } Matcher b = MathNumber.pattern.matcher(expression); if (b.find()) { } return new BigDecimal(a.group()).add(new BigDecimal(b.group())) .toString(); } } 接下来是括号,括号的优先级是最大啦,只要有它就应该先计算。当然,要先计算最内层的括号中的内容。括号 中的内容,计算的时候,可以先拉出来,不用管外面的内容,计算好了,放回去就可以了。 public class Bracket implements MathInterface { static String bracketPattern = BLANK + "[(]{1}[^(]*?[)]" + BLANK; static Pattern pattern = Pattern.compile(bracketPattern); @Override 第 14 章 教计算机程序解数学题 | 101 public Matcher match(String string) { return pattern.matcher(string); } @Override public int priority() { return Integer.MAX_VALUE; } @Override public String eval(String expression) { expression = expression.trim(); return MathEvaluation.eval(expression.substring(1, expression.length() - 1)); } } 到目前为止,我们的程序“宝宝”已经学会数学计算了,出个题让伊试试。 public static void main(String[] args) { String string = "1+2^(4/2)+5%2"; System.out.println("结果是 :" + MathEvaluation.eval(string)); } 程序宝宝的做题过程如下: 求解算式:1+2^(4/2)+5%2 发现算式:(4/2) 求解算式:4/2 发现算式:4/2 4/2计算结果为:2.00,代回原式 (4/2)计算结果为:2.00,代回原式 求解算式:1+2^2.00+5%2 发现算式:2^2.00 2^2.00计算结果为:4,代回原式 求解算式:1+4+5%2 发现算式:5%2 5%2计算结果为:1,代回原式 求解算式:1+4+1 发现算式:1+4 1+4计算结果为:5,代回原式 求解算式:5+1 发现算式:5+1 5+1计算结果为:6,代回原式 结果是 :6 第 14 章 教计算机程序解数学题 | 102 呵呵,程序宝宝的做题过程和人的做题过程非常一致,而且程序实现也非常简单易懂。神马编译原理,神马中缀 表达式都用不上。(执行效率与其它算法比较不一定高,仅用于验证通过规则让程序的处理能力增强,由于没有进 行深入测试,正则表达式和程序逻辑是否写得严密没有经过深入验证) 其实程序虽然很简单,但是,实际上已经是一个简单的规则引擎的雏形。 首先,他加载了许多的业务处理规则,加,减,乘,除,插号,指数,余数等等。 第二,他的业务规则是可以不断进行扩展的。 第三,只要给出事实,最后,他通过规则的不断应用,最后会导出结果,要么是正确的结果,要么说给出的事实 是错误的。 需要源码的童鞋请到GIT上直接获取代码。 git地址:http://git.oschina.net/tinyframework/mathexp.git 第 14 章 教计算机程序解数学题 | 103 1515 借船下海还是造船下海借船下海还是造船下海 1.借船与借力1.借船与借力 三国时期,曹操率大军想要征服东吴,孙权、刘备联合抗曹,“草船借箭”即来源于此,意即运用智谋,凭借他 人的人力或财力来实现自己的目标。我们来看看这个故事的几个关键环节。 为了筹集十万支箭,诸葛亮找到鲁肃。诸葛亮说:“这件事要请你帮我的忙。希望你能借给我20只船,每只船上 30个军士,船要用青布慢子遮起来,还要一千多个草把子,排在船两边。”第三天四更时候,诸葛亮邀请鲁肃一 起,把船用绳索连起来向曹操对岸开去。那天江上大雾迷漫,对面都看不见人。当船靠近曹军水寨时,诸葛亮命 船一字儿摆开,叫士兵擂鼓呐喊。曹操以为对方来进攻,又因雾大怕中埋伏,就派六千名弓箭手朝江中放箭,雨 点般的箭纷纷射在草把子上。过了一会,诸葛亮又命船掉过头来,让另一面受箭。 太阳出来了,雾要散了,诸葛亮令船赶紧往回开。这时船的两边草把子上密密麻麻地插满了箭,每只船上至少 五、六千支,总共超过了十万支。鲁肃把借箭的经过告诉周瑜时,周瑜感叹地说:“诸葛亮神机妙算,我不如 他。” 2.顺势而为2.顺势而为 “明者因时而变,知者随事而制”这个用典,出自汉代桓宽《盐铁论》卷二之枕边第十二篇。汉宣帝的中兴之 道,得益于诸多方面,根本的一条就是“明者因时而变,知者随事而制”,了解民情,把握趋势,与时俱进,开 放而谋实。同样,作为开发团队,在互联网竞争时代,也要干很多事。有些事很顺利,有些却坎坎坷坷的,有些 根本就干不成。细想一下,事情的成败原因可以归结为一个字,势,顺势而为,如水推舟,事半功倍;逆势为 之,则逆水行舟,艰难险阻,功败垂成。 势是什么,就是一种趋势,一种方向,一种潮流。顺势而为,关键在于一个“势”字,要有一双慧眼,判明大势 进退;要有一颗名亮亮的新,悟达通透。做到“顺势而为,乘势而上”。 3.框架开发的“借力”与“顺势”3.框架开发的“借力”与“顺势” 基础的框架开发并不难,但是要想做得优雅、健壮并不容易,要做出一个好的框架往往会花费大量的时间、人力 财力。衡量一个框架是否优秀,往往有这些考量因素。 • 清晰的代码库,简单易用。代码复用是把一个功能写成一个模块,以便当再次需要相同功能的时候,可以直 接使用,而不用重新开发。举个例子,假如你的网站需要验证码,你就可以把验证码这个功能单独提取出来 以便复用。通常代码复用是通过类与对象来实现的,这也是面向对象编程与面向过程编程最主要的区别之 一。以响应式网页设计为例,实现起来并不困难,但是要让它在所有的目标设备上都正常运作会有一点小棘 手。而框架可以让这一工作变得简单。利用框架,你可以花最少的力气创建响应式且符合标准的网站,一切 第 15 章 借船下海还是造船下海 | 105 都很简单并且具有一致性。还有很多好处是显而易见的,比如说简单快速,以及在不同的设备之间的一致性 等等。也就是说,框架最大的“势”就是简单易用,即使只掌握少量的Web知识,你也可以毫无障碍的使用 它们。 • 粉丝使用过程的“倒逼”,强大的框架创新体系,营造良好的社区生态环境。“倒逼”是一种被动行 为,是“迫使”、“反推”等词的升级版,该词强化了反常规、逆向促动之义。时下“倒逼”正在成为美 谈,并曾经入选年度十大流行词。各行各业的问题动辄倒逼,似乎“倒逼则灵”。在许多领域,“倒逼”的 确是这样大显身手的。作为一种开源力量,“倒逼”未尝不是一种正向力量!坚实的社区基础和积累,以及 丰富的模板系统,往往可以为框架树立良好的口碑,形成一种“引力场”。尤其是需要有大量忠实的社区粉 丝,也是框架实力的最好支持。作为社区网站,也要站在运营者角度和用户角度双方面来考虑上诸多问 题。在用户角度上完善网站产品,去满足用户的核心需求,帮助用户解决问题。 • 简单的学习曲线,与相关应用集成更加容易。学习曲线的定义为“在一定时间内获得的技能或知识的速 率”,又称练习曲线(practice curves)。人们为了知道学习进程中的现象和进步的快慢的详情,作为以后 努力的指针,应用统计图的方法作一条线,把它表示出来。它源于“二战”时期的飞机工业,当产量上升 时,生产每架飞机的劳动时间会极大地下降。随后的研究表明,在许多行业都存在这种现象。同样,在框架 应用中,我们需要的不仅仅是模板,还更想要陈述式的可重用的模板框架。尤其需要能够创建可扩展的互联 网应用。 • 文档支持与引导系统。写文档不容易同时也是需要花费一些时间的。作为潜在的用户,我们第一次接触开源 项目,很可能就是通过阅读README文件。我们需要确保它很棒并且包含了有用的信息。以Tiny框架为 例,我们始终认为文档是能为用户做的最好的事了!文档不仅能够节省用户大量的时间,也可以让用户确 信,我们的确是把他们当做上帝,而且,我们是一帮有血有肉的人,不是一个产生代码的机器。 • 向后兼容,同时把握大势,对主流技术发展有一个准确的判断。关于软件开发的一件很令人生气的事,就是 当你升级一个库但是数百个测试失败了。更让我生气的就是我还要重写我一半的基础代码,因为有人在没有 任何警告的前提下决定打破公共的API。因此,向前看齐,同时致力于维护向后兼容性,也是我们重点把握的 方向。以Tiny框架为例,我们常常会关注,使用这个项目有几个月了吗?是否觉得它还是不完整的?是否希 望API在下一个版本会彻底地修改?是否在要求最多并且很老的项目中也能稳定安全的使用?当考虑到向后兼 容时,也能有一个很好的跟踪记录。 • 可延续的技术支持。有些人可能关心许可证,但是真正使用框架或库的人很关心的是有保障的后续服务和支 持。所以真正可用的框架包含了框架的可用及有保障的支持和服务,这样才能让客户在使用过程中更加放 心。 郑和下西洋,没有人关心他使用的是什么船;诸葛亮借箭,也没有人关心他使用的是谁的船。不过,他们都很轻 松的完成了自己的目的。造船下海,还是借船下海,关键还是在于顺势而为,把握大势! 第 15 章 借船下海还是造船下海 | 106 1616 缓存相关代码的演变缓存相关代码的演变 问题引入问题引入 上次我参与某个大型项目的优化工作,由于系统要求有比较高的TPS,因此就免不了要使用缓冲。 该项目中用的缓冲比较多,有MemCache,有Redis,有的还需要提供二级缓冲,也就是说应用服务器这层也可 以设置一些缓冲。 当然去看相关实现代代码的时候,大致是下面的样子。 public void saveSomeObject(SomeObject someObject){ MemCacheUtil.put("SomeObject",someObject.getId(),someObject); //下面是真实保存对象的代码 } public SomeObject getSomeObject(String id){ SomeObject someObject = MemCacheUtil.get("SomeObject",id); if(someObject!=null){ someObject=//真实的获取对象 MemCacheUtil.put("SomeObject",someObject.getId(),someObject); } return someObject; } 很明显与缓冲相关的代码全部是耦合到原来的业务代码当中去的。 后来由于MemCache表现不够稳定,而且MemCache的功能,也可以由Redis完全进行实现,于是就决定从系 统中取消MemCache,换成Redis的实现方案,于是就改成如下的样子: public void saveSomeObject(SomeObject someObject){ RedisUtil.put("SomeObject",someObject.getId(),someObject); 第 16 章 缓存相关代码的演变 | 108 //下面是真实保存对象的代码 } public SomeObject getSomeObject(String id){ SomeObject someObject = RedisUtil.get("SomeObject",id); if(someObject!=null){ someObject=//真实的获取对象 RedisUtil.put("SomeObject",someObject.getId(),someObject); } return someObject; } 这一通改下来,开发人员已经晕头晕脑的了,后来感觉性能还是不够高,这个时候,要把一些数据增加二级缓 冲,也就是说,本地缓冲有就取本地,本地没有就取远程缓冲 于是,上面的代码又是一通改,变成下面这个样子: public void saveSomeObject(SomeObject someObject){ LocalCacheUtil.put("SomeObject",someObject.getId(),someObject); RedisUtil.put("SomeObject",someObject.getId(),someObject); //下面是真实保存对象的代码 } public SomeObject getSomeObject(String id){ SomeObject someObject = LocalCacheUtil.get("SomeObject",id); if(someObject!=null){ return someObject; 第 16 章 缓存相关代码的演变 | 109 } someObject = RedisUtil.get("SomeObject",id); if(someObject!=null){ someObject=//真实的获取对象 RedisUtil.put("SomeObject",someObject.getId(),someObject); } return someObject; } 但是这个时候就出现一个问题: 由于在某一时刻修改值的只能是某一台计算机,这个时候,其它的计算机的本地缓冲实际上与远程及数据库中的 数据会不一致,这个时候,可以有两种办法实现,一种是利用Redis的请阅发布机制进行数据同步,这种方 式,会保证数据能够被及时同步。 另外一种方法就是设置本地缓冲的有效时间比较短,这样,允许在比较短的时间段内出现数据不一致的情况。 不管怎么样,功能是实现了,程序员小伙伴这个时候已经改得眼睛发黑,手指发麻,几乎接近崩溃了。 很明显这种实现方式是不好的,于是项目组又提出了改进意见,能否采用注解方式进行标注,让程序员只要声明 就可以?Good idea,于是,又变成了下面的样子: @Cache(type="SomeObject",parameter="someObject",key="${someObject.id}") public void saveSomeObject(SomeObject someObject){ //下面是真实保存对象的代码 } @Cache("SomeObject",key="${id}") public SomeObject getSomeObject(String id){ SomeObject someObject=//真实的获取 第 16 章 缓存相关代码的演变 | 110 return someObject; } 这个时候,程序员们的代码已经非常清爽了,里面不再有与缓冲相关的部分内容,但是引入一个新的问题,就是 处理注解的代码怎么写?需要引入容器,比如:Spring,这些Bean必须被容器所托管,如果直接new一个实 例,就没有办法用缓冲了。还有一个问题是:程序员的工作量虽然有所节省,但是还是要对程序代码有侵入 性,需要引入这些注解,如果要增加超越现有注解的功能,还是需要重新写过这些类,引入其它的注解,修改现 有的注解。 所以,上面是个可以接受的方案,但明显还不是很好的方案。 假如有一个程序员火大了,他发出下面的抱怨:“我只管做我的业务,放不放缓冲和我有1毛钱关系么?总因为缓 冲的事情让我改来改去,程序改得乱七八糟不说,我的时间,我的工作进度都影响了谁来管?以后和缓冲相关的 事情别他妈的来烦我!”,作为架构师的你,你怎么看?最起码,我觉得他是说得非常有道理的。我们再返过头 来看看最原始的代码: public void saveSomeObject(SomeObject someObject){ //下面是真实保存对象的代码 } public SomeObject getSomeObject(String id){ SomeObject someObject=//真实的获取 return someObject; } 这里是干干净净的业务代码,和缓冲没有一点关系。后来由于性能方面的要求,需要做缓冲,OK,这一点是事 实,但是用什么缓冲或怎么缓冲,与程序员确实是没有什么关系的,因此,是不是可以不让程序员参与,就可以 优雅的做到添加缓冲功能呢?答案当然是肯定的。 需求整理需求整理 代码当中,不要体现与缓冲相关的内容,也就是说做不做缓冲及怎么做缓冲不要影响到业务代码 不管是从容器中 取实例还是new实例,都可以同样的起作用,也就是说可以不必依赖具体的容器 第 16 章 缓存相关代码的演变 | 111 解决思路:解决思路: 放不放缓冲、怎么放缓冲、缓冲有效时间等等,这些内容是在运行期发现存在性能瓶颈,然后提交给程序员来进 行优化的。为此,我们设计了一个配置来描述这些缓冲相关的声明。 当然,这个配置文件的结构,可以根据自己所采用的缓冲框架来进行相应的定义。 比如: 我们在实际应用当中,配置比上面的示例更完善,那现在我先讲一下上面的两段配置的含义。 在UserDao的saveUser的时候,会同步的把User数据放到Redis中进行缓冲,缓冲时间为1秒,存放的缓冲数 据的类型为user,键值为${user.id},也就是要保存的用户的主健。实际进入到Redis的时候,在Redis中的健值 是由上面类型与key的共同组成的。 第 16 章 缓存相关代码的演变 | 112 在调用UserDao的getUser的时候,会先从缓冲中获取类型为user,键值为${id}的数据,如果缓冲中在,则取出 并返回,如果缓冲中没有,则从原有业务代码中取出值并放入缓冲,然后返回此对象。 哇,这个时候,就非常爽了,只要通过声明就可以做到对缓冲的处理了,但是一个问题就出来了,如何实现上面 的需求呢? 通过配置文件外置,确实做到了对业务代码的0侵入,但是如何为原有业务增加缓冲相当的业务逻辑呢?由于需求 2要求可以new,也可以从容器中获取对象实例,因此利用容器AOP解决的跑就被封死了,因此,就得引入字节 码的方式来进行解决。 具体实现具体实现 写一个基于Maven的缓冲代码处理插件,在编译后增加此处理插件,根据配置文件对原有代码进行扫描并修改其 字节码以添加缓冲相关处理逻辑。 现在只要使用Maven进行compile或install就可以自动添加缓冲相关的逻辑到class文件中了。 至此,我们已经分析了缓冲代码直接耦合到代码中,并分析了其中的缺点,最终演化了注解方式,外置配置方 式,并简要介绍了实现方法。 具体实现,采用的技术就比较多了,有Maven插件、有Asm、有模板引擎还有Tiny框架的一些基础工程,如:VF S,FileResolver等等。 如果采用Tiny框架,可以直接拿来用,如果不用Tiny框架,可以参照上面的思路做自己的实现。 第 16 章 缓存相关代码的演变 | 113 1717 与屈原对话及开源精神与屈原对话及开源精神 端午节到了,人们都在讨论屈原不屈不挠的精神,以及龙舟、粽叶等世界文化遗产。粽叶清淡,给人无限的遐 想。或者,你很容易想起和粽叶形状比较类似的竹叶,以及屹立挺拔的骨感竹子。竹在清风中瑟瑟的声音,在夜 月下疏朗的影子,都让文人墨客深深感动。而竹于风霜凌厉中苍翠依然的品格,更让诗人引为同道。苏东坡曾 在《于潜僧绿筠轩》里宣称,“宁可食无肉,不可居无竹。无肉令人瘦,无竹令人俗。人瘦尚可肥,士俗不可 医。”当年郑板桥曾作《竹石》,细细品味,也给人许多思考。 作为开源参与者,其实我们可以联想到很多和竹子相关的典故,以及和竹子相关的精神。端午节前,困顿的晌 午,我决定穿越时空,会一会屈原。 对话屈原对话屈原 为此,我溯江而上,穿越雄伟险峻的长江西陵峡,抬头眺望长江北岸,有一座气势雄伟的建筑,半遮半掩在桔林 与翠柏之中,这便是世人瞩目的屈原祠。 拾级而上,来到屈原祠里,一番膜拜之后,我准备与屈原做一次详谈。“屈大夫,我不想继续做程序员了,你能 给我一个让我坚持的理由吗?”我问。 第 17 章 与屈原对话及开源精神 | 115 屈原回答:“你看看我的祠堂四周,看到那些山蕨和竹子了吗?去年我播种了山蕨和竹子的种子后,给它们光照和 水分。山蕨很快就从地面长出来,茂密的绿叶覆盖了地面。然而,竹子却什么也没长出来。一年过去了,山蕨长 得更加茂密。竹子的种子仍然没有长出任何东西。2年过去了,竹子的种子还是没有发芽。 然而,到了第5年,地 面上冒起了一个细小的萌芽。与山蕨对比,它小到微不足道。但是,仅在6个月之后,竹子就长到100英尺高 了。竹子花了5年时间来长根,正是根给了它生存所需的一切。” 屈原捋了捋胡须,缓缓说道:“孩子,你这段时间所做的挣扎,实际上就是你长根的时候。不要拿自己与别人对 比。现在,你的时机到了。你会上升得很高。”我离开了屈原故里的香溪河口,带回来了这个故事。 一路上,我在反复琢磨,并百度了解毛竹的特性。原来,毛竹萌芽之后,最初4年居然只能长3厘米,但是到了第 5年却以每天30厘米的速度生长,只用6周就可以长到15米。在这潜心钻研的4年里,竹子都是在扎根,它将自己 的根在土壤里面延伸了数百平米。没有这种沉心钻研,哪有今日绿竹成荫! 竹子精神与开源竹子精神与开源 回到开源世界的现实生活,细细品味屈老夫子的话,让人感慨良多。做产品、做框架亦是如此,不要担心你此时 此刻的付出得不到回报,因为这些付出都是为了扎根。人生历练何尝不是如此,多少人,没熬过那3厘米! 竹子的群生精神与开源竹子的群生精神与开源 说到群生精神,李克强总理用很通俗的话解释了一次:“一个人干不过一个团队,一个团队干不过一个系统,一 个系统干不过一个趋势。 团队+系统+趋势=成功。一个人可以走得很快,一群人会走得更远! 你能整合别人,说 明你有能力; 你被别人整合,说明你有价值。 在这个年代,你既整合不了别人,也没人整合你,那说明你离成功 还有很远!”这种启示,在竹子身上也可以看到。平时,人们看到的往往是一片竹林,而不是孤零零的一棵竹 子。对一棵竹子而言,它面对的只有死亡。这说明团队成员之间,只有大家抱团才能生存和发展下去,否则,这 个团队只有死亡。 第 17 章 与屈原对话及开源精神 | 116 说到开源团队的群生精神,我还想引用一个群鸟南方的故事。每当秋季来临,天空中成群结队南飞的大雁就是值 得我们借鉴的企业经营的楷模、一支完美的团队。雁群是由许多有着共同目标的大雁组成,在组织中,它们有明 确的分工合作,当队伍中途飞累了停下休息时,它们中有负责觅食﹑照顾年幼或老龄的青壮派大雁,有负责雁群 安全放哨的大雁,有负责安静休息﹑调整体力的领头雁。在雁群进食的时候,巡视放哨的大雁一旦发现有敌人靠 近,便会长鸣一声给出警示信号,群雁便整齐地冲向蓝天、列队远去。而那只放哨的大雁,在别人都进食的时候 自己不吃不喝,是一种为团队牺牲的精神。 据科学研究表明组队飞要比单独飞提高22%的速度,在飞行中的雁两翼可形成一个相对的真空状态,飞翔的头雁 是没有谁给它真空的,漫长的迁徙过程中总有人带头搏击,这同样是一种牺牲精神。而在飞行过程中,雁群大声 嘶叫以相互激励,通过共同扇动翅膀来形成气流,为后面的队友提供了“向上之风”,而且V字队形可以增加雁 群70%的飞行范围。如果在雁群中,有任何一只大雁受伤或生病而不能继续飞行,雁群中会有两只自发的大雁留 下来守护照看受伤或生病的大雁,直至其恢复或死亡,然后它们再加入到新的雁阵,继续南飞直至目的地。 同样,对于成功的开源团队而言,个人强大的技术是一方面,但团队力量远大于一群人的简单相加。项目组由项 目管理、需求分析、软件设计、编码、测试、实施等各方面的专业人士组成,每位成员在自己专业领域内发挥主 导作用,并可以为项目的成功提出非自己领域内的建议。最终的项目成果是各位专业人士共同努力的结果,所有 人对最终成功承担同等的责任。这样,当系统部署后,系统出现了一个严重缺陷,请问谁应该负责?项目经 理?测试?开发?……都不是,而是项目组全体都要负责! 竹子的自主生长精神竹子的自主生长精神 当年板桥就说过,“咬定青山不放松,立根原在破岩中。千磨万击还坚劲,任尔东西南北风。”也是说的竹子不 屈不挠、自主钻研、任性成长的精神。 在常见的开源项目组中,难免有部分成员是新手,经验和水平不足,某些工作可能一时不能胜任。迫于项目进度 压力,某些任务就会直接安排给他做,不让他提出自己的想法和见解。不少成员也喜欢以“接受任务”的方式来 工作,而不是主动迎接挑战。于是有时候,你可能遇到一些成员会跟你说“今天工作已经完成!”“我按照任务 要求来做的,我没有错!”之类的话。 就像竹子“千磨万击还坚劲”的精神一样,每个项目成员都有当家做主的机会,应相信自己在本领域内是专 家,在我的专业范围内,我可以说了算!只要满足项目的大框架,只要出发点是为了项目成功,那么这段代码应 该怎样写、这个功能点应该如何测试等之类的决定,完全可以让成员来做主!项目成员可能一时没有魄力独立做 决定,可能担心犯错误,没关系,要多多鼓励他!Tiny团队就是这样,我们每个团队成员的自主精神贯穿在日常 的工作始终。TinyFramework的立意是企业级的开发平台,因此在方法论、设计理念、开发体系、设计原则、生 态圈、模块化、热部署、水平扩展、元数据等非功能性要求方面做了大量的自主探索和独立实践,当然在这些领 域也都有了完美或者不错的解。 第 17 章 与屈原对话及开源精神 | 117 “扎根”与开源“扎根”与开源 不知道你注意没有,在成长的过程中,竹子始终是一节一节生长。竹子生长一段,就接一个箍,再生长一段,再 接一个箍。这说明,竹子是会总结和反思的。所以,对团队成员来讲,也要学会总结和反思,才能不断地成长和 发展。说到这里,我想谈一个扎根理论。作为质化研究中的一种建构理论,扎根理论是由斯特劳斯(anselm stra uss)和格拉斯(barney glaser)两位社会学者在1967年出版的《扎根理论的发现》一书中提出的。扎根的理 论,就是用归纳的方法,对现象加以分析整理所得的结果。在开源团队,尤其需要学习竹子的扎根精神。 在开源产品的成长过程中,我们同样要发挥这种扎根精神,不断总结提高。一般来说,一个开源产品,从开 始,到发展,到最后能有收入,能营收平衡,这个一个漫长及艰难的过程。按照一般人的观点,牛人总是喜欢和 牛人在一起工作,这句话是有道理的,然而要组建一个全是精英的团队,对于大多数开源产品来说只能是一个梦 想。一般的团队,招到团队来的人,精英只是个别,也就是所谓的骨干了,而大部分还是熟练工、生手甚至是新 手。团队由各种层次的人组成,这也就是所谓的团队梯队配置了。 为何团队会需要梯队配置?原因其实很简单,你的团队所负责的工作,不可能全部由精英骨干力量去完成。个人 能力再突出,不适合团队的人或许给你团队带来的是负面影响。唐僧四师徒西天取经,如果每个人都是孙悟 第 17 章 与屈原对话及开源精神 | 118 空,不是自己内斗死,就是被妖怪吃掉!从研发编码的角度举例,需要更多具有扎根精神、各具特长的成员来参 与,才能取得成功。比如在Tiny框架开源团队中,有句口头禅,嘴巴讲的不算。所以不管是需求还是BUG,都要 统统录到Issues当中,于是提出问题、批注问题、解决问题、跟踪问题、关闭问题,都在Issues当中进行管 理。当然,所有的过程也就成为呈堂证供,抵赖不得。 竹子精心耕耘,能够延伸数百平米,就是靠的这种不断接“箍”的“扎根”精神。加入到开源团队,做开源产 品,就要耐得住平淡,经得起风雨,把开源之根留住!软件研发活动是人类复杂的高级智力活动,是需要team work 的活动。如果明白这个道理,研发团队中的每一个人将是值得尊重的、有血有肉的、充满激情和战斗力的专 家!也只有这样,才能开发出具有高质量品质的开源产品。Tiny架构的设计师最初就致力于做一个各方面比较均 衡的开发平台,于是就从各种小的专题性验证开始“扎根”,比如:流程化编程、模块化设计、数据库分区分表 等等一一进行验证,当验证的范围越来越大,涵盖的领域越来越多的时候,才真正开始决定做一个开源框架。这 实际上也是一个“竹叶成荫”的开拓过程。 竹子的虚心精神竹子的虚心精神 竹在荒山野岭中默默生长,无论是峰峰岭岭,还是沟沟整整,它都能以坚韧不拔的毅力在逆境中顽强生存。尽管长年 累月守着无边的寂寞与凄凉,一年四季经受着风霜雪雨的抽打与折磨,但她始终“咬定青山”、专心致志、无怨无 悔。这种虚怀若谷的精神,让人敬佩。 大家注意到没有,所有的竹子都是中空的,都是能容得下其他人的,都是能像其他团队成员虚心学习的。以Tiny 框架为例,在Tiny框架早期,整个团队还是默默无闻的,因为Tiny团队不想在框架还是一个半成品的时候就拿出 来,直到Tiny已经开发完毕并且在自己的项目中进行了充分验证的时候才真正的在社区或相关网站进行露面。开 发者们互帮互助,虚心求教,一个问题提交到社区 (http://bbs.tinygroup.org) 的“有问必答”栏目,往往就会有 许多同伴互相帮助,问题也能在最短的时间内得到解决。由于Tiny框架是立足于在需要稳定、安全要求非常高的 应用环境中使用的,因此其稳定性就是框架构建者首要思考目标,核心部分只使用经过充验证及广泛应用的第三 方包。 细细品竹,便会觉得竹子是学习的楷模。竹子挺拔俊逸,做人应该具有这种翩翩君子的开源风度;竹子中空外 直,做人应该修炼这种虚心自持的开源品格;竹子弯而不折,做人应该学习这种柔中有刚的开源智慧。开源是一 个艰辛的选择,需要长久的坚守,需要一份不急不燥的态度。团队合作、团队协作也是如此,达成配合与默契更 需要不断的沟通、磨合与深厚的信任。只有这样,我们才能见证优秀的产品是如何一步步走向成熟,才能在开源 的氛围中快速成长! 第 17 章 与屈原对话及开源精神 | 119 更多信息请访问 http://wiki.jikexueyuan.com/project/open-source-framework-diy/
    还剩120页未读

    继续阅读

    下载pdf到电脑,查找使用更方便

    pdf的实际排版效果,会与网站的显示效果略有不同!!

    需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

    下载pdf

    pdf贡献者

    文杰天下

    贡献于2016-10-28

    下载需要 10 金币 [金币充值 ]
    亲,您也可以通过 分享原创pdf 来获得金币奖励!
    下载pdf