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 titleusers
insufficiently clearly describes the essence, thenusersMap
too.
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, theContext
type in thecontext
package will be calledcontext.Context
. This makes it impossible to use a variable or typecontext
in your package.
func WriteLog(context context.Context, message string)
This will not compile. That is why the local declaration ofcontext.Context
types, 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 usingCamelCase
instead 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
,j
andk
, 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 write
var min int max := 1000
More readable declaration:
min, max := 0, 1000
var
syntax.:=
.Council Explicitly point out complex things.
var length uint32 = 0x80
Here,length
can 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 thedowidth
functiondowidth
responsible 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 nameregistry
does 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
util
utils
helpers
, , . - , . , - .utils
helpers
, , , . , , .«[] , » —
Council . , strings
.
base
common
, . , , , , .net/http
client
server
, client.go
server.go
, transport.go
.Council , .
Get
net/http
http.Get
.- The type
Reader
of the packagestrings
when converted to other packages is converted tostrings.Reader
.- The interface
Error
from the package isnet
clearly related to network errors.
try
and 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.lastRead
and if the previous operation was not ReadRune
, an error is immediately returned. The rest of the function works on the assumption that b.lastRead
more 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 nil
should 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) }
len
both cap
are 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 []string
similar 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.
.go
into 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 http
should be in a file http.go
in a directory http
.messages.go
will contain types Request
and 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/http
net
..go
, , .
go
recognizes 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 go
sees 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/f
can import only a package from a directory tree .../a/b/c
, but not .../a/b/g
any other repository (seedocumentation ).main
and package main
should have minimal functionality, because it main.main
acts as a singleton: there can be only one function in the program main
, including tests.main.main
is a singleton, there are many restrictions on the called objects: they are called only during main.main
or 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") }
CopyFile
always 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 {
ListenAndServe
It takes two parameters: a TCP address to listen for incoming connections and http.Handler
to process an incoming HTTP request. Serve
allows the second parameter to be nil
. The comments indicate that usually the caller will actually pass nil
, which indicates a desire to use http.DefaultServeMux
as an implicit parameter.Serve
has 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)
Councilnil
nil
.
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
.Document
on 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.Fatal
main.main
init
.
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