From 7ec1e1000c68337496898e6f97c7088892037fb7 Mon Sep 17 00:00:00 2001 From: yifili09 Date: Mon, 15 May 2017 00:08:09 -0500 Subject: [PATCH 01/59] Add translation for [boring-design-systems.md] --- TODO/boring-design-systems.md | 102 +++++++++++++++++----------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/TODO/boring-design-systems.md b/TODO/boring-design-systems.md index 400a789a657..9a979426612 100644 --- a/TODO/boring-design-systems.md +++ b/TODO/boring-design-systems.md @@ -1,106 +1,106 @@ > * 原文地址:[The Most Exciting Design Systems Are Boring](https://bigmedium.com/ideas/boring-design-systems.html) > * 原文作者:[JOSH CLARK](https://bigmedium.com/about/josh-clark.html) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) -> * 译者: +> * 译者:[Nicolas(Yifei) Li](https://github.com/yifili09) > * 校对者: -# The Most Exciting Design Systems Are Boring # +# 最激动人心的视觉系统其实是最枯燥乏味的! # -[![Click to enlarge](https://bigmedium.com/bm.pix/normcore-lego-center.orig-250.jpg)](https://bigmedium.com/bm.pix/normcore-lego-center.jpg) +[![点击放大](https://bigmedium.com/bm.pix/normcore-lego-center.orig-250.jpg)](https://bigmedium.com/bm.pix/normcore-lego-center.jpg) -Let us celebrate normcore design systems. +让我们欢迎中性舒适的视觉系统 -We’re building another enterprise design system, and we’re super-excited to make it boring. +我们正在构建另一个企业级视觉系统,并且我们极力把它变得枯燥。 -[Brad Frost](http://bradfrost.com), [Dan Mall](http://superfriend.ly), and I have just begun helping a big company express and communicate a design system to govern their many web products. The company has two decades of digital products and a slew of production teams. Over time, the UX, UI, and technical underpinnings of the company’s interfaces have drifted out of sync. The goal is to give production teams a consistent design language and pattern library to solve common visual, UX, and technical challenges. +[Brad Frost](http://bradfrost.com), [Dan Mall](http://superfriend.ly) 和我刚刚开始帮助一家大型企业快速设计一个视觉系统来管理支配他们众多的互联网产品。这家公司有着近 20 多年的数字产品的经验和大量的产品团队。长久以来,公司有关用户体验, 用户界面和用于巩固技术的接口已经疏于同步。我们的目标是赋予产品团队一个一致性的设计语言和模式库,它可以解决常见的视觉、用户体验和背后的技术问题。 -With projects like this, there’s often a strong temptation to throw out the old and start fresh with fancy new design components. “If we’re going to establish standards,” whispers the devil on your shoulder, “then let’s get rid of all the old stuff. Let’s blow it out with a new look, fancy interactions, and a shiny tech framework.” (Or my favorite: “Let’s make our own Material Design.”) +对于和这个项目类似的情况来说,它会让你忍不住要抛弃现有的内容,然后使用最新的设计组件全新开始。『 如果我们准备建立标准,』(此时) 恶魔会在你身边耳语道,『 那么,让我们彻底摆脱所有的旧牵绊。让我们用一个有着全新外观、时髦交互方式和闪耀的技术框架的新系统彻底结束它吧。』(或者我最爱说的:『 让我们自己来设计。』) -Design-system builders should resist the lure of the new. Don’t confuse design-system work with a rebrand or a tech-stack overhaul. The system’s design patterns should be familiar, even boring. +视觉系统工程师应该抵住新事物的诱惑。不要把系统设计的工作和重塑或者技术栈的检查相混淆。系统设计的模式应该很相似,甚至是枯燥乏味的。 -The job is not to invent, but to curate. +这份工作不是发明,而是策划。 -## Solved problems and settled science ## +## 已经解决的问题和已解决的科学 ## -Design systems are containers for institutional knowledge. They provide tested and proven solutions to design problems. When these solutions are held together by a consistent visual language and UX guidelines, they represent what good design looks like for the organization or platform. +设计系统包含了机构内的知识。他们提供了针对设计问题的设计方案,这些解决方案都已经过测试和验证。当这些解决方案被统一的视觉语言和用户体验指导方案组合的时候,它们就代表了组织机构或者平台应该有的良好设计。 -For projects like this one, we work with our client partners to identify the design problems their teams confront *over and over again*. And then we identify and extract the best solutions to those problems. +对于和这个类似的项目来说,我们**一次一次地**和客户端合作伙伴一起协作来确定他们团队的设计问题直到他们满意。之后,确定和提出对这些问题的最优解决方案。 -The more common the problem, the better. Design systems should prioritize the mundane. +问题越常见越好。设计系统应该优先考虑常见的东西。 -This time around, we’re putting top priority on data entry, form validation, and messaging. Those aren’t exactly glamorous design experiences, but they’re where the company does its heavy lifting and where customers spend the most time. This is where boring gets exciting: how do you scale good, consistent, everyday design? +此时此刻,我们优先考虑数据入口,表单的验证和信息传递。这些并不是完美的设计体验,但是它们却是每个公司业务的重中之重,也是客户们花费时间最多的地方。这也是为什么枯燥乏味的事情却令人兴奋的地方:你如何对每天的设计更好的扩展且保证一致性? -[![Click to enlarge](https://bigmedium.com/bm.pix/lego-office.orig-250.jpg)](https://bigmedium.com/bm.pix/lego-office.jpg) +[![点击放大](https://bigmedium.com/bm.pix/lego-office.orig-250.jpg)](https://bigmedium.com/bm.pix/lego-office.jpg) -Where’s the pain? We’re interviewing designers and developers to identify repetitive tasks. +痛点在哪里? 我们正尝试从设计师和开发人员处定位这些重复的任务。 -The work starts with lots of user research, just like any other product. We’re talking to the company’s developers, designers, and product managers to identify repetitive tasks and unearth reinvented wheels. We’re doing a deep inventory of the company’s web apps to find the best-practice solutions to these problems. We’re doing a similar inventory of the company’s inconsistent visual styles to begin to reinforce a common visual language. +这个工作从很多用户调研开始,和其他产品一样。我们直面公司的程序员,设计师和产品经理们来定位重复的任务和发掘重复制造的轮子。我们进行了对该公司网络产品的深度研究并尝试找到应对这些问题的最优解决方案。我们也对该公司不一致的视觉风格列出清单,并开始重新强化一个通用的视觉语言。 -At the end of the process, we’ll have a cookbook of design recipes for the company’s most *nutritious* business solutions. There will be lots more spinach here than design candy. That’s a good thing. +在这个流程的最后,我们将会为该公司最**有利**的商业解决方案制作一个设计的参考。其中会包含很多干货而非那些看似有用的知识。它会很有用。 -## “Boring” design systems enable inventive designs ## +## 创新性的设计来自于那些 『 枯燥 』 的设计 ## -When the design system is boring, it frees designers and developers to do the new stuff, to solve new problems. **The design system carries the burden of the boring, so that designers and developers don’t have to.** +当设计系统很枯燥乏味的时候,它能释放设计师和开发人员做更多新的东西,去解决新的问题。**设计系统解决了乏味的事情,所以设计师和开发人员就不用操心了。** -Instead of designing a card pattern for the 15th time, it’s already done. Product teams can instead put their time and energy into creating new experiences, new algorithms, new business logic. +不必第 15 次的重复设计卡片的样式,因为它已经被实现了。产品团队能够把时间和精力放到如何创建新的体验,新的算法和新的商业逻辑上。 -[![Click to enlarge](https://bigmedium.com/bm.pix/scientist.orig-250.jpg)](https://bigmedium.com/bm.pix/scientist.jpg) +[![点击放大](https://bigmedium.com/bm.pix/scientist.orig-250.jpg)](https://bigmedium.com/bm.pix/scientist.jpg) -When the design system absorbs mundane problems, designers and developers are freed to pursue new science and higher-order problems. +当设计系统消减了了那些枯燥问题的时候,那些设计师和开发人员们才能放手去从事新技术和高阶的问题。 -This also means that a wonderfully boring design system does more than provide collections of buttons, color swatches, and code samples. Components are commodities. The magic happens in the guidelines that come with them. A great design system suggests which design patterns to use (along with the why and how) for specific circumstances. This elevates a collection of UI components into full-blown design patterns. Designers have readymade solutions for the company’s everyday problems. +这也意味着一个不错的设计系统比能提供很多按钮、颜色板和样例代码做的更多。这些组件都是常见的。奇妙的事情是伴随着他们出现在指导手册中。一个伟大的设计系统会对某些特定情况建议使用哪个设计模式(连同为什么和怎么做一起)。这将仅聚合用户接口组件推向了一个完整的设计模式。设计师对每天公司日常的问题都有了现成的解决方案。 -“Every moment spent saying, ‘should this be a popup or lightbox?’ is waste,” one design manager told us during our research for this latest project. +『 任何时候考虑,比如 「 这里应该展示一个弹出窗口还是提示框?」这类问题都是浪费时间。』这是一位设计经理在这个最近项目上的调研过程中告诉我们的。 -## Faster, faster ## +## 更快,更快 ## -All of this leads to big speed increases. Big Medium helped a major retailer get their design systems group up and running, and the results have been, well, crazy. “We have a 10x increase in speed compared to how we worked before,” the group’s manager reports. “That is, we can do higher quality work 10x faster with a fourth the number of people.” +所有这些都能带来速度上大大的提升。[Big Medium 公司](https://bigmedium.com) 帮助一个重要的零售商搭建并且运行起了他们的设计系统,并且结果出人意料。『 相比之前的工作,我们的速度提升了 10 倍,』团队经理如实汇报道,『 也就是说,我们能仅用四分之一的员工完成 10 倍的高质量的工作。』 -[![Click to enlarge](https://bigmedium.com/bm.pix/before-after-design-system.orig-250.png)](https://bigmedium.com/bm.pix/before-after-design-system.png) +[![点击放大](https://bigmedium.com/bm.pix/before-after-design-system.orig-250.png)](https://bigmedium.com/bm.pix/before-after-design-system.png) -Worth re-emphasizing: this speed and consistency does *not* have to mean cookie-cutter products. Instead, getting the boring stuff out of the way in the design system means product teams have running room to invent the new. In fact, the products are the laboratory for the system’s future patterns. +值得强调重申的是: 这样的速度和一致性并**不**意味着是千篇一律的产品。相反,在设计系统中解决枯燥乏味的工作意味着产品团队有更多创造发明的空间。事实上,这些产品就是系统未来模式的孵化器。 -## Invention happens in the products ## +## 在产品中发明创造 ## -Designers and developers sometimes approach design systems or pattern libraries warily. While most appreciate the need for consistency, they worry that a design system will be too prescriptive and constraining, that there will be no room for new science. “A design system is like public transportation: it’s a good idea, for other people,” one designer told us in describing these mixed feelings. +有些设计师和开发人员常常小心谨慎地对待设计系统或者模式库。尽管大部分人重视一致性的需求,但他们担心一个设计系统会太过教条和有限制性,这会阻碍创新。『 设计系统就好像公共交通工具: 对其他人来说,它是一个好主意。』一位百感交集的设计师如是说道。 -Done right, though, design systems actually encourage and reward innovation by product teams. Designers and developers can build upon, improve, and occasionally replace the design system’s recommendations. +产品团队应做正确的事,尽管如此,设计系统其实是鼓励创新的。设计师和开发人员能依赖它创建、提高,也可以偶尔替换设计系统中的建议。 -It should be a virtuous cycle. New ideas should be tried and tested in products. When good ideas prove out, they should be plucked out and included in the design system, so that future products can benefit. The latest solutions awesomely become the new boring. +它应该是一个良性循环。新的理念应该在新的产品上进行尝试和测试验证。当好想法被证明是可行的时候,它们应该被采纳并且加入进设计系统中,以便未来的产品能获益。这些最新的解决方案都会赫然变成新常态。 -**Design systems should always extract their patterns from working code.** In our design system work, we do this in two ways: +**设计系统应该始终从当前的代码中提取它们的模式。** 在我们的设计系统中,我们采用以下两个方式: -1. Extract proven patterns from legacy applications. -2. Build pilot apps alongside the design system that prove out new and necessary design patterns. +1. 从现存的应用程序中提取被证明可行的模式。 +2. 和设计程序一起创建试点应用程序,它能证明新的和必要的设计模式是可行的。 -This is likewise how sturdy code frameworks emerge in the development world. Ruby on Rails was extracted from the first version of Basecamp, only *after* its core concepts had been proven in production. Design patterns and UX guidelines emerge from production projects, too. +这就好像健壮的框架代码是如何从持续发展的项目中显现的。Ruby on Rails 是从第一代的 Basecamp 中提取出的,只有核心概念**经过**产品的证明。设计模式和用户体验指导手册也同样来自于那些量产的项目。 -Design systems, like code frameworks, are no place for untested ideas. Extract proven ideas from production interfaces. +设计系统,如核心框架,容不下未经测试的想法。请从量产接口中提取那些被证明过可行的想法。 -## This is the canon ## +## 这是规则 ## -[Dan](http://superfriend.ly) likes to use Star Wars (of course) as a metaphor for this process. The Star Wars universe is thick with unofficial tales—the novels, comic books, and fan fiction that build upon the films’ original backstory. That official story is [the Star Wars canon](https://en.wikipedia.org/wiki/Star_Wars_canon), which is carefully managed and groomed by Lucasfilm. The rest is known as “the expanded universe,” and all of its characters, species, galaxies, alliances, and so on are considered off script—until they’re not. +[Dan](http://superfriend.ly) 喜欢使用星球大战 (当然) 作为一个对这个流程的暗喻。星球大战的世界充满了各种非官方的传奇故事、动漫图书以及爱好者们的科幻作品,当然它们都是基于电影原作的背景故事的。这个官方的故事是 [星球大战正典故事](https://en.wikipedia.org/wiki/Star_Wars_canon),它是由 Lucas 电影精心编排制作和管理的。其余的是我们所知的 ”衍生的宇宙“,所有的这些角色、种族、星系、联盟和其他的那些都不被考虑在剧本中-除非他们进入正典。 -Every so often, the minders of the Star Wars canon accept elements of the “expanded universe” into the official story. They, too, become canon. +常常,星球大战正典维护者们会采纳 ”衍生的宇宙“ 中的元素纳入官方故事。它们也就变成了正典中的一部分。 -[![Click to enlarge](https://bigmedium.com/bm.pix/star-wars-virtuous-cycle.orig-250.jpg)](https://bigmedium.com/bm.pix/star-wars-virtuous-cycle.jpg) +[![点击放大](https://bigmedium.com/bm.pix/star-wars-virtuous-cycle.orig-250.jpg)](https://bigmedium.com/bm.pix/star-wars-virtuous-cycle.jpg) -Everyone’s happy when you build a virtuous cycle between the design system (the canon) and applications (the extended universe). - -In Dan’s metaphor, your company’s design system is your design canon. Others go out and build apps and new design patterns on top of it (the “expanded universe”). Some of those design patterns will be so useful and so in tune with good design at your company that they too will become canon. +当你能在设计系统(比如 正典故事)和应用程序(衍生的宇宙)之间创建一个良性的循环系统,每个人都会很乐意看到这样的结果。 + +在 Dan 的暗喻中,你公司的设计系统就是你的设计正典。其他人公布、创建的应用程序和那些新的设计模式都基于它(比如,『 衍生的宇宙 』)。其中的有些设计模式将会是很有用出的,并且与你公司好的设计步调一致,它们也会变成正典。 -That’s the virtuous cycle between design system and product. If you’re the manager of the design system, you get to be the George Lucas of this story. Only you’ll need a lot more humility. +这就是设计系统和量产产品之间的良性循环。如果你是一位设计系统的经理,你将会成为这个故事中的 `George Lucas`。当然你将需要更加谦虚。 -## The humility of the work ## +## 谦卑的工作 ## -Successful design systems are in constant service to many audiences—designers, developers, product managers, and of course the end user. +成功的设计系统不断为许多设计师、开发人员、产品经理、当然也少不了终端用户服务。 -That spirit of service means that keepers of design systems have to keep ego at bay. A design system is not a place to push new frontiers but to gather settled solutions. +这种服务精神意味着设计系统的维护者们切勿浮想联翩,需要握紧手中的线。一个设计系统不是崇尚前沿科技的地方,而是汇集成功解决方案的地方。 -Crafting a design system is about clearing the way for others to invent and imagine. And that means boring has never been so exciting. +打造一个设计系统是为其他人的创新和猜想扫清道路。那就意味着枯燥乏味的内容永远不会变得令人兴奋。 -*Is your organization wrestling with inconsistent interfaces and duplicative design work? Big Medium helps big companies scale great design through design systems. [Get in touch](https://bigmedium.com/hire/) for a workshop, executive session, or design engagement.* +**你的机构是否在全力对付不一致性的接口和重复的设计工作? Big Medium 帮助大型企业通过设计系统规划大型设计。需要工作讨论会、执行会议或者让我们参与设计,[请联系我们](https://bigmedium/com/hire)** --- From 0809220281e5fe1bef445b80577e3abbd24f1d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E6=98=8E?= Date: Tue, 16 May 2017 21:10:13 +0800 Subject: [PATCH 02/59] over --- .../testing-mvp-using-espresso-and-mockito.md | 138 +++++++++--------- 1 file changed, 70 insertions(+), 68 deletions(-) diff --git a/TODO/testing-mvp-using-espresso-and-mockito.md b/TODO/testing-mvp-using-espresso-and-mockito.md index b31ff8af249..c02d103924d 100644 --- a/TODO/testing-mvp-using-espresso-and-mockito.md +++ b/TODO/testing-mvp-using-espresso-and-mockito.md @@ -4,41 +4,41 @@ > * 译者: > * 校对者: -# Testing MVP using Espresso and Mockito # +# 使用 Espresso 和 Mockito 测试 MVP # -As software developers, we try our best to do what is right and make sure that we are not incompetent, and try to have others and our employers trust in the code we write. We all try to follow best practices and apply good architecture patterns, but sometimes many of us find it difficult to actually test what we code. +作为软件开发者,我们尽最大努力做正确的事情确保我们并非无能,并且让其他同事以及领导信任我们所写的代码。我们遵守最好的变成习惯、使用好的架构模型,但是有时发现要确切的测试我们所写的代码很难。 -Personally, I have seen a few open-source projects where the developers are great at building awesome products—and can build any application you can think of—but for some reason lack at writing the proper tests, if any at all. +就个人而言,我发现一些开源项目的开发者非常善于打造令人惊叹的产品(可以打造任何你可以想象的应用),但是由于某些原因缺乏编写正确测试的能力,如果有的话。 -This here is another simplified tutorial on how to unit test the “oh so amazing” MVP architecture pattern that many of us try to follow. +本文是关于如何对广泛应用的 MVP 架构模型进行单元测试的简单教程。 -Before I continue, I want to mention that I assume you are familiar with the MVP pattern, and have used it before. I will not go over the MVP pattern at all, and I will not explain how it works. With that in mind, I’d like to mention ahead of time that I am using one of my favorite libraries for MVPs created by a guy named [Hannes Dorfman](http://hannesdorfmann.com/) called [Mosby](https://github.com/sockeqwe/mosby). For simplicity’s sake, I am also using the view binding library called [ButterKnife](http://jakewharton.github.io/butterknife/). +在开始前需要解释一下,本文假设你熟悉 MVP 模型并且之前使用过。本文不会介绍 MVP 模型,也不会介绍它的工作原理。同样,需要提一下的是我使用了一个我喜欢的 MVP 库 —— 由 [Hannes Dorfman](http://hannesdorfmann.com/) 编写的 [Mosby](https://github.com/sockeqwe/mosby)。为了方便起见,我使用了 view 绑定库 [ButterKnife](http://jakewharton.github.io/butterknife/)。 -So what is this application we will be looking at? +那么这个应用究竟长什么样呢? -It is a very simple Android app, and it does one thing and one thing only: It hides and displays a TextView with the click of a button. That’s it. +这是一个非常简单的 Android 应用,它只做一件事:当点击按钮时隐藏或者显示一个 TextView。 -Here’s how the app looks initially: +这是应用起初的样子: ![Initial](https://i1.wp.com/www.andevcon.com/hubfs/EVENTS_ASSETS/ANDEVCON/Images/Article_Images/MVP%20Mockito/IVvsdac.png) -Here’s how it looks when the button is clicked: +这是按钮点击后的样子: ![724E8fE.png](https://i2.wp.com/www.andevcon.com/hubfs/EVENTS_ASSETS/ANDEVCON/Images/Article_Images/MVP%20Mockito/724E8fE.png) -For the sake of this article, let’s imagine that this is a multi-million-dollar product, and that the way it is now is the way it should be for a very long time. And if it were to ever change, we should be notified immediately. +为了本文价值,我们假设这是一个价值数百万的产品,并且它现在的样子将会持续很长时间。如果一旦发生变化,我们需要立刻知晓。 -So we have three things in this app: a blue toolbar with the app name, a text view that displays “Hello World,” and a button that hides/shows the TextView. +应用中有三部分内容:一个有应用名的蓝色工具栏,一个显示 “Hello World” 的 TextView,以及一个控制 TextView 显隐的按钮。 -Before I start, I would like to mention that you can find all of the code for this article on [my GitHub](https://github.com/josias1991/TestingMVP); if you do not want to read any of my rambling, please feel free to skip the rest of the post and go straight to the code. Comments will be added for clarity. +开始前需要做下说明,本文的所有代码都可以在[我的 GitHub ](https://github.com/josias1991/TestingMVP)找到;如果你不想阅读后文,可以放心去直接阅读源码。源码中的注释十分明确。 -Now, lets get testing! +我们开始吧! -## **The Espresso tests** ## +## **Espresso 测试** ## -The first thing we want to test is our awesome ToolBar design. I mean, it is a million-dollar app after all; we need to make sure it stays that way! +我们首先对炫酷的 ToolBar 进行测试。毕竟是一个价值数百万的应用,我们需要确保它的正确性。 -So first, here’s the complete code used to test the TooBar design. If you have no idea what is going on here, don’t worry: We will walk through it together. +如下是测试 ToolBar 的完整代码。如果你看不懂这到底是什么鬼,也没关系,后面我们一起过一下。 ``` @RunWith (AndroidJUnit4.class) @@ -76,47 +76,49 @@ public class MainActivityTest { } ``` -First things first: We need to tell JUnit what sort of test we are running. This is what the first line does (@RunWith (AndroidJUnit4.class)). It says. “Hey, listen, I want to run an Android test that uses JUnit4 on an actual connected device.” +首先,我们需要告诉 JUnit 我们进行测试的类型。具体内容对应第一行内容 (@RunWith (AndroidJUnit4.class))。它这样声明,“嘿,听着,我将在真机上使用 JUnit4 进行 Android 测试”。 -So what exactly is an Android test? An Android test is a test that runs on the device instead of locally on the [Java Virtual Machine (JVM)](https://en.wikipedia.org/wiki/Java_virtual_machine) on your computer. This means that a device needs to be connected to your computer in order to run the test. This gives the test code access to functional Android framework APIs. +那么 Android 测试到底是什么呢?Android 测试是在 Android 设备上而非电脑上的 [Java 虚拟机 (JVM)](https://en.wikipedia.org/wiki/Java_virtual_machine) 的测试。这就意味着 Android 设备需要连接到电脑以便运行测试。这就使得测试可以访问 Android 框架功能性 APIs。 -These tests go in the androidTest directory. +测试代码存放位置为 androidTest 目录。 ![android_test_directory](https://i0.wp.com/www.andevcon.com/hs-fs/hubfs/EVENTS_ASSETS/ANDEVCON/Images/Article_Images/MVP%20Mockito/gcpEaEX.png?w=442) -Next lets take a look at this thing called an “ActivityTestRule”. Since the android documentation explains it really well, here it is: +下面我们看一下 “ActivityTestRule”,如下 Android 文档做出了详细的介绍: -*“This rule provides functional testing of a single activity. The activity under test will be launched before each test annotated with [Test](http://junit.org/javadoc/latest/org/junit/Test.html) and before methods annotated with [Before](http://junit.sourceforge.net/javadoc/org/junit/Before.html). It will be terminated after the test is completed and methods annotated with [After](http://junit.sourceforge.net/javadoc/org/junit/After.html) are finished. During the duration of the test you will be able to manipulate your Activity directly.”* +**“本规则针对单个 Activity 的功能性测试。测试的 Activity 会在 [Test](http://junit.org/javadoc/latest/org/junit/Test.html) 注释的测试以及 [Before](http://junit.sourceforge.net/javadoc/org/junit/Before.html) 注释的方法运行之前启动。会在测试完成以及 [After](http://junit.sourceforge.net/javadoc/org/junit/After.html) 注释的方法结束后停止。在测试期间可以直接对 Activity 进行操作。”** -This basically says, “This is the activity I want to run my test.” +本质上是说,“这是我要测试的 Activity”。 -Lets get in to the testToolbarDesign() method and see what the hell is going on. +下面我们具体看下 testToolBarDesign() 方法具体做了什么。 -### **Testing the toolbar** ### +### **测试 toolbar** ### -``` onView(withId(R.id.toolbar)).check(matches(isDisplayed())); +``` +onView(withId(R.id.toolbar)).check(matches(isDisplayed())); ``` -What this test does is find a view with the ID that matches “R.id.toolbar,” then checks to make sure that this view is visible/displayed. If this line were to fail, the test would end right there and wouldn’t even bother with the rest. +这段测试代码是找到 ID 为 “R.id.toolbar” 的 view,然后检查它的可见性。如果本行代码执行失败,测试会立刻结束并不会进行其余的测试。 -``` onView(withText(R.string.app_name)).check(matches(withParent(withId(R.id.toolbar)))); +``` +onView(withText(R.string.app_name)).check(matches(withParent(withId(R.id.toolbar)))); ``` -This one says “Hey, lets see if there is some text that equals ‘R.string.app_name’ and has a parent whose id is R.id.toolbar.” +这行是说,“嘿,让我们看看是 R.id.toolbar 的孩子是否文本和 ‘R.string.app_name’ 相同的”。 -The last line in this test is a bit more involved. So basically what it is trying to do is to make sure that the background of the toolbar is equal to the app’s primary color. +最后一行的测试更有趣一些。它是要确认 toolbar 的背景色是否和应用的首要颜色一致。 ``` onView(withId(R.id.toolbar)).check(matches(withToolbarBackGroundColor())); ``` -So by default, Espresso does not provide a straightforward way to do this, so we need to create what is called a [Matcher](https://developer.android.com/reference/android/support/test/espresso/matcher/package-summary.html). A Matcher is exactly what we have been using previously to match some view property to another. In this case, we want to match the primary color to the toolbars background. +Espresso 默认是不支持简单直接的方式进行类似测试,因此我们需要创建 [Matcher](https://developer.android.com/reference/android/support/test/espresso/matcher/package-summary.html)。Matcher 确切的说是我们前面使用的判断 view 属性是否与预期一致的工具。这里,我们需要匹配首要颜色是否与 toolbar 背景一致。 -What we do is we create a [Matcher](https://developer.android.com/reference/android/support/test/espresso/matcher/BoundedMatcher.html) and override the matchesSafely() method. The code inside this method is pretty easy to understand. First we get the toolbar’s background, then we compare it to the app’s primary color. If it is equal, it returns true; false otherwise. +我们需要创建一个 [Matcher](https://developer.android.com/reference/android/support/test/espresso/matcher/BoundedMatcher.html) 并覆盖 matchesSafely() 方法。该方法里面的代码十分易懂。首先我们获取 toolbar 背景色,然后将至与应用首要颜色对比。如果相等,返回 true 否则返回 false。 -### **Test TextView hides/shows properly** ### +### **测试 TextView 的隐藏/显示** ### -Ok, before I show any code, I just want to mention that this code is a bit more verbose, but pretty straightforward to read. I have added some comments to show what exactly is going on. +在讲代码之前,我需要说下代码有点长,但是十分易读。我对代码内容作了详细注释。 ``` @@ -165,13 +167,13 @@ public class MainActivityTest { } ``` -The gist of this code is that it is making sure that when the app opens, the TextView with ID “R.id.tv_to_show_hide” is displayed, and the text displayed on the TextView says “Hello World!” +这段代码的主旨是确认应用打开时,ID 为 “R.id.tv_to_show_hide” 的 TextView 处于显示状态,并且其显示内容为 “Hello World!” -Then we check that the button is also displayed properly, and that the text on the button (by default) is set to “Hide”. +然后检查按钮也是显示状态,并且其文案显示为 “Hide”。 -Next we click on the button. A click on a button is very straightforward, and it is very easy to read how it is done. This time instead of calling “.check” after we find a view by ID, we call .perform(), and inside the perform() method, we pass in click(). The perform() method says “Please do the following action,” and then “performs” whatever action is passed in. In our case it is a click() action. +接着点击按钮。点击按钮十分简单,如何实现的也十分易懂。这里我们对找到相应 ID 的 view 执行 .perform() (而非 “.check”),并且在其内执行 click() 方法。perform() 方法实际是执行传入的操作。这里对应是 click() 操作。 -Since the “Hide” button was clicked, we need to make sure the TextView is actually hidden now. We do this by adding a “not()” in front of the isDisplayed() method we used previously, and the button text has been changed to “Show”. This is the same as doing “!=” in plain ol’ java. +因为点击了 “Hide” 按钮,我们需要验证 TextView 是否真的隐藏了。具体做法是在 diDisplayed() 方法前置一个 “not()”,并且按钮文案变为 “Show”。其实这就和 java 中的 “!=” 操作符一样。 ``` @@ -198,11 +200,11 @@ public class MainActivityTest { } ``` -The code after these lines is the reverse of what we did before. We click the button again, make sure the TextView is visible again, and make sure that the button text has changed to successfully to match the situation. +后面的代码是前面代码的反转。再次点击按钮,验证 TextView 重新显示,并且按钮文案符合当前状态。 -And that’s it! +就这些。 -Here is the full UI test code: +如下是全部的 UI 测试代码: ``` @@ -273,17 +275,17 @@ public class MainActivityTest { } ``` -## **The Unit Tests** ## +## **单元测试** ## -The great thing about unit tests—as opposed to Android tests—is that they run locally on your machine on the JVM. No need to have a device attached, and the tests run so much faster. The downside is that they do not have access to functional Android framework APIs. Overall, when testing anything other then UI you should try your best to write unit tests instead of Android/Instrumentation tests. The faster the tests, the better. +单元测试最大特点是在本机的 JVM 环境上运行(与 Android 测试不同)。无需连接设备,测试跑的也更快。缺点就是无法访问 Android 框架 API。总之进行 UI 之外的测试时,尽量使用单元测试而非 Android/Instrumentation 测试。测试内容运行的越快越好。 -Lets start with the directory of the unit tests. The unit tests go in a different location then the Android tests did. +下面我们看下单元测试的目录。单元测试的位置与 Android 测试不同。 ![different_location](https://i1.wp.com/www.andevcon.com/hubfs/EVENTS_ASSETS/ANDEVCON/Images/Article_Images/MVP%20Mockito/mYBjN1x.png) -Before moving on lets take a look at our presenter and what we are going to consider our model for this tutorial. +开始前我们先看下 presenter 以及关于 model 需要考虑的问题。 -### *Lets start with the presenter* ### +### **首先看下 presenter** ### ``` @@ -312,11 +314,11 @@ public class MainPresenterImpl extends MvpBasePresenter implements MainPresenter } ``` -Simple enough. We have two methods: One checks to see if the view is visible. If it is, hide it, otherwise show it. Once this is done, we call into our view and say “Hey, change the button text to either ‘Hide’ or ‘Show’.” +很简单。两个方法:一个检查 view 是否可见。如果可见就隐藏它,反之显示。之后将按钮的文案改为 “Hide” 或 “Show”。 -Our reverseViewVisibility() method calls into what we call (for this tutorial at least) our “model” to set the proper visibility on the view passed in. +reverseViewVisibility() 方法调用 “model” 对传入的 view 进行相对的可见性设置。 -### *Lets take a look at our model.* ### +### **下面看下 model** ### ``` public final class Utils { @@ -336,17 +338,17 @@ public final class Utils { } ``` -There are two methods: showView(View) and hideView(View). What these methods do is very straightforward and self-explanatory. We check if the view is null; if not, we either hide it or show it. +两个方法:showView(View) 和 hideView(View)。具体功能十分直观。检查 view 是否为 null,不为 null 则对其进行显隐设置。 -Great, now that we are familiar with both our presenter and our “model,” lets go ahead and test them. After all, this is a multi-million-dollar product, and we cannot have anything go wrong. +现在我们对 presenter 和 model 都有所了解了,下面我们开始测试。毕竟这是一个数百万的产品,我们不能有任何错误。 -Let start start testing our presenter first. When it comes to a presenter—ANY presenter—we need to make sure that the view is attached to the presenter. Note: We are NOT testing the view. We just need to make sure that the view is attached so that we can verify the proper view methods are being made in the right time. Keep this in mind as this is very important. +我们首先测试 presenter。当涉及 presenter (任何 presenter)我们需要确保 view 已与之关联。注意:我们并不测试 view。我们只需要确保 view 的绑定以便确认是否在正确的时间调用了正确的 view 方法。记住,这很重要。 -We will be using Mockito to run our tests, so just like we did with our unit tests, we ned to tell Android, “Hey, we want to run these tests with the MockitoJUnitRunner.” To do so we add the @RunWith (MockitoJUnitRunner.class) annotation on top of our test class. +这里我们使用 Mockito 进行测试,就像单元测试那样,我们需要告诉 Android,“嘿,我们需要使用 MockitoJUnitRunner 进行测试。”实际操作时在测试类的顶部添加 @RunWith (MockitoJUnitRunner.class) 即可。 -So we know two things right from the start: We need a mocked View because our presenter uses a View Object to hide or show it, and we also need our presenter. +从前面可知我们需要两个东西:一是模拟一个 View (因为 presenter 使用了 View 对象,对其进行显隐控制),另外一个是 presenter。 -This is how we mock using Mockito +下面是使用 Mockito 的模拟 ``` @RunWith (MockitoJUnitRunner.class) @@ -364,7 +366,7 @@ public class MainPresenterImplTest { } ``` -The first actual test we want to write is called “testReverseViewVisibilityFromVisibleToGone”. As the name says, we are going to make sure that the presenter sets the proper visibility when the view passed in to the reverseViewVisibility() method is visible. +我们要写的第一个测试是 “testReverseViewVisibilityFromVisibleToGone”。顾名思义,我们验证的是当 view 可见时,调用 presenter 的 reverseViewVisibility() 方法结果正确。 ``` @Test @@ -379,11 +381,11 @@ The first actual test we want to write is called “testReverseViewVisibilityFro } ``` -Let’s step through this together. What is actually going on here? Well, when the isShown() method is called on the view that is passed in to the presenter, we want to say yes, the view is shown, because we are testing from visible to gone, so we need to start at visible. Then, we call the presenters reverseViewVisibility() method by passing in the mocked view. Now we need to verify that the mocked views .setVisibility() was called at least once, and it was set to View.GONE. Afterwards, we need to verify that the presenters view setButtonText() method was called. Not that hard right? +我们一起看下,这里具体做了什么?由于我们要测试的是 view 从可见到不可见的操作,我们需要 view 一开始是可见的,因此我们希望一开始调用 view 的 isShown() 方法返回是 true。接着,以模拟的 view 作为入参调用 presenter 的 reverseViewVisibility() 方法。现在我们需要确认 view 最近被调用的方法是 setVisibility(),并且设置为 GONE。然后,我们需要确认 presenter view 的 setButtonText() 方法是否调用。并不难吧? -Alright, lets do the opposite. Before moving on and looking at the next piece of code, take some time to figure it out yourself. How would we test going from hidden to visible? Think about what you already know. +嗯,接着我们进行相反的测试。在继续阅读下面的代码之前,试着自己想一下怎么做。如何测试从隐藏到显示的情况?根据上面已知的信息思考一下。 -Here’s the code: +代码实现如下: ``` @Test @@ -399,7 +401,7 @@ Here’s the code: ``` -Lets move on to testing our “Model.” As before, we start by adding the @RunWith (MockitoJUnitRunner.class) annotation on top of our class. +接着测试 “Model”。和前面一样,我们首先在类顶部添加注解 @RunWith (MockitoJUnitRunner.class) 。 ``` @RunWith(MockitoJUnitRunner.class) @@ -412,9 +414,9 @@ publicclassUtilsTest{ ``` -As mentioned previously, our Utils class first checks if the view is not null. If not, a visibility is applied, otherwise nothing is done. +如前面所说,Utils 类首先检查 view 是否为 null。如果不为 null 将执行显隐操作,反之什么都不会做。 -The tests for this class are very easy, so I am just going to put the whole thing here without stepping through it line by line. +Utils 类的测试十分简单,因此我不再逐行解释,大家直接看代码即可。 ``` @RunWith (MockitoJUnitRunner.class) @@ -451,9 +453,9 @@ public class UtilsTest { ``` -I’ll explain what is going on in the testShowViewWithNullView() and testHideViewWithNullView() methods. Why would we want to test something like this? Well if we think about it for a second, we do not want our method calls to crash the entire application because the view was null. +我解释下 testShowViewWithNullView() 和 testHideViewWithNullView() 方法的作用。为什么要进行这些测试?试想下,我们不希望因为 view 为 null 时调用方法造成整个应用的崩溃。 -Lets take a look at the Utils showView() method. If we remove the null check, the app will throw a NullPointerException and would crash. +我们看下 Utils 的 showView() 方法。如果不做 null 检查,当 view 为 null 时应用会抛出 NullPointerException 并崩溃。 ``` public final class Utils { @@ -470,7 +472,7 @@ public final class Utils { } ``` -In other scenarios we may want the app to actually throw an exception. How would we test an exception? Its actually very easy: You would just pass in an expected param to the @Test annotation like so: +另外一些情况下,我们需要应用抛出一个异常。我们如何测试一个异常?十分简单:只需要对 @Test 注解传递一个 expected 参数进行指定: ``` @RunWith (MockitoJUnitRunner.class) @@ -485,13 +487,13 @@ public class UtilsTest { } ``` -If no exception is thrown, the test would fail. +如果没有异常抛出,该测试会失败。 -Again, you can find all of the code on [GitHub](https://github.com/josias1991/TestingMVP). +再次提示,你可以在 [GitHub](https://github.com/josias1991/TestingMVP) 获取全部代码。 -Now that we are at the end of the post, I want to mention something to you guys: Testing will not always be as straightforward as it was in this example, but it doesn’t mean it can’t, and shouldn’t be done. As software developers, we need to make sure that our applications work properly and as expected. We need to make sure others trust our code. I have been doing this for many years and you couldn’t imagine how many times tests have saved me, even for the simplest thing such as changing a view ID. +本文接近尾声,需要提醒大家的是:测试并不总是像本例这样简单,但也不意味着不会如此或不该如此。作为开发者,我们需要确保应用正确的运行。我们需要确保大家信任我们的代码。我已经持续这样做许多年了,你可能无法想象测试拯救了我多少次,甚至是像改变 view ID 这样最简单的事。 -No one is perfect, but tests help us get a step closer. Keep on coding, keep on testing and until next time! +没有人是完美的,但是测试让我们趋近完美。保持编码,保持测试,直到永远! --- From 5f46617201c221775f6de7a55787e35dceda744a Mon Sep 17 00:00:00 2001 From: sunxinlei Date: Wed, 17 May 2017 18:54:47 +0800 Subject: [PATCH 03/59] init --- TODO/rearchitecting-airbnbs-frontend.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO/rearchitecting-airbnbs-frontend.md b/TODO/rearchitecting-airbnbs-frontend.md index 0e8e2a0ba06..e1d86d08a0f 100644 --- a/TODO/rearchitecting-airbnbs-frontend.md +++ b/TODO/rearchitecting-airbnbs-frontend.md @@ -4,9 +4,10 @@ > * 译者: > * 校对者: -# Rearchitecting Airbnb’s Frontend # +# Airbnb 的前端重构 # Overview: We recently rethought the architecture for the JavaScript side of our codebase at Airbnb. This post will look at (1) the product drivers that precipitated the changes, (2) the steps we took to move away from our legacy Rails solutions, and (3) some of the key pillars of the new stack. *Bonus: We’ll talk about what’s next!* +概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 端的架构。 Airbnb sees more than 75 million searches each day, which makes the search page our highest traffic page. For nearly ten years, engineers have evolved, enhanced, and optimized the way that Rails delivers the page. From 1e573704aea733b7bd1d56deb1e9ab810f7d1e12 Mon Sep 17 00:00:00 2001 From: sunxinlei Date: Thu, 18 May 2017 18:39:51 +0800 Subject: [PATCH 04/59] =?UTF-8?q?=E6=A6=82=E8=A6=81=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/rearchitecting-airbnbs-frontend.md | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/TODO/rearchitecting-airbnbs-frontend.md b/TODO/rearchitecting-airbnbs-frontend.md index e1d86d08a0f..e0009b94df0 100644 --- a/TODO/rearchitecting-airbnbs-frontend.md +++ b/TODO/rearchitecting-airbnbs-frontend.md @@ -1,40 +1,40 @@ > * 原文地址:[Rearchitecting Airbnb’s Frontend](https://medium.com/airbnb-engineering/rearchitecting-airbnbs-frontend-5e213efc24d2) > * 原文作者:[Adam Neary](https://medium.com/@AdamRNeary) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) -> * 译者: +> * 译者:[sunui](https://github.com/sunui) > * 校对者: # Airbnb 的前端重构 # -Overview: We recently rethought the architecture for the JavaScript side of our codebase at Airbnb. This post will look at (1) the product drivers that precipitated the changes, (2) the steps we took to move away from our legacy Rails solutions, and (3) some of the key pillars of the new stack. *Bonus: We’ll talk about what’s next!* -概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 端的架构。 +概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 端的架构。本文将讨论:(1)催生一些变化的产品驱动因素,(2)摆脱遗留的 Rails 解决方案的一些步骤,(3)一些新技术栈的关键性支柱。彩蛋:我们将讨论接下来要做的事。 -Airbnb sees more than 75 million searches each day, which makes the search page our highest traffic page. For nearly ten years, engineers have evolved, enhanced, and optimized the way that Rails delivers the page. +Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们流量最高的页面。近十年来,工程师们一直在发展、加强、和优化 Rails 输出页面的方式。 -Recently, we moved into verticals beyond Homes, [introducing Experiences and Places](https://www.airbnb.com/new) . As a part of bringing these new products to web, we took the time to rethink the search experience itself. +最近,我们转移到了主页以外的垂直页面,[来介绍一些体验和去处](https://www.airbnb.com/new)。作为 web 端新增产品的一部分,我们花时间重新思考了搜索体验本身。 ![](https://cdn-images-1.medium.com/max/800/1*VMRwDmHVeYC3YnJhhtKn4Q.gif) -Transitioning between routes for a broad search +用于一个广泛搜索的路由间的过渡 -Rather than navigating from our landing page at [www.airbnb.com](http://www.airbnb.com) (1) to a search results page (2) to a single listing (3) to the booking flow (4)— *each page delivered standalone via Rails* — we want the user experience to be fluid, adjusting what the user is experiencing as they explore and narrow their search. +我们希望用户体验流畅,要去斟酌用户在浏览页面和缩小搜索范围时遇到的内容,而不是从 [www.airbnb.com](http://www.airbnb.com) 着陆页导航,(1)访问一个搜索结果页,(2)访问一个单一列表页,(3)访问预订流程,(4)**每个页面都由 Rails 单独传送**。 ![](https://cdn-images-1.medium.com/max/800/1*epBwi0kxrcW5a6Wv-T4rSg.gif) -Designs exploring search from three states: New User, Returning User, and Marketing Marquee +设计三种浏览搜索页的状态:新用户,老用户,和营销页。 + +在标签页之间切换和与列表进行交互应该感到惬意而轻松。事实上,如今没有什么可以阻止我们致力于在中小屏幕上提供与本地应用相符的体验。 -Navigating across tabs and interacting with listings should feel luxurious and effortless. In fact, today there is nothing stopping us from delivering an experience on par with native applications on small and medium screens. ![](https://cdn-images-1.medium.com/max/800/1*y_gKoEDVvBvJpGq7hfcr_g.gif) -Future concept for navigating between tabs, considering async-loaded of content +再标签页之间切换的未来概念,考虑异步加载内容 -To tee up this type of experience, we needed to break free of the legacy page-by-page approach that got us here, and in the end we wound up with a fundamental rearchitecting of our Frontend code. +要开发这种类型的体验,我们需要摆脱传统的页面切换方法,最后我们结束了对我们的前端代码的基本重构。 -[Leland Richardson](https://medium.com/@intelligibabble) [recently spoke at React Conf about React Native in the “brownfield” of an existing, high traffic native application](https://www.youtube.com/watch?v=tWitQoPgs8w) . This article will examine how we undertook a dramatic upgrade with similar constraints, but on the web. Hopefully you find it useful if you find yourself in a similar place! +[Leland Richardson](https://medium.com/@intelligibabble) [最近在 React Conf 大会上发表了关于 React Native 的存在于高访问量 native 应用中的“褐色地带”。 ](https://www.youtube.com/watch?v=tWitQoPgs8w)。这篇文章将会探讨如何在类似的约束下进行强制性升级,不过是在 web 端。如果你遇到类似的情况,希望对你有帮助。 -### Breaking Free from Rails ### +### 从 Rails 之中解脱 ### Before firing up the barbecue for all the fun [Progressive Web App](https://developers.google.com/web/progressive-web-apps/) work on our roadmap, we needed to separate from Rails (or at least the way we use Rails at Airbnb in delivering standalone pages). From 00169fa1693546aa683442a57f6850accff269f4 Mon Sep 17 00:00:00 2001 From: "Yuze.Ma" <584653629@qq.com> Date: Thu, 18 May 2017 21:09:49 +0800 Subject: [PATCH 05/59] First draft First draft --- TODO/crafting-better-code-reviews.md | 202 ++++++++++++++------------- 1 file changed, 107 insertions(+), 95 deletions(-) diff --git a/TODO/crafting-better-code-reviews.md b/TODO/crafting-better-code-reviews.md index e81e90dee1b..369d3d34cf4 100644 --- a/TODO/crafting-better-code-reviews.md +++ b/TODO/crafting-better-code-reviews.md @@ -1,223 +1,235 @@ > * 原文地址:[Crafting Better Code Reviews](https://medium.com/@vaidehijoshi/crafting-better-code-reviews-1a5fc00a9312) > * 原文作者:[Vaidehi Joshi](https://medium.com/@vaidehijoshi) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) -> * 译者: +> * 译者:bobmayuze > * 校对者: -# Crafting Better Code Reviews # +# 建立更好的代码审查制度 # -*Adapted and reworked from a talk originally given at RailsConf 2017.* +*来自Rails 2017开发者大会中的一段演讲* -The intersection between humans and technology has never been simple or easy. This truth is particularly evident when it comes to the humans who *create* technology. As a human who also happens to write code for a living, I feel this the most during the code review process. +人与科技之间的交互部分总是那么忽明忽暗,难以捉摸。对于以 *开发科技产品* 为职业的人来说,更是如此。作为一个资深码农,我在代码审查的时候对于这一点的感触特别明显。 -Most developers tend think of their code as a craft and — as seems to be the case with artists and most creators of all kinds — we become incredibly attached to our code. We’ve been told to be [egoless programmers](https://blog.codinghorror.com/the-ten-commandments-of-egoless-programming/), to critique not just our own code, but every single line of code that crosses our desk, as it waits to be merged into a project’s codebase. We’ve heard that having our own code reviewed by our peers and reviewing the code of our colleagues are both Very Good Things™, that [we should all be doing](https://blog.codinghorror.com/code-reviews-just-do-it/) . And a good many of us already happen to be doing all of these highly-recommended things. +大多数开发者们习惯于把他们的代码看成一种艺术品,就好比画家看待自己的画一样,我们的代码总是和我们有着密不可分的关系。一直以来,我们都被教导说要做一个[无私奉献](https://blog.codinghorror.com/the-ten-commandments-of-egoless-programming/)的码农,能够在审查自己的代码的同时也能审查同事们写出来的代码。其实我们都知道这样的审查是对大家都有利的事,是一件[我们都应该做的事情](https://blog.codinghorror.com/code-reviews-just-do-it/),而且很多人都一直在做这件事。 -But when was the last time we evaluated these methodologies? Are any of us sure that our code review processes are actually *working*? Are we certain that they’re serving the roles that they were originally intended to fill? +但是谁还记得上一次我们衡量这些方法论是什么时候?我们真的能保证我们的代码审查制度是 *有效* 的吗?我们能够保证我们的代码审查制度不忘初心吗? -And if not: how can we try and make them better? +如果答案是不,那我们如何才能解决这个问题呢? ![](https://cdn-images-1.medium.com/max/800/1*INwRDJ_vspfJKkyFpv5jww.png) © geek & poke, [http://geek-and-poke.com](http://geek-and-poke.com) -### Why even *bother* with review? ### +### 避免和代码审查*过不去* ### -Before we can really understand the *practical* benefits of peer code review, it’s helpful to know why they even started in the first place. There has been a decent amount of [research](https://en.wikipedia.org/wiki/Code_review#References) around code review best practices, but I think that Steve McConnell’s research in [*Code Complete*](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670) (originally published in 1993) is a good place to start. + +在我们能真正完全理解代码审查的实际意义和好处之后,我们就能知道为什么会有代码审查这个传统了。网上有大量的有关代码审查的[研究](https://en.wikipedia.org/wiki/Code_review#References) ,但是我认为Steve McConnell在1993年发表的[*Code Complete*](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670) 这个才是一个比较适合我们开始讨论的地方。 In his book, he describes code reviews, and what function they *ought* to serve. He writes: +在他的书中,对于代码审查制度*应该有*的功能描述,他写下了下面这些话: -> One part of managing a software-engineering process is catching problems at the “lowest-value” stage — that is, at the time at which the least investment has been made and at which problems cost the least to correct. To achieve such a goal, developers use “quality gates”, periodic tests or reviews that determine whether the quality of the product at one stage is sufficient to support moving on to the next. +> 管理软件工程的一个重点就是找出在“最低价值”的问题。换句话说,就是找出最容易解决的问题。为了达到这样的一个预期,我们可以使用“质量门”这样一个理念,也就是周期性的测试或者审查来决定是否应该进行到开发的下一阶段。 -The most powerful aspect of McConnell’s case for code review on every single team is the way that he ties it back to something he terms the “Collective Ownership in Construction”: the idea that all code is owned by a group of contributors, who can each equally access, change, and modify the collectively-owned project. +McConnell的代码审查研究中最重要的理念就是代码集体拥有制度。具体就是所有代码都是被一组贡献者们所拥有的,这些贡献者们能平等的对代码进行查看和更改。 -> The original intent behind code reviews was that they would help us take collective ownership in the creation of our software. In other words, we’d each be stakeholders in our development process by having a hand in controlling the quality of our products. +> 代码审查制度最初是为了帮助我们共同维护一个软件。换句话说,就像我们同时持股一家公司时我们会做质量检测以确定产品的可行性。 -McConnell goes on to highlight a few different types of code review processes that an engineering team can adopt into their everyday workflows. I strongly recommend that you pick up a copy of *Code Complete* and learn more about each of these techniques; for our purposes, however, a summary will suffice. There are three formats for a code review: +McConnell在他的书中提出了很多种不同的代码审查制度的方式和流程,这些方式都可以被任何一个团队采用在日常的工作流当中。强烈推荐McConnell的 *Code Complete* ,真的非常赞。但是这边的话我们就简短的说3种来帮助理解。 -#### **1. Inspections** #### +#### **1. 3人审查制度** #### -Inspections are longer code reviews that are meant to take approximately an hour, and include a moderator, the code’s author, and a reviewer. +3人审查制度是一个比较长的审查流程,耗时基本都在1小时左右。整个流程会包括一个公司的把关人(扛把子),代码作者(程序猿),和一个审查员(产品狗)。 -When used effectively, inspections typically catch about *60% of defects* (either bugs or errors) in a program. According to McConnell’s research, inspections result in *20–30% fewer defects per 1000 lines of code* than less formalized review practices. +当大家能正确打开这个审查制度的时候,研究表明这个审查可以抓住一个程序 *60%的问题* 。根据McConnell的研究,相比于不怎么流程化的代码审查,这个制度平均每1000行代码能减少20%到30%的错误。 -#### **2. Walkthroughs** #### +#### **2. 带着一起过** #### -A walkthough is a 30–60 minute working meetings, usually intended to provide teaching opportunities for senior developers to explain concepts to newer programmers, while also giving junior engineers the chance to push back on old methodologies. +这个方式的话大概是30~60分钟,通常用于来帮助初级程序员们在一家公司中理解一些技术性难题,同时也能让高级开发人员们回顾旧的方法论。 -Walkthroughs can sometimes be very effective, but they’re not nearly as impactful as a more formalized code review process, such as an inspection. A walkthrough can usually reveal *20–40% of errors* in a program. +带着新人一起过的话有时候还蛮高效的,但是这个方式的话就不像上面那个3人审查一样流程化。这样的一个代码审查大概能检测到程序中20%到40%的问题。 -#### **3. Short code reviews** #### +#### **3. 审查小片段** #### -Short reviews are, as their name would suggest, much faster; however, they are still very much in-depth reviews of small changes, including single-line changes, that tend to be the most error-prone. +就想这个名字一样,这种审查非常短。但是这都是很有深度的审查,还蛮难的。有时候可能就是更改了1行代码,但是会引起大量的问题。 -McConnell’s research uncovered the following about shorter code review: +McConnell的研究发现了下面这些有关于审查小片段的事实: -> An organization that introduced reviews for one-line changes found that its error rate went from 55 percent before reviews to 2 percent afterward. A telecommunications organization in the late 80’s went from 86 percent correct before reviewing code changes to 99.6 percent afterward. +> 一个专门针对单行修改代码审查的机构发现在进行代码审查后代码报错率从55%下降到了2%。一个在80年代晚期的通讯机构代码正确率在审查后从80%上升到了99.6%。 -The data — at least McConnell’s *subset* of collected data — seems to suggest that every software team should be conducting *some* combination of these three types of code reviews. +McConnell的一组组数据似乎在告诉我们作为一个开发团队我们应该构造一些这三种代码审查的融合来进行开发的推进。 -However, McConnell’s book was first researched and written back in 1993. Our industry has changed since then and, arguably, so have our peer review methodologies. But are our implementations of code review today actually effective? How are we putting the *theory* behind code reviews into *practice*? +然而,McConnell的书是在1993年写的。时至今日,我们的工作流程早就已经更新换代了,同事之间互相检查的方法论也随之更新。但是我们现在对于代码审查的构造足够合理完整吗?我们应该如何把*理论*上的东西运营到*实际*中去? To find the answer to these questions, I did what any determined (but a little unsure of where to start) developer would do: I asked the Internet! +为了解答我的问题,我做了大多数码农会做的事:Google一下 [![Markdown](http://i4.buimg.com/1949/4bcc14c27f51262e.png)](https://twitter.com/vaidehijoshi) -Well, okay — I asked Twitter. +好吧,并没有什么卵用,那我来看看推特。 + +### 程序猿们如何看待代码审查 ### + -### What do developers think of code reviews? ### +在我对这个调查进一步阐述之前,我想先说点什么:*我不是一个数据科学家* (我希望我是,但是我也许在处理这篇文章反馈的时候能更加得心应手,或许我连在R语言里画个图都困难)。还有一点,就是说我的数据集其实是非常有限的。首先这个数据是我自己在推特上选过来的,然后的话数据是来自一个基于 branch/pull request的团队的。 -Before I dive into the results of the survey, a quick disclaimer: *I am not a data scientist.* (I wish I were, because I’d probably be a lot better at analyzing all of the responses to this survey, and maybe I’d even be halfway-decent at plotting graphs in R!). Ultimately, this means is that my dataset is limited in *many* ways, the first of which being that it was a self-selecting survey on Twitter, and the second being the fact that the survey itself presupposed a branch/pull request based team. +好,那么重点来了:*程序猿们是到底如何看待代码审查的?* -Okay, now that we have that out of the way: *what do developers think of code reviews?* +#### 量化的数据 #### -#### The quantitative data #### +让我们先来看看这组已经量化的数据。 -We’ll try to answer this question by looking at the quantitative data to start. +首先,这个问题的答案很大程度上取决于你问了*哪个*程序猿。在我写这个报告的时候,我已经收到超过500条回复了。 -First off, the answer to this question depends a lot on *which* developers you ask. At the time that I am writing this, I have received a little over 500 responses to my survey. +下面是一些根据工作时用的语言分类的结果,包括了JS,JAVA,还有Ruby。 -The developers who responded primarily worked in Java, Ruby, or JavaScript. Here’s how those responses break down in terms of developers and the primary language that they and their team work in. ![](https://cdn-images-1.medium.com/max/600/1*hiGuGx5OvayL4dPu1tSC4w.png) I asked every respondent to the survey to what extent they agreed with the statement: *Code reviews are beneficial to my team*. +根据我一个个问完之后,大家都认为 *代码审查对于团队是有好处的* 。 -Overall, Swift developers found code reviews the *most* beneficial to their teams, averaging a 9.5 on a scale of 1–10, where 1 was *strongly disagree*, and 10 was *strongly agree*. Ruby developers came in a close second, averaging about 9.2. +总的来说,从1分到非常不认可,10分非常认可,Swift开发团队给代码审查认可度打了9.5/10的高分。Ruby开发团队第二,打出了9.2的高分。 ![](https://cdn-images-1.medium.com/max/800/1*1zSl-fd9hygIBzxp52yHOQ.jpeg) -While the majority of survey respondents (approximately 70%) stated that all pull requests were reviewed stated that every single pull request was reviewed by someone on the team before being merged, this wasn’t the case on all teams. About 50 respondents (approximately 10% of the entire dataset) stated that pull requests were only peer reviewed in their teams when a review was *requested* by them. +当70%的受调查者告诉我他们的pull request在被merge之前会由团队里的其他人来检查时,有10%的受访者(大概50个)告诉我的说他们的pull request只有在自己要求进行代码审查的时候才会进行代码审查。 ![](https://cdn-images-1.medium.com/max/800/1*fVl3H0KGsauN1Bxs_jsN7A.jpeg) -The data seemed to suggest that this distribution, for the most part, carried across languages and frameworks. No one single language seemed to have an overwhelmingly different result in terms of whether all pull requests were reviewed, or if reviews had to first be requested. In other words, it would appear that it is not the language or the framework that results in a more consistent code review culture, but more likely, the team itself. +这张图片通过不同语言阐明了代码审查的深度。总的来说,每个语言直接没有差很多。换句话来说,决定代码审查的的深度和频率和你用什么语言没有关系,重点在于你在什么样的一个团队。 ![](https://cdn-images-1.medium.com/max/800/1*jFZ_2zCzHM78m_L_p0OK8A.jpeg) -Finally, for those developers who were working on teams that *did* require for pull requests to be reviewed before being merged, it appeared that the majority of teams only needed one other person to peer review before merging code into the shared codebase. +最后,对于那些当有要求进行代码审查才会进行审查的团队来说,大部分通常只有1个人在代码merge到主程序之前审查这个代码。 ![](https://cdn-images-1.medium.com/max/800/1*KsuH1lurvkf5wpoXZ2queQ.png) -#### The qualitative data #### +#### 定性描述的数据 #### -So what about the *unquantifiable* stuff? In addition to multiple choice questions, the survey also allowed respondents to fill in their own answers. And this is where the results actually proved to be the most *illuminating*, not to mention the most useful. -There were a few overarching themes that popped up repeatedly in the anonymized responses. +那么对于那些不可量化的东西来说,我们能知道什么呢?在多项选择题之外,这个调查同时也能够让受访者填写他们自己的答案。这一部分也是这个调查最重要的一部分,最能*说明问题*的一部分。 -> Ultimately, what seemed to make or break a code review experience depended upon two things: how much energy was spent during the review process, and how much substance the review itself had. +这写回答具体聚焦在如下几个重点。 -A code review was bad (and left a bad taste in the reviewer’s and reviewee’s mouth) if there wasn’t enough energy spend on the review, or if it lacked substance. On the other hand, if a code review process was thorough, and time was spent reviewing aspects of the code in a substantive way, it left a much more positive impression overall on both the reviewer and the reviewee. -But what do we mean by *energy* and *substance*, exactly? +> 总的来说,对于代码审查制度有很大关系的有两个因素:执行代码审查所需要消耗的资源和代码审查这一流程的可持续性。 -#### Energy #### +一个非常耗资源和可持续性很差的代码审查会让一个代码审查变得非常差劲。如果一个代码审查不是非常消耗资源,然后可持续性也很高的话,这会给审查者和受审查者双方都留下一个非常好的印象。 -Another way of determining the **energy behind a code review** is by answering the question: *Who all is doing the review?**And how much time are they spending on it?* +但是我们这里说的资源,和可持续性到底指什么呢? -A lot of respondents were conducting code reviews, but many seemed to be unhappy with who was doing them, and how much time they ended up spending while reviewing — or waiting to be reviewed. +#### 资源 #### -Below are just a few of the anonymized responses to the survey: +另一种来找出一个代码审查*到底消耗了多少资源*的方法是问自己这样的问题:*谁在执行这个流程*以及他们花了多少时间来执行这个流程? -> We have one dev who just blindly thumbs-up every PR and rarely leaves comments. That person is attempting to game the rule of “at least two approvals”. It is easy to tell, because inside of one minute they will suddenly have approved 5–6 PRs. +大量的受访者们都是很有生产力的代码审查员,但是大家都表示对于团队里执行这个环节的人不爽,以及对于等待他们的代码被审查时花费的大量时间表示不爽。 -> I find that the 2nd or 3rd reviewer is often more likely to rubber stamp after seeing one approval. +下面是一些匿名的反馈: -> There have been times when the same code has been reviewed differently depending on who submits the PR. +> 我们有一个开发人员只是盲目的给每个PR一个赞然后都不怎么留评论。我可以告诉你这是真的因为他们1分钟能看5到6个PR。 -> Everyone on the team should receive equal review. I feel like it’s so common for senior people to get no feedback because people assume they can do no wrong but they totally can, and might want feedback. And junior people get nit picked to death… remember people’s self esteem is likely to be affected and they’re being vulnerable. +> 我发现第二个或者第三个的审查者大多数都是在打酱油。 -> Commits are too big, so PR’s take long to review. People don’t check out the branch locally to test. +> 有时候相同的代码审查的结果会根据不同的提交者而不同。 -> Especially long PR’s take longer to be reviewed, which is an issue because they have the most effect on future branches/PRs/merges. +> 团队里的每个人应该受到相同的对待。高级工程师也会犯错,而且他们也希望有人能提出来。初级工程师也不能被各种黑。人呐,总是会被事物有偏见。 -The overarching takeaways when it came to *how**much* energy was being spent (or not spent) on a code review boiled down to three things: +> Commits太长了,导致PR需要很久去审查。人们不会先在本地去测试一下代码。 -1. No one feels good about a code review process that’s just a formality & doesn’t carry any weight. -2. It’s not fun to review a long PR with code that you’re unfamiliar with, or have no context for. -3. To err is human, and we’re all human. We should all be reviewed, and review others fairly. +> 长长的PR总是花费大量的时间,然后这个PR还对将来的特性有重要的影响。 -#### Substance #### +各位码农对于代码审查和消耗的消耗资源的关系评论归类之后主要有以下三点: + +1. 大家都对代码审查不爽,而且觉得没什么卵用。 +2. 审查一个你不熟悉的代码很不爽。 +3. 错误都处在人身上,我们都是人。我们应该平等的对待,不能因为谁是老大就说他/她写的代码没有问题。 + +#### 可持续性 #### The **substance of a code review** boils down to the answer to one question question: *What exactly is someone saying, doing, or making another person feel while they review their code?* +*代码审查可持续性*主要被以下几个因素影响:执行代码审查的时候执行者到底*说了什么,做了什么,让被审查者有什么样的感受* -The responses connected to the substance of a code review were, for the most part, grounded in what people were saying in their reviews, and how they were saying it. +重点还是在于大家说了什么以及说话的方式. -Here are a few of the anonymized responses from the survey: +让我们来看看大家的吐槽吧: -> I take any feedback on PR’s at face value. If changing that string to a symbol will make you happy, let’s do that and move on. I’m not going to try and justify an arbitrary decision. It’s not unlike working in an IDE environment, it’s very easy for my brain to fall into a “see red squiggle, fix red squiggle” mindset. I don’t really care why it’s upset, just make it stop yelling at me. +> 面对PR的时候,我觉得你要是不喜欢变量名就直接改,我觉得这个不是最重要的,这完全是个人喜好嘛!就像在IDE里一样,我很容易就被搞蒙了。我不关心为什么不开心,让这个东西停止报错就好,不停叫真的不能忍。 -> Do not do big picture or architectural critiques in a review. Have offline conversations. Too easy to send folks down a rabbit hole and create frustration. +> 不要在公开场合说思想上的大错误。在线下有个友善的对话会非常有帮助。直接PR上说会让人很爽,最后弄得大家都不愉快。 -> I feel pretty strongly that it’s annoying when people demand changes, especially if they don’t take the time to explain why they’re doing so, or leave room for the possibility that they’re wrong. Especially when people just rewrite your code in a comment and tell you to change to their version. +> 当需求不停的改的时候我非常难过,特别是他们不向我解释为什么更改了需求的时候,或者留下他们犯错的可能性。特别是当别人告诉你改写你的代码成为他们的版本的时候,你简直难过的想要抱抱。 -> If a comment thread is getting long, it’s an indication a verbal conversation should be had (report the consensus back in the comment thread) +> 当一个回复过长的时候,我们不如去进行一次线下的交流。 -> People need to do better jobs distinguishing between their own stylistic preferences and feedback that makes a functional difference. It can be tough for a more junior person to figure out which is which. It’s also frustrating when multiple seniors give conflicting feedback (e.g. What to call a route and why). +> 我觉得对于个人喜好问题和功能是否能正常运作完全是两个问题。对于初级工程师来说,这是非常难分辨的。有时候几个高级工程师给出不同的反馈的时候更加懵逼。 The main themes when it came to the *substance* of a code review could be summarized into the following: +总的来说,代码审查的重点有一下几个: + +1. 反馈过度注重语法和习惯的问题,导致让双方都非常不爽。代码习惯与风格和代码功能错误根本就是两回事儿。 +2. 说话方式也很重要。过激的语言会打消对方的自信心,对团队也不是好事。 -1. Comments nitpicking purely at syntax lead to a negative experience. Style and semantics are not the same thing. (Interestingly, 5% of respondents used the word ***nitpick***to describe code review comments in a negative context.) -2. The words we use to review one another’s code really do matter. An unkind review can break someone’s self-confidence. +### 我们如何能够做的更好? ### -### How do we do better? ### -Although this data may not be the most complete, full, or even the most *accurate* representation of our industry’s code review culture, there is one thing that seems like a fair claim to make: we could all stand to revisit our code review processes on our teams and within the larger community. +也许这些数据不能我们最完整,最细致,或者最精准的代表我们代码审查的结构,但是我们还是能学到一些东西:我们能够回顾并检查我们团队的甚至整个社区的代码审查流程。 -This anonymous survey response highlights the immense impact that a review process can have on members of an engineering team: +下面的匿名调查告诉了我们代码审查对于一个团队成员的影响是怎么样的: -> A bad code review almost made me leave the company. A great code review leaves me feeling better equipped to tackle future projects. +> 一个非常差劲的代码审查让我几乎决定离职。一个优秀的代码审查流程会让我有信心面对将来更加困难的项目。 -Indeed, having *some* sense of a formalized code review is incredibly beneficial and statistically powerful; both Steve McConnell’s research and this small survey both seem to support this fact. But, it’s not just enough to implement a code review culture and then never think about it again. In fact, a code review process where members of a team are simply going through the actions of reviewing can be detrimental and discouraging to the team as a whole. +确实,有一个流程化的代码审查制度*似乎*是非常有效且能帮助团队快速成长的;Steve McConnell和这篇文字的调查都证明了这一点。但是,单纯的重构代码审查流程然后永远不改是远远不够的。事实上,代码审查的流程应该根据整个团队的情况来更改以保证团队的生长。 -> Instead, it is the act of introspection, reflection, and reevaluation of our collective code review culture that will allow us to build upon any kind of formalized review process we might have. +> 与直接采用一个代码审查方式不同的是,我们需要重新思考我们的代码审查流程并且流程化工作来帮助我们面对以后的可能遇到的问题。 -In other words, it’s the act of asking ourselves whether our code reviews are effective, and whether they’re making a difference — both on the entire team, as well as the individuals who form it. +换句话说,重点在于我们能够思考我们的代码审查是否足够有效,是否能对于团队和个人同时产生好处。 -#### Easy ways to improve your code review process #### +#### 一些提高代码审查的小技巧 #### -There are a few ways to immediately make the code review process more painless and enjoyable for your team. Here are a few things to help you get started: +这边是一些能够快速帮助提高代码审查感受的一些技巧: -- Implement [linters](https://github.com/showcases/clean-code-linters) or a code analyzer (if available) in order to eliminate the need for syntactical comments on a pull request. -- Use [Github templates](https://quickleft.com/blog/pull-request-templates-make-code-review-easier/) for every pull request, complete with a checklist to make it easy for the author of the code and the reviewer to know what to add, and what to check for. -- Add screenshots and detailed explanations to help provide context to teammates who might not be familiar with the codebase. -- Aim for small, concise commits, and encapsulated pull requests that aren’t massive in size, and thus much easier and quicker to review. -- Assign specific reviewers to a PR — more than one, if possible. Make sure that the role of reviewing is equally distributed amongst engineers of each and every level. +- 使用 [linters](https://github.com/showcases/clean-code-linters) 或者其他的代码构成器来避免PR时的语法格式问题。 +- 使用 [Github 的模板](https://quickleft.com/blog/pull-request-templates-make-code-review-easier/)来生产每个PR。在发PR的时候带上更改列表对于作者和审查者都非常有帮助。 +- 在发PR的时候可以加个截图来帮助那些不是很熟悉的人理解问题。 +- 提高commits的信息量,尽量语言短小精湛。 +- 对于每个PR钦定审查者,如果可能的话人多于一个比较好。确定代码编写和审查的分配对于各个等级的工程师来说是均衡的。 -#### The harder things — but the most important #### +#### 很难做到但是非常重要的事情 #### -Once you’ve picked off some of the low-hanging fruit, there are some bigger changes you can help bring about, too. These are actually the most important things to do if you want to change your code review culture. +当你已经代码审查入门之后,这里是一些更加重要的东西。事实上,对于重构代码审查流程来说,这里的东西是最重要的。 -And, let me warn you: that’s probably what makes them so hard. +警告:前方高能,内容可能有一定难度 -- ***Develop a sense of empathy on your team.*** The greatest burden of making this happen falls on the shoulders of senior, more experienced engineers. Build empathy with people who are newer to the team or the industry. +- ***能够理解你的团队。*** 这项任务一般都由高级工程师们来承担,希望大家对于新人们能多多帮助。 [![Markdown](http://i1.piimg.com/1949/36f270199929bb1e.png)](https://twitter.com/sarahmei) -- ***Push for a culture that values vulnerability — both in actions and in words.*** This means reevaluating the language used in pull request comments, identifying when a review is on track to turn into a downward spiral, and determining when to take conversations offline, rather than questioning the author of the code publicly. +- ***更加委婉的交流。*** 这意味着在发评论之前思考自己的言行是否妥当,是否会让其他人很不爽。在公众下批评别人是一件非常不好的事情。相比,有个私人对话会好很多。 [![Markdown](http://i1.piimg.com/1949/51bfec74a7cf1e42.png)](https://twitter.com/j3) -- ***Have a conversation.*** Sit your team down, start a Slack channel, create an anonymized survey — whichever fits your group’s culture best. Have the conversation that will make everyone comfortable enough to share whether they are each happy with the current code review process, and what they wish the team did differently. +- ***进行一次口头交流。*** 带着整个团队坐下来,到slack(美国的一种团队交流软件)上开个新的频道,让大家能够匿名的交流(选择最适合的即可)。在匿名的情况下大家都愿意说真话。 -I saved the most important one for last because, honestly, if you’ve stuck with me and read this far, you must really want to change the status quo. And that really *is* a Very Good Thing™! Ultimately, though, having the conversation with your team is the most important first step to take in making that change happen. +我把最重要的一点放在最后,因为当你还有耐心读到这里的时候,说明你真的想要更改一下你们的代码审查结构了,这是个好事儿。团队交流真的非常重要,这几乎是每个团队必须跨出的一步如果他们想要提升代码审查制度的话。 -This survey response summarizes why, far better than I ever could: +下面这句话基本上包括了我最想说的: -> I love code reviews in theory. In practice, they are only as good as the group that’s responsible for conducting them in the right manner. +> 理论上来说,我很喜欢代码审查。事实上,这个东西取决于构建这个流程的团队。 -#### Resources #### +#### 更多链接 #### -If you’d like to view a larger collection of curated, anonymized survey responses, you can view the website that accompanies this project: +如果你想看一个更加大的匿名的反馈,你可以看下面这个有关于这个项目的网站: [![Markdown](http://i1.piimg.com/1949/2b892fa3a6988b39.png)](http://bettercode.reviews) -#### Acknowledgements #### +#### 感谢 #### -First and foremost, I’m deeply grateful to the hundreds of developers who took the time and effort to answer my survey. +首先我很想感谢一路上一直支持我的工程师朋友们,感谢你们的答案和接受我的调查。 A huge thank you to [Kasra Rahjerdi](https://medium.com/@jc4p), who helped me analyze the responses to my survey and created many of the graphs in this project. +非常感谢 [Kasra Rahjerdi](https://medium.com/@jc4p) 帮我整理反馈并且生成那么多的图像。 -Thank you to [Jeff Atwood](https://blog.codinghorror.com/code-reviews-just-do-it/), for his articles on peer reviews, Karl Wiegers for his work in [*Humanizing Peer Reviews*](http://www.processimpact.com/articles/humanizing_reviews.html), and Steve McConnell for his extensive research on code review processes in [*Code Complete*](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670) . I hope you’ll consider supporting these authors by reading their writing or purchasing their books. +感谢 [Jeff Atwood](https://blog.codinghorror.com/code-reviews-just-do-it/)的有关于互相检查的文章, Karl Wiegers 的 [*人类化互相审查流程*](http://www.processimpact.com/articles/humanizing_reviews.html), 以及 Steve McConnell 对于 [*Code Complete*](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670) 的研究。 我希望你能购买他们的书来支持他们。 --- From 71eab4cbb2c9c8909f90d10784c89ade343afeb8 Mon Sep 17 00:00:00 2001 From: sunxinlei Date: Fri, 19 May 2017 01:34:09 +0800 Subject: [PATCH 06/59] =?UTF-8?q?=E4=B8=80=E5=B0=8F=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/rearchitecting-airbnbs-frontend.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TODO/rearchitecting-airbnbs-frontend.md b/TODO/rearchitecting-airbnbs-frontend.md index e0009b94df0..9b71c1e3e85 100644 --- a/TODO/rearchitecting-airbnbs-frontend.md +++ b/TODO/rearchitecting-airbnbs-frontend.md @@ -36,11 +36,13 @@ Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们 ### 从 Rails 之中解脱 ### -Before firing up the barbecue for all the fun [Progressive Web App](https://developers.google.com/web/progressive-web-apps/) work on our roadmap, we needed to separate from Rails (or at least the way we use Rails at Airbnb in delivering standalone pages). +在我们的烧烤开火之前,因为我们的线路图上存在所有有趣的[渐进式 web 应用](https://developers.google.com/web/progressive-web-apps/)(WPA),我们需要从 Rails 中解脱出来(或者至少在 Airbnb 用 Rails 提供单独页面的这种方式)。 -Unfortunately, only a matter of months ago, our search page contained some very old code…like, *Lord of the Rings*, touch-that-at-your-peril old. Fun fact: I once replaced a small [Handlebars](http://handlebarsjs.com/) template backed by a Rails presenter with a simple React component, and suddenly things were breaking in entirely separate parts of the page — even in our API response! Turns out, the presenter was mutating the backing Rails model, which had been impacting all downstream data for years, even when the UI wasn’t being rendered. +不幸的是,就在几个月前,我们的搜索页还包含一些非常老旧的代码,像指环王一样,碰它就要小心自负后果。有趣的事实:我曾尝试用一个简单的 React 组件替换一个 Rails presenter 备份过的小巧的 [Handlebars](http://handlebarsjs.com/) 模板,突然很多完全不相关的部分都崩掉了——甚至 API 响应都除了问题。原来,presenter 正在改变后备 Rails 模式,多年来即使在 UI 没有渲染的时候,它也影响着所有的下游数据。 In short, we were in this project like Indiana Jones swapping the idol for a bag of sand, and immediately the temple starts collapsing, and we’re running from a boulder. +简而言之,我们在这个项目中,像 Indiana Jone 用灵魂交换一袋沙子,突然间寺庙开始崩溃,我们正在从一块巨石上跑。 + #### Step 1: Aligning on API Data #### @@ -272,7 +274,7 @@ Also, note the `scheduleAsyncLoad()` utility, which requests the bundle in advan The final benefit of this approach is that `HomesSearch_Map` becomes a named bundle that the browser can cache. As we disaggregate larger route-based bundles, the slowly-changing sections of the app remain untouched across updates, further saving JavaScript download time. -#### Building Accessibility into our Design Language #### +#### Building Accessibility into our Design Language #### Doubtless it warrants a dedicated post, but we have begun building our internal component library with Accessibility enforced as a hard constraint. In the coming months, we will have replaced all UI across the guest flow that is incompatible with screen readers. From ba5f04e531b075cc31389fa1491e9a2a8952ca21 Mon Sep 17 00:00:00 2001 From: sunxinlei Date: Fri, 19 May 2017 18:35:56 +0800 Subject: [PATCH 07/59] =?UTF-8?q?=E7=BB=A7=E7=BB=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/rearchitecting-airbnbs-frontend.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/TODO/rearchitecting-airbnbs-frontend.md b/TODO/rearchitecting-airbnbs-frontend.md index 9b71c1e3e85..86b5f0bfa97 100644 --- a/TODO/rearchitecting-airbnbs-frontend.md +++ b/TODO/rearchitecting-airbnbs-frontend.md @@ -40,11 +40,11 @@ Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们 不幸的是,就在几个月前,我们的搜索页还包含一些非常老旧的代码,像指环王一样,碰它就要小心自负后果。有趣的事实:我曾尝试用一个简单的 React 组件替换一个 Rails presenter 备份过的小巧的 [Handlebars](http://handlebarsjs.com/) 模板,突然很多完全不相关的部分都崩掉了——甚至 API 响应都除了问题。原来,presenter 正在改变后备 Rails 模式,多年来即使在 UI 没有渲染的时候,它也影响着所有的下游数据。 -In short, we were in this project like Indiana Jones swapping the idol for a bag of sand, and immediately the temple starts collapsing, and we’re running from a boulder. 简而言之,我们在这个项目中,像 Indiana Jone 用灵魂交换一袋沙子,突然间寺庙开始崩溃,我们正在从一块巨石上跑。 -#### Step 1: Aligning on API Data #### + +#### 第 1 步: 调整 API 数据 #### When Rails is server-rendering your page, you can get away with throwing data at your server-rendered React components any way you like. Controllers, helpers, and presenters can produce data of any shape, and even as you migrate sections of the page to React, each component can consume whatever data it requires. @@ -54,7 +54,7 @@ If you find yourself in similar waters with a large application, you might find The tricky bit for us was working with all the teams who interact with the guest booking flow: our Business Travel, Growth, and Vacation Rentals teams; our China and India market-specific teams, Disaster Recovery…the list goes on, and we needed to reeducate all these folks that even though it was technically possible to pass data directly to the component being rendered (“yes, I understand it’s just an experiment, but…”), *all data* needs to go through the API. -#### Step 2: Non-API Data: Config, Experiments, Phrases, L10n, I18n… #### +#### 第 2 步: 非 API 数据: 配置、试验、惯用语、本地化、 国际化… #### There is a separate class of data from what we would think of as API data, and it includes application config, user-specific experiment assignment, internationalization, localization, and similar concerns. Over the years, Airbnb has built up some incredible tooling to support all these functions, but the mechanisms for delivering them to the Frontend were a bit under-baked (or possibly fully-baked when built, before the ground began shifting under foot!). @@ -172,11 +172,11 @@ This higher order component does two very important things: In a single shot, we eliminated `add_bootstrap_data` and prevented engineers from passing arbitrary keys through to top level React components. Order was restored to the shire, and before long we were navigating to routes dynamically in the client and rendering content of material complexity without Rails to prop it up (pun intended). -### Super-Charging the Frontend ### +### 进击的前端 ### Server rework in hand, we now turn our gaze to the client. -#### The Lazy-Loaded Single Page App #### +#### 懒加载的单页面应用 #### Gone are the days, friends, of the monster Single Page App (SPA) with a gruesome loading spinner on initialization. This dreaded loading spinner was the objection many folks raised when we pitched the idea of client-side routing with React Router. @@ -196,7 +196,7 @@ Side-by-side comparison fetching Homes for Tokyo: Legacy page load vs client-sid …now transitions between routes are smooth as butter and a step change (~5x) faster, and we can break ground on the animations featured at the beginning of this post. -#### AsyncComponent #### +#### 异步组件 #### Prior to React, we would render an entire page at a time, and this practice carried over into our early React days. But we use an AsyncComponent similar to [this](https://medium.com/@thejameskyle/react-loadable-2674c59de178) as a way to load sections of the component hierarchy after mount. From 1d4f098255eeef0c880bed6fb58a826f8a2d2a2f Mon Sep 17 00:00:00 2001 From: GangsterHyj <919685260@qq.com> Date: Sat, 20 May 2017 01:04:06 +0800 Subject: [PATCH 08/59] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/web-developer-security-checklist.md | 118 +++++++++++------------ 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/TODO/web-developer-security-checklist.md b/TODO/web-developer-security-checklist.md index d312d5b845e..2733f5acd2c 100644 --- a/TODO/web-developer-security-checklist.md +++ b/TODO/web-developer-security-checklist.md @@ -5,96 +5,96 @@ > * 校对者: -# Web Developer Security Checklist # +# Web 开发者安全清单 ![](https://cdn-images-1.medium.com/max/800/1*UOl3ydmbG1ehgoSpBxdGFA.jpeg) -Developing secure, robust web applications in the cloud is **hard, very hard**. If you think it is easy, you are either a higher form of life or you have a painful awakening ahead of you. +开发安全、健壮的云端 web 应用程序是**非常困难**的事情。如果你认为这很容易,要么你过着更高级的生活,要么你还正走向痛苦觉醒的路上。 -If you have drunk the [MVP](https://en.wikipedia.org/wiki/Minimum_viable_product) cool-aid and believe that you can create a product in one month that is both valuable and secure — think twice before you launch your “proto-product”. After you review the checklist below, acknowledge that you are skipping many of these critical security issues. At the very minimum, be *honest* with your potential users and let them know that you don’t have a complete product yet and are offering a prototype without full security. +倘若你已经接受 [MVP(最小化可行产品)](https://en.wikipedia.org/wiki/Minimum_viable_product) 的开发理念,并且相信能在一个月内创造既有价值又安全的产品 —— 在发布你的“原型产品”之前请再三考虑。在你检查下面列出的安全清单后,承认你在开发过程中忽视了很多极其重要的安全问题。至少要对你潜在的用户坦诚,让他们知道你并没有真正完成产品,而仅仅只是提供没有充分考虑安全问题的原型。 -This checklist is simple, and by no means complete. It is a list of some of the more important issues you should consider when creating a web application. +这份安全清单很简单,绝非覆盖所有方面。它列出了在创建 web 应用时需要考虑的比较重要的安全问题。 -Please comment if you have an item I can add to the list. +如果下面的清单遗漏了你认为很重要的问题,请发表评论。 -### **Database** ### +### **数据库** ### -- Use encryption for data identifying users and sensitive data like access tokens, email addresses or billing details. -- If your database supports low cost encryption at rest (like [AWS Aurora](https://aws.amazon.com/about-aws/whats-new/2015/12/amazon-aurora-now-supports-encryption-at-rest/)), then enable that to secure data on disk. Make sure all backups are stored encrypted as well. -- Use minimal privilege for the database access user account. Don’t use the database root account. -- Store and distribute secrets using a key store designed for the purpose. Don’t hard code in your applications. -- Fully prevent SQL injection by only using SQL prepared statements. For example: if using NPM, don’t use npm-mysql, use npm-mysql2 which supports prepared statements. +- 对识别用户身份的数据和诸如访问令牌、电子邮箱地址或账单明细等敏感数据进行加密。 +- 如果数据库支持在休息状态进行低消耗的数据加密 (如 [AWS Aurora](https://aws.amazon.com/about-aws/whats-new/2015/12/amazon-aurora-now-supports-encryption-at-rest/)),那么请激活此功能以加强磁盘数据安全。确保所有的备份文件也都被加密存储。 +- 对访问数据库的用户帐号使用最小权限原则,禁止使用数据库 root 帐号。 +- 使用精心设计的密钥库存储和分发密钥,不要对应用中使用的密钥进行硬编码。 +- 仅使用 SQL 预备语句以彻底阻止 SQL 注入。例如,如果使用 NPM 开发应用,连接数据库时不使用 npm-mysql ,而是使用支持预备语句的 npm-mysql2 。 -### Development ### +### **开发** ### -- Ensure that all components of your software are scanned for vulnerabilities for every version pushed to production. This means O/S, libraries and packages. This should be automated into the CI-CD process. -- Secure development systems with equal vigilance to what you use for production systems. Build the software from secured, isolated development systems. +- 确保检查软件所有组件的每个投入生存环境使用的版本漏洞,包括操作系统、库和软件包。此操作应该以自动化的方式注入 CI/CD 过程。 +- 对开发环境系统的安全问题保持与生产环境同样的警惕,从安全、独立的开发环境系统构建软件。 -### Authentication ### +### **认证** ### -- Ensure all passwords are hashed using appropriate crypto such as bcrypt. Never write your own crypto and correctly initialize crypto with good random data. -- Implement simple but adequate password rules that encourage users to have long, random passwords. -- Use multi-factor authentication for your logins to all your service providers. +- 确保所有密码都使用适当的加密算法(例如 bcrypt )进行哈希。 +- 实现简单但充分的密码规则以激励用户使用长的随机密码。 +- 使用多因素身份验证方式实现对服务提供商的登录操作。 -### **Denial of Service Protection** ### +### **拒绝服务防卫** ### -- Make sure that DOS attacks on your APIs won’t cripple your site. At a minimum, have rate limiters on your slower API paths like login and token generation routines. -- Enforce sanity limits on the size and structure of user submitted data and requests. -- Use [Distributed Denial of Service](https://en.wikipedia.org/wiki/Denial-of-service_attack) (DDOS) mitigation via a global caching proxy service like [CloudFlare](https://www.cloudflare.com/). This can be turned on if you suffer a DDOS attack and otherwise function as your DNS lookup. +- 确保对 API 进行 DOS 攻击不会让你的网站崩溃。至少在执行时间较长的 API 路径(例如登录、令牌生成等程序)使用速度限制器。 +- 对用户提交的数据和请求在大小和结构上增强完整性限制。 +- 通过类似 [CloudFlare](https://www.cloudflare.com/) 的全局缓存代理服务应用缓解 [Distributed Denial of Service](https://en.wikipedia.org/wiki/Denial-of-service_attack) (DDOS)对网站带来的影响。如果你遭受 DDOS 攻击,或者用于 DNS 查找,可以考虑开启此服务。??? -### **Web Traffic** ### +### **网络交通** ### -- Use TLS for the entire site, not just login forms and responses. Never use TLS for just the login form. -- Cookies must be httpOnly and secure and be scoped by path and domain. -- Use [CSP](https://en.wikipedia.org/wiki/Content_Security_Policy) without allowing unsafe-* backdoors. It is a pain to configure, but worthwhile. -- Use X-Frame-Option, X-XSS-Protection headers in client responses -- Use HSTS responses to force TLS only access. Redirect all HTTP request to HTTPS on the server as backup. -- Use CSRF tokens in all forms and use the new [SameSite Cookie](https://scotthelme.co.uk/csrf-is-dead/) response header which fixes CSRF once and for all newer browsers. +- 整个网站使用 TLS (安全传输层协议),不要仅对登录表单使用 TLS。 +- Cookies 必须添加 httpOnly 和 secure 属性,且由属性 path 和 domain 限定作用范围。 +- 使用 [CSP(内容安全策略)](https://en.wikipedia.org/wiki/Content_Security_Policy) 以禁止不安全的后门操作。策略的配置很繁琐,但是值得。 +- 使用 X-Frame-Option 和 X-XSS-Protection 响应头。 +- 使用 HSTS(HTTP Strict Transport Security) 响应强迫客户端仅使用 TLS 访问服务器。将所有 HTTP 请求重定向到服务器上的 HTTPS 作为后备。??? +- 在所有表单中使用 CSRF 令牌,使用新响应头 [SameSite Cookie](https://scotthelme.co.uk/csrf-is-dead/) 一次性解决 CSRF 问题, SameSite Cookie 适用于所有新版本的浏览器。 ### **APIs** ### -- Ensure that no resources are enumerable in your public APIs. -- Ensure that users are fully authenticated and authorized appropriately when using your APIs. +- 确保公有 API 中没有可枚举的资源。 +- 确保每个访问 API 的用户都能被恰当地认证和授权。 -### **Validation** ### +### **校验** ### -- Do client-side input validation for quick user feedback, but never trust it. -- Validate every last bit of user input using white lists on the server. Never directly inject user content into responses. Never use user input in SQL statements. +- 执行客户端输入校验以快速获取用户的反馈,但是不能完全依赖校验。 +- 使用服务器的白名单校验用户输入。不要向响应直接注入用户信息,不要在 SQL 语句使用用户输入。 -### **Cloud Configuration** ### +### **云端配置** ### -- Ensure all services have minimum ports open. While security through obscurity is no protection, using non-standard ports will make it a little bit harder for attackers. -- Host backend database and services on private VPCs that are not visible on any public network. Be very careful when configuring AWS security groups and peering VPCs which can inadvertently make services visible to the public. -- Isolate logical services in separate VPCs and peer VPCs to provide inter-service communication. -- Ensure all services only accept data from a minimal set of IP addresses. -- Restrict outgoing IP and port traffic to minimize APTs and “botification”. -- Always use AWS IAM roles and not root credentials. -- Use minimal access privilege for all ops and developer staff -- Regularly rotate passwords and access keys according to a schedule. +- 确保所有服务开放最少的端口。尽管是通过模糊的安全性不受保护的,使用非标准端口将使得黑客的攻击更加困难。 +- 在对任何公有网络都不可见的私有 VPC 上部署后台数据库和服务。配置 AWS 安全组和对等 VPC 务必谨慎(可能无意间使服务对外部可见)。 +- 在独立的 VPC 和对等的 VPC 隔离逻辑服务,以提供内部服务交流。??? +- 确保所有服务仅接收来自最小的IP地址集合的数据。 +- 限制对外输出的 IP 和端口流量,以最小化 APT(高级持续性威胁) 和 “警告”。 +- 始终使用 AWS 的 IAM (身份与访问管理)角色,而不是使用 root 的认证信息。 +- 对所有操作和开发人员使用最小访问权限原则。 +- 按照预定计划定期轮换密码和访问密钥。 -### **Infrastructure** ### +### **基础架构** ### -- Ensure you can do upgrades without downtime. Ensure you can quickly update software in a fully automated manner. -- Create all infrastructure using a tool such as Terraform, and not via the cloud console. Infrastructure should be defined as “code” and be able to be recreated at the push of a button. Have zero tolerance for any resource created in the cloud by hand — Terraform can then audit your configuration. -- Use centralized logging for all services. You should never need SSH to access or retrieve logs. -- Don’t SSH into services except for one-off diagnosis. Using SSH regularly, typically means you have not automated an important task. -- Don’t keep port 22 open on any AWS service groups on a permanent basis. -- Create [immutable hosts](http://chadfowler.com/2013/06/23/immutable-deployments.html) instead of long-lived servers that you patch and upgrade. (See [Immutable Infrastructure Can Be More Secure](https://simplesecurity.sensedeep.com/immutable-infrastructure-can-be-dramatically-more-secure-238f297eca49)). -- Use an [Intrusion Detection System](https://en.wikipedia.org/wiki/Intrusion_detection_system) like [SenseDeep](https://www.sensedeep.com/) or service to minimize [APTs](https://en.wikipedia.org/wiki/Advanced_persistent_threat) . +- 确保在不停机的情况下对基础架构进行升级,确保以全自动的方式快速更新软件。 +- 利用 Terraform 等工具创建所有的基础架构,而不是通过云端命令行窗口。基础架构应该定义为“代码”,仅需一个按钮的功夫即可重建。对云端任何亲手创建的资源零容忍 —— Terraform 能审查你的所有配置。??? +- 为所有服务使用集中化的日志记录,不该再利用 SSH 访问或检索日志。 +- 除了一次性诊断以外,不要使用 SSH 登录进服务。 经常使用 SSH ,意味着你还没有将执行重要任务的操作自动化。??? +- 不要长期开放任何AWS服务组的22号端口。 +- 创建 [immutable hosts(不可变主机)](http://chadfowler.com/2013/06/23/immutable-deployments.html) 而不是创建需要提交补丁和更新的服务器。(详情请看博客 [Immutable Infrastructure Can Be More Secure](https://simplesecurity.sensedeep.com/immutable-infrastructure-can-be-dramatically-more-secure-238f297eca49))。 +- 使用如 [SenseDeep](https://www.sensedeep.com/) 的 [Intrusion Detection System(入侵检测系统)](https://en.wikipedia.org/wiki/Intrusion_detection_system) 或服务,以最小化 [APTs(高级持续性威胁)](https://en.wikipedia.org/wiki/Advanced_persistent_threat) 。 -### **Operation** ### +### **操作** ### -- Power off unused services and servers. The most secure server is one that is powered down. +- 关闭未使用的服务和服务器,最安全的服务器是关机的。 -### Test ### +### **测试** ### -- Audit your design and implementation. -- Do penetration testing — hack yourself, but also have someone other than you pen testing as well. +- 审核你的设计与实现。 +- 进行渗透测试 — 攻击自己的应用,让其他人为你的应用编写测试代码。 -### **Finally, have a plan** ### +### **最后,制定计划** ### -- Have a threat model that describes what you are defending against. It should list and prioritize the possible threats and actors. -- Have a practiced security incident plan. One day, you will need it. +- 准备用于描述网络攻击防御的威胁模型,它应该列出并优先考虑可能的威胁和网络攻击参与者。 +- 制定经得起实践考验的安全事故计划,总有一天你会用到它。 --- From 493a54546a7ddd5676eb7dffa241d36581a74a41 Mon Sep 17 00:00:00 2001 From: "Yuze.Ma" <584653629@qq.com> Date: Sun, 21 May 2017 10:32:31 +0800 Subject: [PATCH 09/59] Fix inappropriate syntax --- TODO/crafting-better-code-reviews.md | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/TODO/crafting-better-code-reviews.md b/TODO/crafting-better-code-reviews.md index 369d3d34cf4..b8092b7f14f 100644 --- a/TODO/crafting-better-code-reviews.md +++ b/TODO/crafting-better-code-reviews.md @@ -6,13 +6,13 @@ # 建立更好的代码审查制度 # -*来自Rails 2017开发者大会中的一段演讲* +### 来自Rails 2017开发者大会中的一段演讲 ### -人与科技之间的交互部分总是那么忽明忽暗,难以捉摸。对于以 *开发科技产品* 为职业的人来说,更是如此。作为一个资深码农,我在代码审查的时候对于这一点的感触特别明显。 +人与科技之间的交互部分总是那么忽明忽暗,难以捉摸。对于*开发科技产品*的人来说,更是如此。作为一个资深码农,我在代码审查的时候对于这一点的感触特别明显。 -大多数开发者们习惯于把他们的代码看成一种艺术品,就好比画家看待自己的画一样,我们的代码总是和我们有着密不可分的关系。一直以来,我们都被教导说要做一个[无私奉献](https://blog.codinghorror.com/the-ten-commandments-of-egoless-programming/)的码农,能够在审查自己的代码的同时也能审查同事们写出来的代码。其实我们都知道这样的审查是对大家都有利的事,是一件[我们都应该做的事情](https://blog.codinghorror.com/code-reviews-just-do-it/),而且很多人都一直在做这件事。 +大多数开发者们习惯于把他们的代码看成一种艺术品,就好比画家看待自己的画一样,我们的代码总是和我们密切相关。一直以来,我们都被教导说要做一个[利他](https://blog.codinghorror.com/the-ten-commandments-of-egoless-programming/)的码农,在代码合并到主分支之前,我们不仅要审查自己的代码,也要审查同事的代码。其实我们都知道这样的审查是对大家都有利的,是一件[我们都应该做的事情](https://blog.codinghorror.com/code-reviews-just-do-it/),而且很多人恰好已经在做这些强烈推荐的事了。 -但是谁还记得上一次我们衡量这些方法论是什么时候?我们真的能保证我们的代码审查制度是 *有效* 的吗?我们能够保证我们的代码审查制度不忘初心吗? +但是谁还记得上一次我们衡量这些方法论是什么时候?我们真的能保证我们的代码审查制度是**有效**的吗?我们能够保证我们的代码审查制度不忘初心吗? 如果答案是不,那我们如何才能解决这个问题呢? @@ -20,19 +20,19 @@ © geek & poke, [http://geek-and-poke.com](http://geek-and-poke.com) -### 避免和代码审查*过不去* ### +### 不要和代码审查**过不去** ### -在我们能真正完全理解代码审查的实际意义和好处之后,我们就能知道为什么会有代码审查这个传统了。网上有大量的有关代码审查的[研究](https://en.wikipedia.org/wiki/Code_review#References) ,但是我认为Steve McConnell在1993年发表的[*Code Complete*](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670) 这个才是一个比较适合我们开始讨论的地方。 +在我们能真正完全理解代码审查的实际意义和好处之后,我们就能知道为什么会有代码审查这个传统了。网上有大量的有关代码审查最佳实践的[研究](https://en.wikipedia.org/wiki/Code_review#References),不过我建议可以从 Steve McConnell 在[**代码大全**](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670) 中的相关研究入手(该书于 1993年出版)。 In his book, he describes code reviews, and what function they *ought* to serve. He writes: -在他的书中,对于代码审查制度*应该有*的功能描述,他写下了下面这些话: +在他的书中,对于代码审查制度**应该**起到的作用,他写下了下面这些话: -> 管理软件工程的一个重点就是找出在“最低价值”的问题。换句话说,就是找出最容易解决的问题。为了达到这样的一个预期,我们可以使用“质量门”这样一个理念,也就是周期性的测试或者审查来决定是否应该进行到开发的下一阶段。 +> 管理软件工程过程中的一部分就是抓住问题“最低价值”的阶段,即在已投入投资最少且花费最少去解决问题的时机。为了达到这样的一个预期,我们可以使用“质量门”这样一个理念,也就是周期性地测试或者审查来决定是否应该进行到开发的下一阶段。 -McConnell的代码审查研究中最重要的理念就是代码集体拥有制度。具体就是所有代码都是被一组贡献者们所拥有的,这些贡献者们能平等的对代码进行查看和更改。 +McConnell 的代码审查研究中最重要的理念就是"代码集体拥有制度"。所有代码都是被一组贡献者们所拥有的,这些贡献者们能平等地对代码进行查看和更改。 -> 代码审查制度最初是为了帮助我们共同维护一个软件。换句话说,就像我们同时持股一家公司时我们会做质量检测以确定产品的可行性。 +> 代码审查制度的初衷是它会帮助我们获得我们所创造软件的共同所有权。换句话说,通过参与控制产品质量的方式,我们每个人都会成为开发过程中的股东。 McConnell在他的书中提出了很多种不同的代码审查制度的方式和流程,这些方式都可以被任何一个团队采用在日常的工作流当中。强烈推荐McConnell的 *Code Complete* ,真的非常赞。但是这边的话我们就简短的说3种来帮助理解。 @@ -40,7 +40,7 @@ McConnell在他的书中提出了很多种不同的代码审查制度的方式 3人审查制度是一个比较长的审查流程,耗时基本都在1小时左右。整个流程会包括一个公司的把关人(扛把子),代码作者(程序猿),和一个审查员(产品狗)。 -当大家能正确打开这个审查制度的时候,研究表明这个审查可以抓住一个程序 *60%的问题* 。根据McConnell的研究,相比于不怎么流程化的代码审查,这个制度平均每1000行代码能减少20%到30%的错误。 +当这个审查制度被有效使用时,它通常能发现这个程序 **60%** 的问题(不管是程序缺陷还是错误)。。根据McConnell的研究,相比于不怎么流程化的代码审查,这个制度平均每1000行代码能减少20%到30%的错误。 #### **2. 带着一起过** #### @@ -58,7 +58,7 @@ McConnell的研究发现了下面这些有关于审查小片段的事实: McConnell的一组组数据似乎在告诉我们作为一个开发团队我们应该构造一些这三种代码审查的融合来进行开发的推进。 -然而,McConnell的书是在1993年写的。时至今日,我们的工作流程早就已经更新换代了,同事之间互相检查的方法论也随之更新。但是我们现在对于代码审查的构造足够合理完整吗?我们应该如何把*理论*上的东西运营到*实际*中去? +然而,McConnell的书是在1993年写的。时至今日,我们的工作流程早就已经更新换代了,同行审查也随之更新。但是我们现在对于代码审查实行的方法足够合理完整吗?我们应该如何把**理论**上的东西运用到**实际**中去? To find the answer to these questions, I did what any determined (but a little unsure of where to start) developer would do: I asked the Internet! 为了解答我的问题,我做了大多数码农会做的事:Google一下 @@ -70,7 +70,7 @@ To find the answer to these questions, I did what any determined (but a little u ### 程序猿们如何看待代码审查 ### -在我对这个调查进一步阐述之前,我想先说点什么:*我不是一个数据科学家* (我希望我是,但是我也许在处理这篇文章反馈的时候能更加得心应手,或许我连在R语言里画个图都困难)。还有一点,就是说我的数据集其实是非常有限的。首先这个数据是我自己在推特上选过来的,然后的话数据是来自一个基于 branch/pull request的团队的。 +在我对这个调查进一步阐述之前,我想先说点什么:*我不是一个数据科学家* (我希望我是,但是我也许在处理这篇文章反馈的时候能更加得心应手,或许我用R语言画图还过得去)。还有一点,就是说我的数据集其实是非常有限的。首先这个数据是我自己在推特上选过来的,然后的话数据是来自一个基于 branch/pull request的团队的。 好,那么重点来了:*程序猿们是到底如何看待代码审查的?* @@ -169,7 +169,7 @@ The main themes when it came to the *substance* of a code review could be summar 1. 反馈过度注重语法和习惯的问题,导致让双方都非常不爽。代码习惯与风格和代码功能错误根本就是两回事儿。 2. 说话方式也很重要。过激的语言会打消对方的自信心,对团队也不是好事。 -### 我们如何能够做的更好? ### +### 如何做得更好? ### 也许这些数据不能我们最完整,最细致,或者最精准的代表我们代码审查的结构,但是我们还是能学到一些东西:我们能够回顾并检查我们团队的甚至整个社区的代码审查流程。 @@ -188,7 +188,7 @@ The main themes when it came to the *substance* of a code review could be summar 这边是一些能够快速帮助提高代码审查感受的一些技巧: -- 使用 [linters](https://github.com/showcases/clean-code-linters) 或者其他的代码构成器来避免PR时的语法格式问题。 +- 使用 [linters](https://github.com/showcases/clean-code-linters) 或者其他的代码分析工具来避免语法问题。 - 使用 [Github 的模板](https://quickleft.com/blog/pull-request-templates-make-code-review-easier/)来生产每个PR。在发PR的时候带上更改列表对于作者和审查者都非常有帮助。 - 在发PR的时候可以加个截图来帮助那些不是很熟悉的人理解问题。 - 提高commits的信息量,尽量语言短小精湛。 @@ -200,15 +200,15 @@ The main themes when it came to the *substance* of a code review could be summar 警告:前方高能,内容可能有一定难度 -- ***能够理解你的团队。*** 这项任务一般都由高级工程师们来承担,希望大家对于新人们能多多帮助。 +- **能够理解你的团队。** 这项任务一般都由高级工程师们来承担,希望大家对于新人们能多多帮助。 [![Markdown](http://i1.piimg.com/1949/36f270199929bb1e.png)](https://twitter.com/sarahmei) -- ***更加委婉的交流。*** 这意味着在发评论之前思考自己的言行是否妥当,是否会让其他人很不爽。在公众下批评别人是一件非常不好的事情。相比,有个私人对话会好很多。 +- **更加委婉的交流。** 这意味着在发评论之前思考自己的言行是否妥当,是否会让其他人很不爽。在公众下批评别人是一件非常不好的事情。相比,有个私人对话会好很多。 [![Markdown](http://i1.piimg.com/1949/51bfec74a7cf1e42.png)](https://twitter.com/j3) -- ***进行一次口头交流。*** 带着整个团队坐下来,到slack(美国的一种团队交流软件)上开个新的频道,让大家能够匿名的交流(选择最适合的即可)。在匿名的情况下大家都愿意说真话。 +- **进行一次口头交流。** 带着整个团队坐下来,到slack(美国的一种团队交流软件)上开个新的频道,让大家能够匿名的交流(选择最适合的即可)。在匿名的情况下大家都愿意说真话。 我把最重要的一点放在最后,因为当你还有耐心读到这里的时候,说明你真的想要更改一下你们的代码审查结构了,这是个好事儿。团队交流真的非常重要,这几乎是每个团队必须跨出的一步如果他们想要提升代码审查制度的话。 @@ -216,7 +216,7 @@ The main themes when it came to the *substance* of a code review could be summar > 理论上来说,我很喜欢代码审查。事实上,这个东西取决于构建这个流程的团队。 -#### 更多链接 #### +#### 相关资源 #### 如果你想看一个更加大的匿名的反馈,你可以看下面这个有关于这个项目的网站: @@ -224,7 +224,7 @@ The main themes when it came to the *substance* of a code review could be summar #### 感谢 #### -首先我很想感谢一路上一直支持我的工程师朋友们,感谢你们的答案和接受我的调查。 +首先感谢一路上一直支持我的工程师朋友们,感谢你们投入时间和精力参与我的调查。 A huge thank you to [Kasra Rahjerdi](https://medium.com/@jc4p), who helped me analyze the responses to my survey and created many of the graphs in this project. 非常感谢 [Kasra Rahjerdi](https://medium.com/@jc4p) 帮我整理反馈并且生成那么多的图像。 From 95fe976d559fe7b6e3cfa54e6396d855026a03db Mon Sep 17 00:00:00 2001 From: gy134340 Date: Sun, 21 May 2017 18:11:50 +0800 Subject: [PATCH 10/59] =?UTF-8?q?=E5=88=A9=E7=94=A8=E2=80=9CImmutability?= =?UTF-8?q?=EF=BC=88=E4=B8=8D=E5=8F=AF=E5=8F=98=E6=80=A7=EF=BC=89=E2=80=9D?= =?UTF-8?q?=E7=BC=96=E5=86=99=E6=9B=B4=E4=B8=BA=E7=AE=80=E6=B4=81=E9=AB=98?= =?UTF-8?q?=E6=95=88=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...by-leveraging-the-power-of-immutability.md | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md index 08b6ba79a99..656ce8b3170 100644 --- a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md +++ b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md @@ -2,24 +2,24 @@ ](https://medium.freecodecamp.com/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability-7862df04b7b6) > * 原文作者:[Guido Schmitz](https://medium.freecodecamp.com/@guidsen) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) -> * 译者: +> * 译者:[gy134340](https://github.com/gy134340) > * 校对者: -# Write safer and cleaner code by leveraging the power of “Immutability” # +# 利用“Immutability(不可变性)”编写更为简洁高效的代码 ![](https://cdn-images-1.medium.com/max/2000/1*eO8-0-GT5ht8CR7TdK9knA.jpeg) -Photo from [https://unsplash.com](https://unsplash.com) +图片来自[https://unsplash.com](https://unsplash.com) -Immutability is one of the building blocks of functional programming. It allows you to write safer and cleaner code. I’ll show you how you can achieve immutability through some JavaScript examples. +不可变性是函数式编程中的一部分,它允许你写更安全和简洁的代码。我将会通过一些 JavaScript 的例子来告诉你如何达到不可变性。 -**According to Wikipedia ([source](https://en.wikipedia.org/wiki/Immutable_object)):** +**根据维基( [地址](https://en.wikipedia.org/wiki/Immutable_object) ):** -> An immutable object (unchangeable object) is an object whose state cannot be modified after it is created. This is in contrast to a mutable object (changeable object), which can be modified after it is created. In some cases, an object is considered immutable even if some internally used attributes change but the object’s state appears to be unchanging from an external point of view. +> 一个不可变对象(不能被改变的对象)是指在创建之后其状态不能被更改的对象,这与在创建之后可以被更改的可变对象(可以被改变的对象)相反。在某些情况下,一个对象的外部状态如果从外部看来是未不变的,那么即使它的一些内部属性更改了,仍被视为不可变对象。 -### Immutable Arrays ### +### 不可变的数组 -Arrays are a good starting point to get a grasp of how immutability actually works. Lets take a look. +数组是抓住不可变性如何工作的一个要点。我们来看一下。 ``` const arrayA = [1, 2, 3]; @@ -32,9 +32,9 @@ console.log(arrayA); // [1, 2, 3, 4, 5] console.log(arrayB); // [1, 2, 3, 4, 5] ``` -This example assigns **arrayB** to a reference of **arrayA**, so the push method adds the value 5 into both variables. Our code mutates other values indirectly, which is not what we want to do. This violates the principle of immutability. +例子中 **arrayB** 是 **arrayA** 的引用,所以如果我们通过 push 方法向任意数组中添加一个值 5,那么就会间接影响到另外一个,这个是违反不可变性的原则的。 -We can improve our example to be immutable by using the [slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) function, and the behavior of the code is different. +我们可以通过使用 [slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) 函数以达到不可变性,进而优化我们的例子,此时代码的行为是完全不一样的。 ``` const arrayA = [1, 2, 3]; @@ -47,15 +47,15 @@ console.log(arrayA); // [1, 2, 3, 4] console.log(arrayB); // [1, 2, 3, 4, 5] ``` -This is exactly what we want. The code doesn’t mutate the other values. +这才是我们要的,代码不改变其它的值。 -Remember: When using [push](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) to add a value to an array, you are **mutating** the array. You want to avoid mutating variables because it can cause side effects in your code. The [slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) function returns a copy of the array. +记住:当使用 [push](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) 来给数组添加一个值时,你在**改变**这个数组,你想要避免值的改变因为这个可能会影响你代码里的其他部分。[slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) 会返回一个复制的数组。 -### Functions ### +### 函数 -Now you know how to avoid mutating other values. How would you write functions to be “pure”? Pure is another word to call a function that doesn’t have any side effects and will not change state. +现在你知道了如何避免改变其它的值。那要怎样写“纯”的函数呢?纯洁性是指调用一个函数同时不会产生额外的影响和改变状态的函数属性。 -Let’s look at a function that leverages the same principle from the arrays example. First we create a function that mutates another value, then we improve the function to be “pure”. +让我们看看一个函数,它利用了数组实例的相同原理。首先我们写一个会改变其它值的函数,然后我们将这个函数优化为“纯”函数。 ``` const add = (arrayInput, value) => { @@ -72,11 +72,11 @@ console.log(add(array, 4)); // [1, 2, 3, 4] console.log(add(array, 5)); // [1, 2, 3, 4, 5] ``` -So again, we are **mutating** our input which creates an unpredictable function. In the functional programming world, there is a golden rule around functions: **a function with the same input should always return the same result**. +所以再一次的,我们**改变**我们的输入来创建一个不可预测的函数。在函数式编程的世界里,有一个关于函数的铁律:**函数对于相同的输入应当返回相同的值。** -The function above violates the golden rule. Every time our **add** function is called, it mutates the **array** variable and the result is different. +上面的函数违反了这一规则,每次我们调用 **add** 方法,它都会改变**数组**变量导致结果不一样。 -Let’s see how we can change the implementation of our **add **function so it’s immutable. +让我们来看看怎样修改 **add** 函数的实现来使其不可变。 ``` const add = (arrayInput, value) => { @@ -99,37 +99,38 @@ const resultB = add(array, 5); console.log(resultB); // [1, 2, 3, 5] ``` -Now we can call our function multiple times, and expect the output to be the same, based on the input. This is because we are no longer mutating the **array** variable. We can call this function a “pure function”. +现在我们可以多次调用这个函数,然后根据相同的输入,获得相同的输出。这是因为我们不再改变 **array** 变量。我们把这个函数叫做“纯函数”。 -> **Note:** You can also use **concat**, instead of **slice** and **push**. -> So: arrayInput.concat(value); +> **注意:**你还可以使用 **concat**,来代替 **slice** 和 **push**。 +> 那样就是:arrayInput.concat(value); -We can use the [spread syntax](https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Operators/Spread_operator) , available in ES6, to shorten this function. +我们还可以使用 ES6 的[扩展语法](https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Operators/Spread_operator),来简化函数。 ``` const add = (arrayInput, value) => […arrayInput, value]; ``` -### Concurrency ### +### 并发 -NodeJS applications use a concept called concurrency. A concurrent operation means that two computations can both make progress regardless of the other. If there are two threads, the second computation doesn’t need to wait for the completion of the first one in order to advance. +NodeJS 的应用有一个叫并发的概念,并发的操作是指两个计算可以同时的进行而不用管另外的一个。如果有两个线程,第二个的计算不需要等待第一个的完成。 ![](https://cdn-images-1.medium.com/max/800/1*LS1VkNditQwYMJvtIPAhdg.png) -Visualization of a concurrent operation +可视化的并发操作 -NodeJS makes concurrency possible with the event-loop. The event-loop repeatedly takes an event and fires any event handlers listening to that event one at a time. This model allows a NodeJS application to process a huge amount of requests. If you want to learn more, read [this article about the event-loop](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick). +NodeJS 用事件循环机制使并发成为可能。事件循环循环重复接收事件,并对每个事件添加监听。这个模型允许 NodeJS 的应用处理大规模的请求。如果你想学习更多,读一下[这篇关于事件循环的文章](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick)。 -What does immutability have to do with concurrency? Since multiple operations can change a value outside of the function’s scope in a concurrent way, this creates unreliable output and causes unexpected results. Be aware of a function that mutates variables outside of its scope, as this can be really dangerous. +不可变性跟并发又有什么关系呢?由于多个操作可能并发的改变函数的作用域的值,这将会产生不可靠的输出和导致意向不到的结果。明确函数是否改变它作用域之外的值,因为这可能真的会很危险。 -### Next steps ### +### 下一步 -Immutability is an important concept to understand on your journey to learn functional programming. You might want to take a look at [ImmutableJS](https://facebook.github.io/immutable-js), written by developers at Facebook. The library provides certain immutable data structures like **Map**, **Set**, and **List**. +不可变性是你学习函数式编程中重要的概念。你也许想了解一下由 Facebook 开发者写的 [ImmutableJS](https://facebook.github.io/immutable-js),这一个库提供正确的不可变数据结构比如说 **Map**、**Set**、和 **List**。 [![](http://i2.muimg.com/1949/d4d40e047da813b5.png)](https://medium.com/@dtinth/immutable-js-persistent-data-structures-and-structural-sharing-6d163fbd73d2) -Click the 💙 below so other people will see this article here on Medium. Thanks for reading. +点击 💙 让更多的人可以在 Medium 上看见这篇文章,感谢阅读。 --- > [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 + From 7cc8cb636a0ced86550842a56ad206f24f76a63f Mon Sep 17 00:00:00 2001 From: yifili09 Date: Fri, 19 May 2017 04:36:37 -0500 Subject: [PATCH 11/59] Add proofreaders' info and alter according to their comments --- TODO/boring-design-systems.md | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/TODO/boring-design-systems.md b/TODO/boring-design-systems.md index 9a979426612..019628c939f 100644 --- a/TODO/boring-design-systems.md +++ b/TODO/boring-design-systems.md @@ -2,29 +2,29 @@ > * 原文作者:[JOSH CLARK](https://bigmedium.com/about/josh-clark.html) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 译者:[Nicolas(Yifei) Li](https://github.com/yifili09) -> * 校对者: +> * 校对者:[SareaYu](https://github.com/SareaYu), [yzgyyang](https://github.com/yzgyyang) -# 最激动人心的视觉系统其实是最枯燥乏味的! # +# 最激动人心的视觉系统其实是最枯燥乏味的 # [![点击放大](https://bigmedium.com/bm.pix/normcore-lego-center.orig-250.jpg)](https://bigmedium.com/bm.pix/normcore-lego-center.jpg) -让我们欢迎中性舒适的视觉系统 +让我们欢迎中性和舒适的视觉系统 我们正在构建另一个企业级视觉系统,并且我们极力把它变得枯燥。 -[Brad Frost](http://bradfrost.com), [Dan Mall](http://superfriend.ly) 和我刚刚开始帮助一家大型企业快速设计一个视觉系统来管理支配他们众多的互联网产品。这家公司有着近 20 多年的数字产品的经验和大量的产品团队。长久以来,公司有关用户体验, 用户界面和用于巩固技术的接口已经疏于同步。我们的目标是赋予产品团队一个一致性的设计语言和模式库,它可以解决常见的视觉、用户体验和背后的技术问题。 +[Brad Frost](http://bradfrost.com), [Dan Mall](http://superfriend.ly) 和我刚刚开始帮助一家大型企业在他们众多的互联网产品上设计一个视觉系统。这家公司有着近 20 多年的数字产品的经验和大量的产品团队。长久以来,公司有关用户体验,用户界面和用于巩固技术的接口已经疏于同步。我们的目标是赋予产品团队一个一致性的设计语言和模式库,它可以解决常见的视觉、用户体验和技术问题。 -对于和这个项目类似的情况来说,它会让你忍不住要抛弃现有的内容,然后使用最新的设计组件全新开始。『 如果我们准备建立标准,』(此时) 恶魔会在你身边耳语道,『 那么,让我们彻底摆脱所有的旧牵绊。让我们用一个有着全新外观、时髦交互方式和闪耀的技术框架的新系统彻底结束它吧。』(或者我最爱说的:『 让我们自己来设计。』) +和这个项目类似的情况来说,它会让你忍不住要抛弃现有的内容,然后使用最新的设计组件全新开始。『 如果我们准备建立标准,』(此时) 恶魔会在你身边耳语道,『 那么,让我们彻底摆脱所有的旧牵绊。让我们用一个有着全新外观、时髦交互方式和闪耀的技术框架彻底结束它吧。』(或者我最爱说的:『 让我们自己来设计。』) -视觉系统工程师应该抵住新事物的诱惑。不要把系统设计的工作和重塑或者技术栈的检查相混淆。系统设计的模式应该很相似,甚至是枯燥乏味的。 +视觉系统工程师应该抵住新事物的诱惑,不要把系统设计的工作和重塑或者技术栈的检查相混淆。系统设计的模式应该很相似,甚至是枯燥乏味的。 这份工作不是发明,而是策划。 ## 已经解决的问题和已解决的科学 ## -设计系统包含了机构内的知识。他们提供了针对设计问题的设计方案,这些解决方案都已经过测试和验证。当这些解决方案被统一的视觉语言和用户体验指导方案组合的时候,它们就代表了组织机构或者平台应该有的良好设计。 +设计系统包含了机构内的常用知识。他们为设计中的问题提供了已经进过测试和验证的解决方案。当这些解决方案被统一的视觉语言和用户体验指导方案组合的时候,它们就代表了组织机构或者平台应该有的良好设计。 -对于和这个类似的项目来说,我们**一次一次地**和客户端合作伙伴一起协作来确定他们团队的设计问题直到他们满意。之后,确定和提出对这些问题的最优解决方案。 +对于和这个类似的项目来说,我们**一次一次地**和客户端合作伙伴一起来确定他们团队的设计问题直到他们满意。之后,确定和提出对这些问题的最优解决方案。 问题越常见越好。设计系统应该优先考虑常见的东西。 @@ -32,29 +32,29 @@ [![点击放大](https://bigmedium.com/bm.pix/lego-office.orig-250.jpg)](https://bigmedium.com/bm.pix/lego-office.jpg) -痛点在哪里? 我们正尝试从设计师和开发人员处定位这些重复的任务。 +痛点在哪里?我们正尝试从设计师和开发人员处定位这些重复的任务。 -这个工作从很多用户调研开始,和其他产品一样。我们直面公司的程序员,设计师和产品经理们来定位重复的任务和发掘重复制造的轮子。我们进行了对该公司网络产品的深度研究并尝试找到应对这些问题的最优解决方案。我们也对该公司不一致的视觉风格列出清单,并开始重新强化一个通用的视觉语言。 +和其他产品一样,这个工作从很多用户调研开始。我们直面公司的程序员、设计师和产品经理们来识别重复的任务和发掘重复使用的方法。我们进行了对该公司网络产品的深度研究并尝试找到应对这些问题的最优解决方案。我们也对该公司不一致的视觉风格列出清单,并开始重新强化一个通用的视觉语言。 在这个流程的最后,我们将会为该公司最**有利**的商业解决方案制作一个设计的参考。其中会包含很多干货而非那些看似有用的知识。它会很有用。 -## 创新性的设计来自于那些 『 枯燥 』 的设计 ## +## 创新性的设计来自于那些 『枯燥』 的设计 ## 当设计系统很枯燥乏味的时候,它能释放设计师和开发人员做更多新的东西,去解决新的问题。**设计系统解决了乏味的事情,所以设计师和开发人员就不用操心了。** -不必第 15 次的重复设计卡片的样式,因为它已经被实现了。产品团队能够把时间和精力放到如何创建新的体验,新的算法和新的商业逻辑上。 +不必第 15 次的重复设计卡片的样式,因为它已经被实现了。产品团队应该把时间和精力放到如何创建新的体验、新的算法和新的商业逻辑上。 [![点击放大](https://bigmedium.com/bm.pix/scientist.orig-250.jpg)](https://bigmedium.com/bm.pix/scientist.jpg) 当设计系统消减了了那些枯燥问题的时候,那些设计师和开发人员们才能放手去从事新技术和高阶的问题。 -这也意味着一个不错的设计系统比能提供很多按钮、颜色板和样例代码做的更多。这些组件都是常见的。奇妙的事情是伴随着他们出现在指导手册中。一个伟大的设计系统会对某些特定情况建议使用哪个设计模式(连同为什么和怎么做一起)。这将仅聚合用户接口组件推向了一个完整的设计模式。设计师对每天公司日常的问题都有了现成的解决方案。 +这也意味着一个不错的设计系统比能提供很多按钮、颜色板和样例代码做的更多。这些组件都是常见的。奇妙的事情是伴随着他们出现在指导手册中。一个伟大的设计系统会对某些特定情况建议使用哪个设计模式(连同为什么和怎么做一起)。这将集合的用户接口组件推向了一个成熟的设计模式。设计师对每天公司日常的问题都有了现成的解决方案。 『 任何时候考虑,比如 「 这里应该展示一个弹出窗口还是提示框?」这类问题都是浪费时间。』这是一位设计经理在这个最近项目上的调研过程中告诉我们的。 ## 更快,更快 ## -所有这些都能带来速度上大大的提升。[Big Medium 公司](https://bigmedium.com) 帮助一个重要的零售商搭建并且运行起了他们的设计系统,并且结果出人意料。『 相比之前的工作,我们的速度提升了 10 倍,』团队经理如实汇报道,『 也就是说,我们能仅用四分之一的员工完成 10 倍的高质量的工作。』 +所有这些都能带来速度上大的提升。[Big Medium 公司](https://bigmedium.com) 帮助一个重要的零售商搭建并且运行起了他们的设计系统,并且结果出人意料。『 相比之前的工作,我们的速度提升了 10 倍,』团队经理如实汇报道,『 也就是说,我们能仅用四分之一的员工完成 10 倍的高质量的工作。』 [![点击放大](https://bigmedium.com/bm.pix/before-after-design-system.orig-250.png)](https://bigmedium.com/bm.pix/before-after-design-system.png) @@ -64,7 +64,7 @@ 有些设计师和开发人员常常小心谨慎地对待设计系统或者模式库。尽管大部分人重视一致性的需求,但他们担心一个设计系统会太过教条和有限制性,这会阻碍创新。『 设计系统就好像公共交通工具: 对其他人来说,它是一个好主意。』一位百感交集的设计师如是说道。 -产品团队应做正确的事,尽管如此,设计系统其实是鼓励创新的。设计师和开发人员能依赖它创建、提高,也可以偶尔替换设计系统中的建议。 +设计系统其实是鼓励产品团队创新,这应当是正确的。设计师和开发人员能依赖它创建、提高,也可以偶尔替换设计系统中的建议。 它应该是一个良性循环。新的理念应该在新的产品上进行尝试和测试验证。当好想法被证明是可行的时候,它们应该被采纳并且加入进设计系统中,以便未来的产品能获益。这些最新的解决方案都会赫然变成新常态。 @@ -89,15 +89,15 @@ 在 Dan 的暗喻中,你公司的设计系统就是你的设计正典。其他人公布、创建的应用程序和那些新的设计模式都基于它(比如,『 衍生的宇宙 』)。其中的有些设计模式将会是很有用出的,并且与你公司好的设计步调一致,它们也会变成正典。 -这就是设计系统和量产产品之间的良性循环。如果你是一位设计系统的经理,你将会成为这个故事中的 `George Lucas`。当然你将需要更加谦虚。 +这就是设计系统和量产产品之间的良性循环。如果你是一位设计系统的经理,你将会成为这个故事中的 George Lucas 。当然你将需要更加谦虚。 ## 谦卑的工作 ## -成功的设计系统不断为许多设计师、开发人员、产品经理、当然也少不了终端用户服务。 +成功的设计系统不断为许多设计师、开发人员和产品经理,当然也少不了终端用户服务。 这种服务精神意味着设计系统的维护者们切勿浮想联翩,需要握紧手中的线。一个设计系统不是崇尚前沿科技的地方,而是汇集成功解决方案的地方。 -打造一个设计系统是为其他人的创新和猜想扫清道路。那就意味着枯燥乏味的内容永远不会变得令人兴奋。 +打造一个设计系统是为其他人的创新和猜想扫清道路。那就意味着枯燥乏味的内容从未如此令人兴奋。 **你的机构是否在全力对付不一致性的接口和重复的设计工作? Big Medium 帮助大型企业通过设计系统规划大型设计。需要工作讨论会、执行会议或者让我们参与设计,[请联系我们](https://bigmedium/com/hire)** From b0837b90a72a000b0fca6c07018fc7fcf93899c7 Mon Sep 17 00:00:00 2001 From: yifili09 Date: Sun, 21 May 2017 23:30:52 -0500 Subject: [PATCH 12/59] hope to be finalized --- TODO/boring-design-systems.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TODO/boring-design-systems.md b/TODO/boring-design-systems.md index 019628c939f..0a043a2d9b2 100644 --- a/TODO/boring-design-systems.md +++ b/TODO/boring-design-systems.md @@ -12,7 +12,7 @@ 我们正在构建另一个企业级视觉系统,并且我们极力把它变得枯燥。 -[Brad Frost](http://bradfrost.com), [Dan Mall](http://superfriend.ly) 和我刚刚开始帮助一家大型企业在他们众多的互联网产品上设计一个视觉系统。这家公司有着近 20 多年的数字产品的经验和大量的产品团队。长久以来,公司有关用户体验,用户界面和用于巩固技术的接口已经疏于同步。我们的目标是赋予产品团队一个一致性的设计语言和模式库,它可以解决常见的视觉、用户体验和技术问题。 +[Brad Frost](http://bradfrost.com), [Dan Mall](http://superfriend.ly) 和我刚刚开始帮助一家大型企业在他们众多的互联网产品上设计一个视觉系统。这家公司有着近 20 多年的数字产品的经验和大量的产品团队。长久以来,公司有关用户体验、用户界面和用于巩固技术的接口已经疏于同步。我们的目标是赋予产品团队一个一致性的设计语言和模式库,它可以解决常见的视觉、用户体验和技术问题。 和这个项目类似的情况来说,它会让你忍不住要抛弃现有的内容,然后使用最新的设计组件全新开始。『 如果我们准备建立标准,』(此时) 恶魔会在你身边耳语道,『 那么,让我们彻底摆脱所有的旧牵绊。让我们用一个有着全新外观、时髦交互方式和闪耀的技术框架彻底结束它吧。』(或者我最爱说的:『 让我们自己来设计。』) @@ -22,13 +22,13 @@ ## 已经解决的问题和已解决的科学 ## -设计系统包含了机构内的常用知识。他们为设计中的问题提供了已经进过测试和验证的解决方案。当这些解决方案被统一的视觉语言和用户体验指导方案组合的时候,它们就代表了组织机构或者平台应该有的良好设计。 +设计系统包含了机构内的常用知识。他们为设计中的问题提供了已经过测试和验证的解决方案。当这些解决方案被统一的视觉语言和用户体验指导方案组合的时候,它们就代表了组织机构或者平台应该有的良好设计。 对于和这个类似的项目来说,我们**一次一次地**和客户端合作伙伴一起来确定他们团队的设计问题直到他们满意。之后,确定和提出对这些问题的最优解决方案。 问题越常见越好。设计系统应该优先考虑常见的东西。 -此时此刻,我们优先考虑数据入口,表单的验证和信息传递。这些并不是完美的设计体验,但是它们却是每个公司业务的重中之重,也是客户们花费时间最多的地方。这也是为什么枯燥乏味的事情却令人兴奋的地方:你如何对每天的设计更好的扩展且保证一致性? +此时此刻,我们优先考虑数据入口、表单的验证和信息传递。这些并不是完美的设计体验,但是它们却是每个公司业务的重中之重,也是客户们花费时间最多的地方。这也是为什么枯燥乏味的事情却令人兴奋的地方:你如何对每天的设计更好的扩展且保证一致性? [![点击放大](https://bigmedium.com/bm.pix/lego-office.orig-250.jpg)](https://bigmedium.com/bm.pix/lego-office.jpg) From 9d5925ad2765e3827f4863d361adb5f21c0ab110 Mon Sep 17 00:00:00 2001 From: sunxinlei Date: Mon, 22 May 2017 18:51:00 +0800 Subject: [PATCH 13/59] =?UTF-8?q?=E5=88=9D=E7=A8=BF=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/rearchitecting-airbnbs-frontend.md | 130 ++++++++++++------------ 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/TODO/rearchitecting-airbnbs-frontend.md b/TODO/rearchitecting-airbnbs-frontend.md index 86b5f0bfa97..d6fe13c91b0 100644 --- a/TODO/rearchitecting-airbnbs-frontend.md +++ b/TODO/rearchitecting-airbnbs-frontend.md @@ -6,7 +6,7 @@ # Airbnb 的前端重构 # -概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 端的架构。本文将讨论:(1)催生一些变化的产品驱动因素,(2)摆脱遗留的 Rails 解决方案的一些步骤,(3)一些新技术栈的关键性支柱。彩蛋:我们将讨论接下来要做的事。 +概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 端的架构。本文将讨论:(1)催生一些变化的产品驱动因素,(2)我们如何一步步摆脱遗留的 Rails 解决方案,(3)一些新技术栈的关键性支柱。彩蛋:我们将讨论接下来要做的事。 Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们流量最高的页面。近十年来,工程师们一直在发展、加强、和优化 Rails 输出页面的方式。 @@ -30,39 +30,39 @@ Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们 再标签页之间切换的未来概念,考虑异步加载内容 -要开发这种类型的体验,我们需要摆脱传统的页面切换方法,最后我们结束了对我们的前端代码的基本重构。 +要开发这种类型的体验,我们需要摆脱传统的页面切换方法,最终我们兴奋地全面重构了前端代码。 -[Leland Richardson](https://medium.com/@intelligibabble) [最近在 React Conf 大会上发表了关于 React Native 的存在于高访问量 native 应用中的“褐色地带”。 ](https://www.youtube.com/watch?v=tWitQoPgs8w)。这篇文章将会探讨如何在类似的约束下进行强制性升级,不过是在 web 端。如果你遇到类似的情况,希望对你有帮助。 +[Leland Richardson](https://medium.com/@intelligibabble) [最近在 React Conf 大会上发表了关于 React Native 的存在于高访问量 native 应用中的“褐色地带”。 ](https://www.youtube.com/watch?v=tWitQoPgs8w)这篇文章将会探讨如何在类似的约束下进行强制性升级,不过是在 web 端。如果你遇到类似的情况,希望对你有帮助。 ### 从 Rails 之中解脱 ### 在我们的烧烤开火之前,因为我们的线路图上存在所有有趣的[渐进式 web 应用](https://developers.google.com/web/progressive-web-apps/)(WPA),我们需要从 Rails 中解脱出来(或者至少在 Airbnb 用 Rails 提供单独页面的这种方式)。 -不幸的是,就在几个月前,我们的搜索页还包含一些非常老旧的代码,像指环王一样,碰它就要小心自负后果。有趣的事实:我曾尝试用一个简单的 React 组件替换一个 Rails presenter 备份过的小巧的 [Handlebars](http://handlebarsjs.com/) 模板,突然很多完全不相关的部分都崩掉了——甚至 API 响应都除了问题。原来,presenter 正在改变后备 Rails 模式,多年来即使在 UI 没有渲染的时候,它也影响着所有的下游数据。 +不幸的是,就在几个月前,我们的搜索页还包含一些非常老旧的代码,像指环王一样,触碰它就要小心自负后果。有趣的事实:我曾尝试用一个简单的 React 组件替换一个 Rails presenter 备份过的小巧的 [Handlebars](http://handlebarsjs.com/) 模板,突然很多完全不相关的部分都崩掉了——甚至 API 响应都除了问题。原来,presenter 改变了后备 Rails 模型,多年来即使在 UI 没有渲染的时候,它也影响着所有的下游数据。 -简而言之,我们在这个项目中,像 Indiana Jone 用灵魂交换一袋沙子,突然间寺庙开始崩溃,我们正在从一块巨石上跑。 +简而言之,我们在这个项目中,像 Indiana Jone 用自己的宝物交换了一袋沙子,突然间庙宇开始崩塌,我们正在从石块中奔跑。 #### 第 1 步: 调整 API 数据 #### -When Rails is server-rendering your page, you can get away with throwing data at your server-rendered React components any way you like. Controllers, helpers, and presenters can produce data of any shape, and even as you migrate sections of the page to React, each component can consume whatever data it requires. +当使用 Rails 在服务器端渲染页面时,你可以用任何你喜欢的方式把数据丢给服务器端的 React 组件。Controllers、helpers 和 presenters 能生成任何形式的数据,甚至当你把部分页面迁移到 React 时,每个组件都能处理它所需的任何数据。 -But once you endeavor to render the route client-side, you need to be able to request the data you need dynamically and in a predetermined shape. In the future, we may crack this problem with something like [GraphQL](http://graphql.org/), but let’s set that aside for now, as it wasn’t an option when this refactor took place. Rather, we chose to align on a “v2” of our API, and we needed all our components to begin consuming that canonical data shape. +但一旦你想渲染客户端路由,你需要能够以预定的形式动态请求所需的数据。将来我们可能用类似 [GraphQL](http://graphql.org/) 的东西解决这个问题,但是现在暂且把它放到一边吧,因为这件事和重构代码没太大关系。相反,我们选择在我们的 API 的 “v2” 上进行调整,我们需要我们所有的组件来开始处理规范的数据格式。 -If you find yourself in similar waters with a large application, you might find as we did that planning for the migration of existing server-side data plumbing was the easy part. Simply step through any place Rails is rendering a React component, and ensure that data inputs are API shapes. You can further validate compliance with API V2 shapes used as React PropTypes on the client. +如果你发现你自己和我们情况类似并且是一个大型的应用,你可能发现我们像我们这样做,规划迁移现有的服务器端数据管道是很容易的。简单地在任何地方用 Rails 渲染一个React组件,并确保数据输入是 API 所规定的类型。你可以用客户端的 React PropTypes 来进一步验证数据类型是否与 API v2 一致。 -The tricky bit for us was working with all the teams who interact with the guest booking flow: our Business Travel, Growth, and Vacation Rentals teams; our China and India market-specific teams, Disaster Recovery…the list goes on, and we needed to reeducate all these folks that even though it was technically possible to pass data directly to the component being rendered (“yes, I understand it’s just an experiment, but…”), *all data* needs to go through the API. +对我们来说棘手的问题是和那些参与客户预定流程交互的团队协作:商业旅游、发展、度假租赁团队;中国和印度市场团队,灾难恢复团队...等等,我们需要重新培训所有这些人,即使在技术上可以将数据直接传递到正在呈现的组件上("是的,我明白,这仅仅是一种实验,但是..."),所有的数据都要通过 API。 #### 第 2 步: 非 API 数据: 配置、试验、惯用语、本地化、 国际化… #### -There is a separate class of data from what we would think of as API data, and it includes application config, user-specific experiment assignment, internationalization, localization, and similar concerns. Over the years, Airbnb has built up some incredible tooling to support all these functions, but the mechanisms for delivering them to the Frontend were a bit under-baked (or possibly fully-baked when built, before the ground began shifting under foot!). +有一类独特的数据和我们设想的 API 化的数据不同,包括应用配置,用户试验任务,国际化,本地化等等类似的问题。近年来,Airbnb 已经建立了一套难以置信的工具来支持这些功能,但是把这些数据传送到前端的机制就不那么令人愉快了(在革命开始之前,或许就已经很蹩脚了!)。 -We use [Hypernova](https://www.npmjs.com/package/hypernova) to server-render React, but before we went deep on this refactor, it was a bit nebulous whether experiment delivery in a React component would blow up during server-rendering or if string translations available on the client would all be reliably available on the server. Critically, if the server and client output don’t match to the bit, the page not only flashes the diff but also re-renders the entire page after load, which is terrible for performance. +我们使用 [Hypernova](https://www.npmjs.com/package/hypernova) 来服务端渲染 React,但是在我们此次重构深入之前,无论服务端渲染时 React 组件中的试验交付会不会爆发或者客户端上提供的字符串转换是否都可以在服务器上可靠地使用,这些都还有点模糊。最重要的是,如果服务器和客户端输出匹配不到位,页面不仅会不断闪烁刷新 diff,还可以在加载后重新渲染整个页面,这对于性能来说很可怕。 -Worse yet, we had some magical Rails functions written long ago, for instance `add_bootstrap_data(key, value)`, which could ostensibly be called anywhere in Rails to make data available on the client globally via `BootstrapData.get(key)`(though, again, not necessarily for Hypernova). What began as a helpful utility for a small team became a source of untraceable witchcraft for a large application and team. The “data laundering” crimes became increasingly tricky to unwind, as each team owns a different page or feature, and therefore each team cultivated a different mechanism for loading config, each suiting their unique needs. +更糟糕的是,我们有很久以前写过一些神奇的 Rails 功能,比如 `add_bootstrap_data(key, value)` 表面上可以在 Rails 中的任何地方调用,通过 `BootstrapData.get(key)` 使数据在客户端的全局可用(再次强调,对 Hypernova 来说已经不必要了)。这作为小团队的一个实用程序开始成为对大团队和应用来说不可溯源的巫术。由于每个团队拥有不同的页面或功能,因此“数据清洗”变得越来越棘手,因此每个团队都会培养出一种不同的加载配置的机制,以满足其独特需求。 -Clearly, this was already breaking down, so we converged on a canonical mechanism for bootstrapping non-API data, and we began migrating all apps/pages to this handoff between Rails and React/Hypernova. +显然,这已经崩溃了,所以我们融合了一个用于引导非 API 数据的规范机制,我们开始将所有应用程序和页面迁移到 Rails 和 React/Hypernova 之间的这种切换。 ``` import React, { PropTypes } from 'react'; @@ -114,14 +114,14 @@ function withHypernovaBootstrap(App) { userAttributes, } = props; - // clear out bootstrap data on the server to avoid leaking data + // 清除服务器上的引导数据,以避免泄露数据 if (!global.document) { BootstrapData.clear(); } BootstrapData.extend(bootstrapData); ImagePaths.extend(images); - // It is not safe to call L10n.init with empty object in tests + // 在测试中用空对象调用 L10n.init 是不安全的 if (i18nInit) { L10n.init(i18nInit); } @@ -144,10 +144,10 @@ function withHypernovaBootstrap(App) { } render() { - // Ideally, we only want to pass through bootstrapData. If you have redux or alt data from - // the server to bootstrap, you can actually pass that data as a key in bootstrapData. - // - // Other props are consumed and not passed to the app. + // 理想情况下,我们只想传输 bootstrapData + // 如果你有从 redux 或 alt 数据 从服务端到 bootstrap + // 你当然可以只传输一个在 bootstrapData 中的 key + // 其他属性被处理但是不会传入应用 return ; } } @@ -163,42 +163,43 @@ function withHypernovaBootstrap(App) { export default compose(withPhrases, withHypernovaBootstrap); ``` -A canonical higher order component for bootstrapping non-API data +用于引导非 API 数据规范的更高阶的组件 -This higher order component does two very important things: -1. It receives a canonical shape of bootstrap data as a Plain Old JavaScript Object, and initializes all the supporting tooling correctly both for server-rendering and client-rendering identically. -2. It swallows everything except `bootstrapData`, another simple object which we expect ``to load into Redux to be used by children as needed (in place of `BootstrapData.get`). +这个更高阶的组件做了两件更重要的事情: -In a single shot, we eliminated `add_bootstrap_data` and prevented engineers from passing arbitrary keys through to top level React components. Order was restored to the shire, and before long we were navigating to routes dynamically in the client and rendering content of material complexity without Rails to prop it up (pun intended). +1. 它接收一个引导数据作为普通的旧对象的规范形式,并且正确地初始化所有支持的工具,用于服务器渲染和客户端渲染。 +2. 它吞噬除了一切除了 `bootstrapData` ,它是另一个简单的对象,必要时把 `` 组件传入 Redux 作为 children 使用。 + +单纯来看,我们删除了 `add_bootstrap_data`,并阻止工程师将任意键传递到顶级的 React 组件。秩序被重新恢复,以前我们在客户端中动态地导航到路由,并且渲染材料复杂的 content,而不需要Rails来支持它。 ### 进击的前端 ### -Server rework in hand, we now turn our gaze to the client. +服务端的重构已经有了头绪,现在我们把目光转向客户端。 #### 懒加载的单页面应用 #### -Gone are the days, friends, of the monster Single Page App (SPA) with a gruesome loading spinner on initialization. This dreaded loading spinner was the objection many folks raised when we pitched the idea of client-side routing with React Router. +那段日子已经过去了,朋友们,初始化时带着可怕 loading 的巨型单页面应用(SPA)已经不复存在了。当我们提出用 React Router 做客户端路由的方案时,可怕的 loading 是很多人提出拒绝的理由。 ![](https://cdn-images-1.medium.com/max/800/1*O2fK16vfyWaDT-IR61drPw.png) -Lazy loading of route bundles in the Chrome Timeline +在 chrome Timeline 中 route 包的懒加载 -But if you look above, you’ll see the impact of [code-splitting](https://webpack.github.io/docs/code-splitting.html) and [lazy-loading](https://webpack.js.org/guides/lazy-load-react/) bundles by route. In essence, we server render the page and deliver just the bare minimum JavaScript required to make it interactive in the browser, then we begin proactively downloading the rest when the browser is idle. +但是,如果你看到上面的内容,你就会发现[代码分割](https://webpack.github.io/docs/code-splitting.html) 和[延迟加载](https://webpack.js.org/guides/lazy-load-react/) 捆绑路由的影响。实质上,我们是在服务端渲染的页面并且仅仅传输最低限度的一部分用于在浏览器端交互的 Javascript 代码,然后我们利用浏览器的空余时间主动下载其余部分。 -On the Rails side, we have one controller for all routes delivered via the SPA. Each action is simply responsible for (1) making whatever API request the client would have made on client-side navigation, then (2) bootstrapping that data to Hypernova along with config. We went from thousands of lines of Ruby code per action (between the controller, helpers, and presenters) down to ~20–30 lines. Yahtzee. +在 Rails 端,我们有一个 controller 用于通过 SPA 交付的所有路由。每一个 action 只负责:(1)出发客户端导航中的一切请求,(2)将数据和配置引导到 Hypernova。我们把每个 action (controller、helpers 和 presenters 之间)上千行的 Ruby 代码缩减到 20-30 行。实力碾压。 -But it’s not just code that is noticeably different… +但这不仅仅是代码的不同... ![](https://cdn-images-1.medium.com/max/800/1*EpKNHdS4Xzl9fRdGekUgEA.gif) -Side-by-side comparison fetching Homes for Tokyo: Legacy page load vs client-side routing (4–5x difference) +两种方式加载东京主页的对比(4-5 倍的差距) -…now transitions between routes are smooth as butter and a step change (~5x) faster, and we can break ground on the animations featured at the beginning of this post. +...现在页面间的过渡像奶油般顺滑,并且这一步大幅提升了速度(约 5 倍)。而且我们我们可以实现文章开头的那张动画特性。 #### 异步组件 #### -Prior to React, we would render an entire page at a time, and this practice carried over into our early React days. But we use an AsyncComponent similar to [this](https://medium.com/@thejameskyle/react-loadable-2674c59de178) as a way to load sections of the component hierarchy after mount. +之前的 React ,我们需要一次渲染整个页面,我们以前的 React 都是这么做的。但现在我们使用异步组件,类似[这种](https://medium.com/@thejameskyle/react-loadable-2674c59de178)方式, mount 以后加载组件层次结构的部分。 ``` export default class AsyncComponent extends React.Component { @@ -217,7 +218,7 @@ export default class AsyncComponent extends React.Component { render() { const { Component } = this.state; - // `loader` prop unused. It is extracted so we don't pass it down to wrapped component + // `loader` 属性没有被使用。 它被提取,所以我们不会将其传递给包装的组件 // eslint-disable-next-line no-unused-vars const { renderPlaceholder, placeholderHeight, loader, ...rest } = this.props; if (Component) { @@ -232,17 +233,17 @@ export default class AsyncComponent extends React.Component { AsyncComponent.propTypes = { - // specifically loader is a function that returns a promise. The promise - // should resolve to a renderable React component. + // 注意 loader 是返回一个 promise 的函数。 + // 这个 promise 应该处理一个可渲染的组件。 loader: PropTypes.func.isRequired, placeholderHeight: PropTypes.number, renderPlaceholder: PropTypes.func, }; ``` -This is particularly useful for heavy elements that aren’t initially visible, like Modals and Panels. Our explicit goal is to ship precisely the JavaScript required to initially render the visible portion of the page and make it interactive, not one line more. This has also meant that if, for example, teams want to use D3 for a chart in a modal on a page that doesn’t otherwise use D3, they can weigh the “cost” of downloading that library as part of their modal code in isolation from the rest of the page. +这对于最初不可见的重量级元素尤其有用,比如 Modals 和 Panels。我们的明确目标是精确地提供初始化页面可见部分所需的 所需的 JavaScript,并使其可交互,而不只一行。这也意味着如果,比方说团队想使用 D3 用于页面弹窗的一个图表,而其他部分不使用 D3,这时候他们就可以权衡一下下载仓库的代码,可以把他们的弹窗代码和其他代码隔离出来。 -Best of all, it is this simple to use anywhere it is needed: +最重要的是,它可以简单地在任何需要的地方使用: ``` import React from 'react'; @@ -268,15 +269,16 @@ export default function MapAsync(props) { view raw ``` -Here we can simply swap out the synchronous version of our map for an async version, which is particularly useful on small breakpoint, where the map is displayed via user interaction with a button. Since most of these users are on phones, getting them to interactive before worrying about Google Maps comes with a tasty boost in page load time. +这里我们可以简单地把我们的同步版本的地图换成异步版本,这在小断点上特别有用,用户通过点击按钮显示地图。考虑到大多数用户用手机,在担心 Google 地图之前,让他们进入互动这样会缩短加载时的焦虑感。 + -Also, note the `scheduleAsyncLoad()` utility, which requests the bundle in advance of user interaction. Since the map is so frequently used, we don’t need to wait for user interaction to request it. Instead, we can enqueue it when you get to the Homes Search route. If the user does request it prior to download, they see a reasonable `` until the component is available. No sweat. +另外,注意 `scheduleAsyncLoad()` 的效率,在用户交互之前就要请求包。考虑到地图如此频繁的使用,我们不需要等待用户交互就去请求它。而是在用户进入主页和搜索页的时候就把它加入队列,如果用户在下载完成之前就请求了它,他们会看到一个 `` 直到组件可用。没毛病。 -The final benefit of this approach is that `HomesSearch_Map` becomes a named bundle that the browser can cache. As we disaggregate larger route-based bundles, the slowly-changing sections of the app remain untouched across updates, further saving JavaScript download time. +这种方法的最后一个好处是 `HomesSearch_Map` 成为浏览器可以缓存的命名包。当我们分解较大的基于路由的捆绑包时,应用程序中 slowly-changing 的部分在更新时保持不变,从而进一步节省了 JavaScript 下载时间。 -#### Building Accessibility into our Design Language #### +#### 构建无障碍的设计语言 #### -Doubtless it warrants a dedicated post, but we have begun building our internal component library with Accessibility enforced as a hard constraint. In the coming months, we will have replaced all UI across the guest flow that is incompatible with screen readers. +毫无疑问,它保证的是一个专有的需求,但是我们已经开始构建内部组件库,其中辅助功能被强制为一个严格的约束。在接下来的几个月中,我们将替换所有与屏幕阅读器不兼容的 UI。 ``` import React, { PropTypes } from 'react'; @@ -363,27 +365,27 @@ RoomTypeFilter.propTypes = propTypes; RoomTypeFilter.defaultProps = defaultProps; ``` -An example of building accessibility into our product through our design language system +通过我们的设计语言系统加入的无障碍设计到产品的例子 -The UI is rich enough that we want to associate a CheckBox not only with a title, but also a subtitle using `aria-describedby`. To achieve this requires a unique identifier in the DOM, which means enforcing a required ID as a prop that any calling parents need to provide. These are the types of hard constraints the UI can impose to ensure that if a component is used in the product, it is delivered with accessibility built in. +这个 UI 非常丰富,我们希望将 CheckBox 不仅与 title 相关联,还可以使用 `aria-describedby` 与 subtitle 关联。为了实现这一点,需要 DOM 中唯一的标识符,这意味着强制关联一个必须的 ID 作为任何调用方需要提供的属性。如果一个组件被用于生产,这些是 UI 是可以强制约束类型的,它提供内置的可访问性。 -The code above also demonstrates our responsive utilities HideAt and ShowAt, which allow us to dramatically alter what the user experiences at different screen sizes without having to hide and show using CSS. This leads to much leaner pages. +上面的代码也演示了我们的响应式实体 HideAt 和 ShowAt,它使我们能够大幅度地改变用户在不同屏幕尺寸下的体验,而无需使用 CSS 控制隐藏和显示。这造就了更精简的页面。 -#### Getting Surgical and Philosophical about State #### +#### 关于状态的“外科”和“哲学” #### -No Frontend post would be complete without touching on the debate about how to handle app state. +不涉及关于如何处理应用程序状态的争论的前端文章不是完整的前端文章。 -We use Redux for all API data and “globals” like authentication state and experiment configurations. Personally, I like [redux-pack](https://github.com/lelandrichardson/redux-pack) for async. Your mileage may vary. +我们使用 Redux 来处理所有的 API 数据和“全局”数据比如认证状态和体验配置。个人来讲我喜欢 [redux-pack](https://github.com/lelandrichardson/redux-pack) 处理异步,你会发现新大陆。 -However, with all the complexity on the page—particularly around Search—it doesn’t work to use Redux for low-level user interactions like form elements. We found that no matter how we optimized, the Redux loop was going to make typing in inputs feel inadequately responsive. +然而,当遇到页面上所有的复杂性——特别是围绕搜索的——对于一些像表单元素这样低级的用户交互使用 redux 就没那么好用了。我们发现无论如何优化,Redux 循环依然会造成输入体验的卡顿。 ![](https://cdn-images-1.medium.com/max/600/1*12LgecpKz8HA2e2evkYacw.png) -Our Room Type Filter (code featured above) +我们的房间类型筛选器 (代码在上面) -So we use component local state for everything the user does up until it triggers a route changes or a network interaction, and we haven’t had any problems. +所以对于用户的所有操作我们使用组件的本地状态,除非触发路由变化或者网络请求才是用 Redux,并且我们没再遇到什么麻烦。 -At the same time, I like the feel of a Redux container component, and we found that even with local state, we could build Higher Order Components that could be shared. A great example is with our filters. Search for [homes in Detroit](https://www.airbnb.com/s/Detroit--MI--United-States/homes), and you’ll find a few different panels on the page, each operating independently, that can modify your search. Across various breakpoints, there are actually dozens of components that need to know the currently-applied search filters and how to update them, both temporarily during user interaction and officially once accepted by the user. +同时,我喜欢 Redux container 组件的那种感觉,并且我们即使带有本地状态,我们依然可以构建可以共享的高阶组件。一个伟大的例子就是我们的筛选功能。搜索[在底特律的家](https://www.airbnb.com/s/Detroit--MI--United-States/homes),你会在页面上看见几个不同的面板,每一个都可以独立操作,你可以更改你的搜索条件。在不同的断点之间,实际上有几十个组件需要知道当前应用的搜索过滤器以及如何更新它们,在用户交互期间被暂时或z正式地被用户接受。 ``` import React, { PropTypes } from 'react'; @@ -480,24 +482,24 @@ export default function withFilters(WrappedComponent) { } ``` -Here we have a neat trick. Every component that needs to interact with filters can be wrapped with this HOC, and you’re done. It even comes with prop types. Each component wires into the *responseFilters* (those associated with the currently-displayed results) from Redux but keeps a local stagedFilters object available for modification. +这里我们有一个利落的技巧。每一个需要和筛选交互的组件只需被 HOC 包裹起来,你就能做到了。它甚至还有属性类型。每个组件都通过 Redux 连接到**responseFilters**(与当前显示的结果相关联的那些),并同时保有一个本地 stagedFilters 状态对象用于更改。 -By tackling state this way, interacting with our Price Slider has no impact on the rest of the page, so performance is great. But all filters panels are implemented with the same function signatures, so development is simple. +通过以这种方式处理状态,与我们的价格滑块进行交互对页面的其余部分没有影响,所以表现很好。而且但所有过滤器面板都具有相同的功能签名,因此开发也很简单。 -### What’s Next? ### +### 未来做些什么? ### -Now that the grizzly legwork of catching the Frontend up with the present is largely in hand, we can turn our attention to the future. +既然现在已经良策在手,我们可以把目光转向未来。 -- [AMP](https://www.ampproject.org/) versions of all pages in the core booking flow will lead to sub-second (in some cases) *Time To Interactive* from Google search on mobile web, and many of the the changes required to get there will drive dramatic improvements in P50/P90/P95 cold load times across mobile web and desktop web alike. -- [PWA](https://developers.google.com/web/progressive-web-apps/) functionality will lead to sub-second (in some cases) *Time To Interactive* for returning visitors and will open the door to offline-first functionality so very critical to users with flaky connections. -- Dropping the final hammer on legacy tech/frameworks will cut bundle sizes in half. It’s not flashy work, but finally ripping out jQuery, Alt, Bootstrap, Underscore, and all external CSS requests (they block rendering, and 97% of the rules are unused!) will streamline not only the code we ship, but also the footprint of what new hires need to learn as they ramp up. -- Finally, the yeoman’s work of manually bird-dogging rendering bottlenecks, async-loading code not visible at initial render, avoiding unnecessary re-renders, and reducing the cost of re-renders. These improvements are the difference between a clunky feeling app and a well-oiled machine. +- [AMP](https://www.ampproject.org/) 核心预订流程中的所有页面的 AMP 版本将会实现亚秒级(某些情况下)在手机 web 上 Google 搜索的 **可交互时间**,通过移动网络和桌面网络,所需的许多更改将在 P50 / P90 / P95 冷负载时间内实现显着改善。 +- [PWA](https://developers.google.com/web/progressive-web-apps/) 功能将实现亚秒级(在某些情况下)返回访客的**可交互时间**,并将打开离线优先功能的大门,因此对于具有脆弱网络连接的用户非常关键。 +- 将最终的锤子应用到传统的技术/框架上将会将包大小减少一半。这不是华而不实的工作,我们最终翻出 jQuery、Alt、Bootstrap、Underscore 以及所有额外的 CSS 请求(他们使渲染停滞,并且将近 97% 的规则是不会被使用!)不仅精简了我们的代码,还精简了新员工在上升时需要学习的足迹。 +- 最后,yeoman 的手动捕捉瓶颈的工作、异步加载代码在初始渲染时不可见、避免不必要的重新渲染、并降低重新渲染的成本,这些改进正是拖拉机和顶级跑车之间的区别。 -Tune in next time as we chase down these opportunities. Since so many of the wins will have immediate quantitative impact, we will try to capture some of the specific wins in subsequent posts. +下次请收听我们将追逐的这些机会的成果。因为这么多的成果会有一些数量上的冲突,我们将尽量选择一些具体的成果在下篇文章中总结。 -*Naturally, if you enjoyed reading this and thought this was an interesting challenge, we are always looking for talented, curious people to [join the team](https://www.airbnb.com/careers/departments/engineering) . Or, if you just want to talk shop, hit me up on twitter any time [@adamrneary](https://twitter.com/AdamRNeary)* +**自然,如果你欣赏本文并觉得这是一个有趣的挑战,我们一直在寻找优秀出色的人[加入团队](https://www.airbnb.com/careers/departments/engineering)。如果你只想做一些交流,那么随时可以点击我的 twitter [@adamrneary](https://twitter.com/AdamRNeary)。** -Finally, huge props to [Salih Abdul-Karim](https://twitter.com/therealsalih) and [Hugo Ahlberg](https://twitter.com/hugoahlberg) , the experience designers behind the face-melting animations I still can’t stop ogling. The list of many engineers deserving kudos for their role in this effort is indescribably long but most certainly includes Nick Sorrentino, [Joe Lencioni](https://medium.com/@lencioni) , [Michael Landau](https://medium.com/@mikeland86), Jack Zhang, Walker Henderson, and Nico Moschopoulos. +最后,深切地向 [Salih Abdul-Karim](https://twitter.com/therealsalih) 和 [Hugo Ahlberg](https://twitter.com/hugoahlberg) 两位体验设计师致敬,他们的令人动容的动画至今让我目不转睛。许多工程师在他们的领域值得赞美,作出努力人的名单难以一一列出的,但绝对包括 Nick Sorrentino、[Joe Lencioni](https://medium.com/@lencioni)、[Michael Landau](https://medium.com/@mikeland86)、Jack Zhang、Walker Henderson 和 Nico Moschopoulos. --- From 3c8dc7103b56182e7447f6d0c975bd4cc2a97167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E6=98=8E?= Date: Mon, 22 May 2017 21:23:02 +0800 Subject: [PATCH 14/59] =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../testing-mvp-using-espresso-and-mockito.md | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/TODO/testing-mvp-using-espresso-and-mockito.md b/TODO/testing-mvp-using-espresso-and-mockito.md index c02d103924d..6942d6177ca 100644 --- a/TODO/testing-mvp-using-espresso-and-mockito.md +++ b/TODO/testing-mvp-using-espresso-and-mockito.md @@ -1,14 +1,14 @@ > * 原文地址:[TESTING MVP USING ESPRESSO AND MOCKITO](https://josiassena.com/testing-mvp-using-espresso-and-mockito/) > * 原文作者:[Josias Sena](https://josiassena.com/about-me/) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) -> * 译者: -> * 校对者: +> * 译者:[skyar2009](https://github.com/skyar2009) +> * 校对者:[lovexiaov](https://github.com/lovexiaov), [GangsterHyj](https://github.com/GangsterHyj) # 使用 Espresso 和 Mockito 测试 MVP # -作为软件开发者,我们尽最大努力做正确的事情确保我们并非无能,并且让其他同事以及领导信任我们所写的代码。我们遵守最好的变成习惯、使用好的架构模型,但是有时发现要确切的测试我们所写的代码很难。 +作为软件开发者,我们尽最大努力做正确的事情确保我们并非无能,并且让其他同事以及领导信任我们所写的代码。我们遵守最好的编程习惯、使用好的架构模式,但是有时发现要确切的测试我们所写的代码很难。 -就个人而言,我发现一些开源项目的开发者非常善于打造令人惊叹的产品(可以打造任何你可以想象的应用),但是由于某些原因缺乏编写正确测试的能力,如果有的话。 +就个人而言,我发现一些开源项目的开发者非常善于打造令人惊叹的产品(可以打造任何你可以想象的应用),但是由于某些原因缺乏编写正确测试的能力,甚至一点都没有。 本文是关于如何对广泛应用的 MVP 架构模型进行单元测试的简单教程。 @@ -26,7 +26,7 @@ ![724E8fE.png](https://i2.wp.com/www.andevcon.com/hubfs/EVENTS_ASSETS/ANDEVCON/Images/Article_Images/MVP%20Mockito/724E8fE.png) -为了本文价值,我们假设这是一个价值数百万的产品,并且它现在的样子将会持续很长时间。如果一旦发生变化,我们需要立刻知晓。 +出于文章的需要,我们假设这是一个价值数百万的产品,并且它现在的样子将会持续很长时间。一旦发生变化,我们需要立刻知晓。 应用中有三部分内容:一个有应用名的蓝色工具栏,一个显示 “Hello World” 的 TextView,以及一个控制 TextView 显隐的按钮。 @@ -40,7 +40,7 @@ 如下是测试 ToolBar 的完整代码。如果你看不懂这到底是什么鬼,也没关系,后面我们一起过一下。 -``` +``` java @RunWith (AndroidJUnit4.class) public class MainActivityTest { @@ -76,11 +76,11 @@ public class MainActivityTest { } ``` -首先,我们需要告诉 JUnit 我们进行测试的类型。具体内容对应第一行内容 (@RunWith (AndroidJUnit4.class))。它这样声明,“嘿,听着,我将在真机上使用 JUnit4 进行 Android 测试”。 +首先,我们需要告诉 JUnit 所执行测试的类型。对应于第一行代码(@runwith (AndroidJUnit4.class))。它这样声明,“嘿,听着,我将在真机上使用 JUnit4 进行 Android 测试”。 -那么 Android 测试到底是什么呢?Android 测试是在 Android 设备上而非电脑上的 [Java 虚拟机 (JVM)](https://en.wikipedia.org/wiki/Java_virtual_machine) 的测试。这就意味着 Android 设备需要连接到电脑以便运行测试。这就使得测试可以访问 Android 框架功能性 APIs。 +那么 Android 测试到底是什么呢?Android 测试是在 Android 设备上而非电脑上的 [Java 虚拟机 (JVM)](https://en.wikipedia.org/wiki/Java_virtual_machine) 的测试。这就意味着 Android 设备需要连接到电脑以便运行测试。这就使得测试可以访问 Android 框架功能性 API。 -测试代码存放位置为 androidTest 目录。 +测试代码存放在 androidTest 目录。 ![android_test_directory](https://i0.wp.com/www.andevcon.com/hs-fs/hubfs/EVENTS_ASSETS/ANDEVCON/Images/Article_Images/MVP%20Mockito/gcpEaEX.png?w=442) @@ -94,33 +94,33 @@ public class MainActivityTest { ### **测试 toolbar** ### -``` +``` java onView(withId(R.id.toolbar)).check(matches(isDisplayed())); ``` 这段测试代码是找到 ID 为 “R.id.toolbar” 的 view,然后检查它的可见性。如果本行代码执行失败,测试会立刻结束并不会进行其余的测试。 -``` +``` java onView(withText(R.string.app_name)).check(matches(withParent(withId(R.id.toolbar)))); ``` -这行是说,“嘿,让我们看看是 R.id.toolbar 的孩子是否文本和 ‘R.string.app_name’ 相同的”。 +这行是说,“嘿,让我们看看是否有文本内容为 R.string.app_name 的 textView ,并且看看它的父 View 的 id 是否为 R.id.toolbar”。 最后一行的测试更有趣一些。它是要确认 toolbar 的背景色是否和应用的首要颜色一致。 -``` +``` java onView(withId(R.id.toolbar)).check(matches(withToolbarBackGroundColor())); ``` -Espresso 默认是不支持简单直接的方式进行类似测试,因此我们需要创建 [Matcher](https://developer.android.com/reference/android/support/test/espresso/matcher/package-summary.html)。Matcher 确切的说是我们前面使用的判断 view 属性是否与预期一致的工具。这里,我们需要匹配首要颜色是否与 toolbar 背景一致。 +Espresso 没有提供直接的方式来做此校验,因此我们需要创建 [Matcher](https://developer.android.com/reference/android/support/test/espresso/matcher/package-summary.html)。Matcher 确切的说是我们前面使用的判断 view 属性是否与预期一致的工具。这里,我们需要匹配首要颜色是否与 toolbar 背景一致。 -我们需要创建一个 [Matcher](https://developer.android.com/reference/android/support/test/espresso/matcher/BoundedMatcher.html) 并覆盖 matchesSafely() 方法。该方法里面的代码十分易懂。首先我们获取 toolbar 背景色,然后将至与应用首要颜色对比。如果相等,返回 true 否则返回 false。 +我们需要创建一个 [Matcher](https://developer.android.com/reference/android/support/test/espresso/matcher/BoundedMatcher.html) 并覆盖 matchesSafely() 方法。该方法里面的代码十分易懂。首先我们获取 toolbar 背景色,然后与应用首要颜色对比。如果相等,返回 true 否则返回 false。 ### **测试 TextView 的隐藏/显示** ### 在讲代码之前,我需要说下代码有点长,但是十分易读。我对代码内容作了详细注释。 -``` +``` java @RunWith (AndroidJUnit4.class) public class MainActivityTest { @@ -167,15 +167,15 @@ public class MainActivityTest { } ``` -这段代码的主旨是确认应用打开时,ID 为 “R.id.tv_to_show_hide” 的 TextView 处于显示状态,并且其显示内容为 “Hello World!” +这段代码主要功能是保证应用打开时,ID 为 “R.id.tv_to_show_hide” 的 TextView 处于显示状态,并且其显示内容为 “Hello World!” -然后检查按钮也是显示状态,并且其文案显示为 “Hide”。 +然后检查按钮也是显示状态,并且其文案(默认)显示为 “Hide”。 接着点击按钮。点击按钮十分简单,如何实现的也十分易懂。这里我们对找到相应 ID 的 view 执行 .perform() (而非 “.check”),并且在其内执行 click() 方法。perform() 方法实际是执行传入的操作。这里对应是 click() 操作。 -因为点击了 “Hide” 按钮,我们需要验证 TextView 是否真的隐藏了。具体做法是在 diDisplayed() 方法前置一个 “not()”,并且按钮文案变为 “Show”。其实这就和 java 中的 “!=” 操作符一样。 +因为点击了 “Hide” 按钮,我们需要验证 TextView 是否真的隐藏了。具体做法是在 disDisplayed() 方法前置一个 “not()”,并且按钮文案变为 “Show”。其实这就和 java 中的 “!=” 操作符一样。 -``` +``` java @RunWith (AndroidJUnit4.class) @@ -206,7 +206,7 @@ public class MainActivityTest { 如下是全部的 UI 测试代码: -``` +``` java @RunWith (AndroidJUnit4.class) public class MainActivityTest { @@ -277,7 +277,7 @@ public class MainActivityTest { ## **单元测试** ## -单元测试最大特点是在本机的 JVM 环境上运行(与 Android 测试不同)。无需连接设备,测试跑的也更快。缺点就是无法访问 Android 框架 API。总之进行 UI 之外的测试时,尽量使用单元测试而非 Android/Instrumentation 测试。测试内容运行的越快越好。 +单元测试最大特点是在本机的 JVM 环境上运行(与 Android 测试不同)。无需连接设备,测试跑的也更快。缺点就是无法访问 Android 框架 API。总之进行 UI 之外的测试时,尽量使用单元测试而非 Android/Instrumentation 测试。测试运行的越快越好。 下面我们看下单元测试的目录。单元测试的位置与 Android 测试不同。 @@ -287,7 +287,7 @@ public class MainActivityTest { ### **首先看下 presenter** ### -``` +``` java public class MainPresenterImpl extends MvpBasePresenter implements MainPresenter { @@ -316,11 +316,11 @@ public class MainPresenterImpl extends MvpBasePresenter implements MainPresenter 很简单。两个方法:一个检查 view 是否可见。如果可见就隐藏它,反之显示。之后将按钮的文案改为 “Hide” 或 “Show”。 -reverseViewVisibility() 方法调用 “model” 对传入的 view 进行相对的可见性设置。 +reverseViewVisibility() 方法调用 “model” 对传入的 view 进行可见性设置。 ### **下面看下 model** ### -``` +``` java public final class Utils { // ... @@ -342,15 +342,15 @@ public final class Utils { 现在我们对 presenter 和 model 都有所了解了,下面我们开始测试。毕竟这是一个数百万的产品,我们不能有任何错误。 -我们首先测试 presenter。当涉及 presenter (任何 presenter)我们需要确保 view 已与之关联。注意:我们并不测试 view。我们只需要确保 view 的绑定以便确认是否在正确的时间调用了正确的 view 方法。记住,这很重要。 +我们首先测试 presenter。当使用 presenter (任何 presenter)时,我们需要确保 view 已与之关联。注意:我们并不测试 view。我们只需要确保 view 的绑定以便确认是否在正确的时间调用了正确的 view 方法。记住,这很重要。 这里我们使用 Mockito 进行测试,就像单元测试那样,我们需要告诉 Android,“嘿,我们需要使用 MockitoJUnitRunner 进行测试。”实际操作时在测试类的顶部添加 @RunWith (MockitoJUnitRunner.class) 即可。 从前面可知我们需要两个东西:一是模拟一个 View (因为 presenter 使用了 View 对象,对其进行显隐控制),另外一个是 presenter。 -下面是使用 Mockito 的模拟 +下面展示了如何使用 Mockito 进行模拟 -``` +``` java @RunWith (MockitoJUnitRunner.class) public class MainPresenterImplTest { @@ -366,9 +366,9 @@ public class MainPresenterImplTest { } ``` -我们要写的第一个测试是 “testReverseViewVisibilityFromVisibleToGone”。顾名思义,我们验证的是当 view 可见时,调用 presenter 的 reverseViewVisibility() 方法结果正确。 +我们要写的第一个测试是 “testReverseViewVisibilityFromVisibleToGone”。顾名思义,我们将要验证的是,当可见的 View 被传入 presenter 的 reverseViewVisibility() 方法时,presenter 能正确地设置 View 的可见性。 -``` +``` java @Test public void testReverseViewVisibilityFromVisibleToGone() throws Exception { final View view = Mockito.mock(View.class); @@ -381,13 +381,13 @@ public class MainPresenterImplTest { } ``` -我们一起看下,这里具体做了什么?由于我们要测试的是 view 从可见到不可见的操作,我们需要 view 一开始是可见的,因此我们希望一开始调用 view 的 isShown() 方法返回是 true。接着,以模拟的 view 作为入参调用 presenter 的 reverseViewVisibility() 方法。现在我们需要确认 view 最近被调用的方法是 setVisibility(),并且设置为 GONE。然后,我们需要确认 presenter view 的 setButtonText() 方法是否调用。并不难吧? +我们一起看下,这里具体做了什么?由于我们要测试的是 view 从可见到不可见的操作,我们需要 view 一开始是可见的,因此我们希望一开始调用 view 的 isShown() 方法返回是 true。接着,以模拟的 view 作为入参调用 presenter 的 reverseViewVisibility() 方法。现在我们需要确认 view 最近被调用的方法是 setVisibility(),并且设置为 GONE。然后,我们需要确认与 presenter 绑定的 view 的 setButtonText() 方法是否调用。并不难吧? 嗯,接着我们进行相反的测试。在继续阅读下面的代码之前,试着自己想一下怎么做。如何测试从隐藏到显示的情况?根据上面已知的信息思考一下。 代码实现如下: -``` +``` java @Test public void testReverseViewVisibilityFromGoneToVisible() throws Exception { final View view = Mockito.mock(View.class); @@ -403,7 +403,7 @@ public class MainPresenterImplTest { 接着测试 “Model”。和前面一样,我们首先在类顶部添加注解 @RunWith (MockitoJUnitRunner.class) 。 -``` +``` java @RunWith(MockitoJUnitRunner.class) publicclassUtilsTest{ @@ -418,7 +418,7 @@ publicclassUtilsTest{ Utils 类的测试十分简单,因此我不再逐行解释,大家直接看代码即可。 -``` +``` java @RunWith (MockitoJUnitRunner.class) public class UtilsTest { @@ -457,7 +457,7 @@ public class UtilsTest { 我们看下 Utils 的 showView() 方法。如果不做 null 检查,当 view 为 null 时应用会抛出 NullPointerException 并崩溃。 -``` +``` java public final class Utils { // ... @@ -474,7 +474,7 @@ public final class Utils { 另外一些情况下,我们需要应用抛出一个异常。我们如何测试一个异常?十分简单:只需要对 @Test 注解传递一个 expected 参数进行指定: -``` +``` java @RunWith (MockitoJUnitRunner.class) public class UtilsTest { From a5a572d8f43ee6a7e2de26c9d8bf836931fba242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Mon, 22 May 2017 22:51:18 +0800 Subject: [PATCH 15/59] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 36cec87b238..970aaa493e9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [500](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [501](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -24,10 +24,10 @@ ## Android +* [如何创建 BubblePicker – Android 多彩菜单动画](https://juejin.im/post/591e734d2f301e006bea5243?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([hackerkevin](https://github.com/hackerkevin) 翻译) * [通过测试来解耦Activity](https://juejin.im/post/59143d7c8d6d81005854d982?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([mnikn](https://github.com/mnikn) 翻译) * [函数式接口、默认方法、纯函数、函数的副作用、高阶函数、可变的和不可变的、函数式编程和 Lambda 表达式 - 响应式编程 [Android RxJava 2](这到底是什么)第三部分](https://juejin.im/entry/591298eea0bb9f0058b35c7f/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([XHShirley](https://github.com/XHShirley) 翻译) * [开发者(也就是我)与Rx Observable 类的对话 [ Android RxJava2 ] ( 这到底是什么?) 第五部分](https://juejin.im/post/590ab4f7128fe10058f35119/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([stormrabbit](https://github.com/stormrabbit) 翻译) -* [使用 Espresso 隔离测试视图](https://juejin.im/post/59088d650ce463006182a07/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([yazhi1992](https://github.com/yazhi1992) 翻译) * [所有 Android 译文>>](https://github.com/xitu/gold-miner/blob/master/android.md) From 2e1f422cb0eee7695cc34cee95edd5c38a894cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Mon, 22 May 2017 22:53:07 +0800 Subject: [PATCH 16/59] Update android.md --- android.md | 1 + 1 file changed, 1 insertion(+) diff --git a/android.md b/android.md index bdf906053a1..9cce5f64446 100644 --- a/android.md +++ b/android.md @@ -1,3 +1,4 @@ +* [如何创建 BubblePicker – Android 多彩菜单动画](https://juejin.im/post/591e734d2f301e006bea5243?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([hackerkevin](https://github.com/hackerkevin) 翻译) * [通过测试来解耦Activity](https://juejin.im/post/59143d7c8d6d81005854d982?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([mnikn](https://github.com/mnikn) 翻译) * [函数式接口、默认方法、纯函数、函数的副作用、高阶函数、可变的和不可变的、函数式编程和 Lambda 表达式 - 响应式编程 [Android RxJava 2](这到底是什么)第三部分](https://juejin.im/entry/591298eea0bb9f0058b35c7f/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([XHShirley](https://github.com/XHShirley) 翻译) * [开发者(也就是我)与Rx Observable 类的对话 [ Android RxJava2 ] ( 这到底是什么?) 第五部分](https://juejin.im/post/590ab4f7128fe10058f35119/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([stormrabbit](https://github.com/stormrabbit) 翻译) From cb917870296215a03f75b4b5093b7ffa913089ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Mon, 22 May 2017 23:32:00 +0800 Subject: [PATCH 17/59] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 970aaa493e9..1771d54dcd3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [501](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [502](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -76,10 +76,10 @@ ## 设计 +* [最激动人心的视觉系统其实是最枯燥乏味的](https://juejin.im/entry/59228e15a0bb9f005f60915a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Nicolas(Yifei) Li](https://github.com/yifili09) 翻译) * [人人都是设计师,我们可以的。](https://juejin.im/post/59157cdf0ce4630069d79857?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ylq167](https://github.com/ylq167) 翻译) * [设计师装腔指南](https://juejin.im/post/5915880b570c35006932fac9?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Changkun Ou](https://github.com/changkun) 翻译) * [从形式到功能,设计思维的改变](https://juejin.im/post/58fedca744d9040069f720e4/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Ruixi](https://github.com/Ruixi) 翻译) -* [搜索结果页的最佳实践](https://juejin.im/post/58da37c761ff4b00607287a6/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([sunui](https://github.com/sunui) 翻译) * [所有设计译文>>](https://github.com/xitu/gold-miner/blob/master/design.md) From 5726e26e611e1408f68a44b7bdd794db20e1de00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Mon, 22 May 2017 23:32:40 +0800 Subject: [PATCH 18/59] Update design.md --- design.md | 1 + 1 file changed, 1 insertion(+) diff --git a/design.md b/design.md index 1510d54ed89..760846a09ac 100644 --- a/design.md +++ b/design.md @@ -1,3 +1,4 @@ +* [最激动人心的视觉系统其实是最枯燥乏味的](https://juejin.im/entry/59228e15a0bb9f005f60915a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Nicolas(Yifei) Li](https://github.com/yifili09) 翻译) * [人人都是设计师,我们可以的。](https://juejin.im/post/59157cdf0ce4630069d79857?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ylq167](https://github.com/ylq167) 翻译) * [设计师装腔指南](https://juejin.im/post/5915880b570c35006932fac9?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Changkun Ou](https://github.com/changkun) 翻译) * [从形式到功能,设计思维的改变](https://juejin.im/post/58fedca744d9040069f720e4/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Ruixi](https://github.com/Ruixi) 翻译) From c572a11b608f6213facad3979e4e0f3d30016081 Mon Sep 17 00:00:00 2001 From: "Yuze.Ma" <584653629@qq.com> Date: Tue, 23 May 2017 10:36:55 +0800 Subject: [PATCH 19/59] Final draft --- TODO/crafting-better-code-reviews.md | 83 ++++++++++++++-------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/TODO/crafting-better-code-reviews.md b/TODO/crafting-better-code-reviews.md index b8092b7f14f..4d16eb7d1dc 100644 --- a/TODO/crafting-better-code-reviews.md +++ b/TODO/crafting-better-code-reviews.md @@ -30,65 +30,65 @@ In his book, he describes code reviews, and what function they *ought* to serve. > 管理软件工程过程中的一部分就是抓住问题“最低价值”的阶段,即在已投入投资最少且花费最少去解决问题的时机。为了达到这样的一个预期,我们可以使用“质量门”这样一个理念,也就是周期性地测试或者审查来决定是否应该进行到开发的下一阶段。 -McConnell 的代码审查研究中最重要的理念就是"代码集体拥有制度"。所有代码都是被一组贡献者们所拥有的,这些贡献者们能平等地对代码进行查看和更改。 +McConnell 的代码审查研究中最重要的理念就是"代码构建中的集体所有权"。所有的代码都属于团队而不是某一人,并且团队中的所有成员都可以对其进行访问和修改。 -> 代码审查制度的初衷是它会帮助我们获得我们所创造软件的共同所有权。换句话说,通过参与控制产品质量的方式,我们每个人都会成为开发过程中的股东。 +> 代码审查制度的初衷是帮助我们在软件开发中采用集体所有权的思想。换句话说,通过参与控制产品质量的方式,我们每个人都会成为开发过程中的股东。 -McConnell在他的书中提出了很多种不同的代码审查制度的方式和流程,这些方式都可以被任何一个团队采用在日常的工作流当中。强烈推荐McConnell的 *Code Complete* ,真的非常赞。但是这边的话我们就简短的说3种来帮助理解。 +McConnell 在他的书中提出了一些不同类型的代码审查流程,这些流程可以被任何一个团队采用在日常的工作流当中。强烈推荐McConnell的 **代码大全**,真的非常赞。但是这边的话我们就简短的说3种来帮助理解。 -#### **1. 3人审查制度** #### +#### **1. 详查(正式检查)** #### -3人审查制度是一个比较长的审查流程,耗时基本都在1小时左右。整个流程会包括一个公司的把关人(扛把子),代码作者(程序猿),和一个审查员(产品狗)。 +详查是一个比较长的审查流程,耗时基本都在1小时左右。整个流程会包括一个公司的把关人(扛把子),代码作者(程序猿),和一个审查员(产品狗)。 -当这个审查制度被有效使用时,它通常能发现这个程序 **60%** 的问题(不管是程序缺陷还是错误)。。根据McConnell的研究,相比于不怎么流程化的代码审查,这个制度平均每1000行代码能减少20%到30%的错误。 +当这个审查制度被有效使用时,它通常能发现这个程序 **60%** 的问题(不管是程序缺陷还是错误)。根据McConnell的研究,相比于不怎么流程化的代码审查,这个制度平均每1000行代码能减少20%到30%的错误。 -#### **2. 带着一起过** #### +#### **2. 走查** #### -这个方式的话大概是30~60分钟,通常用于来帮助初级程序员们在一家公司中理解一些技术性难题,同时也能让高级开发人员们回顾旧的方法论。 +走查通常持续 30 到 60 分钟,对于高级程序员来说,通常是一个向新手们传授经验的机会,与此同时,对于新手们来说这也是一个机会,可以阐述新的方法论,挑战那些陈腐的、很可能已经过时的假设。 -带着新人一起过的话有时候还蛮高效的,但是这个方式的话就不像上面那个3人审查一样流程化。这样的一个代码审查大概能检测到程序中20%到40%的问题。 +走查有时候还是挺有用的,但是一般而言,走查远没有更加正式的代码复查有效。通常走查可以找到程序中 20% 到 40% 的错误。 -#### **3. 审查小片段** #### +#### **3. 小型代码审查** #### -就想这个名字一样,这种审查非常短。但是这都是很有深度的审查,还蛮难的。有时候可能就是更改了1行代码,但是会引起大量的问题。 +就像这个名字一样,这种审查非常短。但是这都是很有深度的审查,还蛮难的。有时候可能就是更改了1行代码,但是会引起大量的问题。 -McConnell的研究发现了下面这些有关于审查小片段的事实: +McConnell的研究发现了下面这些有关于小型代码审查的事实: -> 一个专门针对单行修改代码审查的机构发现在进行代码审查后代码报错率从55%下降到了2%。一个在80年代晚期的通讯机构代码正确率在审查后从80%上升到了99.6%。 +> 一个专门针对单行修改代码审查的机构发现在进行代码审查后代码报错率从 55% 下降到了 2%。一个在 80 年代晚期的通讯机构代码正确率在审查后从 80% 上升到了 99.6%。 -McConnell的一组组数据似乎在告诉我们作为一个开发团队我们应该构造一些这三种代码审查的融合来进行开发的推进。 +McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应该结合这三种代码审查方式。 -然而,McConnell的书是在1993年写的。时至今日,我们的工作流程早就已经更新换代了,同行审查也随之更新。但是我们现在对于代码审查实行的方法足够合理完整吗?我们应该如何把**理论**上的东西运用到**实际**中去? +然而,McConnell 的书是在 1993 年写的。时至今日,我们的工作流程早就已经更新换代了,同行审查也随之更新。但是我们现在对于代码审查实行的方法足够合理完整吗?我们应该如何把**理论**上的东西运用到**实际**中去? -To find the answer to these questions, I did what any determined (but a little unsure of where to start) developer would do: I asked the Internet! -为了解答我的问题,我做了大多数码农会做的事:Google一下 +为了解答这些问题,我做了大多数码农会做的事:Google 一下! [![Markdown](http://i4.buimg.com/1949/4bcc14c27f51262e.png)](https://twitter.com/vaidehijoshi) -好吧,并没有什么卵用,那我来看看推特。 +嗯,不错。上图是我在推特上发起的一个调查。 + ### 程序猿们如何看待代码审查 ### -在我对这个调查进一步阐述之前,我想先说点什么:*我不是一个数据科学家* (我希望我是,但是我也许在处理这篇文章反馈的时候能更加得心应手,或许我用R语言画图还过得去)。还有一点,就是说我的数据集其实是非常有限的。首先这个数据是我自己在推特上选过来的,然后的话数据是来自一个基于 branch/pull request的团队的。 +在我对这个调查进一步阐述之前,我想先说点什么:**我不是一个数据科学家** (我希望我是,但是我也许在处理这篇文章反馈的时候能更加得心应手,或许我用 R 语言画图还过得去)。还有一点,就是说我的数据集其实是非常有限的。首先这个数据是我自己在推特上选过来的,另外数据是来自一个基于 branch/pull request 的团队的。 -好,那么重点来了:*程序猿们是到底如何看待代码审查的?* +好,那么重点来了:**程序猿们是到底如何看待代码审查的?** #### 量化的数据 #### 让我们先来看看这组已经量化的数据。 -首先,这个问题的答案很大程度上取决于你问了*哪个*程序猿。在我写这个报告的时候,我已经收到超过500条回复了。 +首先,这个问题的答案很大程度上取决于你问了**哪些**程序猿。在我写这个报告的时候,我已经收到超过 500 条回复了。 -下面是一些根据工作时用的语言分类的结果,包括了JS,JAVA,还有Ruby。 +下面是一些根据工作时用的语言分类的结果,包括了 JS,JAVA,还有 Ruby。 ![](https://cdn-images-1.medium.com/max/600/1*hiGuGx5OvayL4dPu1tSC4w.png) -I asked every respondent to the survey to what extent they agreed with the statement: *Code reviews are beneficial to my team*. -根据我一个个问完之后,大家都认为 *代码审查对于团队是有好处的* 。 -总的来说,从1分到非常不认可,10分非常认可,Swift开发团队给代码审查认可度打了9.5/10的高分。Ruby开发团队第二,打出了9.2的高分。 +根据我一个个问完之后,大家都认为 **代码审查对于团队是有好处的**。 + +总的来说,从 1 分(**非常不认可**)到 10 分(**非常认可**),Swift开发团队给代码审查认可度打了 9.5 分的高分。Ruby 开发团队第二,打出了 9.2 的高分。 ![](https://cdn-images-1.medium.com/max/800/1*1zSl-fd9hygIBzxp52yHOQ.jpeg) @@ -96,21 +96,21 @@ I asked every respondent to the survey to what extent they agreed with the state ![](https://cdn-images-1.medium.com/max/800/1*fVl3H0KGsauN1Bxs_jsN7A.jpeg) -这张图片通过不同语言阐明了代码审查的深度。总的来说,每个语言直接没有差很多。换句话来说,决定代码审查的的深度和频率和你用什么语言没有关系,重点在于你在什么样的一个团队。 +这张图片通过不同语言阐明了代码审查的深度。总的来说,每个语言之间没有差很多。换句话来说,决j定代码审查的的深度和频率和你用什么语言没有关系,重点在于你在什么样的一个团队。 ![](https://cdn-images-1.medium.com/max/800/1*jFZ_2zCzHM78m_L_p0OK8A.jpeg) -最后,对于那些当有要求进行代码审查才会进行审查的团队来说,大部分通常只有1个人在代码merge到主程序之前审查这个代码。 +最后,对于那些当有要求进行代码审查才会进行审查的团队来说,大多数团队通常只需要 1 个人在代码合并到主分支之前审查这个代码。 ![](https://cdn-images-1.medium.com/max/800/1*KsuH1lurvkf5wpoXZ2queQ.png) #### 定性描述的数据 #### -那么对于那些不可量化的东西来说,我们能知道什么呢?在多项选择题之外,这个调查同时也能够让受访者填写他们自己的答案。这一部分也是这个调查最重要的一部分,最能*说明问题*的一部分。 - -这写回答具体聚焦在如下几个重点。 +那么对于那些不可量化的东西来说,我们能知道什么呢?在多项选择题之外,这个调查同时也能够让受访者填写他们自己的答案。这一部分也是这个调查最重要的一部分,最能**说明问题**的一部分。 +这些回答具体聚焦在如下几个重点。 + > 总的来说,对于代码审查制度有很大关系的有两个因素:执行代码审查所需要消耗的资源和代码审查这一流程的可持续性。 @@ -120,13 +120,13 @@ I asked every respondent to the survey to what extent they agreed with the state #### 资源 #### -另一种来找出一个代码审查*到底消耗了多少资源*的方法是问自己这样的问题:*谁在执行这个流程*以及他们花了多少时间来执行这个流程? +另一种来找出一个代码审查**到底消耗了多少资源**的方法是问自己这样的问题:**谁在执行这个流程**以及他们花了多少时间来执行这个流程? 大量的受访者们都是很有生产力的代码审查员,但是大家都表示对于团队里执行这个环节的人不爽,以及对于等待他们的代码被审查时花费的大量时间表示不爽。 下面是一些匿名的反馈: -> 我们有一个开发人员只是盲目的给每个PR一个赞然后都不怎么留评论。我可以告诉你这是真的因为他们1分钟能看5到6个PR。 +> 我们有一个开发人员只是盲目的给每个PR一个赞然后都不怎么留评论。我可以告诉你这是真的因为他们 1 分钟能看 5 到 6 个PR。 > 我发现第二个或者第三个的审查者大多数都是在打酱油。 @@ -134,7 +134,7 @@ I asked every respondent to the survey to what extent they agreed with the state > 团队里的每个人应该受到相同的对待。高级工程师也会犯错,而且他们也希望有人能提出来。初级工程师也不能被各种黑。人呐,总是会被事物有偏见。 -> Commits太长了,导致PR需要很久去审查。人们不会先在本地去测试一下代码。 +> Commits 太长了,导致PR需要很久去审查。人们不会先在本地去测试一下代码。 > 长长的PR总是花费大量的时间,然后这个PR还对将来的特性有重要的影响。 @@ -146,24 +146,23 @@ I asked every respondent to the survey to what extent they agreed with the state #### 可持续性 #### -The **substance of a code review** boils down to the answer to one question question: *What exactly is someone saying, doing, or making another person feel while they review their code?* -*代码审查可持续性*主要被以下几个因素影响:执行代码审查的时候执行者到底*说了什么,做了什么,让被审查者有什么样的感受* +**代码审查可持续性**主要被以下几个因素影响:执行代码审查的时候执行者到底**说了什么,做了什么,让被审查者有什么样的感受** -重点还是在于大家说了什么以及说话的方式. +重点还是在于大家说了什么以及说话的方式。 让我们来看看大家的吐槽吧: -> 面对PR的时候,我觉得你要是不喜欢变量名就直接改,我觉得这个不是最重要的,这完全是个人喜好嘛!就像在IDE里一样,我很容易就被搞蒙了。我不关心为什么不开心,让这个东西停止报错就好,不停叫真的不能忍。 +> 面对 PR 的时候,我觉得你要是不喜欢变量名就直接改,我觉得这个不是最重要的,这完全是个人喜好嘛!就像在 IDE 里一样,我很容易就被搞蒙了。我不关心为什么不开心,让这个东西停止报错就好,不停叫真的不能忍。 -> 不要在公开场合说思想上的大错误。在线下有个友善的对话会非常有帮助。直接PR上说会让人很爽,最后弄得大家都不愉快。 +> 不要在公开场合说思想上的大错误。在线下有个友善的对话会非常有帮助。直接 PR 上说会让人很不爽,最后弄得大家都不愉快。 -> 当需求不停的改的时候我非常难过,特别是他们不向我解释为什么更改了需求的时候,或者留下他们犯错的可能性。特别是当别人告诉你改写你的代码成为他们的版本的时候,你简直难过的想要抱抱。 +> 当需求不停的改的时候我非常难过,特别是他们不向我解释为什么更改了需求的时候,或者留下他们犯错的可能性。特别是当别人告诉你改写你的代码成为他们的版本的时候,你简直难过得想要抱抱。 > 当一个回复过长的时候,我们不如去进行一次线下的交流。 > 我觉得对于个人喜好问题和功能是否能正常运作完全是两个问题。对于初级工程师来说,这是非常难分辨的。有时候几个高级工程师给出不同的反馈的时候更加懵逼。 -The main themes when it came to the *substance* of a code review could be summarized into the following: + 总的来说,代码审查的重点有一下几个: 1. 反馈过度注重语法和习惯的问题,导致让双方都非常不爽。代码习惯与风格和代码功能错误根本就是两回事儿。 @@ -172,13 +171,13 @@ The main themes when it came to the *substance* of a code review could be summar ### 如何做得更好? ### -也许这些数据不能我们最完整,最细致,或者最精准的代表我们代码审查的结构,但是我们还是能学到一些东西:我们能够回顾并检查我们团队的甚至整个社区的代码审查流程。 +也许这些数据不能最完整,最细致,或者最精准地代表代码审查的结构,但是我们还是能学到一些东西:我们能够回顾并检查我们团队的甚至整个社区的代码审查流程。 下面的匿名调查告诉了我们代码审查对于一个团队成员的影响是怎么样的: > 一个非常差劲的代码审查让我几乎决定离职。一个优秀的代码审查流程会让我有信心面对将来更加困难的项目。 -确实,有一个流程化的代码审查制度*似乎*是非常有效且能帮助团队快速成长的;Steve McConnell和这篇文字的调查都证明了这一点。但是,单纯的重构代码审查流程然后永远不改是远远不够的。事实上,代码审查的流程应该根据整个团队的情况来更改以保证团队的生长。 +确实,有一个流程化的代码审查制度**似乎**是非常有效且能帮助团队快速成长的;Steve McConnell 和这篇文字的调查都证明了这一点。但是,单纯的重构代码审查流程然后永远不改是远远不够的。事实上,代码审查的流程应该根据整个团队的情况来更改以保证团队的生长。 > 与直接采用一个代码审查方式不同的是,我们需要重新思考我们的代码审查流程并且流程化工作来帮助我们面对以后的可能遇到的问题。 From 06866d2a9395fa52d711e0751b6cab63b5191711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Tue, 23 May 2017 22:02:49 +0800 Subject: [PATCH 20/59] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1771d54dcd3..9005ad796c7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [502](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [503](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -24,10 +24,10 @@ ## Android +* [用 Dagger 2 实现依赖注入](https://github.com/xitu/gold-miner/blob/master/TODO/Dependency-Injection-with-Dagger-2.md?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([tanglie1993](https://github.com/tanglie1993/) 翻译) * [如何创建 BubblePicker – Android 多彩菜单动画](https://juejin.im/post/591e734d2f301e006bea5243?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([hackerkevin](https://github.com/hackerkevin) 翻译) * [通过测试来解耦Activity](https://juejin.im/post/59143d7c8d6d81005854d982?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([mnikn](https://github.com/mnikn) 翻译) * [函数式接口、默认方法、纯函数、函数的副作用、高阶函数、可变的和不可变的、函数式编程和 Lambda 表达式 - 响应式编程 [Android RxJava 2](这到底是什么)第三部分](https://juejin.im/entry/591298eea0bb9f0058b35c7f/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([XHShirley](https://github.com/XHShirley) 翻译) -* [开发者(也就是我)与Rx Observable 类的对话 [ Android RxJava2 ] ( 这到底是什么?) 第五部分](https://juejin.im/post/590ab4f7128fe10058f35119/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([stormrabbit](https://github.com/stormrabbit) 翻译) * [所有 Android 译文>>](https://github.com/xitu/gold-miner/blob/master/android.md) From ecc1d6996caba3881e52277bee1ca3150872cba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Tue, 23 May 2017 22:04:10 +0800 Subject: [PATCH 21/59] Update android.md --- android.md | 1 + 1 file changed, 1 insertion(+) diff --git a/android.md b/android.md index 9cce5f64446..d6f011ae603 100644 --- a/android.md +++ b/android.md @@ -1,3 +1,4 @@ +* [用 Dagger 2 实现依赖注入](https://github.com/xitu/gold-miner/blob/master/TODO/Dependency-Injection-with-Dagger-2.md?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([tanglie1993](https://github.com/tanglie1993/) 翻译) * [如何创建 BubblePicker – Android 多彩菜单动画](https://juejin.im/post/591e734d2f301e006bea5243?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([hackerkevin](https://github.com/hackerkevin) 翻译) * [通过测试来解耦Activity](https://juejin.im/post/59143d7c8d6d81005854d982?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([mnikn](https://github.com/mnikn) 翻译) * [函数式接口、默认方法、纯函数、函数的副作用、高阶函数、可变的和不可变的、函数式编程和 Lambda 表达式 - 响应式编程 [Android RxJava 2](这到底是什么)第三部分](https://juejin.im/entry/591298eea0bb9f0058b35c7f/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([XHShirley](https://github.com/XHShirley) 翻译) From eaca15d3ee5e7172df0a72baadb254154a7960e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Tue, 23 May 2017 22:22:49 +0800 Subject: [PATCH 22/59] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9005ad796c7..c0b460655f7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [503](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [504](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -59,10 +59,10 @@ ## 后端 +* [使用速率限制扩展你的 API](https://github.com/xitu/gold-miner/blob/master/TODO/rate-limiters.md?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([tanglie1993](https://github.com/tanglie1993/) 翻译) * [真相就在代码中](https://juejin.im/post/59152d17da2f60005dd0ae77?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([loveky](https://github.com/loveky) 翻译) * [nginScript 入门](https://github.com/xitu/gold-miner/blob/master/TODO/introduction-nginscript.md?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([1992chenlu](https://github.com/1992chenlu) 翻译) * [我是如何找回 Reddit 密码的](https://juejin.im/entry/590aee94da2f60005328fb2d/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([cdpath](https://github.com/cdpath) 翻译) -* [Node.js 之战: 如何在生产环境中调试错误](https://juejin.im/post/59035d3644d904006919086b/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([mnikn](https://github.com/mnikn) 翻译) * [所有后端译文>>](https://github.com/xitu/gold-miner/blob/master/backend.md) ## 教程 From f42957e33ae7c2d7b5fe7416d4635e4fe757dafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Tue, 23 May 2017 22:24:37 +0800 Subject: [PATCH 23/59] Update backend.md --- backend.md | 1 + 1 file changed, 1 insertion(+) diff --git a/backend.md b/backend.md index 1aad1b13333..02af53617ed 100644 --- a/backend.md +++ b/backend.md @@ -1,3 +1,4 @@ +* [使用速率限制扩展你的 API](https://github.com/xitu/gold-miner/blob/master/TODO/rate-limiters.md?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([tanglie1993](https://github.com/tanglie1993/) 翻译) * [真相就在代码中](https://juejin.im/post/59152d17da2f60005dd0ae77?utm_source=gold-miner&utm_medium=readme&utm_campaign=github)[loveky](https://github.com/loveky) 翻译) * [nginScript 入门](https://github.com/xitu/gold-miner/blob/master/TODO/introduction-nginscript.md?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([1992chenlu](https://github.com/1992chenlu) 翻译) * [我是如何找回 Reddit 密码的](https://juejin.im/entry/590aee94da2f60005328fb2d/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([cdpath](https://github.com/cdpath) 翻译) From 0c4ee936e5e8b66780e53a7c1d5871b7a9d2036e Mon Sep 17 00:00:00 2001 From: zhangqippp Date: Wed, 24 May 2017 12:29:39 +0800 Subject: [PATCH 24/59] =?UTF-8?q?=E5=88=9D=E8=AF=91=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...arrays-holding-elements-weak-references.md | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/TODO/swift-arrays-holding-elements-weak-references.md b/TODO/swift-arrays-holding-elements-weak-references.md index fb9e033e853..f795b607ea8 100644 --- a/TODO/swift-arrays-holding-elements-weak-references.md +++ b/TODO/swift-arrays-holding-elements-weak-references.md @@ -1,26 +1,26 @@ > * 原文地址:[Swift Arrays Holding Elements With Weak References](https://marcosantadev.com/swift-arrays-holding-elements-weak-references/) > * 原文作者:[Marco Santarossa](https://marcosantadev.com/about-me/) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) -> * 译者: +> * 译者:[zhangqippp](https://github.com/zhangqippp) > * 校对者: -# [Swift Arrays Holding Elements With Weak References](https://marcosantadev.com/swift-arrays-holding-elements-weak-references/) # +# [对元素持有弱引用的Swift数组](https://marcosantadev.com/swift-arrays-holding-elements-weak-references/) # ![](https://marcosantadev.com/wp-content/uploads/header-1.jpg) -In iOS development there are moments where you ask yourself: “To weak, or not to weak, that is the question”. Let’s see how “to weak” with the arrays. +在 iOS 开发中我们经常面临一个问题:“使用弱引用,还是不使用弱引用?”。我们来看一下如何在数组中使用弱引用。 -# Overview # +# 概述 # -*In this article, I speak about memory management without explaining it since it would be beyond the goal of this article. The [official documentation](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html) is a good starting point to learn this subject. Then, if you have other doubts, please leave a comment and I’ll reply as soon as possible.* +*在本文中,我会谈到内存管理但是不会解释它,因为这不是本文的主题。 这里的[官方文档](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)可以帮助你开始学习内存管理。 如果你有其它疑问,请留言,我会尽快给予回复。* -`Array` is the most popular collection in Swift. By default, it maintains a strong references of its elements. Even if this behaviour is useful most of the time, you might have some scenarios where you would want to use weak references. For this reason, Apple provides an alternative to `Array` which maintains weak references of its elements: **NSPointerArray**. +`Array` 是Swift中使用最多的集合。它会默认的对其元素持有强引用。 这种默认的行为在大多数时候都很有用,但是在某些场景下你可能想要使用弱引用。因此,苹果公司给我们提供了一个 `Array` 的替代品:**NSPointerArray**,这个类对它的元素持有弱引用。 -Before looking at this class, let’s see and example to understand why we should use it. +在开始研究这个类之前,我们先通过一个例子来了解为什么我们需要使用它。 -# Why Weak References? # +# 为什么要使用弱引用? # -Let’s use, as example, a `ViewManager` class which has two properties of type `View`. In its constructor, we add these views in an array to inject inside `Drawer`—which uses this array to draw something inside the views. Finally, we have a method `destroyViews` to destroy the two `View`s: +举个例子,我们有一个 `ViewManager` ,它有两个 `View` 类型的属性。在它的构造器中,我们把这些视图添加到 `Drawer` 内部的一个数组中,`Drawer` 使用这个数组在其中的视图内绘制一些内容。最后,我们还有一个 `destroyViews` 方法来销毁这两个 `View`: ``` class View { } @@ -56,13 +56,13 @@ class ViewManager { ``` -Unfortunately, `destroyViews` doesn’t destroy the two views because the array inside `Drawer` is maintaining a strong reference of the views. We can avoid this problem replacing the array with a `NSPointerArray`. +但是,`destroyViews` 方法并没能销毁这两个视图,因为 `Drawer` 内部的数组依然对这些视图保持着强引用。我们可以通过使用 `NSPointerArray` 来避免这个问题。 # [NSPointerArray](https://developer.apple.com/reference/foundation/nspointerarray) # -`NSPointerArray` is an alternative to `Array` with the main difference that it doesn’t store an object but its pointer (`UnsafeMutableRawPointer`). +`NSPointerArray` 是 `Array` 的一个替代品,主要区别在于它不存储对象而是存储对象的指针( `UnsafeMutableRawPointer` )。 -This type of array can store the pointer maintaining either a weak or a strong reference depending on how it’s initialised. It provides two static methods to be initialised in different ways: +这种类型的数组可以管理弱引用也可以管理强引用,取决于它是如何被初始化的。它提供两个静态方法以便我们使用不同的初始化方式: ``` let strongRefarray = NSPointerArray.strongObjects() // Maintains strong references @@ -70,9 +70,9 @@ let weakRefarray = NSPointerArray.weakObjects() // Maintains weak references ``` -Since we want an array of weak references, we’ll use `NSPointerArray.weakObjects()`. +我们需要一个弱引用的数组,所以我们使用 `NSPointerArray.weakObjects()`。 -Now, we can add a new object in this array: +现在,我们向数组中添加一个新对象: ``` class MyClass { } @@ -84,7 +84,7 @@ let pointer = Unmanaged.passUnretained(obj).toOpaque() array.addPointer(pointer) ``` -Since using the pointer may be annoying, you can use this extension which I made to simplify the `NSPointerArray`: +如果你觉得这样使用指针很烦,你可以使用我写的这个扩展,可以简化 `NSPointerArray ` 的使用: ``` extension NSPointerArray { @@ -122,7 +122,7 @@ extension NSPointerArray { } ``` -Thanks to this extension, you can replace the previous example with: +有了这个扩展类,你可以将前面的例子替换为: ``` var array = NSPointerArray.weakObjects() @@ -131,13 +131,13 @@ let obj = MyClass() array.addObject(obj) ``` -If you want to clean the array removing the objects with value `nil`, you can call the method `compact()`: +如果你想清理这个数组,把其中的对象都置为 `nil`,你可以调用 `compact()` 方法: ``` array.compact() ``` -At this point, we can refactor the example used in “Why Weak References?” with the following code: +到了这里,我们可以将上一小节 “为什么要使用弱引用?” 中的例子重构为如下代码: ``` class View { } @@ -176,21 +176,21 @@ class ViewManager { ``` -Note: +注意: -1. You may have noticed that `NSPointerArray` stores pointers of `AnyObject` only, it means that you can store just classes—so neither structs nor enums. You can store protocols if they have the keyword [`class`](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID281): +1. 你可能已经注意到了 `NSPointerArray` 只存储 `AnyObject` 的指针,这意味着你只能存储类 —— 结构体和枚举都不行。你可以存储带有 [`class`](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID281) 关键字的协议: ``` protocolMyProtocol: class{} ``` -2. If you want to play with `NSPointerArray`, I suggest you to avoid the Playground since you may have odd behaviours with the retain count. A sample app would be better. +2. 如果你想试用一下 `NSPointerArray`,我建议不要试用 Playground ,因为你可能因为引用计数问题得到一些奇怪的行为。使用一个简单的 app 会更好。 -# Alternatives # +# 备选方案 # -`NSPointerArray` is very useful to store objects maintaining weak references, but it has a problem: it’s not type-safe. +`NSPointerArray` 对于存储对象和保持弱引用来说非常有用,但是它有一个问题:它不是类型安全的。 -For “not type-safe”, I mean that the compiler is not able to infer the type of the objects inside `NSPointerArray`, since it uses pointers of objects `AnyObject`. For this reason, when you get an object from the array, you must cast it to your object type: +“非类型安全”,在此处的意思是编译器无法隐含 `NSPointerArray` 内部对象的类型,因为它使用的是 `AnyObject` 型对象的指针。因此,当你从数组中获取一个对象时,你需要检查它是否是你所需要的类型: ``` ifletfirstObject=array.object(at:0)as?MyClass{// Cast to MyClass @@ -201,11 +201,11 @@ ifletfirstObject=array.object(at:0)as?MyClass{// Cast to MyClass ``` -*`object(at:)` comes from my `NSPointerArray` extension which I shown previously.* +*`object(at:)` 方法来自我先前展示的 `NSPointerArray` 扩展类。* -If we want to use a type-safe alternative we can’t use `NSPointerArray` anymore. +如果我们想使用一个类型安全的数组替代品,我们就不能使用 `NSPointerArray` 了。 -A possible workaround is creating a new class `WeakRef` with a generic weak property `value`: +一个可行的方案是创建一个新类 `WeakRef` ,它带有一个普通的weak属性 `value`: ``` classWeakRefwhereT: AnyObject{ @@ -222,25 +222,25 @@ classWeakRefwhereT: AnyObject{ ``` -*`private(set)` exposes `value` in read-only mode, in this way no one can set its value from outside the class.* +*`private(set)` 方法将 `value` 设置为只读模式, 这样就无法在类的外部设置它的值了。* -Then, we can create an array of `WeakRef`, where `value` is your `MyClass` object to store: +然后,我们可以创建一组 `WeakRef` 对象,将你的 `MyClass` 对象储存到它们的 `value` 属性: ``` -vararray=[WeakRef]() +var array=[WeakRef]() -letobj=MyClass() +let obj=MyClass() -letweakObj=WeakRef(value:obj) +let weakObj=WeakRef(value:obj) array.append(weakObj) ``` -Now, we have an array type-safe which maintains a weak reference of your `MyClass` objects. The disadvantage of this approach is that we must add an extra layer in our code (`WeakRef`) to wrap the weak reference in a type-safe way. +现在,我们拥有一个类型安全的数组,其内部对你的 `MyClass` 对象持有弱引用。这种实现的坏处在于,我们必须在代码中多加一层(`WeakRef`),来用一种类型安全的方式包裹弱引用。 -If you want to clean the array removing the objects with value `nil`, you can write the following method: +如果你想清理数组,去除其中值为 `nil` 的对象,你可以使用下面的方法: ``` funccompact(){ @@ -250,9 +250,9 @@ funccompact(){ } ``` -*`filter` returns a new array with the elements that satisfy the given predicate. You can find more details in the [documentation](https://developer.apple.com/reference/swift/array/1688383-filter).* +*`filter` 返回一个其中元素满足给定条件的新数组。你可以在[文档](https://developer.apple.com/reference/swift/array/1688383-filter)中获取更多的信息。* -Now, we can refactor the example used in “Why Weak References?” with the following code: +现在,我们可以将 “为什么要使用弱引用?” 小节中的例子重构为如下代码: ``` class View { } @@ -290,7 +290,7 @@ class ViewManager { } ``` -A cleaner version using the typealias: +使用类型别名的更简洁的版本如下: ``` typealias WeakRefView = WeakRef @@ -329,15 +329,15 @@ class ViewManager { } ``` -# Dictionary And Set # +# Dictionary 和 Set # -This article has the main focus on `Array`, if you need something similar to `NSPointerArray` for `Dictionary` you can have a look at [NSMapTable](https://developer.apple.com/reference/foundation/nsmaptable), whereas for `Set` you can use [NSHashTable](https://developer.apple.com/reference/foundation/nshashtable). +本文主要讨论了 `Array`,如果你需要 `Dictionary` 的类似于 `NSPointerArray` 的替代品,你可以参考 [NSMapTable](https://developer.apple.com/reference/foundation/nsmaptable),以及 `Set` 的替代品 [NSHashTable](https://developer.apple.com/reference/foundation/nshashtable)。 -If you want a type-safe `Dictionary`/`Set`, you can achieve it storing a `WeakRef` object. +如果你需要一个类型安全的 `Dictionary`/`Set`,你可以通过使用 `WeakRef` 对象来实现。 -# Conclusion # +# 结论 # -I guess you are not going to use arrays with weak references very often, but it’s not an excuse not to know how to achieve it. In iOS development the memory management is very important to avoid memory leaks, since iOS doesn’t have a garbage collector. ¯\_(ツ)_/¯ +你可能不会经常使用持有弱引用的数组,但是你仍然可以学习如何实现它。在 iOS 开发中,内存管理是非常重要的,我们应该避免内存泄露,因为 iOS 没有垃圾回收器。 ¯\_(ツ)_/¯ --- From e53f7e46520d8628352fc66393689375bbfd9bc1 Mon Sep 17 00:00:00 2001 From: GangsterHyj <919685260@qq.com> Date: Thu, 25 May 2017 01:21:06 +0800 Subject: [PATCH 25/59] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E6=84=8F=E8=A7=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/web-developer-security-checklist.md | 45 ++++++++++++------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/TODO/web-developer-security-checklist.md b/TODO/web-developer-security-checklist.md index 2733f5acd2c..8bf23c9f2eb 100644 --- a/TODO/web-developer-security-checklist.md +++ b/TODO/web-developer-security-checklist.md @@ -11,7 +11,7 @@ 开发安全、健壮的云端 web 应用程序是**非常困难**的事情。如果你认为这很容易,要么你过着更高级的生活,要么你还正走向痛苦觉醒的路上。 -倘若你已经接受 [MVP(最小化可行产品)](https://en.wikipedia.org/wiki/Minimum_viable_product) 的开发理念,并且相信能在一个月内创造既有价值又安全的产品 —— 在发布你的“原型产品”之前请再三考虑。在你检查下面列出的安全清单后,承认你在开发过程中忽视了很多极其重要的安全问题。至少要对你潜在的用户坦诚,让他们知道你并没有真正完成产品,而仅仅只是提供没有充分考虑安全问题的原型。 +倘若你已经接受 [MVP(最简可行产品)](https://en.wikipedia.org/wiki/Minimum_viable_product) 的开发理念,并且相信能在一个月内创造既有价值又安全的产品 —— 在发布你的“原型产品”之前请再三考虑。在你检查下面列出的安全清单后,意识到你在开发过程中忽视了很多极其重要的安全问题。至少要对你潜在的用户坦诚,让他们知道你并没有真正完成产品,而仅仅只是提供没有充分考虑安全问题的原型。 这份安全清单很简单,绝非覆盖所有方面。它列出了在创建 web 应用时需要考虑的比较重要的安全问题。 @@ -20,27 +20,28 @@ ### **数据库** ### - 对识别用户身份的数据和诸如访问令牌、电子邮箱地址或账单明细等敏感数据进行加密。 -- 如果数据库支持在休息状态进行低消耗的数据加密 (如 [AWS Aurora](https://aws.amazon.com/about-aws/whats-new/2015/12/amazon-aurora-now-supports-encryption-at-rest/)),那么请激活此功能以加强磁盘数据安全。确保所有的备份文件也都被加密存储。 +- 如果数据库支持在空闲状态进行低消耗的数据加密 (如 [AWS Aurora](https://aws.amazon.com/about-aws/whats-new/2015/12/amazon-aurora-now-supports-encryption-at-rest/)),那么请激活此功能以加强磁盘数据安全。确保所有的备份文件也都被加密存储。 - 对访问数据库的用户帐号使用最小权限原则,禁止使用数据库 root 帐号。 - 使用精心设计的密钥库存储和分发密钥,不要对应用中使用的密钥进行硬编码。 - 仅使用 SQL 预备语句以彻底阻止 SQL 注入。例如,如果使用 NPM 开发应用,连接数据库时不使用 npm-mysql ,而是使用支持预备语句的 npm-mysql2 。 ### **开发** ### -- 确保检查软件所有组件的每个投入生存环境使用的版本漏洞,包括操作系统、库和软件包。此操作应该以自动化的方式注入 CI/CD 过程。 +- 确保已经检查过软件投入生存环境使用的每个版本中所有组件的漏洞,包括操作系统、库和软件包。此操作应该以自动化的方式加入 CI/CD(持续集成/持续部署) 过程。 - 对开发环境系统的安全问题保持与生产环境同样的警惕,从安全、独立的开发环境系统构建软件。 ### **认证** ### -- 确保所有密码都使用适当的加密算法(例如 bcrypt )进行哈希。 -- 实现简单但充分的密码规则以激励用户使用长的随机密码。 +- 确保所有的密码都使用例如 bcrypt 之类的合适的加密算法进行哈希。绝对不要使用自己写的加密算法,并正确地使用随机数初始化加密算法。 +- 使用简单但充分的密码规则以激励用户设置长的随机密码。 - 使用多因素身份验证方式实现对服务提供商的登录操作。 ### **拒绝服务防卫** ### -- 确保对 API 进行 DOS 攻击不会让你的网站崩溃。至少在执行时间较长的 API 路径(例如登录、令牌生成等程序)使用速度限制器。 +- 确保对 API 进行 DOS 攻击不会让你的网站崩溃。至少增加速率限制到执行时间较长的 API 路径(例如登录、令牌生成等程序)。 - 对用户提交的数据和请求在大小和结构上增强完整性限制。 -- 通过类似 [CloudFlare](https://www.cloudflare.com/) 的全局缓存代理服务应用缓解 [Distributed Denial of Service](https://en.wikipedia.org/wiki/Denial-of-service_attack) (DDOS)对网站带来的影响。如果你遭受 DDOS 攻击,或者用于 DNS 查找,可以考虑开启此服务。??? +- 使用类似 [CloudFlare](https://www.cloudflare.com/) 的全局缓存代理服务应用以缓解 [Distributed Denial of Service](https://en.wikipedia.org/wiki/Denial-of-service_attack) (DDOS,分布式拒绝服务攻击)对网站带来的影响。它会在你遭受 DDOS 攻击时被激活,并且还具有类似 DNS 查找等功能。 + ### **网络交通** ### @@ -48,7 +49,7 @@ - Cookies 必须添加 httpOnly 和 secure 属性,且由属性 path 和 domain 限定作用范围。 - 使用 [CSP(内容安全策略)](https://en.wikipedia.org/wiki/Content_Security_Policy) 以禁止不安全的后门操作。策略的配置很繁琐,但是值得。 - 使用 X-Frame-Option 和 X-XSS-Protection 响应头。 -- 使用 HSTS(HTTP Strict Transport Security) 响应强迫客户端仅使用 TLS 访问服务器。将所有 HTTP 请求重定向到服务器上的 HTTPS 作为后备。??? +- 使用 HSTS(HTTP Strict Transport Security) 响应强迫客户端仅使用 TLS 访问服务器,同时服务端需要将所有 HTTP 请求重定向为 HTTPS。 - 在所有表单中使用 CSRF 令牌,使用新响应头 [SameSite Cookie](https://scotthelme.co.uk/csrf-is-dead/) 一次性解决 CSRF 问题, SameSite Cookie 适用于所有新版本的浏览器。 ### **APIs** ### @@ -58,33 +59,33 @@ ### **校验** ### -- 执行客户端输入校验以快速获取用户的反馈,但是不能完全依赖校验。 -- 使用服务器的白名单校验用户输入。不要向响应直接注入用户信息,不要在 SQL 语句使用用户输入。 +- 使用客户端输入校验以及时给予用户反馈,但是不能完全信任客户端校验结果。 +- 使用服务器的白名单校验用户输入。不要直接向响应注入用户信息,切勿在 SQL 语句里使用用户输入。 ### **云端配置** ### -- 确保所有服务开放最少的端口。尽管是通过模糊的安全性不受保护的,使用非标准端口将使得黑客的攻击更加困难。 -- 在对任何公有网络都不可见的私有 VPC 上部署后台数据库和服务。配置 AWS 安全组和对等 VPC 务必谨慎(可能无意间使服务对外部可见)。 -- 在独立的 VPC 和对等的 VPC 隔离逻辑服务,以提供内部服务交流。??? -- 确保所有服务仅接收来自最小的IP地址集合的数据。 -- 限制对外输出的 IP 和端口流量,以最小化 APT(高级持续性威胁) 和 “警告”。 -- 始终使用 AWS 的 IAM (身份与访问管理)角色,而不是使用 root 的认证信息。 +- 确保所有服务开放最少的端口。尽管通过隐藏信息来保障安全是不可靠的,使用非标准端口将使黑客的攻击操作更加困难。 +- 在对任何公有网络都不可见的私有 VPC 上部署后台数据库和服务。在配置 AWS 安全组和对等互联多个 VPC 时务必谨慎(可能无意间使服务对外部可见)。 +- 不同逻辑的服务部署在不同的 VPC 上,VPC 之间通过对等连接进行内部服务的访问。 +- 让连接服务的 IP 地址个数尽可能少。 +- 限制对外输出的 IP 和端口流量,以最小化 APT(高级持续性威胁)和“警告”。 +- 始终使用 AWS 的 IAM(身份与访问管理)角色,而不是使用 root 的认证信息。 - 对所有操作和开发人员使用最小访问权限原则。 - 按照预定计划定期轮换密码和访问密钥。 ### **基础架构** ### - 确保在不停机的情况下对基础架构进行升级,确保以全自动的方式快速更新软件。 -- 利用 Terraform 等工具创建所有的基础架构,而不是通过云端命令行窗口。基础架构应该定义为“代码”,仅需一个按钮的功夫即可重建。对云端任何亲手创建的资源零容忍 —— Terraform 能审查你的所有配置。??? +- 利用 Terraform 等工具创建所有的基础架构,而不是通过云端命令行窗口。基础架构应该代码化,仅需一个按钮的功夫即可重建。请不要手动在云端创建资源,因为使用 Terraform 就可以通过配置自动创建它们。 - 为所有服务使用集中化的日志记录,不该再利用 SSH 访问或检索日志。 -- 除了一次性诊断以外,不要使用 SSH 登录进服务。 经常使用 SSH ,意味着你还没有将执行重要任务的操作自动化。??? -- 不要长期开放任何AWS服务组的22号端口。 -- 创建 [immutable hosts(不可变主机)](http://chadfowler.com/2013/06/23/immutable-deployments.html) 而不是创建需要提交补丁和更新的服务器。(详情请看博客 [Immutable Infrastructure Can Be More Secure](https://simplesecurity.sensedeep.com/immutable-infrastructure-can-be-dramatically-more-secure-238f297eca49))。 +- 除了一次性诊断服务故障以外,不要使用 SSH 登录进服务。频繁使用 SSH ,意味着你还没将执行重要任务的操作自动化。 +- 不要长期开放任何 AWS 服务组的22号端口。 +- 创建 [immutable hosts(不可变主机)](http://chadfowler.com/2013/06/23/immutable-deployments.html) 而不是使用一个经过你长期提交补丁和更新的服务器。。(详情请看博客 [Immutable Infrastructure Can Be More Secure](https://simplesecurity.sensedeep.com/immutable-infrastructure-can-be-dramatically-more-secure-238f297eca49))。 - 使用如 [SenseDeep](https://www.sensedeep.com/) 的 [Intrusion Detection System(入侵检测系统)](https://en.wikipedia.org/wiki/Intrusion_detection_system) 或服务,以最小化 [APTs(高级持续性威胁)](https://en.wikipedia.org/wiki/Advanced_persistent_threat) 。 ### **操作** ### -- 关闭未使用的服务和服务器,最安全的服务器是关机的。 +- 关闭未使用的服务和服务器,关闭的服务器是最安全的。 ### **测试** ### @@ -93,7 +94,7 @@ ### **最后,制定计划** ### -- 准备用于描述网络攻击防御的威胁模型,它应该列出并优先考虑可能的威胁和网络攻击参与者。 +- 准备用于描述网络攻击防御的威胁模型,列出可能的威胁和网络攻击参与者,并按优先级对其排序。 - 制定经得起实践考验的安全事故计划,总有一天你会用到它。 --- From 88cdc51440cb799f7c4da4c68d4b298a5bada09e Mon Sep 17 00:00:00 2001 From: GangsterHyj <919685260@qq.com> Date: Thu, 25 May 2017 10:56:41 +0800 Subject: [PATCH 26/59] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AF=91=E8=80=85?= =?UTF-8?q?=E5=92=8C=E6=A0=A1=E5=AF=B9=E4=BA=BA=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/web-developer-security-checklist.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO/web-developer-security-checklist.md b/TODO/web-developer-security-checklist.md index 8bf23c9f2eb..5e0b962ea00 100644 --- a/TODO/web-developer-security-checklist.md +++ b/TODO/web-developer-security-checklist.md @@ -1,8 +1,8 @@ > * 原文地址:[Web Developer Security Checklist](https://simplesecurity.sensedeep.com/web-developer-security-checklist-f2e4f43c9c56) > * 原文作者:[Michael O'Brien](https://simplesecurity.sensedeep.com/@sensedeep) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) -> * 译者: -> * 校对者: +> * 译者: [GangsterHyj](https://github.com/gangsterhyj) +> * 校对者: [zaraguo](https://github.com/zaraguo), [yzgyyang](https://github.com/yzgyyang) # Web 开发者安全清单 From ec3fb340160bf21b25910468d2ede9ea6028ff61 Mon Sep 17 00:00:00 2001 From: "Yuze.Ma" <584653629@qq.com> Date: Thu, 25 May 2017 21:56:20 +0800 Subject: [PATCH 27/59] Fix more inappropriate syntax --- TODO/crafting-better-code-reviews.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/TODO/crafting-better-code-reviews.md b/TODO/crafting-better-code-reviews.md index 4d16eb7d1dc..2244f950290 100644 --- a/TODO/crafting-better-code-reviews.md +++ b/TODO/crafting-better-code-reviews.md @@ -1,14 +1,14 @@ > * 原文地址:[Crafting Better Code Reviews](https://medium.com/@vaidehijoshi/crafting-better-code-reviews-1a5fc00a9312) > * 原文作者:[Vaidehi Joshi](https://medium.com/@vaidehijoshi) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) -> * 译者:bobmayuze +> * 译者:[bobmayuze](https://github.com/bobmayuze) > * 校对者: # 建立更好的代码审查制度 # -### 来自Rails 2017开发者大会中的一段演讲 ### +### 来自 Rails 2017 开发者大会中的一段演讲 ### -人与科技之间的交互部分总是那么忽明忽暗,难以捉摸。对于*开发科技产品*的人来说,更是如此。作为一个资深码农,我在代码审查的时候对于这一点的感触特别明显。 +人与科技之间的交互部分总是那么忽明忽暗,难以捉摸。对于**开发科技产品**的人来说,更是如此。作为一个资深码农,我在代码审查的时候对于这一点的感触特别明显。 大多数开发者们习惯于把他们的代码看成一种艺术品,就好比画家看待自己的画一样,我们的代码总是和我们密切相关。一直以来,我们都被教导说要做一个[利他](https://blog.codinghorror.com/the-ten-commandments-of-egoless-programming/)的码农,在代码合并到主分支之前,我们不仅要审查自己的代码,也要审查同事的代码。其实我们都知道这样的审查是对大家都有利的,是一件[我们都应该做的事情](https://blog.codinghorror.com/code-reviews-just-do-it/),而且很多人恰好已经在做这些强烈推荐的事了。 @@ -25,7 +25,6 @@ 在我们能真正完全理解代码审查的实际意义和好处之后,我们就能知道为什么会有代码审查这个传统了。网上有大量的有关代码审查最佳实践的[研究](https://en.wikipedia.org/wiki/Code_review#References),不过我建议可以从 Steve McConnell 在[**代码大全**](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670) 中的相关研究入手(该书于 1993年出版)。 -In his book, he describes code reviews, and what function they *ought* to serve. He writes: 在他的书中,对于代码审查制度**应该**起到的作用,他写下了下面这些话: > 管理软件工程过程中的一部分就是抓住问题“最低价值”的阶段,即在已投入投资最少且花费最少去解决问题的时机。为了达到这样的一个预期,我们可以使用“质量门”这样一个理念,也就是周期性地测试或者审查来决定是否应该进行到开发的下一阶段。 @@ -34,7 +33,7 @@ McConnell 的代码审查研究中最重要的理念就是"代码构建中的集 > 代码审查制度的初衷是帮助我们在软件开发中采用集体所有权的思想。换句话说,通过参与控制产品质量的方式,我们每个人都会成为开发过程中的股东。 -McConnell 在他的书中提出了一些不同类型的代码审查流程,这些流程可以被任何一个团队采用在日常的工作流当中。强烈推荐McConnell的 **代码大全**,真的非常赞。但是这边的话我们就简短的说3种来帮助理解。 +McConnell 在他的书中提出了一些不同类型的代码审查流程,这些流程可以被任何一个团队采用在日常的工作流当中。强烈推荐 McConnell 的**代码大全**,真的非常赞。但是这边的话我们就简短的说3种来帮助理解。 #### **1. 详查(正式检查)** #### @@ -70,7 +69,7 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 ### 程序猿们如何看待代码审查 ### -在我对这个调查进一步阐述之前,我想先说点什么:**我不是一个数据科学家** (我希望我是,但是我也许在处理这篇文章反馈的时候能更加得心应手,或许我用 R 语言画图还过得去)。还有一点,就是说我的数据集其实是非常有限的。首先这个数据是我自己在推特上选过来的,另外数据是来自一个基于 branch/pull request 的团队的。 +在我对这个调查进一步阐述之前,我想先说点什么:**我不是一个数据科学家**(我希望我是,但是我也许在处理这篇文章反馈的时候能更加得心应手,或许我用 R 语言画图还过得去)。还有一点,就是说我的数据集其实是非常有限的。首先这个数据是我自己在推特上选过来的,另外数据是来自一个基于 branch/pull request 的团队的。 好,那么重点来了:**程序猿们是到底如何看待代码审查的?** @@ -92,7 +91,7 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 ![](https://cdn-images-1.medium.com/max/800/1*1zSl-fd9hygIBzxp52yHOQ.jpeg) -当70%的受调查者告诉我他们的pull request在被merge之前会由团队里的其他人来检查时,有10%的受访者(大概50个)告诉我的说他们的pull request只有在自己要求进行代码审查的时候才会进行代码审查。 +当70%的受调查者告诉我他们的 pull request 在被 merge 之前会由团队里的其他人来检查时,有10%的受访者(大概50个)告诉我的说他们的 pull request 只有在自己要求进行代码审查的时候才会进行代码审查。 ![](https://cdn-images-1.medium.com/max/800/1*fVl3H0KGsauN1Bxs_jsN7A.jpeg) @@ -126,7 +125,7 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 下面是一些匿名的反馈: -> 我们有一个开发人员只是盲目的给每个PR一个赞然后都不怎么留评论。我可以告诉你这是真的因为他们 1 分钟能看 5 到 6 个PR。 +> 我们有一个开发人员只是盲目的给每个PR一个赞然后都不怎么留评论。我可以告诉你这是真的因为他们 1 分钟能看 5 到 6 个 PR。 > 我发现第二个或者第三个的审查者大多数都是在打酱油。 @@ -134,7 +133,7 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 > 团队里的每个人应该受到相同的对待。高级工程师也会犯错,而且他们也希望有人能提出来。初级工程师也不能被各种黑。人呐,总是会被事物有偏见。 -> Commits 太长了,导致PR需要很久去审查。人们不会先在本地去测试一下代码。 +> Commits 太长了,导致 PR 需要很久去审查。人们不会先在本地去测试一下代码。 > 长长的PR总是花费大量的时间,然后这个PR还对将来的特性有重要的影响。 @@ -225,7 +224,6 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 首先感谢一路上一直支持我的工程师朋友们,感谢你们投入时间和精力参与我的调查。 -A huge thank you to [Kasra Rahjerdi](https://medium.com/@jc4p), who helped me analyze the responses to my survey and created many of the graphs in this project. 非常感谢 [Kasra Rahjerdi](https://medium.com/@jc4p) 帮我整理反馈并且生成那么多的图像。 感谢 [Jeff Atwood](https://blog.codinghorror.com/code-reviews-just-do-it/)的有关于互相检查的文章, Karl Wiegers 的 [*人类化互相审查流程*](http://www.processimpact.com/articles/humanizing_reviews.html), 以及 Steve McConnell 对于 [*Code Complete*](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670) 的研究。 我希望你能购买他们的书来支持他们。 From c5b12e42934d7c4c8a56cfeab3bf549fdda0932f Mon Sep 17 00:00:00 2001 From: "Yuze.Ma" <584653629@qq.com> Date: Thu, 25 May 2017 22:03:58 +0800 Subject: [PATCH 28/59] Fix more inappropriate syntax --- TODO/crafting-better-code-reviews.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TODO/crafting-better-code-reviews.md b/TODO/crafting-better-code-reviews.md index 2244f950290..6ed2892e4e1 100644 --- a/TODO/crafting-better-code-reviews.md +++ b/TODO/crafting-better-code-reviews.md @@ -39,7 +39,7 @@ McConnell 在他的书中提出了一些不同类型的代码审查流程,这 详查是一个比较长的审查流程,耗时基本都在1小时左右。整个流程会包括一个公司的把关人(扛把子),代码作者(程序猿),和一个审查员(产品狗)。 -当这个审查制度被有效使用时,它通常能发现这个程序 **60%** 的问题(不管是程序缺陷还是错误)。根据McConnell的研究,相比于不怎么流程化的代码审查,这个制度平均每1000行代码能减少20%到30%的错误。 +当这个审查制度被有效使用时,它通常能发现这个程序 **60%** 的问题(不管是程序缺陷还是错误)。根据McConnell的研究,相比于不怎么流程化的代码审查,这个制度平均每 1000 行代码能减少 20% 到 30% 的错误。 #### **2. 走查** #### @@ -79,7 +79,7 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 首先,这个问题的答案很大程度上取决于你问了**哪些**程序猿。在我写这个报告的时候,我已经收到超过 500 条回复了。 -下面是一些根据工作时用的语言分类的结果,包括了 JS,JAVA,还有 Ruby。 +下面是一些根据工作时用的语言分类的结果,包括了 JS、JAVA 还有 Ruby。 ![](https://cdn-images-1.medium.com/max/600/1*hiGuGx5OvayL4dPu1tSC4w.png) @@ -151,7 +151,7 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 让我们来看看大家的吐槽吧: -> 面对 PR 的时候,我觉得你要是不喜欢变量名就直接改,我觉得这个不是最重要的,这完全是个人喜好嘛!就像在 IDE 里一样,我很容易就被搞蒙了。我不关心为什么不开心,让这个东西停止报错就好,不停叫真的不能忍。 +> 面对 PR 的时候,我觉得你要是不喜欢变量名就直接改,我觉得这个不是最重要的,这完全是个人喜好嘛!就像在 IDE 里一样,我很容易就被搞蒙了。我不关心为什么不开心,让这个东西停止报错就好,不停地报错真的不能忍。 > 不要在公开场合说思想上的大错误。在线下有个友善的对话会非常有帮助。直接 PR 上说会让人很不爽,最后弄得大家都不愉快。 From 9fe58d906fd701b77d1274441485fa251182ebd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Thu, 25 May 2017 23:30:23 +0800 Subject: [PATCH 29/59] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0b460655f7..cc6e948d4e9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [504](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [505](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -24,10 +24,10 @@ ## Android +* [使用 Espresso 和 Mockito 测试 MVP](https://juejin.im/post/5924e5740ce4630069757f75?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([skyar2009](https://github.com/skyar2009) 翻译) * [用 Dagger 2 实现依赖注入](https://github.com/xitu/gold-miner/blob/master/TODO/Dependency-Injection-with-Dagger-2.md?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([tanglie1993](https://github.com/tanglie1993/) 翻译) * [如何创建 BubblePicker – Android 多彩菜单动画](https://juejin.im/post/591e734d2f301e006bea5243?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([hackerkevin](https://github.com/hackerkevin) 翻译) * [通过测试来解耦Activity](https://juejin.im/post/59143d7c8d6d81005854d982?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([mnikn](https://github.com/mnikn) 翻译) -* [函数式接口、默认方法、纯函数、函数的副作用、高阶函数、可变的和不可变的、函数式编程和 Lambda 表达式 - 响应式编程 [Android RxJava 2](这到底是什么)第三部分](https://juejin.im/entry/591298eea0bb9f0058b35c7f/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([XHShirley](https://github.com/XHShirley) 翻译) * [所有 Android 译文>>](https://github.com/xitu/gold-miner/blob/master/android.md) From 1b2cb168b258252b838f0cccb3aeb6785f6bfc99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Thu, 25 May 2017 23:31:13 +0800 Subject: [PATCH 30/59] Update android.md --- android.md | 1 + 1 file changed, 1 insertion(+) diff --git a/android.md b/android.md index d6f011ae603..566329d1c5c 100644 --- a/android.md +++ b/android.md @@ -1,3 +1,4 @@ +* [使用 Espresso 和 Mockito 测试 MVP](https://juejin.im/post/5924e5740ce4630069757f75?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([skyar2009](https://github.com/skyar2009) 翻译) * [用 Dagger 2 实现依赖注入](https://github.com/xitu/gold-miner/blob/master/TODO/Dependency-Injection-with-Dagger-2.md?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([tanglie1993](https://github.com/tanglie1993/) 翻译) * [如何创建 BubblePicker – Android 多彩菜单动画](https://juejin.im/post/591e734d2f301e006bea5243?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([hackerkevin](https://github.com/hackerkevin) 翻译) * [通过测试来解耦Activity](https://juejin.im/post/59143d7c8d6d81005854d982?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([mnikn](https://github.com/mnikn) 翻译) From b2e2e7f27fddd7d8aca16a56b45a283352e6da89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Thu, 25 May 2017 23:36:30 +0800 Subject: [PATCH 31/59] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cc6e948d4e9..b8de50d7d53 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [505](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [506](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -41,10 +41,10 @@ ## 前端 +* [Web 开发者安全清单](https://juejin.im/post/592651c944d904006400cd88?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([GangsterHyj](https://github.com/GangsterHyj) 翻译) * [解密 Quantum:现代浏览器引擎的构建之道](https://juejin.im/post/591bc865a22b9d00583c17b8?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([xunge0613](https://github.com/xunge0613) 翻译) * [光速 React](https://juejin.im/post/591ad6b7128fe1005ce1123a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ZhangFe](https://github.com/ZhangFe) 翻译) * [我是如何实现世界上最快的 JavaScript 记忆化的](https://juejin.im/post/5912b635a0bb9f0058b44c60?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Aladdin-ADD](https://github.com/Aladdin-ADD) 翻译) -* [别让你的偏爱拖了后腿:快拥抱箭头函数吧!](https://juejin.im/post/59158c92a0bb9f005fd58fd7?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([lsvih](https://github.com/lsvih) 翻译) * [所有前端译文>>](https://github.com/xitu/gold-miner/blob/master/front-end.md) From 2e1a7f35fd3cb12a71a34d1165c9965d44e7ca45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Thu, 25 May 2017 23:37:40 +0800 Subject: [PATCH 32/59] Update front-end.md --- front-end.md | 1 + 1 file changed, 1 insertion(+) diff --git a/front-end.md b/front-end.md index aef69aab824..8adabf1bf12 100644 --- a/front-end.md +++ b/front-end.md @@ -1,3 +1,4 @@ +* [Web 开发者安全清单](https://juejin.im/post/592651c944d904006400cd88?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([GangsterHyj](https://github.com/GangsterHyj) 翻译) * [解密 Quantum:现代浏览器引擎的构建之道](https://juejin.im/post/591bc865a22b9d00583c17b8?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([xunge0613](https://github.com/xunge0613) 翻译) * [光速 React](https://juejin.im/post/591ad6b7128fe1005ce1123a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ZhangFe](https://github.com/ZhangFe) 翻译) * [我是如何实现世界上最快的 JavaScript 记忆化的](https://juejin.im/post/5912b635a0bb9f0058b44c60?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Aladdin-ADD](https://github.com/Aladdin-ADD) 翻译) From 37953ad68b786f79f705412c2ad8da4f63bab14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Thu, 25 May 2017 23:54:19 +0800 Subject: [PATCH 33/59] Create empathy-and-ux-design.md --- TODO/empathy-and-ux-design.md | 107 ++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 TODO/empathy-and-ux-design.md diff --git a/TODO/empathy-and-ux-design.md b/TODO/empathy-and-ux-design.md new file mode 100644 index 00000000000..b8a8001beee --- /dev/null +++ b/TODO/empathy-and-ux-design.md @@ -0,0 +1,107 @@ +> * 原文地址:[Why Empathy Matters as a UX Designer](https://careerfoundry.com/en/blog/ux-design/empathy-and-ux-design) +> * 原文作者:[CLAIRE RACKSTRAW](https://careerfoundry.com/en/blog/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +![](https://careerfoundry.com/en/blog/ux-skills-1-copy-04ce154a70fbb2a38b6931bfb17913551e6f6379d78a3cb88ab8156782934cd6.jpg) + +# Why Empathy Matters as a UX Designer # + +If you’d have seen me being guided around my university campus blindfolded, you might have thought we were playing a game. In actual fact,it was part of my training to become a nurse. + +In order to empathize with patients and their families, a nurse should understand how their patient feels. For example, someone may have a visual impairment or difficulty hearing, they might be worried, they might be in pain or feeling nauseous. To be in a good position to address their needs and help to solve their problems, empathy is important. + +Fast forward 10 years, and I’ve changed careers - from nursing to [UX design](https://careerfoundry.com/en/courses/become-a-ux-designer), and I have found that **a lot of what I learnt during my nursing career is still valuable.** Regardless of your product, digital or otherwise, you should be aware that those using your services are not operating in a **perfect testing environment.** You need to find ways to familiarize yourself with and empathize with situations different to your own, and design with these situations in mind. + +Read on to learn: + +- How my nursing career has informed my role as a UX designer +- The difference between sympathy and empathy (and why you shouldn’t confuse the two!) +- How to use empathy to be a better UX designer +- A successful example of empathy in design: Danone +- Tips for introducing an empathic mindset in your company + +So, let’s begin by looking at **what nursing taught me about empathy…** + +![](https://careerfoundry.com/en/blog/uploads/versions/UX_Skills_4---x----1260-650x---.jpg) + +## How my nursing career has informed my role as a UX designer ## + +When I started training as a student nurse, I realized that **in order to successfully build a rapport with patients and their families I needed to be able to empathize with them**, to understand what this experience might be like for them and all of the factors that were influencing their experience of accessing healthcare. + +I also came to realize that when a person accesses healthcare services, or any other service, their experience does not exist in isolation from the rest of their life. When someone is admitted to hospital for example, what factors are shaping their experience? Is this the person’s first admission to hospital? Is there someone available to offer them support? Have they previously had good or bad experiences of accessing healthcare services? Thinking empathically allows you to see the bigger picture and consider the person and their circumstances as a whole. + +By practising empathy as a UX professional you can apply this holistic approach to assessing user needs by thinking about the user’s experience in it’s entirety, and not just when they are interacting with a particular feature on a screen. + +[Curious about Human Behavior? Take our Free 7-Day UX Design Course > > > Learn More Here...](http://info.careerfoundry.com/ux-design/free-ux-course-think-emails) + +In the field of UX you often hear the phrase “you are not the user” which reminds us that as designers we can’t make any assumptions about the people that we’re designing for. To really empathize with users we must be prepared to learn about all of the factors that influence how they interact with and perceive the product or service we are creating for them. + +![](https://careerfoundry.com/en/blog/uploads/versions/UX_Skills_6---x----1260-650x---.jpg) + +## The difference between sympathy and empathy (and why you shouldn’t confuse the two!) ## + +Before we go any further, let’s take a look at exactly what empathy is, how it differs from sympathy, and why having sympathy for those you are designing for might not be helpful for finding the right solutions. + +The word sympathy is most commonly used to mean “feelings of pity and sorrow for someone else’s misfortune.” Sympathy is associated with caring and a desire to see the situation of others improve, but it doesn’t enable us to create meaningful connections with others. + +Empathy is defined by Dictionary.com as “…the capacity or ability to imagine oneself in the situation of another, thereby vicariously experiencing the emotions, ideas, or opinions of that person.” Empathy is not just about imagining how someone is feeling, but also having some knowledge of their situation and what they are trying to achieve. Unlike sympathy, empathy allows us to make meaningful connections with others. + +Without empathy, we cannot fully understand the problems users might be facing in relation to our product. Sympathy alone will likely result in a solution we think is a great idea but which actually doesn’t provide any benefit to the user. Empathy enables us to understand the user and their experience as a whole, facilitating solutions that fix the right problems. + +![](https://careerfoundry.com/en/blog/uploads/versions/UX_Skills_3---x----1260-650x---.jpg) + +## How to use empathy to be a better UX designer ## + +In my experience, the most effective way to practice empathy as a designer is to observe users interacting with your product or service in their own environment, be that home, work, or out and about. Observing users in the field allows us to glean insights that cannot be captured through other research methods. However, the reality is that observational field work rarely happens because it is time and resource intensive. How else can we build empathy for users? + +Firstly, try to utilize a diverse set of research methods to gather insights about your existing or potential customers. This way you can capture a range of insights from participants and gain a well-rounded picture of how they perceive and experience your product. Understanding how your product fits into and impacts the lives of your customers will help you to develop empathy for them, so create a variety of opportunities to learn as much about them as you can. + +Next, make use of customer service conversations and feedback. Discussions generated in customer service conversations can reveal a lot about how your product or service impacts customers in their day-to-day lives in ways that surveys or usability testing can’t. Take the time to talk with your customer service team, they are likely to have valuable insights to share. + +Finally, experience your product from the perspective of a customer; this can be an incredibly effective way to build empathy. For example, as a student nurse, walking around my university campus blindfolded helped me to think about how I would design healthcare services to better serve blind patients. + +![](https://careerfoundry.com/en/blog/uploads/versions/UX_Skills_5---x----1260-650x---.jpg) + +## A successful example of empathy in design: Danone ## + +In 1996 food company Danone partnered with a non-profit organisation called the Grameen Foundation with the aim of tackling malnutrition and boosting local employment in a district of Bangladesh. + +Danone brought its expert knowledge of food production and combined it with the Grameen Foundation’s extensive experience of distributing products to rural communities in Bangladesh. + +They set up a factory producing a special, nutrient-dense yogurt which would provide a child with 30% of their daily vitamin and mineral requirements. The goal of this project was to create positive social impact, and they managed to do this effectively by empathizing with the unique needs of the local population: + +- Traditional marketing methods weren’t suitable because many rural, Bangladeshi residents aren’t literate, can’t travel far, and aren’t well educated about nutrition. Therefore, local women were recruited to do door-to-door sales to educate people about the product and encourage details of the product to spread by word of mouth. +- The factory used local milk supplies and based the price of the yogurt on the cost of the local milk. This kept the price of the yogurt at a level that was affordable for even the poorest families. +- Constraints in certain aspects of food production led to innovative ideas that could be applied to other markets, such as the use of enzymes to keep unrefrigerated milk fresh for longer. + +Although this is ‘design’ on a very large scale and of a non-digital nature, this example is an excellent demonstration of using empathy to understand the needs of your target customers, how best to meet their needs, and create opportunities for innovation in the process. + +![](https://careerfoundry.com/en/blog/uploads/versions/UX_Skills_2---x----1260-650x---.jpg) + +## Tips for introducing an empathic mindset in your company ## + +Without a mutual understanding of the benefits that an empathic design approach can bring, it can be difficult to stand up to business-driven product requirements or unvalidated assumptions about the right solutions to implement. + +One of the easiest ways to help others in your team or company to start thinking more empathically is to invite them to sit in on user research or user testing. Ask them to be your note taker, or simply have them observe. When a non-design team member can actually see and hear first hand a user experiencing and interacting with a product, it gives them an opportunity to develop empathy for that user and create a meaningful connection with them. + +Another way to help others develop more empathy for users is to share insights from user research with the whole company. Present insights in a way that allows others to see how they might translate into opportunities for innovation or priority areas of the product that need improvement. + +Visual representations of research findings catch people’s attention and convey insights more effectively than a page of bullet points. Clearly presented user research insights can be a powerful weapon against business-driven product requirements and assumptions held by those in roles far detached from customers. + +In summary, by adopting an empathic approach to the design process as designers we can make a holistic assessment of user’s needs to uncover meaningful solutions to problems and opportunities for innovation that are not always immediately apparent. + +Persuading colleagues outside of the design team to see user needs from an empathic viewpoint can be challenging, but by including others in user research and actively sharing research insights, you can encourage everyone to have empathy for users and achieve a greater balance between user and business needs. + +**Want to learn more about the research I discussed here? Check them out:** + +- [Empathy on the Edge: Scaling and sustaining a human-centered approach in the evolving practice of design](http://5a5f89b8e10a225a44ac-ccbed124c38c4f7a3066210c073e7d55.r9.cf1.rackcdn.com/files/pdfs/news/Empathy_on_the_Edge.pdf) by Battarbee K, Fulton Suri J, Gibbs Howard S & IDEO, 2014 +- [A Social Business Success Story: Grameen Danone in Bangladesh](http://knowledge.essec.edu/en/sustainability/a-social-business-success-story.html) by Renouard C, 2012 + +>> Finally, if you want to learn more about UX, take our free 7-day [UX Design Short Course](http://info.careerfoundry.com/ux-design/free-ux-course-think-emails). + + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 From 5cb142d3cb3db7aeab70c26237a04ac07c166e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Fri, 26 May 2017 00:11:19 +0800 Subject: [PATCH 34/59] Create object-detection-with-yolo.md --- TODO/object-detection-with-yolo.md | 344 +++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 TODO/object-detection-with-yolo.md diff --git a/TODO/object-detection-with-yolo.md b/TODO/object-detection-with-yolo.md new file mode 100644 index 00000000000..795ecb45bc4 --- /dev/null +++ b/TODO/object-detection-with-yolo.md @@ -0,0 +1,344 @@ +> * 原文地址:[Real-time object detection with YOLO](http://machinethink.net/blog/object-detection-with-yolo/) +> * 原文作者:[Matthijs Hollemans](http://machinethink.net/blog/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# Real-time object detection with YOLO # + +Object detection is one of the classical problems in computer vision: + +**Recognize *what* the objects are inside a given image and also *where* they are in the image.** + +Detection is a more complex problem than classification, which can also recognize objects but doesn’t tell you exactly where the object is located in the image — and it won’t work for images that contain more than one object. +[![](http://machinethink.net/images/yolo/ClassificationVsDetection.png) ](http://machinethink.net/images/yolo/ClassificationVsDetection@2x.png) + +[YOLO](https://pjreddie.com/darknet/yolo/) is a clever neural network for doing object detection in real-time. + +In this blog post I’ll describe what it took to get the “tiny” version of YOLOv2 running on iOS using Metal Performance Shaders. + +Before you continue, make sure to [watch the awesome YOLOv2 trailer](https://www.youtube.com/watch?v=VOC3huqHrss). 😎 + +## How YOLO works ## + +You can take a classifier like [VGGNet](/blog/convolutional-neural-networks-on-the-iphone-with-vggnet/) or [Inception](https://github.com/hollance/Forge/tree/master/Examples/Inception) and turn it into an object detector by sliding a small window across the image. At each step you run the classifier to get a prediction of what sort of object is inside the current window. Using a sliding window gives several hundred or thousand predictions for that image, but you only keep the ones the classifier is the most certain about. + +This approach works but it’s obviously going to be very slow, since you need to run the classifier many times. A slightly more efficient approach is to first predict which parts of the image contain interesting information — so-called *region proposals* — and then run the classifier only on these regions. The classifier has to do less work than with the sliding windows but still gets run many times over. + +YOLO takes a completely different approach. It’s not a traditional classifier that is repurposed to be an object detector. YOLO actually looks at the image just once (hence its name: You Only Look Once) but in a clever way. + +YOLO divides up the image into a grid of 13 by 13 cells: + +[![The 13x13 grid](http://machinethink.net/images/yolo/Grid@2x.png)](/images/yolo/Grid@2x.png) + +Each of these cells is responsible for predicting 5 bounding boxes. A bounding box describes the rectangle that encloses an object. + +YOLO also outputs a *confidence score* that tells us how certain it is that the predicted bounding box actually encloses some object. This score doesn’t say anything about what kind of object is in the box, just if the shape of the box is any good. + +The predicted bounding boxes may look something like the following (the higher the confidence score, the fatter the box is drawn): + +[![](http://machinethink.net/images/yolo/Boxes.png)](http://machinethink.net/images/yolo/Boxes@2x.png) + +For each bounding box, the cell also predicts a *class*. This works just like a classifier: it gives a probability distribution over all the possible classes. The version of YOLO we’re using is trained on the [PASCAL VOC dataset](http://host.robots.ox.ac.uk/pascal/VOC/), which can detect 20 different classes such as: + +- bicycle +- boat +- car +- cat +- dog +- person +- and so on… + +The confidence score for the bounding box and the class prediction are combined into one final score that tells us the probability that this bounding box contains a specific type of object. For example, the big fat yellow box on the left is 85% sure it contains the object “dog”: + +[![The bounding boxes with their class scores](http://machinethink.net/images/yolo/Scores.png)](http://machinethink.net/images/yolo/Scores@2x.png) + +Since there are 13×13 = 169 grid cells and each cell predicts 5 bounding boxes, we end up with 845 bounding boxes in total. It turns out that most of these boxes will have very low confidence scores, so we only keep the boxes whose final score is 30% or more (you can change this threshold depending on how accurate you want the detector to be). + +The final prediction is then: + +[![The final prediction](http://machinethink.net/images/yolo/Prediction.png)](http://machinethink.net/images/yolo/Prediction@2x.png) + +From the 845 total bounding boxes we only kept these three because they gave the best results. But note that even though there were 845 separate predictions, they were all made at the same time — the neural network just ran once. And that’s why YOLO is so powerful and fast. + +*(The above pictures are from [pjreddie.com](https://pjreddie.com).)* + +## The neural network ## + +The architecture of YOLO is simple, it’s just a convolutional neural network: + +``` +Layer kernel stride output shape +--------------------------------------------- +Input (416, 416, 3) +Convolution 3×3 1 (416, 416, 16) +MaxPooling 2×2 2 (208, 208, 16) +Convolution 3×3 1 (208, 208, 32) +MaxPooling 2×2 2 (104, 104, 32) +Convolution 3×3 1 (104, 104, 64) +MaxPooling 2×2 2 (52, 52, 64) +Convolution 3×3 1 (52, 52, 128) +MaxPooling 2×2 2 (26, 26, 128) +Convolution 3×3 1 (26, 26, 256) +MaxPooling 2×2 2 (13, 13, 256) +Convolution 3×3 1 (13, 13, 512) +MaxPooling 2×2 1 (13, 13, 512) +Convolution 3×3 1 (13, 13, 1024) +Convolution 3×3 1 (13, 13, 1024) +Convolution 1×1 1 (13, 13, 125) +--------------------------------------------- + +``` + +This neural network only uses standard layer types: convolution with a 3×3 kernel and max-pooling with a 2×2 kernel. No fancy stuff. There is no fully-connected layer in YOLOv2. + +**Note:** The “tiny” version of YOLO that we’ll be using has only these 9 convolutional layers and 6 pooling layers. The full YOLOv2 model uses three times as many layers and has a slightly more complex shape, but it’s still just a regular convnet. + +The very last convolutional layer has a 1×1 kernel and exists to reduce the data to the shape 13×13×125. This 13×13 should look familiar: that is the size of the grid that the image gets divided into. + +So we end up with 125 channels for every grid cell. These 125 numbers contain the data for the bounding boxes and the class predictions. Why 125? Well, each grid cell predicts 5 bounding boxes and a bounding box is described by 25 data elements: + +- x, y, width, height for the bounding box’s rectangle +- the confidence score +- the probability distribution over the 20 classes + +Using YOLO is simple: you give it an input image (resized to 416×416 pixels), it goes through the convolutional network in a single pass, and comes out the other end as a 13×13×125 tensor describing the bounding boxes for the grid cells. All you need to do then is compute the final scores for the bounding boxes and throw away the ones scoring lower than 30%. + +**Tip:** To learn more about how YOLO works and how it is trained, [check out this excellent talk](https://www.youtube.com/watch?v=NM6lrxy0bxs) by one of its inventors. This video actually describes YOLOv1, an older version of the network with a slightly different architecture, but the main ideas are still the same. Worth watching! + +## Converting to Metal ## + +The architecture I just described is for Tiny YOLO, which is the version we’ll be using in the iOS app. The full YOLOv2 network has three times as many layers and is a bit too big to run fast enough on current iPhones. Since Tiny YOLO uses fewer layers, it is faster than its big brother… but also a little less accurate. + +[![](http://machinethink.net/images/yolo/CatOrDog.png) ](http://machinethink.net/images/yolo/CatOrDog@2x.png) + +YOLO is written in Darknet, a custom deep learning framework from YOLO’s author. The downloadable weights are available only in Darknet format. Even though the [source code for Darknet is available](https://github.com/pjreddie/darknet), I wasn’t really looking forward to spending a lot of time figuring out how it works. + +Luckily for me, [someone else](https://github.com/allanzelener/YAD2K/) already put in that effort and converted the Darknet models to Keras, my deep learning tool of choice. So all I had to do was run this “YAD2K” script to convert the Darknet weights to Keras format, and then write my own script to convert the Keras weights to Metal. + +However, there was a small wrinkle… YOLO uses a regularization technique called *batch normalization* after its convolutional layers. + +The idea behind “batch norm” is that neural network layers work best when the data is clean. Ideally, the input to a layer has an average value of 0 and not too much variance. This should sound familiar to anyone who’s done any machine learning because we often use a technique called “feature scaling” or “whitening” on our input data to achieve this. + +Batch normalization does a similar kind of feature scaling for the data in between layers. This technique really helps neural networks perform better because it stops the data from deteriorating as it flows through the network. + +To give you some idea of the effect of batch norm, here is a histogram of the output of the first convolution layer without and with batch normalization: + +[![](http://machinethink.net/images/yolo/BatchNorm.png)](http://machinethink.net/images/yolo/BatchNorm@2x.png) + +Batch normalization is important when training a deep network, but it turns out we can get rid of it at inference time. Which is a good thing because not having to do the batch norm calculations will make our app faster. And in any case, Metal does not have an `MPSCNNBatchNormalization` layer. + +Batch normalization usually happens after the convolutional layer but *before* the activation function gets applied (a so-called “leaky” ReLU in the case of YOLO). Since both convolution and batch norm perform a linear transformation of the data, we can combine the batch normalization layer’s parameters with the weights for the convolution. This is called “folding” the batch norm layer into the convolution layer. + +Long story short, with a bit of math we can get rid of the batch normalization layers but it does mean we have to change the weights of the preceding convolution layer. + +A quick recap of what a convolution layer calculates: if `x` is the pixels in the input image and `w` is the weights for the layer, then the convolution basically computes the following for each output pixel: + +``` +out[j] = x[i]*w[0] + x[i+1]*w[1] + x[i+2]*w[2] + ... + x[i+k]*w[k] + b + +``` + +This is a dot product of the input pixels with the weights of the convolution kernel, plus a bias value `b`. + +And here’s the calculation performed by the batch normalization to the output of that convolution: + +``` + gamma * (out[j] - mean) +bn[j] = ---------------------- + beta + sqrt(variance) + +``` + +It subtracts the mean from the output pixel, divides by the variance, multiplies by a scaling factor gamma, and adds the offset beta. These four parameters — `mean`, `variance`, `gamma`, and `beta` — are what the batch normalization layer learns as the network is trained. + +To get rid of the batch normalization, we can shuffle these two equations around a bit to compute new weights and bias terms for the convolution layer: + +``` + gamma * w +w_new = -------------- + sqrt(variance) + + gamma*(b - mean) +b_new = ---------------- + beta + sqrt(variance) + +``` + +Performing a convolution with these new weights and bias terms on input `x` will give the same result as the original convolution plus batch normalization. + +Now we can remove this batch normalization layer and just use the convolutional layer, but with these adjusted weights and bias terms `w_new` and `b_new`. We repeat this procedure for all the convolutional layers in the network. + +**Note:** The convolution layers in YOLO don’t actually use bias, so `b` is zero in the above equation. But note that after folding the batch norm parameters, the convolution layers *do* get a bias term. + +Once we’ve folded all the batch norm layers into their preceding convolution layers, we can convert the weights to Metal. This is a simple matter of transposing the arrays (Keras stores them in a different order than Metal) and writing them out to binary files of 32-bit floating point numbers. + +If you’re curious, check out the conversion script [yolo2metal.py](https://github.com/hollance/Forge/blob/master/Examples/YOLO/yolo2metal.py) for more details. To test that the folding works the script creates a new model without batch norm but with the adjusted weights, and compares it to the predictions of the original model. + +## The iOS app ## + +Of course I used [Forge](https://github.com/hollance/Forge) to build the iOS app. 😂 You can find the code in the [YOLO](https://github.com/hollance/Forge/tree/master/Examples/YOLO) folder. To try it out: download or clone Forge, open **Forge.xcworkspace** in Xcode 8.3 or later, and run the **YOLO** target on an iPhone 6 or up. + +The easiest way to test the app is to point your iPhone at some [YouTube videos](https://www.youtube.com/watch?v=e_WBuBqS9h8): + +[![The example app](http://machinethink.net/images/yolo/App.png)](http://machinethink.net/images/yolo/App@2x.png) + +The interesting code is in **YOLO.swift**. First this sets up the convolutional network: + +``` +let leaky = MPSCNNNeuronReLU(device: device, a: 0.1) + +let input = Input() + +let output = input + --> Resize(width: 416, height: 416) + --> Convolution(kernel: (3, 3), channels: 16, padding: true, activation: leaky, name: "conv1") + --> MaxPooling(kernel: (2, 2), stride: (2, 2)) + --> Convolution(kernel: (3, 3), channels: 32, padding: true, activation: leaky, name: "conv2") + --> MaxPooling(kernel: (2, 2), stride: (2, 2)) + --> ...and so on... + +``` + +The input from the camera gets rescaled to 416×416 pixels and then goes into the convolutional and max-pooling layers. This is very similar to how any other convnet operates. + +The interesting thing is what happens with the output. Recall that the output of the convnet is a 13×13×125 tensor: there are 125 channels of data for each of the cells in the grid that is overlaid on the image. These 125 numbers contain the bounding boxes and class predictions, and we need to sort these out somehow. This happens in the function `fetchResult()`. + +**Note:** The code in `fetchResult()` runs on the CPU, not the GPU. It was simpler to implement that way. That said, the nested loop might benefit from the parallelism of a GPU. Maybe I’ll come back to this in the future and write a GPU version. + +Here is how `fetchResult()` works: + +``` +publicfuncfetchResult(inflightIndex: Int) -> NeuralNetworkResult { + let featuresImage = model.outputImage(inflightIndex: inflightIndex) + let features = featuresImage.toFloatArray() + +``` + +The output from the convolutional network is in the form of an `MPSImage`. We first convert this to an array of `Float` values called `features`, to make it a little easier to work with. + +The main body of `fetchResult()` is a huge nested loop. It looks at all of the grid cells and the five predictions for each cell: + +``` +for cy in0..<13 { + for cx in0..<13 { + for b in0..<5 { + . . . + } + } + } + +``` + +Inside this loop we compute the bounding box `b` for grid cell `(cy, cx)`. + +First we read the x, y, width, and height for the bounding box from the `features` array, as well as the confidence score: + +``` +let channel = b*(numClasses + 5) +let tx = features[offset(channel, cx, cy)] +let ty = features[offset(channel + 1, cx, cy)] +let tw = features[offset(channel + 2, cx, cy)] +let th = features[offset(channel + 3, cx, cy)] +let tc = features[offset(channel + 4, cx, cy)] + +``` + +The `offset()` helper function is used to find the proper place in the array to read from. Metal stores its data in texture slices in groups of 4 channels at a time, which means the 125 channels are not stored consecutively but are scattered all over the place. (See the code for an in-depth explanation.) + +We still need to do some processing on these five numbers `tx`, `ty`, `tw`, `th`, `tc` as they are in a bit of a weird format. If you’re wondering where these formulas come from, they’re given [in the paper](https://arxiv.org/abs/1612.08242) (it’s a side effect of how the network was trained). + +``` +llet x = (Float(cx) + Math.sigmoid(tx)) * 32 +let y = (Float(cy) + Math.sigmoid(ty)) * 32 + +let w = exp(tw) * anchors[2*b ] * 32 +let h = exp(th) * anchors[2*b + 1] * 32 + +let confidence = Math.sigmoid(tc) + +``` + +Now `x` and `y` represent the center of the bounding box in the 416×416 image that we used as input to the neural network; `w` and `h` are the width and height of the box in that same image space. The confidence value for the bounding box is given by `tc` and we used the logistic sigmoid to turn this into a percentage. + +We now have our bounding box and we know how confident YOLO is that this box actually contains an object. Next, let’s look at the class predictions to see what kind of object YOLO thinks is inside the box: + +``` +var classes = [Float](repeating: 0, count: numClasses) +forcin0.. 0.3 { + let rect = CGRect(x: CGFloat(x - w/2), y: CGFloat(y - h/2), + width: CGFloat(w), height: CGFloat(h)) + + let prediction = Prediction(classIndex: detectedClass, + score: confidenceInClass, + rect: rect) + predictions.append(prediction) +} + +``` + +The above code is repeated for all the cells in the grid. When the loop is over, we have a `predictions` array with typically 10 to 20 predictions in it. + +We already filtered out any bounding boxes that have very low scores, but there still may be boxes that overlap too much with others. Therefore, the last thing we do in `fetchResult()` is a technique called *non-maximum suppression* to prune those duplicate bounding boxes. + +``` +var result = NeuralNetworkResult() + result.predictions = nonMaxSuppression(boxes: predictions, + limit: 10, threshold: 0.5) + return result +} + +``` + +The algorithm used by the `nonMaxSuppression()` function is quite simple: + +1. Start with the bounding box that has the highest score. +2. Remove any remaining bounding boxes that overlap it more than the given threshold amount (i.e. more than 50%). +3. Go to step 1 until there are no more bounding boxes left. + +This removes any bounding boxes that overlap too much with other boxes that have a higher score. It only keeps the best ones. + +And that’s pretty much all there is to it: a regular convolutional network and a bit of postprocessing of the results afterwards. + +## How well does it work? ## + +The [YOLO website](https://pjreddie.com/darknet/yolo/) claims that Tiny YOLO can do up to 200 frames per second. But of course that is on a fat desktop GPU, not on a mobile device. So how fast does it run on an iPhone? + +On my iPhone 6s it takes about **0.15 seconds** to process a single image. That is only 6 FPS, barely fast enough to call it realtime. If you point the phone at a car driving by, you can see the bounding box trailing a little behind the car. Still, I’m impressed this technique works at all. 😁 + +**Note:** As I explained above, the processing of the bounding boxes runs on the CPU, not the GPU. Would YOLO run faster if it ran on the GPU entirely? Maybe, but the CPU code takes only about 0.03 seconds, 20% of the running time. It’s possible to do at least a portion of this work on the GPU but I’m not sure it’s worth the effort given that the conv layers still eat up 80% of the time. + +I think a major slowdown is caused by the convolutional layers that have 512 and 1024 output channels. From my experiments it seems that `MPSCNNConvolution` has more trouble with small images that have many channels than with large images that have fewer channels. + +One thing I’m interested in trying is to take a different network architecture, such as SqueezeNet, and retrain this network to predict the bounding boxes in its last layer. In other words, to take the YOLO ideas and put them on top of a smaller and faster convnet. Will the increase in speed be worth the loss in accuracy? + +**Note:** By the way, the recently released [Caffe2](http://caffe2.ai/) framework also runs on iOS with Metal support. The [Caffe2-iOS project](https://github.com/KleinYuan/Caffe2-iOS) comes with a version of Tiny YOLO. It appears to run a little slower than the pure Metal version, at 0.17 seconds per frame. + +## Credits ## + +To learn more about YOLO, check out these papers by its inventors: + +- [You Only Look Once: Unified, Real-Time Object Detection](https://arxiv.org/abs/1506.02640) by Joseph Redmon, Santosh Divvala, Ross Girshick, Ali Farhadi (2015) +- [YOLO9000: Better, Faster, Stronger](https://arxiv.org/abs/1612.08242) by Joseph Redmon and Ali Farhadi (2016) + +My implementation was based in part on the TensorFlow Android demo [TF Detect](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android), Allan Zelener’s [YAD2K](https://github.com/allanzelener/YAD2K/), and the original [Darknet code](https://github.com/pjreddie/darknet). + + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 From 273dc6ebaeacac1c44c7ba288cc6aee561269e09 Mon Sep 17 00:00:00 2001 From: zhangqippp Date: Fri, 26 May 2017 11:02:01 +0800 Subject: [PATCH 35/59] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E6=84=8F=E8=A7=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...arrays-holding-elements-weak-references.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/TODO/swift-arrays-holding-elements-weak-references.md b/TODO/swift-arrays-holding-elements-weak-references.md index f795b607ea8..293ca325fdf 100644 --- a/TODO/swift-arrays-holding-elements-weak-references.md +++ b/TODO/swift-arrays-holding-elements-weak-references.md @@ -2,19 +2,19 @@ > * 原文作者:[Marco Santarossa](https://marcosantadev.com/about-me/) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 译者:[zhangqippp](https://github.com/zhangqippp) -> * 校对者: +> * 校对者:[ZhangRuixiang](https://github.com/ZhangRuixiang),[Danny1451](https://github.com/Danny1451) # [对元素持有弱引用的Swift数组](https://marcosantadev.com/swift-arrays-holding-elements-weak-references/) # ![](https://marcosantadev.com/wp-content/uploads/header-1.jpg) -在 iOS 开发中我们经常面临一个问题:“使用弱引用,还是不使用弱引用?”。我们来看一下如何在数组中使用弱引用。 +在 iOS 开发中我们经常面临一个问题:“用弱引用还是不用,这是一个问题。”。我们来看一下如何在数组中使用弱引用。 # 概述 # -*在本文中,我会谈到内存管理但是不会解释它,因为这不是本文的主题。 这里的[官方文档](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)可以帮助你开始学习内存管理。 如果你有其它疑问,请留言,我会尽快给予回复。* +*在本文中,我会谈到内存管理但是不会解释它,因为这不是本文的主题。[官方文档](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)是学习内存管理的一个好的起点。如果你有其它疑问,请留言,我会尽快给予回复。* -`Array` 是Swift中使用最多的集合。它会默认的对其元素持有强引用。 这种默认的行为在大多数时候都很有用,但是在某些场景下你可能想要使用弱引用。因此,苹果公司给我们提供了一个 `Array` 的替代品:**NSPointerArray**,这个类对它的元素持有弱引用。 +`Array` 是Swift中使用最多的集合。它会默认地对其元素持有强引用。 这种默认的行为在大多数时候都很有用,但是在某些场景下你可能想要使用弱引用。因此,苹果公司给我们提供了一个 `Array` 的替代品:**NSPointerArray**,这个类对它的元素持有弱引用。 在开始研究这个类之前,我们先通过一个例子来了解为什么我们需要使用它。 @@ -184,16 +184,16 @@ class ViewManager { protocolMyProtocol: class{} ``` -2. 如果你想试用一下 `NSPointerArray`,我建议不要试用 Playground ,因为你可能因为引用计数问题得到一些奇怪的行为。使用一个简单的 app 会更好。 +2. 如果你想试用一下 `NSPointerArray`,我建议不要使用 Playground ,因为你可能因为引用计数问题得到一些奇怪的行为。使用一个简单的 app 会更好。 # 备选方案 # `NSPointerArray` 对于存储对象和保持弱引用来说非常有用,但是它有一个问题:它不是类型安全的。 -“非类型安全”,在此处的意思是编译器无法隐含 `NSPointerArray` 内部对象的类型,因为它使用的是 `AnyObject` 型对象的指针。因此,当你从数组中获取一个对象时,你需要检查它是否是你所需要的类型: +“非类型安全”,在此处的意思是编译器无法无法推断隐含在 `NSPointerArray` 内部的对象的类型,因为它使用的是 `AnyObject` 型对象的指针。因此,当你从数组中获取一个对象时,你需要把它转成你所需要的类型: ``` -ifletfirstObject=array.object(at:0)as?MyClass{// Cast to MyClass +if let firstObject=array.object(at:0)as?MyClass{// Cast to MyClass print("The first object is a MyClass") @@ -208,7 +208,7 @@ ifletfirstObject=array.object(at:0)as?MyClass{// Cast to MyClass 一个可行的方案是创建一个新类 `WeakRef` ,它带有一个普通的weak属性 `value`: ``` -classWeakRefwhereT: AnyObject{ +class WeakRefwhereT: AnyObject{ private(set)weakvarvalue:T? @@ -243,7 +243,7 @@ array.append(weakObj) 如果你想清理数组,去除其中值为 `nil` 的对象,你可以使用下面的方法: ``` -funccompact(){ +func compact(){ array=array.filter{$0.value!=nil} @@ -337,7 +337,7 @@ class ViewManager { # 结论 # -你可能不会经常使用持有弱引用的数组,但是你仍然可以学习如何实现它。在 iOS 开发中,内存管理是非常重要的,我们应该避免内存泄露,因为 iOS 没有垃圾回收器。 ¯\_(ツ)_/¯ +你可能不会经常使用持有弱引用的数组,但是这不是不去了解其实现原理的理由。在 iOS 开发中,内存管理是非常重要的,我们应该避免内存泄露,因为 iOS 没有垃圾回收器。 ¯\_(ツ)_/¯ --- From b808e92f46ab3b50bca8fa15a2a3a2bd8db5ed71 Mon Sep 17 00:00:00 2001 From: zhangqippp Date: Fri, 26 May 2017 11:29:01 +0800 Subject: [PATCH 36/59] =?UTF-8?q?=E5=B0=86=E6=96=9C=E4=BD=93=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=8A=A0=E7=B2=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/swift-arrays-holding-elements-weak-references.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TODO/swift-arrays-holding-elements-weak-references.md b/TODO/swift-arrays-holding-elements-weak-references.md index 293ca325fdf..1dc5cee7642 100644 --- a/TODO/swift-arrays-holding-elements-weak-references.md +++ b/TODO/swift-arrays-holding-elements-weak-references.md @@ -12,7 +12,7 @@ # 概述 # -*在本文中,我会谈到内存管理但是不会解释它,因为这不是本文的主题。[官方文档](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)是学习内存管理的一个好的起点。如果你有其它疑问,请留言,我会尽快给予回复。* +**在本文中,我会谈到内存管理但是不会解释它,因为这不是本文的主题。[官方文档](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)是学习内存管理的一个好的起点。如果你有其它疑问,请留言,我会尽快给予回复。** `Array` 是Swift中使用最多的集合。它会默认地对其元素持有强引用。 这种默认的行为在大多数时候都很有用,但是在某些场景下你可能想要使用弱引用。因此,苹果公司给我们提供了一个 `Array` 的替代品:**NSPointerArray**,这个类对它的元素持有弱引用。 @@ -201,7 +201,7 @@ if let firstObject=array.object(at:0)as?MyClass{// Cast to MyClass ``` -*`object(at:)` 方法来自我先前展示的 `NSPointerArray` 扩展类。* +**`object(at:)` 方法来自我先前展示的 `NSPointerArray` 扩展类。** 如果我们想使用一个类型安全的数组替代品,我们就不能使用 `NSPointerArray` 了。 @@ -222,7 +222,7 @@ class WeakRefwhereT: AnyObject{ ``` -*`private(set)` 方法将 `value` 设置为只读模式, 这样就无法在类的外部设置它的值了。* +**`private(set)` 方法将 `value` 设置为只读模式, 这样就无法在类的外部设置它的值了。** 然后,我们可以创建一组 `WeakRef` 对象,将你的 `MyClass` 对象储存到它们的 `value` 属性: @@ -250,7 +250,7 @@ func compact(){ } ``` -*`filter` 返回一个其中元素满足给定条件的新数组。你可以在[文档](https://developer.apple.com/reference/swift/array/1688383-filter)中获取更多的信息。* +**`filter` 返回一个其中元素满足给定条件的新数组。你可以在[文档](https://developer.apple.com/reference/swift/array/1688383-filter)中获取更多的信息。** 现在,我们可以将 “为什么要使用弱引用?” 小节中的例子重构为如下代码: From c8ef7cd02e6cdafffab28b410be64ac14e77a31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Fri, 26 May 2017 13:40:06 +0800 Subject: [PATCH 37/59] Create ux-review-and-redesign-of-the-cocacola-freestyle-kiosk-interface.md --- ...-the-cocacola-freestyle-kiosk-interface.md | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 TODO/ux-review-and-redesign-of-the-cocacola-freestyle-kiosk-interface.md diff --git a/TODO/ux-review-and-redesign-of-the-cocacola-freestyle-kiosk-interface.md b/TODO/ux-review-and-redesign-of-the-cocacola-freestyle-kiosk-interface.md new file mode 100644 index 00000000000..c19f46305ef --- /dev/null +++ b/TODO/ux-review-and-redesign-of-the-cocacola-freestyle-kiosk-interface.md @@ -0,0 +1,227 @@ +> * 原文地址:[UX Review and Redesign of the CocaCola Freestyle Kiosk Interface](https://medium.com/@vedantha/ux-review-and-redesign-of-the-cocacola-freestyle-kiosk-interface-f77fc087c09) +> * 原文作者:[Ved](https://medium.com/@vedantha) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# UX Review and Redesign of the CocaCola Freestyle Kiosk Interface # + +![](https://cdn-images-1.medium.com/max/800/1*ZydJMy1NI8CJ2Uwc0ocewA.jpeg) + +A CocaCola Freestyle Kiosk Interface + +### **Objectives** ### + +- Understand the Kiosk and its UX +- Identify pain points and UX obstacles +- Improve UX/UI of the CocaCola Freestyle Kiosk Interface + +### Design Process ### + +![](https://cdn-images-1.medium.com/max/800/1*Jo9PS6PGeSzVPIJpSlnrLQ.png) + +Design process followed for this project + +I followed a simple design process for this redesign project. + +I’d never used the machine myself before. So I needed to understand how it worked, and its context of use. Observing how the machine was used in different restaurants and a movie theater provided the necessary context. + +Later I conducted some quick users tests and asked the users to think aloud as they were getting their drink. I made notes of what I observed and user quotes, and analyzed this data to make sense of it. This later drove the redesign presented in this post. + +### **Observation** ### + +I visited popular restaurants around my area where the machine is located. The main objective here was to understand the audience, environment, and contexts. I also found out about rush hours by speaking to the staff of the place. + +**Context** + +- The Kiosk is placed mainly in Restaurants and places of leisure such as movie theaters. +- These places are rushed on weekends, holidays and certain times during the day. +- There is usually a queue during rush hours. + +**End Users** + +The end users of the machine include + +- Age group: teens and above (excluding those unable to reach or operate the screen) +- People with different calorie and caffeine preferences. + +### **Quick User Tests** ### + +- Number of participants: 4 +- Familiarity: 2 first time users, 2 returning users +- Time: 8–9 minutes overall + +**Notes from user tests:** + +- Returning users knew exactly what drink they wanted. They were quick to pick the categories and were familiar with the UI. +- The new users took longer to make a choice, and there was some back and forth between the different kind of main drink categories. +- The new users were also confused about the “Push” button under the screen. +- Some users wanted to “try” new flavors and mixes before filling up their cup with their preferred one. Due to this, there was a lot of back and forth as well. + +**Some user quotes** + +> “Woah, so many options!” + +> “My drink is usually over here ” + +> “Do I need to hold the ‘Push’ button?” + +### Analysis ### + +The user tests gave me some good insight into the different issues with the interface, and user flows. To understand more about this, lets consider the current user flow. From the time a user stand before the Kiosk, to getting a drink, the process looks as shown below. + +#### Current user flow (using just the Kiosk) #### + +![](https://cdn-images-1.medium.com/max/800/1*zsd3Jch6qnl0JaerA700-g.png) + +User flow for getting drinks or creating mixes + +This is a best case user flow and applicable for most users. While it seems quite simple at the outset, the major obstacle to a smooth flow here is a cognitive overload if the user does not already have a drink in mind. **Each screen has anywhere between 8 to 15 drink options to choose from. Making a decision is quite taxing in such a scenario, and the existing design does not help with that.** This process is repeated taking up more time if the user wants to create a mix. + +#### Current user flow with mobile app #### + +![](https://cdn-images-1.medium.com/max/800/1*iOMdvGQAE33q_HYoXZZn1Q.png) + +User flow with the mobile app + +Using the app simplifies the process on the kiosk slightly. It saves time by having mixes ready for the users, so they don’t have to navigate and pick each time. + +#### Pain points #### + +From user tests and comments from [many users online](https://www.facebook.com/IHateTheCocaColaFreestyleDrinkMachine/), these are the pain points I discovered + +![](https://cdn-images-1.medium.com/max/800/1*rJ48-RHDEqtJJNfqK0kgSw.jpeg) + +A screen showing 15 drink options! +- Cognitive overload from too many options ([according to Hick’s Law](https://en.wikipedia.org/wiki/Hick%27s_law)) +- Lots of back and forth for new users and for users wanting to mix flavors +- Initial confusion with push button +- Screen timeout too short — creates a sense of urgency +- Filters are either caffeine free or low calorie, but not both +- Takes too long to get some of the regular flavors such as Coke or Sprite + +### Redesign ### + +Based on the findings in the previous stage, the UX can be refined as shown below. The companion mobile app has also been considered for the redesign. First lets look at some goals, constraints and assumptions made for the redesign. + +#### Goals for redesign #### + +- Reduce the number of steps to get a drink for most people +- Reduce cognitive load +- Make it easier to create mixes +- Make navigation easier + +#### **Constraints** #### + +- The main constraint is the touchscreen. It is resistive touch, and works well for tap interactions, but not well suited for more advanced gestures. + +#### Assumptions #### + +- The Kiosk provides data back to CocaCola (or the maintaining company). +- CocaCola and partners maintain and make use of this data for analysis and product enhancement. + +#### Sketches #### + +Sketches are very powerful in generating quick ideas. I arrived at the final redesign from these initial sketches. + +![](https://cdn-images-1.medium.com/max/800/1*YUdZ1df6gR97Ntz1jHVs_w.jpeg) + +Exploring some initial concepts + +![](https://cdn-images-1.medium.com/max/800/1*5Gab6nhVVNOHvP-kLBCY1Q.jpeg) + +Some drink mixing concepts + +#### Low-Fi screens #### + +These initial prototypes were made with Balsamiq. (These come after a lot of quick sketches) + +![](https://cdn-images-1.medium.com/max/1000/1*zBNUTKoV3u_vAM-i2ItgvA.png) + +Left: Start screen || Right: after the user selects a drink + +Consider the screens above. + +Left: The start screen consists of the most popular drink products. Notice that the “low calorie” and “caffeine free” are filters. The user can select both, narrowing down the choices in subsequent screens. + +Right: After the user has made a drink choice, the system “recommends” 4 popular flavors for the selected main drink. Also notice that there are a total of 8 drink choices displayed here. + +How is this better? Well, using data backed recommendations, the design **makes it easier to select the 48 most popular drinks out of around a 100 total**. + +![](https://cdn-images-1.medium.com/max/1000/1*248PiUK1TkFRxuk2hvzQSw.png) + +Left: After selected drink has been finalized || Right: When the user is pouring the drink + +The above two screens show the flow for pouring a selected drink. Notice that the description reads “Press & Hold” addressing a user’s earlier confusion with the system. As a part of improving microinteractions, it also responds to the button press on the UI. + +Another important thing to notice — **the row of circles. These are drink recommendations that go well with the current drink.** The user can quickly press one of these options and add it to the drink mix. Again, **these are data backed recommendations.** + +![](https://cdn-images-1.medium.com/max/800/1*wje3sg8WTiqT0IGdXH6hXA.png) + +After the user selects the second drink to mix + +Other key points: + +- The design makes it easy to navigate. The pagination is an easy back and forth, and gives an idea of the total options available. +- For mixing, the user can also always go back and select a different drink. The design makes an attempt to ease the process, but also gives the user flexibility to go back or start over. + +#### How this changes user flow #### + +Lets consider how this would change the user flow* for most people when backed by data driven insights.* + +![](https://cdn-images-1.medium.com/max/800/1*cyjjSL2T-qXC7uocWjDK8g.png) + +User flow with the redesign + +#### How the mobile app could further improve experience #### + +The mobile app already makes the experience better. But it could further be improved. Lets take a look at how we can currently choose drinks on the mobile app, and how it can be improved. + +![](https://cdn-images-1.medium.com/max/800/1*XG8SU63vMnjNmmsE1UAPzw.png) + +Left: Current screen to choose drink || Right: A design with pull down to search + +#### Hi-Fi screens #### + +Now lets take a look at some hi-fidelity designs. Based on some user feedback, there are some small changes compared to the low-fi version. + +![](https://cdn-images-1.medium.com/max/1000/1*dyJzBQnzaKCw1hFwONe-Nw.png) + +Left: The start screen. The interface shows the top 6 popular drinks on first page. || Right: Once the user taps on a drink, its variants appear as shown. + +One change on the start screen is the water icon has been moved to the top-right for more visibility, and the button also takes a circular shape to be in line with the drink icon shapes. + +![](https://cdn-images-1.medium.com/max/1000/1*tka8iXFvRQNEaDVwvYBz6g.png) + +Left: Once the user taps on a drink variant, its time to pour it into the cup. || Right: UI feedback when the user presses the “PUSH” button. + +These screens are a slight change from the low-fi designs. There is an additional column in the final version — “For other choices” beside mix drink recommendations. This communicates the users’ choices more clearly. If the users want a different drink or flavor from the recommended choices, it guides them to the next steps. + +![](https://cdn-images-1.medium.com/max/1000/1*65IQM2cIqVpx421UaZtrjQ.png) + +Left: Once the user taps on a recommended second drink to mix, this screen is shown. || Right: From the start screen (screen 1), clicking on the “Fruit Flavors” lead to this screen. + +The screen numbered 6 shows the “Fruit Flavors” selection. The 5 most popular flavors are show right on top and highlighted. Less popular flavors are displayed right below. There are 3 “mixed” flavors on the Kiosk, and these are listed under mix flavors. Selecting one f these options will display options similar to screen 2. + +A click through prototype: + +[![](https://marvelapp-live.storage.googleapis.com/serve/2017/5/18854dfb4ab14e5ba0da5aa0eb69942a.png)](https://marvelapp.com/28f7gbe?emb=1&referrer=https%3A%2F%2Fmedium.com%2Fmedia%2F1dba6e6eec95ce7b8611343aef3c1237%3FpostId%3Df77fc087c09) + +### Limitations of the design ### + +Even though the redesign addresses some key points, there are some limitations. + +- The design prioritizes the most popular options over the less common ones. It might take a few extra taps if the user has a less common option in mind. +- If the users want to create a mix with flavors other than the recommended ones, they need to start over or head to the flavors scree and pick another drink. + +*Tools used for prototypes: Balsamiq, Sketch.* + +*Fruit icons are made by [*Freepik*](http://www.freepik.com) and [*Madebyoliver*](http://www.flaticon.com/authors/madebyoliver).* + + +*If you liked this post, please click the little green heart below. It helps others discover the post, and puts a big smile on my face :)* + + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 From 224eb30536cfc355fc0ba150a1b7db8ab8a0a20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Fri, 26 May 2017 13:52:00 +0800 Subject: [PATCH 38/59] Create managing-resources-for-large-scale-testing.md --- ...aging-resources-for-large-scale-testing.md | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 TODO/managing-resources-for-large-scale-testing.md diff --git a/TODO/managing-resources-for-large-scale-testing.md b/TODO/managing-resources-for-large-scale-testing.md new file mode 100644 index 00000000000..55a838d4c36 --- /dev/null +++ b/TODO/managing-resources-for-large-scale-testing.md @@ -0,0 +1,110 @@ +> * 原文地址:[Managing resources for large-scale testing](https://code.facebook.com/posts/1708075792818517/managing-resources-for-large-scale-testing/) +> * 原文作者:[Jeffrey Dunn ](https://www.facebook.com/jd),[Alexander Mols ](https://code.facebook.com/posts/1708075792818517/managing-resources-for-large-scale-testing/#),[Lawrence Lomax ](https://www.facebook.com/lawrencelomax),[Phyllipe Medeiros ](https://code.facebook.com/posts/1708075792818517/managing-resources-for-large-scale-testing/#) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# Managing resources for large-scale testing # + +As more people across the world connect on Facebook, we want to make sure our apps and services work well in a variety of scenarios. At Facebook's scale, this means testing hundreds of important interactions across numerous types of devices and operating systems for both correctness and speed before we ship new code. + +Last year, we introduced the [Facebook mobile device lab](https://code.facebook.com/posts/300815046928882/the-mobile-device-lab-at-the-prineville-data-center/), which lets engineers run tests by accessing thousands of mobile devices available in our data centers. Since then, we've built a new, unified resource management system, codenamed One World, to host these devices and other runtimes such as web browsers and emulators. Engineers at Facebook can use a single API to communicate with these remote resources within their tests and other automated systems. One World has grown to manage tens of thousands of resources and is used to execute more than 1 million jobs each day. At this scale, we have learned a lot, as we encountered unique challenges building a system that can deal with the complexities of device reliability while exposing an easy-to-use API. + +## Architecture ## + +In One World, we aim to support any application that an engineer might want to use with a remote runtime and minimal modifications to their code or environment. This means supporting standard communication mechanisms like adb (Android Debug Bridge) and providing the illusion that remote devices are connected locally. Our system consists of four main components: + +- **Runtime worker service:** Each resource type has its own runtime worker service that runs on machines managing the resource. The worker service manages the life cycle of the resource and responds to requests from clients to use its resources. +- **One World daemon:** This lightweight service runs on machines that will connect to remote resources. The service implements the protocol to communicate with workers and sets up the environment to allow local processes to communicate with remote resources. +- **Scheduler:** We use Jupiter, a job-scheduling service at Facebook, to match clients with workers whose available resources match their specified requirements. +- **Satellite:** This minimal deployment of the worker service allows engineers to connect local resources to the global One World deployment. + +![](https://fb-s-a-a.akamaihd.net/h-ak-fbx/v/t39.2365-6/18316440_1804938769822137_403114450602688512_n.jpg?oh=9d5a6207ce4edd70211834f2d49b61f3&oe=59AD8010&__gda__=1504176542_1c09041e9fae96b1dfc09e8d7d70923c) + +### Runtime worker service ### + +Each resource hosted in One World has a worker service with the following responsibilities: + +- **Resource configuration and setup:** Before receiving a job, most resources require some sort of initial setup. For mobile devices, this may include unlocking the device, disabling the lock screen, and configuring other system settings. For browsers, it may include starting a selenium stand-alone server to allow it to be controlled remotely. +- **Health checks:** Physical devices fail after prolonged use, and devices in our labs receive much more use than the average personal device. Worker services have a series of checks that they run to make sure devices are in a healthy state before allowing a client to access them. Some health checks may require technicians to repair or remove the device, and others may be resolved in an automated fashion such as charging a device due to a low battery. +- **Restoring state:** After a resource has been used, we need to prepare it for the next client. For resources such as emulators, simulators, and browsers, this can be a trivial process like rebooting from an image. Mobile devices present some unique challenges, as a complete reimage is time intensive and adds wear to internal flash storage. To restore to a known good state, worker services will take actions such as rebooting a device to reset most kernel settings, uninstalling applications, and wiping data partitions. + +Within the worker service, these steps are expressed as a state machine. Each state has monitoring and logging so we can understand bottlenecks in the system and failure rates by step. An example state machine might take the following form: + +![](https://fb-s-d-a.akamaihd.net/h-ak-fbx/v/t39.2365-6/18601820_1769494436695653_4055078421737242624_n.jpg?oh=895e499a3dbec724dd33e9c1027333eb&oe=59E82C93&__gda__=1503409907_c558f628c246a3db5ae93a5fc17c6d77) + +In this state machine example, the green steps indicate points where the worker interacts with the client. Tasks like configuration/setup and health checks can occur before a client even connects to a worker. These steps can take several minutes, so running them in advance allows for minimal latency when a client connects — often, our connection latency is as low as just a couple of seconds. Workers can take actions in response to the client request before handing the resource over for use. For example, if a resource is in a distant data center, installing applications on a device may be much faster if run locally on the host machine rather than over the network. After a client disconnects, the worker can attach additional metadata to the session that can be queried later. We use this to store logs (e.g., device logcat) and videos of sessions. By allowing the worker to add metadata asynchronously, the client does not have to wait for uploads to finish. + +Worker services are written in Python 3, which lets us run them on a variety of platforms including Linux, Mac OS, and Windows. A separate instance of the service is started for each managed resource. We attempt to isolate these service instances from each other on platforms that support it. On Linux, for example, this means launching each service in its own control group that has been configured to provide access only to the resource it controls. + +### Remote access to mobile test devices ### + +On Android, we want to support the full set of existing tools on One World, meaning that normal calls to adb must work within our system. Otherwise, every tool used at Facebook would need to be modified to be aware of One World, which would quickly spin out of control. One World runs adb servers on device hosts and provides the illusion of a local adb instance by establishing TCP tunnels. For example, we can create a TCP tunnel on port 5037, the standard adb port, and forward all traffic to the device host's adb instance. To support adb forward/reverse, we deploy a thin wrapper around the adb binary, which understands these commands and creates tunnels with two hops — first to the device host, then to the device itself. + +While the Android development environment has adb for interacting with an Android emulator or device, much of the tooling for iOS development is part of Xcode. As OneWorld runs iOS runtimes remotely, we needed a similar mechanism for remote interactions so that those runtimes could be used for running applications and different kinds of tests. + +In 2015, we open-sourced [FBSimulatorControl](https://l.facebook.com/l.php?u=https%3A%2F%2Fgithub.com%2Ffacebook%2FFBSimulatorControl&h=ATNADrf_0HzpmcoObV1zOk2XTtIHrXavAMyY1-flC2Sd6dVlnc7n3iMuFRkywaXkkI0q5q7TWYYN5Ujyut2o3ECyl7zPyqRWYOLwcK0hTY3hXzkU2bh2M17J3bQPa-ba5ViBlDY&s=1) , a project for controlling iOS simulators. We have since extended this project to allow for interfacing with devices, allowing us to accommodate many of the applications that we have at Facebook. Features of FBSimulatorControl include: + +- **Structured output for automation:** FBSimulatorControl reports on the status of devices and simulators in a machine-readable format suitable for interactions such as booting simulators and launching applications. +- **Application management:** The most common automation scenarios on iOS include the installation and launching of our iOS applications. FBSimulatorControl provides a consistent interface for this across simulators and devices, removing the complexity from the One World worker service. +- **Automation of the user interface:** iOS engineers may be familiar with the XCUITest framework for writing automated UI tests. At Facebook, we've built on top of this framework in our [WebDriverAgent](https://l.facebook.com/l.php?u=https%3A%2F%2Fgithub.com%2Ffacebook%2FWebDriverAgent&h=ATMwVeLgcgHAP5TCr9ZLfipoNz1fAwLB4IHYW496fz0p3dp1tmNn4uMBLXCEFpUFNqRc8jW2d17l5Wz_eW6R8zXbJzlHsv57nD4hygzlPQ8_8LOotDQbfdpg8pfy3JcRfI3TezE&s=1) project, a WebDriver server that runs on iOS. This allows us to automate the user interface of our iOS applications from another machine without running additional software on the worker. Our end-to-end tests apply this to execute on a separate machine from runtime hosts, bringing big performance wins for test runtimes when parallelized. +- **Remote invocation:** When reviewing the results of automated tests, additional diagnostic data can be useful. FBSimulatorControl provides APIs for collecting videos and logs from iOS simulators and devices that can then be accessed by clients. + +### One World daemon ### + +Rather than talk directly to the worker service, clients instead connect to a local daemon that handles the negotiation and environment setup. In this protocol, a client begins by creating a new session with the daemon. The session contains a specification of the type of runtime the client requires and the number of concurrent runtimes it needs. For example, when running a large test suite, a client may request a session for 20 concurrent Android emulators. The daemon prepares the requested resources by reserving a worker service instance and performing runtime-specific preparation steps. For Android sessions, this means setting up the appropriate TCP tunnels to listen on localhost and proxy the traffic to the adb daemon on the remote machine. + +As the client requires access to each reserved resource within its session, it will request a “lease” from the daemon. The daemon will respond with connection details or inform the client if the resource is not yet available. These connection details include information like the local ports to use for adb and FBSimulatorControl. After a client has finished using the resource, it releases it by calling in to the daemon again. At this point, the daemon then either frees the resource entirely to be used by a different client, or retains it to be reused within the same session if possible. + +Throughout the session, the workers and daemon communicate as part of the aforementioned state machine model. Once a worker becomes reserved through the scheduling service, it connects to the corresponding daemon to service the job. During the session, the daemon and worker will perform liveness checks, as either of them might die unexpectedly. Once the client has completed its session, the daemon sends a message to the worker to advance to the “restore state” part of its state machine. + +### Satellite mode ### + +While having access to managed remote resources allows clients to scale, sometimes engineers want to use the same tools on local devices to debug an issue. We offer a “satellite service” that allows engineers to connect a local resource to the One World cloud. This means the phone on your desk can be shared with any other engineer and used by all of Facebook's automation by just running a simple command. Like the worker services, the satellite service establishes a series of SSH tunnels from a local machine to One World to connect to the rest of the infrastructure. Targeting a satellite device instead of a managed device requires no code changes, and the satellite service sets up all required networking paths and publishes the resource's availability. + +## Using One World ## + +The One World daemon described above takes care of the heavy lifting of connecting to the service. We provide simple libraries to handle common patterns of communication with the daemon to enable engineers to easily integrate with One World. The code snippet below demonstrates the Python API for running an adb command on a One World device. It starts the One World daemon, and then `OneWorldADB` establishes a session and blocks until a device is available. A Python [context manager](https://l.facebook.com/l.php?u=https%3A%2F%2Fdocs.python.org%2F3%2Freference%2Fdatamodel.html%23context-managers&h=ATO5hkqZRWzuwNi5_1HRuvGklNDxeNjLwsnXE8LgDZHrXy6CtYaGUxY7LWZnGM0RwqliGXGjSPlx5JBw3ZYDd-4qldsEXHnSNiUo8stqrtH718dXDAf1LyxuIrJw7pqoxmC94zc&s=1) takes care of tearing everything down once the engineer's code has finished. + +``` +with OneWorldDaemon() as daemon, OneWorldADB( + daemon, + consumer='demo', + capabilities={'device-group': 'nexus-6'}, +) as adb: + adb.run('logcat') +``` + +Using multiple concurrent resources is also supported. The One World daemon manages these concurrent resources and, via the API, engineers implement their own system-specific functionality. In the example below, 10 emulators are used to run 100 jobs — the next job will be run as soon as a new emulator becomes available. The results variable at the end will contain 100 results returned by the `run_custom_test` method. + +``` +with OneWorldAndroidADB(daemon, num_emulators=10) as android: + futures = [ + asyncio.ensure_future( + android.run_with_emulator(run_custom_test) + ) for _ in range(100) + ] + results = await asyncio.gather(*futures) +``` + +Ad hoc usage is supported through the CLI: + +![](https://fb-s-d-a.akamaihd.net/h-ak-fbx/v/t39.2365-6/18309310_1292402574212388_5475499375826305024_n.gif?oh=177a635620d29123d5698eef94681a1c&oe=59BA84D2&__gda__=1503712690_1d536092299639b8a5d09f47c88b3e56) + +## Applications ## + +Beyond providing an environment for the ad hoc use of resources, One World supports numerous infrastructure projects at Facebook, including: + +- **End-to-end and integration testing:** On every code change to our apps, we run a large suite of tests to avoid introducing new bugs in our codebase. At Facebook's scale, thousands of code changes are made each day, resulting in hundreds of thousands of test runs. One World allows us to run these tests on emulators, simulators, and devices at this scale and provides quick feedback on results as engineers write code. +- **[CT-Scan](https://code.facebook.com/posts/924676474230092/mobile-performance-tooling-infrastructure-at-facebook/):** Beyond finding bugs, we also carefully test our apps for performance regressions to make sure our apps run smoothly on a large variety of devices. One World provides access to the devices representative of those owned by people who use Facebook, and it allows CT-Scan to focus on testing performance rather than managing devices. +- **[Sapienz](https://www.facebook.com/academics/photos/a.535022156549196.1073741825.144433258941423/1326609704057100/):** A multi-objective end-to-end testing system, Sapienz automatically generates test sequences using search-based software engineering to find crashes using the shortest path it can find. The Sapienz team can focus on the crash-finding algorithms while letting One World manage the emulators it uses. + +We have many important applications for One World today, but we expect our future work to greatly expand how we use One World in our engineering workflow. We're working on some exciting new features, including: + +- **Live streaming:** Engineers often want to reproduce bugs that are platform-specific. Sometimes having just a remote interface like adb isn't enough — you may want to scroll through News Feed, write comments, or tap the Like button. We're building a live streaming service that will allow engineers to interact with devices in our lab from within a web browser. This means they will be able to debug an issue on an obscure model of phone at the click of a button, all while sitting at their desk. +- **Remote profiling:** The same code can have very different performance on different devices due to varying OS versions, hardware differences, and more. We're working on building a service that allows engineers to submit code and retrieve detailed profiler data across many devices simultaneously to understand how these factors impact their code's performance. + + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 From cf5c20aecf535c609d70feaa6faa58dcbaf2426f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Fri, 26 May 2017 14:02:41 +0800 Subject: [PATCH 39/59] Create rest-2-0-graphql.md --- TODO/rest-2-0-graphql.md | 322 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 TODO/rest-2-0-graphql.md diff --git a/TODO/rest-2-0-graphql.md b/TODO/rest-2-0-graphql.md new file mode 100644 index 00000000000..fa905edf63f --- /dev/null +++ b/TODO/rest-2-0-graphql.md @@ -0,0 +1,322 @@ +> * 原文地址:[REST 2.0 Is Here and Its Name Is GraphQL](https://www.sitepoint.com/rest-2-0-graphql/) +> * 原文作者:[Michael Paris](https://www.sitepoint.com/author/mparis/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# REST 2.0 Is Here and Its Name Is GraphQL # + +![Abstract network design representing GraphQL querying data](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/05/1495045443Fotolia_71313802_Subscription_Monthly_M-1024x724.jpg) + +GraphQL is a query language for APIs. Although it’s fundamentally different than REST, GraphQL can serve as an alternative to REST that offers performance, a great developer experience, and very powerful tools. + +Throughout this article, we’re going to look at how you might tackle a few common use-cases with REST and GraphQL. This article comes complete with three projects. You will find the code for REST and GraphQL APIs that serve information about popular movies and actors as well as a simple frontend app built with HTML and jQuery. + +We’re going to use these APIs to look into how these technologies are different so that we can identify their strengths and weaknesses. To start, however, let’s set the stage by taking a quick look at how these technologies came to be. + +![](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/04/14918074751489563681243c760b-dcef-4ab0-b6ac-9a1e1e654483.png) + +## Ending Soon: Get Every SitePoint Ebook and Course for FREE! ## + +87 Ebooks, 70 Courses and 300+ Tutorials all yours for FREE with ANY web hosting plan from SiteGround! +[Claim This Deal!](https://www.sitepoint.com/premium/l/sitepoint-premium-siteground-ma) + +## The Early Days of the Web ## + +The early days of the web were simple. Web applications began as static HTML documents served over the early internet. Websites advanced to include dynamic content stored in databases (e.g. SQL) and used JavaScript to add interactivity. The vast majority of web content was viewed through web browsers on desktop computers and all was good with the world. + +![Normal Image](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/05/1494257477001-TraditionalWebserver.png) + +## REST: The Rise of the API ## + +Fast forward to 2007 when Steve Jobs introduced the iPhone. In addition to the far-reaching impacts that the smartphone would have on the world, culture, and communications, it also made developers’ lives a lot more complicated. The smartphone disrupted the development status quo. In a few short years, we suddenly had desktops, iPhones, Androids, and tablets. + +In response, developers started using [RESTful APIs](https://en.wikipedia.org/wiki/Representational_state_transfer) to serve data to applications of all shapes and sizes. The new development model looked something like this: + +![REST Server](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/05/1494257479002-RestfulServer.png) + +## GraphQL: The Evolution of the API ## + +GraphQL is a **query language for APIs** that was designed and open-sourced by Facebook. You can think of GraphQL as an alternative to REST for building APIs. Whereas REST is a conceptual model that you can use to design and implement your API, GraphQL is a standardized language, type system, and specification that creates a strong contract between client and server. Having a standard language through which all of our devices communicate simplifies the process of creating large, cross-platform applications. + +With GraphQL our diagram simplifies: + +![GraphQL Server](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/05/1494257483003-GraphQLServer.png) + +## GraphQL vs REST ## + +Throughout the rest of this tutorial (no pun intended), I encourage you to follow along with code! You can find the code for this article in [the accompanying GitHub repo](https://github.com/sitepoint-editors/sitepoint-graphql-article). + +The code includes three projects: + +1. A RESTful API +2. a GraphQL API and +3. a simple client web page built with jQuery and HTML. + +The projects are purposefully simple and were designed to provide as simple a comparison between these technologies as possible. + +If you would like to follow along open up three terminal windows and `cd` to the `RESTful`, `GraphQL`, and `Client` directories in the project repository. From each of these directories, run the development server via `npm run dev`. Once you have the servers ready, keep reading :) + +## Querying with REST ## + +Our RESTful API contains a few endpoints: + +![Markdown](http://i4.buimg.com/1949/6c5d1503224ef6b2.png) + +> **Note**: Our simple data model already has 6 endpoints that we need to maintain and document. + +Let’s imagine that we are client developers that need to use our movies API to build a simple web page with HTML and jQuery. To build this page, we need information about our movies as well as the actors that appear in them. Our API has all the functionality we might need so let’s go ahead and fetch the data. + +If you open a new terminal and run + +``` +curl localhost:3000/movies + +``` + +You should get a response that looks like this: + +``` +[ + { + "href": "http://localhost:3000/movie/1" + }, + { + "href": "http://localhost:3000/movie/2" + }, + { + "href": "http://localhost:3000/movie/3" + }, + { + "href": "http://localhost:3000/movie/4" + }, + { + "href": "http://localhost:3000/movie/5" + } +] +``` + +In RESTful fashion, the API returned an array of links to the actual movie objects. We can then go grab the first movie by running `curl http://localhost:3000/movie/1` and the second with `curl http://localhost:3000/movie/2` and so on and so forth. + +If you look at `app.js` you can see our function for fetching all the data we need to populate our page: + +``` +const API_URL = 'http://localhost:3000/movies'; +function fetchDataV1() { + + // 1 call to get the movie links + $.get(API_URL, movieLinks => { + movieLinks.forEach(movieLink => { + + // For each movie link, grab the movie object + $.get(movieLink.href, movie => { + $('#movies').append(buildMovieElement(movie)) + + // One call (for each movie) to get the links to actors in this movie + $.get(movie.actors, actorLinks => { + actorLinks.forEach(actorLink => { + + // For each actor for each movie, grab the actor object + $.get(actorLink.href, actor => { + const selector = '#' + getMovieId(movie) + ' .actors'; + const actorElement = buildActorElement(actor); + $(selector).append(actorElement); + }) + }) + }) + }) + }) + }) +} +``` + +As you might notice, this is less than ideal. When all is said and done we have made `1 + M + M + sum(Am)` round trip calls to our API where **M** is the number of movies and **sum(Am)** is the sum of the number of acting credits in each of the M movies. For applications with small data requirements, this might be okay but it would never fly in a large, production system. + +Conclusion? Our simple RESTful approach is not adequate. To improve our API, we might go ask someone on the backend team to build us a special `/moviesAndActors` endpoint to power this page. Once that endpoint is ready, we can replace our `1 + M + M + sum(Am)` network calls with a single request. + +``` +curl http://localhost:3000/moviesAndActors + +``` + +This now returns a payload that should look something like this: + +``` +[ + { + "id": 1, + "title": "The Shawshank Redemption", + "release_year": 1993, + "tags": [ + "Crime", + "Drama" + ], + "rating": 9.3, + "actors": [ + { + "id": 1, + "name": "Tim Robbins", + "dob": "10/16/1958", + "num_credits": 73, + "image": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTI1OTYxNzAxOF5BMl5BanBnXkFtZTYwNTE5ODI4._V1_.jpg", + "href": "http://localhost:3000/actor/1", + "movies": "http://localhost:3000/actor/1/movies" + }, + { + "id": 2, + "name": "Morgan Freeman", + "dob": "06/01/1937", + "num_credits": 120, + "image": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTc0MDMyMzI2OF5BMl5BanBnXkFtZTcwMzM2OTk1MQ@@._V1_UX214_CR0,0,214,317_AL_.jpg", + "href": "http://localhost:3000/actor/2", + "movies": "http://localhost:3000/actor/2/movies" + } + ], + "image": "https://images-na.ssl-images-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE@._V1_UX182_CR0,0,182,268_AL_.jpg", + "href": "http://localhost:3000/movie/1" + }, + ... +] +``` + +Great! In a single request, we were able to fetch all the data we needed to populate the page. Looking back at `app.js` in our `Client` directory we can see the improvement in action: + +``` +const MOVIES_AND_ACTORS_URL = 'http://localhost:3000/moviesAndActors'; +function fetchDataV2() { + $.get(MOVIES_AND_ACTORS_URL, movies => renderRoot(movies)); +} +function renderRoot(movies) { + movies.forEach(movie => { + $('#movies').append(buildMovieElement(movie)); + movie.actors && movie.actors.forEach(actor => { + const selector = '#' + getMovieId(movie) + ' .actors'; + const actorElement = buildActorElement(actor); + $(selector).append(actorElement); + }) + }); +} + +``` + +Our new application will be much speedier than the last iteration, but it is still not perfect. If you open up `http://localhost:4000` and look at our simple web page you should see something like this: + +![Demo App](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/05/1494257488004-DemoApp.png) + +If you look closely, you’ll notice that our page is using a movie’s title and image, and an actor’s name and image (i.e. we are only using 2 of 8 fields in a movie object and 2 of 7 fields in an actor object). That means we are wasting roughly three-quarters of the information that we are requesting over the network! This excess bandwidth usage can have very real impacts on performance as well as your infrastructure costs! + +A savvy backend developer might scoff at this and quickly implement a special query parameter named fields that takes an array of field names that will dynamically determine which fields should be returned in a specific request. + +For example, instead of `curl http://localhost:3000/moviesAndActors` we might have `curl http://localhost:3000/moviesAndActors?fields=title,image`. We might even have another special query parameter `actor_fields` that specifies which fields in the actor models should be included. E.G. `curl http://localhost:3000/moviesAndActors?fields=title,image&actor_fields=name,image`. + +Now, this would be a near optimal implementation for our simple application but it introduces a bad habit where we create custom endpoints for specific pages in our client applications. The problem becomes more apparent when you start building an iOS app that shows different information than your web page and an Android app that shows different information than the iOS app. + +Wouldn’t it be nice if we could build a generic API that explicitly represents the entities in our data model as well as the relationships between those entities but that does not suffer from the `1 + M + M + sum(Am)` performance problem? Good news! We can! + +## Querying with GraphQL ## + +With GraphQL, we can skip directly to the optimal query and fetch all the info we need and nothing more with a simple, intuitive query: + +``` +query MoviesAndActors { + movies { + title + image + actors { + image + name + } + } +} + +``` + +Seriously! To try it yourself, open GraphiQL (the awesome browser based GraphQL IDE) at [http://localhost:5000](http://localhost:5000) and run the query above. + +Now, let’s dive a little deeper. + +## Thinking in GraphQL ## + +GraphQL takes a fundamentally different approach to APIs than REST. Instead of relying on HTTP constructs like verbs and URIs, it layers an intuitive query language and powerful type system on top of our data. The type system provides a strongly-typed contract between the client and server, and the query language provides a mechanism that the client developer can use to performantly fetch any data he or she might need for any given page. + +GraphQL encourages you to think about your data as a virtual graph of information. Entities that contain information are called types and these types can relate to one another via fields. Queries start at the root and traverse this virtual graph while grabbing the information they need along the way. + +This “virtual graph” is more explicitly expressed as a **schema**. A **schema** is a collection of types, interfaces, enums, and unions that make up your API’s data model. GraphQL even includes a convenient schema language that we can use to define our API. For example, this is the schema for our movie API: + +``` +schema { + query: Query +} + +type Query { + movies: [Movie] + actors: [Actor] + movie(id: Int!): Movie + actor(id: Int!): Actor + searchMovies(term: String): [Movie] + searchActors(term: String): [Actor] +} + +type Movie { + id: Int + title: String + image: String + release_year: Int + tags: [String] + rating: Float + actors: [Actor] +} + +type Actor { + id: Int + name: String + image: String + dob: String + num_credits: Int + movies: [Movie] +} + +``` + +The type system opens the door for a lot of awesome stuff including better tools, better documentation, and more efficient applications. There is so much we could talk about, but for now, let’s skip ahead and highlight a few more scenarios that showcase the differences between REST and GraphQL. + +## GraphQL vs Rest: Versioning ## + +A [simple google search](https://www.google.com/search?q=REST+versioning&oq=REST+versioning) will result in many opinions on the best way to version (or evolve) a REST API. We’re not going to go down that rabbit hole, but I do want to stress that this is a non-trivial problem. One of the reasons that versioning is so difficult is that it is often very difficult to know what information is being used and by which applications or devices. + +Adding information is generally easy with both REST and GraphQL. Add the field and it will flow down to your REST clients and will be safely ignored in GraphQL until you change your queries. However, removing and editing information is a different story. + +In REST, it is hard to know at the field level what information is being used. We might know that an endpoint `/movies` is being used but we don’t know if the client is using the title, the image, or both. A possible solution is to add a query parameter `fields` that specifies which fields to return, but these parameters are almost always optional. For this reason, you will often see evolution occur at the endpoint level where we introduce a new endpoint `/v2/movies`. This works but also increases the surface area of our API as well as adds a burden on the developer to keep up to date and comprehensive documentation. + +Versioning in GraphQL is very different. Every GraphQL query is required to state exactly what fields are being requested in any given query. The fact that this is mandatory means that we know exactly what information is being requested and allows us to ask the question of how often and by who. GraphQL also includes primitives that allow us to decorate a schema with deprecated fields and messages for why they are being deprecated. + +This is what versioning looks like in GraphQL: + +![Versioning in GraphQL](https://philsturgeon.uk/images/article_images/2017-01-24-graphql-vs-rest-overview/graphql-versioning-marketing-site.gif) + +## GraphQL vs REST: Caching ## + +Caching in REST is straightforward and effective. In fact, caching is [one of the six guiding constraints of REST](https://en.wikipedia.org/wiki/Representational_state_transfer) and is baked into RESTful designs. If a response from an endpoint `/movies/1` indicates that the response can be cached, any future requests to `/movies/1` can simply be replaced by the item in the cache. Simple. + +Caching in GraphQL is tackled slightly differently. Caching a GraphQL API will often require introducing some sort of unique identifier for each object in the API. When each object has a unique identifier, clients can build normalized caches that use this identifier to reliably cache, update, and expire objects. When the client issues downstream queries that reference that object, the cached version of the object can be used instead. If you are interested in learning more about how caching in GraphQL works here is a [good article that covers the subject more in depth](http://graphql.org/learn/caching/). + +## GraphQL vs REST: Developer Experience ## + +Developer experience is an extremely important aspect of application development and is the reason we as engineers invest so much time into building good tooling. The comparison here is somewhat subjective but I think still important to mention. + +REST is tried and true and has a rich ecosystem of tools to help developers document, test, and inspect RESTful APIs. With that being said there is a huge price developers pay as REST APIs scale. The number of endpoints quickly becomes overwhelming, inconsistencies become more apparent, and versioning remains difficult. + +GraphQL really excels in the developer experience department. The type system has opened the door for awesome tools such as the GraphiQL IDE, and documentation is built into the schema itself. In GraphQL there is also only ever one endpoint, and, instead of relying on documentation to discover what data is available, you have a type safe language and auto-complete which you can use to quickly get up to speed with an API. GraphQL was also designed to work brilliantly with modern front-end frameworks and tools like React and Redux. If you are thinking of building an application with React, I highly recommend you check out either [Relay](https://facebook.github.io/relay/) or [Apollo client](https://github.com/apollographql/apollo-client). + +## Conclusion ## + +GraphQL offers a somewhat more opinionated yet extremely powerful set of tools for building efficient data-driven applications. REST is not going to disappear anytime soon but there is a lot to be desired especially when it comes to building client applications. + +If you are interested in learning more, check out [Scaphold.io’s GraphQL Backend as a Service](https://scaphold.io). In a [few minutes you will have a production ready GraphQL API deployed on AWS](https://www.youtube.com/watch?v=yaacnYUqY1Q) and ready to be customized and extended with your own business logic. + +I hope you enjoyed this post and if you have any thoughts or comments, I would love to hear from you. Thanks for reading! + + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 From aa4f39eaf1d7584e276c0abe911380229882cf93 Mon Sep 17 00:00:00 2001 From: sunxinlei Date: Fri, 26 May 2017 14:07:08 +0800 Subject: [PATCH 40/59] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E6=84=8F=E8=A7=81=E7=9A=84=E4=B8=80=E4=BA=9B=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E6=A0=A1=E5=AF=B9=E8=80=85=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/rearchitecting-airbnbs-frontend.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/TODO/rearchitecting-airbnbs-frontend.md b/TODO/rearchitecting-airbnbs-frontend.md index d6fe13c91b0..ea1bb372163 100644 --- a/TODO/rearchitecting-airbnbs-frontend.md +++ b/TODO/rearchitecting-airbnbs-frontend.md @@ -2,11 +2,11 @@ > * 原文作者:[Adam Neary](https://medium.com/@AdamRNeary) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 译者:[sunui](https://github.com/sunui) -> * 校对者: +> * 校对者:[Dalston Xu](https://github.com/xunge0613)、[yzgyyang](https://github.com/yzgyyang) # Airbnb 的前端重构 # -概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 端的架构。本文将讨论:(1)催生一些变化的产品驱动因素,(2)我们如何一步步摆脱遗留的 Rails 解决方案,(3)一些新技术栈的关键性支柱。彩蛋:我们将讨论接下来要做的事。 +概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 部分的架构。本文将讨论:(1)催生一些变化的产品驱动因素,(2)我们如何一步步摆脱遗留的 Rails 解决方案,(3)一些新技术栈的关键性支柱。彩蛋:我们将讨论接下来要做的事。 Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们流量最高的页面。近十年来,工程师们一直在发展、加强、和优化 Rails 输出页面的方式。 @@ -17,28 +17,28 @@ Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们 用于一个广泛搜索的路由间的过渡 -我们希望用户体验流畅,要去斟酌用户在浏览页面和缩小搜索范围时遇到的内容,而不是从 [www.airbnb.com](http://www.airbnb.com) 着陆页导航,(1)访问一个搜索结果页,(2)访问一个单一列表页,(3)访问预订流程,(4)**每个页面都由 Rails 单独传送**。 +为了使用户体验流畅,我们选择调整用户浏览页面和缩小搜索范围的交互方式,而不再采用以前那样的多页交互方式:(1)首先访问着落页 [www.airbnb.com](http://www.airbnb.com),(2)接着进入搜索结果页,(3)随后访问某个列表页,(4)最后进入预订流程。**每个页面都是一个独立的 Rails 页面**。 ![](https://cdn-images-1.medium.com/max/800/1*epBwi0kxrcW5a6Wv-T4rSg.gif) -设计三种浏览搜索页的状态:新用户,老用户,和营销页。 +设计三种浏览搜索页的状态:新用户、老用户和营销页。 -在标签页之间切换和与列表进行交互应该感到惬意而轻松。事实上,如今没有什么可以阻止我们致力于在中小屏幕上提供与本地应用相符的体验。 +在标签页之间切换和与列表进行交互应该感到惬意而轻松。事实上,如今没有什么可以阻止我们在中小屏幕上提供与原生应用一致的体验。 ![](https://cdn-images-1.medium.com/max/800/1*y_gKoEDVvBvJpGq7hfcr_g.gif) -再标签页之间切换的未来概念,考虑异步加载内容 +会考虑将来在切换标签页时,异步加载相应内容 -要开发这种类型的体验,我们需要摆脱传统的页面切换方法,最终我们兴奋地全面重构了前端代码。 +为了实现这种体验,我们需要摆脱传统的页面切换方法,最终我们兴奋地全面重构了前端代码。 -[Leland Richardson](https://medium.com/@intelligibabble) [最近在 React Conf 大会上发表了关于 React Native 的存在于高访问量 native 应用中的“褐色地带”。 ](https://www.youtube.com/watch?v=tWitQoPgs8w)这篇文章将会探讨如何在类似的约束下进行强制性升级,不过是在 web 端。如果你遇到类似的情况,希望对你有帮助。 +[Leland Richardson](https://medium.com/@intelligibabble) [最近在 React Conf 大会上发表了演讲,称 React Native 如今正处于和现有的高访问量原生应用共存的“褐色地带”。](https://www.youtube.com/watch?v=tWitQoPgs8w)这篇文章将会探讨如何在类似的限制条件下进行 web 端重构。希望你在遇到类似情况时,这篇文章对你有所帮助。 ### 从 Rails 之中解脱 ### 在我们的烧烤开火之前,因为我们的线路图上存在所有有趣的[渐进式 web 应用](https://developers.google.com/web/progressive-web-apps/)(WPA),我们需要从 Rails 中解脱出来(或者至少在 Airbnb 用 Rails 提供单独页面的这种方式)。 -不幸的是,就在几个月前,我们的搜索页还包含一些非常老旧的代码,像指环王一样,触碰它就要小心自负后果。有趣的事实:我曾尝试用一个简单的 React 组件替换一个 Rails presenter 备份过的小巧的 [Handlebars](http://handlebarsjs.com/) 模板,突然很多完全不相关的部分都崩掉了——甚至 API 响应都除了问题。原来,presenter 改变了后备 Rails 模型,多年来即使在 UI 没有渲染的时候,它也影响着所有的下游数据。 +不幸的是,就在几个月前,我们的搜索页还包含一些非常老旧的代码,像指环王一样,触碰它就要小心自负后果。有趣的事实:我曾尝试用一个简单的 React 组件来替换基于 Rails presenter 的 [Handlebars](http://handlebarsjs.com/) 模板,突然很多完全不相关的部分都崩掉了 —— 甚至 API 响应都出了问题。原来,presenter 改变了底层 Rails 模型,多年来即使在 UI 没有渲染的时候,它也影响着所有的下游数据。 简而言之,我们在这个项目中,像 Indiana Jone 用自己的宝物交换了一袋沙子,突然间庙宇开始崩塌,我们正在从石块中奔跑。 @@ -185,7 +185,7 @@ export default compose(withPhrases, withHypernovaBootstrap); 在 chrome Timeline 中 route 包的懒加载 -但是,如果你看到上面的内容,你就会发现[代码分割](https://webpack.github.io/docs/code-splitting.html) 和[延迟加载](https://webpack.js.org/guides/lazy-load-react/) 捆绑路由的影响。实质上,我们是在服务端渲染的页面并且仅仅传输最低限度的一部分用于在浏览器端交互的 Javascript 代码,然后我们利用浏览器的空余时间主动下载其余部分。 +但是,如果你看到上面的内容,你就会发现[代码分割](https://webpack.github.io/docs/code-splitting.html)和[延迟加载](https://webpack.js.org/guides/lazy-load-react/)捆绑路由的影响。实质上,我们是在服务端渲染的页面并且仅仅传输最低限度的一部分用于在浏览器端交互的 Javascript 代码,然后我们利用浏览器的空余时间主动下载其余部分。 在 Rails 端,我们有一个 controller 用于通过 SPA 交付的所有路由。每一个 action 只负责:(1)出发客户端导航中的一切请求,(2)将数据和配置引导到 Hypernova。我们把每个 action (controller、helpers 和 presenters 之间)上千行的 Ruby 代码缩减到 20-30 行。实力碾压。 @@ -377,7 +377,7 @@ RoomTypeFilter.defaultProps = defaultProps; 我们使用 Redux 来处理所有的 API 数据和“全局”数据比如认证状态和体验配置。个人来讲我喜欢 [redux-pack](https://github.com/lelandrichardson/redux-pack) 处理异步,你会发现新大陆。 -然而,当遇到页面上所有的复杂性——特别是围绕搜索的——对于一些像表单元素这样低级的用户交互使用 redux 就没那么好用了。我们发现无论如何优化,Redux 循环依然会造成输入体验的卡顿。 +然而,当遇到页面上所有的复杂性 —— 特别是围绕搜索的 —— 对于一些像表单元素这样低级的用户交互使用 redux 就没那么好用了。我们发现无论如何优化,Redux 循环依然会造成输入体验的卡顿。 ![](https://cdn-images-1.medium.com/max/600/1*12LgecpKz8HA2e2evkYacw.png) @@ -482,7 +482,7 @@ export default function withFilters(WrappedComponent) { } ``` -这里我们有一个利落的技巧。每一个需要和筛选交互的组件只需被 HOC 包裹起来,你就能做到了。它甚至还有属性类型。每个组件都通过 Redux 连接到**responseFilters**(与当前显示的结果相关联的那些),并同时保有一个本地 stagedFilters 状态对象用于更改。 +这里我们有一个利落的技巧。每一个需要和筛选交互的组件只需被 HOC 包裹起来,你就能做到了。它甚至还有属性类型。每个组件都通过 Redux 连接到 **responseFilters**(与当前显示的结果相关联的那些),并同时保有一个本地 stagedFilters 状态对象用于更改。 通过以这种方式处理状态,与我们的价格滑块进行交互对页面的其余部分没有影响,所以表现很好。而且但所有过滤器面板都具有相同的功能签名,因此开发也很简单。 From 18ad13fb0f4b9d20b836e94eae93c61f0c0fa7cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Fri, 26 May 2017 14:17:13 +0800 Subject: [PATCH 41/59] Create node-js-streams-everything-you-need-to-know.md --- ...-js-streams-everything-you-need-to-know.md | 572 ++++++++++++++++++ 1 file changed, 572 insertions(+) create mode 100644 TODO/node-js-streams-everything-you-need-to-know.md diff --git a/TODO/node-js-streams-everything-you-need-to-know.md b/TODO/node-js-streams-everything-you-need-to-know.md new file mode 100644 index 00000000000..1f9586e0148 --- /dev/null +++ b/TODO/node-js-streams-everything-you-need-to-know.md @@ -0,0 +1,572 @@ +> * 原文地址:[Node.js Streams: Everything you need to know](https://medium.freecodecamp.com/node-js-streams-everything-you-need-to-know-c9141306be93) +> * 原文作者:[Samer Buna](https://medium.freecodecamp.com/@samerbuna?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# Node.js Streams: Everything you need to know # + +![](https://cdn-images-1.medium.com/max/2000/1*xGNVMFqXXTeK7ZyK2eN21Q.jpeg) + +[Image source](https://commons.wikimedia.org/wiki/File:Urban_stream_in_park.jpg) + +Node.js streams have a reputation for being hard to work with, and even harder to understand. Well I’ve got good news for you — that’s no longer the case. + +Over the years, developers created lots of packages out there with the sole purpose of making working with streams easier. But in this article, I’m going to focus on the native [Node.js stream API](https://nodejs.org/api/stream.html). + +> “Streams are Node’s best and most misunderstood idea.” + +> — Dominic Tarr + +### What exactly are streams? ### + +Streams are collections of data — just like arrays or strings. The difference is that streams might not be available all at once, and they don’t have to fit in memory. This makes streams really powerful when working with large amounts of data, or data that’s coming from an external source one *chunk* at a time. + +However, streams are not only about working with big data. They also give us the power of composability in our code. Just like we can compose powerful linux commands by piping other smaller Linux commands, we can do exactly the same in Node with streams. + +![](https://cdn-images-1.medium.com/max/800/1*Fp3dyVZckIUjPFOp58x-zQ.png) + +Composability with Linux commands + +``` +const grep = ... // A stream for the grep output +const wc = ... // A stream for the wc input + +grep.pipe(wc) +``` + +Many of the built-in modules in Node implement the streaming interface: + +![](https://cdn-images-1.medium.com/max/800/1*lhOvZiDrVbzF8_l8QX3ACw.png) + +Screenshot captured from my Pluralsight course — Advanced Node.js + +The list above has some examples for native Node.js objects that are also readable and writable streams. Some of these objects are both readable and writable streams, like TCP sockets, zlib and crypto streams. + +Notice that the objects are also closely related. While an HTTP response is a readable stream on the client, it’s a writable stream on the server. This is because in the HTTP case, we basically read from one object (`http.IncomingMessage`) and write to the other (`http.ServerResponse`). + +Also note how the `stdio` streams (`stdin`, `stdout`, `stderr`) have the inverse stream types when it comes to child processes. This allows for a really easy way to pipe to and from these streams from the main process `stdio` streams. + +### A streams practical example ### + +Theory is great, but often not 100% convincing. Let’s see an example demonstrating the difference streams can make in code when it comes to memory consumption. + +Let’s create a big file first: + +``` +const fs = require('fs'); +const file = fs.createWriteStream('./big.file'); + +for(let i=0; i<= 1e6; i++) { + file.write('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n'); +} + +file.end(); +``` + +Look what I used to create that big file. A writable stream! + +The `fs` module can be used to read from and write to files using a stream interface. In the example above, we’re writing to that `big.file` through a writable stream 1 million lines with a loop. + +Running the script above generates a file that’s about ~400 MB. + +Here’s a simple Node web server designed to exclusively serve the `big.file`: + +``` +const fs = require('fs'); +const server = require('http').createServer(); + +server.on('request', (req, res) => { + fs.readFile('./big.file', (err, data) => { + if (err) throw err; + + res.end(data); + }); +}); + +server.listen(8000); +``` + +When the server gets a request, it’ll serve the big file using the asynchronous method, `fs.readFile`. But hey, it’s not like we’re blocking the event loop or anything. Every thing is great, right? Right? + +Well, let’s see what happens when we run the server, connect to it, and monitor the memory while doing so. + +When I ran the server, it started out with a normal amount of memory, 8.7 MB: + +![](https://cdn-images-1.medium.com/max/800/1*125_8HQ4KzJkeBcj1LcEiQ.png) + +Then I connected to the server. Note what happened to the memory consumed: + +!![](https://cdn-images-1.medium.com/max/800/1*SGJw31T5Q9Zfsk24l2yirg.gif) + +Wow — the memory consumption jumped to 434.8 MB. + +We basically put the whole `big.file` content in memory before we wrote it out to the response object. This is very inefficient. + +The HTTP response object (`res` in the code above) is also a writable stream. This means if we have a readable stream that represents the content of `big.file`, we can just pipe those two on each other and achieve mostly the same result without consuming ~400 MB of memory. + +Node’s `fs` module can give us a readable stream for any file using the `createReadStream` method. We can pipe that to the response object: + +``` +const fs = require('fs'); +const server = require('http').createServer(); + +server.on('request', (req, res) => { + const src = fs.createReadStream('./big.file'); + src.pipe(res); +}); + +server.listen(8000); +``` + +Now when you connect to this server, a magical thing happens (look at the memory consumption): + +![](https://cdn-images-1.medium.com/max/800/1*iWNNIMhF9QmD25Vho6-fRQ.gif) + +*What’s happening?* + +When a client asks for that big file, we stream it one chunk at a time, which means we don’t buffer it in memory at all. The memory usage grew by about 25 MB and that’s it. + +You can push this example to its limits. Regenerate the `big.file` with five million lines instead of just one million, which would take the file to well over 2 GB, and that’s actually bigger than the default buffer limit in Node. + +If you try to serve that file using `fs.readFile`, you simply can’t, by default (you can change the limits). But with `fs.createReadStream`, there is no problem at all streaming 2 GB of data to the requester, and best of all, the process memory usage will roughly be the same. + +Ready to learn streams now? + +> This article is a write-up of part of [my Pluralsight course about Node.js](https://www.pluralsight.com/courses/nodejs-advanced). I cover similar content in video format there. + +### Streams 101 ### + +There are four fundamental stream types in Node.js: Readable, Writable, Duplex, and Transform streams. + +- A readable stream is an abstraction for a source from which data can be consumed. An example of that is the `fs.createReadStream` method. +- A writable stream is an abstraction for a destination to which data can be written. An example of that is the `fs.createWriteStream` method. +- A duplex streams is both Readable and Writable. An example of that is a TCP socket. +- A transform stream is basically a duplex stream that can be used to modify or transform the data as it is written and read. An example of that is the `zlib.createGzip` stream to compress the data using gzip. You can think of a transform stream as a function where the input is the writable stream part and the output is readable stream part. You might also hear transform streams referred to as “*through streams*.” + +All streams are instances of `EventEmitter`. They emit events that can be used to read and write data. However, we can consume streams data in a simpler way using the `pipe` method. + +#### The pipe method #### + +Here’s the magic line that you need to remember: + +``` +readableSrc.pipe(writableDest) +``` + +In this simple line, we’re piping the output of a readable stream — the source of data, as the input of a writable stream — the destination. The source has to be a readable stream and the destination has to be a writable one. Of course, they can both be duplex/transform streams as well. In fact, if we’re piping into a duplex stream, we can chain pipe calls just like we do in Linux: + +``` +readableSrc + .pipe(transformStream1) + .pipe(transformStream2) + .pipe(finalWrtitableDest) +``` + +The `pipe` method returns the destination stream, which enabled us to do the chaining above. For streams `a` (readable), `b` and `c` (duplex), and `d` (writable), we can: + +``` +a.pipe(b).pipe(c).pipe(d) + +# Which is equivalent to: +a.pipe(b) +b.pipe(c) +c.pipe(d) + +# Which, in Linux, is equivalent to: +$ a | b | c | d +``` + +The `pipe` method is the easiest way to consume streams. It’s generally recommended to either use the `pipe` method or consume streams with events, but avoid mixing these two. Usually when you’re using the `pipe` method you don’t need to use events, but if you need to consume the streams in more custom ways, events would be the way to go. + +#### Stream events #### + +Beside reading from a readable stream source and writing to a writable destination, the `pipe` method automatically manages a few things along the way. For example, it handles errors, end-of-files, and the cases when one stream is slower or faster than the other. + +However, streams can also be consumed with events directly. Here’s the simplified event-equivalent code of what the `pipe` method mainly does to read and write data: + +``` +# readable.pipe(writable) + +readable.on('data', (chunk) => { + writable.write(chunk); +}); + +readable.on('end', () => { + writable.end(); +}); +``` + +Here’s a list of the important events and functions that can be used with readable and writable streams: + +![](https://cdn-images-1.medium.com/max/800/1*HGXpeiF5-hJrOk_8tT2jFA.png) + +Screenshot captured from my Pluralsight course - Advanced Node.js + +The events and functions are somehow related because they are usually used together. + +The most important events on a readable stream are: + +- The `data` event, which is emitted whenever the stream passes a chunk of data to the consumer +- The `end` event, which is emitted when there is no more data to be consumed from the stream. + +The most important events on a writable stream are: + +- The `drain` event, which is a signal that the writable stream can receive more data. +- The `finish` event, which is emitted when all data has been flushed to the underlying system. + +Events and functions can be combined to make for a custom and optimized use of streams. To consume a readable stream, we can use the `pipe`/`unpipe` methods, or the `read`/`unshift`/`resume` methods. To consume a writable stream, we can make it the destination of `pipe`/`unpipe`, or just write to it with the `write` method and call the `end` method when we’re done. + +#### Paused and Flowing Modes of Readable Streams #### + +Readable streams have two main modes that affect the way we can consume them: + +- They can be either in the **paused** mode +- Or in the **flowing** mode + +Those modes are sometimes referred to as pull and push modes. + +All readable streams start in the paused mode by default but they can be easily switched to flowing and back to paused when needed. Sometimes, the switching happens automatically. + +When a readable stream is in the paused mode, we can use the `read()` method to read from the stream on demand, however, for a readable stream in the flowing mode, the data is continuously flowing and we have to listen to events to consume it. + +In the flowing mode, data can actually be lost if no consumers are available to handle it. This is why, when we have a readable stream in flowing mode, we need a `data` event handler. In fact, just adding a `data` event handler switches a paused stream into flowing mode and removing the `data` event handler switches the stream back to paused mode. Some of this is done for backward compatibility with the older Node streams interface. + +To manually switch between these two stream modes, you can use the `resume()` and `pause()` methods. + +![](https://cdn-images-1.medium.com/max/800/1*HI-mtispQ13qm8ib5yey3g.png) + +Screenshot captured from my Pluralsight course — Advanced Node.js + +When consuming readable streams using the `pipe` method, we don’t have to worry about these modes as `pipe` manages them automatically. + +### Implementing Streams ### + +When we talk about streams in Node.js, there are two main different tasks: + +- The task of **implementing** the streams. +- The task of **consuming** them. + +So far we’ve been talking about only consuming streams. Let’s implement some! + +Stream implementers are usually the ones who `require` the `stream` module. + +#### Implementing a Writable Stream #### + +To implement a writable stream, we need to to use the `Writable` constructor from the stream module. + +``` +const { Writable } = require('streams'); +``` + +We can implement a writable stream in many ways. We can, for example, extend the `Writable` constructor if we want + +``` +class myWritableStream extends Writable { +} +``` + +However, I prefer the simpler constructor approach. We just create an object from the `Writable` constructor and pass it a number of options. The only required option is a `write` function which exposes the chunk of data to be written. + +``` +const { Writable } = require('stream'); +const outStream = new Writable({ + write(chunk, encoding, callback) { + console.log(chunk.toString()); + callback(); + } +}); + +process.stdin.pipe(outStream); +``` + +This write method takes three arguments. + +- The **chunk** is usually a buffer unless we configure the stream differently. +- The **encoding** argument is needed in that case, but usually we can ignore it. +- The **callback** is a function that we need to call after we’re done processing the data chunk. It’s what signals whether the write was successful or not. To signal a failure, call the callback with an error object. + +In `outStream`, we simply `console.log` the chunk as a string and call the `callback` after that without an error to indicate success. This is a very simple and probably not so useful *echo* stream. It will echo back anything it receives. + +To consume this stream, we can simply use it with `process.stdin`, which is a readable stream, so we can just pipe `process.stdin` into our `outStream`. + +When we run the code above, anything we type into `process.stdin` will be echoed back using the `outStream``console.log` line. + +This is not a very useful stream to implement because it’s actually already implemented and built-in. This is very much equivalent to `process.stdout`. We can just pipe `stdin` into `stdout` and we’ll get the exact same echo feature with this single line: + +``` +process.stdin.pipe(process.stdout); +``` + +#### Implement a Readable Stream #### + +To implement a readable stream, we require the `Readable` interface and construct an object from it: + +``` +const { Readable } = require('stream'); + +const inStream = new Readable({}); +``` + +There is a simple way to implement readable streams. We can just directly `push` the data that we want the consumers to consume. + +``` +const { Readable } = require('stream'); + +const inStream = new Readable(); + +inStream.push('ABCDEFGHIJKLM'); +inStream.push('NOPQRSTUVWXYZ'); + +inStream.push(null); // No more data + +inStream.pipe(process.stdout); +``` + +When we `push` a `null` object, that means we want to signal that the stream does not have any more data. + +To consume this simple readable stream, we can simply pipe it into the writable stream `process.stdout`. + +When we run the code above, we’ll be reading all the data from `inStream` and echoing it to the standard out. Very simple, but also not very efficient. + +We’re basically pushing all the data in the stream *before* piping it to `process.stdout`. The much better way is to push data *on demand*, when a consumer asks for it. We can do that by implementing the `read()` method in a readable stream configuration: + +``` +const inStream = new Readable({ + read(size) { + // there is a demand on the data... Someone wants to read it. + } +}); +``` + +When the read method is called on a readable stream, the implementation can push partial data to the queue. For example, we can push one letter at a time, starting with character code 65 (which represents A), and incrementing that on every push: + +``` +const inStream = new Readable({ + read(size) { + this.push(String.fromCharCode(this.currentCharCode++)); + if (this.currentCharCode > 90) { + this.push(null); + } + } +}); + +inStream.currentCharCode = 65 + +inStream.pipe(process.stdout); +``` + + + +While the consumer is reading a readable stream, the `read` method will continue to fire, and we’ll push more letters. We need to stop this cycle somewhere, and that’s why an if statement to push null when the currentCharCode is greater than 90 (which represents Z). + +This code is equivalent to the simpler one we started with but now we’re pushing data on demand when the consumer asks for it. You should always do that. + +#### Implementing Duplex/Transform Streams #### + +With Duplex streams, we can implement both readable and writable streams with the same object. It’s as if we inherit from both interfaces. + +Here’s an example duplex stream that combines the two writable and readable examples implemented above: + +``` +const { Duplex } = require('stream'); + +const inoutStream = new Duplex({ + write(chunk, encoding, callback) { + console.log(chunk.toString()); + callback(); + }, + + read(size) { + this.push(String.fromCharCode(this.currentCharCode++)); + if (this.currentCharCode > 90) { + this.push(null); + } + } +}); + +inoutStream.currentCharCode = 65; + +process.stdin.pipe(inoutStream).pipe(process.stdout); +``` + +By combining the methods, we can use this duplex stream to read the letters from A to Z and we can also use it for its echo feature. We pipe the readable `stdin` stream into this duplex stream to use the echo feature and we pipe the duplex stream itself into the writable `stdout` stream to see the letters A through Z. + +It’s important to understand that the readable and writable sides of a duplex stream operate completely independently from one another. This is merely a grouping of two features into an object. + +A transform stream is the more interesting duplex stream because its output is computed from its input. + +For a transform stream, we don’t have to implement the `read` or `write` methods, we only need to implement a `transform` method, which combines both of them. It has the signature of the `write` method and we can use it to `push` data as well. + +Here’s a simple transform stream which echoes back anything you type into it after transforming it to upper case format: + +``` +const { Transform } = require('stream'); + +const upperCaseTr = new Transform({ + transform(chunk, encoding, callback) { + this.push(chunk.toString().toUpperCase()); + callback(); + } +}); + +process.stdin.pipe(upperCaseTr).pipe(process.stdout); +``` + +In this transform stream, which we’re consuming exactly like the previous duplex stream example, we only implemented a `transform()` method. In that method, we convert the `chunk` into its upper case version and then `push` that version as the readable part. + +#### Streams Object Mode #### + +By default, streams expect Buffer/String values. There is an `objectMode` flag that we can set to have the stream accept any JavaScript object. + +Here’s a simple example to demonstrate that. The following combination of transform streams makes for a feature to map a string of comma-separated values into a JavaScript object. So `“a,b,c,d”` becomes `{a: b, c: d}`. + +``` +const { Transform } = require('stream'); +const commaSplitter = new Transform({ + readableObjectMode: true, + transform(chunk, encoding, callback) { + this.push(chunk.toString().trim().split(',')); + callback(); + } +}); +const arrayToObject = new Transform({ + readableObjectMode: true, + writableObjectMode: true, + transform(chunk, encoding, callback) { + const obj = {}; + for(let i=0; i < chunk.length; i+=2) { + obj[chunk[i]] = chunk[i+1]; + } + this.push(obj); + callback(); + } +}); +const objectToString = new Transform({ + writableObjectMode: true, + transform(chunk, encoding, callback) { + this.push(JSON.stringify(chunk) + '\n'); + callback(); + } +}); +process.stdin + .pipe(commaSplitter) + .pipe(arrayToObject) + .pipe(objectToString) + .pipe(process.stdout) +``` + +We pass the input string (for example, `“a,b,c,d”`) through `commaSplitter` which pushes an array as its readable data (`[“a”, “b”, “c”, “d”]`). Adding the `readableObjectMode` flag on that stream is necessary because we’re pushing an object there, not a string. + +We then take the array and pipe it into the `arrayToObject` stream. We need a `writableObjectMode` flag to make that stream accept an object. It’ll also push an object (the input array mapped into an object) and that’s why we also needed the `readableObjectMode` flag there as well. The last `objectToString` stream accepts an object but pushes out a string, and that’s why we only needed a `writableObjectMode` flag there. The readable part is a normal string (the stringified object). + +![](https://cdn-images-1.medium.com/max/800/1*u2kQzUD0ruPpt-xx0UOHoA.png) + +Usage of the example above + +#### Node’s built-in transform streams #### + +Node has a few very useful built-in transform streams. Namely, the zlib and crypto streams. + +Here’s an example that uses the `zlib.createGzip()` stream combined with the `fs` readable/writable streams to create a file-compression script: + +``` +const fs = require('fs'); +const zlib = require('zlib'); +const file = process.argv[2]; + +fs.createReadStream(file) + .pipe(zlib.createGzip()) + .pipe(fs.createWriteStream(file + '.gz')); +``` + +You can use this script to gzip any file you pass as the argument. We’re piping a readable stream for that file into the zlib built-in transform stream and then into a writable stream for the new gzipped file. Simple. + +The cool thing about using pipes is that we can actually combine them with events if we need to. Say, for example, I want the user to see a progress indicator while the script is working and a “Done” message when the script is done. Since the `pipe` method returns the destination stream, we can chain the registration of events handlers as well: + +``` +const fs = require('fs'); +const zlib = require('zlib'); +const file = process.argv[2]; + +fs.createReadStream(file) + .pipe(zlib.createGzip()) + .on('data', () => process.stdout.write('.')) + .pipe(fs.createWriteStream(file + '.zz')) + .on('finish', () => console.log('Done')); +``` + +So with the `pipe` method, we get to easily consume streams, but we can still further customize our interaction with those streams using events where needed. + +What’s great about the `pipe` method though is that we can use it to *compose* our program piece by piece, in a much readable way. For example, instead of listening to the `data` event above, we can simply create a transform stream to report progress, and replace the `.on()` call with another `.pipe()` call: + +``` +const fs = require('fs'); +const zlib = require('zlib'); +const file = process.argv[2]; + +const { Transform } = require('stream'); + +const reportProgress = new Transform({ + transform(chunk, encoding, callback) { + process.stdout.write('.'); + callback(null, chunk); + } +}); + +fs.createReadStream(file) + .pipe(zlib.createGzip()) + .pipe(reportProgress) + .pipe(fs.createWriteStream(file + '.zz')) + .on('finish', () => console.log('Done')); +``` + +This `reportProgress` stream is a simple pass-through stream, but it reports the progress to standard out as well. Note how I used the second argument in the `callback()` function to push the data inside the `transform()` method. This is equivalent to pushing the data first. + +The applications of combining streams are endless. For example, if we need to encrypt the file before or after we gzip it, all we need to do is pipe another transform stream in that exact order that we needed. We can use Node’s `crypto` module for that: + +``` +**const crypto = require('crypto'); +**// ... +``` + +``` +const crypto = require('crypto'); +// ... +fs.createReadStream(file) + .pipe(zlib.createGzip()) + .pipe(crypto.createCipher('aes192', 'a_secret')) + .pipe(reportProgress) + .pipe(fs.createWriteStream(file + '.zz')) + .on('finish', () => console.log('Done')); + +``` + +The script above compresses and then encrypts the passed file and only those who have the secret can use the outputted file. We can’t unzip this file with the normal unzip utilities because it’s encrypted. + +To actually be able to unzip anything zipped with the script above, we need to use the opposite streams for crypto and zlib in a reverse order, which is simple: + +``` +fs.createReadStream(file) + .pipe(crypto.createDecipher('aes192', 'a_secret')) + .pipe(zlib.createGunzip()) + .pipe(reportProgress) + .pipe(fs.createWriteStream(file.slice(0, -3))) + .on('finish', () => console.log('Done')); +``` + +Assuming the passed file is the compressed version, the code above will create a read stream from that, pipe it into the crypto `createDecipher()` stream (using the same secret), pipe the output of that into the zlib `createGunzip()` stream, and then write things out back to a file without the extension part. + +That’s all I have for this topic. Thanks for reading! Until next time! + +*If you found this article helpful, please click the💚 below. Follow me for more articles on Node.js and JavaScript.* + +I create **online courses** for [Pluralsight](https://www.pluralsight.com/search?q=samer+buna&categories=course) and [Lynda](https://www.lynda.com/Samer-Buna/7060467-1.html) . My most recent courses are [Getting Started with React.js](https://www.pluralsight.com/courses/react-js-getting-started) , [Advanced Node.js](https://www.pluralsight.com/courses/nodejs-advanced) , and [Learning Full-stack JavaScript](https://www.lynda.com/Express-js-tutorials/Learning-Full-Stack-JavaScript-Development-MongoDB-Node-React/533304-2.html) . + +I also do **online and onsite training** for groups covering beginner to advanced levels in JavaScript, Node.js, React.js, and GraphQL. [Drop me a line](mailto:samer@jscomplete.com) if you’re looking for a trainer. I’ll be teaching 6 onsite workshops this July at Forward.js, one of them is [Node.js beyond the basics](https://forwardjs.com/#node-js-deep-dive). + +If you have any questions about this article or any other article I wrote, find me on [this **slack** account](https://slack.jscomplete.com/) (you can invite yourself) and ask in the #questions room. + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 From 3c9b96c3980026dcaf245384821e4ca1da5fdf41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Fri, 26 May 2017 16:32:16 +0800 Subject: [PATCH 42/59] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b8de50d7d53..a5d44a2a930 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [506](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [507](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -76,10 +76,10 @@ ## 设计 +* [设计准则:如何说服用户去使用新的功能](https://juejin.im/post/59279b650ce463006b029cbc?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([iloveivyxuan](https://github.com/iloveivyxuan) 翻译) * [最激动人心的视觉系统其实是最枯燥乏味的](https://juejin.im/entry/59228e15a0bb9f005f60915a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Nicolas(Yifei) Li](https://github.com/yifili09) 翻译) * [人人都是设计师,我们可以的。](https://juejin.im/post/59157cdf0ce4630069d79857?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ylq167](https://github.com/ylq167) 翻译) * [设计师装腔指南](https://juejin.im/post/5915880b570c35006932fac9?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Changkun Ou](https://github.com/changkun) 翻译) -* [从形式到功能,设计思维的改变](https://juejin.im/post/58fedca744d9040069f720e4/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Ruixi](https://github.com/Ruixi) 翻译) * [所有设计译文>>](https://github.com/xitu/gold-miner/blob/master/design.md) From e71aeb63b46f56a367a380cfa3919a4e2fd8ef0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Fri, 26 May 2017 16:32:40 +0800 Subject: [PATCH 43/59] Update design.md --- design.md | 1 + 1 file changed, 1 insertion(+) diff --git a/design.md b/design.md index 760846a09ac..83e21a4b4e9 100644 --- a/design.md +++ b/design.md @@ -1,3 +1,4 @@ +* [设计准则:如何说服用户去使用新的功能](https://juejin.im/post/59279b650ce463006b029cbc?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([iloveivyxuan](https://github.com/iloveivyxuan) 翻译) * [最激动人心的视觉系统其实是最枯燥乏味的](https://juejin.im/entry/59228e15a0bb9f005f60915a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Nicolas(Yifei) Li](https://github.com/yifili09) 翻译) * [人人都是设计师,我们可以的。](https://juejin.im/post/59157cdf0ce4630069d79857?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ylq167](https://github.com/ylq167) 翻译) * [设计师装腔指南](https://juejin.im/post/5915880b570c35006932fac9?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Changkun Ou](https://github.com/changkun) 翻译) From f167c5b87da608496a7a3dcca232e07ecf13dcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Fri, 26 May 2017 16:38:33 +0800 Subject: [PATCH 44/59] Update ios.md --- ios.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ios.md b/ios.md index 2c2fbc5a35d..a429bc5853d 100644 --- a/ios.md +++ b/ios.md @@ -1,3 +1,4 @@ +* [对元素持有弱引用的 Swift 数组](https://juejin.im/post/5927a34c0ce46300575a81e1?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([zhangqippp](https://github.com/zhangqippp) 翻译) * [在 Swift 中使用闭包实现懒加载](https://juejin.im/post/590a9eeab123db00549776ee/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([lsvih](https://github.com/lsvih)) 翻译) * [掌握 Swift 的字符串细节](https://juejin.im/post/58f822ff5c497d0058e0e427/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Tuccuay(洪朔)](https://github.com/Tuccuay)) 翻译) * [MVVM-C 与 Swift](https://juejin.im/post/58ef16b8da2f60005d180666/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([DeepMissea](https://github.com/DeepMissea)) 翻译) From e3c6b618af6ecc877adcbf760645fb0bb1b89e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Fri, 26 May 2017 16:39:02 +0800 Subject: [PATCH 45/59] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5d44a2a930..d77e8e52405 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [507](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [508](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -33,10 +33,10 @@ ## iOS +* [对元素持有弱引用的 Swift 数组](https://juejin.im/post/5927a34c0ce46300575a81e1?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([zhangqippp](https://github.com/zhangqippp) 翻译) * [在 Swift 中使用闭包实现懒加载](https://juejin.im/post/590a9eeab123db00549776ee/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([lsvih](https://github.com/lsvih)) 翻译) * [掌握 Swift 的字符串细节](https://juejin.im/post/58f822ff5c497d0058e0e427/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Tuccuay(洪朔)](https://github.com/Tuccuay)) 翻译) * [MVVM-C 与 Swift](https://juejin.im/post/58ef16b8da2f60005d180666/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([DeepMissea](https://github.com/DeepMissea)) 翻译) -* [Swift 闭包和代理中的保留周期](https://juejin.im/post/58e4ac5d44d904006d2a9a19/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([oOatuo](http://atuo.xyz/)) 翻译) * [所有 iOS 译文>>](https://github.com/xitu/gold-miner/blob/master/ios.md) ## 前端 From bdb80d61970d7b6eed157613427948fee64c67c7 Mon Sep 17 00:00:00 2001 From: gy134340 Date: Sun, 28 May 2017 00:53:59 +0800 Subject: [PATCH 46/59] =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...by-leveraging-the-power-of-immutability.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md index 656ce8b3170..ee60d8cbf0b 100644 --- a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md +++ b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md @@ -3,23 +3,23 @@ > * 原文作者:[Guido Schmitz](https://medium.freecodecamp.com/@guidsen) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 译者:[gy134340](https://github.com/gy134340) -> * 校对者: +> * 校对者:[bambooom](https://github.com/bambooom),[xunge0613](https://github.com/xunge0613) -# 利用“Immutability(不可变性)”编写更为简洁高效的代码 +# 利用「Immutability(不可变性)」编写更为简洁高效的代码 ![](https://cdn-images-1.medium.com/max/2000/1*eO8-0-GT5ht8CR7TdK9knA.jpeg) 图片来自[https://unsplash.com](https://unsplash.com) -不可变性是函数式编程中的一部分,它允许你写更安全和简洁的代码。我将会通过一些 JavaScript 的例子来告诉你如何达到不可变性。 +不可变性是函数式编程中的一部分,它可以使你写出更安全更简洁的代码。我将会通过一些 JavaScript 的例子来告诉你如何达到不可变性。 **根据维基( [地址](https://en.wikipedia.org/wiki/Immutable_object) ):** -> 一个不可变对象(不能被改变的对象)是指在创建之后其状态不能被更改的对象,这与在创建之后可以被更改的可变对象(可以被改变的对象)相反。在某些情况下,一个对象的外部状态如果从外部看来是未不变的,那么即使它的一些内部属性更改了,仍被视为不可变对象。 +> 一个不可变对象(不能被改变的对象)是指在创建之后其状态不能被更改的对象,这与在创建之后可以被更改的可变对象(可以被改变的对象)相反。在某些情况下,一个对象的外部状态如果从外部看来没有变化,那么即使它的一些内部属性更改了,仍被视为不可变对象。 ### 不可变的数组 -数组是抓住不可变性如何工作的一个要点。我们来看一下。 +数组是了解不可变性如何运作的一个很好的起点。我们来看一下。 ``` const arrayA = [1, 2, 3]; @@ -49,13 +49,13 @@ console.log(arrayB); // [1, 2, 3, 4, 5] 这才是我们要的,代码不改变其它的值。 -记住:当使用 [push](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) 来给数组添加一个值时,你在**改变**这个数组,你想要避免值的改变因为这个可能会影响你代码里的其他部分。[slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) 会返回一个复制的数组。 +记住:当使用 [push](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) 来给数组添加一个值时,你在**改变**这个数组,因为这样可能会影响代码里的其他部分,所以你想要避免使变量值发生改变。[slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) 会返回一个复制的数组。 ### 函数 -现在你知道了如何避免改变其它的值。那要怎样写“纯”的函数呢?纯洁性是指调用一个函数同时不会产生额外的影响和改变状态的函数属性。 +现在你知道了如何避免改变其它的值。那如何写「纯」的函数呢?纯函数是指不会产生任何副作用,也不会改变状态的函数。 -让我们看看一个函数,它利用了数组实例的相同原理。首先我们写一个会改变其它值的函数,然后我们将这个函数优化为“纯”函数。 +我们来看一个示例函数,其原理与前面数组示例的原理相同。首先我们写一个会改变其它值的函数,然后我们将这个函数优化为「纯」函数。 ``` const add = (arrayInput, value) => { @@ -72,11 +72,11 @@ console.log(add(array, 4)); // [1, 2, 3, 4] console.log(add(array, 5)); // [1, 2, 3, 4, 5] ``` -所以再一次的,我们**改变**我们的输入来创建一个不可预测的函数。在函数式编程的世界里,有一个关于函数的铁律:**函数对于相同的输入应当返回相同的值。** +于是我们又一次**改变**输入的变量的值,这使得这个函数变得不可预测。在函数式编程的世界里,有一个关于函数的铁律:**函数对于相同的输入应当返回相同的值。** 上面的函数违反了这一规则,每次我们调用 **add** 方法,它都会改变**数组**变量导致结果不一样。 -让我们来看看怎样修改 **add** 函数的实现来使其不可变。 +让我们来看看怎样修改 **add** 函数来使其不可变。 ``` const add = (arrayInput, value) => { @@ -99,10 +99,10 @@ const resultB = add(array, 5); console.log(resultB); // [1, 2, 3, 5] ``` -现在我们可以多次调用这个函数,然后根据相同的输入,获得相同的输出。这是因为我们不再改变 **array** 变量。我们把这个函数叫做“纯函数”。 +现在我们可以多次调用这个函数,且相同的输入获得相同的输出,与预期一致。这是因为我们不再改变 **array** 变量。我们把这个函数叫做“纯函数”。 > **注意:**你还可以使用 **concat**,来代替 **slice** 和 **push**。 -> 那样就是:arrayInput.concat(value); +> 即:arrayInput.concat(value); 我们还可以使用 ES6 的[扩展语法](https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Operators/Spread_operator),来简化函数。 @@ -112,19 +112,19 @@ const add = (arrayInput, value) => […arrayInput, value]; ### 并发 -NodeJS 的应用有一个叫并发的概念,并发的操作是指两个计算可以同时的进行而不用管另外的一个。如果有两个线程,第二个的计算不需要等待第一个的完成。 +NodeJS 的应用有一个叫并发的概念,并发操作是指两个计算可以同时的进行而不用管另外的一个。如果有两个线程,第二个计算不需要等待第一个完成即可开始。 ![](https://cdn-images-1.medium.com/max/800/1*LS1VkNditQwYMJvtIPAhdg.png) 可视化的并发操作 -NodeJS 用事件循环机制使并发成为可能。事件循环循环重复接收事件,并对每个事件添加监听。这个模型允许 NodeJS 的应用处理大规模的请求。如果你想学习更多,读一下[这篇关于事件循环的文章](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick)。 +NodeJS 用事件循环机制使并发成为可能。事件循环重复接收事件,并一次触发一个监听该事件的处理程序。这个模型允许 NodeJS 的应用处理大规模的请求。如果你想学习更多,读一下[这篇关于事件循环的文章](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick)。 -不可变性跟并发又有什么关系呢?由于多个操作可能并发的改变函数的作用域的值,这将会产生不可靠的输出和导致意向不到的结果。明确函数是否改变它作用域之外的值,因为这可能真的会很危险。 +不可变性跟并发又有什么关系呢?由于多个操作可能会并发地改变函数的作用域的值,这将会产生不可靠的输出和导致意想不到的结果。注意函数是否改变它作用域之外的值,因为这可能真的会很危险。 ### 下一步 -不可变性是你学习函数式编程中重要的概念。你也许想了解一下由 Facebook 开发者写的 [ImmutableJS](https://facebook.github.io/immutable-js),这一个库提供正确的不可变数据结构比如说 **Map**、**Set**、和 **List**。 +不可变性是学习函数式编程过程中的一个重要概念。你可以了解一下由 Facebook 开发者写的 [ImmutableJS](https://facebook.github.io/immutable-js),这一个库提供一些不可变的数据结构,比如说 **Map**、**Set**、和 **List**。 [![](http://i2.muimg.com/1949/d4d40e047da813b5.png)](https://medium.com/@dtinth/immutable-js-persistent-data-structures-and-structural-sharing-6d163fbd73d2) From 571143079a240f3f94eb96aee7a5b243d6f30521 Mon Sep 17 00:00:00 2001 From: gy134340 Date: Sun, 28 May 2017 00:56:29 +0800 Subject: [PATCH 47/59] =?UTF-8?q?=E6=A0=A1=E5=AF=B9=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-and-cleaner-code-by-leveraging-the-power-of-immutability.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md index ee60d8cbf0b..20baf1b1612 100644 --- a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md +++ b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md @@ -5,7 +5,7 @@ > * 译者:[gy134340](https://github.com/gy134340) > * 校对者:[bambooom](https://github.com/bambooom),[xunge0613](https://github.com/xunge0613) -# 利用「Immutability(不可变性)」编写更为简洁高效的代码 +# 利用 Immutability(不可变性)编写更为简洁高效的代码 ![](https://cdn-images-1.medium.com/max/2000/1*eO8-0-GT5ht8CR7TdK9knA.jpeg) From f36db67431cc7730fbca79c136bd81973afd8c11 Mon Sep 17 00:00:00 2001 From: sunxinlei Date: Wed, 31 May 2017 10:24:47 +0800 Subject: [PATCH 48/59] =?UTF-8?q?=E8=B7=9F=E6=8D=AE=E6=A0=A1=E5=AF=B9?= =?UTF-8?q?=E6=84=8F=E8=A7=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/rearchitecting-airbnbs-frontend.md | 78 ++++++++++++------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/TODO/rearchitecting-airbnbs-frontend.md b/TODO/rearchitecting-airbnbs-frontend.md index ea1bb372163..afc24e784ab 100644 --- a/TODO/rearchitecting-airbnbs-frontend.md +++ b/TODO/rearchitecting-airbnbs-frontend.md @@ -6,18 +6,18 @@ # Airbnb 的前端重构 # -概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 部分的架构。本文将讨论:(1)催生一些变化的产品驱动因素,(2)我们如何一步步摆脱遗留的 Rails 解决方案,(3)一些新技术栈的关键性支柱。彩蛋:我们将讨论接下来要做的事。 +概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 部分的架构。本文将讨论:(1)催生一些变化的产品驱动因素,(2)我们如何一步步摆脱遗留的 Rails 解决方案,(3)一些新技术栈的关键性支柱。彩蛋:我们将透露一下未来的发展方向。 -Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们流量最高的页面。近十年来,工程师们一直在发展、加强、和优化 Rails 输出页面的方式。 +Airbnb 每天处理超过 7500 万次搜索,这使得搜索页面成为我们流量最高的页面。近十年来,工程师们一直在发展、加强和优化 Rails 输出页面的方式。 最近,我们转移到了主页以外的垂直页面,[来介绍一些体验和去处](https://www.airbnb.com/new)。作为 web 端新增产品的一部分,我们花时间重新思考了搜索体验本身。 ![](https://cdn-images-1.medium.com/max/800/1*VMRwDmHVeYC3YnJhhtKn4Q.gif) -用于一个广泛搜索的路由间的过渡 +在一个用于宽泛搜索的路由之间过渡 -为了使用户体验流畅,我们选择调整用户浏览页面和缩小搜索范围的交互方式,而不再采用以前那样的多页交互方式:(1)首先访问着落页 [www.airbnb.com](http://www.airbnb.com),(2)接着进入搜索结果页,(3)随后访问某个列表页,(4)最后进入预订流程。**每个页面都是一个独立的 Rails 页面**。 +为了使用户体验流畅,我们选择调整用户浏览页面和缩小搜索范围的交互方式,而不再采用以前那样的多页交互方式:(1)首先访问着陆页 [www.airbnb.com](http://www.airbnb.com),(2)接着进入搜索结果页,(3)随后访问某个列表页,(4)最后进入预订流程。**每个页面都是一个独立的 Rails 页面**。 ![](https://cdn-images-1.medium.com/max/800/1*epBwi0kxrcW5a6Wv-T4rSg.gif) @@ -30,17 +30,17 @@ Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们 会考虑将来在切换标签页时,异步加载相应内容 -为了实现这种体验,我们需要摆脱传统的页面切换方法,最终我们兴奋地全面重构了前端代码。 +为了实现这种体验,我们需要摆脱传统的页面切换方法,最终我们只好全面重构了前端代码。 -[Leland Richardson](https://medium.com/@intelligibabble) [最近在 React Conf 大会上发表了演讲,称 React Native 如今正处于和现有的高访问量原生应用共存的“褐色地带”。](https://www.youtube.com/watch?v=tWitQoPgs8w)这篇文章将会探讨如何在类似的限制条件下进行 web 端重构。希望你在遇到类似情况时,这篇文章对你有所帮助。 +[Leland Richardson](https://medium.com/@intelligibabble) [最近在 React Conf 大会上发表了演讲,称 React Native 如今正处于和现有的高访问量原生应用共存的“褐色地带”](https://www.youtube.com/watch?v=tWitQoPgs8w)这篇文章将会探讨如何在类似的限制条件下进行 web 端重构。希望你在遇到类似情况时,这篇文章对你有所帮助。 ### 从 Rails 之中解脱 ### -在我们的烧烤开火之前,因为我们的线路图上存在所有有趣的[渐进式 web 应用](https://developers.google.com/web/progressive-web-apps/)(WPA),我们需要从 Rails 中解脱出来(或者至少在 Airbnb 用 Rails 提供单独页面的这种方式)。 +在我们的烧烤开火之前,因为我们的线路图上存在所有有趣的[渐进式 web 应用](https://developers.google.com/web/progressive-web-apps/)(WPA),我们需要从 Rails 中解脱出来(或者至少在 Airbnb 用 Rails 提供单独页面的这种方式)。 不幸的是,就在几个月前,我们的搜索页还包含一些非常老旧的代码,像指环王一样,触碰它就要小心自负后果。有趣的事实:我曾尝试用一个简单的 React 组件来替换基于 Rails presenter 的 [Handlebars](http://handlebarsjs.com/) 模板,突然很多完全不相关的部分都崩掉了 —— 甚至 API 响应都出了问题。原来,presenter 改变了底层 Rails 模型,多年来即使在 UI 没有渲染的时候,它也影响着所有的下游数据。 -简而言之,我们在这个项目中,像 Indiana Jone 用自己的宝物交换了一袋沙子,突然间庙宇开始崩塌,我们正在从石块中奔跑。 +简而言之,我们在这个项目中,就好像 Indiana Jone 用一袋沙子替换了宝物,突然间庙宇开始崩塌,我们正在从石块中奔跑。 @@ -50,19 +50,19 @@ Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们 但一旦你想渲染客户端路由,你需要能够以预定的形式动态请求所需的数据。将来我们可能用类似 [GraphQL](http://graphql.org/) 的东西解决这个问题,但是现在暂且把它放到一边吧,因为这件事和重构代码没太大关系。相反,我们选择在我们的 API 的 “v2” 上进行调整,我们需要我们所有的组件来开始处理规范的数据格式。 -如果你发现你自己和我们情况类似并且是一个大型的应用,你可能发现我们像我们这样做,规划迁移现有的服务器端数据管道是很容易的。简单地在任何地方用 Rails 渲染一个React组件,并确保数据输入是 API 所规定的类型。你可以用客户端的 React PropTypes 来进一步验证数据类型是否与 API v2 一致。 +如果你自己和我们处在类似的情况中,在维护一个大型的应用,你可能发现我们像我们这样做,规划迁移现有的服务器端数据管道是很容易的。只需在任何地方用 Rails 渲染一个React组件,并确保数据输入是 API 所规定的类型。你可以用客户端的 React PropTypes 来进一步验证数据类型是否与 API v2 一致。 -对我们来说棘手的问题是和那些参与客户预定流程交互的团队协作:商业旅游、发展、度假租赁团队;中国和印度市场团队,灾难恢复团队...等等,我们需要重新培训所有这些人,即使在技术上可以将数据直接传递到正在呈现的组件上("是的,我明白,这仅仅是一种实验,但是..."),所有的数据都要通过 API。 +对我们来说棘手的问题是和那些参与客户预定流程交互的团队协作:商业旅游、发展、度假租赁团队;中国和印度市场团队,灾难恢复团队等等,我们需要重新培训所有这些人,即使在技术上可以将数据直接传递到正在呈现的组件上("是的,我明白,这仅仅是一种实验,但是..."),**所有的数据**都要通过 API。 -#### 第 2 步: 非 API 数据: 配置、试验、惯用语、本地化、 国际化… #### +#### 第 2 步: 非 API 数据: 配置、试验、惯用语、本地化、国际化… #### -有一类独特的数据和我们设想的 API 化的数据不同,包括应用配置,用户试验任务,国际化,本地化等等类似的问题。近年来,Airbnb 已经建立了一套难以置信的工具来支持这些功能,但是把这些数据传送到前端的机制就不那么令人愉快了(在革命开始之前,或许就已经很蹩脚了!)。 +有一类独特的数据和我们设想的 API 化的数据不同,包括应用配置,用户试验任务,国际化,本地化等等类似的问题。近年来,Airbnb 已经建立了一套很棒的工具来支持这些功能,但是把这些数据传送到前端的机制就不那么令人愉快了(在革命开始之前,或许就已经很蹩脚了!)。 -我们使用 [Hypernova](https://www.npmjs.com/package/hypernova) 来服务端渲染 React,但是在我们此次重构深入之前,无论服务端渲染时 React 组件中的试验交付会不会爆发或者客户端上提供的字符串转换是否都可以在服务器上可靠地使用,这些都还有点模糊。最重要的是,如果服务器和客户端输出匹配不到位,页面不仅会不断闪烁刷新 diff,还可以在加载后重新渲染整个页面,这对于性能来说很可怕。 +我们使用 [Hypernova](https://www.npmjs.com/package/hypernova) 在服务端渲染渲染 React,但是在我们此次重构深入之前,无论服务端渲染时 React 组件中的试验交付会不会爆发或者客户端上提供的字符串转换是否都可以在服务器上可靠地使用,这些都还有点模糊。最重要的是,如果服务器和客户端输出匹配不到位,页面不仅会不断闪烁刷新 diff,还会在加载后重新渲染整个页面,这对于性能来说很可怕。 -更糟糕的是,我们有很久以前写过一些神奇的 Rails 功能,比如 `add_bootstrap_data(key, value)` 表面上可以在 Rails 中的任何地方调用,通过 `BootstrapData.get(key)` 使数据在客户端的全局可用(再次强调,对 Hypernova 来说已经不必要了)。这作为小团队的一个实用程序开始成为对大团队和应用来说不可溯源的巫术。由于每个团队拥有不同的页面或功能,因此“数据清洗”变得越来越棘手,因此每个团队都会培养出一种不同的加载配置的机制,以满足其独特需求。 +更糟糕的是,我们有很久以前写过一些神奇的 Rails 功能,比如 `add_bootstrap_data(key, value)` 表面上可以在 Rails 中的任何地方调用,通过 `BootstrapData.get(key)` 使数据在客户端的全局可用(再次强调,对 Hypernova 来说已经不必要了)。曾经这些小工具对小团队来说很实用,但如今随着团队规模扩大,应用规模扩张,这些小工具反而变成了累赘。由于每个团队拥有不同的页面或功能,因此“数据清洗”变得越来越棘手,因此每个团队都会培养出一种不同的加载配置的机制,以满足其独特需求。 -显然,这已经崩溃了,所以我们融合了一个用于引导非 API 数据的规范机制,我们开始将所有应用程序和页面迁移到 Rails 和 React/Hypernova 之间的这种切换。 +显然, 这套机制已经崩溃了,所以我们融合了一个用于引导非 API 数据的规范机制,我们开始将所有应用程序和页面迁移到 Rails 和 React/Hypernova 之间的这种切换。 ``` import React, { PropTypes } from 'react'; @@ -144,10 +144,8 @@ function withHypernovaBootstrap(App) { } render() { - // 理想情况下,我们只想传输 bootstrapData - // 如果你有从 redux 或 alt 数据 从服务端到 bootstrap - // 你当然可以只传输一个在 bootstrapData 中的 key - // 其他属性被处理但是不会传入应用 + // 理想情况下,我们只想通过 bootstrapData 传输数据 + // 如果你使用 redux 或从服务端转换数据到 bootstrap,你其实可以将数据当作一个键值(key)传入 bootstrapData,其他属性被使用但是不会传入 app 。 return ; } } @@ -166,10 +164,10 @@ export default compose(withPhrases, withHypernovaBootstrap); 用于引导非 API 数据规范的更高阶的组件 -这个更高阶的组件做了两件更重要的事情: +这个非常高阶的组件做了两件更重要的事情: 1. 它接收一个引导数据作为普通的旧对象的规范形式,并且正确地初始化所有支持的工具,用于服务器渲染和客户端渲染。 -2. 它吞噬除了一切除了 `bootstrapData` ,它是另一个简单的对象,必要时把 `` 组件传入 Redux 作为 children 使用。 +2. 它吞噬除了 `bootstrapData` 的一切 ,它是另一个简单的对象,必要时把 `` 组件传入 Redux 作为 children 使用。 单纯来看,我们删除了 `add_bootstrap_data`,并阻止工程师将任意键传递到顶级的 React 组件。秩序被重新恢复,以前我们在客户端中动态地导航到路由,并且渲染材料复杂的 content,而不需要Rails来支持它。 @@ -183,11 +181,11 @@ export default compose(withPhrases, withHypernovaBootstrap); ![](https://cdn-images-1.medium.com/max/800/1*O2fK16vfyWaDT-IR61drPw.png) -在 chrome Timeline 中 route 包的懒加载 +在 Chrome Timeline 中 route 包的懒加载 -但是,如果你看到上面的内容,你就会发现[代码分割](https://webpack.github.io/docs/code-splitting.html)和[延迟加载](https://webpack.js.org/guides/lazy-load-react/)捆绑路由的影响。实质上,我们是在服务端渲染的页面并且仅仅传输最低限度的一部分用于在浏览器端交互的 Javascript 代码,然后我们利用浏览器的空余时间主动下载其余部分。 +但是,再看看上文,你就会发现路由对[代码分割](https://webpack.github.io/docs/code-splitting.html)和[延迟加载](https://webpack.js.org/guides/lazy-load-react/)进行捆绑造成的影响。实质上,我们在服务端渲染页面并且仅仅传输最低限度的一部分用于在浏览器端交互的 Javascript 代码,然后我们利用浏览器的空余时间主动下载其余部分。 -在 Rails 端,我们有一个 controller 用于通过 SPA 交付的所有路由。每一个 action 只负责:(1)出发客户端导航中的一切请求,(2)将数据和配置引导到 Hypernova。我们把每个 action (controller、helpers 和 presenters 之间)上千行的 Ruby 代码缩减到 20-30 行。实力碾压。 +在 Rails 端,我们有一个 controller 用于通过 SPA 交付的所有路由。每一个 action 只负责:(1)触发客户端导航中的一切请求,(2)将数据和配置引导到 Hypernova。我们把每个 action (controller、helpers 和 presenters 之间)都有上千行的 Ruby 代码缩减到 20-30 行。实力碾压。 但这不仅仅是代码的不同... @@ -199,7 +197,7 @@ export default compose(withPhrases, withHypernovaBootstrap); #### 异步组件 #### -之前的 React ,我们需要一次渲染整个页面,我们以前的 React 都是这么做的。但现在我们使用异步组件,类似[这种](https://medium.com/@thejameskyle/react-loadable-2674c59de178)方式, mount 以后加载组件层次结构的部分。 +在(采用)React 之前,我们需要一次渲染整个页面,我们以前的 React 都是这么做的。但现在我们使用异步组件,类似[这种](https://medium.com/@thejameskyle/react-loadable-2674c59de178)方式, 挂在(mount)以后加载组件层次结构的部分。 ``` export default class AsyncComponent extends React.Component { @@ -241,7 +239,7 @@ AsyncComponent.propTypes = { }; ``` -这对于最初不可见的重量级元素尤其有用,比如 Modals 和 Panels。我们的明确目标是精确地提供初始化页面可见部分所需的 所需的 JavaScript,并使其可交互,而不只一行。这也意味着如果,比方说团队想使用 D3 用于页面弹窗的一个图表,而其他部分不使用 D3,这时候他们就可以权衡一下下载仓库的代码,可以把他们的弹窗代码和其他代码隔离出来。 +这对于最初不可见的重量级元素尤其有用,比如 Modals 和 Panels。我们的明确目标是一行也不多地提供初始化页面可见部分所需的 JavaScript,并使其可交互。这也意味着如果,比方说团队想使用 D3 用于页面弹窗的一个图表,而其他部分不使用 D3,这时候他们就可以权衡一下下载仓库的代码,可以把他们的弹窗代码和其他代码隔离出来。 最重要的是,它可以简单地在任何需要的地方使用: @@ -269,16 +267,16 @@ export default function MapAsync(props) { view raw ``` -这里我们可以简单地把我们的同步版本的地图换成异步版本,这在小断点上特别有用,用户通过点击按钮显示地图。考虑到大多数用户用手机,在担心 Google 地图之前,让他们进入互动这样会缩短加载时的焦虑感。 +这里我们可以简单地把我们的同步版本的地图换成异步版本,这在小断点上特别有用,用户通过点击按钮显示地图。考虑到大多数用户用手机,在担心 Google 地图之前,让他们进入互动会缩短加载时的焦虑感。 -另外,注意 `scheduleAsyncLoad()` 的效率,在用户交互之前就要请求包。考虑到地图如此频繁的使用,我们不需要等待用户交互就去请求它。而是在用户进入主页和搜索页的时候就把它加入队列,如果用户在下载完成之前就请求了它,他们会看到一个 `` 直到组件可用。没毛病。 +另外,注意 `scheduleAsyncLoad()` 组件,在用户交互之前就要请求包。考虑到地图如此频繁地被使用,我们不需要等待用户交互才去请求它。而是在用户进入主页和搜索页的时候就把它加入队列,如果用户在下载完成之前就请求了它,他们会看到一个 `` 直到组件可用。没毛病。 这种方法的最后一个好处是 `HomesSearch_Map` 成为浏览器可以缓存的命名包。当我们分解较大的基于路由的捆绑包时,应用程序中 slowly-changing 的部分在更新时保持不变,从而进一步节省了 JavaScript 下载时间。 #### 构建无障碍的设计语言 #### -毫无疑问,它保证的是一个专有的需求,但是我们已经开始构建内部组件库,其中辅助功能被强制为一个严格的约束。在接下来的几个月中,我们将替换所有与屏幕阅读器不兼容的 UI。 +毫无疑问,它保证的是一个专有的需求,但是我们已经开始构建内部组件库,其中辅助功能被强制为一个严格的约束。在接下来的几个月中,我们将替换所有与屏幕阅读器不兼容的横跨客流的 UI 界面。 ``` import React, { PropTypes } from 'react'; @@ -365,9 +363,9 @@ RoomTypeFilter.propTypes = propTypes; RoomTypeFilter.defaultProps = defaultProps; ``` -通过我们的设计语言系统加入的无障碍设计到产品的例子 +通过我们的设计语言系统将无障碍设计加入到产品的例子 -这个 UI 非常丰富,我们希望将 CheckBox 不仅与 title 相关联,还可以使用 `aria-describedby` 与 subtitle 关联。为了实现这一点,需要 DOM 中唯一的标识符,这意味着强制关联一个必须的 ID 作为任何调用方需要提供的属性。如果一个组件被用于生产,这些是 UI 是可以强制约束类型的,它提供内置的可访问性。 +这个 UI 非常丰富,我们不仅希望将 CheckBox 与 title 相关联,还希望与使用了 `aria-describedby` 的 subtitle 关联。为了实现这一点,需要 DOM 中唯一的标识符,这意味着强制关联一个必须的 ID 作为任何调用方需要提供的属性。如果一个组件被用于生产,这些是 UI 是可以强制约束类型的,它提供内置的可访问性。 上面的代码也演示了我们的响应式实体 HideAt 和 ShowAt,它使我们能够大幅度地改变用户在不同屏幕尺寸下的体验,而无需使用 CSS 控制隐藏和显示。这造就了更精简的页面。 @@ -383,9 +381,9 @@ RoomTypeFilter.defaultProps = defaultProps; 我们的房间类型筛选器 (代码在上面) -所以对于用户的所有操作我们使用组件的本地状态,除非触发路由变化或者网络请求才是用 Redux,并且我们没再遇到什么麻烦。 +所以对于用户的所有操作我们使用组件的本地状态,除非触发路由变化或者网络请求才使用 Redux,并且我们没再遇到什么麻烦。 -同时,我喜欢 Redux container 组件的那种感觉,并且我们即使带有本地状态,我们依然可以构建可以共享的高阶组件。一个伟大的例子就是我们的筛选功能。搜索[在底特律的家](https://www.airbnb.com/s/Detroit--MI--United-States/homes),你会在页面上看见几个不同的面板,每一个都可以独立操作,你可以更改你的搜索条件。在不同的断点之间,实际上有几十个组件需要知道当前应用的搜索过滤器以及如何更新它们,在用户交互期间被暂时或z正式地被用户接受。 +同时,我喜欢 Redux container 组件的那种感觉,并且我们即使带有本地状态,我们依然可以构建可以共享的高阶组件。一个伟大的例子就是我们的筛选功能。搜索[在底特律的家](https://www.airbnb.com/s/Detroit--MI--United-States/homes),你会在页面上看见几个不同的面板,每一个都可以独立操作,你可以更改你的搜索条件。在不同的断点之间,实际上有几十个组件需要知道当前应用的搜索过滤器以及如何更新它们,在用户交互期间被暂时或正式地被用户接受。 ``` import React, { PropTypes } from 'react'; @@ -482,24 +480,24 @@ export default function withFilters(WrappedComponent) { } ``` -这里我们有一个利落的技巧。每一个需要和筛选交互的组件只需被 HOC 包裹起来,你就能做到了。它甚至还有属性类型。每个组件都通过 Redux 连接到 **responseFilters**(与当前显示的结果相关联的那些),并同时保有一个本地 stagedFilters 状态对象用于更改。 +这里我们有一个利落的技巧。每一个需要和筛选交互的组件只需被 HOC 包裹起来,就是这么简单。它甚至还有属性类型。每个组件都通过 Redux 连接到 **responseFilters**(与当前显示的结果相关联),并同时保有一个本地 stagedFilters 状态对象用于更改。 -通过以这种方式处理状态,与我们的价格滑块进行交互对页面的其余部分没有影响,所以表现很好。而且但所有过滤器面板都具有相同的功能签名,因此开发也很简单。 +以这种方式处理状态,与我们的价格滑块进行交互对页面的其余部分没有影响,所以表现很好。而且所有过滤器面板都具有相同的功能签名,因此开发也很简单。 -### 未来做些什么? ### +### 未来做些什么?### -既然现在已经良策在手,我们可以把目光转向未来。 +既然现在繁重的前端改造工作已经接近完成,我们可以把目光转向未来。 - [AMP](https://www.ampproject.org/) 核心预订流程中的所有页面的 AMP 版本将会实现亚秒级(某些情况下)在手机 web 上 Google 搜索的 **可交互时间**,通过移动网络和桌面网络,所需的许多更改将在 P50 / P90 / P95 冷负载时间内实现显着改善。 - [PWA](https://developers.google.com/web/progressive-web-apps/) 功能将实现亚秒级(在某些情况下)返回访客的**可交互时间**,并将打开离线优先功能的大门,因此对于具有脆弱网络连接的用户非常关键。 -- 将最终的锤子应用到传统的技术/框架上将会将包大小减少一半。这不是华而不实的工作,我们最终翻出 jQuery、Alt、Bootstrap、Underscore 以及所有额外的 CSS 请求(他们使渲染停滞,并且将近 97% 的规则是不会被使用!)不仅精简了我们的代码,还精简了新员工在上升时需要学习的足迹。 +- 下定决心干掉老旧的技术和框架可以使包大小减少一半。这不是华而不实的工作,我们最终翻出 jQuery、Alt、Bootstrap、Underscore 以及所有额外的 CSS 请求(他们使渲染停滞,并且将近 97% 的规则是不会被使用!)不仅精简了我们的代码,还精简了新员工在上升时需要学习的足迹。 - 最后,yeoman 的手动捕捉瓶颈的工作、异步加载代码在初始渲染时不可见、避免不必要的重新渲染、并降低重新渲染的成本,这些改进正是拖拉机和顶级跑车之间的区别。 -下次请收听我们将追逐的这些机会的成果。因为这么多的成果会有一些数量上的冲突,我们将尽量选择一些具体的成果在下篇文章中总结。 +欢迎下次继续围观我们的成果分享。因为这么多的成果会有一些数量上的冲突,我们将尽量选择一些具体的成果在下篇文章中总结。 **自然,如果你欣赏本文并觉得这是一个有趣的挑战,我们一直在寻找优秀出色的人[加入团队](https://www.airbnb.com/careers/departments/engineering)。如果你只想做一些交流,那么随时可以点击我的 twitter [@adamrneary](https://twitter.com/AdamRNeary)。** -最后,深切地向 [Salih Abdul-Karim](https://twitter.com/therealsalih) 和 [Hugo Ahlberg](https://twitter.com/hugoahlberg) 两位体验设计师致敬,他们的令人动容的动画至今让我目不转睛。许多工程师在他们的领域值得赞美,作出努力人的名单难以一一列出的,但绝对包括 Nick Sorrentino、[Joe Lencioni](https://medium.com/@lencioni)、[Michael Landau](https://medium.com/@mikeland86)、Jack Zhang、Walker Henderson 和 Nico Moschopoulos. +最后,深切地向 [Salih Abdul-Karim](https://twitter.com/therealsalih) 和 [Hugo Ahlberg](https://twitter.com/hugoahlberg) 两位体验设计师致敬,他们的令人动容的动画至今让我目不转睛。许多工程师在他们的领域值得赞美,作出贡献的人数众多,难以一一列出的,但绝对包括 Nick Sorrentino、[Joe Lencioni](https://medium.com/@lencioni)、[Michael Landau](https://medium.com/@mikeland86)、Jack Zhang、Walker Henderson 和 Nico Moschopoulos. --- From 464e544fc49ba3013ecf8e580ec50784d6e16a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Wed, 31 May 2017 10:52:07 +0800 Subject: [PATCH 49/59] Fix a typo --- ...-and-cleaner-code-by-leveraging-the-power-of-immutability.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md index 20baf1b1612..1eb60c3d8be 100644 --- a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md +++ b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md @@ -101,7 +101,7 @@ console.log(resultB); // [1, 2, 3, 5] 现在我们可以多次调用这个函数,且相同的输入获得相同的输出,与预期一致。这是因为我们不再改变 **array** 变量。我们把这个函数叫做“纯函数”。 -> **注意:**你还可以使用 **concat**,来代替 **slice** 和 **push**。 +> **注意:** 你还可以使用 **concat**,来代替 **slice** 和 **push**。 > 即:arrayInput.concat(value); 我们还可以使用 ES6 的[扩展语法](https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Operators/Spread_operator),来简化函数。 From 6df94ae4da7824f63ca72fa58e1711656767c1df Mon Sep 17 00:00:00 2001 From: "Yuze.Ma" <584653629@qq.com> Date: Wed, 31 May 2017 10:53:00 +0800 Subject: [PATCH 50/59] Fix additional errors --- TODO/crafting-better-code-reviews.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/TODO/crafting-better-code-reviews.md b/TODO/crafting-better-code-reviews.md index 6ed2892e4e1..7626fdbcf19 100644 --- a/TODO/crafting-better-code-reviews.md +++ b/TODO/crafting-better-code-reviews.md @@ -2,7 +2,7 @@ > * 原文作者:[Vaidehi Joshi](https://medium.com/@vaidehijoshi) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 译者:[bobmayuze](https://github.com/bobmayuze) -> * 校对者: +> * 校对者:[SareaYu](https://github.com/SareaYu)、[吃土小2叉](https://github.com/xunge0613) # 建立更好的代码审查制度 # @@ -95,7 +95,7 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 ![](https://cdn-images-1.medium.com/max/800/1*fVl3H0KGsauN1Bxs_jsN7A.jpeg) -这张图片通过不同语言阐明了代码审查的深度。总的来说,每个语言之间没有差很多。换句话来说,决j定代码审查的的深度和频率和你用什么语言没有关系,重点在于你在什么样的一个团队。 +这张图片通过不同语言阐明了代码审查的深度。总的来说,每个语言之间没有差很多。换句话来说,决定代码审查的的深度和频率和你用什么语言没有关系,重点在于你在什么样的一个团队。 ![](https://cdn-images-1.medium.com/max/800/1*jFZ_2zCzHM78m_L_p0OK8A.jpeg) @@ -170,7 +170,7 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 ### 如何做得更好? ### -也许这些数据不能最完整,最细致,或者最精准地代表代码审查的结构,但是我们还是能学到一些东西:我们能够回顾并检查我们团队的甚至整个社区的代码审查流程。 +也许这些数据不能最完整、最细致、或者最精准地代表代码审查的结构,但是我们还是能学到一些东西:我们能够回顾并检查我们团队的甚至整个社区的代码审查流程。 下面的匿名调查告诉了我们代码审查对于一个团队成员的影响是怎么样的: @@ -187,10 +187,10 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 这边是一些能够快速帮助提高代码审查感受的一些技巧: - 使用 [linters](https://github.com/showcases/clean-code-linters) 或者其他的代码分析工具来避免语法问题。 -- 使用 [Github 的模板](https://quickleft.com/blog/pull-request-templates-make-code-review-easier/)来生产每个PR。在发PR的时候带上更改列表对于作者和审查者都非常有帮助。 -- 在发PR的时候可以加个截图来帮助那些不是很熟悉的人理解问题。 -- 提高commits的信息量,尽量语言短小精湛。 -- 对于每个PR钦定审查者,如果可能的话人多于一个比较好。确定代码编写和审查的分配对于各个等级的工程师来说是均衡的。 +- 使用 [GitHub 的模板](https://quickleft.com/blog/pull-request-templates-make-code-review-easier/)来生产每个 PR 。在发 PR 的时候带上更改列表对于作者和审查者都非常有帮助。 +- 在发 PR 的时候可以加个截图来帮助那些不是很熟悉的人理解问题。 +- 提高 commits 的信息量,尽量语言短小精湛。 +- 对于每个 PR 钦定审查者,如果可能的话人多于一个比较好。确定代码编写和审查的分配对于各个等级的工程师来说是均衡的。 #### 很难做到但是非常重要的事情 #### @@ -206,7 +206,7 @@ McConnell 的一组组数据似乎在告诉我们,每一个开发团队都应 [![Markdown](http://i1.piimg.com/1949/51bfec74a7cf1e42.png)](https://twitter.com/j3) -- **进行一次口头交流。** 带着整个团队坐下来,到slack(美国的一种团队交流软件)上开个新的频道,让大家能够匿名的交流(选择最适合的即可)。在匿名的情况下大家都愿意说真话。 +- **进行一次口头交流。** 带着整个团队坐下来,到 slack (美国的一种团队交流软件)上开个新的频道,让大家能够匿名的交流(选择最适合的即可)。在匿名的情况下大家都愿意说真话。 我把最重要的一点放在最后,因为当你还有耐心读到这里的时候,说明你真的想要更改一下你们的代码审查结构了,这是个好事儿。团队交流真的非常重要,这几乎是每个团队必须跨出的一步如果他们想要提升代码审查制度的话。 From 689a52b57d883309540606b1b6f396aecbc120f1 Mon Sep 17 00:00:00 2001 From: sunxinlei Date: Wed, 31 May 2017 11:27:00 +0800 Subject: [PATCH 51/59] =?UTF-8?q?=E8=B7=9F=E6=8D=AE=20sqrthree=20=E6=84=8F?= =?UTF-8?q?=E8=A7=81=E4=BF=AE=E6=94=B9=E4=B8=80=E4=BA=9B=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/rearchitecting-airbnbs-frontend.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TODO/rearchitecting-airbnbs-frontend.md b/TODO/rearchitecting-airbnbs-frontend.md index afc24e784ab..9467839a1eb 100644 --- a/TODO/rearchitecting-airbnbs-frontend.md +++ b/TODO/rearchitecting-airbnbs-frontend.md @@ -50,17 +50,17 @@ Airbnb 每天处理超过 7500 万次搜索,这使得搜索页面成为我们 但一旦你想渲染客户端路由,你需要能够以预定的形式动态请求所需的数据。将来我们可能用类似 [GraphQL](http://graphql.org/) 的东西解决这个问题,但是现在暂且把它放到一边吧,因为这件事和重构代码没太大关系。相反,我们选择在我们的 API 的 “v2” 上进行调整,我们需要我们所有的组件来开始处理规范的数据格式。 -如果你自己和我们处在类似的情况中,在维护一个大型的应用,你可能发现我们像我们这样做,规划迁移现有的服务器端数据管道是很容易的。只需在任何地方用 Rails 渲染一个React组件,并确保数据输入是 API 所规定的类型。你可以用客户端的 React PropTypes 来进一步验证数据类型是否与 API v2 一致。 +如果你自己和我们处在类似的情况中,在维护一个大型的应用,你可能发现我们像我们这样做,规划迁移现有的服务器端数据管道是很容易的。只需在任何地方用 Rails 渲染一个 React 组件,并确保数据输入是 API 所规定的类型。你可以用客户端的 React PropTypes 来进一步验证数据类型是否与 API v2 一致。 对我们来说棘手的问题是和那些参与客户预定流程交互的团队协作:商业旅游、发展、度假租赁团队;中国和印度市场团队,灾难恢复团队等等,我们需要重新培训所有这些人,即使在技术上可以将数据直接传递到正在呈现的组件上("是的,我明白,这仅仅是一种实验,但是..."),**所有的数据**都要通过 API。 #### 第 2 步: 非 API 数据: 配置、试验、惯用语、本地化、国际化… #### -有一类独特的数据和我们设想的 API 化的数据不同,包括应用配置,用户试验任务,国际化,本地化等等类似的问题。近年来,Airbnb 已经建立了一套很棒的工具来支持这些功能,但是把这些数据传送到前端的机制就不那么令人愉快了(在革命开始之前,或许就已经很蹩脚了!)。 +有一类独特的数据和我们设想的 API 化的数据不同,包括应用配置、用户试验任务、国际化、本地化等等类似的问题。近年来,Airbnb 已经建立了一套很棒的工具来支持这些功能,但是把这些数据传送到前端的机制就不那么令人愉快了(在革命开始之前,或许就已经很蹩脚了!)。 我们使用 [Hypernova](https://www.npmjs.com/package/hypernova) 在服务端渲染渲染 React,但是在我们此次重构深入之前,无论服务端渲染时 React 组件中的试验交付会不会爆发或者客户端上提供的字符串转换是否都可以在服务器上可靠地使用,这些都还有点模糊。最重要的是,如果服务器和客户端输出匹配不到位,页面不仅会不断闪烁刷新 diff,还会在加载后重新渲染整个页面,这对于性能来说很可怕。 -更糟糕的是,我们有很久以前写过一些神奇的 Rails 功能,比如 `add_bootstrap_data(key, value)` 表面上可以在 Rails 中的任何地方调用,通过 `BootstrapData.get(key)` 使数据在客户端的全局可用(再次强调,对 Hypernova 来说已经不必要了)。曾经这些小工具对小团队来说很实用,但如今随着团队规模扩大,应用规模扩张,这些小工具反而变成了累赘。由于每个团队拥有不同的页面或功能,因此“数据清洗”变得越来越棘手,因此每个团队都会培养出一种不同的加载配置的机制,以满足其独特需求。 +更糟糕的是,我们很久以前写过一些神奇的 Rails 功能,比如 `add_bootstrap_data(key, value)` 表面上可以在 Rails 中的任何地方调用,通过 `BootstrapData.get(key)` 使数据在客户端的全局可用(再次强调,对 Hypernova 来说已经不必要了)。曾经这些小工具对小团队来说很实用,但如今随着团队规模扩大,应用规模扩张,这些小工具反而变成了累赘。由于每个团队拥有不同的页面或功能,因此“数据清洗”变得越来越棘手,因此每个团队都会培养出一种不同的加载配置的机制,以满足其独特需求。 显然, 这套机制已经崩溃了,所以我们融合了一个用于引导非 API 数据的规范机制,我们开始将所有应用程序和页面迁移到 Rails 和 React/Hypernova 之间的这种切换。 @@ -484,7 +484,7 @@ export default function withFilters(WrappedComponent) { 以这种方式处理状态,与我们的价格滑块进行交互对页面的其余部分没有影响,所以表现很好。而且所有过滤器面板都具有相同的功能签名,因此开发也很简单。 -### 未来做些什么?### +### 未来做些什么? ### 既然现在繁重的前端改造工作已经接近完成,我们可以把目光转向未来。 From 603777b182d293e1354d3a5bc882348c3a72ac32 Mon Sep 17 00:00:00 2001 From: sunxinlei Date: Wed, 31 May 2017 11:36:02 +0800 Subject: [PATCH 52/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=80=E5=A4=84?= =?UTF-8?q?=E6=96=87=E5=AD=97=E8=BE=93=E5=85=A5=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO/rearchitecting-airbnbs-frontend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO/rearchitecting-airbnbs-frontend.md b/TODO/rearchitecting-airbnbs-frontend.md index 9467839a1eb..6a1e005cc11 100644 --- a/TODO/rearchitecting-airbnbs-frontend.md +++ b/TODO/rearchitecting-airbnbs-frontend.md @@ -197,7 +197,7 @@ export default compose(withPhrases, withHypernovaBootstrap); #### 异步组件 #### -在(采用)React 之前,我们需要一次渲染整个页面,我们以前的 React 都是这么做的。但现在我们使用异步组件,类似[这种](https://medium.com/@thejameskyle/react-loadable-2674c59de178)方式, 挂在(mount)以后加载组件层次结构的部分。 +在(采用)React 之前,我们需要一次渲染整个页面,我们以前的 React 都是这么做的。但现在我们使用异步组件,类似[这种](https://medium.com/@thejameskyle/react-loadable-2674c59de178)方式, 挂载(mount)以后加载组件层次结构的部分。 ``` export default class AsyncComponent extends React.Component { From 8133d7c0eaab9d9d720f111ec9fc219a62f6c3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Wed, 31 May 2017 14:12:29 +0800 Subject: [PATCH 53/59] Update write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md --- ...-and-cleaner-code-by-leveraging-the-power-of-immutability.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md index 1eb60c3d8be..f9576dfb7f1 100644 --- a/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md +++ b/TODO/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability.md @@ -1,6 +1,6 @@ > * 原文地址:[Write safer and cleaner code by leveraging the power of “Immutability” ](https://medium.freecodecamp.com/write-safer-and-cleaner-code-by-leveraging-the-power-of-immutability-7862df04b7b6) -> * 原文作者:[Guido Schmitz](https://medium.freecodecamp.com/@guidsen) +> * 原文作者:本文已获原作者 [Guido Schmitz](https://medium.freecodecamp.com/@guidsen) 授权 > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 译者:[gy134340](https://github.com/gy134340) > * 校对者:[bambooom](https://github.com/bambooom),[xunge0613](https://github.com/xunge0613) From 99ec15e4ca338885d63b77dddcbc63e65f56766b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Wed, 31 May 2017 14:18:04 +0800 Subject: [PATCH 54/59] Update crafting-better-code-reviews.md --- TODO/crafting-better-code-reviews.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO/crafting-better-code-reviews.md b/TODO/crafting-better-code-reviews.md index 7626fdbcf19..5fc82facecd 100644 --- a/TODO/crafting-better-code-reviews.md +++ b/TODO/crafting-better-code-reviews.md @@ -1,5 +1,5 @@ > * 原文地址:[Crafting Better Code Reviews](https://medium.com/@vaidehijoshi/crafting-better-code-reviews-1a5fc00a9312) -> * 原文作者:[Vaidehi Joshi](https://medium.com/@vaidehijoshi) +> * 原文作者:本文已获原作者 [Vaidehi Joshi](https://medium.com/@vaidehijoshi) 授权 > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 译者:[bobmayuze](https://github.com/bobmayuze) > * 校对者:[SareaYu](https://github.com/SareaYu)、[吃土小2叉](https://github.com/xunge0613) From f8aa8ef7f2a958b50f582423ce8f135e2df5823f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Wed, 31 May 2017 21:54:41 +0800 Subject: [PATCH 55/59] Create 11-things-i-learned-reading-the-flexbox-spec.md --- ...ings-i-learned-reading-the-flexbox-spec.md | 280 ++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 TODO/11-things-i-learned-reading-the-flexbox-spec.md diff --git a/TODO/11-things-i-learned-reading-the-flexbox-spec.md b/TODO/11-things-i-learned-reading-the-flexbox-spec.md new file mode 100644 index 00000000000..9311b9296ae --- /dev/null +++ b/TODO/11-things-i-learned-reading-the-flexbox-spec.md @@ -0,0 +1,280 @@ +> * 原文地址:[11 things I learned reading the flexbox spec](https://hackernoon.com/11-things-i-learned-reading-the-flexbox-spec-5f0c799c776b) +> * 原文作者:[David Gilbertson](https://hackernoon.com/@david.gilbertson) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# 11 things I learned reading the flexbox spec # + +I’ve always found flexbox pretty easy to work with — a breath of fresh air after years of floating and clearfixing. + +Recently though, I found myself fighting against it; something was flexing when I didn’t think it should be flexing. I fixed it, something else got squashed. I fixed that, then something else got pushed right off the screen. What in the George W Bush was going on? + +In the end I got it all working, but the sun had set and my process had been the old CSS fiddle-a-roo. Or … what’s that game where you have to whack a mole and then another mole pops up and you have to whack that one too? + +Anyhoo, I decided it was about time that I behaved like a grown up developer and learned flexbox properly. But rather than read another 10 blog posts, I decided to go straight to the source and read [The CSS Flexible Box Layout Module Level 1 Spec](https://www.w3.org/TR/css-flexbox-1/). + +Here’s the good bits. + +### 1. Margins have special powers ### + +I used to think that if you wanted, say, a header with a logo and site title on the left, and a sign in button over on the right… + +![](https://cdn-images-1.medium.com/max/800/1*Y1xY5s_DFPRaZzTwpfb_WQ.png) + +Dotted lines for clarity + +…you should give the title a flex of 1 to push the other items to either end of the row. + +``` +.header { + display: flex; +} +.header .logo { + /* nothing needed! */ +} +.header .title { + flex: 1; +} +.header .sign-in { + /* nothing needed! */ +} +``` + +This is why flexbox is a Very Good Thing. Simple things are so simple. + +But maybe, for some reason, you don’t want to stretch an item just to push another item to the right. It might be a box with an underline, an image, or some third reason I can’t think of. + +Great news! You can be more direct and instead say “push this item to the right” by defining `margin-left: auto` on that item. Think of it like `float: right`. + +For example if the item on the left was an image: + +![](https://cdn-images-1.medium.com/max/800/1*hFLefXP4fsgnFDIjPIcrTQ.png) + +I don’t need to apply any flex to image, I don’t need to apply `space-between` to the flex container, I just set `margin-left: auto` on the ‘Sign in’ button: + +``` +.header { + display: flex; +} +.header .logo { + /* nothing needed! */ +} +.header .sign-in { + margin-left: auto; +} +``` + +You might think that seems a bit hacky, but nope, it’s right there in the [overview](https://www.w3.org/TR/css-flexbox-1/#overview) of the spec as *the* method used to push a flex item to the end of a flexbox. It even has its own chapter, “[Aligning with auto margins](https://www.w3.org/TR/css-flexbox-1/#auto-margins)”. + +Oh I should add here that I’m assuming `flex-direction: row` everywhere in this post, but it all applies just the same to `row-reverse` or `column` or `column-reverse`. + +### 2. min-width matters ### + +You might think that it would be straightforward to ensure that all of the flex items within a container shrink to fit. Surely if you have `flex-shrink: 1` on the items, that’s what they do, right? + +An example, perhaps. + +Let’s say you’ve got a bit of DOM that shows a book for sale and a button to buy it. + +![](https://cdn-images-1.medium.com/max/800/1*kx1Xl4o5at3whroR9gB0Dw.png) + +(Spoiler: the butterfly dies in the end) + +You’ve laid it out with flexbox and all is well. + +``` +.book { + display: flex; +} +.book .description { + font-size: 30px; +} +.book .buy { + margin-left: auto; + width: 80px; + text-align: center; + align-self: center; +} +``` + +(You want the ‘Buy now’ button to be on the right — even for really short titles — so you’ve cleverly given it `margin-left: auto`.) + +The title is quite long so it uses up as much space as it can, then wraps to the next row. You are happy, life is good. You blissfully ship your code to production with confidence that it will handle anything. + +Then you get a nasty surprise. And not the good kind. + +Some pretentious muppet has written a book with a long word in the title. + +![](https://cdn-images-1.medium.com/max/800/1*skXsBLXnoul3J64xKb1HmA.png) + +That’s totes broken! + +If that red border represents the width of a phone, and you’re hiding overflow, you’ve just lost your ‘Buy now’ button. Your conversion rates — and the poor author’s ego — are going to suffer. + +(Side note: luckily where I work we have a good QA team that have populated our database with all sorts of nasty text like this. It was this issue in particular that prompted me to read the spec.) + +As it turns out, this behaviour is because the `min-width` of the description item is initially set to `auto`, which in this case equates to the width of the word *Electroencephalographically.* The flex item is literally not allowed to be any narrower than that word. + +The solution? Override this troublesome `min-width: auto` by setting `min-width: 0`, instructing flexbox that it’s OK for this item to be narrower than the content within it. + +It’s then up to you to handle the text inside the item. I’d suggest wrapping the word. So your CSS would look like this: + +``` +.book { + display: flex; +} +.book .description { + font-size: 30px; + min-width: 0; + word-wrap: break-word; +} +.book .buy { + margin-left: auto; + width: 80px; + text-align: center; + align-self: center; +} +``` + +The result will be this: + +![](https://cdn-images-1.medium.com/max/800/1*lM96U8XNZJEGPrVwqJk91w.png) + +Again, `min-width: 0` is not some hack to work around a quirk, it’s the [suggested behavior right from the spec](https://www.w3.org/TR/css-flexbox-1/#min-size-auto). + +In the next section I’ll address that ‘Buy now’ button being not at all 80px wide like I quite clearly told it to be. + +### 3. The flexbox authors have crystal balls ### + +As you probably know, the `flex` property is shorthand for `flex-grow`, `flex-shrink` and `flex-basis`. + +I must admit I’ve spent quite a few minutes guessing-and-checking different values for these three when trying to get things to flex the way I want. + +What I didn’t know until now is that I generally only want one of three combinations: + +- If I want an item to squish in a bit if there isn’t enough room, but not to stretch any wider than it needs to: `flex: 0 1 auto` +- If my flex item should stretch to fill the available space and squish in a bit if there’s not enough room: `flex: 1 1 auto` +- If my item should not flex at all: `flex: 0 0 auto` + +I hope you’re not at maximum amazement yet — it’s about to get more amazing. + +You see, the Flexbox Crew (I like to think the flexbox team have leather jackets that say this across the back — available in women’s and men’s sizes). Where was this sentence. Ah yes, the Flexbox Crew knew that these are the three flex property combinations I want most of the time, so they gave them [keywords just for me](https://www.w3.org/TR/css-flexbox-1/#flex-common). + +The first scenario is the `initial` value so doesn’t need the keyword. `flex: auto` is what to use for the second scenario, and `flex: none` is the remarkably simple solution to make something not flex at all. + +Who woulda thunk it. + +It’s kinda like having `box-shadow: garish` and that defaulting to `2px 2px 4px hotpink` because it’s considered a ‘useful default’. + +So back to that extremely ugly book example from before. To make that ‘Buy now’ button a consistently fat tap target… + +![](https://cdn-images-1.medium.com/max/800/1*oaBk_GjcSHAvSkdhJhwkSA.png) + +…I only need to set `flex: none` on it: + +``` +.book { + display: flex; +} +.book .description { + font-size: 30px; + min-width: 0; + word-wrap: break-word; +} +.book .buy { + margin-left: auto; + flex: none; + width: 80px; + text-align: center; + align-self: center; +} +``` + +(Yes, I could have done `flex: 0 0 80px;` and saved a line of CSS. But there’s something nice about the way that `flex: none` clearly represents the intention of the code. This is good for poor Future David who will have forgotten how all this works.) + +### 4. inline-flex is a thing ### + +OK to be honest I learned a few months ago that `display: inline-flex` was a thing. And that it would create a flex container that was inline, instead of a block. + +But I estimate that 28% of people did not yet know this, so … now you do, bottom 28%. + +### 5. vertical-align has no effect on a flex-item ### + +Maybe this is something that I *half* knew, but I’m sure at some point when trying to get something to align just right I may have tried `vertical-align: middle` then shrugged when it didn’t work. + +Now I know for sure, straight from the spec, that “[vertical-align has no effect on a flex item](https://www.w3.org/TR/css-flexbox-1/#flex-containers)” (same as `float`, for the record). + +### 6. Don’t use % margins or padding ### + +This is not just a best practice type situation, it’s advice-from-grandma level stuff, just do what you’re told and don’t ask questions. + +“Authors should avoid using percentages in paddings or margins on flex items entirely” — love, the flexbox spec. + +This follows my favourite quote from any spec ever: + +> Note: This variance sucks, but it accurately captures the current state of the world (no consensus among implementations, and no consensus within the CSSWG)… + +Look out! Honesty-bombing in progress. + +### 7. The margins of adjacent flex items don’t collapse ### + +You may already know that margins collapse onto each other sometimes. You may also know that margins *don’t* collapse onto each other at some other times. + +And now we all know that the margins of adjacent flex items do not ever collapse onto each other. + +### 8. z-index works even if position: static ### + +I’m not sure that I really care about this one. But I feel like one day, maybe, it will come in handy. It’s exactly the same reason I have a bottle of lemon juice in the fridge. + +I’ll have another human in my house one day and they’ll be all like “hey, you got any lemon juice?” and I’ll be all like “sure do, in the fridge” and they’ll be like, “thanks, mate. Hey, if I want to set z-index on a flex item, do I need to specify a position?” and I’ll be all “nah bro, not for flex items.” + +### 9. Flex-basis is subtle and important ### + +Once your requirements go beyond the keywords `initial`, `auto` and `none`, things get a bit more complex, and now that I *get*`flex-basis`, it’s funny, you know, I can’t quite work out how to end this sentence. Feel free to leave a comment if you’ve got any ideas. + +If you have three flex items with flex values of 3, 3, and 4. Then they *will* take up 30%, 30% and 40% of the available space, regardless of their content, if their `flex-basis` is `0`. And *only* if it’s zero. + +However, if you want flex to behave in a friendlier, but less predictable way, use `flex-basis: auto`. This will take your flex values into consideration, but also take other factors into account, have a bit of a think about it, then come up with some widths it thinks will work for you. + +Check out this neato diagram from the spec: + +![](https://cdn-images-1.medium.com/max/800/1*eiAn12jGzun4F7U3mfqUtQ.png) + +I’m quite sure this is mentioned in at least one of the flex blog posts I’ve read, but for whatever reason, it didn’t really click until I saw this schmick pic in the spec (triple rhyme if you’re from New Zealand). + +### 10. align-items: baseline ### + +If I wanted my flex items to align vertically, I’ve always used `align-items: center`. But just like `vertical-align`, you also have the option to set the value to `baseline` which might be more appropriate if your items have different font sizes and you want their baselines to align. + +`align-self: baseline` also works, perhaps obviously. + +### 11. I’m pretty stupid ### + +No matter how many times I read the following paragraph, I remain incapable of comprehending it… + +> The content size is the min-content size in the main axis, clamped, if it has an aspect ratio, by any definite min and max cross size properties converted through the aspect ratio, and then further clamped by the max main size property if that is definite. + +The words make their way into my holes, are converted to electrical impulses that travel up my optic nerve, arriving just in time to see my brain running out the back door in a puff of smoke. + +It’s like Minnie Mouse and Mad Max had a child seven years ago and now he’s drunk on peppermint schnapps, verbally abusing anyone within ear-distance using words he learned when Mummy and Daddy were fighting. + +Ladies and Gentlemen we have begun our descent into nonsense, which means it’s time to wrap it up (or stop reading if you’re just here for the learnin’). + +The most interesting thing I learned reading the spec was exactly how un-thorough my understanding was, despite the half-dozen or so blog posts I’d read, and how relatively simple flexbox is. It turns out that ‘experience’ isn’t just doing the same thing over and over for years on end. + +I can report with delight that my time spent reading has paid for itself already. I’ve gone back through old code and set auto margins, flex values to the shorthand auto or none, and defined a min-width of zero wherever it was needed. + +I feel better about this code now, knowing that I’m doing it properly. + +My other learning was that although the spec is — in places — exactly as dense and vendor-focused as I thought it would be, there is still a lot of friendly words and examples. It even highlights the parts that lowly web developers can skip over. + +However this is a moot point because I’ve told you all the good bits so you needn’t bother reading it for yourself. + +Now, if you’ll excuse me, I have to go and read all the other CSS specs. + +P.S. I highly recommend reading this, a list of all the flexbox bugs by browser: [https://github.com/philipwalton/flexbugs](https://github.com/philipwalton/flexbugs). + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 From cada724ecf642954133e1a19f11311e71f69dcc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Wed, 31 May 2017 22:00:57 +0800 Subject: [PATCH 56/59] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d77e8e52405..77ff62e8212 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [508](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [509](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -41,6 +41,7 @@ ## 前端 +* [Airbnb 的前端重构](https://juejin.im/post/592e3af8ac502e006c9c3f1f?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([sunui](https://github.com/sunui) 翻译) * [Web 开发者安全清单](https://juejin.im/post/592651c944d904006400cd88?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([GangsterHyj](https://github.com/GangsterHyj) 翻译) * [解密 Quantum:现代浏览器引擎的构建之道](https://juejin.im/post/591bc865a22b9d00583c17b8?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([xunge0613](https://github.com/xunge0613) 翻译) * [光速 React](https://juejin.im/post/591ad6b7128fe1005ce1123a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ZhangFe](https://github.com/ZhangFe) 翻译) From 12bd48951bd026cbfacd6c4e14f2c4626fd9f227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Wed, 31 May 2017 22:01:27 +0800 Subject: [PATCH 57/59] Update front-end.md --- front-end.md | 1 + 1 file changed, 1 insertion(+) diff --git a/front-end.md b/front-end.md index 8adabf1bf12..f94133ba6d4 100644 --- a/front-end.md +++ b/front-end.md @@ -1,3 +1,4 @@ +* [Airbnb 的前端重构](https://juejin.im/post/592e3af8ac502e006c9c3f1f?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([sunui](https://github.com/sunui) 翻译) * [Web 开发者安全清单](https://juejin.im/post/592651c944d904006400cd88?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([GangsterHyj](https://github.com/GangsterHyj) 翻译) * [解密 Quantum:现代浏览器引擎的构建之道](https://juejin.im/post/591bc865a22b9d00583c17b8?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([xunge0613](https://github.com/xunge0613) 翻译) * [光速 React](https://juejin.im/post/591ad6b7128fe1005ce1123a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ZhangFe](https://github.com/ZhangFe) 翻译) From 404421dc64fbc69e99276243977530057a4040ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Wed, 31 May 2017 22:05:17 +0800 Subject: [PATCH 58/59] Update front-end.md --- front-end.md | 1 + 1 file changed, 1 insertion(+) diff --git a/front-end.md b/front-end.md index f94133ba6d4..2d32dcf82ce 100644 --- a/front-end.md +++ b/front-end.md @@ -1,3 +1,4 @@ +* [利用“Immutability(不可变性)”编写更为简洁高效的代码](https://juejin.im/post/592eb8bfa22b9d005776d6df?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([gy134340](http://gy134340.com/) 翻译) * [Airbnb 的前端重构](https://juejin.im/post/592e3af8ac502e006c9c3f1f?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([sunui](https://github.com/sunui) 翻译) * [Web 开发者安全清单](https://juejin.im/post/592651c944d904006400cd88?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([GangsterHyj](https://github.com/GangsterHyj) 翻译) * [解密 Quantum:现代浏览器引擎的构建之道](https://juejin.im/post/591bc865a22b9d00583c17b8?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([xunge0613](https://github.com/xunge0613) 翻译) From 4f7c473f119c73e57a13843771704cd5b2851a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=B9=E5=8F=B7=E4=B8=89?= Date: Wed, 31 May 2017 22:05:59 +0800 Subject: [PATCH 59/59] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 77ff62e8212..5fcc060a445 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [509](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [510](#近期文章列表) 篇文章,共有 [300](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 # 官方指南 @@ -41,11 +41,10 @@ ## 前端 +* [利用“Immutability(不可变性)”编写更为简洁高效的代码](https://juejin.im/post/592eb8bfa22b9d005776d6df?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([gy134340](http://gy134340.com/) 翻译) * [Airbnb 的前端重构](https://juejin.im/post/592e3af8ac502e006c9c3f1f?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([sunui](https://github.com/sunui) 翻译) * [Web 开发者安全清单](https://juejin.im/post/592651c944d904006400cd88?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([GangsterHyj](https://github.com/GangsterHyj) 翻译) * [解密 Quantum:现代浏览器引擎的构建之道](https://juejin.im/post/591bc865a22b9d00583c17b8?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([xunge0613](https://github.com/xunge0613) 翻译) -* [光速 React](https://juejin.im/post/591ad6b7128fe1005ce1123a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ZhangFe](https://github.com/ZhangFe) 翻译) -* [我是如何实现世界上最快的 JavaScript 记忆化的](https://juejin.im/post/5912b635a0bb9f0058b44c60?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Aladdin-ADD](https://github.com/Aladdin-ADD) 翻译) * [所有前端译文>>](https://github.com/xitu/gold-miner/blob/master/front-end.md)