Hacker News new | past | comments | ask | show | jobs | submit login
A theory of modern Golang (bourgon.org)
130 points by imwally on June 9, 2017 | hide | past | favorite | 109 comments



In other words, use a pure dependency-injection style, and ZERO globals.

I'm writing Python and some C++, and I have been doing this for maybe 5-6 years. It takes a little practice to structure your programs like this, but once you know how, it has a lot of benefits.

It's not really idiomatic in those communities either. Python and C++ Libraries use globals all the time, although I think it's becoming less common.

I guess Java programmers have been doing dependency injection for even longer. It probably became popular more than 10 years ago. But I'm not really sure how "pure" the Java ecosystem is. Singletons are banned under this style. Instead, a singleton is just an object you instantiate once in main(), and pass throughout the rest of your program.

I think the problem with Java is lack of free functions. Static methods confuse things too.

The pure-dependency injection style is basically functional programming, requiring state as an explicit parameter.


> In other words, use a pure dependency-injection style, and ZERO globals.

And it's madness.

With this style, all the functions expose their implementation details, every single thing they use to accomplish their task. It totally destroys encapsulation.

Then you have the problem that if one of these functions decides to add one more thing, say log a message, then all its callers need to pass it a logger. And all the callers of its callers also need to pass a logger.

And of course, they need to find a way to materialize such a logger in the first place.

Insane advice.

Dependency injection is popular for a reason.


> It totally destroys encapsulation.

Not at all. Only the initializer needs to know about the needs. Then in the object world you can use a combination of dependency injection and factories to abstract over all the details you don't want callers to know about, and you can do the same more elegantly in the functional world with partial application (AKA currying).


> Then in the object world you can use a combination of dependency injection and factories to abstract over all the details you don't want callers to know about

And then you made Go a complicated and awful to read language


This is all common place in Go, we just don't use the terms that you have associated with heavy weight frameworks. DI is simply assembling all the pieces of your application in one single place instead of breaking it up arbitrarily in dozens of different constructors. The first is easier to read and test than the second.

Factories are just functions or objects that can create new instances of a thing of the number or configuration of that thing can't be known at compile time.


Sorry, forgot about mentioning the very language the OP was about. That was stupid.

You would use the "New" constructor function pattern in this case, using a higher order function called at injection time to keep it reusable and hide everything. I also prefer keeping the struct it returns as unexported so you get full encapsulation, but golint doesn't like it. I guess this is a personal issue.


> You would use the "New" constructor function pattern in this case, using a higher order function called at injection time to keep it reusable and hide everything.

Spot on. I love the decorator pattern for dependency injection.

    type Server struct {
    	logger *logrus.Logger // optional
    	store databaste.Store // required
    }
    
    type ServerOption func(Server) Server
    
    func WithLogger(logger *logrus.Logger) ServerOption {
    	return func(s Server) Server {
    		s.logger = logger
    		return s
    	}
    }
    
    func NewServer(store database.Store, options ...ServerOption) *Server {
    	s := Server{store: store}
    	for _, option := range options {
    		s = option(s)
    	}
    	return &s
    }
    
    func main() {
    	myServer := NewServer(myStore, WithLogger(myLogger))
    }


> With this style, all the functions expose their implementation details, every single thing they use to accomplish their task.

Not at all. Under the author's guidelines, a function can encapsulate whatever it wants, so long as it doesn't modify state or have side effects. If it wants to do either of those, it should declare its dependency explicitly. I don't see anything so insane about that.


That is why I said you need practice writing in this style. It's quite easy to overdo it and end up with a mess.

I don't think it has to an all-or-nothing thing. I generally encapsulate all my state, but not all side effects, like printing to the screen.

And yes I believe logging should just be a free function. I think configuration of loggers is overrated. But if you need to use a global for that configuration, so be it.

Functions don't expose any implementation details -- they expose the state they modify and the side effects they produce.

I don't quite understand your last sentence. Dependency injection IS popular and that is what I am advocating and what this post on Go is advocating. It's perhaps not as popular in Go and Python as it is in Java, and perhaps even some corners of the C++ world.


Well, pure DI also has practical downsides. E.g. anything that wants to log something has to be able to access the logging configuration — so it needs to be passed into everything.


I would say that logging and memory allocation are special cases. They're things that need to be done by literally ALL your code.

I agree it would be completely silly to change the signatures for a bunch of methods just to print something.

But sometimes you absolutely DO want to inject dependencies for things. The Lua VM is parameterized by the allocator it uses, which is useful in practice.

And I would distinguish between logging and program output. Logging can be an exception, because it doesn't need to tested. You probably don't want your unit tests to fail if your debug logs change.

