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)

相關文章
相關標籤/搜索