Skip to content

Latest commit

 

History

History
332 lines (181 loc) · 22.5 KB

File metadata and controls

332 lines (181 loc) · 22.5 KB

十三、答案

Many of the questions at the ends of the chapters are intentionally thought-provoking, and as with many things in programming, the answers often depend on the programmer's situation or worldview.

因此,下面的答案可能与您的不同,这没关系。这些是我的答案,不一定是你的正确答案。

第一章,永远不要停止追求更好

1。什么是依赖注入?

在本章中,我将依赖注入定义为这样一种编码方式,即我们所依赖的那些资源(即函数或结构)是抽象的。

我们接着说,因为这些依赖关系是抽象的,所以对它们的更改不需要对代码进行更改。对于这一点,花哨的词是解耦

For me, decoupling is really the essential attribute and goal here. When objects are decoupled, they are just easier to work with. Easier to extend, refactor, reuse, and test. While these are all fantastically important, I also try to be pragmatic. In the end, the software will work just the same if it is not decoupled and does not use dependency injection. But it will become progressively harder to work with and extend.

2。依赖注入的四个突出优点是什么?

  • 依赖项注入通过以抽象或泛型的方式表达依赖项,减少了处理一段代码时所需的知识。 对我来说,这是关于速度的。当我跳转到一段代码时,尤其是在一个大型项目中,当某个特定的部分(例如结构)的依赖项是抽象的时,更容易理解它在做什么。通常,这是因为关系描述得很好,交互也很干净(换句话说,没有对象嫉妒)。
  • 依赖项注入使我们能够在独立于依赖项的情况下测试代码。 与第一点类似,当依赖项是抽象的且交互干净时,通过操纵当前代码段与依赖项的交互来测试当前代码段很容易理解,因此速度更快。
  • 依赖注入使我们能够快速、可靠地测试其他困难或不可能的情况。 我知道,我非常关注测试。我并不是这方面的狂热者;这纯粹是自我保护和我的职业精神。当我为其他人编写代码时,我希望它尽可能好(在资源限制范围内)。此外,我希望它继续以我预期的方式工作。测试帮助我在施工期间和将来澄清并记录我的意图。
  • 依赖注入减少了扩展或更改的影响。 当然,如果一个方法签名改变,它的用法也会改变。当我们依赖自己的代码(如本地接口)时,我们至少可以选择如何对更改做出反应;我们可以在两者之间添加一个适配器。无论我们如何处理它,当我们的代码和测试依赖于未更改的部分时,我们可以确信出现的任何问题都在更改的部分或它提供的功能中。

3。它解决了哪些问题?

答案基本上是关于代码气味的整个部分,其中包括代码膨胀、对更改的抵制、浪费的精力和紧密耦合。

4。为什么怀疑很重要?

在我们的行业中,解决手头问题的方法几乎总是不止一种。同样,几乎总是有很多人向你推销一颗魔弹来解决你所有的问题。就我个人而言,当我被问到一个解决方案是否有效时,我的答案通常是这取决于。这可能会激怒那些向我寻求一个简单答案并收到一大堆问题的人,但实际上很少有一个明确的答案。事实上,这很可能是我回来要更多东西的原因。总有一些新东西需要学习,一些新想法需要尝试,一些旧观念需要重新发现。所以,我恳求你们,时刻倾听,时刻提问,不要害怕尝试和失败。

5。成语围棋对你来说意味着什么?

这绝对没有正确的答案。请不要让任何人告诉你其他情况。如果你在你工作的团队中保持一致,那就足够了。如果你不喜欢这种风格,建议和辩论一个更好的。虽然许多人抵制更改,但反对更好代码的人却少得多。

第 2 章,Go 的实体设计原则

1。单一责任原则如何改进 Go 代码?

通过应用单一责任原则,将代码分解为更小、更简洁的片段,从而降低了代码的复杂性。

With the smaller, more concise pieces, we gain increases in the potential usability of that same code. These smaller pieces are easier to compose into larger systems, due to their lighter requirements and more generic nature.

单一责任原则还使测试更易于编写和维护,因为当一段代码只有一个目的时,测试所需的范围(以及复杂性)就要小得多。

