Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm a little surprised this article and none of the contents here at the time of this writing mentioned inheritance.

The core reason I find OOP to be superior for most classes of business application development in general—and web development in particular—is that external dependencies and other high cost interfaces can be delegated to their own method or instance variable then called. In general, I avoid inheritance if there is a way to approach a problem with composition, but testing via fakes are much more reliable to make by inheriting the original class and overriding a method or setting a default initialization parameter then calling the rest of the constructor. This takes the pain out of so many parts of testing that it's truly hard to understate.[0]

For example, say a library has a class that calls out to a third party service like S3 and an instance of that class is used in a business object in order to persist data. By introducing a fake that inherits from the library's class but overrides it's persistence method to effectively be a noop the rest of the validation, calculation, and other side effects that the instance does is conserved without the downside of waiting on a network call. It's not perfect, for example it could miss validation or side effects that only exist on the external service (e.g., S3 could change a mime type based on some crummy AWS logic) but it's a hell of a lot better than a mock that constantly lies to you about what its done. Furthermore, it's actually extremely simple to code up, unlike pure fakes that need to return hard coded values for certain method calls.

I know that something akin to this is theoretically possible in functional languages, but I've found that in practice it's more difficult. Though I appreciate other areas where functional languages shine, including situations with testing.

It's kinda hard to discuss because it really depends on so much context and good judgement and I've seen truly great development teams use languages like Scala in situations where I would have used Python with Numpy / Cython for speed. So don't take this as some hard and fast pronouncement. Just the general observations of someone that's been coding for decades and is reasonably well paid.

[0] I think the primary reason that I prefer integration testing over unit testing is that I've adapted to its shortcomings by the above approach.



The benefits you describe for OOP can be achieved in FP through function composition. DI doesn't have to mean you set up a container, you know.

> I know that something akin to this is theoretically possible in functional languages, but I've found that in practice it's more difficult.

Could you explain what you think is hard about it?


Function composition doesn't spring to mind when I think fakes/mocks/testing. As a long-time functional programmer, the things I'd reach for are:

Higher-order functions: rather than taking a 'service object' as an argument, and calling one or two of its methods, we can accept the methods/functions we need as arguments. Tests can pass in their own functions, which looks a lot like mocking but doesn't need any tricks or frameworks. This is the easiest approach for simple cases, but incurs lots of boilerplate as it's used more and more.

Records: these are name/value collections (think '{foo: bar, ...}' in JS). Since the values can be functions, they're pretty much objects; but we can swap out these values without having to define classes, etc. We can use these just like 'service objects' in OOP.

Type classes/interfaces: these define certain operations/methods that can be performed on a given type, even if that type's generic. They're essentially records like above, but chosen by the compiler based on the type; some languages (e.g. Agda, Idris, Scala) also allow overrides to be taken from the current scope.

Domain-specific languages: we treat the methods/operations of our API as if they're builtin-functionality of some 'embedded' language. When we use such functionality, the result is a program for that embedded language. We can run these programs by defining an "interpreter" (a function which implements each of these operations); we can define a 'real' interpreter and a test interpreter. This is the most hardcore, and usually benefits from some framework for doing the necessary plumbing. I wouldn't recommend it as a first step, but I've had success doing this with Haskell's Polysemy library; another popular library is Haskell's 'Monad Transformer Library' but I've not used that myself.


I'm relatively new to FP, so maybe I'm getting my terminology wires crossed. Perhaps instead of function composition I should have said currying and you would have gotten my drift. That is what you seem to be saying in your second paragraph.


Inheritance is orthogonal. The stuff you want to override can just as well be composed in instead, as a constructor paramter.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: