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.