Should I prefer consistent design or good design?


  • Impossible Mission - B

    @gribnit All other things being equal, I prefer them short, but correctness is more important.


  • Considered Harmful

    @masonwheeler Are you asking that the property that a null reference will never be dereferenced be verifiable by the compiler?


  • Impossible Mission - B

    @gribnit No. I'm not talking about null references; I'm talking about references that do not exist. For example, if I want to reference a variable named foo but I accidentally type fou instead, that's a reference that does not exist, and the compiler will catch that. (But if I were using a dynamic language such as JavaScript or Python, where there is no static checking for correctness, this would be just fine until it blows up at runtime.)

    If I'm not using direct references, but instead I ask the DI container for something that isn't in there, the compiler is just fine with it and I don't see that there's something wrong until it blows up at runtime, so we've got behavior exactly equivalent to a dynamic language and thrown out the benefits of using a statically compiled one.



  • @gribnit I've seen three main ways to do this:

    1. A static registry of classes you ask for instances from.
      • Someone writing code that uses your class has no idea what they have to register until they run it. And who's to say your dependencies don't have dependencies? They probably do.
    2. A composition root.
      • All registrations are in one place.
      • This code looks awful and there isn't a logical way to structure it.
      • At least your classes don't know about the framework.
    3. Reflection-based, like MEF.
      • You can scan external assemblies for things like plugins.
      • The error messages are stupid and don't help in the slightest.
      • At least you generally just write normal code, and don't think about how it's stitched together.
      • You can just... use things with no container if you want.

    None of those, however, are the point: by making your dependencies known to the caller, your code is easier to think about. And if it doesn't: You're doing something horribly wrong.

    To people who complain about this meaning you expose your implementation details: that is just plain wrong, unless your classes are brain-dead wrappers. Which they certainly can't all be. If you have a set of functionality and data that other things can use, it should be injected. But it should also be small. If you have to call 15 constructors to get an object for some simple task, you have done something awful. But you'll also see that awfulness.

    And what if you have way too many dependencies? Well, then you probably haven't split things well. Find better abstraction points, because otherwise you're just hurting yourself.

    In general, I would say that dependencies should be created and passed in from wherever is most appropriate, and that that isn't always a composition root or anything else static. But managing that well is hard. That doesn't mean you make everything awful, though.


  • Considered Harmful

    @masonwheeler said in Should I prefer consistent design or good design?:

    If I'm not using direct references, but instead I ask the DI container for something that isn't in there

    If you've got to ask your DI container for a reference as indirect as a string or uuid or some other sort of godawfulness, you've had your brussels' sprouts cooked wrong and have been laboring under the correctness guarantees of Class.forName(String), which is a weaker guarantee than you can get the compiler to enforce if you can ask for an interface class or superclass of the injected functionality.


  • Impossible Mission - B

    @gribnit Who said anything about strings and type names? 😕


  • Considered Harmful

    @masonwheeler I'm not the one trying to ask my DI system for something I have no way to refer to, man.


  • Impossible Mission - B

    @gribnit :facepalm: It was an analogy to the sort of incorrect thing that can happen with variable references. The way this works in DI is asking the container for a (real, valid) type that the container doesn't have registered. Surely you've had to debug code that does this at some point?


  • Considered Harmful

    @masonwheeler Yeah, then I figure out why the hell a library I have a type from isn't loading, in a rare case, or I figure out what needs to be loaded in to provide the unmet dependency, which most containers will complain about at wire time during tests. If someone has deployed the application wrong - well, that's what I get for having my dependencies be overridable at runtime.

    If they're not, well, you could use an aspect compiler or something and have static binding that looks a lot like DI and have the compiler be able to check it.



  • @masonwheeler said in Should I prefer consistent design or good design?:

    @gribnit :facepalm: It was an analogy to the sort of incorrect thing that can happen with variable references. The way this works in this one specific implementation of DI is asking the container for a (real, valid) type that the container doesn't have registered. Surely you've had to debug code that does this at some point?

    Because once again, idiot, not even all containers work that way, and containers are not DI. Seperate the concept from the implementation in your head.


  • Impossible Mission - B

    @magus said in Should I prefer consistent design or good design?:

    not even all containers work that way

    I've seen several different DI container implementations, and every last one of them does, in fact, work that way at one critical point or another. If you have a counterexample, please present it and I will consider new facts. But until you do so, I have no reason to believe you have any idea what you're talking about, particularly in light of:

    and containers are not DI.

    Considering that you don't appear to even know that Dependency Injection is not part of SOLID, your definitions don't carry that much weight.



  • @masonwheeler said in Should I prefer consistent design or good design?:

    I've seen several different DI container implementations, and every last one of them does,

    Don't use any of them. Just type a list of the dependencies in the top of the class in its constructor.

    The libraries are crappy. You don't need them. You're adding pointless complexity and gaining nothing.


  • Considered Harmful

    @blakeyrat How's your OCR implementation?



  • @gribnit Nonexistent.



  • @masonwheeler said in Should I prefer consistent design or good design?:

    Considering that you don't appear to even know that Dependency Injection is not part of SOLID, your definitions don't carry that much weight.

    You don't know what DI is, though, so your opinion is worth a teensy little bit less ;)

    @masonwheeler said in Should I prefer consistent design or good design?:

    I've seen several different DI container implementations, and every last one of them does, in fact, work that way at one critical point or another. If you have a counterexample, please present it and I will consider new facts. But until you do so, I have no reason to believe you have any idea what you're talking about, particularly in light of:

    Lots of people use Unity. My company is using some dumb one that's half homebrew. I mentioned MEF before. Many people use Spring (Though Spring's composition root is an XML file of all things, so good luck with typing there!). Conversely, service locators are the exact opposite of DI containers, even if there are service locators named DI Containers. Because you are not injecting your dependencies if they are not inverted.

    The way MEF works is that classes you want to be injected are marked with [Export] or [InheritedExport], optionally also [Shared] if you want any non-enforced singleton instances. Classes that want them put [ImportingConstructor] on their constructor, and then when you tell the system to compose, everything is constructed. You can imagine how bad the error messages are, but it's certainly unusually good at this stuff. You can also do something like [Export(typeof(IWhatever))] if you want a specific interface implementation.

    Meanwhile my company uses one where every injected class has a line like container.Export<SomeType>(() => container.GetInstance<Dependency1>()); in a composition root that's just a couple hundred lines like that. I hate it.

    But my point remains @blakeyrat 's: You don't need any of that to have DI. And it may well be better and easier to reason about.


  • Impossible Mission - B

    @magus said in Should I prefer consistent design or good design?:

    Lots of people use Unity.

    The game engine? How does its DI system work? I'm not familiar with it.

    My company is using some dumb one that's half homebrew.

    Which is not useful as an example here, as it cannot be analyzed.

    I mentioned MEF before.

    (See below.)

    Many people use Spring (Though Spring's composition root is an XML file of all things, so good luck with typing there!).

    I've actually used that one. It works exactly as I described: you can ask for dependencies that it doesn't know how to provide, and then it compiles just fine, bypassing static compiler checks, and blows up on you at runtime.

    The way MEF works is

    [long explanation snipped]

    Yes, I'm quite familiar with MEF. It's how composition for Visual Studio plugins works. It's quite possible to try to import something that it doesn't know how to provide, and then it blows up on you at runtime, just like every other DI system. (And then you have to debug into the bowels of Visual Studio itself to figure out what's going wrong with your plugin. This is never a pleasant experience, even with the help of a good decompiler.)


  • Considered Harmful

    @magus Annotated Spring works out okay. It makes it more difficult to avoid breaking purity regarding container transparency but it allows for type checking, which lack sucks about XML Spring.



  • @masonwheeler said in Should I prefer consistent design or good design?:

    The game engine?

    No. That's Unity3D. Unity is a composition framework common in C#.

    @masonwheeler said in Should I prefer consistent design or good design?:

    It's quite possible to try to import something that it doesn't know how to provide

    There are two main scenarios where this happens:

    1. You didn't make any instances that export.
    2. You have a circular dependency.

    And yes, the error messages are bad. I acknowledged that. But once again, you ignore the fact that DI is best without all of that.

    MEF isn't so bad on its own, but you have to work hard to keep things simple, or you will hate everything. Which isn't all bad, since anyone maintaining your code will hate your lack of simplicity anyway.


  • Impossible Mission - B

    @magus said in Should I prefer consistent design or good design?:

    But once again, you ignore the fact that DI is best without all of that.

    And you ignore the fact that programming is often better without DI in the first place, as I said above, because it frequently violates the principle of Encapsulation.



  • @masonwheeler said in Should I prefer consistent design or good design?:

    And you ignore the fact that programming is often better without DI in the first place, as I said above, because it frequently violates the principle of Encapsulation.

    That's because you are incorrect.


  • Impossible Mission - B

    @magus Repeatedly asserting something does not make it true, especially when it contradicts something that is obviously, trivially true. Asking the outside world to provide your implementation details--in whatever manner--instead of keeping them private means you are not encapsulating those implementation details; you're exposing them. That's a simple matter of definition.



  • @masonwheeler said in Should I prefer consistent design or good design?:

    @magus said in Should I prefer consistent design or good design?:

    But once again, you ignore the fact that DI is best without all of that.

    And you ignore the fact that programming is often better without DI in the first place, as I said above, because it frequently violates the principle of Encapsulation.

    How does saying "I use <thing> that it doesn't make sense for me to initialize, please supply one to me" break encapsulation?

    And principles are not laws. They're guidelines. Competing guidelines sometimes. I'm no DI fanatic, but being explicit about your dependencies (especially when they're better constructed elsewhere) seems like common sense. And that requires no spooky action-at-a-distance and enhances debugging, because you can see/change exactly what you pass in.

    Edit:

    class Foo(string personID)
    {
    
    }
    

    by your standards this breaks encapsulation just as much as

    class Foo(Bar bar)
    {
    
    }
    

    does.

    And that's insane.


  • Impossible Mission - B

    @benjamin-hall said in Should I prefer consistent design or good design?:

    How does saying "I use <thing> that it doesn't make sense for me to initialize, please supply one to me" break encapsulation?

    It doesn't. This is why I was deliberately not absolute in my statement. What you just described makes perfect sense. But that's not the dogmatic principle of Dependency Injection (in capital letters) the way people generally talk about it.

    You don't have to look hard at all to find people, even well-regarded experts in the subject, essentially saying, "don't ever initialize anything yourself; all objects your object is composed from are dependencies that must be injected or you're :doing_it_wrong:." And that's a massive violation of the principle of Encapsulation.



  • @masonwheeler said in Should I prefer consistent design or good design?:

    Repeatedly asserting something does not make it true,

    Correct. Unlike your unsupported assertion about DI breaking encapsulation, which it does not do. There may be people who break encapsulation while using it, but that's them being stupid.



  • @masonwheeler said in Should I prefer consistent design or good design?:

    @benjamin-hall said in Should I prefer consistent design or good design?:

    How does saying "I use <thing> that it doesn't make sense for me to initialize, please supply one to me" break encapsulation?

    It doesn't. This is why I was deliberately not absolute in my statement. What you just described makes perfect sense. But that's not the dogmatic principle of Dependency Injection (in capital letters) the way people generally talk about it.

    You don't have to look hard at all to find people, even well-regarded experts in the subject, essentially saying, "don't ever initialize anything yourself; all objects your object is composed from are dependencies that must be injected or you're :doing_it_wrong:." And that's a massive violation of the principle of Encapsulation.

    Morons are morons. Story at 10. Those people are cargo-cultists and don't understand it themselves.

    You're doing the same thing to a lesser degree by raising "encapsulation" to a high and holy thing.

    You're also fighting against one particular (and flawed) concept of DI as if it were the whole principle. Which it isn't. Even I, who am not a professional programmer and am entirely self-taught, can see that.



  • @masonwheeler said in Should I prefer consistent design or good design?:

    And that's a massive violation of the principle of Encapsulation.

    WHY? WHERE?

    Sure, if you make all your classes into single methods, then yes, you are tr:wtf: - but that isn't DI.


  • Impossible Mission - B

    @benjamin-hall said in Should I prefer consistent design or good design?:

    You're doing the same thing to a lesser degree by raising "encapsulation" to a high and holy thing.

    It's called one of the Three Pillars of OOP for a reason...


  • Impossible Mission - B

    @magus said in Should I prefer consistent design or good design?:

    WHY? WHERE?

    Exactly where I said. Grow some reading comprehension already! :O

    Sure, if you make all your classes into single methods, then yes, you are tr:wtf: - but that isn't DI.

    Who said anything about single-method classes? Why do you keep implying that I'm talking about things I never talked about?



  • @masonwheeler said in Should I prefer consistent design or good design?:

    @benjamin-hall said in Should I prefer consistent design or good design?:

    You're doing the same thing to a lesser degree by raising "encapsulation" to a high and holy thing.

    It's called one of the Three Pillars of OOP for a reason...

    ...and it's a guideline. Not a commandment. And OOP isn't even scripture. You're doing the exact same cargo-culting you are (justly!) getting after some DI proponents for. Stop projecting. You're blinding me.



  • @unperverted-vixen said in Should I prefer consistent design or good design?:

    accessing HttpContext.Current, rather than demanding their needs in the constructor.

    I disagree it's bad design, and I think dependency injection is a silly fad.



  • @sockpuppet7 said in Should I prefer consistent design or good design?:

    I disagree it's bad design, and I think dependency injection is a silly fad.

    Anything that's not 100% focused on creating a HTTP response (and I don't mean "creating data to go in the response" but literally creating the actual response itself) should have no idea that a HttpContext exists. It's possible that isn't bad design (Framework A does HTTP work in the wrong layer), but it's extremely unlikely.

    Libraries should be separated and insulated from user/browser interfaces. (What if next week you wanted to use Framework A in a Windows Service? Or a GUI app?)

    But either way, the real point here to the original question is you gotta pick your hills to die on.


  • And then the murders began.

    @blakeyrat said in Should I prefer consistent design or good design?:

    It's possible that isn't bad design (Framework A does HTTP work in the wrong layer), but it's extremely unlikely.

    Oh, no, it totally accesses it in the wrong layer. Various pieces of utility code (date/time conversion, etc.) trying to find user-specific settings like time zone.

    What if next week you wanted to use Framework A in a Windows Service? Or a GUI app?

    It's already used in both. :( (Fortunately all the places trying to use it have null checks and fall back to a generally-configured default if no HttpContext or user-specific settings are found.)

    Part of me wants to try and use it from a .NET Core console app just to see how many different places I can get PlatformNotSupportedExceptions.



  • @Magus said in Should I prefer consistent design or good design?:

    @masonwheeler said in Should I prefer consistent design or good design?:

    The game engine?

    No. That's Unity3D. Unity is a composition framework common in C#.

    💡 Many other conversations on here suddenly make a whole lot more sense.


  • Considered Harmful

    @masonwheeler If you weren't busy fixing your nests of GOTOs maybe you'd have had the time to learn the things you need to have already learned by now to make this all make sense.


  • Discourse touched me in a no-no place

    @magus said in Should I prefer consistent design or good design?:

    (Though Spring's composition root is an XML file of all things, so good luck with typing there!)

    That's technically just one way of configuring the composition root. It's more common to use annotations now.


    Dependency Injection is, in basic form, very simple: if a class (an instance of a class, properly) needs to call another class (instance) that it doesn't own, it should be provided a reference to that object it should make calls to during its setup phase. The setup phase could be just the constructor. The key point is that the class should not go off during construction or operation and call some funky service discovery shit to do it; it's been given the thing it should talk to, so it doesn't need any of that discovery crap.

    Passing in all dependencies via the constructor works as long as your dependencies form a directed acyclic graph. This might be difficult to achieve in more complex applications, so some DI systems also allow you to use field/setter injection and extend the setup phase to allow for that (and that leads on into extended service lifecycles and stuff like that). But that's an extension of the basic concept: a class should be given its dependencies, it should not look them up.

    When it comes to wiring all these classes together, you can do it all by hand — it's just passing objects into ordinary calls to constructors! — but that's very tedious. Computers are great at automating tedious! So what if it uses reflection to do it? The wiring engine will either get it all right (including respecting all type constraints that you wrote) or it will tell you that it couldn't. But in order to respect the principle that classes should be given their dependencies and not look them up, your classes should not talk about the wiring engine; if you can't talk about it, you can't use it to look things up when you shouldn't.

    That wiring engine? That's what the core of an inversion-of-control container library is. They're complicated internally, but you shouldn't (usually) pay attention to that, just as you don't normally pay much attention to your compiler or your operating system.

    (Also note that these are all guidelines, not holy law. Nothing in programming is holy law. Except the prohibitions on computed GOTO and line numbers.)



  • @magus said in Should I prefer consistent design or good design?:

    A composition root.

    All registrations are in one place.
    This code looks awful and there isn't a logical way to structure it.
    At least your classes don't know about the framework.

    I am doing this in node.js. It scales up nicely to about 30-40 service classes. Didn't need more so far.


  • Considered Harmful

    @gribnit said in Should I prefer consistent design or good design?:

    @masonwheeler said in Should I prefer consistent design or good design?:

    static correctness safety

    How do you like your compile times? I hope you have a great solution to the halting problem, or that you're asking for a much weaker property than it looks like you are asking for.

    Less than I like my run times. Also, is there a reason you keep putting stuff in invisible tags?


  • Considered Harmful

    @pie_flavor I'm trying to be nice to people today.



  • @dkf said in Should I prefer consistent design or good design?:

    Dependency Injection is, in basic form, very simple: if a class (an instance of a class, properly) needs to call another class (instance) that it doesn't own, it should be provided a reference to that object it should make calls to during its setup phase.

    I hate it when people explain it this way because classes don't have a concept of ownership, that's ridiculous. It took me forever to figure out what DI was because people always explain it in this dumb way where classes have ownership and hopes and desires and a sense of humor and all these weird human characteristics that apparently if you take a CS class after about 2005 professors assigned them.

    They don't. They are inanimate objects. They don't own things. They don't desire things. The explanation is stupid. And this particular version doesn't even need the highly confusing term "owns" in it because literally later in the same sentence you say "needs to make calls to" which is the real explanation.

    @dkf said in Should I prefer consistent design or good design?:

    Passing in all dependencies via the constructor works as long as your dependencies form a directed acyclic graph.

    Sad trombone sound

    Ok done reading this garbage. If you're trying to explain something to somebody and you use the term "directed acyclic graph" you should be punched in the face, bam, no warning, I'm not kidding.

    @dkf said in Should I prefer consistent design or good design?:

    So what if it uses reflection to do it?

    Then you've put control of your application into the hands of some moron open source library full of bugs and next time something doesn't build you'll waste days trying to pry open that ridiculous black-box to figure out fucking why! Congratulations, what a time savings.

    @dkf said in Should I prefer consistent design or good design?:

    (Also note that these are all guidelines, not holy law. Nothing in programming is holy law. Except the prohibitions on computed GOTO and line numbers.)

    ... but it is true that classes can't own things. They're inanimate objects. They don't have fucking desires or personalities. I'm sticking by that one.


  • Considered Harmful

    @blakeyrat Owns as in has a reference to it, maybe the last reference that is keeping GC from killing it for instance. Owns is appropriate here.



  • @gribnit said in Should I prefer consistent design or good design?:

    @blakeyrat Owns as in has a reference to it,

    If I have a reference to a Keats poem, do I own it? Can I earn royalties? Sue publishers who have used it without attributing me?

    @gribnit said in Should I prefer consistent design or good design?:

    Owns is appropriate here.

    No. No it is not. People can own things. Groups of people (such as governments) can own things. Abstract concepts written in code cannot own things.

    Computer science is already drowning in jargon, I don't know why people go out of their way to make it more confusing.


  • Considered Harmful

    @blakeyrat Okay, it 001s them, now are you happy?


  • Discourse touched me in a no-no place

    @blakeyrat said in Should I prefer consistent design or good design?:

    I hate it when people explain it this way because classes don't have a concept of ownership, that's ridiculous.

    Well, I'm using it in the sense of how someone familiar with UML Composition would understand it; I say that A owns B if:

    • B is regarded as being a part of A, and
    • when A is destroyed, B is also destroyed.

    I'm not going to type all that guff out when I can use a much shorter phrasing that virtually all programmers understand intuitively already. (All except you, Drax, all except you...)


  • BINNED

    @dkf Especially intriguing since apparently talking about dependency inversion, dependency injection, IoC containers, reflection, and all the other jargon is perfectly fine and understood, but then "owns" is confusing and "directed acyclic graph" is forbidden.
    The ignorance seems entirely feigned.


  • Discourse touched me in a no-no place

    @topspin said in Should I prefer consistent design or good design?:

    The ignorance seems entirely feigned.

    Or is a mark of someone who is undereducated for the roles he is taking on, and stubbornly refuses to fix that by reading the sort of background material that the rest of us mastered decades ago. One doesn't need a college degree to be a software engineer, but one should be familiar with that grade of material (through self learning if nothing else) in order to not be at a serious disadvantage.



  • @dkf I don't mean to disparage anyone with this. But I'm completely self-taught as a programmer. And to me, directed acyclic graph, etc. were clear really quickly. Some of the quirks, no, but the jargon isn't that bad. Physics jargon is way worse. And we do the same anthropomorphizing bit because it helps us conceptualize behavior. It also cuts down on verbiage, since we can use short-hand instead of laboriously spelling out all the things we all know and accept each time.

    Human languages are naturally polysemous. English even more so. And that's a feature, not a bug.



  • @dkf said in Should I prefer consistent design or good design?:

    Or is a mark of someone who is undereducated for the roles he is taking on, and stubbornly refuses to fix that by reading the sort of background material that the rest of us mastered decades ago.

    Well remember I'm a stupid idiot moron dummy at all times forever. If I said it, obviously it's stupid and wrong. You've been on this forum long enough to know that.

    @dkf said in Should I prefer consistent design or good design?:

    One doesn't need a college degree to be a software engineer, but one should be familiar with that grade of material (through self learning if nothing else) in order to not be at a serious disadvantage.

    Keeping shit simple is an advantage in software development.

    @benjamin-hall said in Should I prefer consistent design or good design?:

    @dkf I don't mean to disparage anyone with this. But I'm completely self-taught as a programmer. And to me, directed acyclic graph, etc. were clear really quickly.

    I know what a directed acyclic graph is, it's just a shitty way to explain a concept. It's like people who say "grok" instead of "understand", it's just going out of your way to use obtuse, difficult language instead of speaking plainly.

    You know, like this:

    @benjamin-hall said in Should I prefer consistent design or good design?:

    Human languages are naturally polysemous.



  • @blakeyrat I happen to like precise words. You don't. That's a matter of taste. There's an inherent trade-off and care must be taken when talking to those not in the know, but it greatly speeds up communication and reduces ambiguity and increases clarity. It's the equivalent of having a well-formed XML spec.

    As a physicist, I often get asked to "explain things more simply" or "just give me the concepts." For some things, simplifying it beyond a certain point teaches false information. This is very common among popularizers--they "simplify" and "remove the math", at the cost of correctness. And not just at the margins--the concepts they often teach are just flat out wrong and teach people the wrong habits of thought. Precision is difficult, but important.

    And often using more, simpler words is a way to cloud the discussion entirely. If it takes me an entire paragraph to describe what a 3-word phrase is, and I have to use it constantly, eyes will glaze over after about the first time. Instead, you give a key phrase, then define it (or let the student look it up) later. For things that are immediately important, you define it right there. But once you've defined a symbol, you don't need to keep redefining it everywhere.


  • Considered Harmful

    @benjamin-hall Or I mean, you could just drop usages without definitions, that's an option too.



  • @Unperverted-Vixen Here is the obligatory "It depends on how long you're gonna be there before you quit" response. Could not resist!


Log in to reply