很久沒有發落落長的文章,剛好最近有一個程式重構的流程一直想跟人分享,但無奈自己講的熱血沸騰,旁人應該是聽的索然無味,所以趁重構過程終於告一段落,就來大書特書這精采絕倫的程式重構與規劃過程吧!
若對於程式重構的定義是:從程式架構整體考量功能增減或演進,那麼這個雲端文件典藏(其實就是瀏覽伺服器檔案這件事情)就歷經五次。
首先來看原始需求:伺服器上有實體檔案,實體檔案的位置與檔名分別紀載在資料庫內,希望在iPad上面有一個可以瀏覽這些文件的App。
依照這一個需求,很直覺的我使用了一個UITableView跟UIWebView,來建構這一個程式的雛形:
內容相當單純,藉由WebService接收資料庫內記載的資料夾與檔案列表放置到左側的UITableView中,點選資料夾時會重新回伺服器取得下一階層列表,點選檔案時則會判斷iPad中是否已經有該檔案,若已有檔案使用者可以選擇直接開啟,或是重新下載一次,下載完成後會於右側的UIWebView顯示文件內容(UIWebView內建支援許多檔案格式的瀏覽)。
就這樣,一個看似符合需求的程式完成了,但實際操作起來卻不是這麼順暢,這時就在心中默默想「這東西遲早要打掉重做的吧?」。
一進入App,第一個操作步驟就讓人覺得不暢快,原始設計是點擊左側列表,若是資料夾型態,就回伺服器取得下一層的資料夾與檔案列表,但按下回上一層按鈕時,也會執行回伺服器取得上一層的列表的function,好處是使用了同一組的程式來進行資料取得與展示,壞處是不管移至下一層或上一層都有一段等待時間;當然為了保持資料即時性,我們不可能將資料階層預存在iPad當中,但是對於才取得的列表應該要做一些處理,也就是回上一層時不應該再去伺服器拿取。
想到的解決方案有兩個:
1. 相當單純的就把他存起來,利用程式的操作來進行回上一層的動作,但是這會面臨程式碼的大量修改。
2. 將既有承接資料的UITableView,改成點擊後會自己再建立一個自己,並將由WebService取得的資料列表放入新的UITableView當中,最後利用Navigation的方式把新的TableView推進來。
最終當然是採用了第二種方式,所以架構修正成下圖,若點擊資料夾則會alloc新的UITableView並推進來,這樣就解決了回上一層還需要讀取的問題,左邊選單的操作也變得更加順暢。
改善左側的操作後,立刻面臨一個非常蠢又超重大的問題,就是在雛型設計時並沒有去考慮下載多個檔案的情況,導致如果使用者下載一個10mb的pdf檔案,就必須等下載完成後,才可點擊另一個檔案來進行下載的動作,若下載中直接點選其他檔案,則會直接處理新檔案的下載,而放棄舊的內容;這樣的操作模式相當不符合一般對於這種程式的認知,所以勢必要對下載檔案的處理進行不小幅度的變更。
方式是將檔案下載部分拉出來建立一個NSOperation,利用NSOperationQueue的方式來處理多線程的部分,進行測試→非常順利的在背景默默執行多檔案下載的工作。
但其實這樣的修改對於我原始的架構有一個致命的BUG:因為檔案下載是點選左側列表後要執行的動作,所以想當然爾的將傳遞與接收的各項delegate都寫在UITableVivew當中,但因為已進行第一步驟多個UITableView的處理,所以當操作流程是為:
1. 到達第五層資料夾後點了一個檔案下載。
2. 回到第四層查詢其他需要瀏覽的檔案。
這時候就會發生程式錯誤,原因是第五層的UITableView已經被release了,所以理想的做法是將檔案下載過程的各項處理全數搬移到會一直存在的View上面,而後來我是決定將操作丟在UIWebView當中。
所以目前這個程式已經中庸的完成一般使用者操作的習慣,但對於文件典藏來說橫式的介面可能並不適合文件的瀏覽,大部分的人會希望以直式瀏覽的方式來閱讀A4的word或是pdf檔案,而貼心的iOS也提供了SplitViewController來進行這樣的操作,一般情況下SplitView可以放置兩個View,左側的View會在橫擺的時候於左側,直擺的時候隱藏起來,並且我們可以建立一個於Navigation上面的按鈕,以popover的方式將隱藏的左側View叫出來讓使用者操作。
看起來這會相當的順利,但其實"事情絕對不會像路人、同事、長官想的那般簡單",iOS所提供的SplitViewController使用前提是"必須為RootView"才能一切正常的使用,所以更好的方法是我們可以模仿這種感覺,自己建立一個SplitView;所以架構就會改成下圖:
建立一個以兩個UIView為底的介面,實作出兩個UIView中間的拖曳bar,並且當直立iPad時隱藏左邊的UIView,而改以Popover的方式呈現。
後來我接取了正式資料庫的檔案階層架構,發現整個階層非常的複雜,在這樣的操作模式下有機會迷失自己所在的資料夾,所以增加了一個UILabel來顯示資料夾結構,而這資料夾結構顯示想當然就要跟各個UITableView的操作進行連接,這邊我是直接判斷點擊檔案與是否有利用Navigation的返回鈕離開,來決定要顯示的提示文字。
如果你一直看到這邊,腦海中應該會有一個跟我相同的想法,那下載進度?已經下載好的列表?這類更方便的操作體驗內容不做嗎?
就在別的地方重構也告一段落後,逮到一段時間我決定來好好挑戰所謂的下載進度與下載列表;一般我們在使用有下載功能的App時,都會有一個記數的地方,點選後會有一個列表可以看已經下載的項目跟目前的下載進度。
所以我需要一個UITableView來顯示這些資訊給使用者,並且放入一個客製化含有Progress的UITableViewCell,在使用者每一次於左側檔案列表點選時,將下載的內容放置到下載列表,並且依照NSURLConnection的didReceiveData delegate來顯示下載進度,大致上傳遞的架構如下圖:
整體上實現了最原始的要求:伺服器上有實體檔案,實體檔案的位置與檔名分別寄仔在資料庫內,希望在iPad上面有一個可以瀏覽這些文件的App。裡面加入了很多自己在使用App上認為比較好的操作模式,也利用delegate盡量切割各部份的關聯性,留下一些可以擴充其他功能的空間(自以為)。最後來看一下實際成果圖。
如果你不幸查到這篇文章,我想他可以幫助你的應該非常的少,在台灣進行"專案"的程式開發工作,其實不太允許執行的人做太多次重構的工作,是否要把自己不滿意的地方修正更好,不只受限於自身技術能力,更多的是專案執行的時間與人力,或是主事者是否有想做更好的意思,得過且過應該是許多同為程式設計產業的心態,從上到下貫徹"意思到"就好的部門或公司更不在其數,我們大家都知道先求有再求好的方式是必須的,但是也可以回頭想想,哪一個專案求了有以後,還有求好的?
不斷地檢視程式的架構、邏輯與使用體驗,並且跟"真的有用心"一起思考的人討論,絕對是幫助成果更好的不二法門。
5 則留言:
該說良禽擇木而棲咩:P
檔案傳輸為什麼會受到view release的影響呢?
因為我當時的架構,把接收檔案傳輸結果的delegate寫在那個view裡面。
所以做成model應該就可以了吧
如果要讓使用者可以看到執行的過程跟結果,還是必須要有一個View來顯示。
張貼留言