Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say Hello
Test Double logo
Menu
Services
BackGrid of dots icon
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say hello
Developers
Developers
Developers
Software tooling & tips

Learning Go: A JavaScript developer’s journey

Discover the phases a JavaScript developer experiences when learning Go, from confusion to curiosity, and how it reshapes their programming approach.
Brittany Moore
|
March 14, 2019
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Despite the fact that it's been around longer than I've been a programmer, I feel like the buzz around the Go programming language has increased of late.

This might be why, a few weeks ago, the client I'm working with announced their intention to start migrating portions of their Node microservices into Go. Since then I've spent some time learning Go, and pitching in on a Test Double open source project called Diplomat in order to get my bearings.

The process of learning a new language is, for me, a process of re-evaluating how I think. After almost five years in the land of JavaScript and Node, Go presented not only a challenge in terms of syntax, but in terms of worldview. Reflecting back on the experience, I went through three distinct phases of feeling, that influenced my ability to think and work in Go.

Phase 1: Confusion

The first stage of picking up a new language usually involves learning syntax: the low-level hows and whats that make up your average piece of code. How do you declare and assign variables? How do you iterate over collections of data, and what do those structures look like? How does concurrency work, if at all? I spent my first day or so working through the Tour of Go, and mapping the new syntax onto my pre-existing understanding of what code can do.

By "low-level", I do not mean simple, because the functionality of any two languages rarely forms a perfect overlap. I encountered new things that I couldn't easily link back to my JavaScript knowledge. I got tripped up by the interactions between var, := and =, spent a lot of unnecessary time trying to handle errors like they were exceptions, and managed to place one-way channel type arrows incorrectly every single time in a particularly rough pairing session.

At the same time, the puzzlement I experienced while learning these syntactical tricks paled in comparison to my first encounter with GOPATH. I pulled an existing Go repository down from GitHub, tried to run it, and watched it fizzle anti-climactically. Which is when I learned that projects running Go 1.10 and lower have to be placed inside a particular, preset path on your machine, known as the GOPATH. It meant putting code repositories outside the meticulous system I'd built in my home directory, which kept personal projects in a different space than client work. I felt an inward recoil at this restriction, and I wondered why developers were willing to accept it (as it turns out, they weren't entirely willing).

Fortunately, the recent introduction of Go modules has rendered that hassle unnecessary. Still, that particular experience, and my mental reaction, gave me a glimpse of the "box" I've been working in as a JavaScript developer. I'm used to doing things a certain way, and—more importantly—thinking about things a certain way. Looking back, the aspects of Go that challenged me the most were also those that challenged my JavaScript-y way of looking at the world. This was most noticeable in Go's restrictiveness: specifically, its type system and various compiler checks.

Phase 2: Frustration

The confusion about how Go works eventually led to the second emotional stage: getting frustrated with it. Since I'd been successful in other languages, it was easier to blame Go than my coding skills when things went wrong.

This is when I started working on Diplomat. I wasn't around at the inception of the codebase - another Test Double agent was the originator - and he'd provided a Makefile with a set of scripts to make jumping in easier. The main script, watch, ran the formatting, build, testing, and linting steps on every file change. This is a lot of steps, which made for lots of opportunities for my terminal to shout at me.

And shout it did. If I left an unused variable in a function, it wouldn't run any tests until it was fixed. If the compiler failed in one part of the codebase, it wouldn't run unit tests for the other parts. It wouldn't run integration tests unless all the unit tests passed. I spent a non-trivial amount of that first day fighting with and against that watch script. I still wasn't familiar with the compiler errors, and I had a bad habit of making several big changes between saves, both of which combined to make identifying errors difficult.

In retrospect, some of this pain was the natural outcome of figuring out a new language. But some of it was self-inflicted, a result of my trying to force my usual programming habits onto a language where they didn't quite fit. At one point during a pairing session, I made a change that led to open hanging channels. When the make script output went blank, I restarted it. After doing this a couple of times, my pair suggested that make probably wasn't the issue. He was right; the issue was that I didn't know what was wrong, and I was hoping that it was somehow the tooling's fault.

Phase 3: Curiosity

The transition out of the frustration phase occurred when I finally decided to stop fighting the Go tooling and work with it. At first, my inclination was to make big, sweeping changes and pick up the pieces later. Unfortunately the watch script made this really difficult; because it contained all the build steps, messing up one thing essentially meant shutting down my entire feedback cycle, and I wasn't proficient enough in Go to put the pieces back together without help from the compiler. So I changed my approach.

