ラベル study の投稿を表示しています。 すべての投稿を表示
ラベル study の投稿を表示しています。 すべての投稿を表示

2024年1月14日

YAMLフォーマット

JSONは便利だ。
文字も書けるし数字も書ける。配列も書けるし構造体も書ける。

ここがちょっとなあ、と思うところもある。

  • 数字が 53bit まで
  • 行末にコンマがいるし、次に継続するものがない場合はコンマがあってはダメ
  • コメントが書けなさそう

53bit までしかないので、64bit値になる可能性がある場合は文字列にしてデコード側で変換している。

コンマが必要だったり不要だったりなのは面倒なだけなのだが、ちょっとしたJSONなのでライブラリなど無しで作ろうとした場合、コンマを出力するかどうかを判定しながらになるのがもどかしい。
まあ、コンマがあるおかげで1行で書いたりもできるのだが、設定ファイルのように人間が読み書きする前提だったら改行を末尾としたり、インデントでブロックと見なしたりすればいいやんという気持ちにもなってしまう。

あと、コメントが書きたいことがしばしばあるのだが、そういうのはない。
データ構造だから、まあ仕方ないよね。

 

だからかどうか知らないが YAML もよく使われる。
私の場合は docker compose の設定くらいしかお世話になっていないのだが、

いま(2024/01/14)の時点で、YAML仕様はバージョン1.2、リビジョン1.2.2(2021/10/01)が最新だそうだ。

https://spec.yaml.io/main/spec/1.2.2/

ページを開いて、スクロールバーの小ささに驚く。こんなに量が多いのか......
docker compose の YAMLファイルくらいしか覚えがなかったので、もうちょっとさらっとした仕様かと思っていた。

BMF みたいなのを読むのもだるいので、こういうときは cheat sheet で検索だ。

https://duckduckgo.com/?q=yaml+cheat&ia=web

たくさんあるので、見たいものを見ると良いだろう。
私はあまり難しいことを書きたいわけでもないので、

  • 数値の bit数については記述がないので制限はなさそう。
  • 文字列だからダブルクォーテーションで囲む必要があるという訳ではない。ただ数字だけ書くと数値として扱われるので、書く人が使い分ける。
  • 数値は10進数以外に8進数(0o)や16進数(0x)が使えるようだ(2.4. Tags)
  • 改行コードとしてはいつもの3種(LF, CR, CR/LF)が使える (5.4. Line Break Characters)
  • コメントは # 始まり (6.6. Comments)
  • 1項目は key:value のような感じ。
  • インデントは行の先頭からのスペースで表す。がTAB文字はダメ (6.1. Indentation Spaces)
  • 配列的なものはハイフンを頭に付けて、インデントを同じ高さにして並べる
    • "key:" で改行して、その下にインデントを1つ分付けて各行に並べる
  • ハイフンではなく同じインデントの高さで並べると、JSONのObjectのような扱いになる

このくらい知っておけば普段使いには困らないだろう。

docker compose の yaml でよくある「-」始まりは配列だし、インデントを付けているのも見やすさというよりもYAMLフォーマットの仕様だからということになる。

ここで "sectets" の下が key:value なのに 「-」がついていた。これは "source"から"mode"までが1つのオブジェクトで、それが複数あってもよいけどここでは1つしかないですよ、ということらしい。YAMLをJSONに変換するオンラインコンバーターのサイトがあったので試してみた。

https://jsonformatter.org/yaml-to-json

サンプル

items: 
  - name: hello 
    value: 10 
  - name: world 
    value: 20
  

JSON変換

{ 
    "items": [ 
         { 
            "name": "hello", 
            "value": 10 
        }, 
        { 
            "name": "world", 
            "value": 20 
        } 
    ] 
}
  

うむ。

「-」を付けることを考えると、インデントはスペース2つ分にするのが扱いやすいか。

2023年8月20日

golang: interfaceの勉強 (3)

続き。

hiro99ma blog: golang: interfaceの勉強 (2)
https://hiro99ma.blogspot.com/2023/08/golang-interface-2.html


struct と duck typing

golang の調べ物をしたとき、duck typing という言葉を見ることがある。
メソッド名・引数・戻り値が一致したら同じものと思われる、くらいに思っていたのでこういうことができると考えていた。

package main

import "fmt"

type Abc struct{}

func (a *Abc) Greet() {
    fmt.Println("Hello")
}

type Def struct{}

func (d *Def) Greet() {
    fmt.Println("Aloha")
}

func main() {
    abc := new(Abc)
    abc.Greet()
    abc = new(Def)
    abc.Greet()
}

これは赤文字のところでコンパイルエラーになる。コンパイルエラーというか文法エラーというか。

interface Ghi を作ってそこに Greet() を書いておき、使う場合は Ghi の変数として扱ってやると、Abc と Def が Ghi の派生であるとかを匂わせる必要もなく動くようになる。

package main

import "fmt"

type Ghi interface {
    Greet()
}

type Abc struct{}

func (a *Abc) Greet() {
    fmt.Println("Hello")
}

type Def struct{}

func (d *Def) Greet() {
    fmt.Println("Aloha")
}

func main() {
    var abc Ghi
    abc = new(Abc)
    abc.Greet()
    abc = new(Def)
    abc.Greet()
}

struct と interface を関連付けるような記述をしないというところが「プログラミング言語Go」のインターフェースの章で『暗黙的に満足される』といっている意味なんだろう。

暗黙的だと関連性がわからなくて悩みそうだと思ったなら、明示的に書いてもよいだろう。

type Abc struct{ Ghi }

明示的に interface があることを書いておきながらも、interface の方に定義されたメソッドを書いていない場合はコンパイルエラーになりそうだが、そうはならない。
どうなるかというと、未定義のメソッドを呼び出そうとした時点で実行時エラーになる。

panic: runtime error: invalid memory address or nil pointer dereference

package main

import "fmt"

type Ghi interface {
    Greet()
}

type Abc struct{ Ghi }

// func (a *Abc) Greet() {
//     fmt.Println("Hello")
// }

type Def struct{ Ghi }

func (d *Def) Greet() {
    fmt.Println("Aloha")
}

func main() {
    var abc Ghi
    abc = new(Abc)
    abc.Greet()
    abc = new(Def)
    abc.Greet()
}

goalng は全体的に縛り付けるような決まりが少ない気がする。その辺は C言語っぽいなーと感じる。

ともかく、golang は duck typing な言語なのかもしれないが(厳密な定義はよく知らない)、それは何でもかんでもじゃなくて interface が関係した場合だけなんだと思われる。


テストでinterfaceを使おうとする

mock なのか stub なのか driver なのか厳密なところ避けさせてもらう。
本物ではなく偽物にしたいということで fake という言葉を使おう。

テストで fake を使いたい場合、テストしたい関数なりメソッドなりは変更したくないのだが、その中で実際に行う処理は偽物に置き換えたいということだと思う。
ネットワークにアクセスする処理だがエラーを起こしたことにしてテストしたい、とか、ハードウェアにアクセスする処理だが実機では確認できないのでダミーの処理で置き換えたい、とか。

image

まず、テスト対象の関数/メソッドは、その中から呼び出す次の処理を切替ができるように書いておかないといかん。上の図だと黄色の丸で表しているが、これが golang でいうところの関数だったら切り替えるのは難しいだろう(タグで切り替えるということはできそうだが)。

ではメソッドの中から別のメソッドを呼ぶように書くとして、何をどう書くと良いだろう。

Golangにおけるinterfaceをつかったテストで mock を書く技法 - haya14busa
http://haya14busa.com/golang-how-to-write-mock-of-interface-for-testing/

こちらでは、対象となる方は interface を名前を付けて書いている。テスト対象自体が interface を所持しているのではなく外側にあって(所持してても良いだろうが)、new するときに設定しているのか。そして fake の方では interface を無名の埋め込みにしている。

 

ではもうちょっと簡易なコードにして試そう。
mocktest というディレクトリを作り、その中に main.go,  greet/greet.go, greet/machine.go というファイルを置く。

  • main.go
package main

import (
    "fmt"
    "mocktest/greet"
)

func main() {
    g := greet.NewGreet()
    g.SetData(3)
    fmt.Println(g.GetData())
}
  • greet/greet.go
package greet

type Greet struct {
    m     MachineInterface
    value int32
}

func NewGreet() *Greet {
    g := &Greet{}
    g.m = NewMachineData()
    return g
}

func (g *Greet) GetData() int32 {
    return g.value + g.m.Get()
}

func (g *Greet) SetData(value int32) {
    g.value = value
    g.m.Set(value)
}
  • greet/machine.go
package greet

import (
    "math/rand"
    "time"
)

type MachineInterface interface {
    Get() int32
    Set(value int32)
}

type MachineData struct {
    value int32

    r *rand.Rand
}

func NewMachineData() *MachineData {
    m := &MachineData{}
    m.r = rand.New(rand.NewSource(time.Now().UnixNano()))
    return m
}

func (m *MachineData) Get() int32 {
    multi := m.r.Int31n(100)
    return multi * m.value
}

func (m *MachineData) Set(value int32) {
    m.value = value
}

「go run .」などとして実行すると、毎回異なる値が出力されるだろう。
greet/machine.go が乱数を使っていて、ちょっとそのままではテストしづらいというようにしている。やっている内容として意味は無い。 greet/machine.go が制御できない動作をするということだけわかっていればよい。
ここでテストするならば greet/greet.go のメソッドになるし、fake にしたいのは greet/machine.go になる。

では適当にテストコードを書いてみる。

  • greet/greet_test.go
package greet

import (
    "testing"
)

type fakeMachineData struct {
    MachineInterface

    FakeGetData func() int32
    FakeSetData func(value int32)
}

func (f *fakeMachineData) Get() int32 {
    return f.FakeGetData()
}

func (f *fakeMachineData) Set(value int32) {
    f.FakeSetData(value)
}

func TestGetData1(t *testing.T) {
    fakeData := &fakeMachineData{
        FakeGetData: func() int32 {
            return 10
        },
        FakeSetData: func(value int32) {
        },
    }
    mo := NewGreet()
    mo.m = fakeData
    mo.SetData(3)
    value := mo.GetData()
    if value != 13 {
        t.Errorf("GetData: %v", value)
    }
}

func TestGetData2(t *testing.T) {
    fakeData := &fakeMachineData{
        FakeGetData: func() int32 {
            return 15
        },
        FakeSetData: func(value int32) {
        },
    }
    mo := NewGreet()
    mo.m = fakeData
    mo.SetData(5)
    value := mo.GetData()
    if value != 20 {
        t.Errorf("GetData: %v", value)
    }
}

$ go test ./greet/ -v
=== RUN   TestGetData1
--- PASS: TestGetData1 (0.00s)
=== RUN   TestGetData2
--- PASS: TestGetData2 (0.00s)
PASS
ok      mocktest/greet  0.001s

greet/greet_test.go は package が greet なので、小文字のフィールドにもアクセスできる。それを利用して greet.NewGreet() で設定している NewMachineData() を上書きしている。

 

greet/machine.go を別パッケージにしてみよう。
外部のライブラリを使っているとそうなるだろう。

  • greet/greet.go
package greet

import "mocktest/machine"

type Greet struct {
    m     machine.MachineInterface
    value int32
}

func NewGreet() *Greet {
    g := &Greet{}
    g.m = machine.NewMachineData()
    return g
}

func (g *Greet) GetData() int32 {
    return g.value + g.m.Get()
}

func (g *Greet) SetData(value int32) {
    g.value = value
    g.m.Set(value)
}
  • machine/machine.go
package machine

import (
    "math/rand"
    "time"
)

type MachineInterface interface {
    Get() int32
    Set(value int32)
}

type MachineData struct {
    value int32

    r *rand.Rand
}

func NewMachineData() *MachineData {
    m := &MachineData{}
    m.r = rand.New(rand.NewSource(time.Now().UnixNano()))
    return m
}

func (m *MachineData) Get() int32 {
    multi := m.r.Int31n(100)
    return multi * m.value
}

func (m *MachineData) Set(value int32) {
    m.value = value
}
  • greet/greet_test.go
package greet

import (
    "mocktest/machine"
    "testing"
)

type fakeMachineData struct {
    machine.MachineInterface

    FakeGetData func() int32
    FakeSetData func(value int32)
}

func (f *fakeMachineData) Get() int32 {
    return f.FakeGetData()
}

func (f *fakeMachineData) Set(value int32) {
    f.FakeSetData(value)
}

