The Most Common Mistakes with Read File in Golang

Luu Duc Hoang
The Startup
Published in
2 min readSep 15, 2019

Using a Filename as an Input

Another common mistake is to pass a filename to a function.

Let’s say we have to implement a function to count the number of empty lines in a file. The most natural implementation would be something like this:

filename is given as an input, so we open it and then we implement our logic, right?

func count(filename string) (int, error) {
file, err := os.Open(filename)
if err != nil {
return 0, errors.Wrapf(err, "unable to open %s", filename)
}
defer file.Close()

scanner := bufio.NewScanner(file)
count := 0
for scanner.Scan() {
if scanner.Text() == "" {
count++
}
}
return count, nil
}

Now, let’s say we want to implement unit tests on top of this function to test with a normal file, an empty file, a file with a different encoding type, etc. It could easily become very hard to manage.

Also, if we want to implement the same logic but for an HTTP body, for example, we will have to create another function for that.

Go comes with two great abstractions: io.Reader and io.Writer. Instead of passing a filename, we can simply pass an io.Reader that will abstract the data source.

Is it a file? An HTTP body? A byte buffer? It’s not important as we are still going to use the same Read method.

In our case, we can even buffer the input to read it line by line. So, we can use bufio.Reader and its ReadLine method:

func count(reader *bufio.Reader) (int, error) {
count := 0
for {
line, _, err := reader.ReadLine()
if err != nil {
switch err {
default:
return 0, errors.Wrapf(err, "unable to read")
case io.EOF:
return count, nil
}
}

if len(line) == 0 {
count++
}
}
}

The responsibility of opening the file itself is now delegated to the count client:

func ReadFile() {
filename := os.Getenv("fileExample")
file, err := os.Open(filename)
if err != nil {
return errors.Wrapf(err, "unable to open %s", filename)
}
defer file.Close()
count, err := count(bufio.NewReader(file))
}

With the second implementation, the function can be called regardless of the actual data source. Meanwhile, and it will facilitate our unit tests as we can simply create a bufio.Reader from a string:

count, err := count(bufio.NewReader(strings.NewReader("input")))

--

--