Welcome back.
Lastly, you have rewritten the log parser using structs.
Now it's time to rewrite it once more,
using functions.
Let's talk about how to design a program using functions. When designing a program,
you need to find out its unique responsibilities.
You can do so by looking at the verbs. The log parser program parser a log file and aggregates the
visits by domains.
Let's take a look at what it does.
First it parses the log file line by line.
If it cannot parse a line, it returns an error, otherwise it puts the domain name and the number of visits
to a result value.
Next, it saves the unique domain names to a slice.
It also saves the parsing results to a map.
It also keeps track of the total visits across all the domains.
Lastly it prints a report per domain.
So in this lecture this diagram will be your guide.
I'm going to show you how to split the log parser program to functions.
Let's get started.
The main goal of this lecture is to move the parsing logic out of the main function as much as possible.
Okay let's take a look at the code.
Here it creates a parser but this shouldn't be the responsibility of the main function so it is better
to move this to its own function. First let me copy it from here.
Here I'm going to create a new parser using a function.
Next I'm going to create a new file and I'm going to name its parser.go. Here,
I have created a new file because I want to keep the code clean and separated from the main function.
All right inside the file I'm going to say package main.
Next, I'm going to declare the function like so. It returns a parser of value.
So, I'm going to return a new parser value by pasting the code that I've copied.
This is a constructor function pattern.
Unlike other OOP (Object-Oriented-Programming) languages, Go doesn't have constructors.
However, you can imitate them (OOP constructors) using a function.
It is called a constructor function because it constructs the initial value and returns it here.
It makes the parser value ready to use and returns it.
Okay let's test it.
Cool.
It still works.
Okay let's move all the structs to the parser file as well.
They make unnecessary clutter here. It still works.
Good.
Okay let's take a look at the code again.
I think it does a lot of things indeed.
Let's split it more.
Let's start with the parsing logic here.
Let's cut it from here.
Inside the parser file.
I'm going to declare a function named parse,
like so. Let's paste the code here.
Of course, there are errors now. in.Text() doesn't work here because the in variable is in the main
function.
So let's declare a string parameter to get the current line from outside and let's replace the in.Text()
call here with the line variable instead.
Now it says there is no p variable in the scope of the new parser function.
So let's declare another input parameter to get it from outside.
Doing so fixes the errors.
Now it says the domain variable is declared but not used.
The purpose of the parse function is to parse the log line and return a result value.
So let's do it. Since it returns a result type,
so it also needs to declare it.
Let's do that.
Now there are two more errors.
The function needs to return a value but it doesn't do so here.
Let's declare a new result value above and return it instead.
Okay.
This fixes the errors. By the way, I think I can use the same result value throughout the function.
Let me show you. Instead of saving the result to these variables, I'm going to save them to the parsed
variable.
First I'm going to change the visits variables to parse.visits like so. Now there is an error because
I cannot declare a struct field here because it's already been declared. Before fixing that,
let's also change it in the last return statement.
Okay now let's fix the error.
Let's create an empty error value next to the parsed variable.
Now I'm going to change the declaration to assignment.
Let's also change the domain variable like so.
And lastly, let's return the parsed variable directly because now I already initialized it.
All right.
Done.
Now it's time to return back to the main function and call this function (parse()) from there.
Here I'm going to call the parse function by passing the parsed value adding the current line like so. Let's
also save its results to a variable.
Of course there are errors now because the domain and visits variables are not declared.
I can easily fix this problem by declaring the variables using the parsed value like so. Alright, let's
try it. Awesome! It still works. It catches the errors as well but it shouldn't deal what to do with the
errors.
It should only return them. For example, other programmers were using this function and maybe they were
logging to errors to somewhere.
If you print the errors directly they cannot process the errors and they will be very upset.
So let's return the errors instead.
To do that I need to declare an error result
value as well, like so. Good.
But how can you create an error value? You can easily do that by replacing the print calls with
Errorf calls like so. Errorf works just like Printf.
However, instead of printing an output it returns it as an error value.
Okay, now I'm going to assign them to the error values. There are warnings because the error messages
shouldn't include a new line. It's because the error messages can be chained.
You can return an error and another error can add its own error message to it.
So let's remove them.
OK, now I'm going to return the error values like so. Good.
Now let's handle the error from the main function as well. I'm going to get it from the parse function
like so. Then I'm going to check for the error like so.
If there is an error,
I'm going to print it and return from the function.
Remember the main function doesn't return anything.
That's why I can return from it without any arguments to the return statement.
The main function runs the whole show.
So when I return from it, the program terminates by itself.
Let's see whether it is gonna handle the errors.
I'm going to run it with log_err_missing, then I'm going to run it with
log_err_str
And lastly I'm going to run it with log_err_negative.
It works.
Let's go back to the parse function.
I'm going to show you something. Here,
I declare empty variables,
then I return them three times.
Fortunately, there is an easier way.
Let me show you.
Let me add names to the result
values like so.
Next I'm going to remove these declarations.
Lastly, I'm going to call the return statements without any arguments like so. As you can see it works.
But how?
Let me explain.
These are the names of the result values.
When I do so, they become variables inside the parse function. Behind the scenes,
Go declares them like so.
And when I call the return statement, Go automatically returns them.
This is called a naked return.
It only works when you use the named
result values. Behind the scenes,
it works similar to returning them manually like so.
That is why the parse function still works.
All right.
That's all for now.
See you in the next lecture.