But maybe in some situations you have some really critical logging, and you want to test that part. Then you can parameterize that code by the logger.

Although honestly I would usually just write a shell scripts and invoke "diff".

So yeah I don't agree with absolutely pure DI or pure functional programming. But I definitely think the ideas there will help you structure better programs.


Logging can be different, and that's okay.

It is non-interactive side-effect and irrelevant to the function of your code. It's just for diagnostics, like a debugger.

If that is not true, I wouldn't call it "logging" for the proposes here.

You could inject a logger, but I don't see a reason to.

https://softwareengineering.stackexchange.com/questions/2759...


Logging is absolutely not irrelevant to the function of your code. It's a cornerstone of system observability along with instrumentation (metrics) and tracing. It deserves to be unit-tested, same as anything else. Loggers absolutely deserve to be injected and treated the same as any other first-order dependency.


Eh. You can use gdb for tracing too.

I don't see any hard in gloably DI'ed system observability.


*harm


You solve that with constructors that inject the logger one time, as a matter of convenience. This has all the convenience of a global without the hazards.


I just pass an "app" object down the tree, and the dependents can do app.logSomething("foo"). So basically just move all globals in the descendants to methods on the root app node.


This is also where I always end up with using DI, and it feels like cheating. Basically pretending that these globals aren't.


But they aren't! If someone comes along and wants to run two copies of the app that share some object they can. And in a super straightforward way. As a maintenance programmer, I love these sorts of things. It's the difference between weeks of work and days.


I disagree. Nothing gets messy faster than a big bag of state that gets passed all over for no reason. This is no better than the global issue. The correct solution is to inject what you need and build convenience functions to reduce boilerplate. I have serious doubts about your weeks/days quip; my experience is diametrically opposite.


To be fair I more meant a giant tree of state where each module is responsible for managing the state for other modules it communicates with (for example a module will take it's bag of state for most every method; in an object oriented language - a luxury I don't often have - this bag of state is called an object (and methods that don't take it would be called static methods)). Not a flat state object (obviously terrible).


Fair enough, but my beef wasn't about the shape of the large amount of state or its organization, but rather that the whole thing is passed into everything when most things aren't needed. This makes it difficult to reason about what the true dependencies of a function are.


Yea, but passing a large object around is what I was talking about. Still not great, but when you deal with a 200k LoC library and all of it's API functions take that single large object, it's way better than having to mess with the global state of it. Like in one of those cases I can add naive multi-threading (two copies of the large state object), my own custom functions, and split out functionality for reuse by other systems. In the other I can't without understanding and likely rewriting the whole thing.


Interesting. How else could DI be done? I'm in the camp that 0 params to a fn are the best, followed by 1, occasionally 2, and almost never ever 3.


Ah, yeah, the good ol' fashioned

    public void doStuff() {
        // ...
    }
Everyone loves those!


The number of params to a function doesn't seem like a particularly meaningful metric to optimize for. It seems like a proxy for something else at best.


But aren't you cluttering your "app" object then? Sure, your interfaces are pretty stable now since you pass "app" around. But now "app" leaks all kinds of functions to all callees, which don't really need all functions declared in "app".

If on the other hand your "app" object is quite lean, and follows the behavior of maybe Context in Android, then ignore my above comment, and I would agree with you.


Another problem with large Java DI framesorks is that they obscure (with xml and weird conventions) a fundamentally simple operation. They also often resort to state-changing property-setters versus pure constructor args.

Dependency injection - is just making all the dependencies of a class/function explicit on construction, with a secondary affect that an implied ordering is needed to creating complicated nested objects.

Avoiding hidden state-channels (globals) that can't be easily modified is valuable for being able decipher/reason about behavior, and for mocking test instances.


Most Java DI doesn't use XML these days. Spring projects typically use java config. There's also Guice, Dagger, and if you're using Kotlin, Kapsule is even better than the others.


I've learned about Spring 7 years ago and I never used singleton or global variable since then except for thread-local caching (because incompetent designers decided that SimpleDateFormat couldn't be thread-safe) or something like that. Even if my application is small enough so I wouldn't use Spring or another DI tool, I'm just passing dependencies through constructor and it works just fine.

I guess, many people did it long before.


Yeah as I alluded to, libraries are the thing that hamper this style. You don't control all the code in your application. But it's not too hard to work around.

I also think language-supported thread locals are superfluous in this style. Instead, just create a single object that runs into a single thread. Then you don't pass the object to any other threads. The members are automatically thread locals.

If you have N threads, then you have N objects confined to those threads. There doesn't need to be a separate concept of thread-local IMO.


Yeah, this is a huge benefit, especially in a language like Go where concurrency is cheap and common.


>I'm just passing dependencies through constructor and it works just fine.

Eventually its gets unwieldy when you have lots of classes. I've worked on old code in that style (DI w/o a library), it produces a lot of boilerplate. Piles and piles of "container" classes filled with deps getting passed around gets real old, real fast.

I strongly encourage people to use a framework like guice.


There are two parts to the problem. The first is that many languages (like Java) require a bit more boilerplate than others (like JSON) to express a composition of objects. The other problem is that complex applications have a very large number of compositions, so the overall pain is the product of the number of compositions and the boilerplate per composition. The number of compositions is a necessary evil--a framework won't fix it, but a framework can fix the per composition cost by moving the compositions into a cleaner language. Go solves this problem without a framework simply by having convenient syntax for composition.


> Eventually its gets unwieldy when you have lots of classes.

Yep, if application started to grow, it'll require significant refactoring at some stage. But many application won't grow and using framework for something like 5 services looks like overkill for me.


Have you found a good writeup on using Python like that? I was talking to someone the other day about this, and how it's not that idiomatic or seen as a big deal in Python especially because you can easily swap out globals from the calling / test site by e.g. setting module.Dep = MyDep so that when module's Foo class has a method bar that constructs a new Dep, it gets your MyDep instead.


This drives me crazy. Rather than a uniform separation of object definition and object graph assemblage, we'll just weld arbitrary parts of the object graph assemblage to arbitrary object definitions and write a unique snowflake hack in each test case to work around it, all because we can. The lack of consistency will make the entry point every bit as illegible if not worse, but surely if we weren't supposed to write code this way, Python would make it difficult.

Honestly, this is my favorite argument for static typing. Stupidity should be difficult.

I say this as a Python developer. We waste a lot off time debating this in code review and debugging this junk code when it inevitably leads to bugs or inexplicably broken test cases. Static typing would have precluded these hacks (and others).


I was going to write that I don't have a good pointer, but actually I think the idea is basically the "onion architecture" or "clean architecture". I don't recall if I watched all of this talk, but in general I like Brandon Rhodes' PyCon talks :

https://www.youtube.com/watch?v=DJtef410XaM

http://rhodesmill.org/brandon/slides/2014-07-pyohio/clean-ar...

I don't come from a Java background, but I think that many of these ideas originated in the Java community. They came to Python later (if at all), and it's a little funny to see Go struggling with the same thing.

I think I came at it from more of a functional programming perspective, and the style gradually converged with what people were thinking about OOP. I'm not actually of all the details about "clean architecture" and "onion architecture", but they are definitely related to dependency injection.

Basically all your effects and state can be instantiated on the "outside" in main() and passed INWARD. But I don't believe it necessarily has to be pure. I encapsulate all my state, but I don't encapsulate all my side effects, like printing to the screen.

Some people appeared to like this comment, which is somewhat related:

https://news.ycombinator.com/item?id=11841893

I'm not sure how much this will help, but here is 12K+ lines of code I wrote with zero globals:

https://github.com/oilshell/oil

The main directories to look at are core/ and osh/. The main program imports a lot of stuff and wires it together:

https://github.com/oilshell/oil/blob/master/bin/oil.py

(There is a bunch of surface messiness, but the architecture is sound.) I've also written even larger programs with zero globals.

Also, I believe that web frameworks are some of the worst offenders in terms of preventing you from using this style. I don't think I've seen a single web framework that made the handlers easily testable.

I think you may be fighting against the grain in some parts of the Python world using this style... I can do it in certain projects because I'm writing most of the code from scratch, and forking some dependencies.


Thanks. I skimmed the slides, it looks like he's saying three things. 1 is that dependency injection is doable in a few ways (he didn't mention mine) with Python, but it's kind of tacky. 2 is that what you really want is functional programming so that you don't need to do dependency injection at all, or at least not on as many things, the dependency is just data and pure functions won't change output with the same input (so can't do I/O which is one of the bigger common dependencies to factor out)! (Reminds me of Clojure's value of values.) Tests are easier with functional programming (and not stated but I like that it encourages doctests too). 3 is that the "onion architecture" (I think this is the same thing as the "hexagonal architecture" which you can find around the internet with pretty high levels of praise) is great and helps with this and other goals of good design. All 3 I agree with, though only in principal on #3 because I haven't had a chance to try it all the way in a large system, and I feel like it'd be a lost cause trying to bring it into an existing legacy system without full team support. We just need to get our crap under test first so the fires can die down, worry about better architecture refactoring where testing can fall out naturally later.

I came from Python but have worked a lot in Java (and primarily in Java for the last few years). It took me a while to accept dependency injection but now I see it as fundamental to good Java design. But I don't see the same for Python. My reasoning is that it comes down to the difference in static and dynamic typing and how imports work. Maybe even mostly on how imports work, since DI did take off in Angular for JS, but if JS had a module system it might not have needed to like we see with Python. Ideas that quickly spread and cross quite different language boundaries are the ones I pay more attention to (like the industry shift to more functional designs and patterns, "composition over inheritance" when OOP is still in heavy use, and so on), DI hasn't been one of those.

The canonical example of dependency injection in Java is having a method that constructs a new Date object (or something that is based on the current system time) and does something with it, and asking the question of how you would test it. Well the JVM is pretty dynamic under the hood so if you have to (useful for legacy systems or third party code you can't really modify) you can cheat with something like PowerMock and overwrite the bytecode for the built in Date class with your mock one. But normally, such usage is frowned upon, just change the code under test. So DI comes into play where you bring either the Date class or some common time-related interface up into the method signature argument, and you provide the value in your test. You may or may not have another method for prod code to call that supplies a default value, depending on whether you want the burden of giving a good default (if such a thing makes sense for the use case) to be put on the client or not.

When you have dependencies that your whole class uses, then it's common to put those on the constructor so clients will pass you in the object, instead of you making one yourself. Frameworks like Spring make that slightly easier to work with (especially when you get into cross-module service classes you want to depend on) by letting you just specify a setter() and it will magically make sure it autowires an object for your class methods to use when needed. You can easily resolve circular dependency problems this way too, A <-> B is resolved by having two more modules for IA and IB that each of A and B depend on instead with something behind the scenes to make sure it all wires up correctly. Interfaces are great since now you can depend on much less. If you want a mock, just implement a test class implementing the interface. You can use the less powerful Mockito library to help only mock out the parts of the interface you actually need to test (but this implies access to the source so you can know such things about the implementation). With interfaces though you can in principal even test while treating the function as a blackbox, and if you can prove that you've passed all possible states for the dependency interface you can prove interesting things. Or just fuzz it. Or be allowed to have your IDE helpfully autocomplete types and suggestions.

Python doesn't have interfaces, being duck-typed, nor does it force static typing. This alone makes DI less useful. I can write:

    def unix_time(time_dependency):
        return time_dependency.time()
and have it live in its own file, nothing else in it. Java would throw a fit, it needs to know what type time_dependency is. Python doesn't care, as long as it can call the .time() function on it. I think this is great, but some programmers are terrified. But anyway it's following DI and assuming time_dependency.time() is pure (it may or may not be) would make unix_time also pure itself. But it's not very helpful if you're looking at the signature or even the full source itself. Something more helpful would be:

    import time
    def unix_time(time_dependency=time):
        return time_dependency.time()
Then you at least have an idea what to pass it yourself besides just something that can have .time() called on it, or you can pass it nothing. Something you would see in Java, but with the same method names instead of the different ones that Python requires:

    import time
    def unix_time_internal(time_dependency):
        return time_dependency.time()
    
    def unix_time():
        return unix_time_internal(time)

But the most common implementation in Python is probably this:

    import time
    def unix_time():
        return time.time()
The Pythonista in me also thinks this is in fact the best implementation. It's just as testable as any of the others. As I mentioned in my first comment, in your test code, instead of passing in a time dependency directly, or using a mock/patch library that starts to smell like PowerMock, you can literally just call (assuming the function was in a file called timeutil.py) timeutil.time = myTimeDep and then call timeutil.unix_time() with your substitution used. This is because unlike in Java where imports are definitions for a compiler to lookup and insert things about, in Python they are global (to the file) variables, and they can be redefined, and with Python's dynamicism it looks up the name of the thing each time to get its value instead of doing it once and coding a static reference to it, so your redefinition will take precedence. Of course this pattern isn't even that unusual in non-unit Java tests, frequently the test will save the current value of something and overwrite it in the setup, run the test, and then restore the old value in the teardown. At least in Python you can have a clean decorator for that.

In your code I opened util.py at random. Looking at GetHomeDir, I can't test this in the Java sense, it has dependencies in its implementation on os and pwd. But that's ok, because unlike in Java, those dependencies are actually global (to the file) variables that I can mutate. Plus you probably tested it manually in the REPL, another development benefit Python has over languages like Java, you can build and test things bottom up sometimes. Thanks for the code sample though, I'll keep it around for future reference. (Would you say it's common these days in the community to have test files living next to the implementation files? The only place I see that these days are with JS components.)

Probably the initial resistance I had with DI is that taken to the ultimate extreme, DI would take the form of a style of functional programming whose name escapes me at the moment, but essentially one where everything is passed as a parameter, including language built-ins, and only at the top level where you define a main() can you specify all of these things that the language will inject when it runs your program.

When I work with Python, I don't see a problem with not having the same DI toolkits or seeing projects not following it very well. No more so than not having privates or a culture in love with a sea of getter and setter methods. But I can see why some might disagree, like your sibling comment, and cry out for static types as the answer. Well, there's Python 3, not sure how much it gives you for this, and there's a ton of other static languages out there, I'd suggest working with those and leaving the Python community to do its thing. Functional programming in general, though, that's the popular thing in pretty much every language in modest use at this point, it's good stuff. A lot of bugs in old Python code I wrote could have been avoided if I had written in a more functional style, probably not as many if I had followed DI more rigorously.


Addendum: I get that you can test anything by monkey patching in Python, and I definitely used to do that in setUp and tearDown.

But I guess I have stopped doing that, in favor of testing against my own interfaces, using integration tests with shell scripts, or the odd param that is only for testing. I think I just like to have some indication in the code of where I tested something, and it doesn't cost very much and doesn't come up too often.


Yes I totally get what you are saying. At one point I might not have written the last unix_time() example, but that is my default now, UNTIL say I hit a bug and need to test it thoroughly.

GetHomeDir() is a great example that proves that point... if I were trying to be very pure, I would have reified the pwd dependency and environment dependency.

One reason to avoid the complication is that I'm using shell scripts for tests. So in shell you already have a test environment. You can set $HOME to whatever, and in theory /etc/passwd, although that's a little trickier.

Actually that was one of my motivations for writing a shell :) You can test "one level out" at the OS level. Instead of the "seam" being the language, the seam is the OS.

