How to Avoid Producing Legacy Code at the Speed of Typing

英语很差翻译很烂。英语好的去看原文。css

About the Author

I am a software architect/developer/programmer.
I have a rather pragmatic approach towards programming, but I have realized that it takes a lot of discipline to be agile. I try to practice good craftsmanship and making it work. html

lars.michael.dk, 2 Mar 2015 CPOL 前端

 

原文地址:http://www.codeproject.com/Articles/882165/How-to-Avoid-Producing-Legacy-Code-at-the-Speed-of java

This article provides a recipe on how to avoid producing legacy code at the speed of typing by using a proper architecture and unit testing. web

Introduction

做为一个企业软件开发者,你常常和产出遗留代码(legacy code)作斗争-那种不值得维护或支持的代码。你在不断努力避免重写重复的东西抱着微弱的但愿以期下次你能恰好作的正确。【原文:You are constantly struggling to avoid re-writing stuff repeatedly in a faint hope that next time you will get it just right.】数据库

遗留代码(legacy code)的特征是,其中,很差的设计和建造模式或者依赖于过期的框架或第三方组件。一下是你可能认识的几个典型例子:编程

你或者你的团队产出了一个漂亮的,功能丰富的Windows应用。以后,你意识到真正的需求是一个浏览器(web应用)或者移动应用。你意识到为你的应用替换UI将须要付出更加大量的努力,由于你嵌入了太多领域功能(domain functionality)在它的UI里面。c#

另外一个情景多是你编写了一个后端,它是深度渗透在某一个特定的ORM-例如Nhibernate或者Entity Framework-或者高度依赖与某一个RDBMS。在这一个点上,你想要改变策略来让后端避免使用ORM和使用文件存储的持久化数据库,可是很快你就意识到这几乎是不可能完成的,由于你domain functionality 和data layer 是牢牢耦合。 后端

在上述两种状况下,你以打字的速度来生产遗留代码(legacy code)。 设计模式

 

然而,那仍是有但愿的。经过采用一些简单技巧和原则,你能够永远改变这一已经注定的局面。

 

The Architectural Evolution

下面,我将描述三个阶段标准商业软件开发的三个典型模式。几乎全部开发者都处于第二阶段,但关键是要进入第三阶段,你将最终成为一个建筑模式的忍者。

 

An evolution into a Nija architect

Phase 1 - Doing it Wrong

大多数开发者听过度层设计模式,因此不少第一次尝试设计模式就像下面同样-把先后端进行功能责任分离的两层结构:

 

2-layered architecture diagram. Frontend-Backend

到目前为止还好,可是很快你就意识到那有一个极大的问题,也就是引用程序的业务逻辑和前端以及后端纠缠在一块儿,而且依赖于它们。

 

Phase 2 – A Step Forward

所以,下一个尝试是引入一个中间层-一个domain layer-由你应用程序的真正的业务逻辑组成:

 

3-layered diagram. Frontend-Domain-Backend

