会社のホームページ変更とブログ移転の検討
www.murasame-lab.com
上記が今の会社のホームページとなっている
知人のWebデザイナーが仕事が暇というので仕事をしてもらったが、その後体調を崩してメンテがしてもらえていない
そのため、会社のホームページを一新しようと思っている
とりあえず、2年ほど前から海外移住した友達のWebデザイナーに打診中だが
断られたら、ひとまずWordPressで作ろうと思う(実は今のページを作る前にモックでWPで作ったのがある)
それに加え、自社ドメインを強くするためにも、ブログを自ドメインにしようとおもう
はてなブログはProにすれば独自ドメインにすることが可能だが
もしかしたら自社のWordPressに変更するかもしれない
移行手順はもう完璧だ
とりあえず、少しずつ会社らしくしていこう
Go言語にデストラクタがないので何とかしたい
Goの真実
Goにはコンストラクタもデストラクタもない
ので、便宜上 コンストラクタに当たる関数は 構造体名の頭にNewのプリフィクスを入れた関数である
C言語だと init_hoge とするのが NewHogeになっただけだ
そして、そのNew関数は構造体のポインタを返す(Cでも呼び方わすれたが構造体をClassっぽく書くときに同じことをする)
結局C言語なのだ(念のため、C++ではない)
そしてデストラクタがないので、呼び出し側が終了関数を明示的に呼ばなければならない
Cだと delete_hoge とか release_hoge、teardown_hoge・・・ 呼び名が統一されてる覚えがない
Goにもデストラクタとして呼び名が統一されている気がしない
つまり、よく呼び忘れてメモリリークする!!
https://play.golang.org/p/i6-vTaTVx9Z
package main import ( "fmt" ) type File struct{ filename string } func NewFile(f string) *File{ fmt.Printf("File{%s} new&open\n", f) return &File{f} } func(s *File)Read(){ fmt.Printf("File{%s} read\n", s.filename) } func(s *File)Close(){ fmt.Printf("File{%s} close\n", s.filename) } func main() { fmt.Println("main start") file := NewFile("test") file.Read() fmt.Println("main finish") } main start File{test} new&open File{test} read main finish
上記のコードはRAII原則にのっとり、New時にリソースをOpenしているが
残念なことにライブラリを使う人がClose()を呼び忘れたため、リソースリークしている
defer
いちおうGoにはdeferがあり、関数が終了した時に実行されるファイナライザを書くことができる
https://play.golang.org/p/LPEm7oDSf2M
... file := NewFile("test") defer file.Close() file.Read() ... main start File{test} new&open File{test} read main finish File{test} close
これは便利だが、このdeferはライブラリを使う側の人間が忘れたらCloseしてくれない
構造体の方でdeferを入れたい
そう考えるかもしれないが、deferはあくまで、関数の終了時のファイナライザだ
コンストラクタにdeferを入れると当然コンストラクトした後にすぐCloseが走る
https://play.golang.org/p/aav2psenFxF
... func NewFile(f string) *File{ // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} defer file.Close() return &file } ... main start File{test} new&open File{test} close File{test} read main finish
runtime.SetFinalizer
見るからに悪手だが、ランタイムライブラリに、ガーベージコレクションが走った時に実行されるコールバックがある
x:= "hoge" runtime.SetFinalizer(&x, func(x *string){fmt.Println("SetFinalizer")}) // 上記だと即終了しGCが走らないので故意にGCを走らせ待機させる
と、上記では xがGCされる時に 次のラムダ式が呼ばれる。ラムダ式の引数はGCされるオブジェクトのポインタだ
https://play.golang.org/p/QYkVhC1uQKP
これを構造体に当てはめられないか?
コンストラクタでSetFinalizerする
コンストラクタで構造体を作成し、ポインタを使う側に返すが、このポインタにSetFinalizerを設定し
構造体がGCされるときにCloseしてみよう
念のためインスタンスを複数作る
https://play.golang.org/p/LL3ejYYcowe
package main import ( "fmt" "runtime" "time" ) type File struct{ filename string } func NewFile(f string) *File{ // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} runtime.SetFinalizer(&file, func(f *File){f.Close()}) return &file } func(s *File)Read(){ fmt.Printf("File{%s} read\n", s.filename) } func(s *File)Close(){ fmt.Printf("File{%s} close\n", s.filename) } func(s *File)Nop(){ fmt.Printf("File{%s} nop\n", s.filename) } func hoge(){ file := NewFile("test") file.Read() file2 := NewFile("test2") file2.Read() file3 := NewFile("test3") file3.Nop() } func main() { fmt.Println("main start") hoge() runtime.GC() time.Sleep(1 * time.Second) fmt.Println("main finish") } main start File{test} new&open File{test} read File{test2} new&open File{test2} read File{test3} new&open File{test3} nop File{test3} close File{test2} close File{test} close main finish
綺麗に動いてしまった・・・
レシーバーにSetFinalizerする
先ほどはコンストラクタにSetFinalizerをしたので、たぶん確実にFinalizerが走るが
特定メソッドで、レシーバーに対してSetFinalizerしたらどうなるか?
たとえばReadメソッドでやってみる
func(s *File)Read(){ runtime.SetFinalizer(s, func(f *File){f.Close()}) fmt.Printf("File{%s} read\n", s.filename) } main start File{test} new&open File{test} read File{test2} new&open File{test2} read File{test3} new&open File{test3} nop File{test2} close File{test} close main finish
すばらしい、意図したとおりに動いた(file3はReadメソッドを読んでないのでFinalizeされてない)
使うシーンがあるか不明だが、例えばOpenメソッドを呼んだ時だけCloseのFinalizerを設定
という使い方も不可能ではなさそうだ
でもね
GCされるまでは開放されないので、もし長時間GCされずに存在していたら
その間ファイルを開きっぱなしだったり、ネットワークリソースを開放しない事になる
だから、SetFinalizerでデストラクターをするのは、よくない事は明確である・・
どこを間違えたかと思ったが、Goはオブジェクト指向言語ではないというのが答えだ
本当は関数型のように書くべきなんだろうか?
Callback にすべきではないか?
コンストラクタでdeferしても、コールバックで続きの処理を行えば
コンストラクタのコンテキスト内なので問題ないはずだ・・・
https://play.golang.org/p/1ZBphVFMHKM
package main import ( "fmt" "runtime" "time" ) type File struct{ filename string } func NewFile(f string, cb func(file *File)*File) *File{ // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} defer file.Close(nil) if(cb != nil){ return cb(&file) } return &file } func(s *File)Read(cb func(file *File)*File)*File{ fmt.Printf("File{%s} read\n", s.filename) if(cb != nil){ return cb(s) } return s } func(s *File)Close(cb func(file *File)*File)*File{ fmt.Printf("File{%s} close\n", s.filename) if(cb != nil){ return cb(s) } return s } func(s *File)Nop(cb func(file *File)*File)*File{ fmt.Printf("File{%s} nop\n", s.filename) if(cb != nil){ return cb(s) } return s } func hoge(){ _ = NewFile("test", func(file *File)*File{ file.Read(func(file *File)*File{ fmt.Println("callback hell") return file }) return file }) } func main() { fmt.Println("main start") hoge() runtime.GC() time.Sleep(1 * time.Second) fmt.Println("main finish") } main start File{test} new&open File{test} read callback hell File{test} close main finish
やったぜ!!(やりたくない
もう少し実用的に
ReadやWriteの引数ちゃんと作って、もう少し実用的にします
エラーも返さないとね(エラー処理は省略
https://play.golang.org/p/XbAWauNUNyZ
package main import ( "fmt" "runtime" "time" ) type File struct { filename string } func NewFile(f string, cb func(file *File) (*File, error)) (*File, error) { // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} defer Close(&file, nil) if cb != nil { return cb(&file) } return &file, nil } func Read(file *File, len int, cb func(file *File, readed *string) (*File, error)) (*File, error) { fmt.Printf("File{%s} read\n", file.filename) data := "readed" if cb != nil { return cb(file, &data) } return file, nil } func Write(file *File, data *string, cb func(file *File, written int) (*File, error)) (*File, error) { written := len(*data) fmt.Printf("File{%s} write {%s} size={%d}\n", file.filename, *data, written) if cb != nil { return cb(file, written) } return file, nil } func Close(file *File, cb func(file *File) (*File, error)) (*File, error) { fmt.Printf("File{%s} close\n", file.filename) if cb != nil { return cb(file) } return file, nil } func Nop(file *File, cb func(file *File) (*File, error)) (*File, error) { fmt.Printf("File{%s} nop\n", file.filename) if cb != nil { return cb(file) } return file, nil } func hoge() { _, _ = NewFile("test", func(file *File) (*File, error) { Read(file, 5, func(file *File, readed *string) (*File, error) { fmt.Printf("READ{%s}\n", *readed) data :="hogehoge" Write(file, &data, func(file *File, written int) (*File, error) { fmt.Printf("WRITE [%d]bytes\n", written) return file, nil }) return file, nil }) return file, nil }) } func main() { fmt.Println("main start") hoge() runtime.GC() time.Sleep(1 * time.Second) fmt.Println("main finish") } main start File{test} new&open File{test} read READ{readed} File{test} write {hogehoge} size={8} WRITE [8]bytes File{test} close main finish
ライブラリを使う側からはもうCloseの事考えなくてよくなりました
やったね!!
しかし、コールバック地獄、エラー処理の場所・・・もっと考えなければならない事が増えてしまいました
しかもGoはJavaScriptやC#のような、クロージャを簡単に書く書式もないので、まいかいfunc ()と大変
コールバック必要なのはNewだけだったんや!
別に非同期プログラミングのようなコールバックする必要はない
GoはGoroutineという素晴らしい仕組みがあるので、同期っぽく書けばいい
わざわざ人間に不親切なコールバック地獄にする必要がない
コールバックを使った理由はNew関数を抜けるときにdeferdでCloseし忘れを防ぎたい
処理が終わるまでNew関数を抜けないためだ
ReadやWriteまでコールバックする必要がない。Newだけでよかったんだ
https://play.golang.org/p/WtixVskDI7R
package main import ( "fmt" ) type File struct { filename string } func NewFile(f string, cb func(file *File)error) ( error) { // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} defer file.Close() err := cb(&file) return err } func (s *File) Read(){ fmt.Printf("File{%s} read\n", s.filename) } func (s *File) Close() { fmt.Printf("File{%s} close\n", s.filename) } func (s *File) Nop() { fmt.Printf("File{%s} nop\n", s.filename) } func hoge() { _ = NewFile("test", func(file *File)error{ file.Read() return nil }) } func main() { fmt.Println("main start") hoge() fmt.Println("main finish") } main start File{test} new&open File{test} read File{test} close main finish
当然ちゃんとクローズされるし、コールバックは初回のNewだけなので、コードの見通しも悪くない
ただ、コールバックとして全てを中にいれなければいけない
オブジェクト指向脳だと自由に構造体を使い回したいと思うだろう
コールバックとオブジェクト指向のハイブリッド
Goにはメソッドのオーバーライドやデフォルト引数がない
可変長引数で処理できるが今回はそこは手抜きで、CallbackがnilだとClose管理はライブラリでは行わない
https://play.golang.org/p/M6g-dqUz86P
package main import ( "fmt" ) type File struct { filename string } func NewFile(f string, cb func(file *File) error) (*File, error) { // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} var err error = nil if cb != nil { defer file.Close() err = cb(&file) } return &file, err } func (s *File) Read() { fmt.Printf("File{%s} read\n", s.filename) } func (s *File) Close() { fmt.Printf("File{%s} close\n", s.filename) } func (s *File) Nop() { fmt.Printf("File{%s} nop\n", s.filename) } func hoge() { // クロージャで自動的にCloseする _,_ = NewFile("test", func(file *File) error { file.Read() return nil }) // クローズは使う側が責任持つ file2, _ := NewFile("test2", nil) defer file2.Close() file2.Read() // 忘れると当然リークします file3, _ := NewFile("test3", nil) // defer file3.Close() file3.Read() } func main() { fmt.Println("main start") hoge() fmt.Println("main finish") } main start File{test} new&open File{test} read File{test} close File{test2} new&open File{test2} read File{test3} new&open File{test3} read File{test2} close main finish
結局どれがいいのか?
Finalizerは動作はするが、GCが走るまで開放が行われないので実用的ではない
コールバック地獄はダメ!絶対!(おそらくGoはGoroutineで同期的に書く設計なので、promise/futureやasync/awaitのようなコールバック解決ライブラリはあまり出ない)
Newのコールバックは場合によっては使えると思う
でも基本は、ライブラリを使う側が責任もって後始末しなければいけないっぽい
ハイブリッドは良さそう
デスクトップPCのOS入れ直し
デスクトップPCを新調した
5月にデスクトップPCを超パワーアップした
CPUを corei9 9700Kにし
メモリを32GB
マザーはSLI対応
ビデオはGeforce1080Tiのまま
ところが1つだけ失敗がある
SSDを交換するの忘れた
ので買う事になった
現在ドライブは
SSD 256GB + HDD 4TBだ
OSが30GB、開発ツールが100GBほど使うと残りは100GB程度
結構厳しい
そのため、現在はゲームは全部HDDドライブに入れている
なんのためのゲーミングPCだ・・
という事で、近いうちに1TB~のSSDを買い、OSを移そうと思っている
アクティベートとか面倒化と思っていたが
どうやらSSD入れ替え程度ではOSの再アクティベートも不要で
イメージソフトを使えばイメージコピーすれば、特にインストールし直す事なく使えそうだ
非常にたすかる
うちのマザーは M.2を3本までさせるらしい
1個しか刺さない予定だけど
これで今まで悩まされていた容量問題が解決する
近況
近況
ブログあんま書いてないならブログなんてやめちまえ!
おっしゃる通り
仕事
最近はUNITYのゲーム作成とGolangでのセキュリティ&暗号化関連
の2本立てでございます
UNITYのゲームの方は、かなり人気なタイトルの続編となっていて、今までに何作も出ていて
とても重みを感じます
メインからまるっと開発まかされているので、責任も重大です!!
作業内容としては、開発のタスク管理から技術からみた仕様へのアドバイス、企画がつかうツール作成
シェーダー、全体のベース作成、高速化、コードレビューなど・・
多岐にわたります
ついでに、他案件のシェーダー書いたりサーバコンサルしたり
Goの方はやっと動いてきた感じです
Goは言語はわかってるけどベストプラクティスがあまり見つけられておらず
あまり好きな言語ではないんですが
人妻の友達のアドバイス
「夫婦が上手くいく秘訣は、相手の事あんま好きじゃなくても毎日嘘でいいから好きっていう事」
だそうなので、毎日Goが好きだという事にしました
ニュースリリース
大きな発表が出来るよう、最終交渉中です。がんばろう
仕事以外
ゲームしたい・・
FPS練習でオーバーウォッチしたい
TPS練習でスプラトゥーン2したい
モンハンワールド Icebone前に進めたい
ストV やりたい
Macbookを使わなくなって1年半がたちました
PC歴
Windows時代
昔は仕事はWindowsだったし、ゲームにツールにWindowsの方が便利だった
言語はC++、C#、Javaなどなど
サーバはWindowsサーバかLinuxサーバ
だいたいWindowsサーバにはリモートデスクトップ、LinuxサーバにはTelnet(SSH)で接続してたか
2011年。Macbook元年
iPhoneアプリの台頭により、私もMacbookが必要になった
そのため、はじめてのマッキントッシュ、MacbookPro2011を買った
しかもNetBSDベースのOSのため、サーバ開発も1台で出来た
軽いし薄いし、とてもいいマシンで毎日持ち歩いて仕事をした
弱点はゲームが出来ない事ぐらいだが、ゲームは家のデスクトップで。
2016年。WindowsノートPC RazerBlade購入
MBP2011は、初期不良でロジックボードに爆弾をかかえていた
まだ使える状態だが、 後継ノートPCが必要だった
デスクトップではダメな理由は、仕事柄持ち歩く必要があるから
この頃はiPhoneの仕事もあまり来なくなり
UNITYやUnreal、サーバーの案件がメインになっていた
新しいMacbookProはnVidiaチップを積まなくなり、VGAもMBPは弱く、UNITYやUnrealには不利
また、Dockerのおかげでサーバ開発もWindowsで行いやすくなったため
Windows用のゲーミングノートPCを買った
サーバ開発もほぼ問題なく行える
しかも、LinuxやMacにくらべ、DockerのファイルI/Oが20倍も速い!!
Goの問題
基本的にDockerを使えばLinuxと変わらずに開発できる
もちろん、Nativeで実行することもだいたい可能である
ところが、Goの開発では当時はPluginとShell実行がWindowsは対応していなかった
そのためMBPが必要になり2017年モデルを購入した
Go問題解消
Goが上記の問題を解消した
そのため、Windowsで問題なく開発出来るようになった
MBP2017はバタフライキーボードが不調ですごくストレスがたまる
しかも、Windowsはその間にDocker技術をすごく発展させていて
今はまだExperimentalだがLCOWで直接ホスト上で動いたり、Windowsコンテナや
VSCodeにWSLが乗ったり・・・
そして、完全にMacbookを使わなくなり1年半がたった
個人的な今のWindowsとMacの比較
Windowsの方が良い点
ゲームが出来る
ソフトが多い
スペックのわりに割安
Dockerが速い
GeForceをつめるので機械学習やUnreal、Unity開発が良い
Macの方が良い点
iPhone開発を行う事が出来る
iPhoneのTrueDepthやARKitなどのApple独自の技術が使える
海外旅行中に故障してもAppleストアが世界中にある
中古販売価格も高い
gRPCの勉強
gRPCとは
HTTPによるREST APIみたいなものですが、RESTではなく RPCである事など違いがあります
REST APIと比較すると
- 仕様が柔軟である
- ヘッダが小さくオーバーヘッドが少ない
- HTTP2に対応しており、非同期通信が効果的に行える
- protoファイルを作成し、プロトコルレイヤーを分離できる
などの優れた特徴をもっています
今後gRPCを使った開発が増えてくるんじゃないかな
大切な事はだいたい公式が教えてくれる
公式のガイドに全部書いてあるけど、英語が読めないので・・
私の場合はサンプルコード見るのがはやい!
https://github.com/grpc/grpc-go/tree/master/examples
しかも、サンプル用にプロトコルやシンプルなサーバクライアント
認証用のトークン
など全部用意されているので
コード確認がすぐに出来るすぐれもの!
テストコード
下記にテストコードをかいていく
github.com
Golang再び
Golangの仕事再び
不満点はいっぱいあるけど、今の主流言語のひとつであることは間違いない Golang
仕事の内容は詳しくは書けないが、暗号化とセキュリティが重要な巨大基盤
OpenStackで作ったインフラ上で動かす
前のプロジェクトでGolandを購入したので、Golandを使うことにした
今回はいいデスクトップPCを買ったので、Windowsメインで開発を行うが
1年前とはちがい、今のところGolangでWindowsで出来なかった問題が解決した
Goのバージョンアップ
インストールしていたGoが古いので最新に上げた
というのも、下記のGomodulesを使う必要があるので、必須であった
Chocolatey を使えば管理が楽そうだが、今回は普通にインストールをした
単純にインストーラー起動してうわがきすればよい
ちなみに Chocolatey使ったインストールは下記に
go modules
Go11にExperimentalで入った機能のようだ
Go11では機能をONにするために環境変数必要だが、Go12では不要
いままでのGoで不満だった、GOROOT以下にソースファイルを置く必要があったやつ
ライブラリのバージョン依存の解決のために venderディレクトリ使ってほげほげ してた
そういった問題を解決してくれるものだ
設定ファイルに モジュールのパスをエイリアス指定したりできる
ローカルパッケージの名前解決にも使える
おそらく go modulesを使えば、私のGoに対するイライラの少しが解決する
Goland設定
Lintなどを設定する
ここを見ればすぐに出来た。みなさんの知識ありがたい