func TestGetData1(t *testing.T) {
    fakeData := &fakeMachineData{
        FakeGetData: func() int32 {
            return 10
        },
        FakeSetData: func(value int32) {
        },
    }
    mo := NewGreet()
    mo.m = fakeData
    mo.SetData(3)
    value := mo.GetData()
    if value != 13 {
        t.Errorf("GetData: %v", value)
    }
}

func TestGetData2(t *testing.T) {
    fakeData := &fakeMachineData{
        FakeGetData: func() int32 {
            return 15
        },
        FakeSetData: func(value int32) {
        },
    }
    mo := NewGreet()
    mo.m = fakeData
    mo.SetData(5)
    value := mo.GetData()
    if value != 20 {
        t.Errorf("GetData: %v", value)
    }
}

machine を import するようになるくらいの変更しかない。
外部のパッケージだったら interface があっても不自然ではない。

もしここで machine/machine.go に interface がなかったらどうしよう?
その場合は greet/greet.go の方で作っておけば良い。「暗黙的に満足される」から machine/machine.go は変更しなくても良いのだ。
案外、なんとかなるものだ。

  • macihne/machine.go
package machine

import (
    "math/rand"
    "time"
)

// type MachineInterface interface {
//     Get() int32
//     Set(value int32)
// }

type MachineData struct {
    value int32

    r *rand.Rand
}

func NewMachineData() *MachineData {
    m := &MachineData{}
    m.r = rand.New(rand.NewSource(time.Now().UnixNano()))
    return m
}

func (m *MachineData) Get() int32 {
    multi := m.r.Int31n(100)
    return multi * m.value
}

func (m *MachineData) Set(value int32) {
    m.value = value
}
  • greet/greet.go
package greet

import "mocktest/machine"

type MachineInterface interface {
    Get() int32
    Set(value int32)
}

type Greet struct {
    m     MachineInterface
    value int32
}

func NewGreet() *Greet {
    g := &Greet{}
    g.m = machine.NewMachineData()
    return g
}

func (g *Greet) GetData() int32 {
    return g.value + g.m.Get()
}

func (g *Greet) SetData(value int32) {
    g.value = value
    g.m.Set(value)
}
  • greet/greet_test.go
package greet

import (
    "mocktest/machine"
    "testing"
)

type fakeMachineData struct {
    machine.MachineData

    FakeGetData func() int32
    FakeSetData func(value int32)
}

func (f *fakeMachineData) Get() int32 {
    return f.FakeGetData()
}

func (f *fakeMachineData) Set(value int32) {
    f.FakeSetData(value)
}

func TestGetData1(t *testing.T) {
    fakeData := &fakeMachineData{
        FakeGetData: func() int32 {
            return 10
        },
        FakeSetData: func(value int32) {
        },
    }
    mo := NewGreet()
    mo.m = fakeData
    mo.SetData(3)
    value := mo.GetData()
    if value != 13 {
        t.Errorf("GetData: %v", value)
    }
}

func TestGetData2(t *testing.T) {
    fakeData := &fakeMachineData{
        FakeGetData: func() int32 {
            return 15
        },
        FakeSetData: func(value int32) {
        },
    }
    mo := NewGreet()
    mo.m = fakeData
    mo.SetData(5)
    value := mo.GetData()
    if value != 20 {
        t.Errorf("GetData: %v", value)
    }
}

2023年8月19日

golang: interfaceの勉強 (2)

使ったことがない interface だが、テストでモックを作ろうとするときはやっておいた方がよさそうだ。

hiro99ma blog: [golang] interfaceの勉強
https://hiro99ma.blogspot.com/2022/02/golang-interface.html


struct

golang には class はないが、struct で似たようなことはできる。
こういう意味が無いこともできる。

type Abc struct {}

func (a *Abc) Greet() {
  fmt.Println("Hello")
}

ここの「(a *Abc)」の a はレシーバーと呼ばれる。 class の this なんかに近いし、もし Abc の定義に変数があったなら a.xxx のようにしてアクセスできる。

そして Greet() はメソッドである。おそらくだが、レシーバーがない func は関数と呼んで、レシーバーがある func はメソッドと呼ぶんじゃないだろうか。まあ、C++とかでもそういう使い分けだったと思うし、一般的な呼び方をしておいてよさそうだ。
ちなみに構造体のメンバーは「フィールド」と呼ぶ。

この Greet() はAbc を参照しているわけではないし、そもそも Abc はフィールドを持つわけではないので関数であっても困らない。空の構造体はどういうシーンで使うんだろうか?
思いつくのはチャネルの待ち合わせだ。

hiro99ma blog: [golang] chan で待つ
https://hiro99ma.blogspot.com/2022/06/golang-chan.html

この記事では type を使わず直接 struct {} を使っているが、待ち合わせるチャネルが複数ある場合はそれぞれに type で名前を付けることになるだろう。

それ以外だと・・・特に共通で使うような変数がないけど機能としてまとめておいた方がわかりやすいがパッケージにするほどでもないようなときにグループ化する目的とか?

 

struct は既に定義してある struct を中に組み込むことができる。
このとき、その構造体にフィールド名を付けることもできるし、付けないこともできる。付けた場合は他のフィールドと同じようにドットを使って名前を使って参照する。名前を付けなかった場合はあたかも組み込んだ struct のフィールドがそこに展開されているかのように扱うことができる。

なので、こういう組み込みにした場合は軽い継承関係のようなものができているのかと思っていたのだが、キャストのようなことをして代入することはできないそうだ。unsafe とか使ってやってみようとしたのだが、どうにもダメだった。

 

そう、ダメだったのだ。
だからモックにしたい struct を組み込んだ struct を作ってテストしよう、というのはできないのだ。
そういう場合は interface を使うようにするそうである。

・・・というつもりで書こうとしていたのだが、「モックのためだけに interface を使うのってどうなのよ」みたいな記事も見つけてしまい、ちょっと揺らいでいる。

テストのためだけに`interface`を書きたくないでござる — KaoriYa
https://www.kaoriya.net/blog/2020/01/20/never-interface-only-for-tests/

ただまあ、めんどうになるくらいだったら interface を作ってもいいかなというくらいの気持ちだ。C言語でヘッダファイルを書くような感じで。

ちなみに↑の記事の方は、同じ struct 名を用意し、テストの時と本番用でビルドタグを切り替えて読み替える、というようなことをやっている。ビルドタグという機能を知らなかったのだが、ファイル単位で指定できるのは便利そうだ。

 

長くなったので interface については次回。

2023年6月12日

えいごのべんきょう (1)

技術的な仕事をメインにしていて、かれこれ何十年もそれで生き延びている。

生き延びているのだが、英語を非常に苦手としている。
会社でも「苦手」「うまくできない」という人々がいたのだが・・・裏切られたっという気持ちだ。
あれだ、試験前に「ぼく勉強していません」といいながらも良い点数を取ったりするあれだと思う。
あるいは「Linuxチョットワカル」という、深みを知ってしまったからこそということもある。

が、学生・・・生徒の頃からダメだった人間からすると、裏切られたという気持ちにしかならない。


しかし、英語ができない技術者に世の中は冷たい。
暖かい世界に戻るには、ある程度英語ができねばならないだろう。

ここで「AIがもうすぐ何とかしてくれる」とか「最近は自動翻訳もかなりよい訳をしてくれる」という回答は求めていない。
そういう、静的に解消できるものはやっているのだ。
もう少し具体的に言うと、ネットミーティングなどでお話があるのをリアルタイムで正しく理解したいのだ。


いろいろと、英語の勉強方法については情報がある。
おかげで探せないくらいだ。

勉強の目標にもいろいろあるが、まずは「聞こえること」に重点を置くことにした。
ものすごく弁が立つならわからないが、まずは相手の話を聞かないと返事ができないだろうと考えるからだ。
まあ、弁が立つにしても相手の話を聞かずに話し続けるのは無理だろうと思う。

 

英会話教室というものもあるようだが、いろいろな意味で敷居が高い。

  • ほぼ聞き取れない
  • 自分が発した内容がよくわからない

この2つですな。

シャドーイング、というものがあるようだが、発音が良いとか悪いとか言う以前に、何が何だかよくわからない。
「りぴーとあふたーみー」ができないのだ。

そのうちなんとかなってくれんかなー、と思いつつも、世界の終わりが来たりしないと無理かなー、などと思ったり。

2022年8月21日

[android] AlarmManager でアプリを起こしたい

やりたいこと

AlarmManager を使って、アプリが起動していないように見える状況でもアプリを起動させたい。

 

結果

アプリがタスク一覧に載っていなくてもアラームから起動することはできるが、強制停止状態では無理。

本体の OS が API 29 以降では Alarm のようなバックグラウンドからアプリを起動させることに制限がかかっているので、一旦 Notification などでユーザに通知だけ行い、ユーザ操作によってアプリを起動させることになるだろう。

バックグラウンドからのアクティビティの起動に関する制限  |  Android デベロッパー  |  Android Developers
https://developer.android.com/guide/components/activities/background-starts

 


前回、AlarmManager を使って反復アラームを設定し、AndroidManifest.xml に設定した <receiver> の class がブロードキャストされた intent を受け取ってログ出力するところまで確認した。

私がやりたいのは、アプリが立ち上がっていない(と思われる)状況でも AlarmManager での発火をトリガにしてアプリを起動したい、というものだ。
プリインストールのような system 属性のアプリであれば好き勝手できそうだし、Google Play からインストールしたアプリであればあまり勝手なことをされたいとは思わないだろう。

なので今回は Google Play からインストールするような system 属性を持たないアプリで期待したことができるのか、できないならどこまでやれるのかを調べたい。


前回は 15 分にしたが、あれは WorkManager の最短が 15分だったはず、くらいで決めたものだ。
AlarmManager なら 3 分でもできた。

2022-08-20 20:48:32.650/com.hiro99ma.alarmtest2 D/com.hiro99ma.alarmtest2.MainActivity: setAlarm
2022-08-20 20:53:47.674/com.hiro99ma.alarmtest2 D/com.hiro99ma.alarmtest2.NotificationReceiver: Receive !!
2022-08-20 20:56:28.482/com.hiro99ma.alarmtest2 D/com.hiro99ma.alarmtest2.NotificationReceiver: Receive !!
2022-08-20 20:59:47.663/com.hiro99ma.alarmtest2 D/com.hiro99ma.alarmtest2.NotificationReceiver: Receive !!
2022-08-20 21:02:47.665/com.hiro99ma.alarmtest2 D/com.hiro99ma.alarmtest2.NotificationReceiver: Receive !!

まあ、間隔が短くできるということはリソースを食いやすいということでもあるだろうから、アプリの審査としては厳しくなってしまうのかも? どういう基準で見ているのかは知らんのだけど、面倒なアプリの方が審査に時間がかかりそうな気がするじゃあないか。そういう基準だったら、AlarmManager ではなく WorkManager の方が緩いと思うのだ。

ともかく、3分くらいだったらテストしやすいのでそれで見ていこう。


まず「アプリが終了している」という状態について確認が必要だ。

「アプリが動いている」という状態が一番確実なのは、そのアプリがフォアグラウンドになっていて Activity が表示されているときだ。これは間違いなく動いているだろう。

ならばActivity が表に出ていないときがどうなのか、ということになる。
調べていて出てきたのが「 Doze」と「アプリスタンバイ」というものだ。

Doze とアプリ スタンバイ用に最適化する  |  Android デベロッパー  |  Android Developers
https://developer.android.google.cn/training/monitoring-device-state/doze-standby?hl=ja

書いてあるとおりなら、

【Doze】
デバイスが長い間使用されていない場合にアプリのバックグラウンド CPU とネットワーク アクティビティを保留する

【アプリスタンバイ】
ユーザーがしばらくの間操作していないアプリのバックグラウンド ネットワーク アクティビティを保留

アプリスタンバイはネットワークだけしか保留しないのか? 文章だけ読むと、先に「アプリスタンバイ」になり、それでも長い間使用されないようであれば「Doze」になるようにも見えたのだが、そうではないのだろう。

アプリで Doze をテストする
https://developer.android.google.cn/training/monitoring-device-state/doze-standby?hl=ja#testing_doze

「dumpsys deviceidle force-idle」を実行してそのアプリの動作が変わるなら、Dozeモードで動作が変わるアプリということだろう。

アプリスタンバイの場合はどれをスタンバイにするか指定しているので別物かなぁ、というところだ。
ネットで検索したらもっとよい情報が出てくるだろうが、今回はそこまで考えたくないので無視だ。


私が「アプリが「終了している」というのは、アプリスタンバイに近いと思う。
そして Android のタスク一覧から該当するアプリを上スワイプしたときも「終了している」だろうから起動してほしい。
できればアプリ情報画面で強制停止(force stop)させたあとからも起動してほしいのだが、これは難しいと思う。

