像機器一樣思考(六)—— 腦中的重構(gòu)

上一篇里,我們實現(xiàn)了一個小應用,而且用兩種設計。本文將給出更多的設計實現(xiàn)以繼續(xù)探討設計問題。

我們會看到,即便是紙上的圖,也可以進行重構(gòu)。由于紙是我們大腦的延伸,也可以稱之為腦中的重構(gòu)。而這種重構(gòu)的結(jié)果由于并不是真正的代碼,所以即便重構(gòu)錯了方向,設計不合理,拋棄也非常簡單,相對于改代碼而言,幾乎沒有成本。

簡單的改進

第一步我們先做得簡單一點,上一節(jié)我們停在這張圖上:

image1

我們可能會覺得上一節(jié)的圖有點啰嗦,比如buildStudentInfoPromptString和buildStudentSeqencePromptString兩個函數(shù),甚至generateReport都啰嗦了。那我就可以重構(gòu)一下,去掉這些啰嗦的內(nèi)容,變成下圖:

image2

上面的改進可能我們不太滿意,我們試著走另一個方向,從整個程序的角度來減少輸入輸出。我們前面說了,每個函數(shù)的輸入輸出種類越少越好,那么在我們這個題目里,推到極致就是每個函數(shù)不自己處理打印和讀取,所有的打印和讀取都放到一處。不但不打印和讀取,還不調(diào)用其他函數(shù),那么就會變得像下面這張圖:

image3

概念性思考

上面這個實現(xiàn)在過程層面已經(jīng)實現(xiàn)了只在一個函數(shù)中打印和讀取用戶輸入。然而這個最大的函數(shù)卻很臃腫,除了不干具體的活,這個系統(tǒng)中所有的邏輯它都知道。這種全知全能的函數(shù),如果有更多層級,更多菜單的應用,這個函數(shù)很容易就臃腫到不能維護的地步。

那么我們就來想一下,有沒有一種設計,在層級更多的時候表現(xiàn)的更好。這個時候我們需要一種思考能力,我們稱之為概念性思考。
概念性思考一般分為四步:

  1. 看到復雜場景背后的核心本質(zhì)
  2. 識別到兩個不相關(guān)的情景的相似之處
  3. 用比喻或類比來解釋場景
  4. 用一個框架去解決問題

那么我們按照這四步走,當前的復雜場景就是這個應用,看起來每次輸出之后,還會有下一次輸出之前的提示,好像每次的提示,輸入,輸入后的打印三個是一組的,其實完全可以把輸入后的打印和下一步的提示看作一次處理,也就變成了根據(jù)用戶一次輸入進行一種計算,將計算的結(jié)果轉(zhuǎn)化為一次輸出。這也就是它背后的核心本質(zhì)。

簡化成這種核心本質(zhì)后,有沒有一種已經(jīng)存在的類似情景呢?我們通過觀察很容易可以看出,一個簡單的Web應用和一個命令行應用沒什么區(qū)別,當然是沒有ajax的傳統(tǒng)web應用。用戶每做一次請求,然后得到一個響應,這個響應會渲染成一個頁面。

如果這兩個東西有些相似性的話,那么什么是一次請求到一次響應的結(jié)束呢?在這個應用里,輸入完字符串之后,敲擊回車就是一次請求。當敲擊回車后到下一次看到要求輸入時為止就是一次響應。

在我們這個應用里舉一個具體的例子來類比一下,在主界面輸入1,并敲擊回車,為一次請求;從敲擊回車后,經(jīng)歷諸如下列的文字被打印出來的階段:

請輸入學生信息(格式:姓名, 學號, 民族, 班級, 學科: 成績, ...),按回車提交:

一直到變成可以進行下一次輸入的狀態(tài)為止,為一次響應。其余的情況以此類推都可以類似的理解為請求和響應。

那么有沒有現(xiàn)成的一個web開發(fā)的框架可以照搬來處理這個命令行應用呢?那可以選擇的就多了,各種經(jīng)典的WebMVC框架都可以。當然照搬任何一個WebMVC框架的話的話可能會比較啰嗦,反而降低效率,我們可以在理解它們的概念的前提之下仿造一個,這就是下一節(jié)的內(nèi)容了。

