I have multiple (paid) training courses about the Go programming language:

  • Introduction to Go (3 days)
  • Concurrent programming in Go (1 day)
  • Generics and iterators in Go (2 days)
  • Error handling in Go (1 day)

I also have a paid introduction to Web security (3 days), which I have been teaching since 2022.

I usually train participants remotely but I can also come on premise if your company is based in Western Europe. All of my courses are very practical; as such, they’re designed for relatively small groups (a group size not exceeding six people is typical).

If you’re interested in one or more of those courses, shoot me an email at jcretel-infosec+training@protonmail.com or DM me on Bluesky or on Mastodon. Alternatively, for Go-related courses, you can also DM me on Gophers Slack.


Introduction to Go

Duration: 3 days (21 hours)

If you have programming experience with another language and would like to get started with Go, look no further than this course. Covering basic syntax, object orientation, and concurrency fundamentals, it promises to set you on a path to proficiency with the Go programming language in three days. Far from being merely theoretical, the course is punctuated by practical exercices aiming at iteratively improving a coding project started from scratch.

I’ve been teaching this course since 2019 and I refresh it regularly. Some testimonials (in French) are available on Human Coders' website. If you’re curious, take a peek at the course material, which is freely available at https://github.com/jub0bs/go-course-beginner.

If you’re already familiar with the basics but are mystified by Go’s concurrency features, be aware that the third day of this course is available as a standalone course.

Outline

Day 1: Introduction

  • Preamble
    • About this slidedeck
    • Installation
    • namecheck: the project you’ll develop in this course
    • Genesis and short history of the Go language
    • Logo and mascot
    • A multi-paradigm programming language
    • A rich toolchain
  • First steps in Go
    • The Go playground: a sandbox for playing with the language
    • Your first Go program: Hello, World!
    • Modules and dependency management
    • Source-code encoding and formatting
    • Program compilation and execution
    • Cross-compilation
    • Keywords and builtins
    • Comments
  • Packages
    • Name and import path of a package
    • The unit of compilation
    • Package documentation
    • Encapsulation
    • Importing packages
  • Types
    • Basic types
    • Composite types
    • Zero value of a type
    • Type comparability
    • Type declaration
    • Type conversion
  • Variables
    • Memory safety & garbage collection
    • Variable declaration: the var keyword and short variable declaration
    • Beware accidental shadowing of variables
    • Naming conventions
  • Conditional statements
    • If-else statement
    • Switch statement
    • “Tagless” switch
  • Pointers
    • A value’s address in memory
    • Addressability
    • The zero value of pointer types
    • Comparability of pointer types
    • Dereferencing a pointer
    • The new builtin
    • “Reference” (pointer-ish) types
  • Functions
    • Calling a function
    • Go’s evaluation strategy: call by value
    • Function declaration
    • Multiple results: the key to error reporting!
    • Named results
    • Variadic arguments
    • Syntactic sugar for consecutive identically-typed function parameters
    • Functions are values just like any other
    • Anonymous functions and closures
    • Recursion
    • A teaser about methods
    • A glimpse at generic functions
  • Error handling
    • No exceptions in Go: errors are values!
    • The error type
    • On the importance of not ignoring errors
    • “Line of sight”: handle failure cases as you encounter them
    • Handling a non-nil error
    • Creating error values
    • Good and bad error-handling practices
    • Panic: a signal of some fatal failure case
  • Package design
    • Main packages and library packages
    • Source files of a package
    • Naming conventions
    • Sub-packages
    • No cycles allowed in the import graph!
    • Tips for structuring a project
    • Avoid panics after package initialization
  • Freeing resources with defer
    • The defer keyword: like Java’s finally keyword, but on steroids
    • Beware defer statements within a loop!
    • The defer keyword’s selling point: bye-bye, code duplication!
  • Loops
    • Three-clause loops
    • For-range loops over an integer
    • “While” loops
    • The break and continue keywords
    • Infinite loops
    • For-range loops over built-in composite types
    • For-range loops over iterators

Labs:

  • Create your namecheck project in VS Code
  • (Cross-)compilation of a simple program
  • Implement validation of a GitHub username
  • Move the logic to a dedicated function
  • Handle errors judiciously within that function
  • Design a package dedicated to GitHub
  • Write and run unit tests
  • Measure code coverage and augment your test suite
  • Implement an availability check for a GitHub username

