Hacker News new | past | comments | ask | show | jobs | submit login

Also have large Go systems in production, and I agree lack of generics is a problem. The biggest problem for me though, is the lack of testability.

As a simple example, the fact that log.Logger isn't an interface means everyone creates a wrapper just for testing purposes. DI is also complicated enough that in some cases, it's just easier to forget about unit tests and stick with integration tests.




That is a problem that interfaces can solve. I agree it's a bizarre oversight that the logging package doesn't define an interface, but it's also one very easily corrected, and since interfaces are implicitly fulfilled, the standard logger trivially meets the interface you define. We've found there's a few other places in the core libraries we expect an interface and there isn't one, but, again, fortunately it is trivially fixable.

I'm actually finding Go to be one of the testable imperative languages I've used. This isn't everything I've developed for it, but this is one of the fundamental tricks: http://www.jerf.org/iri/post/2923

Also remember that global variables are still Bad. If you've got a global Logger, you've already lost. Fortunately, struct embedding seriously mitigates the pain of passing things around and using them. While this will probably break apart into multiple objects as I continue developing, I've taken to having this sort of structure:

    type Services struct {
        *logging.logger // actually our custom logging package
        database.Database // locally defined interface
        connections.Tracker // registers connections or whatever
    }
I then have my primary objects embed a Services instance, so logging is just "obj.Error(...)". I pair that with a function that can construction a "null" Services, with a logger that doesn't log, a connection registration system that doesn't register, etc. Combined with what I linked, this enables even rather complicated interactions to be feasibly tested in relative isolation. I've also created some basic mock objects for some of the objects that may for instance assert that the Logger is called when expected, etc.

Technically none of this is impossible in other imperative OO languages, but Go has just enough twists on things to make it qualitatively easier, mostly implicit satisfaction of interfaces, which has had consequences beyond what I've expected. Embedding has had surprisingly pleasant consequences too; complicated objects can be safely built up with rich interfaces that do not require you to hardwire them together, or give you excessive access to the internals of the bits.


You can SetOutput the standard logger. Go's own "go test" does this.

As for DI, just connect your process components through constructors that take interface-typed parameters.




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

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

Search: