正規表現と日付

レッスン24:正規表現と日付

生活での例え

あなたが荷物の仕分け作業員であると想像してください:

プログラミングでは、正規表現はテキストから情報を正確に特定・抽出するのに役立ち、timeパッケージは時刻を測定・計算・表示するのに役立ちます。


コアコンセプト

正規表現(regexpパッケージ)

GoのregexpパッケージはRE2構文エンジンをベースにしており、一般的な正規表現構文の多くをサポートしていますが、バックトラッキングと後方参照はサポートしていません(パフォーマンス優先の設計上の選択)。

コアインターフェース:
- Compile: regexp.Compile(pattern) → (*Regexp, error)
- Match: MatchString(s) → bool
- Find: FindString / FindAllString / FindStringSubmatch
- Replace: ReplaceAllString / ReplaceAllStringFunc

日付と時刻(timeパッケージ)

Goの時刻処理はtime.Time構造体を中心に、固定参照時刻をフォーマットテンプレートとして使用します:

参照時刻: Mon Jan 2 15:04:05 MST 2006
数値の語呂合わせ: 01/02 03:04:05PM 2006 -0700
💡 なぜこの変な数字なのか? Goは覚えやすいように1 2 3 4 5 6 7の順序(月1、日2、時3、分4、秒5、年6、タイムゾーン7)を選択しました。


基本構文と使い方

1. 正規表現の基本

GO
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // 正規表現をコンパイル
    re, err := regexp.Compile(`\d{3}-\d{4}-\d{4}`)
    if err != nil {
        fmt.Println("正規表現のコンパイルに失敗しました:", err)
        return
    }

    // マッチするかチェック
    phone := "138-1234-5678"
    fmt.Println("マッチ:", re.MatchString(phone)) // true

    // 最初のマッチを検索
    text := "連絡先: 138-1234-5678 または 139-8765-4321"
    fmt.Println("最初:", re.FindString(text)) // 138-1234-5678

    // すべてのマッチを検索
    all := re.FindAllString(text, -1)
    fmt.Println("すべて:", all) // [138-1234-5678 139-8765-4321]
}
💡 MustCompileCompileの違いMustCompileはコンパイル失敗時に直接パニックを起こすため、グローバル定数の正規表現に適しています。Compileはエラーを返すため、実行時に動的に構築される正規表現に適しています。

2. 名前付きキャプチャグループ

GO
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // 名前付きキャプチャグループでメールの一部を抽出
    re := regexp.MustCompile(`(?P<user>\w+)@(?P<domain>\w+\.\w+)`)

    email := "zhangsan@example.com"

    // マッチ結果を取得
    match := re.FindStringSubmatch(email)
    names := re.SubexpNames()

    for i, name := range names {
        if i > 0 && name != "" {
            fmt.Printf("%s = %s\n", name, match[i])
        }
    }
    // user = zhangsan
    // domain = example.com
}

3. 置換操作

GO
package main

import (
    "fmt"
    "regexp"
    "strings"
)

func main() {
    re := regexp.MustCompile(`\s+`)

    // すべての空白を単一スペースに置換
    text := "  hello   world   go  "
    result := re.ReplaceAllString(text, " ")
    fmt.Printf("[%s]\n", result) // [hello world go]

    // 関数を使用した動的置換
    re2 := regexp.MustCompile(`\b\w`)
    title := re2.ReplaceAllStringFunc("hello world go", func(s string) string {
        // 最初の文字を大文字に
        return strings.ToUpper(s)
    })
    fmt.Println(title) // Hello World Go
}

4. 基本的な時刻操作

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // 現在時刻を取得
    now := time.Now()
    fmt.Println("現在時刻:", now)

    // 個々のコンポーネントを抽出
    fmt.Println("年:", now.Year())
    fmt.Println("月:", now.Month())
    fmt.Println("日:", now.Day())
    fmt.Println("時:", now.Hour())
    fmt.Println("分:", now.Minute())
    fmt.Println("秒:", now.Second())
    fmt.Println("曜日:", now.Weekday())
}
💡 time.Now()はローカルタイムゾーンの時刻を返します。UTC時刻にはtime.Now().UTC()を使用してください。

