Software Engineer Blog

エンジニアブログです。技術情報(Go/TypeScript/k8s)や趣味の話も書くかもです。

GolangのSort処理について

GolangのSort処理

GolangのSort処理について、まとめました。
(// package sort と記載があるサンプルコードは、Golang本体のソースコードです)

Sort

Sort Interface

Golangでは、structのソートを行うため、sort.Interfaceを実装する必要があります。
(実際はGo1.8以降、下記の sort.Slice() を利用して、ソートすることができるようになりました。参考)

// sort.Interface
type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}
メソッド 特徴
Len() int 配列・リストの要素数
Less(i, j int) bool i < j を満たすかの真偽値
Swap(i,j int) iとjの要素を入れ替える

Sortの方法

標準パッケージで対応している型は下記になります。

  • float64
  • int
  • string

ここでは、ソートの方法をint配列をもとにみていきます。

sort.Ints(a int[])

Ints() メソッドは、int[]型をソートするメソッドです。
これは、昇順(increacing)にソートされます。

nums := []int{4, 3, 2, 10, 8}
sort.Ints(nums)
fmt.Println(nums)

// output: [2 3 4 8 10]

内部では、 IntSlice 型のstructにつめかえを行い、Sort関数を呼んでいます。
(sort.Interfaceを実装するルールは同じ)

// package sort

// Ints sorts a slice of ints in increasing order.
func Ints(a []int) { Sort(IntSlice(a)) }


// IntSlice
// Len,Less,Swapを実装したstruct

// IntSlice attaches the methods of Interface to []int, sorting in increasing order.
type IntSlice []int

func (p IntSlice) Len() int           { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

sort.Reverse(data Interface) Inteface

Reverse(data Interface) は、sort.Interface型のソート方法を逆順に修正する関数です。
実際に内部で行っていることは、下記の2点です。

\1. reverse structstructを詰め替える
reverse struct は、 sort.Interface を埋め込んでいます。

// package sort

type reverse struct {
    // This embedded Interface permits Reverse to use the methods of
    // another Interface implementation.
    Interface
}

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
    return &reverse{data}
}

\2. Lessメソッドの引数をスワップ
reverse struct は、実装を参照したところ、 Less(i,j int) bool のみを再実装しています。
これにより、比較条件を逆にすることで、逆順ソートを実現しています。

// Less returns the opposite of the embedded implementation's Less method.
func (r reverse) Less(i, j int) bool {
    return r.Interface.Less(j, i)
}

sort.Search(n int, f func(int) bool) int

nの長さの配列内でf()=true となる最小のindexを返却します。
→ 配列内で指定のvalueがあるかどうかを調べる関数です。

特徴

  • 条件を満たさなかった場合、nが返却
  • 一致を探すことも、ある数値以上の値を探すことも可能
  • binary search(二分探索)で検索
    • ソートされた配列に対して実行しないと正確な結果が返却されない
  • 下記はSearchのラッパー関数
    • func SearchFloat64s(a float64, x float64) int
    • func SearchInts(a int, x int) int
    • func SearchStrings(a []string, x string) int
// package sort

// binary sort の実装
func Search(n int, f func(int) bool) int {
    // Define f(-1) == false and f(n) == true.
    // Invariant: f(i-1) == false, f(j) == true.
    i, j := 0, n
    for i < j {
        h := i + (j-i)/2 // avoid overflow when computing h
        // i ≤ h < j
        if !f(h) {
            i = h + 1 // preserves f(i-1) == false
        } else {
            j = h // preserves f(j) == true
        }
    }
    // i == j, f(i-1) == false, and f(j) (= f(i)) == true  =>  answer is i.
    return i
}

// Int ラッパー関数
func SearchInts(a []int, x int) int {
    return Search(len(a), func(i int) bool { return a[i] >= x })
}

利用サンプル

nums := []int{4, 3, 2, 10, 8}
x := 8

// 事前にソートしておく
sort.Ints(nums)
// search asc
index = sort.Search(len(nums), func(i int) bool {
  return nums[i] >= x
})