あとは、アプリの中から「終了」を行った場合だ。
これが何をしているのかがよくわからんのだが、Activity が 1枚しかないときに「戻る」を選ぶと「終了しますか?」と尋ねてくるアプリがあったように思う。

ユーザが「アプリを終了したい」と思うことと、アプリとして終了するという実装は、一致しないとはいわないまでも、一致しづらいことが多いと思う。例えばゲームのハイスコアをサーバで管理してみんなで共有するようなアプリがあったとして、ユーザが「ハイスコアを更新したから終了させよう」と思ったタイミングと、アプリが「スコアのデータをサーバにアップしよう」と実装されているタイミングは必ずしも一致しないだろう。ユーザは終わった気分でいても、他のユーザがアクセス中だったりしてアップロードを保留しているかもしれない。サーバにアクセスが集中しないようにわざと遅らせるようにしているかもしれない。

そう考えると、モバイルのアプリは「終了」が即時行われるとは考えづらい。たぶん即時終了させるのがアプリ情報画面から「強制停止」を行う操作だろう。
一覧から上スワイプするのは強制停止ではないと思う。Doze にさせるか、スタンバイにしているかのどちらかではないか。調べていないけどスタンバイじゃないかなぁと思っている(個人の感想です)。

端末を USB接続しているとスリープしない状態になったりするし、その設定をオフにしても実は何かあるんじゃないの?とか、15分を 3分にしたことで挙動が違ったりするんじゃないの?とかいろいろ考えてしまうのだが、まあそのときはそのときだ。

Android Studio の Logcat タブでアプリが (DEAD) になるようであればアラームを受け付けることはさすがにできないだろう(と思ったが、タスク一覧から上スワイプすると DEAD になったので違うのか)。


adb shell でログインすると、 dumpsys というコマンドが使用できる。

dumpsys  |  Android デベロッパー  |  Android Developers
https://developer.android.com/studio/command-line/dumpsys?hl=ja

dumpsys コマンドだけたたくとすべてのシステムサービス情報を出力しようとするので、対象とするサービスを指定するのがよろしい。今回であれば alarm だ。
指定してもたくさん出力されるのだが、試作している alarmtest2 で検索するとこういう情報が見つかった。

    ELAPSED_WAKEUP #5: Alarm{bef203a type 2 origWhen 27853758 whenElapsed 27853758 com.hiro99ma.alarmtest2}
      tag=*walarm*:com.hiro99ma.alarmtest2/.NotificationReceiver
      type=ELAPSED_WAKEUP origWhen=+2m54s204ms window=+2m15s0ms repeatInterval=180000 count=0 flags=0x0
      policyWhenElapsed: requester=+2m54s204ms app_standby=-5s796ms device_idle=-- battery_saver=-5s796ms
      whenElapsed=+2m54s204ms maxWhenElapsed=+5m9s204ms
      operation=PendingIntent{77e94eb: PendingIntentRecord{b073248 com.hiro99ma.alarmtest2 broadcastIntent}}

ここでは ELASPSED_WAKEUP と経過時間で発火するように指定している。おそらく 3行目の type=ELAPSED_WAKEUP の次に出ている origWhen が発火するまでの残り時間だと思う。時間をおいて取得すると減っていたからだ。

これは、alarmtest2 アプリを起動すると出てきたし、タスク一覧で上スワイプして消したときも残っている。たぶん発火すると「App Alarm history」というところにも出てきたし、Alarm stats にも出てくるようになった。しかしアプリ情報から強制停止させると出てこなくなった。おそらくだが、アプリ情報から強制停止ボタンがタップできるような状態であればアラームは動くんじゃないかな。強制停止でアラームが消えるのは、登録元が消えたからか通知先が消えたからか。まあどっちでもよいが、確認するためには通知先を別アプリにしないとわからんな。

 

ん?
ということは、自分で自分にアラームを仕掛けておいて自分を起動する、というのは無理というか、既に起動しているから表に出してあげるだけで目的を達成できるんじゃなかろうか。

override fun onReceive(context: Context?, intent: Intent?) {
    val actionIntent = Intent(context?.applicationContext, MainActivity::class.java).apply {
        action = Intent.ACTION_MAIN
        addCategory(Intent.CATEGORY_LAUNCHER)
        flags = Intent.FLAG_ACTIVITY_NEW_TASK
    }
    context?.startActivity(actionIntent)
}

こんな感じだろうと思ったのだが、例外は発生しないもののアプリが表に出てくることはなかった。 Notification をタップしてアプリを表に出すときはこれでよかったのだが、状況が違うのか。

? I/ActivityTaskManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.hiro99ma.alarmtest2/.MainActivity} from uid 10502
? W/ActivityTaskManager: Background activity start [callingPackage: com.hiro99ma.alarmtest2; ...(中略)...; inVisibleTask: false]
? D/ActivityTaskManager: getPreferredLaunchDisplay userset_displayid false current displayId -1
? D/ActivityTaskManager: getPreferredLaunchDisplay userset_displayid false current displayId -1
? E/ActivityTaskManager: Abort background activity starts from 10502

BroadcastReceiver だからダメなのかと思い、AlarmManager から setRepeating() するときの PendingIntent で同じ actionIntent を呼ぶようにして設定したのだが、それでも同じだった。

 

「ダメだった」という状況について説明していなかった。
手順はこうだ。

  1. alarmtest2 を起動する
  2. タスク一覧を出して alarmstart2 を上スワイプする
  3. アラームが発火するまで待つ(本体は 2分でスリープになる)

3分以上待っているとアラームが発火して Logcat に何か出てくるので、そのときにスマホをスリープ解除して画面を出し、alarmtest2 アプリが表に出ていれば成功、出ていなければダメ、という判定の仕方をしている。


logcat の 「Abort background activity start」がエラー出力なので、そこら辺か。

バックグラウンドからのアクティビティの起動に関する制限  |  Android デベロッパー  |  Android Developers
https://developer.android.com/guide/components/activities/background-starts

あー、ユーザが操作しているのにいきなり表に出てくる可能性があるから制限がかかるのか。
わかる。私だったら怒るね。

ただ、私は alarmtest2 を API 26 以上ということで作っている。であれば API 29 からというこの制限から外れるんじゃないのか?

image

雰囲気からすると、ここに書いている APIレベルは本体の OS のものということだろう。

 

ともかく、Android としては「時間に依存する通知」を使ってほしいそうだ。リンク先や "ヘッドアップ通知" という言葉も出てくるので、いわゆる Notification を使えってことなのだと思う。 Notification → 通知をタップしてアプリを出す、がよいのかな。


というわけで、Notification を追加した alarmtest2 の最新版がこちら。

https://github.com/hirokuma/AndroidAlarmTest/commit/7aea025ed07fd53d4774a526f3328984ff4994a7

前々回の Notification 記事から持ってきたのだが、Notification を表示するタイミングとコンテキストが違うので多少変更されている。

アプリが起動して行うのは Notificationチャネルの作成のみ。
BroadcastReceiver の onReceive() で Notificationの作成と表示を行っている。記事を書いたときは with という Kotlin のスコープ関数なるもので notify() を呼んでいたが、BroadcastReceiver からは使えなかったので普通に書いた。

これを実行すると、数分おきに Notification の通知が行われる。スリープしていても Notification が出てくるので非常にうっとうしい。 API が間違っていなくても用法を間違うと嫌な動作をするという例と思ってもらおう。

2022年8月15日

[android] AlarmManager でアラームする

Android の基礎学習。
AlarmManager でアプリが起動していなくても何かしたい。 notification が出したいのだが、まずは Log が出るだけでも良いだろう。

Schedule alarms  |  Android Developers
https://developer.android.com/training/scheduling/alarms

 

いろいろ書いてあるのだが、こういう系統はだいたいイベントが発火するときに PendingIntent で何かするものだろう。
PendingIntent は作るとき?に Intent を与えられて、PendingIntent の殻をかぶった Intent みたいなものだろうと思ってる。

作るといっても、PendingIntent.getActivity() だったり PendingIntent.getBroadcast() だったりといくつか作り方がある。前回はnotification の学習だったが、そこでは notification をタップしたときの挙動用に PendingIntent.getActivity() を使っていた。
タップすると自アプリが表に出てくる=Activity に対して通知するからだと思っている。

では、notification を出したい場合は誰が受け取るのだろう?
今回は Activity を表に出したくないので getActivity() ではないだろう。
受け取るために Service がいるのだったら getService() かもしれない。でもわざわざ service を作りたくないとも思う。
PendingIntent で他に PendingIntent を返す static メソッドはなさそうだ(ForegroundService みたいなの以外は)。

PendingIntent.getBroadcast() で作ってやって、それを受け取るところで処理できるなら一番楽そうな気がする。


ブロードキャストを受け取る方法について。

Broadcasts overview  |  Android Developers
https://developer.android.com/guide/components/broadcasts

ローカルだったら LocalBroadcastManager があるようなことを書いてあるが、class 自体が deprecated 扱いだったから今から使わなくてもよいだろう。他の observable pattern で置き換えられるようなことは書いてあったが、これを使えとは書かれていないのでチュートリアルの通りにやれば良いのだろう。

英語版を見ているが、受け取り方は「Manifest-declared receivers」と「Context-registered receivers」の2種類のようだ。
AndroidManifest.xml にあらかじめ書いておくタイプか、ソフトウェア上で必要なときだけ受け付けるタイプか。
その2択であれば、アラームについては AndroidManifest に書くタイプだろう。アプリが立ち上がっていないときに受け取りたいのだから。でも、もしかしたら一度登録しておけばよいというタイプかもしれんので実験はしてよかろう。


これに従ってアラームを作っていく。
「反復アラーム」というのは、ワンショットではないアラームのことだろう。

AlarmManager  |  Android Developers
https://developer.android.com/reference/android/app/AlarmManager

 

アラームの大きな設定は、タイプと精度のようだ。

タイプは、「何分後」みたいな経過時間タイプ(ELAPSED_REALTIME 系)と「何時何分」みたいな時間指定タイプ(RTC 系)があるようだ。
ELAPSED は経過時間といいつつ、デバイスが起動してからの経過時間という基準になっているそうだ。実装は、例では「SystemClock.elapsedRealtime()」からの相対値を指定していた。

精度は、曖昧だけど電池に優しいか、時間きっちりかを選択するもののようだ。メソッドとしてはおそらく第1引数に type を取るものがそれだと思う。

image

チュートリアルでは setInexactRepeating() と setRepeating() が紹介されている。 exact の頭に in- が付くことで「正確ではない」ということなのだろう。反復アラームだからその 2つだけ紹介されているのだろう。

アラームというものが Androidのバージョンで変化しているもののようで、APIの説明にいろいろ書いてある。

  • API 31以降で Intent.EXTRA_ALARM_COUNT を使うなら、 PendingIntent の flags に FLAG_MUTABLE がいる
  • API 19 の時点で反復アラームはすべて inexact になっている。繰り返しで正確にしたいなら、ワンショットのアラームを繰り返すことになる。

うーん、よくわからん。
API 20 以降なら setRepeating() だけでexact にできるということかな?

 

とりあえず動いているようなので記録を残しておいた。
Kotlin + Android 8以降 + Empty Activyty をベースにしている。

https://github.com/hirokuma/AndroidAlarmTest/tree/260da35b09a5012999a96de6d1e62903e7d525c2

 

Logcat のログを必要そうなところだけ抜粋。

2022-08-15 17:33:25.893/com.hiro99ma.alarmtest2 D/MainActivity: setAlarm
2022-08-15 17:52:25.390/com.hiro99ma.alarmtest2 D/NotificationReceiver: Receive !!
2022-08-15 18:14:40.987/com.hiro99ma.alarmtest2 D/NotificationReceiver: Receive !!
2022-08-15 18:23:33.683/com.hiro99ma.alarmtest2 D/NotificationReceiver: Receive !!
2022-08-15 18:35:38.638/com.hiro99ma.alarmtest2 D/NotificationReceiver: Receive !!

15分にしたのだが、15分より短いのもある。
WAKEUP の方にしたから放置した状態で確認したかったのだけど、ときどき触ったりしたので実際はどうだか分からん。

何が苦労したかって、 Activity::onCreate() でアラームを設定すると動作しなかったことだ。
何か方法はあるのかもしれないが、setAlarm() を onCreate() で呼び出すとダメだったのだ。動くサンプルがあったのだけどアラームの設定に違いがなさそうだったのでひどく悩んだのだ。そのサンプルはボタンをタップしてアラームを設定するようになっていたので、ためしに onResume() に持っていったら動作したのだった。