I prefer to test against STABLE INTERFACES, not against things that I made up internally. You don't want your tests to calcify the structure of your code. I've seen that happen a lot with fine-grained testing, and it's a big pitfall.

I would say at the beginning, I write unit tests for tricky parts, but don't aim for 100% unit test coverage. I aim more for high integration test coverage. And then at the end of the project, when you are fixing bugs, that is when you can do fine-grained testing without worrying about making a mess of your code.

Here I did parameterize random() (irr_rand), because it's very important to the function of the code and needs to be tested:

https://github.com/google/rappor/blob/master/client/python/r...

So it all depends on the context. That's why I say it takes some practice. It takes practice to:

- not end up with more than 3-4 parameters for each class.

- not end up with too many classes. Java code seems to fall into this. A lot of things are just functions with dependencies. Actually this style I think reduces the need for classes -- they are your parameters rather than being your context!

- not structuring your code as a deep tree of calls. Instead it should be a relatively flat object graph.

A relatively static "Object graph" is really the idea that distinguishes OOP from "structured programming" (i.e. a pyramid).

I think I would differ with you in that I don't think DI and functional programming are that different. I think they are trying to get at the same core idea.

Things I ALMOST NEVER use in Python:

- setters and getters.

- Especially, setters for dependency injection. I always pass params through constructors.

