USF MSDS501 计算数据科学中文讲义 2.7 如何阅读代码
來源:ApacheCN『USF MSDS501 計算數據科學中文講義』翻譯項目
原文:How to read code
譯者:飛龍
協議:CC BY-NC-SA 4.0
從根本上說,程序員與代碼交流。我們不僅向計算機,也向其他開發人員表達了我們的想法。到目前為止,我們專注于設計程序和編寫 Python 代碼。這是關鍵的創作過程,但是,為了編寫代碼,程序員必須能夠閱讀其他人編寫的代碼。
為什么閱讀代碼
我們閱讀代碼以便:
- 獲得新體驗。就像在自然語言中,我們通過傾聽他人來學習說話一樣,我們通過識別他人代碼中的酷炫模式來學習編程技巧。能夠快速閱讀代碼,使您可以獲得觀看編程講座或視頻的經驗。
- 查找并修改代碼段。我們經常可以通過試用 Google 搜索或 StackOverlow找到的代碼段,找到編碼問題的提示或解決方案。 請注意,您不違反版權法,如果是學生項目,則不違反學術誠信規則。
- 發現庫函數或其他共享代碼的行為。 從名稱或參數列表中并不總是清楚庫函數的完整行為。 查看該函數的源代碼是了解它的作用的最佳方法。代碼就是文檔。
- 在我們的代碼或其他代碼中發現錯誤。 所有代碼都有錯誤,特別是我們剛剛編寫,但沒有經過詳盡測試的代碼。作為編碼過程的一部分,我們不斷跳來跳去,閱讀我們現有的代碼庫,來確保一切都組合在一起。
<img src="https://gitee.com/wizardforcel/usf-msds501-notes-zh/raw/master/docs/img/redbang.png" width="30" align="left">
在我們討論庫函數時,讓我強調一條黃金法則:*你永遠不應該向你的程序員詢問參數的細節和庫函數的返回值。*你可以通過 PyCharm 中的“跳轉到定義”或網絡搜索來自己發現它。
本文檔的目的是解釋程序員如何讀取代碼。 我們的第一個線索來自于我們不是計算機這一事實,因此,我們不應該像計算機一樣閱讀代碼,一個接一個地檢查一個符號。 相反,我們將尋找關鍵元素和代碼模式。
這就是我們用外語閱讀句子時所做的事情。 例如,我的法語非常糟糕,因此,在閱讀法語句子時,我必須有意識地詢問誰在對誰做什么。在實踐中,這意味著識別主語,動詞和賓語。從這些關鍵要素中,我試圖想象作者心中的思維模式。基本上我試圖反轉作者所遵循的過程。
在編程世界中,過程如下:代碼作者可能會想到“*通過除以 2 *將價格轉換為新列表”,然后將它們轉換為“映射”的偽代碼,最后轉換為 Python for循環。在閱讀循環代碼時,我們的工作是反轉過程,并想象作者的原始目標。 我們不是試圖通過在我們的頭腦或紙上模擬它,來弄清楚代碼的突現行為;相反,我們正在尋找模式,它們能夠告訴我們正在執行哪些高級操作。
這就是為什么在編寫代碼時應該強調清晰度,以便讀者內容。約翰 F. 伍茲 有一個很好的引言,總結了很多東西:
寫代碼的時候總是想象,維護你代碼的家伙是一個知道你住在哪里的暴力精神病患者。
獲得程序的要點
在第一次查看教科書時,掃描目錄來獲得書籍內容的整體視圖,是有意義的。 第一次看節目時也是如此。 查看所有文件以及這些文件中包含的函數的名稱。 同時,找出主程序的位置。 根據您在程序中的目標,您可能會開始單步執行主程序或立即跳轉到感興趣的函數。
從樣例運行或單元測試中查看程序的輸入 - 輸出對也很有用,因為它可以幫助您了解程序的功能。 從某種意義上說,我們通過檢查和測試程序,對程序的工作計劃進行逆向工程。 以前,我們在前進方向使用程序的工作計劃來設計程序。
獲得函數的要點
一旦我們確定了要檢查的主程序或函數,就應該對函數的工作計劃進行反向工程。 函數的名稱可能是函數功能的最大線索,假設代碼作者是一個不錯的程序員。 (使用像f這樣的通用函數名稱,是教師在不泄露答案的情況下,編寫代碼閱讀問題的方式。)例如,毫無疑問,以下函數的目標是什么:
def average(...):...即使不查看參數或函數語句。
程序員通常會提供函數用法的注釋,但要小心。 程序員通常會在不更改注釋的情況下更改代碼,因此注釋會產生誤導。可接受的注釋可能如下所示:
def average(...):"Compute and return the average of a list of numbers"...如果我們幸運的話,該注釋對應于工作計劃中的函數目標描述。
下一步是確定參數和返回值。 同樣,參數的名稱經常告訴我們很多,但不幸的是,Python 通常沒有明確的參數類型(它們不會被 Python 檢查)所以我們必須自己解決這個問題。 了解值和變量的類型對于理解程序至關重要。 在這樣的簡單函數中,我們通常可以快速找出參數的類型和返回值。 在其他情況下,我們將不得不深入研究函數的語句來解決這個問題(稍后會詳細介紹)。 讓我們放大來查看我們函數的更多細節:
def average(data):...return sum / n在這一點上,我們知道data幾乎肯定是一個數字列表,函數返回一個數字。 這意味著我們可以填寫該功能的工作計劃的第一部分。
在函數代碼中尋找什么
因為我們事先知道平均值是什么,所以我們可以填寫函數目標的工作計劃描述。 但是,一般來說,我們必須掃描函數的語句才能弄明白。 (我們可能會很幸運并找到合理的函數注釋。)現在讓我們看一下完整的函數:
def average(data):n = len(data)sum = 0.0for x in data:sum = sum + xreturn sum / n缺乏經驗的程序員必須單獨和逐字地檢查函數的語句,模擬計算機來找出突現行為。 相比之下,經驗豐富的程序員在代碼中尋找模式,代表映射,搜索,過濾等高級操作的實現.....
通過類比,考慮在游戲過程中記住棋盤的狀態。 初學者必須單獨記住所有東西在哪兒,而國際象棋大師則認為棋盤只是布達佩斯開局的變種。
我們如何知道從哪里開始以及看什么? 那么,讓我們回想一下我們的通用數據科學程序模板:
該過程的要點是,將數據加載到方便的數據結構中并對其進行處理。加載數據,創建數據結構和處理數據結構有什么共同之處?它們都重復執行一組操作,這意味著處理數據的程序的要點是循環。(甚至有一本著名的書名為算法+數據結構=程序,其中算法表示偽代碼或代碼描述的過程。)沒有循環的程序可能會非常無聊,因為它無法遍歷數據結構或處理數據文件。
從這里,我們可以得出結論,所有的動作都發生在循環中,所以我們應該首先在代碼中尋找循環**。閱讀代碼是在函數代碼中找到這樣的模板的問題,它立即告訴我們作者想要的操作或模式的類型。
識別代碼中的編程模式
讓我們深入研究一些循環示例,嘗試識別高級模式和相應的操作。 要尋找的關鍵要素是我們研究的模板中的空位。 這通常意味著識別循環變量,循環邊界,我們正在遍歷的數據結構以及對數據元素執行的操作。目標是對代碼作者的意圖進行逆向工程。
練習:首先,上面的sum函數中的代碼模式的對應操作是什么?
sum = 0.0 for x in data:sum = sum + x那是一個累積器。
練習:讓我們看一個循環,我故意使用蹩腳的變量名稱,所以你必須專注于功能。
foo = [] for blah in blort:foo.append(blah * 2)這是一個映射操作,我們可以從空目標列表的初始化和foo.append(...)調用中看到。 除了目標列表是blah的函數,它來自源列表blort之外,blah * 2與尋找模式無關。
練習:你在下面的代碼中看到了什么樣的循環(for-each,索引,嵌套等等)? 代碼執行什么樣的高級操作?
blort = [] for boo in range(len(foo)):blort.append(foo[boo] * 2)這是一個索引循環,它再次執行映射操作。 它是一個索引循環的線索是,邊界是range(len(foo)),它給出一系列索引。 由于blort.append和foo[boo]的引用,我們知道它是一個映射操作。 因為[boo]索引運算符,我們知道foo是某種類型的列表。
練習:對應此代碼中模式的高級操作是什么:
foo = [] for i in range(len(X)):foo.append(X[i]+Y[i])它將兩列(列表)組合成目標列/列表foo。我們知道X和Y是列表,因為[i]數組索引。
練習:此代碼執行什么高級數學運算?
for i in range(n):for j in range(n):C[i][j] = A[i][j] + B[i][j]矩陣加法。這里重要的是要認識到,嵌套的索引循環給出了在[0..n]范圍內的循環變量i和j的所有組合。 執行此操作的最常見原因之一是迭代矩陣或圖像的元素。 這里的答案也可能是圖像加法。
練習:這個循環打印了多少個hi?
for i in range(n):for j in range(n):print('hi')n * n。內循環n次。外循環意味著我們執行整個內循環n次。
練習:
blort = [] for foo in A:for bar in B:blort.append(foo + bar)這從A和B的所有可能組合中找到所有情況。
練習:這段代碼在做什么? 即,循環完成后,blort的值是多少?
blort = float('-inf') for x in X:if x > blort:blort = x print(blort)X的最大值。
<img src="https://gitee.com/wizardforcel/usf-msds501-notes-zh/raw/master/docs/img/redbang.png" width="30" align="left">
無論何時在循環內部看到if語句,請考慮過濾或搜索或條件累積。 它通常是其中一個的變體。這假設條件表達式是直接或間接的循環變量的函數。
練習:這個變體打印了什么?
blort = float('-inf') for i in range(len(X)):if X[i] > blort:blort = X[i] print blort完全一樣的東西; blort是X的最大值。 您會看到一個條件表達式,它是循環內部循環變量的函數。這只是前一次的重組。
練習:這段代碼的目標是什么? 即,循環后它為foo打印的值是多少?
foo = -1 bar = -99999 for i in range(len(X)):if X[i] > bar:bar = xfoo = i print(foo)X的最大值索引(argmax)。 我們知道與條件相關的代碼,是從前面的例子中找出最大值,但它也跟蹤了索引i。
可以把它想象成你已經想到的標準模式,但這種變體可以做一些額外的事情。 然后問兩者之間有什么區別。
這是嘗試理解輸入 - 輸出對是什么的一個很好的例子(雖然我們在這里談論的是代碼段而不是完整的函數)。 在最大值的計算中,輸出是取自X的值。 在這種情況下,打印出的值是0..len(X)-1中的索引。
練習:描述此代碼完成后bar的值。
foo = [] bar = [] for blah in blort:foo.append(blah * 2) for zoo in foo:if zoo>10:bar.append(zoo)這里有很多東西,但它實際上只不過是一個序列中的兩個模式。 第一個模式是一個映射操作,它將blort中的值加倍,來創建foo列表,該列表由第二個循環使用。第二個循環只是一個過濾,它將所有> 10的值從foo提取到bar。
練習:執行此代碼后,a和b是什么值?
a = 0 b = 0 for x in X:if x < 10:a = a + 1else:b = b + 1這是一個具有條件的雙重累積。 它是一個累積,因為它在循環中更新至少一個變量。 它有一個累積條件,因為它是一個累積循環中的條件,其中條件表達式測試循環迭代器的值。 a是X中小于 10 的值的數量,b是大于或等于 10 的值的數量。
練習:循環后Y是什么值?
a = 2 b = 5 Y = [] for i in range(len(X)):if i>=a and i<=b:Y.append(X[i])此循環實現切片操作,從列表中提取元素的子集。 在這種情況下,它選擇范圍a..b中的X的元素,包含邊界,并將它們添加到Y。
該實現效率非常低,因為它遍歷整個列表來獲取范圍內的元素。 如果我們將循環的邊界更改為所需范圍,它會更快更容易理解:
a = 2 b = 5 Y = [] for i in range(a, b+1): # range is [a,b]Y.append(X[i])總結
編寫代碼是成為程序員的重要部分。代碼是程序員的溝通方式。 這是我們有效地使用額外的庫,調試,以及獲得經驗的方式。
閱讀代碼的技巧是翻轉從函數工作計劃到偽代碼再到 Python 代碼的編程過程。 最大的線索來自變量和函數名稱,可能還有代碼注釋。 然后,我們查找代碼中所表達的代碼模板,根據該模板的選擇,對作者的原始意圖進行反向工程。 例如,詢問代碼代表映射還是搜索操作。 不要試圖模仿計算機,并使用表達式值和時間的圖表來猜測突現行為。 有時你必須這樣做才能進行調試,但總的來說,你的目標是猜測代碼作者的意圖。
因為閱讀代碼是您流程的重要組成部分,所以通過編寫高質量的代碼,善待其他開發人員和您未來的自已。 這包括選擇優秀的變量和函數名稱以及編寫清楚說明您意圖的代碼。
轉載于:https://my.oschina.net/wizardforcel/blog/3068329
總結
以上是生活随笔為你收集整理的USF MSDS501 计算数据科学中文讲义 2.7 如何阅读代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 51单片机开发入门(3)-IO口应用
- 下一篇: 在软件开发生命周期中使用应用程序验证器