Architecture

收集一些关于Software Maintenance & Evolution的文献 

收集一些关于Software Maintenance&Evolution的文献  wikipedia上的software maintenance页:  http://en.wikipedia.org/wiki/Software_maintenance,介绍了软件维护的概念和发展历史,主要篇幅用在:从软件工程的角度来介绍软件维护,比如规划、过程,成本、收益等 wikipedia上的software evolution页: http://en.wikipedia.org/wiki/Software_evolution wikipedia: 软件进化之Lehman’s law: http://en.wikipedia.org/wiki/Lehman%27s_laws_of_software_evolution wikipedia上的maintainability页:http://en.wikipedia.org/wiki/Maintainability infoq上的著名文章:架构腐化之迷: http://www.infoq.com/cn/articles/cjz-architecture-corruption 给开发维护大型项目开发者的建议:  http://blog.jobbole.com/16526/ 期刊: http://www3.interscience.wiley.com/cgi-bin/jhome/5391/ 书: Software Maintenance Management: Evaluation and Continuous Improvement (Practitioners)

软件维护及可维护性的基本概念

软件维护是指: Software maintenance in software engineering is the modification of a software product after delivery to correct faults, to improve performance or other attributes. 软件维护是一种evolution的过程: Lehman demonstrated that systems continue to evolve over time. As they evolve, they grow more complex unless some action such as code refactoring is taken to reduce the complexity.  Lehman还提出了软件evlution的Lehman’s …

软件维护及可维护性的基本概念 Read More »

为什么说二进制依赖一个组件时不准调用它的内部API?

为什么说二进制依赖一个组件时不准调用它的内部API? 比如说,对bean-biz.jar,应用层只准调用BeanService.getBean(),而不准调用BeanDAO.getBean()? 你会说这是为了“封装性”,以“避免不必要的耦合”。但直接耦合BeanDAO.getBean()和BeanDAO.deleteBean()有什么“具体”的坏处呢?  我的结论是:     1. 如果同时依赖BeanService.getBean()和BeanDAO.getBean(),就意味着对同一个操作形成了“调用上的冗余”; 当一个发生变化时,另一个也要跟着变,而且变得很尴尬。     2. 如果绕过BeanService直接调用BeanDAO.deleteBean(),就意味着依赖了组件的内部实现;而内部实现往往是不稳定的,当内部实现改变时,应用层也要发生改变,一种本可以避免的改变。 代码阐述: 1. 调用的冗余     //改变发生前 public class BeanService { public Bean getBean(Long id) { return beanDAO.getBean(id); } } public class WiseAction { public void showBeanDetail(){ Bean bean = beanService.getBean(id); } } public class RogueAction { public void showBeanDetail() { Bean bean = beanDAO.getBean(id); } } …

为什么说二进制依赖一个组件时不准调用它的内部API? Read More »

一个应用为什么要拆成多个jar包,并且包要有版本?

都说应用大了就要拆,有人建议拆成多个应用并构成分布式系统,有人建议维持一个应用但拆成多个包(指jar包,不是package);本文讨论的是后一种分拆法。    下面从多个方面来讲述分包的好处。我将主要使用“反证法”,论述大应用如果不分包可能导致的后果,反过来证明分包的合法性。 本文的观点和论据大部分来自于Robert C. Martin的《敏捷软件开发》,有兴趣的朋友可以细阅这本书的第20章-“包的设计原则”     从软件开发的多个方面看待分包问题:     系统的可理解性      分包可以提供模块化的视图,帮助了解系统的组织结构    如果一个大公司没有分部门,你是不是很难说出这家公司哪些人有哪些职能? 如果一个系统的java代码都堆在一起,你是不是也很难说清这个系统大概有哪几块? 如果系统的结构不清晰,对大家理解系统、系统架构的未来规划都有很大的坏处。    而系统分包就是系统模块化的过程;结合包依赖图,可以帮助了解系统的组成部分以及模块之间的依赖关系   编译      a. 分包可以避免一次编译所有代码,减少编译时间      如果代码不分包,则意味着每次编译都要编译整个应用;对于一个大的应用,这个编译时间会很长;如果应用是c/c++写的,这个编译时间可能会达到无可容忍的地步。    分包之后,大部分代码已经编译掉、作为jar文件引入了,此时你要编译的代码只是你正在改的那一小部分。这种情况下,编译时间可大大缩短。      b. 分包后,服务提供者的接口变更不会立即导致消费者代码编译失败      如果代码不分包,且大家都在同一份代码库里开发,会有这样一种场景:A写完FooService准备提交时,发现它依赖的BarService已经被删掉了! 这时A就没办法提交代码了。如果A是一个冒进的人,他在提交之前不会更新代码库,而是直接提交FooService,然后下班;比他下班晚的人如果签出最新代码,就会发现代码无法编译。      如果大家各搞一个私有分支,可以避免这种问题;大家在各自的分支里进行开发,最后再合到一起。但最后合并的工作量可能很多,因为要解决的代码冲突可能会很多或者很难搞,上面说的BarService被删掉的问题就很难搞;应用越大,这种冲突的概率就越高。      把应用拆成带版本的jar包可以解决这个问题:把BarService放到bar-1.0.jar里,FooService依赖bar-1.0.jar. 如果BarService被删掉,删它的人会打出一个bar-1.1.jar; FooService则仍然使用bar-1.0.jar,不会出现编译问题。   3.代码的正确性      分包后,服务消费者不必使用服务提供者的最新版本,避免出现未经验证的代码组合      如果大家都在同一份代码库里开发,A写完FooService自测并提交,然后B把它依赖的BarService改掉,FooService和新的BarService共同运行时可能就会导致错误的结果,因为对于这个Foo+Bar组合,还没人测过。      把应用拆成带版本的jar包可以解决这个问题:把BarService放到bar-1.0.jar里,FooService依赖bar-1.0.jar. …

