Quantcast
Channel: December 2023 – Michael Tsai
Viewing all articles
Browse latest Browse all 85

How to Control the World

$
0
0

Brandon Williams and Stephen Celis (2018, via Christian Tietze):

While unconventional, we hope that it’s obvious that this solution of controlling dependencies is superior to the traditional solutions in use today. It also gives us an opportunity to reevaluate deep-seated beliefs we may have. We should continuously question our assumptions. In this case, we found that:

  • Singletons can be good (as long as we have a means to control them) and global mutation can be good (when it’s limited to development and testing). Blanket statements against singletons and global mutation are fun to make, but we were able to find real value in using them.

  • Protocols aren’t necessarily a good choice to control dependencies. Protocol-oriented programming is all too easy to reach for when a simple value type requires less work.

The global mutable struct approach certainly reduces boilerplate code, but I’ve never understood how it can work with threads. Most basically, how do you deal with modifying the dependencies for multiple unit tests that are running concurrently?

Their newer Dependencies library handles this more directly:

A dependency management library inspired by SwiftUI’s “environment.”

[…]

For example, you can easily control these dependencies in tests. If you want to test the logic inside the addButtonTapped method, you can use the withDependencies function to override any dependencies for the scope of one single test.

The dependencies are stored in a TaskLocal. But it still feels like a partial solution because not all code uses Swift Concurrency, and you still need to worry about propagating dependencies:

It is important to note that task locals are not inherited in all escaping contexts. It does work for Task.init and TaskGroup.addTask, which make use of escaping closures, but only because the standard library special cases those tools to inherit task locals (see copyTaskLocals in this code).

But generally speaking, task local overrides are lost when crossing escaping boundaries.

[…]

In order to access dependencies across escaping closures, e.g. in a callback or Combine operator, you must do additional work to “escape” the dependencies so that they can be passed into the closure.

It’s more ergonomic not to have to propagate dependencies explicitly, but relying on implicit behavior can be harder to understand and error-prone.

Previously:


Viewing all articles
Browse latest Browse all 85

Trending Articles