Go Fundamentals - Sample

Table of Contents

Chapter 1.3: Folders, Files, and Organization

With a few exceptions, the name of the folder and the name of the package are the same.

$ tree

.
├── go.mod
├── smtp
│   ├── smtp.go
│   └── template.go
└── sound
    ├── analyzer.go
    └── sound.go

2 directories, 5 files
Listing 1.1: A folder structure with multiple Go packages.

For example, if we look at sound/sound.go, Listing 1.2, the folder name is sound and the package name is sound.

package sound

// Sound is a package for working with sound.
Listing 1.2: The sound package.

If we look at smtp/smtp.go, Listing 1.3, the folder name is smtp and the package name is smtp.

package smtp

// SMTP is a package for working with SMTP
Listing 1.3: The smtp package.

While the folder name and the package name don't always have to match, it is generally a good idea, and idiomatic, to follow this convention. By following this convention, we can easily find the files that belong to a package.

Multiple Packages in a Folder

With rare exceptions, a folder can only contain one package. Let's look at the file structure in Listing 1.4.

$ tree

.
├── a.go
└── b.go

0 directories, 2 files
Listing 1.4: Listing of files in a folder named good.

Both of the files in Listing 1.4 declare their package name as good.

package good

// a.go
package good

// b.go
Listing 1.5: The .go files in the good folder.

Now, let's look at the file structure in Listing 1.6.

$ tree

.
├── a.go
└── b.go

0 directories, 2 files
Listing 1.6: Listing of files in a folder named bad.

Both of the files in Listing 1.6 should declare their package name as bad, but they do not.

package bad

// a.go
Listing 1.7: Source code for a.go.

If we look at the file, a.go, in, Listing 1.7, we can see that the package name is bad. However, b.go, Listing 1.8, declares its package name as sad.

package sad

// b.go
Listing 1.8: The b.go file.

We now have two different package names declared in the same folder.

When trying to build the package bad, we get an error telling us that we can not build the package bad because two different files have declared different package names.

$ go build .

found packages bad (a.go) and sad (b.go) in .

--------------------------------------------------------------------------------
Go Version: go1.19
Listing 1.9: Building the bad package.

All files within the same directory/package have to have the same package name in the file.

File Names

File names are less strict than package names. The convention is to use lower-case letters and underscores.

$ tree

.
├── bad
│   ├── ServiceManagerTest.go
│   ├── USER_test.go
│   ├── User.go
│   └── serviceManager.go
├── go.mod
└── good
    ├── service_manager.go
    ├── service_manager_test.go
    ├── user.go
    └── user_test.go

2 directories, 9 files
Listing 1.10: Examples of good and bad file names.

Package Organization

There are a lot of different ways to organize packages in Go. A quick Google search will return numerous blog posts, articles, and conference presentations that discuss the various ways to organize packages in Go. All of these ways have their pros and cons. These patterns are often extracted from one particular use case, that worked well for them in that case. However, not everyone is writing the same applications, and libraries, so there can't be any one best way to organize packages.

When organizing packages you should consider the factors listed in Listing 1.11.

Package Organization Consideration Factors

  • Code organization is important.
  • Documentation is important.
  • Maintainability is important.
  • Reusability is important.
  • Testing is important.
Listing 1.11: Factors that should be considered when organizing packages.

Focus

Packages should be singularly focused. For example, if you have a package that contains tools for working with time and for working with HTTP, then that package should be split into two packages, one for time and one for HTTP. Now the packages have a clear, singular purpose.

API Scope

Package APIs should be clean, simple, and easy to use. Export only the most necessary identifiers, such as types, functions, and variables. It is always easier to expose more of a package's API later than it is to hide it after it's been exposed and is in use.

File Organization

Package files should be organized in a way that is clear and obvious to the reader. Avoid using one, or two, large files for a package. Instead, break the package into smaller files, with each having a clear purpose.

If we look at the file in Listing 1.12, we can see that the file contains three different type definitions, along with their methods. This file is difficult read and as the file grows, it will become even more difficult to read and maintain.

package bad

import "sync"

type User struct {
	Name string
}

func NewOrder(u User) *Order {
	return &Order{
		User: u,
	}
}

type Order struct {
	User
	products []Product
	sync.Mutex
}

type Product struct {
	Name string
	Cost int
}

func (p Product) String() string {
	return p.Name
}

func (o *Order) AddProduct(p Product) {
	o.Lock()
	o.products = append(o.products, p)
	o.Unlock()
}

func (u User) String() string {
	return u.Name
}
Listing 1.12: The bad.go file.

If, instead, we split the code into more files, Listing 1.13, one for each of the types declared, then we can see that each file is now easier to read and maintain. In addition, the file names are now clear and easy to understand, which makes finding the code your need that much easier.

$ tree

.
├── order.go
├── order_test.go
├── product.go
├── product_test.go
├── user.go
└── user_test.go

0 directories, 6 files
package good

import "sync"

type Order struct {
	User
	products []Product
	sync.Mutex
}

func NewOrder(u User) *Order {
	return &Order{
		User: u,
	}
}

func (o *Order) AddProduct(p Product) {
	o.Lock()
	o.products = append(o.products, p)
	o.Unlock()
}
package good

type Product struct {
	Name string
	Cost int
}

func (p Product) String() string {
	return p.Name
}
package good

type User struct {
	Name string
}

func (u User) String() string {
	return u.Name
}
Listing 1.13: The good folder and its files.

Organizing your code into smaller files that have clear and specific purposes will make it easier to navigate your project as it grows. It will also make it easier for developers new to your project to find the code they are looking for.


Subscribe to our newsletter