Day 1: 開発環境構築とHello World - 解答例
課題2の解答
問題2-1: 基本のHello World
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
実行方法:
$ go run main.go
Hello, World!
コードの構造:
package main ← パッケージ宣言(実行可能プログラム)
│
import "fmt" ← fmtパッケージをインポート
│
func main() { ← プログラムのエントリーポイント
│
fmt.Println("Hello, World!") ← 文字列を出力
│
} ← main関数の終わり
---
問題2-2: 自己紹介
package main
import "fmt"
func main() {
fmt.Println("私の名前は太郎です")
}
別解(複数の方法):
// 方法1: 文字列連結
package main
import "fmt"
func main() {
name := "太郎"
fmt.Println("私の名前は" + name + "です")
}
// 方法2: Printf使用
package main
import "fmt"
func main() {
name := "太郎"
fmt.Printf("私の名前は%sです\n", name)
}
---
問題2-3: 複数行表示
package main
import "fmt"
func main() {
fmt.Println("こんにちは")
fmt.Println("今日からプログラミングを始めます")
fmt.Println("よろしくお願いします")
}
実行結果:
こんにちは
今日からプログラミングを始めます
よろしくお願いします
別解(複数行文字列):
package main
import "fmt"
func main() {
message := `こんにちは
今日からプログラミングを始めます
よろしくお願いします`
fmt.Println(message)
}
> ポイント: バッククォート(` `)で囲むと、複数行の文字列をそのまま記述できます。
---
課題3の解答
問題3-1: PrintとPrintlnの違い
Printlnバージョン:
package main
import "fmt"
func main() {
fmt.Println("りんご")
fmt.Println("みかん")
fmt.Println("ぶどう")
}
出力:
りんご
みかん
ぶどう
Printバージョン:
package main
import "fmt"
func main() {
fmt.Print("りんご")
fmt.Print("みかん")
fmt.Print("ぶどう")
fmt.Println() // 最後に改行を追加
}
出力:
りんごみかんぶどう
違いのまとめ:
| 関数 | 動作 | 使用場面 |
|---|---|---|
| `Println` | 出力後に改行 | 1行ずつ表示したい場合 |
| `Print` | 出力後に改行しない | 連続して同じ行に表示したい場合 |
---
問題3-2: アスキーアート
package main
import "fmt"
func main() {
fmt.Println(" *")
fmt.Println(" ***")
fmt.Println("*****")
fmt.Println(" |")
}
出力:
*
***
*****
|
別解(バッククォート使用):
package main
import "fmt"
func main() {
tree := ` *
***
*****
|`
fmt.Println(tree)
}
応用: より大きなツリー:
package main
import "fmt"
func main() {
fmt.Println(" *")
fmt.Println(" ***")
fmt.Println(" *****")
fmt.Println(" *******")
fmt.Println("*********")
fmt.Println(" |||")
fmt.Println(" |||")
}
---
問題3-3: エラー修正
元のコード(3つのエラーあり):
package main
import fmt // エラー1: ""がない
func main() {
fmt.println(Hello, Go!) // エラー2: println → Println
// エラー3: ""がない
}
修正後のコード:
package main
import "fmt" // 修正1: "fmt"に変更
func main() {
fmt.Println("Hello, Go!") // 修正2: Println(大文字P)
// 修正3: "Hello, Go!"(""で囲む)
}
エラーの詳細解説:
| エラー | 原因 | 修正 |
|---|---|---|
| `import fmt` | パッケージ名は文字列として扱う | `import "fmt"` |
| `fmt.println` | Goは大文字小文字を区別 | `fmt.Println` |
| `Hello, Go!` | 文字列はダブルクォートで囲む | `"Hello, Go!"` |
---
よくある間違いと正しい書き方
1. セミコロン
// NG: 不要なセミコロン
fmt.Println("Hello");
// OK: セミコロン不要
fmt.Println("Hello")
2. 波かっこの位置
// NG: Goではこの書き方は許可されない
func main()
{
fmt.Println("Hello")
}
// OK: 開き波かっこは同じ行に
func main() {
fmt.Println("Hello")
}
3. 文字列のクォート
// NG: シングルクォート
fmt.Println('Hello')
// OK: ダブルクォート
fmt.Println("Hello")
// OK: バッククォート(複数行可)
fmt.Println(`Hello`)
4. 大文字小文字
// NG: 小文字で始まる公開関数はない
fmt.println("Hello")
// OK: 公開関数は大文字で始まる
fmt.Println("Hello")
---
実行時のトラブルシューティング
「command not found: go」と表示される場合
# Goがインストールされているか確認
which go
# なければ再インストール
# macOS:
brew install go
# パスが通っているか確認
echo $PATH
「package main is not in GOROOT」と表示される場合
# カレントディレクトリにmain.goがあるか確認
ls -la
# ファイル名が正しいか確認(.goで終わっているか)
日本語が文字化けする場合
# ファイルのエンコーディングを確認
file main.go
# UTF-8になっていることを確認
# UTF-8以外の場合はエディタで保存時にUTF-8を指定
---
発展学習
これらの解答を理解したら、以下に挑戦してみましょう:
- 数字の表示: fmt.Println(42)
を試す - 計算結果の表示: fmt.Println(1 + 2)
を試す - 変数の使用: name := "太郎"
のように変数を使ってみる
これらは明日以降で詳しく学習します。
---
最適化パス:Hello Worldプログラムの進化
フェーズ1: 最もシンプルな実装
package main
import "fmt"
func main() {
// 最もシンプル:文字列リテラルを直接出力
fmt.Println("Hello, World!")
}
特徴:
- コード行数: 7行
- メモリ使用: 最小限(文字列リテラルのみ)
- 実行速度: 最速(インライン化可能)
- 保守性: 変更が必要な場合は直接編集
フェーズ2: 変数を使った実装
package main
import "fmt"
func main() {
// 変数を導入:再利用可能な構造
message := "Hello, World!"
fmt.Println(message)
}
改善点:
- メッセージの再利用が可能
- 変更が1箇所で済む
- デバッグ時に値の確認が容易
トレードオフ:
- わずかにメモリ使用量が増加(変数分)
- 実行速度はほぼ同等(コンパイラが最適化)
フェーズ3: 関数化による抽象化
package main
import "fmt"
// printGreeting は挨拶メッセージを出力する
// 引数:
// - message: 出力するメッセージ文字列
// 戻り値: なし
func printGreeting(message string) {
fmt.Println(message)
}
func main() {
// 関数を使用:ロジックの分離
printGreeting("Hello, World!")
}
改善点:
- 処理の再利用性が向上
- テストが容易(単体テスト可能)
- 責任の分離(関数は出力のみを担当)
トレードオフ:
- 関数呼び出しのオーバーヘッド(微小)
- コード量が増加
フェーズ4: 設定可能な実装
package main
import (
"fmt"
"os"
)
// Config はアプリケーション設定を保持
type Config struct {
Greeting string // 挨拶メッセージ
Language string // 言語設定
}
// getGreeting は設定に基づいて適切な挨拶を返す
// 引数:
// - cfg: アプリケーション設定
// 戻り値:
// - string: 選択された言語の挨拶メッセージ
func getGreeting(cfg Config) string {
// 言語に応じたメッセージを返す
switch cfg.Language {
case "ja":
return "こんにちは、世界!"
case "es":
return "¡Hola, Mundo!"
case "fr":
return "Bonjour, le monde!"
default:
return cfg.Greeting
}
}
func main() {
// 環境変数から言語を取得(デフォルトは"en")
lang := os.Getenv("LANG")
if lang == "" {
lang = "en"
}
// 設定を初期化
cfg := Config{
Greeting: "Hello, World!",
Language: lang,
}
// 挨拶を取得して出力
message := getGreeting(cfg)
fmt.Println(message)
}
改善点:
- 国際化対応
- 環境に応じた動作変更
- 拡張性の向上
トレードオフ:
- 複雑性の増加
- メモリ使用量の増加
- シンプルなタスクには過剰
最適化の選択基準
| 実装 | 適用場面 | 理由 |
|---|---|---|
| フェーズ1 | 学習、プロトタイプ、ワンライナー | 最もシンプルで理解しやすい |
| フェーズ2 | 小規模スクリプト、単一目的ツール | 適度な柔軟性と読みやすさ |
| フェーズ3 | 中規模アプリ、テストが必要な場合 | 再利用性とテスト容易性 |
| フェーズ4 | 大規模アプリ、国際化が必要 | スケーラビリティと保守性 |
---
よくある間違いと解決策
1. パッケージ宣言の誤り
間違い:
// NG: パッケージ名が誤っている
package hello
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
エラーメッセージ:
package command-line-arguments is not a main package
原因分析:
- 実行可能なプログラムは必ずpackage main
でなければならない - パッケージ名とディレクトリ名の混同
正しい実装:
// OK: 実行可能プログラムはpackage main
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
教訓:
- package main
:実行可能プログラム - その他のパッケージ名:ライブラリ
---
2. import文の書き方
間違い:
package main
// NG: クォートなし
import fmt
func main() {
fmt.Println("Hello, World!")
}
エラーメッセージ:
syntax error: unexpected name, expecting string
正しい実装(単一import):
package main
// OK: ダブルクォートで囲む
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
正しい実装(複数import):
package main
// OK: 複数の場合はまとめて記述
import (
"fmt"
"strings"
)
func main() {
message := strings.ToUpper("hello")
fmt.Println(message)
}
ベストプラクティス:
package main
import (
// 標準ライブラリ
"fmt"
"os"
"strings"
// サードパーティライブラリ(空行で区切る)
"github.com/user/package"
)
---
3. main関数の定義
間違い1: シグネチャが違う
package main
import "fmt"
// NG: main関数は引数を取らない
func main(args []string) {
fmt.Println("Hello, World!")
}
間違い2: 戻り値を指定
package main
import "fmt"
// NG: main関数は戻り値を返さない
func main() int {
fmt.Println("Hello, World!")
return 0
}
正しい実装:
package main
import (
"fmt"
"os"
)
func main() {
// OK: 引数はos.Argsで取得
// OK: 終了コードはos.Exit()で指定
fmt.Println("Hello, World!")
// 正常終了の場合は不要(デフォルトで0)
// 異常終了の場合のみ明示的に指定
if someError {
os.Exit(1)
}
}
---
4. 文字列のクォート
間違い:
package main
import "fmt"
func main() {
// NG: シングルクォートは文字(rune)用
fmt.Println('Hello, World!')
}
エラーメッセージ:
illegal rune literal
正しい実装:
package main
import "fmt"
func main() {
// OK: ダブルクォート(文字列)
fmt.Println("Hello, World!")
// OK: バッククォート(生文字列、エスケープ不要)
fmt.Println(`Hello, World!`)
// OK: シングルクォート(単一文字/rune)
char := 'A'
fmt.Println(char) // 65(ASCIIコード)が出力される
}
使い分けガイド:
package main
import "fmt"
func main() {
// ダブルクォート:エスケープシーケンス解釈
str1 := "Hello\nWorld" // 改行が含まれる
// バッククォート:エスケープ不要
str2 := `Hello\nWorld` // \nがそのまま表示される
// シングルクォート:文字(rune型、int32のエイリアス)
char := 'あ' // Unicode文字
fmt.Println(str1)
fmt.Println(str2)
fmt.Println(char) // 12354(Unicodeコードポイント)
}
---
5. 波かっこの位置
間違い:
package main
import "fmt"
// NG: Goでは開き波かっこは同じ行に書く必要がある
func main()
{
fmt.Println("Hello, World!")
}
エラーメッセージ:
syntax error: unexpected semicolon or newline before {
原因:
- Goコンパイラは自動的にセミコロンを挿入する
- 改行があるとfunc main();
と解釈される
正しい実装:
package main
import "fmt"
// OK: 開き波かっこは同じ行
func main() {
fmt.Println("Hello, World!")
}
Goのスタイルガイド:
// すべて開き波かっこは同じ行
if condition {
// ...
}
for i := 0; i < 10; i++ {
// ...
}
switch value {
case 1:
// ...
}
---
6. 未使用のimport
間違い:
package main
import (
"fmt"
"strings" // 使用していない
)
func main() {
fmt.Println("Hello, World!")
}
エラーメッセージ:
imported and not used: "strings"
原因:
- Goは未使用のimportを許可しない
- コードの品質維持のため
解決策1: 不要なimportを削除
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
解決策2: ブランク識別子(デバッグ時のみ)
package main
import (
"fmt"
_ "strings" // 副作用のためにimport(通常は使わない)
)
func main() {
fmt.Println("Hello, World!")
}
ツールによる自動修正:
# goimportsを使って自動整理
go install golang.org/x/tools/cmd/goimports@latest
goimports -w main.go
---
ベンチマークとパフォーマンス分析
実行速度の測定
テストプログラム:
package main
import (
"fmt"
"time"
)
func main() {
// 開始時刻を記録
start := time.Now()
// Hello Worldを出力
fmt.Println("Hello, World!")
// 経過時間を計算
elapsed := time.Since(start)
fmt.Printf("実行時間: %v\n", elapsed)
}
実行結果例:
Hello, World!
実行時間: 47.167µs
複数の実装パターンの比較
ベンチマークコード:
package main
import (
"fmt"
"testing"
)
// 方法1: Println使用
func BenchmarkPrintln(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Println("Hello, World!")
}
}
// 方法2: Printf使用
func BenchmarkPrintf(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Printf("%s\n", "Hello, World!")
}
}
// 方法3: Print + 改行
func BenchmarkPrintWithNewline(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Print("Hello, World!\n")
}
}
ベンチマーク実行:
go test -bench=. -benchmem
結果例:
BenchmarkPrintln-8 1000000 1147 ns/op 16 B/op 1 allocs/op
BenchmarkPrintf-8 1000000 1203 ns/op 16 B/op 1 allocs/op
BenchmarkPrintWithNewline-8 1000000 1089 ns/op 16 B/op 1 allocs/op
分析:
- Print
が最も高速(フォーマット処理が最小) - Printf
が最も遅い(フォーマット文字列の解析が必要) - メモリ使用量はほぼ同等
出力先による性能差
package main
import (
"fmt"
"io"
"os"
"testing"
)
// 標準出力
func BenchmarkStdout(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Fprintln(os.Stdout, "Hello, World!")
}
}
// /dev/null(出力を破棄)
func BenchmarkDevNull(b *testing.B) {
devNull, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0666)
defer devNull.Close()
for i := 0; i < b.N; i++ {
fmt.Fprintln(devNull, "Hello, World!")
}
}
// io.Discard(最も高速)
func BenchmarkDiscard(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Fprintln(io.Discard, "Hello, World!")
}
}
教訓:
- 実際の出力先によって性能が大きく変わる
- テスト環境ではio.Discard`が有用
---
プロダクションでの考慮事項
1. ログ出力の実装
開発環境:
package main
import "fmt"
func main() {
// 開発時はシンプルなfmt.Printlnで十分
fmt.Println("Application started")
fmt.Println("Processing data...")
fmt.Println("Done")
}
本番環境:
package main
import (
"log"
"os"
)
func main() {
// ログレベル、タイムスタンプ、ファイル・行番号を含む
log.SetFlags(log.LstdFlags | log.Lshortfile)
// ログファイルへの出力
logFile, err := os.OpenFile("app.log",
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
defer logFile.Close()
log.SetOutput(logFile)
log.Println("Application started")
log.Println("Processing data...")
log.Println("Done")
}
2. エラーハンドリング
シンプル版:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
本番向け:
package main
import (
"fmt"
"os"
)
func main() {
// エラーハンドリングを含む
_, err := fmt.Println("Hello, World!")
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
3. 国際化対応
package main
import (
"fmt"
"os"
)
var messages = map[string]string{
"en": "Hello, World!",
"ja": "こんにちは、世界!",
"es": "¡Hola, Mundo!",
"fr": "Bonjour, le monde!",
"de": "Hallo, Welt!",
"zh": "你好,世界!",
}
func getMessage(lang string) string {
if msg, ok := messages[lang]; ok {
return msg
}
return messages["en"] // デフォルトは英語
}
func main() {
// 環境変数から言語を取得
lang := os.Getenv("LANGUAGE")
if lang == "" {
lang = "en"
}
fmt.Println(getMessage(lang))
}
使用例:
# 日本語で実行
LANGUAGE=ja go run main.go
# こんにちは、世界!
# スペイン語で実行
LANGUAGE=es go run main.go
# ¡Hola, Mundo!
4. 設定ファイルからの読み込み
package main
import (
"encoding/json"
"fmt"
"os"
)
type Config struct {
Message string `json:"message"`
Language string `json:"language"`
}
func loadConfig(filename string) (*Config, error) {
file, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var config Config
if err := json.Unmarshal(file, &config); err != nil {
return nil, err
}
return &config, nil
}
func main() {
config, err := loadConfig("config.json")
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
os.Exit(1)
}
fmt.Println(config.Message)
}
config.json:
{
"message": "Hello, Production World!",
"language": "en"
}
---
セキュリティ考慮事項
1. ユーザー入力の検証
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("名前を入力してください: ")
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
// 入力検証
if len(name) == 0 {
fmt.Println("エラー: 名前が空です")
return
}
if len(name) > 100 {
fmt.Println("エラー: 名前が長すぎます")
return
}
// サニタイズ(特殊文字の除去)
// 本番環境では適切なバリデーションライブラリを使用
fmt.Printf("こんにちは、%sさん!\n", name)
}
2. 機密情報の出力回避
package main
import (
"fmt"
"os"
)
func main() {
// NG: パスワードをログに出力
password := os.Getenv("DB_PASSWORD")
fmt.Printf("Password: %s\n", password)
// OK: 機密情報はマスク
if password != "" {
fmt.Println("Password: ********")
}
}
---
まとめと次のステップ
習得すべきスキルレベル
| レベル | 内容 | 確認方法 |
|---|---|---|
| 初級 | 基本的なHello Worldが書ける | このページの基本問題を解く |
| 中級 | エラーを読んで修正できる | よくある間違いセクションを理解 |
| 上級 | 目的に応じた実装を選択できる | 最適化パスを理解して応用 |
| 実務 | 本番環境を考慮した実装ができる | プロダクション考慮事項を実装 |
次に学ぶべきトピック
- 変数と定数 (Day 2)
- 制御構文 (Day 3-4)
- 関数 (Day 5)