1. 程式人生 > 實用技巧 >審閱“史上”最爛的程式碼

審閱“史上”最爛的程式碼

Facebook 上有一個名為“Il Programmatore di Merda”(翻譯為“ The Shitty Programmer”,中文含義為“糟糕的程式猿”)的社群, 我經常去瀏覽。網站經常分享一些糟糕的程式碼和有關程式設計的話題。今天,我看到一段令我難以置信的程式碼:

image.png

本週最爛程式碼

仔細看看,上面的程式碼錯誤太多,以至於我不知從何談起。

如果你是一個初級開發工程師,這篇文章會幫你明白上述程式碼中存在的一些非常嚴重的問題,並讓你引以為鑑。

1、28 行錯誤程式碼

我把上面的程式碼摘錄下來,以便我們進行後面的討論:

image.png

一時之間,我竟不知道從何說起。

上述錯誤大致分為 3 類:

  • 安全問題
  • 基本程式設計概念問題
  • 程式碼格式化問題

2、安全問題

我們非常確定以下程式碼會在客戶端執行,因為它被包裝在兩個<script>標記間(當然,它使用 jQuery 程式設計框架)。不要誤會我的意思,這些程式碼即使是執行在伺服器端也很糟糕,在客戶端上執行這些程式碼會將你的資料庫暴露給……每個人。

讓我們先看一下authenticateUser函式:

審閱“史上”最爛的程式碼

我們的程式碼在某些地方有個叫做apiServices的介面,它公開了一個.sql方法,可以對資料庫進行 SQL 操作。這意味著,如果你在執行上述程式碼的瀏覽器上開啟控制檯,就可以執行各種查詢,安全隱患極高。比如,你無需獲得授權就可以這樣做:

image.png

呼叫上述 API,程式碼執行後會返回資料庫的所有表名稱。

我們暫且假裝這不是一個嚴重的問題。但是請接著往下看:

審閱“史上”最爛的程式碼

所以,作者的意思是直接儲存了使用者所有的明文密碼,而沒有對它們進行雜湊處理?

這簡直不可思議!現在我可以開啟 Chrome 瀏覽器的偵錯程式,直接檢視每個使用者的明文密碼。

我非常確定,很大一部分使用者會在社交網路、電子郵件服務、銀行賬戶等服務中使用相同的使用者名稱和密碼,想象一下,別人可以在沒有任何障礙下就可以拿到你的賬戶和密碼,這得有多可怕。

作者嘗試設定登入cookie 的方式也存在問題:

審閱“史上”最爛的程式碼

所以按照程式碼的意思,作者使用 jQuery 設定 cookie,讓該 cookie 告知 Web 應用程式使用者是否通過身份驗證。

好吧,千萬不要使用 JavaScript 來設定此類 cookie

如果你有儲存此類登陸資訊的需求,那麼使用 cookie 確實是最常見的解決方案,這沒有什麼問題!但是使用 JavaScript 設定它們意味著你無法設定httpOnly屬性,這會導致每個惡意指令碼都能輕而易舉地訪問和獲取你的 cookie 內容。

是的,我知道,他們只是儲存'loggedin': 'yes'的鍵值資訊,可能不是上面我講的那種情況,但總之這是一個糟糕的做法。

另外,開啟 Chrome 控制檯,我隨時可以輸入$ .cookie('loggedin','yes',{expires: 1000000000000})命令, 而且即使我沒有使用者帳戶,也會永遠保持登入狀態。

3、基本程式設計概念問題

想說的話太多,但無奈時間有限。

很明顯,authenticateUser函式寫的就是一堆垃圾,該函式的實現充分表明作者缺乏一些基本的程式設計概念。

審閱“史上”最爛的程式碼

程式碼作者為什麼不只查詢給定使用者名稱和密碼的使用者,而是檢索出資料庫中的所有使用者呢?如果該資料庫中擁有數百萬個使用者怎麼辦?

還有前面我已經說過了,在這裡我再提一下,為什麼作者不對資料庫中的明文密碼進行雜湊處理

讓我們接著看一下authenticateUser函式的返回值。

我們可以看到,該函式接收兩個 string 型別的引數,最後返回一個布林型別的值。所以,下面的程式碼即使很糟糕(明文密碼),但也有一定的意義:

審閱“史上”最爛的程式碼

上面程式碼的含義很清楚,“是否存在具有 X 使用者名稱和 Y 密碼的使用者?是的,所以函式執行結果返回 true”。

但是下面這個程式碼:

審閱“史上”最爛的程式碼

這根本沒有任何道理呀。

為什麼該函式不去掉always-true條件判斷,直接返回 false?

現在,我們繼續接著分析後面的程式碼:

image.png

使用 jQuery 獲取屬性值的程式碼部分沒有什麼問題。問題在於它如何處理loggedin 使用者的 cookie。

我們之前討論過這樣一個問題,我可以在我的 Chrome 控制檯輸入$ .cookie('loggedin','yes',{expires:1}); 保持認證一整天,甚至都不需要一個帳戶。

所以,這個網站到底是怎麼確定我是誰的?也許它只是通過使用者名稱 / 密碼身份驗證顯示一些私人內容,所以它沒有展示任何個人資料。總之,沒有人知道程式碼為什麼會這麼寫。

4、程式碼格式化問題

程式碼格式可能是整個程式碼中不太重要的部分,但我們可以很容易地判斷出該開發人員複製 / 貼上了某些網站上的程式碼。

下面的程式碼片段,我們可以看到開發者使用了雙引號引用字串:

審閱“史上”最爛的程式碼

然而,下面的程式碼卻又使用了單引號字串:

審閱“史上”最爛的程式碼

這些看起來可能沒有那麼重要,但實際上我們可以確定,開發人員可能已經從 StackOverflow 複製貼上了一些程式碼,甚至都沒有遵循整個程式碼庫的程式碼規範來重寫它們。當然,這只是一個小問題,但它表明開發人員並不真正關心和理解程式碼的工作方式,只是希望程式碼以某種方式工作。

大家不要誤會,我每天都會在 Google 上進行搜尋,但比起僅僅複製和貼上程式碼來實現功能,理解程式碼的工作原理——比如理解如何設定 Cookie,實際上更為重要。如果由於某種原因整個程序中斷了怎麼辦?你如何確定是指令碼的哪一部分不起作用呢?

5、總結

我絕對可以確定上面的程式碼是偽造的。這是我第一次看到使用同步方式進行 SQL 查詢:

審閱“史上”最爛的程式碼

通常,我希望查詢功能的實現類似下面這樣:

審閱“史上”最爛的程式碼

或者這樣:

審閱“史上”最爛的程式碼

即使使用同步方式呼叫apiService.sql返回查詢值(我對此表示懷疑),在內部也必須進行與資料庫的連線、執行查詢語句併發送返回查詢結果,這些過程(你可能已經知道了)明顯是不同步的。

但是,即使上面的程式碼不是偽造的,我也可以確信它是由初級開發人員編寫的。我剛剛開始入行寫程式碼的一段時間裡,我很確定自己為之前的公司也寫過這麼糟糕的程式碼。

這個鍋不能甩給初級開發人員

讓我們假設上面的程式碼是真實的。這裡的初級開發人員正在竭盡所能實現功能。他 / 她尚未開始學習如何正確處理 SQL 查詢、cookie 以及其他需要注意的技術點,這完全可以理解!

高階開發人員應該提供某種形式的指導,以確保初級開發人員可以理解他們的錯誤,保證這樣的錯誤程式碼不會在生產環境中使用。

我也可以確認,有些公司其實並不真正在乎開發人員編寫的程式碼質量。

程式碼能解決問題嗎?——生產環境部署一下就知道了呀。程式碼是由初級開發人員編寫的,甚至都沒有高階開發人員的批准嗎?——部署執行一下就知道結果了呀。

哎,Shit happens!

6、後記

我在 Reddit 對此進行了一番討論後,一個非常給力的小夥伴分享了下面的 Reddit 話題:

“This JavaScript code powers a 1,500 user intranet application”

https://www.reddit.com/r/programminghorror/comments/66klvc/this_javascript_code_powers_a_1500_user_intranet

所以,是我錯了。這段程式碼並不是偽造的