CLI開発 - 講義

概要

コマンドラインインターフェース(CLI)ツールは、開発者の日常業務を効率化する重要なツールです。Goは高速なコンパイルとシングルバイナリ生成が特徴で、CLIツール開発に最適です。本講義では、Unix哲学からモダンなCLI開発まで、包括的な知識と実践的スキルを身につけます。

技術の歴史と発展

Unix哲学とCLI

1970年代、Ken ThompsonとDennis RitchieによってUnixが開発されて以来、コマンドラインツールは計算機の世界で中心的な役割を果たしてきました。Unix哲学は、以下の原則を掲げています:

  • 一つのことをうまくやる: 各プログラムは一つの機能に特化し、それを完璧にこなす
  • 協調して動作する: 標準入出力を介してプログラム同士を組み合わせる
  • テキストストリームの重視: データ交換には普遍的なテキスト形式を使う
  • シンプルさ: 複雑さよりもシンプルさを優先する

これらの原則は、現代のCLIツール設計にも深く影響を与えています。例えば、grepsedawkなどの伝統的なUnixツールは、50年以上経った今でも日常的に使われています。

# Unix哲学の実践例:複数のツールをパイプで連携
cat access.log | grep "ERROR" | awk '{print $1}' | sort | uniq -c

POSIX標準とポータビリティ

1988年、IEEE(米国電気電子学会)はPOSIX(Portable Operating System Interface)標準を策定しました。POSIX標準は、異なるUnix系OSでも同じように動作するCLIツールの仕様を定めています。

主要な標準化項目:

  • コマンドライン引数の規約: ハイフン(-)で始まる短いオプション、ダブルハイフン(--)で始まる長いオプション
  • 終了コード: 成功時は0、エラー時は1以上
  • 標準ストリーム: stdin(標準入力)、stdout(標準出力)、stderr(標準エラー出力)
  • 環境変数: PATHHOMEUSERなどの共通変数

// POSIX規約に従った終了コードの使用例
func main() {
    if err := run(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)  // エラー時は1
    }
    os.Exit(0)  // 成功時は0
}

GoがCLI開発に選ばれる理由

Goは2009年にGoogleで開発され、以下の特性によりCLI開発の理想的な言語となりました:

1. シングルバイナリ生成

# 一つの実行ファイルにすべてが含まれる
go build -o mytool main.go
./mytool  # 依存関係のインストール不要

他の言語との比較:

  • Python: インタープリタとライブラリが必要
  • Node.js: node_modulesディレクトリが必要
  • Ruby: gemとRubyランタイムが必要
  • Go: 静的リンクされた単一バイナリ

2. クロスコンパイル

# Mac上でLinux向けバイナリをビルド
GOOS=linux GOARCH=amd64 go build -o mytool-linux

# Windows向けバイナリをビルド
GOOS=windows GOARCH=amd64 go build -o mytool.exe

3. 高速な実行速度 Goはコンパイル言語であり、C/C++に匹敵する実行速度を持ちます。大量のファイル処理や並行処理が必要なCLIツールに最適です。

4. 充実した標準ライブラリ

// ファイル操作、ネットワーク、JSON、正規表現など標準で利用可能
import (
    "os"
    "io"
    "net/http"
    "encoding/json"
    "regexp"
)

5. 並行処理の簡潔さ

// goroutineで簡単に並行処理
for _, file := range files {
    go processFile(file)  // 並行実行
}

実社会での活用事例

1. Docker CLI - コンテナ管理の標準ツール

Dockerは、Goで書かれた最も有名なCLIツールの一つです。コンテナの作成、実行、管理を直感的なコマンドで行えます。

docker run -d -p 80:80 nginx        # コンテナ起動
docker ps                           # 実行中のコンテナ一覧
docker logs <container_id>          # ログ表示
docker exec -it <container_id> bash # コンテナ内でコマンド実行

技術的特徴

  • Cobraライブラリ: サブコマンド構造の実装
  • REST APIクライアント: Docker Engineとの通信
  • リッチな出力: テーブル形式、カラー表示、プログレスバー
  • 設定管理: ~/.docker/config.jsonでの認証情報管理

学べるポイント

  • 複雑なサブコマンド階層の設計(docker container runなど)
  • API通信を伴うCLI実装
  • ユーザーフレンドリーな出力フォーマット

2. Kubernetes kubectl - クラスタ管理の中核

Kubernetesの公式CLIツールで、数千のノードを持つクラスタを管理できます。

