《程序員修煉之道》——第二章 注重實效的途徑(一)

7、重複的危害
  咱們以爲,可靠地開發軟件、並讓咱們的開發更易於理解和維護的惟一途徑,是遵循咱們稱之爲DRY的原則:程序員

  系統中的每一項知識都必須具備單1、無歧義、權威的表示。數據庫

    DRY-Don't Repeat Yourself.編程

      不要重複你本身。緩存

  與此不一樣的作法是在兩個或更多的地方表達同一事物。若是你改變其中一處,你必須記得改變其餘各處。或者,就像那些異型計算機,你的程序將由於自相矛盾而被迫屈服。這不是你是否能記住的問題,而是你什麼時候忘記的問題。服務器

 

  重複是怎樣發生的編程語言

  咱們所見到的大多數重複均可納入下列範疇:函數

  • 強加的重複(imposed duplication)。開發者以爲他們無可選擇——環境彷佛要求重複
  • 無心的重複(inadvertent duplication)。開發者沒有意識到他們在重複信息。
  • 無耐性的重複(inpatient duplication)。開發者偷懶,他們重複,由於那樣彷佛更不容易。
  • 開發者之間的重複(interdeveloper duplication)。同一團隊(或不一樣團隊)的幾我的重複了一樣的信息。

 

  強加的重複性能

  有時,重複彷佛是強加給咱們的。項目標準可能要求創建含有重複信息的文檔,或是重複代碼中的信息的文檔。多個目標平臺各自須要本身的編程語言、庫以及開發環境,這會使咱們重複共有的定義和過程。編程語言自身要求某些重複信息的結構。咱們都在咱們以爲無力避免重複的情形下工做過。然而也有一些方法,可用於把一項知識存放在一處,以遵照DRY原則,同時也讓咱們的生活變得更容易一點。學習

  信息的多種表示。在編碼一級,咱們經常須要以不一樣的形式表示同一信息。咱們也許在編寫服務器應用,在客戶和服務器端使用了不一樣的語言,而且須要在兩端都表示某種共有的結構。咱們或許須要一個類,其屬性是某個數據庫表的schema(模型、方案)的鏡像。你也許在描寫一本書,其中包括的程序片斷,也正是你要編譯並測試的程序。測試

  發揮一點聰明才智,你一般可以消除重複的須要。答案經常是編寫簡單的過濾器或代碼生成器。能夠在每次構建軟件時,使用簡單的代碼生成器,根據公共的元數據表示構建多種語言下的結構。能夠根據在線數據庫schema、或是最初用於構建schema的元數據,自動生成類定義。

  代碼中的文檔。程序員被教導說,要給代碼加上註釋;好代碼有許多註釋。遺憾的是,沒有人教他們,代碼爲何須要註釋;糟糕的代碼才須要許多註釋。

  DRY法則告訴咱們,要把低級的知識放在代碼中,它屬於那裏;把註釋留給其餘的高級說明。不然,咱們就是在重複知識,而每一次改變都意味着既要改變代碼,也要改變註釋。註釋將不可避免地變得過期,而不可信任的註釋徹底沒有註釋更糟。

  文檔與代碼。你撰寫文檔,而後編寫代碼。有些東西變了,你修訂文檔、更新代碼。文檔和代碼都含有同一知識的表示。而咱們都知道,在最緊張的時候——最後期限在逼近,最重要的客戶在喊叫——咱們每每會推遲文檔的更新。

  語言問題。許多語言會在源碼中強加可觀的重複。若是語言使模塊的接口與實現分離,就經常會出現這樣的狀況。C和C++有頭文件,在其中重複了被導出變量、函數和(C++)類的名稱和信息。Object Pascel甚至會在同一文件裏重複這些信息。若是你使用遠地過程調用或CORBA,你將會在接口規範與實現它的代碼之間重複接口信息。

  

  無心的重複

  有時,重複來自設計中的錯誤。

  讓咱們看一個來自配送行業的例子。假定咱們的分析揭示,一輛卡車有車型、牌照號、司機及其餘一些屬性。與此相似,發運路線的屬性包括路線、卡車和司機。基於這一理解,咱們編寫了一些類。

  但若是Sally打電話請病假、咱們必須改換司機,事情又會怎麼樣呢?Truck和DeliverRoute都包含有司機。咱們改變哪個?顯然這樣的重複很糟糕。根據底層的商業模型對其進行規範化——卡車底層的屬性集真的應包含司機?路線呢?又或許咱們須要第三種對象,把司機、卡車及路線結合在一塊兒。無論最終解決方案是什麼咱們,咱們都應避免這種不規範的數據。

  當咱們擁有多個互相依賴的數據元素時,會出現一種不那麼顯而易見的不規範數據。讓咱們看一個表示線段的類:

1 class Line{
2     public:
3      Point start;
4      Point end;
5      double length;
6 };

  第一眼看上去,這個類老是合理的。線段顯然有起點和終點,並老是有長度(即便長度爲零)。但這裏有重複。長度是由起點和終點決定的:改變其中一個,長度就會變化。最好是讓長度成爲計算字段:

class line{
    public:
     Point start;
     Point end;
     double length() {return start.distanceTo(end);}
};

  在之後的開發過程當中,你能夠由於性能緣由而選擇違反DRY原則。這常常發生在你須要緩存數據,以免重複昂貴的操做時。其訣竅是使影響局部化。對DRY原則的違反沒有暴露給外界:只有類中的方法須要注意「保持行爲良好「。

class Line {
    private:
     bool changed;
     double length;
     Point start;
     Point end;

    public:
     void setStart(Point p)    {start = p; changed = true;}
     void setEnd(Point p)    {end = p; changed = true;}

    Point getStart(void)    {return start;}
    Point getEnd(void)    {return end;}

    double getLength() {
        if (changed) {
            length = start.distanceTo(end);
            changed = false;
        }
        return length;
    }
};

這個例子還說明了像Java和C++這樣的面嚮對象語言的一個重要問題。在可能的狀況下,應該老是用訪問器(accessor)函數讀寫對象的屬性。這將使將來增長功能(好比緩存)變得更容易。

 

  無耐性的重複

  每一個項目都有時間壓力——這可以驅使咱們中間最優秀的人走捷徑的力量。須要你與寫過的一個例程類似的例程?你會受到誘惑,去拷貝原來的代碼,並作出一些改動。須要一個表示最大點數的值?若是我改動頭文件,整個項目就得從新構建。也許我應該在這裏使用直接的數字,這裏,還有這裏,須要一個與Java Runtime中的某個類類似的類?源碼在那裏(你有使用許可),那麼爲何不拷貝它、並做出你所需的改動呢?

  若是你以爲受到誘惑,想想古老的格言:「欲速則不達」。你如今也許能夠節省幾秒鐘,但之後卻可能損失幾小時。、

  無耐性的重複是一種容易檢測和處理的重複形式,但那須要你接受訓練,並願意爲避免之後的痛苦而預先花一些時間。

 

  開發者之間的重複

  另外一方面,或許是最難檢測和處理的重複發生在項目的不一樣開發者之間。整個功能集均可能在無心中被重複,而這些重複可能幾年裏都不會被發現,從而致使各類維護問題。

  在高層,能夠經過清晰的設計、強有力的技術項目領導,以及在設計中進行獲得了充分理解的責任劃分,對這個問題加以處理。可是,在模塊層,問題更加隱蔽。不能劃入某個明顯的責任區域的經常使用功能和數據可能會被實現許屢次。

  咱們以爲,處理這個問題的最佳方式是鼓勵開發者相互進行主動的交流。設置論壇,用以討論常見問題。讓某個團隊成員擔任項目資料管理員,其工做是促進知識的交流。在源碼樹種指定一箇中央區域,用於存放實用例程和腳本。必定要閱讀他人的源碼與文檔,不論是非正式的,仍是進行代碼複查。你不是在窺探——你是在向他們學習。並且要記住,訪問時互惠的——不要由於別人鑽研你的代碼而苦惱

  

  讓複用變得容易

  你所要作的是營造一種環境,在其中要找到並複用已有的東西,比本身編寫更容易。若是不容易,你們就不會複用。而若是不進行復用,大家就會有重複知識的風險。

相關文章
相關標籤/搜索