What is defensive programming?
Published: 2022-09-11 03:03:51

## What is defensive programming?

> Defensive programming is a form of defensive design intended to develop programs that are capable of detect potential security abnormalities and make predetermined response - Wikipedia 

in other words, defensive programming is writing code to handle cases that you do not think will, or even can happen, because you have a belief that your own beliefs are unreliable or assuming the worst case: that your users are complete crazy people and you must defend yourself and your program from their crazy inputs

for example, lets take a function that add two numbers and yields an output

```go
func add(a, b int) (int, error) {
	if a == 0 || b == 0 {
		// gracefully return and error
		return 0, errors.New("can't add zero values")
	}

	// finally compute the function
	return a + b, nil
}

func main() {
	result, err := add(10, 30)
	if err != nil {
		log.Fatalln(err)
	}
	log.Println(result)
}

```

Here, errors are checked before the resulting operation is done

You can fight logical errors with unit tests that cover all code paths using code coverage tools for example, you can write a test to guard against zero values

```go
package main

import "testing"

func TestAdd(t *testing.T) {
	output, err := add(0, 1)

	if err != nil {
		t.Fatalf(`TestAdd = %d, error -> %s`, output, err)
	}
}

```
 
Which fails
```sh
Running tool: /opt/go/bin/go test -timeout 30s -run ^TestAdd$ github.com/navicstein/def

--- FAIL: TestAdd (0.00s)
    /home/navicstein/Idea/def/main_test.go:9: TestAdd = 0, error -> can't add zero values
FAIL
FAIL	github.com/navicstein/def	0.002s
FAIL
```

In a static language like C or Go, the compiler would perform some of the sanity checks for us, for example not allowing to pass string where a number is expected.

There are three sources of errors to guard against

1. Logical errors in the code itself.
2. Errors in the input data, especially user supplied input.
3. Errors in the environment


let's look at another real world example, in this example we're going to assume we'll actually convert any video to an .mp4 video file

```go
package main

import (
	"errors"
	"log"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/exp/slices"
)

type VideoParams struct {
	Url          string
	AudioBitrate string
	VideoBitrate string
	VideoCodec   string
	AudioCodec   string
}

// ConvertAnyVideoToMp4 converts any video to mp4
func ConvertAnyVideoToMp4(args VideoParams) error {
	if args.Url == "" {
		return errors.New("an input URL is required, please provide one")
	}

	var (
		fileExt = filepath.Ext(args.Url)
		exts    = []string{".flv", ".avi"}
	)

	// don't convert video is already an mp4 file
	if strings.ToLower(fileExt) == ".mp4" {
		return errors.New("video is alread an mp4 file, nothing to do")
	}

	// validate against supported files
	if !slices.Contains(exts, fileExt) {
		return errors.New("unsupported video file")
	}

	// Here, we're just supplying defaults as we can tolerate the nil arguments
	if args.AudioBitrate == "" {
		args.AudioBitrate = "8k"
	}

	if args.VideoBitrate == "" {
		args.VideoBitrate = "30k"
	}

	if args.AudioCodec == "" {
		args.AudioCodec = "mp3"
	}

	if args.VideoCodec == "" {
		args.VideoCodec = "4264"
	}

	doneCh := make(chan bool, 1)

	func() {
		log.Printf("Converting with: %#v", args)
		time.AfterFunc(time.Second*5, func() {
			doneCh <- true
		})
	}()

	if <-doneCh {
		log.Println("Finished converting video to mp4")
	}

	return nil
}

```

And in our usage file

```go
	args := VideoParams{
		Url: "https://sample-videos.com/video123/flv/720/big_buck_bunny_720p_1mb.flv",
	}
	
	if err := ConvertAnyVideoToMp4(args); err != nil {
		log.Fatalln(err)
	}
```

Which we can see that we have default values for attributes that were not specified by the user

```sh
❯ go run .
2022/09/11 03:40:12 Converting with: main.VideoParams{Url:"https://sample-videos.com/video123/flv/720/big_buck_bunny_720p_1mb.flv", AudioBitrate:"8k", VideoBitrate:"30k", VideoCodec:"4264", AudioCodec:"mp3"}
2022/09/11 03:40:17 Finished converting video to mp4
```

in the example above, we're just checking and setting defaults then overriding the defaults with user defined attributes but It's a good practice to write test cases before the actual implementation of a function because it gives you a clear representation of what the function will do



## Difference between defensive programming and exception handling
  The term “defensive programming” seems to have two complementary meanings. In the first meaning, the
term is used to describe a programming style based on assertions, where you explicitly assert any assumptions
that should hold true as long as the software operates correctly.
In the other meaning, however, “defensive programming” denotes a programming style that aims at making
operations more robust to errors, by accepting a wider range of inputs.

While
Exception handling is the process of responding to unwanted or unexpected events when a computer program runs

## Some key concepts to take home

> Anything that can go wrong will go wrong - Murphy's Law

- Error conditions will occur, and your code needs to deal with them (Out of memory, disk full, file missing, file corrupted, network error, etc)
- Software should be tested to see how it performs under various error conditions
- Program defensively, i.e., assume that errors are going to arise, and write code to detect them when they do.
- Put assertions in programs to check their state as they run, and to help readers understand how those programs are supposed to work.
- Use preconditions to check that the inputs to a function are safe to use.
- Use post conditions to check that the output from a function is safe to use.
- Write tests before writing code in order to help determine exactly what that code is supposed to do.


https://faculty.cs.byu.edu/~rodham/cs240/lecture-notes/Lecture-16-ErrorHandling&DefensiveProgramming/Lecture-16-ErrorHandlingAndDefensiveProgramming.pdf 

https://www.state-machine.com/doc/Samek0308.pdf

https://glebbahmutov.com/blog/defensive-coding-examples/