- decorators. This can always be accomplished with composition of objects. I find that style a lot more readable. decorators are non-trivial code at the global level, when it really should be in main().

- classmethod and staticmethod. Static methods should just be functions. Class method is kind of a hack for singleton-like behavior.

- As mentioned, the singleton pattern is banned. Singletons are just classes you instantiate once.

I like that this style gets rid of a lot of concepts: thread local (as mentioned above), explicit singleton pattern, and "static/class methods".

Also I think it's true that Java's static type system might get in the way a little bit, but I don't have a strong conclusion on that. However, I also wish Python were a little more strict. I don't use 90% of the dynamism of classes.


If you are using spring (or any of the less cumbersome DI frameworks) this is exactly true. Generally in Java DI frameworks you make a bunch of factory methods for your logic classes(this may be magically done through annotations). A singleton in this approach is just a factory that always returns the same object.


What I don't really understand is why you need a DI framework in Java.

I use this style Python all the time with zero frameworks. You just make the objects in main() and make the objects in your test main(). Use helper functions if necessary.

I do think Spring was very influential because I remember other people in the mid-2000's talking about testing, dependency injection, and Spring.

I recall lots of people using Guice at Google but I never used it since I did very little Java. I feel like there is something about Java where you need a DI framework, and in Python you don't.

I don't feel like you need one in C++ either. Or it might not even be possible to write one. I think LLVM is written in C++ in a style with zero globals. It's a library-based design.


Python has had closures for a long time, which make great ad-hoc factories that can capture state from upstream functions without making state visible along the way.


Yeah that can probably help. Another thing that helps is keyword arguments, so you avoid the verbose builder pattern that's idiomatic in Java.

I was going to say that I don't use closures in Python all that much, and Python has some issues with closures (e.g. nonlocal is ugly, and it wasn't even there until Python 3).

