do di erently ? We've heard of: When you write go, do as the Gophers do. github.com/go-proverbs/go-proverbs.github.io/issues/21#issuecomment-304559209 (https://github.com/go-proverbs/go-proverbs.github.io/issues/21#issuecomment-304559209) The question is how and why ?
package ? A package in Go is simply a directory/folder with one or more .go les inside of it. All Go code lives in a package and a package is the entry point to access Go code. Understanding and establishing good practices around packages is important to write e ective Go code.
care go package that much ? Package is also the key to: Abstract Design Reusability Composability And perhaps productivity. Gophers by golang.org/doc/gopher (https://golang.org/doc/gopher)
- doc.go // package documentation - headers.go // HTTP headers types and code - cookies.go // HTTP cookies types and code - http.go // HTTP client implementation, request and response types, etc. Do not overuse it, restrict it by responsibility ( single domain concept ).
A common practise from other languages is to organize types together in a package called models or types. package models // DON'T DO IT!!! // User represents a user in the system. type User struct {...} A User type should live in a service-layer package. package mngtservice // User represents a user in the system. type User struct {...} func UsersByQuery(ctx context.Context, q *Query) ([]*User, *Iterator, error) func UserIDByEmail(ctx context.Context, email string) (int64, error) In Go, we organize code by their functional responsibilities.
make code better A package's name provides context for its contents, making it easier for clients to understand what the package is for and how to use it. The name also helps package maintainers determine what does and does not belong in the package as it evolves. Well-named packages make it easier to nd the code you need.
names Package names should be short, but should be unique and representative. Users of the package should be able to grasp its purpose from just the package’s name. Avoid overly broad package names like “common” and “util”. import "pkgs.org/common" // DON'T!!! If you cannot avoid a bad name, it is very likely that there is a problem with your overall structure and code organization.
Packages are given lower case, single-word names; there should be no need for underscores or mixedCaps The package name is the base name of its source directory ( eg. base64 ) Use package name to avoid stutter ( eg. bu o.Reader, not bu o.BufReader ) A helpful doc comment can often be more valuable than an extra long name.
guidelines Lowercase only Clean import paths No plurals package httputils // DON'T DO IT, USE SINGULAR FORM!! Renames should follow the same rules Enforce vanity URLs bonus slide: talks.golang.org/2014/names.slide#1 (https://talks.golang.org/2014/names.slide#1)
package Package documentation is a top-level comment immediately preceding the package clause. // Package ioutil implements some I/O utility functions. package ioutil // Command gops lists all the processes running on your system. package main // Sample helloworld demonstrates how to use x. package main Sometimes, package docs can get very lengthy, specially when they provide details of usage and guidelines. If so, move the package's doc to a doc.go le.
Most of the time follow "Clean import paths". ( eg. github.com/upspin/upspin (https://github.com/upspin/upspin) ) For projects that provide both binaries and libraries, or combine Go code with other, non-Go assets. Put library code under a pkg/ subdirectory Put binaries under a cmd/ subdirectory
to design ? Design is the art of arranging code that needs to work today, and to be easy to change forever. –Sandi Metz I think we will missing some important things to talk only about package without talking about design. Package is the tool for how you implement design. You will see package and design are closely related. - Be aware we are going to have a more high level of thinking !
Go packages Namespacing Allows us to choose short and clear names for types and functions in a package. We don’t need to worry if common names have already been used in other packages. Encapsulation We control what is accessible outside of a package. The possibility of having a very intentional API at the package level. The flexibility to change unexported code without worrying about breaking that API. Internal packages Disallows the importing of code containing the element “internal”
is an acronym for the rst ve object-oriented design(OOD) principles by Robert C. Martin, popularly known as Uncle Bob. Agile Software Development, Principles, Patterns, and Practices (https://www.amazon.co.uk/dp/0135974445/ref=pd_lpo_sbs_dp_ss_2/253-1946330-6751666?pf_rd_m=A3P5ROKL5A1OLE&pf_rd_s=lpo-top- stripe&pf_rd_r=23C4AHYV7EXGYHKD6G8Q&pf_rd_t=201&pf_rd_p=569136327&pf_rd_i=0132760584)
A class should have one, and only one, reason to change. –Robert C Martin Go obviously doesn’t have classes—instead we have the far more powerful notion of composition (https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html) . Encourages you to structure the functions, types, and methods into packages that exhibit natural cohesion. For package, it is mean the single domain package.
Small, sharp tools which combine to solve larger tasks, oftentimes tasks which were not envisioned by the ori –Doug McIlroy Go packages embody the spirit of the UNIX philosophy. In e ect each Go package is itself a small Go program, a single unit of change, with a single responsibility.
Principle ( part 1 ) Software entities should be open for extension, but closed for modification. –Bertrand Meyer, Object-Oriented Software Construction Encourages you to compose simple types into more complex ones using embedding.
Principle - Go embedding ( part 2 ) Embedding is a powerful tool which allows Go’s types to be open for extension. type Cat struct { Name string } func (c Cat) Legs() int { return 4 } func (c Cat) PrintLegs() { fmt.Printf("I have %d legs\n", c.Legs()) } type OctoCat struct { Cat } func (o OctoCat) Legs() int { return 5 } func main() { var octo OctoCat fmt.Println(octo.Legs()) octo.PrintLegs() } Run
( part 1 ) Two types are substitutable if they exhibit behaviour such that the caller is unable to tell the di erence. In a class based language, It is commonly interpreted as a speci cation for an abstract base class with various concrete subtypes. But Go does not have classes, or inheritance, so substitution cannot be implemented in terms of an abstract class hierarchy.
- Go Interface ( part 2 ) I think it achieved by Go’s interfaces, the general abstraction. Well designed interfaces are more likely to be small interfaces Small interfaces lead to simple implementations, because it is hard to do otherwise. Which leads to packages comprised of simple implementations connected by common behaviour.
- Go Interface ( part 3 ) io.Reader type Reader interface { // Read reads up to len(buf) bytes into buf. Read(buf []byte) (n int, err error) } It seems simple but it’s very powerful. Because io.Reader‘s deal with anything that can be expressed as a stream of bytes, we can construct readers over just about anything. Such as A constant string, a byte array, standard in, a network stream, a gzip’d tar file.
- Go Interface ( part 4 ) So the Liskov substitution principle, applied to Go, could be summarised by this lovely aphorism from the late Jim Weirich. Require no more, promise no less. –Jim Weirich Encourages you to express the dependencies between your packages in terms of interfaces, not concrete types.
Clients should not be forced to depend on methods they do not use. –Robert C. Martin Refer as process of isolating the behaviour required for a function to do its job. A great rule of thumb for Go is Accept interfaces, return structs. –Jack Lindamood Encourages you to de ne functions and methods that depend only on the behaviour that they need.
? If you’ve applied all the principles we’ve talked about up to this point then your code should already be factored into discrete packages, each with a single well de ned responsibility or purpose. Your code should describe its dependencies in terms of interfaces, and those interfaces should be factored to describe only the behaviour those functions require. There shouldn’t be much left to do.
( part 1 ) High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions. –Robert C. Martin In the context of Go, it is the structure of your import graph. The import graph of a well designed Go program should be a wide, and relatively at, rather than tall and narrow.
( part 2 ) If you have a package whose functions cannot operate without enlisting the aid of another package, that is perhaps a sign that code is not well factored along package boundaries. Encourages you to push the responsibility for the speci cs, as high as possible up the import graph, to your main package or top level handler. Leaving the lower level code to deal with abstractions–interfaces. Which also means to move the knowledge of the things your package depends on from compile time to run time.
Interfaces let you apply the SOLID principles to Go programs. Because interfaces describe what their package provides–not how it does it. Another way of saying “decoupling”, which is indeed the goal. Loosely coupled is software that is easier to change.
responsibility is the key to both package and good design. Abstract design is helpful for re-use ( design into library ). A good design Reduce maintenance costs ( easier to change and refactoring ) Increase productivity by make our work more e ciency
rst question How does Gophers do differently ? Here we explained one aspect of how Gophers do di erent about package and design. I hope you now have a better understanding of how does Gophers do di erently. At last, We now recommend: Design go packages as Gophers do.