From 86c98a08c80d343665db11d576fe2a80157e25b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B8=E5=BE=92=E5=85=AC=E5=AD=90?= Date: Tue, 31 Mar 2020 21:19:03 +0800 Subject: [PATCH] =?UTF-8?q?Go=20=E5=8F=91=E5=B8=83=E6=96=B0=E7=89=88=20Pro?= =?UTF-8?q?tobuf=20API=20(#6827)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Achieve translation * 8 个值得了解的树形数据结构 (#6804) * 8个值得了解的树形数据结构 8个值得了解的树形数据结构 * Update 8-Useful-Tree-Data-Structures-Worth-Knowing.md * Update 8-useful-tree-data-structures-worth-knowing.md Co-authored-by: sun <776766759@qq.com> * 眼动跟踪和移动世界的最佳用户体验实践 (#6806) * 眼动跟踪和移动世界的最佳用户体验实践 翻译完成 * 眼动跟踪和移动世界的最佳用户体验实践 #6806 谢谢指点~@xionglong58 @fanyijihua 根据第一轮校对意见修改完成 * 眼动追踪和移动世界的最佳用户体验实践 * 眼动追踪和移动世界的最佳用户体验实践 * 我并不讨厌箭头函数(#6610) (#6659) * 我并不讨厌箭头函数(#6610) * 我并不讨厌箭头函数(#6610) - 根据校对结果修改 * Update i-dont-hate-arrow-functions.md Co-authored-by: 小添 Co-authored-by: sun <776766759@qq.com> * 掌握 JavaScript 面试:什么是纯函数? (#6828) * 掌握 JavaScript 面试:什么是纯函数? 掌握 JavaScript 面试:什么是纯函数? * Update master-the-javascript-interview-what-is-a-pure-function.md * Update master-the-javascript-interview-what-is-a-pure-function.md * Update master-the-javascript-interview-what-is-a-pure-function.md * Update master-the-javascript-interview-what-is-a-pure-function.md * Update master-the-javascript-interview-what-is-a-pure-function.md * Update master-the-javascript-interview-what-is-a-pure-function.md * Update master-the-javascript-interview-what-is-a-pure-function.md Co-authored-by: lsvih * Address comments Co-authored-by: Amberlin1970 <37952468+Amberlin1970@users.noreply.github.com> Co-authored-by: sun <776766759@qq.com> Co-authored-by: Charlo <49369951+Charlo-O@users.noreply.github.com> Co-authored-by: TiaossuP Co-authored-by: 小添 Co-authored-by: niayyy Co-authored-by: lsvih --- TODO1/a-new-go-api-for-protocol-buffers.md | 119 +++++++++++---------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/TODO1/a-new-go-api-for-protocol-buffers.md b/TODO1/a-new-go-api-for-protocol-buffers.md index e8c0c8cf842..88c3144333b 100644 --- a/TODO1/a-new-go-api-for-protocol-buffers.md +++ b/TODO1/a-new-go-api-for-protocol-buffers.md @@ -2,40 +2,40 @@ > * 原文作者:Joe Tsai, Damien Neil, and Herbie Ong > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-new-go-api-for-protocol-buffers.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-new-go-api-for-protocol-buffers.md) -> * 译者: -> * 校对者: +> * 译者:[司徒公子](https://github.com/todaycoder001) +> * 校对者:[quzhen](https://github.com/quzhen12)、[Chauncey Chen](https://github.com/colorsakura) -# A new Go API for Protocol Buffers +# Go 发布新版 Protobuf API -## Introduction +## 介绍 -We are pleased to announce the release of a major revision of the Go API for [protocol buffers](https://developers.google.com/protocol-buffers), Google's language-neutral data interchange format. +我们很高兴地宣布:发布[protocol buffers](https://developers.google.com/protocol-buffers)的 Go API 主要修订版本 —— Google 独立于编程语言的数据交换接口格式。 -## Motivations for a new API +## 构建新 API 的动机 -The first protocol buffer bindings for Go were [announced by Rob Pike](https://blog.golang.org/third-party-libraries-goprotobuf-and) in March of 2010. Go 1 would not be released for another two years. +第一个用于 Go 的 protocol buffer 版本由 Rob Pike 在 2010 年 3 月发布,Go 的首个正式版在两年后才发布。 -In the decade since that first release, the package has grown and developed along with Go. Its users' requirements have grown too. +在第一个版本发布的数十年间,随着 Go 的发展,package 也在不断发展壮大。用户的需求也在不断的增长。 -Many people want to write programs that use reflection to examine protocol buffer messages. The [`reflect`](https://pkg.go.dev/reflect) package provides a view of Go types and values, but omits information from the protocol buffer type system. For example, we might want to write a function that traverses a log entry and clears any field annotated as containing sensitive data. The annotations are not part of the Go type system. +许多人希望使用 reflection(反射) package 来编写检查 protocol buffer message 的程序,[`reflect`](https://pkg.go.dev/reflect) package 提供了 Go 类型和值的视图,但是忽略了 protocol buffer 类型系统的信息。例如,我们可能希望编写一个函数来遍历日志项,清除所有标注为敏感信息的数据,标注并不是 Go 类型系统的一部分。 -Another common desire is to use data structures other than the ones generated by the protocol buffer compiler, such as a dynamic message type capable of representing messages whose type is not known at compile time. +另一个常见的需求就是使用 protocol buffer 编译器来生成其他的数据结构,例如动态 message 类型,它能够表示在编译时类型未知的 message。 -We also observed that a frequent source of problems was that the [`proto.Message`](https://pkg.go.dev/github.com/golang/protobuf/proto?tab=doc#Message) interface, which identifies values of generated message types, does very little to describe the behavior of those types. When users create types that implement that interface (often inadvertently by embedding a message in another struct) and pass values of those types to functions expecting a generated message value, programs crash or behave unpredictably. +我们还观察到,时常发生问题的根源在于 [`proto.Message`](https://pkg.go.dev/github.com/golang/protobuf/proto?tab=doc#Message) 接口,该接口标识生成的 message 类型的值,对描述这些类型的行为几乎没有任何帮助。当用户创建实现该接口的类型(时常不经意间将 message 嵌入其他的结构中),并且将这些类型的值传递给期待生成 message 值的函数时,程序发生崩溃或行为难以预料。 -All three of these problems have a common cause, and a common solution: The `Message` interface should fully specify the behavior of a message, and functions operating on `Message` values should freely accept any type that correctly implements the interface. +这三个问题都有一个共同的原因,而通常的解决方法:`Message` 接口应该完全指定 message 的行为,对 `Message` 值进行操作的函数应该自由的接收任何类型,这些类型的接口都要被正确的实现。 -Since it is not possible to change the existing definition of the `Message` type while keeping the package API compatible, we decided that it was time to begin work on a new, incompatible major version of the protobuf module. +由于不可能在保持 package API 兼容性的同时更改 `Message` 类型的现有定义,所以我们决定是时候开始开发新的、不兼容 protobuf 模块的主要版本了。 -Today, we're pleased to release that new module. We hope you like it. +今天,我们很高兴地发布这个新模块,希望你们喜欢。 -## Reflection +## Reflection(反射) -Reflection is the flagship feature of the new implementation. Similar to how the `reflect` package provides a view of Go types and values, the [`google.golang.org/protobuf/reflect/protoreflect`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc) package provides a view of values according to the protocol buffer type system. +Reflection(反射)是新实现的旗舰特性。与 `reflect` 包提供 Go 类型和值的视图相似,[`protoreflect`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc) 包根据 protocol buffer 类型系统提供值的视图。 -A complete description of the `protoreflect` package would run too long for this post, but let's look at how we might write the log-scrubbing function we mentioned previously. +完整的描述 `protoreflect` package 对于这篇文章来说太长了,但是,我们可以来看看如何编写前面提到的日志清理函数。 -First, we'll write a `.proto` file defining an extension of the [`google.protobuf.FieldOptions`](https://github.com/protocolbuffers/protobuf/blob/b96241b1b716781f5bc4dc25e1ebb0003dfaba6a/src/google/protobuf/descriptor.proto#L509) type so we can annotate fields as containing sensitive information or not. +首先,我们将编写 `.proto` 文件来定义 [`google.protobuf.FieldOptions`](https://github.com/protocolbuffers/protobuf/blob/b96241b1b716781f5bc4dc25e1ebb0003dfaba6a/src/google/protobuf/descriptor.proto#L509) 类型的扩展名,以便我们可以将注释字段作为标识敏感信息的与否。 ```go syntax = "proto3"; @@ -46,7 +46,7 @@ extend google.protobuf.FieldOptions { } ``` -We can use this option to mark certain fields as non-sensitive. +我们可以使用此选项来将某些字段标识为非敏感字段。 ```go message MyMessage { @@ -54,16 +54,16 @@ message MyMessage { } ``` -Next, we will write a Go function which accepts an arbitrary message value and removes all the sensitive fields. +接下来,我们将编写一个 Go 函数,它用于接收任意 message 值以及删除所有敏感字段。 ```go -// Redact clears every sensitive field in pb. +// 清除 pb 中所有的敏感字段 func Redact(pb proto.Message) { // ... } ``` -This function accepts a [`proto.Message`](https://pkg.go.dev/google.golang.org/protobuf/proto?tab=doc#Message), an interface type implemented by all generated message types. This type is an alias for one defined in the `protoreflect` package: +函数接收 [`proto.Message`](https://pkg.go.dev/google.golang.org/protobuf/proto?tab=doc#Message) 参数,这是由所有已生成的 message 类型实现的接口类型。此类型是 `protoreflect` 包中已定义的别名: ```go type ProtoMessage interface{ @@ -71,11 +71,11 @@ type ProtoMessage interface{ } ``` -To avoid filling up the namespace of generated messages, the interface contains only a single method returning a [`protoreflect.Message`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Message), which provides access to the message contents. +为了避免填充生成 message 的命名空间,接口仅包含一个返回 [`protoreflect.Message`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Message) 的方法,此方法提供对 message 内容的访问。 -(Why an alias? Because `protoreflect.Message` has a corresponding method returning the original `proto.Message`, and we need to avoid an import cycle between the two packages.) +(为什么是别名?由于 `protoreflect.Message` 有返回原始 `proto.Message` 的相应方法,我们需要避免在两个包中循环导入。) -The [`protoreflect.Message.Range`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Message.Range) method calls a function for every populated field in a message. +[`protoreflect.Message.Range`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Message.Range) 方法为 message 中的每一个填充字段调用一个函数。 ```go m := pb.ProtoReflect() @@ -85,38 +85,38 @@ m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { }) ``` -The range function is called with a [`protoreflect.FieldDescriptor`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#FieldDescriptor) describing the protocol buffer type of the field, and a [`protoreflect.Value`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Value) containing the field value. +使用描述 protocol buffer 类型的 [`protoreflect.FieldDescriptor`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#FieldDescriptor) 字段和包含字段值的 [`protoreflect.Value`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Value) 字段来调用 range 函数。 -The [`protoreflect.FieldDescriptor.Options`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Descriptor.Options) method returns the field options as a `google.protobuf.FieldOptions` message. +[`protoreflect.FieldDescriptor.Options`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Descriptor.Options) 方法以 `google.protobuf.FieldOptions` message 的形式返回字段选项。 ```go opts := fd.Options().(*descriptorpb.FieldOptions) ``` -(Why the type assertion? Since the generated `descriptorpb` package depends on `protoreflect`, the `protoreflect` package can't return the concrete options type without causing an import cycle.) +(为什么使用类型断言?由于生成的 `descriptorpb` package 依赖于 `protoreflect`,所以 `protoreflect` package 无法返回正确的选项类型,否则会导致循环导入的问题) -We can then check the options to see the value of our extension boolean: +然后,我们可以检查选项以查看扩展为 boolean 类型的值: ```go if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) { - return true // don't redact non-sensitive fields + return true // 不要删减非敏感字段 } ``` -Note that we are looking at the field **descriptor** here, not the field **value**. The information we're interested in lies in the protocol buffer type system, not the Go one. +请注意,我们在这里看到的是字段**描述符**,而不是字段**值**,我们感兴趣的信息在于 protocol buffer 类型系统,而不是 Go 语言。 -This is also an example of an area where we have simplified the `proto` package API. The original [`proto.GetExtension`](https://pkg.go.dev/github.com/golang/protobuf/proto?tab=doc#GetExtension) returned both a value and an error. The new [`proto.GetExtension`](https://pkg.go.dev/google.golang.org/protobuf/proto?tab=doc#GetExtension) returns just a value, returning the default value for the field if it is not present. Extension decoding errors are reported at `Unmarshal` time. +这也是我们已经简化了 `proto` package API 的一个示例,原来的 [`proto.GetExtension`](https://pkg.go.dev/github.com/golang/protobuf/proto?tab=doc#GetExtension) 返回一个值和错误信息,新的 [`proto.GetExtension`](https://pkg.go.dev/google.golang.org/protobuf/proto?tab=doc#GetExtension) 只返回一个值,如果字段不存在,则返回该字段的默认值。在 `Unmarshal` 的时候报告扩展解码错误。 -Once we have identified a field that needs redaction, clearing it is simple: +一旦我们确定了需要修改的字段,将其清除就很简单了: ```go m.Clear(fd) ``` -Putting all the above together, our complete redaction function is: +综上所述,我们完整的修改函数如下: ```go -// Redact clears every sensitive field in pb. +// 清除 pb 中的所有敏感字段 func Redact(pb proto.Message) { m := pb.ProtoReflect() m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { @@ -130,49 +130,50 @@ func Redact(pb proto.Message) { } ``` -A more complete implementation might recursively descend into message-valued fields. We hope that this simple example gives a taste of protocol buffer reflection and its uses. -## Versions +一个更加完整的实现应该是以递归的方式深入这些 message 值字段。我们希望这些简单的示例能让你更了解 protocol buffer reflection(反射)以及它的用法。 -We call the original version of Go protocol buffers APIv1, and the new one APIv2. Because APIv2 is not backwards compatible with APIv1, we need to use different module paths for each. +## 版本 -(These API versions are not the same as the versions of the protocol buffer language: `proto1`, `proto2`, and `proto3`. APIv1 and APIv2 are concrete implementations in Go that both support the `proto2` and `proto3` language versions.) +我们将 Go protocol buffer 的原始版本称为 APIv1,新版本称为 APIv2。因为 APIv2 不支持向前兼容 APIv1,所以我们需要为每个模块使用不同的路径。 -The [`github.com/golang/protobuf`](https://pkg.go.dev/github.com/golang/protobuf?tab=overview) module is APIv1. +(这些 API 版本与 protocol buffer 语言的版本:`proto1`、`proto2`、`proto3` 是不同的,APIv1 和 APIv2 是 Go 中的具体实现,他们都支持 `proto2` 和 `proto3` 语言版本。) -The [`google.golang.org/protobuf`](https://pkg.go.dev/google.golang.org/protobuf?tab=overview) module is APIv2. We have taken advantage of the need to change the import path to switch to one that is not tied to a specific hosting provider. (We considered `google.golang.org/protobuf/v2`, to make it clear that this is the second major version of the API, but settled on the shorter path as being the better choice in the long term.) + [`github.com/golang/protobuf`](https://pkg.go.dev/github.com/golang/protobuf?tab=overview) 模块是 APIv1。 -We know that not all users will move to a new major version of a package at the same rate. Some will switch quickly; others may remain on the old version indefinitely. Even within a single program, some parts may use one API while others use another. It is essential, therefore, that we continue to support programs that use APIv1. +[`google.golang.org/protobuf`](https://pkg.go.dev/google.golang.org/protobuf?tab=overview) 模块是 APIv2。我们利用需要改变导入路径来切换版本,将其绑定到不同的主机提供商上。(我们考虑了 `google.golang.org/protobuf/v2`,说得更清楚一点,这是 API 的第二个主要版本,但是从长远来看,我们认为更短的路径名是更好的选择。) -* `github.com/golang/protobuf@v1.3.4` is the most recent pre-APIv2 version of APIv1. -* `github.com/golang/protobuf@v1.4.0` is a version of APIv1 implemented in terms of APIv2. The API is the same, but the underlying implementation is backed by the new one. This version contains functions to convert between the APIv1 and APIv2 `proto.Message` interfaces to ease the transition between the two. -* `google.golang.org/protobuf@v1.20.0` is APIv2. This module depends upon `github.com/golang/protobuf@v1.4.0`, so any program which uses APIv2 will automatically pick a version of APIv1 which integrates with it. +我们知道不是所有的用户都以相同的速度迁移到新的 package 版本中,有些会迅速迁移,其他的可能会无限期的停留在老版本上。甚至在一个程序中,也有可能使用不同的 API 版本,这是至关重要的。所以,我们继续支持使用 APIv1 的程序。 -(Why start at version `v1.20.0`? To provide clarity. We do not anticipate APIv1 to ever reach `v1.20.0`, so the version number alone should be enough to unambiguously differentiate between APIv1 and APIv2.) +* `github.com/golang/protobuf@v1.3.4` 是 APIv1 最新 pre-APIv2 版本。 +* `github.com/golang/protobuf@v1.4.0` 是由 APIv2 实现的 APIv1 的一个版本。API 是相同的,但是底层实现得到了新 API 的支持。该版本包含 APIv1 和 APIv2 之间的转换函数,`proto.Message` 接口来简化两者之间的转换。 +* `google.golang.org/protobuf@v1.20.0` 是 APIv2,该模块取决于 `github.com/golang/protobuf@v1.4.0`,所以任何使用 APIv2 的程序都将会自动选择一个与之对应的集成 APIv1 的版本。 -We intend to maintain support for APIv1 indefinitely. +(为什么要从 `v1.20.0` 版本开始?为了清晰的提供服务,我们预计 APIv1 不会达到 `v1.20.0`。因此,版本号就足以区分 APIv1 和 APIv2。) -This organization ensures that any given program will use only a single protocol buffer implementation, regardless of which API version it uses. It permits programs to adopt the new API gradually, or not at all, while still gaining the advantages of the new implementation. The principle of minimum version selection means that programs may remain on the old implementation until the maintainers choose to update to the new one (either directly, or by updating a dependency). +我们打算长期地保持对 APIv1 的支持。 -## Additional features of note +无论使用哪个 API 版本,该组织都会确保任何给定的程序都仅使用单个 protocol buffer 来实现。它允许程序逐步采用新的 API 或者完全不采用,同时仍然获得新实现的优势。最低版本选择原则意味着程序需要保留原来的实现方法,直到维护者选择更新到新的版本(直接升级或通过更新依赖项)。 -The [`google.golang.org/protobuf/encoding/protojson`](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson) package converts protocol buffer messages to and from JSON using the [canonical JSON mapping](https://developers.google.com/protocol-buffers/docs/proto3#json), and fixes a number of issues with the old `jsonpb` package that were difficult to change without causing problems for existing users. +## 注意其他的一些特性 -The [`google.golang.org/protobuf/types/dynamicpb`](https://pkg.go.dev/google.golang.org/protobuf/types/dynamicpb) package provides an implementation of `proto.Message` for messages whose protocol buffer type is derived at runtime. +[`google.golang.org/protobuf/encoding/protojson`](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson) package 使用[规范 JSON 映射](https://developers.google.com/protocol-buffers/docs/proto3#json)将 protocol buffer message 转化为 JSON,并修复了旧 `jsonpb` package 的一些问题,这些问题很难在不影响现有用户的情况下进行更改。 -The [`google.golang.org/protobuf/testing/protocmp`](https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp) package provides functions to compare protocol buffer messages with the [`github.com/google/cmp`](https://pkg.go.dev/github.com/google/go-cmp/cmp) package. + [`google.golang.org/protobuf/types/dynamicpb`](https://pkg.go.dev/google.golang.org/protobuf/types/dynamicpb) package 提供了对 message 中 `proto.Message` 的实现,用于在运行时派生 protocol buffer 类型的 message。 -The [`google.golang.org/protobuf/compiler/protogen`](https://pkg.go.dev/google.golang.org/protobuf/compiler/protogen?tab=doc) package provides support for writing protocol compiler plugins. +[`google.golang.org/protobuf/testing/protocmp`](https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp) package 提供了使用 [`github.com/google/cmp`](https://pkg.go.dev/github.com/google/go-cmp/cmp) package 来比较 protocol buffer message 的函数。 -## Conclusion +[`google.golang.org/protobuf/compiler/protogen`](https://pkg.go.dev/google.golang.org/protobuf/compiler/protogen?tab=doc) package 提供了对编写 protocol 编译器插件的支持。 -The `google.golang.org/protobuf` module is a major overhaul of Go's support for protocol buffers, providing first-class support for reflection, custom message implementations, and a cleaned up API surface. We intend to maintain the previous API indefinitely as a wrapper of the new one, allowing users to adopt the new API incrementally at their own pace. +## 结论 -Our goal in this update is to improve upon the benefits of the old API while addressing its shortcomings. As we completed each component of the new implementation, we put it into use within Google's codebase. This incremental rollout has given us confidence in both the usability of the new API and the performance and correctness of the new implementation. We believe it is production ready. +`google.golang.org/protobuf` 模块是对 Go protocol buffer 支持的重大改进,为反射(reflection)、自定义 message 实现以及整洁的 API surface 提供优先的支持。我们打算用新的 API 包装的方式来永久维护原来的 API,从而使得用户可以按照自己的节奏逐步采用新的 API。 -We are excited about this release and hope that it will serve the Go ecosystem for the next ten years and beyond! +我们这次更新的目标是在解决旧 API 问题的同时,放大旧 API 的优势。当我们完成每一个新实现的组件时,我们将在 Google 的代码库中投入使用,这种逐步推出的方式使我们对新 API 的可用性、性能以及正确性都充满了信心。我相信已经准备好可以在生产环境使用了。 -## Related articles +我们很激动地看到这个版本的发布,并且希望它能在未来十年甚至更长的时间内为 Go 生态系统持续服务。 + +## 相关文章 * [Working with Errors in Go 1.13](/go1.13-errors) * [Debugging what you deploy in Go 1.12](/debugging-what-you-deploy) @@ -207,4 +208,4 @@ We are excited about this release and hope that it will serve the Go ecosystem f --- -> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 \ No newline at end of file