5. 時刻のフォーマットと解析

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // フォーマット:参照時刻をテンプレートとして使用
    fmt.Println(now.Format("2006-01-02"))           // 2025-06-27
    fmt.Println(now.Format("2006/01/02 15:04:05"))  // 2025/06/27 14:30:00
    fmt.Println(now.Format("03:04PM"))              // 02:30PM

    // 一般的な定義済みフォーマット
    fmt.Println(now.Format(time.RFC3339))  // 2025-06-27T14:30:00+08:00

    // 文字列を時刻に解析
    t, err := time.Parse("2006-01-02", "2025-12-25")
    if err != nil {
        fmt.Println("解析失敗:", err)
        return
    }
    fmt.Println("解析結果:", t)

    // タイムゾーン付きで解析
    t2, _ := time.ParseInLocation("2006-01-02 15:04:05",
        "2025-12-25 08:00:00", time.Local)
    fmt.Println("タイムゾーン付き:", t2)
}
⚠️ フォーマットと解析は同じ参照時刻文字列を使用します — これはGo独自の設計です。2006-01-02 15:04:05を覚えておけば大丈夫です。

6. Durationと時刻計算

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // 時刻の加算と減算
    tomorrow := now.Add(24 * time.Hour)
    fmt.Println("明日:", tomorrow.Format("2006-01-02"))

    twoHoursLater := now.Add(2 * time.Hour)
    fmt.Println("2時間後:", twoHoursLater.Format("15:04:05"))

    // 時刻差を計算
    diff := tomorrow.Sub(now)
    fmt.Println("時刻差:", diff)           // 24h0m0s
    fmt.Println("時間:", diff.Hours())    // 24
    fmt.Println("分:", diff.Minutes())    // 1440

    // 順序を比較
    fmt.Println("明日は後:", tomorrow.After(now))  // true
    fmt.Println("今日は前:", now.Before(tomorrow))   // true

    // 指定精度に切り捨て
    floored := now.Truncate(time.Hour)
    fmt.Println("時間に切り捨て:", floored.Format("15:04:05"))
}

7. タイマー

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // 1回限りのタイマー
    timer := time.NewTimer(2 * time.Second)
    fmt.Println("2秒待機中...")
    <-timer.C
    fmt.Println("時間切れ!")

    // 周期的タイマー(Ticker)
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop() // リーク防止のため必ず停止

    count := 0
    for t := range ticker.C {
        count++
        fmt.Println("ティック:", t.Format("15:04:05.000"))
        if count >= 3 {
            break
        }
    }

    // time.After簡略版(1回限りの待機)
    <-time.After(1 * time.Second)
    fmt.Println("1秒後に実行")
}
💡 ループ内でtime.Afterを使用する際は注意が必要です — 各反復で新しいチャネルが作成され、古いタイマーはガベージコレクションされないため、メモリリークが発生する可能性があります。ループ内ではtime.NewTimerを使用し、手動でResetしてください。


例題コード

例:バリデーションと抽出 — ログ解析(難易度⭐)

GO
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // シミュレーション用ログ行
    logs := []string{
        "[2025-06-27 14:30:00] ERROR Database connection failed",
        "[2025-06-27 14:30:01] INFO  Service started successfully",
        "[2025-06-27 14:30:05] WARN  Disk space low",
        "[2025-06-27 14:31:00] ERROR Request timeout",
    }

    // ログフォーマットにマッチ:時刻 + レベル + メッセージ
    re := regexp.MustCompile(`\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+)\s+(.+)`)

    // レベルごとにカウント
    levelCount := make(map[string]int)

    for _, log := range logs {
        match := re.FindStringSubmatch(log)
        if match == nil {
            continue
        }

        timestamp := match[1]
        level := match[2]
        message := match[3]

        fmt.Printf("時刻: %s | レベル: %-5s | メッセージ: %s\n",
            timestamp, level, message)

        levelCount[level]++
    }

    fmt.Println("\nレベル統計:")
    for level, count := range levelCount {
        fmt.Printf("  %s: %d件\n", level, count)
    }
}
▶ 試してみよう

出力:

