20200514のGoに関する記事は7件です。

[Go言語]ターミナルの入力をリアルタイムに取得する方法

はじめに

本記事では、Go言語を使用してターミナルの入力をリアルタイムで取得する方法をご紹介します。
今回ご紹介するのは、UNIX系OSに限った話です。
Windowsの場合は、Microsoftが用意してるAPIで実現できるらしいが......(調査不足)

目次

結論

ターミナルを非カノニカルモードにすること、即時に入力を取得可能です。
非カノニカルモードにするには、termios構造体のフィールドの値を変更しなければなりません。
以下では、サンプルコードとLinuxのマニュアルを交えて、それぞれのモードを説明します。

動作環境

  • macOS Catalina
  • CentOS 7
  • go version go1.12.6

カノニカルモードとは

非カノニカルモードを説明する前に、まずカノニカルモードについてです。
わかりやすく言えば、デフォルトの状態ですね。Enterが押下されるまで入力を受け付けます。

引用 Linuxの日本語翻訳マニュアル

  • 入力は行単位に行われる。 行区切り文字が打ち込まれた時点で、入力行が利用可能となる。行区切り文字は NL, EOL, EOL2 および行頭での EOF である。 EOF 以外の場合、 read(2) が返すバッファーに行区切り文字も含められる

  • 行編集が有効となる (ERASE, KILL が効果を持つ。 IEXTEN フラグが設定された場合は、 WERASE, REPRINT, LNEXT も効果を持つ)。 read(2) は最大でも 1行の入力しか返さない。 read(2) が要求したバイト数が現在の入力行のバイト数よりも少ない場合、 要求したのと同じバイト数だけが読み込まれ、 残りの文字は次回の read(2) で読み込まれる

非カノニカルモードとは

非カノニカルモード、リアルタイムに入力を受け付け可能なのがこちらのモードです。
MIN (c_cc[VMIN])で受付文字数を設定できます。
例えば、c_cc[VMIN] = 3とした場合、3文字入力した時点で、入力完了となりプログラムに読み込まれます。
TIME (c_cc[VTIME])でタイムアウト時間を設定でき、タイムアウトを必要としない場合は0にします。

引用 Linuxの日本語翻訳マニュアル

  • 非カノニカルモードでは、入力は即座に利用可能となり (ユーザーは行区切り文字を打ち込む必要はない)、入力処理は実行されず、行編集は無効となる。 MIN (c_cc[VMIN]) と TIME (c_cc[VTIME]) の設定により、 read(2) が完了する条件が決定される

サンプルコード

C言語の場合、termios構造体にアクセスする際には、「termios.h」の関数群を使用します。
これをGo言語で同じように使用できるよう、有志が作成したpkg/termパッケージをお借りします。
termios構造体のパラメータを変更するときは、Tcgetattr()で現在の設定を取得してから、ビット演算などで必要な設定をしていきます。
設定できるパラメータは、Linuxの日本語翻訳マニュアルを参考にしてください。
ちなみに、C言語で使用できるフラグは、基本的にはpkg/termパッケージでも使用できました。
必要な設定が完了したら、Tcsetattr()で反映できます。

sample.go
package main

import (
    "fmt"
    "syscall"

    "github.com/pkg/term/termios"
)

func main() {
    var ttystate syscall.Termios

    // ターミナルの設定を取得
    termios.Tcgetattr(uintptr(syscall.Stdin), &ttystate)

    // ターミナルの設定変更
    setNonCanonicalMode(&ttystate)

    // 標準入力を取得
    bufCh := make(chan []byte, 1)
    go readBuffer(bufCh)

    for {
        fmt.Printf(" あなたが入力したのは: %c\n", <-bufCh)
    }
}

// 非カノニカルモードに設定する
func setNonCanonicalMode(attr *syscall.Termios) {

    // カノニカルモードを無効に設定 (&^ AND NOT)
    attr.Lflag &^= syscall.ICANON

    // 読み込み時の最小文字数 = 1文字
    attr.Cc[syscall.VMIN] = 1

    //非カノニカル読み込み時のタイムアウト時間 = 0
    attr.Cc[syscall.VTIME] = 0

    // 変更した設定を反映
    termios.Tcsetattr(uintptr(syscall.Stdin), termios.TCSANOW, attr)
}

// バッファの値を取得する
func readBuffer(bufCh chan []byte) {
    buf := make([]byte, 1024)

    for {
        if n, err := syscall.Read(syscall.Stdin, buf); err == nil {
            bufCh <- buf[:n]
        }
    }
}

上記コードを実行すると、以下のような挙動になります。
入力したキーが画面に表示された後に、Printfが実行されます。
ちなみにVMINの数値を増やせば、複数の値を取得可能です。

Demo01

Rawモードとは

非カノニカルモードの特徴に加えて、入力したキーが画面に表示されません。
また特殊処理が効かないので、Ctrl + C で強制終了ができなくなります。

引用 Linuxの日本語翻訳マニュアル

  • 入力は文字単位に可能であり、エコーが無効となり、 端末の入出力文字に対する特殊処理はすべて無効となる

サンプルコード

Linuxの日本語翻訳マニュアルに示す通りにRawモードに設定したパターンです。(※ 不必要だと判断した箇所はコメントアウトしてます。)

