关于样式维护

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您旅途中的每个阶段提供云产品。立即开始使用 价值 200 美元的免费积分!

前几天我和一位才华横溢的工程师朋友聊天,他提到他从未从头开始构建过任何东西。他整个职业生涯都是维护其他人(通常很糟糕)的代码。

在一个完美的世界里,我们都可以从头开始编写代码,代码将完美运行,然后我们会将其放入天空中的一个箱子里,永远不会再被任何人看到。

我们都知道情况并非如此。代码需要维护

Kyle Simpson 经常在演讲开始时解释说,我们只有 30% 的时间用于编写代码,而 70% 的时间用于维护它。成为一个好同事和程序员不仅仅是成为一个熟练的解决问题的人,还需要做到可读性强。优秀的开发人员会考虑到维护性来编写代码。

我经常开玩笑说,我不想雇佣一个代码忍者。忍者在半夜潜入,留下一片血腥。

我想要一个代码清洁工。一个在代码长廊中行走的人,清理碎片,掸去被忽视的部分的灰尘,擦亮其他部分,丢弃不必要的碎片。我更喜欢这个更温和、更准确的比喻。这个人是你想要在团队中的。这个人是你想要在代码审查中拥有的。

JD Cantrell 致敬,他是一位很棒的代码清洁工和代码审查员。

让我们从两种不同的编程范式的角度思考代码维护

在编程中,有很多工具和流程可以实现相同的目的。没有正确答案。以下是 Michael Feather 对两种非常流行的编程范式简洁的定义:

面向对象编程通过封装移动部件来使代码易于理解…

函数式编程通过最小化移动部件来使代码易于理解。

让我们从易于理解和维护的角度来考虑两者。

函数式编程

我自己亲眼见证了在前端 JavaScript 中使用函数式方法的价值。当我们编写代码时,有时我们想假设代码将永远按照我们预期的方式使用,但我们中的大多数人已经做了足够长的时间来承认情况并非总是如此。

函数式编程包括但不限于:

  • 它是声明式的——我们以可以重用的方式编写代码,而不是告诉计算机在每个步骤中需要做什么。这与抽象有一些相似之处。
  • 它是纯净的——我们不会修改或改变函数作用域之外的东西,因此…
  • 它是不可变的——你不会遇到这种情况:你输入相同的值,但在多次执行中得到不同的结果。

我相信函数式编程在维护方面非常有用,因为缺乏副作用。这是巨大的。这可以防止我们的代码变得脆弱。人们有时认为最大的问题是代码中的错误。我认为最糟糕的代码不是出现错误并失败的代码。我们可以通过有条理的隔离找到问题。最糟糕的代码是无法预测其行为的代码,它在各个地方安静地运行。你到处在代码库中玩打地鼠游戏,有时很难找到罪魁祸首。函数式编程风格先发制人地解决这个问题,因为它从一开始就被设计为防止这种情况发生(尽可能)。

如果你想更深入地研究函数式编程,这里有一些资源:

虽然我非常喜欢函数式编程,但请注意,维护方面仍然可能存在问题。如果一些东西使用相同的纯函数,并且随着时间的推移,你为其中一个应用程序调整了该函数,你可能会遇到一个问题:你还会改变其他隐藏的依赖项。良好的文档在这里非常有用。

面向对象编程

相比之下,面向对象编程更像是遵循食谱的步骤。它使用对象,这些对象可能包含或不包含数据,以及方法,这些方法往往是过程化的。通常,对象有一个自我概念(例如 JavaScript 中的“this”)。面向对象并不关注纯度,而是倾向于使用封装来确保没有任何东西泄漏到外部范围。

在最好的情况下,面向对象方法倾向于考虑事物的最高阶,然后缩减到可以拥有的每种实例,并区分出每种情况下要做什么。如果你把它想成林奈分类系统和我们如何制作形态树(谁不会呢?:)),你会先问它是否温血动物?然后,它是否有毛皮?然后它是否有鼻子?等等。我在生物学方面真的说得很难听,但这是一个例子。

在最坏的情况下,面向对象编程会受到很多必要的批评,因为有时你并没有清楚地描述你认为你正在描述的东西。你可以把它想象成:你认为某样东西是香蕉,但实际上是桃子,因为唯一向你描述的是你有水果。我们之前谈到过,那种代码可能成为维护的噩梦,所以虽然情况并非总是如此,但它确实会发生。

CSS 中的适用示例

有了函数式和面向对象方法,让我们考虑一下它如何应用于 CSS 和创作与维护的概念。CSS 以声明式和纯净的方式编写。你无法使用另一个 CSS 块来改变一个 CSS 块。