まだいろいろわかっていないが、今回はアラーム動いた記念なのでよしとしよう。

2022年8月11日

[android] Notificationを出す

基礎の勉強。

Android Studio でプロジェクトを作る。
対象は Android 8.0 以降。Kotlin。Empty Activity。

Create a Notification  |  Android Developers
https://developer.android.com/training/notify-user/build-notification

日本語版もあるのだけど、若干更新されていない。まあ、実行時エラーで気づける程度のものだが。
ともかく、基礎的な notification を作成する。

Create a basic notification
https://developer.android.com/training/notify-user/build-notification#SimpleNotification

手順

  1. notification で表示する中身を作る。
    NotificationCompat.Builder() の後ろに設定するメソッドを連結して呼んでいくだけ。
    Builder() の第2引数である CHANNEL_ID は String型。これは Android 8.0 以降で追加されたものらしく、それより前は無視するとのこと。
    これは、この次に作る notification channel を指定するものだ。
    なお、日本語版で~と書いたのは、ここの PendingIntent.getActivity() の最後の引数が 0 になっていたからだ。今は何か指定しないといけないそうである。フラグの違いは検索すると出てくるので探してほしい。
  2. notification channel を作る。
    そういえば、Android のどこからかアプリの上にドットを出したりするようになったなぁ。
  3. notify() で表示。

 

こんな感じだ。

    private fun createNotificationChannel() {
        val name = "のてぃふぃけーしょんちゃんねる"
        val descriptionText = "のてぃふぃけーしょんちゃんねるのですくりぷしょん"
        val importance = NotificationManager.IMPORTANCE_DEFAULT
        val channel = NotificationChannel(NOTIFICATION_ID, name, importance).apply {
            description = descriptionText
        }
        val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }

    private fun createNotification(): NotificationCompat.Builder {
        // 自アプリを起こす
        val wakeupMeIntent = Intent(this, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }
        val wakeupMePendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, wakeupMeIntent, FLAG_IMMUTABLE)

        return NotificationCompat.Builder(this, NOTIFICATION_ID)
            .setSmallIcon(com.google.android.material.R.drawable.ic_clock_black_24dp)
            .setContentTitle("のてぃふぃけーしょんたいとる")
            .setContentText("のてぃふぃけーしょんてきすと")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(wakeupMePendingIntent)
            .setAutoCancel(true)
    }

これらを、たとえば onCreate() などで呼び出す。
notify() の第1引数は Int 型で、ユニークならよいらしい。あとから中身を更新したり削除したりするときに識別するものだそうだ。

        createNotificationChannel()
        val builder = createNotification()
        with(NotificationManagerCompat.from(this)) {
            notify(NOTIFY_TEST_ID, builder.build())
        }

まあ、アプリの起動と同時に notification を出すことは少ないかもしれんが、うまいことやってほしい。


このアプリを起動すると notification が出てくる。
左側の時計アイコンは、適当に Android Studio の自動補完で候補に出てきたものだから、ちゃんとしたものかどうか知らん。

image

notification の中身は出てきたから良いとして、notification channel で設定したものはどこで出てくるのか?

見つけたのは、アプリごとの「アプリ情報」画面から「通知」をタップして表示される画面だった。

image

これだと notification channel の name だけなのだが、さらに表示されている項目をタップすると description の方も出てきた。

image

 

日本語にしておくと、そしてひらがなにしておくと他の文字列やデバッグ情報と重ならないからありがたいね。

 

ちなみにアプリ名が「AlarmTest」なのは、AlarmManager の実験をしたかったからだ。
時間になったら何かしたい→ notification を出すか、という流れだ。

2022年3月23日

Lambda関数?

いろいろと耳を塞いで生きてきたと思う。
運が良ければスルーできるのだが、そうならない場合も多い。

Lambda関数はどうだろう?
まだわからないのだが、調べるに越したことはない気がしている。


まずはこちらから。

アロー関数 - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/future-javascript/arrow-functions

アロー関数の説明で、「または」ということで Lambda関数という呼び名を紹介している。
アロー関数は私もしばしば使う。 function でも => でもどっちでもよいときはどうしたもんだか、と悩みはするものの、アロー関数じゃないとダメなところもたまにあるので、そんなに忌避しているわけではない。

this がどうのこうのは JavaScript の話だと思う。

となると、アロー関数が Lambda 関数とも呼ばれるような要素と言ったら「function と打つのが長い」しか当てはまらないと思うのだよ。

しかし、 Kotlin なんかは fun だし、Golang(に Lambda関数があるかは知らんが) は func だ。これを長いというのはちょっと無理があるだろう。
となると、変数に代入するような書き方ができるとかか?

 

検索すると AWS のも出てくるが、そちらで調べていくと今の知識だと間違えそうなのでやめておく。
あとは Excel と Python が出てきた。
よし、Python で見よう。

Pythonのlambdaって分かりやすい - Qiita
https://qiita.com/nagataaaas/items/531b1fc5ce42a791c7df

最初に「無名関数」と説明してある。
あれ、無名関数って別の呼び方をされてなかったっけ?
無名クラスか。 Java だったような気がするが、入れ子でクラスを書くときにそういうのが出てきたような。

hiro99ma blog: [android]Java8への思いやりを形に
https://hiro99ma.blogspot.com/2015/11/androidjava8.html

Android Studio が私が書いたコードを勝手に省略しようとするから腹が立ったという話だ。
このときもあんまりわからずに終わっているが、結局は見栄えだとか手軽とか、そこら辺が魅力ということなのだろうか。


もっと一般的な何かがあるはずだ。

language agnostic - What is a lambda (function)? - Stack Overflow
https://stackoverflow.com/questions/16501/what-is-a-lambda-function

一気に難しそうな話になってしまった・・・。
少なくとも、私の力で読み解くのは難しそうだ。

無名関数 - Wikipedia
https://ja.wikipedia.org/wiki/%E7%84%A1%E5%90%8D%E9%96%A2%E6%95%B0

ラムダ式・・・関数型言語・・・うぅ、頭が・・・。

 

しかし、JavaScript ってこんな書き方ができるんだ、と思っていた部分の一つが Lambda関数 / Lambda式 / 関数オブジェクト / 無名関数 と呼ばれるものの概念などを言語に落とし込んだ結果なのだろう。

なので、あまり難しく考えず、JavaScript のアロー関数など、と覚えておくことにしよう。どうせ正解はないのだ。

2022年2月13日

[golang] testing

コードを書いたら極力テストは行うべきだ(戒め)。
というわけで、golang のテストを調べる。

過去の私もいくらかテストについては調べていたようだ。

hiro99ma blog: [golang]本体無しでテストをしたい (1)
https://hiro99ma.blogspot.com/2020/03/golang-1.html

しかし、もう記憶にない。

最近は fuzzing というのもあるようだが、golang v1.18 以降らしいので扱わない。


まず、コード以外のルールから。

  • ファイル名は _test.go で終わるようにする
    • そうすると go build の対象にはならず go test の対象になる
  • "testing" を import する
  • 関数名は "Test" で始まるようにする
    • そうするとテスト関数という扱いになる
  • 関数名で "Test" の次は大文字で始める

関数名はともかく、ファイル名は気をつけねば。テスト用じゃないのに _test.go と付けてしまうことがあり得なくもないからだ。golang って大文字小文字ルールもそうだけど、名前で決められていることがしばしばあるからあせるね。

あれ、golang の関数名は UTF-8 を使えるのだろうか?
使える場合、日本語なんかは大文字扱いされるのだろうか?

 

main.go

01: package main
02: 
03: import (
04:     "fmt"
05: )
06: func よしお() string {
07:     return "よしお"
08: }
09: 
10: func main() {
11:     fmt.Printf("%s\n", よしお())
12: }

main_test.go

01: package main
02: 
03: import "testing"
04: 
05: func Testよしお(t *testing.T) {
06:     yoshio := よしお()
07:     if yoshio != "よしお" {
08:         t.Error(`not yoshio`)
09:     }
10: }

$ go test -v
=== RUN   Testよしお
--- PASS: Testよしお (0.00s)
PASS
ok      github.com/hirokuma/go-test1    0.012s

よいらしい。
t.Error() の中がバッククォートになっているが、これは本でそう書いてあったからだ。別にバッククォートである必要はない。
JavaScript だとバッククォートなら ${xxx} みたいにして変数を展開できるが、golang だと bash のヒアドキュメントみたいにそのまま展開されるというだけのようだ。 raw string literal というらしい。

 

ちなみに、Test の次を小文字にするとテスト関数と見なされず実行されなかった。
一時的に実行したくない場合なんかによいかもしれぬ。

 

あと、本に書いてあることで興味があるのはカバレッジだろうか。
ただ載っているとおり -run=Coverage だとテストが実行されなかった。

$ go test -v -run=Coverage -coverprofile=c.out
testing: warning: no tests to run
PASS
coverage: 0.0% of statements
ok      github.com/hirokuma/go-test1    0.003s

-cover だと実行された。

$ go test -v -cover -coverprofile=c.out
=== RUN   Testよしお
--- PASS: Testよしお (0.00s)
PASS
coverage: 50.0% of statements
ok      github.com/hirokuma/go-test1    0.003s

結果は、載っているとおりに実行するとブラウザが立ち上がった。

go tool cover -html=c.out

image

まあ、main関数は仕方ない。

と思ったが、main関数もテストできるのか。

Test the main function in Go | GO.into Development
https://mj-go.in/golang/test-the-main-function-in-go

main関数は特別なものだと思っていたが、go test なら普通に呼び出せるのか。

func TestMain(t *testing.T) {
    main()
}

普通に呼べた。

image

 

go test できれいにテストを回したいなら、実装の方も多少はテストを意識して実装する必要があるだろう。
めんどくさいという気もするが、その方が実装も小さい単位になってメンテナンスしやすくなることも多い。そこまで悪くない話だと思っている。
main関数も、中に全部書いてしまうのではなく本体は別関数にして戻り値をテストしやすくする、みたいな。

私はそこら辺が苦手な気がする。
なんというか、見える範囲に揃っていないとよくわからなくなるというか。
コース料理よりも丼にいろいろ載っている方がうれしいというか。
誰に言い訳してるんだ、私は。


私が作ったことがある golang のテストは、RC-S370/S のエンコードくらいだった。

go_pasori370/msg_test.go at master · hirokuma/go_pasori370
https://github.com/hirokuma/go_pasori370/blob/master/dev/msg_test.go

よく見ると t.Fatail を使っているな。
t.Error は継続し、t.Fatal は止めるようだ。
私は途中で止めてしまいたいのだが、一通り全部通してみたいと考える方が普通なのかもしれないがどうなんだろう?

なるほど、最初のエラーで止めるオプションがあるのか。

unit testing - Stop on first test failure with `go test` - Stack Overflow
https://stackoverflow.com/questions/32046192/stop-on-first-test-failure-with-go-test

Testよしお() で失敗するようにして試してみよう。

まずは普通に実行すると、Testよしお() も TestMain() も実行されている。

$ go test -v
=== RUN   Testよしお
    main_test.go:8: not yoshio
--- FAIL: Testよしお (0.00s)
=== RUN   TestMain
よしお
--- PASS: TestMain (0.00s)
FAIL
exit status 1
FAIL    github.com/hirokuma/go-test1    0.014s

これを -failfast オプションを付けると、 TestMain() は実行されていない。

$ go test -v -failfast
=== RUN   Testよしお
    main_test.go:8: not yoshio
--- FAIL: Testよしお (0.00s)
FAIL
exit status 1
FAIL    github.com/hirokuma/go-test1    0.003s

開発後半になって、テスト実行にものすごく時間がかかるようになった場合なんかによいのかもしれない。


直接 go test とは関係ないのだが、どこでエラーが起きたかの根源を知りたいことは多い。
error を返すようにしたとして、"InvalidParam: -20 < 0" とか返されたとしても困るのだ。
コマンドを実行してエラー、とかなら因果関係がわかりやすいのだが、サーバー的なプログラムでログの中にエラーが残っていたとしても追跡できなくては意味が無い。

がんばって fmt.Errorf() なんかで書いていくのだけど、まあ面倒だ。
エラーにした理由はもちろん、自分のパッケージ名や関数名を書かないと誰だか分からないからだ。
そしてそれをログにも出すのか return するだけなのかも結構悩む。その関数が A からも B からも呼ばれるようであれば自分の情報を返すだけではなく、呼び出し元の方でも何かやってもらわないと追えないからだ。ログに出せば間違いはないのかもしれないが、ログがたくさん出るというのもあまりうれしくない。

 

