📚

Go 言語勉強会を 1 年間やってみた!〜基礎から実践的な CLI アプリ開発まで〜

に公開

こんにちは!株式会社 TRUSTDOCK でバックエンドエンジニアをしている @kurupeku です。

今回は、社内で 1 年間にわたって開催してきた Go 言語勉強会の振り返りをしたいと思います!

2024 年後半からスタートし、Go 言語の基礎を学ぶところから、2025 年前半にはオリジナルの CLI アプリケーションを開発するまで、参加メンバーで切磋琢磨しながら学習を進めてきました。

この記事が、弊社の技術カルチャーやエンジニアの成長環境に興味を持っていただけるきっかけになれば嬉しいです。

なぜ Go 言語勉強会を始めたのか?

私たちのチームには、Ruby や TypeScript をはじめ、様々なバックグラウンドを持つエンジニアが在籍しています。そんな彼らから「Go 言語、興味あるけどなかなか触る機会がなくて…」という声を耳にする機会がありました。

新しい言語を学ぶとき、個人的に一番大変なのが「最初の動くものを作るまで」のインプット期間で、それが故に興味止まりになってしまいがちだと思っています。この最初のステップを、もっと楽しく、もっと実践的に乗り越えられないか?そうすればみんなサラッと Go を書けるようになるんじゃなかろうか?と考えたのが、この勉強会を企画した最初のきっかけです。

また、弊社はフルリモート勤務なので、チームを越えたエンジニア同士の交流を深める機会を増やしたい、という思いもありました。なので勉強会を通じて、普段の業務では関わりの少ないメンバーとも気軽に話せる場が増えれば一石二鳥だなと思ったのが始まりです。

幸いにも、上司から「エンジニアのスキルアップやコミュニケーション活性化のために、ぜひやってみよう!」と力強い後押しをもらい、この勉強会がスタートしました 💪

Go の基礎をインプットしつつ、手を動かして対応する課題を解いていく「即時アウトプット」な学習コンテンツを用意し、内容を徐々に高度にしていくことで気がついたら Go が書ける!という勉強会を目指して準備をすすめ、開催へと至りました。

勉強会の概要

今回の勉強会は、大きく分けて 2 つのパートで構成されています。

  • 前半戦(2024 年後半): A Tour of Go との併用学習を目的としたリポジトリを活用し基礎学習
  • 後半戦(2025 年前半): ライトな CLI アプリ開発を 3 つ内包したリポジトリの実装を通して実践的な Go プログラミングを学ぶ

勉強会は、前半戦は 4 名、そして後半戦にはなんと 8 名ものメンバーが参加してくれました!ほとんどのメンバーが「Go は初めて」という状態からのスタートでしたが、みなさん 1 人も欠けずに完走していただきました✨

弊社はフレックスタイム制を導入しているため、日中のコアタイムに開催したのですが、会議の合間を縫って参加してくれるメンバーも多く、本当に頭が下がる思いでした。それでも、誰一人欠けることなく最終回まで全員で走り抜けることができたのは、参加メンバーの熱意の賜物です。感謝!🙏

前半戦: A Tour of Go との併用学習を目的としたリポジトリを活用し基礎学習 📚

前半戦では、A Tour of Go に沿って作られたリポジトリを使って、Go 言語の基本的な文法や考え方を学びました。

勉強会の進め方は、まず各自でリポジトリ内の課題を解いてきてもらい、週に一度のセッションで私がコードレビューを実施。分からない点はその場で深掘りして解説する、というスタイルを取りました。

このリポジトリには、あらかじめユニークな課題が用意されています。
以下に一部を抜粋します。

  • タクシーの料金計算: 型の概念や型違いの値同士の四則演算など、静的型付け言語ならではの基礎構文に触れてもらいます。
  • 山手線料金計算: ifswitch、ループ処理など、制御構文を実践的に使います。
  • ラーメン屋のレシート発行: 構造体やスライス、マップといったデータ構造の理解を深めます。

課題の例として山手線の料金計算は以下のような内容になっています。

README.md
# 山手線料金計算

東京駅から引数で渡した駅までの乗車料金を計算して返す関数を実装せよ。

## 前提条件

### 出発駅

東京駅固定とします。

### 引数(到着駅)

駅名は「駅」を含まない名称

`e.g.) ✕新宿駅 ◯新宿`

