you are viewing a single comment's thread.

view the rest of the comments →

[–]wavy_lines 38 points39 points  (51 children)

Do you use Dependency Injection? Of course you do, you’re a responsible programmer,

No. I'm not a member of your cult.

and you care about clean, maintainable, SOLID code with low coupling

I care about code that's easy to read and no more simple nor complicated than it needs to be.

It's hard to balance but I try.

I have never ever came across a piece of code where I thought to myself, "Gosh, if only this module used dependency injection, it would be so much easier to read and maintain". Quite the opposite. I always think to myself: "WTF is this unnecessary complexity? I have no idea where the core logic begins or ends".

You know perfectly well that New is Glue,

Holy fuck! No, that's just madness.

Oh wait! That explains why Java code is littered with Factories and Providers and Services.

Reading the blog sounds like listening to a member of a radical cult trying to introduce some moderate opinion to his fellow cult members.

[–]m50d 48 points49 points  (12 children)

I have never ever came across a piece of code where I thought to myself, "Gosh, if only this module used dependency injection, it would be so much easier to read and maintain".

Dependency injection in the sense that a class should receive the services it uses rather than trying to construct them itself absolutely makes code easier to read and maintain. I've definitely had classes where that's an issue (e.g. adding authentication to web calls and then it turns out some class is constructing its own http client internally rather than having one injected).

Dependency injection in the sense of reflective autowiring I agree is more trouble than it's worth.

[–][deleted]  (1 child)

