Golang 101 Series
Presented by: Matt Boyle
Originally aired on December 6, 2021 @ 8:30 AM - 9:00 AM EST
An introduction to the Golang programming language.
English
Programming
Transcript (Beta)
So good morning from London. My name is Matt Boyle and I'm a Systems Engineer here at Cloudflare.
So today I'm going to run you through the basics of Go and hopefully add some clarity to some of the topics I found challenging whilst learning.
As I only have 30 minutes I'm not going to be able to cover everything, so please consider this session supplemental to anything else you may be learning while looking at Go.
This session is for anyone who's interested in learning a little bit more about Go, the programming language.
Ideally you have a little bit of experience, but if you don't, don't worry about it.
Some of the topics we cover might just require a tiny bit more reading.
One thing I wanted to cover while looking at this slide is Go versus Golang.
You'll see these used pretty interchangeably and the main reason for this is just due to the searchability of the word Go.
So you'll hear people use Go and Golang interchangeably, don't worry about which one you use, but the programming language is called Go, hence the Go language.
So here's some stuff we're going to cover today.
So first I'm going to talk you through some reasons to select Go, the programming language, and why you might want to learn and adopt it.
We'll move quickly on to writing a really basic program and we'll talk about exactly what each line does in that program too.
After that we're going to talk about building some more complex programs, how to structure them and some strategies for how to structure them.
We're going to talk about interfaces, which is one of the hardest things about learning Go, I think, because at least to me they were very different to how any other language I'd ever done had done them before.
We're going to take a quick look at testing and TDD.
If you don't know what TDD is, we're going to look into that too, so don't worry about it.
If you have any questions at any time, you can send them across to livestudio at Cloudflare.tv and they'll make their way to me or feel free to send your feedback too, I'd love to hear it.
If there's any time at the end, I've also prepared some bonus content on what's coming next with the Go programming language, which is pretty interesting.
Let's get to it. Why Go? Let's start with that.
This is from golang.org, which is the official site for the Go programming language.
They claim that it's simple, reliable and efficient. Let's dig into those a little bit more and just talk about why they're each of those things.
The reason Go is super simple is it's got a really low barrier to entry.
You don't need to have a bunch of extra libraries or have a bunch of other software installed on your system to write good Go software.
The standard library has everything you need to perform most jobs and even comes with a web server.
Furthermore, it compiles down to a single binary that executes, so it's really simple to get things running and we'll see that in a second.
So let's talk about reliability as well.
So Go is memory safe by default. You don't need to worry about allocating or cleaning up memory, it'll do this for you.
There's tons of other languages that do this as well and give you the same guarantee, but Go has some pretty great performance characteristics, which makes it a great choice, which leads us on nicely to its efficiency.
Although very little can touch C or C++ in terms of efficiency, Go often gets compared to these languages.
Go is really fast compared to the high programming languages and it gives C++ a run for its money in most benchmarks.
It's a little slower, but the complexity it abstracts away from you is sometimes a worthwhile trade-off, but you're going to have to make this call yourself when you're thinking about your use case.
These aren't on golang.org and these are just my views, but here's some other reasons that I think Go is a great choice for you.
So it gives great developer productivity. There's a joke that Go was created whilst waiting for C++ to compile.
Go compiles in seconds and it's super easy to do.
Google has this concept of readability in-house, which I take very seriously, with people having to be certified as readers.
They took this one step further with Go and it won't compile if it's not formatted correctly or if you have an unused import.
This actually makes it really productive to write because if I pick up someone else's software, it looks at least somewhat similar to mine and I can follow it.
They provide a tool called Go Format for this. Another great productivity feature is it comes with some great testing frameworks built into it, which makes TDD very easily, but we'll cover that off shortly.
Add the learning curve.
So when I joined my previous company, I'd never written any Go and by the end of week one, I was deploying small changes into production.
If you look around the web, you'll find multiple reports of people achieving similar feats.
If you've ever wrote Java, C++ or C, you'll find learning Go very, very easy.
It also kind of promotes clean code and being able to read it from top to bottom without many layers of abstractions, which makes following other people's code very, very nice.
And the final one is fun. It might sound a little silly, but I genuinely enjoy writing it and would find it really difficult to quantify why.
I've wrote PHP and Java in the past, as well as JavaScript, but Go's the only language I've found myself looking for reasons to pick up personal projects to learn concepts or writing small toy programs.
So definitely check it out.
And let's move on to writing our very first program. So this is going to be super basic, but we're going to move on very quickly to some more complex things.
But we're going to step through every single line I've written here and explain what it does.
And with that, you actually have enough context to write some pretty, pretty useful Go programs.
So Hello World is a conventional way to introduce any language.
So we're going to invite that too. So this will be the entire program for Hello World.
So let's talk through So package main, what does this mean? So in Go, packages are nothing more than a directory with some code files.
They expose different features and allow developers to organize their code into logical groups.
They also allow you to introduce a level of privacy to code. In Go, functions and variables that start with a lowercase letter cannot be accessed from outside the package, but uppercase ones can.
So in this instance, the function main you see here is lowercase.
So I won't be able to access that from a different package.
So this is similar to other programming languages such as Java where you have private variables.
Main is a really special package in Go and every runnable piece of software must have a main package with a main function in it.
This just tells the compiler where to look to kick off the start of a program.
So let's move on to the next line, import FMT.
So like most of the languages, Go provides a standard library to help you achieve some things and allows you to import third party code that others have written to help you achieve your goals.
In this example here, I'm importing FMT from a standard library.
You can use as many imports as you like. Go provides a really powerful standard library and due to this, most users try to avoid third party packages where possible.
You can see from the FMT package we're using the println function and you can see it's got a capital letter, which is why I'm able to access it.
So this is it. We've actually written our first program. So let's have a look at how we run it.
And with this simple one line, this will run this program.
So go and run main.go and if I run this, you'll see the output, hello world.
So it's quite a contrived example, but hopefully you can see how you can start with like a very, very small block of code and quickly have something that's runnable and potentially usable.
So we've seen a really basic example now of how we could build a really simple program with one function.
So let's talk about how we might do something with multiple packages and why we might care about using multiple packages.
So we're going to actually walk through some code I've written to do this.
So when projects reach a certain size, we may want to start splitting it down into smaller sections or packages as I call it in Go and this has tons of benefits.
So it allows for a better separation of concerns.
We can make our code more readable and maintainable. It's easier to test.
There's going to be less merge conflicts when working with other people in our team and we can follow more established patterns from other languages.
So I've actually wrote some code to demonstrate this. So let's take a look.
So I've wrote a really basic piece of software here that is what I've called a configuration checker.
And so what it actually does is it checks that any website I provide to it has a registered DNS server and also checks that I can get an IP address for it.
As you can imagine, this isn't really what you would do to check a website is configured, but it does enough to demonstrate the concept.
So if you look over onto the left hand side of my screen here, you can see I've split my code into different packages or folders.
The one I'd like to call out here is internal.
Internal is a really special folder in Go in that anything inside this folder can't be exported.
So if anyone tries to import any of this code into another project, they won't be able to do so because I put it inside this internal folder.
In my main function here, you can see I'm importing from another package that I've written.
So let's take a look at how I've done that. So you can see I've made two packages, one called DNS and one called IP.
If I click into this, you can see some code I've written.
Let me just make that a little bit bigger. So there's a couple of things I want to call out here.
You'll notice that you'll see a lot of this in Go.
This do something, error, and if the error is not nil, return something in the error.
If this isn't right, return the error. Otherwise, return the string.
This is a really common pattern. Errors are a first -class citizen in Go, and you'll see this if error equals nil kind of thing quite a lot, and it's a really nice pattern.
It allows us to short-circuit code, and we never do too much.
As soon as we've done the benefit of what we're going to do, if it doesn't work, then we'll return and carry on.
So in this IP function here, you can see I just look up the IP address for the address I pass in and return it, and it's in its own package.
I've done exactly the same for DNS. Actually, the code is pretty similar.
You can see that I pass an address. I look up the name server for it. If there's any errors, I return those.
Otherwise, I return the string, and then in my actual what I call my service, which is effectively my business logic that brings all these things together, you can see that I effectively get the DNS, get the IP, and if either of those are empty, I return...
If these are not empty, sorry, I return true.
I'm saying that this website is configured. Otherwise, I return false.
And then finally, in my main.go, you can see that I can bring all those things together and go configuration checker.
Is it configured? If it's not, I can say fail check.
Otherwise, it is, and I can run this very quickly just to show. And there we go.
You can see with what is not very much code at all separated into packages to make it maintainable.
I've been able to run a piece of software that says, is Cloudflare.com configured?
And according to my very naive program, it is. So that's good news.
So here's some things we learned going through that.
So we talked about the if errors not equal nil.
It's a pattern you're going to see a lot, so still get comfortable with it.
We learned about internal being a special package, and we talked about not being able to import lowercase functions into a different package.
So we might want to quickly just revisit that one. So you can see this is a configured function, and I'm accessing it from the package.
So if I jump in here and make this a lowercase letter, is it configured?
Same function name, and go back to main.go.
You can see I've got an error now, even if I put this as lowercase, because in a different package, I'm allowed to access it.
And goland, which is the IDE I use, is actually telling me that you can't use this.
It's not exported.
So naming things in Go is really important, and you have to be really careful with things like that.
Cool. So now we're going to talk about interfaces.
So interfaces are one of the more complex things that I struggle to learn when learning Go, and this is due to the implicit nature of interfaces.
So let's talk a little bit about what that means.
So for any Java programmers out there, the top will look very familiar to you, or maybe C sharp, stuff like that.
But this is just a great example of the difference between implicit and explicit interfaces.
So you can see in the top section that if I define any class in Java, I have to explicitly say that it implements an interface, and I can see that by looking at the code.
This isn't the same in Go. In Go, I can define an interface, and just by the nature of having that function that's defined in the interface, it means that I implement it.
This means it's sometimes quite hard to figure out exactly what interface you're satisfying.
So interfaces, for those who don't know, are used to help model our code and to achieve loose coupling.
It makes code more maintainable, ready for change, and testable.
And I've got another example that I'd like to show to hopefully kind of make that concept a little clearer.
So let's just check out some code.
Give me one moment, sorry. Okay, so we've got something a little bit different now.
Let's imagine that I'm building some software that needs to capture payment.
And to do that, there's two companies that I could potentially use to capture payment.
So there's one called CashFriend, and there's one called PayHouse.
These are both things that will allow me to get money off people's cards.
My company right now is using PayHouse to capture payments from people, because we got a really good deal with them.
But it turns out that CashFriend is just offered as a much, much cheaper deal.
So we want to swap to them, because it's going to save us a bunch of money.
So here I've got some code I wrote, which allows me to pay for a product.
And how this works is it takes a product ID, it takes a number, and just returns the amounts that it's going to cost.
I need to charge the customer, basically. So if you put in product ID one, it's going to charge me 30, product ID two, it's going to get 50, and then everything else costs 3,000.
So make sure you buy product one or two, I would say. And then what I've done is I've declared this interface called a payer.
And I'm saying anything that has a pay function that takes an int and returns an error is a payer.
And then you can see I'm using this payer and this function here to capture the money.
So then what I can do is, this is called abstraction. And then you can see in my main.go here that I've got some code here that starts a pay service, and then I pay for the product.
I put in product ID 30. And if there's no error, then the program's been successful, and I've captured the money off the user.
So again, very contrived, but you could see how you could make this into a bigger, more functional program.
So right now I'm doing pay house. And because I've used interfaces as well, and I've abstracted all the logic into separate files and packages, all I need to do is swap this to payment.cashfriend.
And I'm now using cashfriend instead of pay house.
So you can see that I've changed one line, and I've actually completely changed my provider.
So this is really, really powerful and is a really commonly used concept in Go and many other languages too.
One thing worth pulling out is if you look at my sort of pay house function package, it's not very clear that this implements that payer interface.
Thankfully, Golang gives me some help, and I can see here that the interface satisfies it.
But this is not easy to see without the help of an IDE. So the last thing I want to touch on is the kind of choice of implicit interfaces.
So the Go team picked it because they claimed it allows more rapid development.
A lot of the decisions they made were around supporting the fact that you can develop faster if you use Go.
I'm not sure this is true personally, and I've seen some anti-patterns such as implementing interfaces of third party package and depending on it.
It can also be pretty hard sometimes to figure out what interface is satisfying, as you saw in my example.
But thankfully, tools can help us there.
But I'll leave that up to you to figure out what you think on the matter. So let's move on to another topic.
Let's talk about testing and TDD. Bear with me one moment.
So Go comes with some really powerful testing tools, which really help with productivity.
It has a much wider suite than most languages, includes a powerful testing framework.
You can do things like benchmark testing, race detection to make sure you're not accessing a shared variable in a non -thread-safe manner.
And it also has code coverage tool. And the thing I personally like is it enables TDD.
For those not familiar, TDD stands for test-driven development.
As a software development methodology, we have rapid increments, all starting with writing the test and writing a failing test at that.
So I'm going to show you how to do this with a really simple example.
So let's take a look.
So let me check out some more code. Okay, cool.
So let's stay in the payment domain. And let's imagine that we're working for a company that gives us the following ticket.
Let's imagine we've got this Jira ticket.
So as a customer, I want to be able to be told how much a product costs, but I know I can afford it.
And this is our kind of acceptance criteria. If you have product one, it should cost 30.
Product two should cost 50, and product three should cost 60.
If we don't know the product, a useful error should be returned.
So let's write this code in a TDD fashion using Go. So the first thing I'm going to do is going to make a new test file called payUnscoredTest.
This is pretty much a convention for Go.
And I'm going to put it in a new package as well called paymentUnscoredTest.
The reason I've done this is by putting it in a different package, I'm going to be doing what's called black box testing.
I'm going to be testing the function as a consumer from the outside.
This means I don't have access to the implementation details that I might do if I'm in the same package.
And this is a pretty good way to make sure you're testing the behavior rather than the implementation.
So the first thing I want to do is I'm going to write a test that product one should cost 30.
So I'm going to copy and paste that. And we look at our TDD cycle.
The first thing I need to do is write a failing test. So let's do that.
So Go should help me here. So you can see that I can split things like this, pay for product.
And then what I tend to do is write sub tests like this. The product one should cost 30.
So the reason I like writing tests like this is my tests have basically become documentation already.
Like for someone in the future who comes along and wants to look at what my intention was for this piece of code, they can see really quickly just by looking at my tests.
So, so our code is called pay for product.
So, so I'm going to go res. Error equals a payment, pay for products, and we're going to pay for product one.
And we go if error equals nil, t.fail.
So I'm going to fail the test if I get an error, because I don't expect to get one.
And if res does not equal to 30, t.fail. So as you can see, it follows the same sort of short circuit pattern that we did for other code without error equals nil pattern in that if I don't get a result I expect, I fail the test immediately.
And if I get to the bottom of the test I passed, you'll notice as well, I didn't use any third party things here.
This is all standard library.
So if I run this, it's probably going to fail. And it does, and that's what we expected.
So we've completed this, write a failing test. The second piece seems a little strange, how we're going to approach it, but it says make the test pass.
So let's, let's do that. So to make this test pass, all I have to do is return 30.
So let's do that. Hmm.
So I actually can't figure out why this is failing, so I'm actually going to do some debugging.
So you can, you can watch me do that. So what I'm going to do here is I'm going to set a breakpoint.
And now I can step into it.
And you'll see here that Golang is helping me with this. And you can see here that the result is 30.
So it's strange that it fails. Interesting.
So I'm just going to remove this one for now for the sake of, of showing it working.
But then you can see that if I run this test, yeah, something, something's not going quite right here.
Product one should cost 30.
Name of product, sorry.
Not sure.
I'll have to come back to this one. Let's keep moving for the sake of, for the sake of time.
But that's how you're meant to do TDD anyway, is that given that I pass in a product ID, and then I should be able to assert against these specific results here.
So what I'm going to do now is I'm going to show you a, just a slightly different thing that you can also do in Go, is that you can, you can do something called table tests.
And these are really, really popular. So I just wanted to spend a little bit of time talking about these.
So one thing that Go allows you to do is declare sort of table tests like this, which means you can do things like you can declare a bunch of stuff about your test.
So the name of the test, the input type, and the expected output.
And then basically you can loop over them and run the tests.
So this is very similar to what we did before, but you can define all your test cases and just like a structure like this, and then just basically keep adding test cases to it and run them like this.
So this is a really interesting way to kind of structure tests.
And it's very popular in the Go community. These are cool, but they get overused quite a bit.
And I find them quite hard to follow in some situations.
So I think it's great in this specific example, and it's a good use case for it.
But if you imagine that, if you imagine that this third one failed, and I've run a loop over 50 or 100 tests, I'm going to get a really sort of convoluted and difficult output, and it's going to be hard to isolate exactly what failed.
So I tend to not like these bigger sort of handler style tests. So yeah, so that's that.
So since we've got a couple of extra minutes left, I'm going to head back over to this piece, and I'm going to talk about these slides that I did have available to talk about.
So what's coming next in Go? So Go isn't a finished language, like nearly every programming language out there.
There's a lot more to come, and it's got a quite exciting future ahead of it.
So one thing that you might remember when we were looking through all our code is that if error equals null was coming up like an awful lot.
So you can see an example on the slide here.
So do this. If not error equals null, error equals null, error equals null, which can get pretty, at least the code being very, very long and convoluted.
So what Go does is it has a really great community of being able to, like anyone can go into the GitHub repo, and Go is open source, and you can take a look at different people's suggestions on how we can improve the language, and then they ask people to write proposals about how they could improve the language, and this is one of them.
So it's a better way to handle errors. So you can see that we actually handle the error up front, and then just all these functions that may return an error, we just assume they returned one.
So at least a little bit more concise code.
This has caught some steam, and I think there are some proposals being implemented on how to do this, but I'll leave it up to you to decide whether you think this actually makes for better Go or not.
I'm personally okay with this left-hand side.
I think it's okay to be really clear, and it's one of the reasons I like Go is as you're reading through it, it's very clear what's happening.
The other thing that they're looking at implementing is generics.
So generics come from pretty much every other programming language that has some form of generics, but they were purposely left out of Go because they get pretty complicated.
As you can see by the examples here, they're not simple, right?
So I'm just going to talk you through the left-hand side here to the level I can, and why people are talking about generics.
It's worth noting these are not part of the language yet.
They may never become part of the language, and there's a lot of fierce discussion about it.
So in Go, we have this concept of an interface.
An interface basically means anything. Anything can satisfy the interface construct.
So people often use it a lot to do some clever things such as this.
So if we have a stack data structure that should be able to take anything, some sort of content, and this function is reusable for any type, I can just make it take an interface, and then I can add that interface onto it, and this does work for the most part.
The only problem is this can get really dangerous because as I take things off of the stack, I need to check the type of it every time before I perform any other action on it, and if someone wants to push something onto the stack that I didn't know how to deal with, my code would probably crash or panic, so I'd have to be really careful with error handling here.
So there's definitely an argument for not doing this because what's the point of using a type-safe language if you're not going to use the type safety that it provides?
So that's where generics come in. So generics allow you to provide a generic type, so in this case it would be T, and it allows you to kind of push something or pop it onto the stack, but when you get it returned, it's already in the correct type.
So for example, if I made a stack of type string, when I pulled that thing off of the stack, it would already be a string.
This is really powerful because I can now reuse this code for anything, like it doesn't need to be strings, it could be integers, it could be a custom type.
So you can see why this would be a popular thing to do and why people may want to do it.
Again, this can add complexity and it's kind of fiercely debated about whether Go should even have them or not, and if we do have them, exactly how they should be implemented.
So again, I'll leave it up to you guys to figure out whether it's worth it or not.
The one final other thing I wanted to talk about is I wanted to show you the Golang home page.
So let me just jump over to that now, because there's some really great things on here that I think are really worth calling out.
So for one thing, there's a complete Go compiler here, but you can also have some of the best documentation of any language I've ever seen.
So there's some really great tutorials here, so I really recommend going to take a look at them.
It gives you some things about editors and IDs you might use.
So the one I was using there was called Golang.
You have to pay for Golang, but there's some great free ones like VS Code, which is really popular.
This is a really popular document that I really recommend you spend some time looking at.
It's like how to write good Go, and it covers everything.
As you can see, it's very, very long and covers pretty much everything you can think of.
So once you're more comfortable with the language, take a little look at this, and it'll help you be a better team member, because you can kind of use this as a standards document.
The other thing that's worth pointing out is the packages here.
So this is what we call Godoc. And effectively, this is generated purely from comments.
So if I go back to main, and let me just check out...
If I click into one of these functions, which come from the standard library, you'll see that it has some comments above it.
These are just regular comments that you see in any other code.
But by placing these comments here, it allows me to generate documentation, which is called Godoc.
And it's super popular, and it's really, really useful as well.
So if I just clipped into this crypto package here, you can see it's got really good documentation about how everything works.
And if I click it, it takes me into the actual implementation of the functions.
In some cases as well, that was a bad example for it, but some of them even give examples on how to use the specific code with concrete examples.
So here you go.
It's given an example about how you might use it. So that's really, really powerful and really, really useful.
The other thing is check out the Playground as well.
So the Go Playground is really popular for putting together really short snippets of code and sharing them with other people.
So as you can see, it's got a full Go compiler that's runnable.
You can import things into it, but the most valuable piece is you can share this too.
So if you wanted to show a colleague or a friend something cool you've found or you've made, all you need to do is come into here, copy and paste your code, maybe write something in here, and then you can share it with them and they'll be able to kind of use that and interact with it.
So there's a really great community behind the Go programming language.
It's always improving and they've got lots of great documents, tools, the blog is well worth a read as well.
And just check it out. There's even, here you go, Next Step for Generics is a featured article on here right now.
So it's very relevant, I spoke about it here today.
And that's pretty much everything I have for you today.
So I want to thank you all for tuning in.
Thanks for listening.