重构CSS:策略、回归测试和维护教程

112 阅读18分钟

第一部分中,我们已经介绍了低质量的CSS代码库对终端用户、开发团队和管理层的副作用。维护、扩展和使用低质量的代码库是很困难的,通常需要额外的时间和资源。在向管理层和利益相关者提出重构建议之前,用一些关于代码库健康状况的硬数据来支持这个建议是很有用的--不仅可以说服管理部门,而且还可以为重构过程制定一个可衡量的目标。

让我们假设管理层已经批准了CSS重构项目。开发团队已经分析并指出了CSS代码库中的弱点,并为重构设定了目标(文件大小、特殊性、颜色定义等等)。在这篇文章中,我们将深入探讨重构过程本身,并涵盖增量重构策略、可视化回归测试和维护重构后的代码库。

准备和计划

在开始重构过程之前,团队需要查看代码库问题 CSS健康审计数据(CSS文件大小、选择器复杂性、重复的属性和声明等),并讨论如何处理个别问题以及预期的挑战。这些问题和挑战是针对代码库的,会使重构过程或测试变得困难。正如前文所总结的那样,建立内部规则和代码库标准并将其记录下来是很重要的,以确保团队处于同一起跑线上,有一个更加统一和标准化的重构方法

团队还需要列出各个重构任务,并设定完成重构项目的最后期限,同时考虑到当前的任务,确保重构项目不会妨碍团队处理紧急任务或在计划的功能上工作。在估算重构项目的时间长度和工作量之前,团队需要与管理层协商短期计划,并根据计划中的功能和常规维护程序调整估算和工作量。

与常规功能和错误修复不同,重构过程在前端几乎没有产生可见和可测量的变化,管理层无法自行跟踪进度。建立透明的沟通,让管理层和其他项目利益相关者及时了解重构的进展和结果是很重要的。像MiroMURAL这样的在线协作工作区工具也可以用于团队成员和管理层之间的有效沟通和协作,同时也是一个快速而简单的任务管理工具。

trivago的团队进行CSS重构项目时,Christoph Reinartz指出了透明度和清晰沟通的重要性。

"沟通和清楚地让整个公司看到进展和任何即将发生的问题是我们唯一的武器。我们决定建立一个非常简单的看板,建立一个项目会议和一个项目Slack频道,并通过我们的内部社交网络让管理层和公司

了解最新情况。

规划重构过程中最关键的因素是将CSS重构的任务范围尽可能的小。这使得任务更容易管理,也更容易测试和集成。

Harry Roberts将这些任务称为 "重构隧道"。例如,一下子重构整个代码库以遵循BEM方法,可以对代码库和开发过程产生巨大的改善。这起初可能看起来是一个简单的搜索和替换任务。然而,这项任务会影响到每个页面上的所有元素(高范围),而且团队不能马上 "看到隧道尽头的光";在这个过程中,很多东西可能会坏掉,意外的问题会拖慢进度,没有人可以知道这项任务何时会完成。团队可能会花上几天或几周的时间去做,但却有可能碰壁,积累额外的技术债务,或者使代码库更加不健康。团队最终要么放弃这项任务,要么重新开始,在这个过程中浪费了时间和资源。

相比之下,仅仅改进导航组件的CSS是一个范围较小的任务,而且更容易管理和完成。它也更容易测试和验证。这项任务可以在几天内完成。即使有潜在的问题和挑战拖累了任务,也有很大的成功机会。团队在做这项任务的时候,总能 "看到隧道的尽头",因为他们知道一旦组件被重构,所有与组件有关的问题都被修复,这项任务就能完成。

最后,团队需要就重构策略和回归测试方法达成一致。这也是重构过程变得具有挑战性的地方--重构应该尽可能的精简,并且不应该引入任何回归或错误。

让我们深入了解最有效的CSS重构策略之一,看看我们如何利用它来快速有效地改进代码库。

渐进式重构策略

重构是一个具有挑战性的过程,它比简单地删除遗留的代码并用重构后的代码取代它要复杂得多。还有一个问题是要将重构后的代码库与遗留的代码库整合起来,避免回归、意外删除代码、防止样式表冲突等等。为了避免这些问题,我建议使用增量(或细化)的重构策略。