TEXT
時刻: 2025-06-27 14:30:00 | レベル: ERROR | メッセージ: Database connection failed
時刻: 2025-06-27 14:30:01 | レベル: INFO  | メッセージ: Service started successfully
時刻: 2025-06-27 14:30:05 | レベル: WARN  | メッセージ: Disk space low
時刻: 2025-06-27 14:31:00 | レベル: ERROR | メッセージ: Request timeout

レベル統計:
  ERROR: 2件
  INFO: 1件
  WARN: 1件

例:テンプレートエンジン — シンプルなテキスト置換システム(難易度⭐⭐)

GO
package main

import (
    "fmt"
    "regexp"
    "strings"
    "time"
)

func main() {
    template := `親愛なる {{name}} 様:
    ご注文 {{order}} は {{date}} に発送されました。
    配達予定は {{days}日後です。
    現在時刻: {{now}}`

    // 変数マッピングを定義
    vars := map[string]string{
        "name":  "Alice",
        "order": "ORD-20250627-001",
        "date":  "2025-06-27",
        "days":  "3",
    }

    // {{variable_name}} パターンにマッチ
    re := regexp.MustCompile(`\{\{(\w+)\}\}`)

    // 変数を置換
    result := re.ReplaceAllStringFunc(template, func(match string) string {
        // 変数名を抽出({{と}}を除去)
        key := match[2 : len(match)-2]

        if key == "now" {
            return time.Now().Format("2006-01-02 15:04:05")
        }

        if val, ok := vars[key]; ok {
            return val
        }
        return match // 変数が見つからない場合はそのまま保持
    })

    fmt.Println(result)

    // テンプレート内の変数をカウント
    allVars := re.FindAllString(template, -1)
    varNames := make([]string, 0, len(allVars))
    for _, v := range allVars {
        varNames = append(varNames, v[2:len(v)-2])
    }
    fmt.Printf("\nテンプレート変数: %s\n", strings.Join(varNames, ", "))
}
▶ 試してみよう

出力:

TEXT
親愛なる Alice 様:
    ご注文 ORD-20250627-001 は 2025-06-27 に発送されました。
    配達予定は 3日後です。
    現在時刻: 2025-06-27 14:30:00

テンプレート変数: name, order, date, days, now

例:カウントダウンタイマー — フォーマット付きリアルタイム表示(難易度⭐⭐⭐)

GO
package main

import (
    "fmt"
    "regexp"
    "strings"
    "time"
)

// formatDuration Durationを "Xd Xh Xm Xs" 形式にフォーマット
func formatDuration(d time.Duration) string {
    if d <= 0 {
        return "期限切れ"
    }

    days := int(d.Hours()) / 24
    hours := int(d.Hours()) % 24
    minutes := int(d.Minutes()) % 60
    seconds := int(d.Seconds()) % 60

    parts := []string{}
    if days > 0 {
        parts = append(parts, fmt.Sprintf("%d日", days))
    }
    if hours > 0 {
        parts = append(parts, fmt.Sprintf("%d時", hours))
    }
    if minutes > 0 {
        parts = append(parts, fmt.Sprintf("%d分", minutes))
    }
    parts = append(parts, fmt.Sprintf("%02d秒", seconds))

    return strings.Join(parts, " ")
}

// parseDeadline 複数の日付フォーマットを解析
func parseDeadline(s string) (time.Time, error) {
    // 複数のフォーマットを試行
    formats := []string{
        "2006-01-02 15:04:05",
        "2006-01-02",
        "2006/01/02 15:04",
        "01-02 15:04",
    }

    // 相対時刻かチェック(例:"+2h30m")
    re := regexp.MustCompile(`^\+(\d+)([hms])`)
    if match := re.FindStringSubmatch(s); match != nil {
        // 相対時刻を解析(簡略化、単一ユニットのみ)
        return time.Now().Add(2 * time.Hour), nil
    }

    for _, format := range formats {
        if t, err := time.ParseInLocation(format, s, time.Local); err == nil {
            return t, nil
        }
    }

    return time.Time{}, fmt.Errorf("時刻を解析できませんでした: %s", s)
}