But after watching a recent PyCon talk [1], I realized that the "bound method" feature of Python is a useful special case of a closure. And he makes the good point that the code is shorter than JavaScript which requires an explicit anonymous function or bind().

[1] https://us.pycon.org/2017/schedule/presentation/768/


I've always thought that a large percentage of OO design patterns originated from Java's strict OO trappings.

Since Java doesn't allow for first-class functions, it necessitated design patterns in spite of a more straight forward constructs in more imperative or function languages.

But this is likely just me and my biases. (Note: I don't hate Java, but it does feel constraining at times.)


This is basically "Use DI, and move towards SOLID"

Well, yeah! Those are great principles in any language - I find it concerning (and maybe indicative of something) that the Go community has voted this up. These are fundamental requirements to building testable, scalable systems in teams. Having global variables has been bad practice for what, 20 years? One of the first lines in Javascript, the Good Parts (closest book handy) is:

  JavaScript is built on some very good ideas and a few very bad ones. ... The bad ideas include a programming >model based on global variables.


It's relevant because the Go community needs to rebuild all this, and is re-discovering that they can learn from existing patterns that many claimed were irrelevant because Go was so simple and so different.

Plus the lack of runtime control means you are essentially forced to do explicit DI, or you have an untestable mess. Hopefully the DI libs will catch up to e.g. Java's sophistication within a year or two.


Explicit DI is far better than a DI framework is your languages supports a sane syntax for composing structures. Java is a boilerplate-laden language and so it profits from a DI framework whereas Go doesn't. Building a DI framework isn't hard (contrary to your post, Go has the necessary runtime support), simply no one is interested. :)


What do you mean by "sane syntax for composing structures"? I use both Go and Java at work, and when it comes to constructing complex objects they both seem to have the same amount of boilerplate.


Go has struct, slice, and map literal syntax.


You know that most languages support those things right?

And they have virtually nothing to do with DI?

If you are going to make an argument for more natural support for DI in go than other "strongly" typed languages it starts and ends with structural typing.


Java doesn't. C# doesn't. C++ doesn't (or maybe it does as of the last few years). And the languages which have them (or something passably close) don't use DI frameworks (Python, JS, etc). They might exist in those languages, but they're fare from common. The point is that DI frameworks reduce boilerplate. Go head less boilerplate than, say Java.


Are you suggesting that the fact that the language has special syntax for initializing a few types means it has better support for dependency injection?

I don't even understand the basic premise of that argument. It seems nonsensical.


I wouldn't put it that way; it simply means the ROI for a framework is lower, and in Go's case, it's zero or less. It makes perfect sense because Go's syntax servers the same main purpose as a DI framework--to reduce initialization boilerplate.


C++ has had it since C++11 with uniform initialization syntax. You can even create literals for your own classes, which you can't in Go.


Thanks for clarifying


How does having struct literals help with DI? Can you give an example?


The primary purpose of a DI framework is to alleviate boilerplate by offering a simpler mechanism for describing compositions than provided by the language. If a language already has a handy initialization/composition syntax, you don't get anything from a DI framework. Different frameworks have different bells and whistles, so this is a general point.


Again, I find for constructing things Golang is no more or less boilerplate-y than Java. Can you give an explicit example (like, with code) where Golang's struct literal syntax allows you to construct objects with less boilerplate than Java?


Sure. Anything with hash map or list literals (heaven forbid a list of hash maps!), off the top of my head. I'm not near a computer or I'd type out the example.


Well Java does have array literals. They don't have hash map literals, but I really don't use map literals very often in Go except for constants. And you can make a HashMap in Java in the same number of lines as Golang (though not for an array of hash maps). I really don't see that as a killer feature.


Arrays aren't equivalent to slices in Go. Map literals are nice for things like the driver pattern (a map of strings to some interface type; the user specifies which driver implementation should be used to handle some input). It's not about the number of lines, it's about having a declarative initialization syntax. In other words, it's about the number of expressions.

This isn't a killer feature, it jjust means Go requires less boilerplate when doing explicit DI, so the ROI offered by a DI framework is low. If you don't agree that initialization in Go requires less boilerplate, maybe you should be asking yourself what you gain from your DI framework?


DI frameworks were not originally introduced to replace boilerplate.

They were originally intended to move the behavior changes provided by DI from compile time to configuration time.

This was especially valuable when you are delivering enterprise software to sites you don't control and you need to support a wide array of integrations.

They also happen to reduce boilerplate & DI can help with test-ability so they were adopted for that as well, but if you are only doing DI for tests and only using a framework to reduce lines of code it's a fairly widely accepted anti pattern.

I believe your basic assumptions about DI are wrong. The reason it hasn't taken off in the golang world is that the kind of software it was used for is less common & if you are writing it you'd not use golang for a host of reasons, none related to initializer syntax.


oooh no, people are interested. Go DI frameworks are appearing rapidly. Simple ones are indeed simple (and often all you need, which is great), but that's nowhere near all there is to the field.


Can you explain how a library for dependency injection could improve a Go standard library like net/http, io, encoding/json, or database/sql?


It wouldn't. It is for when you want to swap out that library, or test business logic using that library beyond simple unit tests.

DI is also great for creating modular code you compose, instead of monoliths. It encourages the use of interfaces and information hiding - you should look into it, it's a very powerful technique.

Instead of asking how it would improve an arbitrary library, ask yourself "how screwed am I if 100% of the API surface for this library​ changes tomorrow?" Dependency injection would help you out of that faster that rewriting everything.


this article falls into a particular genre "sensible advice that is either so internalized in other languages or the language itself prevents the pathology so why would you write that."

It's a winning combo because you can recycle old and simple ideas for a whole new context.

[edit] felt bad because rereading this it implies the author has bad intentions. Don't mean that. Just mean it's cute to watch the golang community learn Java.


In some ways, I consider Go to be a bit of a redo of the early ideas of Java, but with a bit more focus on tooling, and a stronger aim towards simplicity. And Go's standard library authors have the benefit of seeing how Java's early days played out.

I do enjoy that Go gives a bit more obvious control over memory layout than Java did, and I enjoy using it as a small projects language. I've never used it in full anger/production, so I haven't seen the worst of it.

One funny thing about Guice(mentioned elsewhere): I've heard more than one Googler say that dislike the framework quite a bit, due to how heavily it can be abused.

One bit of advice that seems to have gone either missing in Java/JVM, or common knowledge, is the idea that only main/binary packages should have config options via flags/env vars. Many, many JVM libraries do not follow that convention, and I've had to hunt down a log4j XML file for Spark in order to turn down the logging levels before. Would much rather have that be controlled via a flag.


Yes, flags vs. config files in Unix are a classic example of dependency injection vs. side effects, but at the process level rather than the programming language level.

At Google pretty much all server configuration is done with flags. Binaries can and do have thousands of flags. And I view that as a much better solution than a config file that is searched for.

The idea is that modular units like processes and functions don't "search for configuration", they just accept parameters, and led the higher level application configure them. Then the code becomes very straightforward to read and debug.


Go has had syntax support for DI since day 1 (map, slice, struct literal syntax). JaVA has to choose between frameworks and boilerplate.


I'm not sure what you're describing, but it's not Dependency Injection. People are downvoting you for ignorance, which is unfair. Here is what we are discussing, it does not relate to value object declaration syntax.

https://en.m.wikipedia.org/wiki/Dependency_injection


You use the syntax to inject the dependencies. I'm not ignorant about DI, I was probably downvoted for being unclear. I could also imagine some people down voting because they don't understand that DI is a general concept and not a framework or class of frameworks (at least this confusion seems common).


I think many Go converts (myself included) came to the language because it's type system (composition, structural subtyping), syntax, standard library, and conventions all work together towards DI/SOLID.

I interpret these upvotes as more of a "Yes, we get this right" than a "Wow, what a great idea!"

In particular, Go programs are much more likely in my experience to be SOLID/DI than JS, Python, Java, C#, etc, mostly because the language was designed to support it.


I don't think, in the limit, that this is a scalable approach. While I think injecting dependencies is fine in some cases, large programs might have hundreds or thousands of collaborators. It is simply not feasible to instantiate all of them in the main function then pipe all of them down to the package that uses them. This is especially true when you keep the interface small like this program seems to.

I think making the dependency explicit is good when it is likely that it is going to be desired to change the implementation on a per object basis. If it's unlikely or meaningless to have multiple different implementations within the same process, there's nothing wrong with making the configuration global.

Imagine how painful it would be to use the http module if it required the injection of its clock, compression suite, network interfaces, address resolvers, loggers, etc. And that's just one module of many.


I think the author's two examples - a logger and a database connection - should be handled differently from each other. The database connection should be passed as a parameter, and the logger should be a global variable.

One difference is that it's reasonable to add logging to nearly any function anywhere, which means that you have to either make the logger global, preemptively provide a logger parameter to almost everything, or else change a whole bunch of method signatures at once every time you need to add logging to somewhere that didn't previously have any log statements. Global variables are unfortunate, but the other options are much worse.

The second difference is that you pretty much only ever have one logger object in a process, but processes often use multiple database connections and it may matter which one is being used. For example, a caller may want to make your function's database accesses part of its transaction. Or use a unit test database instead of a real database. Or a number of other things. Omitting the parameter hides the fact that a function uses the database, which is an important thing to know about it.


You may have many different loggers, and different loggers for different parts of an app. For example, you don't want to log certain database or password related stuff explicitly as it creates a security issues.

Also it's a lot easier to test logging, or anything that you would us a global object for, if it's DI based. That said, you can always pass a config object that holds what would normally be global variables but allows you to override.


I agree with every bit of this, especially with constructors taking their dependencies explicitly. In fact, I try to write my Go code in such a way that constructors aren't needed (sometimes this can't be helped) so the "constructor" is simply a type initialization expression `foo := Foo{Bar: 7}`. This keeps everything simple and testable.


Absolutely agreed on the ridding of constructors. Offer the option to set things (eg the Logger), and if it's nil, use some defaults. This seems much cleaner than making a huge function signature. The net/http package does this really well.


Are you going to do the nil check in every member function though? I run into this problem with Go a lot, actually. (I'd like to make initialization of a some member variable optional, but checking if it's nil every time I use it becomes a pain. Not all types have useful zero values either... I'm looking at you, map.)


Well, in my cases I usually add an unexported 'check()' method, which is generally called by every other method that needs state. It would basically just be something of x.Z == nil {x.Z = DEFAULTZ}


I favor an DI style as well, but sometimes in rare cases I like using the init functions.

This boils down to functions which follow the template.Must pattern, i.e.: either the function can be evaluated or the program should panic.

The other case is read-only lookup tables.


I've recently been bit by global state that used function initializers (func init() isn't the only way to execute logic when a package is imported!). The package used very reasonable defaults, but panic()'d when things didn't look right. So, forking, maintaining patches until upstream changes the behavior, painful.


> func init() isn't the only way to execute logic when a package is imported!

What are other ways to execute logic when a package is imported? init() is the only one I'm aware of.


package globals

var ( X = blah() )

func blah() int { return 123 }


Those are all appended into the Package.init function in the compiled .a, so they are part of func init() not just in behavior, but in implementation as well.


Ha, you're totally right. I'm silly. Thanks!


> "modern Go"

The language was first released in 2007 :). Man...


It was developed starting around 2007 but it wasn't released / open-sourced until late 2009.


This isn't "modern go" but more the "standard" in any language.


Sadly not in Python or JavaScript or C++. But yes, definitely good ideas for any language.


The stdlib does include some globals/statics, for good reasons (the default logger, the default http mux).

I'm always wary about advice that says "you must never use {this tool provided by a language}". Never say never ;)

Globals can be and are useful in some circumstances. Having a blanket ban on globals because "globals are bad mmkay" is not useful.

I draw this line at "do I need to mock this for testing?". If I need to mock it to run tests, then it shouldn't be a global and should be DI'd (so I can DI the mock). If I'm not going to mock it, then it's probably fine to leave it as a global.

The Db and Logger are perfect examples: I frequently mock the Db connection, so I can test sql output. I almost never mock the logger, because I only use it to output meta information. So having a global logger is OK.

YMMV...


Shouldn't need a DB connection to create strings. Sometimes want to turn off, scope, or classify logging especially during testing.

When these new levels of granularity are desired in your project, switching from globals is no fun.


The author is basically complaining that Go (and its standard library) isn't functional.


An internet discovers dependency injection. Hackernews responds, "Well of course we knew THAT." The Rust Evangelism Strikeforce is off on a secret mission in the Balkans.


"DI isn't idiomatic in Go" is what I've heard quite a few times. Java did it so it must be wrong.


Which pretty much confirms that Go is engineered to do things the stupid way around, and that developers inculcated in its idioms are just now beginning to see the benefits of what boring old Java programmers have been doing for a decade or more.


I've only heard this in reference to DI frameworks. DI is very idiomatic.


That's not dependency injection, it's dependency passing.


It's the method of DI known as Constructor Injection. https://en.wikipedia.org/wiki/Dependency_injection#Construct...


Expected to see an article about the development of modern Go the board game theory after the advent of AlphaGo and AI generally. Got disappointed. Happens every time :(


Ok, have a 'lang'.


Not cool. That's not the name of the language or the article. Please change it back.


It's common enough to have become a convention, especially where ambiguity is an issue, like in HN titles.


Me too :(


Perhaps blog posters on the Go language need to refer to it as Golang in titles, etc.


Why? The name of the language is "Go" and not "Golang". "Golang" is a search engine hack.


Clarity has value. "Golang" in the title provides more information to the reader at no cost.

Why not use "Golang"?


> "Golang" is a search engine hack

Although "Go" is the name of the language, "Golang" can also be a submission title hack so as not to confuse those who "Expected to see an article about the development of modern Go the board game theory".




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: