Live data from Hacker News

Diving into Go by building a CLI application

eryb.space

81–90 of 124 posts

Re: Diving into Go by building a CLI application

#81
post #62

Seems overcooked in some places and undercooked in others. I of course prefer my own CLI template but so it goes: https://github.com/carlmjohnson/go-cli

Would you mind sharing some concrete feedback? I'll make improvements for next time.

Sure, I’m on a real keyboard now.

You don’t need separate client and models packages. You should have two packages: main (which has only one line) and everything else (call it package xkcd).

Your main continues even after it finds an error in fetching the comic. This is because you can’t return err in main and you didn’t use panic/log.Fatal. You also don’t exit with a non-zero code on error.

The solution to all of this is the one line main function. Main should look something like os.Exit(xkcd.Run(os.Args[1:])). I have a helper package at github.com/carlmjohnson/exitcode so you can return an error with an associated exit code, but you can also be simple and just return an int.

Separate flag gathering/processing from execution. You’re not using global flags, which is a good step, but you should go further and make an appEnv struct that consolidates what you’re taking in from the flags. See here: https://play.golang.org/p/-gs5nqXBSuB

Your client package basically doesn’t need to exist. What you want are some simple convenience wrappers around the http package and a URL builder for XKCD. Make something generic for HTTP and you can copypasta it into your future projects. Basically, it just needs to be httphelper.GetJSON(cl ∗http.Client, url string, data interface{}) error. Saving to disk is a separate idea that you’re conflating with downloading. Make something like jsonhelper.SaveToDisk(path string, data interface{}) error. The timeout stuff you’re doing is overblown. Either just use context.WithTimeout or put a ∗http.Client in your appEnv and have ParseArgs set the default timeout on that.

The stuff with the base URL is unnecessary. Obviously you know the XKCD base URL is a constant and will never change. But don’t you need to set it in an XKCDClient struct for testing purposes? The answer is no. If you take in a ∗http.Client, that can set a different http.RoundTripper for testing purposes and the test RoundTripper can read from memory or do whatever you want. (You can see this principle at work in Google’s Go http libraries. Once you realize how powerful ∗http.Client is, it makes a lot of the hoops other libraries jump through see like a waste of time. It can do all your auth stuff, caching stuff, everything. It’s great.)

The model package should just be a file in your xkcd package. I find the names Comic vs. ComicResponse confusing. Do you need Comic at all? Maybe just add some nice helper methods onto ComicResponse. Don’t do cr.FormattedDate() string. Do cr.Date() time.Time and let the output layers handler formatting, not the model layer. The c.JSON() string method doesn’t need to exist. With Go, you can run into this problem of trying to make things more convenient by adding helpers but you end up with methods that just run two commands and don’t actually make things more convenient on net. Is this really a model level concern or should it just be in the xkcd.Run() function?

Anyway, not to be overly negative. For such a small app, none of this really matters. I’ve been making a lot of Go CLIs for a long time[1] and my experience is that the most important thing is to separate flag stuff from execution, and everything else is not a big deal to let evolve over time. The main challenge is avoiding create abstractions that don’t actually pay for themselves in setup time vs. time saved in extension.

[1]: https://blog.carlmjohnson.net/post/2018/go-cli-tools/

Re: Diving into Go by building a CLI application

#82

My Opinion: The best cli lib I found: https://github.com/urfave/cli For deployment I recommend: https://github.com/goreleaser/goreleaser During development I recommend: https://github.com/golangci/golangci-lint and https://github.com/stretchr/testify

I only recently discovered goreleaser and I can't get over how simple it was to add to my CI setup. Distributing binaries was my goal for having a "real" open source project people could use. After a year of avoiding the issue, I discovered goreleaser and got it running in less than an hour.

Re: Diving into Go by building a CLI application

#83
post #82

My Opinion: The best cli lib I found: https://github.com/urfave/cli For deployment I recommend: https://github.com/goreleaser/goreleaser During development I recommend: https://github.com/golangci/golangci-lint and https://github.com/stretchr/testify

I only recently discovered goreleaser and I can't get over how simple it was to add to my CI setup. Distributing binaries was my goal for having a "real" open source project people could use. After a year of avoiding the issue, I discovered goreleaser and got it running in less than an hour.

I wrote a few small cli tools and the first 2 things i always add is urfave cli and goreleaser... even if you only deploy and use local, this is a great way to release getopt compilant multiple platform builds with or without subcommands.

Re: Diving into Go by building a CLI application

#84
post #21

Go is a particularly good language for CLI's I have found. At least compared to Java/C#/Python. It's reasonably fast, compiles down to a simple to distribute binary, and the language is forgiving enough that you can do exploratory programming in it. Go-routines make it especially easy to deal with network calls in it as well. For anything that needs absolute performance though look elsewhere, but even then Go might b…

Maybe I'm just unreasonable, but it always slightly bothered me when a JS or Python CLI utility has that half second of startup before doing anything, even displaying help. I can't be too annoyed, since in reality it's only a fraction of a second and they're spinning up the entire interpreter.

