854 karma · joined February 11, 2011
Love this approach
Conceptually that makes sense. How complicated is it to implement this failover logic in a safe way? If there are two processes, competing for CAS wins, is there not a risk that both will think they're non-leaders and terminate themselves?
I'd love to see a full sample implementation based on s3 + ecs - just to study how it works.
Would jscodeshift work for this? Maybe in conjunction with claude?
Mad props to the team, it's an amazing achievement
We're using tsgo only on the command line btw, we're not using the vscode plugin yet
Yep, I'm a fan.
Ah yes, a German politician representing Bavaria. Just like Gavin Nüsom is an American politician representing Kalifornien.
Datadog by contrast seems to be driven by marketing and companies having a "observability" checkbox to tick.
i = !git add -N . && git add -p
`git i` lets you interactively add new files as well as existing ones class Foo {
private nowFn: () => Date;
constructor(nowFn: () => Date) {
this.nowFn = nowFn;
}
doSomething() {
console.log(this.nowFn());
}
static create(opts: { nowFn?: () => Date } = {}) {
return new Foo(opts.nowFn ?? (() => new Date()));
}
}James explains this a lot better than I can: https://www.jamesshore.com/v2/blog/2023/the-problem-with-dep...
If you lift that assumption, the problem kind of goes away. James Shore calls this Parameterless Instantiation: https://www.jamesshore.com/v2/projects/nullables/testing-wit...
It means that in most cases, you just call a static factory method like create() rather than the constructor. This method will default to using Instance.now() but also gives you a way to provide your own now() for tests (or other non-standard situations).
At the top of the call stack, you call App.create() and boom - you have a whole tree of dependencies.
I find this topic difficult to navigate because of the many trade-offs. One aspect that wasn't mentioned is temporal. A lot of the time, it makes sense to start with a "database-oriented design" (in the pejorative sense), where your types are just whatever shape your data has in Postgres.
However, as time goes on and your understanding of the domain grows, you start to realize the limitations of that approach. At that point, it probably makes sense to introduce a separate domain model and use explicit mapping. But finding that point in time where you want to switch is not trivial.
Should you start with a domain model from the get-go? Maybe, but it's risky because you may end up with domain objects that don't actually do a better job of representing the domain than whatever you have in your SQL tables. It also feels awkward (and is hard to justify in a team) to map back and forth between domain model, sql SELECT row and JSON response body if they're pretty much the same, at least initially.
So it might very well be that, rather than starting with a domain model, the best approach is to refactor your way into it once you have a better feel for the domain. Err on the side of little or no abstraction, but don't hesitate to introduce abstraction when you feel the pain from too much "concretion". Again, it takes judgment so it's hard to teach (which the talk does an admirable job in pointing out).
> China issued an alert warning its citizens and students of the potential risk of traveling in the U.S. and attending schools there. > > “Recently, due to the deterioration of China-US economic and trade relations and the domestic security situation in the United States, the Ministry of Culture and Tourism reminds Chinese tourists to fully assess the risks of traveling to the United States and be cautious,” the ministry said in an alert.
Can't blame them honestly
The beauty of the nullable approach is that when you're testing the system under test (SUT), you're testing it together with its dependencies. Not just that - you're running the real production code of the dependencies (including transitive dependencies!), just with a off switch toggled.
What the off switch does is really just prevent side effects, like making actual HTTP requests to the outside world or reading bytes from the file system. Using embedded stub makes this a breeze (another revelation for me).
For example, while building the "/v1/add-account" endpoint, you write a test for the request handler function. If you write this test using Shore's approach, you'll exercise the whole tree of dependencies, down to the leaves.
This is what is meant by sociable tests - the SUT isn't isolated from its dependencies. It's the real code all the way down, except that you're calling createNull() instead of create() to instantiate the classes. There are many benefits here, but to me the most important ones are: (1) you don't need to write mocks as separate classes, (2) you can catch errors that arise in the interplay of the various classes or modules and (3) as a result you get a lot more confidence that everything is still working after refactoring.
A sociable microtest is a little like an integration test, but you don't exercise any out-of-process dependencies (perhaps with the exception of the RDBMS) so the tests run in milliseconds rather than seconds.
You commented elsewhere that you're worried about the separation of test code and prod code. Yes, this is a bit of a holy cow that I also had trouble adapting to. It turns out, having test-focused code mixed in with your prod classes is totally fine. I'd perhaps prefer a cleaner separation but honestly it's not a big issue in practice.
At first I wasn't sure how well this was going to work, but 30,000 lines later I can see how Shore's approach applies beautifully to real-life projects.
The only thing I'm struggling with is getting the rest of the team to adopt Testing Without Mocks. It takes a bit of effort to learn how to use it correctly.
SICP is hard to work through even if you're just reading but wow, the exercises are on another level! I wonder how it compares to, say, a physics or biology textbook