Runner in the High

技術のことをかくこころみ

ioutil.Discardとio.CopyNでメモリアロケーションせずデータサイズを判定する

ioutil パッケージに Discard という /dev/null 的な io.Writer が用意されている。

最近これを使うタイミングがあったのでメモ。以下のようなコードがあるとする。

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "strings"
)

func main() {
    a := strings.NewReader("123456789")

    _, err := io.CopyN(ioutil.Discard, a, 10)
    if err == io.EOF {
        fmt.Println("more than 9")
    } else if err != nil {
        panic(err)
    }
}

上記のコードを実行すると more than 9 が出力になる。

ある io.Reader に対して io.CopyN でサイズ指定をしてデータコピーを試み、それがEOFかどうかを見ることで対象のデータのサイズが任意のサイズを超えているかどうかを疑似的に判定している、という感じ。データサイズの判定がしたいだけでコピー先のデータは捨ててしまってokなので ioutil.Discard を使っている。

このコードではイメージ付きづらいが、仮にsrc変数に300MiBとかのデータが入っているとすると、その具体的なサイズを取得するのに []byte などへ変換して len に通せばとんでもないサイズのメモリアロケーションが発生することになる。一方で io.Reader のまま引き回せば []byte へ変換するアロケーションのコストを払わずにサイズ判定もできる。

net/mailパッケージだけでもメールアドレスのバリデーションはできる

stackoverflow.com

こんな感じで使える。

package main

import (
    "fmt"
    "net/mail"
)

func main() {
    addr, err := mail.ParseAddress("izumisy.test@example.com")
    if err != nil {
        panic(err)
    }

    fmt.Println(addr.Address) // izumisy.test@example.com
}

ひとつだけ気をつけないといけないところがあり、例えば <izumisy> izumisy.test@example.com (aaa) のようなメールアドレスもこのメソッドではエラー無しで通ってしまう。

RFC上は正しいのでバグではない&実際にはパースされてアドレス部だけが取り出せるので大きな問題にはならないが。

deferで参照している変数のポインタを更新しても参照は変わらないっぽい

こういうやつ

type Closer struct{
    Value string
}

func (c *Closer) Close(id string) {
    fmt.Printf("Closed(%s): %p\n", id, c)
}

func newCloser(c *Closer) {
    n := &Closer{Value: "aaa"}
    fmt.Printf("New: %p\n", n)
    c = n
}

func main() {
    c := &Closer{Value: "bbb"}
    fmt.Printf("Main: %p\n", c)
    defer c.Close("main") // <-- ここのcはnewCloserで作られたCloserのポインタになっているはず...?
    newCloser(c)
}

上記を実行するとこうなる

Main: 0xc000010240
New: 0xc000010250
Closed(main): 0xc000010240

newCloser関数の内部で参照渡しとしてリソースを上書きしたとしてもmainの中のdefer節で呼ばれるCloseメソッドの呼び出し対象の変数cの参照は古いままなので、newCloser関数の中で作られた方のリソースのクローズ漏れが起きる可能性がある。

これを防ぐためにはnewCloser関数のなかでもdeferでリソースのクローズをするしかない。

BlueoothドングルTP-Link UB400をUbuntuで使う

これを買った

バイス情報を見てみるとどうやら Cambridge Silicon Radio とかいうやつらしい。

at 15:26:52 ❯ lsusb
Bus 005 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 004 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 003: ID 2357:011e TP-Link 802.11ac WLAN Adapter 
Bus 002 Device 005: ID 046d:c52b Logitech, Inc. Unifying Receiver
Bus 002 Device 004: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 002 Device 002: ID 05e3:0610 Genesys Logic, Inc. 4-port hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

軽くググってみるとこのCSR製のやつはUbuntuと相性が悪いとか... beautifulajax.dip.jp

とりあえずBluetooth周りのデバイスは自分の使っている5.8.0-59-genericのカーネルにドングルがあれば動くには動くが、ちょっと気になるところもあり

あたりがちょいちょい不便で困っている。

バイスに原因があるのかLinux側に原因があるのかは分からないので、まだ試行錯誤中。

試したこと

なんとなくデバイスサスペンドが原因かもと思い、カーネルパラメータに usbcore.autosuspend=-1 btusb.enable_autosuspend=n をつけてみたりした。体感、急にBluetoothが切断されることは無くなったような気がする。

起動直後に繋がらないのを改善するために /etc/bluetooth/main.conf にあるFastConnectionとかいうやつもtrueにしてみたが、これもあんまりワークしてる気がしないな...

もし同じように困ってる人がいたら、このaskubuntuの回答がなんかヒントになるかも。

askubuntu.com

フロントエンドアプリケーションにおいて状態をどこに置くべきか論

後学のために自分の考えていることをまとめてみる。

考えられるパターン

これまでの経験から以下4つのパターンがある。

  1. ローカルStateでprop-drillingする
  2. ローカルStateかつイベント経由でデータ交換をする
  3. グローバルStoreとローカルStateを併用する
  4. グローバルStoreのみを使用する

1. ローカルStateでprop-drillingする

propとしてコンポーネント間のデータをやりとりする手法。

ほぼすべてのUIコンポーネントを親からデータを受け取りDOMを出力するだけの純粋な関数として表現できるため、全体の設計自体はシンプルになる。手間は多いが魔法は少ない。

コンポーネントの粒度が小さいアプリケーションの場合にはいわゆるバケツリレーと揶揄されるデータの受け渡しが頻発し、これに嫌悪感を持つエンジニアもいる。

2. ローカルStateかつイベント経由でデータ交換をする

Flux登場以前のフロントエンド・アプリケーションでよく見られた手法。

ほとんどFluxライブラリの再発明と同じであるが、コンポーネント間で双方向にデータが飛び交う点が大きな違い。イベントをインターフェイスとしてコンポーネントの関係性を疎結合にできる点がメリット。このデータの流れを単方向にしたものがFluxアーキテクチャである。

Vue.jsやAngular.jsにおいてはかつて親子間でのイベント送信を相互に行える機能が用意されていたため、これを使って横断的にコンポーネント間通信をする設計で開発が行われ徐々にカオスになっていく事例が見られた。いまはFluxパターンの登場によってアンチパターンになったと思われる。

3. グローバルStoreとローカルStateを併用する

仕様に応じてグローバルStoreとローカルStateを使い分ける。

一見良さそうなアイデアに見えるが、この手法の大きな問題点は「なにをStoreに置き、なにをStateに置くか」のルールづくりが大変なこと。チームが大きくなるとすぐにルールが守られなくなる。また、UIの改修によって状態の置き場がStateからStoreに移動したりなど、仕様変更時の影響範囲が状態管理部分を巻き込んで大きくなる可能性もある。

一方で、ローカルStateにすることで影響範囲を小さくしたり、別コンポーネントからの意図しないデータ参照を防ぐなどの設計も可能になるのがメリット。しかし、これはグローバルStoreにデータを置いたうえで仕様に応じた個別のデータ型を用意することでも達成できる。

4. グローバルStoreのみを使用する

グローバルStoreのみを状態管理に使う手法。

この手法では「グローバルStoreが膨らんで困る」とよく言われる。ココで言うところの「困る」とは大抵の場合「グローバルStoreのフィールドが増えて見づらくなる」と「グローバルStoreが大きくなると状態更新差分パフォーマンスが悪くなる」のふたつの懸念に集約される。これがクリアできるのであれば有用な手法になる。

利用するFluxライブラリによってはこれを解決できる機能や追加のプラグインなどが用意されていることがある。

どれを使うべきか。

まず、チームメンバが1人とか2人とかの場合にはどれを使おうが気にする必要はない場合が多い。好きなのを使えばよい。よっぽどめちゃくちゃな人がいなければコードベースは破綻しない。

しかし、経験上4-5人目くらいからが分岐点で、更に「2-3人のメンバで構成されるチームが複数ある」のようなケースになると途端に画一的なルール作りのほうが重要になってくる。設計方針に秩序を持たせたい場合には「いい感じでよしなにお願いします」がワークしなくなる。

ルールの作りやすさ順でいうと1>4>3>2かなという感じ。

Elmの場合には1しか選択肢がないのであんまり悩むことがない。

Elmにおける比較処理の高速化テクニック

ここで触れられてた話がおもしろいかったのでざっくり紹介

discourse.elm-lang.org

