天天看點

OO第三次部落格作業

1, 規格化設計的發展曆程

關于規格化設計一詞,能找到的資料實在少之又少。在筆者已經找到的資料中,筆者認為,規格化設計來自于曆史上第一次軟體危機之後,雖然當時已經有了大量的面向過程式的程式設計語言譬如COBOL,BASIC,C語言等等,但是其中含有的大量的goto語句導緻的面條式代碼極大地限制了程式規模。

為了解決如上的問題,人們提出了結構化程式設計這一中程式設計範型。它采用子程式(函數就是一種子程式)、代碼區塊、for循環以及while循環等結構,來替換傳統的goto。希望借此來改善計算機程式的明晰性、品質以及開發時間,并且避免寫出面條式代碼。

但是,由于硬體的飛速發展,業務的需求越來越複雜,爆發了第二次軟體危機。此次危機主要展現在“可擴充性”,“可維護性”兩個方面。由于傳統面向過程式的程式設計難以适應快速多變的業務需求,因而産生了面向對象式的程式設計思想。在筆者看來,規格化設計一詞,是在結構化設計的基礎上,為了适應程式的“可擴充性”和“可擴充性”而衍生出來的重要設計思想。

一個經過優秀的規格化設計的程式,有着如下的優勢:其一,有助于開發者們互相了解對方的程式,友善程式員之間的分工,加快開發速度。如今的工程,往往都是由一個團隊共同完成。那麼對于團隊中的成員來說,互相了解對方的代碼是十分重要的,這樣才能更好地完成某個項目。其次,對于編寫函數庫的程式員來說規格化設計更為重要。其原因就在于函數庫的使用者并不需要知曉庫程式員的代碼思路與實作過程,他隻需要知道庫中的某個函數,它需要的參數是什麼,會對什麼資料進行修改,修改的效果是什麼,也就是我們JSF中的REQUIRES,MODIFIES,EFFECTS三個重要的功能。其二,規格化的設計能很好地提高程式的“可擴充性”與“可維護性”。當開發者清楚自己所寫的每一個方法的規格時,他能夠很快速地針對每一個bug追本溯源,找出問題所在。其次,在面對如今工程需求快速變化的情況時,開發者通過規格化的設計可以很好地了解自己的程式應該如何進行修改以适應需求的變化。

2,三次作業的規格BUG

筆者的後三次計程車的作業并沒有在規格上被測試者報告BUG,但這并不意味着筆者的規格便沒有任何的錯誤,僅僅是遇到了“心地善良”的測試者罷了。

那麼筆者便來自己細數自己在規格上出現的纰漏以及修改的方案。

3,JSF不規範的寫法以及改進

1)

1 /**
 2      * @REQUIRES:(\all String str;str != null,path != null)
 3      * @MODIFIES:fw
 4      * @EFFECTS:
 5      *     (\all str != null) ==> fw.write(str+"\n"); 
 6      */
 7     public static void appendFile(String Path,String str) {
 8         try {
 9             File f = new File(path);
10             FileWriter fw = new FileWriter(f, true);
11             fw.write(str + "\n");
12             fw.close();
13         } catch(Exception e) {
14         }
15     }
16 }      

該方法的的作用顯而易見,便是向檔案中寫入資訊,那麼該方法的JSF出了什麼纰漏呢?

首先我們對REQUIRES的定義是什麼?它的定義是前置條件,也就是執行該方法前對該輸入或者系統狀态的要求。

該方法的前置條件是字元串str的含義是檔案的路徑,那麼其前置條件便是str是合法的檔案路徑,而源代碼中的前置條件僅僅強調了path != null,顯然這并沒有滿足要求。

而MODIFIES的定義副作用,是對input的修改,即對輸入的修改。顯而易見的是,本方法并沒有對輸入進來的str進行任何修改,是以無法滿足要求。

最後的EFFECTS的定義是後置條件,即執行後的傳回結果或者系統狀态應該滿足的限制。本方法對系統造成的影響是往檔案中寫入字元串

是以對該規格的改進為:

1 1 /**
 2  2      * @REQUIRES:(str!=null) && (path is a legitimate file path) 
 3  3      * @MODIFIES:none
 4  4      * @EFFECTS:
 5  5      *     (\all str != null) ==> fw.write(str+"\n"); 
 6  6      */
 7  7     public static void appendFile(String Path,String str) {
 8  8         try {
 9  9             File f = new File(path);
10 10             FileWriter fw = new FileWriter(f, true);
11 11             fw.write(str + "\n");
12 12             fw.close();
13 13         } catch(Exception e) {
14 14         }
15 15     }
16 16 }      

2)

1 /**
 2      * @REQUIRES:Point p,q;int f;
 3      * @MODIFIES:flow[][];
 4      * @EFFECTS:
 5      *     (isAdh(p,q) == false) ==> (/result == false);
 6      *     (isAdh(p,q) == true) ==> (/result == true)&&(flow[getPointId(p)][getPointId(q)] == flow[getPointId(p)][getPointId(q)]+f)&&(flow[getPointId(q)][getPointId(p)] == flow[getPointId(q)][getPointId(p)]+f);
 7      */
 8     public boolean addFlow(Point p, Point q, int f) {
 9         if(!isAdj(p, q)) {
10             return false;
11         }
12         synchronized(flow) {
13             flow[getPointId(p)][getPointId(q)] += f;
14             flow[getPointId(q)][getPointId(p)] += f;
15         }
16         return true;
17     }      

該方法的REQUIRES不符合要求,應該要标明p,q和f的要求等情況

修改方法如下所示

/**
     * @REQUIRES:(Point p,q;p!=null,q!=null;int f,f > 0);
     * @MODIFIES:flow[][];
     * @EFFECTS:
     *     (isAdh(p,q) == false) ==> (/result == false);
     *     (isAdh(p,q) == true) ==> (/result == true)&&(flow[getPointId(p)][getPointId(q)] == flow[getPointId(p)][getPointId(q)]+f)&&(flow[getPointId(q)][getPointId(p)] == flow[getPointId(q)][getPointId(p)]+f);
     */
    public boolean addFlow(Point p, Point q, int f) {
        if(!isAdj(p, q)) {
            return false;
        }
        synchronized(flow) {
            flow[getPointId(p)][getPointId(q)] += f;
            flow[getPointId(q)][getPointId(p)] += f;
        }
        return true;
    }      

3)

/**
     * @MODIFIES: None
     * @EFFECTS: /result == dist[getPointId(q)];
     */
    public int getDistance(Point p, Point q) {
        int[] dist = new int[map_size * map_size];
        int[] minFlow = new int[map_size * map_size];
        Point[] prev = new Point[map_size * map_size];
        minPath(p, q, dist, minFlow, prev);
        return dist[getPointId(q)];
    }      

本方法沒有寫清楚前置條件和副作用

該方法并不僅僅是獲得p,q兩點之間的距離,它為了得到這個距離尋找了兩點之間的最短路徑,是以會有一定的副作用。

修改方法如下所示:

1 /**
 2      *REQUIRES:(Point p,q;p != null,q != null);
      * @MODIFIES: minPath(p, q, dist, minFlow, prev);
 3      * @EFFECTS: /result == dist[getPointId(q)];
 4      */
 5     public int getDistance(Point p, Point q) {
 6         int[] dist = new int[map_size * map_size];
 7         int[] minFlow = new int[map_size * map_size];
 8         Point[] prev = new Point[map_size * map_size];
 9         minPath(p, q, dist, minFlow, prev);
10         return dist[getPointId(q)];
11     }      

4,功能BUG與規格BUG的聚焦分析

筆者的功能BUG與規格的BUG之間并沒有太大的聯系,可能原因在于筆者是在寫完整個程式之後方才寫的規格,而且由于部分方法所完成的功能太多,行數過長,因而導緻JSF難以完成,是以這部分的JSF筆者并沒有很好地去完善,測試者也并沒有認真地去追究這些問題。

3,設計規格和撰寫規格的基本思路和體會

筆者在完成這三次作業的時候,是先寫完整個程式之後再去寫的每個方法和類的規格,仔細分析,有好有壞,但是弊大于利。其好處就在于寫程式的時候不需要考慮的規格的問題,因而能提高速度。但是缺點也很明顯,由于沒有事先設計好規格,沒有仔細明确每個方法的要求,目的,以及副作用,在後期DEBUG的時候會造成巨大的困擾——不知道究竟是哪個類,哪個方法的問題。是以,痛定思痛過後,還是應該養成先确定好每個類的目标,即Overview,再确定為了完成這些目标所需要的方法,确定其REQUIRES,MODIFIES,EFFECTS,然後再依據自己的規格去實作自己的代碼。我想這樣的話能夠減少不少DEBUG的時間,也能達到規格化訓練的目的。更加重要的是,這樣做可以更加友善測試者了解并且測試你的程式,利己利人,何樂而不為呢?

以上便是筆者對于這三次作業的拙見,OO這門課即将到達尾聲,一路走來經曆了太多,正如那句耳熟能詳的話語:OO不易,和諧6系。為了完成作業經常熬夜通宵什麼的确實付出了很多,但同樣的是,我們收獲的,也很多!