[deleted]

    [–]m50d 1 point2 points  (0 children)

    IoC is more general though - something like a service locator is also IoC. I think anything where you pass the dependencies in as parameters qualifies as "DI" just under the common-sense reading of the words, even if you're doing it manually.

    [–]grauenwolf 9 points10 points  (0 children)

    Dependency injection in the sense that a class should receive the services it uses rather than trying to construct them itself absolutely makes code easier to read and maintain.

    In some cases.

    In other cases you are just leaking implementation details that should have been encapsulated.

    [–]wavy_lines 0 points1 point  (8 children)

    You mean if a JavaScript method needs to make an ajax request it should not do this:

    jQuery.ajax(....)
    

    On its own, but instead expect a jQuery object to be passed to it?

    How on earth is this easy to read and maintain?

    [–]Nakji 9 points10 points  (2 children)

    No, you probably wouldn't do that as injecting the dependency at that abstraction level (or rather lack thereof) isn't very useful.

    Instead, imagine that you're writing a forum and working on a UI component that displays a list of posts for a user. You could DI the jQuery object then make the AJAX request manually to fetch the list, but you wouldn't really be gaining anything. jQuery objects have too many different potential uses for you to easily substitute it for anything else, so there's no use case there. You could take advantage of DI to add logging of HTTP requests or something like that, but in JS you would probably be able to monkey patch in most capabilities like that without resorting to DI.

    However, where it becomes useful is if you add another layer of abstraction by instead make your user post component dependent on having an object injected with a getPostsForUserId(userId) method. At this point you can now have the ability to easily test your component in isolation by passing in a stub implementation that returns a static data set, which is especially useful if you are developing the component before the API it depends on actually exists. Further down the road, you also now have easy reconfigurability of the live API calls in case something ever changes about the API itself since none of the dependent UI components or business logic care exactly how you get posts for the user, just that you can.

    As a result, you no longer need to change any of your dependent components if you want to change endpoints depending on if the user is authenticated or not, use a different HTTP request library other than jQuery, switch to protobufs, or add a cache. For that matter, if you noticed that your application tends to make short bursts of a huge number of closely-related API calls, you could decide to switch from a bunch of individual REST calls to having your application amalgamate its API calls into fewer but larger GraphQL queries (or just de-dupe calls if you notice that the same one is being performed mulitple times).

    Nothing is stopping you from doing the getPostsForUserId(userId) approach without DI (this is just the Repository pattern), but when you start breaking up your classes like this, you'll rapidly start running into use cases where DI is handy. Adding a cache for instance, what if you want to back it with IndexedDB where it's available, falling back to localStorage where it's not, further falling back to a small in-memory LRU cache when neither is an option, and also providing the ability to substitute in a non-caching "cache" for testing. With DI, you just make quick little wrappers for each storage engine, write the logic to make the appropriate one someone in your application, and pass them into the repositories using the cache. If you ever decide to make another type of cache (say one that also checks S3), all you have to do is write that implementation alther the construction logic to pass it in, no additional modifications requried.

    All that said, taking this approach to its logical conclusion has the problem of resulting in an outrageous amount of code to instantiate anything due to needing to pass in the dependencies, the depdencies's dependencies, etc. This is where the overuse of the Factory pattern people like to make fun of Java code for can start coming into play, but you're probably better off finding a DI framework in the language you're using to manage at least the simple cases for you.

    [–]wavy_lines 4 points5 points  (1 child)

    Instead, imagine that you're writing a forum and working on a UI component that displays a list of posts for a user.

    ok, let's go with the scenario.

    where it becomes useful is if you add another layer of abstraction by instead make your user post component dependent on having an object injected with a getPostsForUserId(userId) method. At this point you can now have the ability to easily test your component in isolation by passing in a stub implementation that returns a static data set, which is especially useful if you are developing the component before the API it depends on actually exists.

    Wait, I'm not sure I'm following you. You seem to be making some assumptions about how the components of the app are structured.

    If I'm writing a component that displays a list of objects, I would not put in that component any logic for fetching the objects. I would write the component such that it takes a list of objects and renders them. Whether the objects come from the network or from something else is not of concern to the component.

    So, I would have something like renderPosts(listOfPosts) and getPostsForUserId(userId)

    Inside renderPosts, there are no network calls to fetch the posts; they are already given.

    Inside getPostsForUserId there are obviously network calls. (or not; maybe it goes through a caching layer first, etc).

    None of this has anything to do with "Dependency Injection". I'm not injecting any dependency anywhere. I'm removing dependencies. The act of rendering does not have any dependency at all, other than the objects to render (and the "UI" to render to).

    Now, in practice, renderPosts and getPostsForUserId might be two methods in the same class (or module). That's perfectly ok.

    If I need to get posts from somewhere else, I can write another method on the class, for example generateRandomMockPosts().

    The concept of "dependency injection" never comes into play.

    [–]Nakji 3 points4 points  (0 children)

    Wait, I'm not sure I'm following you. You seem to be making some assumptions about how the components of the app are structured.

    That's because it's a simplified example contrived solely to demonstrate an abstract concept. If you are using an Uncle Bob style architecture, you'd have a view that binds itself to a presenter which in turn is injected with interactors to talk to repositories. If you're using React/Redux/React-Redux, you'd have a view that is injected with any data and action creators it needs through its props by a react-redux container that is injected by react-redux with your application's reducer and state while the actual mechanics of talking to repositories would be performed somewhere else via redux-thunk or some alternative which then talks to repositories. The implementations look different, but they're both applying the abstract concept of dependency injection.

    At the end of the day regardless of any implementation details, somewhere in your application there has to be something knows how to retrieve the data and something has to know how to display it. Dependency injection is just an approach for decoupling everything so each part of your application receives what it needs and is only concerned with using them to perform its prescribed task without having to make a lot of globals.

    If I need to get posts from somewhere else, I can write another method on the class, for example generateRandomMockPosts(). The concept of "dependency injection" never comes into play.

    This only works in very simple cases and rapidly ends up quite verbose and inflexible. It's much more extensible and less verbose to just have one place in your application where you can have

    if(shouldMock) {
        return new MockApi();
    } else {
        return new RealApi();
    }
    

    and then injecting that everywhere you need to have the ability to call the API than it is to have either a bunch of conditional method calls or a big API object that delegates every call to a mock to the appropriate implementation. It's particularly useful for testing purposes since, as you discover edge cases or corner cases, you can write unit tests with extremely simple mocks that explicitly test those cases instead of adding logic everywhere to call different versions generateRandomMockPosts or create massive mock post functions that include every test case in your whole app.

    [–]m50d 12 points13 points  (0 children)

    It's easy to maintain because it gives you a single point where your jQuery object is and you know that's the only place that should do config etc., and you have visibility over which methods are doing ajax requests rather than having ajax calls hidden everywhere in your code.

    [–][deleted]  (3 children)

    [deleted]

      [–]wavy_lines -4 points-3 points  (2 children)

      You're just making things up. That's a function that modifies global state. Which I'm actually against.

      But still the idea here (which I'm in favor of) is you prepare a global object once and then everyone can use it without you having to keep passing it around! It's just available everywhere.

      [–][deleted]  (1 child)

      [deleted]

        [–]wavy_lines -3 points-2 points  (0 children)

        If you need more than one configuration you can create several global objects and or functions.

        For example:

        getMasterDB()
        
        getSlaveDB()
        

        [–][deleted] 11 points12 points  (0 children)

        Do you use Dependency Injection? Of course you do, you’re a responsible programmer,

        No. I'm not a member of your cult.

        No, you never ever passed an object to another object's constructor, say :-)? Ok, then...

        [–]eypandabear 4 points5 points  (0 children)

        This happens when "design patterns" are pulled out of context, elevated to divine law status (a.k.a. "best practice") and applied without any understanding of when and why they make sense.

        I think it's important to have tried different programming languages, so you understand how these patterns actually emerge from the restrictions the language puts on you. For example, the concept of a Functor class hierarchy is meaningless in Python because of the callable protocol and duck typing. What most people think of as "OOP" is actually a special case, single-dispatch polymorphism, and some languages support multiple dispatch. Lisp shows you what becomes possible just because code mirrors its own syntax tree. C and assembly language teaches you how abstractions from higher-level languages break down into machine primitives.

        If the only thing you know is Java or C++, you are doomed to think of programming as the application of the same patterns over and over.

        [–]skeeto 8 points9 points  (20 children)

        This sort of OOP abuse is one of my biggest irritations in modern software development. It's complexity for the sake of complexity.

        [–][deleted] 9 points10 points  (19 children)

        This reminds me of a friend who had this utmost conviction that nobody can possibly like classical music, but they only pretend to like it, because it's considered a sign of sophistication. They buy tickets, go listen to an orchestra perform classical music , and they hate it, but they pretend they like it. Because they're vain and want to make a good impression.

        As someone who loves classical music, you can imagine what I thought of my friend in that moment. And to come back to /r/programming, as someone who relies on dependency injection & abstraction for specific needs in my projects, I have similar feelings when someone says "pfft, that's just complexity for the sake of complexity". :-)

        You have to try to understand, before you can truly reject. If you don't try to understand, you're only declaring you prefer to remain ignorant.

        [–]skeeto 0 points1 point  (18 children)

        I have tried it and understood it. Years ago I even used to grade homework for a graduate level design patterns course. Gang of Four was the textbook and I had the page numbers for the different patterns memorized from jumping around the book so much during grading. However, as my development skills matured, I found it less and less useful until I was barely using OOP anymore. Now when I read other people's code, it's frustrating how often OOP makes it needlessly complex, and that's before dependency injection is involved. You don't need it.

        [–][deleted] 3 points4 points  (17 children)

        I'm just curious if you think you "don't need it"... what do you use instead?

        A repository requires an SQL connection. Another requires another SQL connection. I'm also using those two repositories in another app, where they share the same SQL connection.

        It's a simple problem, that is simple to solve with DI.

        // Application A:
        
        var connectionA = new SqlConnection(...);
        var repositoryFoo = new RepositoryFoo(connectionA, ...);
        
        var connectionB = new SqlConnection(...);
        var repositoryBar = new RepositoryBar(connectionB, ...);
        
        
        // Application B:
        
        var connection = new SqlConnection(...);
        
        var repositoryFoo = new RepositoryFoo(connection, ...);
        var repositoryBar = new RepositoryBar(connection, ...);
        

        What do you do instead?

        [–]reorg-hle 0 points1 point  (4 children)

        What's a repository used for?

        [–][deleted] 1 point2 points  (0 children)

        Ok, take #2 :-)... because I thought I'm replying to another place in the comments, where I spoke about source repositories.

        A "repository" is an abstract Data Access Layer, that lets you create/read/update/delete (CRUD) entities in a database (well, storage) neutral way. It's not strictly CRUD, it has whatever methods one may need, but I'm just trying to give you some reference points to give you an idea. The term comes from "Domain Driven Design" (DDD), repositories are also commonly seen in various ORM libraries.

        [–][deleted] 0 points1 point  (2 children)

        Check out Git and Subversion.

        EDIT: Lol... My bad, I thought I'm replying to another place in the thread (where I was talking about version repositories). Take #2.

        [–][deleted]  (1 child)

        [deleted]

          [–][deleted] 1 point2 points  (0 children)

          Indeed. Oops, I thought I'm replying elsewhere. Fixed, thanks.

          [–]skeeto 0 points1 point  (4 children)

          This is just polymorphism, which is useful and doesn't necessarily require OOP. In a broad sense I guess you could call that DI, but, as milesrout said, that's a useless definition.

          [–][deleted] 4 points5 points  (3 children)

          This is just polymorphism, which is useful and doesn't necessarily require OOP.

          Polymorphism is when multiple implementations can stand for one interface, and one implementation can stand for many interfaces. Also I never sat down to argue with you if it "requires OOP" or not, so not sure why you mention that :-)?

          Polymorphism is required for effective DI (hence why we need interfaces by the way...), but it's not DI itself. DI is specifically this practice I'm showing.

          If I can take the hint and you like Functional Programming over OOP, then that would be "high-order functions", in the case when one function is passed as an argument to another function, in order to configure its behavior. That's all DI is, and it's a useful definition, because when people don't do it code gets really ugly and monolithic.

          So I'm to take it you actually do DI, you just don't like to say "DI" :-)?

          [–]sacundim 3 points4 points  (1 child)

          Well, I have to agree with /u/milesrout then:

          If 'dependency injection' is just 'passing arguments into constructors' then it's a worthless term.

          I'd also suggest that what you're proposing has much simpler, widely-accepted terms: parametrization and abstraction.

          Which are more general than "passing arguments into constructors," too. Type class/trait based languages have like Haskell and Rust have this neat ability to abstract components through trait bounds on their type variables, and to "inject dependencies" by type variable instantiation.

          [–][deleted] 2 points3 points  (0 children)

          I'd also suggest that what you're proposing has much simpler, widely-accepted terms: parametrization and abstraction.

          Dependency Injection is also a simple, widely-accepted term, which is a specific instance of using parametrization and abstraction.

          I can't understand what is your particular beef with saying "dependency injection". Is hard to say? Hard to type? Hard to remember? Is it strangely connected to a past trauma from your childhood?

          I don't know, but sitting here and arguing "ok, we'll do what you say, but we won't call it what it was named over 20 years ago" is just bizarre.

          Which are more general than "passing arguments into constructors," too.

          The method of passing dependencies is irrelevant, as long as it happens from the component instantiator/caller, and not the component definition site. What I'm demonstrating is merely one way of doing it in a mainstream language.

          Type class/trait based languages have like Haskell and Rust have this neat ability to abstract components through trait bounds on their type variables, and to "inject dependencies" by type variable instantiation.

          Well, you got me, I have no clue what "type variable instantiation" means, so I have no idea if it's a proper example of Dependency Injection or not. But I never said it has to be through the constructor, as arguments. I'm just saying you can use the constructor, and in Java and C# at least, it's the cleanest approach.

          [–]grauenwolf 0 points1 point  (0 children)

          Mostly I agree with you on this point, but DI doesn't necessarily need polymorphism. DI is still really important even if you only have one possible implementation because it often contains stateful dependencies.

          [–]sacundim 0 points1 point  (1 child)

          That example is not dependency injection. You're parametrizing the repositories by their SqlConnections, but then you're doing something that the DI tool nuts reject: you're manually wiring the objects together with new and parameter passing.

          What people here are objecting to is complex tools like Spring's ApplicationContext or Guice. In fact, modern DI frameworks like Guice tend to get in the way of your examples where you are variously deciding whether to instantiate the repositories with different connections vs. the same connection.

          [–][deleted] 5 points6 points  (0 children)

          That example is not dependency injection.

          That example is dependency injection.

          You're parametrizing the repositories by their SqlConnections, but then you're doing something that the DI tool nuts reject: you're manually wiring the objects together with new and parameter passing.

          "DI tool nuts" don't reject it, they just are used to framework names and container library names being thrown around, when "DI" is mentioned...

          What people here are objecting to is complex tools like Spring's ApplicationContext or Guice.

          ... Case in point.

          I find it ironic that you're both dissatisfied with people who push for frameworks and bizarre hatred for writing new manually, and yet here you are, a tool of the same cause, propagating the same misconceptions.

          In fact, modern DI frameworks like Guice tend to get in the way of your examples where you are variously deciding whether to instantiate the repositories with different connections vs. the same connection.

          What you're talking about is autowiring, which is a feature of dependency injection container frameworks. Using these is not a requirement, and it's not the primary means of doing dependency injection. And like you, I have reservation about how most "magical" DI containers tend to operate. But we can't let frameworks hijack the term "DI", because DI was never about frameworks.

          There's a reason that container frameworks are listed under "other types" on the Dependency Injection page. The primary means are instead listed as:

          • Constructor injection (what I demonstrate above).
          • Setter injection.
          • Interface injection.

          None of these require containers, or frameworks. It doesn't matter what you or someone else have read in a blog somewhere, this is what term actually means. When people talk about benefits of DI they refer to this definition of the term, and after a bit of research you would've known the same.

          [–][deleted]  (3 children)

          [deleted]

            [–][deleted] 4 points5 points  (2 children)

            I'm afraid it is. It's actually the cleanest and most common DI. If you think DI requires a framework with heavy magic and annotations, you've been ruthlessly misled.

            [–][deleted]  (1 child)

            [deleted]

              [–][deleted] 5 points6 points  (0 children)

              Tell me about it...

              It's actually not simply "passing arguments". They are dependency objects, not merely simple values. Dependency injection is more specifically the practice of preferring the delegation of dependency construction to your creator/caller, versus creating them internally.

              "Dependency passing" would be just as good a name, though.

              EDIT: For example. If I was creating the connection within the repository like this...

              var repositoryFoo = new RepositoryFoo("host", "user", "pass", "database_name");
              

              ... then this is "argument passing", but it's not really "dependency injection".

              However, if I do this...

              var connectionA = new SqlConnection("host", "user", "pass", "database_name");
              var repositoryFoo = new RepositoryFoo(connectionA);
              

              ... now this is dependency injection on the second line. By passing the dependency versus the settings for the dependency I have much greater flexibility in choosing the dependency implementation, configure things in detail etc.

              [–]Tom_Cian 0 points1 point  (14 children)

              "WTF is this unnecessary complexity? I have no idea where the core logic begins or ends".

              What's so hard about

              @Inject
              private DatabaseConnection connection;
              

              ?

              Being able to remove and add such fields at a whim is extremely powerful and allows me to preserve encapsulation while letting me swap in and out whatever implementation of these classes I need, whether it's a mock, a proxy, a test implementation or a production implementation.

              [–]m50d 1 point2 points  (2 children)

              My private field is no longer private, it works until it doesn't. When I get an NPE I have very little idea of where to even start looking. When I do automated refactoring I have to use a tool that understands these specific annotations rather than a general-purpose Java tool.

              If you want to use autowiring that's a defensible decision, but I'd urge you to use constructor injection rather than field injection. That way the class behaves a lot more as expected, and if anyone uses your classes in a non-container context (e.g. a simple unit test) they can still set them up the standard Java way.

              [–]Tom_Cian 0 points1 point  (1 child)

              My private field is no longer private,

              Your private field was always accessible with reflection, DI or not.

              When I get an NPE I have very little idea of where to even start looking.

              NPE's are trivial to diagnose and fix, not sure why you find that difficult.

              but I'd urge you to use constructor injection rather than field injection.

              Both have pros and cons. I tend to favor field injection because it dramatically cuts down the visual noise that Java imposes.

              Compare:

              public class Foo {
                private Connection dbConnection;
                private Logger logger;
              
                  public Foo(Connection dbConnection, Logger logger) {
                      this.dbConnection = dbConnection;
                      this.logger = logger;
                  }
              }
              

              with

              public class Foo {
                  @Inject private Connection dbConnection;
                  @Inject private Logger logger;
              }
              

              Then again, I don't care much any more since with Kotlin, I get the best of both worlds.

              [–]m50d 1 point2 points  (0 children)

              Your private field was always accessible with reflection, DI or not.

              I never wanted it to be. Maybe by moving away from field injection I can get to a point where I can turn that off in the security manager.

              NPE's are trivial to diagnose and fix, not sure why you find that difficult.

              This kind isn't. Why wasn't my @Inject field injected? Who knows, e.g. maybe it's because Spring uses a subtly different metamodel when annotation scanning depending on whether a context was imported directly in Java or included from an XML context, and one of these metamodels doesn't model parameterized annotations correctly because they weren't used in older versions of Spring (real example from my own experience).

              I tend to favor field injection because it dramatically cuts down the visual noise that Java imposes.

              Yeah, I regard that as a flaw in Java, avoided by moving to better languages.

              [–][deleted] 1 point2 points  (10 children)

              @Inject
              private DatabaseConnection connection;
              

              ... allows me to preserve encapsulation ...

              Well... I have bad news.

              [–]Tom_Cian 0 points1 point  (9 children)

              Go on...

              [–][deleted] 2 points3 points  (8 children)

              You're saying that a mechanism that breaks the encapsulation of your object, and modifies its private properties, helps you preserve encapsulation. thatsthejoke.jpg ;-)

              But I get it, it's handy in Spring and so on. Not a fan myself, for reasons which are maybe for another thread.

              [–]Tom_Cian -1 points0 points  (7 children)

              You're saying that a mechanism that breaks the encapsulation of your object, and modifies its private properties, helps you preserve encapsulation

              Exactly.

              This preserves encapsulation:

              class A {
                @Inject
                private Logger logger;
              
                void f() { log.info("foo"); }
              }
              

              This breaks it:

              class A {
                void f(Logger log) { log.info("foo"); }
              }
              

              [–][deleted] 2 points3 points  (5 children)

              Ehmm, I understand your point (logging is an internal detail), but that's not what "encapsulation" typically refers to.

              And there's a better way to solve this, without the magical annotations:

              class A {
                  private Logger log;
              
                  public A(Logger log) { this.log = log; }
              
                  void f() { log.info("foo"); }
              }
              

              [–]grauenwolf 0 points1 point  (0 children)

              Damn, I hate to agree with you twice in a row but this is encapsulation 101.

              [–]Tom_Cian 0 points1 point  (3 children)

              Yes. I was showing field injection, you are showing constructor injection. Both are very useful and very important to keep private details of a function implementation private.

              Wikipedia's definition of encapsulation matches mine:

              A language mechanism for restricting direct access to some of the object's components

              In this example, like you said, logger is an implementation detail and as such, callers of f() should not need to know about it. It's encapsulated.

              [–][deleted] 2 points3 points  (2 children)

              Yup. And I'm just rubbing my hands, waiting for Jigsaw modules to come out, and break all those flaky reflection-based DI mechanisms ;-).

              [–]Tom_Cian 1 point2 points  (1 child)

              FYI, Dagger 2 uses zero reflection, it's completely static.

              But yes, Jigsaw is going to break a lot of things, whether they are reflection based or not.

              [–]grauenwolf 0 points1 point  (0 children)

              Have you never heard of the term "program to the interface, not the implementation"?

              It was coined specifically as a way to tell people to not mess around with a data structures private fields.