Go言語で作る対話型CLI:ユーザー入力の実装ガイド

Go言語は、高速かつ効率的なプログラムを簡潔に記述できるモダンなプログラミング言語です。本記事では、Goを使った対話型CLI(Command Line Interface)プログラムの実装方法について解説します。対話型CLIは、ユーザーと直接やりとりを行うプログラムを構築する上で非常に重要なスキルです。たとえば、ユーザーが入力したデータを処理したり、操作手順をガイドしたりするツールとして幅広く利用されています。本記事を通じて、CLIの基本から応用例までを学び、Goを用いて効率的に開発できる方法を習得しましょう。

目次

対話型CLIの基礎とは


対話型CLI(Command Line Interface)とは、コンピュータとユーザーが対話形式でやり取りを行うツールやプログラムを指します。ユーザーがキーボードからコマンドを入力し、その結果がリアルタイムで表示される仕組みは、シンプルながら非常にパワフルです。

CLIの役割と重要性


CLIは、GUI(グラフィカルユーザーインターフェイス)に比べてリソース効率が高く、特に開発者や技術者向けのツールとして最適です。以下の点で重要な役割を果たします:

  • 柔軟な操作:テキスト入力で細かい制御が可能。
  • スクリプト化可能:複数の操作を自動化しやすい。
  • 軽量性:システムリソースを節約できる。

対話型CLIの特徴


対話型CLIは、単にコマンドを実行するだけでなく、次のような機能を提供します:

  • リアルタイム入力:プログラムの途中でユーザーの選択やデータ入力を受け付ける。
  • フィードバックの提供:入力に応じた結果や次の手順を表示する。
  • インタラクティブな動作:動的にプロセスを変更できる柔軟性を持つ。

CLIは初心者にとって敷居が高く見えることもありますが、その効率性と汎用性から、技術者や開発者にとって欠かせないツールとなっています。本記事では、このCLIの基礎をGo言語でどのように実装するかを詳しく見ていきます。

Go言語でCLIを構築するメリット

Go言語は、その設計思想や特性から、CLIプログラムを構築するのに最適な言語の一つです。以下に、Goを用いたCLI開発のメリットを挙げて説明します。

シンプルで効率的な構文


Goの構文はシンプルで読みやすく、CLIのような効率的なプログラムを短時間で構築することが可能です。無駄のない設計により、コードが簡潔になり、保守性が向上します。

コンパイルされた単一バイナリの生成


Goはコンパイル言語であり、依存関係を内包した単一のバイナリを生成できます。これにより、CLIプログラムを容易に配布でき、特定のプラットフォームでの実行も簡単です。

優れた標準ライブラリ


Goの標準ライブラリには、CLI開発に便利な機能が多数含まれています。たとえば、以下のモジュールを利用して基本的なCLI機能を実装できます:

  • fmt:テキスト入出力のフォーマット処理。
  • bufio:バッファ付き入力でユーザーの入力を効率よく処理。
  • flag:コマンドライン引数の解析。

高いパフォーマンス


Goはネイティブコードにコンパイルされるため、高速で効率的なCLIプログラムを実現できます。大規模データの処理やリアルタイムな応答が求められるCLIでも性能を発揮します。

豊富なサードパーティライブラリ


Goには、CLI開発を支援するライブラリが豊富に存在します。特に以下のライブラリが人気です:

  • Cobra:高度なCLIツールの構築を支援するフレームワーク。
  • Viper:設定ファイルの管理や環境変数の処理をサポート。

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


Goで構築したCLIプログラムは、Windows、macOS、Linuxなどさまざまなプラットフォームで動作します。追加の変更がほとんど不要なため、開発効率が向上します。

CLI開発にGoを採用することで、開発スピードとプログラムの信頼性を高め、ユーザーに優れた体験を提供することが可能になります。

基本的なユーザー入力の取り扱い

対話型CLIプログラムでは、ユーザー入力を効率的に処理することが重要です。Go言語では、シンプルなコードでユーザーからの入力を受け取り、それを処理できます。この章では基本的なユーザー入力の方法を解説します。

標準入力からのデータ取得


