2022年6月29日水曜日

yogiboのリペアサービスが感動的だった

無意味ではなかった。

6/1 - 6/19までyogiboはリペアサービスのセールをしていたので家にあるyogiboを出してみることにしました。




発送前のyogiboですが、もう4年くらい使っていてへたり過ぎてしまってます。本来はこの3倍くらいあったはず。中のビーズも2回くらい継ぎ足したけど、もう手の施しようがなかった。



で、発送から8日で帰ってきました。

えっっっっっ

でっっっかっっっ!?

どうしちゃったわけ?

もう1つのyogibo(1年前に購入。ビーズ継ぎ足しなし)と比べてこの大きさ。
しかも新品のかほりがする。

yogiboは毎日使うので満足。
(リペアサービスは送料が高いのでそれだけが欠点)

次は赤いyogiboにビーズを継ぎ足さなきゃ。

と思っていたら





やるじゃない。

2022年3月15日火曜日

久しぶりにLT登壇してきた (開発 x テスト LT会 vol.2)

最後にLT会に出たのは前職のときに豊洲で発表したとき以来ですね。

「開発 x テスト LT会 vol.2」

今回は個人開発でテストを書いていたので、その時の楽しみと大変だったことを題材にLTしてきました。

個人開発でテストをする人はあまりいないかと思ったので、勉強してみたい、やってみたいと思ってもらえたら幸いです。



他の方の発表も、テストを並列にしたり、機械学習で落ちやすいテストを先にやったりと開発時間を短縮するアイデアが出てて興味深かったです。

明日もLT会があるみたいです。すごい。


個人開発のプロダクトを早くリリースできるようにがんばります。



2021年9月29日水曜日

Raspberry Piで自宅用音楽ストリーミングサーバーを作った

Raspberry PiにLibreELECというOSを載せて自宅用の音楽ストリーミングサーバーを作りました。ブラウザで開けばサーバーに用意した楽曲をストリーミングで聞くことができます。

発端


2月にリリースしたウマ娘にハマって以来、特典がついてくるウマ娘CDたちが聞けていません。
それだけでなく、妻の持っているアイマスのCDも聞けていません。ライブにも行ってるのに。

妻はYoutube Musicで手持ちの曲をアップロードして管理しているようですが、他人にはシェアできない仕組みのようです。おそらく私的用途から外れるからでしょうか。

自宅にメディアサーバーを作れば、家で気軽に好きなだけ流せそうです。
ちょうど、家には昔買って放置したRaspberry Piがあります。これでメディアサーバーを作ります。

OSのインストール


手元にあるRaspberry Pi 2 B+にメディア用OSを入れます。

Raspberry PiにはいくつかOSの種類があります。
  • 汎用OS
    • Raspberry Pi OS
    • Ubuntu
  • ゲーム用OS
    • RetroPie
    • Recalbox
  • メディア用OS
    • LibreELEC(今回使ったOS)
    • OSMC
  • その他!
Raspberry Pi Imagerを使えば簡単にSDカードにイメージを焼くことができます。
それだけでなく、32GB以上のSDカードのフォーマットをFAT32にしてくれる機能や、自前で用意したイメージを焼いてくれる機能もあります。めちゃくちゃ便利。

ただ、今回はいくらやってもImagerでLibreELECやOSMCのイメージを焼くことができなかった!(これは、なぜか分からないです...。いくつかのディストリビューションで試してもRaspberry Pi OSしか入れられなかった...。)

悲しいですが、昔懐かしいNOOBSを使いました。NOOBSを使ったOSインストールでは、一旦NOOBSをSDカードに入れて起動し、起動後にインストールするOSを選んでインストールします。ちなみに公式ではNOOBSは非推奨のようです。

NOOBSのイメージは下のQiitaの記事を参考にしました。

今回はNOOBSに入っていたLibreELECを入れました。
ちなみにLibreELECとOSMCでどちらを入れるか迷いましたが、そこまで変わらないようです。後述するKodiさえ入れられれば問題ないと思って今回はインストール可能だったLibreELECを入れています。


セットアップ


LibreELECは基本的にKodiをメインで起動します。他にもAdd-Onを追加する形でゲーム機能を追加したりできます。僕の用途は音楽ストリーミングだけなので、ファイルアップロード用にSambaとSSH、Web Interfaceを有効にしました。

基本は下のサイトを見て設定しました。

ストリーミングで楽曲を聞く


Sambaでファイル共有ができるので、手元のPCから音楽ソースに追加されているディレクトリにコピーします。

追加しただけではKodiのWeb Interfaceからはストリーミング配信できません。

