I am having a bit of an issue with some of these things being called "pattern", but clearly, monads, guards and validators are different beasts one may consider patterns.
Particularly interesting about monads is of course, that their available operations mean you can apply other functions transparently within the monad (read: map
). Therefore, monads are quite readily reusable.
Similarly, validators are typically reusable outside of your core business logic. I believe you are not taking full advantage of this yet. When I see UserNameAtLeast3Chars
, I wonder what this method does that is so specific to a user name and which cannot be handled by a more easily reusable validator StringSizeAtLeast(username, 3)
. In terms of functional programming, a string size validator really just needs a function to provide the string and the minimum/maximum allowed length, which makes it applicable to a lot of possible strings.
Therefore, to answer your last question first: What speaks against a GuardAndValidateBySpecificationWithMonads
-framework, is that you can barely reuse it elsewhere, because it is a very specific beast. In contrast, the other frameworks provide abstractions that are highly reusable on their own.
As for a more practical approach to the issue, it may be worth considering to add a custom abstraction layer, which unites these frameworks if you find yourself continuously writing the same sort of code. Of course, this union is similar to a custom framework, and the above answer about lack of reusability applies again as well.
I'd be more interested in discussing though, which of these frameworks are really necessary, because each of those has different pros/cons and you may reduce the overlap by eliminating those frameworks, where you do not really need the pros all that much.
Quick discussion of the individual patterns and their pros/cons:
Specification: I'm not aware of this as a pattern. Of course, you can have a specification and check against it and call the corresponding framework "specification" again, but that doesn't really make it a pattern of its own.
Condition: This is also not clear to me in the given context. A condition is a well-defined term in programming and occurs for example as part of an if
-statement. As a pattern for checking your method input, it makes little sense.
Guard: This is the first one I recognize as a pattern, but it comes with a distinct behavior: a Guard (condition) checks for a certain property to be present on the input and fails hard if that property is not holding, i.e. throws an exception or something similar. This is a pro as well as a con though. On the pro side, every user of your API is immediately penalized for bad inputs, on the con side, every untested usage fails hard during runtime with potentially dangerous consequences due to the changed control flow.
Monads: These are not really a pattern for checking inputs w.r.t. some property. They have a huge number of advantages and really cool stuff you can do with them, but they are quite orthogonal to the rest of the matter discussed here. To say it frankly, you probably do not want to discuss whether a List-framework should be removed, yet lists are the best-known monads. That most people don't think of Lists as monads just shows how transparent they can be.
Validation: This is another interesting pattern for checking inputs, but has a clear distinction to Guarding - it does not fail hard. Validation is typically used when you want to also generate appropriate feedback for users to let them know what is wrong (f.ex. display a message like "Usernames must contain at least 3 characters").
From the point of view of checking method inputs, I'd argue that Guarding and Validation are the main frameworks to look at, and they have quite different applications, because one is geared towards developers, the other towards users.
The remaining "patterns" are no input-checking patterns to me and if you have correspondingly-named frameworks in your application, they may well have a huge overlap with the two main patterns, in which case I suggest you investigate how they differ in terms of treating a violation of the checked property.
Final example how not to consider these concepts as overlapping: consider the property that the given user
may be null
. You can handle this case with monads, guards, and validations, hence these three things overlap. No! They just share the (implicit) condition user == null
, but treat it in completely different ways:
- monads: I don't want to care about
null
, so if it it's not there, just don't do anything with the user
. In particular, do not fail or report a problem to the user.
- guards: if the
user
is not present, a developer made a mistake and I'm going to tell her by blowing up. Can't ignore a big bomb exploding in your face after all..
- validation: looks like the application-user forgot to select a
user
, so I better inform the application-user that he should do so.