kitslog

何かを書く

4月 学び

今はRemix/React, GoでAPIで画面の動きとAPIだけを作り続けています。(個人開発)

UIコンポーネントは何を採用するかとても悩んでいるけど、ここに時間を割くのは今は無駄だと感じているので切り捨てて完全ノースタイルで文章構造と複数個のCSSだけで機能を成り立たせている。コンポーネント設計はむしろノーデザインのほうが良いんじゃないかと思い始めてきた。

自分の中で面白い試みとして、自分一人しか作ってないのでE2Eと単体テストをほぼ捨てて、「今までの知識と経験を使っていかに設計しやすくできるかチャレンジ」とか今の常識ではありえない無法チャレンジをしてる。こういうのは個人・趣味開発ならではだ。別にコードを公開するつもりも現状はないので、こういうのも好き勝手にやっている。

こういった趣味開発みたいなやつは過去にも何個か挑戦してみたりしたけど、自分がユーザーになれないサービスを作っててもモチベーションの維持も出来ないし単純に面白くないので、これがあったらイイな〜というアイデアベースで雑に作っている。あとは、最近インフラ関係の学びが薄いな〜と思っているので、お金は掛かってしまうが自分でドメインを取得してサービスインするところまではなんとかモチベーションを保ちたいと思っている。

がんばれ明日の自分

進捗はこんなかんじです。洗い出したユーザーストーリーは全体でいうと3分の2ぐらい

進捗

いま書いてるOSS `edinet-go` の紹介

いくつか個人開発、みたいな文脈でプロジェクトをやってきたんだけど、大体モチベーションがいつの間にか霧散してる。

機能が実装されていなかったら、Contributeしてくれればいいんだ。責任も何もない開発っていうのは楽です。そういうレベルの開発をしています。まぁそういうもんだと思ってます。

今書いてるコードは、edinet-go ってやつです。リポジトリ自体はとても昔に作っていたんだけど、モチベが霧散して途中で開発しなくなってたやつです。

edinetについてはあとで触れるけど結構前にedinet apiというのがv2になってなんかほんの少し変わったので久々に触ってみようかなという思いで開発を再開しました。

github.com

edinet-goは何か軽く触れておきます。

EDINETは、金融商品取引法上で開示用電子情報処理組織と呼ばれる、内閣府の使用するホストコンピューター・提出会社の使用するコンピューター・金融商品取引所のコンピューターを結んだ、同法に基づく開示文書に関する電子情報開示システムである。

いわゆるEDINETというシステムがあって、そのシステムにはEDINET APIというのが提供されているんだけど、使い勝手がまぁ微妙です。

開示文書をとある形式で取得したりするんだけど、取得するためのドキュメントIDは一覧から一度取ってこないといけないが、そしてそのドキュメントIDって特定の日付で取得した一覧からしか得られないのです。

EDINET APIの基本的な利用方法はこんな感じ

出典:金融庁 EDINET API仕様書 (version 2)

簡単に思いつくニーズとして XXXX という企業名の有価証券報告書XBRLの形式で取得したい。といったニーズには現状のEDINET APIで実現させようとするとかなりの手間になるわけです。

これを実際にEDINET APIのみでやろうとすると、日付を少しずつ変えて(もしくは1年間とか長い期間の日付をリスト化して)APIをめちゃくちゃ叩いて企業として持つユニークなEDINET CODE(や法人番号)が XXXX なデータでフィルタリングして、有価証券報告書以外の文書IDを省いて、有価証券報告書の文書IDを抜き出して、別のエンドポイントにアクセスして有価証券報告書をダウンロードしろ、と言ってるわけです。正気か?誰に聞いて作ったんだこのシステム。

ということでこのEDINET APIをWrapしたAPIを作ってしまおうというのが最初のモチベです。 EDINET-APIをWrapする上では、基本機能としてEDINET-APIの使用感はRAW機能として残しつつ、 取得周りのニーズを満たせるようにしていこうかなというところです。いずれ自社に買い取ってもらうんだ、このOSS。という欲望も抱えてます。

取得されたデータ自体の活用や分析はスコープ外になるのでEDINETで検索してきてこの記事がヒットしてしまっていたら申し訳ないけど諦めてお帰りください

2023年もうすぐ終わり

お久しぶりです。

色々あって長い期間休職しており、プログラミングの世界から隔離していました。

12/1から短時間で復職することになりました。長いこと働いていないので働き始めて体調がどうなるかはまだわかりません。

復帰前の少しの期間、GitHubを整理したり少しだけコードを書くなどしています。

今後も体調には気をつけて生きていきたいと思います。

2021年の活動(2021/11~2021/12)

2021/11~2021/12

Headless CMSのSleepy Hollowは途中でコミットしなくなってしまった。モチベ超絶ダウン中。

プライベート趣味が忙しくなり、技術にプライベートで触れる機会が著しく減った。

業務内外で触れている技術とか他

  • 11月: Elixir, Clojure, Spring Boot, Gauge
  • 12月: Clojure, Spring Boot, Gauge

