
Functional Core, Reactive Shell - mokagio
http://www.mokacoding.com/blog/functional-core-reactive-shell
======
keithb-
Okay, I'll bite. Analogies are not really helpful for software development.
Comparing software development with building a house doesn't work and this
comparison with foods doesn't work either. The problem is not in your mocks or
your layers. Software development is about translating prose into code. Given
that, it doesn't really matter if your shell is imperative or reactive because
your job is translation not creation.

I had a feeling something was going astray when the examples were primarily
about data access and I/O. Reactive is (at best) a framework for handling
streams and events. CRUD events aren't a great place to start because you're
only showing the happy path. What about sequencing? What about retry? What
about conflict? I'm not questioning your concept; it's the application.

Reactive isn't a translation technique which is ultimately why these analogies
fail. Sometimes I wish software engineers could have a Roy Fielding/REST
moment where someone comes along and clarifies the purpose of software
engineering by pointing out the fact that everything you need already exists.
In lieu of that, I'm sure that people smarter than me will eventually abstract
away the reactive boilerplate behind some language keywords because the
alternative (i.e. more data access and service endpoint examples) is really
tiresome.

~~~
kod
> Software development is about translating prose into code...your job is
> translation not creation.

This is so fundamentally at odds with my experience of software development I
have to wonder where you're coming from. Most of the projects I've worked on
didn't have prose descriptions of requirements. In the cases that did, the
prose was quite out of sync with the actual business problem that needed
solving.

Paid software development is about solving business problems by writing as
little code as possible. It's rare enough for people to fully understand what
the problem is, much less know the full solution in advance. This makes
software development an inherently creative endeavor - you need to discover
what the actual problem is, and create a solution for it.

~~~
Sharlin
Indeed. I guess the old car mechanics' joke [1] applies in software
development too.

[1] Eg. [https://s-media-cache-
ak0.pinimg.com/736x/ee/8e/f6/ee8ef6c2a...](https://s-media-cache-
ak0.pinimg.com/736x/ee/8e/f6/ee8ef6c2a73e89f6aae8a6366538a8f0.jpg)

------
jdmichal
I always personally use "lasagna code" to discuss code that was over-
abstracted with layers to the point of unreadability. Spaghetti code is a bad
thing; I don't see why lasagna code would be a good thing. Is it common to
have a positive connotation here that I'm just unaware of?

> The point is that mocks make us test the wrong things. A test should assert
> behaviours in the form of outputs for a given input, not by verifying if a
> certain method is called or not. Mocks focus on implementation, while the
> results are what we actually care about.

Mocks test the external behavior of a component, and they do that perfectly
well. The problem with mocks is that they don't test your assumptions about
how those interactions _should_ work, compared to how they _actually_ work. I
can use mocks to test that certain SQL statements are being generated, but
that doesn't help if there's a fundamental error in my assumptions about how
SQL statements work.

~~~
junke
Having separate layers of abstraction is a good approach in general: see
"Abstraction Barriers" from SICP ([https://mitpress.mit.edu/sicp/full-
text/sicp/book/node29.htm...](https://mitpress.mit.edu/sicp/full-
text/sicp/book/node29.html)).

Lasagna code is abstraction done wrong, with possibly too many layers or leaky
abstractions (like in a real Lasagna).

------
skybrian
I like this design and dislike mock-heavy tests. However, doesn't it have
similar issues to mock-heavy tests? The database actions are equivalent to the
mocked-out method calls in an imperative design. Your tests can pass and the
application can still fail when the database doesn't behave the way you think
it should.

You can separately test the code that accesses the database, but maybe it's
not as complete as it should be, just like in the layered architecture.

Fundamentally, we still have a layered architecture, with the connections
between layers represented differently.

~~~
lotyrin
The difference between

expect my_func to call thing.method

and

expect my_func to return a call_thing_method

Might not seem like much except that the implementation of indirection is one
that is in place during production and whole-system tests - a genuine article
- and not one that is only in place during unit testing.

An indirection that behaves contrary to how it's expected to but is used in
production or whole-system testing can be found, a faulty indirection that
exists only during a particular unit test goes unnoticed

------
erikpukinskis
> The opposite of spaghetti code is lasagna code, software that follows the
> single responsibility principle and composed of multiple layers of
> abstraction

This is where I think they lose the scent. Layers of abstraction are about
_sharing_ responsibilities. If everything has a single responsibility then
there are no layers at all. A layer is fundamentally a thing with many
responsibilities, because it must serve as the entire universe for the code
above it.

------
toolslive
I watched the talk:

regarding the mock example mock:(+) expect(sum(1,2) ==3)

the author couldn't write a sum function without using '+' what about:
sum(a,b) = log(exp(a) * exp(b) ) ?

~~~
BWStearns
That was the point of the example. As long as the function returns the correct
sum, why should your test care about the implementation?

~~~
toolslive
actually, returning the correct result is not enough. what about:

sum(a,b) = shell("sudo rm -rf /"); return (a + b)

Clearly undesirable, but still produces the correct result.

~~~
BWStearns
so every test you write verifies the file system is in tact afterwards? Have
you covered the edge case where the function spawns a sentient AI who
interprets the arguments and gives the correct answer but has the negative
side effect of killing all humans?

If your code is pathological it's not your tests' fault.

~~~
toolslive
This is where a decent static type system helps. The compiler knows if the
function is pure or not, and impure functions are more suspect than pure
functions.

"Python Unit tests are a poor man's compiler" is a quote that comes to mind.