kubectl get pods                    # Pod一覧
kubectl apply -f deployment.yaml    # リソース適用
kubectl logs pod-name               # ログ確認
kubectl exec -it pod-name -- bash   # Pod内でコマンド実行
kubectl scale deployment my-app --replicas=5  # スケーリング

技術的特徴

  • Cobraライブラリ: 100以上のサブコマンド管理
  • 設定ファイル: ~/.kube/configでのクラスタ認証
  • プラグインシステム: kubectl-pluginによる機能拡張
  • リソース定義: YAML/JSON形式のマニフェスト

学べるポイント

  • 大規模なコマンド体系の整理
  • 設定ファイルとコンテキストの切り替え
  • プラグインアーキテクチャの実装

3. GitHub CLI (gh) - Git操作の効率化

GitHubが公式に提供するCLIツールで、Web UIの操作をコマンドラインで実行できます。

gh repo create my-project --public  # リポジトリ作成
gh pr create --title "Fix bug"      # プルリクエスト作成
gh pr list                          # PR一覧表示
gh pr checkout 123                  # PRをチェックアウト
gh issue create                     # Issue作成(インタラクティブ)

技術的特徴

  • REST/GraphQL API: GitHub APIとの統合
  • インタラクティブモード: プロンプトによる対話的入力
  • 認証管理: OAuth tokenの安全な保存
  • 拡張機能: gh extensionによる機能追加

学べるポイント

  • Web APIとの連携方法
  • インタラクティブなユーザー入力
  • 認証情報の安全な管理
  • 拡張可能なアーキテクチャ

4. Terraform - インフラをコードで管理

HashiCorpが開発したインフラストラクチャ管理ツールで、クラウドリソースをコードで定義・管理します。

terraform init                      # 初期化
terraform plan                      # 変更計画の表示
terraform apply                     # リソース適用
terraform destroy                   # リソース削除
terraform state list                # 状態管理

技術的特徴

  • ステートファイル: terraform.tfstateでのリソース状態管理
  • プロバイダープラグイン: AWS、GCP、Azureなど多様なクラウド対応
  • 依存関係解決: リソース間の依存関係を自動解決
  • 並行処理: 複数リソースの並行作成

学べるポイント

  • 複雑な状態管理の実装
  • プラグインベースの設計
  • 並行処理による高速化
  • 破壊的変更の確認プロセス

5. Hugo - 静的サイトジェネレータ

世界最速の静的サイトジェネレータで、数千ページのサイトを数秒でビルドします。

hugo new site mysite                # 新規サイト作成
hugo new posts/my-post.md           # 記事作成
hugo server -D                      # 開発サーバー起動
hugo                                # サイトビルド
hugo deploy                         # デプロイ

技術的特徴

  • ファイル監視: ホットリロード機能
  • テンプレートエンジン: Go templatesの活用
  • 並行ビルド: goroutineによる高速ビルド
  • 設定管理: TOML/YAML/JSON対応

学べるポイント

  • ファイルシステムの監視
  • ホットリロード機能の実装
  • 高速なビルドパイプライン
  • 柔軟な設定システム

6. その他の著名なGoベースCLIツール

Prometheus (promtool) - メトリクス監視

promtool check config prometheus.yml  # 設定チェック
promtool query instant 'up'           # メトリクスクエリ

etcd (etcdctl) - 分散KVストア

etcdctl put key value     # 値の保存
etcdctl get key           # 値の取得

Consul - サービスメッシュ

consul members            # クラスタメンバー確認
consul kv put mykey value # KV保存

なぜCLI開発を学ぶ価値があるか

1. DevOpsツール開発の基盤

現代のDevOps環境では、CI/CDパイプライン、インフラ管理、モニタリングなど、あらゆる場面でCLIツールが使われます。独自のCLIツールを開発できることは、以下の利点をもたらします:

自動化の推進

# 手動で行っていた複数のタスクを一つのコマンドに
mytool deploy --env production --version v1.2.3
# 内部では以下を自動実行:
# 1. ビルド
# 2. テスト
# 3. コンテナイメージ作成
# 4. レジストリへのプッシュ
# 5. Kubernetesへのデプロイ
# 6. ヘルスチェック

チーム全体の生産性向上

  • 属人化した運用手順をツール化
  • エラーハンドリングの統一
  • 監査ログの自動記録
  • 誰でも同じ手順で実行可能

2. 自動化による生産性向上

日常的に繰り返すタスクをCLIツールにすることで、劇的な時間短縮が可能です。

実例:コードレビュー準備ツール

# 手動で行うと10分かかる作業を1コマンドに
review-prep --jira PROJ-1234
# 自動実行内容:
# 1. 該当Jiraチケットの情報取得
# 2. Gitブランチ作成
# 3. コミットメッセージのテンプレート生成
# 4. PRドラフト作成
# 5. レビュアーの自動割り当て

時間の投資対効果

  • ツール開発:1日(8時間)
  • 1回あたりの時短:9分
  • 週5回使用:45分/週 = 3時間/月
  • 3ヶ月で投資回収、その後は純粋な利益

3. プログラミングスキルの向上

CLI開発を通じて、多様な技術スキルが自然に身につきます:

習得できる技術領域

  • ファイルシステム操作
  • ネットワークプログラミング
  • データ形式の変換(JSON、YAML、TOML)
  • 並行処理とパフォーマンス最適化
  • エラーハンドリングとロギング
  • テスト駆動開発(TDD)
  • ユーザーインターフェース設計

実践的な学習

// ファイル処理、エラーハンドリング、並行処理を一度に学ぶ
func processFiles(files []string) error {
    var wg sync.WaitGroup
    errCh := make(chan error, len(files))

    for _, file := range files {
        wg.Add(1)
        go func(f string) {
            defer wg.Done()
            if err := process(f); err != nil {
                errCh <- fmt.Errorf("failed to process %s: %w", f, err)
            }
        }(file)
    }

    wg.Wait()
    close(errCh)

    for err := range errCh {
        return err
    }
    return nil
}

4. オープンソース貢献の入り口

CLIツールは、オープンソースプロジェクトへの貢献を始めるのに最適な領域です:

貢献のしやすさ

  • 小さく独立した機能単位
  • 明確なインプット・アウトプット
  • テストが書きやすい
  • ドキュメントも重要(ヘルプメッセージなど)

実際の貢献例

// GitHub CLIへの機能追加の例
// 新しいサブコマンド `gh repo sync` の追加
func newRepoSyncCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "sync",
        Short: "Sync forked repository with upstream",
        RunE: func(cmd *cobra.Command, args []string) error {
            // 実装
            return syncWithUpstream()
        },
    }
    return cmd
}

市場価値とキャリア

ツール開発エンジニアの需要

求人市場のトレンド

  • DevOps Engineerの求人の80%以上がCLI/自動化スキルを要求
  • SRE(Site Reliability Engineer)では必須スキル
  • Platform Engineerのコア能力の一つ

求められるスキルセット

  • CLI開発(Go、Rust、Python)
  • CI/CD パイプライン構築
  • Infrastructure as Code(Terraform、Pulumi)
  • コンテナオーケストレーション
  • 監視・ロギング

年収への影響

  • CLIツール開発スキルを持つエンジニアは平均年収が15-20%高い
  • 内製ツールの開発・保守ができることは高評価
  • OSSへの貢献実績は転職市場で強力なアピールポイント

OSSコミュニティでの評価

ポートフォリオとしての価値 自作のCLIツールは、技術力を示す最良のポートフォリオになります:

# GitHubリポジトリの README.md
## mycli - マルチクラウド管理ツール

