DEV Community

Edgar Nahama Alochi
Edgar Nahama Alochi

Posted on • Originally published at edgar.co.ke on

NASA's Rule of 10: The rules of programing that put flying metal in orbit

The NASA Ten Rules of Programming

My interpretation of these rules with Golang examples.

NASA did not write these rules to win arguments on the internet.
They wrote them because software failed in ways that were expensive frightening and sometimes irreversible.

The rules are short. Almost annoyingly so.
That is intentional.

Each rule is a reminder that complexity is not free and that computers are fast but humans are fragile.

They helped put flying metals in orbit and manage Voyager 1, the most distant human made object.

Let’s walk through all ten using Go not because Go is perfect but because it makes these ideas painfully obvious.

image


Rule 1

Restrict all code to very simple control flow

If your logic requires a mental stack trace to understand it will fail at the worst possible moment.

Go helps here by being boring on purpose.

Bad

func process(x int) int {
    if x > 0 {
        for x < 100 {
            if x%2 == 0 {
                if x%3 == 0 {
                    x += 7
                } else {
                    x += 3
                }
            } else {
                break
            }
        }
    }
    return x
}
Enter fullscreen mode Exit fullscreen mode

Good

func process(x int) int {
    if x <= 0 {
        return x
    }

    for x < 100 {
        if x%2 != 0 {
            return x
        }
        x = nextValue(x)
    }

    return x
}

func nextValue(x int) int {
    if x%3 == 0 {
        return x + 7
    }
    return x + 3
}
Enter fullscreen mode Exit fullscreen mode

Simple flow is not about fewer lines.
It is about fewer thoughts.


Rule 2

Give all loops a fixed upper bound

Unbounded loops are promises you cannot keep.

They say trust me this will eventually stop.
NASA does not trust that. Neither should you.

Bad

for {
    data := read()
    if data == nil {
        break
    }
    process(data)
}
Enter fullscreen mode Exit fullscreen mode

Better

for i := 0; i < maxReads; i++ {
    data := read()
    if data == nil {
        return
    }
    process(data)
}
Enter fullscreen mode Exit fullscreen mode

Even when reading streams you can still impose limits.
Timeouts retries counters budgets.

Infinite loops belong in theory not production.


Rule 3

Avoid heap memory allocation after initialization

This rule scares people because it sounds extreme.

What it really says is do not allocate memory unpredictably while running critical logic.

In Go you do not control the heap but you can control when and how you allocate.

Bad

func handle(req Request) {
    buf := make([]byte, req.Size)
    process(buf)
}
Enter fullscreen mode Exit fullscreen mode

Better

type Handler struct {
    buf []byte
}

func NewHandler(maxSize int) *Handler {
    return &Handler{
        buf: make([]byte, maxSize),
    }
}

func (h *Handler) Handle(req Request) {
    process(h.buf[:req.Size])
}
Enter fullscreen mode Exit fullscreen mode

This is not about micro optimization.
It is about predictability.

The garbage collector is helpful.
It is not magic.


Rule 4

Restrict functions to a single purpose

If a function does more than one thing it lies about its name.

Bad

func SaveUser(u User) error {
    validate(u)
    db.Save(u)
    log.Printf("saved user %s", u.ID)
    sendEmail(u)
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Better

func SaveUser(u User) error {
    if err := validate(u); err != nil {
        return err
    }
    return db.Save(u)
}
Enter fullscreen mode Exit fullscreen mode

Side effects should be explicit and separate.

Small functions fail smaller.


Rule 5

Use a minimum of two runtime assertions per function

This rule is about defending assumptions.

In Go assertions usually look like early checks.

Bad

func divide(a b int) int {
    return a / b
}
Enter fullscreen mode Exit fullscreen mode

Better

func divide(a b int) (int error) {
    if b == 0 {
        return 0 errors.New("division by zero")
    }
    return a / b nil
}
Enter fullscreen mode Exit fullscreen mode

Assertions are kindness to future readers.
Especially future you at 2am.


Rule 6

Declare data objects at the smallest possible scope

The longer a variable lives the more places it can hurt you.

Bad

var result int

func compute(a b int) int {
    result = a + b
    return result
}
Enter fullscreen mode Exit fullscreen mode

Better

func compute(a b int) int {
    result := a + b
    return result
}
Enter fullscreen mode Exit fullscreen mode

Short lifetimes mean fewer ghosts in the system.


Rule 7

Check the return value of all non void functions

This is the rule Go takes personally.

Ignoring errors is lying to yourself.

Bad

file _ := os.Open("data.txt")
Enter fullscreen mode Exit fullscreen mode

Better

file err := os.Open("data.txt")
if err != nil {
    return err
}
Enter fullscreen mode Exit fullscreen mode

Errors are information.
Discarding them does not make them go away.


Rule 8

Limit pointer use

Pointers increase the number of possible states your program can be in.

Go uses pointers but discourages pointer gymnastics.

Bad

func update(u *User) {
    u.Name = strings.ToUpper(u.Name)
}
Enter fullscreen mode Exit fullscreen mode

Better

func update(u User) User {
    u.Name = strings.ToUpper(u.Name)
    return u
}
Enter fullscreen mode Exit fullscreen mode

Use pointers when you must not because you can.


Rule 9

Compile with all warnings enabled and treat them as errors

Go does not have warnings.
It simply refuses to compile nonsense.

Unused variables unused imports unreachable code.
All dead on arrival.

That is a feature not a limitation.

If your language allows warnings treat them like failures.
Your future stability depends on it.


Rule 10

Use static analysis tools

Humans miss patterns.
Machines do not get tired.

In Go this means tools like

  • go vet
  • staticcheck
  • race detector

Example

go test -race ./...
Enter fullscreen mode Exit fullscreen mode

These tools are not optional extras.
They are part of the engineering system.

NASA trusted them with spacecraft.
You can trust them with your backend.


The quiet lesson behind the rules

The NASA rules are not about cleverness.
They are about humility.

They assume that you will forget.
That you will misread.
That you will be interrupted.

They are written for humans under pressure.

Go as a language aligns with this philosophy almost accidentally.
Simple syntax explicit errors limited abstraction boring defaults.

The rules remind us that reliability is not achieved by brilliance.
It is achieved by restraint.

And restraint scales better than genius ever will.

Top comments (0)