mirror of
https://github.com/bootdotdev/fcc-learn-golang-assets.git
synced 2025-12-13 16:51:17 +00:00
first
This commit is contained in:
14
course/12-local_development/exercises/1-intro/code.go
Normal file
14
course/12-local_development/exercises/1-intro/code.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package mailio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func test(text string) {
|
||||
fmt.Println(text)
|
||||
}
|
||||
|
||||
func main() {
|
||||
test("starting Mailio server")
|
||||
test("stopping Mailio server")
|
||||
}
|
||||
14
course/12-local_development/exercises/1-intro/complete.go
Normal file
14
course/12-local_development/exercises/1-intro/complete.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func test(text string) {
|
||||
fmt.Println(text)
|
||||
}
|
||||
|
||||
func main() {
|
||||
test("starting Mailio server")
|
||||
test("stopping Mailio server")
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
starting Mailio server
|
||||
stopping Mailio server
|
||||
28
course/12-local_development/exercises/1-intro/readme.md
Normal file
28
course/12-local_development/exercises/1-intro/readme.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Packages
|
||||
|
||||
Every Go program is made up of packages.
|
||||
|
||||
You have probably noticed the `package main` at the top of all the programs you have been writing.
|
||||
|
||||
A package named "main" has an entrypoint at the `main()` function. A `main` package is compiled into an executable program.
|
||||
|
||||
A package by any other name is a "library package". Libraries have no entry point. Libraries simply export functionality that can be used by other packages. For example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("My favorite number is", rand.Intn(10))
|
||||
}
|
||||
```
|
||||
|
||||
This program is an executable. It is a "main" package and *imports* from the `fmt` and `math/rand` library packages.
|
||||
|
||||
## Assignment
|
||||
|
||||
Fix the bug in the code.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "What does 'go install' do?",
|
||||
"answers": [
|
||||
"Compiles and installs the program locally",
|
||||
"Installs dependencies",
|
||||
"Saves local code to the remote source control provider"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Go Install
|
||||
|
||||
## Build an executable
|
||||
|
||||
Ensure you are in your `hellogo` repo, then run:
|
||||
|
||||
```bash
|
||||
go install
|
||||
```
|
||||
|
||||
Navigate out of your project directory:
|
||||
|
||||
```bash
|
||||
cd ../
|
||||
```
|
||||
|
||||
Go has installed the `hellogo` program globally. Run it with:
|
||||
|
||||
```bash
|
||||
hellogo
|
||||
```
|
||||
|
||||
## Tip about "not found"
|
||||
|
||||
If you get an error regarding "hellogo not found" it means you probably don't have your Go environment setup properly. Specifically, `go install` is adding your binary to your `GOBIN` directory, but that may not be in your `PATH`.
|
||||
|
||||
You can read more about that here in the [go install docs](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies).
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Code must be compiled with 'go build' before running 'go install'",
|
||||
"answers": [
|
||||
"False",
|
||||
"True"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Go Install
|
||||
|
||||
## Build an executable
|
||||
|
||||
Ensure you are in your `hellogo` repo, then run:
|
||||
|
||||
```bash
|
||||
go install
|
||||
```
|
||||
|
||||
Navigate out of your project directory:
|
||||
|
||||
```bash
|
||||
cd ../
|
||||
```
|
||||
|
||||
Go has installed the `hellogo` program globally. Run it with:
|
||||
|
||||
```bash
|
||||
hellogo
|
||||
```
|
||||
|
||||
## Tip about "not found"
|
||||
|
||||
If you get an error regarding "hellogo not found" it means you probably don't have your Go environment setup properly. Specifically, `go install` is adding your binary to your `GOBIN` directory, but that may not be in your `PATH`.
|
||||
|
||||
You can read more about that here in the [go install docs](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies).
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "What was the output from 'go build' in the library package",
|
||||
"answers": [
|
||||
"The compiled package is silently saved to the local build cache",
|
||||
"An executable program"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# Custom Package
|
||||
|
||||
Let's write a package to import and use in `hellogo`.
|
||||
|
||||
Create a sibling directory at the same level as the `hellogo` directory:
|
||||
|
||||
```bash
|
||||
mkdir mystrings
|
||||
cd mystrings
|
||||
```
|
||||
|
||||
Initialize a module:
|
||||
|
||||
```bash
|
||||
go mod init {REMOTE}/{USERNAME}/mystrings
|
||||
```
|
||||
|
||||
Then create a new file `mystrings.go` in that directory and paste the following code:
|
||||
|
||||
```go
|
||||
// by convention, we name our package the same as the directory
|
||||
package mystrings
|
||||
|
||||
// Reverse reverses a string left to right
|
||||
// Notice that we need to capitalize the first letter of the function
|
||||
// If we don't then we won't be able access this function outside of the
|
||||
// mystrings package
|
||||
func Reverse(s string) string {
|
||||
result := ""
|
||||
for _, v := range s {
|
||||
result = string(v) + result
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
Note that there is no `main.go` or `func main()` in this package.
|
||||
|
||||
`go build` won't build an executable from a library package. However, `go build` will still compile the package and save it to our local build cache. It's useful for checking for compile errors.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go build
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Why is the function 'Reverse()' instead of 'reverse()'?",
|
||||
"answers": [
|
||||
"Lowercase names aren't exported for external use",
|
||||
"Conventionally uppercase names are used in Go"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# Custom Package
|
||||
|
||||
Let's write a package to import and use in `hellogo`.
|
||||
|
||||
Create a sibling directory at the same level as the `hellogo` directory:
|
||||
|
||||
```bash
|
||||
mkdir mystrings
|
||||
cd mystrings
|
||||
```
|
||||
|
||||
Initialize a module:
|
||||
|
||||
```bash
|
||||
go mod init {REMOTE}/{USERNAME}/mystrings
|
||||
```
|
||||
|
||||
Then create a new file `mystrings.go` in that directory and paste the following code:
|
||||
|
||||
```go
|
||||
// by convention, we name our package the same as the directory
|
||||
package mystrings
|
||||
|
||||
// Reverse reverses a string left to right
|
||||
// Notice that we need to capitlize the first letter of the function
|
||||
// If we don't then we won't be able access this function outside of the
|
||||
// mystrings package
|
||||
func Reverse(s string) string {
|
||||
result := ""
|
||||
for _, v := range s {
|
||||
result = string(v) + result
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
Note that there is no `main.go` or `func main()` in this package.
|
||||
|
||||
`go build` won't build an executable from a library package. However, `go build` will still compile the package and save it to our local build cache. It's useful for checking for compile errors.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go build
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "Does a package in a folder named 'dateparser' need to also be called 'dateparser'",
|
||||
"answers": [
|
||||
"No, but it should by convention",
|
||||
"Yes",
|
||||
"No, by convention we should avoid consistent naming"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# Custom Package
|
||||
|
||||
Let's write a package to import and use in `hellogo`.
|
||||
|
||||
Create a sibling directory at the same level as the `hellogo` directory:
|
||||
|
||||
```bash
|
||||
mkdir mystrings
|
||||
cd mystrings
|
||||
```
|
||||
|
||||
Initialize a module:
|
||||
|
||||
```bash
|
||||
go mod init {REMOTE}/{USERNAME}/mystrings
|
||||
```
|
||||
|
||||
Then create a new file `mystrings.go` in that directory and paste the following code:
|
||||
|
||||
```go
|
||||
// by convention, we name our package the same as the directory
|
||||
package mystrings
|
||||
|
||||
// Reverse reverses a string left to right
|
||||
// Notice that we need to capitlize the first letter of the function
|
||||
// If we don't then we won't be able access this function outside of the
|
||||
// mystrings package
|
||||
func Reverse(s string) string {
|
||||
result := ""
|
||||
for _, v := range s {
|
||||
result = string(v) + result
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
Note that there is no `main.go` or `func main()` in this package.
|
||||
|
||||
`go build` won't build an executable from a library package. However, `go build` will still compile the package and save it to our local build cache. It's useful for checking for compile errors.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go build
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "What was printed by the new hellogo program?",
|
||||
"answers": [
|
||||
"dlrow olleh",
|
||||
"hello world",
|
||||
"world hello"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Custom Package Continued
|
||||
|
||||
Let's use our new `mystrings` package in `hellogo`
|
||||
|
||||
Modify hellogo's `main.go` file:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"{REMOTE}/{USERNAME}/mystrings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(mystrings.Reverse("hello world"))
|
||||
}
|
||||
```
|
||||
|
||||
Don't forget to replace {REMOTE} and {USERNAME} with the values you used before. Then edit hellogo's `go.mod` file to contain the following:
|
||||
|
||||
```go
|
||||
module example.com/username/hellogo
|
||||
|
||||
go 1.20
|
||||
|
||||
replace example.com/username/mystrings v0.0.0 => ../mystrings
|
||||
|
||||
require (
|
||||
example.com/username/mystrings v0.0.0
|
||||
)
|
||||
```
|
||||
|
||||
Now build and run the new program:
|
||||
|
||||
```bash
|
||||
go build
|
||||
./hellogo
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"question": "How does the go toolchain know where to find the imported code?",
|
||||
"answers": [
|
||||
"We used the 'replace' keyword in go.mod to point it to the relative location of mystrings",
|
||||
"It downloads it from Google's servers",
|
||||
"NPM hosts the files publicly",
|
||||
"It was fetched from Github"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Custom Package Continued
|
||||
|
||||
Let's use our new `mystrings` package in `hellogo`
|
||||
|
||||
Modify hellogo's `main.go` file:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"{REMOTE}/{USERNAME}/mystrings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(mystrings.Reverse("hello world"))
|
||||
}
|
||||
```
|
||||
|
||||
Don't forget to replace {REMOTE} and {USERNAME} with the values you used before. Then edit hellogo's `go.mod` file to contain the following:
|
||||
|
||||
```go
|
||||
module example.com/username/hellogo
|
||||
|
||||
go 1.20
|
||||
|
||||
replace example.com/username/mystrings v0.0.0 => ../mystrings
|
||||
|
||||
require (
|
||||
example.com/username/mystrings v0.0.0
|
||||
)
|
||||
```
|
||||
|
||||
Now build and run the new program:
|
||||
|
||||
```bash
|
||||
go build
|
||||
./hellogo
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "How did the Go toolchain know where to download the go-tinytime package?",
|
||||
"answers": [
|
||||
"The module import path is used for remote lookups, e.g. https://github.com/wagslane/go-tinytime",
|
||||
"The go toolchain has every open-source Go module's location memorized"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
# Remote Packages
|
||||
|
||||
Let's learn how to use an open-source package that's available online.
|
||||
|
||||
## A note on how you should publish modules
|
||||
|
||||
Be aware that using the "replace" keyword like we did in the last assignment *isn't advised*, but can be useful to get up and running quickly. The *proper* way to create and depend on modules is to publish them to a remote repository. When you do that, the "replace keyword can be dropped from the `go.mod`:
|
||||
|
||||
### Bad
|
||||
|
||||
This works for local-only development
|
||||
|
||||
```go
|
||||
module github.com/wagslane/hellogo
|
||||
|
||||
go 1.20
|
||||
|
||||
replace github.com/wagslane/mystrings v0.0.0 => ../mystrings
|
||||
|
||||
require (
|
||||
github.com/wagslane/mystrings v0.0.0
|
||||
)
|
||||
```
|
||||
|
||||
### Good
|
||||
|
||||
This works if we publish our modules to a remote location like Github as we should.
|
||||
|
||||
```go
|
||||
module github.com/wagslane/hellogo
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/wagslane/mystrings v0.0.0
|
||||
)
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
First, create a new directory in the same parent directory as `hellogo` and `mystrings` called `datetest`.
|
||||
|
||||
Create `main.go` in `datetest` and add the following code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
tinytime "github.com/wagslane/go-tinytime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tt := tinytime.New(1585750374)
|
||||
|
||||
tt = tt.Add(time.Hour * 48)
|
||||
fmt.Println(tt)
|
||||
}
|
||||
```
|
||||
|
||||
Initialize a module:
|
||||
|
||||
```bash
|
||||
go mod init {REMOTE}/{USERNAME}/datetest
|
||||
```
|
||||
|
||||
Download and install the remote go-tinydate package using `go get`:
|
||||
|
||||
```bash
|
||||
go get github.com/wagslane/go-tinytime
|
||||
```
|
||||
|
||||
Print the contents of your go.mod file to see the changes:
|
||||
|
||||
```bash
|
||||
cat go.mod
|
||||
```
|
||||
|
||||
Compile and run your program:
|
||||
|
||||
```bash
|
||||
go build
|
||||
./datetest
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "What was printed after running the new datetest program?",
|
||||
"answers": [
|
||||
"2020-04-03T14:12:54Z",
|
||||
"2025-04-03T14:12:54Z",
|
||||
"Year: 2020, Month: 04, Day: 05"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
# Remote Packages
|
||||
|
||||
|
||||
Let's learn how to use an open-source package that's available online.
|
||||
|
||||
## A note on how you should publish modules
|
||||
|
||||
Be aware that using the "replace" keyword like we did in the last assignment *isn't advised*, but can be useful to get up and running quickly. The *proper* way to create and depend on modules is to publish them to a remote repository. When you do that, the "replace keyword can be dropped from the `go.mod`:
|
||||
|
||||
### Bad
|
||||
|
||||
This works for local-only development
|
||||
|
||||
```go
|
||||
module github.com/wagslane/hellogo
|
||||
|
||||
go 1.20
|
||||
|
||||
replace github.com/wagslane/mystrings v0.0.0 => ../mystrings
|
||||
|
||||
require (
|
||||
github.com/wagslane/mystrings v0.0.0
|
||||
)
|
||||
```
|
||||
|
||||
### Good
|
||||
|
||||
This works if we publish our modules to a remote location like Github as we should.
|
||||
|
||||
```go
|
||||
module github.com/wagslane/hellogo
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/wagslane/mystrings v0.0.0
|
||||
)
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
First, create a new directory in the same parent directory as `hellogo` and `mystrings` called `datetest`.
|
||||
|
||||
Create `main.go` in `datetest` and add the following code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
tinytime "github.com/wagslane/go-tinytime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tt := tinytime.New(1585750374)
|
||||
|
||||
tt = tt.Add(time.Hour * 48)
|
||||
fmt.Println(tt)
|
||||
}
|
||||
```
|
||||
|
||||
Initialize a module:
|
||||
|
||||
```bash
|
||||
go mod init {REMOTE}/{USERNAME}/datetest
|
||||
```
|
||||
|
||||
Download and install the remote go-tinydate package using `go get`:
|
||||
|
||||
```bash
|
||||
go get github.com/wagslane/go-tinytime
|
||||
```
|
||||
|
||||
Print the contents of your go.mod file to see the changes:
|
||||
|
||||
```bash
|
||||
cat go.mod
|
||||
```
|
||||
|
||||
Compile and run your program:
|
||||
|
||||
```bash
|
||||
go build
|
||||
./datetest
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Should you export code from the main package?",
|
||||
"answers": [
|
||||
"Nope",
|
||||
"Yup"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Clean Packages
|
||||
|
||||
I’ve often seen, and have been responsible for, throwing code into packages without much thought. I’ve quickly drawn a line in the sand and started putting code into different folders (which in Go are different packages by definition) just for the sake of findability. Learning to properly build small and reusable packages can take your Go career to the next level.
|
||||
|
||||
## Rules Of Thumb
|
||||
|
||||
### 1. Hide internal logic
|
||||
|
||||
If you're familiar with the pillars of OOP, this is a practice in *encapsulation*.
|
||||
|
||||
Oftentimes an application will have complex logic that requires a lot of code. In almost every case the logic that the application cares about can be exposed via an API, and most of the dirty work can be kept within a package. For example, imagine we are building an application that needs to classify images. We could build a package:
|
||||
|
||||
```go
|
||||
package classifier
|
||||
|
||||
// ClassifyImage classifies images as "hotdog" or "not hotdog"
|
||||
func ClassifyImage(image []byte) (imageType string) {
|
||||
return hasHotdogColors(image) && hasHotdogShape(image)
|
||||
}
|
||||
|
||||
func hasHotdogShape(image []byte) bool {
|
||||
// internal logic that the application doesn't need to know about
|
||||
return true
|
||||
}
|
||||
|
||||
func hasHotdogColors(image []byte) bool {
|
||||
// internal logic that the application doesn't need to know about
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
We create an API by only exposing the function(s) that the application-level needs to know about. All other logic is unexported to keep a clean separation of concerns. The application doesn’t need to know how to classify an image, just the result of the classification.
|
||||
|
||||
### 2. Don’t change APIs
|
||||
|
||||
The unexported functions within a package can and should change often for testing, refactoring, and bug fixing.
|
||||
|
||||
A well-designed library will have a stable API so that users aren’t receiving breaking changes each time they update the package version. In Go, this means not changing exported function’s signatures.
|
||||
|
||||
### 3. Don’t export functions from the main package
|
||||
|
||||
A `main` package isn't a library, there's no need to export functions from it.
|
||||
|
||||
### 4. Packages shouldn't know about dependents
|
||||
|
||||
Perhaps one of the most important and most broken rules is that a package shouldn’t know anything about its dependents. In other words, a package should never have specific knowledge about a particular application that uses it.
|
||||
|
||||
## Further Reading
|
||||
|
||||
You can optionally [read more here](https://blog.boot.dev/golang/how-to-separate-library-packages-in-go/) if you're interested.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "When should you NOT export a function, variable, or type?",
|
||||
"answers": [
|
||||
"When the end-user doesn't need to know about it",
|
||||
"Never, its better to share code!"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Clean Packages
|
||||
|
||||
I’ve often seen, and have been responsible for, throwing code into packages without much thought. I’ve quickly drawn a line in the sand and started putting code into different folders (which in Go are different packages by definition) just for the sake of findability. Learning to properly build small and reusable packages can take your Go career to the next level.
|
||||
|
||||
## Rules Of Thumb
|
||||
|
||||
### 1. Hide internal logic
|
||||
|
||||
If you're familiar with the pillars of OOP, this is a practice in *encapsulation*.
|
||||
|
||||
Oftentimes an application will have complex logic that requires a lot of code. In almost every case the logic that the application cares about can be exposed via an API, and most of the dirty work can be kept within a package. For example, imagine we are building an application that needs to classify images. We could build a package:
|
||||
|
||||
```go
|
||||
package classifier
|
||||
|
||||
// ClassifyImage classifies images as "hotdog" or "not hotdog"
|
||||
func ClassifyImage(image []byte) (imageType string) {
|
||||
return hasHotdogColors(image) && hasHotdogShape(image)
|
||||
}
|
||||
|
||||
func hasHotdogShape(image []byte) bool {
|
||||
// internal logic that the application doesn't need to know about
|
||||
return true
|
||||
}
|
||||
|
||||
func hasHotdogColors(image []byte) bool {
|
||||
// internal logic that the application doesn't need to know about
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
We create an API by only exposing the function(s) that the application-level needs to know about. All other logic is unexported to keep a clean separation of concerns. The application doesn’t need to know how to classify an image, just the result of the classification.
|
||||
|
||||
### 2. Don’t change APIs
|
||||
|
||||
The unexported functions within a package can and should change often for testing, refactoring, and bug fixing.
|
||||
|
||||
A well-designed library will have a stable API so that users aren’t receiving breaking changes each time they update the package version. In Go, this means not changing exported function’s signatures.
|
||||
|
||||
### 3. Don’t export functions from the main package
|
||||
|
||||
A `main` package isn't a library, there's no need to export functions from it.
|
||||
|
||||
### 4. Packages shouldn't know about dependents
|
||||
|
||||
Perhaps one of the most important and most broken rules is that a package shouldn’t know anything about its dependents. In other words, a package should never have specific knowledge about a particular application that uses it.
|
||||
|
||||
## Further Reading
|
||||
|
||||
You can optionally [read more here](https://blog.boot.dev/golang/how-to-separate-library-packages-in-go/) if you're interested.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "Should you often change a package's exported API?",
|
||||
"answers": [
|
||||
"No, try to keep changes to internal functionality",
|
||||
"Yes, move fast and break things",
|
||||
"If the package is 'main' then yes"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
# Clean Packages
|
||||
|
||||
I’ve often seen, and have been responsible for, throwing code into packages without much thought. I’ve quickly drawn a line in the sand and started putting code into different folders (which in Go are different packages by definition) just for the sake of findability. Learning to properly build small and reusable packages can take your Go career to the next level.
|
||||
|
||||
## Rules Of Thumb
|
||||
|
||||
### 1. Hide internal logic
|
||||
|
||||
If you're familiar with the pillars of OOP, this is a practice in *encapsulation*.
|
||||
|
||||
Oftentimes an application will have complex logic that requires a lot of code. In almost every case the logic that the application cares about can be exposed via an API, and most of the dirty work can be kept within a package. For example, imagine we are building an application that needs to classify images. We could build a package:
|
||||
|
||||
```go
|
||||
package classifier
|
||||
|
||||
// ClassifyImage classifies images as "hotdog" or "not hotdog"
|
||||
func ClassifyImage(image []byte) (imageType string) {
|
||||
return hasHotdogColors(image) && hasHotdogShape(image)
|
||||
}
|
||||
|
||||
func hasHotdogShape(image []byte) bool {
|
||||
// internal logic that the application doesn't need to know about
|
||||
return true
|
||||
}
|
||||
|
||||
func hasHotdogColors(image []byte) bool {
|
||||
// internal logic that the application doesn't need to know about
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
We create an API by only exposing the function(s) that the application level needs to know about. All other logic is unexported to keep a clean separation of concerns. The application doesn’t need to know how to classify an image, just the result of the classification.
|
||||
|
||||
### 2. Don’t change APIs
|
||||
|
||||
The unexported functions within a package can and should change often for testing, refactoring, and bug fixing.
|
||||
|
||||
A well-designed library will have a stable API so that users aren’t receive breaking changes each time they update the package version. In Go, this means not changing exported function’s signatures. If you *must* make breaking changes, then you should use [semantic versioning](https://semver.org/) to communicate the changes to your users by bumping the major version.
|
||||
|
||||
For example `v1.0.0` -> `v2.0.0`
|
||||
|
||||
### 3. Don’t export functions from the main package
|
||||
|
||||
A `main` package isn't a library, there's no need to export functions from it.
|
||||
|
||||
### 4. Packages shouldn't know about dependents
|
||||
|
||||
Perhaps one of the most important and most broken rules is that a package shouldn’t know anything about its dependents. In other words, a package should never have specific knowledge about a particular application that uses it.
|
||||
|
||||
## Further Reading
|
||||
|
||||
You can optionally [read more here](https://blog.boot.dev/golang/how-to-separate-library-packages-in-go/) if you're interested.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"question": "What would be the conventional package name of a package with the path github.com/wagslane/parser?",
|
||||
"answers": [
|
||||
"parser",
|
||||
"wagslane",
|
||||
"go-parser",
|
||||
"github.com"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Package Naming
|
||||
|
||||
## Naming Convention
|
||||
|
||||
By *convention*, a package's name is the same as the last element of its import path. For instance, the `math/rand` package comprises files that begin with:
|
||||
|
||||
```go
|
||||
package rand
|
||||
```
|
||||
|
||||
That said, package names aren't *required* to match their import path. For example, I could write a new package with the path `github.com/mailio/rand` and name the package `random`:
|
||||
|
||||
```go
|
||||
package random
|
||||
```
|
||||
|
||||
While the above is possible, it is discouraged for the sake of consistency.
|
||||
|
||||
## One Package / Directory
|
||||
|
||||
A directory of Go code can have **at most** one package. All `.go` files in a single directory must all belong to the same package. If they don't an error will be thrown by the compiler. This is true for main and library packages alike.
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"question": "Given the import path of path/to/rand, which of these is a valid package name?",
|
||||
"answers": [
|
||||
"Any of these",
|
||||
"path",
|
||||
"rand",
|
||||
"random",
|
||||
"spam"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Package Naming
|
||||
|
||||
## Naming Convention
|
||||
|
||||
By *convention*, a package's name is the same as the last element of its import path. For instance, the `math/rand` package comprises files that begin with:
|
||||
|
||||
```go
|
||||
package rand
|
||||
```
|
||||
|
||||
That said, package names aren't *required* to match their import path. For example, I could write a new package with the path `github.com/mailio/rand` and name the package `random`:
|
||||
|
||||
```go
|
||||
package random
|
||||
```
|
||||
|
||||
While the above is possible, it is discouraged for the sake of consistency.
|
||||
|
||||
## One Package / Directory
|
||||
|
||||
A directory of Go code can have **at most** one package. All `.go` files in a single directory must all belong to the same package. If they don't an error will be thrown by the compiler. This is true for main and library packages alike.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "What does the 'go version' command print?",
|
||||
"answers": [
|
||||
"go version {version} {os}/{architecture}",
|
||||
"go version {os}/{architecture} {version}",
|
||||
"{version} {os}/{architecture}"
|
||||
]
|
||||
}
|
||||
32
course/12-local_development/exercises/3-help/readme.md
Normal file
32
course/12-local_development/exercises/3-help/readme.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# About this chapter
|
||||
|
||||
Our courses can be completed *almost* completely in the browser, but there are some things that you need to do on your local machine for the sake of learning!
|
||||
|
||||
For the majority of this chapter, coding in the browser won't be useful because you need to learn how Go code is organized in a local development environment. We'll be explaining *how* to do this and quizzing you on it.
|
||||
|
||||
This section will almost entirely be quiz-style, with very little coding in the browser. As usual, if you get lost please use our discord channel as a resource:
|
||||
|
||||
[Discord Community](https://boot.dev/community)
|
||||
|
||||
## An example project to follow along with
|
||||
|
||||
If you get lost during this chapter, refer to this `src` folder of our "Social Media Server in Go" project that can be [found here](https://github.com/bootdotdev/projects/tree/main/projects/social-media-backend-golang/10-posts_endpoints/src). It will serve as a basic example of many of the concepts in this chapter.
|
||||
|
||||
## Unix
|
||||
|
||||
This guide will assume you are on a Unix environment like Linux or Mac. If you're on Windows you may have to do just a *bit* of Google-ing or ask in Discord to figure out how some commands translate to Windows.
|
||||
|
||||
If you are on Windows, I'd optionally recommend checking out [WSL (Windows Subsystem for Linux)](https://docs.microsoft.com/en-us/windows/wsl/install) so that you can work in a Unix environment on your local machine.
|
||||
|
||||
## Download Go locally
|
||||
|
||||
I typically recommend one of two ways:
|
||||
|
||||
* [Official Download](https://golang.org/doc/install)
|
||||
* [Webi Downloader](https://webinstall.dev/golang/)
|
||||
|
||||
Make sure to use at least version `1.20`. This can be verified after installation by typing:
|
||||
|
||||
```bash
|
||||
go version
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"question": "What is a Go module?",
|
||||
"answers": [
|
||||
"A collection of packages that are released together",
|
||||
"A library package",
|
||||
"An executable main package",
|
||||
"A file of Go code"
|
||||
]
|
||||
}
|
||||
33
course/12-local_development/exercises/4-modules/readme.md
Normal file
33
course/12-local_development/exercises/4-modules/readme.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Modules
|
||||
|
||||
Go programs are organized into *packages*. A package is a directory of Go code that's all compiled together. Functions, types, variables, and constants defined in one source file are visible to **all other source files within the same package (directory)**.
|
||||
|
||||
A *repository* contains one or more *modules*. A module is a collection of Go packages that are released together.
|
||||
|
||||
## A Go repository typically contains only one module, located at the root of the repository.
|
||||
|
||||
A file named `go.mod` at the root of a project declares the module. It contains:
|
||||
|
||||
* The module path
|
||||
* The version of the Go language your project requires
|
||||
* Optionally, any external package dependencies your project has
|
||||
|
||||
The module path is just the import path prefix for all packages within the module. Here's an example of a `go.mod` file:
|
||||
|
||||
```
|
||||
module github.com/bootdotdev/exampleproject
|
||||
|
||||
go 1.20
|
||||
|
||||
require github.com/google/examplepackage v1.3.0
|
||||
```
|
||||
|
||||
Each module's path not only serves as an import path prefix for the packages within but *also indicates where the go command should look to download it*. For example, to download the module `golang.org/x/tools`, the go command would consult the repository located at [https://golang.org/x/tools](https://golang.org/x/tools).
|
||||
|
||||
> An "import path" is a string used to import a package. A package's import path is its module path joined with its subdirectory within the module. For example, the module `github.com/google/go-cmp` contains a package in the directory `cmp/`. That package's import path is `github.com/google/go-cmp/cmp`. Packages in the standard library do not have a module path prefix.
|
||||
|
||||
- Paraphrased from Golang.org's [code organization](https://golang.org/doc/code#Organization)
|
||||
|
||||
## Do I need to put my package on GitHub?
|
||||
|
||||
You don't *need* to publish your code to a remote repository before you can build it. A module can be defined locally without belonging to a repository. However, it's a good habit to keep a copy of all your projects on a remote server, like GitHub.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Do packages in the standard library have a module path prefix?",
|
||||
"answers": [
|
||||
"No",
|
||||
"Yes"
|
||||
]
|
||||
}
|
||||
33
course/12-local_development/exercises/4a-modules/readme.md
Normal file
33
course/12-local_development/exercises/4a-modules/readme.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Modules
|
||||
|
||||
Go programs are organized into *packages*. A package is a directory of Go code that's all compiled together. Functions, types, variables, and constants defined in one source file are visible to **all other source files within the same package (directory)**.
|
||||
|
||||
A *repository* contains one or more *modules*. A module is a collection of Go packages that are released together.
|
||||
|
||||
## A Go repository typically contains only one module, located at the root of the repository.
|
||||
|
||||
A file named `go.mod` at the root of a project declares the module. It contains:
|
||||
|
||||
* The module path
|
||||
* The version of the Go language your project requires
|
||||
* Optionally, any external package dependencies your project has
|
||||
|
||||
The module path is just the import path prefix for all packages within the module. Here's an example of a `go.mod` file:
|
||||
|
||||
```
|
||||
module github.com/bootdotdev/exampleproject
|
||||
|
||||
go 1.20
|
||||
|
||||
require github.com/google/examplepackage v1.3.0
|
||||
```
|
||||
|
||||
Each module's path not only serves as an import path prefix for the packages within but *also indicates where the go command should look to download it*. For example, to download the module `golang.org/x/tools`, the go command would consult the repository located at [https://golang.org/x/tools](https://golang.org/x/tools).
|
||||
|
||||
> An "import path" is a string used to import a package. A package's import path is its module path joined with its subdirectory within the module. For example, the module `github.com/google/go-cmp` contains a package in the directory `cmp/`. That package's import path is `github.com/google/go-cmp/cmp`. Packages in the standard library do not have a module path prefix.
|
||||
|
||||
- Paraphrased from Golang.org's [code organization](https://golang.org/doc/code#Organization)
|
||||
|
||||
## Do I need to put my package on GitHub?
|
||||
|
||||
You don't *need* to publish your code to a remote repository before you can build it. A module can be defined locally without belonging to a repository. However, it's a good habit to keep a copy of all your projects on a remote server, like GitHub.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "What is an import path?",
|
||||
"answers": [
|
||||
"A module path + package subdirectory",
|
||||
"An HTTP connection",
|
||||
"A RESTful server"
|
||||
]
|
||||
}
|
||||
33
course/12-local_development/exercises/4b-modules/readme.md
Normal file
33
course/12-local_development/exercises/4b-modules/readme.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Modules
|
||||
|
||||
Go programs are organized into *packages*. A package is a directory of Go code that's all compiled together. Functions, types, variables, and constants defined in one source file are visible to **all other source files within the same package (directory)**.
|
||||
|
||||
A *repository* contains one or more *modules*. A module is a collection of Go packages that are released together.
|
||||
|
||||
## A Go repository typically contains only one module, located at the root of the repository.
|
||||
|
||||
A file named `go.mod` at the root of a project declares the module. It contains:
|
||||
|
||||
* The module path
|
||||
* The version of the Go language your project requires
|
||||
* Optionally, any external package dependencies your project has
|
||||
|
||||
The module path is just the import path prefix for all packages within the module. Here's an example of a `go.mod` file:
|
||||
|
||||
```
|
||||
module github.com/bootdotdev/exampleproject
|
||||
|
||||
go 1.20
|
||||
|
||||
require github.com/google/examplepackage v1.3.0
|
||||
```
|
||||
|
||||
Each module's path not only serves as an import path prefix for the packages within but *also indicates where the go command should look to download it*. For example, to download the module `golang.org/x/tools`, the go command would consult the repository located at [https://golang.org/x/tools](https://golang.org/x/tools).
|
||||
|
||||
> An "import path" is a string used to import a package. A package's import path is its module path joined with its subdirectory within the module. For example, the module `github.com/google/go-cmp` contains a package in the directory `cmp/`. That package's import path is `github.com/google/go-cmp/cmp`. Packages in the standard library do not have a module path prefix.
|
||||
|
||||
- Paraphrased from Golang.org's [code organization](https://golang.org/doc/code#Organization)
|
||||
|
||||
## Do I need to put my package on GitHub?
|
||||
|
||||
You don't *need* to publish your code to a remote repository before you can build it. A module can be defined locally without belonging to a repository. However, it's a good habit to keep a copy of all your projects on a remote server, like GitHub.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "Do you need to put your code inside of your GOPATH?",
|
||||
"answers": [
|
||||
"No, in fact you shouldn't",
|
||||
"It doesn't matter",
|
||||
"Yes"
|
||||
]
|
||||
}
|
||||
23
course/12-local_development/exercises/5-gopath/readme.md
Normal file
23
course/12-local_development/exercises/5-gopath/readme.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Setting up your machine
|
||||
|
||||
Your machine will contain many version control *repositories* (managed by Git, for example).
|
||||
|
||||
Each repository contains one or more *packages*, but will typically be a single *module*.
|
||||
|
||||
Each package consists of one or more *Go source files* in a single directory.
|
||||
|
||||
The path to a package's directory determines its *import path* and where it can be downloaded from if you decide to host it on a remote version control system like Github or Gitlab.
|
||||
|
||||
## A note on GOPATH
|
||||
|
||||
The $GOPATH environment variable will be set by default somewhere on your machine (typically in the home directory, `~/go`). Since we will be working in the new "Go modules" setup, you *don't need to worry about that*. If you read something online about setting up your GOPATH, that documentation is probably out of date.
|
||||
|
||||
These days you should *avoid* working in the `$GOPATH/src` directory. Again, that's the old way of doing things and can cause unexpected issues, so better to just avoid it.
|
||||
|
||||
## Get into your workspace
|
||||
|
||||
Navigate to a location on your machine where you want to store some code. For example, I store all my code in `~/workspace`, then organize it into subfolders based on the remote location. For example,
|
||||
|
||||
`~/workspace/github.com/wagslane/go-password-validator` = [https://github.com/wagslane/go-password-validator](https://github.com/wagslane/go-password-validator)
|
||||
|
||||
That said, you can put your code wherever you want.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "Why does Go include a remote URL in module paths?",
|
||||
"answers": [
|
||||
"To simplify remote downloading of packages",
|
||||
"To confuse new gophers",
|
||||
"To ensure that developers are using source control"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# First Local Program
|
||||
|
||||
Once inside your personal workspace, create a new directory and enter it:
|
||||
|
||||
```bash
|
||||
mkdir hellogo
|
||||
cd hellogo
|
||||
```
|
||||
|
||||
Inside the directory declare your module's name:
|
||||
|
||||
```bash
|
||||
go mod init {REMOTE}/{USERNAME}/hellogo
|
||||
```
|
||||
|
||||
Where `{REMOTE}` is your preferred remote source provider (i.e. `github.com`) and `{USERNAME}` is your Git username. If you don't use a remote provider yet, just use `example.com/username/hellogo`
|
||||
|
||||
Print your `go.mod` file:
|
||||
|
||||
```bash
|
||||
cat go.mod
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "What is hellogo in our case?",
|
||||
"answers": [
|
||||
"The repository/directory name",
|
||||
"The module path prefix"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# First Local Program
|
||||
|
||||
Once inside your personal workspace, create a new directory and enter it:
|
||||
|
||||
```bash
|
||||
mkdir hellogo
|
||||
cd hellogo
|
||||
```
|
||||
|
||||
Inside the directory declare your module's name:
|
||||
|
||||
```bash
|
||||
go mod init {REMOTE}/{USERNAME}/hellogo
|
||||
```
|
||||
|
||||
Where `{REMOTE}` is your preferred remote source provider (i.e. `github.com`) and `{USERNAME}` is your Git username. If you don't use a remote provider yet, just use `example.com/username/hellogo`
|
||||
|
||||
Print your `go.mod` file:
|
||||
|
||||
```bash
|
||||
cat go.mod
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "What does the first line of go.mod contain?",
|
||||
"answers": [
|
||||
"module {REMOTE}/{USERNAME}/hellogo",
|
||||
"{REMOTE}/{USERNAME}/hellogo",
|
||||
"module hellogo"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# First Local Program
|
||||
|
||||
Once inside your personal workspace, create a new directory and enter it:
|
||||
|
||||
```bash
|
||||
mkdir hellogo
|
||||
cd hellogo
|
||||
```
|
||||
|
||||
Inside the directory declare your module's name:
|
||||
|
||||
```bash
|
||||
go mod init {REMOTE}/{USERNAME}/hellogo
|
||||
```
|
||||
|
||||
Where `{REMOTE}` is your preferred remote source provider (i.e. `github.com`) and `{USERNAME}` is your Git username. If you don't use a remote provider yet, just use `example.com/username/hellogo`
|
||||
|
||||
Print your `go.mod` file:
|
||||
|
||||
```bash
|
||||
cat go.mod
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Does 'go run' build a production executable?",
|
||||
"answers": [
|
||||
"No",
|
||||
"Yes"
|
||||
]
|
||||
}
|
||||
31
course/12-local_development/exercises/7-go_run/readme.md
Normal file
31
course/12-local_development/exercises/7-go_run/readme.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Go Run
|
||||
|
||||
Inside `hellogo`, create a new file called `main.go`.
|
||||
|
||||
Conventionally, the file in the `main` package that contains the `main()` function is called `main.go`.
|
||||
|
||||
Paste the following code into your file:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("hello world")
|
||||
}
|
||||
```
|
||||
|
||||
## Run the code
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
The `go run` command is used to quickly compile and run a Go package. The compiled binary is *not* saved in your working directory. Use `go build` instead to compile production executables.
|
||||
|
||||
I rarely use `go run` other than to quickly do some testing or debugging.
|
||||
|
||||
## Further reading
|
||||
|
||||
Execute `go help run` in your shell and read the instructions.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "Which can 'go run' accept as arguments?",
|
||||
"answers": [
|
||||
"Both",
|
||||
"File names",
|
||||
"Package names"
|
||||
]
|
||||
}
|
||||
31
course/12-local_development/exercises/7a-go_run/readme.md
Normal file
31
course/12-local_development/exercises/7a-go_run/readme.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Go Run
|
||||
|
||||
Inside `hellogo`, create a new file called `main.go`.
|
||||
|
||||
Conventionally, the file in the `main` package that contains the `main()` function is called `main.go`.
|
||||
|
||||
Paste the following code into your file:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("hello world")
|
||||
}
|
||||
```
|
||||
|
||||
## Run the code
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
The `go run` command is used to quickly compile and run a Go package. The compiled binary is *not* saved in your working directory. Use `go build` instead to compile production executables.
|
||||
|
||||
I rarely use `go run` other than to quickly do some testing or debugging.
|
||||
|
||||
## Further reading
|
||||
|
||||
Execute `go help run` in your shell and read the instructions.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "What was created after running 'go build'?",
|
||||
"answers": [
|
||||
"An executable file named 'hellogo'",
|
||||
"An executable file named main",
|
||||
"A package named cmd"
|
||||
]
|
||||
}
|
||||
17
course/12-local_development/exercises/8-go_build/readme.md
Normal file
17
course/12-local_development/exercises/8-go_build/readme.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Go Build
|
||||
|
||||
`go build` compiles go code into an executable program
|
||||
|
||||
## Build an executable
|
||||
|
||||
Ensure you are in your hellogo repo, then run:
|
||||
|
||||
```bash
|
||||
go build
|
||||
```
|
||||
|
||||
Run the new program:
|
||||
|
||||
```bash
|
||||
./hellogo
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "What happens when you run './hellogo'?",
|
||||
"answers": [
|
||||
"'hello world' is printed",
|
||||
"The program panics",
|
||||
"The code compiles"
|
||||
]
|
||||
}
|
||||
17
course/12-local_development/exercises/8a-go_build/readme.md
Normal file
17
course/12-local_development/exercises/8a-go_build/readme.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Go Build
|
||||
|
||||
`go build` compiles go code into an executable program
|
||||
|
||||
## Build an executable
|
||||
|
||||
Ensure you are in your hellogo repo, then run:
|
||||
|
||||
```bash
|
||||
go build
|
||||
```
|
||||
|
||||
Run the new program:
|
||||
|
||||
```bash
|
||||
./hellogo
|
||||
```
|
||||
Reference in New Issue
Block a user