で渡されてきます。

山手線に存在する各駅のみが渡される(存在しない駅は渡されない)前提の実装で問題ありません。

ただし、出発駅と同じ駅(東京)が渡されてきた場合には `0` を返してください。

### 料金の計算式

乗車距離に基づいて料金が決まっています。

料金テーブルは以下の通りで、内回り / 外回りを問わずテーブルに準じます。

| 下限 (m) | 上限 (m) | 料金 (円) |
|-------:|-------:|-------:|
|      - |   3999 |    140 |
|   4000 |   6999 |    160 |
|   7000 |  10999 |    170 |
|  11000 |  15999 |    200 |
|  16000 |  20999 |    270 |
|  21000 |  25999 |    350 |
|  26000 |  30999 |    420 |
|  31000 |      - |    490 |

### 条件分岐のハンドリング方法指定

内回り用の関数 `func InnerChargeFromTokyo(station string) int` では `if` を用いて、外回り用の関数
`func OuterChargeFromTokyo(station string) int` では `switch` を用いて距離の判定を行ってください。

### 次の駅の取得方法

`helper` パッケージ内に

- `func InnerNextStation(current string) string` 内回り用
- `func OuterNextStation(current string) string` 外回り用

を用意しています。

この関数に現在の駅名を渡すと次の駅名を返してくれるので、ループ内で活用しましょう。

### 駅間の距離の取得方法

`helper` パッケージ内に

- `func InnerLoopDistance(current string) int` 内回り用
- `func OuterLoopDistance(current string) int` 外回り用

を用意しています。

この関数に駅名を渡すと一つ手前の駅からその駅までの距離をメートル単位の整数で返してくれます。

次の駅名が判明した後にこれを使用して距離を取得しましょう。
exam.go
package exam

// If を使用して料金の条件分岐を行ってください
func InnerChargeFromTokyo(station string) int {
	// TODO: 実装
	return 0
}

// Switch を使用して料金の条件分岐を行ってください
func OuterChargeFromTokyo(station string) int {
	// TODO: 実装
	return 0
}
exam_test.go
package chapter03

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestInnerChargeFromTokyo(t *testing.T) {
	type args struct {
		station string
	}
	tests := []struct {
		name string
		args args
		want int
	}{
		{
			name: "To 東京",
			args: args{"東京"},
			want: 0,
		},
		{
			name: "To 神田",
			args: args{"神田"},
			want: 140,
		},
		{
			name: "To 日暮里",
			args: args{"日暮里"},
			want: 160,
		},
		{
			name: "To 巣鴨",
			args: args{"巣鴨"},
			want: 170,
		},
		{
			name: "To 池袋",
			args: args{"池袋"},
			want: 200,
		},
		{
			name: "To 新宿",
			args: args{"新宿"},
			want: 270,
		},
		{
			name: "To 大崎",
			args: args{"大崎"},
			want: 350,
		},
		{
			name: "To 品川",
			args: args{"品川"},
			want: 420,
		},
		{
			name: "To 新橋",
			args: args{"新橋"},
			want: 490,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.want, InnerChargeFromTokyo(tt.args.station))
		})
	}
}

func TestOuterChargeFromTokyo(t *testing.T) {
	type args struct {
		station string
	}
	tests := []struct {
		name string
		args args
		want int
	}{
		{
			name: "To 東京",
			args: args{"東京"},
			want: 0,
		},
		{
			name: "To 有楽町",
			args: args{"有楽町"},
			want: 140,
		},
		{
			name: "To 品川",
			args: args{"品川"},
			want: 160,
		},
		{
			name: "To 目黒",
			args: args{"目黒"},
			want: 170,
		},
		{
			name: "To 恵比寿",
			args: args{"恵比寿"},
			want: 200,
		},
		{
			name: "To 新宿",
			args: args{"新宿"},
			want: 270,
		},
		{
			name: "To 池袋",
			args: args{"池袋"},
			want: 350,
		},
		{
			name: "To 鶯谷",
			args: args{"鶯谷"},
			want: 420,
		},
		{
			name: "To 秋葉原",
			args: args{"秋葉原"},
			want: 490,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.want, OuterChargeFromTokyo(tt.args.station))
		})
	}
}

各課題にはテストコードが用意されており、CI が通ることを目標に実装を進めるという方式をとっていました。