if index < len(nums) && nums[index] == x {
  // nums[index] = 8 = x
} else {
  // x は nums[]中に存在しなかった
  // だが、indexはnums[]の中にxをインサートする位置にある
}

sort.Slice(slice interface{}, less func(i, j int) bool)

渡したsliceを、ソートする関数。
less関数で定義された順にソートされる。

特徴

  • Go1.8以上
  • sort.Interfaceを実装していなくてもソートが可能となっている
  • stable sort(安定ソート)を保証していない
    • sort.SliceStableを利用することで保証される

(参考)実際のソート処理の中身について

sort.Sort(data sort.Interface) は渡されるデータによってソートの種別変えています。
流れとしては、基本的にクイックソートを利用して、条件を満たした際の他のソート手法に切り替わるといった挙動です。

  • ヒープソート
    • 条件: log(n+1)の整数値の2乗の深さとなった場合
      • 右に1ビットずつシフト演算して0になるまでの回数の2乗が最大の深さ(maxDepth)
      • maxDepthが0となったときに、ヒープソートに切り替わる
// package golang

// Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
// package golang

// Use ShellSort for slices <= 12 elements

まとめ

Golangのsortパッケージについて、学びました。
実際のソート処理の中身までは、深く追求しませんでしたが、
ソートを行う利用方法については触れられたかなと思います。

参考

公式ドキュメント
golang の sort インタフェース難しい問題が解決した

golang.tokyo #6 に行ってきました

概要

golang.tokyo #6 に参加しましたので、レポートを記載いたします。
(会場は、DeNAさんでお寿司(食べ損ねた)とお酒をご用意いただいておりました。)
下記は各セッションのまとめです。

Gopher Fest 2017

@tenntenn さん

スライド

概要

サンフランシスコで開催されるGoSFが主催のイベントの参加レポートです。
セッションの中のひとつ The state of Go のお話

The state of Go

Go1.9での仕様変更や標準ライブラリの改善についてのお話
(余談ですが、tip.golang.orgで最新のmasterのドキュメントが見れるそうです)

  • Go1.9は5/1にコードフリーズ済み
  • リファクタリングを安全にする方法
  • Goで安全にリファクタできるのか?
    • 定数の場合は問題なし
    • 関数はラップして引き継げば問題なし
    • 型の場合→ 問題有り
      • 型を作る → メソッドが引き継げない
      • 埋め込み → キャストができない
      • Go1.9でAlias導入
  • 言語仕様変更
    • Alias を作成できる
      • 型のエイリアスを定義できる
      • こんな感じで type A = http.Client
      • 完全に同じ型
      • キャスト不要
      • エイリアスの方ではメソッドを定義できない
  • 標準ライブラリ変更
    • math/bits ビット演算に便利なライブラリ
    • syc.Map スレッドセーフなマップ
    • html.templateがより安全に利用可能に
      • errorを返却するようになる
    • os.Exec
      • 環境変数が重複の際、一番後ろのものを優先する
        • ソース中で上書き可能
  • go testの改善
    • vendorを無視するようになった

感想

Go1.9にあたっての改善点のお話がメインとなりました。
@tenntennさんもおっしゃってましたが、 go testvendor配下を
みなくなる修正はなかなか良いですね。

初めてGolangで大規模Microservicesを作り得た教訓

Yuichi Murata さん

スライド未公開(2017/06/01)

概要

GAE/Goで大規模なMicroservices(30くらい)を作った際の、教訓のお話

  • いきなりつかってみたため、失敗談が中心
  • 構成
    • GAE/Go
    • Gin/Echo
    • Microservices構成
  • 結論
    • 困ったときはGoの哲学にそったシンプルなアプローチを取る
    • Goを過信せずパフォーマンスに気を配る

教訓

1. フレームワークにこだわらない

  • Railsの経験から良いフレームワーク探しをこだわって行った
  • Gin
    • 良い点
      • シンプル
      • App Engine とのインテグレーション有り
      • Framework Context vs App Engine Context
        • gin.ContextからApp Engine Contextに派生させられることで解消
    • 困った点
  • Echo
    • 良かった点
    • 困った点
      • 開発が盛んなことにより設計が書き換わる
        • バージョンを固定することで対応(アップデートできない..)
  • まとめ
    • Goでは、net/httpパッケージで十分な部分もある
      • 足りない部分はライブラリを利用
    • フレームワークを利用しなくても統一的なコードとなる
      • 統一的なコードを書くためのフレームワークだったが、Goなら標準で統一的となる

2. Interfaceを尊重する

  • 独自のエラー型を定義
    • こちらの型にerrorを寄せる
      • 毎回キャストするのが面倒のため
  • Nilに型がある問題発生
  • まとめ
    • Interfaceが定義されたものはそのまま利用するほうが自然に実装できる

3. regex compile/ reflectionが遅い

  • バリデーション
    • JSON Schemaを利用
  • GolangでのJSON Schema
  • パフォーマンステストで問題発覚
    • バリデーションの有無でパフォーマンスに顕著な差が出る
    • pprof を利用して実行を解析
  • 問題点の解消
    • Regexコンパイルを毎回行う実装
      • キャッシュすることで解消
    • reflectionを多用していた
      • 利用しないように修正
    • go-jsvalを利用すればさらに早くなった
  • まとめ
    • regex/reflectionのコスト高を意識する
    • regex compile / reflectionはとにかく重い

4. 非対称暗号は遅い

  • 認証・認可処理
    • 非対称鍵の処理が重い
      • opensslと比べcypto/rsaが貧弱
        • 一桁程度の差が生じる
      • cgoを利用したopensslのバインディングも存在
        • GAEでは利用不可
  • 荒業で解消
    • 非対称鍵の処理署名処理をPHPにして外出し
    • httpリクエストのほうが早い

感想

GAE/Goでサービスを作る際の問題点がいくつか聞けました。
サービスでの利用はこれからあるかもなので、参考になりました。
またnilに型がある問題は、まだあたったことがないので知れて良かったです。

[LT]ゲーム開発に欠かせないあれをしゅっとみる

ゴリラのアイコンの方(@Konboi)

スライド

概要

  • CSVの話
  • カヤックではデータの受け渡しとしてCSVを利用している
    • マスターデータ等
  • CSV困る時
    • カラムとデータの関連性が見づらい
    • 空欄がつらい
    • DBにインポートする系はその際にわかる
    • 調査系で利用する時つらい
  • csviewer
    • csvをDBのテーブルを検索するように参照できる
  • ライブラリ
    • sliceflag
      • 同一オプションで複数の値を受け取りたい
      • 普段のflagの利用方法と変わらない
    • tablewriter
      • データをいい感じにテーブル表示してくれる

[LT] Go Review Commentを翻訳した話

@knsh14 さん

スライド

概要

Go Code Review Commentを読もう
(→ 読みます)

  • Go Code Review Commentとは
    • コードレビューをする際に見るべき箇所をまとめたもの
    • Effective Goの簡略版
  • 翻訳してみました(qitta)
  • 良かった点
    • 正解のパターンを勉強できるところが効率が良い
  • 内容
    • コードの見た目を改善
      • ツールでなんとかしよう
      • golintは優秀
    • コメントや文章の体裁
    • Tips系
      • より良いパターンを記載
    • 設計の指針
  • まとめ
    • 良いドキュメントの翻訳は英語の勉強に最適

[LT] ScalaからGo

James さん

スライド未公開(2017/06/01)

概要

  • 関数型開発はGoでできるか?
  • 関数型開発のコンセプトは利用できるか?
    • 利用できる!!
  • 関数型開発とは?
    • 副作用がない開発
    • 副作用あり
      • テストしにくい、バグの原因
    • 部分適用
  • Goは初心者が入りやすく会社での導入がスムーズに行える点が良い

[LT] Crypto in Go

概要

  • Goにおける暗号アルゴリズムの利用
  • AES
    • 固定長でしか利用できない
  • AES + Padding + HMac
    • 行数があって面倒
  • AES + GCM
    • 機密モード + 認証モードがひとつに
    • とってもシンプル

まとめ

各セッションとLTについて、ざっくりと記載いたしました。
詳細知りたい際は、スライドが公開されるはず(ほぼされてますが)ですのでそちらをご参照ください。
全体的に有意義な発表が聞けて楽しかったです。
開催いただきありがとうございました。(次回も参加したいです)

JUnit4を利用したJavaのテスト概要

概要

JUnitを利用した、Javaユニットテストを作成する際の基本的な部分について記載しました。
JUnit実践入門の内容 + 経験から記載しました。

テストコード基本

ユニットテスト対象物

テストクラスに対する、テスト対象物はテストクラスのコードのみとします。
そのため、外部オブジェクトの作成等は基本的にモック化します。
(外部クラスのコードのテストは、外部クラスのテスト上で行えば良いとの考え方です。)
またテストメソッドの対象物ですが、基本的に publicメソッド を対象とします。
privateメソッドは、publicメソッドを正確にテストすることで、同時にテストされていなければ、ならないと考えているためです。

各命名と役割

命名 役割 備考
XXXTest テストクラス名
xxx_条件_結果 テストメソッド - xxx()メソッド
- @Testアノテーションを付ける
sut テスト対象オブジェクト変数名 - テスト対象のオブジェクトを明確化するために同一名称で宣言する。
- System Under Testの略
setUp() テストごとの前処理 - 各テスト実行ごとの前処理を記載
- @Before アノテーションを付ける
tearDown() テストごとの後処理 - 各テスト実行ごとの後処理を記載
- @After アノテーションを付ける
setUpClass() テストクラスごとの前処理 - テストクラス内のテストが一つでも実行される前の処理を記載
- @BeforeClass アノテーション記載
- public static methodで宣言
tearDownClass() テストクラスごとの後処理 - テストクラス内のテストが全て実行された後の処理を記載
- @AfterClass アノテーション記載
- public static methodで宣言

テストのフェーズ

  1. 事前準備 = set up
    下記を行います。

    • テストデータ作成
    • DBの接続
    • モックの作成
    • モックからの返り値の設定
    • Testクラスに渡すオブジェクトの作成
    • Testクラスのオブジェクト化
    • ..etc
  2. 実行 = exercise
    テスト対象のメソッドを実行します

  3. 検証 = verify
    メソッドの実行結果を検証します。
    メソッドの返り値が期待値通りかを判定するフェーズです。

  4. 後処理 = tear down
    テスト実行後の後処理を行います。

    • テストデータの破棄
    • DBの切断

Assert

テスト結果を検証するための方法についてです。
検証とは基本的に、テスト結果が期待値通りかを確かめるフェーズとなります。

  • Assertは、org.junit.Assert.assertThat() を利用
     - `assertThat` 一つで基本的に事足りる
     - static importを行っておく
    
  • Matcher
    • org.hamcrest.CoreMatchers
      • is() equalsメソッドによる比較
      • nullValue() nullであることを検証する
        • 利用する際は、 is(nullValue()) と利用する
      • not() 評価値を反転させる
    • org.junit.matchers.JUnitMatchers
      • hasItem() Iterableインタフェースを実装したクラスに、期待値が含まれているか検証
      • hasItems() 複数指定可能
    • BaseMatcher<> を継承することで、カスタムMatcherが作成できる

サンプルコード

上記までの内容を踏まえた、テストサンプルコードです。

public class TargetClassTest {
  @Before
  public void setUp() {
    // テストごとの共通の前(初期化)処理
  }

  @After
  public void tearDown() {
    // テストごとの共通の後処理
  }

  @Test
  public void hasUserName_ユーザー名称をもつ場合_trueを返却() throws Exception {
   // set up
   /** テスト対象のオブジェクトの変数名は sut とする **/
   TargetClass sut = new TargetClass)();

   // exercise
   boolean actual = sut.hasUserName();

   // verify
   /** 英語の構文 "assert that actual is expected" を意識する**/
   asseertThat(actual, is(true));
  }

(追記) Exception発生時のテスト

  /**
  * Exception発生時のテスト
  */
  @Test(expected = NullPointerException.class)
  public void fetchUserName_ユーザー名称を取得に失敗_exception発生() {
   // set up
   /** テスト対象のオブジェクトの変数名は sut とする **/
   TargetClass sut = new TargetClass)();

   // exercise
   /** 下記メソッド実行時にException発生 **/
   Result actual = sut.fetchUserName();
  }
}

テスト作成にあたっての他用語

Fixture

事前準備にて、設定する情報のことで、下記2パターンのFixtureの設定方法があります。

  • inline set up
    • 各テストメソッドごとに、fixtureのset upを行う
    • simpleに設定を記述すれば良い
    • コードが長くなり、可読性が悪くなりがち
  • implicit set up

パラメータ化テスト

テストに対してパラメータを設定したい際に、利用します。

Theories を利用して実現します。

  • Theories
    • @RunWith(Theories.class) をクラス宣言の前に宣言
      • テストランナーの一つ
    • @Theory テストメソッドのアノテーション
    • @DataPoint パラメータ
// サンプルコード
@RunWith(Theories.class)
public class TargetClassTest {
  @DataPoint
  public static int PARAM_1 = 1;

  @DataPoint
  public static int PARAM_2 = 2;

  public TargetClassTest() {
    // 初期化処理
  }

  @Theory
  public void testCase(int x) throws Exception {

  }
}

Rule

テストをプラグイン的に拡張できる機能のことです。
テストに関係なく、初期化+後処理をしたい場合 に便利です。
publicなfiledにアノテーションをつけて利用します。

// サンプル
@Rule
public Timeout timeout = new Timeout(100);
  • 処理はテストごとに実行される
    → テストごとに実行したい共通処理をRuleとしてまとめられる
  • @ClassRule にてテストClassごとに1回のRuleも作成可能
  • カスタムルールの作成 方法1
    • org.junit.rules.TestRule インターフェイスを継承する
    • Statement apply(final Statement base, Description description) をOverrideする
      • 引数で渡される Statement base が各テストメソッドのイメージ
      • base.evaluate() を実行することでテストが実行される
    • これを利用することでテストに共通の前後処理を定義することができる
public class HogeRule implements TestRule {

  private void before() {}
  private void after() {}

  @Override
  public Statement apply(final Statement base, Description description) {
    // new Statement()することで実際のテストを拡張している
    return new Statement() {
      // 前処理
      before()
      
      // テスト実行 (@Before -> テスト実行 -> @After)
      base.evaluate();

      // 後処理
      after()
    }
  }
}
  • カスタムルールの作成 方法2 (← こちらのほうが効率よく作れます)
    • 上記の設定を、事前に行っているクラス(ExternalResource)を利用
    • ExternalResourceを継承して作成
    • 利用する際は、before()after()をOverrideして、前後処理を記載する
    • Rule内で利用する変数はprivate fieldに宣言しておき、コンストラクタで受け取る
// 上記のサンプルコードをすでに実装した抽象クラス
public abstract class ExternalResource implements TestRule {
    public Statement apply(Statement base, Description description) {
        return statement(base);
    }

    private Statement statement(final Statement base) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                before();
                try {
                    base.evaluate();
                } finally {
                    after();
                }
            }
        };
    }

    /**
     * Override to set up your specific external resource.
     *
     * @throws if setup fails (which will disable {@code after}
     */
    protected void before() throws Throwable {
        // do nothing
    }

    /**
     * Override to tear down your specific external resource.
     */
    protected void after() {
        // do nothing
    }
}

モック

モックとは、テスト対象クラス以外のクラスを擬似的に作成して、対象外クラスのメソッドの 返り値を設定する事ができます。
テスト対象クラス に絞ったテストを行うために重要な要素です。

  • org.mockito.Mockito ライブラリを利用する
    • JavaDoc
      • mockの利用方法について詳細が記載されている
    • モックの作成 mock(TargetClass.class)
    • 返り値の設定 when(sut.exec(anyInt(), any(Date.class))).thenReturn(obj)

データベースのテスト

(手法が確立できていないため一時保留)

  • H2 Databaseの利用
    • ピュアJavaSQLデータベース
    • インメモリ or ファイルの2種類の作成方法がある
    • 組み込みモード、サーバーモード、ミックスモードで動作
    • jarファイル1つ(1.5MB)動作
    • JDBCサポート
  • DBUnit
    • @Ruleとして、作成することでDB接続周りを一手に引き受けられる
    • org.dbunit.AbstractDatabaseTester を継承する

