问题
I am in the process of getting to grips with the Golang way of doing things. I'd be much obliged to anyone who might be able to help with the following. First some sample code
package main
import (
"log"
"os"
)
func logIt(s string) {
f, _ := os.OpenFile("errors.log", os.O_RDWR|os.O_CREATE|os.O_APPEND,
0666)
defer f.Close()
log.SetOutput(f)
log.Println(s)
}
type iAm func(string)
func a(iam string) { logIt(iam + " A") }
func b(iam string) { logIt(iam + " B") }
func c(iam string) { logIt(iam + " C") }
var funcs = map[string]iAm{"A": a, "B": b, "C": c}
func main() {
funcs["A"]("Je suis")
funcs["B"]("Ich bin")
funcs["A"]("Yo soy")
funcs["D"]("Soy Yo")
}
Explanations
- I am channeling all my log out put to a file so I can monitor it later. Is this the right way to channel?
- I want to identify the right function to call at run time based on user inputs. To that end I have packed the functions as a Golang map - In PHP I would have used an associative array. This works. However, is this an efficient way to do things.
- Finally, you will note that I don't actually have a D key in my map. That last funcs call causes Go to throw a wobbly. In another language I would have wrapped those calls in a try... block and avoided the problem. From what I have understood the Go philosophy is to check the validity of the key first and panic rather than trying to blindly use that key. Is that correct?
I am a Go newbie so I probably have baggage from the other languages I use. To my mind dealing with exceptional conditions in a pre-emptive way (check the key prior to using it) is neither smart nor efficient. Right?
回答1:
Logging to file
I wouldn't open and close the file each time I want to log something. At startup I would just open it once and set it as output, and before the program exists, close it. And I wouldn't use a logIt()
function: just log using the functions of the log package, so you can do formatted logging e.g. with log.Printf() etc.
Dynamic function choosing
A function map is completely OK, and does well performance-wise. If you need something faster, you can do a switch
based on the function name, and call directly the target function in the case
branches.
Checking the existence of the key
The values in the map
are function values. The zero value of a function type is nil
and you can't call a nil
function, so you have to check the value before proceeding to call it. Note that if you index a map with a non-existing key, the zero-value of the value type is returned which is nil
in case of function type. So we can simply check if the value is nil
. There is also another comma-ok idiom, e.g. fv, ok := funcs[name]
where ok
will be a boolean value telling if the key was found in the map.
You can do it in one place though, you don't have to duplicate it in each call:
func call(name, iam string) {
if fv := funcs[name]; fv != nil {
fv(iam)
}
}
Note:
If you would choose to use a switch
, the default
branch would handle the invalid function name (and here you would not need the function map of course):
func call(name, iam string) error {
switch name {
case "A":
a(iam)
case "B":
b(iam)
case "C":
c(iam)
default:
return errors.New("Unknown function: " + name)
}
return nil
}
Error handling / reporting
In Go functions can have multiple return values, so in Go you propagate error by returning an error value, even if the function normally has other return value(s).
So the call()
function should have an error
return type to signal if the specified function cannot be found.
You may choose to return a new error
value created by e.g. the errors.New() function (so it can be dynamic) or you may choose to create a global variable and have a fixed error value like:
var ErrInvalidFunc = errors.New("Invalid function!")
The pros of this solution is that callers of the call()
function can compare the returned error
value to the value of the ErrInvalidFunc
global variable to know that this is the case and act accordingly, e.g.:
if err := call("foo", "bar"); err != nil {
if err == ErrInvalidFunc {
// "foo" is an invalid function name
} else {
// Some other error
}
}
So the complete revised program:
(Slightly compacted to avoid vertical scroll bars.)
package main
import ("errors"; "log"; "os")
type iAm func(string)
func a(iam string) { log.Println(iam + " A") }
func b(iam string) { log.Println(iam + " B") }
func c(iam string) { log.Println(iam + " C") }
var funcs = map[string]iAm{"A": a, "B": b, "C": c}
func main() {
f, err := os.OpenFile("errors.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
defer f.Close()
log.SetOutput(f)
call("A", "Je suis")
call("B", "Ich bin")
call("C", "Yo soy")
call("D", "Soy Yo")
}
func call(name, iam string) error {
if fv := funcs[name]; fv == nil {
return errors.New("Unknown funcion: " + name)
} else {
fv(iam)
return nil
}
}
来源:https://stackoverflow.com/questions/30890591/writing-good-golang-code