I started making smaller changes, saving and checking the watch output between each change. If something broke, I either went back or used the compiler output to tell me what to do next. That made it simpler to thread a single type change through the entire application, for example, instead of trying to make five type changes at once and blowing everything up. I was able to refactor a big component by building up a second one and using TDD to migrate behavior into it.

This felt strange, and it took a while for me to realize why. What I'd done was gone "back to basics" in terms of how I interact with a language. There was a time when I wrote JavaScript this way, using very disciplined TDD practices and taking advantage of tooling feedback. It took time to develop the confidence I have now in JavaScript, as well as the ability to model an application in my head without looking at any tools. Since I'd already been through this, I should have expected that it would take time to reach the same confidence with Go.

In the end, finding a solution to my frustrations led to increased curiosity, both about Go and about how I approach programming. I was able to ask better questions about how Go works, and how our packages should be architected to be understandable and easier to work with. I was engaging in a dialogue with the language and the tools, instead of trying to impose my will on them. At one point during a refactor we observed a repeated pattern throughout our packages, which we couldn't abstract away because Go doesn't support a generic type. This natural guardrail led me to reflect on why I was averse to duplicated code, and consider the complexity cost of compulsive DRY-ing. I realized that simply knocking my mind off of its usual tracks created space for curiosity about things that didn't specifically involve Go.

Conclusion

Some caveats: As you've probably guessed, this post isn't about teaching you to write Go. There are plenty of better teachers, some of which I've linked to above, and none of which are substitutes for writing some Go yourself. I also don't claim to know everything there is to know about Go, or that all of the decisions we've made in Diplomat are correct. There's always more to learn and understand. Someone else learning Go from JavaScript may experience entirely different phases than I did. This is simply a retrospective of a few weeks of learning Go, my state of mind, and lessons learned from picking up a new language.

Based on my experience, I'm inclined to think that the languages we use affect how we approach programming as a discipline, and that our perspectives can become fixed over time. Learning Go influenced me to approach problems from a different direction; asking how and why in an unfamiliar space prompted me to think about the answers to those questions on old terrain. I'm better poised to appreciate the nuances that make JavaScript and Go different as I work with both in the future.

The last few weeks have also given me pause to reflect on the perceived fluidity of what we call "coding skills" in our industry. It can be tempting to operate as though programming languages are interchangeable, and assume that software developers can flow between languages at any time. Which, unfortunately, isn't the case (as much as we'd like it to be). Developers, and humans in general, struggle to context switch between different activities. In a similar way, I think it's important to view a change of language as a change of context, and ensure that a team has the time and space to make their own journey to understanding.

Related Insights

No items found.

Explore our insights

See all insights
Leadership
Leadership
Leadership
The business of AI: Solve real problems for real people

After participating in the Perplexity AI Business Fellowship, one thing became clear: the AI hype cycle is missing the business fundamentals. Here are 3 evidence-based insights from practitioners actually building or investing in AI solutions that solve real problems.

by
Cathy Colliver
Leadership
Leadership
Leadership
Pragmatic approaches to agentic coding for engineering leaders

Discover essential practices for AI agentic coding to enhance your team’s AI development learning and adoption, while avoiding common pitfalls of vibe coding.

by
A.J. Hekman
by
Aaron Gough
by
Alex Martin
by
Dave Mosher
by
David Lewis
Developers
Developers
Developers
16 things software developers believe, per a Justin Searls survey

Ruby on Rails developer Justin Searls made a personality quiz, and more than 7,000 software developers filled it out. Here's what it revealed.

by
Justin Searls
Letter art spelling out NEAT

Join the conversation

Technology is a means to an end: answers to very human questions. That’s why we created a community for developers and product managers.

Explore the community
Test Double Executive Leadership Team

Learn about our team

Like what we have to say about building great software and great teams?

Get to know us
Test Double company logo
Improving the way the world builds software.
What we do
Services OverviewSoftware DeliveryProduct ManagementLegacy ModernizationDevOpsUpgrade RailsTechnical RecruitmentTechnical Assessments
Who WE ARE
About UsCulture & CareersGreat CausesEDIOur TeamContact UsNews & AwardsN.E.A.T.
Resources
Case StudiesAll InsightsLeadership InsightsDeveloper InsightsProduct InsightsPairing & Office Hours
NEWSLETTER
Sign up hear about our latest innovations.
Your email has been added!
Oops! Something went wrong while submitting the form.
Standard Ruby badge
614.349.4279hello@testdouble.com
Privacy Policy
© 2020 Test Double. All Rights Reserved.