覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Go言語 - Goの特徴

Go言語(Golang)の特徴(メリット)として以下のようなものが一般的に挙げられています


高パフォーマンス

一般的に軽量言語(LL)ではメモリ管理のコストなどに注意してコードを構成しなければパフォーマンスが極端に悪くなることがあります。
それに対して、Goは細部を気にせずにコードを書いても比較的パフォーマンスを引き出しやすい傾向があります。


メモリ管理からの解放

C/C++等はメモリ管理を手動で行う必要がありますが、Goではメモリ管理をGoランライムに任せることができます


コンパイル速度の速さ

C/C++等に比べてGoのコンパイル速度は極端に早いです。
コンパイル速度が速いので、コードを修正してビルド・実行という開発の流れを気軽に何度も実行することができます


スタイルやコード整形

プログラムをチーム開発している際に、どの書式スタイルに統一するかという議論がなされることがあります。
Goではスタイルの規定が最初からしっかりしていて、かつ、そのスタイルをサポートするためのコード整形ツールも揃っています。
このことにより、Goではコードのスタイルに関して質を簡単に保つことができます。


シンプルな言語仕様

Goは機能の割にとてもシンプルな仕様で作られています。
人によっては1日程度で言語仕様を理解することもできます。


シングルバイナリ

Goで書かれたプログラムは基本的には単体で実行可能なシングルバイナリとして生成されます。
一旦コンパイルしてしまえばLL系言語で必要なランタイムや依存関係の管理は必要なくなります。
アプリのインストール・デプロイといった作業も非常に簡単になります。



関連エントリ

Go言語 - Ginを使ってHello World

Go言語ではnet/httpを利用することでHTTPサーバを立てることができまが、GinといったWebアプリ用のフレームワークも存在しています。


事前準備

以下を実行してGinのパッケージ取得を行います

go get github.com/gin-gonic/gin


とりあえずHello World

以下 Hello World の文字列を返す例です。

Engine.GETにより、ハンドリング対象のパスとハンドラを登録します。


■サンプルコード

package main

import (
    "log"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    router.GET("/hello", func(ctx *gin.Context) {
        ctx.String(200, "Hello World")
    })

    if err := router.Run(":8686"); err != nil {
        log.Fatal("Server Run Failed.: ", err)
    }
}


■実行結果

http://localhost:8686/helloにアクセスすると以下の結果が出力されます

Hello World


静的ページを返す

URLへのアクセスに対して静的ページを返す場合は、router.StaticFS 等を利用します。

以下サンプルでは、staticディレクトリにindex.htmlとuser.htmlというファイルを用意しているものとします

f:id:nini_y:20170519185348p:plain


■サンプルコード

package main

import (
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    router.StaticFS("/web", http.Dir("static"))

    if err := router.Run(":8686"); err != nil {
        log.Fatal("Server Run Failed.: ", err)
    }
}

■実行結果

  • http://localhost:8686/web/ に アクセスすると staticディレクトリのindex.html の内容が返される
  • http://localhost:8686/web/user.html に アクセスすると staticディレクトリのuser.html の内容が返される



関連エントリ

Go言語 - HTTPサーバでHello World

Go言語ではnet/httpを利用することでHTTPサーバを立てることができます。

とりあえず今回はお試しな感じでHTTPサーバを立ててみます


とりあえずHello World

以下 Hello World の文字列を返す例です。

http.HandleFuncにより、ハンドリング対象のパスとハンドラを登録します。その後にhttp.ListenAndServeでHTTPサーバを起動します

■サンプルコード

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {

    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World")
    })

        // ポート = 8686
    if err := http.ListenAndServe(":8686", nil); err != nil {
        log.Fatal("ListenAndServe failed.", err)
    }
}


■実行結果

http://localhost:8686/helloにアクセスすると以下の結果が出力されます

Hello World


静的ページを返す

URLへのアクセスに対して静的ページを返す場合は、http.FileServer を利用します。

以下サンプルでは、staticディレクトリにindex.htmlとuser.htmlというファイルを用意しているものとします

f:id:nini_y:20170519174645p:plain


■サンプルコード

package main

import (
    "log"
    "net/http"
)

