Moving from PHP to Go and learning to love them both

José Carlos Chávez
devthoughts
Published in
9 min readFeb 27, 2018
Irrelevant picture for the post just to discourage you to buy ivory

A few days ago I read this post and remembered mixed feeling and some “heated” discussions that I had with my colleagues about Go. At that time I was just starting out with Go after working primarily with PHP for the previous 5 years.

Following from a year working with both languages on a daily basis I am in a good position to share my personal experience in this journey. I believe explaining my initial thoughts and how they changed across time might help others in a similar situation (moving to Go or thinking about it) to get an insight into what you can face during the process and some advice on to make more informed decisions (in case you want to quit or keep going).

Go is a great language, so is PHP, but in my opinion it is not the most friendly host language albeit the massive documentation and tutorials all around the internet (playground is something every single programming language should officially offer and support). On top of that you will find a strong opinionated community, usually technically well educated. I have worked with PHP, Python and JS and I’ve never felt as much pressure to write idiomatic code as when working with Go (which is a good thing in my opinion) maybe because go was created with a style guide from the beginning whereas other languages built their guides on the fly.

The beginnings

It is impossible not to draw comparisons when moving from one language to another so my very first impressions about Go were not as tolerant as they are now:

  • Highly imperative: Even though you can write declarative code, by design Go is highly imperative.
  • Strong typing: Go is a strongly typed language and, while I did not like the fact that I had to explicitly state the type of the returned values every time, in the end I found it more convenient as I could catch problems before they arose.
  • Interface implementation is not explicit: I considered this a disadvantage at the beginning because I thought people could misuse one struct instead of another just because it fulfills the signature (which does not always reflect an intention to fulfill the contract). My experience now is that this is very unlikely to happen, because in a single purpose application there is not much room for confusion. It also bothered me that right after an interface changed you had to chase down all the structs that were implementing it in order to make them fulfill the contract again but this is probably something IDE related.
  • Capitalization for package visibility: Personally I dislike the inconsistency of some objects/functions having names starting with an uppercase letter and others with a lowercase one.
  • Struct annotations for marshalling/unmarshalling make it feel like the object is coupled to implementation details: I still think this is true.
  • No default values for function/methods’ arguments: This is true, but if your function requires a lot of optional parameters there is probably something wrong with it. Still, in situations where you really need to pass optional arguments, you can use zero valuecheck arguments, pointer arguments or variadic function arguments.
  • if err !=nil {...} everywhere: This seems odd at first but at some point you realize it is the same as doing try/catchfor exceptions. The main difference -in feeling- is that you repeat this check many times whereas with try/catch you can treat a big block and handle different exceptions in different ways, however this sometimes means you lose the context on which exception could be thrown by which method:
<?phptry {
functionA();
functionB();
} catch (ExceptionTypeC $e) {
...
} catch (ExceptionTypeD $e) {
...
}

If you really want it to understand where the errors have come from, you might need to do something like

<?php
try {
functionA();
} catch (ExceptionTypeC $e) {
...
}
try {
functionB();
} catch (ExceptionTypeD $e) {
...
}

Which is essentially

err := functionA()
if err != nil {
...
}
err = functionB()
if err != nil {
...
}
  • Array functions and array manipulation in general: Functions like array_map or array_filter are things I missed, but you can still do it as described here.
  • There’s no ternary (?:) operator: I really missed this as I find it more readable and I still don’t like the idiomatic suggestion of assigning the default value first and then overriding it later depending on conditions.
  • Versioning and vendoring: This is probably the thing I missed the most. Using external dependencies in Go means that you have to choose between including all the code used in a library and using the master of the repository. Both approaches seem inconvenient to me but between them I prefer the vendoring one. I don’t like the massive PRs including vendored files that are all over repositories in github using this approach though.

On the other hand, what I liked about Go from the very beginning was:

  • Go does not have inheritance (thank God for that): Instead you have to use some other techniques such as embedded objects which look more like composition to me. I have heard many times about how is inheritance one of the biggest benefits of OOP. In my opinion that is a very flawed claim.
  • Ability to return multiple values: I really missed this in C and PHP. Of course you can return an array or list of values in PHP but that looks very odd and not idiomatic. Also might require some type checking in runtime.
  • Strong typing: This is an area where PHP is improving; PHP7 includes return types and I am really thankful for this. PHP 7.1 even includes a nullable return type. Still, the check is in runtime not in compile time, and, although there are tools such as phpstan that help you overcome this limitation, it is really nice that in Go it is a built in feature of the language.
  • map/list with types: This is something I really missed in PHP — there is no way to declare a map[string]Something forcing you to do runtime checks for array elements.
  • Go compiler forces you to drop unused functions, variables and imports: At the start this was a pain (while hacking around with some examples), but in a space of a few days I really found it convenient as make the code simpler and clearer.
  • Circular dependencies are avoided:Even if the language allows you to do it I highly recommend avoid it.