今までどうしてきたかというと、組み込み開発だとそもそもログを出せる状況にないことがほとんどだったし、文字で出せないからビットのパターンで表したりとかしてたしね。エラーになっても仕方ないのでそもそもリセット掛けて再起動するし。
そんなわけで、私のエラー処理は初心者レベルだと認識している。
精進がいりますな。

[golang] interfaceの勉強

go.mod をだいたいわかったつもりでいたけど、go mod tidy してモジュールが見つかりませんと言われると自信がなくなりますな。
はい、忘れるために別の勉強をしましょう。


interface は class とセットで存在するイメージだったのだが、golang には class はない。予約語にもない(どうでもいい話だが、予約語は英語だと"keywords"なので検索しづらい)。
とはいえ、C++ では class と struct が姉妹みたいなものなので違和感はない。

 

プログラミング言語Goの例であげられているのは io パッケージの Writer。
短いので貼り付けよう。

type Writer interface {
	Write(p []byte) (n int, err error)
}

Write という名前で、引数として []byte を持ち、戻り値が (int, error) という構成になっていればよいということになる。
ただ、

func Write(p []byte) (n int, err error) {
  return 0, nil
}

みたいな「関数」だとダメだというか、意味が無いと思う。

func (x SomeStruct) Write(p []byte) (n int, err error) {
  return 0, nil
}

のように「メソッド」になっていないとありがたみがないだろう。
メソッドになっているから、複数のメソッドがあるけど interface が同じだから呼び出すことができる、という使い方になるだろう。

 

他の言語では、interface が先にあって、その interface を持つ class を実装することを明示的に書くことが多いと思うが、Go言語は明示的に書かないようになっている。
コードの検索をするときには探しづらいのだが、実装しているときにはあれこれ書かなくて良いので便利なのかもしれない。

明示的に書かなくて良いので、全然 interface を考えずに実装していたメソッドを interface になっているようなつもりで呼ぶことができる。本では「暗黙的に満足される」と書いてある。

 

あとは、宣言した interface 型を組み合わせたり含んだりする interface を作ることもできるということと、中身に何も書かない空インターフェース(empty interface)も作ることができるというくらいか。


というだけにしては本のページが多いので、何か気をつけることがあるのだろう。

 

まず、 empty interface。
これは C言語の voidポインタみたいな感じで何も要求しない interfaceだ。

package main

import (
    "fmt"
)

type dummy1 struct {}
func (d *dummy1) Print(param interface{}) {
    fmt.Printf("dummy1 int: %d\n", param)
}

type dummy2 struct {}
func (d *dummy2) Print(param interface{}) {
    fmt.Printf("dummy2 string: %s\n", param)
}

type dummys interface {
    Print(param interface{})
}

func main() {
    d1 := &dummy1{}
    d2 := &dummy2{}
    d  := []dummys{d1, d2}
    d[0].Print(123)
    d[1].Print("abc")
}

最初は dummy1.Print は param int、dummy2.Print は param string にして「interface{} を引数にしておけばなんでもいけます」みたいな例にしようとしたのだが、それだとコンパイルエラーになった。
interface の中の interface{} は、そのまま interface{} として実装されないといけないようだ。まあ当たり前か。

 

あとは使い方の注意点や型アサーションのことや助言などが書かれている。


empty interface が int や string のような基本型もとれるということは、最初に書いたようなただの関数であっても interface を使って活用させる方法があったりするのだろうか?

でも、あったとしてもあんまりわかりやすいことにはならない気がするので、気にしないことにしよう。

2021年10月31日

docker再びの再び (5)

docker のネットワークについて勉強中。
前回はドライバの bridge で、今回は host だ。

Networking using the host network | Docker Documentation
https://docs.docker.com/network/network-tutorial-host/


ホストとコンテナの分離を削除する、という表現で混乱したのだが、ホストからコンテナのネットワークにアクセスできるということだと思っている。
アクセスだけなら ping でもできていたのだが、ここではもっとアプリ層に近いところだ。

チュートリアルでは nginx (えんじんえっくす、と読むのを知ったのはずいぶん最近のことだ)のコンテナを立ち上げて、ホスト側のブラウザで 80番ポート(デフォルトの http)にアクセスしている。

この手順では、ポート番号が出てきていないのだが、コンテナの外からホスト環境で nginx が立ち上がっているかのようにアクセスできるのだ。私の場合、Windows10 から VirtualBox で Lubuntu をゲストOS として起動し、ゲストOS 上で docker を立ち上げているのだが、Windows10 側から Lubuntu の IPアドレスをブラウザで指定してもアクセスできる。
実体はコンテナ側で動いているので、ポート番号だけ間借りしているというか転送しているというか、そんな感じなのか。


さて、 デフォルトの bridge を使うと他のコンテナやホストの間で通信ができ、ユーザ定義の bridge を使うと特定のコンテナの間で通信ができた。 1つのコンテナが デフォルトとユーザ定義の bridge の両方に接続?することができるので、そうするとどっちにもアクセスできた。

じゃあ、host とか使わずにデフォルトの bridge を使ってもいいんじゃ無いかと思ったのだが、これだと ss -nat | grep :80 などとしても 80番ポートが見当たらない。
docker ps で見ると PORTS に 80/tcp と出てくるので、80番ポートが使われているのは間違いないだろう。
しかし、 host を使った場合に docker ps で見ても PORTS には何も出てこない。

 

これだけだと bridge の意味がよくわからんのだが。。。
と思ったら、ブラウザでコンテナの IPアドレスにアクセスすると ss コマンドで現れた。 Listen していないのにアクセスできる・・・?
いや 80番ポートで Listenしているわけではないというだけかもしれない。
ss -l | grep docker とすると6つくらい出てきたので、今はそういうことにしておこう。


私の今のイメージでは、bridge と host はこんな感じだ。
丸いのがコンテナね。

image

  • bridge に名前を付けて作成すると、それと接続しているコンテナ間で LANを形成できる。
  • コンテナは複数の bridge に接続できる
  • host を指定するとホスト環境のネットワークとして見える

bridge と host も同時にできるかと思ったが、それはエラーになった。

bridge接続済みのコンテナを host につなごうとしたとき
container cannot be disconnected from host network or connected to host network

host接続済みのコンテナを bridge につなごうとしたとき
container sharing network namespace with another container or host cannot be connected to any other network

ホストのネットワークなら bridge のコンテナ内からでもアクセスできるので必要ないのだろう。

Connect a container to a user-defined bridge
https://docs.docker.com/network/bridge/#connect-a-container-to-a-user-defined-bridge

--publish を使うことで特定のポートをホスト側に見せることができる。だから host を使わなくてもよいというか、なるべく bridge を使って公開するポートを限定した方が不安が少ないと思う。


他にもいろいろあるだろうが、テスト環境を作るのであればこのくらいの知識でなんとかなるんじゃなかろうか。

2021年10月30日

docker再びの再び (4)

4回もやっていると何でこんなタイトルだったのか忘れてしまいそうになるが、今回で dockerを勉強するのが3回目になるからだった。


今回はネットワークについて。

Docker コンテナ・ネットワークの理解 — Docker-docs-ja 19.03 ドキュメント
http://docs.docker.jp/engine/userguide/networking/dockernetworks.html

日本語だ。
多少情報が古いかもしれないが、基本的なことは変わってないんじゃなかろうか。まあ、後で調べよう。

 

ifconfig で見えるのは docker0 だけだが、docker コマンドでは書いてあるように 3つ見える。

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
cc02564a80e1   bridge    bridge    local
4dee0c98d161   host      host      local
51c798d9077c   none      null      local

うーん、少し読んだけどさっぱりわからんよ。
もっと概要から説明してほしいのだが。

Networking overview | Docker Documentation
https://docs.docker.com/network/

Dockerのnetworkingサブシステムは、driver を使って追加/削除できる(プラグイン方式)らしい。
デフォルトで存在したりコアネットワークとして提供されているドライバはこれら。

  • bridge
  • host
  • overlay
  • macvlan
  • none

さっきの network ls で出てきたものでも DRIVER として表示されているな。

 

bridge

これがデフォルトのネットワークdriver。
固有名詞じゃなさそうだから「ドライバ」でよいのかな。
どういう意味でデフォルトかというと、普通はこれが使われる、というデフォルトのようだ。そりゃそうだろうと言われそうだが「デフォルトで付属している(けどあまり使われない)」みたいなこともあるので気にしたのだ。

名前からすると、docker コンテナ内のネットワークとリアル環境のネットワークを橋渡ししてくれるのだろうか。
なんというか、そうだったら bridge 以外はいらないような気がするのだが、他は何をするんだろう?

 

host

   うーん?

For standalone containers, remove network isolation between the container and the Docker host, and use the host’s networking directly.
スタンドアロンコンテナの場合、コンテナとDockerホスト間のネットワーク分離を削除し、ホストのネットワークを直接使用します。

通常はコンテナとホストが分離されているけど、それを削除するということは、分離しなくなるということか?

 

overlay

複数の docker daemonをそれぞれ接続し、 swarm service?がそれぞれ通信できるようにする もの らしい。
うん、よくわからん。

1台の PCでは、docker daemonは 1つ動いているということになるんだろうか。
bridge と host では「standalone containers」と書いてあったのだが、それぞれのコンテナで docker daemonが 1つ動いているという扱いにするのなら、これがコンテナ間を直接つないでくれるのかもしれん。
swarm というのは知らん。後回し。

 

macvlan

MacOS 専用のドライバかと思ったが、MACアドレスの方を表しているようだ。
MACアドレスをコンテナに割り当てることができるらしい。ということはマシン外とアクセスするときに使えるということだろうか。それに MACアドレスということなら物理層とか下の方の階層になるはずなので他のドライバと併用できるとかだろうか。

まあ、用途として特殊になりそうだから後回しでいいかな。

 

none

host が外部と接続しないようにするドライバだったが、 none はネットワーク接続をしないというドライバらしい。
ネットワークドライバなのにネットワークに接続しないとは、なんか面白いものだ。

カスタムネットワークドライバと一緒に使うのが普通らしい。
よくわからんが、後回しで良い気がする。ただ、初期設定で動作しているのでよく使うものなのかも。

 

 

説明の中で「standalone containers」という言葉がしばしば出てくる。
スタンドアローンなのにコンテナは複数形なのだ。だから、スタンドアローンのコンテナが複数あって、それを bridge とかで接続するとか、それぞれが host とかで自分の中だけで運用するとか、そういう話なんじゃなかろうか。

あと、「swarm service」という言葉も出てくる。

【swarm】
群れ、群衆。群がる。

swarm mode というページがある。

Current versions of Docker include swarm mode for natively managing a cluster of Docker Engines called a swarm.
Dockerの現在のバージョンには、スウォームと呼ばれるDockerエンジンのクラスターをネイティブに管理するためのスウォームモードが含まれています。

クーベネイティス、だったか、複数のコンテナを使ったシステムをうまいこと立ち上げるのがあったと思うが、それで使うのだろうか? わからんことは後回しだ。


Networking with standalone containers | Docker Documentation
https://docs.docker.com/network/network-tutorial-standalone/

bridge ドライバがデフォルトだとあるが、ここでは「デフォルト bridge ネットワーク」と「ユーザ定義 bridge ネットワーク」の2つが書かれている。ドライバとは書いていないので別の話なのか。
デフォルトの bridge ネットワークは製品版には推奨しないそうだ。

このチュートリアルではどちらも見せてくれるそうなので、やってみるのが良かろう。

 

まず、デフォルトの方。
特に何か特別なことをするわけでもなく、コンテナを2つ立ち上げると IPアドレスがそれぞれホストとは異なるプライベートアドレスが割り当てられた(ホストは 192.168.0.0 だが、コンテナは 172.17.0.0 だった)。
そのコンテナの中からグローバルなIPアドレスにアクセスできるし、相手のコンテナにもアクセスできる。

そういえばホストにもアクセスできるのかは確認していなかった。
docker stop でコンテナを終わらせた状態なのだが「再開」はできるのだろうか? チュートリアルと同じ docker run のコマンドを打ち込んでも名前が既にあるとエラーになった。
コンテナを削除すれば良いそうだが、単に再開させるだけなら docker start でよいようだ。

で、コンテナの中からホストに向けて ping しても成功したし、ホストからコンテナに向けて ping しても成功した。
私の記憶では、ネットワークのことをやるのにポート番号をどうのこうの設定していた気がするのだが... まあ ping だからポート番号は関係ないというだけで、後から出てくるのか。
わからん。

 

