kazasiki's blog

プログラミングとかゲームとか

enum値を引数に持つメソッドを持つstructのInterfaceを作るコツ

golangではライブラリ自体はinterfaceを提供しておらず、使う側で必要になったら作れという風潮があると思ってます。golangのinterfaceはダックタイプなので、概ねはそれで上手く行きます。
ただ、引数にenum値がある場合、素直にメソッドのシグネチャをコピペするとinterface自体が実装に依存してしまうので一工夫が必要です。
やってることはinterfaceにも同じenumを作りましょうというだけなのでなんということはないのですが、DIとかに慣れてない人はちょっと嵌りそうな気がします。

実装例は以下です。参考になれば幸いです。

package main

import "fmt"

// 例えば、以下のようなinterface化したいライブラリ(?)があったとする。

type Hoge struct {
    a int
}

type Mode int

const (
    ModeOne = iota
    ModeTwo
)

func NewHoge(a int) *Hoge {
    return &Hoge{a: a}
}

func (h Hoge) Mul(m Mode) int {
    return h.a * int(m)
}

// ここから下はインターフェース

type MulerMode int

const (
    MulerModeOne = iota
    MulerModeTwo
)

// 普通にやるとMulの引数の型にModeを指定してしまうが、そうするとInterfaceが実装に依存してしまう
// ここでは同じ値域のenum、MulerModeをinterfaceとセットで用意して回避する
type Muler interface {
    Mul(m MulerMode) int
}

// ここから下はインターフェースを通じてHogeを使いたい場合

type MulerImpl struct {
    Hoge
}

func NewMulerImpl(a int) *MulerImpl {
    return &MulerImpl{Hoge: Hoge{a: a}}
}

func (m *MulerImpl) Mul(mm MulerMode) int {
    var md Mode // ここでenum同士のマッピングをする
    switch mm {
    case MulerModeOne:
        md = ModeOne
    case MulerModeTwo:
        md = ModeTwo
    default:
        return 0 // 本当はエラーとかだと思う
    }
    return m.Hoge.Mul(md)
}

// 実際に呼び出す

func main() {
    var m Muler
    m = NewMulerImpl(1)
    fmt.Print(m.Mul(MulerModeOne))
}

Slidesファイルをpdfに変換するGoogleAppsScript

仕様書やワイヤーフレームをGoogleSlidesでよく作るor作ってもらうんだけど、地味に開くのに時間がかかるのでpdfかするgasを書きました。 destDir.createFile(file.getBlob()) だけで変換自体はできるんだけど、同名のファイルが既にあれば消したり、前回変換したときから更新されてるやつだけ対象にしたら結構なスクリプトになったので公開します。

function myFunction() {
  var srcDir = DriveApp.getFolderById("slides folder");
  var destDir = DriveApp.getFolderById("pdf folder");
  var files = srcDir.getFiles();
  while (files.hasNext()) {
    var file = files.next();
    if (file.getMimeType() != "application/vnd.google-apps.presentation") {
      continue
    }
    destName = file.getName() + ".pdf";
    var exist = false;
    var dFile;
    destFiles = destDir.getFilesByName(destName); // 毎回getFilesByNameをするの効率悪い...
    while (destFiles.hasNext()) {
      var destFile = destFiles.next()
      if (destFile.getName() == destName) {
        exist = true;
        dFile = destFile
        break;
      }
    }
    if (!exist){
      destDir.createFile(file.getBlob())
    }
    if (exist && dFile.getLastUpdated() < file.getLastUpdated()) {
      dFile.setTrashed(true)
      destDir.createFile(file.getBlob())
    }
  }
}

自分が重宝してるGolangのtesthelper

golangに限らず、テストを書いてると同じようなパターンを何回も書くことが多いです。 そこで私が重宝してるヘルパー関数を紹介します。

golangの関数を書く場合、戻り値にerrorと何かを返すパターンが多いですね。 そこで以下のヘルパー関数を用意します。

// CheckErr エラーの存在チェック
func CheckErr(t *testing.T, i int, ok bool, err error) bool {
    t.Helper()
    if ok {
        if err != nil {
            t.Errorf("[%d] err should be nil, but got:%s", i, err)
        }
        return false
    }
    if err == nil {
        t.Errorf("[%d] err should not be nil, but got:%s", i, err)
        return true
    }
    return true
}

err以外のレスポンスをチェックする場合はfalseを返します。
iはTDDのループのindexを入れます。TDDしてるとどこででたエラーが出たかわからなくなることがあるので...
okはerrが出るテストケースかどうかをboolで入れます(trueならエラーがない)
errはテスト対象の関数のerror戻り値を入れます

例えばこんな感じで使います。
errorを返す場合は、他の戻り値はどうせゼロ値なのでチェックしないでしょう。

func Something(x int) (string, error){
    if x < 0 {
        return "", errors.New("")
    } else {
        return strconv.Itoa(x), nil
    }
}

func TestSomething(t *testing.T){
    testTable := []struct{
        x int
        res string
        isErr bool
    }{
        {x: 1, res: "1", isErr: false},
        {x: -1, isErr: true},
    }
    for i, tt := range testTable {
        res, err := Something(i)
        if !CheckErr(t, i, tt.isErr, err) {
            if w, g := tt.x, res; w != g {
                t.Errof("unmatch w: %s, g: %s", w, g)
            }
        }
    }
}

さらに例えばerrorがcodeを返すように拡張(?)してる場合もあるでしょう。

// ErrCodeを返すMyErrorがあるとする
type MyError interface{
    Error() string
    ErrCode() uint32
}

type HogeError struct{}

func (e HogeError) Error() string {
    return "error"
}

func (e HogeError) ErrCode() uint32 {
    return 1001
}

その場合はCheckErrも以下のように拡張します。

// CheckErrCode 想定されるエラーコードがあればそれを確認する
func CheckErrCode(t *testing.T, i int, code uint32, err error) bool {
    t.Helper()
    noErr := code == 0
    if !noErr && err != nil {
        if terr, ok := errors.Cause(err).(MyError); ok {
            if w, g := terr.ErrCode(), code; w != g {
                t.Errorf("[%d] err_code unmached. want:%d, but got %d", i, w, g)
            }
        } else {
            t.Errorf("[%d] err type unmached. %T", i, terr)
        }
    }
    return CheckErr(t, i, noErr, err)
}

func渡したり構造体作ったりもせずシンプルな関数なので適当に拡張できて便利です。

私的 VR Game of the Year 2018

2018年ももう12月ですね。2018年は皆さんにとってどういう年でしたでしょうか。私は年中VRゲームを遊んでいる年でした。
昨年の12月くらいにOculus Riftを買ってようやく本格的に遊び始めたのが1月ごろ。今でも定期的にVRゲームのストアを漁って、買って、プレイしています。
The Game Awardの様々な発表を見て刺激を受けたので、自分も今年遊んだVRゲームのいろいろをまとめてみようと思います。(今年発売のゲームには限ってません、私的なので^-^;)

Best VR Game & Best Sports Game

Racket: NX

簡単に紹介すると、360度が壁に囲われてる状態でやるスカッシュ+的あてという感じです。一人プレイモードでは主に制限時間内に次々と出現する的のすべてにボールを当てらるかを競います。
VRゲームが通常のゲームより優れているところはたくさんありますが、このゲームでは特に360度見渡せるところと、手が自由に使えることをうまく生かしています。 最初は慣れない操作に苦労しますが、慣れてくるとちょうどテニスのボレーに近いようなフォームで遊ぶようになります。

Best Narrative

Virtual Virtual Realiy

本作のストーリーを一言でいうと、「ほぼすべての労働がAIによって自動化された社会で、あなたは人間としてAI様のために労働をしよう」というものです。
世界観や登場するキャラクター(AI達)が独特で、勝手にしゃべってるのを聞くだけで結構面白いです。VRアドベンチャーゲームとして操作性が非常に良く、日本語字幕も見やすい位置に表示され、体験として全く粗がありません。

Best Action Game & Best Shooting Game

OVERTURN

目を覚ますと謎の研究所に幽閉された主人公がそこからの脱出と報復を目指すアクションシューティングゲームです。ヒロインは同じく研究所に幽閉されていた少女で、謎の超能力(ビームとか)が使えるのでそれでサポートしてくれます。
特にステージの構成が良くできていて、最初から最後まで手ごたえのあるゲームです。また、ストーリーもよくできており、先が気になる展開や、主人公やヒロインに感情移入してしまうシーンも多いです。

Best Rythem Game

Airtone

VR音ゲーです。操作としては前方から飛んでくるノーツに合わせてコントローラを動かすだけで、音ゲーとしてはとても素直な形式です。
楽曲数が30曲とVR界隈の音ゲーにしては多めで、難易度的にも幅広いので誰でも遊べる良いゲームだと思います。ガイド役の女の子のキャラが可愛く、ストーリー要素もほんのりあります。最近PSVRでも発売しました。

Best Puzzle Game

星のかけらの物語、ひとかけら版

宇宙空間のような世界の特異点に迷い込んだ少女と協力してそこからの脱出を図る謎解きアドベンチャー。主人公はただ見ることしかできず、物を動かすのもスイッチを押すのも、ヒロインに目線で指示してやってもらいます。
このゲームは自分が本当にその空間にいるような観察能力が求められます。見るべきところが平面にまとめってはくれないですし、特定のタイミングで後ろを振りむいたり下からのぞき込むような操作に意味があることもあります。
謎解きの難易度が全体的に高めで好みが分かれるとは思いますが、謎が解けたときの達成感も素晴らしく、ぜひ頑張って最後まで自力で進んでほしい作品です。

次点

以上に紹介したゲーム以外で面白かったものをずらっとあげておきます。「こいつの趣味わりと近いな」と思ったかたは参考までにどうぞ。

www.oculus.com www.oculus.com

GoogleHomeに「今日は何のゴミの日」か教えてもらえるようにした

ついにGoogleHomeのルーティン機能が日本でも使えるようになりました。 メニュー上ではルーティン機能という見出しですが、実際にはタイマー機能もありますし、いろいろできるようになりました。 詳細は以下の記事をご覧ください。

japanese.engadget.com

そこでルーティン機能を使って、GoogleHomeに「今日は何のゴミの日」か教えてもらえるようにしました

仕組み

健全な市民である読者諸兄はおそらく毎日決まった曜日に決まった種類のゴミを出していると思います。 なので、それをGoogleHomeに聞いたら答えてくれるようにしました。 といっても、曜日を使った条件分岐なんて器用なことはルーティン機能ではできません。なので単に曜日分だけルーティンを登録します。

メニューから ルーティン を選んで、一覧画面をひらくとこんな感じ。曜日分ずらっと並んでます。

f:id:kazasiki:20180907190020p:plain:w230 f:id:kazasiki:20180907185651p:plain:w230

例として月曜日のルーティンを見せるとこんな感じ。カスタムメッセージを使って、キーワード(曜日)に対応したメッセージ(ゴミの種類)を返答するように1つ1つ設定します。いつも同じ時間に家を出てるのであれば、その少し前の時間をタイマーに設定しておくと良いでしょう。

f:id:kazasiki:20180907185314p:plain:w200 f:id:kazasiki:20180907185318p:plain:w200

仕組みとしてはこれだけです。簡単でしょう?

最初の入力は手間ですが、一度入力すれば基本的にずっと使えますし、曜日は7つしかないので手間といってもたかが知れてます。これでゴミ出しを忘れたりすることもなくなり、よりストレスフリーな生活になりました。

みなさんも快適なgoogle homeライフを。

期限が複数ある場合のバーンダウンチャートの書き方

この記事では、プロジェクトにおいて期限が近いタスク群Aと期限が遠いタスク群Bがあるときにどのようにバーンダウンチャートを運用すると良いかを書く。 あくまでも提案のようなもので、こうすると必ずうまくいくという類いのものではないので注意してほしい。

バーンダウンチャートとは?

バーンダウンチャートをご存知だろうか。驚いたことに日本語のwikipediaの記事がなかったのだが、backlogの解説記事を読むと、以下のように書いてある。

「期限までに全ての作業を消化できるのか?」ということが一目で分かるグラフです。 バーンダウンチャートでは縦軸に「作業量」、横軸に「時間」を割り当てて残りの作業量がグラフ表示されます。時間が進むと残りの作業量が減っていくので、右肩下がりのグラフになります。

私がspreadsheetで書いた雑な図がこちら。この図では幸運にも期日通りに作業が完了している。

f:id:kazasiki:20180720231451p:plain:w600

進捗が芳しくない場合は 実線が理想線よりも上にある という形でひと目で分かるようになる。作業が終わらない不幸なプロジェクトは以下のようになる。

f:id:kazasiki:20180720232238p:plain:w600

複数の期限がある場合にどうするか

さて、バーンダウンチャートの紹介はここまでにして本題に入ろう。 基本的にバーンダウンチャートはチームに対して大きなタスクが1つだけあり、それには単一の締切(もしくはマイルストーン)があるという前提で書かれるものだ。 その大きなタスクを細かいタスクに分けて、その経過を見ることで全体の進捗を推し量る手法だ。 ただ、現実にはそんなにわかりやすい状況はあまりない。チームとして複数のタスクが同時進行し、そのタスクにはそれぞれ違う締切が切られている場合も多いだろう。

最初に述べたとおり、期限が近いタスク群Aと期限が遠いタスク群Bがあるときにどのようにバーンダウンチャートを書くと良いだろう? 前提として、タスク群の優先度は考慮しない。基本的にはA群とB群のどちらも必ず終わらせる必要があるし、進捗が悪くなれば残業なり、人を増やして対応するとする。

まず、A群とB群でそれぞれバーンダウンチャートを書くというのはどうだろう?これはうまく機能しない。図を書くまでもない。 例えば、A群の締切が5日後で、残りの作業量が4日分だったとする。また、B群の締切が10日後で、残りの作業量が8日分だったとする。 どちらも単体であれば、終わりそうに見えるが、A群→B群で順序で作業した場合、総作業量は12日分であり、B群の締切には2日分間に合わない。 別々にバーンダウンチャートを書いている場合、このことには気づきにくい。

最も簡単な解決策は、A群のバーンダウンチャートの上に、B群のバーンダウンチャート積み上げることだ。要するに、期限が近い方を下に置き、遠い方を上に積み上げていくグラフにする。 例えば以下の図のようにする。この図は作業Aは間に合ってるが作業Bは間に合っていない。

f:id:kazasiki:20180721002526p:plain:w600

この図は先程の説明とほぼ同じ状態になっている。作業想定は50h/日で、A群は4日分の200hを5日で、B群は8日分の400hを10日でやろうとしている。全体として600hを10日でやろうとする。 A群をこなすだけなら速度は想定より低い40h/日でも足りる。だが、A+B群をこなそうとすると、60h/日は必要だ。(わかりすくするために50h/日の補助線を緑で引いた)

この図ではお行儀よくA群を全て終わらせてからB群に取り掛かってるが、そうでなくてもこの図は同じように機能する。 これを通常のバーンダウンチャートと同じようにチーム単位や、個人単位で見ることが出来れば、速度の遅れやキャパシティの超過は同じように追えるだろう。

察しのいい人はわかると思うが、B群単体の理想線は引いても意味がない。なぜならA群の作業をしている間、B群の作業は進捗しないからである。A群と並列で作業している間は、B実線がB理想線よりも上にくるのは当然であり、そんな図を見ても意味がない。A群とB群にどのくらい作業量を振り分けるかを計画できているなら計画線(理想線とは別に実績の計画を書く線)は引けるが、そんな芸当ができるケースは正直そんなにないと思う。

あなたが使っているバーンダウンチャート作成ツールがこういった形式の出力にうまく対応できることを祈る。

私と、競馬と、美穂という女性について

私と競馬について

私の父は競馬を愛している。私が実家を出てからは実際には見ていないが、たぶん今でも気になるレースでは馬券を買っているだろう。ギャンブルに身を滅ぼず様な賭け方ではなく、1000円程度の慎ましい賭け事を楽しんでいた(筈だ)。競馬場には行かずに、家で携帯やパソコンから馬券を注文していた。それもあって、家でスポーツ新聞の競馬の面を開いてよく唸っていたし、テレビでレースを見ては時折リビングでわーわーと騒いでいた。

f:id:kazasiki:20090307115000j:plain:w400

私が小学生くらいの頃は父がよくゲームセンターに連れていってくれて、競馬のメダルゲームを遊んでいた。父いわく「あれが一番1円あたり長く遊べる」らしい。小さいバケツのようなメダル入れ一杯分くらいのメダルを買って、私と父で半分に分けてゲームを始める。私は(幼かったのもあって)馬券の種類すらよくわからないまま遊んでいたが、なんとなく楽しかった記憶がある。おそらくミニチュアの馬たちが競馬場を模した盤面を走っているのが良かったのだろう。実際はモーターで動いていたわけだが、子供の私にはそれで十分だった。

ルールもよくわかってないからすぐに私の分のメダルが尽きてしまうのだが、そういったときは父から少しメダル分けて貰って延命を施した。それを繰り返し、二人ともメダルが尽きたら、その日はもう家に帰るという具合だった。

美穂という女性について

小学生の頃の私の友人に、美穂という名前の年上の女性がいた。姉の同級生であり、また美穂の妹も私の同級生だったので偶然接点があったのだ。子供同士が仲良くしてたのもあって、授業参観か運動会かなんかで父親同士も話す機会があったらしく、そこで彼女の父親も競馬好きであることを知った。今考えると大した偶然だと思うが、当時の私は大人の男性はみんな競馬が好きなものだと思っていたのでそんなものかと思っていた。

ある日、父が晩酌しながら「ちょっと面白い話があって」と話をしてくれた、曰く「美穂という名前は実は競走馬であるミホシンザンにちなんでつけた」と美穂の父親が言っていたらしい。競馬に詳しくない読者に伝えておくと、ミホシンザンというのは大体1985〜1987年に活躍した競走馬で、1985年の皐月賞菊花賞に優勝し、中央競馬クラシック二冠を達成している。私にもこの凄さはよくわからないがとにかく凄い馬だそうだ。確かに、美穂は1987年生まれなので時期的には一致している。私の姉も同じ年度の生まれだが、私の父はミホシンザンがそこまで好きではなかったようだ。

もともと日本人の女性に美穂という名前は多い。日本の女性の名前ランキング(生まれ年別) を参照すると、1973~1984年でも7~10位にはたまに入っている。ただ、やはりミホシンザンの効果なのか1986年は2位、続く1987年は5位、さらに翌年の1988年は3位、1989年も3位である。その年代のタレントに中山美穂氏もいるので、その影響もあるかもしれないが、どちらにしても子供につける名前とはそういうものということだろう。

私の元同僚にも1987年生まれの美穂という名前の女性がいる。流石にあなたの名前は競走馬が由来ですかとは聞いてない。

f:id:kazasiki:20150321151246j:plain:w400

私の名前にも馬編が入っている。父は自身が競馬が好きだからお前の名前に馬編の漢字を入れたと豪語していた。私はまだ独身だが、もし今女の子を授かれば鈴香(すずか)と名付けるのだろうか。