Re:Functional Core, Reactive Shell
by keithb- on Friday June 03, 2016 @09:12AM
(#11830097)
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.
Re:Functional Core, Reactive Shell
by kod on Friday June 03, 2016 @01:23PM
(#11831694)
> 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.
Re:Functional Core, Reactive Shell
by sesm on Friday June 03, 2016 @09:34AM
(#11830199)
I think those slides try to convey the same idea, as the famous paper "Out Of the Tar Pit" (although, I'm not sure author read the paper)
Reactive and functional approach don't help you to deal with the essential complexity of your problem, but they do minimize incidental complexity, the primary source of which is the mutable state.
Re:Functional Core, Reactive Shell
by jdmichal on Friday June 03, 2016 @10:19AM
(#11830432)
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.
Re:Functional Core, Reactive Shell
by skybrian on Friday June 03, 2016 @11:59AM
(#11831073)
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.
Re:Functional Core, Reactive Shell
by lotyrin on Friday June 03, 2016 @02:08PM
(#11832079)
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
Re:Functional Core, Reactive Shell
by erikpukinskis on Friday June 03, 2016 @03:15PM
(#11832513)
> 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.
Re:Functional Core, Reactive Shell
by BWStearns on Friday June 03, 2016 @11:58AM
(#11831070)
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.
Re:Functional Core, Reactive Shell
by toolslive on Friday June 03, 2016 @12:13PM
(#11831190)
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.
Re:Functional Core, Reactive Shell
by Sharlin on Friday June 03, 2016 @02:00PM
(#11832006)
I guess that's the beauty of a pure functional language - the type system simply disallows side effects unless you play by its rules. You don't even need unit tests to ensure purity!
Re:Functional Core, Reactive Shell
by jkrems on Friday June 03, 2016 @03:48PM
(#11832737)
"You can't proof the absence of defects" comes to mind. No matter what language you use, this will be a "problem". Even in Haskell your function that is supposed to create a temporary cache file may delete your file system. There are just limits to certainty.
Re:Functional Core, Reactive Shell
by JadeNB on Friday June 03, 2016 @08:13PM
(#11834177)
> "You can't proof the absence of defects" comes to mind.
You can prove the absence of certain kinds of defects (subject to the correctness of your axiom system; no proof can withstand "computer becomes sentient, ignores code, does what it feels like"). Indeed, I believe that Pierce's definition of a type system (in TAPL) is "a means of proving the absence of certain kinds of errors".
Re:Functional Core, Reactive Shell
by BWStearns on Friday June 03, 2016 @09:34PM
(#11834466)
Sure, but when you make a request to a system you don't control (file system as per the example) all bets are off as far as formal correctness goes. It's beyond silly to test for the class of issues that your gp brought up as an example since it would effectively be testing if you wrote malware, which you shouldn't, unless you're a malware author. I get that untested and unaccounted for side effects can feel like malware sometimes, but neither types nor tests will save you from yourself if you act like an unattended dog in a chocolate factory (I count wanton use of side effects to avoid making a clear design decision as such behavior).
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.
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.
[1] Eg. https://s-media-cache-ak0.pinimg.com/736x/ee/8e/f6/ee8ef6c2a...
Reactive and functional approach don't help you to deal with the essential complexity of your problem, but they do minimize incidental complexity, the primary source of which is the mutable state.
> 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.
Lasagna code is abstraction done wrong, with possibly too many layers or leaky abstractions (like in a real Lasagna).
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.
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
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.
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) ) ?
sum(a,b) = shell("sudo rm -rf /"); return (a + b)
Clearly undesirable, but still produces the correct result.
If your code is pathological it's not your tests' fault.
"Python Unit tests are a poor man's compiler" is a quote that comes to mind.
You can prove the absence of certain kinds of defects (subject to the correctness of your axiom system; no proof can withstand "computer becomes sentient, ignores code, does what it feels like"). Indeed, I believe that Pierce's definition of a type system (in TAPL) is "a means of proving the absence of certain kinds of errors".