Single binary is also another one that really shouldn't matter to me, but still does. Especially for small utilities or web services, it's just really nice to know that I can `scp` to my server and just run it if I wanted to, even though, in reality, I always use a Docker container.

Re: Diving into Go by building a CLI application

#85
post #40
post #21

Go is a particularly good language for CLI's I have found. At least compared to Java/C#/Python. It's reasonably fast, compiles down to a simple to distribute binary, and the language is forgiving enough that you can do exploratory programming in it. Go-routines make it especially easy to deal with network calls in it as well. For anything that needs absolute performance though look elsewhere, but even then Go might b…

I've found Rust to be more interesting for CLIs than go. Especially when using https://clap.rs

clap looks cool, thanks.

Re: Diving into Go by building a CLI application

#86
post #21

Go is a particularly good language for CLI's I have found. At least compared to Java/C#/Python. It's reasonably fast, compiles down to a simple to distribute binary, and the language is forgiving enough that you can do exploratory programming in it. Go-routines make it especially easy to deal with network calls in it as well. For anything that needs absolute performance though look elsewhere, but even then Go might b…

I work on ML infrastructure (https://github.com/cortexlabs/cortex) and we originally wrote our CLI in Python. Rewriting it in Go has been a major win both in terms of performance and cross-platform support.

Re: Diving into Go by building a CLI application

#87
post #28
post #21

Go is a particularly good language for CLI's I have found. At least compared to Java/C#/Python. It's reasonably fast, compiles down to a simple to distribute binary, and the language is forgiving enough that you can do exploratory programming in it. Go-routines make it especially easy to deal with network calls in it as well. For anything that needs absolute performance though look elsewhere, but even then Go might b…

CLIs are sorta my ideal use-case for Go. Goroutines are so error-prone to control since you don't have many options for abstraction, so it's relatively difficult to build long-running highly-stable programs... But CLIs don't usually need that. They can be ctrl-C'd if they go off the rails, and any dangling goroutines just die when the process dies. The simple distribution, fast startup, simple type system, and yolo-c…

"Goroutines are so error-prone to control since you don't have many options for abstraction, so it's relatively difficult to build long-running highly-stable programs..."

This comment does not really makes sense, Go #1 usage is for backend services, so it's indeed long running / stable program.

"They can be ctrl-C'd if they go off the rails, and any dangling goroutines just die when the process dies"

There are solution in Go to handle that case, using context and channel but ultimatly it's not a Go problem if you kill an app right away usually there is no way to clean-up everything in a clean way.

Re: Diving into Go by building a CLI application

#88
post #21

Go is a particularly good language for CLI's I have found. At least compared to Java/C#/Python. It's reasonably fast, compiles down to a simple to distribute binary, and the language is forgiving enough that you can do exploratory programming in it. Go-routines make it especially easy to deal with network calls in it as well. For anything that needs absolute performance though look elsewhere, but even then Go might b…

Cobra is great, I created a test project long ago if anyone wants some sample code: https://github.com/rickcrawford/commandline

Re: Diving into Go by building a CLI application

#89
post #37
post #21

Go is a particularly good language for CLI's I have found. At least compared to Java/C#/Python. It's reasonably fast, compiles down to a simple to distribute binary, and the language is forgiving enough that you can do exploratory programming in it. Go-routines make it especially easy to deal with network calls in it as well. For anything that needs absolute performance though look elsewhere, but even then Go might b…

> [Go] is forgiving enough that you can do exploratory programming in it. I agree with most of your post, but I’m not sure I would describe Go as “forgiving”. In fact, it’s well known for being strict. For example, exploratory programming would be significantly easier if the Go compiler could (optionally) ignore unused variables and unreachable code. I’ve also found exploratory programming in Go is hindered by needin…

I had the same opinion you did until I went to a Go meetup and everyone else was a C programmer. Coming from Ruby, Go wasn't expressive or forgiving. But everyone coming from C thought it was wonderful.

Personally, I feel you with respect to casting integer types. I'll code away with int's until something suddenly needs an int64 to use a package and I have to cast everything or refactor everything to int64. I once commented in a thread where people were asked, "In hindsight what feature would you like in Go?" I said that int should just be an alias for int64 (and float == float64), since these were the defaults in the stdlib. I was downvoted into oblivion in a thread on hypotheticals. I understand the historical machine dependent 32/64 difference, but since the stdlib made a choice, the default should line up nicely. That said, I mostly run into this in Project Euler problems, so not in my day to day work.

Re: Diving into Go by building a CLI application

#90
post #85
post #40

Earlier quoted context omitted.

I've found Rust to be more interesting for CLIs than go. Especially when using https://clap.rs

clap looks cool, thanks.

Check out structopt. It's a declarative layer atop clap. It's a bit polarising, but if you don't mind the "magic" (which you probably don't if you think clap looks nice) it's amazing.
Post reply on HN