func main() {
    http.Handle("/", http.FileServer(http.Dir("static")))

    if err := http.ListenAndServe(":8686", nil); err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

■実行結果

  • http://localhost:8686/ に アクセスすると staticディレクトリのindex.html の内容が返される
  • http://localhost:8686/user.html に アクセスすると staticディレクトリのuser.html の内容が返される


URLからprefix部分を取り除いてハンドリングする場合

アクセスされたURLのPrefixを除いて、ハンドリングする場合は http.StripPrefix を利用します

■サンプルコード

package main

import (
    "log"
    "net/http"
)

func main() {
    
    // アクセスされたURLから /web 部分を取り除いてハンドリングする
    http.Handle("/web", http.StripPrefix("/web", http.FileServer(http.Dir("static"))))

    if err := http.ListenAndServe(":8686", nil); err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

■実行結果

  • http://localhost:8686/web/ に アクセスすると staticディレクトリのindex.html の内容が返される
  • http://localhost:8686/web/user.html に アクセスすると staticディレクトリのuser.html の内容が返される



関連エントリ

Go言語 - WindowsのアクティブなセッションのIDを取得する

Windowsではセッションという概念が存在しています。(セッション 0 の分離 - Windows 7 対応アプリケーションの互換性

複数セッションの中で現在アクティブになっている(物理コンソールにアタッチしている)セッションのセッションIDをGo言語で取得するサンプルです。


■サンプルコード

package main

import (
    "fmt"
    "log"
    "syscall"
)

var (
    kernel32, _ = syscall.LoadLibrary("kernel32.dll")

    wtsGetActiveConsoleSessionID, _ = syscall.GetProcAddress(kernel32, "WTSGetActiveConsoleSessionId")
)

func abort(funcname string, err error) {
    panic(fmt.Sprintf("%s failed: %v", funcname, err))
}

// ActiveSessionID : アクティブなセッションのIDを返す。アクティブなセッションが無い場合は-1を返す
func ActiveSessionID() int32 {
    r0, _, callErr := syscall.Syscall(uintptr(wtsGetActiveConsoleSessionID),
        2,
        0,
        0,
        0)
    if callErr != 0 {
        abort("Call WTSGetActiveConsoleSessionId", callErr)
    }
    return int32(r0)
}

func main() {
    log.Println("App start.")

    log.Printf("Active Session ID %d", ActiveSessionID())

    log.Println("App End.")
}

単純に、WindowsAPIのWTSGetActiveConsoleSessionIdを呼び出しているだけです。(WTSGetActiveConsoleSessionId function | Microsoft Docs


■実行結果

2017/05/18 05:28:32 App start.
2017/05/18 05:28:32 Active Session ID 1
2017/05/18 05:28:32 App End.



関連エントリ

コード品質特性を悪化させるには

プログラムのより良い設計を支える中心的概念としてコード品質特性があります。これに点数付けした場合に、悪い点をとるためにはどうすればいいかについて書きました。

以下内容のベースはJavaです


そもそもコード品質特性とは

ここでは、以下をコード品質特性としてとりあげます

  • 凝集度(cohesion)
  • 疎結合(loose coupling)
  • 重複無し(zero duplication)
  • カプセル化(encapsulation)
  • テスト容易性(testability)
  • 可読性(readablity)

各々の項目で悪い点を取るためにはどうすればいいか以下の各項目を参考にしてください


凝集度(cohesion)

基本的に、凝集度は高い方が良いと言われます。凝集度を低くするためには、クラスやパッケージの責務を増やせばよいです。そのためには以下に従うと良いでしょう。

  • 1クラスを大きくする
    • 1クラスが大きければ、基本的にはそのクラスが持つ責務は増えます
  • 大量のインターフェースを実装させたクラスを作る
    • 実装したインターフェースが多いということは、それだけ役割があるということです。したがって責務も多くなります
  • クラス名を極端に汎用的にする
    • 汎用的過ぎると色々なメソッドが配置できます。結果的に責務が増えます
  • パッと見たときのクラス名の意味が分からないようにする
    • クラス名の意味がよく分からないと、クラスが整理されることは無く、機能追加時に何となくメソッドが増えたりします
  • 1パッケージに大量のクラスを配置する
    • パッケージで見たときの責務がたくさんある状態と言えるでしょう


疎結合(loose coupling)

基本的に、結合度が低い(疎結合である)ことが望ましいと言われます。結合度を高くするにはどうすればいいかは以下項目を参考にしてください

  • 実装の継承をする
    • 実装の継承は親クラスと子クラスの結合度を高めてくれる道具の一つです
  • インターフェースを作らず実装クラスだけ作る
    • 実装クラス同士の依存関係は結合度を高めてくれます
  • DIは用いない
    • Dependency Injection(DI)を使ってしまうと結合度が下がることがほとんどです。基本的に使わないようにしましょう。
  • シングルトンパターンを多用する
    • シングルトンパターンは具体的なオブジェクトを必要とします。結合度を高くするには良い手段です。
  • Observerパターンを用いない
    • Observerパターンはクラス間の関係性を緩くすることがあります。Observerを介さずに、直接必要な機能を呼び出しましょう。
  • パッケージ間で相互依存をする
    • 一方向に比べて双方向の依存は依存関係をとても複雑にします。
  • 必要な機能を提供するクラスのインスタンスを生成するためにコンストラクタを呼び出す
    • new によりインスタンス生成するのは普通にやることですが、コンストラクタを呼び出すと必ず具象クラスが登場することになります。コンストラクタ呼び出しによりクラス間の結合度は高まります。


重複無し(zero duplication)

いわゆるDRY原則に反することをすればおのずと悪い点数となるでしょう。

  • コピペを繰り返す
    • コピペを繰り返せば重複が大量に発生します
  • 汎用的な処理を見出さない
    • ほとんどの業務アプリケーションにおいて、ビジネスロジックと呼べる核となる部分は大きくはありません。核となる部分以外は基本的に汎用的な物として切り出せます。が、あえてそれをしなければ各機能の中に重複した処理がたくさん生まれます。
  • ライブラリを使用しない
    • 世の中にある汎用的なライブラリを使用しなければ、基本的に車輪の再発明をし続けることになります。重複コードがきっと増えます

カプセル化(encapsulation)

スコープを広げればカプセル化は破られるでしょう。また、できるだけ内部の実装に依存されるようにすればさらに悪い点が取れるでしょう。

  • スコープを大きく広げる
    • どんなクラスもpublicにしましょう。メソッドやメンバのスコープも広げましょう
  • 実装の継承をする
    • 実装の継承をすると基本的にカプセル化が破られます。
  • ポリモーフィズムを用いない
    • ポリモーフィズムを使用することは、実装を隠蔽(カプセル化)することにつながります。


テスト容易性(testability)

ユニットテストをするためのセットアップ等を困難にすれば悪い点数となるでしょう。
そもそも意識的にユニットテストがし易いように作らなければ良い点は取りにくいです。

  • 外部環境に依存させる
    • 外部環境(ファイルやミドルなど)に依存したクラスを作ると、インスタンス化が困難になります。インスタンス化できなければ簡単にはユニットテストは書けません。
  • クラス間にインターフェースを介在させない
    • インターフェースを介在させるとテストし易くなります。できるだけ実装クラス同士で関係性を持つようにしましょう
  • Injectionを使用しない
    • コンストラクタInjection等で対象クラスに必要な機能を外からセットできるようにするとテストし易くなってしまいます。必要な機能は対象クラスの中で自ら取得するようにしましょう
  • メソッドの戻り値をできるだけvoidにする
    • 戻り値が無ければ対象メソッドの振る舞いが予定通りなのかどうか判定しにくくなります
  • クラスにたくさんのインスタンス変数を持たせる
    • 対象クラスのセットアップがかなり大変な状態になり、テストがしにくくなります。


可読性(readablity)

長いコードや深いコードを書きましょう。また命名を適当にやっていればソースコードは読みにくくなります。

  • 「ソースコードを汚くするには」に記載した内容を実施する
  • 基本的に以下のようなコードは可読性が低いと思えばよいです
    • 長い
    • ネストが深い
    • 条件分岐が多い
    • 名前が分かりにくい
  • マジックナンバーを使用する
    • マジックナンバーを使用するとその値の意図が伝わらなくなります。
  • 手続き的な書き方をする
    • 何がしたいか?ではなく、どうやって実現するのか?をソースコードに書き表せば可読性は下がるでしょう。


補足

コード品質特性を悪化させる方法をいくつか書きましたが、ほとんどの人が、通常はこのようなことを意識的にやることはありません
しかし、当初品質が高く保たれていたプログラムでも、機能追加をしてリリースを繰り返しているといつの間にか品質特性が悪化しています。
(機能追加によりプログラム改修をする際には、)我々は無意識のうちに品質の悪化につながるようなプログラムの書き方をしていることになります。
コードの品質を保つことについては、プログラムの新規作成時よりも機能追加する際により注意が必要ということになります。 プログラム改修時にメソッドの処理内に新しくifの条件分岐を足す場合も、本当にその分岐を配置していいのか?と自問すべきでしょう。



関連エントリ