そのためテストから仕様をより明確に掴んでもらったり、テーブル駆動テストの書き方や読み方に慣れてもらったりしながら、課題を解いてもらいました。参加者からは「期待する挙動が明確で、実装に集中できた」と好評でした!

課題のテーマも「山手線の料金計算関数」のようなイメージしやすいものにしていたり、たまに某お笑い芸人さんをリスペクトした「ナ ◯ アツ関数」みたいなおふざけ要素も入れたりと、課題の内容にもこだわって作ってみました。これも楽しんでいただけた…と信じています!(笑)

後半戦: ライトな CLI アプリ開発を通して実践的な Go プログラミングを学ぶ 💻

後半戦も進め方自体は前半と同じレビュー形式ですが、2〜3 週間かけて一つの CLI を実装するという、より腰を据えた開発に挑戦しました。

基礎を固めた後半戦では、より実践的な課題として 3 つの CLI アプリケーション開発に挑戦しました。

  1. curl ライクな HTTP クライアント
    • HTTP リクエストの組み立てや、net/http パッケージの扱いに習熟しました。
  2. ripgrep ライクなファイル内文字列検索ツール
    • ファイル I/O 、 goroutine を使った並行処理によるパフォーマンス向上や context パッケージによる非同期処理の制御を学びました。
  3. 標準出力をインタラプトして HTTP で転送するログ転送ツール
    • io.Reader/Writer を使ったストリーム処理や、goroutinechannel を使った分散処理アーキテクチャなど、より高度なトピックに触れました。

参考までに 3 を作り上げた後の挙動です

※ブラウジングしている UI は勉強会用のリポジトリにあらかじめ実装していたログ転送確認用の別アプリのものです

自分で実装したコードで動くものが出来上がるというのはエンジニアにとって大きなモチベーションということもあり、前倒しでガンガン課題を解いてきてくれる方もいらっしゃいました!

また課題内容も Go らしさを押し出したものになっており、非同期処理の手軽さや go build 一発で単一の実行ファイルを生成できたりなど、Go の魅力を体感してもらうことを意識したものにしてみました。参加者にとっては、前半にくらべてかなり歯ごたえがあったのではないでしょうか 💪

一方で、前半のようにテストコードだけでは要件を伝えきれない部分もあり、機能要件をドキュメントで正確に伝えることの難しさなど、主催者としての私自身の課題も見つかりました。このあたりは、今後の勉強会運営にも活かしていきたいです。

1 年間の勉強会を終えて

当初の目標だった「スキルアップ」については、期待以上の成果があったと感じています。

勉強会終盤には、レビューで指摘することがほとんどなくなるほど皆さんのレベルが向上し、Go でシンプルな Web アプリケーションなら問題なく書けるだろう、という状態まで到達できました。これは、単なるインプット主体の勉強会では得られなかったであろう大きな成果です。

参加者からは「ちょうど Slack 連携のスクリプトを書きたかったので、Go で実装してみます!」といった嬉しい声も聞かれました 🎉

一方で、「コミュニケーションの活性化」という点では、改善の余地も感じています。進め方の都合上、どうしても「主催者 vs 参加者」という構図のコミュニケーションが多くなってしまいました。今後は、参加者同士がもっと気軽に議論できるような場作りも意識していきたいです。

とはいえ、主催者である私自身にとっても、アウトプットを通じて知識を整理する絶好の機会となりました。今回の経験を活かし、テーマや進め方をブラッシュアップしながら、今後も継続的に勉強会を企画していきたいです。

改めて、参加してくださった皆さん、本当にありがとうございました!

最後に

TRUSTDOCK では、エンジニア一人ひとりの成長をサポートし、チーム全体で技術力を高めていく文化を大切にしています。

今回の Go 言語勉強会もその一環であり、今後も様々な技術領域でこのような活動を続けていきたいと考えています。

現在、私たちは各ポジションでエンジニアを絶賛募集中です!
高い技術力を持つメンバーに囲まれながら、フルリモートで自身のスキルを磨いていける環境がここにはあります。

もし、私たちのようなカルチャーに興味を持っていただけましたら、ぜひお気軽にご連絡ください!
そして、さらにブラッシュアップされた次回の勉強会に、未来の仲間として参加していただけると、個人的にとても嬉しいです。

一緒に働ける日を楽しみにしています!

TRUSTDOCK テックブログ

Discussion