次はユーザ定義の方。
docker network create で bridge をベースにして(?)新たなドライバを作っているのか。
この方式だと、ping 先としてコンテナ名を使うことができる(bridge の場合は IPアドレスだけだった)。

デフォルトがなんで製品に向かないかというと、コンテナを立ち上げてしまうだけでネットワークにアクセスできてしまうからだと思う。他にも理由はあるとは思うが、システムごとに network create して作るのが無難だろう。


長くなったので、Hostのチュートリアルは次回にしよう。

2021年10月21日

docker再びの再び (3)

前回、Dockerfile を使って簡単な、本当に簡単なイメージを作るところまでやった。
RUN で実行した内容までイメージに含まれるから、準備はそこでやっておけというところだろう。

つまり、Dockerfile をもらった場合は、それで docker build してイメージを作って、docker run してやればよいということか。
しかし、Dockerfile だけでイメージを作ることができれば良いのだけど、GitHub に上がってるやつなんかは他のファイルもいるだろうから、コードとか気にせず使いたいだけだったらイメージそのものがほしいところだ。

そこで Docker Hubの出番か。


docker run するときも、ローカルにイメージがなければ docker pull でダウンロードしてくると書いてあった。
なので、私も動かしたいものを Docker Hub から探してきて、docker pull して docker run すれば動かせると思う。

 

困ったことに、これを動かしたい、というものが思いつかない。。。
仕方ないので、これにした。

https://hub.docker.com/r/btcpayserver/lightning/tags

$ docker pull btcpayserver/lightning:v0.10.1-2
v0.10.1-2: Pulling from btcpayserver/lightning
a330b6cecb98: Pull complete
d18683af84c4: Pull complete
d089f3521651: Pull complete
00b9a3bf1da8: Pull complete
c6394cf1c296: Pull complete
241733e1daf6: Pull complete
de9614f35f2b: Pull complete
735eb65d54fb: Pull complete
8047f9f53e51: Pull complete
c9ed1993582a: Pull complete
Digest: sha256:929386072091408c8574fdda9c5e059cf2846fdef82b194f810ddb2281e09ac9
Status: Downloaded newer image for btcpayserver/lightning:v0.10.1-2
docker.io/btcpayserver/lightning:v0.10.1-2

$ docker run btcpayserver/lightning:v0.10.1-2
Invalid combinaion of LIGHTNINGD_NETWORK and LIGHTNINGD_CHAIN. LIGHTNINGD_CHAIN should be btc or ltc. LIGHTNINGD_NETWORK should be mainnet, testnet or regtest.
ltc regtest is not supported

イメージは取ってくることができたが、単に docker run するだけではダメだった。
環境変数かな?

Docker run reference | Docker Documentation
https://docs.docker.com/engine/reference/run/#env-environment-variables

docker run の後に -e "xxx=yyy" の形か、export された環境変数なら -e xxx で指定できるようだ。

$ docker run -e "LIGHTNINGD_NETWORK=regtest" -e "LIGHTNINGD_CHAIN=btc" btcpayserver/lightning:v0.10.1-2
network=regtest added in /root/.lightning/config
rpc-file=/root/.lightning/lightning-rpc added to /root/.lightning/config
Installing bundled plugins
C-Lightning starting, listening on port 9735
2021-10-20T10:31:52.693Z UNUSUAL lightningd: Creating configuration directory /root/.lightning/regtest
2021-10-20T10:31:52.693Z UNUSUAL lightningd: Creating configuration directory /root/.lightning/regtest
2021-10-20T10:31:52.920Z INFO    database: Creating database
2021-10-20T10:31:53.000Z UNUSUAL hsmd: HSM: created new hsm_secret file

ちゃんとした動作環境を用意していないからここで止まってしまうが、 -e で指定すれば動くことは分かった。
ついでに bitcoind も動かせることを確認しよう。

https://hub.docker.com/r/btcpayserver/bitcoin/tags

$ docker pull btcpayserver/bitcoin:22.0-1-amd64
22.0-1-amd64: Pulling from btcpayserver/bitcoin
07aded7c29c6: Pull complete
d17b51f7cfe8: Pull complete
6d6d52f12809: Pull complete
e47859bdfcf2: Pull complete
a57e01ab8b33: Pull complete
Digest: sha256:aacdcf9419dc88d80fd130fc7d51dda46b1be323967798c113ff77b6b6eea74c
Status: Downloaded newer image for btcpayserver/bitcoin:22.0-1-amd64
docker.io/btcpayserver/bitcoin:22.0-1-amd64

$ docker run btcpayserver/bitcoin:22.0-1-amd64
2021-10-20T10:40:41Z Ignoring unknown configuration value mainnet
2021-10-20T10:40:41Z Bitcoin Core version v22.0.0 (release build)
2021-10-20T10:40:41Z Assuming ancestors of block 00000000000000000008a89e854d57e5667df88f1cdef6fde2fbca1de5b639ad have valid signatures.
(略)

立ち上がる。
立ち上がるのだが、設定ファイルを書き換えて動かしたい。しかしイメージは既にできあがった状態だ。
たしか、ボリュームをマウントする方法があったはずだ。


docker runのオプションにある。

VOLUME (shared filesystems)
https://docs.docker.com/engine/reference/run/#volume-shared-filesystems

-v=ローカルのディレクトリ:コンテナ側のディレクトリ、という形だ。
指定すると、確かにローカルのディレクトリにコンテナ側で作成されたファイルが入ってくることが分かった。

が、どうもこの Dockerfile というかイメージは設定ファイルも一緒に作るようで、設定ファイルを先に置いていても上書きされてしまう。

こういうのはどうしようもないな。
幸いなことに、設定ファイルを作る部分は Dockerfile の RUN ではなく CMD 側で実行されているようなので、例えば regtest用にしたいならこんな感じで変数を与えてやると良い。

$ docker run -e "BITCOIN_NETWORK=regtest" -v=`pwd`/bitcoin:/home/bitcoin/.bitcoin btcpayserver/bitcoin:22.0-1-amd64

"entrypoint.sh" のような名前のファイルは、コンテナ起動時の設定をするためによく使われると Dockerの本にも書いてあったような気がする。今思えば、会社にあったオライリーの Docker本はなかなかよさそうだったのでもっと読んでおけば良かった。

ともかく、docker pull して楽ができるとしても、カスタマイズをしたいならオリジナルが何をしているのかを知っていないといかん。
とは思ったものの、今回は Docker Hubのリポジトリとほぼ同じ名前で GitHub のリポジトリがあったからたどり着けたのだが、普通はどうするのだろうか? IMAGE LAYERのところを見ても詳細はわからんし。


あとは、ポート番号だ。
単に docker run だけした場合、どうもコンテナで起動したアプリのポート番号はホスト側には出てこないようなのだ。

EXPOSE (incoming ports)
https://docs.docker.com/engine/reference/run/#expose-incoming-ports

こういう「incomming」って、誰に向けての説明か悩むよね。コンテナを起動するコマンドが主体なのか、コマンドを実行するのはホストだからホストが主体なのか。

オプションは -p ホスト:コンテナ らしい。「Publish a container's port or a range of ports to the host」なので、コンテナのポートをホストに公開するオプションになるようだ。

$ docker run -e "BITCOIN_NETWORK=regtest" -v=`pwd`/bitcoin:/home/bitcoin/.bitcoin -p 18444:18444 -p 18443:18443 btcpayserver/bitcoin:22.0-1-amd64

こうするとホスト側で ss -nat などでポート番号の使用状況を確認すると 18443, 18444 が出てくるようになる。

ちなみに、実行中のコンテナにログインしてコマンドを実行したいと思うなら docker exec を使うとよいだろう。

$ docker ps
$ docker exec -it d33e6ab4e04f /bin/bash

"d33...04f"は実行中のコンテナIDで、doker ps などで確認できる。

 

ネットワーク関係は次回だな。

2021年10月20日

docker再びの再び (2)

さて、docker本体のことをやっていこう。

あれからちょっと本を読んで勉強したのだが、私は「まずDockerfileから!」と思っていたのだが、コマンドラインからでもできるけどいちいち面倒だからDockerfileにする、という感じなのだな。
まあ、そういう意味ではDockerfileから始めるのも間違いではないのか。

今回はこういうバージョンで進めます。

$ docker --version
Docker version 20.10.7, build 20.10.7-0ubuntu1~20.04.2


基本に忠実にいこう。
というよりも、本家のドキュメントが一番わかりやすい気がする。

Docker overview | Docker Documentation
https://docs.docker.com/get-started/overview/

Docker objectsのところに動かし方の例が載っていた。 "docker run" でいろいろやってくれるそうだ

$ docker rm `docker ps -qa`
$ docker rmi `docker images -qa`
$ docker run -i -t ubuntu /bin/bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
7b1a6ab2e44d: Pull complete
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:latest
root@972d70ba7c5b:/#

最初の2コマンドは、残っていたコンテナやイメージを全部消したかっただけだ。
3つめのコマンドがサイトに書いてあった例である。
Dockerfile がなくても使えるのだが、引数が多くなると面倒だから Dockerfileにしよう、というところだろう。

コンテナはメインプロセス?が終了すると実行が終わると言うことだった(本に書いてあった)。
ここでは /bin/bash がそれに当たる。シェルだからユーザが何もしないと生き続ける。 exit などで終了するとコンテナも終了する。

root@972d70ba7c5b:/# exit
exit
$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND       CREATED          STATUS                      PORTS     NAMES
972d70ba7c5b   ubuntu    "/bin/bash"   14 minutes ago   Exited (0) 27 seconds ago             kind_austin

docker ps は結果が長くなって折り返して見づらいのだが、STATUS が Exited になっていることがわかる。

 

docker runのオプション

-i ... Keep STDIN open even if not attached
-t ... Allocate a pseudo-TTY

シェルを動かしているから、ターミナルを動かさないといかんし、入力させるから STDIN を開いたままにしないといかんし、ということかな。


イメージを自分で作るのはいずれやるとして、コマンドラインの使い方を覚えるのが面倒そうだから Dockerfile の書き方を先に調べよう。

Best practices for writing Dockerfiles | Docker Documentation
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

Dockerfile はイメージを自動的にビルドするためのテキストファイルと書いてあるので、実行を簡単にするものではない?
でも「contains all commands」と書いてあるので、なんとかなるのか?

$ cat Dockerfile
FROM ubuntu:20.04
CMD /bin/bash

こんな Dockerfileを作った。
CMD と RUN の違いがよくわからんが、RUN が事前準備で CMD が実行するメインプロセスだと思う。

$ docker build -t test:v1 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM ubuntu:20.04
20.04: Pulling from library/ubuntu
7b1a6ab2e44d: Pull complete
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:20.04
  ---> ba6acccedd29
Step 2/2 : CMD /bin/bash
  ---> Running in 4500cd01b011
Removing intermediate container 4500cd01b011
  ---> b3180f0f44d8
Successfully built b3180f0f44d8
Successfully tagged test:v1

これで終わりだ。
CMD で書いたものが実行されたりはしていない・・・? いや Step 2/2 でやろうとしているな。

サンプル通りにやってみよう。

$ cat Dockerfile
FROM ubuntu:20.04
COPY /hello.txt /
RUN cat /hello.txt

$ cat hello.txt
Hello, World!

hello.txt はあらかじめカレントディレクトリに作ってある。

$ docker build -t test:v1 .
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM ubuntu:20.04
  ---> ba6acccedd29
Step 2/3 : COPY /hello.txt /
  ---> cbc39019824a
Step 3/3 : RUN cat /hello.txt
  ---> Running in 178ad7dff873
Hello, World!
Removing intermediate container 178ad7dff873
  ---> b66dc3af8f2a
Successfully built b66dc3af8f2a
Successfully tagged test:v1

おお、実行されているではないか!
シェルはちょっと特殊だと思うから、なんかオプションがあったような気もする。

docker run したのと違い、これで実行すると docker images で test というリポジトリが見えた。

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
test         v1        b66dc3af8f2a   14 minutes ago   72.8MB
< none>       <none>    9cdcb8314e60   16 minutes ago   72.8MB
< none>       <none>    b3180f0f44d8   20 minutes ago   72.8MB
ubuntu       20.04     ba6acccedd29   4 days ago       72.8MB

イメージを自動的に作るという、あれだろう。
サイズが全部同じなのは、これらは ubuntu という layer の上に全部載っかってるからということでよいのかな?
わからん。

 

まず、簡単に分かることからやろう。
さっきは RUN で cat したが、 CMD でもいいんじゃなかろうか。