2。打开/关闭原则如何改进 Go 代码?

The open/closed principle helps reduce the risk of additions and extensions by encouraging us not to change existing code, particularly exported APIs.

打开/关闭原则还有助于减少添加或删除功能所需的更改数量。当远离某些代码模式(如 switch 语句)时,这种情况尤其普遍。Switch 语句很好,但它们往往存在于多个地方,在添加新功能时很容易错过一个实例。

此外,当问题确实出现时,它们更容易发现,因为它们要么在新添加的代码中,要么在代码与其用法之间的交互中。

3。Liskov 替换原理如何改进 Go 代码?

通过遵循 Liskov 替换原则,我们的代码无论注入的依赖项是什么,都能保持一致的性能。另一方面,违反 Liskov 替代原则导致我们违反开放/封闭原则。这些冲突导致我们的代码对实现有太多的了解,这反过来破坏了注入依赖项的抽象。

在实现接口时,我们可以使用 Liskov 替换原则关注一致行为,作为检测与错误抽象相关的代码气味的方法。

4。接口隔离原则如何改进 Go 代码?

接口隔离原则要求我们定义精简接口和显式输入。这些特性允许我们将代码与实现依赖关系的实现分离。

所有这些都会导致依赖项定义简洁、易于理解和方便使用,特别是在测试期间将它们与 mock 和 stub 一起使用时。

5。依赖倒置原则如何改进 Go 代码?

依赖倒置原则迫使我们关注抽象的所有权,并将其焦点从使用更改为需要

It also further decouples our dependency definitions from their implementations. As with the interface segregation principle, the result is code that is more straightforward and separate, particularly from its users.

第 3 章,用户体验编码

1。为什么代码的可用性很重要?

好的用户体验远不如坏的用户体验明显。这是因为当用户体验好的时候,它只是起作用

通常,一段代码越复杂、模糊或不寻常,就越难理解。代码越难遵循,维护或扩展就越难,出错的可能性就越高。

2。谁从具有出色用户体验的代码中获益最多?

作为程序员,我们既是代码的创造者,也是最大的用户;因此,我们的同事和我们自己受益最大。

3。如何构建良好的用户体验?

The best UXes are intuitive and natural to their users. The key, therefore, is to try to think as your users do. Chances are that the code you write will make sense and hopefully be natural to you, but can you say the same for the rest of your team?

在本章中,我们定义了一些需要记住的方面:

  • 从简单开始,只有在你必须的时候才变得复杂。
  • 应用足够的抽象。
  • 遵循行业、团队和语言惯例。
  • 只导出必须导出的内容。
  • 积极运用单一责任原则。

我们还引入了用户体验发现调查作为进入用户头脑的一种方式。调查包括四个问题:

  • 用户是谁?
  • 你的用户能做什么?
  • 用户为什么要使用您的代码?
  • 您的用户希望如何使用它?

4。单元测试能为您做什么?

简言之,很多事情。它确实因人而异。主要是,我通过测试让自己有信心快速承担重要工作,这取决于需要什么。

我还发现,测试在记录作者意图方面做得很好,而且不像评论那样过时。

5. What kind of test scenarios should you consider?

你总是想考虑至少三种情况:

  • **快乐之路:**你的函数做了你期望它做的事吗?
  • **输入错误:**可预测的使用错误(特别是输入)
  • **依赖关系问题:**当依赖关系失败时,您的代码是否正常工作?

6。表驱动测试TDTs****有何帮助?

TDT 对于减少相同功能的多个测试场景造成的重复非常有用

它们通常比复制/粘贴大量测试更有效。

7。测试如何损害您的软件设计?

There are many ways that this could happen, and some are quite subjective/personal; but, in this chapter, we outlined a few common causes:

  • Parameters, config options, or outputs that only exist because of tests
  • 导致或由泄漏的抽象引起的错误的参数
  • 在生产代码中发布模拟
  • 过度测试覆盖率

第 4 章,ACME 注册服务简介

1。为我们的服务定义的目标中,哪一个对您个人最重要?

