Command-line tools always make me smile. I love using small, useful utilities to solve problems, but I've found it even more fun to build my own tools. The Go programming language is one of the best ways to build these tools because you can create self-contained executables for multiple platforms. In this issue, you'll see how quickly you can build a tiny Go CLI app by building one that serves a directory over HTTP, even if you've never used Go before.
Education myths make me frown, though, because they make it harder on learners and teachers alike. One of those myths is the Learning Pyramid. You've heard of it, and if you've ever done content, you've had someone else tell you how some things are "better" than others for retaining information. Like learning styles, it's another myth without scientific backing. Read more to learn why.
You've heard this before:
"You remember 10% of what you read, 20% of what you hear, 30% of what you see..."
You've heard people use this to justify video content over written content or promote lectures over reading. If you've done any teaching or curriculum development, you've no doubt been directed to keep this in mind. But like learning styles, it's a myth that isn't backed up by any data. In fact, the theory's originator makes it clear that there is no science here at all.
Edgar Dale's Cone of Experience was a theoretical model that aimed to categorize learning experiences based on their concreteness or abstractness. The bottom of the cone represented direct, purposeful experiences, such as hands-on learning. As you moved up the cone, experiences became more abstract, like reading about a topic or hearing a lecture. Dale's cone was a visual metaphor intended to suggest that more concrete experiences are more effective in learning.
This is the original version of the cone:
There are ten categories:
Dale never meant for this to be prescriptive. In his book Audio-Visual Methods of Teaching, he stated that the cone should not be taken literally. He emphasized that the cone was a visual aid meant to illustrate a continuum of learning experiences, from concrete to abstract, rather than a precise guide to be followed rigidly.
"The cone device is a visual metaphor of learning experiences, in which the various types of audio-visual materials are arranged in the order of increasing abstractness as one proceeds from direct experiences"
Dale never included percentages in his original model. He stated clearly that his cone isn't based on scientific study. The percentages are inaccurately attributed to Dale and lack empirical support.
The percentages seem to have come from an article in the magazine Film and Audio Visual Communications in 1967, assigned somewhat randomly by its author, and then popularized by "The Learning Pyramid" promoted by the National Training Laboratories (NTL) Institute for Applied Behavioral Sciences. In fact, they even attempt to cite Dale's Cone, inaccurately stating that it had numbers.
You've probably seen all sorts of variations of this "learning pyramid." Content producers, HR learning and development professionals, and college curriculum developers continue to use this pyramid as the basis for decisions about the learning pathways they create. And there's no clear evidence it's anything other than an opinion. But there are plenty of people who've worked to disprove it.
Here are two sources:
The specific percentages oversimplify the complex process of learning and memory. When you're creating a learning experience, offer learners the opportunity to watch, listen, read, and do. Give them the opportunity to practice and get feedback on their work. That's what Dale was really getting at with his cone; a holistic learning experience is the best kind of experience.
You can use the Go programming language for many tasks, but it shines at building self-contained tools that work across platforms. You can write the code on your computer but produce binaries that run on other operating systems. Your users won't need to have Go installed to run these apps, either, unlike tools written in Node.js or Python.
Follow along to create your first CLI app. You don't need Go experience; feel free to copy and paste the code. The goal is for you to experience building a CLI tool, not learn Go.
First, install Go. You can do this by following the instructions on the official Go website.
Next, create a new file in your text editor called server.go
and add the following code to define a package:
package main
import (
"flag"
"fmt"
"log"
"net/http"
)
In Go, a command-line program must have a package called main
to indicate it is an executable program rather than a library.
The flag
package lets you define command-line flags. The fat
package lets you print strings to the screen. The log
package lets you write logs to the screen or other devices, and the net/http
library lets you define and run a server. Go's standard library is robust.
Next, define the arguments your program will take. You'll want one flag to set the port for the server and another flag to display the app's version on the screen:
var (
port = flag.String("port", "8000", "define the port for your server")
version = flag.Bool("version", false, "show version information")
)
When defining flags, you can set the default values. In this case, if the user doesn't specify a port, it will default to 8000
. Each entry also defines the help message for the argument, which is displayed when you pass the -h
flag to the program.
Next, add a constant to hold the current version and a function to display the current version:
const serverVersion = "1.0"
func showVersion() {
fmt.Printf("Simple Go Server version: %s\n", serverVersion)
}
Next, add a function to start the server that listens on the IP address 0.0.0.0
on the port passed in as the function's argument:
func startServer(port string) {
fs := http.FileServer(http.Dir("."))
http.Handle("/", fs)
log.Printf("Serving on http://0.0.0.0:%s...", port)
err := http.ListenAndServe("0.0.0.0:"+port, nil)
if err != nil {
log.Fatal(err)
}
}
Go's net/http
library already includes a file server. The line fs := http.FileServer(http.Dir("."))
tells the file server to use the current directory.
The line http.Handle("/", fs)
means "register fs
, which is a file server, as the handler for all HTTP requests coming to the root URL and beyond."
The rest of the function attempts to start the server. If there's an error, like the port is in use, the program will halt with an error message.
Finally, add a function called main
, which is the entry point of your command-line program:
func main() {
flag.Parse()
if *version {
showVersion()
return
}
startServer(*port)
}
This function parses out the flags. If the version
flag is set, the code displays the version and exits. Otherwise, it starts the server, passing the port.
You probably noticed that the version
and port
variables are prefixed with an asterisk. The flags
package uses pointers, so the asterisk is how you get the actual value you need. If you left the asterisk off, you'd reference the pointer rather than the value. If you decide to dig into Go further, you'll learn much more about how pointers work. For now, just consider this a requirement to get the values from the command line arguments.
That's all the code you need. The complete program looks like this:
package main
import (
"flag"
"fmt"
"log"
"net/http"
)
var (
port = flag.String("port", "8000", "define the port for your server")
version = flag.Bool("version", false, "show version information")
)
const serverVersion = "1.0"
func showVersion() {
fmt.Printf("Simple Go Server version: %s\n", serverVersion)
}
func startServer(port string) {
fs := http.FileServer(http.Dir("."))
http.Handle("/", fs)
log.Printf("Serving on http://0.0.0.0:%s...", port)
err := http.ListenAndServe("0.0.0.0:"+port, nil)
if err != nil {
log.Fatal(err)
}
}
func main() {
flag.Parse()
if *version {
showVersion()
return
}
startServer(*port)
}
You have a complete HTTP file server in under 50 lines of code.
Save the file. It's time to make sure it works.
Test out your server. First, run it with the following command at the command prompt:
$ go run server.go
The file server starts and displays the following message:
2024/02/28 21:37:11 Serving on http://0.0.0.0:8000...
Visit that address in your browser to see the contents of the current directory.
To stop the server, return to the command window running your server and press CTRL+C
.
Now test the help message by passing the -h
flag:
$ gu run server.go -h
The help message appears on the screen, using the messages you defined:
-port string
define the port for your server (default "8000")
-version
show version information
Next, test the -version
flag.
$ go run server.go -version
You'll see the version printed:
Simple Go Server version: 1.0
Now try launching with a different port, like 1337
:
$ go run server.go -port 1337
This time, the server starts on the port you specified, which you see in the log output:
2024/02/28 21:44:57 Serving on http://0.0.0.0:1337..
Press CTRL-C
to stop the server.
Now that you have a working program, you can compile it.
The go build
command builds executables. By default, it makes an executable that works on the platform you're currently using, but you can use environment variables to specify the target OS and CPU architecture. The GOOS
variable specifies the operating system, while GOARCH
specifies the architecture. The `-o' flag specifies the output file name.
To build a 64-bit version for Windows, run the following command:
$ GOOS=windows GOARCH=amd64 go build -o server.exe server.go
To build a version for Macs running one of Apple's M1, M2, or M3 chips, use this command:
$ GOOS=darwin GOARCH=arm64 go build -o server server.go
For Macs running Intel, change the GOARCH
value:
$ GOOS=darwin GOARCH=amd64 go build -o server server.go
If you're using Linux, change GOOS
to linux
:
GOOS=linux GOARCH=amd64 go build -o server main.go
Once they're built, try running them. On macOS or Linux, you'll need to run the command as ./server
unless you move the file somewhere on your system path.
This little project just scratches the surface of Go and its capabilities, but hopefully, you can see how quickly you can build small cross-platform tools. And remember: unlike tools written with Node.js or Python, users don't need to install the programming language to use your tools; they only need your executable. That's a huge advantage.
Interested enough to dive deeper? I had the pleasure of editing a great book on building CLI apps with Go by Ricardo Gerardi, and it guides you through creating some complex, real-world applications. You'll learn a ton about Go and the CLI as you work through the projects in the book.
Here are a couple things to think about before the next issue hits your inbox.
As always, thanks for reading.
I'd love to talk with you about this newsletter on Mastodon, Twitter, or LinkedIn. Let's connect!
Please support this newsletter and my work by encouraging others to subscribe or by buying a friend a copy of Exercises for Programmers, Small, Sharp Software Tools, or any of my other books.