Bizarre. The article doesn't explain much about why the author hates design patterns. The best I could come up with is in this passage:
Perhaps I’m being unfair to Design Patterns, but those articles [about design patterns] seem like the worst kind of tech writing, the most likely to inspire useless discussion that doesn’t help move a team forward. Catering to software developers perfectionism tends to be bad for business. And this particular type of article seems to be the most extreme in that regard.
To recap, DP articles:
- are the worst kind of writing (still no explanation)
- likely to inspire useless discussion (sometimes play is a lie?)
- catering to perfectionism (this can be said about any tool)
The article reminds me of many folks I've met who despise analysis and planning. They're constantly getting themselves into trouble that requires quick action. When it works, the chalk the success up to their quick action. They denigrate project planning tools of nearly every stripe. They're also typically very bad communicators and don't manage time well.
But in the end, the need for quick action resulted from the immense dis-ease the person feels around planning, brainstorming, and up-front analysis. The cycle repeats on every project.
There must be an anti-pattern that describes this kind of behavior...
> There must be an anti-pattern that describes this kind of behavior...
Well, the anti-pattern that describes this kind of logic is the Straw Man. If all Design Pattern articles were theoretical blah blah blah, The Fine Article might have a point. They aren't. Not even the majority. As long as we just point to the (bad) minority, and act as if they're representative of the whole, then we can continue to hate all articles about design patterns, and play — because this is just as much a form of "play" as the "learning" or "avoiding" techniques being decried — in our preferred, and specifically unstructured way.
Can you pick one DP article that seems good to you, please? I won't complain about the article you'll mention. In both OP and your comment, the lack of a concrete example irks me.
It seems to me that those making blanket statements on how design patterns are bad or useless are actually the ones who have an obligation to support their baseless assertions. Meanwhile, it seems rather obvious that similar problems can be solved by similar solutions, and experience derived from solving those frequent problems can be levetaged to avoid reinventing the wheel every single time that very same design problem pops up. These solutions have a name: design patterns. It boggles the mind how some people insist in claiming that reusing tried and true solutions is useless.
I thought it was explained really clearly at the very beginning: It's what those cargo cult architecture conversations are full of. The ones where people sound smart but aren't actually designing anything useful.
I think the author might be taking it a bit too far, but they are certainly describing a real thing that I see all the time. Which is bad engineering choices made without thinking and then justified with a string of design pattern terms, used incorrectly.
> “The ones where people sound smart but aren't actually designing anything useful.”
I’ve been part of lots of teams and teams composed of great and poor levels of talent and I’ve never seen this type of behavior which you describe. Usually the folks getting up in arms over architecture are pissed because folks with experience shot down their idea for reasons of pragmatism and the individual couldn’t handle it because of ego.
You may have been lucky. One of the worst messes I had to make work was nonsense a senior dev was working on up until he quit. It was a mess of object oriented wankery that soured me to even the term 'object' for years. He needed to implement 6 funtions with a couple of sql statements to do session handling in php, and instead there were factories and visitors and repositories and facades and I couldn't tell you what else this guy was thinking. He was supposed to be done, and it just needed deployment, but it ended up taking me 3 months, and a half dozen late night deployments and rollbacks before I managed to get it working. If I'd been more experienced at the time I would have ripped it all out and just implemented the 6 functions we needed, but I really assumed he'd had a good reason for the nonsense he'd written.
that reminds me of all the EJB/JBoss/Spring nonsense that soured Java for a lot of people... presumably it was a necessary step but it would have been nice to just skip ahead to web services
Years ago I was a TA for first year computer science. I graded my students' assignments on code style, with reference to automatic grades generated by a test suite.
One of the most interesting things I learned was how much variance there was in code size between submissions. I would first read an assessment from a good student which was completely correct and 500 lines long. The code would seem reasonably clear, compact and well written. Then I'd pick up the next assignment, which had also gotten 100% on the automatic tests and it would be only 150 lines of code. And the code wouldn't seem any more compact or unreadable compared to the first - they both looked like decent solutions; but one of those solutions had implemented the same spec in 3x less code!
And then I'd pick up a submission by a weaker student who got 50% of the automarking results done. The submission would be 600 lines, and you could see the sweat that had gone into writing it. The student was clearly struggling to keep track of all the moving parts in their code. If that was all I looked at I would have assumed the assignment would have taken 1000 lines to implement, and be way too hard for my first year students.
(In case you're wondering, there was no difference in programming languages, tooling or knowledge. All students were using the same environments to write plain C code.)
The lesson I learned was that its very hard to tell whether you or your coworkers are actually using a good approach to solving your problems. I never hear people say things like "Oh, I think this different abstraction could save us 70% of our code". But having seen enough examples I think its usually the case that those abstractions exist. We just (bizarrely) don't seem to spend any time looking for them. Intuitively I would expect that bad abstractions would be obvious in code, but that seems to be often not true in practice. After reading the first students' submission I usually would still have no idea how long the average correct submission was going to be.
It seem clear to me that both students described in second paragraph produced good code, although the shorter one was better. The second paragraph student was clearly behind them. It just does not seem hard.
I heard colleagues say "if we split this away, join these two classes into one and hide it under interface, it will be simpler/shorter". The word abstraction was not used, but it was abstraction they were talking about.
So maybe it depends on local culture, whether people talk about how things are done overall or whether they focus on idioms only.
> instead there were factories and visitors and repositories and facades and I couldn't tell you what else this guy was thinking.
Factories: generate objects whose type can freely change. See polymorphism.
Visitors: create any operation you'd like to implement without having to change a type. See open/close principle.
Repositories: separate SQL voodoo from your code and establish a dedicated component to handle database transactions.
Façade: isolate the world from all the gory detail in a manner that you are free to change both the component and its clients. See separation of concerns.
I find it more worrying that the code developed by a senior developer was dumped just because the junior developer who replaced him was entirely clueless and not obly unable to understand existing codebut also unable to even grasp basic design problems that justify the senior developer's decision.
I find it more troubling that someone would think they needed any of these patterns to implement php sessions.
What you fail to grasp was the code was so completely unnecessarily complicated that it introduced a fuckton of subtle bugs that were difficult to track down. Things would look like they're working, and then reports of terribly broken behavior forcing an emergency rollback followed by days of trying to figure out what bullshit state things were in to repro the problem.
Worse, is so much of the code was some misguided attempt to future proof the code in ways that make no sense. When your code will never have more than a couple dozen simple sql queries you will never benefit from a polymorphic sql class intended to allow the swapping out of your sql backend with another vendor. It's easier just to rewrite that small piece of code when the time comes.
Just because you can implement fizzbuzz using a dozen design patterns doesn't mean the problem actually requires it.
To me, this essay read more like a troll; a mish-mash of complaints and anecdotes that don't frankly make any sense when put together. But the author succeeded in getting to the first page of HN, so–congrats?
As such, it's hard for me to bring myself to respond to any one point in the piece. It'd be quite easy for most of us to do so, but I suspect that was the intention.
personally, I can't stand to read any article about an 'MVC variant' that has some new idea for how to delineate the difference between model, view, and controller
Or as a friend of mine would put it: Coding is closer to art then to sience, and briliant code more like poetry then math.
I've been in this industry for over 35 years. On dark days I'd give my left leg for working templates and strickt design patterns. On good days I love the close to absolute freedom we have.
It's not that nobody knows. It's as some frameworks prefer to shoe-horn the MVC pattern to their particular implementation which has some nuances, and therefore prefer to create a whole new jargon to dance around that fact. In some cases there are even frameworks who force gui responsibilities onto the model and don't even provide any controllers and yet still feel entitled to call what they did as MVC.
> They had begun work in 2011, but they rejected all of the existing document databases (such as MongoDB or Riak). In 2013 they decided to work on version 2.0 of their software, which would be a complete re-write, but they decided to continue to keep JSON strings in PostGres.
That's speaks in their favor, in my opinion. We actually consciously moved the way MongoDB -> PostgreSQL JSONB column, and are pretty happy with that.
Sorry, original author, but if you recommended us to use shiny new databases like MongoDB or Riak, we'd probably think of you as a young and inexperienced engineer.
Although I agree about Design Patterns. For example, you'd use/no use ReactiveX regardless of whether you know about Observer pattern or not.
The #1 enginering design pattern is to obey speed of light transfer of information latency. Moving data into and out of MongoDB has that unavoidable cost, and it is a waste if the only thing you want is JSON formatting of the same data already in your RDBMS.
Overall latency is important. Where it comes from isn't very important. Transferring data a few light-nanos of distance into or out of a colocated Mongo DB instance simply isn't going to move the needle on any important metric. (Disk IO latency might, but that has little to do with the speed of light.)
This is a really serious indictment, folks: “Also, they were doing some unusual things, such as storing JSON strings in a text field in a PostGres database”
Don't understand if you are being sarcastic. I can say that the giant blobs of XML in our text fields are very annoying. I have to compare them with Kdiff and too many of them don't fit in DBVisualizer's buffer.
I think sarcastic. Psql has great support for json - they were probably taking advantage of it. If you've already got psql set up in your project and have minimal json storage requirements, it can be all you need. You also gain the possibility of retaining some normalization of the data if necessary, which is a lot more complicated if you throw it all in an entirely different database.
Small nit: psql is the command line client bundled with Postgres (PostgreSQL). And yeah, the JSON/JSONB support in Postgres is pretty nifty. Postgres 10 added full text search for JSON/JSONB columns.
I hate to be negative, but as far as I can tell, the only thing this article does is indicate a lack of understanding of design patterns. Equating them to a pseudo-intellectual exercise is missing the point by a proverbial mile.
Here's a brief explanation: Often when you are coding, you find an interesting solution to a problem. Then when you look at someone else's code, in a different domain, you realise that they are using a very similar solution. You ask them where they got the idea and they say that they saw it in some code in yet a different domain. So you think, "Hey, this seems to be a useful solution to a problem that occurs in several domains". You tuck it away in the back of your mind and when you see that problem again you say, "I've got a solution to that problem!" and you use it.
That's a design pattern. Everybody uses them. The alternative is to bloody mindedly refuse to learn and to use completely novel approaches to the same problems every time you see them. That fact that people write down these solutions and give names to them doesn't suddenly make the solutions ridiculous. The fact that some programmers mindlessly apply "patterns" in the hopes that they can escape thinking doesn't make the solutions bad. That's like saying we should never make libraries because some bad programmers think they can glue 1500 libraries together to make a website without having to know how to program.
What the author is complaining about has nothing to do with design patterns. People sitting around building mythical designs that never get built has been going on since before the time of Fred Brooks. There are whole classes of architects, analysts, and consultants who have mastered the art of getting paid big money to talk impressively about software without ever writing a single line of code. Quite a lot of them write popular blogs! That's nothing to do with design patterns.
I am half curious to read the author's rants about OO, but I suspect I'll find more of the same -- some programmers wrote some terrible, terrible code and called it OO. It must stop! Sigh...
I'm partial of the explanation of DP as missing language features. In fact, Design patterns all use the human compiler to check for correctness of implementation.
PaulGraham said "Peter Norvig found that 16 of the 23 patterns in Design Patterns were 'invisible or simpler' in Lisp."
With this mindset, functions (or subroutines) are language patterns in assembler.
In the other extreme, if you use a sufficient advanced language, all your language patterns are actually checked by the compiler and/or interpreter, instead of the programmer. They stop being patterns and are just language features.
Isn't there a point where it's more desirable to have 'features' available through patterns, than through the language itself? The larger and more complex the language grows, the more cognitive overhead - so programmers of a hypothetical 'perfect' future language might still use patterns, simply so the language didn't grow out of reasonable proportions.
If code is data then in principle you could implement them as libraries rather than language extensions. I don't use lisp or haskell enough to know whether someone has already done this.
That said I'm also not convinced that it would help in many cases. What would you enforce about a wrapper for example other than it's constructed from one argument and returns a different type containing a reference to the original one? If you're not able to do that yourself then would a wrapper library really be any use?
That's basically the role of frameworks. Any web application I build is going to need at least some subset of functionality common to all web applications, and implementing everything from first principles is absurd, so I use a framework.
Someone else building something completely different with the same language doesn't need those things, so cooking my needs into the language itself is just going to add overhead or force unwanted design choices on to them, but there's probably a different framework for whatever they're doing too.
Clearly having a language that makes it easy to implement common solutions to problems is a good thing. I don't think this means we should stop studying those solutions though. To take a notorious example, visitor pattern is just a work around for the lack of multiple dispatch. However, I do not have enough fingers to count the number of times I've had to explain the benefits of multiple dispatch to people. And perversely I can say the same about the number of times I've heard people conflate multiple dispatch with functional programming! The truth is that sometimes I have an extensible group of types with relatively static operations, and sometimes I have a relatively static group of types with extensible operations. The techniques I employ to write generic code in both cases is different. Another great example is the reactor pattern (a la Douglas Schmidt), which many people view as being the cost of using Node, rather than being a very useful technique for controlling real time processing.
In the other extreme, if you use a sufficient advanced language, all your language patterns are actually checked by the compiler and/or interpreter, instead of the programmer. They stop being patterns and are just language features.
I've heard people mention that a lot, but the only concrete example the ever give is "you don't need the strategy pattern because of first class functions", which is doesn't hold up when you examine it closely.
>I'm partial of the explanation of DP as missing language features.
It is a way to encode basic features in terms of simple standard building blocks: objects. However, I think that explaining everything in terms of objects is a terrible idea. Objects are extremely complex already, it is a bad idea to use them as a basic entity. Just compare lambda semantic rules (there are only three of them for untyped lambda calculus), and an object's semantic. Later is much more complex.
I think that the complexity of objects led to the complexity of patterns, making them verbose and ugly. Just compare visitor and pattern matching, or command and higher order function.
It is true that in more advanced languages having more features patterns become invisible, but I thing they also would become invisible if they were composed of more simple entities.
An object is literally an encapsulation of state. Imagine having 3 functions which do something with points on a graphical plane. The first 2 parameters in each function represent the x and y positions of the point. You can make an OO "class" by simply grouping those three functions. You can make an object by partially applying x and y to the three functions. You can "respond to messages" by calling those functions.
I agree with you that many OO languages add unnecessary syntax and lack features, but objects are not related. We need to encapsulate state and we like to group functions into cohesive modules that are related to the state that they are going to deal with. We do this whether we use object nomenclature or not.
> I'm partial of the explanation of DP as missing language features.
I find this argument to be rather disingenuous as it ignores the fundamental purpose of design patterns: communicate intent through a common language. I mean, 9 out of 10 patterns are nothing more than specific uses of inheritance and polymorphism. Technically, they don't require anything more than what inheritance and polymorphism already offet, and the only difference is precisely why and how inheritance and polymorphism are used in a specific problem. I mean, what's the fundamental difference between a state pattern and a strategy patern? Hell, isn't the strategy pattern a rather direct use of polymorphism? Is the language even missing anything at all? Of course not, because the purpose of using design patterns is to communicate intent and describe precisely what was the problem and more importantly how it was solved.
The point remain: why the average OOP languages does not have any "strategy", "command", "factory", or "observer" keyword? Or direct expressions of those concepts? Communicating intent through a common language is all and good, but then why that language isn't formally encoded in the compiler?
> Is the language even missing anything at all?
This should be easily measured by the heaps and heaps of similar looking code that have no direct bearing with the actual problem.
> The point remain: why the average OOP languages does not have any "strategy", "command", "factory", or "observer" keyword?
Because they add or take nothing from the language. Arguing for the needless addition of these keywords makes as much sense as arguing in favour of camelCase or polish notation or "m_" and "s_" prefixes. They add nothing to the language, they are redundant and they are used to convey meaning alone.
Therefore, what problem would be solved by adding useless keywords to a language, particularly when those making these claims come from a point of ignorance and lack of experience?
> This should be easily measured by the heaps and heaps of similar looking code that have no direct bearing with the actual problem.
This makes no sense at all. Rather straigh-forward applications of polymorphism don't vanish if you call them by different names.
Keywords are of course secondary. Did you miss the "or direct expressions of those concepts" part? I was implying the existence of some boilerplate reducing syntax behind them. With the right features, most patterns vanish. For instance:
// C++98
class Adder {
public:
Adder(int inc) : _inc(inc) {}
int const operator+(int n) { return n + _inc; }
private:
int _inc;
};
// C++11
std::function(int(int)) adder(int inc) {
return [=](int n) { return x + n; };
}
(* Ocaml *)
let adder inc n = inc + n
-- Haskell
adder inc n = inc + n
Assuming currying is a good fit for your problem, you probably don't want to bother with factories, if you can use a language that supports currying directly. Same goes for other patterns: whichever you need suggest what language features you want.
Or you could be content with repeating the same kind of code over and over, but I'm not going to accuse you of such obvious stupidity.
> With the right features, most patterns vanish. For instance:
The thing is they don't. For example, a function pointer does not fill the need that strategy patterns fill. You look at a function pointer and you have no idea where and how and why it is supposed to be used, there is no type constraint, and there isn't even any way to track where how many different instances there are. A function pointer alone says nothing. Yet, specifying a common interface does, as well as implementing any concrete strategy. That's the whole point of using patterns.
Furthermore, design patterns help clarify technical discussions as they communicate clear concepts as well as solutions to particular problems they solve. You get a clear idea of how a data access module is designed if the designer states that he used active records or repositories, and if classes are named appropriately you can already form a clear mental model of what to expect and, more importantly, what you will can or may need to do to fix a bug or implement a feature.
Who ever said "the right features" were limited to function pointers? Who ever even talked about function pointers? My example didn't even use function pointers! Are your imagination so limited that you cannot think of language features that would reduce the need for any specific pattern? I just showed you one, dammit!
Let's step back a little:
> 9 out of 10 patterns are nothing more than specific uses of inheritance and polymorphism.
Of course, inheritance and polymorphism are the only thing Java has! Or had, back in 1995. Hammer & nail. There are other ways of expressing those patterns, other ways of solving the same problems that do not require those patterns at all. You just need to wake the fuck up and realise OOP is not the only game in town.
> design patterns help clarify technical discussions as they communicate clear concepts as well as solutions to particular problems they solve.
I have yet to witness a single instance of that in my job, and I've seen loads. On the contrary, naming patterns often clouds the underlying concepts. A pet peeve of mine is the Singleton. It's overused for sure, but that also makes it one of the most widely understood patterns. Yet few seem to realise that singletons are nothing more than global variables with a couple bells and whistles. And since the thing is not called "global variable", it doesn't trigger the appropriate mental warnings.
What good are pattern names when programmers can't think clearly to begin with?
It's not just the conversations. Every time I saw a pattern name in real code, I could find a simpler way. I also saw misapplied patterns, like factories that actually were memory pools. Whoever did that didn't know what they were talking about, and patterns didn't save them.
I've read lots of your postings and I like them. I think you are a smart person and I realise that this topic is getting a bit frustrating. But because I think we're all talking past one another, I'll hopefully be able to clear up a few things.
Patterns of code exist. It's a mistake to look at the GoF pattern catalogue and think that it represents patterns that you should use in every situation (or even at all!). I remember hearing someone say one time that all programming should be made up of GoF patterns (touring complete??? ;-) ). Yes, it's absurd. Going all the way back to the original poor article, that's really the problem. People latch on to these ideas; they hear something and it sounds attractive; whatever. Poor programmers are poor programmers. I personally don't think they get worse having learned about patterns. It can help, but it's not the only thing you need by a long shot.
In FP there are lots of patterns. A Functor could be a pattern if anyone bothered to write it up in that fashion. It would make a very nice pattern. Applicative is a great example of a generated pattern -- it's a thing you would never actually design up front, but just pops out. Maybe/Option, Either, etc monads.... awesome patterns (again, if anyone decided to write them up in that fashion).
Let's go back to GoF patterns, maybe the Singleton. The Singleton is not a global variable with a couple of bells and whistles. That's a very popular, but misconstrued view. It's actually the bells and whistles that are the point of the Singleton. A Singleton is a method to ensure that something occurs at most one time in your system (potentially scoped). You use it when you have a piece of hardware that will crash if you initialise it twice. Or when you have to initialise a communications protocol exactly once. Or when you have a very expensive operation and you want to ensure that it only gets called once in a particular scope. Usually people use a global scope for their singletons -- because that's what they actually want. But it's not uncommon to scope singletons at a module level, or other kinds of scope. How do you usually implement a singleton? By providing a function in the scope you want that memoises a piece of data. Yeah, it's not rocket science. It's not supposed to be. It's just a technique. And if you don't write essentially the same code in non-object based langues to achieve the same result, I'll eat my hat (or at least I'll ask you how you would do it differently and then suggest that it might make a good pattern).
One of the weaknesses of the GoF book was that it wrote all the examples in similar languages. At the time, those were the languages that were relevant. Those were the languages that the authors were proficient in. It might surprise many people to know that the GoF was not the first publication on software patterns (not by a long shot). In fact the GoF was only interesting because they attempted to make a pattern catalogue -- a set of patterns that would be relevant to a certain kind of programming. That this is not the kind of programming you enjoy is completely irrelevant. There are other patterns. You can make other catalogues. That is the whole point.
And, you are correct. Patterns don't make a good programmer. But good programmers know programming techniques that are relevant to the style of programming that they use. How do they learn those techniques? How do they communicate where they are appropriate and where they are not? Back in my day we just said, "Here's a keyboard. Don't suck!" And then we'd look at their code and say, "You suck. Why can't you write code like mine?" I'm not really so happy to go back to those days.
The singleton thing is interesting, I admit I overlooked the scope thing. In practice though, I only ever used it to overcome a particular C++ deficiency: initialisation dependencies. C++ doesn't specify the order of initialisation across compilation units, so I can't just lean on constructors.
To ensure initialisation occurs only once, a static scope and a constructor are enough. And to ensure nobody else can spin up another instance, the constructor can be private, though I've never needed that. Anyway, for extra "clean" points, I tend to just expose the interface of the singleton as a set of global functions, doing whatever's necessary for the user to not having to think about initialisation or `get_instance()` at all.
If C++ could guaranteed the order of initialisation satisfies dependencies, we could shave a few lines off that pattern. (Whether this is a good idea is a separate issue.) My point was, each pattern has a corresponding language feature (or set thereof) that could make it less verbose.
while I agree with you I think it is also common for authors of articles on design patterns to talk up the design pattern as something new, and give it a horribly abstract name like 'dependency injection' instead of something common and specific that people otherwise familiar with the pattern can easily map their experience to. In the case of 'dependency injection' which to the best of my knowledge is just 'passing in data to the constructor' this may have been a new concept for the people that came up with it, or it may have been a rebranding effort.
That's not a name and it's not a good name. Naming is hard: you need a succint yet descriptive and specific phrase. "Dependency injection" nicely evokes the issue of your code depending on other code and how you can avoid hardcoding dependencies by injecting a reference to them at start of execution.
The main point of dependency injection isn't that you're passing collaborators into the constructor though -- that's just an implementation detail.
It's about achieving inversion of control, and using a constructor to inject dependencies is just one example of how to do that. There's also setter based DI, and interface based injection, but again, those are just implementation details.
Dependency injection is a very specific way of passing in data to the constructor. In fact, those specifics are one of the important lessons of dependency injection; it makes a big difference whether your constructor accepts parameters for a database connection or a database connection itself.
> In the case of 'dependency injection' which to the best of my knowledge is just 'passing in data to the constructor'
Not quite, it's passing dependencies (structural ones, like a logger or a database connection) to the constructor. If you're passing data in via a dependency injector then something has gone terribly wrong. Before dependency injection was common creating these dependencies was typically handled by either a factory or created inline, the biggest issues this create was difficulty to test and difficulty to scope. DI handles these well, at the cost of reflection magic and occasionally harder traceability.
So yes, I've drunk the cool aid on DI. Lately however I've been getting back into c which obviously lacks a lot of features conducive to unit testing and DI which, but so far I've been able to get similar results from a few macros. So while I still like/use DI when I'm working with c#, I think we've gone wrong at some level, we should be able to write tests without adding so much indirection everywhere.
If a design pattern is new, then it isn't a design pattern -- by definition :-). I neglected to say it outright, but a design pattern is a solution to a problem that already exists in code bases within different domains. The recommended number of different domains is 3 (which is why I used that in my example). The pattern is there to document the situation where the solution applies, where it doesn't apply (very important!), and to give a name to it. Some of the common names for design patterns are arguable pretty poor, although I've never actually seen dependency injected written up as a design pattern (it definitely could be).
I could go on for quite a while about specifics of design patterns (and particularly "generative design patterns" and "pattern languages"), but I think I would be accused of writing an article about design patterns ;-). I think the biggest problem I've seen with articles on design patterns is that there are a lot of articles that are written by people who don't understand them, and haven't read the original literature about them. This isn't confined to design patterns, though. I could say the same thing about pretty much everything in our industry. If you find an article saying x-famous-thing-is-crap, you can almost be positive that if you go back to the origins of x-famous-thing that it's not actually crap at all -- it's just been badly misunderstood by successive generations of anti-evangelists.
> Perhaps I’m being unfair to Design Patterns, but those articles seem like the worst kind of tech writing, the most likely to inspire useless discussion that doesn’t help move a team forward.
Design patterns have their place, though I can see merit to arguments that assert they have been emphasized out of proportion to their utility over the years. But this is a strange critique, given the content of this particular article.
Not a good read to be honest. Author does not sound like a solid programmer. Examples he is giving actually say he might be a “change chaser” - A person who would make a change just because he can add something new to his CV.
The fact that guys he was talking about used PostGres to store JSON as a text field literally means that they chose a simplicity over introducing another dependency from no-sql db with all the cost of supporting it. Unless one needs to search over information stored in JSON it is perfectly and probably disarable to store it as a text field.
Author remark “they spent years for what they can spent weeks just by using a framework” is a proof that he has little or no experience of doing such things himself. No framework can save you so much time. I was designing UI using frameworks and using just basic language tools. Difference is in trade off obetween quality of standard framework components over flexibility of desinging your own. Time is not so much different and might even take longer with frameworks as adoption time for frameworks could be long especially for teams with a high turnover.
I decided to not read till the end because author did not present himself as a person whose opinion might be useful.
The problem arises when people use something just because they think it's cool and someone wrote about it on Twitter and not because it has clear advantages compared to other solutions. Do not blame design patterns if you don't understand them. But you can blame book authors for not explaining it to you properly; maybe they don't understand the patterns too.
Also I disagree about DB and normalization: in most cases the best choice will be SQL DBMS and normalized data. Please let me explain why:
- normalization saves you from duplicating data; MongoDB will make you do the opposite. Updating the data will be more difficult and you will spend more time writing the code.
- fixed schema will make it easier to understand DB structure for new developers and helps you to detect errors; with MongoDB all invalid data will go straight into the storage and you will spend additional time fixing them later.
- foreign keys and transactions save you from saving inconsistent data into the database; MongoDB won't help you with this.
Designing a database properly doesn't require much thinking, it is a natural thing and you are not losing time.
But I understand what the author is talking about. Recently I was using two of Google's products, Doubleclick for Publishers and Google Analitics. Their UI is super slow, at least on my hardware. One Chromium tab with DFP page consumes over 700 Mb of memory. When I decided to inspect it, I found that on DFP page they used Dart "apps" compiled to JS; each of them was not less than 2.4 Mb. Even the "changelog" app which was just displaying information about recent changes (yes, they did it as a separate app).
In Analytics UI, they decided to use Material Design with large number of animations, it is very slow and UI elements are too big for 1280px wide display. It looks more like your legacy enterprise app written 10 years ago in Java and IIS, rather than a Google product.
They could just use something simple, like Symfony Framework and jQuery and avoid most of those problems. I think the reason why they didn't is because Google employs smart people and gives them a boring job; to entertain themselves they tried to play with something new, like Dart or make Android-like UI but didn't have enough time to do it properly.
All these comments are terribly reactionary. I completely agree with the author.
Before you write a design pattern, abstraction, or interface, ask yourself: Is this the simplest possible thing that can work?
And then: Will I ever touch this code again?
And then: How hard would this be to rewrite if the requirements change?
Design patterns were meant to future proof code, but usually they just introduce complexity and useless abstraction, or serve to paper over limitations of your language. You probably can't predict the future as well as you think you can.
Your program is a document. You can change it. You can make it do anything.
Whenever anyone asks me if they should read Design Patterns by Gamma et al, I tell them to go read Refactoring. If they say they've already read it I tell them to read it again.
The patterns are loosely what to do, but that's not the thing that lasts across the years and languages. Refactoring is why and how.
> That's a misguided but unfortunately common view. Best practice is to refactor to patterns, don't try to divining them from the start.
I would add that IIRC Martin Fowler himself, a manwho writes whole books dedicated to desing patterns and software design, is also the man behind the "monolithic design first" mantra.
Not exactly, most of them are intended to encapsulate change. If you don't have a reasonable idea how your system is likely to change then they're mostly useless.
> You probably can't predict the future as well as you think you can
It's not at all unusual for people to be good at predicting where change will occur, even if they don't know exactly what the change is. In my field, I have a very good understanding as to where the industry is heading and I design accordingly.
> Your program is a document. You can change it. You can make it do anything.
Sure, if you're talking about a project with a couple of man years work in it. That's not really the space that design patterns were intended for.
Design Patterns are used very often; you probably just don't notice them. Most of the patterns described by Fowler [1] can be found in large web application frameworks.
For example, Ruby on Rails uses Active Record, Page Controller, Template View patterns. If you look at an ORM, for example Hibernate or Doctrine, they use even more patterns described by Fowler.
But of course they are mostly used in frameworks. If you are writing a CRUD app, just take a web framework and make everything as simple as possible.
It's much easier to express what's wrong with design pattern chatter:
Each design pattern represents a failure in the host language to provide facilities sufficient to abstract the pattern as a library component. when you find yourself relying on design patterns, it is a sign that it is long past time to switch to a more expressive language.
I get what he's trying to say. I think he expresses it very unclearly. I'll try to elucidate the main idea in an example:
If I give a programmer a task. His task is to create an api that can email someone a message with a subject and tell me whether the message is successfully sent. All I need is this:
Class Email(AddressObject, SubjectObject, MessageObject, ConfigObject, ClientObject).send_mail()
The definition of the Email object vs the email function is about 10x more complex with little benefit. The send_mail method is virtually IDENTICAL to the email function, the objects and patterns you are building are just extra cruft you are layering around the email method. Not to mention you literally have to define 5 extra objects as well. It's obvious to me that most of the code is 100% cruft yet why does everyone who is knee deep in design patterns gravitate towards that Email object? I look at Email object and I think: I get what you're doing, but WHY.. what is the point? Are people really unable to see why this abstraction is unneeded?
Some clown once told me dependency injection (The example object pattern above) helps with unit testing. I agree. Literally, the Email object has 5 extra objects that need to be unit tested so more unit tests!. More lines of code = more unit tests = a better program... In all seriousness, the core method is send_mail and that cannot be unit tested because it's IO. Everything you build around that method is extra BS that only needs to be tested because you made it exist.
Sometimes it baffles me how people just start using design patterns everywhere. I think what's going on is that people reach this cathartic state when they understand a new mind bending concept. Dependency Injection is a bit of mind bender, and this catharsis people feel when they reach a point of understanding blinds them from the downsides of patterns in general. Actually dependency injection is a stupidly simple concept, it's the word "dependency injection" that adds artificial complexity to the concept by making it sound cool and harder to understand.
Of all the patterns you could pick on, you pick on one of the most useful in modern OOP; however, I think you actually intend on picking on dependency inversion, not injection. Dependency injection is simply an automated way to satisfy constructor dependencies in OOP languages where your intent is to invert a dependency graph. I won't go into why dependency inversion (see: Dependency Inversion Principle, the "D" in SOLID) is so valuable because much has already been written on the topic.
As far as your email example, sometimes the language's basic type system abstractions (e.g., string, integer, etc.) do not encapsulate a real-world concept sufficiently. Encapsulation failures can frequently lead to bugs when edge cases are hit. I may, indeed, need to create an Address class to replace "string address" parameters if there is validation logic inherent to the existence of an email address in my code. If there isn't, then the base type of string would do just fine, of course.
>Of all the patterns you could pick on, you pick on one of the most useful in modern OOP; however, I think you actually intend on picking on dependency inversion, not injection. Dependency injection is simply an automated way to satisfy constructor dependencies in OOP languages where your intent is to invert a dependency graph. I won't go into why dependency inversion (see: Dependency Inversion Principle, the "D" in SOLID) is so valuable because much has already been written on the topic.
Both patterns are total failures. You are talking about the most misguided religiously followed pattern(s) in all of modern programming. AN equal amount has been written about how horrible both DI concepts are if you care to look. You think putting an acronym in your response and using big words like injection proves a point? Not SOLID at all. It proves nothing.
You have literally ignored my main point. It baffles me how blind some people are. The Email object requires 10x more code and 10x more abstractions and 10x more unit test coverage to finish then a singular function. You haven't even addressed this fact yet.
>As far as your email example, sometimes the language's basic type system abstractions (e.g., string, integer, etc.) do not encapsulate a real-world concept sufficiently. Encapsulation failures can frequently lead to bugs when edge cases are hit. I may, indeed, need to create an Address class to replace "string address" parameters if there is validation logic inherent to the existence of an email address in my code. If there isn't, then the base type of string would do just fine, of course.
Do you seriously think that encapsulating an email address in an object makes a difference? Here's some "validation logic" for you:
//raises error if invalid
validate_email_address(email: str)
I cannot make an example more clear. If you want to send a VALID email all you need is a function that validates an email and a function that sends it. You do not need to shove validation into an extra object then inject it into another one, then instantiate that object then call the send method... Doing this adds NOTHING beneficial to your code.
This one guy told me that by encapsulating the email address into an Address object I am effectively eliminating a runtime exception from the system because the type checker is, in a way, doing the validation. haha. I cannot comprehend how this guy cannot visualize the fact that all he is doing by using an address object rather then a validation function he is just creating a class then basically moving the validation function over to be a method of that class. The runtime error can still occur... just in a different location and obscured by useless abstractions and code surrounding it.
Many OOP patterns are pointless abstractions. Many smart people hate OOP all together, (including the founder of YC and this site). Maybe you should look into why.
> 10x more unit test coverage to finish then a singular function.
That is not true. Assuming that both have same functionality, e.g. both either validate address correctness or both don't, you need exactly same test cases. The only difference is that they will be structured differently - in one case you validate address as part of `AddressObject` test and in another as part of `func email` or `func validate_email_address`.
The tests with valid and invalid address however needs to exist in both cases.
Whether you go by functional or object oriented way, validation should be separated from main. It can be in another object or in another function, but it definitely does not belong to main function that does also sending. There is very little difference between two, the only one is that whether you validate before hitting send function of after.
> You do not need to shove validation into an extra object then inject it into another one. Doing this adds NOTHING beneficial to your code.
It is not big deal either. It does not make it harder to understand, both variants are dead simple. Both are immediately readable. Frankly, people who have low abstract thinking or were lazy to learn basics of OOP hate it. It is the same as with people who hate any other paradigm - functional programming and what have you. Functional programming is too hard to read unless you have put work into learning it.
There is such a thing as bad OOP, but when you pick dead simple easy to read example and claim it is something complicated, the the issue is not with OOP.
>That is not true. Assuming that both have same functionality, e.g. both either validate address correctness or both don't, you need exactly same test cases. The only difference is that they will be structured differently - in one case you validate address as part of `AddressObject` test and in another as part of `func email` or `func validate_email_address`.
Typically when you make a class. You create additional methods under that class. Those additional methods such as getters, setters, constructors, all need to be unit tested for full coverage. Like I said... Cruft code.
>Whether you go by functional or object oriented way, validation should be separated from main. It can be in another object or in another function, but it definitely does not belong to main function that does also sending. There is very little difference between two, the only one is that whether you validate before hitting send function of after.
We're in agreement here except I never brought up functional programming. Where in my post did I say it? I'm talking about procedural programming. Like C.
>It is not big deal either. It does not make it harder to understand, both variants are dead simple. Both are immediately readable. Frankly, people who have low abstract thinking or were lazy to learn basics of OOP hate it. It is the same as with people who hate any other paradigm - functional programming and what have you. Functional programming is too hard to read unless you have put work into learning it.
This is besides the point. I don't care about how hard or how simple or how lazy people are. I'm talking about pointless logic and unneeded abstractions. This is what the article is talking about. Here is a way to make it even more clear. Compare the two below:
Do you see how the send_mail method is IDENTICAL to the send_mail function? The class and the dependencies getting injected into the class are a pointless layer of abstractions when ALL you need to send an email is the send_mail method. This is the core of the argument. Don't get into semantics about people being lazy.
And also, again, I never mentioned functional programming. I'm not talking about it. I'm talking about straight up procedural programs of just regular functions that you see often in C code.
>There is such a thing as bad OOP, but when you pick dead simple easy to read example and claim it is something complicated, the the issue is not with OOP.
I picked something dead simple and easy so readers can understand what I'm talking about. You'd rather me hand you some OO source code so you can parse that insanity?
> Typically when you make a class. You create additional methods under that class. Those additional methods such as getters, setters, constructors, all need to be unit tested for full coverage. Like I said.
That is great argument for why full coverage is nonsense. Why would you test getters, setters and constructors if they do little?
> Compare the two below
I read and understand both just fine. Moreover, second one must have a place where equivalent of config and client objects go - but I dont see where it is. Or, you added do-nothing config and client objects go to OOP example to make it look worst. OOP example also have address twice, subject twice and message twice.
Here is more equivalent code:
* Class Email(AddressObject, SubjectObject, MessageObject).send_mail()
>That is great argument for why full coverage is nonsense. Why would you test getters, setters and constructors if they do little?
Errors and code changes can happen at anytime and anywhere and in places you never look. You have full coverage for full safety.
>I read and understand both just fine. Moreover, second one must have a place where equivalent of config and client objects go - but I dont see where it is. Or, you added do-nothing config and client objects go to OOP example to make it look worst. OOP example also have address twice, subject twice and message twice.
The function doesn't need a config object or a client object because those are patterns and abstractions that are unnecessary. That is the point of the article. Patterns that are blindly followed religiously and abstractions used in a way that is excessive.
>The trouble is, that readers dont see it.
It's not possible to speak about what other people see. You can only speak to your own intelligence which is the only logical possibility here.
>The point of functional was analogy.
The point was that you think functional and oop are just two different styles of programming that are both equal and good. This is incorrect. One is better than the other but at the end of the day I wasn't even comparing those two styles.
Which patter requires config object and client object for send mail functionality? I read a lot about them and there is none. So, unless you can name it, you are making stuff up.
It is not even about abstraction. I can write func send_mail(msg: str, subject: str, address: str, from: str, server: str, port: num, <following 20 config variables>) and have procedural code with same functionality.
Writing test for every getter and setter will not give you safety at all. This sort of test is completely pointless.
I don't want to start a war. I'm neither an expert programmer nor a smart person. But isn't it better to put data and validation all together? What if someone forgets to call the validation function? I mean in some complex projects, there may be tens of those validation functions that need to called, probably in a special sequence.
This makes it hard to "unit test" as hard core unit testing purists like to test the email function decoupled from other dependencies such as validation. The reality is, it's not that big of a deal to test both functions at once in a single unit test. You can't prove "unit_tests" is any better than regular "tests"
And if you really want to do DI you can always do this:
Though travelling in this direction we're diving deeper into another type of pattern that can be abused. Still, passing functions as parameters still leads to less cruft code then straight up passing a method that's part of an object.
In this example, the main point is to have the type system do work for you. Some languages have bad type systems and I agree in those languages it is often more work than it's worth to write type safe code. However, in general, I would much rather have an EmailAddress type and if I am passed an EmailAddress I can be confident it is actually an email address.
For example I could have the only way to construct an email address be (in Haskell)
makeEmailAdress : String -> Maybe EmailAddress
With the validation logic in the constructor. This is no more complicated than your solution.
As an added benefit my type signatures are now also doubling as documentation.
I know Haskell. We don't even need to address replacing exceptions with monads to show how and why OOP and patterns are in many many cases a bad solution.
The point has nothing to do with functional vs oop, or exceptions versus monads. It has to do with using the features of your language of choices type system to prevent bugs. The thing going on here is putting validation logic in your type constructors so it's impossible to construct an invalid type without triggering a runtime "exception" or error.
The runtime error can still occur... just in a different location and obscured by useless abstractions and code surrounding it.
One point of a type such as EmailAddress is that the validation is invoked at one place (inside the constructor) as opposed to an invocation at every usage site like send_email, which is a lot of work and error prone.
Another point is that when validation fails you get the runtime exception with a useful stack trace: instead of "address X was not valid when trying to send email" you actually get "you tried to convert string X to email address but it's not valid". In the first case all you can do is fail to send the message, but in the second case you can ask the user again, fix your escaping code etc.
See my response to the other guy above. You want to validate an email? make a validation function. You don't need to put that validation function under a class then inject it into an object then send the mail.
The email class does work fine. It's just full of extra cruft. Can you not see how the code is 10x more complex when you use DI patterns?
To accomplish the task all you need is an implementation of an email function. That's it. This email function is replicated IDENTICALLY as a single method under the Email class, which indicates that all the classes, objects and dependency injecting that is surrounding the method are pointless abstractions over what already works.
This trivial example does not illustrate the usefulness of OO design. Extending the example imagine the requirement is that the target of an email may be either a single user or a group of users, and in addition, email filters can be configured for both individual users and groups of users
Leading to an OO class design roughly like this:
(there are other possible OO designs, but this illustrates the point)
// Send an email to a specific user or a group of user taking into account both user and group level filters.
// returns group filters
List<Filters> getFilters();
List<User> getMembers();
// returns all recipients after applying group and
// individual recipient filters
List<User> getRecipients(Email);
}
class User implements EmailDestination {
// returns user specific filters.
List<Filters> getFilters();
// returns itself unless filtered
Iterator<User> getRecipients(Email);
Your example has a globally defined function sendEmail. Big OOP languages such as java or C# do not permit that style. You must define all functions as a method of an object if you want to follow the OOP style. You are illustrating a hybrid style of procedural and OO.
Less lines, less cruft, more modular and No objects.
A bit off topic but note that... anExampleFilterFunction can be implemented to filter off of a User and a group at the same time or it can filter off of the email address itself. I could also compose filterByAFunction with itself in an innumerable amount of ways to produce any amount of custom filters:
//a, b, c are different filter functions.
filteredUsers: User = filterByAFunction(filterByAFunction(filterByAFunction(x, a), b), c)
//or
filterdUsers: User = filterByAFunction(x, func(y){ return a(y) && b(y) && c(y)})
The tricks(s) above cannot be done if I had everything encapsulated in objects. Objects are not compose-able. You cannot combine User or Group to create a UserAndGroup filter without explicitly defining a whole new class. Also in the end all a program really is doing is calling functions. Classes are just layers of cruft that have no meaning during execution.
But the requirements were to allow filters to be configured on users and groups, which would require more info in your data structures. This doesn't meet the specified requirements.
Plus even with it not meeting the requirements, it
is confusingly presented, and is filled with terminology related to the implementation rather than the domain. Where's a group definition? How would you configure a filter for a group? Or a user for that matter. (Answer: you would require a much more complex data model, and functions to access and modify it).
I'll stick to OO for the top level paradigm I use until something better comes along.
OO being dead is fake news perpetuated by ignorant know-it-alls.
Dude. Your group definition Is based on how you define your filter function. That is essentially a group. Users are filtered off the group string in which they belong. Read the code before you respond like a child.
There is zero terminology related to implementation. NOT A single function was defined. All it is are function signatures analogous to your classes.
Read the pseudo code and understand it before you criticize there is enough information in the user tuple type to filter off of users and emails.
You think you are seeing over complexity but my code is actually more simpler and straightforward then your code. NOT to be insulting but you can't see it not because the code is complex but because you lack knowledge.
I'll tell you this. I completely understand your code and I completely understand my code. My judgement is less biased. Respond to me when your level of understanding reached mine.
Nothing gets my blood boiling more then deliberatr ignorance. Read and understand my code before you bitch.
If you have only done greenfield development and source-based extension (instead of having to either provide 3rd party extension points or use them), your opinion on modularity is invalid.
Patterns exist whether or not you are aware of them or give them a name. Most nontrivial programs will end up recreating patterns whether the programmer is conscious of them or not.
110 comments
[ 5.3 ms ] story [ 127 ms ] threadPerhaps I’m being unfair to Design Patterns, but those articles [about design patterns] seem like the worst kind of tech writing, the most likely to inspire useless discussion that doesn’t help move a team forward. Catering to software developers perfectionism tends to be bad for business. And this particular type of article seems to be the most extreme in that regard.
To recap, DP articles:
- are the worst kind of writing (still no explanation)
- likely to inspire useless discussion (sometimes play is a lie?)
- catering to perfectionism (this can be said about any tool)
The article reminds me of many folks I've met who despise analysis and planning. They're constantly getting themselves into trouble that requires quick action. When it works, the chalk the success up to their quick action. They denigrate project planning tools of nearly every stripe. They're also typically very bad communicators and don't manage time well.
But in the end, the need for quick action resulted from the immense dis-ease the person feels around planning, brainstorming, and up-front analysis. The cycle repeats on every project.
There must be an anti-pattern that describes this kind of behavior...
Well, the anti-pattern that describes this kind of logic is the Straw Man. If all Design Pattern articles were theoretical blah blah blah, The Fine Article might have a point. They aren't. Not even the majority. As long as we just point to the (bad) minority, and act as if they're representative of the whole, then we can continue to hate all articles about design patterns, and play — because this is just as much a form of "play" as the "learning" or "avoiding" techniques being decried — in our preferred, and specifically unstructured way.
I think the author might be taking it a bit too far, but they are certainly describing a real thing that I see all the time. Which is bad engineering choices made without thinking and then justified with a string of design pattern terms, used incorrectly.
I’ve been part of lots of teams and teams composed of great and poor levels of talent and I’ve never seen this type of behavior which you describe. Usually the folks getting up in arms over architecture are pissed because folks with experience shot down their idea for reasons of pragmatism and the individual couldn’t handle it because of ego.
One of the most interesting things I learned was how much variance there was in code size between submissions. I would first read an assessment from a good student which was completely correct and 500 lines long. The code would seem reasonably clear, compact and well written. Then I'd pick up the next assignment, which had also gotten 100% on the automatic tests and it would be only 150 lines of code. And the code wouldn't seem any more compact or unreadable compared to the first - they both looked like decent solutions; but one of those solutions had implemented the same spec in 3x less code!
And then I'd pick up a submission by a weaker student who got 50% of the automarking results done. The submission would be 600 lines, and you could see the sweat that had gone into writing it. The student was clearly struggling to keep track of all the moving parts in their code. If that was all I looked at I would have assumed the assignment would have taken 1000 lines to implement, and be way too hard for my first year students.
(In case you're wondering, there was no difference in programming languages, tooling or knowledge. All students were using the same environments to write plain C code.)
The lesson I learned was that its very hard to tell whether you or your coworkers are actually using a good approach to solving your problems. I never hear people say things like "Oh, I think this different abstraction could save us 70% of our code". But having seen enough examples I think its usually the case that those abstractions exist. We just (bizarrely) don't seem to spend any time looking for them. Intuitively I would expect that bad abstractions would be obvious in code, but that seems to be often not true in practice. After reading the first students' submission I usually would still have no idea how long the average correct submission was going to be.
I heard colleagues say "if we split this away, join these two classes into one and hide it under interface, it will be simpler/shorter". The word abstraction was not used, but it was abstraction they were talking about.
So maybe it depends on local culture, whether people talk about how things are done overall or whether they focus on idioms only.
Edit: fixed typo in clearn/clear
Factories: generate objects whose type can freely change. See polymorphism.
Visitors: create any operation you'd like to implement without having to change a type. See open/close principle.
Repositories: separate SQL voodoo from your code and establish a dedicated component to handle database transactions.
Façade: isolate the world from all the gory detail in a manner that you are free to change both the component and its clients. See separation of concerns.
I find it more worrying that the code developed by a senior developer was dumped just because the junior developer who replaced him was entirely clueless and not obly unable to understand existing codebut also unable to even grasp basic design problems that justify the senior developer's decision.
Recommended reading: https://en.m.wikipedia.org/wiki/Wikipedia:Chesterton%27s_fen...
What you fail to grasp was the code was so completely unnecessarily complicated that it introduced a fuckton of subtle bugs that were difficult to track down. Things would look like they're working, and then reports of terribly broken behavior forcing an emergency rollback followed by days of trying to figure out what bullshit state things were in to repro the problem.
Worse, is so much of the code was some misguided attempt to future proof the code in ways that make no sense. When your code will never have more than a couple dozen simple sql queries you will never benefit from a polymorphic sql class intended to allow the swapping out of your sql backend with another vendor. It's easier just to rewrite that small piece of code when the time comes.
Just because you can implement fizzbuzz using a dozen design patterns doesn't mean the problem actually requires it.
As such, it's hard for me to bring myself to respond to any one point in the piece. It'd be quite easy for most of us to do so, but I suspect that was the intention.
The author doesn’t hate design patterns. They hate articles about them.
This left me feeling like nobody really knew, and that everyone was just chucking dirt and hoping it stuck.
I've been in this industry for over 35 years. On dark days I'd give my left leg for working templates and strickt design patterns. On good days I love the close to absolute freedom we have.
Meanwhile,
Sounds like a cowboy coder.
That's speaks in their favor, in my opinion. We actually consciously moved the way MongoDB -> PostgreSQL JSONB column, and are pretty happy with that. Sorry, original author, but if you recommended us to use shiny new databases like MongoDB or Riak, we'd probably think of you as a young and inexperienced engineer.
Although I agree about Design Patterns. For example, you'd use/no use ReactiveX regardless of whether you know about Observer pattern or not.
That's not a thing, at least not in this context or that phrasing.
As you said yourself, you migrated to PG JSONB columns, not to PG Text columns for storing JSON. So it seems to me that you agree with the author.
Here's a brief explanation: Often when you are coding, you find an interesting solution to a problem. Then when you look at someone else's code, in a different domain, you realise that they are using a very similar solution. You ask them where they got the idea and they say that they saw it in some code in yet a different domain. So you think, "Hey, this seems to be a useful solution to a problem that occurs in several domains". You tuck it away in the back of your mind and when you see that problem again you say, "I've got a solution to that problem!" and you use it.
That's a design pattern. Everybody uses them. The alternative is to bloody mindedly refuse to learn and to use completely novel approaches to the same problems every time you see them. That fact that people write down these solutions and give names to them doesn't suddenly make the solutions ridiculous. The fact that some programmers mindlessly apply "patterns" in the hopes that they can escape thinking doesn't make the solutions bad. That's like saying we should never make libraries because some bad programmers think they can glue 1500 libraries together to make a website without having to know how to program.
What the author is complaining about has nothing to do with design patterns. People sitting around building mythical designs that never get built has been going on since before the time of Fred Brooks. There are whole classes of architects, analysts, and consultants who have mastered the art of getting paid big money to talk impressively about software without ever writing a single line of code. Quite a lot of them write popular blogs! That's nothing to do with design patterns.
I am half curious to read the author's rants about OO, but I suspect I'll find more of the same -- some programmers wrote some terrible, terrible code and called it OO. It must stop! Sigh...
PaulGraham said "Peter Norvig found that 16 of the 23 patterns in Design Patterns were 'invisible or simpler' in Lisp."
With this mindset, functions (or subroutines) are language patterns in assembler.
In the other extreme, if you use a sufficient advanced language, all your language patterns are actually checked by the compiler and/or interpreter, instead of the programmer. They stop being patterns and are just language features.
http://wiki.c2.com/?AreDesignPatternsMissingLanguageFeatures
That said I'm also not convinced that it would help in many cases. What would you enforce about a wrapper for example other than it's constructed from one argument and returns a different type containing a reference to the original one? If you're not able to do that yourself then would a wrapper library really be any use?
Someone else building something completely different with the same language doesn't need those things, so cooking my needs into the language itself is just going to add overhead or force unwanted design choices on to them, but there's probably a different framework for whatever they're doing too.
I've heard people mention that a lot, but the only concrete example the ever give is "you don't need the strategy pattern because of first class functions", which is doesn't hold up when you examine it closely.
It is a way to encode basic features in terms of simple standard building blocks: objects. However, I think that explaining everything in terms of objects is a terrible idea. Objects are extremely complex already, it is a bad idea to use them as a basic entity. Just compare lambda semantic rules (there are only three of them for untyped lambda calculus), and an object's semantic. Later is much more complex.
I think that the complexity of objects led to the complexity of patterns, making them verbose and ugly. Just compare visitor and pattern matching, or command and higher order function.
It is true that in more advanced languages having more features patterns become invisible, but I thing they also would become invisible if they were composed of more simple entities.
I agree with you that many OO languages add unnecessary syntax and lack features, but objects are not related. We need to encapsulate state and we like to group functions into cohesive modules that are related to the state that they are going to deal with. We do this whether we use object nomenclature or not.
I find this argument to be rather disingenuous as it ignores the fundamental purpose of design patterns: communicate intent through a common language. I mean, 9 out of 10 patterns are nothing more than specific uses of inheritance and polymorphism. Technically, they don't require anything more than what inheritance and polymorphism already offet, and the only difference is precisely why and how inheritance and polymorphism are used in a specific problem. I mean, what's the fundamental difference between a state pattern and a strategy patern? Hell, isn't the strategy pattern a rather direct use of polymorphism? Is the language even missing anything at all? Of course not, because the purpose of using design patterns is to communicate intent and describe precisely what was the problem and more importantly how it was solved.
> Is the language even missing anything at all?
This should be easily measured by the heaps and heaps of similar looking code that have no direct bearing with the actual problem.
Because they add or take nothing from the language. Arguing for the needless addition of these keywords makes as much sense as arguing in favour of camelCase or polish notation or "m_" and "s_" prefixes. They add nothing to the language, they are redundant and they are used to convey meaning alone.
Therefore, what problem would be solved by adding useless keywords to a language, particularly when those making these claims come from a point of ignorance and lack of experience?
> This should be easily measured by the heaps and heaps of similar looking code that have no direct bearing with the actual problem.
This makes no sense at all. Rather straigh-forward applications of polymorphism don't vanish if you call them by different names.
Or you could be content with repeating the same kind of code over and over, but I'm not going to accuse you of such obvious stupidity.
The thing is they don't. For example, a function pointer does not fill the need that strategy patterns fill. You look at a function pointer and you have no idea where and how and why it is supposed to be used, there is no type constraint, and there isn't even any way to track where how many different instances there are. A function pointer alone says nothing. Yet, specifying a common interface does, as well as implementing any concrete strategy. That's the whole point of using patterns.
Furthermore, design patterns help clarify technical discussions as they communicate clear concepts as well as solutions to particular problems they solve. You get a clear idea of how a data access module is designed if the designer states that he used active records or repositories, and if classes are named appropriately you can already form a clear mental model of what to expect and, more importantly, what you will can or may need to do to fix a bug or implement a feature.
Let's step back a little:
> 9 out of 10 patterns are nothing more than specific uses of inheritance and polymorphism.
Of course, inheritance and polymorphism are the only thing Java has! Or had, back in 1995. Hammer & nail. There are other ways of expressing those patterns, other ways of solving the same problems that do not require those patterns at all. You just need to wake the fuck up and realise OOP is not the only game in town.
> design patterns help clarify technical discussions as they communicate clear concepts as well as solutions to particular problems they solve.
I have yet to witness a single instance of that in my job, and I've seen loads. On the contrary, naming patterns often clouds the underlying concepts. A pet peeve of mine is the Singleton. It's overused for sure, but that also makes it one of the most widely understood patterns. Yet few seem to realise that singletons are nothing more than global variables with a couple bells and whistles. And since the thing is not called "global variable", it doesn't trigger the appropriate mental warnings.
What good are pattern names when programmers can't think clearly to begin with?
It's not just the conversations. Every time I saw a pattern name in real code, I could find a simpler way. I also saw misapplied patterns, like factories that actually were memory pools. Whoever did that didn't know what they were talking about, and patterns didn't save them.
Patterns of code exist. It's a mistake to look at the GoF pattern catalogue and think that it represents patterns that you should use in every situation (or even at all!). I remember hearing someone say one time that all programming should be made up of GoF patterns (touring complete??? ;-) ). Yes, it's absurd. Going all the way back to the original poor article, that's really the problem. People latch on to these ideas; they hear something and it sounds attractive; whatever. Poor programmers are poor programmers. I personally don't think they get worse having learned about patterns. It can help, but it's not the only thing you need by a long shot.
In FP there are lots of patterns. A Functor could be a pattern if anyone bothered to write it up in that fashion. It would make a very nice pattern. Applicative is a great example of a generated pattern -- it's a thing you would never actually design up front, but just pops out. Maybe/Option, Either, etc monads.... awesome patterns (again, if anyone decided to write them up in that fashion).
Let's go back to GoF patterns, maybe the Singleton. The Singleton is not a global variable with a couple of bells and whistles. That's a very popular, but misconstrued view. It's actually the bells and whistles that are the point of the Singleton. A Singleton is a method to ensure that something occurs at most one time in your system (potentially scoped). You use it when you have a piece of hardware that will crash if you initialise it twice. Or when you have to initialise a communications protocol exactly once. Or when you have a very expensive operation and you want to ensure that it only gets called once in a particular scope. Usually people use a global scope for their singletons -- because that's what they actually want. But it's not uncommon to scope singletons at a module level, or other kinds of scope. How do you usually implement a singleton? By providing a function in the scope you want that memoises a piece of data. Yeah, it's not rocket science. It's not supposed to be. It's just a technique. And if you don't write essentially the same code in non-object based langues to achieve the same result, I'll eat my hat (or at least I'll ask you how you would do it differently and then suggest that it might make a good pattern).
One of the weaknesses of the GoF book was that it wrote all the examples in similar languages. At the time, those were the languages that were relevant. Those were the languages that the authors were proficient in. It might surprise many people to know that the GoF was not the first publication on software patterns (not by a long shot). In fact the GoF was only interesting because they attempted to make a pattern catalogue -- a set of patterns that would be relevant to a certain kind of programming. That this is not the kind of programming you enjoy is completely irrelevant. There are other patterns. You can make other catalogues. That is the whole point.
And, you are correct. Patterns don't make a good programmer. But good programmers know programming techniques that are relevant to the style of programming that they use. How do they learn those techniques? How do they communicate where they are appropriate and where they are not? Back in my day we just said, "Here's a keyboard. Don't suck!" And then we'd look at their code and say, "You suck. Why can't you write code like mine?" I'm not really so happy to go back to those days.
To ensure initialisation occurs only once, a static scope and a constructor are enough. And to ensure nobody else can spin up another instance, the constructor can be private, though I've never needed that. Anyway, for extra "clean" points, I tend to just expose the interface of the singleton as a set of global functions, doing whatever's necessary for the user to not having to think about initialisation or `get_instance()` at all.
If C++ could guaranteed the order of initialisation satisfies dependencies, we could shave a few lines off that pattern. (Whether this is a good idea is a separate issue.) My point was, each pattern has a corresponding language feature (or set thereof) that could make it less verbose.
It's about achieving inversion of control, and using a constructor to inject dependencies is just one example of how to do that. There's also setter based DI, and interface based injection, but again, those are just implementation details.
Not quite, it's passing dependencies (structural ones, like a logger or a database connection) to the constructor. If you're passing data in via a dependency injector then something has gone terribly wrong. Before dependency injection was common creating these dependencies was typically handled by either a factory or created inline, the biggest issues this create was difficulty to test and difficulty to scope. DI handles these well, at the cost of reflection magic and occasionally harder traceability.
So yes, I've drunk the cool aid on DI. Lately however I've been getting back into c which obviously lacks a lot of features conducive to unit testing and DI which, but so far I've been able to get similar results from a few macros. So while I still like/use DI when I'm working with c#, I think we've gone wrong at some level, we should be able to write tests without adding so much indirection everywhere.
I could go on for quite a while about specifics of design patterns (and particularly "generative design patterns" and "pattern languages"), but I think I would be accused of writing an article about design patterns ;-). I think the biggest problem I've seen with articles on design patterns is that there are a lot of articles that are written by people who don't understand them, and haven't read the original literature about them. This isn't confined to design patterns, though. I could say the same thing about pretty much everything in our industry. If you find an article saying x-famous-thing-is-crap, you can almost be positive that if you go back to the origins of x-famous-thing that it's not actually crap at all -- it's just been badly misunderstood by successive generations of anti-evangelists.
One thing we did very early on was going after clients where there are no well-established design patterns.
Design patterns have their place, though I can see merit to arguments that assert they have been emphasized out of proportion to their utility over the years. But this is a strange critique, given the content of this particular article.
The fact that guys he was talking about used PostGres to store JSON as a text field literally means that they chose a simplicity over introducing another dependency from no-sql db with all the cost of supporting it. Unless one needs to search over information stored in JSON it is perfectly and probably disarable to store it as a text field.
Author remark “they spent years for what they can spent weeks just by using a framework” is a proof that he has little or no experience of doing such things himself. No framework can save you so much time. I was designing UI using frameworks and using just basic language tools. Difference is in trade off obetween quality of standard framework components over flexibility of desinging your own. Time is not so much different and might even take longer with frameworks as adoption time for frameworks could be long especially for teams with a high turnover.
I decided to not read till the end because author did not present himself as a person whose opinion might be useful.
Also I disagree about DB and normalization: in most cases the best choice will be SQL DBMS and normalized data. Please let me explain why:
- normalization saves you from duplicating data; MongoDB will make you do the opposite. Updating the data will be more difficult and you will spend more time writing the code.
- fixed schema will make it easier to understand DB structure for new developers and helps you to detect errors; with MongoDB all invalid data will go straight into the storage and you will spend additional time fixing them later.
- foreign keys and transactions save you from saving inconsistent data into the database; MongoDB won't help you with this.
Designing a database properly doesn't require much thinking, it is a natural thing and you are not losing time.
But I understand what the author is talking about. Recently I was using two of Google's products, Doubleclick for Publishers and Google Analitics. Their UI is super slow, at least on my hardware. One Chromium tab with DFP page consumes over 700 Mb of memory. When I decided to inspect it, I found that on DFP page they used Dart "apps" compiled to JS; each of them was not less than 2.4 Mb. Even the "changelog" app which was just displaying information about recent changes (yes, they did it as a separate app).
In Analytics UI, they decided to use Material Design with large number of animations, it is very slow and UI elements are too big for 1280px wide display. It looks more like your legacy enterprise app written 10 years ago in Java and IIS, rather than a Google product.
They could just use something simple, like Symfony Framework and jQuery and avoid most of those problems. I think the reason why they didn't is because Google employs smart people and gives them a boring job; to entertain themselves they tried to play with something new, like Dart or make Android-like UI but didn't have enough time to do it properly.
I would like to see this, given that IIS never had an official Java ISAPI module.
Using Symphony as a full MVC?
Before you write a design pattern, abstraction, or interface, ask yourself: Is this the simplest possible thing that can work?
And then: Will I ever touch this code again?
And then: How hard would this be to rewrite if the requirements change?
Design patterns were meant to future proof code, but usually they just introduce complexity and useless abstraction, or serve to paper over limitations of your language. You probably can't predict the future as well as you think you can.
Your program is a document. You can change it. You can make it do anything.
That's a misguided but unfortunately common view. Best practice is to refactor to patterns, don't try to divining them from the start.
> paper over limitations of your language.
See http://wiki.c2.com/?DesignPatternsAreMissingLanguageFeatures
The patterns are loosely what to do, but that's not the thing that lasts across the years and languages. Refactoring is why and how.
I would add that IIRC Martin Fowler himself, a manwho writes whole books dedicated to desing patterns and software design, is also the man behind the "monolithic design first" mantra.
Not exactly, most of them are intended to encapsulate change. If you don't have a reasonable idea how your system is likely to change then they're mostly useless.
> You probably can't predict the future as well as you think you can
It's not at all unusual for people to be good at predicting where change will occur, even if they don't know exactly what the change is. In my field, I have a very good understanding as to where the industry is heading and I design accordingly.
> Your program is a document. You can change it. You can make it do anything.
Sure, if you're talking about a project with a couple of man years work in it. That's not really the space that design patterns were intended for.
For example, Ruby on Rails uses Active Record, Page Controller, Template View patterns. If you look at an ORM, for example Hibernate or Doctrine, they use even more patterns described by Fowler.
But of course they are mostly used in frameworks. If you are writing a CRUD app, just take a web framework and make everything as simple as possible.
[1] https://martinfowler.com/eaaCatalog/
Each design pattern represents a failure in the host language to provide facilities sufficient to abstract the pattern as a library component. when you find yourself relying on design patterns, it is a sign that it is long past time to switch to a more expressive language.
If I give a programmer a task. His task is to create an api that can email someone a message with a subject and tell me whether the message is successfully sent. All I need is this:
I do not need this: The definition of the Email object vs the email function is about 10x more complex with little benefit. The send_mail method is virtually IDENTICAL to the email function, the objects and patterns you are building are just extra cruft you are layering around the email method. Not to mention you literally have to define 5 extra objects as well. It's obvious to me that most of the code is 100% cruft yet why does everyone who is knee deep in design patterns gravitate towards that Email object? I look at Email object and I think: I get what you're doing, but WHY.. what is the point? Are people really unable to see why this abstraction is unneeded?Some clown once told me dependency injection (The example object pattern above) helps with unit testing. I agree. Literally, the Email object has 5 extra objects that need to be unit tested so more unit tests!. More lines of code = more unit tests = a better program... In all seriousness, the core method is send_mail and that cannot be unit tested because it's IO. Everything you build around that method is extra BS that only needs to be tested because you made it exist.
Sometimes it baffles me how people just start using design patterns everywhere. I think what's going on is that people reach this cathartic state when they understand a new mind bending concept. Dependency Injection is a bit of mind bender, and this catharsis people feel when they reach a point of understanding blinds them from the downsides of patterns in general. Actually dependency injection is a stupidly simple concept, it's the word "dependency injection" that adds artificial complexity to the concept by making it sound cool and harder to understand.
As far as your email example, sometimes the language's basic type system abstractions (e.g., string, integer, etc.) do not encapsulate a real-world concept sufficiently. Encapsulation failures can frequently lead to bugs when edge cases are hit. I may, indeed, need to create an Address class to replace "string address" parameters if there is validation logic inherent to the existence of an email address in my code. If there isn't, then the base type of string would do just fine, of course.
Both patterns are total failures. You are talking about the most misguided religiously followed pattern(s) in all of modern programming. AN equal amount has been written about how horrible both DI concepts are if you care to look. You think putting an acronym in your response and using big words like injection proves a point? Not SOLID at all. It proves nothing.
You have literally ignored my main point. It baffles me how blind some people are. The Email object requires 10x more code and 10x more abstractions and 10x more unit test coverage to finish then a singular function. You haven't even addressed this fact yet.
>As far as your email example, sometimes the language's basic type system abstractions (e.g., string, integer, etc.) do not encapsulate a real-world concept sufficiently. Encapsulation failures can frequently lead to bugs when edge cases are hit. I may, indeed, need to create an Address class to replace "string address" parameters if there is validation logic inherent to the existence of an email address in my code. If there isn't, then the base type of string would do just fine, of course.
Do you seriously think that encapsulating an email address in an object makes a difference? Here's some "validation logic" for you:
I cannot make an example more clear. If you want to send a VALID email all you need is a function that validates an email and a function that sends it. You do not need to shove validation into an extra object then inject it into another one, then instantiate that object then call the send method... Doing this adds NOTHING beneficial to your code.This one guy told me that by encapsulating the email address into an Address object I am effectively eliminating a runtime exception from the system because the type checker is, in a way, doing the validation. haha. I cannot comprehend how this guy cannot visualize the fact that all he is doing by using an address object rather then a validation function he is just creating a class then basically moving the validation function over to be a method of that class. The runtime error can still occur... just in a different location and obscured by useless abstractions and code surrounding it.
Many OOP patterns are pointless abstractions. Many smart people hate OOP all together, (including the founder of YC and this site). Maybe you should look into why.
You are basically the topic of this article.
That is not true. Assuming that both have same functionality, e.g. both either validate address correctness or both don't, you need exactly same test cases. The only difference is that they will be structured differently - in one case you validate address as part of `AddressObject` test and in another as part of `func email` or `func validate_email_address`.
The tests with valid and invalid address however needs to exist in both cases.
Whether you go by functional or object oriented way, validation should be separated from main. It can be in another object or in another function, but it definitely does not belong to main function that does also sending. There is very little difference between two, the only one is that whether you validate before hitting send function of after.
> You do not need to shove validation into an extra object then inject it into another one. Doing this adds NOTHING beneficial to your code.
It is not big deal either. It does not make it harder to understand, both variants are dead simple. Both are immediately readable. Frankly, people who have low abstract thinking or were lazy to learn basics of OOP hate it. It is the same as with people who hate any other paradigm - functional programming and what have you. Functional programming is too hard to read unless you have put work into learning it.
There is such a thing as bad OOP, but when you pick dead simple easy to read example and claim it is something complicated, the the issue is not with OOP.
Typically when you make a class. You create additional methods under that class. Those additional methods such as getters, setters, constructors, all need to be unit tested for full coverage. Like I said... Cruft code.
>Whether you go by functional or object oriented way, validation should be separated from main. It can be in another object or in another function, but it definitely does not belong to main function that does also sending. There is very little difference between two, the only one is that whether you validate before hitting send function of after.
We're in agreement here except I never brought up functional programming. Where in my post did I say it? I'm talking about procedural programming. Like C.
>It is not big deal either. It does not make it harder to understand, both variants are dead simple. Both are immediately readable. Frankly, people who have low abstract thinking or were lazy to learn basics of OOP hate it. It is the same as with people who hate any other paradigm - functional programming and what have you. Functional programming is too hard to read unless you have put work into learning it.
This is besides the point. I don't care about how hard or how simple or how lazy people are. I'm talking about pointless logic and unneeded abstractions. This is what the article is talking about. Here is a way to make it even more clear. Compare the two below:
Do you see how the send_mail method is IDENTICAL to the send_mail function? The class and the dependencies getting injected into the class are a pointless layer of abstractions when ALL you need to send an email is the send_mail method. This is the core of the argument. Don't get into semantics about people being lazy.And also, again, I never mentioned functional programming. I'm not talking about it. I'm talking about straight up procedural programs of just regular functions that you see often in C code.
>There is such a thing as bad OOP, but when you pick dead simple easy to read example and claim it is something complicated, the the issue is not with OOP.
I picked something dead simple and easy so readers can understand what I'm talking about. You'd rather me hand you some OO source code so you can parse that insanity?
That is great argument for why full coverage is nonsense. Why would you test getters, setters and constructors if they do little?
> Compare the two below
I read and understand both just fine. Moreover, second one must have a place where equivalent of config and client objects go - but I dont see where it is. Or, you added do-nothing config and client objects go to OOP example to make it look worst. OOP example also have address twice, subject twice and message twice.
Here is more equivalent code:
* Class Email(AddressObject, SubjectObject, MessageObject).send_mail()
* func send_mail(msg: str, subject: str, address: str)
> I picked something dead simple and easy so readers can understand what I'm talking about.
The trouble is, that readers dont see it.
The point of functional was analogy.
Errors and code changes can happen at anytime and anywhere and in places you never look. You have full coverage for full safety.
>I read and understand both just fine. Moreover, second one must have a place where equivalent of config and client objects go - but I dont see where it is. Or, you added do-nothing config and client objects go to OOP example to make it look worst. OOP example also have address twice, subject twice and message twice.
The function doesn't need a config object or a client object because those are patterns and abstractions that are unnecessary. That is the point of the article. Patterns that are blindly followed religiously and abstractions used in a way that is excessive.
>The trouble is, that readers dont see it.
It's not possible to speak about what other people see. You can only speak to your own intelligence which is the only logical possibility here.
>The point of functional was analogy.
The point was that you think functional and oop are just two different styles of programming that are both equal and good. This is incorrect. One is better than the other but at the end of the day I wasn't even comparing those two styles.
It is not even about abstraction. I can write func send_mail(msg: str, subject: str, address: str, from: str, server: str, port: num, <following 20 config variables>) and have procedural code with same functionality.
Writing test for every getter and setter will not give you safety at all. This sort of test is completely pointless.
And if you really want to do DI you can always do this:
Though travelling in this direction we're diving deeper into another type of pattern that can be abused. Still, passing functions as parameters still leads to less cruft code then straight up passing a method that's part of an object.For example I could have the only way to construct an email address be (in Haskell)
With the validation logic in the constructor. This is no more complicated than your solution.As an added benefit my type signatures are now also doubling as documentation.
You are correct in that building types leads to a more safe program but I was more addressing the flaw with objects rather then types.
One point of a type such as EmailAddress is that the validation is invoked at one place (inside the constructor) as opposed to an invocation at every usage site like send_email, which is a lot of work and error prone.
Another point is that when validation fails you get the runtime exception with a useful stack trace: instead of "address X was not valid when trying to send email" you actually get "you tried to convert string X to email address but it's not valid". In the first case all you can do is fail to send the message, but in the second case you can ask the user again, fix your escaping code etc.
The email class does work fine. It's just full of extra cruft. Can you not see how the code is 10x more complex when you use DI patterns?
To accomplish the task all you need is an implementation of an email function. That's it. This email function is replicated IDENTICALLY as a single method under the Email class, which indicates that all the classes, objects and dependency injecting that is surrounding the method are pointless abstractions over what already works.
Leading to an OO class design roughly like this: (there are other possible OO designs, but this illustrates the point)
// Send an email to a specific user or a group of user taking into account both user and group level filters.
sendEmail(Email, EmailDestination)
class Email {Title, Body, Attachments}
interface EmailDestination {
}class Group implements EmailDestination {
}class User implements EmailDestination {
}how would this look with a procedural design?
A bit off topic but note that... anExampleFilterFunction can be implemented to filter off of a User and a group at the same time or it can filter off of the email address itself. I could also compose filterByAFunction with itself in an innumerable amount of ways to produce any amount of custom filters:
The tricks(s) above cannot be done if I had everything encapsulated in objects. Objects are not compose-able. You cannot combine User or Group to create a UserAndGroup filter without explicitly defining a whole new class. Also in the end all a program really is doing is calling functions. Classes are just layers of cruft that have no meaning during execution.Plus even with it not meeting the requirements, it is confusingly presented, and is filled with terminology related to the implementation rather than the domain. Where's a group definition? How would you configure a filter for a group? Or a user for that matter. (Answer: you would require a much more complex data model, and functions to access and modify it).
I'll stick to OO for the top level paradigm I use until something better comes along.
OO being dead is fake news perpetuated by ignorant know-it-alls.
There is zero terminology related to implementation. NOT A single function was defined. All it is are function signatures analogous to your classes.
Read the pseudo code and understand it before you criticize there is enough information in the user tuple type to filter off of users and emails.
You think you are seeing over complexity but my code is actually more simpler and straightforward then your code. NOT to be insulting but you can't see it not because the code is complex but because you lack knowledge.
I'll tell you this. I completely understand your code and I completely understand my code. My judgement is less biased. Respond to me when your level of understanding reached mine.
Nothing gets my blood boiling more then deliberatr ignorance. Read and understand my code before you bitch.