Despite what I like about the Go language, I would like to mention a couple of things I did not like from what is described as idiomatic go:

  • I don’t agree with the one letter variable, I think over the past 40 years the software industry has agreed that developers spend much more time reading code than actually writing it, meaning that the code should be written thinking about readers. I don't want to have to remember that r most likely stands for Request and w for Writer.
  • Coupling is sometimes associated with idiomatic go, it’s easy to find examples in which business logic is strongly coupled with infrastructure or implementation details. Personally I try, even when writing Go, to decouple my code as much as possible with the help of DDD and CQRS, and, I don’t think Go was ever intended to be used in such a way, it’s totally doable. Good code principles apply when working with any language and Go should not be an exception.

My opinions about the Ivan Jaros’ post

I would really like to share my opinions about the post that spurred me on to create this blog post, bear in mind my opinions are based on my experience working with Go and PHP.

Go was born out of frustration with existing languages and environments for systems programming.

I can understand the pain. I am a Maths student, and for my studies I’ve been working with C for the past 10 months. I must confess that every time I finished something I told myself: “I could write this in half of the time with Go”. Even more I pair programmed with a colleague in a kata some months ago and we really missed Go’s simplicity and syntax over C.

In addition, something important to mention (thanks Marc) is that what Go understands as a system is not the same as what C understands as a system. Go includes features that makes it different from classical systems language, so we have to understand the system as a cloud system, made of microservices that communicate each other, so systems for the purposes of writing Go are one layer up from the OS.

Go’s simplicity works very well in your favour if you are creating a single purpose low-level library or application that is small in size and purpose.

Go has been designed for simple single purpose applications (aka microservices) and that is not a defect but a feature. I like the “monolith first” approach and I’d certainly not write a monolith using Go.

But another point I want to make is that in Go, you absolutely, under no circumstances, should be even trying to reuse code or define an interface. Sooner or later, it will bite you in the ass. […] For example, Go would be perfect language to use for a micro service whose purpose is to receive events from event store in event sourced architecture and normalize them into consumable messages for the message broker to distribute to interested 3rd parties. Now imagine a microservice where you have to connect to a database, fetch some results, perform some business logic and send some messages out.

At Typeform we use Go not only for low level tasks/tools but also for writing microservices using DDD and CQRS. Go has proven compatible with this and it also made me realize that sometimes we abuse of classes in order to make the so called Domain objects. In addition, one feature that I love in Go (and from C actually) is how easy is create custom types and associate them with some behaviour.

Something worth to mention here is how we understand the concept of microservice. I recommend this talk from Udi Dahan about what is and what is not a microservice.

When it comes to Go, spaghetti code is what you want to write. Go has composition instead of inheritance and polymorphism but you will be running in circles if you’ll try to create any re-usable code. You will fail badly.

It is entirely possible to write reusable code in Go you just have to consider that if it will be reusable it is better to ship it in a different package.But remember, as the author said before, Go is designed for single purpose applications, how often do you expect to reuse your code if that’s the case?

There is a reason why Go is lacking “real” frameworks. Sure, you have Go Kit, Micro, Gin and few others but they will never be able to come even close to Symfony, Laravel, RoR, Django or Flask.

You don’t need a framework for Go applications as they have a single purpose, it is not very likely you will find a Go application routing, dispatching events, serving assets and rendering html all at the same time. Microservices do very specific things and for doing specific things you don’t need a full-stack framework like Rails or Symfony. These kind of frameworks are meant to write monoliths and Go is not intended for that.

Conclusions

After some months working with Go, some of my dislikes for the language became my likes, so right now if I am asked about what do I prefer I would reply: It depends.

  • I really believe in the monolith first approach and if I were to write a monolith I would not write it in Go because it is not meant for this.
  • I would be perfectly happy to write a microservice in PHP unless the implementation required certain features like high availability, concurrency, async tasks, multithreading, etc. If that was the case I would definitely use Go, not because these things are not possible in PHP (although concurrency is definitely not possible in PHP SAPI), but because it is more natural in Go, because of the ease of sharing data across channels (I recently saw this talk where @adrianco shows how to send channels over channels) over the complicated restrictive model of pthreads.

--

--

José Carlos Chávez
devthoughts

Software Engineer @Traceableai, ex @ExpediaGroup. Wine lover and Llama ambassador