在我看来,这是我目前遇到的最安全、最合理、最值得推荐的CSS重构策略之一。Harry Roberts在2017年曾概述过这个策略。自从我第一次听说这个策略后,它一直是我个人常用的CSS重构策略。

让我们一步步来看看这个策略。

第1步:选择一个组件并孤立地开发它

这个策略依赖于单个任务的低范围,也就是说,我们应该逐个组件地重构这个项目。我们建议从低范围的任务(单个组件)开始,然后转到高范围的任务(全局样式)。

根据项目结构和CSS选择器,单个组件的样式由组件(类)样式和全局(广泛的元素)样式组合而成。组件样式和全局样式都可能是代码库问题的来源,可能需要重构。

让我们来看看更常见的CSS代码库问题,这些问题会影响到单个组件。组件(类)选择器可能太复杂,难以重用,或者会有很高的特异性,强制执行特定的标记或结构。全局(元素)选择器可能很贪婪,会把不需要的样式泄露到多个组件中,这需要用高特异性的组件选择器来撤销。

在选择了一个要重构的组件(一个较低范围的任务)后,我们需要在一个隔离的环境中开发它,远离遗留的代码、它的弱点和冲突的选择器。这也是一个改善HTML标记的好机会,去除不必要的嵌套,使用更好的CSS类名,使用ARIA属性,等等。

你不必为此去建立一个完整的构建系统,你甚至可以用CodePen来重建各个组件。为了避免与遗留的类名冲突,并将重构后的代码与遗留的代码更清楚地分开,我们将在CSS类名选择器上使用rf- 前缀。

第二步:与遗留代码库合并并修复错误

一旦我们在一个孤立的环境中完成了组件的重建,我们就需要用重构后的标记(新的HTML结构、类名、属性等)来替换遗留的HTML标记,并将重构后的组件CSS与遗留的CSS放在一起。

我们不希望过于仓促地行动,马上删除遗留的样式。如果同时做太多的改变,我们会失去对由于代码库冲突和多次改变而可能发生的问题的跟踪。现在,让我们替换标记,将重构的CSS添加到现有代码库中,看看会发生什么。请记住,重构的CSS在其类名中应该有.rf- 前缀,以防止与遗留的代码库发生冲突。

遗留的组件CSS和全局样式可能会引起意想不到的副作用,并将不需要的样式泄漏到重构的组件中。重构后的代码库可能会缺少有问题的CSS,而这些CSS是消除这些副作用所必需的。由于这些样式的影响范围更广,可能会影响其他组件,我们不能简单地直接编辑有问题的代码。我们需要使用一种不同的方法来解决这些冲突。

我们需要创建一个单独的CSS文件,我们可以把它命名为overrides.cssdefend.css ,它将包含黑客式的、高规格的代码,以对抗来自遗留代码库的不需要的泄露的样式。

overrides.css ,它将包含高规格的选择器,以确保重构后的代码库与遗留代码库一起工作。这只是一个临时文件,一旦遗留代码被删除,它将被删除。现在,添加高规格的样式覆盖,以取消遗留样式所应用的样式,并测试一切是否按预期工作。

如果你注意到任何问题,请检查重构后的组件是否缺少任何样式,回到隔离的环境中,或者是否有任何其他样式泄漏到组件中,需要被覆盖。如果添加了这些覆盖后,组件看起来和工作状态与预期的一样,请删除重构后的组件的遗留代码,检查是否有任何问题发生。从overrides.css 删除相关的黑客代码并再次测试。

根据不同的情况,你可能无法立即删除每个覆盖。例如,如果问题出在一个全局元素选择器上,而这个选择器会将样式泄露给其他需要重构的组件。对于这些情况,我们不会冒险扩大任务和拉取请求的范围,而是等到所有组件都被重构后,在我们从所有其他组件中移除相同的样式依赖后再处理高范围的任务。

在某种程度上,你可以把overrides.css 文件当作你临时的TODO列表,用于重构贪婪的、范围广泛的元素选择器。你也应该考虑更新任务板,以包括新发现的问题。确保在overrides.css 文件中添加有用的评论,这样其他团队成员就会在同一页面上,立即知道为什么要应用覆盖,以及对哪个选择器的响应。

