4 ms·
While I agree there aren't really any new concepts, I can think of two areas in Go that C++ and Java don't really prepare you for: error handling / nesting and
by Xeoncross 4y ago
While I agree there aren't really any new concepts, I can think of two areas in Go that C++ and Java don't really prepare you for: error handling / nesting and classes. Go doesn't use exceptions or classes for anything really. Instead it's all DI/object embedding + interfaces and nested error values + context which can be more of a mind hurdle than expected since inheritance and try/catch are so common these days.
- latchkey 4y agoAs an old Java head, I found FX [1] to fill a missing DI gap for me. Some can argue that it isn't really needed in go, and I tend to agree, but after using it in a few large projects, I find that it encourages a good separation of concerns that enables things to be easier tested as well as prevents cyclical imports. [1] https://github.com/uber-go/fx https://github.com/uber-go/fx
- arwineap 4y agoI've found that fx is very popular with java devs Can you help me understand what this style of dependency injection solves for in java? I use packages to encourage separation of concerns
- latchkey 4y agoIf you start to write pkg.NewFoo() inside of functions, you get into a whole object dependency mess that makes testing really really difficult. Having something that encourages you to pass in constructed objects (ala: DI), in the constructor, it ends up being much easier because you can more easily mock objects as necessary. Obviously, you can use that same pattern on your own without fx, but going all in on fx really kind of forces you to do it. Bonus fx points for being able to start/stop services as well. However, this tends to factor more into single binary apps (which is what I've built), more than say restful web endpoints (also built, but don't use this feature of fx as much).
- randomdata 4y ago> Obviously, you can use that same pattern on your own without fx, but going all in on fx really kind of forces you to do it. Unless you are trying to add tests to legacy code, where you also wouldn't have much say in the use of fx, wouldn't the stated testing difficultly already force you away from that dependency mess?
- latchkey 4y agoI've found that unless I write code in golang in a way that was intentionally written to be unit tested, it is very hard to bolt on unit testing after the fact. I'm stuck with effectively just integration testing. I don't envy you working on legacy code. struct Foo { } func (f *Foo) doSomething() { client := pkg.NewClient() client.doHttpRequest() } vs. struct Foo { client Client } func (f *Foo) doSomething() { f.client.doHttpRequest() } func NewFoo(client Client) *Foo { return &Foo{ client: client } } var FooModule = fx.Provide(NewFoo) then to use it... struct Bar { foo *Foo } func NewBar(foo *Foo) *Bar { return &Bar{foo: foo} } var BarModule = fx.Provide(NewBar) how do you unit test the first example? There is no easy way to that without having the client do things special internally, which leads to a rats nest of issues and doesn't scale. Easier to just pass in a mock client and make sure the right http request/responses are being done. fx kind of highly encourages the second approach for all of your code. It is a bit more boilerplate, but by default, I'm always going to be thinking about passing my dependencies around instead of just being lazy and doing NewClient in the function. I also get to take advantage of the fact that Client is just magically passed into my constructor. If there is some error doing so (like I forget to Provide), then fx will give me errors at start up. My dependency chain is clear. It just feels nicer to me. Once again, of course I can write it the second way without fx... but it is kind of nice that every time I want to use Foo, I don't have to type NewFoo() in a constructor... I just reference *Foo. By the way, mockery [1] generates super easy to use mocks for exactly this workflow. [1] https://github.com/vektra/mockery https://github.com/vektra/mockery
- randomdata 4y ago> I've found that unless I write code in golang in a way that was intentionally written to be unit tested, it is very hard to bolt on unit testing after the fact. Absolutely. This is true in most, if not all, languages. But when you're in the driver's seat, able to make choices like whether or not to use fx, why would you bolt on testing after the fact? Not much you can do about legacy code, if that's what you have to deal with, but when dealing with legacy code it is unlikely the authors used fx, so...
- skrtskrt 4y agoGo channels are one that's pretty new depending on the language. I came from Python using async for all concurrency, so channels are an odd concept. Passing context around is an interesting one too
- kubb 4y agoyes, channels and goroutines are a concept that doesn't transfer from C++ or Java :) good job pointing out the exception!
- 62951413 4y agoI believe idioms for dealing with golang channels resemble NIO channels/selectors (e.g. https://docs.oracle.com/javase/8/docs/api/java/nio/channels/package-summary.html https://docs.oracle.com/javase/8/docs/api/java/nio/channels/...). JDK1.4 was already widely used around 2005 ;)
- morelisp 4y agoThe idioms for Go channels as well as NIO channels resemble those of any asynchronous file descriptor handling. Long predating 2005. (And more to the point, still relatively uncommon outside actual I/O in Java.)
- fnord123 4y ago> good job pointing out the exception! No, go doesn't have exceptions. :D To answer the sibling comment (I don't have enough karma to go deeper), Java NIO channels are barely usable. Using them is the quickest way to inject bugs into your code I've seen in Java. Go channels are closer to blocking queues or a selection of concurrent queues depending on whether it's bounded or unbounded, or whether you use select, etc.
- kubb 4y agoit has panic/recover though which is like exceptions :)
- kubb 4y agoyou're right, fortunately stopping to use a concept is easier than learning a new one
- 62951413 4y agoI cannot help thinking that golang's error handling is isomorphic to * returning an error code from functions in C and all the time writing "err = f(); if (err != 0) .." * returning a potentially null reference from a method in Java and all the time writing "var ref = f(); if (ref != null) .." Exceptions were supposed to save us from this drudgery in late 90s. They did if you ask me. Options are a nice alternative if FP is your cup of tea.