1. 程式人生 > 其它 >2021 OO 第四單元總結部落格

2021 OO 第四單元總結部落格

2021 OO 第四單元總結部落格

需求回顧

本單元需要完成的任務是實現一個UML類圖分析器,能夠處理類圖、狀態圖、時序圖,並對模型的有效性進行檢查。

這一單元並不是很難,但總體而言對細節要求比較高。

一 本單元架構設計

1.1 Homework13

本次作業需要實現對類圖的處理。

我們首先需要解決的問題是如何從各個Element中將各個屬性解析出來並建立有機聯絡。它們的關係是這樣的:

  • UmlClass
    • UmlAttribute
    • UmlOperation
      • UmlParameter
  • UmlInterface

因此為實現層層存放,方便新增屬性和方法,我分別對頂層類非底層型別建立自己的類,即:MyClass

MyOperationMyInterface.

由於本單元作業資料輸入方式非常特殊,當讀到END_OF_MODEL時一切資訊都已塵埃落定,我們把這些資訊搞到手了,不需要考慮資料的動態變化,只需要根據指令查就完了。所以我新開了一個MyPretreatment類,用於處理和將資訊組織進各個類(進行兩次遍歷,第一次遍歷將元素按就九大型別分類儲存;第二次按順序自底層向上遍歷,構建資訊樹),並將存有所有類和介面的HashMap返回給MyUmlInteraction類,在該類中實現各種方法。在方法實現中,有兩點值得一提:

  • 將重複資訊存入HashSet以檢測異常

    在第一次遍歷的資訊分類存放時,我把Class

    idKey值存成了HashMap;在第二次遍歷時遍歷該HashMap,並組建以Name為索引的雜湊字典。每次加入一個新的Class時我都會檢測重名,一旦重名,就會把這個名字加入重名的HashSet中:

    public void handleClass() {
            for (MyClass myClass : this.classes.values()) {
                this.names.put(myClass.getMyClassName(), 0);
                if (!this.className.containsKey(myClass.getMyClassName())) {
                    //如果叫這個名字的類還沒有來過
                    this.className.put(myClass.getMyClassName(), myClass);
                    //存入按名字索引的字典中
                } else {
                    this.dupClass.put(myClass.getMyClassName());
                    //存入重名雜湊集合中
                }
            }
        }
    

    這樣做的好處是,我們只需要根據dupClass.contains(name)就能判斷是否發生異常了。

    同樣的思想可以用於檢測屬性是否重名。

  • 設定髒位,使需遞迴呼叫的複雜方法僅執行一遍

    本單元作業的資料輸入形式決定了兩行相同的查詢指令一定能得到相同的結果,因此如果這是一個複雜度較高的查詢,我們可以把第一次的結果存下來,使第二次只有IO時間。以介面父類查詢指令為例,每個MyInterface中都有這樣一個屬性:

    private HashSet<String> interfaceName;
    

    用於存放自己父類的所有名字。每次查詢介面父類的指令到來時,我們只需檢測interfaceName這一屬性是否為空,如果不空則說明已經進行過查詢,可直接返回;如果為空,則表示我們需要進行查詢操作。

    public HashSet<String> getInterfaceName() {
            if (this.interfaceName.size() == 0) {
                //進行一波查詢操作
            }
            return this.interfaceName;
        }
    

1.2 Homework14

本單元在上次作業的基礎上加入了順序圖和狀態圖的相關需求。總體的架構可以直接沿用上一次的架構,只是這次各Element之間的關係變得更加複雜:

  • UmlClass
    • UmlAttribute
    • UmlOperation
      • UmlParameter
  • UmlInterface
  • UmlInteraction
    • UmlLifeline
  • UmlStateMachine
    • UmlState, UmlFinalState, UmlPesudostate
      • UmlTransition
        • UmlEvent

仍然對頂層類非底層型別建立自己的類,即新增:MyInteractionMyLifelineMyMachineMyStateMyTrasition類。第二次作業的組織結構和第一次完全一致,只是增添了很多更細節的處理,在此不過多贅述。

1.3 Homework15

第三次作業中新增了模型有效性檢查的需求。本次對於之前的架構也沒有任何更改,只需在END_OF_MODEL之後進行八種模型檢查,如各有效性都符合,則直接呼叫第二次作業中的各方法實現查詢即可。

本次架構唯一修正的地方是因為MyUmlGeneralInteraction方法過長,對以下部分進行了封裝:

  • 封裝了所有的異常檢查,將重複出現的各異常檢查封裝為MyTools類中的函式
  • 封裝了引數型別的有效性檢查,將其變為MyTools類中的函式
  • 封裝了將引數型別的字串轉換,將其變為MyTools類中的函式

為了進一步降低MyUmlGeneralInteraction類行數的負擔,統一採用在MyPretreatment類中進行檢查的方式,MyUmlGeneralInteraction中僅負責I/O操作,以R001為例:

