Software engineering is what happens with programming if you add the time factor to other programmers.
“Simplicity is a prerequisite for readability” - Edsger Dijkstra
“There are two ways to design software: the first is to make it so simple that there are no obvious flaws, and the second is to make it so complicated that there are no obvious flaws. The first is much more difficult. ” - C. E. R. Hoar
“Readability is an integral part of maintainability” - Mark Reinhold, JVM conference, 2018
“Programs should be written for people, and machines only execute them” - Hal Abelson and Gerald Sassman, “Structure and Interpretation of Computer Programs”
“The most important skill for a programmer is the ability to effectively convey ideas” - Gaston Horker
“Design is the art of organizing code so that it works today, but always supports change.” - Sandy Mets
“A bad name is a symptom of bad design” - Dave Cheney
“It is important that the code is obvious. What can be done in one line, you must do in three " - Ukiya Smith
“A good name is like a good joke. If you need to explain it, it's not funny anymore. ” - Dave Cheney
“The greater the distance between the announcement of the name and the use of the object, the longer the name must be” - Andrew Gerrand
type Person struct { Name string Age int } // AverageAge returns the average age of people. func AverageAge(people []Person) int { if len(people) == 0 { return 0 } var count, sum int for _, p := range people { sum += p.Age count += 1 } return sum / count } p , and it is called only once from the next line. That is, the variable lives on the page for a very short time. If the reader is interested in the role of p in the program, it is enough to read only two lines.people declared in the parameters of the function and live seven lines. The same applies to sum and count , so they justify their longer names. The reader needs to scan more code to find them: this justifies more distinctive names.s for sum and c (or n ) for count , but this will reduce the importance of all variables in the program to the same level. You can replace people with p , but there will be a problem, how to call the iteration variable for ... range . A single person will look weird, because a short-lived iteration variable produces a longer name than several values ​​from which it is derived.Council Separate the function flow with empty lines, as empty lines between paragraphs break the flow of text. In AverageAge we have three consecutive operations. First, check the division by zero, then the total age and the number of people, and the last is the calculation of the average age.i and index ids? For example, it is impossible to say for sure that such code for index := 0; index < len(s); index++ { // } for i := 0; i < len(s); i++ { // } i or index limited to the body of the for loop, and the additional verbosity adds little to the understanding of the program. func (s *SNMP) Fetch(oid []int, index int) (int, error) func (s *SNMP) Fetch(o []int, i int) (int, error) oid is an abbreviation of SNMP Object ID, and an additional abbreviation to o causes the code to go from documented notation to shorter notation in code. Similarly, the reduction of index to i makes it difficult to understand the essence, since in SNMP messages, the sub value of each OID is called an index.Council Do not combine long and short formal parameters in one ad.
var usersMap map[string]*User *User : this is probably good. But usersMap is really a map, and Go, as a statically typed language, will not accidentally use such a name where a scalar variable is required, therefore the Map suffix is ​​redundant. var ( companiesMap map[string]*Company productsMap map[string]*Products ) usersMap , companiesMap and productsMap , and all strings are matched with different types. We know that these are maps, and we also know that the compiler will give an error if we try to use companiesMap where the code expects map[string]*User . In this situation, it is clear that the Map suffix does not improve the clarity of the code, it’s just extra characters.Council If the titleusersinsufficiently clearly describes the essence, thenusersMaptoo.
type Config struct { // } func WriteConfig(w io.Writer, config *Config) config name for the *Config parameter is redundant. We already know that this is *Config , right there next written.conf or c if the lifetime of the variable is short enough.*Config , then the names conf1 and conf2 less meaningful than the original and updated , since the latter are more difficult to confuse.Note Don't let package names steal good variable names.
The name of the identifier being imported contains the name of the package. For example, theContexttype in thecontextpackage will be calledcontext.Context. This makes it impossible to use a variable or typecontextin your package.func WriteLog(context context.Context, message string)
This will not compile. That is why the local declaration ofcontext.Contexttypes, for example, traditionally uses names likectx.func WriteLog(ctx context.Context, message string)
d *sql.DB , dbase *sql.DB , DB *sql.DB and database *sql.DB it is better to use one thing: db *sql.DB db , you know that it is *sql.DB and it is declared locally or provided by the caller.Note The agreement on short names of recipients in Go contradicts the previously voiced recommendations. This is one of those cases where the choice made at an early stage becomes a standard style, like usingCamelCaseinstead ofsnake_case.
Council The Go style indicates single letter names or abbreviations for recipients, derived from their type. It may turn out that the receiver's name sometimes conflicts with the name of the parameter in the method. In this case, it is recommended to make the parameter name a little longer and not to forget to use it consistently.
i , j and k are usually inductive variables in for cycles, n usually associated with a counter or accumulator, v is a typical value reduction in the coding function, k usually used for a card key, and s often used as an abbreviation for string parameters .db example above, programmers expect i be an inductive variable. If they see it in code, then they expect to see a loop soon.Council If you have so many nested loops that you have exhausted the supply of variablesi,jandk, then you should split the function into smaller units.
var x int = 1 var x = 1 var x int; x = 1 var x = int(1) x := 1 var . var players int // 0 var things []Thing // an empty slice of Things var thing Thing // empty Thing struct json.Unmarshall(reader, &thing) var acts as a hint that this variable is intentionally declared as a null value of the specified type. This is consistent with the requirement to declare variables at the package level using var in contrast to the syntax of a short declaration, although I will later argue that package-level variables should not be used at all.:= . This makes it clear to the reader that the variable to the left of := intentionally initialized. var players int = 0 var things []Thing = nil var thing *Thing = new(Thing) json.Unmarshall(reader, thing) var players = 0 var things []Thing = nil var thing = new(Thing) json.Unmarshall(reader, thing) players explicitly initialized to 0 , which is redundant, because the initial value of the players is zero anyway. Therefore, it is better to make it clear that we want to use a zero value: var players int var things = nil nil no type . Instead, we have a choice: or we use the zero value for the slice ... var things []Thing var things = make([]Thing, 0) things := make([]Thing, 0) things . var thing = new(Thing) new , which some Go programmers do not like, are simultaneously. If we apply the recommended short syntax, we get thing := new(Thing) thing explicitly initialized to the result of new(Thing) , but still leaves an atypical new . The problem could be solved with the help of a literal: thing := &Thing{} new(Thing) , and such duplication grieves some Go programmers. However, this means that we explicitly initialize a thing with a pointer to Thing{} and a zero value of Thing .thing declared with a zero value, and use the operator’s address to transfer the address of the thing to json.Unmarshall : var thing Thing json.Unmarshall(reader, &thing) Note Of course, there are exceptions to any rule. For example, sometimes two variables are closely related, so it would be strange to writevar min int max := 1000
More readable declaration:min, max := 0, 1000
var syntax.:= .Council Explicitly point out complex things.var length uint32 = 0x80
Here,lengthcan be used with a library, which requires a specific numeric type, and this option more explicitly indicates that the type of length is specifically chosen as uint32 than in the short declaration:length := uint32(0x80)
In the first example, I intentionally break my rule by using the var declaration with explicit initialization. A departure from the standard makes the reader understand that something unusual is happening.
gofmt , then usually the problem is not worth discussing.Council If you want to rename the entire code base, do not mix it with other changes. If someone uses git bisect, he will not like to wade through thousands of renames to find another altered code.
“A good code has a lot of comments, and a bad code requires a lot of comments” - Dave Thomas and Andrew Hunt, “Pragmatic Programmer”
// Open . // . // var results []chan error for _, dep := range a.Deps { results = append(results, execute(seen, dep)) } return &v2.Cluster_CommonLbConfig{ // HealthyPanicThreshold HealthyPanicThreshold: &envoy_type.Percent{ Value: 0, }, } const randomNumber = 6 // randomNumber assigned the value 6 and where it came from. The comment does not describe where randomNumber will be used. Here are some more examples: const ( StatusContinue = 100 // RFC 7231, 6.2.1 StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2 StatusProcessing = 102 // RFC 2518, 10.1 StatusOK = 200 // RFC 7231, 6.3.1 100 known as StatusContinue , which is defined in RFC 7231, section 6.2.1.Council For variables without an initial value, the comment should describe who is responsible for initializing this variable.// sizeCalculationDisabled , // . . dowidth. var sizeCalculationDisabled bool
Here the comment informs the reader that thedowidthfunctiondowidthresponsible for maintaining the state ofsizeCalculationDisabled.
Council Hide in sight. This is advice from Kate Gregory . Sometimes the best name for a variable is hidden in the comments.// SQL var registry = make(map[string]*sql.Driver)
The comment was added by the author, because the nameregistrydoes not sufficiently explain its purpose - this is the registry, but what is the registry?
If you rename a variable in sqlDrivers, it becomes clear that it contains SQL drivers.var sqlDrivers = make(map[string]*sql.Driver)
Now the comment has become redundant and can be deleted.
package ioutil // ReadAll r (EOF) // .. err == nil, not err == EOF. // ReadAll , // . func ReadAll(r io.Reader) ([]byte, error) // Read io.Reader func (r *FileReader) Read(buf []byte) (int, error) io package. // LimitReader Reader, r, // EOF n . // *LimitedReader. func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} } // LimitedReader R, // N . Read N // . // Read EOF, N <= 0 R EOF. type LimitedReader struct { R Reader // underlying reader N int64 // max bytes remaining } func (l *LimitedReader) Read(p []byte) (n int, err error) { if lN <= 0 { return 0, EOF } if int64(len(p)) > lN { p = p[0:lN] } n, err = lRRead(p) lN -= int64(n) return } LimitedReader declaration is immediately preceded by the function that uses it, and the LimitedReader.Read declaration follows the LimitedReader.Read declaration itself. Although LimitedReader.Read itself is not documented, but it can be understood that this is an implementation of io.Reader .Council Before writing a function, write a comment describing it. If you find it difficult to write a comment, then this is a sign that the code you are going to write will be difficult to understand.
“Do not comment on the bad code - rewrite it” - Brian Kernigan
// TODO(dfc) O(N^2), . “Good code is the best documentation. When you are going to add a comment, ask yourself: “How to improve the code so that this comment is not needed?” Refactor and leave the commentary to make it clearer. ” - Steve McConnell
“Write modest code: modules that do not show anything extra to other modules and that do not rely on the implementations of other modules” - Dave Thomas
Council , .
base , common utilutils helpers , , . - , . , - .utils helpers , , , . , , .«[] , » —
Council . , strings .base common , . , , , , .net/http client server , client.go server.go , transport.go .Council , .
Getnet/httphttp.Get.- The type
Readerof the packagestringswhen converted to other packages is converted tostrings.Reader.- The interface
Errorfrom the package isnetclearly related to network errors.
tryand blocks catch. Instead of a multi-level hierarchy, the Go code goes down the screen as the function advances. My friend Mat Ryer calls this practice a “line of sight . ”bytes: func (b *Buffer) UnreadRune() error { if b.lastRead <= opInvalid { return errors.New("bytes.Buffer: UnreadRune: previous operation was not a successful ReadRune") } if b.off >= int(b.lastRead) { b.off -= int(b.lastRead) } b.lastRead = opInvalid return nil } UnreadRune, the status is checked b.lastReadand if the previous operation was not ReadRune, an error is immediately returned. The rest of the function works on the assumption that b.lastReadmore than opInvalid. func (b *Buffer) UnreadRune() error { if b.lastRead > opInvalid { if b.off >= int(b.lastRead) { b.off -= int(b.lastRead) } b.lastRead = opInvalid return nil } return errors.New("bytes.Buffer: UnreadRune: previous operation was not a successful ReadRune") } if, and the condition for a successful exit return nilshould be detected by carefully matching the closing brackets. The last line of the function now returns an error, and you need to track the execution of the function to the corresponding opening bracket to find out how to get to this point.sync.Mutex , , . sync.Mutex. The code takes into account this fact, so that the type is suitable for use without explicit initialization. type MyInt struct { mu sync.Mutex val int } func main() { var i MyInt // i.mu is usable without explicit initialisation. i.mu.Lock() i.val++ i.mu.Unlock() } bytes.Buffer. You can declare and start writing to it without explicit initialization. func main() { var b bytes.Buffer b.WriteString("Hello, world!\n") io.Copy(os.Stdout, &b) } lenboth capare equal 0, and y array, a pointer to memory with the contents of the backup array of the slice, value nil. This means that you do not need to explicitly make a cut, you can simply declare it. func main() { // s := make([]string, 0) // s := []string{} var s []string s = append(s, "Hello") s = append(s, "world") fmt.Println(strings.Join(s, " ")) } Note .var s []stringsimilar to the two commented lines above, but not identical to them. There is a difference between the value of the slice, equal to nil, and the value of the slice, which has a zero length. The following code will print false.func main() { var s1 = []string{} var s2 []string fmt.Println(reflect.DeepEqual(s1, s2)) }
type Config struct { path string } func (c *Config) Path() string { if c == nil { return "/usr/home" } return c.path } func main() { var c1 *Config var c2 = &Config{ path: "/export", } fmt.Println(c1.Path(), c2.Path()) } common.Council ,common, (back-port fixes)common, , , API.
cmd/contour , , Kubernetes, .public , protected , private default ). ++.. «» « » public private.
CouncilIn each package exceptcmd/andinternal/must contain the source code.
CouncilCome with Java?
If you come from the world of Java or C #, then remember the unspoken rule: a Java package is equivalent to one source file.go. The Go package is equivalent to the whole Maven module or .NET assembly.
.gointo several? How do I know that you have gone too far and need to think about merging files?.go. Give this file the same name as the directory. For example, the package httpshould be in a file http.goin a directory http.messages.gowill contain types Requestand Response, file client.go- type Client, file server.go- type of server.messages.go HTTP- , http.go , client.go server.go — HTTP .Council .
. Go . ( — Go). .
go testing . http2 , http2_test.go http2 . http2_test.go , http2 . .go , test , http_test . , , , . , . .Example). This ensures that when viewed in godoc, the examples will receive the appropriate package prefix and can be easily copied.Council , .
, , Gogo. ,net/httpnet..go, , .
gorecognizes a special folder name internal/that can be used to place code that is open to your project but closed to others.internal/or in its subdirectory. When the team gosees the import package with the path internal, it checks the location of the caller in the directory or subdirectory internal/..../a/b/c/internal/d/e/fcan import only a package from a directory tree .../a/b/c, but not .../a/b/gany other repository (seedocumentation ).mainand package mainshould have minimal functionality, because it main.mainacts as a singleton: there can be only one function in the program main, including tests.main.mainis a singleton, there are many restrictions on the called objects: they are called only during main.mainor main.init, and only once . This makes it difficult to write tests for code main.main. Thus, one should strive to derive as much logic as possible from the main function and, ideally, from the main package.Council func main() must analyze the flags, open connections with databases, loggers, etc., and then transfer the execution to a high-level object.«API » —
func Max(a, b int) int func CopyFile(to, from string) error Max(8, 10) // 10 Max(10, 8) // 10 CopyFile("/tmp/backup", "presentation.md") CopyFile("presentation.md", "/tmp/backup") CopyFile. type Source string func (src Source) CopyTo(dest string) error { return CopyFile(dest, string(src)) } func main() { var from Source = "presentation.md" from.CopyTo("/tmp/backup") } CopyFilealways called correctly here - this can be asserted using a unit test - and can be made private, which further reduces the likelihood of incorrect use.Council An API with several parameters of the same type is difficult to use correctly.
package http // ListenAndServe listens on the TCP network address addr and then calls // Serve with handler to handle requests on incoming connections. // Accepted connections are configured to enable TCP keep-alives. // // The handler is typically nil, in which case the DefaultServeMux is used. // // ListenAndServe always returns a non-nil error. func ListenAndServe(addr string, handler Handler) error { ListenAndServeIt takes two parameters: a TCP address to listen for incoming connections and http.Handlerto process an incoming HTTP request. Serveallows the second parameter to be nil. The comments indicate that usually the caller will actually pass nil, which indicates a desire to use http.DefaultServeMuxas an implicit parameter.Servehas two ways to do the same. http.ListenAndServe("0.0.0.0:8080", nil) http.ListenAndServe("0.0.0.0:8080", http.DefaultServeMux) nil . http http.Serve , ListenAndServe : func ListenAndServe(addr string, handler Handler) error { l, err := net.Listen("tcp", addr) if err != nil { return err } defer l.Close() return Serve(l, handler) } ListenAndServe nil , http.Serve . , http.Serve « nil , DefaultServeMux ». nil , nil . Serve http.Serve(nil, nil) Councilnilnil.
http.ListenAndServe API , .nil DefaultServeMux . const root = http.Dir("/htdocs") http.Handle("/", http.FileServer(root)) http.ListenAndServe("0.0.0.0:8080", nil) const root = http.Dir("/htdocs") http.Handle("/", http.FileServer(root)) http.ListenAndServe("0.0.0.0:8080", http.DefaultServeMux) const root = http.Dir("/htdocs") mux := http.NewServeMux() mux.Handle("/", http.FileServer(root)) http.ListenAndServe("0.0.0.0:8080", mux) Council , . , .
Council API- , . , , , .
func ShutdownVMs(ids []string) error ids , , . , . if svc.MaxConnections > 0 || svc.MaxPendingRequests > 0 || svc.MaxRequests > 0 || svc.MaxRetries > 0 { // apply the non zero parameters } if , . : // anyPostive indicates if any value is greater than zero. func anyPositive(values ...int) bool { for _, v := range values { if v > 0 { return true } } return false } if anyPositive(svc.MaxConnections, svc.MaxPendingRequests, svc.MaxRequests, svc.MaxRetries) { // apply the non zero parameters } anyPositive , - : if anyPositive() { ... } anyPositive false . . , anyPositive true . // anyPostive indicates if any value is greater than zero. func anyPositive(first int, rest ...int) bool { if first > 0 { return true } for _, v := range rest { if v > 0 { return true } } return false } anyPositive .Documenton disk. // Save f. func Save(f *os.File, doc *Document) error Save , Document *os.File . .Save . , , .Save , . , , .f .*os.File , Save , , , . , Save *os.File . // Save // ReadWriterCloser. func Save(rwc io.ReadWriteCloser, doc *Document) error io.ReadWriteCloser — Save , .io.ReadWriteCloser , *os.File .Save , , *os.File .Save *os.File , io.ReadWriteCloser .Save , , , — . // Save // WriteCloser. func Save(wc io.WriteCloser, doc *Document) error Save .Save , . , wc .Save Close , . // Save // Writer. func Save(w io.Writer, doc *Document) error io.Writer , , .Save , io.Writer .. « ». , .
func CountLines(r io.Reader) (int, error) { var ( br = bufio.NewReader(r) lines int err error ) for { _, err = br.ReadString('\n') lines++ if err != nil { break } } if err != io.EOF { return 0, err } return lines, nil } CountLines io.Reader , *os.File ; io.Reader , .bufio.Reader , ReadString , , , . _, err = br.ReadString('\n') lines++ if err != nil { break } ReadString , , . , .. , ?
ReadString io.EOF , . , ReadString - «, ». , CountLine , , io.EOF , , nil , . func CountLines(r io.Reader) (int, error) { sc := bufio.NewScanner(r) lines := 0 for sc.Scan() { lines++ } return lines, sc.Err() } bufio.Scanner bufio.Reader .bufio.Scanner bufio.Reader , , .. bufio.Scanner , .sc.Scan() true , . , for . , CountLines , .sc.Scan false , for . bufio.Scanner , , sc.Err() , .sc.Err() io.EOF nil , .Council , .
ioutil.ReadFile ioutil.WriteFile . -. . HTTP-, HTTP-. type Header struct { Key, Value string } type Status struct { Code int Reason string } func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error { _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason) if err != nil { return err } for _, h := range headers { _, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value) if err != nil { return err } } if _, err := fmt.Fprint(w, "\r\n"); err != nil { return err } _, err = io.Copy(w, body) return err } fmt.Fprintf . , . , \r\n , . , io.Copy , , WriteResponse .errWriter .errWriter io.Writer , . errWriter . . type errWriter struct { io.Writer err error } func (e *errWriter) Write(buf []byte) (int, error) { if e.err != nil { return 0, e.err } var n int n, e.err = e.Writer.Write(buf) return n, nil } func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error { ew := &errWriter{Writer: w} fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason) for _, h := range headers { fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value) } fmt.Fprint(ew, "\r\n") io.Copy(ew, body) return ew.err } errWriter WriteResponse , . . ew.err , io.Copy. // WriteAll writes the contents of buf to the supplied writer. func WriteAll(w io.Writer, buf []byte) { w.Write(buf) } w.WriteAll . func WriteAll(w io.Writer, buf []byte) error { _, err := w.Write(buf) if err != nil { log.Println("unable to write:", err) // annotated error goes to log file return err // unannotated error returned to caller } return nil } w.Write , , , , , , . func WriteConfig(w io.Writer, conf *Config) error { buf, err := json.Marshal(conf) if err != nil { log.Printf("could not marshal config: %v", err) return err } if err := WriteAll(w, buf); err != nil { log.Println("could not write config: %v", err) return err } return nil } unable to write: io.EOF could not write config: io.EOF err := WriteConfig(f, &conf) fmt.Println(err) // io.EOF func WriteConfig(w io.Writer, conf *Config) error { buf, err := json.Marshal(conf) if err != nil { log.Printf("could not marshal config: %v", err) // oops, forgot to return } if err := WriteAll(w, buf); err != nil { log.Println("could not write config: %v", err) return err } return nil } buf : , , , JSON.WriteAll . , , . , , — , JSON, .fmt.Errorf . func WriteConfig(w io.Writer, conf *Config) error { buf, err := json.Marshal(conf) if err != nil { return fmt.Errorf("could not marshal config: %v", err) } if err := WriteAll(w, buf); err != nil { return fmt.Errorf("could not write config: %v", err) } return nil } func WriteAll(w io.Writer, buf []byte) error { _, err := w.Write(buf) if err != nil { return fmt.Errorf("write failed: %v", err) } return nil } Error() - : could not write config: write failed: input/output error fmt.Errorf , . , , , :errors : func ReadFile(path string) ([]byte, error) { f, err := os.Open(path) if err != nil { return nil, errors.Wrap(err, "open failed") } defer f.Close() buf, err := ioutil.ReadAll(f) if err != nil { return nil, errors.Wrap(err, "read failed") } return buf, nil } func ReadConfig() ([]byte, error) { home := os.Getenv("HOME") config, err := ReadFile(filepath.Join(home, ".settings.xml")) return config, errors.WithMessage(err, "could not read config") } func main() { _, err := ReadConfig() if err != nil { fmt.Println(err) os.Exit(1) } } could not read config: open failed: open /Users/dfc/.settings.xml: no such file or directory func main() { _, err := ReadConfig() if err != nil { fmt.Printf("original error: %T %v\n", errors.Cause(err), errors.Cause(err)) fmt.Printf("stack trace:\n%+v\n", err) os.Exit(1) } } original error: *os.PathError open /Users/dfc/.settings.xml: no such file or directory stack trace: open /Users/dfc/.settings.xml: no such file or directory open failed main.ReadFile /Users/dfc/devel/practical-go/src/errors/readfile2.go:16 main.ReadConfig /Users/dfc/devel/practical-go/src/errors/readfile2.go:29 main.main /Users/dfc/devel/practical-go/src/errors/readfile2.go:35 runtime.main /Users/dfc/go/src/runtime/proc.go:201 runtime.goexit /Users/dfc/go/src/runtime/asm_amd64.s:1333 could not read config errors , . , Go .select go . Go , , . : , - , Go. package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, GopherCon SG") }) go func() { if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }() for { } } for{} main - main, -, , - . package main import ( "fmt" "log" "net/http" "runtime" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, GopherCon SG") }) go func() { if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }() for { runtime.Gosched() } } package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, GopherCon SG") }) go func() { if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }() select {} } select . , runtime.GoSched() . , .http.ListenAndServe -, - main, http.ListenAndServe -.Council main.main , Go , -, . package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, GopherCon SG") }) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } } Council Go -, . , .
// ListDirectory returns the contents of dir. func ListDirectory(dir string) ([]string, error) // ListDirectory returns a channel over which // directory entries will be published. When the list // of entries is exhausted, the channel will be closed. func ListDirectory(dir string) chan string ListDirectory , . , , .ListDirectory , . , , . ListDirectory , - .. -: , , , , . , .
ListDirectory :ListDirectory - . . , , .ListDirectory : , . , , , . func ListDirectory(dir string, fn func(string)) filepath.WalkDir .Council -, . .
/debug/pprof . package main import ( "fmt" "net/http" _ "net/http/pprof" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) { fmt.Fprintln(resp, "Hello, QCon!") }) go http.ListenAndServe("127.0.0.1:8001", http.DefaultServeMux) // debug http.ListenAndServe("0.0.0.0:8080", mux) // app traffic } func serveApp() { mux := http.NewServeMux() mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) { fmt.Fprintln(resp, "Hello, QCon!") }) http.ListenAndServe("0.0.0.0:8080", mux) } func serveDebug() { http.ListenAndServe("127.0.0.1:8001", http.DefaultServeMux) } func main() { go serveDebug() serveApp() } serveApp serveDebug , main.main . , serveApp serveDebug .serveApp , main.main , .Council Go , , . : .
serveDebug -, - , . , , /debug . func serveApp() { mux := http.NewServeMux() mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) { fmt.Fprintln(resp, "Hello, QCon!") }) if err := http.ListenAndServe("0.0.0.0:8080", mux); err != nil { log.Fatal(err) } } func serveDebug() { if err := http.ListenAndServe("127.0.0.1:8001", http.DefaultServeMux); err != nil { log.Fatal(err) } } func main() { go serveDebug() go serveApp() select {} } serverApp serveDebug ListenAndServe log.Fatal . -, select{} .ListenAndServe nil , log.Fatal , HTTP .log.Fatal os.Exit , ; , - , . .Councillog.Fatalmain.maininit.
func serveApp() error { mux := http.NewServeMux() mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) { fmt.Fprintln(resp, "Hello, QCon!") }) return http.ListenAndServe("0.0.0.0:8080", mux) } func serveDebug() error { return http.ListenAndServe("127.0.0.1:8001", http.DefaultServeMux) } func main() { done := make(chan error, 2) go func() { done <- serveDebug() }() go func() { done <- serveApp() }() for i := 0; i < cap(done); i++ { if err := <-done; err != nil { fmt.Println("error: %v", err) } } } done , - .done , for range , -. -, .http.Server , . serve http.Handler , http.ListenAndServe , stop , Shutdown . func serve(addr string, handler http.Handler, stop <-chan struct{}) error { s := http.Server{ Addr: addr, Handler: handler, } go func() { <-stop // wait for stop signal s.Shutdown(context.Background()) }() return s.ListenAndServe() } func serveApp(stop <-chan struct{}) error { mux := http.NewServeMux() mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) { fmt.Fprintln(resp, "Hello, QCon!") }) return serve("0.0.0.0:8080", mux, stop) } func serveDebug(stop <-chan struct{}) error { return serve("127.0.0.1:8001", http.DefaultServeMux, stop) } func main() { done := make(chan error, 2) stop := make(chan struct{}) go func() { done <- serveDebug(stop) }() go func() { done <- serveApp(stop) }() var stopped bool for i := 0; i < cap(done); i++ { if err := <-done; err != nil { fmt.Println("error: %v", err) } if !stopped { stopped = true close(stop) } } } done stop , - http.Server . , - ListenAndServe . - , main.main .Council — . - , ó .
Source: https://habr.com/ru/post/441842/
All Articles