Learning Golang, Day 14 – Removing Code Duplication

Learning Golang, Day 14 – Removing Code Duplication

Today, on day 14, I created a custom method to remove code duplication creeping into the weather station codebase. Come read the fun story behind getting that done.


It was a fun and rewarding session, today. As I’m continuing to port my PHP Weather Station to Go, I’m continuing to have the opportunity to pick some aspect of the original implementation that I want to port and have a go at re-implementing it in Go, based on my current level of knowledge and experience.

Given this flexible approach to learning, the session might be an easy one, or it might be quite challenging. Today’s was a bit of both. Here’s why.

Up until today’s session, the Go implementation had two routes each handled by a separate function. Both functions ultimately returned HTML output based on a combination of three templates:

  • An outer template that provides output applicable to any route, e.g., the page title, meta tags, etc.

  • A footer template to provide a simplistic footer with a copyright notice.

  • An inner template that provides the content for that specific route.

It’s a two-step view, if you will.

The file paths to each of the templates was generated, in both functions, using the code below. Well, almost the code above. In one function, a variable concatenated with ".html" to determine the path to routeTemplate (another issue to be addressed).

routeTemplate := filepath.Join("templates", "routes", "default.html")
footerTemplate := filepath.Join("templates", "footer.html")
layoutTemplate := filepath.Join("templates", "layout.html")
tmpl := template.Must(template.ParseFiles(routeTemplate, footerTemplate, layoutTemplate))

You can see that the first three lines generate the paths to the three templates. Then, on the fourth line, the templates are parsed and assembled.

It’s only four lines, and it’s only duplicated once. However, there are a few more routes still to be added to the application. Given that, following the current trend, it’s highly likely that the code will be duplicated in each of the remaining routes. How many times might these lines be duplicated over the next six months?

So today’s session was all about removing that duplication, refactoring it into a single utility function that could be called wherever and whenever required. Here’s what I came up with:

func BuildTemplate(templateName string) *template.Template {
    tmpl := template.Must(
        template.ParseFiles(
             filepath.Join("templates", "routes", templateName+".html"),
             filepath.Join("templates", "footer.html"),
             filepath.Join("templates", "layout.html"),
        ))

    return tmpl
}

The function takes a single string argument, the core name of the template, then does the same as before, with two key differences:

  1. routeTemplate, footerTemplate, and layoutTemplate are inlined in the call to template.ParseFiles(). Given the brevity of the calls to filepath.Join(), it seemed to me that, if the method were formatted properly, it wouldn’t be any harder to read if the variables were inlined. What’s more, I could avoid the three extra symbols being added to Go’s symbol table. Sure, any performance improvement would be less than negligible, but the principle is important.

  2. It’s formatted for better readability, which is a must for me. The code is easy to follow, as it indents very smoothly. Because of the lack of complexity in the function calls, it doesn’t add any unnecessary cognitive load.

After that, the duplicated lines in the handler functions was replaced with the following call to the function.

tmpl := BuildTemplate("default")

For more seasoned Go developers, this is likely quite uninteresting. And, if I’d just written this first, I’d agree. But I didn’t. What made developing the function an excellent learning opportunity was the function’s return definition, *template.Template.

At first, you see, I set the return type as Template and was left more than a little perplexed seeing undefined: Template when I attempted to run the code. After all, the definition of template.Must shows that it returns Template. So what was wrong with setting that as the function’s return type?

I looked at template.Must's definition again, for a minute or so, trying to find my issue. Then I double-checked the imports in the top of weather-station.go to make sure that it was imported. Perhaps I hadn’t imported it, so that was the issue.

I saw that it was, so wondered a little longer as to what the problem was. It was then that it hit me! "html/template" was imported. Sure. But Template is a part of that package. It wasn’t imported directly. After that realisation, I updated the function’s return type to *template.Template.

A moment of true learning and clarity!

This might seem like a tiny thing, and perhaps it is. But these moments are crucial as they’re the moments when things "click", when things truly make sense, and I move forward in my learning of Go.

Perhaps it’s because I’ve spent such a significant proportion of my career with (mainly) one development language, PHP, and didn’t become a polyglot develop much earlier. Perhaps it’s because I’m only using Go for relatively short amount of time each day. Perhaps it’s because I’m a bit of a slow learner.

Based on learning German, French, and Czech, I think there’s a lot in those first two points. But that’s a story for another day.

Regardless, these was an exciting day for me, as I’ve properly ingrained how imports work now and don’t expect to get tripped up by them again.

Anyway, that’s me for today. See you, next time!


You might also be interested in these tutorials too...


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus