Refactoring my first Go code
Tags: go, refactorit
Published: 2020-12-03 20:04:47

My first Golang code was published on 25th April 2018. You may have a look at it on Github. It's a SHA256 hash checker done in 43 lines.

{% github davidkroell/shariff no-readme %}

Today I am going to review and refactor it. Start by analyzing the `main` func. Well, it's the only func in this project.

```go
func main() {
	args := os.Args[1:]

	inFile := args[0]
	inHash := args[1]

	// keep the check case insensitive
	inHash = strings.ToLower(inHash)

	file, err := os.Open(inFile)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

        // review: maybe change the name to hasher here
	hash := sha256.New()
	if _, err := io.Copy(hash, file); err != nil {
		log.Fatal(err)
	}

	hashToCheck := hex.EncodeToString(hash.Sum(nil))

        // review: introduce string formatting here
	if inHash == hashToCheck {
		color.Green(inFile + " has SHA256 " + inHash)
	} else {
		color.Red(inFile + " doesn't match with " + inHash)
		color.Red("The correct hash is: " + hashToCheck)
	}
}
```

So we found already some problems. Furthermore I'd like to extract the checking logic out and write tests for it.

## Add parameter check
At the top of the main func I'll add an parameter count check for improved error handling. I'm also adding an non-zero exit code here so we can check in our command line if the command ran succesfully or not.

```go
func main() {
        // check for missing parameters
	if len(os.Args) != 3 {
		fmt.Println("Error occured: missing parameters")
		// emit non-zero exit code
		os.Exit(1)
	}
        // continue
}
```

## Add string formatting
It's pretty bad doing a string concatenation for printing on the command line. It's of course preferred to use string formatting to output something.

So go from this:
```go
color.Green(inFile + " has SHA256 " + inHash)
```
to that:
```go
color.Green("%s has SHA256 %s", inFile, inHash)
```


## Pull out the code
I'd like to create a func which checks hashes for arbitrary streams, not only files. One could think of checking a hash when receiving files in an HTTP server - so the file would already be in memory. Therefore I am using the `io.Reader` interface. It will also make the code suitable for unit tests.

```go
// checkHash calculates the hash using and io.Reader, so it is now testable
func checkHash(reader io.Reader, hash string) (isValid bool, calculatedHash string, err error) {
	// keep the check case-insensitive
	hash = strings.ToLower(hash)

	hasher := sha256.New()
	if _, err := io.Copy(hasher, reader); err != nil {
		return false, "", err
	}

	calculatedHash = hex.EncodeToString(hasher.Sum(nil))
	return hash == calculatedHash, calculatedHash, nil
}
```

## Write test
Now since the code is testable, we have to write tests for it.


```go
func TestCheckHash(t *testing.T) {
	b := []byte{0xbe, 0xef, 0x10, 0x10, 0xca, 0xfe}

	r := bytes.NewReader(b)

	isValid, calculatedHash, err := checkHash(r, "77efeeff80507604bbd4c32b32ce44306879154f83f46afe8c15223932a6a4cb")

	if err != nil {
		t.Error(err)
	} else if !isValid {
		t.Error("hashes do not match", calculatedHash)
	}
}
```

So in the code above, we create a byte slice with the hex codes 
`0xbeef1010cafe`, create an `io.Reader` from it and calculate it's hash.


## Conclusion
My first published Go code has now a test coverage of incredibly 22.7%. Wow!! After all, I'd say the code from 2018 wasn't that bad as expected beforehand but of course, there are always improvements possible, even if it's only some name changes or added exit codes.