[譯] 再談 CSS 中的程式碼味道

IridescentMia發表於2017-03-10

再談 CSS 中的程式碼味道

回到 2012 年,我寫了一篇關於潛在 CSS 反模式的文章 CSS中的程式碼味道。回看那篇文章,儘管四年過去了,我依然認同裡面的全部內容,但是我有一些新的東西加到列表中。再次說明,這些內容並不一定總是壞的東西,因此把它們稱為程式碼味道:在你的使用案例中它們也許可以很好的被接受,但是它們仍然讓人覺得有一點奇怪。

在我們開始前,讓我們回想一下什麼是程式碼味道,摘自 維基百科 (emphasis mine):

程式碼味道,也被稱作程式碼異味,在計算機程式設計領域,指程式原始碼中的任何 有可能預示著更深層次問題 的徵兆。按照 Martin Fowler 所說的,「程式碼味道是一種表面跡象,通常對應著系統中的深層次問題」。另外一種看待程式碼味道方式是關於準則和質量:「程式碼味道是程式碼中某種特定的結構表明了 違反了基本的設計準則 並且對設計質量產生負面影響」,程式碼味道通常不是 bug —— 它們不是技術性的錯誤 並且不會當時就對程式的功能產生阻礙。相反的,它們預示著可能拖慢開發的設計缺陷 或者增大未來出現 bug 或者故障的風險。程式碼異味是導致技術債的因素的指示器。Robert C. Martin 將一系列程式碼味道稱作軟體技藝的「價值體系」。

因此, 它們並不總是技術上的錯誤, (不過)它們可作為一個不錯的檢驗方法。

@extend

希望我可以把這第一條講得細緻又簡潔:我早就被告知 @extend 的副作用和陷阱,我也會積極地認為它是程式碼味道。它也並不絕對的不好,雖然通常是的。對它應該持懷疑態度。

@extend的問題是多方面的,可以概括如下:

  • 它對效能的影響事實上比 mixins 更嚴重。 Gzip 偏愛重複性的內容,所以具有更高重複性 CSS 檔案 (如 mixins) 取得更高的壓縮量。

  • 它是貪婪的。 Sass 的 @extend 將會 @extend 它找到的每個 class 的例項,返回給我們一個相當長的選擇器鏈 看起來像這樣

  • 它移動你的程式碼庫的順序。 在 CSS 中原始的順序至關重要,所以應該總是避免在你的專案中移動選擇器的位置。

  • 它使檔案晦澀難懂。 @extend 在你的 Sass 中隱藏了很多複雜的東西,你需要逐步的拆開,然而在你審閱檔案的過程中,這個複雜的 class 方法將所有的資訊置於焦點。

擴充套件閱讀:

為類使用連線字串

另外一個 Sass 讓人惱火的地方就是在你的類上使用 & 連線字串,例如:

.foo {
  color: red;

  &-bar {
    font-weight: bold;
  }

}複製程式碼

編譯成:

.foo {
  color: red;
}

.foo-bar {
  font-weight: bold;
}複製程式碼

顯而易見的好處是簡潔:事實上我們只用寫一次名稱空間 foo 確實是很 DRY (Don't repeat yourself)。

一個不那麼明顯的缺點是,字串 foo-bar 現在在原始碼中不存在。搜尋程式碼庫查詢 foo-bar 只會返回 HTML 中的結果(或者是編譯過的 CSS 檔案,如果你已經把它納入到你的專案中)。想要在原始碼中定位 .foo-bar 的樣式變得非常困難。

我不僅僅是 CSS 全稱寫法的愛好者:總的來說,相比於重新為元素命名一個類,我更喜歡查詢到它原有的類名,所以可查詢性對我來說很重要。如果我加入一個專案大量使用 Sass 的字串連線,追蹤查詢通常都會是非常艱難的。

當然你也可以說 sourcemaps 將會幫助我們,或者如果我正在查詢 .nav__item 這個類,我可以簡單的開啟 nav.scss 這個檔案,但是不幸的是這並不總是奏效。獲得更多的資訊,可以看我做的關於它的 錄屏

Background 簡寫

我最近討論的另外一個主題就是使用 background 簡寫語法。想了解更多細節,請參考 the relevant article,但是在這裡做一個總結如下:

.btn {
  background: #f43059;
}複製程式碼

…當你可能想要表達的意思是:

.btn {
  background-color: #f43059;
}複製程式碼

…這是另一種程式碼味道的實踐。當我看到前者被使用的時候,很少是開發者實際上想要的:幾乎任何時候他們真正的意思是後者。後者 僅僅 設定或者改變背景色,而前者將會也重置或者復原背景圖、背景位置、背景連結等。

在 CSS 專案中看到這樣的形式立即提醒我,我們終究會因為它遇到問題。

關鍵選擇器多次出現

關鍵選擇器是獲得目標或者是被賦予樣式的選擇器。它通常在左花括號 ({) 前面的內容,但也並不總是。在下面的 CSS 中:

.foo {}

nav li .bar {}

.promo a,
.promo .btn {}複製程式碼

…關鍵選擇器是:

  • .foo,
  • .bar,
  • a,
  • .btn.

如果我負責一個程式碼庫並且 ack for .btn,我可能看到如下輸出:

.btn {}

.header .btn,
.header .btn:hover {}

.sidebar .btn {}

.modal .btn {}

.page aside .btn {}

nav .btn {}複製程式碼

除了很多普遍存在的相當糟糕的 CSS,我在這裡想指出的問題是 .btn 被定義了很多次,這告訴我:

  1. 沒有遵循 Single Source of Truth 告訴我按鈕看起來是什麼樣的;
  2. 有很多變化 意思是 .btn 類有很多潛在的不同的樣式,所有的這些都是通過 CSS 的可變性造成的。

一看到像這樣的 CSS,我就意識到在按鈕上做任何工作都將會有很大的影響,追蹤按鈕樣式到底來自哪裡將會非常困難,並且任何位置的改動都有可能對其他地方造成影響。這就是 CSS 可變性的關鍵性問題之一。

使用 BEM 的命名形式以便建立全新的類名稱以應對這些改變,例如:

.btn {}

.btn--large {}

.btn--primary {}

.btn--ghost {}複製程式碼

每個只有一個關鍵選擇器。

一個類名出現在另一個元件的檔案中

在一個和上面相似但是稍微不同的場景裡,類名出現在另一個元件的檔案中預示著程式碼味道。

上一個程式碼味道處理同一個關鍵選擇器有多於一個例項的問題,這個程式碼味道處理這些選擇器應該放在哪。這個問題來自於 Dave Rupert

如果我們需要給某些因為它們的上下文的不同而加樣式,我們應該把這些額外的樣式加到哪呢?

  1. 要加樣式的物件所在的檔案裡?
  2. 控制該物件上下文的檔案裡?

讓我們假設我們有如下 CSS:

.btn {
  [styles]
}

.modal .btn {
  font-size: 0.75em;
}複製程式碼

.modal .btn {} 應該放在哪?

它應該 .btn 所在的檔案中。

我們應該儘量將我們的樣式基於主題(例如:關鍵選擇器)分組。在這個例子中,主題是 .btn:這才是我們真正關心的。.modal 只不過是 .btn 的上下文,所以我們根本沒給它新增樣式。為此,我們不應該將 .btn 的樣式移出到另外的檔案中。

我們不這樣做簡單的因為它們是並列的:將所有按鈕的上下文放在一處更方便。如果我想得到專案中所有按鈕樣式的概觀,我僅僅需要開啟 _components.buttons.scss,而不是一堆其他的檔案。

這樣做使得將所有按鈕的樣式移入另外一個新專案變得更容易,更重要的是這樣做提前讀懂變得容易。我相信你們都對這種感覺相當熟悉,就是文字編輯器中開啟十餘個檔案,而僅僅試圖修改很小的一處樣式。這是我們能夠避免的。

將你的樣式基於主題的分組到檔案中:如果是給按鈕的樣式,無論它是什麼樣的,我們應該讓它在 _components.buttons.scss 檔案中。

一個簡單的經驗法則就是,問問你自己這樣的問題,我是在給 x 新增樣式還是 y?如果答案是 x,那麼你的 CSS 應該在 x.css 檔案中;如果答案是 y,它應該在 y.css 中。

BEM Mixes

事實上很有趣的,我根本不會這樣寫 CSS —— 我使用 BEM mix —— 但是這是另一個不同問題的答案。不是像下面這樣:

// _components.buttons.scss

.btn {
  [styles]
}

.modal .btn {
  [styles]
}

// _components.modal.scss

.modal {
  [styles]
}複製程式碼

而是像這樣:

// _components.buttons.scss

.btn {
  [styles]
}

// _components.modal.scss

.modal {
  [styles]
}

  .modal__btn {
    [styles]
  }複製程式碼

第三,新的類名稱將會應用於 HTML 上,像這樣

<div class="modal">
  <button class="btn  modal__btn">Dismiss</button>
</div>複製程式碼

這被叫做 BEM mix,我們介紹第三種新的類名稱來指向屬於 modal 的按鈕。這樣避免了它在哪裡的問題,它通過避免巢狀,減少了名稱唯一性的問題,同時通過重複 .btn 類避免可變性帶來的問題。完美!

CSS @import

我會說 CSS @import 不僅僅是程式碼味道,它的的確確是壞的實踐。它推遲 CSS 檔案的載入(效能的決定性因素),比實際的需要載入的更晚,造成嚴重的效能下降。下載具有 @import 的 CSS 檔案的(簡化的)工作流程看起來有點像:

  1. 獲取 HTML 檔案,這個 HTML 檔案中請求 CSS 檔案;
  2. 獲取 CSS 檔案,這個 CSS 檔案請求另外一個 CSS 檔案;
  3. 獲取最後一個 CSS 檔案;
  4. 開始渲染頁面。

如果我們得到 @import 的內容,將其壓入一個單獨的檔案,工作流程看起來將會是這樣:

  1. 獲取 HTML 檔案,這個 HTML 檔案中請求 CSS 檔案;
  2. 獲取 CSS 檔案;
  3. 開始渲染頁面。

如果我們不能將所有的 CSS 放入一個檔案(例如我們連結了谷歌字型),那麼我們應該在 HTML 中使用兩個 <link /> 元素,而不是使用 @import。這可能讓人感覺有點不那麼壓縮(但也是更好的方式處理所有 CSS 檔案的依賴),它對於效能仍然是比較友好的:

  1. 獲取 HTML 檔案,這個 HTML 檔案中請求 CSS 檔案;
  2. 獲取所有的 CSS 檔案;
  3. 開始渲染頁面。

所以我們在這裡對我先前那篇關於程式碼味道的文章做了幾點新增。這些是我已經看到的並且忍受著的幾點:希望現在你也可以避開他們。

相關文章