这是主观的,因此,没有正确的答案。就个人而言,它必须是可读性或可测试性。如果代码很容易阅读,那么我就可以更容易地理解它,并可能记住更多关于它的信息。另一方面,如果它更易于测试,那么我可以利用这一事实编写更多的测试。有了更多的测试,我就不必记住那么多,我可以让测试确保一切都按照我的需要执行。

2。概述的问题中,哪一个似乎是最紧迫或最重要的?

这也是主观的。这可能会让你感到惊讶,但我要说的是,在测试中缺乏隔离。对于测试,每个测试都有点类似于端到端测试。这意味着测试设置很长,当出现问题时,找出问题所在将非常耗时。

第 5 章,依赖注入与猴子补丁

1。猴子修补是如何工作的?

在最基本的层面上,Go 中的 monkey 补丁包括在运行时将一个变量替换为另一个变量。此变量可以是依赖项的实例(以结构形式)或包装对依赖项的访问的函数。

在更高的层次上,monkey patching 是指替换或拦截对依赖项的访问,以用另一个实现(通常是存根或模拟)替换依赖项,从而简化测试。

2。猴子修补的理想用例是什么?

猴子补丁可用于多种情况,但最值得注意的是:

  • 使用依赖于单例的代码
  • 对于当前没有测试、没有依赖项注入的代码,您希望在其中添加更改最少的测试
  • To decouple two packages without having to change the dependent package

3。如何使用 monkey patching 在不更改依赖包的情况下解耦两个包?

我们可以引入一个函数类型的变量来调用依赖项包。然后,我们可以对局部变量进行修补,而不必更改依赖项。在本章中,我们看到这对于与我们无法更改的代码(如标准库)分离特别有用。

第 6 章,依赖注入与构造器注入

1。我们采用构造器注入的步骤是什么?

  1. 我们确定了要提取并最终注入的依赖项。
  2. 我们删除了该依赖项的创建,并将其升级为成员变量。
  3. 然后,我们将依赖项的抽象定义为本地接口,并将成员变量更改为使用该接口,而不是真正的依赖项。
  4. 然后,我们添加了一个构造器,将依赖项的抽象作为一个参数,以便确保依赖项始终可用。

2。什么是保护条款?什么时候使用?

我们将保护子句定义为一段代码,确保提供了依赖项(换句话说,不是 nil)。在某些情况下,我们在构造器中使用它们,这样我们就可以 100%确定提供了依赖关系。

3。构造器注入如何影响依赖项的生命周期?

当依赖项通过构造器传递到中时,我们确信它们总是可用于其他方法。因此,不存在与使用依赖项相关的零指针崩溃风险。

此外,我们不需要在方法中乱扔保护子句或其他健全性检查,因为任何此类验证只需要存在于构造器中。

4.构造器注入的理想用例是什么?

构造器注入在许多情况下都很有用,包括以下情况:

  • 需要的依赖项在哪里
  • 对象的大多数或所有方法使用的依赖项
  • 其中一个依赖项有多个实现
  • 请求之间的依赖关系不发生变化

Chapter 7, Dependency Injection with Method Injection

1。方法注入的理想用例是什么?

方法注入在以下方面非常有用:

  • 功能、框架和共享库
  • 请求作用域依赖项,如上下文或用户凭据
  • 无状态对象
  • 在请求中提供上下文或数据的依赖项,因此在调用之间会有所不同。

2。为什么不保存用方法注入注入的依赖项很重要?

因为依赖项是函数或方法的参数,所以每次调用都将提供一个新的依赖项。虽然在调用其他内部方法之前保存依赖项似乎比将参数作为依赖项传递更简单,但这种做法将导致多个并发使用之间的数据竞争。

3。如果我们过多地使用方法注入会发生什么?

这个问题有些主观,取决于您对测试引起的损坏和代码 UX 的看法。就我个人而言,我非常关心用户体验。因此,通过减少参数使函数更易于使用一直是我的想法(构造器除外)。 

从测试的角度来看,有某种形式的依赖注入比没有要灵活得多。务实,;你会找到一种适合你的平衡。