下記コードのように何も考えずにマニュアル通りに設定してもいいが、必要に応じて設定パラメータを選択した方がいい気がしますね。

sample.go
package main

import (
    "fmt"
    "syscall"

    "github.com/pkg/term/termios"
)

func main() {
    var ttystate syscall.Termios

    // ターミナルの設定を取得
    termios.Tcgetattr(uintptr(syscall.Stdin), &ttystate)

    // ターミナルの設定変更 ※ここを変更
    //setNonCanonicalMode(&ttystate)
    setRawMode(&ttystate)

    bufCh := make(chan []byte, 1)
    go readBuffer(bufCh)

    for {
        fmt.Printf(" あなたが入力したのは: %c\n", <-bufCh)
    }
}

// Rawモードに設定する
func setRawMode(attr *syscall.Termios) {

    // 設定値は、[https://linuxjm.osdn.jp/html/LDP_man-pages/man3/termios.3.html]から引用
    // OPOSTをビットクリアすると、自分の環境では出力がおかしくなるのでコメントアウト
    // ISIGを無効にすると、Ctrl+Cが押せなくてデバッグがしづらいのでコメントアウト
    // マニュアルでは、文字サイズをCS8に指定しているが、今回の検証では不必要と判断してコメントアウト

    attr.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON

    //attr.Oflag &^= syscall.OPOST

    attr.Cflag &^= syscall.CSIZE | syscall.PARENB

    //attr.Cflag |= syscall.CS8

    attr.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN //| syscall.ISIG

    attr.Cc[syscall.VMIN] = 1
    attr.Cc[syscall.VTIME] = 0

    // 変更した設定を反映
    termios.Tcsetattr(uintptr(syscall.Stdin), termios.TCSANOW, attr)
}

// バッファの値を取得する
func readBuffer(bufCh chan []byte) {
    // 省略
}

Demo02
上記の非カノニカルモードと違って、押下したキーが画面に表示されないですね。

参考サイト

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/termios.3.html
https://www.slideshare.net/c-bata/golang-ui
https://grimoire.f5.si/archives/125

おわりに

termiosについてC言語で扱う情報は、たくさんあるのですが、Go言語でやってる事例が少なかったです。
ジョークコマンドを作成するために調べ始めたのですが、思ったより時間を取られてしまいました。
未来の自分のため、そして同じように詰まってる人のために記事にまとめました。
普段意識しない低レイヤーに触れることができて、面白かったですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語はじめました 〜その1〜

目的

とある開発案件で、はじめてGo言語を使ったWebアプリケーションを開発することになりました!
なので、自分用の学習メモとして記録を残しておこうと思いこの記事を書いています。
また、同じシチュエーションでこれからGo言語を始める方の参考になれば幸いです。

開発環境

  • macOS Catalina 10.15.4(19E287)
  • go 1.14.2

Go言語とは

2009年にGoogleが開発したオープンソースのプログラミング言語であり、Google社内におけるソフトウェア開発の生産性や拡張性のために開発されました。開発者は、UNIX開発に携わったロブ・パイク氏と、UNIX開発とC言語を開発したケン・トンプソン氏が設計しました。
対応OSは、Linux、MacOS X、Windows、Android、iOSとなっており主要なものはサポートされています。
ちなみに、正式名称は「Go」なのですが、それだけだと別の意味に捉えられることもあるので、「Golang」や「Go言語」と呼ばれることが多いようです。本記事では、「Go言語」と呼びます。

Go言語の特徴

C言語を意識しながら設計されている言語で、C言語以上にシンプルかつ信頼性があり、無駄のない有能なソフトウェア開発を簡単に実現できます。ゆえに、実用性があり、効率よく作業を行うことが可能なため、軽量・高速・シンプルなプログラミング言語として知られています。

1.シンプルな言語構造

複雑な機能は削られており、とてもシンプルな言語構造になっています。例えば、プログラミング言語では繰り返し処理を行う時に、for文とwhile分の2つが準備されています。しかしGo言語の繰り返し処理はfor文しか存在していません。このように言語をシンプルにすることで、高速コンパイルの実現やミスの軽減に役立っています。

2.誰が書いたコードでも読みやすい

言語構造がシンプルであるため、だれが作っても殆ど同じになる様に設計されています。また、コーディング規約に沿ってソースコードを自動整形してくれるgo fmtがあり、これを利用すると見た目が整い非常に見やすくなります。また、記述ルールが厳格なので、記述に迷うことなく決まった書き方で開発が進められます。

3.実行速度が速い

直接ネイティブコード(機械語)に変換することにより、高速でコンパイルが可能となります。また、コードの種類がシンプルで限定的であるため、内部処理も簡易化され、同時に複数の処理を実行できる仕様になっていることからも、より軽量・高速での処理が可能です。

4.並列処理に強い

大量データ処理には並列処理が必要になりますが、Go言語は並列処理を得意としており、goroutinechannelなどの機能を使うことでCPUへの負荷などを気にせずに処理を進められます。

5.拡張性(スケーラビリティ)が高い

Go言語では、小さな機能を必要に応じて組み合わせることで高い機能性を実現していくため、拡張性が高い言語と言えます。

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

Go言語は、クロスコンパイルという、開発しているOSとは異なる環境向けに実行可能なコードを生成することが可能であるため、OSやソフトウェアが異なるシステム上でも実行できます。

