10 Useful Go Technologies

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

10 Useful Go Technologies

Here is my best practice of summarizing the many go codes I have written over the last few years. I believe they are resilient. The elasticity here means:
An application needs to be adaptable to a flexible environment. You don't want to have to refactor all of them every 3-4 months. It should be easy to add new features. Many people participate in the development of the application, it should be understandable and easy to maintain. Many people use the app, and bugs should be easy to spot and quickly repaired. It took me a long time to learn these things. Some of them are tiny, but they can affect many things. All of this is just a suggestion, specific to the situation, and be sure to let me know if it helps. Feel free to leave a message:)

1. Use a single Gopath

Multiple Gopath cases are not resilient. The Gopath itself is highly self-sufficient (via the import path). Having multiple Gopath can cause some side effects, such as the use of different versions of a given library. You may have upgraded it somewhere, but there is no upgrade elsewhere. Furthermore, I have not encountered any cases where multiple gopath are required. So just use a single gopath, which will improve your Go development progress.

Many people disagree with this view, and then I will make some clarification. Large projects like ETCD or camlistore use tools like GODEP to save all dependencies to a directory. In other words, these projects themselves have a single gopath. They can only find the corresponding version in this directory. Do not use different gopath for each project unless your project is large and extremely important. If you think the project needs a gopath directory of its own, create it, or do not try to use more than one gopath. It will only slow down your progress.

2. Encapsulating the For-select into a function

If you need to exit from for-select under a certain condition, you need to use a label. For example:

Func Main () {L: for    {        Select {case        <-time. After (time. Second):            FMT. Println ("Hello")        default: Break            L        }    }    fmt. Println ("Ending")}

As you can see, you need to break use tags together. It has its uses, but I don't like it. The For loop in this example looks small, but usually they are larger, and break the conditions for judging are more verbose.

If you need to exit the loop, I'll encapsulate the for-select into the function:

Func Main () {    foo ()    FMT. Println ("ending")}func foo () {for    {        Select {case        <-time. After (time. Second):            FMT. Println ("Hello")        default:            return        }}    }

You can also return an error (or any other value) that is equally beautiful and only requires:

Block If err: = foo (); Err! = Nil {    //Handle ERR}

3. Use tagged syntax when initializing a struct

This is an example of a label-free syntax:

Type T struct {    Foo string    Bar int}func main () {    T: = t{"Example", 123}//No tag syntax    FMT. Printf ("t%+v\n", t)}

Then if you add a new field to the T struct, the code will fail to compile:

Type T struct {    Foo string    Bar int    qux string}func main () {    T: = t{"Example", 123}//cannot compile    fmt. Printf ("t%+v\n", t)}

If tag syntax is used, the Go compatibility rule (HTTP://GOLANG.ORG/DOC/GO1COMPAT) handles the code. For example net , to add a field called to a package type Zone , see: Http://golang.org/doc/go1.1#library. Back to our example, use the tag syntax:

Type T struct {    Foo string    Bar int    qux string}func main () {    T: = T{foo: "Example", qux:123}    fmt. Printf ("t%+v\n", t)}

It's not a problem to compile, and it's flexible. No matter how you add other fields to the T struct. Your code will always compile, and this will be ensured in later versions of Go. go vetall non-tagged syntax can be found as long as it is executed in the code set.

4. Splitting the initialization of a struct into multiple rows

If you have more than two fields, use more than one line. It makes your code easier to read, which means don't:

T{foo: "Example", Bar:somelongvariable, Qux:anotherlongvariable, B:forgettoaddthistoo}

But:

t{    Foo: "Example",    bar:somelongvariable,    qux:anotherlongvariable,    B:forgettoaddthistoo,}

This has many benefits, first it is easy to read, and secondly it makes it easy to initialize the Allow or masked fields (as long as you annotate or delete them), and finally add additional fields (just add one line).

5. Add a String () method to an integer constant

If you are using Iota to use a custom integer enumeration type, be sure to add the String () method to it. For example, like this:

Type State intconst (    Running state = Iota     Stopped    Rebooting    Terminated)

If you create a variable of this type and then output it, you get an integer (Http://play.golang.org/p/V5VVFB05HB):

Func Main () {state    : = Running    //print: ' State 0 '    FMT. PRINTLN ("state", state)}

Unless you look back at the constant definition, it 0 doesn't seem to make sense here. State String() You can fix this problem by simply adding a method to the type (http://play.golang.org/p/ewMKl6K302):

Func (S State) string () string {    switch s {case    Running:        return ' Running ' case    Stopped:        return ' Sto pped "Case    Rebooting:        return" Rebooting "Case    Terminated:        return" Terminated "    default:        return "Unknown"    }}

The new output is: state: Running . Obviously, it seems to be a lot more readable right now. This will bring more convenience when you debug the program. It is also possible to use the same approach when implementing Marshaljson (), Unmarshaljson ().

6. Let iota start from a +1 increments

In the previous example, there was also a bug that I had encountered many times. Suppose you have a new struct that has a State field:

Type T struct {    Name  string    Port  int State State    }

Now if you create a new variable based on T and then output it, you get strange results (http://play.golang.org/p/LPG2RF3y39):

Func Main () {    T: = t{name: "Example", port:6666}    //prints: "T {name:example port:6666 state:running}"    fmt. Printf ("t%+v\n", t)}

Did you see the bug? Statefield is not initialized, Go defaults to fill with the corresponding type of 0 value. Because State it's an integer, 0 is the value 0 , but in our case it says Running .

So how do you know that the state is initialized? Or is it really in the Running pattern? There is no way to differentiate them, so this creates an unknown, unpredictable bug. However, repairing this is easy, just let iota start from +1 (HTTP://PLAY.GOLANG.ORG/P/VYAQ-3OITV):

Const (    Running state = iota + 1    Stopped    Rebooting    Terminated)

Now the t variable will be output by default Unknown , isn't it? :

Func Main () {    T: = t{name: "Example", port:6666}    //output: "t {name:example port:6666 State:unknown}"    fmt. Printf ("t%+v\n", t)}

But getting iota to start with a zero value is also a workaround. For example, you can introduce a new state called Unknown , modify it to:

Const (    Unknown state = Iota     Running    Stopped    Rebooting    Terminated)

7. Returning function calls

I've seen a lot of code for example (Http://play.golang.org/p/8Rz1EJwFTZ):

Func Bar (string, error) {    V, err: = foo ()    if err! = Nil {        return "", Err    }    return V, nil}

However, you only need to:

Func bar () (string, error) {    return foo ()}

Easier and easier to read (unless, of course, you want to record some of the internal values).

8. Define slice, map, etc. as a custom type

Defining a slice or map as a custom type can make your code easier to maintain. Suppose you have a Server type and a function that returns a list of servers:

Type Server struct {    name String}func listservers () []server {    return []server{        {name: ' Server1 '},        { Name: "Server2"},        {name: "Foo1"},        {name: "Foo2"},}    }

Now assume that you need to get some specific names for the server. You need to make some changes to listservers () to increase the filter criteria:

Listservers returns the list of servers. Only the server that contains name is returned. The empty name will return all servers. Func listservers (name string) []server {    Servers: = []server{        {name: ' Server1 '},        {name: ' Server2 '},        { Name: "Foo1"},        {name: "Foo2"},    }    //Return all servers    if Name = = "" {        return servers    }    //Return filtered Results C11/>filtered: = Make ([]server, 0)    for _, Server: = Range Servers {        if strings. Contains (server. Name, name) {            filtered = append (filtered, server)}}    return filtered}

You can now use this to filter Foo the server with the string:

Func Main () {    Servers: = Listservers ("Foo")    //output: "Servers [{name:foo1} {Name:foo2}]"    FMT. Printf ("Servers%+v\n", servers)}

Obviously this function works correctly. But its elasticity is not good. What if you want to introduce other logic to the collection of servers? For example, check the status of all servers, create a database record for each server, filter with other fields, etc...

Now introduce a Servers new type called, and modify the original version of Listservers () to return this new type:

Type Servers []server//listservers returns the server list Func listservers () Servers {    return []server{        {Name: "Server1"},        {Name: "Server2"},        {Name: "Foo1"},        {name: "Foo2"},}    }

Now all you need to do is Servers add a new method to the type Filter() :

Filter returns the server that contains name. The empty name will return all servers. Func (S Servers) Filter (name string) Servers {    Filtered: = Make (Servers, 0)    for _, Server: = Range S {        if St Rings. Contains (server. Name, name) {            filtered = append (filtered, server)}}    return filtered}

You can now filter the server for strings Foo :

Func Main () {    Servers: = listservers ()    servers = Servers. Filter ("Foo")    FMT. Printf ("Servers%+v\n", servers)}

Ha! How easy is it to see your code? Also want to check the status of the server? Or add a database record for each server? No problem, add the following new method:

Func (S Servers) Check () func (S Servers) AddRecord () func (S Servers) Len () ...

9. withcontext Package function

Sometimes there is some duplication of effort for the function, such as lock/unlock, initializing a new local context, preparing initialization variables, etc... Here's an example:

Func foo () {    mu. Lock ()    defer mu. Unlock ()    //foo related work}func bar () {    mu. Lock ()    defer mu. Unlock ()    //bar related work}func Qux () {    mu. Lock ()    defer mu. Unlock ()    //Qux related work}

If you want to change something, you need to change everything. If it's a common task, it's a good idea to create a withContext function called. The input parameter of this function is another function that invokes it with the context provided by the caller:

Func Withlockcontext (fn func ()) {    mu. Lock    defer mu. Unlock ()    fn ()}

You only need to encapsulate the previous function with this:

Func foo () {    Withlockcontext (func () {        ///foo related work    })}func Bar () {    Withlockcontext (func () {        //bar Related work    })}func Qux () {    Withlockcontext (func () {        //Qux related Work    })}

Don't think about the lock-up situation. The best use case for this is the database link. Now make some minor changes to the Withcontext function:

Func Withdbcontext (FN func (db db) error) error {    //Get a database connection from the connection pool    dbconn: = newdb ()    return fn (dbconn)}

As you can see, it gets a connection, passes it to the supplied argument, and returns an error when the function is called. All you need to do is:

Func foo () {    Withdbcontext (func (db *db) error {        //foo related work    })}func Bar () {    Withdbcontext (func (db *db) ER ROR {        //bar related work    })}func Qux () {    Withdbcontext (func (db *db) error {        //Qux related Work    })}

Are you considering a different scenario, such as doing some pre-initialization? No problem, just add them to withDBContext it. This is equally effective for testing.

There is a flaw in this approach, which increases indentation and is more difficult to read. Again, always look for the simplest solution.

10. Add Setter,getters to access map

If you use the map to read and write data heavily, add getter and setter to it. With getter and setter you can put the logic seal into the function separately. The most common error here is concurrent access. If you have such a code in a Goroutein:

m["foo"] = Bar

And this one:

Delete (M, "foo")

What's going to happen? Most of you should already be very familiar with such a race. In short, this race is due to the fact that map is not thread-safe by default. But you can protect them with mutexes:

Mu. Lock () m["foo"] = "Bar" Mu. Unlock ()

And:

Mu. Lock () Delete (M, "foo") Mu. Unlock ()

Let's say you use this map somewhere else. You have to put the mutex all over the place! However, it is easy to avoid this problem by getter and setter functions:

Func Put (key, value string) {    mu. Lock ()    M[key] = value    mu. Unlock ()}func Delete (key string) {    mu. Lock ()    Delete (M, key)    Mu. Unlock ()}

The use of interfaces can make further improvements to this process. You can completely hide the implementation. Use only a simple, well-designed interface, and then let the users of the package use them:

Type Storage Interface {    Delete (key string)    Get (key String) string    Put (key, value string)}

This is just an example, but you should be able to appreciate it. It doesn't matter what the underlying implementation is used for. Not only is the use of the interface itself simple, but it also solves a large number of problems caused by exposing internal data structures.

But admittedly, sometimes it's too much to use interfaces just to lock up a number of variables at once. Understand your program and use these improvements when you need it.

Summarize

Abstraction is never an easy thing. Sometimes, the simplest is the method you have already implemented. You know, don't make your code look smart. Go is inherently a simple language, and in most cases there is only one way to do something. Simplicity is the source of power and why it is so resilient at the human level.

Use these cardinality, if necessary. For example []Server , it will be translated into Servers another abstraction, only if you have a reasonable reason to do so. However, there are some techniques, such as iota starting from 1 count is always useful. Remind again, keep it simple forever.

Special thanks to Cihangir Savas, Andrew Gerrand, Ben Johnson and Damian Gryski for their invaluable feedback and advice.


This article transferred from: http://studygolang.com/wr?u=http%3a%2f%2fmikespook.com%2f2014%2f07%2f%25e5%258d%2581%25e6%259d%25a1%25e6% 259c%2589%25e7%2594%25a8%25e7%259a%2584-go-%25e6%258a%2580%25e6%259c%25af%2f

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.