4. Why is stopping short useful to the system as a whole?

当没有人监听响应时,能够停止处理请求是非常有用的。它不仅使系统更接近用户的期望,而且降低了整个系统的负载。我们正在使用的资源中的许多资源是有限的,尤其是数据库,我们可以做任何事情来更快地完成请求的处理,即使请求以失败告终,也是有利的。

5。延迟预算如何改善用户体验?

诚然,延迟预算是一个我没有经常听到讨论的话题。鉴于 API 在我们行业中的流行,也许我们应该更多地讨论它们。它们对于触发短停和为用户设置一些界限或期望值具有双重重要性。

当我们发布我们的最长执行时间以及我们的 API 文档时,用户将对我们的最坏情况性能有明确的期望。此外,我们可以使用延迟预算生成的错误来返回更多信息性错误消息,从而使用户能够做出更明智的决策。

第 8 章,配置依赖注入

1。配置注入与方法或构造器注入有何不同?

配置注入是方法和构造器注入的扩展形式。它打算通过隐藏常见问题和环境问题来改进代码的用户体验。参数的减少使方法更易于理解、扩展和维护。

2. How do we decide what parameters to move to config injection?

要考虑的关键是参数与方法或构造器的关系。如果依赖关系无关紧要但却是必需的,比如日志记录器和插装,那么将其隐藏在配置中可以提高函数签名的清晰度,而不是削弱它。类似地,来自配置文件的配置通常是必要的,但不是信息性的。

3。为什么我们不通过配置注入注入所有依赖项?

将所有依赖项合并为一个依赖项有两个重要问题。第一是可读性。方法/函数的用户每次希望了解哪些参数可用时,都必须打开配置的定义。其次,作为一个接口,用户将被迫创建和维护一个接口实现,该接口可以提供所有参数。虽然所有配置可能来自同一个地方,但其他依赖项可能不来自。包含环境依赖项有点厚颜无耻,但它们的存在几乎无处不在,而且它们在每个构造器中的重复将非常恼人。

4。为什么我们要注入环境依赖项(如记录器),而不是使用全局公共变量?

作为程序员,我们喜欢不要重复自己干式原则。在任何地方注入环境依赖性都是重复的。

5。为什么边界测试很重要?

我希望我们都能同意测试的重要性。测试的部分价值是通过反复运行测试和尽快检测回归。为了尽量减少经常运行测试的成本,我们需要测试速度合理且绝对可靠。如果测试依赖于外部系统,特别是我们不负责的系统,那么我们就将测试的价值置于风险之中。

任何事情都可能发生在外部系统上。主人可以打破它;互联网/网络可能会瘫痪。内部面向边界测试类似于我们的单元测试。它们保护我们的代码不受回归的影响。面向外部的边界测试是我们记录和确保外部系统执行我们需要的操作的自动化方式。

6。配置注入的理想用例是什么?

配置注入可以在与构造器或方法注入相同的情况下使用。关键的决定因素是依赖项本身是否应该通过配置注入进行组合和某种程度的隐藏,以及这如何改善或降低代码的用户体验。

第 9 章,即时依赖注入

1. How does Just-in-Time (JIT) dependency injection differ from constructor injection?

这在很大程度上取决于构造器注入的使用方式;特别是,存在多少种不同的依赖性实现。如果一个依赖项只有一个生产实现,那么它们在功能上是等价的。唯一的区别是 UX(也就是说,是否有一个较少的依赖注入构造器)。

但是,如果存在多个生产实现,则不能使用 JIT 依赖项注入。

2。在处理可选依赖项时,为什么使用 NO-OP 实现很重要?

如果构造器没有设置成员变量,那么它实际上是可选的。因此,我们无法确保该值已设置,而不是为零。通过添加可选依赖项的 NO-OP 实现并自动将其设置为成员变量,我们可以假设依赖项始终为非 nil,并且可以放弃对 guard 子句的需要。

3。JIT 注入的理想用例是什么?

JIT 注入适用于以下情况:

  • 替换一个依赖项,否则该依赖项将被注入构造器,并且只有一个生产实现
  • 在对象和全局单例之间提供一个间接层或抽象层,特别是当我们想在测试期间交换全局单例时
  • 允许用户选择性地提供依赖项

