Why I hate all articles about design patterns (2016)
smashcompany.com
smashcompany.com
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...
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.
// 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.
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.
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.
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:
func email(address: str, subject: str, message: str) -> bool
I do not need 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.
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:
//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.
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:
Class Email(AddressObject, SubjectObject, MessageObject, ConfigObject, ClientObject).send_mail(msg: str, subject: str, address: str)
func send_mail(msg: str, subject: str, address: str)
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.
validate_email(email: str, subject: str, msg) -> bool
return validate_msg(msg) && validate_subject(subject: str)
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:
email(address: str, subject: str, message: str, address_validator_function: func)
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)
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.
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 {
Iterator<User> getRecipients(Email);
List<Filter> getFilters();
}class Group implements EmailDestination {
// 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);
}how would this look with a procedural design?
User : NamedTuple[user: str, email: str, group: str]
func anExampleFilterFunction(any_Tuple_including_User: NamedTuple[str,...]) -> bool
func filterByAFunction(iterable: List[NamedTuple[str,...]], filter_func: func) -> List[NamedTuple[str,...]]
func getValuesFromTupleByName(name: str, List[NamedTuple[str,..]]) -> List[str]
func sendEmails(email_list: List[str], msg: str, subject: str)
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.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.
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.
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.
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?
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.
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.