文字列比較はcase文のほうが早い(ことがある)

これは遅い

ensureNonBreakingSpace : Char -> Char
ensureNonBreakingSpace char =
    if char == ' ' then
        nonBreakingSpace
    else
        char

これは速い

ensureNonBreakingSpace : Char -> Char
ensureNonBreakingSpace char =
    case char of
        ' ' ->
            nonBreakingSpace
        _ ->
            char

理由は以下の通り

Currently, a comparison with a character literal isn’t optimized in the same way an integer literal is. By using an elm case expression, we make the compiler generate a javascript case statement. These again are much faster because there is no function call involved.

今のElmコンパイラ(おそらく0.19)では文字リテラルの比較が最適化されていないため、こういう結果になる。

余談ではあるがElmコンパイラは比較の際にどちらかがリテラルではない場合にパフォーマンス劣化を起こす。なぜなら、ElmにおいてJSの等価演算子はオペレータで比較される2値のどちらかがリテラルでなければ生成されないからだ。

リテラル同士の比較は内部的にはJSの等価演算子だけではなくElmランタイムの比較処理を呼び出すため、その分のオーバーヘッドが発生することになる。したがって、数値の比較であっても x == y よりは (x - y) == 0 のほうがパフォーマンスには優れているとのこと。

これ以外にもcase文でMaybeやResultをパターンマッチするほうがパイプで関数をチェーンするよりも速い、レコード更新のシンタクス({ a | field = value } みたいなやつ)を使うよりも新しいレコードを生成するほうが速い、などの話が elm-physics の作者から出ているが、ベンチマークがあるわけではないので真偽は不明。

Elmにおけるビルダパターンには後方互換性というメリットがある

Elmにおけるビルダパターンというのは以下のようなインターフェイスを指す。

-- setXxxのような関数をパイプでつないでいく雰囲気のやつ

Button.new
    |> setPadding 10
    |> setBackground "blue"
    |> setLabel "next"
    |> view

このインターフェイスの優れた点は新機能追加の際に後方互換性の維持が容易な点にある。つまり、今後 setXxx 系の関数が増えたとしても、その変更は既存でそのモジュールに依存している箇所に影響を与えないため、変更による影響範囲を最小にできる。

メリットを分かりやすくするために、ビルダパターンを採用せずに上記のインターフェイスをレコード型で表現してみるとしよう。

シンプルに考えると以下のようになる。

Button.view
    { padding = Nothing
    , background = Nothing
    , label = Just "next"
    }

可読性という観点では、上記のようにパラメタの名前を分かりやすくするためにビルダパターンを用いずRecord型を使うことはおかしくはない。しかし、ElmにおいてRecord型ではoptionalな性質のフィールドをMaybe型でしか表現することができない。これが意味するのは、新しい設定値のフィールドを追加するたびに既存で変更を加えたモジュールを利用している箇所すべてを修正しなければいけなくなってしまうということだ。

一方で、逆に考えてみるとレコード型や単純な引数でパラメタを与える実装はインターフェイスでそのパラメタが"必須である"ということを表明できる。

たとえば、上記のButtonモジュールにおいてlabelを必須のパラメタにしたいとする。その場合にはlabelの設定はビルダのインターフェイスになっていることは適切ではないため、次のようなインターフェイスにするほうが望ましい。

Button.view
    { label = "next" }
    |> setPadding 10
    |> setBackground "blue"
    |> view

このようなインターフェイスであれば、labelには必ずStringでなんらかのデータを与えないといけないということが明示できる。もちろん空文字列を与えてしまうことはできるが、その時点で違和感があるということに気付けるだけでもインターフェイス設計としては意味がある。

少し抽象化して考えるならば、あえてモジュールの変更を影響範囲として明確にさせたい(コンパイラにエラーを起こさせたい)ようなケースでは、ビルダパターンが適しているとは言えないということになる。

また、常ににビルダパターンを適用しようとすると対象のモジュールをほぼ必ずOpaque Typeの形に設計する必要があり、ここにも若干の手間がある。しかし、対象のモジュールが今後少なくない箇所から依存される未来が見えているのであれば後方互換性を維持する優先度が高いであろうし、ビルダパターンの適用はその時点で支払うべきコストだとも言える。

