Manipulando arquivos: um CRUD de arquivos em Go
Tags: go, programming, tutorial, github
Published: 2023-08-28 22:31:46

#Como Criar um CRUD de arquivos em Go 
##Um guia básico e passo a passo

Uma das coisas que dev mais faz no seu dia a dia é resolver problemas aplicando os fundamentos da programação. 

Um destes fundamentos é **a capacidade de ler e escrever em arquivos.** 

Quem trabalha desenvolvendo software, acaba trabalhando com manipulação de arquivos em algum momento.

Neste artigo eu vou mostrar como criar um **CRUD** (Create, Read, Update, Delete) **de arquivo** usando a linguagem de programação Go.

Vale ressaltar que neste exemplo eu vou usar os próprios recursos do Go sem a necessidade importar nenhuma biblioteca externa para o meu software.

Caso você queria pesquisar mais a fundo sobre cada biblioteca usada na criação deste exemplo, você pode [consultar a documentação da Standard Library do Go](https://pkg.go.dev/std).

**Se você preferir ir direto para o repositório do código ao invés de ler o artigo, você pode acessar o** [repositório no GitHub](https://github.com/marcelomatz/manipulando_arquivos_em_go).

---

## Show me the code

O ponto de partida que qualquer software em Go é a partir da função main contida no pacote main. Eu vou escrever todo o código dentro da mesma função `main`.

Abaixo temos a estrutura base do nosso software:

```
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	// Agora só falta implementar
	// Duas barras e um texto indica um comentário
}
```
##O CRUD na prática
###Create
Eu vou começar implementando o **C**reate do CRUD e para fazer isso em Go eu vou usar o método nativo da linguagem `os.Create()`.

>O pacote `os` do Go permite acessar diversos recursos do sistema operacional, e como nós queremos trabalhar com arquivos, nada mais justo do que usar o pacote que permite isso.

```
f, err := os.Create("test.txt")
if err != nil {
	panic(err)
}
// Não esqueça de fechar o arquivo quando a criação for concluída.
defer func(f *os.File) {
	err := f.Close()
	if err != nil {
		panic(err)
	}
}(f)
```
Dentro da função Main nós temos uma variável `f` que recebe como valor o `os.Create()`.

Note que além da variável `f` existe uma outra variável chamada `err` que também foi criada.

_Em Go quando nós temos `algumaCoisa := "valorDeAlgumaCoisa"` significa que nós estamos criando uma variável e ao mesmo tempo estamos atribuindo um valor. O `:=` é o operador Marmota (logo do Go) usado para criação e inferência de tipo._

###O processo se resume em:
####1) Criar a variável `f` 
(ou file, ou arquivo ou o nome que você quiser) e criar também uma variável para receber o erro `err`.

O método (função) `Create` do pacote `os` do Go funciona da seguinte forma: ele recebe um _nome_ como parâmetro e retorna **dois valores**: O primeiro retorno é o arquivo em si (no caso um ponteiro para o arquivo, mas não vou entrar no mérito dos ponteiros aqui) e também recebe como retorno um erro.

####2) Usar o método `defer` para garantir que o arquivo vai ser fechado depois que ele for criado.

O `defer` em Go é utilizado para garantir que uma função seja executada **posteriormente**.

>A instrução `defer` **agenda uma função** a ser chamada após a função que a contém ser concluída.

Pode parecer confuso, mas você vai perceber que em outras palavras, a função é "adiada" até que o resto do código seja executado. 

Um uso comum para defer é justamente para fechar um arquivo, garantindo que, **independentemente do que acontecer no código após a abertura do arquivo**, ele (o arquivo) será fechado quando a função for terminada. 
Por exemplo:

```
file, err := os.Open("file.txt")
if err != nil {
    log.Fatal(err)
}

defer file.Close()

// Outras operações no arquivo...
// O arquivo será fechado quando a função terminar
```

Neste exemplo, `file.Close()` só será chamado depois que o resto do código na função for executado. **Isso é útil porque mesmo se uma operação subsequente no arquivo resultar em um erro e você retornar da função mais cedo do que o esperado, o arquivo ainda será fechado corretamente.**

###Defer e Closure
Outra maneira de usar o defer neste caso é em conjunto com uma função anônima. **Closure é uma função anônima.**

Muitas vezes é recomendado usar closures com a declaração defer, principalmente quando estamos lidando com operações que também envolvem erros, **como fechamento de arquivos ou conexões em Go.**

**Um dos principais motivos para combinar `defer` com uma closure é que _a closure pode acessar e manipular_ as variáveis definidas no escopo onde ela foi criada.**

Portanto, ela pode lidar com valores ou estados que possam ter mudado ao longo do tempo antes de o defer ser realmente executado.
Aqui está um exemplo de fechamento de um arquivo usando closure e defer:

```
f, err := os.Open("file.txt")
if err != nil {
    log.Fatal(err)
}

defer func(f *os.File) {
    if err := f.Close(); err != nil {
        log.Fatal(err) // ou qualquer outra manipulação de erro
    }
}(f)

// Outras operações no arquivo...
```

Em resumo, após criarmos um arquivo usando `os.Create("nomeDoArquivo.extensaoDoArquivo")` nós precisamos fechar ele. De acordo com a implementação do método na standard library, na hora que passamos o nome do arquivo como parâmetro para a função Create, caso o arquivo não exista ele vai ser criado e, caso ele já exista, o Go faz um _truncate_ do arquivo.

---

###Read
####Ler do Arquivo

A leitura do arquivo é feita em duas etapas. Primeiro, abrimos o arquivo com `os.Open()` e em seguida lemos o arquivo utilizando `bufio.NewReader()`.

```
f, err := os.Open("test.txt")
if err != nil {
	panic(err)
}
defer func(f *os.File) {
	err := f.Close()
	if err != nil {
		panic(err)
	}
}(f)

reader := bufio.NewReader(f)
buffer := make([]byte, 10)
for {
	n, err := reader.Read(buffer)
	if err != nil && err != io.EOF {
		panic(err)
	}
	if n == 0 {
		break
	}
	fmt.Print(string(buffer[:n]))
}
```
Agora mais duas variáveis foram criadas. Uma `reader` e uma `buffer`. _Você pode abrir um arquivo sem buffer, mas lembre-se que dependendo do tamanho do arquivo ele pode exceder a memória disponível e acabe gerando um erro.

A variável `reader` agora armazena o retorno do método `bufio.NewReader(f)` onde o `f` nós estamos passando como parâmetro para este método.

O mais legal de trabalhar com buffer é você poder definir _o tamanho da fatia_ que você quer ler de cada vez. No exemplo eu estou usando o método `make` para criar um `slice de byte` de tamanho `10`.

`buffer := make([]byte, 10)`

A função `make` é função built-in do Go que aloca e inicializa um objeto do tipo slice, map ou char. No caso eu estou usando o tipo slice.

>Você pode consultar a [documentação do Go](https://pkg.go.dev/go/types) para saber mais sobre o pacote de tipos em Go.

Continuando com a explicação do exemplo, depois disso eu uso um laço `for` para fazer algumas coisas:
* `n, err := reader.Read(buffer)` Aqui eu estou acessando o método Read da função `bufio.NewReader(f)` que eu usei para criar o meu reader e salvando na variável `n`. _A variável de erro eu já expliquei antes como funciona.

Observe no código exemplo como lidamos com o `io.EOF` (End of File).
```
if err != nil && err != io.EOF {
		panic(err)
	}
```

É importante lembrar de fazer isso **porque a leitura pode retornar io.EOF a qualquer momento**, por isso já tratamos isso no `for` e é assim que nós sabemos quando terminamos de ler o arquivo.

Sobre o `io.EOF` (End Of File): ele é um erro que é comum em operações de arquivo e é usado para indicar que o fim de um arquivo foi atingido.

Como uma **convenção em programação**, sempre que uma função está lendo de um arquivo e _atinge o fim do arquivo_, **essa função retorna io.EOF**.

Isso é útil para que o código que chamou a função de leitura possa saber quando parar de ler.

O nosso exemplo é bastante simples mas ele mostra como usamos o `io.Reader` (uma interface que abstrai a leitura de dados de uma fonte).

Nós usamos um loop (`for`) para tentar ler `10 bytes` do `reader` de cada vez em nosso `buffer`. 

Se o reader.Read() encontrar um erro que não seja io.EOF, o programa entra em pânico. 

>Uma coisa que eu sempre gostei em Go foi poder definir que ele vai entrar em pânico caso algum erro aconteça porque literalmente a função que trata isso se chama `panic` 😂

Se nenhum byte for lido (n == 0), isso significa que atingimos io.EOF e o arquivo não tem mais nada a ser lido, então saímos do loop. 

Caso contrário, processamos os bytes que foram lidos com sucesso.

Então, resumindo, `io.EOF` é usado em Go (e muitas outras linguagens) para sinalizar que a leitura de um arquivo ou outra fonte de entrada chegou ao fim. Ele é útil para controlar loops e outras estruturas que continuam a ler até que não haja mais nada para ler.

---

###Update
####Escrever no Arquivo
Depois de criar o arquivo, podemos escrever nele. Para fazer isso, utilizamos a função f.Write(), que aceita uma fatia de bytes como argumento.

```
size, err := f.Write([]byte("Criando conteúdo do arquivo"))
if err != nil {
	panic(err)
}
```

Assegure-se de sincronizar os dados no disco usando `f.Sync()` para garantir que todos os dados pendentes sejam efetivamente escritos no arquivo.

```
err = f.Sync()
if err != nil {
	panic(err)
}
```
Escrever em um arquivo é algo bem simples. No exemplo eu usei uma variável chamada `size` para armazenar o retorno da função Write e uma variável de erro, como já de costume.

Abaixo eu mostro como imprimir no console essa informação usando o pacote `fmt` e a função `Printf` para formatar a saída de texto usando o `%s`(de string) que vai ser substituído pelo conteúdo do arquivo, convertido em string.

Além disso eu também mostro o tamanho do arquivo em bytes.

```
fmt.Printf("Tamanho: %d bytes\n", size)
	fmt.Printf("Conteúdo do arquivo: %s\n", string(arquivo))
```
Existe um outro método chamado writeString onde eu não precisaria passar um slice de bytes, porém, entretanto, contudo, eu teria que garantir que em 100% das vezes o que tipo de dado que vai trafegar ali é do tipo string. Se eu tenho a opção de trabalhar com uma cadeia de bytes, sejam eles em algum momento um string ou não, eu vou optar por isso.

De qualquer forma, escrever dados em um arquivo é algo que se torna muito fácil quando aproveitamos os recursos da linguagem.

---

###Delete
####Apagando um arquivo
Apagar um arquivo usando Go é extremamente simples. Para deletar um arquivo, usamos a função os.Remove().

```
err = os.Remove("test.txt")
if err != nil {
	panic(err)
}
fmt.Println("\nArquivo deletado com sucesso!")
```
###Deployando
Neste tutorial eu te mostrei como, fundamentalmente, fazer operações CRUD em arquivos no Go. 

Abaixo eu deixo um exemplo do código completo. No início do post tem o link para o repositório no GitHub.

```
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	// Criar um novo arquivo
	f, err := os.Create("test.txt")
	if err != nil {
		panic(err)
	}
	defer func(f *os.File) {
		err := f.Close()
		if err != nil {
			panic(err)
		}
	}(f)

	// Escrevendo no arquivo criado
	size, err := f.Write([]byte("Criando conteúdo do arquivo"))
	if err != nil {
		panic(err)
	}

	// Sincroniza os dados no disco
	err = f.Sync()
	if err != nil {
		panic(err)
	}

	arquivo, err := os.ReadFile("test.txt")
	if err != nil {
		panic(err)
	}

	fmt.Println("Arquivo criado com sucesso!")
	fmt.Printf("Tamanho: %d bytes\n", size)
	fmt.Printf("Conteúdo do arquivo: %s\n", string(arquivo))

	// Abrindo um arquivo existente
	f2, err := os.Open("test.txt")
	if err != nil {
		panic(err)
	}
	defer func(arquivo2 *os.File) {
		err := arquivo2.Close()
		if err != nil {
			panic(err)
		}
	}(f2)

	// Lendo arquivo usando bufio
	reader := bufio.NewReader(f2)
	buffer := make([]byte, 10)
	for {
		n, err := reader.Read(buffer)
		if err != nil && err != io.EOF {
			panic(err)
		}
		if n == 0 {
			break
		}
		fmt.Print(string(buffer[:n]))
	}

	// Deletando um arquivo
	/*
		err = os.Remove("test.txt")
		if err != nil {
			panic(err)
		}
		fmt.Println("\nArquivo deletado com sucesso!")
	*/
}

```

Assim como em qualquer linguagem de programação, existem muitas nuances que nós podemos explorar mas eu considero este um ótimo ponto de partida. 

Espero que este guia tenha sido útil e encorajo a todos a continuar explorando e aprendendo Go!