やる気がストロングZERO

やる気のストロングスタイル

SynologyのNAS設定が楽しい

去年くらいにSynologyのNASを購入した。

GWに入って改めて設定を触ってて「便利だなー楽しいなー買ってよかったなー」と思ったので書いてみる。

なにが便利か?楽しいか?

NASだけどサーバーとして色々機能をもたせられる。
VPNサーバーとしての機能をもたせたり、Dropbox的な機能をもたせられたりもする。
これらの設定は、それぞれアプリとして提供されていて、GUIでポチポチと簡単に設定できるようになっている。
(synologyのnasしか触ったことないので、NASってのがそういうものなのか、synologyのNASだけがそうなのかはよくわからない。)

ホームネットワーク内でdropboxのようなディレクトリ同期機能を再現できる

nasと家の各デバイスnasの機能の「Cloud Station Server」で同期すれば、Dropbox的なディレクトリ同期機能を何台でも実現できる。
さらに、nas内の同期ディレクトリをDropBoxと同期しておけば、実質「無料版は3台までしか同期連携できない」というDropBoxの制限を超えて同期することができるようになる。
(synologyサーバーからのdropbox連携は端末件数にカウントされない(API連携だから?))

VPNサーバー設定して出張・旅行先から安全にネットできる

VPNサーバーとして設定すると、外からVPN接続でネット接続できるようになる。
ビジネスホテルの無料wifi使うときとか、VPNで安全にネットすることができる。

堅牢にデータを保管できる

macのtimemachineやubuntuのバックアップ保存先に指定して自動的に定期的にバックアップを取っていれば、NAS内で勝手に冗長化してくれる。
より安全にしたければこれをどこかのパブリッククラウドに連携して自動的にアップロードされるようにしておけば、より安全に保管することができそう。

ほかにも、、、

振り返ってみると「色々」というほどなんかしたわけでもないけど、サーバーとしてできることなら何でもできそうな感じなので「こんなんできないだろうか?」と考えるの自体が楽しい。

scpコマンドの挙動でハマった(ちょっとだけGo)

Goを書いていてsshでファイル転送したかったが、Goでのいい感じのクライアントが見つからなかったので普通にscpコマンドを実行させることにしたが、

scpコマンドの挙動でハマった。

雑にメモ。

やりたいこと

remoteの/var/data/ディレクトリの中身を localの/download/以下にダウンロードしたい。

remote
└── var
    └── data <- ここ以下のファイル群を
        ├── a
        │   └── 1.txt
        ├── b
        │   └── 2.txt
        └── c
            └── 3.txt

local
└── download
    └── 20200401 <-こんな感じでダウンロードしたい(ディレクトリ名はダウンロード日)
        ├── a
        │   └── 1.txt
        ├── b
        │   └── 2.txt
        └── c
            └── 3.txt

scpコマンド検討

/downloads/20200401ディレクトリを作っていない状態で

以下のように、事前に20200401ディレクトリを作ってない状態で

local
└── download

以下のコマンドだと、、

scp -r remote:/var/data/ /downloads/20200401

bashで実行した場合、意図通りになる

local
└── download
    └── 20200401
        ├── a
        │   └── 1.txt
        ├── b
        │   └── 2.txt
        └── c
            └── 3.txt

Goで実行した場合、意図通りになる

exec.Command(
   "scp",
   "-r",
   "remote:/var/data/",
   /downloads/20200401,
).CombinedOutput()

local
└── download
    └── 20200401
        ├── a
        │   └── 1.txt
        ├── b
        │   └── 2.txt
        └── c
            └── 3.txt

/downloads/20200401ディレクトリを作った状態で

以下のように、事前に20200401ディレクトリを作った状態で

local
└── download
    └── 20200401 <- こいつを事前に用意しておく

以下のコマンドだと、、

scp -r remote:/var/data/ /downloads/20200401

bashで実行した場合、意図通りにならない

