7 ms·
I did Go for years, but stopped doing any serious work in it about a year ago. In general, I found it a chore to maintain Go-based systems. There's a lot to l
by latch 9y ago
I did Go for years, but stopped doing any serious work in it about a year ago.
In general, I found it a chore to maintain Go-based systems.
There's a lot to like about Go. But it doesn't seem pragmatic for the types of applications I see people using it for.
For example, the entire error thing is absurd. Anders Hejlsberg got this right many years ago: 9 out of 10 errors are "handled" by a central error handler (log + "sorry, try again"). You can do this with error values, but at best it's verbose. Maybe error values makes sense for system programming, I could certainly believe it makes more sense.
But, really, the main problem is the type system. I'm a huge fan of dynamic languages. I think they're so much more productive. This is especially true when your system is interacting with external data - like user input and a database. I did C# and Java for years. These interactions are, at best, painful with static languages. But they're downright infuriating in Go (poor reflection, no generics). I wrote a somewhat popular library to help deal with map[string]interface{} nonsense (2), but it's not enough.
When it comes to web/tcp stuff, I think OpenResty for any middleware-ish system and Elixir for everything else are far more productive and enjoyable ecosystems. We've been doing Elixir for about a year now, and I've never seen such a consensus on productivity and quality (everything else always had some issue: Ruby is slow, Node is single threaded, Java is verbose).
There's certainly more space than just that. Go might still be a good choice for CLI tools, and both OpenResty and Elixir have a distinct memory/threading model that precludes some use cases - but those are in the minority.
(1) - Nothing against Erlang model, but I do think it's more restrictive.
(2) - https://github.com/karlseguin/typed https://github.com/karlseguin/typed
- 43224gg252 9y ago>I found it a chore to maintain Go-based systems My opinion is the opposite. Almost every go code base I've seen is extremely consistent compared to other languages. Going from one code base to another is almost always seamless because learning Go involves learning the Go tools which forces you to follow Go coding standards. I'd choose to work on a Go code base over Java or C++ any day of the week. I don't have much experience with elixir, but that's because I like to stay employed. >the entire error thing is absurd. Anders Hejlsberg got this right many years ago: 9 out of 10 errors are "handled" by a central error handler This is almost always unacceptable in production, especially when it comes to mission critical hard/software. You need to be able to handle errors in production that you may not even be able to catch in testing and that means you need to catch all the errors and think critically about what should be done when that error is encountered. "Central error checkers" are almost never robust enough for mission critical systems. Go basically forces people to write high-quality, resilient code bases, and I like it for that.
- thinbeige 9y ago> I don't have much experience with elixir, but that's because I like to stay employed. While the previous commenter gave an objective and rational overview why he think Go gives him a hard time you can't resist to get polemic.
- 43224gg252 9y agoI gave rational counter arguments and employment is pretty important too imo. It seems a lot of the anti-Go people tend to favor languages that no one gets paid to use.
- thinbeige 9y agoYes, but the discussion wasn't about the employment situation and if--your tone was slightly inappropriate. You could have also written: "I've no experience with Elixir but your words catch my interest. The inferior Elixir enployment situation (compared to Go) held me back from Elixir. But I should give it a try." Same message and nobody loses his face.
- deleted 9y ago[deleted]
- actsasbuffoon 9y agoI'm getting paid to write Elixir right now.
- jorgec 9y agoIn reality, Go is a language that practically no one gets paid to use. Go is niche and the few projects around here are short term.
- zimpenfish 9y agoI interviewed for two Go jobs in London last month that were specifically building up Go teams for long term projects. It's definitely getting less niche by the month.
- seanwilson 9y ago> This is especially true when your system is interacting with external data - like user input and a database. I did C# and Java for years. These interactions are, at best, painful with static languages. Why? I find when you're importing external data or user input, that's exactly where you want strong types as that's the most likely place unexpected values are going to be generated (e.g. unexpected null values, string instead of an array).
- junke 9y agoStrong types, not necessarily static ones.
- latch 9y agoI don't disagree. It's just more tedious in static languages. Let's say we're doing a user registration. In most dynamic languages the JSON body will get parsed into a map. Excuse the fat controller and pseudo-language, but it'll end up looking something like: func create(conn, params) do if not Validator.is_email?(params["email"]) do return error(conn, "email is not valid") end if not Validator.min_length?(params["password"], 10) do return error(conn, "password must be 10 or more characters") end create_user(params) true end In Go, if you want this nice and typed, you'd add a RegistrationInput struct with the field mapping tags: type RegistrationInput struct { Email string `json:"email"` Password string `json:"password"` } Map the user's input to the structure, handling the error: var input RegistrationInput if err := json.Unmarshal(req.body, &input); err != nil { ... } And then write the exact same validation checks. What safety does Go's version buy you?
- Pxtl 9y agoIn C# MVC, the JSON gets deserializrd into a tree of statically-typed objects you declared beforehand. Any malformations of the JSON are an error.
- mercurial 9y agoWell, the dynamic version won't complain until runtime the day you mix up the order of your parameters and invoke "create(params, conn)", for instance. Besides, you can make it statically impossible to generate an invalid user object with a static language. In a dynamic language, an invalid user object is just an assignation away. Now, if we are talking about error handling, the way it is done in Go is not particularly representative. Most static languages would go with exceptions (or maybe checked exceptions in Java). Haskell/Rust/Scala would probably go with a Result type, and then it's more a question of the language giving you the tools to propagate the errors and wrap them (Rust with error-chain is excellent at this kind of thing).
- jorangreef 9y agoThe Node event loop (the control plane) is single threaded, but the data plane is multi-threaded, you can easily write C++ bindings to run in the thread pool to handle CPU intensive work.
- tannhaeuser 9y agoSpeaking of Node in the context of error handling, what are current best practices in Node (other than running node-forever respawning)? Up to v0.12, Node used to have heuristic cleanup code for file handles and other resources on exception domain error handler execution, but the domain API has been deprecated for some time now ([1], [2]). [1]: <https://nodejs.org/api/domain.html> https://nodejs.org/api/domain.html> [2]: <https://github.com/nodejs/node/issues/66> https://github.com/nodejs/node/issues/66>
- ludwigvan 9y agoThis is pretty informative on handling errors in node: https://www.joyent.com/node-js/production/design/errors https://www.joyent.com/node-js/production/design/errors
- atombender 9y agoI don't like the "central exception handler" idea -- it is invariably too far away from the error site to do anything intelligent. I do like Erlang's approach with hierarchical error domains ("processes" and "supervisor trees"), and I honestly think Go made a mistake here: The semi-recent introduction of Context acknowledges that a control hierarchy is present and an important part of a concurrent, distributed application and that there are aspects of this that need to flow through everything. The Context exists to permit a caller to call functions and launch goroutines that inherit deadlines, the ability to cancel their work, and so on. But this is a rather blunt and intrusive tool that could have been better solved by baking termination into the goroutine scheduling runtime. Of course, one reason goroutines cannot be forcibly terminated is because everything is mutable all over the place, but you could still have termination induce system-level errors that the goroutine would be forced to handle (or maybe you could safely induce a panic)... but that ship seems to have sailed a long time ago. And yet a hierarchical approach similar to supervisor trees would have been awesome to have in Go. Managing hordes of complex, "crash only" goroutines in Go gets gnarly fast, and you tend to end up with your own ad-hoc type of supervisor trees that uses waitgroups and maps and what not to corral trees of composed workers to ensure they are all able to cancel, retry + back off, and time out appropriately. Even something like retry with backoff isn't part of the standard library, even though it's extremely core to distributed, concurrent programming. I think I agree with Go's authors that exceptions are bad, but exceptions in an explicitly hierarchical, supervisor-type process model, where processes are "crash only", actually make sense. It means you tend to design your system into discrete parts consisting of dumb workers (that give up and crash on the smallest error) and slightly less dumb supervisors (that react to said crashes). It's not a panacea, but it's better than the loose, somewhat impoverished set of tools that Go gives you.
- KallDrexx 9y agoI don't think a central exception handler means ONLY handle exception in one very specific point. It means that if you can handle an error handle it, otherwise let the central exception handler handle it. I really like Elixir and the Erlang VM but supervisor trees are not a perfect solution either. For all the talk about how in Erlang you just let things fail and don't have to do defensive coding, any non-trivial application requires a lot of very thorough design on how to handle error scenarios and keep your application consistent. If a process fails too many times too often (say your web service it's trying to ping is inaccessible) it can take out your whole supervisor. If that supervisor is holding an ETS table that holds important coordination data (but not data that requires persistence) that ETS table (and all it's data) is now completely gone, and unless that case has been handled you have a lot of processes running but not doing what they should be doing, and your application is inconsistent until you completely shut it down and restart it. And if you don't plan for a lot of these scenarios then you will be caught completely surprised when it happens in production.
- fnord123 9y ago>For example, the entire error thing is absurd. Anders Hejlsberg got this right many years ago: 9 out of 10 errors are "handled" by a central error handler (log + "sorry, try again"). You are correct that this is how it's often done - 9 out of 10 errors probably ARE "handled" by a central error handler. But this is an incorrect approach if the goal is a reduction of catastrophic errors. In the following paper, "Simple Testing Can Prevent Most Critical Failures" (OSDI '14). the authors discuss how some catastrophic errors occur in distributed systems that they test (Cassandra, HBase, Redis, HDFS, MapReduce). http://www.eecg.toronto.edu/~yuan/papers/failure_analysis_osdi14.pdf http://www.eecg.toronto.edu/~yuan/papers/failure_analysis_os... Notably section 4.1: Trivial Mistakes in Error Handlers: """ Figure 5 further breaks down the mistakes into three categories: (i) the error handler ignores explicit errors; (ii) the error handler over-catches an exception and aborts the system; and (iii) the error handler contains “TODO” or “FIXME” in the comment. 25% of the catastrophic failures were caused by ignoring explicit errors (an error handler that only logs the error is also considered as ignoring the error). For systems written in Java, the exceptions were all explicitly thrown, whereas in Redis they were system call error returns. """ I haven't used Go much but I personally much prefer Rust's error handling which appears to be largely similar.
- rumcajz 9y agoDepends on the app. In GUI, yes, why not, display an error message and you are done. In infrastructure software, OTOH, you want to handle errors in thoughtful manner. If you can't it's often better to crash the application than to continue with broken state.
- masklinn 9y agoErlang/Elixir is pretty interesting there in that it will "crash the application" but said application's scope is a single process (unless others are linked to it, which lets you build supervision tree and take down and restart the entire tree when one of the processes crashes).
- fnord123 9y agoSure, if you're in a GUI then you report to the user that an operation failed and it's up to them to figure out what they can do about it. As for infrastructural software, yes you want to handle errors in a more thoughtful manner. If you can't and the process has corrupted it's own stack or heap (in the case of C or C++) then sure, abort. But I'm saying this line is often chucked over to "log the exception and do nothing" too quickly because it's often the easier thing to do. 25% of catastrophic failures in this one study appear to occur because of this. tl;dr: The devil is in the "If you can't".
- brightball 9y agoI always wonder if the Go error issues could be handled more cleanly by using the suture library. I haven't seen widespread use but it's supposed to give a supervisor pattern to Go.
- jorgec 9y ago"I'm a huge fan of dynamic languages. I think they're so much more productive. This is especially true when your system is interacting with external data - like user input and a database." Im not so sure about it. Database is not dynamic and user input should be controlled, or number, or date, or text, for example an user could enters the price of a product a text.
- vince14 9y ago>Node is single threaded You can still utilize all of your cores in Nodejs. So which problem are you encountering?
- coldtea 9y agoThat the utilitization of the cores is sub-par, and at a crude coarse level (node process).
- ejcx 9y agoWat. OpenResty as an alternative to go? Wat? Please, nobody write full fledged WebApps or all of your middleware in lua. Trust me that's a horrible idea. I have seen about everything you can do in lua/nginx and it does not turn out pretty.
- oceanghost 9y agoI honestly thought the OP's post was a troll. Lua is for masochists.
- coldtea 9y ago>Lua is for masochists Is this a happy-hour for Reddit users on HN?
- brango 9y agoI've done my first project with go. Goroutines are undoubtedly cool and I like the strong typing. * Unfortunately error handling sucks (no stacktrace by default? Let's go back to the days of peppering print statements around just for fun). * DB support is terrible (I've spent hours doing what I could have done in half an hour with python). * It's so verbose, even simple things like mapping over an array to create another array requires a for-loop dance. Same for just checking if a value is in an array. Same, obviously for no exceptions which you either love or hate. I hate. I can't see myself doing any other projects with it.
- zimpenfish 9y agoI get stacktraces from `panic()` - is this not what you're after? goroutine 1 [running]: main.b() /home/rjp/git/godiggy/test.go:8 +0x64 main.a() /home/rjp/git/godiggy/test.go:13 +0x9b main.main() /home/rjp/git/godiggy/test.go:18 +0x9b exit status 2
- brango 9y agoI want to be able to handle errors, pass them up a few functions then log a stacktrace. I don't want to panic everywhere since robustness is important.
- weberc2 9y ago> I'm a huge fan of dynamic languages. I think they're so much more productive. This hasn't been my experience. I work in Python (and JS) by day, and I can't wait until we upgrade to 3.6 so I can reasonably employ type annotations. Without them, people tend to write hacky code, such as functions which do completely different things based on the data type passed in (rather than fixing the abstraction) or build up huge, nested data structures with no formal definition (various parts of the code base just tack on whatever they like, and the documentation inevitably falls out of date). Even my prototyping is in Go, because I would rather have a compiler catch my errors than to spend tenfold the time trying to debug. Also, when I'm working in Python, I sorely miss the tooling that I have in Go and other static languages. I don't think Go is the end-all-be-all of programming languages, but I think any dynamic language would be more productive if it was at least optionally typed.