模块化及组件化的一些概念

模块化是什么:       In software design, modularity refers to a logical partitioning of the "software design" that allows complex software to be manageable for the purpose of implementation and maintenance. The logic of partitioning may be based on related functions, implementation considerations, data links, or other criteria.    模块化编程的好处:    Modular programming improve maintainability by enforcing …

模块化及组件化的一些概念 Read More »

如何理解“每个组件只应提供单一的API调用入口”

每个组件只应提供单一的API调用入口,这句话的意思是什么? 我的看法: 1. 正解:组件中的每个操作都应该只有一个入口;应用层不能既调用BeanService.getBean(),又调用BeanDAO.getBean(). 原因可见这里。 2. 误解:每个模块只能使用一个类作为API. 像bean-biz.jar这种组件,可以同时提供BeanService作为CRUD、业务逻辑入口,以及BeanSearchService作为搜索查询入口;否则,如果把所有操作都丢给单一的BeanService,那 BeanService 这个类本身的内聚性就很差。 其实,关于“组件中的每个操作都应该只有一个入口”也可以有例外情况。 比如一个缓存客户端 cache-client.jar中,可以同时提供CommonCacheTemplate作为通用的、灵活性高的API,以及SimpleCacheOperations作为最常用的、极其易用(比如可以少传几个参数)的的API。不过,后者必须建立在前者基础上,只作为前者的易用版本来使用,不准附加任何与前者冲突的业务逻辑

为什么说二进制依赖一个组件时不准调用它的内部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 »

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

软件维护是指: 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 »

收集一些关于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)

缓存对象的数据结构要变更,怎么办?

如果缓存对象的数据结构要变更,也就是说,新的代码里将使用新的数据结构,那么上线时,线上已有缓存对象的数据结构可能跟新代码中定义的数据结构不匹配,在反序列化时可能会产生数据完整性的问题。 为了解决这个问题,就 要让新代码取不到旧缓存或者忽略旧缓存。 一个解决方案是在新代码中使用新的序列号,新代码获取旧缓存时会发生反序列化错误; 我们再显式地捕捉这种错误,在发生这种错误时再去后端取数据,并使用取来的数据覆盖旧缓存。 然而,“更新序列号”这个方案有个触不到的地方:泛型的更换。如果缓存对象的数据结构从ArrayList<A>变成了ArrayList<B>,即使A和B的序列号不同,新代码在遇到旧缓存时也不会发生序列化错误。后果是, 新代码以为它拿到的是ArrayList<B>对象,实际上却是ArrayList<A>对象,最终的后果就是在访问List中元素时发生ClassCastException. 你会问,能否侦测这种情况?比如说,新代码从缓存中拿到一个ArrayList时,判断它的泛型是不是ArrayList<B>,如果不是则忽略?  答案是做不到,java的“伪泛型”不支持这种判断。 另一个解决方案是上线前先清空所有旧缓存,这样新代码永远取不到旧缓存。然而它亦有短板: 如果你们采用了灰度发布流程,旧缓存仍然可能跟新代码接触。 所谓灰度发布是指将新代码发布到集群服务器时,先发到部分机器,针对这部分机器验证通过后再继续发下去。 显然,在这个过程中,新旧代码会同时存在于集群中;运行中的旧代码有可能会产生旧的缓存,且被新的代码获取,发生问题;同理,新代码产生的缓存也会被旧代码取到,照样发生问题。  清空旧缓存这种方案还有一个问题是,你需要在发布时手工介入,比较麻烦。 那怎么办才好呢? 有个方案是:除了使用新的序列号, 新代码也要使用新的缓存key.如果老代码使用 key_ record_id来存取,新代码就用new_key_ record_id来存取. 这样的话,新代码永远取不到老的缓存;老的代码也取不到新的缓存。 有了这个基础,泛型变换、灰度发布时的新旧代码共存都不是问题。 使用新的缓存key,可以把新旧缓存隔离开来,相当于在逻辑上产生了两套缓存空间,是一种相当彻底的解决方案; 另外,这种方案也非常简单,不需要在发布时手动做什么事情。 不过,实施这种方案有个前提:由于旧缓存会被新代码忽略,从而成为无用的缓存;新系统上线时会有大量的Cache Miss,你要确保这不会引发性能问题。