Hey friends 👋 Welcome to a spooky new edition of Frontend at Scale. Well, it's not really that spooky. Unless you're afraid of poor grammar and questionable design decisions, in which case, get ready for one of the most terrifying experiences of your life.
嘿,朋友们 👋 欢迎来到一个神秘的新版本《大规模前端》。其实没那么神秘。除非你害怕糟糕的语法和可疑的设计决策,在这种情况下,准备好迎接你一生中最恐怖的经历之一。
This week we'll talk about readability vs. familiarity, the verdict on web components, and what a 17-year-old video game can teach us about software design.
这周我们将讨论可读性与熟悉度、网页组件的评判,以及一款 17 年历史的视频游戏能教会我们关于软件设计的知识。
Let's dive in. 让我们深入探讨。
SOFTWARE DESIGN 软件设计
Intuitive Programming 直观编程
Don't you just love it when you look at a piece of code and immediately understand how it works?
你难道不喜欢看到一段代码时立刻明白它是如何工作的吗?
Or better yet, when you have to change some code and you can tell exactly where you should make the change without even thinking about it?
或者更好的是,当你必须更改一些代码时,你可以明确知道应该在哪里进行更改,而无需思考?
Or maybe you haven't noticed this at all—because that's the entire point. When code is intuitive to us, the understanding happens in an instant, without any conscious effort on our part.
或者你根本没有注意到这一切——因为这就是整个重点。当代码对我们直观时,理解会在瞬间发生,而不需要我们有任何意识上的努力。
What got me thinking about all of this wasn't some fancy software design book or research paper—it was Valve's 2007 classic video game, Portal. I was playing it last week in "developer commentary" mode, and I learned about a very cool technique the developers used to explain how portals worked in the game.
让我思考这一切的并不是一些华丽的软件设计书籍或研究论文,而是 Valve 2007 年的经典视频游戏Portal。我上周在“开发者评论”模式下玩它时,了解到开发者用来解释游戏中传送门工作原理的一个非常酷的技巧。
It turns out that many of the early playtesters initially thought that portals took you to another dimension. They had to play the game for a while before figuring out they were, in fact, just moving through different parts of the same room. So to make this mechanic clear from the beginning, the developers did something pretty clever: they positioned the very first portal of the game so that you'll inevitably see yourself on the other side of it.
原来许多早期的游戏测试者最初认为传送门会将你带到另一个维度。他们玩了游戏一段时间后才明白,实际上他们只是穿越同一个房间的不同部分。因此,为了从一开始就让这个机制变得清晰,开发者做了一件相当聪明的事:他们把游戏的第一个传送门放置在一个位置,使得你不可避免地会在另一端看到自己。
They didn't have to explain how portals worked or put up a sign saying, "You're still in the same world!" when you came out the other side. The developers made the behavior of a portal completely intuitive by just putting it in the right place.
他们不必解释门户的工作原理,也不需要挂上一个标牌,上面写着:“你仍然在同一个世界!”当你从另一边出来时。开发者们通过将其放在正确的位置使门户的行为变得完全直观。
A normal person would have thought "Oh, that's smart," and continued playing the game. But you know me—I'm a massive nerd, and apparently I hate fun because instead of enjoying the amazing puzzles Portal has to offer, my head immediately started thinking about how we can use this technique to make better software design decisions.
一个普通人可能会想“哦,这很聪明,”然后继续玩游戏。但你知道我——我是个超级书呆子,显然我讨厌乐趣,因为我没有享受《传送门》提供的精彩谜题,而是立刻开始思考我们如何利用这一技巧来做出更好的软件设计决策。
So today I want to share with you three ways in which we can apply this principle to make our code easier to understand, both by other people and your future self. We probably can't make navigating our codebase as fun as solving puzzles with a portal gun, but we can do our best to make it as natural and intuitive as a cleverly designed video game.
今天我想和大家分享三种方法,我们可以应用这个原则,使我们的代码更易于理解,无论是对别人还是对未来的自己。我们可能无法让浏览我们的代码库像用传送门枪解密题那样有趣,但我们可以尽力让它像设计巧妙的视频游戏一样自然和直观。
Choose Good Names 选择好名字
You probably don't need me to tell you that naming things is very, very hard. But it's worth spending time choosing a good name because it’s the main mechanism we have for making something intuitive and easy to understand.
您可能不需要我告诉您,命名事物是非常、非常困难的。但花时间选择一个好名字是值得的,因为 这是我们使某样东西直观易懂的主要机制。
In The Art of Readable Code, authors Dustin Boswell and Trevor Foucher suggest that we should “pack information into our names”, meaning that we should do our best to choose names that are specific and convey meaning.
在可读代码的艺术中,作者达斯汀·博斯韦尔和特雷弗·福切尔建议我们“在名称中打包信息”,这意味着我们应该尽力选择具体且具有意义的名称。
A function named getData(), for example, doesn’t pack a lot of information. In fact, it brings up more questions than answers: what exactly is this data we’re getting, and where are we getting it from? A database? Local storage? A floppy disk?
一个名为 getData() 的函数,并没有包含很多信息。事实上,它提出的问题比答案还要多:我们到底得到的数据是什么?我们从哪里获取这些数据?数据库?本地存储?软盘?
A name like fetchUserSettings(), on the other hand, packs a lot more information and immediately answers our questions: the data we’re getting are the user’s settings, and we’re fetching them from an API.
像 fetchUserSettings() 这样的名称则包含了更多信息,并立即回答了我们的问题:我们获取的数据是用户的设置,而我们是从 API 中获取这些设置的。
But what if the information we need to pack is just too much? After all, the reason naming is hard is that names have to do a lot of different jobs at once: they have to convey meaning and explain how something works, they have to describe the intent of the code and any side effects it might have, and, if that wasn't enough, they have to be as short as possible. When the thing we're trying to describe does many different other things, finding an intuitive name for it can be a real challenge.
但是,如果我们需要打包的信息只是太多了呢?毕竟,命名之所以困难,是因为名称必须同时完成许多不同的工作:它们必须传达意义并解释某物的运作方式,它们必须描述代码的意图及其可能产生的副作用,而且,如果这还不够,它们必须尽可能简短。当我们试图描述的事物有许多其他不同功能时,为其找到一个直观的名称可能真是一项挑战。
In these cases—when we're really struggling to find a good name for a function, component, class, module, or even an entire system—it can be helpful to follow Arlo Belshee's advice of thinking of naming as a four-step process:
在这些情况下——当我们真的很难为一个函数、组件、类、模块,甚至整个系统找到一个好名字时,遵循阿尔洛·贝尔希的建议,把命名看作一个四个步骤的过程是很有帮助的:
- Get to obvious nonsense: start with an honest name that describes your current understanding of the code, something like savesSomethingInTheDBIThinkAndSomeOtherStuff()
直面明显的无稽之谈:从一个诚实的名称开始,描述您对代码的当前理解,像是 savesSomethingInTheDBIThinkAndSomeOtherStuff() - Find what it does: describe all the things the code does, which will probably lead to a lengthy but still correct name.
找出它的功能:描述代码所做的所有事情,这可能会导致一个冗长但仍然正确的名称。 - Split it into chunks: move your code around so that you only have to deal with naming one thing at a time.
将其拆分为若干部分:调整您的代码,这样您就只需一次处理一个名称。 - Show context: reveal the intent of the code and find the domain abstraction.
显示上下文:揭示代码的意图并找到领域抽象。
Arlo wrote a seven-part series on Naming as a Process with a detailed explanation of these steps as well as some good examples. If you prefer a video version, Emily Bache has a recorded training on YouTube that is well worth a watch.
Arlo 撰写了一个关于 命名作为一个过程 的七部分系列,详细解释了这些步骤以及一些好的例子。如果你更喜欢视频版本,Emily Bache 在 YouTube 上有一个 录制的培训,非常值得观看。
Use Familiar Patterns 使用熟悉的模式
In the same way that we can pack information into our names, we can also pack a ton of information in the patterns that we use.
同样地,我们可以将信息封装到我们的名字中,我们也可以将大量的信息封装到我们使用的模式中。
One of the main benefits of using classic design patterns such as Singleton, Observer, Factory, and so on is that they give us a shared vocabulary to talk about complex behaviors.
使用经典设计模式,如单例模式、观察者模式、工厂模式等的主要好处之一是它们为我们提供了一个 共享词汇 来讨论复杂行为。
For instance, if you’re browsing through your codebase and you stumble upon a block of code that uses the Pub/Sub pattern, you’ll immediately understand a number of things about the code around it: there are going to be some publishers and subscribers somewhere, there might be some sort of message broker between them, and they’ll all talk to each other by sending events.
例如,如果您在浏览代码库时遇到一个使用了发布/订阅模式的代码块,您会立即理解关于周围代码的许多事情:那里会有一些发布者和订阅者,它们之间可能存在某种消息代理,并且它们会通过发送事件互相交流。
When a pattern is familiar to us, its behavior is intuitive—we don’t have to read a comment or README file to understand how it works, just like how Portal players don’t have to read a manual to understand that portals don’t take you to another dimension.
当一个模式对我们来说是熟悉的,它的行为是直观的—我们不需要阅读评论或 README 文件来理解它是如何工作的,就像《传送门》的玩家不需要阅读手册就能明白传送门不会带你到另一个维度一样。
The key word here is familiar, of course—after all, not all of us are familiar with the same things. With patterns, there's always a learning process that needs to happen when you encounter them for the first time, but once you incorporate them, you can apply them intuitively forever.
这里的关键字是熟悉,当然——毕竟,不是所有人都对相同的事物感到熟悉。对于模式,当你第一次遇到它们时,总是需要一个学习过程,但一旦你将它们融入自己的理解中,你就可以永远直观地应用它们。
Even if you never use classic OOP patterns (which is probably the case for those of us writing UI components all day), you can take advantage of this principle by sticking to commonly used patterns within your own codebases.
即使你从未使用经典的面向对象编程模式(对于我们这些整天编写 UI 组件的人来说,这可能是事实),你也可以通过遵循自己代码库中常用的模式来利用这一原则。
For example, I bet you can tell what the snippet below does by just glancing at it, even when it uses poor variable names like a and b and does weird things like subtracting the user's ages.
例如,我敢打赌你只需瞥一眼下面的代码片段就能知道它的作用,即使它使用了诸如a和b这样的糟糕变量名,并做一些奇怪的事情,比如减去用户的年龄。
You can tell that this code is sorting an array of user objects from youngest to oldest because you've seen this pattern a million times before—it became familiar.
您可以看出这段代码正在将用户对象数组从年轻到年长进行排序,因为您之前见过这种模式无数次——它变得熟悉了。
This also applies to patterns that might be specific to your team. For example, one pattern that used to be pretty common in my team's codebases was what we called “maybe operations”, which are functions that may or may not perform an action depending on some asynchronous condition (like checking user permissions via an API call.) So if we saw a function called maybeSendEmail() for instance, we immediately knew a few things about it:
这同样适用于可能特定于您团队的模式。例如,我团队的代码库中曾经相当常见的一种模式是我们称之为“可能操作”的东西,它是指根据某些异步条件(如通过 API 调用检查用户权限)可能会或可能不会执行某个操作的函数。因此,如果我们看到一个名为 maybeSendEmail() 的函数,我们立刻就知道了一些事情:
- The email may or may not be sent as a result of calling this function,
电子邮件可能会或可能不会因调用此函数而发送 - The function is asynchronous and returns a promise,
该函数是异步的,并返回一个 Promise - The promise resolves to either true or false depending on whether the action happened or not.
该承诺的结果是 true 或 false,这取决于行为是否发生。
It’s certainly not a perfect pattern, and we don't even use it that often these days, but it worked for our team as long as we applied it consistently.
这当然不是一个完美的模式,而且现在我们甚至不常用它,但只要我们坚持一致地应用,它对我们的团队是有效的。
And that's the good thing about this principle; the patterns don't even need to be good ones to take advantage of it. As long as they're familiar to us, sticking to them will certainly make our code much more intuitive.
这就是这个原则的好处;模式甚至不需要是好的模式就可以利用它。只要它们对我们来说是熟悉的,遵循它们肯定会使我们的代码更直观。
Represent Logic As Data 将逻辑表示为数据
In Grokking Simplicity, Eric Normand talks about how functional programmers always classify code into three categories: actions, calculations, and data:
在理解简约中,埃里克·诺曼德讨论了函数式程序员如何将代码始终分类为三类:操作、计算和数据:
- Actions depend on when they’re called or how many times they’re called. Think of a sendEmail() or getCurrentTime() function.
操作 取决于它们被调用的时间或被调用的次数。想想 sendEmail() 或 getCurrentTime() 函数。 - Calculations are computations from inputs to outputs, and they always behave the same no matter when or how many times they’re called. For instance, a sum() function.
计算是从输入到输出的计算,它们的行为总是相同,无论何时或调用多少次。例如,一个sum()函数。 - Data is… well, just data—facts about recorded events. Think of an array of numbers or an object with a user’s first and last name.
数据就是……好吧,数据——关于记录事件的事实。想象一下一个数字数组或一个包含用户名字和姓氏的对象。
This distinction is important because, in general, data is easier to deal with than calculations, and calculations are easier to deal with than actions. Data is more intuitive and easier to manipulate than logic, so, if we can find a way to represent something as data, we probably should.
这个区别很重要,因为一般来说,数据比计算更容易处理,而计算比行动更容易处理。数据比逻辑更直观,更容易操作,因此,如果我们能找到一种将某物表示为数据的方法,我们可能应该这样做。
A simple example of this principle in JavaScript is replacing a switch statement with an object literal. But we can also use it for more complex use cases, like when the data and rules of our code are deeply intertwined.
在 JavaScript 中,这一原理的一个简单例子是用对象字面量替换 switch 语句。但我们也可以将其用于更复杂的用例,例如当我们代码的数据和规则紧密交织时。
I give an example of this in my talk The Messy Middle when I talk about reducing cognitive load. You can watch it for the full explanation, but the short version is this: when the logic is too complex, instead of looking to replace it with data, we can try to encapsulate it and expose its rules via a simple data structure.
在我的演讲《复杂的中间过程》中,我举了一个例子,讲到减少认知负担。你可以观看演讲以获取完整的解释,但简而言之就是:当逻辑过于复杂时,我们可以尝试通过一个简单的数据结构来封装它,并通过该结构暴露其规则,而不是试图用数据去替代它。
Another way in which we can use data to make something more intuitive is when we use data structures that are more specific and communicate intent.
另一种我们可以利用数据使事物更直观的方式是使用更具体的数据结构,并传达意图。
For example, can you guess what the difference between these three arrays is?
例如,你能猜出这三个数组之间的区别是什么吗?
That was a bit of a trick question. There is no real difference here, they’re just three arrays.
那是个有点难度的问题。实际上没有太大区别,它们只是三个数组。
But what about now? 但现在怎么办?
Aha! Now the data structures reveal much more information. We know that the first one doesn’t have repeated values and that the other two are intended to behave as stacks and queues respectively, so the order of the elements is important and we’ll be doing a lot of pushing and popping but not grabbing elements from the middle.
哈哈!现在数据结构揭示了更多信息。我们知道第一个没有重复值,而另外两个分别设计为栈和队列,因此元素的顺序很重要,我们会进行大量的压入和弹出操作,而不是从中间抓取元素。
That was a mouthful, but you didn't need me to tell you any of that. All of that information was immediately available to you just because we chose more specific data structures. And just like choosing a good name, choosing a good way to represent our data can make our code not just more intuitive, but just generally more enjoyable to work with.
这真是一段复杂的话,但你并不需要我告诉你这些。所有这些信息之所以立即对你可用,仅仅因为我们选择了更具体的数据结构。就像选择一个好名字一样,选择一种好的方式来表示我们的数据可以让我们的代码不仅更直观,而且让工作起来更愉快。
Earlier I mentioned that intuitive programming can help you grasp the essence of a piece of code without having to read comments. This is not an anti-comment article, though. I think comments are great, and I think they’re sometimes even necessary.
我之前提到过,直观编程可以帮助你理解一段代码的本质,而不必阅读注释。不过,这并不是一篇反对注释的文章。我认为注释很好,有时甚至是必要的。
But while comments can turn a hard-to-understand concept into an easy-to-understand one, intuitive code goes one step further, making the understanding almost instantaneous.
但是,虽然注释可以将一个难以理解的概念变得易于理解,直观的代码更进一步,使理解几乎是瞬间的。
Why does this matter? You might recall that in an earlier issue, we defined software complexity as anything that makes a system hard to change or hard to understand. So by making our code more intuitive, we're not just saving the few seconds it takes to read a comment here and there. We're accomplishing something much deeper—we're attacking complexity directly at its core.
这为什么重要?你可能还记得在早期的一个问题中,我们将软件复杂性定义为任何使系统难以更改或难以理解的事物。因此,通过使我们的代码更直观,我们不仅仅是在节省阅读这里和那里评论所需的几秒钟时间。我们正在完成更深层次的事情——我们在核心上直接攻击复杂性。
ARCHITECTURE SNACKS 建筑零食
Links Worth Checking Out 值得查看的链接
- The topic of familiar patterns in today's essay was inspired by Cory Brown's talk Don't confuse Readability with Familiarity, which I had the pleasure of watching live at UtahJS last month. I very much enjoyed Corey's talk—it's a fun watch and a great introduction to the concept of readability in software design.
今天文章中关于熟悉模式的话题受到了 Cory Brown 的演讲不要将可读性与熟悉性混淆的启发,我上个月在 UtahJS 上现场观看了这场演讲,感到非常荣幸。我非常喜欢 Corey 的演讲——这是一场有趣的讲座,也是软件设计中可读性概念的极佳介绍。 - People on the internet have been fighting about web components if you can believe it! We're late to this party (as usual), but I didn't want to miss the opportunity to share Nolan Lawson's article on the topic because, also as usual, he has the more nuanced takes.
网络上的人们一直在争论网络组件,你信吗!我们来得晚了(和往常一样),但我不想错过分享Nolan Lawson 的文章的机会,因为,和往常一样,他的观点更为细致。 - ViteConf 2024 was last week, and if you missed any of its 43 talks, you can now binge-watch the entire replay on their website. It'll only take you 30 minutes if you watch it at 24x speed (or 12 hours at 1x)
ViteConf 2024 上周举行,如果你错过了其中的 43 场演讲,现在可以在他们的网站上疯狂观看全部重播。如果你以 24 倍速观看,只需 30 分钟(或以 1 倍速观看 12 小时)。 - If you only have one hour to spare, you can watch the comparatively short Deno 2.0 announcement, which features one of the funniest and best-produced intros you've ever seen for an open-source project.
如果你只有一个小时的空闲时间,可以观看相对较短的 Deno 2.0 发布,其中包含你所见过的开源项目中最搞笑和制作最好的介绍之一。 - From the creators of clientless clients and networkless networks, Vercel introduced serveless servers, a feature that lets a single serverless function handle multiple calls concurrently. So it's like a regular server, but still serverless. I hope that clears things up.
来自无客户端和无网络的创造者,Vercel 推出了 无服务器功能,这一功能允许单个无服务器函数同时处理多个调用。这就像一个常规服务器,但仍然是无服务器的。希望这能澄清问题。 - Blake Watson wrote HTML for People, a free book that can teach anyone to build websites with HTML and just a bit of CSS. You're all probably too experienced for this book, but it's a good resource to share with anyone who might be interested in getting started building websites with code.
布莱克·沃森写了《人们的 HTML》,这是一本免费的书,能够教会任何人用 HTML 和一点 CSS 构建网站。你们可能都对这本书太有经验了,但它是一个很好的资源,可以与那些可能对用代码开始构建网站感兴趣的人分享。 - Sam Selikoff wrote an interactive tutorial that teaches us how to build React components using the browser's native state management mechanism, the URL.
萨姆·塞利科夫编写了一个交互式教程,教我们如何使用浏览器的本地状态管理机制,即 URL,来构建 React 组件。 - This software architecture reading list just made my Amazon wishlist a lot bigger.
这个软件架构阅读清单让我在亚马逊的愿望清单上增加了很多内容。 - Amelia Wattenberger wrote about how user interfaces help us bridge the gap between the hard world of machines and the soft world of humans.
阿梅利亚·瓦滕伯格撰写了关于用户界面如何帮助我们 弥合差距,连接机器的硬世界和人类的软世界。 - It's hard to write code for computers, but it's even harder to write code for humans. That's Erik Bernhardsson on the challenges of writing libraries, frameworks, and programming languages.
为计算机编写代码很难,但为人类编写代码更难。这是埃里克·伯恩哈德森谈论编写库、框架和编程语言的挑战。
That’s all for today, friends! Thank you for making it all the way to the end. If you enjoyed the newsletter, it would mean the world to me if you’d share it with your friends and coworkers. (And if you didn't enjoy it, why not share it with an enemy?)
今天就到这里,朋友们! 感谢您坚持看到最后。如果您喜欢这个新闻简报,能与您的朋友和同事分享,对我来说将是莫大的支持。(如果您不喜欢,那为什么不与敌人分享呢?)
Did someone forward this to you? First of all, tell them how awesome they are, and then consider subscribing to the newsletter to get the next issue right in your inbox.
有人把这个转发给你了吗? 首先,告诉他们有多棒,然后考虑 订阅 新闻通讯,以便下期直接送到你的收件箱。
I read and reply to all of your comments. Feel free to reach out on Twitter, LinkedIn, or reply to this email directly with any feedback or questions.
我会阅读并回复你们的所有评论。 如果有任何反馈或问题,请随时通过 推特、领英,或者直接回复这封电子邮件与我联系。
Have a great week 👋
祝你有一个美好的一周 👋
– Maxi – 马克西