DEV Community

Cover image for Refactoring my first Go code
David Kröll
David Kröll

Posted on

Refactoring my first Go code

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 logo davidkroell / shariff

SHA(riff) - Fingerprint checking with Go

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

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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
}
Enter fullscreen mode Exit fullscreen mode

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:

color.Green(inFile + " has SHA256 " + inHash)
Enter fullscreen mode Exit fullscreen mode

to that:

color.Green("%s has SHA256 %s", inFile, inHash)
Enter fullscreen mode Exit fullscreen mode

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.

// 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
}
Enter fullscreen mode Exit fullscreen mode

Write test

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

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)