9 ms·
What I learned in 2017 writing Go
- krylon 9y agoI concur on gometalinter. It can be a pain sometimes to satisfy it, but most of the time, my code ends up better than it was before. Well, except for gocyclo. Some functions just are complex - simplifying them or breaking them up into several smaller functions just to keep each function small is not always the best idea or even possible. But that can easily be disabled on a per-function basis.
- payne92 9y ago>> ...a careless approach to a main function can result in needless complexity. Compare two versions of the same main function: before and after. One version is over 400 lines. The other is about 40 lines. That’s an order of magnitude. One will be easy to change. A potentially contrarian view: a long-ish straight linear function without duplicated code is often much easier to understand than a smaller function broken into subroutine callouts (because you don't have to chase down the implementation of the subroutines to figure out what's going on).
- acangiano 9y agoGood naming helps. If you strive to make it obvious what each subroutine does, you can treat them as small black boxes (until you need to look into a specific function). Obviously, there needs to be some balance and not go crazy in either direction.
- jeremiep 9y agoHaving a lot of single-use functions, even if properly named, still splits the context in that many pieces. For some cases that results in more confusing code.
- krylon 9y agoI agree with you. Sometimes a single long-ish function is preferrable to a flock of shorter ones. Still, I think the GP has a point - clear naming is very important, regardless of one's attitude towards function size. ;-)
- vram22 9y agoI partly agree with acangiano and partly with you. On the whole, I tend to agree more with him, with this proviso: don't just name the functions well, name their parameters well too. That can make the overall program easier to understand, and we can still get the benefit of modularity and black boxes, while not creating more confusion. YMMV, of course.
- jeremiep 9y agoI agree about modularity and encapsulation being important. Usually I try to weight the overhead of having more function prototypes to track, indirections in the main function and whatnot versus the overhead of having it all inlined. A lot of things become more evident as you inline one-use functions, because the whole context is now local.
- vram22 9y agoYes, it's a tradeoff. Interesting, your comment reminded me of inline functions in C++ and C99; not the same thing (or at least goal) as what we were discussing, but closely related: https://en.wikipedia.org/wiki/Inline_function https://en.wikipedia.org/wiki/Inline_function
- haberman 9y agoWhile I tend to agree with this, I think a counterargument is: long functions mean more local variables that are in scope. This makes it harder to understand the interaction between different variables at different scopes. A separate function introduces a separate namespace, and all the data flow in and out has to be explicit, through function parameters/returns.
- vram22 9y agoYes, agreed about the counter-argument. See my nearby comment here: https://news.ycombinator.com/item?id=16064412 https://news.ycombinator.com/item?id=16064412 I think the important thing is that modularity (breaking up longer functions into shorter ones) helps, but only if not done blindly. One has to spend some time doing it, since there are many possible ways it can be done, and some of them will be better than others. So it can pay off in the long term, but only if one is willing to put in the effort and time, and be ready to throw away some not-so-good attempts. I've done this sometimes and seen that it does help.
- deleted 9y ago[deleted]
- Waterluvian 9y agoI would love an IDE that unrolls subroutine calls inline as a view mode.
- dec0dedab0de 9y agoThat is really interesting. They would have to be made to stand out somehow, but it might be helpful. I can only imagine what some library heavy code would look like.
- WaxProlix 9y agoPeek definition does this, sort of. Definitely exists in VS and VS Code, and probably in the JetBrains things.
- deleted 9y ago
- riku_iki 9y agoIf you want to find specific place in large function, you will need to read all code line by line. If case of smaller functions, you can locate branch of interest much faster if naming is good enough.
- jeremiep 9y agoIt really depends on the context of the problem being solved. Some problems become more complex when modularized like this. Splitting functions is basically adding indirections and spreading the context all over the place. For reusable tasks that works wonders, but for single-use functions that usually obfuscates the intent more than anything. I don't want to follow fifteen 10-lines one-use functions when I could read one 150-line function (which would most likely be a lot less due to less boilerplate).
- mvaliente2001 9y agoIMO the advantage of small functions is that they act as a form of documentation. In the given example (short main) I can see that it loads some configuration without having to worry about how it does it. With the long main, I'm forced to see the details. Also, if we're talking about OOP, small methods are better. With small methods it's easier to change behavior by subclassing. In several occasions, I've been forced to make a subclass with a copy of a big method with only one line changed.
- sinhpham 9y agoI find myself increasingly doing this in Rust: let lines = { let ret = vec![]; // Open file // Read into ret ret }; let processed_data = { // Open another file // Use lines // Construct ret ret }; .... You get the best of both worlds this way: scoped, meaningful names like with sub-functions and the continuity of a long one. Everything is an expression is an underrated feature of Rust.
- sbov 9y agoIn at least Java, and probably more languages, you can also use braces to limit variable scope. E.g.: List<String> lines; { int whatever; lines = new ArrayList<>(); // modify lines } List<Object> processed_data; { int whatever; processed_data = new ArrayList<>(); // modify processed_data, using lines }
- maxxxxx 9y agoI do this a lot in unit tests where a pattern is repeated.
- fishywang 9y agoIn Go something similar to this is actually more common because of `defer`, e.g.: if err := func() error { f, err := os.Open(filename) if err != nil { return err } defer f.Close() // work with f return nil }(); err != nil { return err }
- Stoids 9y agoA viewpoint shared by the go-kit faq: (https://gokit.io/faq/#dependency-injection-mdash-why-is-func-main-always-so-big https://gokit.io/faq/#dependency-injection-mdash-why-is-func...), which I tend to agree with.
- logicchains 9y ago>because you don't have to chase down the implementation of the subroutines to figure out what's going on If the subroutines are sensibly named, you shouldn't need to. I'd argue they're also easier to follow than blocks of code within a long function as well-written small functions will depend only on their inputs, whereas code in a large function could depend on any of the params to that large function.
- guru4consulting 9y agoI have to disagree with this. If the function is larger, then the mental cognitive overload to understand (and follow the flow) is much higher. If a 1000 line method is divided into 10 methods with each 100 lines, and if those 10 methods are named appropriately, then it would be much easier to understand the flow. It also gives a high-level picture of what the function does, and if needed, I can always jump into particular sub-routine.
- lclarkmichalek 9y agoThe `pkg` package is a horrible convention. Having a `cmd` directory makes sense, as each binary needs to be a separate package, but putting all your libraries under a `pkg` directory is unneeded.
- 013a 9y agoI hate the pattern primarily because it overloads the name "pkg" which is already used in your go workspace at least one directory up to store compiled library files. The word "lib" makes more sense. But, also, its unnecessary.
- jdc0589 9y agoI've always used `lib` in every other language. I see no reason to change....
- wyattjoh 9y agoTypically, unless I'm writing a shared library, I find myself putting everything under a `internal` folder. It keeps people from importing possibly unstable code until it's exposed.
- rocmcd 9y agoI tend to agree, though I also find having everything be in the root of the repository equally rough for larger projects.
- lobster_johnson 9y agoSome projects dump everything in the root of the repo. I find that to be annoyingly messy. Comingling non-Go dirs with Go code makes it hard to navigate, since some dirs will be Go packages, and some won't. You may want to have documentation (/docs, perhaps), test data (/test?), build output dirs (/build or /dist), Docker/Kubernetes/Helm dirs (/docker, /k8s, etc.), Protobuf files (/proto), and so on, usually all in the root of the project. Secondly, these directories might conflict with Go package names. We have a project that has a package called "documents", which is about persistent documents, which we'd have preferred to call "docs", but the root already had a /docs folder for actual documentation. Personally, I prefer "src".
- tschellenbach 9y agoFor dependency management we use Dep (https://github.com/golang/dep https://github.com/golang/dep) combined with (https://github.com/getstream/vg https://github.com/getstream/vg). This works pretty well.
- raziel2p 9y agoI didn't know about vg and it looked promising, but I was disappointed to see that it still requires that I initialize all my projects in a subdirectory of $GOPATH/src. Why does go (and/or its tools) enforce this? Why can't I put my projects in ~/code? Why do I need a "github.com" subdirectory? Feels so silly.
- JelteF 9y agoMain developer of vg here. In principle vg should work for packages outside of GOPATH when using full isolation mode (which has its own set of problem, see README for details). Its dep integration doesn't work then though last time I tried. As far as I can tell the main reason why Go tooling needs GOPATH is because everything uses absolute imports and relative ones can translated to absolute ones by always working from inside the workspace. I've solved my main issue with GOPATH in a very easy way though. I just create symlinks in my normal directories (like ~/work) to my projects in GOPATH. This allows me to go to my own projects easily while still having everything in GOPATH.
- collinvandyck76 9y agoI use zsh and there is an easy way to find your golang projects using cdpath: # setup easy access to cd paths setopt auto_cd cdpath=($HOME/code/go/src/github.com \ $HOME/code/go/src/github.com/collinvandyck \ $HOME/code \ $HOME/code/go/src)
- roganartu 9y agoThere is a detailed explanation of how package names are used by the compiler here: https://golang.org/cmd/go/#hdr-Description_of_package_lists https://golang.org/cmd/go/#hdr-Description_of_package_lists Specifically this bit is relevant here: > Every package in a program must have a unique import path. By convention, this is arranged by starting each path with a unique prefix that belongs to you. For example, paths used internally at Google all begin with 'google', and paths denoting remote repositories begin with the path to the code, such as 'github.com/user/repo'. Most (all?) Go tooling assumes this convention. Conceptually, I don't see it as that different from how other languages determine input paths, except in Go they are guaranteed to be unique which I consider a good thing. In Java for example you still have file namespaces they're just inside the project itself (looking at you `src/java/com/example/package`) or Python which looks for the root module name in `PYTHONPATH` and then recurses down for submodule names. Most if not all languages use file structures to namespace imports in one way or another, Go just applies it globally instead of at a project level. I agree it raises the barrier to entry for someone to just come in and run `go run` on a project but I find it works very well once you've got it setup. Convention over configuration is a big thing in Go tooling.
- LanceH 9y agoHow do people differentiate between packages being worked on and packages installed? Let's say I have my-package (version 1.0) installed and in use in a lot of my other projects. But I'm working on version 1.1, but need to have 1.0 as the reference for other projects. Right now I just fiddle with $GOPATH but that feels like a hack and is an annoyance if I have to swap around a bit. Right now my-package might be in a broken state because I'm working on it, so I can't build anything relying on it if I go with the single $GOPATH hierarchy that every discussion seems to assume.
- bigdubs 9y agoGenerally we use vendoring, i.e. you have 1.1 in your gopath, but vendor 1.0 in your projects (as long as they're standalone projects). It's also important to limit cross package dependencies as much as possible to avoid these situations.
- rocmcd 9y agoYes, vendoring is the best choice here. I'd also recommend everyone give dep[1] a shot. [1] https://github.com/golang/dep https://github.com/golang/dep
- m90 9y agoHaving tried to migrate from godep to dep a couple of times now I always gave up seeing how many additional unneeded files dep will pull into my /vendor folder. Just a few days ago using dep instead of godep on a medium-sized web application gave me a delta of 1,920,834 additions and 1,942 deletions (_after_ manually pruning _test files), mostly caused by depending on aws-sdk-go. Do you have any strategy for keeping your vendor small with dep or do you just accept it the way it is? There seems to be this: https://github.com/golang/dep/issues/858 https://github.com/golang/dep/issues/858 , but it looks like it's not implemented yet and the status is highly unclear.
- rocmcd 9y agoNo, I've mostly accepted it at this point since I don't care too much about the size of /vendor (as long as it's not outrageous). I have noticed that it brings in extra files, though, which a `dep prune` typically helps alleviate.
- Dirlewanger 9y agoThis is coming from a comparatively spoiled Rails developer, but: why would anyone bother using a language in production if it lacks fundamental things taken for granted like dependency management and stable testing frameworks/practices? Do people just not do enough due diligence and get blinded by a language's honeymoon period?
- geodel 9y agoSome applications require a higher efficiency in terms of resource usage e.g RAM, CPU etc. Go solution is better in that scenario. Cloud software vendor would use more efficient language especially if that application is not directly billable to customers.
- ovao 9y agoWhile Go’s dependency management story isn’t an encouraging one, from a testing perspective I personally have few complaints with Go. Mocking with interfaces is straightforward, and despite the basic testing package, I find it quite easy and straightforward to both write and run Go tests — a testing “framework” is not required. I do generally use the testify assert package for convenience[0], but it’s not strictly necessary. [0]: https://github.com/stretchr/testify https://github.com/stretchr/testify
- rufugee 9y agoYou realize that bundler isn't a part of Ruby, correct? You're comparing apples to oranges.
- LukaD 9y agoDoes Go have a comparable tool that is as widely adopted as Bundler is?
- collinvandyck76 9y agoIt looks like `dep` will be this tool for Go.
- 9y ago
- it 9y agoDelve's main doesn't seem all that exemplary. It's irritating to open up main only to find that the princess is in another castle.
- deleted 9y ago[deleted]