How to Avoid Producing Legacy Code at the Speed of Typing
英语不好翻译很烂。英语好的去看原文。
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.
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.
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里面。
另一个情景可能是你编写了一个后端,它是深度渗透在某一个特定的ORM-例如Nhibernate或者Entity Framework-或者高度依赖与某一个RDBMS。在这一个点上,你想要改变策略来让后端避免使用ORM和使用文件存储的持久化数据库,但是很快你就意识到这几乎是不可能完成的,因为你domain functionality 和data layer 是紧紧耦合。
在上述两种情况下,你以打字的速度来生产遗留代码(legacy code)。
然而,那还是有希望的。通过采用一些简单技巧和原则,你可以永远改变这一已经注定的局面。
The Architectural Evolution
下面,我将描述三个阶段标准商业软件开发的三个典型模式。几乎所有开发者都处于第二阶段,但关键是要进入第三阶段,你将最终成为一个建筑模式的忍者。
Phase 1 - Doing it Wrong
大多数开发者听过分层设计模式,所以很多第一次尝试设计模式就像下面一样-把前后端进行功能责任分离的两层结构:
到目前为止还好,但是很快你就意识到那有一个极大的问题,也就是引用程序的业务逻辑和前端以及后端纠缠在一起,并且依赖于它们。
Phase 2 – A Step Forward
因此,下一个尝试是引入一个中间层-一个domain layer-由你应用程序的真正的业务逻辑组成:
这种模式看起来具有迷惑性的良好结构和解耦性。然而,事实并非如此。问题是红色的依赖箭头表明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
你要做的其实很简单。你只需调转代表依赖关系的红色箭头。这是一个微小的调整,但是结果大不同:
这一设计模式坚持依赖倒置原则【Dependency Inversion Principle 】(DIP)-面向对象设计最重要的原则之一。重点是,一旦这一模式被确立-依赖关系立刻调转-领域层的潜在生命周期得到大幅度增加。UI需求或者转变从Windows窗口到浏览器或者移动设备,或者你的持久化存储可能从关系型数据库(RDBMS)转换到文件型存储,但是现在所有改变都可以很容易在不修改领域层的情况下实现。因为这样的实现前端和后端很好的与领域层解耦。因此,领域层编程一个代码库理论上你几乎永远不用去替代-至少持续到你的业务改变或者整体框架发生改变的时候。现在,你可以有效地和你的遗留代码战斗了
另一方面来说,让我给你一个简单的示例来演示如何在实践中提升DIP:
也许你有一个product service在领域层,它可以对定义在后端的products repository执行CRUD操作。这样经常导致像下图一样的错误指向的依赖关系:
这样是因为你不得不在product service的某处使用”new“,这就产生了对product repository的依赖:
var repository = new ProductRepository();
应用DIP原则来倒转这样依赖关系,你必须在领域层以IProductRepository
接口的方式引入一个product repository的抽象并且让product repository 实现这个接口(implementation of this interface):
现在,作为使用New产生product repository 实例的替代方案,你可以注入repository 到service 通过一个构造参数(constructor argument):
private readonly IProductRepository _repository;
public ProductService(IProductRepository repository)
{
_repository = repository;
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
这是依赖注入的知识(Dependency injection DI)。我以前已经在一篇博客中做过详细介绍见: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:
最外层的组件消费领域库代码(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)
How to Avoid Producing Legacy Code at the Speed of Typing的更多相关文章
- Rails 5 Test Prescriptions 最后一章,如何测试继承下来的代码,legacy code
Set expectations 你不可能把一个老旧的代码野兽只用一晚就转变成优雅的奇迹marvel.你需要如下做法: 让自己有好的状态,用15分钟挥舞拳头诅咒之前的程序员 开始工作,这个codeba ...
- What I Learned as a Junior Developer Writing Tests for Legacy Code(转载)
I go to the gym and lift weights because I like the feeling of getting stronger and better. Two mont ...
- C++学习书籍推荐《Effective STL(英文)》下载
百度云及其他网盘下载地址:点我 作者简介 Scott Meyers is one of the world's foremost authorities on C++, providing train ...
- Code Complete阅读笔记(二)
2015-03-06 328 Unusual Data Types ——You can carry this technique to extremes,putting all the ...
- Linking code for an enhanced application binary interface (ABI) with decode time instruction optimization
A code sequence made up multiple instructions and specifying an offset from a base address is identi ...
- Use Spring Insight Developer to Analyze Code, Install it with Tomcat, and Extend it with Plugins--转载
原文地址:http://www.tomcatexpert.com/blog/2012/12/05/use-spring-insight-developer-analyze-code-install-i ...
- Visualize Code with Visual Studio
In this post, App Dev Manager Ed Tovsen spotlight the features and benefits of Code Maps in Visual S ...
- 10 Tips for Writing Better Code (阅读理解)
出发点 http://www.tuicool.com/articles/A7VrE33 阅读中文版本<编写质优代码的十个技巧>,对于我编码十年的经验,也有相同感受, 太多的坑趟过,太多的经 ...
- Code Project精彩系列(转)
Code Project精彩系列(转) Code Project精彩系列(转) Applications Crafting a C# forms Editor From scratch htt ...
随机推荐
- 微软TTS,Neospeech TTS 简单使用
今天搞了下微软的TTS,逛了好多网页.博客,拼拼凑凑搞了点东西吧. 首先添加类库调用,系统自带的system.speech using System.Speech.Synthesis; 然后就能调用方 ...
- 你所不了解的css选择器
我们目前接触到的选择器:.class #id div ...... 不了解的选择器:a>b a+b [a~=b] [a|=b]...... 一下说举5 6 7 8为css3中的定 ...
- CSS 设计彻底研究(五)文字与图像
第五章 文字与图像 5.1.2 设置字体 通过font-family属性设置字体.可以声明多种字体,字体之间用逗号分隔开.如一些字体名称中间有空格,需用双引号将其引起来,使浏览器知道这是一种字体的名称 ...
- (转) Name visibility
Scopes Named entities, such as variables, functions, and compound types need to be declared before b ...
- android启动activity文本框不获得焦点
在开发中,常常会碰到这种情况,打开一个activity后,第一个文本框自动获得焦点,同时会弹出软键盘输入框,这样很影响用户体验,现在来看解决方法. 我们先来看看为什么会出现上述情况,原因很简单,文本框 ...
- php get_ini 和 get_cfg_var 的区别
get_ini 和 get_cfg_var 都是用来获取 php 配置信息的函数. 区别是 get_ini 是用来获取当前运行的配置信息,get_cfg_var 是用来获取配置文件(php.ini)的 ...
- webservice 技术改进
Webservice 技术改进 1.不同系统不同语言之间的交互 基于http协议进行传输,使用REST服务实现WS 2.不同系统相同语言之间的交互 使用RPC(romate process call) ...
- Linux服务器配置WEB应用日志文件到指定目录
在Linux服务器上配置WEB应用程序日志到指定文件 服务器环境是 RedHat Linux, 其上运行的是 Apache + Tomcat,容器中运行的是我们公司的壹个小型电子商务网站,原来项目 ...
- SPI 四种模式
SPI时钟极性CPOL, = 0表示在没有数据传输时为低电平,= 1表示没有数据传输时为高电平. SPI时钟相位CPHA,= 0表示时钟的第一个沿更新数据.第二个沿锁存数据,= 1表示时钟的第一个沿锁 ...
- Qt 自定义事件详细实例(继承QEvent,然后QCoreApplication::postEvent()、sendEvent())
创建用户事件 创建一个自定义类型的事件,首先需要有一个事件号,其值通常大于QEvent::User.为了传递事件信息,因此必须编写自定义的事件类,该事件类从QEvent继承. 编写用户事件:编写用户事 ...