Conf42 Golang 2022 - Online

Go - Best practices

Video size:

Abstract

Ranjan would like to share the best development practices that has helped us reduce the amount of time/effort spent in maintaining the application.

Even Golang beginners can attend and a GitHub repository containing the sample code/notes used in this presentation for future reference will be provided.

Summary

  • Andela has matched thousands of technologists across the globe to their next career adventure. Now the future of work is yours to create. Anytime, anywhere. The world is at your fingertips.
  • Today I'll be taking a session on Gotchas and a few best practices in Golang. One thing to be careful of is when you pass trucks, always consider the possibility of pass by reference. Especially if the function that you're passing the input to is manipulating the input in some way, it could cause problems later.
  • Be careful when redeclaring variables using the shorthand notation. It can get a bit tricky in terms of shadowing variable shadowing. To fix this, we should be reinitializing the variable. And when we do that, we get the actual result as desired.
  • Coming to slices, one of my favorite parts of code. When I take the slice, I would like to see whether the slice produces a completely new string array or whether it only refers to the original string array. Any changes that you make to the slice variable will affect the original array.
  • In certain cases, especially when you have unicode characters emojis symbols, the len output is no longer the number of characters. A safer way to measure the length of the string is to use Rune count and string and not Len. Consider labels to break out of nested loops or even for switch scenarios.
  • Using the defer keyword to run tasks after everything else in the function is done. Whenever we use deferred statements, the behavior is like a stack. Whatever is the last function that's deferred is what runs first.
  • When we talk about iteration variable closure, especially in the context of a differ, you need to redeclare those variables and then use them in the subroutine. Just because we kick off subroutines, it doesn't mean all of them will occur one after the other. Some may terminate before, some may terminate after.
  • You cannot recover panics from panics by just calling recover in the same context of a panic or even after it. The approach to take is by recovering in a defer call.
  • I'll be uploading this to a git repository, GitHub specifically on GitHub, and I'll share the link. So it'll containing all the sample codes you could play around with it. Eventually I'll add a lot more go gotchas and best practices. So please feel free to take a look, reach out to me and I would love to hear from you.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
