Best practices for ElePHPants coding in Go
Thanks to Guido da Rozza for the base image CC BY 2.0
I’ve often coded in PHP. Recently, I realized that when I code in Go, it’s easier to spot my PHPisms. After weeks of cleaning them up, it only takes a day to fry my brain with them again. I’m sure I’m not the only developer doing this, so I started collecting tips and tricks to help with the process. I’m sure other ElePHPants have their own tips and tricks, but here are a few of mine.
You don’t always need an additional memory allocation.
PHP’s API is formatted into functions that return values. Want to parse JSON? Just read a string into a variable and json_decode
it.
I know - there are exceptions like fputcsv
and fgetcsv
. Still, you can anticipate that you pass a variable as input and get some kind of output. Variables, as opposed to resources, must be noted here.
Conversely, Go allows for variables as much as streams. You’re able to json.Marshal
or json.(*Encoder).Encode
an io.Reader
.
In Go, you have to determine what to do and the ramifications of your actions. Will it be better to process loading/unloading from memory (json.Marshal
) or just iteration through the io.Reader
(json.Encoder
)?
Associated Arrays: PHP vs Structs in Go
PHP requires an anonymous structure defaulting to use an associative array() which can accept data organized in any way. The downside is you cannot tell where the array ends and the associative starts; and you can’t inspect its structure.
Go offers maps, which are impressive because any comparable value can serve as a map key. This entices you to use map[string]interface{}
as a container of data with mixed types. It works, yes... but it can be done better. Anonymous structs are available, and they replace interfaces with greater readability.
Thus this in PHP:
$data = [ "name" => "Carl Sagan", "book" => "Cosmos", ];
Does not translate as:
var data = map[string]string{ "name": "Carl Sagan", "book": "Cosmos", }
But as:
var data = struct{ name string book string }{"Carl Sagan", "Cosmos"}
Think carefully when you choose data structures
PHP has scalar constants, scalar variables and arrays. PHP 7 introduced constant arrays. Generally, you don’t set the size of arrays. (There are SPL data structure, but they aren’t the subject here.) This means that in PHP we sometimes ignore the size of structures or forget declare immutable local variables as constant.
I follow these rules:
- Types: if I can’t use a struct (
type T struct{}
), then I use a fixed size array (var x = [...]T
). If I can’t use a fixed size array, then I use a slice (var x = []T
). If I can’t use a slice, then use a map (var x = map[T]T
).
- Variables: if possible I use constants (
const name = "Carl Sagan"
). If I can’t use constants, then I declare variables with concrete type (name := "Carl Sagan"
). If I can’t declare them with concrete type, then assign an interface to them. If nothing fits, only then I would assign them an empty interface.
Don’t ignore errors
This is second nature for most Gophers, but not ElePHPants.
PHP’s API often mirrors the base the of C implementation: it’s not homogeneous because different extensions handle errors differently. PHP satisfies language needs, but lacks a uniform way of exposing errors. This can result in an empty response caused by an error that lacks sufficient information to make a decision about what the code should do.
Go can be handled more aggressively, it’s a standard in the language: all methods that can have an error will return it.
If this is acceptable in PHP:
$json = json_decode('{malformed)Json');
This is not cool in Go:
json.Unmarshal([]byte("{malformed)Json"), &data)
The way to do this in PHP ‘correctly’:
$json = json_decode('{malformed)Json'); $err = json_last_error(); if ($err != JSON_ERROR_NONE) { echo json_last_error_msg(); }
Whereas in Go:
if err := json.Unmarshal([]byte("{malformed)Json"), &data); err != nil { // do something with err }
ElePHPants and Gophers: A way of thinking
If you’d like to read something that pairs well with this post, try Thinking, Fast and Slow by Daniel Kahneman.
In short, he says there are two ways the mind functions: the Fast Think (emotions, frequent, stereotypic and subconscious), and the Slow Think (calculating, conscious, effortful, infrequent and logical).
Since PHP is permissive, things (mostly) work out when you code with the Fast Think system; you feel that you accomplished things quickly. Your Fast Think system is happy and your Slow Think system is hibernating unless you find a mistake made by the former (debugging).
Go, however, allows your Fast Think system to translate what your Slow Think system creates. In Go, you will have to use the Slow Think system, even if you’re coding with the Fast Think system.
Beginner ElePHPants tend to code until error messages are gone. It’s perfectly possible, and not all that uncommon, to see unused imports and variables. Beginner Gophers usually try to make things work. Tooling like go fmt
forces newbies to really think about making their code correct and tight. This is paramount in the later evolution of the programmer.
There’s a lot of theoretical understanding needed to code in Go: data types, interfaces, streams, memory allocation and GC pressure, composition over inheritance, etc. ElePHPants can simply “start coding” while pondering deeper aspects of the application later.
You may be overwhelmed with all of this at first; there’s bound to be concepts and ideas that you’ve never heard before. The best thing to remember is you’re not alone, someone out there is trying to figure it all out too.
Carlos started programming in PHP back in 2001, while helping a radio group build a site for young listeners. He made the jump to Go in 2011 when he found some PHP projects struggling to standup to scale. He now works on infrastructure, and codes on the regular at Iron.io.