一个应用为什么要拆成多个jar包,并且包要有版本? Read More »

反省:不必追求100%的系统精确性

    去年我看了一部很烂的电影:《变形金刚3》。这部电影真的很烂很烂,但其中有一类镜头引发了我的思考:机器人的一条腿被打断后,它仍然用身体的其他部件进行战斗,虽然动作严重变形,但并没有放弃战斗。     我当时就想,断了一条腿,相当于分布式架构中当了一个子系统;这种异常不是业务异常,而是技术异常,应声明为RuntimeException,在它抛出后不但要中止当前腿的动作,而且要中止接下来的其他手足的动作; 否则,可能导致系统的行为进入一个不可知的状态,轻则“动作严重变形”,重则一脚踏空、高空坠落,造成灾难性的后果。     很显然,我那时的想法是不切实际的。作为战斗者,变形金刚的高可用性远远比它的“精确性”来得重要。对应到互联网的开发中,就是“网站看上去很健康”比“每笔交易无误”来得重要。当然,对敏感的系统比如支付系统、动车调度系统,还是后者更重要;但对普通的电子商务,“动作变形”比“拒绝服务”要友好的多。   这里不妨总结一下严格追求精确性的常见的实践:         1.技术异常统统定义成或包装成RuntimeException,并且不捕捉,让用户看到类似于“暂时无法为您提供服务”的提示     2.数据库里严格使用事务,以避免单边数据     3.数据库里使用各种约束,如外键、check constraint,以保证数据的完整性        替代的“宽大处理”方案是:        1. UI层调用业务层、或者一个系统调用另一个系统时,可以考虑捕捉RuntimeException,遇到RuntimeException时使用一个“最合适”的值作为调用的结果,并继续执行当前的流程。 例如,获取商品的折扣失败时,就认定优惠为0,以原价把商品卖给消费者。不过,最好要有配套的动作:        a.给系统配备实时监控功能,当发生这种事时,运维人员能马上收到通知,解决问题        b.发生这种事时,应记录日志,供未来审计和业务修正。     2. 事务最好还是要有的。但如果某些场景下实现事务很难,比如分布式事务,那就让它去吧     3. 不干净的数据输入最好能通过校验来阻拦掉。如果校验不够充分,数据通过了应用要进入数据库,而且中止当前SQL运行会导致后续SQL都无法执行,那就可以考虑让DBMS容忍这种错误,使用一个“最佳”的数据插入进去。MySql就可以做这种事情。

不建议把可以做在webapp里面的基础架构做到webapp外面

有很多基础服务,本来可以做在webapp里面的,但有人却选择把它做在webapp外面。 比如jboss提供的log4j服务,就是一个很讨厌的东西,很容易跟你应用里面的log4j冲突。 还有些服务,比如连接池服务,被强行做成了jndi,由容器来打理,这也很讨厌。 在我看来,把可以放里面的东西丢到外面有这样几个缺点:     1. 本可以由程序员完全控制的基础设施,现在要由运维人员来分担了。jndi就是一个例子。   2. 本可以由scm工具控制的东西,现在要经常手动来管理,这在版本管理方面往往产生问题。比如那些由jca写的、丢在jboss下面的东西,没办法用maven+svn直接管理了   3. 内外冗余造成的冲突问题。如果你的应用里使用了log4j,就可能跟jboss自带的log4j产生版本冲突和class loading冲突   4. 开发环境和其它环境的不一致问题。比如你开发用Tomcat,Tomcat不提供JNDI,所以你开发时不使用jndi那一套; 但正式环境用的又是jboss + jndi。结果就是你的代码或配置要支持两种东西,开发时不搞jndi,发布时要搞jndi