Remix.run Logo
mrkeen 38 minutes ago

That's wishful OO thinking.

It's led to Mockito-style testing (some people call it 'unit', others call it 'integration'). You decide that the behaviour of file.read() is that it returns a string of its contents. You judge your software correct on the basis of how it processes the returned string. Then you launch, and in the real world, the behaviour of file.read() is to return a FileHandleNotOpen exception, so you fix your code, improve your mocks to cover the opening/closing scenario, and re-release it. Next time you run it, it throws a FileNotFound exception. So you fix your code, then extend your mocks to cover that scenario too. Later still, you call read() twice in prod, and hit a FileHandleAlreadyClosed exception (apparently the first read closed the file for you). Fix the mocks again.

You wanted Mockito to lead reality, but it lags it. Rather than Mockito being a useful tool to get your prod system working well, your prod system is actually a useful tool to get your Mockito mocks working well.

Choreographic programming lets you specify this protocol in one place. "The file will be opened, the file will be read, the file will be closed, etc." Then the different parties can't guess/assume or come up with their own half of the protocol incorrectly.

crabbone 4 minutes ago | parent [-]

Two things:

* Unit testing is different from integration testing. They aren't interchangeable. Mainstream languages offer units of code s.a. a function, a class, a module and so on. Unit testing refers to testing the functionality implemented by a single such unit. There's no expectation that it will prove the entire program is correct, but it helps the developer to identify errors on the smallest testable level. Integration is a kind of testing that involves multiple testable units. In the ultimate case, it becomes end-to-end testing when the number of units tested are enough to cover functionality expected from the entire application rather than its individual parts. Even at this point, there's no expectation that the testing will prove the program is correct (unless the test can be made exhaustive), but it can prove that the program is correct sometimes.

* Encoding desired sequence of behaviors into a type doesn't prevent errors, s.a. in your example: FileNotFound. It's either a bad example, or there's little value in encoding the entire chain of expected events into a type.

I think that what parent referred to is what's also known as https://en.wikipedia.org/wiki/Law_of_Demeter . The reason for it you can gleam from the Advantages section of the linked page: in short, less knowledge of the unrelated components makes the replacement of individual components easier, therefore decreasing maintenance efforts. It also implies benefits in creating interfaces with simpler / more common types that are less likely to change over time s.a. to minimize the potential need to alter unrelated objects in order to replace / modify the desired one.

You could argue that this leads to loose coupling (same as, eg. the world of microservices) and make the system more difficult to analyze as a whole. But it seems like that people who embrace this approach are willing to trade system-wide guarantees for their ability to evolve or to patch the system at low cost.