7.消費リソースが少ない

リソースのコストパフォーマンスが非常に良いと言われています。例えば、同様の処理をJavaと比較した場合、メモリはJavaの約30分の1ですみます。

Go言語のデメリット

1.Genericsがない

Go言語ではGenericsをサポートしていないため、Javaのgeneric typeのような記述はできません。これにより、安全性の高いメモリ管理やシンプルな構造を保っていると言えます。
Genericsは型を汎用化することができますが、適切に使用しないと逆にソースコードが乱雑になる危険性があります。それを避けるため、Go言語ではあえてGenericsをサポートしていないのです。

2.継承をサポートしない

コードの再利用や拡張性を高める上で役立つ「継承」もサポートされていません。少ないコードでプログラムできるというメリットはありますが、コードが複雑になれば継承により読みづらくなるという場合もあるからです。
規模の大きなシステム開発におけるコードの読みやすさや、メンテナンスのしやすさが優先されています。

3.例外処理がない

RubyやJavaの例外処理と同様の機能はありません。これも、Go言語がシンプルであることを保つために削ぎ落とされた機能です。
その代わり、PanicRecoverを使えば、エラーを処理する例外と近い機能が実装できます。

Go言語でできること

Go言語がどのような用途で主に使われているのか、得意なのかについて、以下で紹介します。
以下のもの以外でも、AI開発でも徐々に需要を増やしており、多くのシェアを持っているPythonと並ぶ程の注目があり期待されています。

Webサーバー/Web API構築

標準ライブラリのnet/httpパッケージを使用すれば、Webサーバー・Web APIを簡単に構築できます。
Go言語の特徴である、高速・軽量・並列処理に強い点などが生かされています。
代表的なものとして、YouTubeもGo言語で構築されています。

アプリケーション開発

AndroidやiPhoneといったスマートフォンで動作するネイティブアプリケーションの開発も可能です。モバイル向けのアプリ制作に使うツールが集められたGo mobileを使えば、開発がとても快適に行えます。

コマンドラインインターフェース(CLI)ツール作成

コマンドベースで操作を行うCLIによって動作するアプリケーション制作にもGo言語は向いています。
Go言語はマルチプラットフォームに対応したクロスコンパイルの機能があるので、それぞれのプロットフォーム による互換性や依存関係を心配せずに、環境に適したツールの制作が簡単に行えます。

Go言語のインストール

ちょっと座学が長くなってしまいましたね。
それでは、まずMacにGo言語をインストールしてみましょう。
事前準備として、MacにHomebrewをインストールしておいてください。

インストール

$ brew install go

Updating Homebrew...
==> Downloading https://homebrew.bintray.com/bottles/go-1.14.2_1.catalina.bottle.tar.gz
==> Downloading from https://akamai.bintray.com/15/15b5623471330edcc681d7f9d57b449660e6d4b98c7f67af67f4991fc75d61fc?__gda__=exp=1589359948~hmac=bf89e2d60937f105e542c1b210786b7ef609ac57c7934ddd5f4a03f9f7749682&response-content-disposit
######################################################################## 100.0%
==> Pouring go-1.14.2_1.catalina.bottle.tar.gz
?  /usr/local/Cellar/go/1.14.2_1: 9,440 files, 424.4MB

バージョン確認

$ go version

go version go1.14.2 darwin/amd64

これでインストールは完了です。
2020年5月現在の最新バージョンは1.14.2です。

Go言語でHello World!

それでは実際にプログラムを動かしてみましょう!!

# ディレクトリ作成
$ mkdir -p ~/golang/example

# 移動
$ cd ~/golang/example

# テストプログラムを作成
$ vi hello.go

hello.goの中身は以下のように記述します。

hello.go
package main

import "fmt"

func main() {
    fmt.Printf("Hello, World\n")
}

それでは実行です。

$ go run hello.go

Hello, World

ビルドもしてみましょう。

$ go build hello.go
$ ls
hello       hello.go
$ ./hello
Hello, World

ビルドするとプログラムファイルと同じディレクトリにhelloというバイナリファイルができました!
これを実行すると、go runと同じ結果が返ってきますね。

ちなみに、go rungo buildの違いはなんでしょうか。。。

go runは、自動的にコンパイル・実行を同時に行ってくれるコマンドですね。簡単なテストなどで有効ですね。
ただし、カレントディレクトリ以下の全ファイルを読み込むわけではないので、importされたパッケージのみ読み込み、同階層のmainパッケージに属する別関数などは無視されてしまうので注意が必要です。

go buildは、自動的にmainパッケージを読み込み、main関数があるファイルを特定し、そのファイル名を使ったバイナリファイルを生成します。
基本的にはカレントディレクトリ以下をスコープにして読み込んでコンパイルするファイルを決めるので、カレントディレクトリ以下にあるファイルが読み込まれないといったことはありません。
ビルド時に色々とオプションなどがあるみたいなので、そのあたりままた別途学習してみようと思います。

ちなみに、、、

下記のように、main関数の括弧を別の行に書くと"syntax error"になってしまいました。
結構、がちがちにコーディング規約が決まっているのね。。。

hello.go
package main

import "fmt"

func main()
{
    fmt.Printf("Hello, World\n")
}
$ go run hello.go