@Override
public void checkForUml001() throws UmlRule001Exception {
    HashSet<AttributeClassInformation> cacheR001 = this.myPretreatment.checkR001();
    if (cacheR001.size() != 0) {
        throw new UmlRule001Exception(cacheR001);
    }
}

在方法實現上,值得一提的是,我最初被R002卡了不久的時間,我不知道該如何輸出環上的所有介面,即如何在dfs中有效地記錄路徑,在構造這個演算法的過程中我持續遇到各種奇奇怪怪的bug,比如,如果它屬於多個環怎麼辦,環中環怎麼辦....最後發現自己的想法完全走偏了,我們只需要遍歷所有介面和類,判斷當前這個類或介面是否迴圈繼承,如果出現了迴圈繼承就把它加入到待輸出的HashSet中就好了。

二 四個單元中架構設計及OO方法理解的演進

1.1 Unit One

第一單元實現的是表示式求導。我的架構主要是通過繼承與介面,實現了自底向上的求導。各因子分為常數因子、冪因子、表示式因子、正弦函式因子和餘弦函式因子,統一繼承Factor抽象類。其餘包括表示式類、多項式類、因子類,及通過工廠模式解析原始輸入的工廠類。

本單元對OO思想的指導是飛躍性質的,第二次作業的煎熬使我發現,難住我的正是我的思維方式,因此我一直在思考的是,我到底通過怎樣的過程才能把這一次的程式碼實現。而我真正需要思考的是,我需要什麼類,每個類中需要放什麼方法和屬性,類與類之間需要構造什麼樣的關係才能把方法實現。經歷這些思考之後,我對面向物件的思維方式和繼承有了比較深入的理解。

(碎碎念一句:第一單元還是有遺憾的,我應該學一學遞迴下降的,而不是抱殘守缺地使用又臭又長的正則表示式,好傢伙,那個正則展開之後自己能佔IDEA的半屏...)

1.2 Unit Two

來到了第二單元,傳說中難度巔峰的電梯單元,雖然沒有被死鎖折磨過,但我時常被CTLE折磨。本單元感覺架構比較簡單,重點在於電梯執行演算法和多執行緒互動。由排程器將各請求分配給各個電梯(即將總請求佇列totalQueue中的各請求分給各電梯的waitqueue),每一個執行緒電梯通過自己等待佇列中人員的變化進行運輸。在第三次作業嘗試實現換乘的過程中,出了1mol多執行緒bug,各種人還沒從這個電梯出去,早就已經待在另一個電梯的靈異事件。通過解決這些問題我對多執行緒”鎖“的概念有了更多理解。

1.3 Unit Three

第三單元是jml約束下的社會關係網路。本單元不太需要架構,只需要按照課程組給出的jml寫成自己的java程式碼。這一單元我出現了許多效能問題,甚至第一次作業來一堆qnr我的程式就能1、2秒才蹦出來一個結果,慢到離譜。於是在修鍋的過程中對各個容器的使用有了更多認識。這一單元同時也是個比較強調演算法的單元,我動不動就在CTLE的邊緣瘋狂試探。不過藉此契機學會了變數維護這一思想,順帶學了一下並查集、Dijsktra堆優化,在效能和演算法上還是有張進的,這些都在第四單元繼續幫助了我。

看到這裡可能會覺得,你這而三單元也不咋有面向物件的收穫啊。沒錯,確實沒有什麼顯式收穫,但隱式收穫是很大的,正是由於前期的這些練習,第四單元的架構才能那麼順利。而且,在這個時期發生了一件小事使我感嘆於OO對我的影響。

就是在寫一次的數學建模作業時,需要用到程式設計計算一個量不小的資料表格,我這種建模萬年Python黨竟然覺得Python難用,迅速地設計出了一個通過面向物件思維實現的計算方式。當時恰巧趕上有不可推脫的事兒要我處理,但作業很快ddl了,隊友竟然告訴我

要不您寫個jml,我幫趕緊把它實現了吧。

這也太奇妙了,這大概就是我對面向物件理解的隱式演進吧,我已經願意把這些知識用到解決生活中實際遇到的問題了,那再談起我在OO中的收穫,此處無聲勝有聲。二三單元真的沒有白做訓練,它對於我的OO思想是量變積累質變的量變積累。

1.4 Unit Four

第四單元需要實現一個UML類圖分析器,詳細的架構其實在這篇部落格的第一章已經描述了。大致是通過MyPretreatment類進行存放的處理,把Uml...變成My...,接著再實現各種模式檢查和資訊查詢。

我好像面對那一堆Element的資訊自然而然就知道要構建自己的類(第一單元學的),把我們想存的東西通過我們喜歡的容器(第三單元學的),編寫合適的方法並實現出來。可以說之前看似無關的知識,在第四單元中都被綜合地應用起來了。一個個類已經不是費勁擠牙膏似的擠出來的了,而是一個水到渠成的過程,大概在這裡我收穫到了質變的回饋吧。

這應該就是我和我的作業架構以及OO思想演進的故事了。

三 測試理解與實踐演進

我對於程式正確性的驗證方式有個一以貫之的思想,就是讀程式碼為主,對拍打輔助。如果對拍機一直跑不出我的bug,我很難相信我的程式差不多行了;但讀了一遍程式碼之後,我往往會收穫一種莫名的安心(特別感謝從第一次作業開始次次都願意陪我一起讀程式碼的hxd!)。

雖然自己讀程式碼去驗證很香,但對拍機也真的很重要。從第三單元開始,我加入了一個對拍小組,我們在方寸田園裡共享一套對拍機,從搭評測機到樣例生成器到找資料到手動構造極端情況,我剋制不住地大喊,隊友們真的太強了!但也因為持續被帶飛,我有些一直停留在自己的舒適區內,應該勇敢一點往外走走的。

所以,我測試程式的過程總結起來是這樣的:

  • 第一單元

    主要依賴讀程式碼,並通過同學寫的評測機對拍。這時候的資料生成器是宇宙無敵純隨機,但只要測得夠多,就能量變引發質變地找出bug。但當時沒有平衡好資料好debug資料強度的關係。在第三次作業的測試裡盲目取消了大整數(為了檢查WF和好算bug在哪兒),然後我的大整數WF bug就巧妙避開了對拍機的搜查,被強測逮住了。不過主要還是那次寫得太拉沒來得及讀程式碼吧。

  • 第二單元

    主要依賴讀程式碼,並試圖改造了一個現成評測機。但實際上,並沒有實現對CTLE的判斷,導致輪巡陰魂不散地跟了整個電梯月。

  • 第三單元

    先利用對拍測試,直到對拍機健康執行後開始讀程式碼。也在對拍小組裡其他成員寫的樣例生成器中學到了很多東西,比如利用樹構造特殊圖;照著單條指令反覆懟測效能;把最初隨機生成的一些資訊存放起來再使用,保證資料別光測異常而沒有強度...資料很有效,幫我找出了不少效能問題。

  • 第四單元

    先利用對拍測試,直到對拍機健康執行後開始讀程式碼。在這個單元裡,寫樣例的隨機生成器變得比較困難,我們擁有的測試點變得有限。於是開始試圖手動構造極端樣例(比如胡亂存放null之類的)對程式進行測試。不少奇奇怪怪的鍋被翻了出來。

可以說,做測試的時間往往不比寫程式碼的時間短,二者旗鼓相當。

四 課程收穫

收穫其實很多啊,小的大的,真的能囉嗦一籮筐。我撿著比較重要的三點,說一說吧。

  • 面向物件的程式設計思想

    這部分的詳細內容已經在第二章中分析了。

  • 合作的重要性

    一個人走得快,一群人走得遠。

    遇到對拍小組的各位是真的很開心,雖然更近距離認識到了自己的菜,但在合作的過程中看到了好多一個人可能看不到的風景。

  • 永遠不要相信自己的程式碼沒有bug

    我之前總是覺得,程式碼中出現bug,類似於一種需要女媧補天的嚴重漏洞。經過OO的學習我才發現,bug真的可以非常微小,但輕易達成千裡之堤潰於蟻穴的效果。它可以是忘記一個=,可以是寫錯一個字母,可能是一個變數沒加this.導致混用...因此充分的設計與良好的程式碼風格都是程式設計師的必修課,我們需要採取多方努力,儘量減少bug中的出現,並永遠對自己的程式碼保持謙卑的心,沒有程式碼不值得除錯!

五 改進性建議

  1. 可以在部落格周開放下一單元的Training

    本學期的四次Training真的對我學習當前單元起了非常大的作用,但也導致了每單元第一次作業的時間緊迫,總覺得有太多東西要學,太高的門檻要入。所以或許可以提前開放以下Training,下放一些下單元的學習資料。

  2. 稍稍增加一些pre的訓練量

    第一單元的開幕雷擊很大程度是還沒有太熟悉java的面向物件程式設計思想,導致一切都慌張倉促著。而經過大半學期的訓練,到最後寫第四單元的作業,架構時明顯順滑了很多。所以究其原因,很多難受還是熟練度帶來的,因此或許可以稍稍增加一些pre的程式碼量,並與後面的知識產生一定的關聯,降低一下OO的開門門檻。

  3. 堅持開設官方答疑帖

    希望大家能一起真的把討論區利用起來。三四單元比較瑣碎,很多地方難以理解,這時候同學們就需要一點必要的解釋,可以利用官方答疑帖釋疑的方式滿足同學們的需求並及時實現訊息共享。

算是一些吹毛求疵的建議啦,OO課程組已經很用心了,年年革新的勇氣也令我非常敬佩!

寫在最後

真的到了最後一筆,曾以為的千言萬語突然如鯁在喉,那就...這樣吧:

public class MainClass {
    public static void main(String[] args) throws Exception {
        System.out.println("Thanks to OO, wish the team prosperous!");
    }
}