コードカバレッジ

カバレッジの種類

  • C0(命令網羅)
    • プログラム中に定義された命令が1回以上実行されたかを測定
    • line coverageと同等
    • 基準
  • C1(分岐網羅)
    • すべての分岐(if)を1回以上実行したかを測定
    • if条件の true or false のどちらも通す必要がある
  • C2(条件網羅)
    • すべての条件を1回以上実行したかを測定
    • if条件のtrueになる全ての条件とfalseになるすべての条件を見る必要がある

カバレッジ測定ツール

# テスト実行 + カバレッジ測定
% mvn clean jacoco:prepare-agent test jacoco:report

# カバレッジレポート参照
% target/site/jacoco/index.html

おわりに

JUnitを利用した、ユニットテストに関してざっくりとした記事を書きました。
昨今は、マイクロサービス化の利用等でますますユニットテストの価値が上がってきているかと思います。
(テストがないコードはレガシーとまで言われるかもしれないですね。。)
テストを何のために書くのかや、どういったテストが良いテストなのかについては特に記載しませんでした。
そのあたりは自分で考えていただいて、テストの有用性に対して正確な理解を個人でしてもらえればと思います。
また、自分で利用したことがないため、テストランナーについてあまり記載できていませんでしたので、 学んだ際に追記できればと思います。

参考文献

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

Vagrantに開発環境構築

新規サービスの開発環境を整えるために、新たにnginx+php-fpm環境を構築しました。

参考のリンクまとめ

1.Vagrant構築

qiita.com

2.nginx構築

qiita.com

3.php-fpm構築

qiita.com

centOS7では、
nginxのスタートコマンドは
systemctl start nginx
自動起動コマンドは
systemctl enable nginx

php-fpmも同様に
systemctl start/enable php-fpm

また、開発ディレクトリを変更してPHPを動作させるには、/etc/nginx/conf.d/default.conf内の

 location / {
      root   /var/www;
      index  index.php;
}

と変更し、以下の部分も

location ~ \.php$ {
      root           /var/www;
      fastcgi_pass   127.0.0.1:9000;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  /var/www$fastcgi_script_name;
      include        fastcgi_params;
    }

と変更することで、動きました。 書いてたら、Qiitaに書けよと言われそうな(もしくは、この記事いる?)記事になってました。

Alloyにて、別Viewのオブジェクトの取得方法

Viewは、Requireを利用してViewごとにファイルを分けて記述していたのですが、当該ControllerのViewではないViewの情報を引っ張ってきたい処理がしたかったので、やってみました。
めっちゃ簡単でした。正直、MVC慣れしていないのでまだ手探り状態です。

例えば、hoge Controller上にて、indexのを引っ張ってきたい場合

var tab = Alloy.createController("index").getView("tab1");

と書けばよいですね。
Controllerを経由して情報を引っ張ってくる ということが重要。

Appcelerator studioでのアプリ開発のTextFieldクリック時に、アプリが落ちる問題

weird bug after clicking on textField » Community Questions & Answers » Appcelerator Developer Center

上記を参考に、変更を加えました。 どうやら、TableViewの中身が何もないと、落ちてしまうようです。 なので、以下のように一行足してあげればOKです。

    $.table.setData([{"title": ""}]);

(実際のところ、この行邪魔になるけどなぁ)

laravel4.2プロジェクト作成

composer create-project laravel/laravel=~4.2 /vagrant/hoge --prefer-dist

でcreateすると、

 [RuntimeException]
  Failed to clone git@github.com:laravel/laravel.git via git, https,
  ssh protocols, aborting.
  - git://github.com/laravel/laravel.git
    Cloning into '/vagrant/green'...
    error: could not commit config file /vagrant/green/.git/config

このようなエラーが出ました。

composer create-project laravel/laravel=~4.2 hoge --prefer-dist

ディレクトリを真上にすると、うまくいきました。
原因は不明です。