至此,我把我在這件事情上進行概念性思考的過程展示給了大家。做一個優(yōu)秀的程序員,分析性思考和概念性思考是兩大關(guān)鍵思考能力,希望每個讀者都可以通過這一系列教程理解這兩種能力,從而在工作中進行刻意練習直到掌握這兩種能力。

我們的框架

有些話要說在前面,這個題目比較簡單,但我給出的解決方案是可以處理更多層級,更多菜單的,只是如果我給出的題目太過復雜的話,大家可能連讀完需求的耐心都沒有,更不要提理解解決方案了。所以我們算是用了一個復雜的方案來解決一個簡單的題目,中間的差距請大家自行想象,這個方案在更多層級和更多菜單時,才會真正顯現(xiàn)威力。

言歸正傳,在我們的這個框架里,我們的過程方面有三個概念:Router,Command和Service。
Router負責當每一個用戶輸入進來的時候,它知道去找哪一個具體的響應者,這個響應者就是我們的Command。
Router通過簡單的輸入解析,找到具體的Command,Command負責處理各種具體的用戶輸入解析,但是有一些核心的計算,比如說添加學生信息,我們定義為Service。Command會調(diào)用Service函數(shù)和將Service計算結(jié)果翻譯為用戶友好的輸出。當Command處理完一切,就會返回結(jié)果,結(jié)果就會統(tǒng)一的輸出。Command一般不直接輸出到用戶接口,這樣就會便于測試,就像我們所有的WebMVC框架一樣。管理這些輸入輸出的是我們的main函數(shù),它還會負責把我們的Router,Command配置在一起,像極了WebMVC里的配置文件。

過程方面分析完了,在數(shù)據(jù)方面,我們只需要把Service和Command之間的通訊,Command和最外層之間的通訊抽象出一些概念就可以了,比如Service和Command之間的數(shù)據(jù)可以叫Response,Command和最外層之間的數(shù)據(jù)可以叫View。

所以綜上,我們可以開始畫圖了,但是這個畫圖呢比較難畫在一張圖上,所以我們就把一次“請求”和一次“響應”畫成一張圖,這也是一種可視化的技巧,當我們把一件事畫成多張圖的時候,我們的大腦會自己把這些圖聯(lián)系到一起,并不需要我們在圖上表示出,他們是有關(guān)系的。

總之,這個應用可以畫成的圖如下所示:

應用啟動:

application-example-home

去界面1:


application-example-goto-add

添加學生成績:


application-example-add

去界面2:


application-example-goto-print

打印學生成績:


application-example-print

退出:


application-example

將上述圖片翻譯成Task的時候,就會用到Response和View。

課后練習

  1. 列出所有的任務
  2. 改進需求1: 請引入加分策略,參照第二篇
  3. 改進需求2: 請把每次添加學生信息輸出到文件,每次打印成績單時,從文件讀取。畫出圖和任務列表。

題外話-1

問:一個用戶輸入到一個用戶輸出算一張圖的話,這個命令行程序還好說,正常應用里的拖拽怎么辦呢?
答: 首先,拖拽的時間是可以切分成時間片的。對于每一類時間片,其實我們可以畫一張圖。(當然,你會嫌煩,有這功夫不如做出來了)其次,如果我們拖拽的話,整個過程其實只在前面出現(xiàn)效果而已,沒有任何數(shù)據(jù)往后發(fā)送,只有拖拽結(jié)束那一刻,才會有嚴肅的數(shù)據(jù)產(chǎn)生。所以我們一般不關(guān)注效果部分,因為他不觸達核心,所以即便想的不全,改起來也不困難。所以完全可以先做再改。

題外話-2

為什么這么劃分圖?
除了像request和response之外,之所以這么劃分,還因為這是一個個的業(yè)務場景。我們在前面的幾節(jié)里,使用的思維主要是結(jié)構(gòu)化思維,就是把一個整體的分解為多個模塊的思路。而在這里面,我們使用的是場景化思維,每一張圖實際上都是一個業(yè)務場景。需求的場景化是非常重要的一種思維,這會有助于我們把業(yè)務、組織和軟件進行有機的設計。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容