Goでは、標準ライブラリのfmtbufioを使用して、ユーザー入力を簡単に取得できます。以下は、ユーザーに名前を入力させ、その内容を表示する例です:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("名前を入力してください: ")
    name, _ := reader.ReadString('\n')
    fmt.Printf("こんにちは、%sさん!\n", name)
}

この例では、bufio.NewReaderを使用して標準入力(os.Stdin)から入力を読み取り、ReadString('\n')で改行が入力されるまでのデータを取得しています。

単純な入力の取り扱い


fmt.Scanを使うと、より簡潔に入力を扱えます。以下の例では、数値を入力し、その値を倍にする簡単なプログラムを示します:

package main

import (
    "fmt"
)

func main() {
    var num int
    fmt.Print("数値を入力してください: ")
    fmt.Scan(&num)
    fmt.Printf("入力された数値の2倍は: %d\n", num*2)
}

ここでは、fmt.Scanを用いて直接変数に値を代入しています。この方法は、簡易な入力処理に適しています。

複数の入力を処理する


複数の値を同時に入力したい場合も、fmt.Scanが利用できます。以下は、2つの数値を受け取って合計を計算する例です:

package main

import (
    "fmt"
)

func main() {
    var a, b int
    fmt.Print("2つの数値をスペース区切りで入力してください: ")
    fmt.Scan(&a, &b)
    fmt.Printf("合計: %d\n", a+b)
}

入力の読み取り時の注意点

  • 入力を数値や特定のフォーマットに変換する場合、エラー処理が必要です。
  • 改行やスペースを含む入力を扱う際は、bufioを使用すると柔軟性が高まります。

まとめ


Go言語では、標準ライブラリを使用して効率的にユーザー入力を処理できます。fmtbufioを適切に活用することで、CLIプログラムのユーザー体験を向上させることができます。次章では、入力のバリデーションやエラー処理についてさらに深掘りして解説します。

入力のバリデーションとエラー処理

対話型CLIプログラムにおいて、ユーザー入力の正確性を確保するためには、バリデーション(入力の検証)とエラー処理が重要です。本章では、Go言語を用いて安全かつ正確な入力処理を実装する方法を解説します。

基本的なバリデーションの実装


ユーザーが入力したデータを正しく処理するには、適切なバリデーションを行う必要があります。以下は、数値が正の整数であるかをチェックする例です:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    var input string
    fmt.Print("正の整数を入力してください: ")
    fmt.Scan(&input)

    num, err := strconv.Atoi(input)
    if err != nil || num <= 0 {
        fmt.Println("エラー: 正の整数を入力してください。")
        return
    }

    fmt.Printf("入力された数値は: %d\n", num)
}

この例では、strconv.Atoiを使用して文字列を整数に変換しています。変換エラーや負の値が入力された場合にエラーメッセージを表示します。

範囲やフォーマットのチェック


入力が特定の範囲やフォーマットに従っているかを確認するには、条件を組み合わせてチェックします。以下は、年齢が1~120の範囲に収まるかを検証する例です:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    var input string
    fmt.Print("年齢を入力してください(1〜120): ")
    fmt.Scan(&input)

    age, err := strconv.Atoi(input)
    if err != nil || age < 1 || age > 120 {
        fmt.Println("エラー: 1〜120の範囲で正しい年齢を入力してください。")
        return
    }

    fmt.Printf("入力された年齢は: %d歳\n", age)
}

エラーハンドリングのベストプラクティス


CLIプログラムのエラーハンドリングでは、ユーザーにわかりやすいメッセージを表示することが重要です。以下のポイントを押さえると、信頼性の高いプログラムが作れます:

  1. エラーメッセージを具体的に:エラー内容を明確に伝える。
  2. リトライの方法を提示:正しい入力方法を示す。
  3. エラー内容をログに記録:デバッグや運用時の問題解決を容易にする。

エラーハンドリング例:再入力の促進


ユーザーが間違った入力をした場合に再入力を促す例です:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    reader := bufio.NewReader(os.Stdin)
    for {
        fmt.Print("1〜10の数値を入力してください: ")
        input, _ := reader.ReadString('\n')
        input = strings.TrimSpace(input)

        num, err := strconv.Atoi(input)
        if err != nil || num < 1 || num > 10 {
            fmt.Println("エラー: 1〜10の範囲内の数値を入力してください。")
            continue
        }

        fmt.Printf("正しい値: %d\n", num)
        break
    }
}

この例では、正しい入力が得られるまで再入力を促します。

まとめ


Go言語での入力バリデーションとエラー処理は、CLIプログラムの品質を向上させる重要な要素です。エラーの種類や状況に応じて適切に対処し、ユーザーにとって使いやすいインターフェイスを実現しましょう。次章では、これらを活用したサンプルプログラムを具体的に紹介します。

サンプルプログラム:簡易計算ツールの作成

本章では、Go言語を使って簡易計算ツールを作成する方法を解説します。このツールは、ユーザーが入力した数値と演算子に基づいて計算を実行し、結果を表示します。これにより、ユーザー入力の処理やエラー処理を学べます。

プログラムの仕様


この計算ツールは以下の仕様を持ちます:

  1. ユーザーから2つの数値と1つの演算子(+, -, *, /)を入力として受け取る。
  2. 入力値のバリデーションを行い、不正な値を入力した場合はエラーメッセージを表示。
  3. 有効な入力値に基づいて計算結果を表示。

プログラムコード

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    reader := bufio.NewReader(os.Stdin)

    fmt.Println("簡易計算ツール")
    fmt.Println("====================")

    // 1つ目の数値を取得
    fmt.Print("1つ目の数値を入力してください: ")
    num1Str, _ := reader.ReadString('\n')
    num1Str = strings.TrimSpace(num1Str)
    num1, err := strconv.Atoi(num1Str)
    if err != nil {
        fmt.Println("エラー: 有効な数値を入力してください。")
        return
    }

    // 2つ目の数値を取得
    fmt.Print("2つ目の数値を入力してください: ")
    num2Str, _ := reader.ReadString('\n')
    num2Str = strings.TrimSpace(num2Str)
    num2, err := strconv.Atoi(num2Str)
    if err != nil {
        fmt.Println("エラー: 有効な数値を入力してください。")
        return
    }

    // 演算子を取得
    fmt.Print("演算子を入力してください(+ - * /): ")
    operator, _ := reader.ReadString('\n')
    operator = strings.TrimSpace(operator)

    // 計算処理
    var result int
    switch operator {
    case "+":
        result = num1 + num2
    case "-":
        result = num1 - num2
    case "*":
        result = num1 * num2
    case "/":
        if num2 == 0 {
            fmt.Println("エラー: 0で割ることはできません。")
            return
        }
        result = num1 / num2
    default:
        fmt.Println("エラー: 有効な演算子を入力してください(+ - * /)。")
        return
    }

    // 結果を表示
    fmt.Printf("計算結果: %d %s %d = %d\n", num1, operator, num2, result)
}

コードの説明

  1. ユーザー入力の取得
  • bufio.NewReaderを使ってユーザー入力を取得します。
  • strings.TrimSpaceで余分な空白や改行を削除します。
  1. 入力値のバリデーション
  • strconv.Atoiで文字列を整数に変換し、エラーが発生した場合はメッセージを表示して終了します。
  1. 演算の実行
  • switch文を使って入力された演算子に応じた計算を実行します。
  • /演算子の場合、ゼロ除算を防ぐために追加のチェックを行います。
  1. 計算結果の表示
  • 計算が成功した場合、結果を表示します。

プログラムの実行例

簡易計算ツール
====================
1つ目の数値を入力してください: 10
2つ目の数値を入力してください: 5
演算子を入力してください(+ - * /): +
計算結果: 10 + 5 = 15

エラーケースの例

  • 無効な数値を入力した場合:
  エラー: 有効な数値を入力してください。
  • ゼロ除算の場合:
  エラー: 0で割ることはできません。
  • 無効な演算子を入力した場合:
  エラー: 有効な演算子を入力してください(+ - * /)。

まとめ


この簡易計算ツールは、基本的なユーザー入力の処理、バリデーション、エラー処理、そして分岐処理の実装例を示しています。Go言語の特性を活かして、使いやすいCLIプログラムを構築する手順を理解できたでしょう。次章では、さらに複雑なコマンド解析について掘り下げていきます。

複雑な入力の処理とコマンド解析

対話型CLIプログラムでは、単純な入力処理だけでなく、コマンドライン引数を解析して柔軟に動作する機能が求められることがあります。この章では、Go言語を用いた複雑な入力処理とコマンド解析の実装方法について解説します。

コマンドライン引数の解析の基本


Go言語の標準ライブラリflagを使うと、コマンドライン引数を簡単に解析できます。以下は、簡単な計算コマンドを解析する例です:

package main

import (
    "flag"
    "fmt"
)

func main() {
    // コマンドライン引数を定義
    num1 := flag.Int("num1", 0, "1つ目の数値")
    num2 := flag.Int("num2", 0, "2つ目の数値")
    operator := flag.String("op", "+", "演算子(+ - * /)")

    // 引数の解析
    flag.Parse()

    // 演算の実行
    var result int
    switch *operator {
    case "+":
        result = *num1 + *num2
    case "-":
        result = *num1 - *num2
    case "*":
        result = *num1 * *num2
    case "/":
        if *num2 == 0 {
            fmt.Println("エラー: 0で割ることはできません。")
            return
        }
        result = *num1 / *num2
    default:
        fmt.Println("エラー: 有効な演算子を入力してください(+ - * /)。")
        return
    }

    // 結果を表示
    fmt.Printf("計算結果: %d %s %d = %d\n", *num1, *operator, *num2, result)
}

コードの説明

  1. flagパッケージの使用
  • flag.Intflag.Stringなどで引数の型とデフォルト値、説明を定義します。
  • flag.Parse()で引数を解析します。
  1. 引数の利用
  • *num1*operatorのようにポインタを参照して値を取得します。
  1. エラー処理
  • 演算子が不正だったり、ゼロ除算の場合にはエラーメッセージを表示します。

サードパーティライブラリを用いた解析


Goのサードパーティライブラリcobraを使用すると、より高度で柔軟なコマンド解析が可能になります。以下は、cobraを使ったサンプルコードです:

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

