優秀的程序員如何清晰表達代碼的意圖
當讀者用所喜愛的IDE來工作,并用到了其中各種吸引人的附加功能(語法檢查、自動完成、靜態分析和其他功能)時,會不會感嘆缺少一個尚未被發明的 特定功能?對,筆者指的就是意圖檢查器。對此大家都很了解。當我們在思考時,就會需要這樣的功能:“我希望它能按照我的意思而不是按照我敲的東西來編 程!”或許在奮力編寫一個棘手的算法時,就會想要這個功能。可能在調用了這個功能后,就發現了一個愚蠢的敲錯一個字符這樣的bug。不管在什么情況下,所 面臨的就是將意圖轉化成實現的復雜性。
另一方面,大家以前都問過像這樣的問題:“這段代碼是做什么的?”或者更極端地問:“這個開發人員當時是怎么想的?”測試所做的所有事情,就是要驗 證實現與顯性或隱性的開發意圖之間的匹配性:顯性表現在代碼應該要完成一些目標;隱性表現在代碼在完成上述目標時,也應該具有特定的可用性、強壯性等這些 特征,而不管這些是否被特別地考慮過[ 重要的是要考慮到,上述意圖會發生在軟件開發過程的許多層面上。開發人員將用戶所想要的,轉化為前者所認為后者所想要的,哪些功能應該被添加以解決后者的 需要,如何將這些特性映射到現有或期望的應用上,如何將這些納入系統的架構和設計之中,以及如何編寫代碼。本章所討論的許多觀點,都可以外推到其他層面 上。]。
意圖都被放到哪里去了
意圖是一個捉摸不定的東西。在更廣闊的社會里,存在著“意圖式生活”(intentional living)這個詞。在實踐意圖式生活時,會試圖把所做的每一個行為當作是刻意的,而不是當作習慣性的或偶然的,同時要考慮到行為發生的地點和在這些行 為的背景下會出現的后果。意圖的明確性也常常會與極簡主義的那些方法聯系起來。上述道理雖然顯而易見,但是要實踐這種生活,需要用更多的紀律和努力。
軟件需要同樣的專注力。筆者在參與開發了多個有關人身安全方面的項目和產品之后,對于所寫代碼的后果變得很敏感。這些后果可以在某種程度上擴展到整 個軟件。如果電子郵件程序將郵件發送給了錯誤的收件人,那么就會違反保密性、破壞信任,有時還會帶來重大的金融和政治影響。一個字處理程序的惱人崩潰似乎 微不足道,特別是在沒有造成數據丟失的情況下,但如果這種情況出現幾百萬次,那么它就會演變成大量的刺激,喪失生產力。
極簡主義也適用于軟件。代碼編寫得越少,要維護的內容也就越少。要維護的內容越少,必須要理解的內容也就越少。必須要理解的內容越少,犯錯誤的機會也就越少。更少的代碼成就更少的bug。
筆者有意囊括了超乎安全性、金錢和生產力之外的因素。漸漸地,軟件被集成到了周圍一切的事物中,包括被集成到伴隨日常生活的各種設備中。這種無處不 在使得軟件對我們的生活質量和精神狀態所施加的影響不斷增大。所以上文中包含了像“信任”和“刺激”這樣描述影響的字眼。產品的意圖包含了非功能性的方 面。那些取得巨大成功的公司,不僅擷取了用戶的頭腦,也俘獲了他們的內心[ 若想深入了解這個話題,可以參考Wiley出版社1995年出版的Alan Cooper的著作《交互設計精髓》(About Face)及其后續版本。]。
將意圖與實現分離
實現僅僅是完成意圖的眾多方式中的一種而已。如果一個人能夠對實現與意圖之間的邊界有一個清楚的認識,那么他在編寫和測試軟件時就能有一個更好的心 智模式(mental model)。實現與意圖極其相像的情況屢見不鮮。例如,一個“獎勵獲勝玩家”的意圖可以實現為“查詢獲勝用戶,遍歷他們,然后為每人的成績清單中添加一 個徽章”。實現的語言與意圖的表述緊密對應,但這兩者并不相同。
明確地劃分意圖與實現之間的界限,能有助于將測試工作的規模與軟件相匹配。在不摻雜實現元素的前提下,對意圖測試得越多,測試耦合到代碼的情況就越 少。耦合得少了,就不用被迫隨著實現中的變化更新或重寫測試了。測試改變得越少,花費在測試上的精力也就越少,而這會增加測試保持正確的可能性。所有這一 切都會使得在驗證、維護和擴展軟件上的花費更少,在長遠來看更是如此。
也可以將代碼的意圖與功能相分離。此處所說的分離是將代碼原本的工作用意與它實際的行為分隔開。當需要測試實現時,應該對代碼本應做的事情進行測 試。而對代碼所編寫出的那些行為進行測試時,若該代碼編寫得不正確,那么這種測試就會造成一個安全的假象。一個運行通過的測試會告訴我們一些有關代碼質量 和代碼與目的之間契合度的信息。而一個不應運行成功的測試若運行通過了,那么該測試就會在上述信息上對我們撒謊。
當編寫代碼時,要使用編程語言和框架中的特性來最清晰地表達意圖。在Java語言中將變量聲明為final或private,在C++中聲明為 const,在Perl中聲明為my,或者在JavaScript中聲明為var,都是表達了有關該變量用途的意圖。在像Perl和JavaScript 這樣帶有弱參數需求的動態語言中,在Perl中傳遞哈希參數[PBP]和在JavaScript中傳遞對象參數[JTGP]時,參數值本身的命名能用于在 代碼內部更加清晰地記錄意圖。
一個能引發思考的簡單例子
讓我們看看一個使用Java語言的例子。代碼清單2-1顯示了一個簡單的Java類,該類帶有幾條線索,展示了在構造該類時所表現出來的意圖。考慮 ScoreWatcher類,它是一個跟蹤體育比賽分數系統的一部分。它封裝了從一個新聞源(a news feed)獲得比賽分數的功能。
代碼清單2-1 一個簡單的Java類展示了帶有意圖的構造
class ScoreWatcher { private final NewsFeed feed; private int pollFrequency; // Seconds public ScoreWatcher(NewsFeed feed, int pollFrequency) { this.feed = feed; this.pollFrequency = pollFrequency; } public void setPollFrequency(int pollFrequency) { this.pollFrequency = pollFrequency; } public int getPollFrequency() { return pollFrequency; } public NewsFeed getFeed() { return feed; } ... }
首先,看一下該類所定義的那些屬性(attribute)。編寫該類的作者把feed定義為final[ 在Java語言中,final關鍵字本身就表明了,由它所聲明的變量一旦初始化,其值就不能再改變了。]的屬性,卻沒有把pollFrequency也定 義為final。這告訴了我們什么?它表達了這樣一個意圖:feed應該只能在類構建時被賦值一次,但pollFrequency能夠在該對象的整個生命 周期中被修改。接下來,在代碼中所看到的pollFrequency同時具備getter和setter,而feed僅有一個getter,又強化了這一 點。
但這僅僅讓我們了解了實現上的意圖。上面的做法可能會支持哪個功能性的意圖呢?可以根據代碼的上下文做出一個合理的結論,即對于每一個能夠使用的新 聞源,應該只恰好分配一個類來封裝它。還可以繼續推論,或許對于每一個要被監測的比賽分數,也應該恰好只存在一個用來初始化ScoreWatcher的 NewsFeed。還可以繼續推測,如果存在多個新聞源,那么多個源的管理可能會隱藏在一個新聞源的接口后。這一點需要驗證,但是在目前的情況下看起來是 合理的。
然而,或許是由于Java語言在表達能力上的限制,上述假設有一個弱點。即便不知道NewsFeed類的構造情況,我們也能推測出:即使feed這個引用本身不能被改變,但還是有可能通過它來修改它所引用的對象。在C++語言中,可以這樣聲明屬性:
const NewsFeed * const feed;
這個聲明不僅表達了指針不能被改變,而且還表達了不能使用指針來改變它所指向的對象。這在C++語言中提供了一個額外的上下文不變性 (contextual immutability)的標記,而這一點在Java語言中并不存在。在Java語言中,想讓一個類的所有實例都不可變(immutable)還是比較 容易的。但是想讓一個特定的引用所引用的對象不可變,就需要花費相當多的努力了,或許需要創建一個處理不可變性的代理來封裝該對象實例。
然而,這些又是如何改變測試的呢?類的構造——實現——清楚地規定了賦給那個類的feed在該類的整個聲明周期中不應改變。這是意圖嗎?讓我們看看如代碼清單2-2所示的驗證這個假設的測試。
代碼清單2-2 驗證代碼清單2-1中的新聞源不會改變的測試
class TestScoreWatcher { @Test public void testScoreWatcher_SameFeed() { // Set up NewsFeed expectedFeed = new NewsFeed(); int expectedPollFrequency = 70;// Execute SUT[ SUT是Software Under Test(被測軟件)的縮寫。[xTP]] ScoreWatcher sut = new ScoreWatcher(expectedFeed, expectedPollFrequency); // Verify results Assert.assertNotNull(sut); // Guard assertion Assert.assertSame(expectedFeed, sut.getFeed()); // Garbage collected tear down
} }</pre>
在JUnit中,assertSame斷言驗證的是,期望的引用和實際的引用都指向同樣的對象。回到有關該類的意圖的推測上,假設引用到同樣的 feed很重要這一點是合理的,但是同樣的NewsFeed這一點是不是在這種情況下有些超出規格所規定的范圍?例如,要是代碼的實現為了選擇加強初始新 聞源的不變性,從getter將其返回之前就克隆其屬性,從而確保任何變化都不會影響ScoreWatcher的NewsFeed的內部狀態,那該怎么 辦?在這種情況下,測試構造器的參數是相同的這一點就不正確了。這種設計的意圖,更有可能需要驗證feed的深度相等性[ 驗證兩個對象的深度相等性,即驗證這兩個對象內部所保存的各個數據的值都一一相等。——譯者注](deep equality)。
本文來自《優質代碼:軟件測試的原則、實踐與模式》
本書專門從軟件開發人員和技術人員關注的代碼質量的角度來講軟件測試的原理、實踐和模式。作者有20多年軟件開發經驗,10多年軟件測試技術的教授 經驗。書中積累了來自大量高水準軟件工程師的多年經驗。無論你是在寫一個新系統,還是試圖駕馭一個遺留系統,本書都會讓你高效地開發高質量的代碼。
測試驅動、測試先行和盡早測試這些開發實踐,正在幫助成千上萬的軟件開發組織改善其軟件。在本書中,作者立足于所有讀者已經熟知的測試驅動開發知識,幫助讀者實現前所未有的優質代碼。
為了幫助讀者更加全面、有效和輕松地測試任何軟件系統,本書使用真實的代碼示例介紹了測試的模式、原則和20多個技術細節,并通過兩個完整的案例分 析,即測試一個全新的Java應用程序和一個未被測試的“遺留”JavaScript jQuery插件,將本書講述的所有內容整合在了一起。此外,作者還展示了一個概念框架,幫助讀者將精力重點放在改善貫穿整個軟件生命周期的可測試性上, 并給讀者提供了簡化代碼構造的全系列測試的實操指南。
無論是最常見的場景還是多線程,本書都會幫讀者學會如何針對每一種情景選擇最好的測試技術;無論是為一個新的創業公司開發前沿代碼,還是維護一個很難駕馭的老舊系統,本書都會幫讀者交付其真正需要的優質代碼。
簡化所有代碼的單元測試,并改善集成測試和系統測試。
來自:http://www.jianshu.com/p/c291cb2019e0詳述意圖和實現,促進更加可靠和可擴展的測試。 克服對編寫測試的機制的混淆和誤解。 測試“副作用”、行為特征和上下文約束。 了解軟件設計與可測試性之間微妙的交互,并對其進行利用,而非受困其中。 揭示能夠指導關鍵測試決策的一些核心原則。 探討以下內容的測試:getter/setter、字符串處理、封裝、覆寫變化、可見性、單例模式、錯誤條件等。
確定性地重現并測試一些復雜的競態條件。 ID:ptpressitbooks
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!