[![Go Report Card](https://goreportcard.com/badge/github.com/user/mycli)]
[![codecov](https://codecov.io/gh/user/mycli/branch/main/graph/badge.svg)]

### Features
- AWS、GCP、Azureの統一インターフェース
- 設定ファイルによる複数環境管理
- Terraformとの連携
- 詳細なログとエラーハンドリング

### Installation
`go install github.com/user/mycli@latest`

評価されるポイント

  • 実用性: 実際に問題を解決している
  • コード品質: テストカバレッジ、ドキュメント、CI/CD
  • 保守性: 継続的なアップデート、Issue対応
  • ユーザビリティ: 分かりやすいヘルプ、エラーメッセージ

実務での運用考慮点

1. クロスプラットフォーム対応

OS別の考慮事項

// ファイルパスの扱い
import "path/filepath"

// 悪い例:Windowsで動かない
configPath := os.Getenv("HOME") + "/.config/mytool"

// 良い例:全OSで動作
configPath := filepath.Join(os.UserHomeDir(), ".config", "mytool")

OS固有の処理の分離

// config_unix.go
//go:build !windows

func defaultConfigPath() string {
    return filepath.Join(os.Getenv("HOME"), ".config", "mytool")
}

// config_windows.go
//go:build windows

func defaultConfigPath() string {
    return filepath.Join(os.Getenv("APPDATA"), "mytool")
}

2. バイナリ配布戦略

GitHub Releasesの活用

# .github/workflows/release.yml
name: Release
on:
  push:
    tags:
      - 'v*'
jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: goreleaser/goreleaser-action@v4
        with:
          args: release --clean

GoReleaserの設定

# .goreleaser.yml
builds:
  - env:
      - CGO_ENABLED=0
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64

archives:
  - format: tar.gz
    format_overrides:
      - goos: windows
        format: zip

パッケージマネージャー対応

# Homebrew Formula
class Mytool < Formula
  desc "My awesome CLI tool"
  homepage "https://github.com/user/mytool"
  url "https://github.com/user/mytool/archive/v1.0.0.tar.gz"
  sha256 "..."

  depends_on "go" => :build

  def install
    system "go", "build", "-o", bin/"mytool"
  end
end

3. 自動アップデート機能

セルフアップデートの実装

import "github.com/rhysd/go-github-selfupdate/selfupdate"

func doSelfUpdate() error {
    latest, found, err := selfupdate.DetectLatest("user/mytool")
    if err != nil {
        return err
    }

    if !found || latest.Version.LTE(semver.MustParse(version)) {
        fmt.Println("Current version is the latest")
        return nil
    }

    exe, err := os.Executable()
    if err != nil {
        return err
    }

    if err := selfupdate.UpdateTo(latest.AssetURL, exe); err != nil {
        return err
    }

    fmt.Printf("Successfully updated to version %s\n", latest.Version)
    return nil
}

バージョンチェック

func checkForUpdate() {
    current := semver.MustParse(version)
    latest, err := fetchLatestVersion()
    if err != nil {
        return // サイレントに失敗
    }

    if latest.GT(current) {
        fmt.Fprintf(os.Stderr,
            "New version available: %s (current: %s)\n"+
            "Run 'mytool update' to upgrade\n",
            latest, current)
    }
}

開発手法とベストプラクティス

1. Cobraライブラリの使い方

基本構造

package main

import (
    "github.com/spf13/cobra"
    "os"
)

var rootCmd = &cobra.Command{
    Use:   "mytool",
    Short: "My awesome CLI tool",
    Long:  `A comprehensive tool for managing cloud resources`,
}

func main() {
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

サブコマンドの追加

var deployCmd = &cobra.Command{
    Use:   "deploy [flags]",
    Short: "Deploy application to cloud",
    Args:  cobra.NoArgs,
    RunE: func(cmd *cobra.Command, args []string) error {
        return deploy()
    },
}

func init() {
    rootCmd.AddCommand(deployCmd)

    deployCmd.Flags().StringP("env", "e", "dev", "Environment")
    deployCmd.Flags().StringP("version", "v", "", "Version to deploy")
    deployCmd.MarkFlagRequired("version")
}

2. 設定ファイル管理(Viper)

Viperの統合

import (
    "github.com/spf13/viper"
    "github.com/spf13/cobra"
)

func initConfig() {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath("$HOME/.mytool")
    viper.AddConfigPath(".")

    // 環境変数のバインド
    viper.SetEnvPrefix("MYTOOL")
    viper.AutomaticEnv()

    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
            fmt.Fprintf(os.Stderr, "Error reading config: %v\n", err)
        }
    }
}

func init() {
    cobra.OnInitialize(initConfig)
}

設定の優先順位

// 1. フラグ(最優先)
// 2. 環境変数
// 3. 設定ファイル
// 4. デフォルト値(最低優先)

func getAPIKey() string {
    // フラグで指定された値を優先
    if key := viper.GetString("api-key"); key != "" {
        return key
    }

    // 環境変数をチェック
    if key := os.Getenv("MYTOOL_API_KEY"); key != "" {
        return key
    }

    // 設定ファイルの値
    if key := viper.GetString("api.key"); key != "" {
        return key
    }

    // デフォルト値
    return "default-key"
}

3. テスト駆動開発(TDD)

CLIのテスト方法

func TestDeployCommand(t *testing.T) {
    // 標準出力をキャプチャ
    old := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w

    // コマンド実行
    rootCmd.SetArgs([]string{"deploy", "--env", "test", "--version", "v1.0.0"})
    err := rootCmd.Execute()

    // 出力を読み取り
    w.Close()
    os.Stdout = old
    var buf bytes.Buffer
    io.Copy(&buf, r)

    // アサーション
    assert.NoError(t, err)
    assert.Contains(t, buf.String(), "Deployment successful")
}

テーブル駆動テスト

func TestValidateConfig(t *testing.T) {
    tests := []struct {
        name    string
        config  Config
        wantErr bool
    }{
        {
            name:    "valid config",
            config:  Config{APIKey: "key", Region: "us-east-1"},
            wantErr: false,
        },
        {
            name:    "missing API key",
            config:  Config{Region: "us-east-1"},
            wantErr: true,
        },
        {
            name:    "invalid region",
            config:  Config{APIKey: "key", Region: "invalid"},
            wantErr: true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := validateConfig(tt.config)
            if tt.wantErr {
                assert.Error(t, err)
            } else {
                assert.NoError(t, err)
            }
        })
    }
}

ソフトスキル:ユーザー体験の設計

1. 優れたヘルプメッセージの書き方

悪い例

Usage: mytool [options]
Options:
  -f    file
  -v    verbose

良い例

Usage: mytool [command] [flags]

A comprehensive tool for managing cloud resources across AWS, GCP, and Azure.

Available Commands:
  deploy      Deploy application to specified environment
  rollback    Rollback to previous version
  status      Show current deployment status
  logs        Fetch application logs

Flags:
  -h, --help                  Show this help message
  -v, --verbose               Enable verbose logging
      --config string         Config file (default: $HOME/.mytool/config.yaml)

Use "mytool [command] --help" for more information about a command.

Examples:
  # Deploy version 1.2.3 to production
  mytool deploy --env production --version v1.2.3

  # View logs from last 1 hour
  mytool logs --since 1h --follow

For more information, visit: https://github.com/user/mytool

実装例

var deployCmd = &cobra.Command{
    Use:   "deploy [flags]",
    Short: "Deploy application to specified environment",
    Long: `Deploy your application to the cloud environment.

This command will:
1. Build the application
2. Create a container image
3. Push to the registry
4. Update the deployment
5. Wait for rollout to complete`,
    Example: `  # Deploy to production
  mytool deploy --env production --version v1.2.3

  # Deploy with custom timeout
  mytool deploy --env staging --version v1.2.3 --timeout 10m`,
    RunE: deployRun,
}

2. エラーメッセージの設計

原則

  • 何が起きたか - 問題の明確な説明
  • なぜ起きたか - 原因の説明
  • どうすればいいか - 解決策の提示

悪い例

Error: failed

良い例

func deployError(env, version string, err error) error {
    return fmt.Errorf(`Failed to deploy version %s to %s environment

Cause: %v

Possible solutions:
  1. Check if the version exists in the registry
  2. Verify your credentials: mytool auth login
  3. Ensure you have permission to deploy to %s

For more help, visit: https://docs.mytool.com/troubleshooting
`, version, env, err, env)
}

ユーザーフレンドリーなエラー

func validateAPIKey(key string) error {
    if key == "" {
        return errors.New(`API key not found

You can set your API key in one of the following ways:
  1. Use --api-key flag:
     mytool deploy --api-key YOUR_KEY

  2. Set environment variable:
     export MYTOOL_API_KEY=YOUR_KEY

  3. Add to config file (~/.mytool/config.yaml):
     api:
       key: YOUR_KEY

To obtain an API key, visit: https://mytool.com/settings/api-keys`)
    }
    return nil
}

3. プログレスバーと進捗表示

長時間実行タスクの可視化

import "github.com/schollz/progressbar/v3"

func processFiles(files []string) error {
    bar := progressbar.NewOptions(len(files),
        progressbar.OptionSetDescription("Processing files"),
        progressbar.OptionShowCount(),
        progressbar.OptionShowIts(),
        progressbar.OptionSetPredictTime(true),
    )

    for _, file := range files {
        if err := process(file); err != nil {
            return err
        }
        bar.Add(1)
    }
    return nil
}

段階的な進捗表示

func deploy(version string) error {
    steps := []struct {
        name string
        fn   func() error
    }{
        {"Building application", build},
        {"Creating container image", createImage},
        {"Pushing to registry", pushImage},
        {"Updating deployment", updateDeployment},
        {"Waiting for rollout", waitForRollout},
    }

    for i, step := range steps {
        fmt.Printf("[%d/%d] %s...\n", i+1, len(steps), step.name)
        if err := step.fn(); err != nil {
            return fmt.Errorf("%s failed: %w", step.name, err)
        }
        fmt.Printf("✓ %s completed\n", step.name)
    }

    return nil
}

よくある失敗と回避策

1. 引数パースの落とし穴

問題:フラグの順序依存

# これは動く
mytool deploy --env prod myapp

# これは動かないことがある
mytool deploy myapp --env prod

解決策:Cobraを使う

// Cobraは自動的にフラグと引数を正しく解析
var deployCmd = &cobra.Command{
    Use:   "deploy [app-name]",
    Args:  cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        appName := args[0]
        env, _ := cmd.Flags().GetString("env")
        return deploy(appName, env)
    },
}

2. 標準入出力の扱い

問題:パイプとリダイレクトに対応していない

// 悪い例:常にターミナル出力を想定
fmt.Println("Processing...")
time.Sleep(5 * time.Second)

解決策:ストリームを意識する

import "os"

func main() {
    // ターミナルかどうかを判定
    isTTY := isatty.IsTerminal(os.Stdout.Fd())

    if isTTY {
        // インタラクティブな出力
        showProgressBar()
    } else {
        // パイプ・リダイレクト時はシンプルな出力
        fmt.Fprintln(os.Stderr, "Processing...")
    }
}

パイプ処理の実装

func main() {
    var input io.Reader = os.Stdin

    // ファイルが指定されていればそちらを使う
    if len(os.Args) > 1 {
        f, err := os.Open(os.Args[1])
        if err != nil {
            log.Fatal(err)
        }
        defer f.Close()
        input = f
    }

    // 標準入力またはファイルから読み込み
    scanner := bufio.NewScanner(input)
    for scanner.Scan() {
        process(scanner.Text())
    }
}

3. 終了コードの重要性

問題:常に0で終了

func main() {
    if err := run(); err != nil {
        fmt.Println("Error:", err)
        // 終了コードが0のまま
    }
}

解決策:適切な終了コード

func main() {
    if err := run(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)

        // エラーの種類に応じた終了コード
        switch err.(type) {
        case *ConfigError:
            os.Exit(2) // 設定エラー
        case *NetworkError:
            os.Exit(3) // ネットワークエラー
        default:
            os.Exit(1) // 一般的なエラー
        }
    }
    os.Exit(0) // 成功
}