What if you could work with some of the world's most innovative companies, all from the comfort of a remote workplace? Andela has matched thousands of technologists across the globe to their next career adventure. We're empowering new talent worldwide, from Sao Paulo to Egypt and Lagos to Warsaw. Now the future of work is yours to create. Anytime, anywhere. The world is at your fingertips. This is Andela, everyone. Ranjan here. Today I'll be taking a session on Gotchas and a few best practices in Golang. So let's get started. To start with, if you take a look at this snippet of code, as innocent as it may seem, it does primarily one thing. It takes in a list of numbers, or in this case, it's an array of numbers, and then it prints them out, and then it tries to call a square numbers function, which ends up squaring them, and then it prints the number again, and after which, you're printing the output, which is squared numbers. Now, why are we doing this? Why are we printing it before and after calling the function? We'll figure out why. That's one behavior that's common to many languages, where more expensive objects like arrays, lists, and other data structures are passed by reference, as opposed to passed by value. So if I run this program, as you can see, the numbers that I has passed were 12345 and minus six, and that's what has been printed here. And that was what was passed to square numbers to obtain the result. But the funny part over here is when you go inside and then you look at the logic of this, what you're doing here is you're squaring the numbers and storing it in the same array. So when you're doing that, it points to the same memory location as the array that has passed. So since it was a pass by reference, the original location gets updated. And on calling this, even though it returns the same array with the squared numbers, at this point, squared numbers and numbers have the same reference. That's the reason why the numbers and square numbers that are printed after have the same values. Even if you look at the addresses, it'll remain the same. So one thing to be careful of is when you pass trucks, or when you pass arrays or any other objects that are not primitive types, always consider the possibility of pass by reference. And especially if the function that you're passing the input to is manipulating the input in some way and reusing the variable, it could cause problems later, so keep a keen eye on that regard. Moving on, when we talk about this example, it's pretty straightforward. You have an array of variables similar to the previous example. You're printing it out, and then you're calling a function called rpositive numbers, where you pass it, and inside that, the gist of the logic is to check if everything within the past input array is positive. If it's positive, it's going to return true, else it's going to return false. So with such a simple example, as innocent as it may look, iterates through the numbers. It checks if any of the numbers while iterating is less than zero, then it sets the result to false, prints it out and breaks. What would we expect as the answer for this? Since it contains a negative number, we would expect the result to be false. It's not going to be all positive, but the actual result is true. And why is that? We're going to look at that. So what is happening here is it enters this place, this if loop, and the proof of that is this print statement result, and result is set to false. But one minor detail over here is the fact that we are redeclaring that variable. That's what colon equal to is a shorthand notation for. So when we are doing that, this local variable within this code shadows this variable. So any result, reference in these two lines is a reference to this result, not this result. So that is another thing to be careful of. It may seem ridiculously small, but it can cause problems like what we saw here. So to fix this, we shouldn't be redeclaring the variable, we should be reinitializing the variable. And when we do that, we get the actual result as desired. So be careful when redeclaring variables using the shorthand notation. It can get a bit tricky in terms of shadowing variable shadowing. Okay, moving on. Coming to slices, one of my favorite parts of code. We have an array of strings over here of names. And over here, when I say names of colon two, I'm basically taking a subset of the original array, right, a slice of it. And when I take the slice, I would like to see whether the slice produces a completely new string array or whether it only refers to the original string array. Hence these details. Right. I'm printing the length of the names as well as the new names which are supposed to be a subset, the capacity of it and the address of it. Let's see what it looks like. So if you take a look over here for names, the length is four, the capacity is four, and the address ends in. Whereas for the new names, which are supposed to be the slice, the length is two, right? Since we are cutting it colon two, it's only going to take the values in the index zero and one, which makes complete sense. But the capacity is four. How is that possible? Isn't it supposed to be smaller? This answers it. It's 00:40 the same address as the original array. So any changes that you make to the slice variable, the sliced portion of the string array that you store in new names, will affect the original array. So you got to be careful while slicing arrays or any other data structures, as it can affect the original data structure. Moving on, something as innocent as getting the length of the string right, we just go ahead and say le n of the string. But in certain cases, especially when you have unicode characters emojis symbols, the len output is basically the number of bytes. It's no longer the number of characters. A clear proof of it is running this. You see the length of that particular string, even though visually it's one character, it's three. So a safer way to actually measure the length of the string if you are expecting any unicode characters like these, is to use Rune count and string and not Len. So that's also another thing to note. Now, coming to logical operators in Python, Java, and a lot of other languages, you may have used the exclamation mark or even the tilde symbol for not operation, but in Golang specifically, it uses the mountain symbol or the carrot symbol for not as well as xor. So that is something to note. When it is used in a unitary sense, it's taken as a not operator. When it's used in a binary sense with two operands on either side, it's taken as an XOR operator. So that is something to note as well. So when you see it like this, you may be wondering what this is. And in some languages this is also used for exponentiation to say this can be interpreted as one part two in some of the languages. So that's something to note over here. And this is an interesting problem. Let's say I'm iterating through a range of numbers, and within that I'm using a switch case to see if any number that I'm iterating through, any of the numbers that I'm iterating through, is the maximum possible unsigned integer value. If that's the case, there is no way for me to compute the sum in a variable with the same capacity. So I'm just going to set the sum to max uint and break. That's the desired behavior over here. So for that particular scenario, what I'm going to do is I'm going to just say sum is equal to max un, break. And then after that, since the sum value has been set, it has to print here. And what I am expecting over here is for it to break and come out of the loop. But that's exactly what doesn't happen. So if I run this snippet of code, sorry about that. So if I run the snippet of code, you see the result is nine. There is a value here called max un, right? Which maximizes the sum, and there's nine. How can we explain that? Whenever it identifies max un, it sets the sum to max un. And what are the remaining values? 1234. And what are the sum of it? 7910. Right. So when you overflow past the maximum value, you start from 012-34-5678 910 values, basically. And that's the reason why the sum is nine, just basically overflowing. And that's definitely not a desired behavior. So in order to fix this, when you want to break out of force, switch cases like this and break out of the loop, the way to approach this problem is to use something that all of us would have seen in much older languages, labels. So if you take a look here. Sorry about that. So if you take a look here, I set a label for this group of statements, right? For this particular for loop. And whenever I want to break out of the for loop, I just specify the label along with the break keyword. So that tells me I need to break out of the statements contained within that label. So that exits the loop. Now, if I run it, when it iterates through this list or this array, it hits a maximum unsigned integer value. In that scenario, it sets the sum to that and it just breaks the loop, which should exit the loop, and then the sum should be the maximum unsigned integer value with a lot of digits. So keep that in mind whenever you talk about nested loops or even for switch scenarios, consider using labels to break out. Okay, next one, we're moving to a very, very interesting part of go, using the defer keyword to run tasks after everything else in the function is done. So considering this particular scenario, let's see how the call argument evaluation works for tiffo. In this particular scenario, if you take a look, we have a list of strings, basically a bunch of names that we want to pass to the say goodbye function, which ends up just printing goodbye followed by the name. And how are we passing the array of strings over there? We're using strings join and we are passing it now, since I have differ over here, this method, say goodbye is basically going to be executed after the main method content is processed. So over here, if I change the value of the names, which is evaluated as a part of the argument to the function, is it going to affect the argument that goes into say goodbye? If this change is going to affect it, then he's no longer going to be called albus, he'll be called goodbye megabus perceiver Wolfreg, Bryant, Dumbledore whereas if any changes post the defer statement does not affect the arguments provided to the defer function at that point of time, we can be sure that it's evaluated right here. So let's run it and check it out. Perfect. We have his original name, so what does that tell us? It tells us that the argument evaluation for default statements are done then and there. So plan accordingly. While writing code, you cannot change the code or the arguments, the variables that are referred to in the arguments after the deferred statement, and expect that to be passed. So, moving on. Another very simple fact. Whenever we use deferred statements, the behavior is like a stack. When we stack multiple deferred statements, whatever is the last function that's deferred is what runs first. So it's as though you are stacking up the deferred functions in a stack. So last and first out. So first, when I run this code, it first differs printing first defer, and then it then defers. Printing second defer. And then since this is the last function, that's function call that's put into the stack that gets executed first, followed by the other one. So it prints second differ followed by first defer. So that's pretty much the order of execution of differ statements in a leaf form fashion. Okay, moving on. When we talk about iteration variable closure, especially in the context of a differ, right, I'm taking the same example of a list of names, and I'm iterating through that. And then I'm going to check out over here to see if I print the index and the name within this. It'll be interesting to note this and differ as well. My bad. This is an example for the go keyword where we are actually running subroutines. What is the value that it would take? That is the question. So when we run this piece of code, you see it has taken the last value for all iterations. It doesn't do it for each loop, because what happens is this runs pretty fast. And when the subroutines kick in whatever is the latest value in index name gets referred to over here within the subroutines. So to prevent this from happening, what you do is you need to redeclare those variables and then use them in the subroutine if you want to take that route. So say I have index copy and then I have name copy, right? And then here I'm going to use index copy, and here I'm going to use name copy. And let's see what happens now. Perfect. It's out of order. Because just because we kick off subroutines, it doesn't mean all of them will occur one after the other. It occurs concurrently. Some may terminate before, some may terminate after, so the order will not be preserved. But at least you're getting the different values as the output over here, which was the expected behavior. Okay, moving on. When we talk about recovering panics in general in Golan, one important thing to note is you cannot recover panics from panics by just calling recover in the same context of a panic or even after it. So when you panic, that's when it basically, the panic propagates. It's like throwing an exception or error in other languages. It's going to go up the call stack. So one way to actually prevent panicking is not by doing this. This will continue, it'll panic and it'll tell hey omg. Oh my God. So if you want to go ahead and prevent that from happening, the approach to take is by recovering in a defer call. So here I have a function that I've deferred for execution until the rest of the contents in main has been executed, and within that I'm calling the recover function, and then I'm panicking. OMG. So let's see what happens in this scenario. Perfect. It has recovered, and it has also printed out the output of the recover call, which is the panic that has been recovered, that it has recovered from. So if you ever want to recover from panics, you don't use recover in the same place, you use it in a differ. Moving on, last but not least, okay. Oh perfect, I think we're done. So please feel free to reach out if you have any questions, any comments, any concerns, and I'll be uploading this to a git repository, GitHub specifically on GitHub, and I'll share the link. So it'll containing all the sample codes you could play around with it, and it will not only contain this, eventually I'll add a lot more go gotchas and best practices. So please feel free to take a look, reach out to me, and I would love to hear from you. So thank you for your time and hope to see you soon in another conference.
...

Ranjan Mohan

Senior Software Engineer @ Menlo Security

Ranjan Mohan's LinkedIn account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways