Skip to Content
Course content

2: Anatomy of a Go Program

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Now that you've got your environment set up, we need to talk about how a Go program is actually put together. If you're coming from a scripting language like Python or Ruby, your instinct might be to just start writing code at the top of a file and let it run linearly. In Go, that's not how it works. Go is strictly structured, and while that might feel like overhead at first, it's exactly what keeps large Go codebases from turning into a nightmare as they grow.

The Scripting Temptation

I often see developers treat their first few Go programs like scripts. They cram every single line of logic—variable declarations, calculations, and output—directly into the main function. Let's look at a naive implementation of a simple tool that calculates the final price of an item after tax.

package main

import "fmt"

func main() {
    price := 100.0
    taxRate := 0.07
    total := price + (price * taxRate)
    fmt.Printf("The total price is: %.2f\n", total)
}

On the surface, this works perfectly. It compiles, it runs, and it gives you the right answer. But as a professional engineer, I'd tell you this is "wrong"—not because it doesn't work, but because it's fragile. Everything is trapped inside main. If you suddenly need to calculate taxes for ten different items, or if you want to write a test to ensure your tax logic is correct, you're stuck. You can't "call" the logic inside main from anywhere else; it's a dead end.

Decoupling Logic from Execution

The better way is to treat your main function as a "coordinator" rather than a "doer." The main function should handle the program's entry and exit, but the actual work—the business logic—should live in its own functions. This is the core of Go's anatomy: the package declaration, the imports, and the separation of concerns.

package main

import "fmt"

// calculateTotal handles the math. It doesn't care about printing; 
// it just cares about returning a value.
func calculateTotal(price, taxRate float64) float64 {
    return price + (price * taxRate)
}

func main() {
    price := 100.0
    taxRate := 0.07
    
    total := calculateTotal(price, taxRate)
    fmt.Printf("The total price is: %.2f\n", total)
}

I might seem like I'm just adding more lines of code for no reason, but look at the trade-off. By moving the logic into calculateTotal, I've created a reusable tool. I can now call that function a hundred times with different values without rewriting the math. More importantly, I can now write a unit test for that function in a separate file without having to actually "run" the whole program.

Where the Structure Actually Breaks

You'll notice that both examples start with package main. This is a non-negotiable rule in Go: if you want to create an executable program, the file must be part of the main package and it must contain a function called main(). If you rename the package to calculator, the Go compiler will treat it as a library (a package meant to be used by other programs) and will refuse to run it as a standalone app.

Another detail is the import block. You'll see people use a grouped import style—import ( "fmt"; "math" )—once they have more than one dependency. I highly recommend this. It keeps the top of your file clean and prevents a long, vertical list of imports from pushing your actual code off the screen. It's a small habit, but it's the standard in every production Go project I've ever worked on.




📋 Practical Task

Refactoring the Messy Tip Calculator

Below is a program that calculates a restaurant bill including a tip. It's written in the "naive" style, with all the logic crammed into the main function. Your task is to refactor this program to follow the professional anatomy we discussed.

package main

import "fmt"

func main() {
    billAmount := 50.0
    tipPercentage := 0.20
    
    tip := billAmount * tipPercentage
    total := billAmount + tip
    
    fmt.Printf("Bill: %.2f, Tip: %.2f, Total: %.2f\n", billAmount, tip, total)
}

Requirements for your refactor:

  • Create a separate function called calculateTip that takes the bill amount and percentage as arguments and returns the tip amount.
  • Create a second function called calculateTotal that takes the bill amount and the calculated tip and returns the final sum.
  • Keep the main function solely for initializing the variables and printing the final result.
  • Ensure the program still outputs the exact same string format.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.