6 ms·
That's interesting. I found Go to be a very productive and easy language, coming from Typescript. But I had a similar experience like yours with PHP, I just co
by hpeter 2y ago
That's interesting. I found Go to be a very productive and easy language, coming from Typescript.
But I had a similar experience like yours with PHP, I just couldn't get into it.
- renegade-otter 2y agoI find the claims that Go is easy just wrong. It's actually a harder language to write in because without discipline, you are going to end up maintaining massive amounts of boilerplate. That's from someone who did a bunch - Perl, Ruby, Python, Java, C++, Scala. Syntax is one thing, assembling an application with maintainable code is something else.
- NomDePlum 2y agoWhat in particular did you find difficult building a maintainable codebase in Golang? Not quite understanding the boilerplate reference. Code generation in Golang is something I've found removed a lot of boilerplate.
- renegade-otter 2y agoI am not used to writing code where 2/3 of it is "if err" statements. Also, refactoring my logging statements so I could see the chain of events seemed like work I rarely had to do in other languages. It's a language the designers of which - with ALL due respect - clearly have not built a modern large application in decades.
- aaomidi 2y agoYes because other language just hide errors from the user. I think the reason people find go a bit annoying with the error condition is because go actually treats errors as a primary thought, not an after thought like Python, Java.
- freedomben 2y agoI assume you're talking about languages with exceptions when saying "other language just hide errors from the user." I think that's a gross over-simplification of exception-based error handling. I generally do prefer explicit, but there are plenty of cases where exceptions are clearly elegant and more understandable. My preference is a language like Elixir where most methods have an error-code returning version and a ! version that might raise an exception. Then you (the programmer) can choose what you need. If you're writing a controller method that is for production important code, use explicit. If you're writing tests and just want to catch and handle any exception and log it, use exceptions. Or whatever makes the most sense in each situation.
- hughesjj 2y agoI've never gotten the explicit argument. Java checked exceptions are also part of the function signature/interface and nothing prevents one from making a language where all exceptions are checked then just doing try { maybeError := FunctionThrowingValueError() } catch (ValueError e) { // do stuff } I get at the end of the day it's all semantics, but personally I kinda like the error-specific syntax. If you want to do the normal return path, that's fine, but I prefer the semantics of Rust's Result type (EITHER a result OR an error may be set). To each their own, it's not something I really worry about.
- freedomben 2y agoYeah same, Go's explicit argument never resonated with me either. In Elixir it's similar to a Result type, being a tuple such as either `{:ok, return_val}` or `{:err, err_msg}`, which is perfect for using with `case` or `with` depending on your situation.
- renegade-otter 2y agoYou can't hide an exception if it crashes your program. You can definitely ignore a return from a function, essentially swallowing it. It's the definition of an anti-pattern.
- NomDePlum 2y agoI understand were you are coming from but I actually like the explicit error handling in Golang. Things being explicit reduces complexity for me a lot and I find it easier to spot and resolve potential issues. It's definitely something that I can understand not working for everyone. I agree on the logging point but my experience was the explicit error handling and with good test coverage meant we rarely got into situations were we had non-deterministic situation were we relied extensively on logging to resolve. But we also went through several iterations of tuning how we logged errors. It's definitely a rough edge in what is readily available in the language.
- takeda 2y ago> I understand were you are coming from but I actually like the explicit error handling in Golang. Things being explicit reduces complexity for me a lot and I find it easier to spot and resolve potential issues. It's definitely something that I can understand not working for everyone. This sound a lot of like Apple user arguments about iPhone 1 missing copy & paste over a decade ago. I am very pedantic about checking responses for errors, but from my experience when working with a team and existing project I see that people notoriously forget to check the result. TBH it is a pain to essentially repeating the boilerplate `if err !=nil ...`. What's worse is that even documentation skips checks. For example `Close()` method. It's almost always returning error, but I almost never seen anyone check it. The reason for it, is if you want to use `defer` (which most people do) you would end up with very ugly code. The other alternative would be to then making sure you place (and properly handle error) close in multiple places (but then you risk of missing a place). And other solution would be using `goto` in similar way as it is used in Linux Kernel, but there are people who have big problem with it. I had a boss who religiously was against goto (who did not seem to understand Dijkstra's argument), and asked me to remove it even though it made the code more readable.
- xyzzy123 2y agoI think go makes more sense if you imagine spending more time reading MRs and code than writing it. Standard go error handling maximises for locality. You don't see many "long range" effects where you have to go and read the rest of the code to understand what's going to happen. Ideally everything you need is in the diff in front of you. Stuff like defer() schedules de-alloc "near" to where things get allocated, you don't have to think about conditionals. If an MR touches only part of a large function you don't have to read the whole thing and understand the control flow. The relative lack of abstraction limits the "infrastructure" / DSLs that ICs can create which renders code impenetrable to an outside reader. In a lot of C++ codebases you basically can't read an MR without digging into half the program because what looks like a for loop is calling down into a custom iterator, or someone has created a custom allocator or _something_ that means code which looks simple has surprising behaviour. A partial solution for that problem is to have a LOT of tests, but it manifests in other ways, e.g. figuring out the runtime complexity of a random snippet of C++ can be surprisingly hard without reading a lot of the program. I personally find these things make go MRs somewhat easier to review than in other languages. IMHO people complaining "it's more annoying to write" (lacking stronger abstractions available in many other languages) are correct but that's not the whole story. P.S: For Close(), you're right that most examples skip checking the error and maybe it would be better if they didn't. It only costs a few lines to have a function that takes anything Closable and logs an error (usually not much else you can do) but people like to skip that in examples. type Closable interface { Close() error } func checkedClose(c Closable, resourceName string) { if err := c.Close(); err != nil { log.Printf("failed to close %s: %v", resourceName, err) } }
- ZealousIdeal 2y agodisagree. k8s is written in it just fine. plus, tons of other modern large applications in enterprise settings
- renegade-otter 2y agoK8s was famously written in Go by ex-Java developers, and the code base was full of Java patterns. Which kind of proves my point. Even Google struggled to write clean, idiomatic Go.
- throwaway2037 2y ago> I am not used to writing code where 2/3 of it is "if err" statements. I don't write Go, but I have seen this a lot when reading Go. It seems hard to escape. The same is true for pure C. You really need to check every single function output for errors, else errors compound, and it is much harder to diagnose failures. When I write Java with any kind of I/O, I need careful, tight exception handling so that the exception context will be narrow enough to allow me to diagnose failures after unexpected failures. Error handling is hard to do well in any language.
- hpeter 2y agoI prefer to handle errors than ignore them. "If err" is actually one of the best things about Go
- renegade-otter 2y agoIn most web applications I write, I have one error-handling block. Access forbidden? Log a warning and show a 403 page. Is is JSON? Then return JSON. Exception-handling in general is a pretty small part of most applications. In Go, MOST of the application is error-handling, often just duplicate code that is a nightmare to maintain. I just don't get why people insist it's somehow better, after we "evolved" from the brute-force way.
- hpeter 2y agoErrors usually happen during IO, but not in the main business logic and those two can be neatly separated. But If you are coming from java I can understand the single error handling block is more comfortable, but coming from JavaScript/Typescript it's much more easy to check if err != nil, than to debug errors I forgot to handle, during runtime.
- janosdebugs 2y agoNot the parent, but I find that doing dependency injection or defensive programming results in a lot of boilerplate. Custom error types are extemely wordy. The language also doesn't allow for storing metadata with types, only on structs as tags, which seriously hampers the ability to generate code. For example, you can't really express the concept of a slice in a slice containing an integer needing validation metadata well. You'll need to describe your data structure externally (OpenAPI, JSON schema, etc) and then generate code from that.
- NomDePlum 2y agoMy experience of Golang is that dependency injection doesn't really have much benefit. It felt like a square peg in a round hole exercise when my team considered it. The team was almost exclusively Java/Typescript Devs so it was something that we thought we needed but I don't believe we actually missed once we decided to not pursue it. If you are looking at OpenAPI in Golang I can recommend having a look at https://goa.design/ https://goa.design/. It's a DSL that generates OpenAPI specs and provides an implementation of the endpoints described. Can also generate gRPC from the same definitions. We found this removed the need to write almost all of the API layer and a lot of the associated validation. We found the generated code including the server element to be production ready from the get go.
- janosdebugs 2y agoFor OpenTofu specifically, having DI for implementing state encryption would have been really nice. Of you look at the PR, a lot of code needed to be touched because the code was entirely procedural. Of course, one could just make a global variable, but that is all sorts of awful and makes the code really hard to test efficiently. But then again, this is a 300k line project, which in my opinion is way beyond what Go is really good for. ContainerSSH with 35k+ lines was already way too big.
- NomDePlum 2y agoOut of interest what language do you think would have been more appropriate and why? For that size of codebase I'd have thought code structure and modularisation would be more important than language choice.
- karmajunkie 2y ago> Code generation in Golang is something I've found removed a lot of boilerplate. Not a gopher by any stretch, but to my way of thinking code generation is literally boilerplate, that's why its generated. Or does Go have some metaprogramming facilities I'm unaware of?
- NomDePlum 2y agoI took took the comment to relate to writing boilerplate. So unrelated to code generated, if that makes sense. The generated code I'm sure had lots of boilerplate, it's just not code we needed to consider when developing.
- janosd 2y agoAfter having written probably over 100k lines of Go code, my impression is that Go is simple, but not easy. The language has very few features to learn, but that results in a lot of boilerplate code and there are more than a few footguns burried in the language itself. (My favorite [1]) I find it very hard to write expressive, easy to read code and more often than not I see people using massive switch-case statements and other, hard to maintain patterns instead of abstracting away things because it's so painful to create abstractions. (The Terraform/OpenTofu codebase is absolutely guilty of this btw, there is a reason why it's over 300k lines of code. There is a lot of procedural code in there with plenty of hidden global scope, so getting anything implemented that touches multiple parts typically requires a lot of contortions.) It's not a bad language by any stretch, but there are things it is good at and things it is not really suited for. [1]: [link redacted]
- djbusby 2y agoIs it because secondSlice is a reference (pointer?) to firstSlice?
- janosdebugs 2y agoYes-ish? Slices are this weird construct where they sometimes behave like references and sometimes not. When I read the explanation, it always makes sense, but when using them it doesn't. For me the rule is: don't reuse slices and don't modify them unless you are the "owner" of the slice. Appending to a slice that was returned to you from a function is usually a pretty good way to have a fun afternoon debugging.
- geoka9 2y agoIt's because slices have underlying arrays which define their capacity (cap(s)). Both slices start out having the same underlying (bigger) array -so appending to one slice can affect the other one. In the "bonus" part, though, the appends outgrew the original array, so new underlying arrays were allocated (i.e. the slices stopped sharing the same backing array). Thanks for the heads-up, janosdebugs :)
- MoOmer 2y ago