local
└── download
    └── 20200401
        └── data <- これいらない
            ├── a
            │   └── 1.txt
            ├── b
            │   └── 2.txt
            └── c
                └── 3.txt

Goで実行した場合、意図通りにならない

out, err := exec.Command(
   "scp",
   "-r",
   "remote:/var/data/",
   /downloads/20200401,
).CombinedOutput()

local
└── download
    └── 20200401
        └── data <- これいらない
            ├── a
            │   └── 1.txt
            ├── b
            │   └── 2.txt
            └── c
                └── 3.txt

*を使った場合

scp -r remote:/var/data/* /downloads/20200401/

bash実行だとエラーになる

no matches found: remote:/var/data/*

Go実行だと意図通りになる

out, err := exec.Command(
   "scp",
   "-r",
   "remote:/var/data/*",
   /downloads/20200401/,
).CombinedOutput()

local
└── download
    └── 20200401
        ├── a
        │   └── 1.txt
        ├── b
        │   └── 2.txt
        └── c
            └── 3.txt

【Go】gorpにdecimal型を認識させる

経緯

データベースライブラリにgorpを使ってみている。
gorpでは(gorpというより、database/sqlの範疇かも)Goのint型はDBのintと、GoのstringはDBのtextやvarcharと、みたいに自動的にマッピングされて意識しなくてもデータやり取りができる。

Goには標準でdecimal型がなかったので外部ライブラリを使うことにした。
decimal - GoDoc

これをそのまま使った場合は、DBのdecimal型と勝手にマッピングしてくれた。

type Human struct {
   Id               uint64
   Money       decimal.Decimal 
}

外部ライブラリを裸でそのまま使うのが嫌だったので、内部でラップして使うようににしたら勝手にはマッピングしてくれなくなった。

type Human struct {
   Id               uint64
   Money       MyDecimal 
}

どうすればマッピングしてくれるか?

DBとのやり取りに使うには、database/sqlパッケージの「Scannerインターフェース」とdatabase/sql/driverパッケージの「Valuerインターフェース」が実装されている必要があるようだった。

decimalライブラリにはそれが定義されていたので、それをそのまま実行してやるようにすればOKだった。

type MyDecimal struct {
    decimal decimal.Decimal
}

func (d MyDecimal) Value() (driver.Value, error) {
   return d.decimal.Value()
}


func (d *MyDecimal) Scan(value interface{}) error {
   return d.decimal.Scan(value)
}

...

読み解きづらいコード

自分が「読み解きづらいな」と思ったコードに対して、なぜ読み解きづらいと思ったのかを考察し、
また、そうならないようにどうしたいかを考えたので書いてみる。

個人的な考えだし、エンジニアレベルとか状況とかで変わると思うし、僕が見えてない事情とかもあると思うけど、一旦そういうのは無視して書いてる。

フックベースで処理が組まれていて、フックの種類が多くネストが深い

イベント駆動みたいに「なんらかをフックして次の処理が実行される」みたいな機能をふんだんに使って処理を組まれていると、次なにが実行されるのかが全然わからない。

たとえばActive Recordは以下のコールバックがある。

before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit/after_rollback

before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
after_commit/after_rollback

before_destroy
around_destroy
after_destroy
after_commit/after_rollback

これらを全て確認しないと処理を追えない。
実行順序も意識しておく必要もある。

さらにそのコールバック処理の中で別のActive Recordが使われていたりすると、そのコールバックも把握して追っていかないといけない。。

これらのコールバックを駆使して組まれた処理は

someObject.save()って実行すると上手く処理されるけど、どういう処理が行われているのかよくわからん。処理を追うのも大変。。」

という感じになる。

対策:
フックに過剰に頼らない設計にする。(フックに向いている処理ってのはあると思う)

何が実行されるのかを外部が制御してる

「実行メソッド名が外部から渡ってきてて、そのメソッドを実行する」みたいなやつ。
実際に外部からデータもらわないと何が実行されるかわからん。

大体は「内部処理を外からも汎用的に使えるようにした」みたいなやつがこういう感じになってる気がする。

対策:
外部が内部の実行メソッド名を知っているという状態がそもそもおかしい。
何を実行するのかは内部で制御するようにする。

メソッドだけ見ても何をしているのかよくわからない

こんな感じのやつ。

calculator = New Calculator(user)
calculator.execute()
price = calculator.price

priceがどういうデータからどういう計算をされた値なのか全然わからん。
ユーザー情報を渡してなんらかの計算をしているっぽい雰囲気しかわからん。
executeメソッドの中身を見ると、データベースから色々データを取得して、なんらかの計算をするロジックが長々と書かれていたりする。

対策:
引数と返り値を使って、なんのデータがどういうデータになるのか解るように表現する。
不必要にに広範囲のデータを渡さないようにする。

単体で動かせない

コードリーディングで挙動を読み解けない場合、実行してみて動きを見たいが依存が多く動かせない。
巨大なダミーデータの作成が必要になって、どういうデータが必要なのかを理解するためにコードリーディングが必要になって、読み解けなくて、、

対策:
依存をできるだけ減らしていつでもどこでも単体で動かせるようにする。

実現したいことに対して実現するための構成要素がなんか多い

1つのことを実現するために5個くらいclassが存在したり、5個くらいテーブルがあって絡み合ってる。 その思想を読み解けず、なんのためにこれだけの要素があるのかわからないので処理が追いにくい。

対策:
それを実現するのに一番自然に思い浮かぶロジックをそのまま表現できるようなコードにする。
今必要ではない念の為の機能のために不要にコードを太らせないようにする。(使われてない機能だから余計読み解きにくい。。)

Goのエラーハンドリングについて

したいこと

なにかエラーが出た時に、ログから

  • 「なにが発生したのか」
  • 「どこで発生したのか?」

がわかるようにしたい。

Go標準のerrorはスタックトレース情報を持ってない

errors.New("エラーメッセージ")

これだとスタックトレース情報がない。

エラーが出た時下記のようにして上位レイヤーでエラーログ出力する感じになると思うが、 これだと「どこで発生したのが?」が追えない。

// レイヤーの下の方でエラーがあったとする。
_, err := someFunc()
if err != nil {
  return err // 呼び出し元に返される
}

// 上位レイヤーでキャッチしてログ出力
err := underLayer()
if err != nil {
  logger.Error(err) //loggerが出力ファイル位置を吐き出すようにしてたとしても、あくまでこの位置なのでエラーの発生箇所がわからない。
}

なので、
https://github.com/pkg/errors
を使うことにした。(errorと同じインターフェースで使えて、スタックトレース情報を付加してくれる。)

使い方の指針

自分で作るエラーの場合

標準のerrorsの使い方と同じ。

import "github.com/pkg/errors"

err := errors.New("エラーメッセージ")

// こうやるとスタックトレース情報がとれる
fmt.Printf("%+v", err)

goの既存関数とかLibraryの関数が返してくるエラーの場合

既存関数(僕らが触れない部分)が返してくるエラーは標準のerrorsなのでスタックトレース情報を持ってない。
だから、下記の様にスタックトレース情報を付加してやる。

import "github.com/pkg/errors"

err := someFunc()
if err != nil {
  return errors.WithStack(err) // 呼び出し元に返される
}

Goでdecimalを使う時に考えたこと(floatの精度について)

Goで金額計算するのでdecimal型を探したが、基本ライブラリには無いようなのでどうしようか悩んでいた。

基本ライブラリにmath.bigパッケージがあり、Floatの精度を自由に決められるようなものがあったのでそれで行けるかと思ってたけど(不必要に外部ライブラリを使いたくなかった)考え方が根本から間違ってたのでまとめる。

(結局はdecimal型を提供してくれるライブラリを採用した。 decimal - GoDoc

参考:
料率計算における小数の扱いについて
なぜBigDecimalを使わなければならないのか | Java好き

float型で精度をたっぷり用意すれば金額計算でも使える?

「float型は誤差が出る」というのは知ってたので金額計算は使えないという認識はあった。

でも基本ライブラリにdecimalが無いのでどうしようか悩んでる時、ふと
今回扱うのはせいぜい少数第2位くらいまでなので、じゃあ少数第10位くらいまでの「精度」を用意してやれば使えるんじゃね?

みたいに思った。

漠然とこんなイメージ

0.1 + 0.7 = 0.8000000012
で、少数第二位くらいで切り捨てればいいじゃん
答え:0.80

情報系の専門学校や大学を出てたら当然の話なのかもですけど、、
すみません。完全に間違ってました。

floatの精度とは?

floatでは10進数で表せない数値が存在する。
例えば0.01は2進数だと循環してしまうので、メモリに保持するにはどこかで切り捨てなければならない。
このとき「どこまでデータを保持して切り捨てるか」がfloatで言うところの精度となる。

だからどれだけ精度をあげても0.01は決して0.01としては扱えず、ごくわずかの誤差が入ってくる。

色んな所で切り捨てが起こり誤差は膨らむ一方。正確な計算は出来ない。

f1 = 0.1
f2 = 0.7
f3 = f1 + f2

f3 == 0.8 // false 

人の感覚的にはf3は0.8なのでtrueになるべきだが、 0.1と0.7の定数定義の時点で「切り捨て」が発生し、f3には2度の「切り捨て」が含まれた結果になっている。 なので0.8とは「切り捨て」の事情が異なる分だけ差が出ているのでfalseとなる。

最初の思惑「少数第二位くらいで切り捨てればいいじゃん」をやろうとすると、常にどこで「切り捨て」が発生するのかを意識して自分で切り捨て(もしくは切り上げ)を行い続けなくてはならず(他にも何を考慮すればいいのかさえよくわからない)完全に間違った策であることを理解した。

そもそもfloat型ってのは大きい数値を扱えるかわりに精度に関しては犠牲にするっていう型なのでキッチリした計算に使ってはいけないものだと言うことでした。

decimalはどうやって計算してんの?

0.01という数値は1(int)と小数点位置-2(int)をデータとして持って扱うので誤差が出ない。

【Go】gorpを使ってDDDのRepositoryを書いた

goを使ったシステムにて、DBアクセス周りでgorpを使うことにしたので色々調査してDDDで言うところのrepositoryを書いた。

gorp
GitHub - go-gorp/gorp: Go Relational Persistence - an ORM-ish library for Go

gorpを選んだ理由

データ取得は「SQL文を直接書く」というところが気に入った。
(insert,updateは構造体を渡せばやってくれる。)

僕はActiveRecord的なクエリビルダがあまり好きではない。

JOIN等を含んだちょっと複雑な取得条件になると、クエリビルダで表現するにはどうすればいいのかわからず調べなければならない事が多い。
ライブラリごとに方言があり例えばActiveRecordのクエリビルダをマスターしても、Djangoではその知識は使えないので別途学ぶ必要がある。
特に[or条件]は実装が難しいのか、どのライブラリも直感的ではないインターフェースになっている感じがする。

また保守時に「最終的にどういうSQLが発行されるのか」を追わなければならないシーンも多い。

対して「SQL文を直接書けばよい」であればすぐ書けるし、どういうSQLが実行されるのかもコードからすぐわかる。
最初からSQL文を使いたいと思うようになってしまった。

DDDのrepositoryをgorpを使って書いたらこんな感じになった。

gorpの初期化関数

// gorp初期化処理
func OpenDb() (*gorp.DbMap, error) {
   dbUserName := "DB_USERNAME"
   dbPassWord := "DB_PASSWORD"
   dbHost := "DB_HOST"
   dbPort := "DB_PORT"
   dbDbName := "DB_DBNAME"
   db, err := sql.Open("mysql", fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?parseTime=true", dbUserName, dbPassWord, dbHost, dbPort, dbDbName))
   if err != nil {
      return nil, err
   }

   dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}

   dbmap.AddTableWithName(entities.User{}, "user").SetKeys(true, "Id")

   dbmap.CreateTablesIfNotExists()
   dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myApp:", log.Lmicroseconds))

   return dbmap, nil
}

Userエンティティ

type User struct {
   Id         int64     `db:"id, primarykey, autoincrement"`
   UserName   string    `db:"username, notnull"`
   CreateTime time.Time `db:"create_time, notnull"`
   UpdateTime time.Time `db:"update_time, notnull"`
}

repository

type UserDb struct {}

// コンストラクタ
func NewUserDb() *UserDb {
   return &UserDb{}
}

// User構造体を受け取ってuserテーブルに保存する
func (r *UserDb) Save(user *User, executor gorp.SqlExecutor) error {
   user.CreateTime = time.Now()
   user.UpdateTime = time.Now()
   err := executor.Insert(user)
   if err != nil {
      return err
   }
   return nil
}

// idでuserデータを取得
func (r *UserDb) GetById(id int64, executor gorp.SqlExecutor) *User {
   user := &User{}
   err := executor.SelectOne(user, "select * from `user` where id = ?", id)
   if err != nil {
      return err
   }
   return user
}

保存処理

func someFunc() error {
   db, err := OpenDb()
   if err != nil {
      return err // 成否チェック
   }
   defer db.Db.Close() // 関数終了時にClose処理するために記載
   userRepository := NewUserDb()

   user1 := &User{UserName: "テスト太郎"}
   err = userRepository.Save(user1, db) // dbコネクションを渡して保存。ここはトランザクションの範囲外
   if err != nil {
       return err // 成否チェック
   }

   tx, err := db.Begin() // トランザクション開始
   if err != nil {
      return err // 成否チェック
   }
   defer tx.Rollback() // コミットされていれば実行されても何もされない(内部的には「既にトランザクションが閉じています」的なエラーがでてる)。コミットされてなければRollbackされる。

   user2 := &User{UserName: "テスト次郎"}
   err = userRepository.Save(user2, tx) // トランザクションを渡して保存
   if err != nil {
      return err
   }
   tx.Commit() // 問題なければコミット
   return nil
}

気になった点

トランザクション分離レベルが指定できない

gorpはトランザクション開始時にトランザクション分離レベルを指定することができない。
DB側のデフォルト設定が適用されることになる。
※内部的には設定を渡せる部分があるが、かならずnilが渡されている。

楽観的同時実行制御の機能があるので問題ないのかもしれない。
https://github.com/go-gorp/gorp#optimistic-locking

rollbackの書き方

gorpというよりgo言語の問題かもしれないが、例外機能が無いので「例外をキャッチしたらrollback」というような書き方ができない。

色々悩んだ結果、上記「保存処理」のコードのように

   tx, err := db.Begin() // トランザクション開始
   if err != nil {
      return err // 成否チェック
   }
   defer tx.Rollback() // コミットされていれば実行されても何もされない(内部的には「既にトランザクションが閉じています」的なエラーがでてる)。コミットされてなければRollbackされる。

と書いた。

gorpのRollback()はトランザクションがcloseしているとsql.ErrTxDoneを返してくるだけで何もしない。
だから、Commitされていなければ(途中でエラーが出たとか、Commitし忘れていたとか)Rollbackされるし、
Commitされていればsql.ErrTxDoneを返して何もされない。

以下疑問が浮かんだが問題なさそうだった。

・ 「Rollbackが失敗していた場合どうなる?」

Rollbackが失敗したところで、Commitしていなければ変更は反映されないので問題なさそう。
※となると明示的にRollback処理書かなくてもいいのかな。。?

・ 「Rollbackが失敗し、次のトランザクションが開始されCommitされた場合、RollbackされるべきデータがDBへ反映されてしまわないか?」

使うトランザクションオブジェクトが異なるので影響しない。問題なさそう。