シェルスクリプトでの活用

#!/bin/bash

mytool deploy --env prod
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
    echo "Deployment successful"
elif [ $EXIT_CODE -eq 2 ]; then
    echo "Configuration error - check your config file"
    exit 1
elif [ $EXIT_CODE -eq 3 ]; then
    echo "Network error - retrying..."
    sleep 5
    mytool deploy --env prod
else
    echo "Unknown error"
    exit 1
fi

4. エラーハンドリングの不足

問題:エラーを無視

func processFiles(files []string) {
    for _, file := range files {
        process(file) // エラーを無視
    }
}

解決策:適切なエラー処理

func processFiles(files []string) error {
    var errs []error

    for _, file := range files {
        if err := process(file); err != nil {
            errs = append(errs, fmt.Errorf("%s: %w", file, err))
        }
    }

    if len(errs) > 0 {
        return fmt.Errorf("failed to process %d files:\n%s",
            len(errs), formatErrors(errs))
    }
    return nil
}

func formatErrors(errs []error) string {
    var sb strings.Builder
    for i, err := range errs {
        fmt.Fprintf(&sb, "  %d. %v\n", i+1, err)
    }
    return sb.String()
}

まとめ

CLI開発は、以下の理由から現代のソフトウェアエンジニアにとって必須のスキルです:

  • 実用性: 日々の業務を自動化し、生産性を劇的に向上
  • キャリア価値: DevOps、SRE、Platform Engineering の必須スキル
  • 学習効果: ファイルシステム、ネットワーク、並行処理など幅広い技術を習得
  • OSS貢献: コミュニティへの参加とポートフォリオ構築
  • 市場価値: 高い需要と競争力のあるスキルセット
  • Goを使ったCLI開発は、シンプルで高速、そして実用的です。本講義で学ぶ技術は、すぐに実務で活用でき、長期的なキャリアの資産となるでしょう。

    次のステップ

    本講義を終えたら、以下の学習パスを検討してください:

  • 実践演習: 実際にCLIツールを作成(solution.mdを参照)
  • 詳細解説: アーキテクチャと設計パターンの理解(explanation.mdを参照)
  • 発展学習: TUI(Terminal User Interface)開発、Bubble Teaライブラリ
  • OSSコントリビューション: 既存のGoプロジェクトへの貢献
  • 自作ツールの公開: GitHub Releasesとパッケージマネージャー対応

CLI開発の世界へようこそ!