我认为很多人都预计纯函数式方法最适合 CSS。他们可能将其与保持 CSS 的尽可能纯净联系起来,这就是 CSS Modules 这样的理念的来源。概念是,如果你封装了你正在修改它们的东西的样式,就在你需要它们的地方。你只处理该实例。没有副作用。我不同意。你以此避免了一些事情。

  • 你避免了冲突。这就是面向对象编程有时会受到批评的地方。
  • 你避免了命名。命名很困难。使用这种方法,你可以避免命名。
  • 你避免了处理级联。我会在一分钟内讨论这个问题。

对于其他类型的(非 CSS)编程,我们保持纯净并避免全局范围,我们就没问题!所以它一定适用于所有地方,对吧?

这就是我们需要谈论适合工作的正确工具的地方。如果我们考虑维护而不是编写,其他方法就会在这里发挥作用

  • 大大小小的公司往往至少每 1-2 年进行一次重新设计。如果你的代码库很大,有很多人,并且你需要将所有地方的行高从 1.1rem 更改为 1.2rem,你是否需要回到每个模块中更改该值?一个全局对象或扩展的对象在这里就变得非常有用。
  • 级联可以提供帮助,如果你了解并组织它。这与任何复杂的软件设计都一样。你可以查看你要构建的内容,并对构建和设计做出负责任的决定。你可以决定什么可以放在顶层,需要被其他更小部分继承。这可以避免 CSS 中的意大利面条代码,并使你的代码保持DRY
  • CSS 是关于设计的。好的设计,本质上,当它具有凝聚力时是成功的。一个与它构建的其设计基础设施保持一致的 CSS 代码库可以实现更干净的代码,并且额外的好处是更好的协作。CSS 也可以为设计师进行自我检查:“等等,我们还有另一个三级按钮?为什么?”在这种模型中,凝聚力 UI/UX 中的漏洞会很好地表现出来。
  • 命名可以帮助进行文档化。这一点有点棘手,我不确定是否完全必要,但值得一提。无论你喜欢 BEM、SMACSS/OOCSS 还是 Atomic,命名如果做得好的话,实际上可以让你了解一个类在什么地方使用以及为什么使用。

范式共享特性

你可能已经猜到了,虽然我认为其他范式很有价值,但我更喜欢函数式风格。考虑到这一点,你可能想知道我为什么会在 面向对象的 CSS 方法 中找到价值。

尽管它的名字,OOCSS 与函数式编程共享特性。要按照预期正确使用 OOCSS,你主要是在创建混合,这些混合以类似于带有参数(通常带有默认值)的函数的方式在你的系统中表达。它们是纯净的,可以应用于多种用途。

它仍然使用面向对象的方案。具有非常智能的架构,可以预测事物如何相互影响,并仔细考虑应该继承什么。考虑到 CSS 本质上是一组对象,这在 CSS 上下文中比在其他语言中更有意义。

去年,我带领团队对一个庞大的前端组件系统进行了彻底重构。它使用 OOCSS 架构,我亲眼见证了这种模型的好处。设计师可以要求我修改某些内容,我们可以快速看到它在整个网站上更新。他们甚至可以改变主意,而不会太麻烦。来自上级的最后时刻的要求并没有阻碍我们的发布。我绝对不是说它没有痛点——但对于它的规模来说,它出奇地顺利。这使我以全新的视角看待我为其他应用程序编写的 CSS。

面向未来

人们倾向于将这些东西相互比较

  • CSS
  • 通过 JavaScript 进行样式化

我认为,无论哪种方式,你都可以保持维护友好或创建维护噩梦。

你绝对可以在 Aphrodite、React-CXS 或 CSS Modules 中负责地处理行高、字体和行为。我非常喜欢其中的一些方法。它们可以处理继承,并可以编写为扩展以获得更 DRY 和更可维护的方法。

但即使你 *可以*,我个人还没有看到足够的项目 *确实* 这样做了。(我很想看到一些例子!)

大多数情况下,我看到人们为品牌颜色提取一个或两个变量,而没有其他任何东西。几个变量并不构成一个负责任的系统架构。我认为我们可以做得更好,如果我们考虑我们的代码,不是从什么是更容易编写开始,而是从什么是更容易更改开始。或者更好的是,从什么对 *其他人* 更容易更改开始。

请注意创建良好的文档!无论使用哪种编程范式,在调整它之前,你都必须了解你所有依赖项。优秀的文档也意味着你也会在决策中更加有目的性。当你不得不向其他人解释某个东西是什么以及它在哪里使用时,就会有更少的“好吧”了。

我在本文中要解决的问题是协作和规模的问题。并非每家公司、每个网站甚至每个 Web 应用程序都正在处理这些问题。这是一篇基于经验的观点文章。不同的方法适合不同的项目。

我希望这篇文章能鼓励开发人员提前思考。我们都在一起,我们能做的最好的事情就是互相学习。如果你完全不同意我的观点,那也没关系。我希望,即使是采取立场,你也可以巩固一个方向,以 **维护** 为核心概念。