$ docker build -t test:v1 .
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM ubuntu:20.04
20.04: Pulling from library/ubuntu
7b1a6ab2e44d: Pull complete
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:20.04
  ---> ba6acccedd29
Step 2/3 : COPY /hello.txt /
  ---> 4522c706bb17
Step 3/3 : CMD cat /hello.txt
  ---> Running in e50e77c5baa6
Removing intermediate container e50e77c5baa6
  ---> e71d4d3c2fe5
Successfully built e71d4d3c2fe5
Successfully tagged test:v1

うーん、Running と出力されているので、動いていないことは無いのだと思う。
こちら側の標準出力に出力させる理由がなかったとかだろうか。

RUN
https://docs.docker.com/engine/reference/builder/#run
RUN命令は、現在のイメージの上にある新しいレイヤーでコマンドを実行し、結果をコミットします。

CMD
https://docs.docker.com/engine/reference/builder/#cmd
CMDの主な目的は、実行中のコンテナーにデフォルトを提供することです。

CMDのデフォルトは、上で書いたメインプロセスと同じ意味だろう。
RUNの「結果をコミットします」は、イメージを作るときに実行結果もイメージに含めますよ、ということだろう。

$ docker run test:v1
Hello, World!

ほら、動いた。

2021年10月3日

docker再びの再び (1) - 無料プランの話

2016年、2019年とdockerのことを勉強しては忘れる日々。

hiro99ma blog: docker ? (1)
https://hiro99ma.blogspot.com/2016/08/docker-1.html

hiro99ma blog: docker ? (2)
https://hiro99ma.blogspot.com/2016/08/docker-2.html

hiro99ma blog: Docker再び (1)
https://hiro99ma.blogspot.com/2019/05/docker-1.html

hiro99ma blog: Docker再び (2)
https://hiro99ma.blogspot.com/2020/03/docker-2.html

今も忘れた状態を維持している。
コマンドであれば、メモを残しておいてまねすれば良いのだが、dockerみたいなのはそうもいかないと思う。

いや、dockerもそういうレベルで落とし込まないと忘れるに決まっている。忘れないという前提で勉強するのに意味が無いことは、今までの記事でも分かる。

今シリーズは、なるべくそういう記事になるようがんばろう。


記事の前提だが、Linuxのみとする。
うちだとWindows10がホストで Virtual Boxに Ubuntuをインストールしていることが多い。今週には Windows11 がリリースされるようだが、うちのPCたちはどれも推奨環境を満たせなかった。ThinkPad T460sは期待したのだが、まさかCPUが引っかかるとは。。。
そういうのはともかく、Windows上での dockerではなく、Ubuntu上での dockerとする。

あとは、ハードウェアを操作するようなのはあまり考えないことにする。
VirtualBox でハードウェアとなると USBデバイスを動かしたいこともあるのだが、そういうのは無しだ。


こちらがGet Startedページ。

Get Started with Docker | Docker
https://www.docker.com/get-started

Windowsで見ているので、Docker Desptop(for Windows)と Docker Hubが見えている。
Ubuntuで動かすときにはコマンドしか使ったことがないのだが、そうなると Linux Engineというのがそれになるのか?

image

Linux Engineのページに行くと、Fedora, CentOS, AWS, Azure, Ubuntu, Debianが一覧に出てきた。

Explore Docker's Container Image Repository | Docker Hub
https://hub.docker.com/search?offering=community&operating_system=linux&q=&type=edition

ちなみに Desktop は、Windows と Macが 1つずつだ。

Dockerの利用が企業規模によっては有料になったという記事を見たのだが、2021年8月31日のブログに書いてあるのは Docker Desktopについてだけのように見える。

Docker is Updating and Extending Our Product Subscriptions - Docker Blog
https://www.docker.com/blog/updating-product-subscriptions/

「No changes to Docker Engine」とも書いてあるので、Linux Engineを使うだけなら気にしなくてよいかもしれない。

 

ただ、Docker Hubというものについても今年アナウンスが出ていた記憶がある。
実のところ、Docker Hubのこともよく知らないのだが、Dockerのコンテナ?イメージ?を公開して置いておいたり、自分の開発しているものを置いておいたりできるようなものじゃなかったか。

ふだん、よく「dockerからコンテナを取ってきて」みたいな感じで、どこからか動かすものを取得しているのだけれども、あれは Docker Hubに公開してくれている誰かのリポジトリを使わせてもらっているのだと思っている。

とりあえず、1つアカウントを作ってみた。もちろん無料版だ。

image

このアンダーラインを引いたのが、Docker Hubに自分のリポジトリを持つことができる上限なのだろう。
「public」については unlimited としか書いていないが、設定を見るとこうなっていたので「private」は1つまでしか持てないのだろう。

image

お仕事で使うとなると、なんでもかんでも public にするわけにはいかんので、有料プランを選択するという選択もあるのだろう。


Dockerが、いや Docker社がここまで一般的に使用できる環境を提供し続けているというのは、非常に驚くべきことだと思っている。

デファクトスタンダードというか、dockerを利用しても環境縛りに思われないくらいの立ち位置にいるというのはすごい。
環境を作るということに特化しているのに「コンパイルはgccでやります」みたいなノリで「環境はdockerでつくります」になっているので、そのことに恐怖すら覚えるところだ。だいたい数年するといくつも同じようなサービスが立ち上がるものだけど、Dockerがこの立ち位置を維持できているのは不思議な気がする。

2021年2月7日

私はHTTPをよく知らない

「自分、組み込み屋ですので」でネットワークのことから逃げられる時代はもう終わった・・・。
いや、ずいぶん前に終わっていたのだが、認める勇気がなかったのだろう。

ネットワークは、socketを開いてlistenさせ、相手側はconnectすればあとはプロトコルで勝手にやればよい、くらいの知識で十分だったのだが、もうちょっと上の方も知らないと話に追いつけないのだ。

MQTTは実装したことがあるので調べ直せば思い出しそうだが、やはりHTTPをスルーはできまい。
というわけで、今回はHTTPについてさらっとまとめよう。


HTTPといえば、やはりブラウザだろう。
昔はMosaicというブラウザがあってね、SunのSS10で見せてもらったことが記憶に残っているよ。

HyperText Transfer Protocolの略で、HTTP。

https://ja.wikipedia.org/wiki/Hypertext_Transfer_Protocol
https://tools.ietf.org/html/rfc2616
https://developer.mozilla.org/ja/docs/Web/HTTP/Overview

3番目に載せたMozillaの説明がわかりやすそうだ。

  • クライアント・サーバプロトコル
  • クライアントのメッセージ:リクエスト
  • サーバのメッセージ:レスポンス
  • 直接クライアントとサーバが接続することもあるし、間にプロシキが挟まることもある。
  • プロトコルとしてはリクエスト間に関係性は無い(ステートレス)。ただCookieによってセッションという概念を持ち込むことはできる。

 

クライアントとサーバのフローはそれぞれメッセージを投げるだけなので、あとはメッセージの仕様を確認すればなんとなくHTTPがわかったと思ってよいのでは無かろうか。

HTTPメッセージ
https://developer.mozilla.org/ja/docs/Web/HTTP/Overview#http_messages

 

リンク先の画像ではちょっと分かりづらいかもしれんが、1行目はこう。

Method + (space) + Path + (space) + Version of the protocol

Pathは、たとえばcurlコマンドで "curl http://127.0.0.1/time" とアクセスしたとすると、

GET /time HTTP/1.1

となった。


むかし組み込みのHTTPサーバをデバッグしたこともあるし、デバッグ用にHTTPサーバをPocoライブラリ使って作業したことがあるのだが、暗号の処理と同じ感じ匂いがしている。
危ないので自分で実装するものじゃ無いよね、というやつだ。

ただ、自分で実装しない=自分で知っておく必要はない、ではないのだ。まあ万人が知っておく必要があるというものでもないとは思うが、知っていると楽しいと思うのだよ。

2020年11月1日

JWT

JWTという形式のデータが現れた。
現れたといっても、仕事の中で言葉が出てきたというだけだが。

JWTのJはJSONのJ、ということがわかって満足していたのだが、どうもちゃんと調べないとダメらしい。
ちっ。


JSON Web Tokenの略で、JWT。

JSON Web Tokens - jwt.io
https://jwt.io/

RFC 7519にもなっているので、私が知らなかっただけで巷では有名なのかもしれん。
AbstractだけDeepLで翻訳しておこう。

JSON Web Token (JWT) は、2 つの当事者間で転送されるクレームを表現するためのコンパクトで URL 安全な手段です。JWTのクレームは、JSON Web Signature(JWS)構造体のペイロードとして、またはJSON Web Encryption(JWE)構造体の平文として使用されるJSONオブジェクトとしてエンコードされ、クレームがデジタル署名されるか、メッセージ認証コード(MAC)で保護されるか、および/または暗号化されることを可能にする。

クレームというと、お店に文句を付けるというネガティブなイメージになるのだが、”claim”という単語としては「主張、要求、権利、資格」のようなもので、苦情っぽい要素はない。
和製英語だったら別に構わないのだけど、日本語で使うときに意味が変わるというのは止めてほしいな・・・。

 

JSON-RPCのリクエストみたいな使い方をするのだろうか?
使い道は後で調べるとして、まずは文法というかフォーマットを知っておきたい。


RFCは長いのでまだ読みたくない・・・ので、導入のドキュメントを見ておく。
無料のEBOOKもあるようなのだが、メールアドレスだけじゃなくてCompanyとかの入力もあったので、面倒なのでやめた。

JSON Web Token Introduction - jwt.io
https://jwt.io/introduction/

Introductionでは署名するタイプのJWTに焦点を当てるそうだ。まあ、単にJSONをJWTのフォーマットにするだけだとありがたみがあまり無いのだろう。

JWTの使いどころは、AuthorizationとInformation Exchange。承認と情報交換。
他にもあるのだろうけど、まあよかろう。

 

そしてフォーマット。

<Header>.<Payload>.<Signature>と、3つの部分をドットで区切ってあるそうだ。

Header

Base64Urlでエンコードされる。
エンコードするのはJSONデータで、JSONのところは署名アルゴリズムとトークンのタイプを載せる想定のようだ。他にitemがあってもよいのだろうが、”alg”と”typ”はたぶん必須だ。

”typ”は”JWT”でよいとして、”alg”は何が使えるかというと、RFC-7518の3.1章にJWSの”alg”ヘッダパラメータの一覧があった。
サンプルは“HS256”となっているが、これはHMAC SHA-256のことのようだ。

https://www.rfc-editor.org/rfc/rfc7518.html#section-3.1

 

そういえばHMACのしくみをよく知らない。
いつも使う署名といえば、署名する人が秘密鍵を持っていて、データをSHA256のようなハッシュ関数でハッシュ値を求め、それに対して署名計算している。データ長を気にせず署名できる計算があるのかどうか知らないが、私が知っているタイプはデータ長が決まっているので事前にハッシュ計算してデータ長を整えるのだ。

 

HMACの場合は共通鍵のようだ。
じゃあ、JWTというかJWSというか、署名がついてHMACの場合には事前に鍵を決めるというシーケンスがいるのだろうか?
それはそれで面倒だ。

ただ、署名は無視することもできる。
送っている方が「俺だよ、おれおれ」っていう証明を付けてきて、確かに俺さんの秘密鍵を持っていないと作ることが難しいデータを付加しているだけなので、その人が受信した人が思っている俺さんかどうかわからないし、そのデータが正常かどうかなどというのはさらに関係が無い。

“HS256”だけはRequiredなので、これだけはどの環境でも使えないとRFC-7518を名乗ることができないはずだ。

 

Payload

ここにはclaimが入っている。claimには3タイプある。

  • Registered claims
  • Public claims
  • Private claims

PayloadもBase64Urlでエンコードされている。

 

 

Signature

HeaderとPayloadをまとめて署名する。
HMAC-SHA256の例が載っているのがわかりやすかろう。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

かなり省略した。。。すまぬ。
HMACのときにどうやって署名検証するかが気になって、残りがどうでも良くなってきたのだ。

JSON Web Token(JWT)の紹介とYahoo! JAPANにおけるJWTの活用 - Yahoo! JAPAN Tech Blog
https://techblog.yahoo.co.jp/advent-calendar-2017/jwt/

ここを読むと、”kid”というところで公開鍵を参照したりするそうだ。
しかし、HMACって共通鍵だから・・・?

 

まあ、今回は概要を知りたかっただけなので、これでよしとしよう。
あとは会社で詳しい人に聞くとするかね。

2020年8月17日

[js][ts]node.d.tsはローカルにインストールした場合だけなのか

