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
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.
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
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
good
.Both of the files in Listing 1.4 declare their package name as good
.
package good
// a.go
package good
// b.go
.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
bad
.Both of the files in Listing 1.6 should declare their package name as bad
, but they do not.
package bad
// a.go
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
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.23.0
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
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.
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
}
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
}
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.