func main() {
    var num1, num2 int
    var operator string

    rootCmd := &cobra.Command{
        Use:   "calculator",
        Short: "簡易計算ツール",
        Run: func(cmd *cobra.Command, args []string) {
            var result int
            switch operator {
            case "+":
                result = num1 + num2
            case "-":
                result = num1 - num2
            case "*":
                result = num1 * num2
            case "/":
                if num2 == 0 {
                    fmt.Println("エラー: 0で割ることはできません。")
                    return
                }
                result = num1 / num2
            default:
                fmt.Println("エラー: 有効な演算子を入力してください(+ - * /)。")
                return
            }
            fmt.Printf("計算結果: %d %s %d = %d\n", num1, operator, num2, result)
        },
    }

    rootCmd.Flags().IntVarP(&num1, "num1", "a", 0, "1つ目の数値")
    rootCmd.Flags().IntVarP(&num2, "num2", "b", 0, "2つ目の数値")
    rootCmd.Flags().StringVarP(&operator, "op", "o", "+", "演算子(+ - * /)")

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

`cobra`の特徴

  • コマンドの階層化:サブコマンドを簡単に実装可能。
  • フラグの管理:フラグを簡潔に定義できる。
  • 使いやすいヘルプメッセージ:デフォルトでヘルプメッセージを生成。

実行例

  • 標準的な実行:
  $ go run main.go --num1 10 --num2 5 --op +
  計算結果: 10 + 5 = 15
  • エラーメッセージの表示:
  $ go run main.go --num1 10 --num2 0 --op /
  エラー: 0で割ることはできません。

まとめ


Go言語の標準ライブラリやcobraを活用することで、柔軟で複雑なコマンドライン引数の解析が可能になります。これにより、対話型CLIプログラムにより高度な機能を追加し、ユーザーにとって便利なツールを構築できるでしょう。次章では、CLIプログラムを設計する際のベストプラクティスを解説します。

対話型CLIの設計ベストプラクティス

効果的な対話型CLIを設計するためには、ユーザー体験を考慮した設計が不可欠です。本章では、Go言語を使ったCLIプログラムの開発において、設計のベストプラクティスを解説します。

1. ユーザーフレンドリーなインターフェイス

対話型CLIを設計する際には、ユーザーが迷わず操作できるシンプルで直感的なインターフェイスを目指しましょう。以下の点を考慮してください:

  • 分かりやすいコマンド名:動詞や名詞を使い、機能を明確に表現する(例:adddelete)。
  • 標準的な引数形式--flag value-f valueの形式を採用する。
  • ヘルプメッセージの提供:コマンドや引数の説明をヘルプメッセージで明確に表示する。

例(cobraを利用したヘルプの生成):

$ calculator --help
Usage:
  calculator [flags]

Flags:
  -a, --num1 int    1つ目の数値
  -b, --num2 int    2つ目の数値
  -o, --op string   演算子(+ - * /) (default "+")

2. エラーメッセージの適切な表示

ユーザーがエラーを理解しやすいように、具体的で親切なエラーメッセージを表示します:

  • 入力ミスがあれば正しいフォーマットを例示する。
  • システム的なエラーとユーザーエラーを区別する。

例:

エラー: 有効な演算子を入力してください(+ - * /)。  
例: calculator --num1 10 --num2 5 --op +

3. 再利用可能なコードの設計

CLIプログラムのコードは再利用性を高めるためにモジュール化を意識しましょう。例えば、次のように設計します:

  • 入力処理の関数化:入力値のバリデーションや解析を独立した関数に分離。
  • ビジネスロジックの分離:計算処理やデータ処理をUIロジックから分離。

例:

func calculate(num1, num2 int, operator string) (int, error) {
    switch operator {
    case "+":
        return num1 + num2, nil
    case "-":
        return num1 - num2, nil
    case "*":
        return num1 * num2, nil
    case "/":
        if num2 == 0 {
            return 0, fmt.Errorf("0で割ることはできません")
        }
        return num1 / num2, nil
    default:
        return 0, fmt.Errorf("無効な演算子: %s", operator)
    }
}

4. ログとデバッグ機能の活用

CLIプログラムでは、エラーや重要な操作を記録するログ機能を組み込むと便利です:

  • 開発時にはlogパッケージを活用してデバッグログを表示。
  • 運用時にはログレベル(例:INFOWARNERROR)を分けて記録する。

例:

import "log"

func main() {
    log.Println("プログラムが開始されました")
    // エラー時
    log.Println("エラー: 不正な入力が検出されました")
}

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

CLIプログラムが異なるプラットフォームで動作することを考慮しましょう。Go言語はクロスプラットフォームに優れていますが、以下の点に注意します:

  • ファイルパスの取り扱いはOSごとに異なる(例:filepathパッケージを使用)。
  • 標準入力/出力の扱いもプラットフォームに依存する場合があるため、動作確認が必要。

6. ユーザーフィードバックを重視

CLIプログラムをユーザーが快適に利用できるように、以下の工夫を加えます:

  • 操作が成功した場合に適切なフィードバックを表示。
  • 長時間処理には進捗状況を表示(例:プログレスバー)。

例:進捗表示にgithub.com/cheggaaa/pb/v3ライブラリを利用。

まとめ


効果的な対話型CLIの設計には、ユーザー体験を重視し、直感的なインターフェイスや適切なエラーメッセージを提供することが重要です。また、再利用性や運用性を考慮したコード設計がCLIプログラムの品質を向上させます。次章では、Goのライブラリを活用してさらに効率的にCLIを構築する方法を解説します。

Goのライブラリを活用したCLI開発

Go言語にはCLI開発を効率化するための強力なサードパーティライブラリが豊富に存在します。本章では、特に人気のあるライブラリを取り上げ、それぞれの特徴と使用方法を解説します。

1. Cobra

Cobraは、複雑なCLIツールを簡単に構築できるライブラリで、多くのGoプロジェクトで採用されています。以下はCobraの特徴です:

  • コマンドの階層構造をサポート:メインコマンドに加え、サブコマンドを簡単に実装可能。
  • 自動生成されるヘルプメッセージ:詳細で使いやすいヘルプ機能が組み込まれている。
  • 設定管理の統合:Viperと組み合わせて設定ファイルや環境変数を簡単に扱える。

Cobraのインストールと基本的な使用法

go get -u github.com/spf13/cobra@latest

以下はCobraを用いた簡単なCLIツールの例です:

package main

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

func main() {
    var rootCmd = &cobra.Command{
        Use:   "app",
        Short: "CLIアプリケーションのサンプル",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("Hello, Cobra!")
        },
    }

    var greetCmd = &cobra.Command{
        Use:   "greet [name]",
        Short: "挨拶を表示する",
        Args:  cobra.MinimumNArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("こんにちは、%sさん!\n", args[0])
        },
    }

    rootCmd.AddCommand(greetCmd)
    rootCmd.Execute()
}

