前几天我和一位才华横溢的工程师朋友聊天,他提到他从未从头开始构建过任何东西。他整个职业生涯都是维护其他人(通常很糟糕)的代码。
在一个完美的世界里,我们都可以从头开始编写代码,代码将完美运行,然后我们会将其放入天空中的一个箱子里,永远不会再被任何人看到。
我们都知道情况并非如此。代码需要维护。
Kyle Simpson 经常在演讲开始时解释说,我们只有 30% 的时间用于编写代码,而 70% 的时间用于维护它。成为一个好同事和程序员不仅仅是成为一个熟练的解决问题的人,还需要做到可读性强。优秀的开发人员会考虑到维护性来编写代码。
我经常开玩笑说,我不想雇佣一个代码忍者。忍者在半夜潜入,留下一片血腥。
我想要一个代码清洁工。一个在代码长廊中行走的人,清理碎片,掸去被忽视的部分的灰尘,擦亮其他部分,丢弃不必要的碎片。我更喜欢这个更温和、更准确的比喻。这个人是你想要在团队中的。这个人是你想要在代码审查中拥有的。
向 JD Cantrell 致敬,他是一位很棒的代码清洁工和代码审查员。
让我们从两种不同的编程范式的角度思考代码维护
在编程中,有很多工具和流程可以实现相同的目的。没有正确答案。以下是 Michael Feather 对两种非常流行的编程范式简洁的定义:
面向对象编程通过封装移动部件来使代码易于理解…
函数式编程通过最小化移动部件来使代码易于理解。
让我们从易于理解和维护的角度来考虑两者。
函数式编程
我自己亲眼见证了在前端 JavaScript 中使用函数式方法的价值。当我们编写代码时,有时我们想假设代码将永远按照我们预期的方式使用,但我们中的大多数人已经做了足够长的时间来承认情况并非总是如此。
函数式编程包括但不限于:
- 它是声明式的——我们以可以重用的方式编写代码,而不是告诉计算机在每个步骤中需要做什么。这与抽象有一些相似之处。
- 它是纯净的——我们不会修改或改变函数作用域之外的东西,因此…
- 它是不可变的——你不会遇到这种情况:你输入相同的值,但在多次执行中得到不同的结果。
我相信函数式编程在维护方面非常有用,因为缺乏副作用。这是巨大的。这可以防止我们的代码变得脆弱。人们有时认为最大的问题是代码中的错误。我认为最糟糕的代码不是出现错误并失败的代码。我们可以通过有条理的隔离找到问题。最糟糕的代码是无法预测其行为的代码,它在各个地方安静地运行。你到处在代码库中玩打地鼠游戏,有时很难找到罪魁祸首。函数式编程风格先发制人地解决这个问题,因为它从一开始就被设计为防止这种情况发生(尽可能)。
如果你想更深入地研究函数式编程,这里有一些资源:
- Anjana Vakil: 用 JavaScript 学习函数式编程——JSUnconf 2016
- Mary Rose Cook: 函数式编程的实用入门
- Kyle Simpson: Functional Lite
虽然我非常喜欢函数式编程,但请注意,维护方面仍然可能存在问题。如果一些东西使用相同的纯函数,并且随着时间的推移,你为其中一个应用程序调整了该函数,你可能会遇到一个问题:你还会改变其他隐藏的依赖项。良好的文档在这里非常有用。
面向对象编程
相比之下,面向对象编程更像是遵循食谱的步骤。它使用对象,这些对象可能包含或不包含数据,以及方法,这些方法往往是过程化的。通常,对象有一个自我概念(例如 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 应用程序都正在处理这些问题。这是一篇基于经验的观点文章。不同的方法适合不同的项目。
我希望这篇文章能鼓励开发人员提前思考。我们都在一起,我们能做的最好的事情就是互相学习。如果你完全不同意我的观点,那也没关系。我希望,即使是采取立场,你也可以巩固一个方向,以 **维护** 为核心概念。
感谢您分享这篇有趣的文章。维护,尤其是文档,是始终会出现的问题。分享我工作场所的一些内容:即使在一个只有 3 名开发人员(和一名实习生)的团队中,适当的决策和文档也至关重要。
我们团队中的一名成员目前正在休假,当谈到修复问题时,缺乏文档真的会让我们的努力变得效率低下。
我喜欢忍者和清洁工的类比。我受不了雇主总是说他们正在寻找“代码忍者”。
我完全同意。
很棒的文章!我并不完全认同“DRY”部分。CSS 似乎在每一种情况下都与 DRY 作斗争……
感谢您的评论,我会尝试澄清我的意思。假设您有一些内联表单元素。一个标签、一个输入和一个按钮。为了保持视觉一致性,您可能会编写一个 mixin 或扩展一个类,允许您一次性应用填充、字体族和行高。这样,您就不会分别编写这些样式(最糟糕的是)维护这些样式。
我知道这是一个小例子,但希望它能表达为什么 DRY 出样式,无论您如何编写它们,都是非常好的。
实际上我认为 CSS 鼓励 DRY,仅仅因为级联的工作方式。它只是通过组合(经典的 OOP 原则)更好地工作。
您可以拥有建立必要外观和感觉的基本样式作为广泛的笔触,然后随着模块变得更具体,您可以添加更具体的样式,这些样式有助于功能和外观。
这样,您就不必在模块之间重新建立基本样式,并且可以通过特定层的特异性来避免覆盖其他模块,这或多或少是 OOCSS 在大局中的工作方式。
实践中的 OOP 只有在设计良好的情况下才能发挥作用,而这通常取决于开发人员在理解事物应该如何构建方面的经验。我个人尽我所能使我的样式 DRY,但正如你所说,某些边缘情况需要一些难看的解决方案来满足截止日期。
这是我第一次听到有人谈论维护,自从 Jens Meiert 在 2004 年写过关于它的事情以来。也许你应该和他一起合作,更新他写过的可维护性指南。 http://meiert.com/en/blog/20090617/maintainability-guide/ – 这是我们所拥有的一切。认真地说。
哇,太酷了。我以前没见过。这是一项非常棒的资源,没错,它确实需要稍微改进一下。感谢您发布它!
这篇文章的基调与我产生共鸣。虽然我这些年来一直受雇为 Web 设计师和开发人员,但我 70% 以上的工作都是由我接手的其他人的代码重用(最终是重写)组成。我从来没有真正有机会发挥我作为真正设计师或开发人员的作用。相反,我更像是一个清洁工——一个维护人员。当然,这不幸地阻碍了我的职业抱负,因为似乎没有人想要雇佣“中级”设计师或开发人员(也就是“清洁工”、维护人员);除非有人正在阅读这篇文章并且想要这样的人,否则我正在寻找!
我喜欢你的作品,Sarah,我一直努力追求更高的目标,希望有一天我能为实际报酬创造出很棒的东西。
谢谢!没错,我认为我们许多人都是维护人员,或者至少在我们职业生涯的很大一部分时间里都在做这项工作。
如今我们编写 CSS 的方式正在发生很大变化(而且变化是好的)。随着 JavaScript 中更多基于组件的设计,我们正在努力使 CSS 适应相同的文化。但我一直将级联视为 CSS 的一个强大功能,而不是一个副作用(现在我有点怀疑……不确定 :))。
你对如今基于组件的 CSS 设计中的级联功能有什么想法?
你好 Gyandeep,
我在本文的这一部分之后讨论了级联和基于组件的样式:https://css-tricks.org.cn/on-style-maintenance/#article-header-id-3
函数式编程真的又成为酷孩子了吗?似乎“小心你在初中叫谁丑”的表情包可能很合适。
我对函数式编程的大多数论据的看法是,它们假设你的对象是哑对象,并且你与它们的交互总是对内容一无所知。
以你所说的那个类比为例,我假设一种水果是香蕉,因为有人告诉我它是一种水果。这 *是* 一个问题,但问题不在于我得到的是一种水果,问题在于我对此做了假设。
OOP *和* 函数式编程都是如此。
如果我得到一个(块)水果,我会检查它。即使我这样做非常简短,它也能阻止我咬一口西红柿,以为它是杏子。OOP 也是如此。如果你的对象可以处于不同的状态,并且交互会根据该状态而有所不同,那么你就会将确认该状态作为交互的一部分。
public class fruit {
protected $skin;
public function eat() {
if ($this->skin) {
//移除皮
$this->skin = FALSE;
}
//吃
}
function __construct (boolean $skin) {
$this->skin = $skin;
}
}
呵呵,我刚注意到我在文本和代码之间切换了例子。
与对象交互时假设它们处于特定状态是问题所在。而不是根本没有那个对象。
水果类比可能是一个很好的例子,说明我们何时应该考虑扩展一个对象。
我认为 OO 范式是一个失败。它在理论上听起来不错,但在实践中,它要求你跟踪太多活动部件,当这些部件在整个团队中传播时,它们会呈指数级放大。此时,我相信解决方案是遵循 Dave Rupert 在 CodePen 一集中提出的建议:让每个设计师/开发人员将他们的样式转储到一个 junk.css 文件中,并且应该有一个内部 CSS 仓库管理员将这些垃圾转换为一个可管理/有文档的系统,以及一个样式指南。
我也觉得很有趣,关于 Atomic CSS 的嘲讽推文并不缺乏,但 Atomic CSS 似乎是组合式 CSS 的典范,而 OOCSS 似乎是全局语言中的继承的典范。
目前,我认为最好的解决方案是
创建一个包含通用排版的全局重置。
将代码分解成更小的组件文件。Jake Archibald 关于样式表 `<link>` 和 HTTP2 喜欢大量小文件的文章,将使网站逐步快速加载。
使用类似 CSS Modules 这样的工具消除全局样式和特异性,同时保持组件的可组合性。
如果公司有足够的资金,可以聘请一些 CSS 维护人员。如果负担不起,那么您的 CSS 可能并不难管理,也不会给您带来太大的成本。
“选择合适的工具”这句话开始感觉像是一种逃避。我也有过这样的想法,但我们只有在能够明确地说出某些东西很糟糕时才会进步。在现实世界中,OOCSS 并不理想。它让我们开始批判性地思考 CSS,现在我们有了修复它的想法和工具。让我们使用它们,让 OOCSS 成为过去。
您不同意我的观点很正常,这篇文章的重点不是让每个人都使用 OOCSS。重点是澄清关于维护的思考。您的计划看起来不错,前提是您已经使用了 HTTP2(即使您没有使用 HTTP2,也有其他方法进行编译)。也就是说,我不确定您的论点中是否真的考虑了长期的维护。在您建议雇人处理网站样式的同时,也强烈的表达了对网站样式如何编写应该如何编写的意见,这有点像“让他们吃蛋糕”。
关于“选择合适的工具”是一种逃避的说法,我认为在开发过程中不理解灰色地带是有危险的。如果您之前听过这句话,可能是因为人们倾向于重复那些听起来真实的话。
好吧,对于新手和老手来说,这是一个摆脱功能膨胀和强制可维护性的方案或计划。
从 2001 年开始,我一直遵循同样的规则,没有设置大型清单,但我会在有机会时尝试强制使用它。我的方案被称为语义网。
cu, w0lf.
我可以从重构 bet365.com(特别是 mobile.bet365.com) CSS 编写方式的经验中谈起。这种经验让我写了 http://ecss.io(反过来,是基于 https://benfrain.com/enduring-css-writing-style-sheets-rapidly-changing-long-lived-projects/ 编写的)。
很早就发现,尽管大多数人表面上都承认用户需求是最重要的,但如果您专注于 CSS 的开发人员可用性,并建立工具来强制执行一致性(对于 20 多个开发人员参与的项目来说尤其重要),最终的结果是代码更容易被开发人员移除和更改。解耦对于代码维护非常重要。这就是 ECSS 在这方面投入大量资源的原因。我非常喜欢 CSS Modules,因为它解决了相同的大部分问题,但在开发 ECSS 时它还没有出现。
原子是单一责任原则方法的“鼻祖”。这些方法通常会导致更少的 CSS,但需要理解抽象,并且通常来说需要在模板中做更多工作。这可能适合您的需求,也可能不适合(它不适合我,但该系统有很多优点)。
对我来说,在一个大型代码库中,主要的选择是使用抽象还是隔离。这是两个主要的选择,除此之外还有其他一些次要的选择。
抽象(例如 SRP 方法)可以以更小的文件大小为代价,通常需要更多模板工作,并且开发人员需要理解一个陌生的抽象。
隔离可以轻松删除整个模块,并提供更直观的开发人员可用性(通常你只是像往常一样编写 CSS),但它本质上会导致更多 CSS,尽管对我来说从未多到成为问题。