标签:
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.
lars.michael.dk, 2 Mar 2015 CPOL
原文地址:http://www.codeproject.com/Articles/882165/How-to-Avoid-Producing-Legacy-Code-at-the-Speed-of
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.
As an enterprise software developer, you are in a constant fight against producing legacy code – code that is no longer worthy of maintenance or support. You are constantly struggling to avoid re-writing stuff repeatedly in a faint hope that next time you will get it just right.
The characteristics of legacy code is, among others, poor design and architecture or dependencies on obsolete frameworks or 3rd party components. Here are a few typical examples that you might recognize:
You and your team produced a nice, feature-rich Windows application. Afterwards, you realize that what was really needed was a browser or mobile application. That is when you recognize the tremendous effort it would take to provide an alternative UI to your application, because you have embedded too much domain functionality within the UI itself.
Another scenario might be that you made a backend that is deeply infiltrated in a particular ORM – such as NHibernate or Entity Framework – or highly dependent on a certain RDBMS. At one point, you want to change backend strategy to avoid ORM and use file-based persistence, but then you realize it is practically impossible because your domain functionality and the data layer are tightly coupled.
In both of the above scenarios, you are producing legacy code at the speed of typing.
However, there is still hope. By adapting a few simple techniques and principles, you can change this doomed pattern of yours forever.
In the following, I will describe 3 phases in a typical architectural evolution for a standard enterprise software developer. Almost any developer will make it to phase 2, but the trick is to make it all the way through phase 3 which will eventually turn you into an architectural Ninja.
Most developers have heard about layered architecture, so very often the first attempt on an architecture will look something like this – two layers with separated responsibilities for frontend and backend functionality:
So far so good, but quit soon you will realize that it is a tremendous problem that the domain logic of your application is entangled into the platform-dependent frontend and backend.
Thus, the next attempt is to introduce a middle layer – a domain layer – comprising the true business functionality of your application:
This architecture looks deceptively well-structured and de-coupled. However, it is not. The problem is the red dependency arrow indicating that the domain layer has a hard-wired dependency on the backend – typically, because you are creating instances in the domain layer of backend classes using the new
keyword (C# or Java). The domain layer and the backend are tightly coupled. This has numerous disadvantages:
The domain layer functionality cannot be reused in isolation in another context. You would have to drag along its dependency, the backend.
The domain layer cannot be unit tested in isolation. You would have to involve the dependency, the backend.
One backend implementation (using for example a RDBMS for persistence) cannot easily be replaced by another implementation (using for example file persistence).
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.
你要做的其实很简单。你只需调转代表依赖关系的红色箭头。这是一个微小的调整,但是结果大不同:
What you have to do is actually quite simple. You have to turn the direction of that red dependency arrow around. It is a subtle difference, but one that makes all the difference:
这一设计模式坚持依赖倒置原则(DIP)-面向对象设计最重要的原则之一。重点是,一旦这一模式被确立-依赖关系立刻调转-领域层的潜在生命周期得到大幅度增加。UI需求或者转变从Windows窗口到浏览器或者移动设备,或者你的持久化存储可能从关系型数据库(RDBMS)转换到文件型存储,但是现在所有改变都可以很容易在不修改领域层的情况下实现。因为这样的实现前端和后端很好的与领域层解耦。因此,领域层编程一个代码库理论上你几乎永远不用去替代-至少持续到你的业务改变或者整体框架发生改变的时候。现在,你可以有效地和你的遗留代码战斗了
This architecture adheres to the Dependency Inversion Principle (DIP) – one of the most important principles of object-oriented design. The point is that once this architecture is established – once the direction of that dependency arrow is turned around – the domain layer dramatically increases its potential lifetime. UI requirements and trends may switch from Windows to browsers or mobile devices, and your preferred persistence mechanism might change from being RDBMS-based to file-based, but now that is all relatively easily exchangeable without modifying the domain layer. Because at this point the frontend as well as backend is de-coupled from the domain layer. Thus, the domain layer becomes a code library that you theoretically never ever have to replace – at least as long as your business domain and overall programming framework remain unchanged. Now, you are efficiently fighting that legacy code.
另一方面来说,让我给你一个简单的示例来演示如何在实践中提升DIP:
On a side note, let me give you one simple example on how to implement DIP in practice:
也许你有一个product service在领域层,它可以对定义在后端的products repository执行CRUD操作。这样经常导致像下图一样的错误指向的依赖关系:
Maybe you have a product service in the domain layer that can perform CRUD operations on products in a repository defined in the backend. This very often leads to a dependency graph like the one shown below, with the dependency arrow pointing in the wrong direction:
这样是因为你不得不在product service的某处使用”new“,这就产生了对product repository的依赖:
This is because somewhere in the product service you will "new" up a dependency to the product repository:
var repository = new ProductRepository();
应用DIP原则来倒转这样依赖关系,你必须在领域层以IProductRepository
接口的方式引入一个product repository的抽象并且让product repository 实现这个接口(implementation of this interface)
To inverse the direction of the dependency using DIP, you must introduce an abstraction of the product repository in form of an IProductRepository
interface in the domain layer and let the product repository be an implementation of this interface:
现在,作为使用New产生product repository 实例的替代方案,你可以注入repository 到service 通过一个构造参数(constructor argument):
private readonly IProductRepository _repository;
public ProductService(IProductRepository repository)
{
_repository = repository;
}
这是依赖注入的知识(Dependency injection DI)。我以前已经在一篇博客中做过详细介绍见:Think Business First.
This is known as dependency injection (DI). I have previously explained this in much more detail in a blog post called Think Business First.
一旦你正确的应用了全部设计模式,对抗遗留代码的目标显而易见:把尽量多个功能引入domain layer(领域层),让前端和后端不断收缩同时让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:
You are probably starting to realize that not only traditional frontends or backends, but all other components – including the unit tests or for example an http-based Web API – should act as consumers of the domain layer. Thus, it makes a lot of sense to depict the architecture as onion layers:
最外层的组件消费领域库代码(domain library code)-通过提供领域抽象(接口或者基类)具体实现或者作为领域方法(domain functionality)的直接用户(domain model 和services)。
无论如何,要记住:耦合的方向总是指向中心的-指向domain layer。
在这一点上,它看起来好像太理论化,and,well…,有点抽象。不过,它原则上不需要做很多。在另一篇文章中(CodeProject article of mine ),我描述和提供了一些遵从所有原则的简单的代码。那个示例的代码非常简单,但是非常接近于正式的产品代码。
At this point, it might all seem a bit theoretic and, well…, abstract. Nevertheless, it does not take a lot to do this in practice. In another CodeProject article of mine I have described and provided some sample code that complies with all of the principles in this article. The sample code is simple, yet very close to real production code.
作为一个商业软件开发者避免产生遗留代码(legacy code)是一场持久的战斗。想获胜的话,执行下列操作:
Being an enterprise software developer is a constant battle to avoid producing legacy code at the speed of typing. To prevail, do the following:
Make sure all those dependency arrows point toward the central and independent domain layer by applying the Dependency Inversion Principle (DIP) and Dependency Injection (DI).
Constantly nourish the domain layer by moving as much functionality as possible into it. Make that domain layer grow fat and heavy while shrinking the outer layers.
使用单元测试(unit tests)覆盖领域层(domain layer)的每个的单个功能。
Cover every single functionality of the domain layer by unit tests.
Follow these simple rules and it will all come together. The code that you write will potentially have a dramatically longer lifetime than before because:
遵循这些简单原则也许最终将汇合到一起。你的code也许将比以前拥有一个超乎想象的长生命周期,因为:
The domain layer functionality can be reused in many different contexts.
领域层的功能(domain layer functionality)可以在许多不同的上下文环境中复用。
100%覆盖率的单元测试(unit test)可以使domain layer 非常健壮和坚如磐石。
The domain layer can be made robust and solid as a rock with a 100% unit test coverage.
领域层的抽象(例如持久化机制)实现可以轻松的替换成其他的实现方式
Implementations of domain layer abstractions (for example persistence mechanisms) can easily be replaced by alternative implementations.
领域层是容易维护的。
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
How to Avoid Producing Legacy Code at the Speed of Typing
标签:
原文地址:http://www.cnblogs.com/wit13142/p/4627803.html