2021年の活動(2021/8~2021/10)

2021/08~2021/09

Headless CMSのSleepy Hollowに時間があるだけコミット

github.com

業務内外で触れている技術とか他

  • 8月: Go, Kotlin, Spring Boot(WebFlux), Spring Security, Gauge
  • 9月: Go, Kotlin, Spring Boot(WebFlux), Spring Security, Gauge
  • 10月: Go, Kotlin, Spring Boot(WebFlux), Spring Security, Gauge

  • 11月(予定): Elixir

2021年の活動(2021/4~2021/7)

2021/04/27:「TDD実践を経て変わったこと」で発表。ペアプロならぬペア登壇した

イベントレポート: zine.qiita.com

発表資料:

speakerdeck.com

2021/04 ~ 2021/07 触っている技術

  • Angular Dart
  • Kotlin

2021/07 Headless-CMSを作りたくてorganizationを含めて以下を立ち上げた github.com

AngularDartでビルド時にクエリ付与をしてjsファイルをキャッシュさせないライブラリ built_html

こんにちは、@tkitsunaiです。

Angular Dartに最近ハマってます。

静的ファイルとして配布した後は、index.htmlなどに含まれるjsファイルはブラウザ側にキャッシュされてしまいます。

基本的にはキャッシュは有効にさせつつも、新しいバージョンの静的ファイルを確実に反映されてほしいと思います。

webpackでいうhtml-loaderのようなアレです。なかったら作ろうかと思ってましたが、https://pub.dev/でそんな感じのライブラリを探したらちょうど良さそうなものを見つけたので共有

pub.dev

使い方

とても簡単。

index.htmlindex.template.htmlにリネームし、キャッシュ対策のクエリを付与したい場所に差し込みます

<!DOCTYPE html>
<html>
  <head>
    <title>TITLE</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css?q={{digest styles.css}}">
    <link rel="icon" type="image/png" href="favicon.png">
    <base href="/">
    <script defer src="main.dart.js?q={{digest main.dart.js}}"></script>
  </head>
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

digestで指定したファイルstyles.cssmain.dart.jsをビルド時のハッシュ値で付与するようです。

そんで webdev build

出来上がったbuild/index.htmlを確認します。

<!DOCTYPE html>
<html>
  <head>
    <title>TITLE</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css?q=90f3bcadc1c9712de722965fd869e4b9">
    <link rel="icon" type="image/png" href="favicon.png">
    <base href="/">
    <script defer src="main.dart.js?q=1c6ec810980a87a9a73bd0972a8396fe"></script>
  </head>
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

付与されてますね。

ハッシュ値、ということなのでmain.dartの中身だけ変更してみます。

void main() {
  runApp(ng.AppComponentNgFactory);
}

一行だけ追加

void main() {
  print("hello digest");
  runApp(ng.AppComponentNgFactory);
}

もう一回 webdev buildをしてbuild/index.htmlを確認します。

<!DOCTYPE html>
<html>
  <head>
    <title>TITLE</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css?q=90f3bcadc1c9712de722965fd869e4b9">
    <link rel="icon" type="image/png" href="favicon.png">
    <base href="/">
    <script defer src="main.dart.js?q=db301b802bec6119391b6a794be6d53f"></script>
  </head>
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

ちゃんと変わっています。styleのほうは変えていないので、そのままとなります。

うむ、期待通りです。使えそう。

Go 1.13beta1 のerrorsの挙動をテストする

こんにちは、@tkitsunaiです

Go 1.13beta1がリリースされていたので、errors周りについて色々挙動をテストしてみました

Go 1.13beta1のダウンロードはここからできます -> Downloads - The Go Programming Language

リリースノートはこちら -> Go 1.13 Release Notes - The Go Programming Language

この記事のコードを直接見る場合はGitHubへどうぞ

github.com

テストのAssertionにgithub.com/stretchr/testifyを利用しています

基本:新しくエラーを作る

err := errors.New("it is error")
assert.EqualError(t, err, "it is error")

結果

=== RUN   Testエラーを新しく作成する
--- PASS: Testエラーを新しく作成する (0.00s)

エラーをWrapする

err1 := errors.New("this is error 1")
wrapError := fmt.Errorf("wrap error: %w", err1)
assert.EqualError(t, wrapError, "wrap error: this is error 1")
assert.Equal(t, false, wrapError == err1)

結果

=== RUN   Testエラーをエラーで包み込む
--- PASS: Testエラーをエラーで包み込む (0.00s)

注意しなければならないのは、: %wでWrapするという点です

エラーの等値性をチェックをする

新機能のerrors.Is関数を使います。

err1 := errors.New("this is error 1")
assert.True(t, errors.Is(err1, err1))
assert.True(t, err1 == err1)

結果

=== RUN   Testエラーの値を同値かチェックする_生成された同じエラーを使う
--- PASS: Testエラーの値を同値かチェックする_生成された同じエラーを使う (0.00s)

同じインスタンスを使っているので、等値。Isを使わない場合の動作も従来通り等値。

別のインスタンスでIsを使う場合