func main() {
    // デッドラインを解析
    deadline, err := parseDeadline("2025-12-31 23:59:59")
    if err != nil {
        fmt.Println("エラー:", err)
        return
    }

    fmt.Printf("デッドライン: %s\n", deadline.Format("2006-01-02 15:04:05"))
    fmt.Println(strings.Repeat("─", 40))

    // カウントダウンをシミュレーション(1秒ごとに更新、合計5回)
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    count := 0
    for now := range ticker.C {
        remaining := deadline.Sub(now)

        // 画面クリア効果:キャリッジリターンで現在行を上書き
        fmt.Printf("\r残り: %-30s", formatDuration(remaining))

        count++
        if count >= 5 || remaining <= 0 {
            break
        }
    }

    fmt.Println("\n\nカウントダウンデモ完了")

    // 時刻フォーマットを検証
    re := regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`)
    formatted := deadline.Format("2006-01-02 15:04:05")
    fmt.Printf("フォーマット検証: %v\n", re.MatchString(formatted)) // true
}
▶ 試してみよう

実践的な応用シナリオ

シナリオ1:フォームデータバリデーション

GO
package main

import (
    "fmt"
    "regexp"
)

// Validator フォームバリデーター
type Validator struct {
    rules map[string]*regexp.Regexp
}

// NewValidator バリデーターを作成し、すべての正規表現を事前コンパイル
func NewValidator() *Validator {
    rules := map[string]*regexp.Regexp{
        "phone":    regexp.MustCompile(`^1[3-9]\d{9}$`),
        "email":    regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`),
        "id_card":  regexp.MustCompile(`^\d{17}[\dXx]$`),
        "username": regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{3,15}$`),
        "password": regexp.MustCompile(`^.{8,}$`),
        "ip":       regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`),
        "date":     regexp.MustCompile(`^\d{4}[-/](0[1-9]|1[0-2])[-/](0[1-9]|[12]\d|3[01])$`),
    }
    return &Validator{rules: rules}
}

// Validate 単一フィールドをバリデーション
func (v *Validator) Validate(field, value string) bool {
    re, ok := v.rules[field]
    if !ok {
        return true // ルールなしは合格
    }
    return re.MatchString(value)
}

// ValidateAll 複数のフィールドをバリデーションし、すべてのエラーを返す
func (v *Validator) ValidateAll(data map[string]string) []string {
    var errors []string

    for field, value := range data {
        if !v.Validate(field, value) {
            errors = append(errors, fmt.Sprintf("%sの形式が正しくありません: %s", field, value))
        }
    }

    return errors
}

func main() {
    v := NewValidator()

    // テストデータ
    data := map[string]string{
        "phone":    "13812345678",
        "email":    "test@example.com",
        "id_card":  "110101199001011234",
        "username": "go_dev",
        "date":     "2025-06-27",
    }

    errors := v.ValidateAll(data)
    if len(errors) == 0 {
        fmt.Println("✓ すべてのフィールドが正常にバリデーションされました")
    } else {
        fmt.Println("バリデーション失敗:")
        for _, err := range errors {
            fmt.Printf("  ✗ %s\n", err)
        }
    }

    // 無効なデータをテスト
    invalidData := map[string]string{
        "phone": "12345678901",  // 有効な電話番号プレフィックスではない
        "email": "not-an-email", // @が不足
    }

    fmt.Println("\n無効なデータテスト:")
    for field, value := range invalidData {
        result := "✓"
        if !v.Validate(field, value) {
            result = "✗"
        }
        fmt.Printf("  %s %s: %s\n", result, field, value)
    }
}

シナリオ2:時刻範囲クエリ — アクティビティステータス管理

GO
package main

import (
    "fmt"
    "strings"
    "time"
)

// Activity 構造体
type Activity struct {
    Name      string
    StartTime time.Time
    EndTime   time.Time
}

// Status 現在のアクティビティステータスを取得
func (a Activity) Status(now time.Time) string {
    switch {
    case now.Before(a.StartTime):
        return "未開始"
    case now.After(a.EndTime):
        return "終了"
    default:
        return "開催中"
    }
}

