Good package name tells you what it provides, not what it contains
-- Practical Go, Dave Cheney
I do believe in User-Centric Design. It is not only about UX/UI. It is about how things will be used. So when I create functions, programs, APIs, I always think about how to use them. And this is why I love Test-Driven Development (TDD) methodology as well because it encourages me to think about how my function will be used (with tests).
The package is the unit in a Go program that other packages or modules use it. When I create a package, I'll ask how this package will be used.
In Go, we always use functions through the package.
The package name is essential in Go. So this is the guideline,
The package name must be unique in your Go program
If you have found that packages name have been used multiple time in your Go program, those packages would be too generic, or there is some design problem that needs to validate.
To demonstrate creating a Go package, think about shopping cart on e-commerce. I'm going to provide "add to cart" function. I've started with the cart package.
When you create a Go package, always put Go file with the same name as a package name.
Every time I create a new package, I always put Go file with the same name as the package name. And I'll maintain all functionality of this package in this Go file until I understand the boundary of this package, then use another Go files to re-organize this package. If some feature doesn't make sense to be in this package, I'll move it to a new package.
From the code snippet, two entities need to consider; Cart and Product. Cart is the receiver of Add method, so I create Cart struct. And also generate Product struct. From this point, I create the "New" function that will return a Cart.
When you want a cart, you can call New function.
c := cart.New()
It is quite natural and readable in my point of view.
Think about creating a new product. You would have seen pd := cart.NewProduct()
. As I mentioned, the package name will tell you what it provides. From my perspective, the cart should not be responsible for proving the product's functionality. So I decided to create a new package; product and move Product to a new one. When you want to create a product, you can call New function from the product package like pd := product.New()
Keep using Go file to re-organize your package, use sub-package when it makes sense
In the Go program, mostly, we don't call a package function with package chaining like this; net.http.Get("http://www.example.com")
. So we need to keep the package name being unique in the Go program. We can create sub-package if it is reasonable. the net/http is an excellent example for sub-package.
So when you want to re-organize data in your package, you can use Go file to group related data and function and give the meaningful name to a file. Let's see this example
common/kafka/producer
common/kafka/consumer
From that design, when you want to create a producer or a consumer, you will use pdc := producer.New()
and csm := consumer.New()
It seems to be OK. But it loses the context. What do the producer and consumer mean?
One thing I want to mention is
Avoid package name like base, common, or util
Those packages are too generic. If there is something you would reuse, you will put them to those packages. And then the package name cannot tell you what it provides.
So I decide to refactor the above package to this
Then you can create a producer and consumer with pdc := kafka.NewProducer()
and csm := kafka.NewConsumer()
When you face that your package keeps growing, you can use Go file to re-organize data and functionality. From the previous example, I'll create producer.go and consumer.go to organizer data and functionalities of a producer and a consumer instead of creating sub-package.
Most of the time, I don't design all packages of Go program in the first place. But I will think about how the package will be used and what it should provide.
I hope you will have fun to create software that improves our user's life gets better.
Top comments (0)