我們經常會遇到需要解析命令行參數的場景。如果沒有趁手的工具,我們可以自己寫一個,自己想辦法處理傳給main函數的參數。
Args是一個經典的編程練習題,復雜度與我們日常開發中接觸到的需求大致相當,能很好地體現出程序員的熟練度。
7小時
“Args”也是一個經典的編程練習題。“鮑勃大叔”Robert Martin在《Clean Code》的第14章裡介紹過這個題目。
我們經常會遇到需要解析命令行參數的場景。如果沒有趁手的工具,我們可以自己寫一個,自己想辦法處理傳給main函數的參數。
傳入一個程序的參數包含瞭“標記”(flag)和“值”(value)。標記都是一個字母,前面加上“-”號(例如“-p”這樣)。每個標記可以有一個值與之對應,也可以沒有對應的值。
我們要開發一個解析器(parser)來處理這些參數。這個解析器需要一個參數結構(schema)來描述“這個程序應該接受哪些參數”的信息,包括:
參數結構指定好以後,就可以把實際接收到的參數列表傳給參數解析器。解析器會首先驗證參數列表是否與參數結構相匹配。然後,程序就可以向參數解析器查詢每個參數的值(根據參數的標記名)。返回值的類型應該與參數結構中規定的類型相一致。
例如程序接收到的參數是這樣:
-l -p 8080 -d /usr/logs
那麼對應的參數結構應該規定3個標記:l、p、d
如果參數結構中規定瞭的標記在實際的參數列表中沒有出現,那麼就應該返回合理的缺省值,例如佈爾型的缺省值可以是False,數值型的缺省值可以是0,字符串型的缺省值可以是空字符串。
如果實際給出的參數與參數結構不匹配,需要給出良好的錯誤信息,解釋清楚出錯的原因。
擴展代碼,支持列表類型的參數。例如下列參數中:
-g this,is,a,list -d 1,2,-3,5
“g”標記對應的是字符串類型的列表([“this”, “is”, “a”, “list”]),“d”標記對應的是整數類型的列表([1, 2, -3, 5])。
代碼應該具有良好的可擴展性,這樣在添加新的值類型時才會簡單明瞭。
7小時
Args的難度比FizzBuzz高得多。如果沒有做好基本功的準備,貿然開始這道題目,恐怕很多人會感到很受挫。據我們的觀察,大多數程序員首次練習完成這道題需要4小時以上,甚至用瞭一整天還完不成的情況也絕非罕見。
Args這道題目有意地在很多地方對需求描述不夠清晰,例如參數結構的定義方式、參數的輸入格式等。這就需要我們學會用測試來準確地表達自己對需求的理解,用測試作為與客戶溝通的橋梁,用測試框定需求的范圍。
一個有一定規模的需求,不僅要知道建造完成後的樣子,還要知道如何一步步建造出來的過程。這個過程設計得好,整個需求的開發會很平穩,工作量評估會很準確,出錯的可能性會很低。合理拆分任務,是良好建造過程的基礎。
在開發遇到困難的時候,要學會把測試的步伐放得小一點、再小一點,從最簡單的問題開始,每次專註寫一個測試、讓一個測試通過。
雖說作為練習題顯得有些難度,其實這道題的復雜度跟日常工作中拆分良好的用戶故事差不多,也就是大多數程序員需要一到兩天開發出來的規模。練功房的小夥伴已經練到能在半小時內完成這道題。這就是基本功帶來的效率差距。
本項目分為7個任務:
第一個任務很簡單:做一遍Args這道題。
做題的要求:
如果超過90分鐘還沒完成,就先暫停。我們還需要留出一點時間來反思。
反思:
如果在任務一遇到瞭困難,有很大可能是對需求的范圍框得不夠清晰,不知不覺中把需求擴大化瞭。(當然,也可能漏掉瞭一些重要的需求。)
比如說,有很多同學會花大量精力思考如何做字符串的解析。但是我們是否先問過客戶,輸入參數的格式應該是什麼樣?我們是否首先思考過,這個需求可以分解成幾個大塊?各個大塊的優先級順序是什麼?
在動手寫代碼之前,我們要首先弄清需求的范圍和優先級,並用測試的形式把它記錄下來。
做題的要求:
反思:
在處理Args的需求時,一個常見的錯誤是沒有考慮必要的靈活性。很多同學把“-l參數是佈爾型”、“-p參數是整數型”這些信息硬編碼寫在代碼中,就像這樣:
於是這個程序就與例子中這個使用場景綁死瞭。比如說,假如另一個使用者要使用我們開發的程序,然而在他的場景中,要處理的是“-w”這個參數,我們怎麼辦呢?難道每增加一個使用者我們就要去改一遍代碼嗎?很多不可維護的焦油坑軟件就是這麼來的。
題目裡已經給出瞭“參數結構(schema)”這個概念。今天我們的任務就是,開發Schema輸入和解析的邏輯。
請註意:今天我們隻處理“將Schema讀入對象結構”的邏輯。至於怎麼使用Schema,暫時不考慮。
做題的要求:
反思:
最好能再練一遍,驗證反思的結果。
除瞭Schema之外,Args的另一個大塊功能,就是從用戶的輸入中讀入參數。今天我們的任務就是實現這部分邏輯。
請註意:今天我們隻處理“將參數讀入對象結構”的邏輯。至於怎麼使用參數,今天暫不考慮。
做題的要求:
反思:
最好能再練一遍,驗證反思的結果。
http://mp.weixin.qq.com/s/YojJITzYIycG6PyGCrRxPw 《用一次Map就失去一個對象》
在前面兩天的練習裡,你很可能用瞭不少的容器,尤其是Map。例如此前有個同學有這樣一段測試:
8a8228feec2e438496f634a23451e925
同樣的邏輯,我寫的測試就沒有暴露任何容器:
我在一篇公眾號文章裡這樣說:
“Map的出現幾乎一定意味著有個對象的概念被錯過瞭” “Map即對象”
從內容來看,Map和對象真的是一樣的:Map的key就是對象的字段名;Map的value就是對象的字段值。JavaScript早年間沒有那麼明確的面向對象特征的時候,大傢真的就是把Map和對象混用的。
但是Map和對象最大的區別在於:你是否給它一個合適的名字。老話說名不正則言不順。沒有一個合適的名字說“這是一個Flag對象”,你就傾向於不會多想想,到底哪些行為應該屬於這個對象。
其實還不僅限於Map。再比如List(甚至數組),很多時候也是被過度使用的。當一個對象給另一個對象提供一個List,很多時候就意味著暴露瞭自己的內部信息。所以我的觀點是,List和Map(以及其他集合結構)不應該在對象之間傳遞,隻應該在對象內部使用。
這些本來應該封裝成對象的集合結構沒有得到合適封裝, 其結果就是我昨晚上說的: “我註意到這是大傢的代碼另一個常見的問題” “對象太少” “帶來的結果就是很多邏輯在一個函數裡一層套一層”
所以下次一旦發現自己開始用Map,不妨多想一想:你需要的真的是一個Map嗎?或者,其實你需要的是一個正等著被定義的對象?
感受一下。把前面做過的題目再練一遍。
做題的要求:
反思:
最好能再練一遍,驗證反思的結果。
有瞭前面的對象做基礎,完成Args的最後一步“取出參數值”應該很簡單瞭。這就是我們今天的任務。
做題的要求:
反思:
最好能再練一遍,驗證反思的結果。
代碼質量不是主觀的審美判斷。它是有相當客觀的判斷依據的。這個判斷依據就是Martin Fowler在《重構》裡說的“壞味道”(bad smell)。
這堂課,我們翻開《重構》(第二版),翻到第三章“代碼的壞味道”。這裡面列舉瞭24種壞味道,今天我們專註看其中的兩個壞味道:Long Method(過長函數)和Large Class(過大的類)。
多長的函數就算“過長”?多大的類就算“過大”?我有一個簡單的經驗法則。對於Args這樣一個典型的需求場景:
你的代碼中是否已經出現瞭這兩個壞味道?《重構》第三章給出瞭對應的重構手法,試著照書裡寫的重構手法,重構一下看看吧。
練習的要求:
反思:
一、什么是uni-app? uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到 H5、以及各种小程 ...