搜尋
首頁web前端js教程清洗你的程式碼:別讓我思考

Washing your code: don’t make me think

您正在閱讀我關於乾淨程式碼的書「清洗程式碼」的摘錄。提供 PDF、EPUB、平裝本和 Kindle 版本。立即取得副本。


聰明的程式碼是我們在工作面試問題或語言測驗中可能會看到的東西,當他們希望我們知道我們以前可能從未見過的語言功能是如何工作的。我對所有這些問題的回答是:「它不會通過程式碼審查」。

有些人將簡潔清晰度混淆。短代碼(簡潔)並不總是最清晰的程式碼(清晰),通常情況恰恰相反。努力讓程式碼更短是一個崇高的目標,但它不應該以犧牲可讀性為代價。

在程式碼中表達相同想法的方法有很多種,有些比其他更容易理解。我們應該始終致力於減少下一個閱讀我們程式碼的開發人員的認知負擔。每當我們偶然發現一些不太明顯的東西時,我們就浪費了我們的大腦資源。

資訊:我從 Steve Krug 的同名網路可用性書中「竊取」了本章的名稱。

JavaScript 的暗模式

讓我們來看一些例子。試著涵蓋答案並猜測這些程式碼片段的作用。然後,數數你猜對了多少個。

範例1:

const percent = 5;
const percentString = percent.toString().concat('%');

此程式碼僅將 % 符號加到數字中,應重寫為:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'

範例2:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}

~ 符號稱為 位元 NOT 運算子。它在這裡的有用作用是,只有當 indexOf() 傳回 -1 時,它才會傳回一個假值。這段程式碼應該重寫為:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}

範例 3:

const value = ~~3.14;

位元 NOT 運算子的另一個晦澀用法是丟棄數字的小數部分。使用 Math.floor() 代替:

const value = Math.floor(3.14);
// → 3

範例 4:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}

這個很快就可以理解了:它檢查兩個數組中的任何一個是否有任何元素。不過,最好說清楚一點:

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}

範例 5:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);

這個問題我花了一段時間才明白。假設我們有 URL 的一部分,例如 filename="pizza"。首先,我們用 = 分割字串並取得第二部分「pizza」。然後,我們將第一個和最後一個字元切片以獲得披薩。

我可能會在這裡使用正規表示式:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'

或者更好的是 URLSearchParams API:

const header = 'filename="pizza.rar"';
const filename = new URLSearchParams(header)
  .get('filename')
  .replaceAll(/^"|"$/g, '');
// → 'pizza'

不過這些引用很奇怪。通常我們不需要在 URL 參數周圍加上引號,因此與後端開發人員交談可能是個好主意。

範例 6:

const percent = 5;
const percentString = percent.toString().concat('%');

在上面的程式碼中,當條件為真時,我們會為物件新增一個屬性,否則我們什麼都不做。當我們明確定義要解構的物件而不是依賴虛假值的解構時,意圖更加明顯:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'

我通常更喜歡物件不改變形狀,所以我會移動​​值欄位內的條件:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}

範例7:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}

這個奇妙的單行程式碼創造了一個充滿從 0 到 9 的數字的陣列。 Array(10) 創建了一個包含10 個 元素的數組,然後keys() 方法返回鍵(從0 開始的數字)到9) 作為迭代器,然後我們使用擴展語法將其轉換為普通數組。爆炸頭表情符號…

我們可以使用 for 迴圈來重寫它:

const value = ~~3.14;

儘管我喜歡避免程式碼中出現循環,但循環版本對我來說更具可讀性。

中間的某個地方將使用 Array.from() 方法:

const value = Math.floor(3.14);
// → 3

Array.from({length: 10}) 建立一個包含 10 個 未定義 元素的數組,然後使用 map() 方法,我們用 0 到 9 的數字填入該數組。

我們可以使用 Array.from() 的映射回呼將其寫得更短:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}

明確的map()可讀性稍好一些,我們不需要記住Array.from()的第二個參數的作用。此外,Array.from({length: 10}) 比 Array(10) 更具可讀性。雖然只有一點點。

那麼,你的分數是多少?我想我的應該是3/7左右。

灰色地帶

有些模式介於聰明和可讀性之間。

例如,使用布林值過濾掉虛假的陣列元素(本例為 null 和 0):

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}

我覺得這種模式可以接受;雖然它需要學習,但它比其他選擇好:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);

但是,請記住,這兩種變體都會過濾掉 falsy 值,因此如果零或空字串很重要,我們需要明確過濾未定義或 null:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'

讓程式碼的差異變得明顯

當我看到兩行看起來相同的棘手程式碼時,我認為它們在某些方面有所不同,但我還沒有看到差異。否則,程式設計師可能會為重複的程式碼建立一個變數或函數,而不是複製貼上它。

例如,我們有一段程式碼可以為我們在專案中使用的兩個不同工具(Enzyme 和 Codeception)產生測試 ID:

const header = 'filename="pizza.rar"';
const filename = new URLSearchParams(header)
  .get('filename')
  .replaceAll(/^"|"$/g, '');
// → 'pizza'

很難立即發現這兩行程式碼之間的差異。還記得那些需要找出十個不同點的圖片嗎?這就是這段程式碼對讀者的作用。

雖然我通常對極端的程式碼乾燥持懷疑態度,但這是一個很好的例子。

訊息:我們在分而治之或合併與放鬆章節中詳細討論了「不要重複自己」原則。

const percent = 5;
const percentString = percent.toString().concat('%');

現在,毫無疑問,兩個測試 ID 的程式碼是完全相同的。

讓我們來看一個更棘手的例子。假設我們對每個測試工具使用不同的命名約定:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'