このように、状況に応じた観点でElmにおけるビルダパターンの使い分けを考慮してみると、モジュールのインターフェイス設計にはっきりとした意図を込められるだろう。

余談

なお、さらに応用的な実装例としてファントムビルダパターンというものがある。

medium.com

これは幽霊型を利用して「ある関数Aが呼ばれた場合のみ、関数Bも呼び出せるようにする」という制約を与え、ビルダのインターフェイスに依存関係を作りだすテクニックである。

内部的にやっていることは自分がelm-firestoreのインターフェイス設計で話したことと同じであるので、詳しくはそちらの記事を参照のこと。

izumisy.work

朝会と夕会を両方やっている

自分のチームでは朝会と夕会を両方実施しているので、その話を書きます。

アジャイルとかスクラムとかは分からないです、念のため)

自分のチームにおける朝会の課題

もともと自分のチームでは「朝会」のみをデイリーのmtgとして実施しており、そこで「今日やること」「やっていたこと」「困っていること」などをメンバに共有してもらっていた。

しかし正直のところ、朝会におけるこの報告時間はなんとなく形骸化した雰囲気が蔓延していて、とくに「やっていたこと」「困っていること」に関しては前日の話が抜けて、実質午前中の作業の話だけ(なぜなら前日の仕事はほとんど忘れてしまっているから)ということがほとんどだった。

これのまずいところは、実質朝会あとの5-6時間を占める大部分の作業報告が翌日の朝会で報告されず空白になってしまい、結果的にメインで業務を進めている午後の時間で発生した重大な報告事項などが抜け漏れてしまったり、場合によっては遅れてあとから「実は朝会で言い忘れてたんですが...」というようなコミュニケーションが発生したりするリスクがある。

とりわけ自分は「チーム開発はとりあえず朝会やっとけばOKよな」くらいの軽い気持ちで朝会を実施しており、そういう意味でもリスクトラッキングへの認識が激甘だった。

夕会の実施

インターネットで調べると「デイリーのmtgは朝会と夕会のどちらか一方をやる」という形態が(主観的には)多いように見られたが、朝会から夕会にしたとしてもそのmtgの目的性が変わらない限り、形を変えて朝会と同じ課題を抱えることになる。

というわけで、自分のチームでは朝会と夕方の両方をそれぞれ(15min-30min)で実施することとした。

それぞれのmtgの目的は次の通り。

MTG 目的
朝会 ・今日やる作業の報告
・必要に応じて作業関係者とのMTGを設定
夕会 ・今日やった作業の報告
ガントチャートへ進捗データの入力
・明日やる作業の確認

かつては朝会だけで実施していた内容を未来と過去の時間軸に基づき朝会と夕会に分離した。これによって進捗が最速で把握できるようになり、報告事項の忘却リスクを減らしている。

自分のチームでは夕会の時間を18:30に設定しているため、朝会の時点で確認した日当たりのタスクのデッドラインも常に18:30になる。タスクの内容や稼働開始時間にもよるが、夕会時点で作業が終わっていないことが判明すれば、その時点でそのタスクにビハインドのリスクがあるということを早い段階で明らかにできる。

進捗を日単位で把握するなら朝会で前日の進捗をチェックするだけでもいいのでは? とも思えるが、夕会にはそれ以外の良さもあった。例えば次のようなもの。

  • 1日作業して出てきたチームで共有したい事項をアツいうちにシェアできる
  • 進捗確認があるので進捗データの入力漏れが防止できる
  • ダラダラと作業をせず退勤するタイミングが作れる

自分は可能なかぎり夕会が終わり次第退勤するようにしている。

夕会の課題

しかし一方でまだ課題もある。

  • 夕方に他のmtgが多いメンバは参加できないケースが増える
  • 夕会で共有事項が充実してしまうことがある(個人的にはあんまり夕方過ぎに長いmtgしたくない)
  • タスクの最小単位が1日以上になればなるほどトラッキングが意味をなさなくなる

まず最初の「夕会に参加できないメンバがいる」というのはまあまあ致命的で、結局そこで参加できないメンバが出る限り進捗のトラッキングにはリスクを抱えていることになる。人によってはmtgの優先度を上げてもらうこともできるが、必ずしもそうではないこともある。

このあたりはまだ模索中である。