Day 2: Composite types, methods, and interfaces

  • Arrays
    • A fixed-size sequence of identically-typed elements
    • Array literals
    • Arrays are not “reference” types
    • The zero value of an array type
    • Comparability of array types
    • Indexing an array
    • Iterating over an array
    • Arrays are inflexible and rarely used directly
  • Slices
    • Slices are ubiquitous in Go
    • A resizable view inside a backing array
    • Slice literals
    • Initializing a slice via the make builtin
    • The zero value of slice types
    • Incomparability of slice types
    • Indexing a slice
    • Slices under the hood
    • Length and capacity of a slice
    • Why favor slices over arrays
    • Slicing operator
    • Iterating over a slice
    • Growing a slice with the append builtin
    • Inadvertent slice aliasing
    • Inadvertent slice decoupling
    • The slices package
    • Other common operations on slices
  • Maps
    • Au unordered collection of key-value pairs
    • Map literals
    • Initializing a map via the make builtin
    • The zero value of map types
    • Incomparability of map types
    • Map lookup
    • Lifting ambiguity at map lookup with the comma-ok idiom
    • Adding/updating a key-value pair in a map
    • Deleting a key (and its value) from a map
    • Implementing a mathematical set with a map
    • Iterating over a map
    • The maps package
    • Other common operations on maps
  • Structs
    • An ordered sequence of fields
    • Struct-type declaration
    • The zero value of a struct type
    • Struct literals
    • The empty-struct type
    • Reading and updating struct fields
    • Encapsulation of struct fields
    • Field tags
    • Comparability of struct fields
    • Embedded fields and promoted fields
    • A glimpse at generic types
    • A glimpse at iterators
  • Methods
    • Functions tied to a specific type
    • Method declaration
    • Method receiver
    • Naming conventions for methods
    • Naming conventions for method receivers
    • Calling a method
    • “Value methods” vs. “pointer methods”
    • Make the zero value useful (if possible)
    • Method sets of a concrete type
    • Dependency injection through a method’s receiver
  • Interfaces
    • The power of structural typing
    • Basic interfaces
    • Comparability of interfaces
    • Interface-type declaration
    • Naming conventions
    • Implicit interface satisfaction
    • Method sets of an interface type
    • The empty interface
    • Keep your interface types small and focused
    • Interface composition
    • Don’t declare interface types earlier than necessary
    • The fmt.Stringer interface type
    • The io.Reader and io.Writer interface types
    • Favor interface-typed function parameters if you care about is behavior rather than data
    • Require no more behavior than necessary
    • The error interface
    • Interfaces under the hood
    • A common gotcha: when nil isn’t nil
    • Type assertions
    • Type switch
    • Declare interface types where they’re useful
    • Dependency injection thanks to interfaces
    • Test doubles (mocks, stubs, etc.)

Labs:

  • Retrieve the username to test as a command-line argument
  • Refactor tests as table-driven subtests
  • Design a struct type representing a binary tree and operations on it
  • Declare struct types dedicated to each platform of interest
  • Turn functions for checking username validity and availability into methods
  • Declare a judicious interface to abstract those types
  • Reduce code duplication thanks to this new interface
  • Stub the HTTP client and write unit tests

Day 3: concurrent programming

  • Introduction to concurrency
    • Concurrency: the art of composing a program in order to delineate independent tasks
    • Concurrency vs. parallelism: close but distinct
    • Concurrency enables parallelism and promises free performance gains as the number of cores increases
    • Amdahl’s law and Gunther’s law: parallelism’s diminishing returns
    • Go’s concurrency trifecta: goroutines, channels, et select statements
  • Goroutines
    • Goroutines: featherweight threads
    • The Go scheduler
    • Starting goroutines with the go keyword
    • The golden rule: don’t start a goroutine if you can’t explain when it will terminate
    • The main function doesn’t wait for other goroutines to terminate
    • Wait groups
    • Synchronization bugs and race conditions
    • Concurrency safety
    • Data races
    • Race detector
  • Channels
    • Shared-memory concurrency vs. message-passing concurrency
    • Share memory by communicating
    • Channels: the preferred mechanism for communicating between goroutines
    • Naming conventions
    • Channels are values just like any other
    • The zero value of channel types
    • Initializing a channel via the make builtin
    • Capacity of a channel
    • Channels are FIFO
    • On the importance of choosing a channel’s capacity judiciously
    • Sending a value to a channel
    • Receiving a value from a channel
    • Lifting ambiguity at receiption with the comma-ok idiom
    • Deadlock
    • Goroutine leak
    • Closing a channel
    • Good and bad reasons to close a channel
    • Ranging over a channel
    • Directional channels
  • HTTP server
    • Introduction to the net/http package
    • A simple “Hello, World!” server
    • What about Web frameworks?
    • Beware: handlers are invoked concurrently
    • JSON encoding and decoding
  • Third-party libraries
    • Installing third-party libraries
    • The go.mod and go.sum files
  • A short detour in the land of shared-memory concurrency
    • Communicating by sharing memory
    • Mutexes and critical sections
    • The sync.Mutex type
  • Select statements
    • Multiplexing channel communications
    • The select keyword
    • Event loop: a select statement nested in a loop
  • Canceling goroutines
    • The need to force goroutines to terminate
    • Emit and detect a cancellation signal: the basic technique
    • The context.Context type
    • Emit and detect a cancellation signal: the modern approach
    • Cancelable channel send
    • context.Background() and context.TODO()
    • Deriving contexts
    • Cancellation signals propagate down the tree of contexts
    • Embrace “cancel culture”: design most of your functions as cancelable
    • Contexts on the client side and on the (Web) server side
  • Learning resources

Labs:

  • Refactor the namecheck project with concurrency in mind
  • Analysis and resolution of a simple deadlock
  • Analysis and resolution of a simple goroutine leak
  • Aggregate the results from multiple goroutines thanks to a channel
  • Turn the CLI tool into a Web server
  • Configure your server for CORS thanks to a third-party library
  • Use a mutex to guard a shared map storing statistics about the service in memory
  • Analysis and resolution of a goroutine leak thanks to contexts
  • Use contexts to cancel requests on the server side