这种模式看起来具备迷惑性的良好结构和解耦性。然而,事实并不是如此。问题是红色的依赖箭头代表domain layer对后端具备天生的依赖-典型的,由于你在domain layer使用new(c#或者java)来建立后端类(backend classes)的实例。domain layer 和后端是牢牢耦合的。这有许多缺点:

  • domain layer 功能不能再其余的上下文环境中单独重用。你须要把他的依赖项(the backend)一并引入。
  • domain layer 没法单独的进行单元测试。你须要关联它的依赖项,后端代码
  • 一个后端的实现(例如一个使用RDMBS数据库)没法简单的被另外一个后端(使用文件数据库)实现替换

All of these disadvantages dramatically reduces the potential lifetime of the domain layer. That is why you are producing legacy code at the speed of typing.

全部这些缺点都在减小domain layer的声明周期。这是为何你在以打字的速度产生遗留代码的缘由

Phase 3 – Doing it Right

你要作的其实很简单。你只需调转表明依赖关系的红色箭头。这是一个微小的调整,可是结果大不一样:

 

3-layered architecture diagram. Using Dependency Inversion Principle. Frontend-Domain-Backend

这一设计模式坚持依赖倒置原则【Dependency Inversion Principle 】(DIP)-面向对象设计最重要的原则之一。重点是,一旦这一模式被确立-依赖关系马上调转-领域层的潜在生命周期获得大幅度增长。UI需求或者转变从Windows窗口到浏览器或者移动设备,或者你的持久化存储可能从关系型数据库(RDBMS)转换到文件型存储,可是如今全部改变均可以很容易在不修改领域层的状况下实现。由于这样的实现前端和后端很好的与领域层解耦。所以,领域层编程一个代码库理论上你几乎永远不用去替代-至少持续到你的业务改变或者总体框架发生改变的时候。如今,你能够有效地和你的遗留代码战斗了

另外一方面来讲,让我给你一个简单的示例来演示如何在实践中提高DIP:

也许你有一个product service在领域层,它能够对定义在后端的products repository执行CRUD操做。这样常常致使像下图同样的错误指向的依赖关系:

 

Dependency diagram 1

这样是由于你不得不在product service的某处使用”new“,这就产生了对product repository的依赖:

var repository = new ProductRepository();
 
应用DIP原则来倒转这样依赖关系,你必须在领域层以接口的方式引入一个product repository的抽象而且让product repository 实现这个接口(implementation of this interface):IProductRepository

 

Dependency injection diagram 2

 

如今,做为使用New产生product repository 实例的替代方案,你能够注入repository 到service 经过一个构造参数(constructor argument):

private readonly IProductRepository _repository;
 
public ProductService(IProductRepository repository)
{
    _repository = repository;
}

 

这是依赖注入的知识(Dependency injection DI)。我之前已经在一篇博客中作过详细介绍见:Think Business First.

 

一旦你正确的应用了所有设计模式,对抗遗留代码的目标显而易见:把尽可能多个功能引入domain layer(领域层),让前端和后端不断收缩同时让domain layer(领域层)不断丰满:

3-layered architecture diagram. Fat domain layer

这一设计模式产出的一个实用的副产品,它使它本身很容易对domain functionality(领域功能)进行单元测试。由于domain layer 的耦合特性以及面对全部的依赖都是表现为抽象的(如一个接口或者一个抽象基类)。这样很容易为他们的抽象伪造出一个对象来实现单元测试。因此它是”在公园散步“来守卫整个domain layer和单元测试(unit tests)【注:原文 So it is “a walk in the park” to guard the entire domain layer with unit tests.  】.你要作的无外乎就是努力提供超过100%覆盖率的单元测试来保证你的domain layer足够健壮而且坚如磐石。这有增长了你domain layer的生命周期。

你可能已经了解到这不只仅是传统的前端和后端,可是全部其余的组件-包括单元测试或者一个http-based 的Web API-会担当一个domain layer的消费者角色。由于,这样的设计模式描述起来像一个onion layers:

 

onion layer architecture diagram

最外层的组件消费领域库代码(domain library code)-经过提供领域抽象(接口或者基类)具体实现或者做为领域方法(domain functionality)的直接用户(domain model 和services)。

不管如何,要记住:耦合的方向老是指向中心的-指向domain layer。

在这一点上,它看起来好像太理论化,and,well…,有点抽象。不过,它原则上不须要作不少。在另外一篇文章中(CodeProject article of mine ),我描述和提供了一些听从全部原则的简单的代码。那个示例的代码很是简单,可是很是接近于正式的产品代码。

 

Summary

做为一个商业软件开发者避免产生遗留代码(legacy code)是一场持久的战斗。想获胜的话,执行下列操做:

  • 确保全部的依赖箭头经过应用依赖倒置原则(DIP)和依赖注入(DI)而指向中央和独立的domain layer
  • 不断地健壮domain layer,经过尽量多的把functionality移动到domain layer,使domain layer 变得丰满而是外层(onion layer 中的outer layer)逐渐萎缩。
  • 使用单元测试(unit tests)覆盖领域层(domain layer)的每一个的单个功能。

 

遵循这些简单原则也许最终将汇合到一块儿。你的code也许将比之前拥有一个超乎想象的长生命周期,由于:

  • 领域层的功能(domain layer functionality)能够在许多不一样的上下文环境中复用。
  • 100%覆盖率的单元测试(unit test)可使domain layer 很是健壮和坚如磐石。

  • 领域层的抽象(例如持久化机制)实现能够轻松的替换成其余的实现方式

  • 领域层是容易维护的。

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

相关文章
相关标签/搜索