How to Avoid Producing Legacy Code at the Speed of Typing

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

About the Author

%7B27a2c13d-5b57-43d0-a97a-917ad088f622%7D.png

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

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

 

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):

 

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)

转载于:https://www.cnblogs.com/buyixiaohan/p/4627803.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/471793.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c语言程序做成可执行文件,windows环境下C程序生成可执行文件

windows环境下,编写C程序,生成.exe,用于操作某个文件。包含三部分:搭建环境、程序实现、程序分析。1、搭建程序编写和编译环境在windows下安装Git Bash(下载页面)。安装完成后,可以在windows的任意文件夹下&#xff0c…

LeetCode MySQL 1890. 2020年最后一次登录(year)

文章目录1. 题目2. 解题1. 题目 表: Logins -------------------------- | 列名 | 类型 | -------------------------- | user_id | int | | time_stamp | datetime | --------------------------(user_id, time_stamp) 是这个表的主键。 每一…

LeetCode MySQL 1873. 计算特殊奖金(case when then else end)

文章目录1. 题目2. 解题1. 题目 表: Employees ---------------------- | 列名 | 类型 | ---------------------- | employee_id | int | | name | varchar | | salary | int | ----------------------employee_id 是这个表的主键。 此表的每…

LeetCode 1868. 两个行程编码数组的积(双指针)

文章目录1. 题目2. 解题2.1 模拟超时2.2 优化1. 题目 行程编码(Run-length encoding)是一种压缩算法,能让一个含有许多段连续重复数字的整数类型数组 nums 以一个(通常更小的)二维数组 encoded 表示。 每个 encoded[…

LeetCode MySQL 1587. 银行账户概要 II

文章目录1. 题目2. 解题1. 题目 表: Users ----------------------- | Column Name | Type | ----------------------- | account | int | | name | varchar | -----------------------account 是该表的主键. 表中的每一行包含银行里中每一个用户的账号…

LeetCode MySQL 1667. 修复表中的名字

文章目录1. 题目2. 解题1. 题目 表: Users ------------------------- | Column Name | Type | ------------------------- | user_id | int | | name | varchar | -------------------------user_id 是该表的主键。 该表包含用户的 I…

c语言汇编混合编程写一个乘法,求通过C语言实现矩阵的加、减及乘法。要自己写的,不要复制过来...

满意答案eevfikx22013.11.28采纳率:53% 等级:13已帮助:8891人#include using namespace std;int main(){int am3,bm3,an3,bn3;int a[am][an];int b[bm][bn];for(int i0;i{for(int j0;j{a[i][j]i*amj;}}for(int i0;i{for(int j0;j{b[i][j]i…

LeetCode MySQL 1821. 寻找今年具有正收入的客户

文章目录1. 题目2. 解题1. 题目 表:Customers -------------------- | Column Name | Type | -------------------- | customer_id | int | | year | int | | revenue | int | --------------------(customer_id, year) 是这个表的主键。 这个表…

【Head First Java 读书笔记】(一)基本概念

Java的工作方式 你要做的事情就是会编写源代码 Java的程序结构 类存于源文件里面 方法存在类中 语句存于方法中 剖析类 当Java虚拟机启动执行时,它会寻找你在命令列中所指定的类,然后它会锁定像下面这样一个特定的方法: public static void main(String[…

oid 值 内存使用_[技术干货] zabbix监控项原型组合键值

自动发现中监控项原型使用多个值组合成一个新的键值。这里我们以华为RH5885V3的内存为例:我们先walk出要用来作为组合键值的值,我们称之为VALUE。而OID节点后面延伸出来的数值,例如.1、.2、.3这种,我们称之为INDEX。组合键值的关键…

LeetCode MySQL 1853. 转换日期格式(日期格式化)

文章目录1. 题目2. 解题1. 题目 表: Days ------------------- | Column Name | Type | ------------------- | day | date | -------------------day 是这个表的主键。 给定一个Days表,请你编写SQL查询语句,将Days表中的每一个日期转化为&qu…

自定义计算器 android,自定义公式计算app下载

自定义公式计算器是非常强大的一款计算器软件,可以帮助大家计算各种函数,还能够自定义公式进行保存,便于以后的计算;软件包含了科学计算器的所有功能,而且没有广告,非常的方便和强大,喜欢的朋友…

android 行布局选择器,『自定义View实战』—— 银行种类选择器

在工作中难免遇到自定义 View 的相关需求,本身这方面比较薄弱,因此做个记录,也是自己学习和成长的积累。自定义View实战前言年前的最后一个开发需求,将之前H5开卡界面转变成native。意思就是开卡这个需求做成Android原生的界面&am…

LeetCode 1971. Find if Path Exists in Graph(图的遍历)

文章目录1. 题目2. 解题1. 题目 There is a bi-directional graph with n vertices, where each vertex is labeled from 0 to n - 1 (inclusive). The edges in the graph are represented as a 2D integer array edges, where each edges[i] [ui, vi] denotes a bi-directi…

更新wpscan_wpscan扫描工具

简介WPScan是一个扫描WordPress漏洞的黑盒子扫描器,可以扫描出wordpress的版本,主题,插件,后台用户以及爆破后台用户密码等,Kali Linux默认自带了WPScan,也可以到Github项目仓库[1]中下载安装,其…

LeetCode 1974. 使用特殊打字机键入单词的最少时间

文章目录1. 题目2. 解题1. 题目 有一个特殊打字机,它由一个 圆盘 和一个 指针 组成, 圆盘上标有小写英文字母 ‘a’ 到 ‘z’。 只有 当指针指向某个字母时,它才能被键入。指针 初始时 指向字符 ‘a’ 。 每一秒钟,你可以执行以…

LeetCode 1979. 找出数组的最大公约数

文章目录1. 题目2. 解题1. 题目 给你一个整数数组 nums ,返回数组中最大数和最小数的 最大公约数 。 两个数的 最大公约数 是能够被两个数整除的最大正整数。 示例 1: 输入:nums [2,5,6,9,10] 输出:2 解释: nums 中…

BZOJ 1529: [POI2005]ska Piggy banks( 并查集 )

每一连通块砸开一个就可以拿到所有的钱, 所以用并查集求连通块数 -------------------------------------------------------------------#include<bits/stdc.h>#define rep(i, n) for(int i 0; i < n; i)#define clr(x, c) memset(x, c, sizeof(x))using namespace …

LeetCode 1980. 找出不同的二进制字符串

文章目录1. 题目2. 解题1. 题目 给你一个字符串数组 nums &#xff0c;该数组由 n 个 互不相同 的二进制字符串组成&#xff0c;且每个字符串长度都是 n 。 请你找出并返回一个长度为 n 且 没有出现 在 nums 中的二进制字符串。 如果存在多种答案&#xff0c;只需返回 任意一个…

#时间预测算法_改进的智慧交通系统出行时间预测算法

引用Chowdhury N K, Leung C K S. Improved travel time prediction algorithms for intelligent transportation systems[C]//International Conference on Knowledge-Based and Intelligent Information and Engineering Systems. Springer, Berlin, Heidelberg, 2011: 355-3…