# command-line-arguments
./hello.go:5:6: missing function body
./hello.go:6:1: syntax error: unexpected semicolon or newline before {

また、Go言語には標準でコードを整形(静的解析)してくれる機能gofmtがあるようです。
下記のようなコードをgofmtにかけてみます。

(整形前)hello.go
package main
import "fmt"
func main(){
fmt.Printf("Hello, World\n")
}
$ gofmt -w hello.go
(整形後)hello.go
package main

import "fmt"

func main() {
    fmt.Printf("Hello, World\n")
}

空行・インデントが追加されました!!
これを使えば、複数のエンジニアで記述したコードもある程度統一できそうですね。

まとめ

ここまでで、Go言語の基礎知識、インストール方法、実行まで学習してきました。
まだ、曖昧な部分が多くてフワフワしていますが、徐々に知識を深めていければと思います。
次回は、Go言語の公式ドキュメントをもとに、基本構文を学習してみようと思います。

ども、ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語への道 〜その1〜

目的

とある開発案件で、はじめてGo言語を使ったWebアプリケーションを開発することになりました!
なので、自分用の学習メモとして記録を残しておこうと思いこの記事を書いています。
また、同じシチュエーションでこれからGo言語を始める方の参考になれば幸いです。

開発環境

  • macOS Catalina 10.15.4(19E287)
  • go 1.14.2

Go言語とは

2009年にGoogleが開発したオープンソースのプログラミング言語であり、Google社内におけるソフトウェア開発の生産性や拡張性のために開発されました。開発者は、UNIX開発に携わったロブ・パイク氏と、UNIX開発とC言語を開発したケン・トンプソン氏が設計しました。
対応OSは、Linux、MacOS X、Windows、Android、iOSとなっており主要なものはサポートされています。
ちなみに、正式名称は「Go」なのですが、それだけだと別の意味に捉えられることもあるので、「Golang」や「Go言語」と呼ばれることが多いようです。本記事では、「Go言語」と呼びます。

Go言語の特徴

C言語を意識しながら設計されている言語で、C言語以上にシンプルかつ信頼性があり、無駄のない有能なソフトウェア開発を簡単に実現できます。ゆえに、実用性があり、効率よく作業を行うことが可能なため、軽量・高速・シンプルなプログラミング言語として知られています。

1.シンプルな言語構造

複雑な機能は削られており、とてもシンプルな言語構造になっています。例えば、プログラミング言語では繰り返し処理を行う時に、for文とwhile分の2つが準備されています。しかしGo言語の繰り返し処理はfor文しか存在していません。このように言語をシンプルにすることで、高速コンパイルの実現やミスの軽減に役立っています。

2.誰が書いたコードでも読みやすい

言語構造がシンプルであるため、だれが作っても殆ど同じになる様に設計されています。また、コーディング規約に沿ってソースコードを自動整形してくれるgo fmtがあり、これを利用すると見た目が整い非常に見やすくなります。また、記述ルールが厳格なので、記述に迷うことなく決まった書き方で開発が進められます。

3.実行速度が速い

直接ネイティブコード(機械語)に変換することにより、高速でコンパイルが可能となります。また、コードの種類がシンプルで限定的であるため、内部処理も簡易化され、同時に複数の処理を実行できる仕様になっていることからも、より軽量・高速での処理が可能です。

4.並列処理に強い

大量データ処理には並列処理が必要になりますが、Go言語は並列処理を得意としており、goroutinechannelなどの機能を使うことでCPUへの負荷などを気にせずに処理を進められます。

5.拡張性(スケーラビリティ)が高い

Go言語では、小さな機能を必要に応じて組み合わせることで高い機能性を実現していくため、拡張性が高い言語と言えます。

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

Go言語は、クロスコンパイルという、開発しているOSとは異なる環境向けに実行可能なコードを生成することが可能であるため、OSやソフトウェアが異なるシステム上でも実行できます。

7.消費リソースが少ない

リソースのコストパフォーマンスが非常に良いと言われています。例えば、同様の処理をJavaと比較した場合、メモリはJavaの約30分の1ですみます。

Go言語のデメリット

1.Genericsがない

Go言語ではGenericsをサポートしていないため、Javaのgeneric typeのような記述はできません。これにより、安全性の高いメモリ管理やシンプルな構造を保っていると言えます。
Genericsは型を汎用化することができますが、適切に使用しないと逆にソースコードが乱雑になる危険性があります。それを避けるため、Go言語ではあえてGenericsをサポートしていないのです。

2.継承をサポートしない

コードの再利用や拡張性を高める上で役立つ「継承」もサポートされていません。少ないコードでプログラムできるというメリットはありますが、コードが複雑になれば継承により読みづらくなるという場合もあるからです。
規模の大きなシステム開発におけるコードの読みやすさや、メンテナンスのしやすさが優先されています。

3.例外処理がない

RubyやJavaの例外処理と同様の機能はありません。これも、Go言語がシンプルであることを保つために削ぎ落とされた機能です。
その代わり、PanicRecoverを使えば、エラーを処理する例外と近い機能が実装できます。

Go言語でできること

Go言語がどのような用途で主に使われているのか、得意なのかについて、以下で紹介します。
以下のもの以外でも、AI開発でも徐々に需要を増やしており、多くのシェアを持っているPythonと並ぶ程の注目があり期待されています。

Webサーバー/Web API構築

標準ライブラリのnet/httpパッケージを使用すれば、Webサーバー・Web APIを簡単に構築できます。
Go言語の特徴である、高速・軽量・並列処理に強い点などが生かされています。
代表的なものとして、YouTubeもGo言語で構築されています。

アプリケーション開発

AndroidやiPhoneといったスマートフォンで動作するネイティブアプリケーションの開発も可能です。モバイル向けのアプリ制作に使うツールが集められたGo mobileを使えば、開発がとても快適に行えます。

コマンドラインインターフェース(CLI)ツール作成

コマンドベースで操作を行うCLIによって動作するアプリケーション制作にもGo言語は向いています。
Go言語はマルチプラットフォームに対応したクロスコンパイルの機能があるので、それぞれのプロットフォーム による互換性や依存関係を心配せずに、環境に適したツールの制作が簡単に行えます。

Go言語のインストール

ちょっと座学が長くなってしまいましたね。
それでは、まずMacにGo言語をインストールしてみましょう。
事前準備として、MacにHomebrewをインストールしておいてください。

インストール

$ brew install go

Updating Homebrew...
==> Downloading https://homebrew.bintray.com/bottles/go-1.14.2_1.catalina.bottle.tar.gz
==> Downloading from https://akamai.bintray.com/15/15b5623471330edcc681d7f9d57b449660e6d4b98c7f67af67f4991fc75d61fc?__gda__=exp=1589359948~hmac=bf89e2d60937f105e542c1b210786b7ef609ac57c7934ddd5f4a03f9f7749682&response-content-disposit
######################################################################## 100.0%
==> Pouring go-1.14.2_1.catalina.bottle.tar.gz
?  /usr/local/Cellar/go/1.14.2_1: 9,440 files, 424.4MB

バージョン確認

$ go version

go version go1.14.2 darwin/amd64

これでインストールは完了です。
2020年5月現在の最新バージョンは1.14.2です。

Go言語でHello World!

それでは実際にプログラムを動かしてみましょう!!

# ディレクトリ作成
$ mkdir -p ~/golang/example

# 移動
$ cd ~/golang/example

# テストプログラムを作成
$ vi hello.go

hello.goの中身は以下のように記述します。

hello.go
package main

import "fmt"

func main() {
    fmt.Printf("Hello, World\n")
}

それでは実行です。

$ go run hello.go

Hello, World

ビルドもしてみましょう。

$ go build hello.go
$ ls
hello       hello.go
$ ./hello
Hello, World

ビルドするとプログラムファイルと同じディレクトリにhelloというバイナリファイルができました!
これを実行すると、go runと同じ結果が返ってきますね。

ちなみに、go rungo buildの違いはなんでしょうか。。。

go runは、自動的にコンパイル・実行を同時に行ってくれるコマンドですね。簡単なテストなどで有効ですね。
ただし、カレントディレクトリ以下の全ファイルを読み込むわけではないので、importされたパッケージのみ読み込み、同階層のmainパッケージに属する別関数などは無視されてしまうので注意が必要です。

go buildは、自動的にmainパッケージを読み込み、main関数があるファイルを特定し、そのファイル名を使ったバイナリファイルを生成します。
基本的にはカレントディレクトリ以下をスコープにして読み込んでコンパイルするファイルを決めるので、カレントディレクトリ以下にあるファイルが読み込まれないといったことはありません。
ビルド時に色々とオプションなどがあるみたいなので、そのあたりままた別途学習してみようと思います。

ちなみに、、、

下記のように、main関数の括弧を別の行に書くと"syntax error"になってしまいました。
結構、がちがちにコーディング規約が決まっているのね。。。

hello.go
package main

import "fmt"

func main()
{
    fmt.Printf("Hello, World\n")
}
$ go run hello.go

# command-line-arguments
./hello.go:5:6: missing function body
./hello.go:6:1: syntax error: unexpected semicolon or newline before {

また、Go言語には標準でコードを整形(静的解析)してくれる機能gofmtがあるようです。
下記のようなコードをgofmtにかけてみます。

(整形前)hello.go
package main
import "fmt"
func main(){
fmt.Printf("Hello, World\n")
}
$ gofmt -w hello.go
(整形後)hello.go
package main

import "fmt"

func main() {
    fmt.Printf("Hello, World\n")
}

空行・インデントが追加されました!!
これを使えば、複数のエンジニアで記述したコードもある程度統一できそうですね。

まとめ

ここまでで、Go言語の基礎知識、インストール方法、実行まで学習してきました。
まだ、曖昧な部分が多くてフワフワしていますが、徐々に知識を深めていければと思います。
次回は、Go言語の公式ドキュメントをもとに、基本構文を学習してみようと思います。

ども、ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang の modules 管理 [Docker, go mod]

はじめに

バックエンドは Ruby, TypeScript 等を触ってきて、「Go やるか!」と思って手を出してみたものの、みごとにハマりました。(悪い意味で)

他言語から来た人がGoを使い始めてすぐハマったこととその答え
こちらの記事を読ませていただいて、そこでやっと色々と解決できました…

ここには、上記の記事を読むだけでは解決できなかった課題に立ち向かった記録を残しておきます。

使用環境

  • macOS Catalina 10.15.4

Docker で環境構築

Docker コンテナ内で動かしたいので、golang の公式イメージを使って環境を作ります。
テキストエディタの補完だったりに必要そうであればローカルにもインストールしておきます。

Go には「GOPATH 以下に作業環境を作る」という特徴があるようで、ここが第一つまづきポイントでした。
Docker で作業する場合でも、これに従った構成にします。

docker-compose

version: "3.8"
services:
  go:
    image: golang:1.13.10
    working_dir: /go/src/github.com/{ユーザー名}/{リポジトリ名}
    volumes:
      - .:/go/src/github.com/{ユーザー名}/{リポジトリ名}

GOPATH

$ docker-compose run go /bin/bash -c "go env"
...
GOPATH="/go"

ディレクトリ構成

/go
 └── src
     └── github.com
         └── {ユーザー名}
             └── {リポジトリ名}
                 ├── docker-compose.yml
                 └── main.go

モジュール管理

プロジェクト内に使用するモジュールの一覧を保持して、git で管理して、というようなことをやりたかったのですが、手順がよくわからずまた少し詰まっていました。

以降の操作はすべて、dockerコンテナ 内での実行を前提としています。

go1.13 未満

go のバージョンが 1.13 未満の場合、後述する go mod init 等のコマンドをそのまま実行しようとすると、下記のエラーが出て実行できません。

go: modules disabled inside GOPATH/src by GO111MODULE=auto; see 'go help modules'

環境変数を設定してから実行することで正常に実行できます。

$ export GO111MODULE=on

go.mod の作成

プロジェクトで go mod 系のコマンドを初めて実行する場合、まず init で初期化します。

$ go mod init

すると、 go.mod のファイルが作成されます。

vendor でモジュール管理

$ go mod vendor

このコマンドで、必要なモジュールを vendor ディレクトリに保持します。
未ダウンロードのモジュールがあった場合は、モジュールのダウンロードプロセスが先に走ります。

vendor フォルダと go.sum ファイルが作成されれば成功しているはず。
これで各種モジュールが使えるようになります。

vendor を作成したくない場合は go mod download 等のコマンドだけ使えば良さそうです。
go mod download のあとに go mod vendor すると、download で引っ張ってきたモジュールが既にローカルにあるので、ダウンロード処理は走らず、ささっと vendor のみが作成されます。

他言語に置き換えると?

使用モジュールの管理 依存性管理 本体
Go go.mod go.sum vendor
Node.js package.json package-lock.json node_modules
Ruby Gemfile Gemfile.loc vendor/bundle

こんな感じの関係性になるのかなと思っています。

ディレクトリ構成

/go
 └── src
     └── github.com
         └── {ユーザー名}
             └── {リポジトリ名}
                 ├── docker-compose.yml
                 ├── go.mod
                 ├── go.sum
                 ├── main.go
                 └── vendor
                     ├── 各種モジュール
                     └── modules.txt

さいごに

なにぶん初学者ゆえ、『ちげぇよコノヤロー!』な箇所があればやさしくご指摘お願いしたいですm(_ _)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoでDeque(両端キュー)

  • Golangの標準ライブラリにDeque両端キューが見つからなかったので自作した
  • sliceの先頭に追加する処理は(実装によるだろうが)遅かったので採用していない
package main

import (
    "fmt"
)

func newDeque() *deque {
    return &deque{}
}

type deque struct {
    prep []interface{}
    ap   []interface{}
}

func (d *deque) push(item interface{}) {
    d.ap = append(d.ap, item)
}

func (d *deque) unshift(item interface{}) {
    d.prep = append(d.prep, item)
}

func (d *deque) empty() bool {
    return len(d.ap) == 0 && len(d.prep) == 0
}

func (d *deque) pop() interface{} {
    lenap := len(d.ap)
    if lenap != 0 {
        v := d.ap[lenap-1]
        d.ap = d.ap[:lenap-1]
        return v
    }
    v := d.prep[0]
    d.prep = d.prep[1:]
    return v
}

func (d *deque) shift() interface{} {
    lenprep := len(d.prep)
    if lenprep != 0 {
        v := d.prep[lenprep-1]
        d.prep = d.prep[:lenprep-1]
        return v
    }
    v := d.ap[0]
    d.ap = d.ap[1:]
    return v
}

func main() {

    q := newDeque()

    for i := 1; i < 10; i++ {
        q.push(i)
    }
    for i := 1; i < 10; i++ {
        q.unshift(i)
    }
    q.push(-999)

    for !q.empty() {
        fmt.Println(q.pop())
    }

    for i := 1; i < 10; i++ {
        q.push(i)
    }
    for i := 1; i < 10; i++ {
        q.unshift(i)
    }
    q.push(-999)

    for !q.empty() {
        fmt.Println(q.shift())
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】Byte型

【Golang】Byte型

Golangの基礎学習〜Webアプリケーション作成までの学習を終えたので、復習を兼ねてまとめていく。 基礎〜応用まで。

package main
//バイト型
//ネットワーク系、ファイルの読み書き系で使われる

import "fmt"

func main() {
    //バイト型 アスキーコード
    //初期化
    b := []byte{72, 73}
    fmt.Println(b)
    //>>72,73

    //string() 文字列に変換
    fmt.Println(string(b))
    //>>HI

    //バイトのスライスに変換
    c := []byte("HI")
    fmt.Println(c)
    //>>72,73

    //文字列に変換
    fmt.Println(string(c))
    //>>HI
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang1.14でt.Logの出力がちょっと変わったらしいので今更試してみた

□ 背景っぽいもの

最近golangの最新情報追えてないなーと思いながら、golang1.14のリリースノートを眺めていたらにこんな事が書いてありました。

Testing
go test -v now streams t.Log output as it happens, rather than at the end of all tests.

引用 https://golang.org/doc/go1.14

英語が苦手なニワトリが和訳に自信がなかったので、Google先生に頼んで翻訳してもらうと
「go test -vt.Logすべてのテストの最後ではなく、発生時に出力をストリーミングするようになりました。」
とのこと。

正直これを見て
「ほーん、なるほど(え?大して変わってなくない?)」
程度にしか感じていなかったのですが、何事も知ったかぶりは良くない。
慢心、ダメ絶対。

というわけで、「そうだ試してみよう」と思い立ったので試した結果をまとめていきます。

□ 試してあそぼう

というわけで適当なテストコードを作っていきます。
今回はログ出力を見ることが目的なので、めっちゃ適当に作ってみました。

package main

import "testing"

func Test3の倍数を入れるとアホになる関数(t *testing.T) {
    t.Log("アホになる関数のテスト開始")
    if _, err := aho(4); err != nil {
        t.Error(err, "アホになれませんでした")
    }
    t.Log("アホになる関数のテスト終了")
}

で、これは実行したらこんな感じになります。
まあ、見慣れた子ですね。

$ go test -v
=== RUN   Test3の倍数を入れるとアホになる関数
--- FAIL: Test3の倍数を入れるとアホになる関数 (0.00s)
    main_test.go:6: アホになる関数のテスト開始
    main_test.go:8: これは3じゃないからアホにはなれません アホになれませんでした
    main_test.go:10: アホになる関数のテスト終了
FAIL
exit status 1
FAIL    _/Users/i-dach/Documents/sandbox/toy    0.234s

ちなみにバージョンは「1.13.5」です。

$ go version
go version go1.13.5 darwin/amd64

ということで、下準備はできたので早速Versionを1.14に変えましょう。
バージョン管理ツールを使ってgolangを入れておくと便利で、私はgvmを使ってます(好きなのをお使いください)
gvmだとこんな感じで切り替えができます。

# まずは対象がDLできるかどうかを確認
$ gvm listall

gvm gos (available)

~
   go1.13.9
   go1.13.10
   go1.14
   go1.14beta1
   go1.14rc1
   go1.14.1
   go1.14.2
~   

# できそうだったらinstallして適用する
$ gvm install go1.14
Updating Go source...
Installing go1.14...
 * Compiling...
successfully installed!

$ gvm use go1.14
Now using version go1.14

$ go version
go version go1.14 darwin/amd64

これでgolangのVersionが1.14になりました。
では早速試してみる。

$ go test -v 
=== RUN   Test3の倍数を入れるとアホになる関数
    Test3の倍数を入れるとアホになる関数: main_test.go:6: アホになる関数のテスト開始
    Test3の倍数を入れるとアホになる関数: main_test.go:8: これは3じゃないからアホにはなれません アホになれませんでした
    Test3の倍数を入れるとアホになる関数: main_test.go:10: アホになる関数のテスト終了
--- FAIL: Test3の倍数を入れるとアホになる関数 (0.00s)
FAIL
exit status 1
FAIL    _/Users/i-dach/Documents/sandbox/toy    0.256s

お?確かに微妙に変わってる?

単体で見ても分かりづらかったので、並べてみます。

golang1.13.5
$ go test -v
=== RUN   Test3の倍数を入れるとアホになる関数
--- FAIL: Test3の倍数を入れるとアホになる関数 (0.00s)
    main_test.go:6: アホになる関数のテスト開始
    main_test.go:8: これは3じゃないからアホにはなれません アホになれませんでした
    main_test.go:10: アホになる関数のテスト終了
FAIL
exit status 1
FAIL    _/Users/i-dach/Documents/sandbox/toy    0.234s
golang1.14
$ go test -v 
=== RUN   Test3の倍数を入れるとアホになる関数
    Test3の倍数を入れるとアホになる関数: main_test.go:6: アホになる関数のテスト開始
    Test3の倍数を入れるとアホになる関数: main_test.go:8: これは3じゃないからアホにはなれません アホになれませんでした
    Test3の倍数を入れるとアホになる関数: main_test.go:10: アホになる関数のテスト終了
--- FAIL: Test3の倍数を入れるとアホになる関数 (0.00s)
FAIL
exit status 1
FAIL    _/Users/i-dach/Documents/sandbox/toy    0.256s

すべてのテストの最後ではなく、発生時に出力をストリーミングするようになりました

なるほど、確かに書いてある通りですね。
しかし、良さがわからない。

そうだ、テストを増やしてみよう。

package main

import "testing"

func Test3の倍数を入れるとアホになる関数(t *testing.T) {
    for i := 0; i < 100; i++ {
        t.Log("アホになる関数のテスト開始 - ", i)
        if _, err := aho(i); err != nil {
            t.Error(err, i, "はアホになれませんでした")
        }
        t.Log("アホになる関数のテスト終了 - ", i)
    }
}

とりあえずテストはこんな感じに修正して、100個サブケースが回るようにしてみます。
で、実行した結果を並べて比較してみます。

golang1.13
$ go test -v
=== RUN   Test3の倍数を入れるとアホになる関数
--- FAIL: Test3の倍数を入れるとアホになる関数 (0.00s)
    main_test.go:7: アホになる関数のテスト開始 -  0
    main_test.go:11: アホになる関数のテスト終了 -  0
    main_test.go:7: アホになる関数のテスト開始 -  1
    main_test.go:9: これは3じゃないからアホにはなれません 1 はアホになれませんでした
    main_test.go:11: アホになる関数のテスト終了 -  1
    main_test.go:7: アホになる関数のテスト開始 -  2
(中略)
    main_test.go:9: これは3じゃないからアホにはなれません 94 はアホになれませんでした
    main_test.go:11: アホになる関数のテスト終了 -  94
    main_test.go:7: アホになる関数のテスト開始 -  95
    main_test.go:9: これは3じゃないからアホにはなれません 95 はアホになれませんでした
    main_test.go:11: アホになる関数のテスト終了 -  95
    main_test.go:7: アホになる関数のテスト開始 -  96
    main_test.go:11: アホになる関数のテスト終了 -  96
    main_test.go:7: アホになる関数のテスト開始 -  97
    main_test.go:9: これは3じゃないからアホにはなれません 97 はアホになれませんでした
    main_test.go:11: アホになる関数のテスト終了 -  97
    main_test.go:7: アホになる関数のテスト開始 -  98
    main_test.go:9: これは3じゃないからアホにはなれません 98 はアホになれませんでした
    main_test.go:11: アホになる関数のテスト終了 -  98
    main_test.go:7: アホになる関数のテスト開始 -  99
    main_test.go:11: アホになる関数のテスト終了 -  99
FAIL
exit status 1
FAIL    _/Users/i-dach/Documents/sandbox/toy    0.312s
golang1.14
$ go test -v
=== RUN   Test3の倍数を入れるとアホになる関数
    Test3の倍数を入れるとアホになる関数: main_test.go:7: アホになる関数のテスト開始 -  0
    Test3の倍数を入れるとアホになる関数: main_test.go:11: アホになる関数のテスト終了 -  0
    Test3の倍数を入れるとアホになる関数: main_test.go:7: アホになる関数のテスト開始 -  1
    Test3の倍数を入れるとアホになる関数: main_test.go:9: これは3じゃないからアホにはなれません 1 はアホになれませんでした
    Test3の倍数を入れるとアホになる関数: main_test.go:11: アホになる関数のテスト終了 -  1
    Test3の倍数を入れるとアホになる関数: main_test.go:7: アホになる関数のテスト開始 -  2
(中略)
    Test3の倍数を入れるとアホになる関数: main_test.go:9: これは3じゃないからアホにはなれません 94 はアホになれませんでした
    Test3の倍数を入れるとアホになる関数: main_test.go:11: アホになる関数のテスト終了 -  94
    Test3の倍数を入れるとアホになる関数: main_test.go:7: アホになる関数のテスト開始 -  95
    Test3の倍数を入れるとアホになる関数: main_test.go:9: これは3じゃないからアホにはなれません 95 はアホになれませんでした
    Test3の倍数を入れるとアホになる関数: main_test.go:11: アホになる関数のテスト終了 -  95
    Test3の倍数を入れるとアホになる関数: main_test.go:7: アホになる関数のテスト開始 -  96
    Test3の倍数を入れるとアホになる関数: main_test.go:11: アホになる関数のテスト終了 -  96
    Test3の倍数を入れるとアホになる関数: main_test.go:7: アホになる関数のテスト開始 -  97
    Test3の倍数を入れるとアホになる関数: main_test.go:9: これは3じゃないからアホにはなれません 97 はアホになれませんでした
    Test3の倍数を入れるとアホになる関数: main_test.go:11: アホになる関数のテスト終了 -  97
    Test3の倍数を入れるとアホになる関数: main_test.go:7: アホになる関数のテスト開始 -  98
    Test3の倍数を入れるとアホになる関数: main_test.go:9: これは3じゃないからアホにはなれません 98 はアホになれませんでした
    Test3の倍数を入れるとアホになる関数: main_test.go:11: アホになる関数のテスト終了 -  98
    Test3の倍数を入れるとアホになる関数: main_test.go:7: アホになる関数のテスト開始 -  99
    Test3の倍数を入れるとアホになる関数: main_test.go:11: アホになる関数のテスト終了 -  99
--- FAIL: Test3の倍数を入れるとアホになる関数 (0.03s)
FAIL
exit status 1
FAIL    _/Users/i-dach/Documents/sandbox/toy    0.352s

大量データで比較してみるとgo1.14のほうが結果が追いやすくなっていることが見て取れますね。
この理由を言語化するなら下記のような理由となるのではないでしょうか。

  1. どのテストでの実行の結果なのか、サブケースが入れ子になっているので、ぱっと見でわかりやすい
  2. 実行ファイル名の前に親テストの名称が入るので、テストケースが膨大になって親ファイル名が見えなくなってもなんのテストでのサブケースなのかがわかりやすい
  3. 最後に親テストの終了が来るので、サブケースが全て終わったかどうかがわかりやすい

□ 結論

go1.14のテストログは見やすくなった!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む