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)
- 処理の再利用 - テスト可能な設計