6 ms·
Hashi code, such as Terraform, was (is) an amazing example of a good reference Go codebase. It was very hard for me to get into Go because, outside of the langu
by renegade-otter 3y ago
Hashi code, such as Terraform, was (is) an amazing example of a good reference Go codebase. It was very hard for me to get into Go because, outside of the language trivia and hype, it was hard to learn about the patterns and best practices needed for building even a mid-sized application.
- hpeter 3y agoThat'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 3y 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 3y 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 3y 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 3y 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 3y 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 3y 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 3y 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.
- janosdebugs 3y 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 3y 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 3y 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 3y 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 3y 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 3y 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 3y 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 3y agoIs it because secondSlice is a reference (pointer?) to firstSlice?
- janosdebugs 3y 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 3y 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 3y ago