這兩行程式碼之間的差異很難注意到,而且我們永遠無法確定名稱分隔符號(- 或 _)是這裡唯一的差異。

在有這樣需求的項目中,這種模式很可能會出現在很多地方。改進它的一種方法是創建為每個工具產生測試 ID 的函數:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}

這已經好多了,但還不夠完美——重複的程式碼仍然太大。讓我們也解決這個問題:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}

這是使用小函數的極端情況,我通常會盡量避免如此拆分程式碼。然而,在這種情況下,它效果很好,特別是如果專案中已經有很多地方我們可以使用新的 getTestIdProps() 函數。

有時,看起來幾乎相同的程式碼有細微的差異:

const value = ~~3.14;

這裡唯一的區別是我們傳遞給函數的參數具有很長的名稱。我們可以在函數呼叫中移動條件:

const value = Math.floor(3.14);
// → 3

這消除了類似的程式碼,使整個程式碼片段更短且更易於理解。

每當我們遇到使程式碼略有不同的條件時,我們應該問自己:這個條件真的有必要嗎?如果答案是“是”,我們應該再問自己一次。通常,對於特定條件並沒有真正需求。例如,為什麼我們甚至需要為不同的工具分別新增測試ID?我們不能配置其中一個工具來使用另一個工具的測試 ID 嗎?如果我們挖掘得夠深,我們可能會發現沒有人知道答案,或者最初的原因不再相關。

考慮這個例子:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}

此程式碼處理兩種邊緣情況:當 assetDir 不存在時,以及當 assetDir 不是陣列時。此外,物件產生程式碼是重複的。 (我們不要談論嵌套三元組...)我們可以擺脫重複和至少一個條件:

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}

我不喜歡 Lodash 的castArray() 方法將 undefined 包裝在陣列中,這不是我所期望的,但結果仍然更簡單。

避免走捷徑

CSS 具有簡寫屬性,開發人員經常過度使用它們。這個想法是單一屬性可以同時定義多個屬性。這是一個很好的例子:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);

等同於:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'

一行程式碼而不是四行,仍然很清楚發生了什麼:我們在元素的所有四個邊上設定了相同的邊距。

現在,看這個例子:

const percent = 5;
const percentString = percent.toString().concat('%');

要了解他們的作用,我們需要知道:

  • margin屬性有四個值時,順序為上、右、下、左;
  • 當有三個值時,順序為上、左/右、下;
  • 當它有兩個值時,順序是上/下,左/右。

這會造成不必要的認知負擔,並使程式碼更難以閱讀、編輯和審查。我避免使用這種簡寫。

速記屬性的另一個問題是它們可以為我們不打算更改的屬性設定值。考慮這個例子:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'

該聲明設定了 Helvetica 字體系列,字體大小為 2rem,並使文字變為斜體和粗體。我們在這裡沒有看到的是,它還將行高更改為預設值正常。

我的經驗法則是僅在設定單一值時才使用簡寫屬性;否則,我更喜歡手寫屬性。

這裡有一些很好的例子:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}

以下是一些需要避免的例子:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}

雖然簡寫屬性確實使程式碼更短,但它們通常會使其難以閱讀,因此請謹慎使用它們。

編寫平行程式碼

消除條件並不總是可能的。但是,有一些方法可以使程式碼分支中的差異更容易被發現。我最喜歡的方法之一就是我所說的平行編碼

考慮這個例子:

const value = ~~3.14;

這可能是我個人的煩惱,但我不喜歡返回語句處於不同的級別,這使得它們更難以比較。讓我們加入一個 else 語句來解決這個問題:

const value = Math.floor(3.14);
// → 3

現在,兩個回傳值都處於相同的縮排級別,使它們更容易比較。當沒有條件分支處理錯誤時,此模式有效,在這種情況下,儘早返回將是更好的方法。

訊息:我們在避免條件章節中討論提前退貨。

這是另一個例子:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}

在此範例中,我們有一個按鈕,其行為類似於瀏覽器中的鏈接,並在應用程式中顯示確認模式。 onPress 屬性的相反條件使這個邏輯很難看出。

讓兩個條件都為正:

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}

現在,很明顯我們可以根據平台設定 onPress 或 link 屬性。

我們可以停在這裡或更進一步,這取決於組件中 Platform.OS === 'web' 條件的數量或我們需要有條件設定的 props

我們可以將條件道具提取到一個單獨的變數中:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);

然後,使用它而不是每次都硬編碼整個條件:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'

我也會將 target 屬性移至 Web 分支,因為應用程式無論如何也不會使用它。


當我二十多歲的時候,記得事情對我來說並不是什麼問題。我可以回憶起我讀過的書以及我正在從事的專案中的所有功能。現在我四十多歲了,情況不再是這樣了。我現在看重不使用任何技巧的簡單程式碼;我重視搜尋引擎、快速存取文件以及幫助我推理程式碼和導航項目的工具,而無需將所有內容都記在腦子裡。

我們不應該為現在的自己寫程式碼,而應該為幾年後的自己寫程式。思考是困難的,而程式設計需要大量的思考,即使不需要破解棘手或不清楚的程式碼。

開始思考:

  • 當你覺得自己很聰明並編寫了一些簡短、聰明的程式碼時,請考慮是否有一種更簡單、更易讀的方法來編寫它。
  • 使程式碼略有不同的條件是否確實必要。
  • 快捷方式是否使程式碼更短但仍然可讀,或只是更短。

如果您有任何回饋,請發送給我、發推文、在 GitHub 上提出問題,或發送電子郵件至 artem@sapegin.ru。取得您的副本。

以上是清洗你的程式碼:別讓我思考的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python還是JavaScript更好?Python還是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

如何安裝JavaScript?如何安裝JavaScript?Apr 05, 2025 am 12:16 AM

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版