DEV Community

Cover image for It's Been Three Months Since My First Go LoC πŸ€“ πŸŽ“
Boris Jamot ✊ /
Boris Jamot ✊ /

Posted on • Updated on • Originally published at mamyn0va.github.io

It's Been Three Months Since My First Go LoC πŸ€“ πŸŽ“

It's been 3 months since I wrote my first Line of Code in Golang.

I'm still excited about learning this new language but it seems I faced my first issues with Go. I'll talk about it later.

I introduced one of my previous post by saying that PHP was my main language and that I won't switch definitively to Go. Maybe this could change...

I really enjoy strong typing and compilation. I can have real-time feedback on my code without running it and without additional 3rd-party tools. The Go SDK is fully featured for linting, formatting, testing, compiling, running.

Reading from request's body

In my previous post, I was also saying that Go was a language for craftsmen. But this was far from the truth. I'll take an example: in Go, if you want to read the HTTP request's body in a middleware to analyze it for logging purpose, it becomes tricky:

func LoggingMiddleware(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // first, read the body and put it in a variable
        body, err := ioutil.ReadAll(r.Body)
        if err != nil {
            log.Printf("Error reading body: %v", err)
            http.Error(w, "can't read body", http.StatusBadRequest)
            return
        }

        log.Printf("incoming request's body: %s", body)

        // And now, re-assign the request's body with the original data
        r.Body = ioutil.NopCloser(bytes.NewBuffer(body))

        // finally, call next handler
        handler.ServeHTTP(mrw, r)
    })
}
Enter fullscreen mode Exit fullscreen mode

As you may guess, reading the request's body makes it empty. That's why you have to re-assign it afterwards. That's because request's body is a kind of stream you can read byte after byte. It can be useful if you want to start the request's processing without having to read all the data. But in most cases, you don't need that.

(un)-marshalling

(un)marshalling is another thing that is not straightforward. What you need to know is that unmarshalling from JSON, BSON, YAML or whatever to a struct will almost never fail.

package main

import "encoding/json"
import "fmt"

type User struct {
    Name string `json:"name"`
    Age int `json:"age"`
}

func main() {
    var bob, alice User
    json.Unmarshal([]byte(`{"name":"bob","age":25}`), &bob)
    err := json.Unmarshal([]byte(`{"name":"alice","address":"NY"}`), &alice)

    fmt.Printf("err: %v\n", err)
    fmt.Printf("bob: %v\n", bob)
    fmt.Printf("alice: %v\n", alice)
}

Enter fullscreen mode Exit fullscreen mode

Output:

err: <nil>
bob: {bob 25}
alice: {alice 0}
Enter fullscreen mode Exit fullscreen mode

In this example, I omitted the age attribute in the JSON of Alice, but Go unmarshals it with no error. Confusing? That's because Go allocates default values for all missing attributes. In fact, you just have to provide valid JSON.

Note that supplementary JSON attributes are just ignored.

What really confused me was that I expected unmarshalling to fail with missing attributes. But if you want to check what you really got in the JSON, you can't trust the unmarshalling, you have to do it by yourself by checking all the fields of your struct manually. If one of the field has the default value for its type (e.g. 0 for integers, or empty string), then it could mean that it was missing from the JSON. But it could also mean that the client sent {"name":"alice","age":0}. Then, how to distinguish between missing attributes and attributes with default values? You could unmarshal to a map[string]interface{} and check manually that no key is missing, but it would be so WTF?!

If someone knows a better solution, don't hesitate!

Unit testing

Writing unit tests is the worst part of my developer journey in Golang. As an experimented OOP developer in PHP and Java, I used to write a lot of unit tests to have the best code coverage I can reach. In order to achieve it, I design my code with a high usage of dependency injection to allow me to replace dependencies by mocks in unit tests.

With Go, there is no equivalent of PHPUnit or JUnit that allows to quickly mock any dependency. Mocking in Go is based on interfaces. If you're using a struct as a dependency somewhere in your code, you have to write a custom interface for it. That's where Go lacks of consistency. Depending on the library you're using, if you're lucky, it will provide an interface and not a struct (that's the case for gin-gonic & uber-go/zap). But otherwise, you may encounter the mocking hell (that's the case with mgo mongo driver).

The drawback of writing interfaces for dependency injection is that you will lose the completion in your IDE. Let me explain it: when you write an interface for a 3rd-party library, you just put the functions you need in it. It means that if some day you want to use another function of that library, you don't even know what are the available functions. You can't hit ctrl+Space on the object to auto-complete because it will only display the functions of your custom interface, not the one of the original library object. So you have to browse the official doc on internet.

Knowing that, I would recommend to prefer "mocking-ready" libraries. It will save your time!

Mocking your dependencies

Depending on your testing strategy, you may have to mock some parts of your code, or some external library. As I said earlier, the first thing is to use interfaces instead of structs in your code, wether it be in the prototypes of your functions or in a Dependency Injection Container.

The principle is really simple: you just have to switch the real object with a struct implementing the interface you want to mock. You can do it manually, or you can do it with gomock if you want a fully featured mock, allowing you to count the number of calls, to check the types or even to capture the arguments. The only drawback of gomock is that you have to generate the mocks manually, and to update them each time you modify the interface. You also have to commit the mocks along with your "real" code.

My custom development environment

In my first post about Go, I was saying that I tried many IDE: Atom, Intellij Ultimate and Vim. After that, I also tried Visual Studio Code which was pretty nice, but none of them fully statisfied me. So, as many of my colleagues use Intellij, I gave it a new try and finally succeeded to make it work. I use it to write code, but also to debug and to browse the code.

Apart from Intellij, I also have a customized terminal with 3 panes using tmux. One of the panes is for playing with git or to type any other command. One is dedicated to vim. The last one is to run my app, check my conf and run the tests, all of this being possible with modd, a file watcher:

  • any modification of the codebase triggers a new run of my app (go run ./...),
  • any modification of a test file triggers the tests (go test ./...)
  • any modification of my conf files triggers a new check using pykwalify which is a JSON/YAML validator. The 3rd pane is a Vim allowing me to quickly edit files.

Conclusion

What I miss the most as a PHP developer is the community and the online resources. It seems like the official Go doc (which is great) is quite the only available resource, except posts on StackOverflow.
What is also confusing is that if you google something like "go + stuff", you won't get many results. You'll have more luck with "golang + stuff".

Anyway, these obstacles didn't hindered my motivation and my enthusiasm. I love the simplicity of the language.

I'll keep you informed of my progress in a future post.

Thanks for reading!

Top comments (15)

Collapse
 
rhymes profile image
rhymes

Hi Boris, happy you're liking Go so far!

(un)-marshalling
If someone knows a better solution, don't hesitate!

I think the best solution is validation. I used govalidator successfully in a project.

You can do stuff like:

type Ticket struct {
  Id        int64     `json:"id"`
  FirstName string    `json:"firstname" valid:"required"`
}

Writing unit tests is the worst part of my developer journey in Golang

It took me a while to get testing right in my first project but Go has really powerful testing chops. I'm starting to like test tables and you can easily debug test cases from VSCode.

With Go, there is no equivalent of PHPUnit or JUnit that allows to quickly mock any dependency.

testify seems nice, haven't tried it yet

A couple of posts about unit testing in Go that I read:

It seems like the official Go doc (which is great) is quite the only available resource, except posts on StackOverflow.

There are resources around, you just have to find them. A good place to start are the newsletters Golang Weekly and Master the World of Golang.

What is also confusing is that if you google something like "go + ", you won't get many results. You'll have more luck with "golang + ".

Ah ah yes. Naming it Go was a bad move, it's too late for that

Have fun!

Collapse
 
gbafana25 profile image
Gareth M.

They should've just called it Gopher, like GopherCon right?

Collapse
 
biros profile image
Boris Jamot ✊ /

I will have a look at govalidator.
I already use testify for assertions, but it's not designed for mocking.

Thanks for your support, I'll have a look at your resources!

Collapse
 
rhymes profile image
rhymes

What about this part of testify's documentation? github.com/stretchr/testify#mock-p...

It doesn't work?

Thread Thread
 
biros profile image
Boris Jamot ✊ /

I didn't know about mocking with testify. It seems to be working similarly to gomock.
The problem is that the mocks have to be generated manually and to be maintained along with your codebase.

Thread Thread
 
rhymes profile image
rhymes

Yeah, fortunately I don't use mocking that much

Collapse
 
damnjan profile image
Damnjan Jovanovic • Edited

This is the best article for Go I read because it comes from the person who is native in PHP. Awesome work Boris!
I appreciate your focus on tests; it helps me understand mocking in Go. Please continue to write about your from PHP to GO transition. I’m really curious about that.

Collapse
 
biros profile image
Boris Jamot ✊ /

Thank you so much, I'm very happy that you found it useful!

Collapse
 
damnjan profile image
Damnjan Jovanovic

It is beneficial to me, since I am also thinking to start learning Go. Not necessary to be my primary language, I first want to understand the concept.

Collapse
 
ladydascalie profile image
Benjamin Cable

how to distinguish between missing attributes and attributes with default values?

Use pointers!

Imagine the following:

type Person struct {
    Name *string
    Age  int
}

then try to unmarshal this: { "Age": 30 }

You will get this:

fmt.Printf("%#v\n", p)

> main.Person{Name:(*string)(nil), Age:30}

if a key is missing, it's a simple nil check away.

Another nice technique I like to use is as follows:

Declare an ok interface:

type ok interface {
    OK() error
}

Implement it on your type:

func (p *Person) OK() error {
    if p.Name == nil {
        return errors.New("missing field: name")
    }
    return nil
}

Then simply:

p.OK()

> 2009/11/10 23:00:00 missing field: name

You can see the full example in the go playground:

play.golang.org/p/P_88niVqOke

Collapse
 
jeromelaforge profile image
JΓ©rΓ΄me Laforge

For me, the reading the body of request (or response in case of HTTP client) is not a big deal. You can easily define helper function that read all Body, create a new io.Reader then return read Body as bytes array.

For me (and also for Go maintener net/http Brad Fitzpatrick), HTTP client has to be improved (c.f. github.com/bradfitz/exp-httpclient) and will be.

Collapse
 
biros profile image
Boris Jamot ✊ /

Hi JΓ©rΓ΄me, it's fun to see you here on dev.to!
What's the weather like in caen?
What you suggest with creating an io.Reader proves what I said above : many trivial things in popular languages can become complicated in Go.

Collapse
 
jeromelaforge profile image
JΓ©rΓ΄me Laforge • Edited

What's the weather like in caen?

cloudy ...

What you suggest with creating an io.Reader proves what I said above : many trivial things in popular languages can become complicated in Go.

Maybe, but many complicated things in popular languages become trivial in Go ;) (Concurency, very good performance on tiny configuration, async task without tuning your thread pool, deploy on "From Scratch" docker's image and so on).

Collapse
 
dannypsnl profile image
ζž—ε­η―†

You can use pointer so missing value would be nil

Collapse
 
biros profile image
Boris Jamot ✊ /

It's a good idea, thanks!