実行例

$ go run main.go greet Taro
こんにちは、Taroさん!

2. Viper

Viperは、設定ファイルや環境変数を扱うためのライブラリで、CLI開発と相性が良いです。特に以下の機能が便利です:

  • 複数形式の設定ファイルをサポート:JSON、YAML、TOML、HCLなど。
  • 環境変数との統合:環境変数を自動的にマッピング。
  • 動的な再読み込み:設定ファイルを変更すると、自動で再読み込みされる。

以下はViperを使った設定管理の例です:

package main

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

func main() {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".")

    if err := viper.ReadInConfig(); err != nil {
        fmt.Printf("エラー: 設定ファイルの読み込みに失敗しました: %v\n", err)
        return
    }

    server := viper.GetString("server.address")
    port := viper.GetInt("server.port")
    fmt.Printf("サーバーアドレス: %s:%d\n", server, port)
}

YAML形式の設定ファイル例(config.yaml):

server:
  address: "localhost"
  port: 8080

実行結果

$ go run main.go
サーバーアドレス: localhost:8080

3. Promptui

Promptuiは、対話型CLIツールを構築するためのライブラリで、以下のような機能を提供します:

  • 選択メニュー:ユーザーがオプションを選択できるインターフェイス。
  • 入力プロンプト:カスタマイズ可能なユーザー入力プロンプト。

以下は選択メニューを作成する例です:

package main

import (
    "fmt"
    "github.com/manifoldco/promptui"
)

func main() {
    prompt := promptui.Select{
        Label: "選択してください",
        Items: []string{"オプション1", "オプション2", "オプション3"},
    }

    _, result, err := prompt.Run()
    if err != nil {
        fmt.Printf("選択エラー: %v\n", err)
        return
    }

    fmt.Printf("選択されたオプション: %s\n", result)
}

実行例

? 選択してください:
  ▸ オプション1
    オプション2
    オプション3

まとめ


Go言語には、CLI開発を効率化するライブラリが豊富に存在します。Cobraはコマンドの階層化やヘルプ生成に優れ、Viperは設定管理を簡単にし、Promptuiは対話型プロンプトを提供します。これらを組み合わせることで、機能的かつ使いやすいCLIプログラムを構築できます。次章では、本記事の内容を総括し、CLI開発の全体像を振り返ります。

まとめ

本記事では、Go言語を用いた対話型CLIプログラムの実装方法について、基本から応用までを解説しました。CLIの基礎とその重要性を理解し、Go言語の特性を活かしたユーザー入力の処理、エラー処理、コマンド解析、さらに便利なサードパーティライブラリの活用方法を学びました。

Go言語はシンプルな構文、優れたパフォーマンス、そして強力なエコシステムにより、CLI開発に最適なプログラミング言語です。実用的なサンプルプログラムや設計ベストプラクティスを活用して、効率的で使いやすいCLIツールを構築できるようになるでしょう。

これらの知識を実際のプロジェクトに応用し、実践的なスキルを磨いてください。Goを使ったCLI開発は、あなたの開発力をさらに高める大きな武器となるはずです。

コメント

コメントする

目次