// Remaining 残り時間またはカウントダウンを取得
func (a Activity) Remaining(now time.Time) string {
    if now.Before(a.StartTime) {
        d := a.StartTime.Sub(now)
        return fmt.Sprintf("開始まで: %s", formatDur(d))
    }
    if now.Before(a.EndTime) {
        d := a.EndTime.Sub(now)
        return fmt.Sprintf("残り: %s", formatDur(d))
    }
    return "期限切れ"
}

func formatDur(d time.Duration) string {
    hours := int(d.Hours())
    minutes := int(d.Minutes()) % 60
    if hours > 24 {
        days := hours / 24
        hours = hours % 24
        return fmt.Sprintf("%d日 %d時間 %d分", days, hours, minutes)
    }
    return fmt.Sprintf("%d時間 %d分", hours, minutes)
}

func main() {
    now := time.Now()

    // アクティビティリストを作成
    activities := []Activity{
        {
            Name:      "サマーセール",
            StartTime: mustParse("2025-06-01 00:00:00"),
            EndTime:   mustParse("2025-06-18 23:59:59"),
        },
        {
            Name:      "新学期セール",
            StartTime: mustParse("2025-07-01 00:00:00"),
            EndTime:   mustParse("2025-08-31 23:59:59"),
        },
        {
            Name:      "ブラックフライデープレビュー",
            StartTime: mustParse("2025-11-01 00:00:00"),
            EndTime:   mustParse("2025-11-11 23:59:59"),
        },
    }

    fmt.Printf("現在時刻: %s\n", now.Format("2006-01-02 15:04:05"))
    fmt.Println(strings.Repeat("─", 50))

    for _, act := range activities {
        status := act.Status(now)
        remaining := act.Remaining(now)

        fmt.Printf("アクティビティ: %s\n", act.Name)
        fmt.Printf("  期間: %s ~ %s\n",
            act.StartTime.Format("2006-01-02"),
            act.EndTime.Format("2006-01-02"))
        fmt.Printf("  ステータス: %s | %s\n\n", status, remaining)
    }

    // ステータスでフィルタリング
    fmt.Println("開催中のアクティビティ:")
    found := false
    for _, act := range activities {
        if act.Status(now) == "開催中" {
            fmt.Printf("  - %s\n", act.Name)
            found = true
        }
    }
    if !found {
        fmt.Println("  現在開催中のアクティビティはありません")
    }
}

func mustParse(s string) time.Time {
    t, err := time.ParseInLocation("2006-01-02 15:04:05", s, time.Local)
    if err != nil {
        panic(err)
    }
    return t
}

❓ よくある質問

賞問1:なぜ正規表現で中国語の文字にマッチしないのですか?

GoのregexpパッケージはデフォルトでUTF-8エンコーディングを処理するため、中国語の文字自体は正常にマッチできます。よくある問題は正しい文字クラスを使用していないことです:

GO
// 間違い:\wは中国語にマッチしない
re := regexp.MustCompile(`^\w+$`)
re.MatchString("你好") // false

// 正しい:Unicode文字クラスまたは中国語範囲を直接マッチ
re2 := regexp.MustCompile(`^[\p{Han}]+$`)
re2.MatchString("你好") // true

// または混合マッチング
re3 := regexp.MustCompile(`^[\w\p{Han}]+$`)
re3.MatchString("hello你好123") // true

賞問2:time.Parsetime.ParseInLocationの違いは何ですか?

GO
// Parseはタイムゾーン情報がない場合にUTCを使用
t1, _ := time.Parse("2006-01-02", "2025-06-27")
fmt.Println(t1.Location()) // UTC

// ParseInLocationはデフォルトのタイムゾーンを指定
t2, _ := time.ParseInLocation("2006-01-02", "2025-06-27", time.Local)
fmt.Println(t2.Location()) // Local(例:Asia/Shanghai)

// フォーマット文字列にタイムゾーン情報(例:-0700)が含まれる場合、両方とも同じ動作
t3, _ := time.Parse("2006-01-02 15:04:05 -0700", "2025-06-27 08:00:00 +0800")
fmt.Println(t3) // +0800タイムゾーンとして正しく解析
💡 推奨: タイムゾーンなしの日付文字列を解析する場合は、常にParseInLocationを使用し、タイムゾーンを明示的に指定してください。