/* overrides.css */
/* Resets dimensions enforced by ".sidebar > div" in "sidebar.css" */
.sidebar > .card {
  min-width: 0;
}

/* Resets font size enforced by ".hero-container" in "hero.css"*/
.card {
  font-size: 18px;
}

第三步:测试、合并和重复

一旦重构后的组件成功地与传统代码库集成,创建一个拉动请求,并运行一个自动化的视觉回归测试,以捕捉任何可能没有注意到的问题,并在将它们合并到一个主要的git分支之前修复它们。视觉回归测试可以被视为合并单个拉动请求前的最后一道防线。我们将在本文接下来的章节中更详细地介绍视觉回归测试。

现在,冲洗并重复这三个步骤,直到代码库被重构,overrides.css 是空的,可以安全地被删除。

第四步:从组件转移到全局样式

让我们假设我们已经重构了所有单独的低范围的组件,并且overrides.css 文件中剩下的都是与全局范围内的元素选择器有关的。这是一个非常现实的情况,从经验上讲,许多CSS问题都是由于大范围的元素选择器将样式泄露给多个组件造成的。

通过先处理单个组件,并使用overrides.css 文件将它们从全局性的CSS副作用中屏蔽出来,我们使这些范围更广的任务更容易管理,而且风险更小。我们可以比以前更安全地重构全局CSS样式,从单个组件中删除重复的样式,用一般的元素样式和工具来替代它们--按钮、链接、图像、容器、输入、网格等等。通过这样做,我们将逐步删除我们临时的TODOoverrides.css 文件中的代码和在多个组件中重复的代码。

让我们应用同样的增量重构策略的三个步骤,首先是孤立地开发和测试样式。

接下来,我们需要将重构后的全局样式添加到代码库中。在合并两个代码库时,我们可能会遇到同样的问题,我们可以在overrides.css 中添加必要的覆盖。然而,这一次,我们可以预期,随着我们逐渐移除遗留的样式,我们也将能够逐渐移除我们为对抗那些不需要的副作用而引入的覆盖。

孤立地开发组件的缺点可以在多个组件之间共享的元素样式中找到--样式指南元素,如按钮、输入、标题等等。当从传统代码库中孤立地开发这些元素时,我们不能访问传统的样式指南。此外,我们不希望在遗留代码库和重构代码库之间产生那些依赖关系。

这就是为什么删除重复的代码块,并在以后将这些样式移到单独的、更通用的样式指南组件和选择器中,会更容易。它允许我们在最后解决这些变化,而且风险较低,因为我们正在使用一个更健康和一致的代码库,而不是混乱的、不一致的和有错误的传统代码库。当然,任何意外的副作用和错误仍然可能发生,这些应该用可视化的回归测试工具来捕捉,我们将在文章的一个章节中介绍。

当代码库被完全重构,并且我们已经从overrides.css 文件中删除了所有临时的TODO项目,我们就可以安全地删除它,我们就剩下重构和改进后的CSS代码库了。

增量式CSS重构实例

让我们使用增量重构策略来重构一个简单的页面,该页面由一个标题元素和一个网格组件中的两个卡片组件组成。每个卡片元素由一个图片、标题、副标题、描述和一个按钮组成,并被放置在一个具有水平和垂直间距的2列网格中。

根据项目的情况,测试工具不需要复杂或精密就能有效。在对圣丹斯学院的CSS代码库进行重构时,开发团队使用Jekyll生成的一个简单的静态风格指南页面来测试重构的组件。

"在Jekyll实例上执行抽象重构的一个意外结果是,我们现在可以把它作为一个活的风格指南发布到Github页面。这已经成为我们开发团队和外部供应商参考的宝贵资源。"

一旦CSS重构任务完成,重构后的代码可以投入生产,团队也可以考虑做一个A/B测试,检查重构后的代码库对用户的影响。例如,如果重构过程的目标是减少整个CSS文件的大小,那么A/B测试就有可能为移动用户带来显著的改善,这些结果也会对项目的利益相关者和管理层有利。这正是Trivago的团队在部署大规模重构项目时的做法。

("(......)我们能够以A/B测试的方式发布技术迁移。我们对迁移进行了一周的测试,在移动设备上取得了积极的结果,在移动优先的情况下,仅在四周后就接受了迁移。"

追踪重构进度

看板、GitHub问题、GitHub项目板,以及标准的项目管理工具可以很好地跟踪重构的进展。然而,根据这些工具和项目的组织方式,可能很难估计每页的进度,或者快速检查哪些组件需要重构。

这就是我们的.rf-预设的CSS类的用武之地。Harry Roberts已经详细地谈到了使用前缀的好处。底线是--这些类不仅允许开发人员将重构的CSS代码库与传统的代码库明确分开,而且还可以快速地向项目经理和其他项目利益相关者展示每页的进展。

例如,管理层可能决定通过只部署重构后的主页代码来尽早测试重构后的代码库的效果,他们想知道什么时候主页组件会被重构并准备好进行A/B测试。

与其浪费一些时间将主页组件与看板上的可用任务进行比较,开发人员只需临时添加以下样式,以突出在类名中有rf- 前缀的重构组件,以及需要重构的组件。这样,他们就可以重新组织任务,优先重构主页上的组件。

/* Highlights all refactored components */
[class*="rf-"] {
  outline: 5px solid green;
}

/* Highlights all components that havent been refactored */
[class]:not([class*="rf-"]),
body *:not([class]) {
  outline: 5px solid red;
}

维护重构后的代码库

在重构项目完成后,团队需要确保在可预见的未来保持代码库的健康发展--新的功能会被开发出来,一些新的功能甚至可能被仓促开发而产生技术债务,各种错误修复也会被开发出来,等等。总而言之,开发团队需要确保,只要他们负责代码库,代码库的健康状况就会保持稳定。

可能导致潜在错误的CSS代码的技术债务应该被隔离、记录,并在一个单独的CSS文件中实现,该文件通常被命名为shame.css

记录在重构项目中建立和应用的规则和最佳实践非常重要。有了这些书面的规则,就可以进行标准化的代码审查,加快新团队成员的项目入职,更容易进行项目交接等。

一些规则和最佳实践也可以通过自动化的代码检查工具(如stylelint)来执行和记录。Andrey Sitnik是PostCSS和Autoprefixer等广泛使用的CSS开发工具的作者,他曾指出,自动打码工具可以使代码审查和入职更容易,压力更小

"然而,自动提示并不是在你的项目中采用Stylelint的唯一原因。它对团队中新的开发人员的入职非常有帮助:大量的时间(和神经!)被浪费在代码审查上,直到初级开发人员完全了解公认的代码标准和最佳实践。Stylelint可以使这个过程对每个人来说都不那么紧张"。

此外,团队可以创建一个Pull Request模板,并包括标准和最佳实践的检查表以及项目代码规则文件的链接,这样,提出Pull Request的开发者就可以自己检查代码,并确保它遵循约定的标准和最佳实践。

总结

在重构CSS的时候,增量重构策略是最安全和最值得推荐的方法之一。开发团队需要逐个组件地重构代码库,以确保任务的范围较小,并且是可管理的。然后,个别组件需要被隔离开发--远离有问题的代码--然后与现有的代码库进行合并。从冲突的代码库中可能出现的问题可以通过添加一个临时的CSS文件来解决,该文件包含所有必要的覆盖以消除CSS样式的冲突。之后,目标组件的遗留代码可以被删除,这个过程一直持续到所有组件都被重构,直到包含覆盖的临时CSS文件为空为止。

视觉回归测试工具,如Percy和Chromatic,可以用来测试和检测任何回归和Pull Request级别的不需要的变化,所以开发者可以在重构后的代码部署到实际网站之前修复这些问题。

开发人员可以使用A/B测试,并使用监控工具来确保重构不会对性能和用户体验产生负面影响,然后再最终在实时网站上启动重构的项目。开发人员还需要确保在项目中使用商定的标准和最佳实践,以便在未来继续保持代码库的健康和质量。