TypeScriptの勉強中で、tscでコンパイルするのと、型がある、くらいのところまで把握した。

 

今日は、これ。

グローバル変数の宣言 - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/type-system/intro/variables

ここに、node.d.tsがあればprocessを使うのに何もしなくていい、みたいなことが書かれている。
なので私はこうしていた。

npm i @types/node -g

プロジェクト作成のところにも書いてあったし、tscは勉強中はグローバルにインストールしておいた方がわかりやすかったので、@types/nodeも-gでインストールしたのだ。

 

しかし、これだとprocessが見えなくてね。
なにかimportみたいなことがいるのかと思って調べたのだが、node.d.tsをどうやって使うのか見つけられなかった。

そういえば、以前グローバルにインストールすると使えないものがあったことを思いだし、-gなしでインストールした。
うん、そうするとコードとしては何も書かずにprocessが使えるようになった。

 

こういうのが、まだよくわかってないのだな。。

2020年8月15日

[js]typescript (3)

さて、夏休みのTypeScript勉強の3回目だ。

 


プロジェクトの環境設定

プロジェクトの環境設定
https://typescript-jp.gitbook.io/deep-dive/project

宣言空間

TypeScriptには型宣言空間(type declaration space)と変数宣言空間(variable declaration space)がある、と。

「型アノテーションとして使用できるものが含まれている」と書いてあるのだが、そもそも型アノテーションがわからん。
調べた感じからすると、変数宣言で型指定するあれのことをそう呼んでいるようである。TypeScriptだと変数宣言の後ろにコロンと型名を書くやつだ。
あってもなくても動作するからアノテーションなのかな?

 

型はまあいいとして、変数の方がよくわからん。
変数の宣言は、まあいい。型宣言したものも全部ではないが変数として扱えるらしい。たぶん右辺値だけだとは思うが。。。どうだろう。。。

varだけ書いてあるが、letやconstも変数宣言空間にいるんだろうか?
いてほしいな。

 

モジュール

オリジナルだと”Modules”で、項目も”Global Module”と”File Module”だった。後からオリジナルが更新されたのかもしれん。

ともかく、ファイルの中に普通に宣言するとグローバル扱いになって、別ファイルだろうと名前が衝突する可能性がある。importとかしなくてもいいのか?

ともかく、極力ファイルモジュールにしましょう、ということだった。
external moduleとも呼ぶそうだ。

そのファイルモジュールだが、TypeScriptファイルにimportかexportがあるだけでローカルスコープになると書いてある。
本当か?

 

いろいろ疑わしくなったので試そう。

まず、mod1.tsにfooを、mod2.tsにfooを代入するbarを用意し、tscしてnodeでmod2.tsを実行。
・・・うん、bar is not definedでエラーになるね。

そういえば、そもそもグローバルにいるとかいっていても、mod1.tsとmod2.tsをコンパイルしてリンクするわけでもないので、何かしらプロジェクトみたいな概念があるはずだ。

先にそっちを整えないとTypeScriptの勉強にならんな。
3回目にしてようやくそういう課題が見つかるとは。。。


Node.js & TypeScriptのプロジェクト作成

Node.js & TypeScriptのプロジェクト作成 - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/nodejs

npmを使うようだ。どこかでnpmは略語じゃないと書いてあったが、ここには関係ない話だ。

tscはプロジェクトでインストールするようだ。まあ、これは-gでもよかろうが、バージョンで挙動が変わるかもしれんので安全に行くならローカルか。
そのうち、nvmみたいにバージョン管理されるようになるんだろうか。

@types/nodeというのもインストールする。これは知らなかった。
node.d.tsらしいが、importして使うのだろうか?

そしてtsconfig.jsonももちろんいる。
パラメータを見ると、srcとlibになっている。ここら辺はNodeのお作法なのかな。

package.json の”scripts”にコマンドを書いておくと、 npm runでコマンドが実行できるようだ。
npm のお作法なんだろうけど、そういうのを私は知らんのだ。
取りあえず”build”に”tsc –p .”を書いておくと、npm run buildでビルドできる。


モジュール再び

さて、これでビルドはできるようになったが、今は他のモジュールも使っていないので手動でコンパイルするのとほとんど変わらん。

Node.jsには globalというオブジェクトもあるらしい。

https://nodejs.org/docs/latest-v12.x/api/globals.html#globals_global

Node.jsではそのモジュール内でのローカルになると書いてあるので、他のファイルにあってもグローバルだから見える、というのはブラウザでの話だろうか?

global.fooなどとしても globalは見つからないと言われるから、うん、そういうものだと思い込もう。

 

コンパイルしてリンクする言語じゃないから、1ファイル内で完結しないといかんのかな。
1ファイルといっても、そこからimportしていくだけだろうけど。

 

ファイルモジュールの詳細

ファイルモジュールの詳細 - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/project/modules/external-modules

公開する方は、 exportを付けておけばよい。
その付け方がいくつかあるので混乱してしまうのだが、どれか1種類だけ覚えておいて、自分が使うときはそれを、他の人が違うのを使っていたらツールに任せる、くらいでいいか。

問題はimportだ。
なんかいろいろ書き方があるのだが、これからやるんだったらES module syntaxというやつだけにしましょう、ということか。

ES module syntaxは、exportされたものをimportするときに fromを使うやつだと思っている。
そうじゃないやつはCommonJS syntaxとかいう呼び方で、exportされたものをimportするときにrequire()を使う。
require()の方が良く使われていたし、使い方もシンプルだったのでそうしていたのだが、どうも時代はfromを使うタイプのようだ。

試しにNode.jsでfromを使ってみたらエラーになって、そういえば拡張子を.mjsにしないと動かないんだった、というのを思い出した。


 

力尽きました・・・。

さすがに3回でたどり着くのは無理でした。
まあ、環境の作り方が分かったのと、前提となるJavaScriptの知識が足りてないのがわかったから、じわじわやりますかね。

2020年8月14日

[js] typescript (2)

では、2回目はJavaScriptについてだ。
TypeScriptというタイトルなのに・・・なのだけど、TypeScript全開で書いたコードはNode.jsでは動かせないけれども、JavaScript全開で書いたものはTypeScriptでも有効なのだから、まずはJavaScriptのところを見ておかねばなるまい。

といっても、JavaScriptを全部やるわけではなく、TypeScriptとの比較という意味でそういう章立てになってるのだろう。


JavaScript

JavaScript - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/recap

等価演算子の同一

null/undefinedチェック以外は===使っておけ、ということか。
自動型変換は気付きにくいのでやっかいだな。

 

Object型については、===で比較できるのは同一のアドレスを指しているかどうかだけのようだ。
むかしC言語の話で「C言語には値渡ししかない」と説明されて、ポインタもアドレス値を渡しているだけだから値渡ししかないのだ、と納得したのだが、そんな感じだな(なにがだ)。

 

nullとundefined

nullとundefinedについては、基本的に==でnullをチェックすれば良い。
==nullだと==undefinedもチェックできるからだ。
ただし、その変数がそもそも定義されていないという場合には、nullだろうとundefinedだろうと比較したら例外が発生するので、もしそんなことをしたいならtypeofを使う。

むしろ、じゃあどこでundefinedを気にする使い方をするのかというと、構造体とか戻り値とかだそうだ。
戻り値でundefinedを使うくらいなら、golangのようにerrを使うのがよいのか。型アノテーションというのがそういうやつなのかな。

 

JSONのシリアライズは、JSON仕様にnullはあるけどundefinedはないので、nullだったらメンバになるけどundefinedは除外されるらしい。ほほぅ。

まとめとして、TypeScriptのコーディングガイドラインとしてはnullを使わずundefinedだけにするのが推奨らしい。
まあ、変数定義後のデフォルト値がundefinedだから、わざわざnullを突っ込む必要はなかろうということか。
ともかく、==nullでチェックしてる分にはいいんじゃないの、ということだった。

 

クロージャ

聞くね、クロージャという言葉。いくつかの言語で出てきたと思う。

クロージャ - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures

「クロージャは、関数と、その関数が宣言されたレキシカル環境の組み合わせです」。既に意味が分からん。

うーん・・・C++のtemplateみたいな使い方ができる?というほどtemplateにも詳しくないのだが、可変にできそうなところを可変のままにして関数の枠を作っておき、そこに値を与えて新たな関数として使えるようにしてしまう、という感じなんじゃなかろうか。新たな関数にするのも1段だけじゃなく、その新しい関数にパラメータを与えてさらに新しい関数を作る、みたいなこともできるようだ。

まあ・・・使い道が思いつくまでは放置だな。

 

 

Number型

この前かった「開眼!JavaScript」にも書いてあったが、JavaScriptにはネイティブなオブジェクトもあるし、プリミティブ型ももちろんあるのだった。
数値に関してはNumberしかない。

で、Numberっていったいどういうものかと思っていたのだが、倍精度の64bitらしい。double相当か。
最近の言語だからか、2進数による誤差や64bitを超える値を扱うことが考慮されているようで、big.jsというものがあるそうだ。golangのbigみたいなもんか。C言語にはなかったのだけど、最近の動向は調べてないなぁ。

あとは、NaN、無限大、無限小の定義があるらしい。
NaNは、他の言語だとエラーだったり例外だったりするやつか。例では√-1が挙げられていた。
無限大と無限小は、この範囲を超えたら無限になるという閾値ね。外れるとInfinity(あるいは-Infinity)になる。

 

Truthy

「Trueと評価される値」だそうな。反対語はfalsyね。
ちょっとC言語っぽく、0以外がtruthyだ。空文字列でなければtruthyだし、nullやundefinedでなければtruthy。

でもやっぱりbooleanっぽいのがいいよねー、という場合は、これもCっぽく!!が使えるそうだ。
まあ、!が使えるなら!!も使えるわな。
個人的には!を使う時点でtruthyを認めてるんだから!!しなくていいんじゃないの、とも思うのだけど、全体的にそうコーディングしておくと静的解析ツールなんかで間違いを検出しやすいんじゃなかろうか。最初booleanだったものがnumberに変更になったのを忘れてたとか。


モダンなJavaScriptの機能

モダンなJavaScriptの機能 - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/future-javascript

 

classes

確かに本で出てこなかったな、class。
JavaScriptのObjectでも同じようなことはできた・・・というか、できないとTypeScriptでも実現できないんだった。

 

アロー関数

アロー関数はthisの束縛だっけ、あれがコールバックと違うということだったと思うのだが、今やサポートしていないのはIEだけらしいから全環境で使えるという扱いでいいのかな。

あまり理解はできていないのだが、実際にアロー関数じゃないとダメだった場合に遭遇してしまったので、嫌でも覚えないといかんだろうという気がしている。

 

let, const

varじゃなくてlet使っておけばいいと思う。
constも使える。
ブロックスコープじゃないのがあるというのは思ってもなかった。

 

分割代入, スプレッド演算子

困るのがこういうやつなのよ。。。
こういう記法があるんだろうということはコードを読んでいてわかるのだけど、キーワードが分からなくて検索できないのだ。

「スプレッド演算子」といっても、そういう演算子があるわけでもなさそうだし。
検索すると「スプレッド構文」が出てくるのだが、ここではSpread Operatorになっているから誤訳ではなさそうだ。

 

for...of

golangだとrange戻り値の1番目がindex、2番目が要素だったっけ。
for...inがindexを返すやつで、for...ofが要素を返すやつ、ということか。

最近、forでぐるぐる回すのを書いた気がするが、for...ofを知っていればすっきりしただろうに。というか、知ってる人がコード見たら「この人for...of知らんのか」って思うんだろうな。

 

テンプレートリテラル

知ってしまえば「文字列をシングルやダブルで囲む代わりにバッククォートで囲むやつ」なのだけど、あの書き方とこの名前は結びつかないな。今も出てこない。

まあ、便利になるだけで、今の書き方でもなんとかなるから、思い出せたら使うくらいでいいだろう。

 

Promise

わしの若い頃はPromiseがなくてのぅ、と言いたくなったが、単に知らんかっただけだろう。
最近はPromiseを使うところではasync-awaitで事足りることが多かったのだが、あれはあれでPromiseの違う運用みたいなものだったと思う。

ともかく、ここの例になっている非同期とtry-catchのところを読んだだけでも、自分で正しく実装できる自信がない。

 

ジェネレータ

これもコードを見ても名前が思いつかないパターンだ。幸い「function*」が検索できるのでセーフだが。

イテレータの作成をするもので、関数ポインタではない。


 

疲れた・・・。

TypeScriptだったら、みたいなのがほとんど出てこなかった。
3回だけブログにするつもりだったので、あと1回なのだが、はてさて何をやるべきかね。