err1 := errors.New("this is error 1")
err2 := errors.New("this is error 1")
assert.Equal(t, false, errors.Is(err1, err2))

結果

=== RUN   Testエラーの値を同値かチェックする_エラーのテキストは同じ
--- PASS: Testエラーの値を同値かチェックする_エラーのテキストは同じ (0.00s)

もちろんインスタンスが違うので、等値ではない

Wrapされたエラーに対してIsでチェックする

今回の目玉

err := errors.New("this is error")
wrappedError := fmt.Errorf("wrap error: %w", err)
assert.True(t, errors.Is(wrappedError, err))

結果

=== RUN   Test包まれた最初のエラーであることを同じかを判定する
--- PASS: Test包まれた最初のエラーであることを同じかを判定する (0.00s)

Isを使うことで、WrapされたwrappedErrorがerrであることが確認できました

良いですね。

では何回もWrapするとどうでしょうか

err := errors.New("this is error")
wrappedError1 := fmt.Errorf("one wrap: %w", err)
wrappedError2 := fmt.Errorf("two wrap: %w", wrappedError1)
assert.True(t, errors.Is(wrappedError2, err))

結果

=== RUN   Test包まれた最初のエラーであること同じかを判定する_何回も包む
--- PASS: Test包まれた最初のエラーであること同じかを判定する_何回も包む (0.00s)

これも、Isを使うと複数回包まれたwrappedError2がerrであることが確認できました

正しくWrapできなかった場合

Wrapするには、%w でフォーマットしてあげる必要がありました

%w以外でWrapしたつもりになったらどうでしょうか

err := errors.New("error")
wrappedError := fmt.Errorf("wrap error: %s", err)
assert.Equal(t, false, errors.Is(wrappedError, err))

例えば%sで包みます

結果

=== RUN   Testエラーを包む時にフォーマットしない場合は同一性が担保できない
--- PASS: Testエラーを包む時にフォーマットしない場合は同一性が担保できない (0.00s)

等値ではないことが確認できました

errors.Asを使って、エラー型を変換する

As関数も新機能です

独自エラー型を使う場合やエラーそのものを取り扱いたい場合にはAsが使えそうです

type NotFoundError struct {
    Err   string
    Cause string
}

func (n NotFoundError) Error() string {
    return fmt.Sprintf("%s: %v", n.Err, n.Cause)
}

func FindHoge() error {
    return &NotFoundError{
        Err:   "not found error",
        Cause: "i dont know",
    }
}

func Testエラーに対しAsを利用して指定したエラー型に変換する(t *testing.T) {
    if err := FindHoge(); err != nil {
        var notFoundErr *NotFoundError
        if errors.As(err, &notFoundErr) {
            assert.Equal(t, "i dont know", notFoundErr.Cause)
            return
        }
    }
    t.Fail()
}

FindHoge()を実行した結果、返ってきたerrorインターフェースがNotFoundErrorであるか。

もしNotFoundErrorに変換できていれば、bool値でtrueが返ってくる。

結果

=== RUN   Testエラーに対しAsを利用して指定したエラー型に変換する
--- PASS: Testエラーに対しAsを利用して指定したエラー型に変換する (0.00s)

期待通りです

では、変換できない場合

type UnknownError struct {
    Err string
}

func (s UnknownError) Error() string {
    return s.Err
}

func FindSomething() error {
    return &UnknownError{
        Err: "unknown error",
    }
}

func Testエラーに対しAsを利用して指定したエラー型に変換したけど変換できなかった(t *testing.T) {
    err := FindSomething()
    var notFoundErr *NotFoundError
    assert.Equal(t, false, errors.As(err, &notFoundErr))
    assert.Nil(t, notFoundErr)
}

FindHogeで返ってくるのはUnknownError型のため、NotFoundErrorに変換できません

結果

=== RUN   Testエラーに対しAsを利用して指定したエラー型に変換したけど変換できなかった
--- PASS: Testエラーに対しAsを利用して指定したエラー型に変換したけど変換できなかった (0.00s)

これも期待通りです。notFoundErrをポインタにしておかないと、zero value structとなるので注意が必要です

WrapされたエラーをAsで変換する

func TestWrapされたエラーに対しAsを利用して指定したエラー型に変換する(t *testing.T) {
    err := FindHoge()
    wrapError := fmt.Errorf("wrapping error: %w", err)
    var notFoundErr *NotFoundError
    if errors.As(wrapError, &notFoundErr) {
        assert.NotNil(t, notFoundErr)
        assert.EqualError(t, notFoundErr, "not found error: i dont know")
        return
    }
    t.Fail()
}

Wrapした場合、Asでちゃんと変換できるか?

結果

=== RUN   TestWrapされたエラーに対しAsを利用して指定したエラー型に変換する
--- PASS: TestWrapされたエラーに対しAsを利用して指定したエラー型に変換する (0.00s)

大丈夫ですね。

まとめ

全体的に問題なく使えるのではないでしょうか。正式リリースが楽しみです。

正式リリースはまだ先なのでそれまではxerrorsで代用しましょう。