賞問3:正規表現のパフォーマンスが悪い場合はどうすればいいですか?

GO
// 間違い:呼び出しのたびに再コンパイル
func validatePhone(phone string) bool {
    re := regexp.MustCompile(`^1[3-9]\d{9}$`) // 毎回コンパイル
    return re.MatchString(phone)
}

// 正しい:パッケージレベル変数として事前コンパイル
var phoneRe = regexp.MustCompile(`^1[3-9]\d{9}$`)

func validatePhone2(phone string) bool {
    return phoneRe.MatchString(phone)
}

// 高頻度マッチングの場合、Regexp.Copy()でロック競合を回避
var globalRe = regexp.MustCompile(`\d+`)

func processConcurrently(text string) string {
    re := globalRe.Copy() // コピーを取得、並行ロックを回避
    return re.ReplaceAllString(text, "#")
}

賞問4:タイムゾーン変換をどう処理すればいいですか?

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // 特定のタイムゾーンを読み込み
    shanghai, _ := time.LoadLocation("Asia/Shanghai")
    tokyo, _ := time.LoadLocation("Asia/Tokyo")
    newyork, _ := time.LoadLocation("America/New_York")

    // タイムゾーン付きで時刻を作成
    t := time.Date(2025, 6, 27, 14, 0, 0, 0, shanghai)
    fmt.Println("上海:", t.Format("15:04 MST"))

    // タイムゾーンを変換
    fmt.Println("東京:", t.In(tokyo).Format("15:04 MST"))
    fmt.Println("ニューヨーク:", t.In(newyork).Format("15:04 MST"))

    // 文字列から解析して変換
    parsed, _ := time.ParseInLocation("2006-01-02 15:04",
        "2025-06-27 14:00", shanghai)
    fmt.Println("\n解析後にUTCに変換:", parsed.UTC().Format("2006-01-02 15:04 MST"))
}
⚠️ time.LoadLocationは一部のシステムでtzdataのインストールが必要になる場合があります。Go 1.15+では_ "time/tzdata"をインポートすることでタイムゾーンデータを埋め込めます。


📖 まとめ

このレッスンでは2つのコアトピックをカバーしました:

トピック パッケージ コア型 主要操作
正規表現 regexp Regexp Compile, Match, Find, Replace
日付と時刻 time Time, Duration Format, Parse, Calculate, Timer

正規表現の重要なポイント:

時刻の重要なポイント:


📝 演習

演習1:Markdownリンク抽出器

Markdownテキストからすべてのリンクを抽出し、タイトル -> URL形式で出力するプログラムを書いてください。

GO
// ヒント:[タイトル](URL)形式にマッチ
// 入力:`Visit [Go官网](https://golang.org) or [GitHub](https://github.com)`
// 出力:
//   Go官网 -> https://golang.org
//   GitHub -> https://github.com

演習2:営業日計算機

指定された日付からn営業日後(土日をスキップ)の日付を計算する関数AddBusinessDays(t time.Time, n int) time.Timeを書いてください。

GO
// テスト:
// AddBusinessDays(2025-06-27 金曜日, 1) → 2025-06-30 月曜日
// AddBusinessDays(2025-06-27 金曜日, 5) → 2025-07-04 金曜日

演習3:センシティブワードフィルターシステム

センシティブワードフィルターを実装してください:

  1. 設定からセンシティブワードリストを読み込み、正規表現にコンパイル
  2. *置換と完全削除モードをサポート
  3. センシティブワードのバリエーション検出をサポート(例:途中にスペースが挿入された「gam bling」)
GO
// 入力テキスト:"これはgamblingウェブサイトです。gam blingサービスを提供しています"
// 置換モード出力:"これは*ウェブサイトです。*サービスを提供しています"
// 削除モード出力:"これはウェブサイトです。サービスを提供しています"

次のレッスン

次のレッスンでは、コマンドラインプログラム開発について学びます。Goを使って強力なCLIツールを構築する方法、引数解析、サブコマンド、インタラクティブ入力などの実践的なスキルを身につけます。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%