第 10 章,现货注射

1。当采用依赖注入框架时,您希望获得什么?

当然,这在不同的框架之间有很大的不同,但是通常,您可以看到以下内容:

  • A reduction in boilerplate code
  • 设置和维护依赖项创建顺序的复杂性降低

2。在评估依赖注入框架时,您应该注意哪些问题?

除了前面提到的收益,我的主要标准是它对代码的影响;换一种说法,不管我是否喜欢采用框架后的代码外观。

我也会考虑框架本身的可配置性。需要一些配置,但太多可能会导致复杂的用户体验。

要考虑的最后一个方面是框架项目的健康。是否正在积极维护?报告的 bug 是否得到响应?在框架之间切换可能并不便宜;花一点时间来确保你选择的那个从长远来看是适合你的,这是一个好主意。

3。采用现成注射的理想用例是什么?

通常,框架只支持构造器注入。因此,现成的注入可以在已经使用构造器注入的项目中使用。

4。为什么保护您的服务不受意外 API 更改的影响很重要?

服务的 API 有时被描述为契约。合同一词是经过仔细选择的,因为它旨在表达 API 与其用户之间的关系是多么重要和有约束力。

When we publish an API, we do not have control over how our users use our API and, perhaps more importantly, we have no control over how their software reacts to changes in our API. For us to deliver on our contract, it is imperative that we do everything we can to ensure we do not break their software by making unplanned changes to our API.

第十一章,抑制你的热情

1。你最常看到的依赖注入导致的损害是什么?

对我来说,这绝对是过多的参数。在学习依赖注入并对此感到兴奋之后,很容易抽象和注入所有内容。随着每个对象的职责的减少,它会使测试变得更容易。缺点是对象太多,注入太多。

如果我发现自己有太多的依赖关系,我将尝试后退一步,检查我的对象设计,特别是寻找单一责任原则问题。

2。为什么不总是盲目地应用依赖注入很重要?

仅仅因为某样东西很酷或者是新的,并不意味着它是这项工作的最佳工具。我们应该始终努力解决问题,尽可能避免使用c**argo cult编程。

3。采用像 GoogleWire 这样的框架是否消除了依赖注入引起的所有形式的损害?

遗憾的是,没有。由于它只支持构造器注入,它甚至不能应用于所有情况。除此之外,它还可以使过多参数的管理大大减轻痛苦。

虽然这是一件好事,但它减轻了痛苦这一事实使我们不太可能感到有必要解决根本问题。

第 12 章,回顾我们的进展

1。对我们的样品服务最重要的改进是什么?

这是主观的,因此,没有正确的答案。对我来说,这要么是脱钩,要么是去除全球化。当代码解耦后,我就更容易测试了,每一块代码都变成了一小块,这意味着很容易处理。基本上,我不必想太多或记住太多的上下文。

至于 globals,我在过去曾被这一点所困扰,尤其是在测试期间发生的数据竞争。我不能忍受我的测试不可靠。

2。在我们的依赖关系图中,为什么数据包不在 main 下?

我们可以通过重构来实现这一点,但目前我们正在模型层和数据层之间使用 JIT 注入。这意味着代码的用户体验得到了改进,但依赖关系图并没有像它可能的那样平坦。数据层还输出 DTO 而不是基本数据类型,因此任何用户也将使用数据包。

如果我们也决定删除它,我们可以为 DTO 制作一个特殊的包,然后将该包从依赖关系图中排除,但这是额外的工作,并没有给我们带来这么多好处。

3. What would you do differently if you were starting a new service?

这是主观的,因此,没有正确的答案。在完成 UX 调查之后,我会首先编写足够的代码来启动 web 服务器,即使这还没有使用依赖项。然后,我将设计所有端点,并使用硬编码响应实现它们。这将使我能够通过示例与用户讨论我的可交付成果。我还可以进行一些端到端测试,以防止 API 回归。

然后,我的用户就可以放心、清晰地使用我的 API,我也可以填写详细信息