1. 10
  1.  

  2. 10

    I feel that the author may be attacking a straw man here

    Patterns should emerge, they shouldn’t be create. If we see a pattern emerging a lot in your domain, add it to the domain specific libraries and lingo. If we see a pattern emerging everywhere, add it to our languages and standard libraries.

    Isn’t this … err … exactly the point of design patterns? Common names for patterns in programming that emerge as a result of refactoring (“Integrating”, in the author’s words) your code.

    Also I would suggest that SOLID is not a design pattern however you look at it. There are many criticisms you can reasonably make of SOLID, but “I think there should be fewer letters in it” doesn’t really feel like actionable advice

    1. 0

      I think there should be fewer letters in it

      Was that one of the criticisms being raised ? As in, I don’t remember anything about SOLID having too many principles under it.

      I do remember saying SOLID should be 3 different sets of patterns (or principles, whatever term you want to use), but that’s not really related to what you’re quoting.

    2. 7

      First of all, SOLID isn’t a design pattern, it’s essentially coding guidelines but for software design instead of source code. I personally never got the impression it was meant to be strictly adhered to 100% of the time.

      Second, how can anybody disagree with the Liskov substitution principle?!? Inheritance (or “subtyping”) models an “is a” relationship - consequently a subclass can be used in place of a parent class because it is one by definition.

      1. 5

        Second, how can anybody disagree with the Liskov substitution principle?!?

        I’ve always wondered this. To me, the LSP feels more like a law of nature or a mathematical definition than a pattern or principle. If you “violate” it then you just literally aren’t doing what you say you’re doing (creating a subtype).

        If I claim to have invented a perpetual motion machine, I didn’t violate the laws of thermodynamics, I just didn’t actually invent a perpetual motion machine. I made a machine, and it might even do something useful, but it isn’t what I claimed, so the laws of thermodynamics themselves aren’t even particularly relevant at that point.

        1. 4

          Second, how can anybody disagree with the Liskov substitution principle?!? Inheritance (or “subtyping”) models an “is a” relationship - consequently a subclass can be used in place of a parent class because it is one by definition.

          People don’t think about LSP because it’s usually enforced by the language itself. For a case when a language didn’t enforce LSP, check out Eiffel’s CATcall problem. Eiffel has covariant subtyping on argument inputs, which makes its type system unsound.

          1. 6

            You can still break LSP in C# or Java by overriding a method to make it incompatible with the parent class.

          2. 0

            Inheritance (or “subtyping”) models an “is a” relationship - consequently a subclass can be used in place of a parent class because it is one by definition.

            
            #include <iostream>
            
            int main() {
                class Base {
                    public:
                        auto g() {
                            std::cout << "My class can be instantiated into valid and useable objects\n";
                        };
                        auto f() {
                            std::cout << "I am part of the public interface of the base class\n";
                        };
                };
            
                class Child : public Base {
                    public:
                        auto g() {
                            std::cout << "Mine too\n";
                        };
                    private:
                        auto f() {
                            std::cout << "But not of the public interface of the child class\n";
                        };
                };
            
            	auto b = Base();
            	auto c = Child();
                    b.g();
                    c.g();
            }
            
            

            I mean… even with the most pedantic possible definition you can come up with, the above would violate this and yet this is perfectly valid code. So either:

            C++ classes&inheritance are incorrect (and Python as well… and probably a lot of other ones, but I don’t really work with OOP code so I wouldn’t know)… or what you’re saying is not true. At least that’s how I’d interpret it… maybe I’m wrong ? If so please correct me, I’ve happily enough not have to work with OOP codebases in 4+ years, so I hardly remember anything about the subject.

            And again, this is the most pedantic and minimalistic and pointless definition I’m breaking here.

            If you interpret the principle as “Can be substituted” as “The code should work” rather than “Without the compiler throwing an error”… oh golly, I think it’s find to fine a single large OOP codebase in the world that fully respects that.

            1. 3

              The fact that you can write code violating LSP is the whole reason it exists as a guideline. Inheritance models an “is a” relationship, so that’s what people expect when they see it in code.

              1. 0

                So if 100% of people expect that, why do OO languages allow you to violate this principle ?

                1. 2

                  why do OO languages allow you to violate this principle ?

                  I can also give all of my variables, functions, and classes random 8 character names like “jk7sdkj3”, but it’s not a good idea if I want my code to be understandable by the people reading it.

                  There’s no way for a language to forbid people from writing confusing or “bad” code. It’s up to the programmer to use common sense and try to make their code as readable and as easy to understand as possible.

                  1. 0

                    I can also give all of my variables, functions, and classes random 8 character names like “jk7sdkj3”, but it’s not a good idea if I want my code to be understandable by the people reading it.

                    But it’s intuitive that you shouldn’t give your function bad names, or random names at least, as per your example. LSP is not intuitive.

                    Otherwise why does LSP need to be state and “Don’t give functions bad names” is not part of solid ?

                    1. 2

                      I don’t understand your logic.

                      On one hand your own article points to the LLVM and Linux kernel coding standards as good examples of what coding standards should be, yet they both include rules about “intuitive” things like variable and function naming, and both have arbitrary restrictions on how the implementation language can be used.

                      Also in your article, you point to rules like, “Keep ‘Internal’ Headers Private” and “Do not use RTTI or Exceptions”, as good examples of coding guidelines, yet in the comments here, you’ve argued that LSP is a bad guideline because languages allow you to violate it. Doesn’t C++ allow you to violate “Do not use RTTI or Exceptions,” by including those features? Shouldn’t they be bad rules by your logic?

                      Furthermore, “intuitive” is a subjective matter. It’s unfortunate that LSP isn’t intuitive for you, but it is for many people. One purpose of coding guidelines is to get everybody on the same page regarding issues and conventions that aren’t intuitive for everybody.

                      As far as why LSP is in SOLID and naming conventions aren’t, you’d have to ask the people who came up with SOLID.

                      Honestly, it seems like you’re just ranting against SOLID more than anything else.

                      1. 1

                        Ok, so it seemed to me like you were making a different claim from the one you seem to be making now, maybe I am mistaken.

                        Let me try to split those claims and tell me if you are making either one or the other:

                        Claim 1 – Inheritance (or “subtyping”) models an “is a” relationship - consequently a subclass can be used in place of a parent class because it is one by definition.

                        Here, to me, you seemed to be claiming that inheritance, as it is implemented in the vast majority of popular language (C++, Java, Python, Javascript) models an “is relationship. Which is what my comment was specifically arguing against. That is to say, this is not the case, inheritance as we have it today in most language is not an “is a” relationship the way LSP understands it.

                        But if this is not your claim, that is to say, if you are referring to “inheritance” as… idk, an abstract mathematical model under “X” definition, then disregard my previous 2 comments.

                        At any rate, this has nothing to do with what I’m arguing in the article.

                        Claim 2 – LSP is good, because inheritance should be treated as an “is a” relationship and if you aren’t you’re doing something really bad

                        This is what you seem to be saying based on this last comment.

                        I agree with this claim, broadly speaking, BUT

                        That doesn’t mean I agree with the LSP. My main gripe with the LSP is not “some interpretation of it can’t be correct” my main gripe with it is “It’s high brow bs that’s not explicit about it’s prescription”.

                        Namely, LSP can prescribe three things:

                        a) Children shouldn’t override the parents interface (e.g. what I did above, the most conservative implementation)

                        b) Parents should be replaceable with children and the code should still be valid in that it compiles.

                        E.g.

                        # Car_Controller -> Model_T_Controller
                        # Car_Controller -> Model_S_Controller
                        
                        func make_sure_it_breaks_work(Car_Controller c) ...
                        
                        # The bellow code compiles
                        c1 = Model_T_Controller()
                        c2 = Model_S_Controller()
                        make_sure_it_breaks_work(c1)
                        make_sure_it_breaks_work(c2)
                        

                        c) Parents should be replaceable with children and the code should still be valid in that it behaves as expected

                        E.g:

                        In the above example, if make_sure_it_breaks_work is used to check a car with a controller of type Model_T_Controller and Model_S_Controller, then it won’t throw an Exception/Error unless is does for a similar object of type Car_Controller (i.e. one that has the same value for all the shared members) nor will it run successfully and result in the cars crashing and burning because the breaks didn’t actually work.

                        You can argue a and b are the same, in that case just focus on b & c, I don’t think that particular argument is very interesting.

                        My Issue with LSP is:

                        1, It’s not clear whether it should be interpreted as c) or as b) or as a) or as something else 2. It’s not clear what it adds compared to the Open Close Principle (since inheriting a class is extending a class) , especially under interpretation a and b 3. It’s poorly worded, in a way that based on my paper reading experience smells of high-brow bs worded that way to seem more authoritative/smart. Namely, if LSP was in one of the guidelines mentioned above, it would be worded in a way more similar to how I worded a),b),c) 4. Since a version of the LSP doesn’t seem to be present in any of those guidelines, it might be that LSP (even if points 1,2 and 3 stand) is redundant and shouldn’t be mentioned to being with (much the same way we aren’t explicit about “name your functions as something that make sense, not just random jibberish”)

                        …. Hopefully that makes my stance here a bit more explicit, sorry for the previous misunderstandings (if indeed you were arguing about Claim 2 and getting annoyed that I was arguing about Claim 1)

                        1. 1

                          Here, to me, you seemed to be claiming that inheritance, as it is implemented in the vast majority of popular language (C++, Java, Python, Javascript) models an “is relationship. Which is what my comment was specifically arguing against. That is to say, this is not the case, inheritance as we have it today in most language is not an “is a” relationship the way LSP understands it.

                          As far as C++, Java, and Python are concerned, public inheritance is definitely intended to model an “is a” relationship. I think it’s also true in Javascript, but it’s weird because Javascript uses prototype classes and that makes things more complicated.

                          In any case, Chapter 12 of “The C++ Programming Language” makes it clear in section two, by explaining a simple employee/manager class heirarchy:

                          struct Employee {
                          string first_name, family_name;
                          short department;
                          
                          string full_name() { return first_name + " " + family_name; }
                          virtual string do_work() { return "an employee's work"; }
                          };
                          
                          struct Manager : public Employee {} {
                          virtual string do_work() { return "a manager's work"; }
                          }
                          

                          The text explains: “A Manager is (also) an Employee, so a Manager* can be used as an Employee*,” and later, “In general, if a class Derived has a public base class Base, then a Derived* can be assigned to a variable of type Base* without the use of explicit type conversion.”

                          It’s not hard to think through the three options and figure out which one it must be.

                          a) Children shouldn’t override the parents interface (e.g. what I did above, the most conservative implementation)

                          This would make inheritance pointless, so clearly it’s not the right answer.

                          b) Parents should be replaceable with children and the code should still be valid in that it compiles.

                          This is how how the lanugage works, so this explaination would be redundant.

                          c) Parents should be replaceable with children and the code should still be valid in that it behaves as expected

                          So this must be the correct answer. The benefit is that it lets users of the class treat all Employees the same where possible (they all have a full_name(), they all do_work(), etc.), and hides the low level details that differ between them (both Employees and Managers work, but they do different work).

                          1. 1

                            This is how how the lanugage works, so this explaination would be redundant.

                            So, again, you claim this is how the language works, in spite of the example above proving that many language, including C++ which is the one in question, don’t work that way. I don’t understand your argument here.

                            “The C++ Programming Language”, in spite of what anyone might tell you, is not the ISO standard for C++

                            If within the bounds of “ISO/IEC 14882:2017(E) – Programming Language C++” you can find a clear statement that means the code I wrote above is invalid, I will personally send you a video of me eating a hat and you should submit a bug to all major C++ compilers.

                            Otherwise, you really seem to be arguing in bad faith here, you are using miss-using phrases such as “this is how the language works” (with a convoluted meaning of : “This is how most people assume code should be written in the this language”) in order to not discuss the central idea. So, again, barring that I think it’s pointless to pursue this discussion further.

          3. 5

            I also see this in tooling nowadays. The trend seems to be towards loading tooling up with the current fashion best practice, and then making the tool enforce that at compile/gunpoint. It’s like the belief is that if the tool forces users to engage in said best practice, then that’s some sort of substitute for actual taste and experience.

            It’s not like the biggest problem in software is that Joe put his model somewhere other than app/models. However, inevitably, someone runs into this problem of people not being able to organize source files properly (e.g. lacking taste), and decides that it’s better to force a specific opinion on everyone. Branding it Convention over Configuration, the users are then happy to follow their rules, and they praise the creator for making them follow them. (Said people often remark how much better their life is without Java imposing ceremony on them. Irony.)

            It’s really weird.

            1. 1

              Interesting point that SOLID itself doesn’t adhere to the Interface Segregation Principle, because they’re supposed to be applicable to all code in all circumstances. And that the goals of:

              1. Code being understandable
              2. Code being flexible
              3. Code being maintainable

              Are probably 3 separate interfaces that require different principles.

              Very interesting way to think about it, and it seems like the author is advocating for being pragmatic while still thinking deeply about what we’re trying to get out of our code.

              1. 2

                From my personal experience, this is mostly because you are not expected to tick all the boxes. SOLID principles are more like overall rules that you’d better follow unless you have good enough an excuse not to do so.

                In other words, the dangerous thing is that they can only do you any good when you are clueless.

                Nowadays, what I do is that I assume I’m working on a temporary solution if I notice that these principles do me a better service than the actual project requirements. YMMV.

                1. 1

                  I don’t buy it - if the three goals were addressing three things that were actually different things then yes. But these goals are fundamentally interconnected: three adjectives used in combination to describe “software which is easy to make changes to”. “Maintainable” is a direct dependency of “understandable” and “flexible”: “flexible” software must itself be understood before it can be flexed - they all lead into each other. This is like saying “life, liberty and the pursuit of happiness” are unrelated, but you can ask Dr Maslow what happens when you kick out the bottom layer of the pyramid.