您还在调试吗?
調試是“以交互方式運行程序/方法,在每個語句后中斷執行流程并顯示……的過程。”簡而言之,它是一種非常有用的技術……對于一個糟糕的程序員而言。 或仍然在用C編寫過程代碼的老程序員。面向對象的程序員從不調試其代碼-他們編寫單元測試。 我的意思是,單元測試是一種完全替代調試的技術。 如果需要調試,則設計很糟糕 。
The Revenant(2015),作者:Alejandro G.I?árritu
假設我是一個糟糕的命令式程序程序員,這是我的Java代碼:
class FileUtils {public static Iterable<String> readWords(File f) {String text = new String(Files.readAllBytes(Paths.get(f)),"UTF-8");Set<String> words = new HashSet<>();for (String word : text.split(" ")) {words.add(word);}return words;} }此靜態實用程序方法讀取文件內容,然后在其中找到所有唯一的單詞。 很簡單 但是,如果它不起作用,我們該怎么辦? 假設這是文件:
We know what we are, but know not what we may be.從中,我們得到以下單詞列表:
"We" "know" "what" "we" "are,\n" "but" "not" "may" "be\n"現在,這對我而言似乎不正確……那么下一步是什么? 文件讀取無法正常工作或拆分中斷。 讓我們調試吧? 讓我們通過輸入為它提供文件,并逐步進行操作,跟蹤并觀察變量。 我們將找到該錯誤并進行修復。 但是,當出現類似問題時,我們將不得不再次調試! 這就是單元測試應該避免的 。
我們應該一次創建一個單元測試,以重現該問題。 然后,我們解決問題并確保測試通過。 這就是我們節省解決問題投資的方式。 我們不會再修復它,因為它不會再發生。 我們的測試將阻止它的發生。
如果您認為調試變得更快,更輕松,請考慮一下代碼的質量
但是,只有在創建單元測試很容易的情況下,所有這些方法才有效。 如果困難的話,我會懶得做。 我將調試并解決問題。 在此特定示例中,創建測試是相當昂貴的過程。 我的意思是單元測試的復雜度會很高。 我們必須創建一個臨時文件,用數據填充它,運行該方法,然后檢查結果。 為了弄清楚到底發生了什么,以及漏洞在哪里,我必須創建一些測試。 為了避免代碼重復,我還必須創建一些補充實用程序來幫助我創建該臨時文件并用數據填充它。 這是很多工作。 好吧,也許不是“很多”,而是經過了數分鐘的調試。
因此,如果您認為調試更快,更輕松,請考慮一下代碼的質量。 我敢打賭,它有很多重構的機會,就像上面示例中的代碼一樣。 這是我將如何修改它。 首先,我將其轉換為一個類,因為實用程序靜態方法是一種不好的做法 :
class Words implements Iterable<String> {private final File file;Words(File src) {this.file = src;}@Overridepublic Iterator<String> iterator() {String text = new String(Files.readAllBytes(Paths.get(this.file)),"UTF-8");Set<String> words = new HashSet<>();for (String word : text.split(" ")) {words.add(word);}return words.iterator();} }看起來已經更好了,但是復雜性仍然存在。 接下來,我將其分解為較小的類:
class Text {private final File file;Text(File src) {this.file = src;}@Overridepublic String toString() {return new String(Files.readAllBytes(Paths.get(this.file)),"UTF-8");} } class Words implements Iterable<String> {private final String text;Words(String txt) {this.text = txt;}@Overridepublic Iterator<String> iterator() {Set<String> words = new HashSet<>();for (String word : this.text.split(" ")) {words.add(word);}return words.iterator();} }您現在怎么看? 為Words類編寫測試是一項非常簡單的任務:
import org.junit.Test; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; public class WordsTest {@Testpublic void parsesSimpleText() {assertThat(new Words("How are you?"),hasItems("How", "are", "you"));} }那花了多少時間? 少于一分鐘。 我們不需要創建一個臨時文件并向其中加載數據,因為Words類對文件沒有任何作用。 它只是解析輸入的字符串并在其中找到唯一的單詞。 現在,由于測試很小,我們可以輕松創建更多測試,因此很容易修復。 例如:
import org.junit.Test; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; public class WordsTest {@Testpublic void parsesSimpleText() {assertThat(new Words("How are you?"),hasItems("How", "are", "you"));}@Testpublic void parsesMultipleLines() {assertThat(new Words("first line\nsecond line\n"),hasItems("first", "second", "line"));} }我的觀點是,當編寫單元測試的時間遠遠大于單擊那些“ Trace-In / Trace-Out”按鈕所花費的時間時,必須進行調試。 這是合乎邏輯的。 我們都很懶惰,想要快速簡便的解決方案。 但是調試會浪費時間并浪費能量。 它可以幫助我們發現問題,但并不能阻止它們再次出現。
當我們的代碼是需要調試的程序和算法,當代碼是所有的目標應該如何實現的,而不是我們的目標是什么 。 再次參見上面的示例。 第一個靜態方法是關于我們如何讀取文件,解析文件以及查找單詞的所有方法。 它甚至被命名為readWords() (一個動詞 )。 相反,第二個例子是關于將要實現的。 它可以是文件的Text ,也可以是Text的Words (都是名詞 )。
我相信在干凈的面向對象編程中沒有調試的地方。 只有單元測試!
翻譯自: https://www.javacodegeeks.com/2016/11/are-you-still-debugging.html
總結
- 上一篇: iPhone 15系列官宣搭载A17 P
- 下一篇: iPhone 15系列四款机型配置区别