右下の三点ボタンから「Scan Audio Library」を押すとMusicに並びます。



アルバムを押すとアルバムの楽曲画面に移ります。
Kodiは右の画面に「Kodi」と「Local」というパネルがあります。
「Kodi」では本体から流れる音楽、つまりRaspberry Piから流れる音楽を表しています。
「Local」はWeb Interface上から流れる音楽を表しています。ストリーミング配信では「Local」を選びます。
アルバムにある「Stream」のボタンを押せば「Local」のプレイリストにすべての楽曲が入り、再生開始します。

茜ちゃんがかわいい of the world


これで自宅内のどこでも音楽を楽しめます!

展望


いくつかやり残しがあります。
  • 楽曲自動バックアップ
    • HDDを繋いでCronで自動的にバックアップさせれば良さそうです。
  • Raspberry Piに光学ドライブを繋いでリップさせる
    • これは試したけどできなかったです。新しいRaspberry Piならできるかも?
  • Amazon Alexaから楽曲再生
    • 夢です。やり方はわかりません。教えて下さい。
というわけで、余っているラズパイを有効活用した話でした。

そして再度放置されるラズパイであった...


2021年3月25日木曜日

Vueのrefを通してリアクティブの雰囲気を理解する

Vueのリアクティブ要素にはrefとreactiveがあります。

たとえばrefをつかったリアクティブの場合、


const count = ref(0);
watchEffect(() => console.log(count.value)); // -> 0
count.value = 1; // -> 1
 

countが変更されるたびにwatchEffect()に登録した関数が実行されます。

countが変更されたらconsole.log()が呼び出される仕組みがわからないので理解していきます。


const count = ref(0)

countにはref(0)が代入されています。このrefはproxyです。

proxyは値とHandlerを持っています。ref(0)だと0が値です。ではHandlerは?

Handlerはrefの中に隠蔽されています。下のコードのような感じです。

Handlerはgetterとsetterを持っています。

getterはtrack()を、setterはtrigger()を持っています。

このtrack()とtrigger()は何をしているのでしょうか?



const dinner = { meal: 'tacos' }
const handler = {
  get(target, prop, receiver) {
    track(target, prop)
    return Reflect.get(...arguments)
  },
  set(target, key, value, receiver) {
    trigger(target, key)
    return Reflect.set(...arguments)
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

(公式から引用)


track()

trackは

Map<proxyのオブジェクト, proxyを呼び出す関数群>
を保存します。

つまり最初のコードでは、

map.set(count, new Set( {() => console.log(count.value)} ))
を行っています。


const count = ref(0);
watchEffect(() => console.log(count.value));


trigger()

triggerはtrack()で保存したproxyに関連する関数をすべて呼び出します。


const functionSet = map.get(count) // -> Set<Function>
functionSet.forEach( { // 関数を実行 } ) 


ふりかえると

watchEffectの中でcountを呼び出すと、呼び出したラムダが登録されます。

count.valueが変更されると登録されたラムダが実行されます。


const count = ref(0);
watchEffect(() => console.log(count.value)); // -> 0
count.value = 1; // -> 1


リアクティブがどんな感じで実現されているのか雰囲気がつかめてきました。


参考

2020年12月3日木曜日

最近読んでいる本、読んで良かった本と継続して読書する話(2020年ver)

こんにちは!

この記事は琉大 Advent Calendar 2020 の3日目になります。現時点(2020/12/01 20:00)ではまだ空きがあるようですので、ぜひ参加していってください。

前日の記事はこちらです。

明日の記事はこちらに書くものとします(追記予定)。


はじめに

社会人になって3年目となり、いろいろ知識が足りていないなーと痛感することが増えてきました。知識を補うために、会社での個人OKRとしていくつかやることを決めて取り組んでいます(できているとは言っていない)。そこで僕はOKRの中に「本を読むこと」を入れています。

ここでは最近読んでいる本と、これは良かったと思う本を紹介します。また、できるだけ継続して本を読むために取り組んでいることを紹介します。


おすすめ本

Clean Code

今読んでます。

クラス設計や関数の書き方から学ぼうと思って読んでいます。

まだ前書きと1章しか読んでませんが、cleanなコードとはどういうものかについて、ストラウステップやロン・ジェフリーズの言葉を引用しながら語ってます。


Clean Agile

今年の10月に発売されました。ロバート・C・マーチン(通称ボブおじさん)のアジャイル開発本です。

世の中に出回っているアジャイル開発のウソを紹介して成敗している本です。「アジャイルは早い段階から希望を殺し、継続的に冷たくて厳しい現実を提供する」がキャッチーでした。


Clean Architecture

「クリーンアーキテクチャって、あの円のやつでしょ?」レベルから「依存性逆転の法則が重要なやつ」レベルまで僕を引き上げてくれた本です。

世の中にあるクリーンアーキテクチャの説明よりも原典を読んだ方が早いと思いました。

今の会社ではクリーンアーキテクチャを基本に開発しているので必須の本でした。


テスト駆動開発

テスト駆動開発(TDD)がどういうものかケント・ベックがペアプログラミングを通して教えてくれる本です。

TDDがどのように発展してきたのを訳者の和田さんが紹介してくれているので、そちらだけでも立ち読みしても良いと思います。

今の会社ではテスト駆動開発を基本に開発しているので必須の本でした。


NEW GAME!!

ゲーム制作会社に入社した女の子のきゃっきゃウフフな話です。とくに星川ほたるが好きです。

ほたるんかわいい

僕は9巻の60ページあたり*1にある話が好きです。

コウ:(ほたるはみんなが思っているような天才じゃない…)

優秀なITエンジニアはまんがタイムきららキャラットを買う、と言われています。


継続して読書する

もともと継続して読書することが苦手です。今も苦手で月の読書冊数も平均で1冊もないと思います。

ちょっとでも継続しようと、Scrapboxと読書メーターを使っています。

Scrapboxには読んでいる最中のメモを書いています。あとから振り返りできるし、いつ書いたのかはテロメア*2を見ればわかるので便利です。スマホからも書けるので僕はtwitterのプロフィールに載せて、いつでも見れるようにしています。twitterはよく見るので。

読書メーターには読みたい本のリストと読んだ感想を書いています。感想を書くと何が良かったのか考えるきっかけになり、ちょっとだけ達成感も得られます。とくに良いと思ったのは、読んでいる本を絞れることです。僕は移り気で本を10冊以上同時に読んでしまうことがありました。今は1つに絞って時間を当てて読んでいます。

この施策で、とりあえず2ヶ月くらいは続けて読めています。


おわりに

最近読んでいる本と読んで良かった本、継続して読書する方法を、ざっくり紹介しました。興味を持ってもらえれば幸いです。

またよかったらおすすめの本のタイトルをコメントに書き残して、本当によければおすすめの理由を書いていって欲しいです。


参考

*1: ほたるん努力回はこちら
*2: scrapboxの記事の左側にあるバーのこと。マウスカーソルを合わせるといつの書き込みか行単位でわかる。

2020年8月31日月曜日

Rustを学ぶ(2.5) : RustでAPIサーバー (Responseを返す)

はじめに

前回は、GETとPOSTができるようになりました。

ただし、誤ったリクエストを送った時に、その誤りの理由までは教えてくれません。

また、任意のステータスコードを返せるようになったら、連携するアプリケーション側で考慮するステータスコードを絞れます。

今回は、以下の2つを実装します。

  1. GETとPOSTで/system/error にアクセスすると404が返り、「wrong resource access.」を返す
  2. POSTで/system/pingに'{"wrong":"error"}' でアクセスすると400が返り、「wrong request.」を返す


実装する前に

そもそも現在はどういうステータスコードが返るのでしょうか?

まずは1のGETをしてみます。

% curl localhost:8080/system/error -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /system/error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< content-length: 0
< date: Sun, 30 Aug 2020 00:16:52 GMT
<
* Connection #0 to host localhost left intact
* Closing connection 0

404が返ります。1のPOSTもしてみます。

% curl -XPOST localhost:8080/system/error -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /system/error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< content-length: 0
< date: Sun, 30 Aug 2020 14:21:27 GMT
<
* Connection #0 to host localhost left intact
* Closing connection 0

こちらも404です。理由は特に書かれていません。

2もやってみます。


% curl -XPOST localhost:8080/system/ping -d '{"wrong":"error"}'  -v
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /system/ping HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 17
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 17 out of 17 bytes
< HTTP/1.1 422 Unprocessable Entity
< content-length: 0
< date: Sun, 30 Aug 2020 14:24:20 GMT
<
* Connection #0 to host localhost left intact
* Closing connection

422 (Unprocessable Entity)が返っています。これは正しそうな挙動ですが、アプリケーション側で422を考慮するコードを書く必要が出てきます。今のところ私しか使う人もいないですし、ここは400 (Bad Request)でいいと考えています。もし必要ならコードを読めばいいのです。

余談ですが、curlに"Note: Unnecessary use of -X or --request, POST is already inferred."と出ています。初めて知りましたが、実はXPOSTを付けなくても-dのPOSTデータがあれば、curlはPOSTで送ることを推測してくれるみたいです。


「GETとPOSTで/system/error にアクセスすると404が返り、「wrong resource access.」を返す」を実装する


1を実装します。

9~18行目までにtide::utils::Afterを使って、外にResponseを出す前に返すレスポンス処理を加えます。

curlでGETしてみます。


% curl localhost:8080/system/error -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /system/error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< content-length: 30
< date: Sun, 30 Aug 2020 15:25:11 GMT
< content-type: text/plain;charset=utf-8
<
Error: wrong resource access.
* Connection #0 to host localhost left intact
* Closing connection 0

POSTもしてみます。


 % curl localhost:8080/system/error -d '{"wrong":"error"}' -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /system/error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 17
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 17 out of 17 bytes
< HTTP/1.1 404 Not Found
< content-length: 30
< date: Sun, 30 Aug 2020 15:26:14 GMT
< content-type: text/plain;charset=utf-8
<
Error: wrong resource access.
* Connection #0 to host localhost left intact
* Closing connection 0

できているようです。


「POSTで/system/pingに'{"wrong":"error"}' でアクセスすると400が返り、「wrong request.」を返す」を実装する


2を実装していきます。

先ほどと同じように実装を追加していきます。16~20行目に追記しました。

curlしてみます。


% curl localhost:8080/system/ping -d '{"wrong":"error"}' -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /system/ping HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 17
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 17 out of 17 bytes
< HTTP/1.1 400 Bad Request
< content-length: 22
< date: Sun, 30 Aug 2020 15:33:26 GMT
< content-type: text/plain;charset=utf-8
<
Error: wrong request.
* Connection #0 to host localhost left intact
* Closing connection 0

できてる!


おわりに

今回はResponseを任意のステータスコードで、任意のメッセージを返す実装をしました。

次回はDBへの疎通確認をしたいと思います。


参考文献

先輩と覚える HTTP ステータスコード


2020年8月24日月曜日

Rustを学ぶ(2) : RustでAPIサーバー (GETとPOST)

はじめに

前回、Rustの環境構築をしたので今回はサーバーを実装します。

単純なAPIサーバーとして以下の実装をしていきます。

  1. GET /system/pingでpongを返す
  2. POST /system/pingで{"ping" : "value"}を送ると、{"pong" : "value"}を返す

WebサーバーはTideを使います。Warpのような立ち位置にあり、簡単なWebサーバーを作ることができます。ちなみに、Tideはv0.13.0からProductionで使えるようになったようです。

Tideの起動


Tideを動かしてみます。

任意の場所でプロジェクトを作ります。

% cargo new example-tide
% cd example-tide

Cargo.tomlのdependenciesに以下を追記します。

[dependencies]
tide = "0.13.0"
async-std = { version = "1.6.0", features = ["attributes"] }

main.rsを以下に変更します。


ここまでできたらcargo runします。
試しにcurlしてみましょう。

% curl localhost:8080
Hello, world!

Webサーバーの起動に成功しました。

「GET /system/pingでpongを返す」を実装


以下のように5行目を変更します。


curlしてみましょう。

% curl localhost:8080/system/ping
pong

できましたが、APIサーバーとしては扱いやすいようにjsonで返したいです。

Rustでjsonといえばserde_jsonですが、Tideはすでにjson macroを持っています。
ですので、serde_jsonを使わずともjsonを返すことが可能です。
以下のように5行目を変更します。


curlしてみましょう。

% curl localhost:8080/system/ping
{"ok":"pong"}

jsonで返すことができました。

「POST /system/pingで{"ping" : "value"}を送ると、{"pong" : "value"}を返す」を実装する


6行目にPOSTを受け付ける記述を追記します。(この時点ではPOSTパラメータを無視しています)


curlしてみましょう。

% curl -XPOST localhost:8080/system/ping -d '{"ping":"value"}'
{"ok":"pong"}

やった!POSTで受付に成功しています。

それではPOSTパラメータを受け取り、値を返すようにします。
まずはserdeとserde_jsonをdependenciesに追加します。

[dependencies]
tide = "0.13.0"
async-std = { version = "1.6.0", features = ["attributes"] }
serde = "1.0.114"
serde_json = "1.0"

serdeは構造体をSerialize、Deserializeする時に使います。これで構造体を簡単にjsonにすることができます。(ここはもしかしたらTideがやってくれる...?)

以下のようにmainを修正します。


curlしてみましょう。

% curl -XPOST localhost:8080/system/ping -d '{"ping":"value"}'
{"pong":"value"}

できました!

おわりに


今回はTideでルーティング、GETとPOSTができるようになりました。

次回は、ちゃんとResponseを返す(ステータスコードなど)ところまでやりたいと思います。