さんちゃのblog 2024-03-03T15:55:03+09:00 threetea0407 Hatena::Blog hatenablog://blog/10328749687251073169 2024年2月を振り返る hatenablog://entry/6801883189087838562 2024-03-03T15:55:03+09:00 2024-03-03T15:55:03+09:00 ブログを書いた dawn.hateblo.jp mastodon を fork して、実装を追ったり、スパム対策のパッチを当てたり、絵文字リアクションを表示できるようにしたり、デプロイを高速化したりしていました。 関連して、 puma の hot restart 周りのドキュメントを読んだりしていた。 github.com 大昔に以下の記事を書いたのだが、puma での hot restart は、いずれの方式とも違うやり方をしていることがわかり、unicorn 方式に比べるとイケてねえなと思いました。 qiita.com 葛西臨海公園へ行った 葛西臨海公園|公園へ行こう! 入口正面にあるレス… <h2 id="ブログを書いた">ブログを書いた</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdawn.hateblo.jp%2Fentry%2F2024%2F02%2F25%2F184643" title="最近の mastodon 事情について諸々 - さんちゃのblog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://dawn.hateblo.jp/entry/2024/02/25/184643">dawn.hateblo.jp</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> を fork して、実装を追ったり、スパム対策のパッチを当てたり、絵文字リアクションを表示できるようにしたり、デプロイを高速化したりしていました。</p> <p>関連して、 puma の hot restart 周りのドキュメントを読んだりしていた。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fpuma%2Fpuma%2Fblob%2Fmaster%2Fdocs%2Frestart.md" title="puma/docs/restart.md at master · puma/puma" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/puma/puma/blob/master/docs/restart.md">github.com</a></cite></p> <p>大昔に以下の記事を書いたのだが、puma での hot restart は、いずれの方式とも違うやり方をしていることがわかり、<a class="keyword" href="https://d.hatena.ne.jp/keyword/unicorn">unicorn</a> 方式に比べるとイケてねえなと思いました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fgenya0407%2Fitems%2F974eb0b64027c4be5c01" title="ホットデプロイを実現する2つの方法 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/genya0407/items/974eb0b64027c4be5c01">qiita.com</a></cite></p> <h2 id="葛西臨海公園へ行った"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B3%EB%C0%BE%CE%D7%B3%A4%B8%F8%B1%E0">葛西臨海公園</a>へ行った</h2> <p><a href="https://www.tokyo-park.or.jp/park/kasairinkai/">&#x845B;&#x897F;&#x81E8;&#x6D77;&#x516C;&#x5712;&#xFF5C;&#x516C;&#x5712;&#x3078;&#x884C;&#x3053;&#x3046;&#xFF01;</a></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240303/20240303150845.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> 入口正面にある<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%B9%A5%C8%A5%CF%A5%A6%A5%B9">レストハウス</a></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240303/20240303150855.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> 干潟</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240303/20240303150905.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> 水族館入口の天井</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240303/20240303151224.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> 淡水生物館の展示</p> <p>公園自体も海も思った以上に綺麗だった。夏は暑そう。 併設されている水族館も良かった。特に淡水生物館は建物の作りが面白かった。</p> <h2 id="スプラトゥーン3DLC-Side-Order-をクリア"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%E9%A5%C8%A5%A5%A1%BC%A5%F3">スプラトゥーン</a>3<a class="keyword" href="https://d.hatena.ne.jp/keyword/DLC">DLC</a> &quot;Side Order&quot; をクリア</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.nintendo.com%2Fjp%2Fswitch%2Fav5ja%2Fproducts%2Fdlc.html" title="スプラトゥーン3 : 商品情報 | Nintendo Switch | 任天堂" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.nintendo.com/jp/switch/av5ja/products/dlc.html">www.nintendo.com</a></cite></p> <p>正直あまり期待してなかったがものすごく面白かった。一人プレイのゲームは癒やし。 次に癒やしが必要になったら<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%EC%A5%F3">シレン</a>をやろうと思います。</p> <h2 id="劇場版ハイキューゴミ捨て場の決戦を観た">「劇場版<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%A4%A5%AD%A5%E5%A1%BC%21%21">ハイキュー!!</a> ゴミ捨て場の決戦」を観た</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Feiga.com%2Fmovie%2F97706%2F" title="劇場版ハイキュー!! ゴミ捨て場の決戦 : 作品情報 - 映画.com" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://eiga.com/movie/97706/">eiga.com</a></cite></p> <p>作品自体のファンというわけでもないのだけど、この映画は面白かった。 スパイク・レシーブ・サーブといった動作の表現が良くて、観てて気持ちよかった。</p> <h2 id="LeetCode-の-SQL-50-を完走">LeetCode の <a class="keyword" href="https://d.hatena.ne.jp/keyword/SQL">SQL</a> 50 を完走</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fleetcode.com%2Fstudyplan%2Ftop-sql-50%2F" title="SQL 50 - Study Plan - LeetCode" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://leetcode.com/studyplan/top-sql-50/">leetcode.com</a></cite></p> <p>完走した感想ですが...Window function の練習問題としてはよかった。 あと HAVING とかの普段それほど使わないような文法も一通り使えるようになったと思う。 問題自体もパズルみたいで楽しいのでオススメです。</p> threetea0407 最近の mastodon 事情について諸々 hatenablog://entry/6801883189086066740 2024-02-25T18:46:43+09:00 2024-02-25T19:36:56+09:00 というわけでねhttps://t.co/IcPz05fXfH— 𝘼𝙧𝙧𝙖𝙮-𝙨𝙖𝙣 (@genya0407) July 5, 2023 背景 2023年7月頃に mastodon サーバーを立てて、8月頃から Twitter への投稿をやめた。 それから半年ぐらい経過し、現在では私にとっては mastodon がほぼ Twitter の代替となりつつある。 ここ数週間で話題となっていたスパム騒動への対応も含めて、このへんで簡単に振り返っておく。 前史 2017年ごろに mastodon サーバーを立てていたことがあった。 qiita.com このときは、立てたサーバーを自分自身も使わなくなって… <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">というわけでね<a href="https://t.co/IcPz05fXfH">https://t.co/IcPz05fXfH</a></p>&mdash; 𝘼𝙧𝙧𝙖𝙮-𝙨𝙖𝙣 (@genya0407) <a href="https://twitter.com/genya0407/status/1676647911940313090?ref_src=twsrc%5Etfw">July 5, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <h2 id="背景">背景</h2> <p>2023年7月頃に <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> サーバーを立てて、8月頃から <a class="keyword" href="https://d.hatena.ne.jp/keyword/Twitter">Twitter</a> への投稿をやめた。 それから半年ぐらい経過し、現在では私にとっては <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> がほぼ <a class="keyword" href="https://d.hatena.ne.jp/keyword/Twitter">Twitter</a> の代替となりつつある。</p> <p>ここ数週間で話題となっていたスパム騒動への対応も含めて、このへんで簡単に振り返っておく。</p> <h2 id="前史">前史</h2> <p>2017年ごろに <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> サーバーを立てていたことがあった。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fgenya0407%2Fitems%2Fafb9c3f075225de856ed" title="AWSの無料SSLを使ってmastodonインスタンスを立てる手順 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/genya0407/items/afb9c3f075225de856ed">qiita.com</a></cite></p> <p>このときは、立てたサーバーを自分自身も使わなくなってしまい、自分以外の利用者もそれほど多くないようだったので、一ヶ月程度で閉じてしまった。</p> <p>余談だが、記事中で「無料<a class="keyword" href="https://d.hatena.ne.jp/keyword/SSL">SSL</a>」というキーワードを強調しているのは、当時はまだ Let's Encrypt 等が普及しておらず、暗号通信を利用するには追加料金が必要となることが多かったことによる。</p> <h2 id="socialgenya0407link-を公開">social.genya0407.link を公開</h2> <p>2023年7月頃、X が頻繁に壊れるのに嫌気が差し、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%D5%C4%A5%A4%EA">逆張り</a>精神も相まって <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> に移住することを決めた。 所属するサーバーをどれにするか悩んでいたが、<a href="https://configure.ac/@shyouhei">Urabe Shohei 氏</a> が自前でサーバーを立てているのを観測し、自分もこれにしようと思い至った。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> は公式が <a href="https://github.com/mastodon/mastodon/pkgs/container/mastodon">docker image</a> を公開しているので、これを <a href="https://dawn.hateblo.jp/entry/spot-instance-web-server">スポットインスタンスで個人開発 Web サーバーを運用する技術</a> で構築していた基盤に乗せるだけで問題なく動作した。 設定がうまく行ってなくてたまに意図せず<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BB%AA%CD%EE%A4%C1">鯖落ち</a>したりしてたけど、現在では概ね安定して稼働している。</p> <p>ここからはしばらく平和な <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> ライフを楽しんでいた。 2017年のときと違って misskey.io もあるし、日本語話者ユーザーもそこそこいたので、見かけた人を片っ端からフォローするようにしていると、<a class="keyword" href="https://d.hatena.ne.jp/keyword/SNS">SNS</a>欲求が満たせるぐらいのTL流速になった。 大昔、高校生ぐらいのときに <a class="keyword" href="https://d.hatena.ne.jp/keyword/Twitter">Twitter</a> でフォローしていた技術系の人々(mikutter とかやってた人たち)が <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> に生息していて、「皆さんここにいらっしゃったんですね」という感想になったり、 意外とお一人様<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を運用してる人も多いんだなあと思ったりした。</p> <h2 id="投稿がちょっとだけバズる--misskey-との互換性問題">投稿がちょっとだけバズる &amp; misskey との互換性問題</h2> <p>ある日、適当な投稿をして就寝したところ、朝起きたらそこそこ RT / Like されているということがあった。</p> <iframe src="https://social.genya0407.link/@genya0407/111845788155269608/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe> <script src="https://social.genya0407.link/embed.js" async="async"></script> <p>23 RT / 69 Like に到達してる。</p> <p>実はこの投稿を <a href="https://misskey.io/notes/9p44imyoq0l6003c">misskey.io から見る</a>とちょっと面白いことになっている。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240225/20240225173043.png" width="1200" height="694" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>misskey には <a href="https://misskey-hub.net/ja/docs/for-users/features/reaction/">リアクション</a> という機能があるのだが、これを <strong><a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> 側から観測すると Like に丸められてしまう</strong>のだ<a href="#f-21959aab" id="fn-21959aab" name="fn-21959aab" title="右端の uwa_xtu... というやつはたぶん、misskey.io 以外から飛んできたカスタム絵文字リアクションの画像がリンク切れしたものと思われる">*1</a>。</p> <p>このことは以下のページでも解説されている。</p> <p><a href="https://scrapbox.io/activitypub/%E7%B5%B5%E6%96%87%E5%AD%97%E3%83%AA%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3">&#x7D75;&#x6587;&#x5B57;&#x30EA;&#x30A2;&#x30AF;&#x30B7;&#x30E7;&#x30F3; - ActivityPub&#x307E;&#x3068;&#x3081;wiki</a></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> にもリアクションに使われた絵文字の情報自体は飛んできているので、それを適切にハンドリングしてやれば <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> 上でも絵文字を表示することができる。</p> <p>当然のことながら、絵文字リアクションに対応した <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> fork も存在する。</p> <p><iframe src="https://fedibird.com/@noellabo/106243204343470341/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://fedibird.com/embed.js" async="async"></script><cite class="hatena-citation"><a href="https://fedibird.com/@noellabo/106243204343470341">fedibird.com</a></cite></p> <p>ともあれ、<a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> の公式イメージを利用する限り、misskey.io から飛んできた絵文字リアクションを確認することはできないということがわかった。</p> <h2 id="mastodon-を-fork"><a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> を fork</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> のコードベースに自分で手をいれて絵文字リアクションを表示させるのは楽しそう、というモチベーションから、公式レポジトリを fork して自前のレポジトリを作った。</p> <p><a href="https://github.com/genya0407/mastodon">https://github.com/genya0407/mastodon</a></p> <p>upstream への追従を楽に実施するために、以下のようなポリシーで更新を行っている。</p> <ul> <li>upstream の <code>v4.2.7</code> から <code>trunk</code> というブランチを切り、独自の修正はそこにマージしていく</li> <li>リリースしたくなったら <code>trunk</code> から <code>v4.2.7-genya0407-0</code> のようなタグを切り、docker image をビルドしてデプロイ</li> <li>upstream に新しいタグが生えたら <code>trunk</code> にマージし、新しくタグを切ってビルド &amp; デプロイ</li> </ul> <p>いまのところは、後述の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%D1%A5%E0%A5%D5%A5%A3%A5%EB%A5%BF%A1%BC">スパムフィルター</a>と絵文字リアクションの表示機能ぐらいしか差分がないため、upstream への追従も難なく行えている。 メジャーバージョンの更新時など、大きな差分が入るタイミングでコンフリクトが発生し、頓挫する可能性もあるが、それはその時考えればいいと楽観的に考えている。</p> <h2 id="スパム騒動">スパム騒動</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechcrunch.com%2F2024%2F02%2F20%2Fspam-attack-on-twitter-x-rival-mastodon-highlights-fediverse-vulnerabilities%2F" title="Spam attack on Twitter/X rival Mastodon highlights &#39;fediverse&#39; vulnerabilities | TechCrunch" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techcrunch.com/2024/02/20/spam-attack-on-twitter-x-rival-mastodon-highlights-fediverse-vulnerabilities/">techcrunch.com</a></cite></p> <p>この記事に取り上げられている通り、2月の上旬頃から ActivityPub 界ではスパムが問題となっていた<a href="#f-74a50be8" id="fn-74a50be8" name="fn-74a50be8" title="スパムの発信源等については [https://www.docswell.com/s/fono/K8GPNY-mastodon-spam-response-log:title] が詳しい">*2</a>。 私のアカウントにも、謎の <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> サーバーに作られた謎のアカウントから謎のメンションが一日数件ほど寄せられる事態となった。</p> <p>ただのメンションではあるのだが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>の通知が鳴るのと、<a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> の通知欄がそれで埋まってしまうのも嫌だったので、何らかの対策を打つ必要があると考えていたところ、 rosylilly 氏によって対策パッチが <a href="https://best-friends.chat/">https://best-friends.chat/</a> に導入されたことを知った<a href="#f-aed96a1c" id="fn-aed96a1c" name="fn-aed96a1c" title="[https://best-friends.chat/@rosylilly/111952483410053586]">*3</a>。</p> <p>これを上述の fork に取り込んだところ、スパムが一通も届かなくなったことを確認した。</p> <iframe src="https://social.genya0407.link/@genya0407/111969106379033222/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe> <script src="https://social.genya0407.link/embed.js" async="async"></script> <p>その後、スパムを弾いたときにログに出す仕組みを独自に実装し、たまにサーバーのログを眺めて楽しんだりしている。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%D1%A5%DE%A1%BC">スパマー</a>はカス!と思う一方で、いにしえの <a class="keyword" href="https://d.hatena.ne.jp/keyword/Unix">Unix</a> 版のエピソードを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C4%C9%C2%CE%B8%B3">追体験</a>しているようで面白かった。</p> <p><a href="https://www.nicovideo.jp/watch/sm395218">2001&#x5E74;8&#x6708;25&#x65E5; - 2&#x3061;&#x3083;&#x3093;&#x306D;&#x308B;&#x9589;&#x9396;&#x306E;&#x5371;&#x6A5F; - &#x30CB;&#x30B3;&#x30CB;&#x30B3;&#x52D5;&#x753B;</a></p> <h2 id="絵文字リアクションの表示">絵文字リアクションの表示</h2> <p>misskey からの絵文字リアクションが単なる Like に見えてしまうという話はしたが、これを <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> からも閲覧できるように修正を加えた。</p> <ul> <li><a href="https://github.com/genya0407/mastodon/pull/1">Log emoji by genya0407 &middot; Pull Request #1 &middot; genya0407/mastodon &middot; GitHub</a></li> <li><a href="https://github.com/genya0407/mastodon/pull/3">Fix emoji capture logic by genya0407 &middot; Pull Request #3 &middot; genya0407/mastodon &middot; GitHub</a></li> <li><a href="https://github.com/genya0407/mastodon/pull/4">Denormalized emoji count by genya0407 &middot; Pull Request #4 &middot; genya0407/mastodon &middot; GitHub</a></li> <li><a href="https://github.com/genya0407/mastodon/pull/6">Fix bug on serializer by genya0407 &middot; Pull Request #6 &middot; genya0407/mastodon &middot; GitHub</a></li> <li><a href="https://github.com/genya0407/mastodon/pull/7">Show emoji on accounts API &amp; place emojis in paragraph by genya0407 &middot; Pull Request #7 &middot; genya0407/mastodon &middot; GitHub</a></li> <li><a href="https://github.com/genya0407/mastodon/pull/11">Show custom emoji by genya0407 &middot; Pull Request #11 &middot; genya0407/mastodon &middot; GitHub</a></li> <li><a href="https://github.com/genya0407/mastodon/pull/13">Set timeout for emoji fetching by genya0407 &middot; Pull Request #13 &middot; genya0407/mastodon &middot; GitHub</a></li> </ul> <p>リアクションを「つける」実装はめんどくさそうなのと、自分もそれを求めているわけでもない<a href="#f-8a1c3390" id="fn-8a1c3390" name="fn-8a1c3390" title="人がつけたリアクションが見れればいい">*4</a>ので割愛した。 超絶雑な実装だが、これによって<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BC%AB%BB%AA">自鯖</a>から misskey リアクションが閲覧できるようになった。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240225/20240225183021.png" width="1136" height="382" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240225/20240225183026.png" width="1136" height="896" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="まとめ">まとめ</h2> <p>2023年7月ごろから <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> サーバーを自前で運用して利用している。 今年に入ってからレポジトリを fork して、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%D1%A5%E0%A5%D5%A5%A3%A5%EB%A5%BF%A1%BC">スパムフィルター</a>や絵文字リアクションの表示機能など、独自の差分を入れて楽しんでいる。 他のサーバー管理者からパッチを拝借したり、コメントしたり、自分で実装した機能が動いたりするのは楽しい。</p> <div class="footnote"> <p class="footnote"><a href="#fn-21959aab" id="f-21959aab" name="f-21959aab" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">右端の uwa_xtu... というやつはたぶん、misskey.io 以外から飛んできたカスタム絵文字リアクションの画像がリンク切れしたものと思われる</span></p> <p class="footnote"><a href="#fn-74a50be8" id="f-74a50be8" name="f-74a50be8" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">スパムの発信源等については <a href="https://www.docswell.com/s/fono/K8GPNY-mastodon-spam-response-log">2024&#x5E74;2&#x6708;&#x30B9;&#x30CF;&#x309A;&#x30E0;&#x5BFE;&#x51E6;&#x985B;&#x672B;&#x8A18;+&alpha; | &#x30C9;&#x30AF;&#x30BB;&#x30EB;</a> が詳しい</span></p> <p class="footnote"><a href="#fn-aed96a1c" id="f-aed96a1c" name="f-aed96a1c" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://best-friends.chat/@rosylilly/111952483410053586">https://best-friends.chat/@rosylilly/111952483410053586</a></span></p> <p class="footnote"><a href="#fn-8a1c3390" id="f-8a1c3390" name="f-8a1c3390" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">人がつけたリアクションが見れればいい</span></p> </div> threetea0407 2024年1月を振り返る hatenablog://entry/6801883189079318822 2024-01-31T01:28:43+09:00 2024-01-31T01:28:43+09:00 本年もよろしくお願いいたします。 初詣 柴又帝釈天 に初詣に行った。ちなみに帝釈天は寺なので、厳密には初詣ではないです。 そのあと 水元公園 まで歩いて、水元公園を一周した。寒いし距離も長いし死ぬかと思った。綺麗だし景色は良かった。 さんぽ神 at 高井戸周辺 ドロッセルマイヤーさんのさんぽ神アプリ Kazushi Ikezawa エンターテインメント ¥330 apps.apple.com ↑を使って散歩をした。出発点は高井戸にある自宅。指示に従って一日中歩いた。 蘆花恒春園 が良かったです。あと近所の井戸を発見した。 自転車 メンテナンスのために Y's Road 府中多摩川店 まで自転車… <p>本年もよろしくお願いいたします。</p> <h3 id="初詣">初詣</h3> <p><a href="http://www.taishakuten.or.jp/">柴又帝釈天</a> に初詣に行った。ちなみに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C4%EB%BC%E1%C5%B7">帝釈天</a>は寺なので、厳密には初詣ではないです。</p> <p>そのあと <a href="https://www.tokyo-park.or.jp/park/format/index041.html" target="_blank">水元公園</a> まで歩いて、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%E5%B8%B5%B8%F8%B1%E0">水元公園</a>を一周した。寒いし距離も長いし死ぬかと思った。綺麗だし景色は良かった。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240130/20240130232727.jpg" width="533" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240130/20240130232659.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h3 id="さんぽ神-at-高井戸周辺">さんぽ神 at 高井戸周辺</h3> <div class="itunes-embed freezed itunes-kind-software"><a href="https://apps.apple.com/jp/app/%E3%83%89%E3%83%AD%E3%83%83%E3%82%BB%E3%83%AB%E3%83%9E%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%95%E3%82%93%E3%81%AE%E3%81%95%E3%82%93%E3%81%BD%E7%A5%9E%E3%82%A2%E3%83%97%E3%83%AA/id6473621679?uo=4&amp;at=10l8JW&amp;ct=hatenablog" rel="nofollow" target="_blank"><img src="https://cdn.image.st-hatena.com/image/scale/52bb6112b8bdf6eea8040ec89c456865d5fdb998/enlarge=0;height=200;version=1;width=200/https%3A%2F%2Fis1-ssl.mzstatic.com%2Fimage%2Fthumb%2FPurple116%2Fv4%2Fc0%2Fe4%2Fd8%2Fc0e4d8a7-c204-e9c2-9cb9-3cfde2ffc6d8%2FAppIcon-1x_U007epad-85-220.png%2F100x100bb.jpg" alt="ドロッセルマイヤーさんのさんぽ神アプリ" title="ドロッセルマイヤーさんのさんぽ神アプリ" class="itunes-embed-image" /></a> <div class="itunes-embed-info"> <p class="itunes-embed-title"><a href="https://apps.apple.com/jp/app/%E3%83%89%E3%83%AD%E3%83%83%E3%82%BB%E3%83%AB%E3%83%9E%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%95%E3%82%93%E3%81%AE%E3%81%95%E3%82%93%E3%81%BD%E7%A5%9E%E3%82%A2%E3%83%97%E3%83%AA/id6473621679?uo=4&amp;at=10l8JW&amp;ct=hatenablog" rel="nofollow" target="_blank">ドロッセルマイヤーさんのさんぽ神アプリ</a></p> <ul> <li class="itunes-embed-artist">Kazushi Ikezawa</li> <li class="itunes-embed-genre">エンターテインメント</li> <li class="itunes-embed-price">¥330</li> <li class="itunes-embed-badge"><a href="https://apps.apple.com/jp/app/%E3%83%89%E3%83%AD%E3%83%83%E3%82%BB%E3%83%AB%E3%83%9E%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%95%E3%82%93%E3%81%AE%E3%81%95%E3%82%93%E3%81%BD%E7%A5%9E%E3%82%A2%E3%83%97%E3%83%AA/id6473621679?uo=4&amp;at=10l8JW&amp;ct=hatenablog" rel="nofollow" target="_blank"><img src="https://cdn.blog.st-hatena.com/images/theme/itunes/itunes-badge-appstore@2x.png" width="60px" height="15px" /></a></li> </ul> </div> </div> <p><cite class="hatena-citation"><a href="https://apps.apple.com/jp/app/%E3%83%89%E3%83%AD%E3%83%83%E3%82%BB%E3%83%AB%E3%83%9E%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%95%E3%82%93%E3%81%AE%E3%81%95%E3%82%93%E3%81%BD%E7%A5%9E%E3%82%A2%E3%83%97%E3%83%AA/id6473621679">apps.apple.com</a></cite></p> <p>↑を使って散歩をした。出発点は高井戸にある自宅。指示に従って一日中歩いた。</p> <p><a href="https://www.tokyo-park.or.jp/park/format/index007.html">蘆花恒春園</a> が良かったです。あと近所の井戸を発見した。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240130/20240130233540.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h3 id="自転車">自転車</h3> <p>メンテナンスのために <a href="https://ysroad.co.jp/fuchu/" target="_blank">Y's Road 府中多摩川店</a> まで自転車を漕いだ。</p> <p>帰りに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C2%BF%CB%E0%C0%EE">多摩川</a>河川敷で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A4%C9%A4%F3%A4%C9%BE%C6%A4%AD">どんど焼き</a>の準備を目撃した。<br /><a href="https://sanny-care.com/event/komae-hatsuharu-2024/" target="_blank">第9回こまえ初春まつり~どんど焼き~</a> </p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240130/20240130234042.jpg" width="1200" height="783" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>写真だと伝わらないかもしれないが、唐突に謎の物体が現れてワクワク感があった。</p> <h3 id="散歩-at-代々木緑道">散歩 at 代々木緑道</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftown.mec-h.com%2Fmh-yoyogi%2F98" title="玉川上水旧水路緑道(代々木緑道) ~ 代々木エリア|すてきな街を、見に行こう。" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://town.mec-h.com/mh-yoyogi/98">town.mec-h.com</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%B7%BD%C9%B1%D8">新宿駅</a>の近くを散策した後、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%B7%BD%C9%C3%E6%B1%FB%B8%F8%B1%E0">新宿中央公園</a>の近くから<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BA%FB%C4%CD%B1%D8">笹塚駅</a>まで続く「代々木緑道」を歩いた。特別面白みのある緑道というわけでもなかったが、、、</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240131/20240131003208.jpg" width="533" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240131/20240131003154.jpg" width="533" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240131/20240131003544.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h3 id="長野県は小諸市に旅行した">長野県は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BE%AE%BD%F4%BB%D4">小諸市</a>に旅行した</h3> <p>小諸で働いている知人に誘われて一泊二日の旅行に行った。</p> <p>レンタカーを借りて、<a href="https://komorodistillery.com/" target="_blank">小諸蒸留所</a> や <a href="https://corporate.valuebooks.jp/nabo/" target="_blank">BOOKS &amp; CAFE NABO</a> にいったり、雪山を登ったり、そばを食べたりした。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240131/20240131004202.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240131/20240131004148.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240131/20240131004134.jpg" width="533" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240131/20240131004119.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240131/20240131004105.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20240131/20240131004050.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><iframe src="https://social.genya0407.link/@genya0407/111788662069709545/embed" class="mastodon-embed" style="max-width: 100%; border: 0;" width="400" allowfullscreen="allowfullscreen"></iframe></p> <p> <script src="https://social.genya0407.link/embed.js" async="async"></script> </p> <p> </p> <h3 id="確定申告をした">確定申告をした</h3> <p>確定申告というか還付申告です。寄付金が思った以上に戻ってきた。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/e-Tax">e-Tax</a> は初回は面倒だが、2回目からは楽と言われる所以がわかった。様々なやり方があるなかで、自分にとってベストな方法を見つければ楽、ということだと思う。</p> <h3 id="グノーシア">グノーシア</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fstore-jp.nintendo.com%2Flist%2Fsoftware%2F70010000027791.html" title="グノーシア ダウンロード版" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://store-jp.nintendo.com/list/software/70010000027791.html">store-jp.nintendo.com</a></cite></p> <p>前から気になっていた「グノーシア」をクリアした。</p> <p>グノーシアは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%CD%CF%B5">人狼</a>を繰り返しプレイすることにより、登場人物の秘密が一つ一つ明らかになるというゲーム。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%CD%CF%B5">人狼</a>をやったことは一切なかったのだが、とても楽しめた。このゲームを通じて<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%CD%CF%B5">人狼</a>のルールを学ぶことができた。初心者の人は、対人戦をやるよりもこういう対CPU戦をたくさんやることで学習したほうがいいのかもと思ったりした。</p> <p>たぶん長くとも10時間ぐらいでクリアできると思う。濃密なプレイ体験ができてよかった。</p> <p><iframe src="https://social.genya0407.link/@genya0407/111736772816381420/embed" class="mastodon-embed" style="max-width: 100%; border: 0;" width="400" allowfullscreen="allowfullscreen"></iframe></p> <p> <script src="https://social.genya0407.link/embed.js" async="async"></script> </p> <h3 id="読んだ本">読んだ本</h3> <ul> <li> <p><a href="https://www.amazon.co.jp/dp/4062817152?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">「オルグ」の鬼 労働組合は誰のためのものか (講談社+α文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/4757436947?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">仕事を教えることになったら読む本</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/4862464998?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">写真を撮りたくなったら読む本 : 最高の一枚を巨匠に学ぶ</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/4334035817?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">森山大道 路上スナップのススメ (光文社新書)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/4478028338?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">「課長」から始める 社内政治の教科書</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/4094064427?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">サラバ! (上) (小学館文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/4777148483?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">富嶽百景</a></p> </li> </ul> <h3 id="雑感">雑感</h3> <p>年明けから調子が悪かったはずなのに、振り返ってみると妙に活動的でビビる</p> <p><iframe src="https://social.genya0407.link/@genya0407/111833915678220739/embed" class="mastodon-embed" style="max-width: 100%; border: 0;" width="400" allowfullscreen="allowfullscreen"></iframe></p> <p> <script src="https://social.genya0407.link/embed.js" async="async"></script> </p> <p> </p> threetea0407 2023年を振り返る hatenablog://entry/6801883189070817240 2023-12-31T17:42:11+09:00 2023-12-31T17:42:11+09:00 ここ数年を振り返る 2018年を振り返る - 不眠日記 2019年を振り返る - 不眠日記 2020年を振り返る - 不眠日記 2021年を振り返る - 不眠日記 2022年を振り返る - 不眠日記 今月を振り返るを振り返る 2023年1月を振り返る - 不眠日記 2023年2月を振り返る - 不眠日記 2023年3月を振り返る - 不眠日記 2023年4月を振り返る - 不眠日記 2023年5月を振り返る - さんちゃのblog 2023年6月を振り返る - さんちゃのblog 2023年07月を振り返る - さんちゃのblog 2023年8月を振り返る - さんちゃのblog 2023年9… <h3 id="ここ数年を振り返る">ここ数年を振り返る</h3> <ul> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2018/12/31/233832" target="_blank">2018年を振り返る - 不眠日記</a></p> </li> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2019/12/31/215448" target="_blank">2019年を振り返る - 不眠日記</a></p> </li> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2020/12/31/134915" target="_blank">2020年を振り返る - 不眠日記</a></p> </li> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2021/12/31/224206" target="_blank">2021年を振り返る - 不眠日記</a></p> </li> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2022/12/31/220309" target="_blank">2022年を振り返る - 不眠日記</a></p> </li> </ul> <h3 id="今月を振り返るを振り返る">今月を振り返るを振り返る</h3> <ul> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2023/01/30/010130" target="_blank">2023年1月を振り返る - 不眠日記</a></p> </li> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2023/02/28/010323" target="_blank">2023年2月を振り返る - 不眠日記</a></p> </li> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2023/04/02/144034" target="_blank">2023年3月を振り返る - 不眠日記</a></p> </li> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2023/05/04/015842" target="_blank">2023年4月を振り返る - 不眠日記</a></p> </li> <li> <p><a href="https://dawn.hateblo.jp/entry/2023/06/01/000500" target="_blank">2023年5月を振り返る - さんちゃのblog</a></p> </li> <li> <p><a href="https://dawn.hateblo.jp/entry/2023/07/02/122647" target="_blank">2023年6月を振り返る - さんちゃのblog</a></p> </li> <li> <p><a href="https://dawn.hateblo.jp/entry/2023/08/01/001031" target="_blank">2023年07月を振り返る - さんちゃのblog</a></p> </li> <li> <p><a href="https://dawn.hateblo.jp/entry/2023/09/02/012946" target="_blank">2023年8月を振り返る - さんちゃのblog</a></p> </li> <li> <p><a href="https://dawn.hateblo.jp/entry/2023/10/30/015158" target="_blank">2023年9月/10月を振り返る - さんちゃのblog</a></p> </li> <li> <p><a href="https://dawn.hateblo.jp/entry/2023/11/30/231212" target="_blank">2023年11月を振り返る - さんちゃのblog</a></p> </li> <li> <p><a href="https://dawn.hateblo.jp/entry/2023/12/30/231015">2023年12月を振り返る - さんちゃのblog</a></p> </li> </ul> <h3 id="読んだ本">読んだ本</h3> <ul> <li> <p><a href="https://www.amazon.co.jp/dp/4101006040?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">津軽 (新潮文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B00CL6N04E?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">江戸川乱歩傑作選(新潮文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B009DEMBO2?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">わたしを離さないで (ハヤカワepi文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/410112115X?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">砂の女 (新潮文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B0BGXT8L5Z?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">発達障害の人には世界がどう見えるのか (SB新書)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B085TB617F?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">人間たちの話 (ハヤカワ文庫JA)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B00LWZUCL4?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">TUGUMI (中公文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B09XMKNTHM?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">中国共産党 世界最強の組織 1億党員の入党・教育から活動まで (星海社 e-SHINSHO)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/4022619430?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">女ぎらい (朝日文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/4087520331?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">夢十夜・草枕 (集英社文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B071X6J3GN?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">アフターダーク (講談社文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B07KVTV42B?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">ノルウェイの森 (講談社文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B00FGY4XJY?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">蹴りたい背中 (河出文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B08YF4R7F4?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">狭い部屋でも快適に暮らすための家具配置のルール</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B01N1SK9TT?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">スプートニクの恋人 (講談社文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B0CDKYTLV1?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">人を動かす 改訂文庫版</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B08PC9V6MM?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">細雪(上中下)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B0BLVJ324Q?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">イヌはなぜ愛してくれるのか 「最良の友」の科学 (ハヤカワ文庫NF)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B07BDDCC2J?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">すらすら読める風姿花伝 (講談社+α文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B0C52SFYDC?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">自作OSで学ぶマイクロカーネルの設計と実装</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/4041026148?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">完全版 社会人大学人見知り学部 卒業見込 (角川文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B09MY8SF1C?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">ナナメの夕暮れ (文春文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B08KQ6MWXR?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">表参道のセレブ犬とカバーニャ要塞の野良犬 (文春文庫)</a></p> </li> </ul> <h3 id="やったゲーム">やったゲーム</h3> <ul> <li> <p><a href="https://www.amazon.co.jp/dp/B09Y9JMPBN?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">スプラトゥーン3 -Switch</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B09L1B6GJN?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">【PS5】ELDEN RING</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B0BFBL2S6S?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">オクトパストラベラー2(PlayStation 5)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B0C3LXS6ZG?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">【PS5】ARMORED CORE Ⅵ FIRES OF RUBICON</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B07TS7CKLV?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">A列車で行こうExp.+(エクスプレス プラス) - PS4</a></p> </li> </ul> <h3 id="アクティビティ">アクティビティ</h3> <ul> <li>自転車 <ul> <li>近場を走るのはちょいちょいやってる</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B2%E2%A5%F6%B1%BA">霞ヶ浦</a></li> </ul> </li> <li>旅行 <ul> <li> <p><a href="https://genya0407.hatenadiary.jp/entry/2023/10/30/012341" target="_blank">長崎旅行 - 不眠日記</a></p> </li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B2%E2%A5%F6%B1%BA">霞ヶ浦</a>に泊りがけで自転車を乗りに行ったやつ</li> </ul> </li> <li>ギター <ul> <li> <p><a href="https://www.amazon.co.jp/dp/B085MTNGJP?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">ヤマハの安いギター</a>を買って、たまに弾いて楽しんでいる</p> </li> </ul> </li> <li>その他、行ったところなど <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C2%BF%CB%E0%C6%B0%CA%AA%B8%F8%B1%E0">多摩動物公園</a></li> <li>吉祥寺動物園</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%C0%C5%C4%CC%C0%BF%C0">神田明神</a></li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C0%F5%C1%F0%BB%FB">浅草寺</a></li> <li>横浜中華街</li> <li>宿河原の桜並木</li> <li>大岳鍾乳洞</li> <li>神城植物公園</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%BC%C2%E7%BB%FB">深大寺</a></li> <li>東京タワー</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%F9%C5%C4%C0%EE">隅田川</a></li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%F0%C2%F4%A5%AA%A5%EA%A5%F3%A5%D4%A5%C3%A5%AF%B8%F8%B1%E0">駒沢オリンピック公園</a></li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C8%AC%B7%CA%C5%E7%A5%B7%A1%BC%A5%D1%A5%E9%A5%C0%A5%A4%A5%B9">八景島シーパラダイス</a></li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CE%A9%B2%CA%B3%D8%C7%EE%CA%AA%B4%DB">国立科学博物館</a></li> </ul> </li> </ul> <h3 id="趣味プログラミング">趣味プログラミング</h3> <ul> <li>カメラからパソコンに写真を同期するやつ <ul> <li> <p><a href="https://github.com/genya0407/grsync" target="_blank">https://github.com/genya0407/grsync</a></p> </li> </ul> </li> <li>jpg をいい感じにリサイズして、10MB以下のファイルサイズになるようにしてくれるやつ <ul> <li> <p><a href="https://github.com/genya0407/resizer" target="_blank">https://github.com/genya0407/resizer</a></p> </li> </ul> </li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 専用の database_rewinder <ul> <li> <p><a href="https://github.com/DeNA/mysql_rewinder" target="_blank">https://github.com/DeNA/mysql_rewinder</a></p> </li> </ul> </li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/Q4M">Q4M</a> の <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8対応の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%B0%A5%D5%A5%A3%A5%C3%A5%AF%A5%B9">バグフィックス</a> <ul> <li> <p><a href="https://github.com/aeroastro/q4m/pull/1" target="_blank">https://github.com/aeroastro/q4m/pull/1</a></p> </li> </ul> </li> <li>Box <a class="keyword" href="https://d.hatena.ne.jp/keyword/SDK">SDK</a> の <a class="keyword" href="https://d.hatena.ne.jp/keyword/typo">typo</a> を直した <ul> <li> <p><a href="https://github.com/box/box-node-sdk/pull/842" target="_blank">https://github.com/box/box-node-sdk/pull/842</a></p> </li> </ul> </li> <li>mruby 用の webserver interface のエッジケースにおける挙動を修正 <ul> <li> <p><a href="https://github.com/katzer/mruby-shelf/pull/3" target="_blank">https://github.com/katzer/mruby-shelf/pull/3</a></p> </li> </ul> </li> </ul> <h3 id="その他">その他</h3> <ul> <li>引っ越し <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C0%BB%C0%D8%BA%F9%A5%F6%B5%D6">聖蹟桜ヶ丘</a> → 高井戸</li> <li>都心が近くなったのと、駅まで近くなったので色々と便利にはなった</li> </ul> </li> <li> 仕事の変化 <ul> <li>(去年まで)実装マン → 基盤マン → 実装しつつ基盤するマン <ul> <li>全体的に技術的なチャンレンジを仕事を通じて実践できた年だった</li> </ul> </li> <li>毎日出社している</li> </ul> </li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/SNS">SNS</a> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/Twitter">Twitter</a> やめて <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> になった</li> <li> <p><a href="https://social.genya0407.link/@genya0407" target="_blank">@genya0407@social.genya0407.link</a></p> </li> </ul> </li> <li>脱<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CC%B2%BA%DE">眠剤</a> <ul> <li>ほぼ完全に脱したと言っても過言ではない</li> </ul> </li> <li>髪 <ul> <li>後ろでお団子が作れるぐらい伸びた</li> </ul> </li> <li>カメラ <ul> <li>デジカメを買ったので写真を撮っていきたい</li> </ul> </li> </ul> <h3 id="2023年の雑感">2023年の雑感</h3> <blockquote> <p><span style="color: #3d3f44; font-family: 'PT Sans', 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic Pro', Meiryo, 'MS PGothic', sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">2022年を総括すると、地味ではあるが良い変化を得られた年だった、と言えると思う。2023年もそのような年でありますように。</span><a href="https://genya0407.hatenadiary.jp/entry/2022/12/31/220309" target="_blank"> </a></p> </blockquote> <p>これは <a href="https://genya0407.hatenadiary.jp/entry/2022/12/31/220309" target="_blank" rel="noopener">2022年を振り返る</a> からの引用だが、変化という観点でいうと今年も小さな変化を楽しむことができた年だったと思う。昨年以前からの積み重ねの上で、様々なものにおいてチャンレンジを行い、それが成果として現れた年だったとも言える。今年は、私個人の人生という観点では、総じて様々なものが良い意味で安定していた。</p> <h3 id="2024年について">2024年について</h3> <p>安定を保ちつつ、チャンスがあれば様々なことに挑戦していきたい。それができると思えるようになったという点で、2023年はとても良い一年だった。2024年も良い一年でありますように。</p> threetea0407 2023年12月を振り返る hatenablog://entry/6801883189070517141 2023-12-30T23:10:15+09:00 2023-12-30T23:22:50+09:00 カメラを買った My new gear... RICOH GR III / GR IIIx / デジタルカメラ / 製品 | RICOH IMAGING カメラを買えなかったを書いてから2ヶ月ぐらい経ってやっとカメラが届いた。 写真を撮った iPhone のカメラの偉大さを知る日々という話もある。 カメラに関連したソフトウェアをいくつか ① カメラからパソコンに画像を同期する CLI ツール。 スマホアプリはリコー公式で出てるっぽいんだけどパソコン用のがなさそうなので自分で作った。 GitHub - genya0407/grsync: Unofficial image downloader f… <h2 id="カメラを買った">カメラを買った</h2> <p>My new gear...</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231228/20231228223123.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><a href="https://www.ricoh-imaging.co.jp/japan/products/gr-3/">RICOH GR III / GR IIIx / &#x30C7;&#x30B8;&#x30BF;&#x30EB;&#x30AB;&#x30E1;&#x30E9; / &#x88FD;&#x54C1; | RICOH IMAGING</a></p> <p><a href="https://dawn.hateblo.jp/entry/2023/10/30/015158#%E3%83%87%E3%82%B8%E3%82%AB%E3%83%A1%E3%82%92%E8%B2%B7%E3%81%88%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F">カメラを買えなかった</a>を書いてから2ヶ月ぐらい経ってやっとカメラが届いた。</p> <h2 id="写真を撮った">写真を撮った</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229004159.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229004214.jpg" width="533" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229004227.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229002459.jpg" width="533" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229002444.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229002429.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229002415.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231230/20231230005614.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231230/20231230005627.jpg" width="533" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231230/20231230005601.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/iPhone">iPhone</a> のカメラの偉大さを知る日々という話もある。</p> <h2 id="カメラに関連したソフトウェアをいくつか">カメラに関連したソフトウェアをいくつか</h2> <h3 id="-カメラからパソコンに画像を同期する-CLI-ツール">① カメラからパソコンに画像を同期する <a class="keyword" href="https://d.hatena.ne.jp/keyword/CLI">CLI</a> ツール。</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>アプリはリコー公式で出てるっぽいんだけどパソコン用のがなさそうなので自分で作った。</p> <p><a href="https://github.com/genya0407/grsync">GitHub - genya0407/grsync: Unofficial image downloader for RICOH GR III</a></p> <p>python2 製の化石みたいな<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>が出回ってるのを見ると一定の需要はあるのだと思う。 ただ、↑は win 対応させてないので、実際に使われるのかというとなあ...というところ。</p> <p><a href="https://zakazukuri.com/ricoh-digital-camera-gr-iii-wifi-photo-transfer/">&#x30EA;&#x30B3;&#x30FC;GR&#x306E;&#x5199;&#x771F;&#x3092;&#x30D1;&#x30BD;&#x30B3;&#x30F3;&#xFF08;Windows 10/11&#x3068;macOS&#xFF09;&#x306B;Wi-Fi&#x3067;&#x8EE2;&#x9001;&#x3059;&#x308B;&#x65B9;&#x6CD5; | ZAKAZUKURI</a></p> <h3 id="-画像を-10MB-以下になるようにいい感じにリサイズする-CLI-ツール">② 画像を 10MB 以下になるようにいい感じにリサイズする <a class="keyword" href="https://d.hatena.ne.jp/keyword/CLI">CLI</a> ツール</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%D6%A5%ED%A5%B0">はてなブログ</a>とか <a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> に画像をアップするとき、ファイルサイズが10MBより大きいとアップロードできないので、いい感じにリサイズしてくれるツールを作った。</p> <p><a href="https://github.com/genya0407/resizer">GitHub - genya0407/resizer</a></p> <p>色々と解像度を変えて画像を変換して、10MB に収まる適切な画像サイズを二分探索するという<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C9%D9%B9%EB%C5%AA">富豪的</a>な作りになっている。 いい感じにギリギリを攻めたサイズにしてくれるので便利ではある。</p> <p>たのしい。カメラそのものよりも楽しんでいる</p> <h2 id="善福寺公園に行った"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C1%B1%CA%A1%BB%FB%B8%F8%B1%E0">善福寺公園</a>に行った</h2> <p>吉祥寺の近くにある公園に行った。</p> <p><a href="https://www.tokyo-park.or.jp/park/format/index010.html">&#x5584;&#x798F;&#x5BFA;&#x516C;&#x5712;&#xFF5C;&#x516C;&#x5712;&#x3078;&#x884C;&#x3053;&#x3046;&#xFF01;</a></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229004103.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229004241.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>帰りは家まで歩いて返った。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231229/20231229002402.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="アドベントカレンダーの記事書いた"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%C9%A5%D9%A5%F3%A5%C8%A5%AB%A5%EC%A5%F3%A5%C0%A1%BC">アドベントカレンダー</a>の記事書いた</h2> <p>新しい展開として、作った gem を会社の org に置いて会社のブログで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%C9%A5%D9%A5%F3%A5%C8%A5%AB%A5%EC%A5%F3%A5%C0%A1%BC">アドベントカレンダー</a>の記事を書くということをやってみた。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineering.dena.com%2Fblog%2F2023%2F12%2Fmysql_rewinder%2F" title="MysqlRewinder という gem を作った | BLOG - DeNA Engineering" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineering.dena.com/blog/2023/12/mysql_rewinder/">engineering.dena.com</a></cite></p> <p>割と読まれたみたいで良かったです。</p> <h2 id="Rails-アプリを最適化した"><a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> アプリを最適化した</h2> <p>会社で <a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> アプリをガッツリ最適化しようということで、ガッツリ最適化した。 思わぬところに信じられない最適化の余地があったりするので、やってみると案外成果は出るものかもしれんと思いました。</p> <h2 id="大掃除した">大掃除した</h2> <p>だいたい2人日ぐらい見ておかないと大掃除の完遂は難しいということがわかった。</p> <h2 id="読んだ本">読んだ本</h2> <ul> <li><a href="https://www.amazon.co.jp/dp/B091H57DMW?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">仕事を教えることになったら読む本</a></li> <li><a href="https://www.amazon.co.jp/dp/B06XKB4GNV?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">「オルグ」の鬼 労働組合は誰のためのものか (講談社+α文庫)</a></li> </ul> <h2 id="今月のまとめ">今月のまとめ</h2> <iframe src="https://social.genya0407.link/@genya0407/111658724042736119/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe> <script src="https://social.genya0407.link/embed.js" async="async"></script> threetea0407 2023年11月を振り返る hatenablog://entry/6801883189061803168 2023-11-30T23:12:12+09:00 2023-11-30T23:12:12+09:00 Gem を作った github.com MySQL を利用したテストにおいて、テストケース間で DB の状態がリークしないようにデータを消す Gem である MysqlRewinder を作りました。 先行研究としては amatsuda/database_rewinder が存在するが、① 設計上 Rails の内部構造に強く依存しており頻繁に壊れる ② fork したプロセスで書き込みしたテーブルのデータが消されない という問題があり、Gem を自前で作るに至った。 手元のプロジェクトに導入した感じだと DatabaseRewinder と同等ぐらいの速度は出てるので、テスト内で fork … <h2 id="Gem-を作った">Gem を作った</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgenya0407%2Fmysql_rewinder" title="GitHub - genya0407/mysql_rewinder" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/genya0407/mysql_rewinder">github.com</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> を利用したテストにおいて、テストケース間で DB の状態がリークしないようにデータを消す Gem である MysqlRewinder を作りました。</p> <p>先行研究としては <a href="https://github.com/amatsuda/database_rewinder">amatsuda/database_rewinder</a> が存在するが、① 設計上 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> の内部構造に強く依存しており頻繁に壊れる ② fork したプロセスで書き込みしたテーブルのデータが消されない という問題があり、Gem を自前で作るに至った。</p> <p>手元のプロジェクトに導入した感じだと DatabaseRewinder と同等ぐらいの速度は出てるので、テスト内で fork してる人は使ってみてください。</p> <h2 id="新しいキーボードを買った">新しいキーボードを買った</h2> <p><a href="https://www.amazon.co.jp/dp/B098NS87PF?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin"><img src="https://m.media-amazon.com/images/I/41I0t9Qn-uS._SL500_.jpg" alt="ロジクール ERGO K860 エルゴノミック スプリット キーボード bluetooth Unifying Windows Mac ワイヤレスキーボード ワイヤレス 無線 パームレスト 国内正規品 グラファイト" title="ロジクール ERGO K860 エルゴノミック スプリット キーボード bluetooth Unifying Windows Mac ワイヤレスキーボード ワイヤレス 無線 パームレスト 国内正規品 グラファイト" class="asin"></a></p> <p>Good</p> <ul> <li>キーが分割されているので肩を広く使える</li> <li>一体型なのでキーボードの位置が安定する(片方だけ動いちゃうみたいなことがない)</li> <li>中央が盛り上がった形状であることから上半身が自然な体勢になる</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D1%A1%BC%A5%E0%A5%EC%A5%B9%A5%C8">パームレスト</a>(手首置き)が思いの外よい</li> </ul> <p>Bad</p> <ul> <li>JIS 配列</li> <li>テンキーがついてるのでスペースが無駄になる</li> <li>もうちょっと打鍵感がほしい</li> </ul> <p>総評としては、なかなか良いが、もっとよくなれるだろうという感じ。 プログラミングよりは事務作業向きかもしれない。</p> <p>似たようなコンセプトの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE">プログラマ</a>用のキーボードがあればいいのになとは思う。</p> <h2 id="吉祥寺動物園に行った">吉祥寺動物園に行った</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fpark.tachikawaonline.jp%2Fzoo%2F48_inogashira.htm" title="井の頭自然文化園(動物園)|TOKYOおでかけガイド" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://park.tachikawaonline.jp/zoo/48_inogashira.htm">park.tachikawaonline.jp</a></cite></p> <p>都心にこれがあるのはなかなかすごいと思った。</p> <p>モルモットが可愛かったです。あとコウモリは怖い。</p> <h2 id="駒沢オリンピック公園に行った"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%F0%C2%F4%A5%AA%A5%EA%A5%F3%A5%D4%A5%C3%A5%AF%B8%F8%B1%E0">駒沢オリンピック公園</a>に行った</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.tokyo-park.or.jp%2Fpark%2Fformat%2Findex040.html" title="駒沢オリンピック公園|公園へ行こう!" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.tokyo-park.or.jp/park/format/index040.html">www.tokyo-park.or.jp</a></cite></p> <p>広くて気持ちよかった。サイクリングコースがあったので自転車でも行ってみたい。</p> <h2 id="あすけんをはじめた">あすけんをはじめた</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231126/20231126120514.png" width="1200" height="213" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>自分はビタミン・ミネラルを摂っておらず、脂質を摂りすぎているということがわかった。</p> <h2 id="A列車で行こうExp-をプレイ"><a class="keyword" href="https://d.hatena.ne.jp/keyword/A%CE%F3%BC%D6%A4%C7%B9%D4%A4%B3%A4%A6">A列車で行こう</a>Exp.+ をプレイ</h2> <p><a href="https://www.amazon.co.jp/dp/B07TS7CKLV?tag=genya040704-22+&amp;linkCode=ogi&amp;th=1&amp;psc=1">A列車で行こうExp.+(エクスプレス プラス) - PS4</a></p> <p>ものすごい時間が溶けてる。こういうのは解説プレイ動画をじっくり見てからやるべきだったという感が強い。</p> <h2 id="リングフィットアドベンチャーを再開">リングフィットアド<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC">ベンチャー</a>を再開</h2> <p>身体が弱体化しすぎという危機感を覚え、リングフィットアド<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC">ベンチャー</a>を再開した。</p> <p><a href="https://www.amazon.co.jp/dp/B07XV8VSZT?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin"><img src="https://m.media-amazon.com/images/I/512+B+W+XmL._SL500_.jpg" alt="リングフィット アドベンチャー -Switch" title="リングフィット アドベンチャー -Switch" class="asin"></a></p> <p>引っ越したことによりモニターと居住空間のレイアウトが改善され、プレイしやすくなって良い。</p> <h2 id="僕は昇給した">僕は昇給した</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B0%A6%BC%D2%C0%BA%BF%C0">愛社精神</a>が高まるとき</p> <h2 id="読んだ本">読んだ本</h2> <ul> <li><a href="https://www.amazon.co.jp/dp/4062817330?tag=genya040704-22+&amp;linkCode=ogi&amp;th=1&amp;psc=1">すらすら読める風姿花伝 (講談社+α文庫)</a></li> <li><a href="https://www.amazon.co.jp/dp/B0C52SFYDC?tag=genya040704-22+&amp;linkCode=ogi&amp;th=1&amp;psc=1">自作OSで学ぶマイクロカーネルの設計と実装</a></li> <li><a href="https://www.amazon.co.jp/dp/4041026148?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">完全版 社会人大学人見知り学部 卒業見込 (角川文庫)</a></li> <li><a href="https://www.amazon.co.jp/dp/B09MY8SF1C?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">ナナメの夕暮れ (文春文庫)</a></li> <li><a href="https://www.amazon.co.jp/dp/B08KQ6MWXR?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">表参道のセレブ犬とカバーニャ要塞の野良犬 (文春文庫)</a></li> </ul> <h2 id="視聴した番組">視聴した番組</h2> <ul> <li><a href="https://www.nhk-ondemand.jp/goods/G2023132228SA000/">NHK&#x30AA;&#x30F3;&#x30C7;&#x30DE;&#x30F3;&#x30C9; | &#x306D;&#x307B;&#x308A;&#x3093;&#x3071;&#x307B;&#x308A;&#x3093; &#x30E4;&#x30D0;&#x3044;&#x8077;&#x5834;&#x306E;&#x63A1;&#x7528;&#x62C5;&#x5F53;&#x8005;</a></li> <li><a href="https://www.nhk-ondemand.jp/goods/G2023131985SA000/">NHK&#x30AA;&#x30F3;&#x30C7;&#x30DE;&#x30F3;&#x30C9; | &#xFF2E;&#xFF28;&#xFF2B;&#x30B9;&#x30DA;&#x30B7;&#x30E3;&#x30EB; &#x8ABF;&#x67FB;&#x5831;&#x9053;&#x30FB;&#x65B0;&#x4E16;&#x7D00; &#xFF26;&#xFF49;&#xFF4C;&#xFF45;&#xFF11; &#x4E2D;&#x56FD;&ldquo;&#x7D4C;&#x6E08;&#x5931;&#x901F;&rdquo;&#x306E;&#x771F;&#x5B9F;</a></li> <li><a href="https://www.nhk-ondemand.jp/goods/G2023127672SA000/">NHK&#x30AA;&#x30F3;&#x30C7;&#x30DE;&#x30F3;&#x30C9; | &#xFF2E;&#xFF28;&#xFF2B;&#x30B9;&#x30DA;&#x30B7;&#x30E3;&#x30EB; &#x56FD;&#x5BB6;&#x4E3B;&#x5E2D; &#x7FD2;&#x8FD1;&#x5E73;</a></li> </ul> <h2 id="雑感">雑感</h2> <p>色々と充実した一ヶ月だったように思う。自転車は乗れてない。</p> <h2 id="今月のまとめ">今月のまとめ</h2> <iframe src="https://social.genya0407.link/@genya0407/111389448586184803/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe> <script src="https://social.genya0407.link/embed.js" async="async"></script> threetea0407 2023年9月/10月を振り返る hatenablog://entry/6801883189054453208 2023-10-30T01:51:58+09:00 2023-10-30T01:51:58+09:00 9月分を書き忘れていたので合併号です。 学生時代のバイト先の飲み会 大学生の時、 株式会社スプーキーズ という会社でプログラマのアルバイトをしていたのですが、そのメンバーの人たちと飲み会をし、二次会までついていって色々と話しました。 エモかった。 八景島シーパラダイスに行った 八景島シーパラダイスに行った。 www.seaparadise.co.jp ペンギン 魚群(イワシ?) 広くてなかなか良かった。アクセスは悪い。 ちょうどその頃はスプラトゥーン3のコラボイベントが開催されていた(写真撮り忘れた)。 www.seaparadise.co.jp インターネットが開通した 9月上旬にやっとイン… <p>9月分を書き忘れていたので合併号です。</p> <h2 id="学生時代のバイト先の飲み会">学生時代のバイト先の飲み会</h2> <p>大学生の時、 <a href="https://www.spookies.co.jp/">株式会社スプーキーズ</a> という会社で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE">プログラマ</a>のアルバイトをしていたのですが、そのメンバーの人たちと飲み会をし、二次会までついていって色々と話しました。 エモかった。</p> <h2 id="八景島シーパラダイスに行った"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C8%AC%B7%CA%C5%E7%A5%B7%A1%BC%A5%D1%A5%E9%A5%C0%A5%A4%A5%B9">八景島シーパラダイス</a>に行った</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C8%AC%B7%CA%C5%E7%A5%B7%A1%BC%A5%D1%A5%E9%A5%C0%A5%A4%A5%B9">八景島シーパラダイス</a>に行った。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.seaparadise.co.jp%2Findex.html" title="海・島・生きもののテーマパーク|横浜・八景島シーパラダイス - YOKOHAMA HAKKEIJIMA SEA PARADISE" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.seaparadise.co.jp/index.html">www.seaparadise.co.jp</a></cite></p> <p><figure class="figure-image figure-image-fotolife" title="ペンギン"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231029/20231029215446.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ペンギン</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="魚群(イワシ?)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231029/20231029215510.jpg" width="900" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>魚群(<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%EF%A5%B7">イワシ</a>?)</figcaption></figure></p> <p>広くてなかなか良かった。アクセスは悪い。</p> <p>ちょうどその頃は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%E9%A5%C8%A5%A5%A1%BC%A5%F3">スプラトゥーン</a>3のコラボイベントが開催されていた(写真撮り忘れた)。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.seaparadise.co.jp%2Fspecial%2Fsplatoon3_seapara%2F" title="スプラトゥーン3 × 横浜・八景島シーパラダイス イカしたヤツらの夏祭り" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.seaparadise.co.jp/special/splatoon3_seapara/">www.seaparadise.co.jp</a></cite></p> <h2 id="インターネットが開通した">インターネットが開通した</h2> <p>9月上旬にやっとインターネットが開通した。 生活するのに困るほどではないけどちょっと遅い(v6プラスにしてもやや遅い)ので、プロバイダの変更も必要かもしれんと思い始めた。</p> <p>余談だが、povo の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C6%A5%B6%A5%EA%A5%F3%A5%B0">テザリング</a>の体感がなかなか良く、回線速度もよかった(というか <a class="keyword" href="https://d.hatena.ne.jp/keyword/docomo">docomo</a> の回線が弱すぎる)ので、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MNP">MNP</a>の手続きをする気になったら Ahamo から povo に乗り換えようと思っている。</p> <h2 id="上野の国立科学博物館に行って特別展海を見た">上野の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CE%A9%B2%CA%B3%D8%C7%EE%CA%AA%B4%DB">国立科学博物館</a>に行って特別展「海」を見た</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fumiten2023.jp%2F" title="特別展「海 ―生命のみなもと―」Special Exhibition The OCEAN -The Origin of Life" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://umiten2023.jp/">umiten2023.jp</a></cite></p> <p>夏休みの?キッズがたくさんいた。 深海の展示がよかった。心惹かれるものがある。</p> <h2 id="デジカメを買えなかった">デジカメを買えなかった</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.ricoh-imaging.co.jp%2Fjapan%2Fproducts%2Fgr-3%2F" title="RICOH GR III / GR IIIx / デジタルカメラ / 製品 | RICOH IMAGING" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.ricoh-imaging.co.jp/japan/products/gr-3/">www.ricoh-imaging.co.jp</a></cite></p> <p>カメラほしいなと思ってこれを買おうと思ったんですけど、どこも品薄でヨドバシドットコムで3ヶ月待ちとか書いてあってツラい。 注文するだけしたけど届くのはいつになることやらというところです。</p> <h2 id="長崎旅行">長崎旅行</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgenya0407.hatenadiary.jp%2Fentry%2F2023%2F10%2F30%2F012341" title="長崎旅行 - 不眠日記" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://genya0407.hatenadiary.jp/entry/2023/10/30/012341">genya0407.hatenadiary.jp</a></cite></p> <h2 id="アーマードコア6をトロコンした"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%DE%A1%BC%A5%C9%A5%B3%A5%A2">アーマードコア</a>6をトロコンした</h2> <iframe src="https://social.genya0407.link/@genya0407/111199598685608711/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe> <script src="https://social.genya0407.link/embed.js" async="async"></script> <p>感想</p> <ul> <li>ストーリーや登場人物の具合が、クサすぎず淡白すぎず好みだった</li> <li>ボスやミッションを突破するために<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EA">アセンブリ</a>を工夫するのがとにかく楽しい</li> </ul> <p>トロコンしたらやることなくなったのでもう遊んでないです。 一応オンライン対戦はあるけど、純然たるタイマンしかないのであんまりやる気にならない。 PvE とかあったら死ぬほどやったかもしれない。</p> <h2 id="トゥルーマンショーを見た"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%A5%A5%EB%A1%BC%A5%DE%A5%F3%A1%A6%A5%B7%A5%E7%A1%BC">トゥルーマン・ショー</a>を見た</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgenya0407.hatenadiary.jp%2Fentry%2F2023%2F10%2F20%2F212310" title="『トゥルーマン・ショー』を観た - 不眠日記" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://genya0407.hatenadiary.jp/entry/2023/10/20/212310">genya0407.hatenadiary.jp</a></cite></p> <h2 id="麻雀を再開">麻雀を再開</h2> <p>コロナが始まった直後ぐらいに夜な夜な <a href="https://mahjongsoul.com/">雀魂</a> をやっていた時期があったのだが、ふとしたきっかけでオンライン麻雀をやるようになった。 以前やっていたときよりも勝てなくなっている気がするので、とりあえず点数計算の方法を勉強しています。</p> <h2 id="Kaigi-on-Rails-に参加した">Kaigi on <a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> に参加した</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkaigionrails.org%2F2023%2F" title="Kaigi on Rails 2023" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://kaigionrails.org/2023/">kaigionrails.org</a></cite></p> <ul> <li>Q まだ <a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> で消耗してるの?</li> <li>A はい</li> </ul> <p>社内で <a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> やってる他のエンジニアの人達と一緒に Kaigi on <a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> に現地参加しました。</p> <ul> <li>同僚と参戦できた <ul> <li>少なくとも孤立という悲しい事態は避けられる</li> <li>講演内容に対して<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B4%B6%C1%DB%C0%EF">感想戦</a>ができて楽しい</li> </ul> </li> <li>講演者の方に声かけたり名刺を交換したり X アカウントを交換したりできた <ul> <li>自分も社会人なので臆せずに話しかけに行けたということはありそう</li> <li>講演に関連した社内の事例とかをお話して、お互いに理解を深めるみたいなこともできたと思う</li> </ul> </li> <li>会場をきょろきょろしてたら、学生時代の知り合いや昔の同僚に思いがけず遭遇した <ul> <li>世界は狭い...というやつ</li> </ul> </li> <li>対面コミュニケーションの効果を感じることができた <ul> <li>知ってる人Aと知らない人Bが話しているところで、知ってる人Aに話しかけることにより、知らない人Bと知り合いになることができる</li> <li>この体験は Web 会議やチャットでは実現が難しい</li> <li>懇親会に限らず、人間関係ってそういうところあるよなあ...などと思うのであった</li> </ul> </li> <li>企業ブースや参加者の工夫がすごかった <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/NFC">NFC</a>タグが仕込んであるキーホルダーに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>をかざすと X のユーザーが自動で開かれる、という体験をしたときは本気でびっくりした</li> <li>色々と新しくなってるんだなあという気持ち</li> </ul> </li> </ul> <p>どうしても学生時代に Rubykaigi に参加したときのことと比べてしまうのだが、講演の理解度という意味でも、断然有意義な体験だった。 学生時代の Rubykaigi も精進のモチベーションになったという意味では悪くなかったと思いますけどね。</p> <h2 id="荻窪を歩いた"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B2%AE%B7%A6">荻窪</a>を歩いた</h2> <p>これは角川庭園の水琴窟です</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20231030/20231030012834.jpg" width="900" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.city.suginami.tokyo.jp%2Fshisetsu%2Fkouen%2F02%2Fogikubo%2F1007132.html" title="角川庭園" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.city.suginami.tokyo.jp/shisetsu/kouen/02/ogikubo/1007132.html">www.city.suginami.tokyo.jp</a></cite></p> <h2 id="9月10月のまとめ">9月/10月のまとめ</h2> <iframe src="https://social.genya0407.link/@genya0407/111318564887880635/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe> <script src="https://social.genya0407.link/embed.js" async="async"></script> threetea0407 2023年8月を振り返る hatenablog://entry/820878482963864686 2023-09-02T01:29:46+09:00 2023-09-02T01:29:46+09:00 引っ越し 引っ越ししました!!!!! social.genya0407.link 家からオフィスまでドアツードアで30分ちょっとぐらいのところ。 所要時間はそこまで変わらないんだけど、京王線に乗らなくていいので体力の消耗度合いが全然低い。 荷造りが大変すぎて断捨離を始めました。 人生がときめく片づけの魔法 改訂版作者:近藤麻理恵河出書房新社Amazon インターネットが使えない 様々な事情があり、引越し後に光回線が開通しない状態が続いている。 今は povo のテザリングで生活してます。非常につらい。 「もののけ姫」はこうして生まれた と もののけ姫 を観た 「もののけ姫」はこうして生まれた。… <h2 id="引っ越し">引っ越し</h2> <p>引っ越ししました!!!!!</p> <p><iframe src="https://social.genya0407.link/@genya0407/110876469366589692/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://social.genya0407.link/embed.js" async="async"></script><cite class="hatena-citation"><a href="https://social.genya0407.link/@genya0407/110876469366589692">social.genya0407.link</a></cite></p> <p>家からオフィスまで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%A2%A5%C4%A1%BC%A5%C9%A5%A2">ドアツードア</a>で30分ちょっとぐらいのところ。 所要時間はそこまで変わらないんだけど、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%FE%B2%A6%C0%FE">京王線</a>に乗らなくていいので体力の消耗度合いが全然低い。</p> <p>荷造りが大変すぎて断捨離を始めました。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07NDC3CSP?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/319lTDeXTML._SL500_.jpg" class="hatena-asin-detail-image" alt="人生がときめく片づけの魔法 改訂版" title="人生がときめく片づけの魔法 改訂版"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07NDC3CSP?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">人生がときめく片づけの魔法 改訂版</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%B6%E1%C6%A3%CB%E3%CD%FD%B7%C3" class="keyword">近藤麻理恵</a></li><li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B2%CF%BD%D0%BD%F1%CB%BC%BF%B7%BC%D2">河出書房新社</a></li></ul><a href="https://www.amazon.co.jp/dp/B07NDC3CSP?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <h2 id="インターネットが使えない">インターネットが使えない</h2> <p>様々な事情があり、引越し後に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%F7%B2%F3%C0%FE">光回線</a>が開通しない状態が続いている。 今は povo の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C6%A5%B6%A5%EA%A5%F3%A5%B0">テザリング</a>で生活してます。非常につらい。</p> <h2 id="もののけ姫はこうして生まれたともののけ姫を観た">「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A4%E2%A4%CE%A4%CE%A4%B1%C9%B1">もののけ姫</a>」はこうして生まれた と <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A4%E2%A4%CE%A4%CE%A4%B1%C9%B1">もののけ姫</a> を観た</h2> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B00005Q5C6?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/5142Y7vmqVL._SL500_.jpg" class="hatena-asin-detail-image" alt="「もののけ姫」はこうして生まれた。 [DVD]" title="「もののけ姫」はこうして生まれた。 [DVD]"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B00005Q5C6?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">「もののけ姫」はこうして生まれた。 [DVD]</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">アーティスト:</span><a href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%BF%A5%B8%A5%AA%A5%B8%A5%D6%A5%EA" class="keyword">スタジオジブリ</a></li><li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A6%A5%A9%A5%EB%A5%C8%A1%A6%A5%C7%A5%A3%A5%BA%A5%CB%A1%BC%A1%A6%A5%B8%A5%E3%A5%D1%A5%F3">ウォルト・ディズニー・ジャパン</a>株式会社</li></ul><a href="https://www.amazon.co.jp/dp/B00005Q5C6?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B00EODU3H4?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/415F9czYoLL._SL500_.jpg" class="hatena-asin-detail-image" alt="もののけ姫 [Blu-ray]" title="もののけ姫 [Blu-ray]"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B00EODU3H4?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">もののけ姫 [Blu-ray]</a></p><ul class="hatena-asin-detail-meta"><li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A6%A5%A9%A5%EB%A5%C8%A1%A6%A5%C7%A5%A3%A5%BA%A5%CB%A1%BC%A1%A6%A5%B8%A5%E3%A5%D1%A5%F3">ウォルト・ディズニー・ジャパン</a>株式会社</li></ul><a href="https://www.amazon.co.jp/dp/B00EODU3H4?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>非常に良かったです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A4%E2%A4%CE%A4%CE%A4%B1%C9%B1">もののけ姫</a>も面白かったけど、「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A4%E2%A4%CE%A4%CE%A4%B1%C9%B1">もののけ姫</a>」はこうして生まれた の方が面白かった。 声優のアフレコ風景が良かった。</p> <h2 id="アーマードコア6"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%DE%A1%BC%A5%C9%A5%B3%A5%A2">アーマードコア</a>6</h2> <p>チャプター1までクリアしました。非常に良いです。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0C3LXS6ZG?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41ZphK7co3L._SL500_.jpg" class="hatena-asin-detail-image" alt="【PS5】ARMORED CORE Ⅵ FIRES OF RUBICON" title="【PS5】ARMORED CORE Ⅵ FIRES OF RUBICON"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0C3LXS6ZG?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">【PS5】ARMORED CORE Ⅵ FIRES OF RUBICON</a></p><ul class="hatena-asin-detail-meta"><li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%ED%A5%E0%A5%BD%A5%D5%A5%C8%A5%A6%A5%A7%A5%A2">フロムソフトウェア</a></li></ul><a href="https://www.amazon.co.jp/dp/B0C3LXS6ZG?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%BB%A5%F3%A5%D6%A5%EB">アセンブル</a>を工夫する楽しさがキチンと作り込まれてる感じがある。</p> <p><iframe src="https://social.genya0407.link/@genya0407/110967999352238906/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://social.genya0407.link/embed.js" async="async"></script><cite class="hatena-citation"><a href="https://social.genya0407.link/@genya0407/110967999352238906">social.genya0407.link</a></cite></p> <h2 id="今月の雑感">今月の雑感</h2> <p>引っ越しにエネルギーを割きすぎてそれ以外のことが何もできてない。9月は色々やっていきたい。</p> <p><iframe src="https://social.genya0407.link/@genya0407/110946083188755537/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://social.genya0407.link/embed.js" async="async"></script><cite class="hatena-citation"><a href="https://social.genya0407.link/@genya0407/110946083188755537">social.genya0407.link</a></cite></p> threetea0407 2023年07月を振り返る hatenablog://entry/820878482954362870 2023-08-01T00:10:31+09:00 2023-08-01T00:10:31+09:00 mastodon 移行 もはや Twitter はほとんど見てません(と言いつつたまに見てるが) そういうわけで、引き続きよろしくお願いいたします。 social.genya0407.link Twitter と違って気軽に bot 等を立てられるのはすごくいいなと思った。 出社 毎日出社してます。体がだんだん適応してきた。 引っ越し 8月中旬頃に高井戸のあたりに引っ越します。 オフィスまでドア to ドア30分の世界観が実現される予定。 マイナンバーカードと格闘した 感想: 日本にはプラットフォーム様(Apple)のケツを舐める根性が足りない とはいえ諸々の手続きがスマホで完結するのはとって… <h3 id="mastodon-移行"><a class="keyword" href="https://d.hatena.ne.jp/keyword/mastodon">mastodon</a> 移行</h3> <p>もはや <a class="keyword" href="https://d.hatena.ne.jp/keyword/Twitter">Twitter</a> はほとんど見てません(と言いつつたまに見てるが)</p> <p>そういうわけで、引き続きよろしくお願いいたします。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsocial.genya0407.link%2F%40genya0407" title="genya0407 (@genya0407@social.genya0407.link)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://social.genya0407.link/@genya0407">social.genya0407.link</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Twitter">Twitter</a> と違って気軽に <a class="keyword" href="https://d.hatena.ne.jp/keyword/bot">bot</a> 等を立てられるのはすごくいいなと思った。</p> <h3 id="出社">出社</h3> <p>毎日出社してます。体がだんだん適応してきた。</p> <h3 id="引っ越し">引っ越し</h3> <p>8月中旬頃に高井戸のあたりに引っ越します。 オフィスまでドア to ドア30分の世界観が実現される予定。</p> <h3 id="マイナンバーカードと格闘した"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%CA%A5%F3">マイナン</a>バーカードと格闘した</h3> <p>感想:</p> <ul> <li>日本にはプラットフォーム様(<a class="keyword" href="https://d.hatena.ne.jp/keyword/Apple">Apple</a>)のケツを舐める根性が足りない</li> <li>とはいえ諸々の手続きが<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>で完結するのはとっても便利</li> </ul> <h3 id="COVID-19">COVID-19</h3> <p>月初あたりに熱が出たので病院に行ったら COVID-19 でした。 せいぜい 37.5℃ぐらいまでしか熱は上がらなかったし、今は完全に回復していますが、最後の方は血中酸素濃度が90%ぐらいまで下がったりしててすごかった。</p> <h3 id="神代植物公園深大寺"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%C0%C2%E5%BF%A2%CA%AA%B8%F8%B1%E0">神代植物公園</a>・<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%BC%C2%E7%BB%FB">深大寺</a></h3> <p>そろそろ多摩にもお別れですので、多摩の観光施設には行っておこうと思って行きました。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.tokyo-park.or.jp%2Fjindai%2F" title="神代植物公園へ行こう!" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.tokyo-park.or.jp/jindai/">www.tokyo-park.or.jp</a></cite></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20230731/20230731230926.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20230731/20230731230935.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20230731/20230731230945.jpg" width="900" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>夏!!!!!!!!!って感じで良かったです。風は涼しかった。</p> <h3 id="東京タワー及び隅田川">東京タワー及び<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%F9%C5%C4%C0%EE">隅田川</a></h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20230731/20230731231342.jpg" width="675" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20230731/20230731231356.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20230731/20230731231405.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>河川の土木感ほんとすき</p> <h3 id="読んだ本">読んだ本</h3> <ul> <li><a href="https://www.amazon.co.jp/dp/4101005133?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">細雪(中) (新潮文庫)</a></li> <li><a href="https://www.amazon.co.jp/dp/4101005141?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">細雪 (下) (新潮文庫)</a></li> <li><a href="https://www.amazon.co.jp/dp/B0BLVJ324Q?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">イヌはなぜ愛してくれるのか 「最良の友」の科学 (ハヤカワ文庫NF)</a></li> </ul> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%BA%D9%C0%E3">細雪</a>をついに読み終わりましたがとても良かった。一生三女の見合いをしてるだけなんだけどなんでこんなに面白いんだろうな。</p> <h3 id="耳をすませば観た"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%BC%AA%A4%F2%A4%B9%A4%DE%A4%BB%A4%D0">耳をすませば</a>観た</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C0%BB%C0%D8%BA%F9%A5%F6%B5%D6">聖蹟桜ヶ丘</a>に住んでるうちに観ておくかと思って<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BC%AA%A4%F2%A4%B9%A4%DE%A4%BB%A4%D0">耳をすませば</a>を観たが、あまりにも、あまりにも<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C0%BB%C0%D8%BA%F9%A5%F6%B5%D6">聖蹟桜ヶ丘</a>だったので涙ちょちょぎれた。 オープニングの電車とか駅のシーンがもう完全に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C0%BB%C0%D8%BA%F9%A5%F6%B5%D6">聖蹟桜ヶ丘</a>でした。</p> <h3 id="ポケモンスリープ"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DD%A5%B1%A5%E2%A5%F3">ポケモン</a>スリープ</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DD%A5%B1%A5%E2%A5%F3">ポケモン</a>スリープをやってわかったことは、自分は普段6時間ぐらいしか寝てない(目が覚める)ということです。 さらなる研究の結果、就寝時間を前倒すと睡眠時間を伸ばせるっぽいということがわかったので、退勤時間を調整したり色々やっている。</p> <h3 id="今月のまとめ">今月のまとめ</h3> <p><iframe src="https://social.genya0407.link/@genya0407/110713248641763671/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://social.genya0407.link/embed.js" async="async"></script><cite class="hatena-citation"><a href="https://social.genya0407.link/@genya0407/110713248641763671">social.genya0407.link</a></cite></p> threetea0407 2023年6月を振り返る hatenablog://entry/820878482946492943 2023-07-02T12:26:47+09:00 2023-07-02T12:26:47+09:00 多摩動物公園に行った www.tokyo-zoo.net 近所にあるけどいったことがなかった動物園に行きました。 個人的見どころ: 「京王動物園線」という、動物公園に行くためだけの路線がある 京王動物園線 - Wikipedia 電車なのに路線に勾配があって楽しい 普通にモノレール使って行くほうが便利だとは思う 死ぬほど広い 上野動物公園の4倍ぐらいの面積があるらしい かつ、山を削った土地に作られているので、全体的に坂が多い この記事書いてて気づいたけど、けものフレンズの「しんざきおにいさん」は多摩動物公園の所属だった サーバルも見ました。可愛かったです。 東京の西の方の出身の人は、小学校の遠… <h4 id="多摩動物公園に行った"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C2%BF%CB%E0%C6%B0%CA%AA%B8%F8%B1%E0">多摩動物公園</a>に行った</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.tokyo-zoo.net%2Fzoo%2Ftama%2F" title="多摩動物公園公式サイト - 東京ズーネット" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.tokyo-zoo.net/zoo/tama/">www.tokyo-zoo.net</a></cite></p> <p>近所にあるけどいったことがなかった動物園に行きました。</p> <p>個人的見どころ:</p> <ul> <li>「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%FE%B2%A6%C6%B0%CA%AA%B1%E0%C0%FE">京王動物園線</a>」という、動物公園に行くためだけの路線がある <ul> <li><a href="https://ja.wikipedia.org/wiki/%E4%BA%AC%E7%8E%8B%E5%8B%95%E7%89%A9%E5%9C%92%E7%B7%9A">&#x4EAC;&#x738B;&#x52D5;&#x7269;&#x5712;&#x7DDA; - Wikipedia</a></li> <li>電車なのに路線に勾配があって楽しい</li> <li>普通にモノレール使って行くほうが便利だとは思う</li> </ul> </li> <li>死ぬほど広い <ul> <li>上野動物公園の4倍ぐらいの面積があるらしい</li> <li>かつ、山を削った土地に作られているので、全体的に坂が多い</li> </ul> </li> <li>この記事書いてて気づいたけど、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A4%B1%A4%E2%A4%CE%A5%D5%A5%EC%A5%F3%A5%BA">けものフレンズ</a>の「しんざきおにいさん」は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C2%BF%CB%E0%C6%B0%CA%AA%B8%F8%B1%E0">多摩動物公園</a>の所属だった <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B5%A1%BC%A5%D0%A5%EB">サーバル</a>も見ました。可愛かったです。</li> </ul> </li> </ul> <p>東京の西の方の出身の人は、小学校の遠足とかでよく行くらしいですね。 小学生をこの過酷な土地に行かせるのは虐待では?と思いますが......</p> <p><figure class="figure-image figure-image-fotolife" title="野生を失ったチーター"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20230702/20230702105837.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>野生を失った<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C1%A1%BC%A5%BF">チータ</a>ー</figcaption></figure></p> <h4 id="出社強化キャンペーン">出社強化キャンペーン</h4> <p>先月から引き続き出社強化キャンペーンを実施しており、6月はほぼ毎日出社していた。 労働時間が短くなったとしても、出社したほうが全体的に進捗が出るように思った。</p> <p>通勤に関しても、往復2時間かかるのは肉体的につらいけど、慣れると案外行けるものだなとは思った。 思ったが.....</p> <h4 id="引っ越し">引っ越し</h4> <p>往復2時間の通勤はしんどいので引っ越すことにしました。 良さげな物件を見つけて審査を投げるところまではやりました。 順調に行けば8月中旬ぐらいには新居に引っ越すことができ、往復1時間で乗換なしの世界観が実現する予定です。</p> <h4 id="読んだ本">読んだ本</h4> <ul> <li><a href="https://www.amazon.co.jp/dp/B01N1SK9TT?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">スプートニクの恋人 (講談社文庫)</a></li> <li><a href="https://www.amazon.co.jp/dp/B01ASX39NS?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">人を動かす 文庫版</a></li> <li><a href="https://www.amazon.co.jp/dp/4101005125?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">細雪(上) (新潮文庫)</a> <ul> <li>いまは(中)を読んでます</li> </ul> </li> <li><a href="https://www.amazon.co.jp/dp/B097P61TVQ?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">初恋、ざらり(1) (コルクスタジオ)</a></li> </ul> <h4 id="ジモティー"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A5%E2%A5%C6%A5%A3">ジモティ</a>ー</h4> <p>引っ越しを見据えて不要になった家具を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A5%E2%A5%C6%A5%A3">ジモティ</a>ーで処分するなどしていたが、受取人が時間通りに指定した地点に来てくれる確率が低くてびっくりしてた。 思うところはあるが危険な主張に突入しそうなので自重しておきます。</p> <h4 id="髪">髪</h4> <p>「作中で死亡する母親の髪型」ができるぐらい髪が伸びてました。 まだまだいくぞ 💪</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftogetter.com%2Fli%2F1422740" title="「どういう原理なんだろう…」&quot;アニメやマンガの作中で死亡する母親のキャラクター&quot;はなぜ皆同じ髪型をしているのか" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://togetter.com/li/1422740">togetter.com</a></cite></p> <h4 id="今月のまとめ">今月のまとめ</h4> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">これは友達に教えてもらった<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B0%E6%A4%CE%C6%AC%B8%F8%B1%E0">井の頭公園</a>のちょっといい道です <a href="https://t.co/NMnRxcrvqv">pic.twitter.com/NMnRxcrvqv</a></p>&mdash; 𝘼𝙧𝙧𝙖𝙮-𝙨𝙖𝙣 (@genya0407) <a href="https://twitter.com/genya0407/status/1672604889455951872?ref_src=twsrc%5Etfw">2023年6月24日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> threetea0407 ブログを統合します hatenablog://entry/820878482937491520 2023-06-01T00:48:24+09:00 2023-06-01T00:48:24+09:00 see: ブログを統合します - 不眠日記 <p>see: <a href="https://genya0407.hatenadiary.jp/entry/2023/06/01/004709">&#x30D6;&#x30ED;&#x30B0;&#x3092;&#x7D71;&#x5408;&#x3057;&#x307E;&#x3059; - &#x4E0D;&#x7720;&#x65E5;&#x8A18;</a></p> threetea0407 2023年5月を振り返る hatenablog://entry/820878482937482809 2023-06-01T00:05:00+09:00 2023-06-01T00:05:00+09:00 オクトパストラベラー2をクリアした クリアしました。裏ボスは倒してないけど、これ以上進めない気がする(飽きたので)。 やはり、戦闘システムはとても良いが、アウトゲームやストーリーはあんまり好みに合わないなあという感想ではある。エルデンリングのときも思ったけど、「自分の思った順序で攻略したい」という欲求が自分にはあんまりない。 ギター買った 渋谷の楽器屋さんでこのギターを買いました(色は違う)。 ヤマハ YAMAHA エレキギター PACIFICA112VM グレー PAC112VM GRY ヤマハ(YAMAHA) Amazon 雰囲気でクリープハイプの曲を弾いてみたり、気まぐれに入門書を読んで… <h2 id="オクトパストラベラー2をクリアした">オクトパストラベラー2をクリアした</h2> <p>クリアしました。裏ボスは倒してないけど、これ以上進めない気がする(飽きたので)。</p> <p>やはり、戦闘システムはとても良いが、アウトゲームやストーリーはあんまり好みに合わないなあという感想ではある。エルデンリングのときも思ったけど、「自分の思った順序で攻略したい」という欲求が自分にはあんまりない。</p> <h2 id="ギター買った">ギター買った</h2> <p>渋谷の楽器屋さんでこのギターを買いました(色は違う)。</p> <div class="freezed"> <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B085MTNGJP?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31GBm+pv53L._SL500_.jpg" class="hatena-asin-detail-image" alt="ヤマハ YAMAHA エレキギター PACIFICA112VM グレー PAC112VM GRY" title="ヤマハ YAMAHA エレキギター PACIFICA112VM グレー PAC112VM GRY" /></a> <div class="hatena-asin-detail-info"> <p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B085MTNGJP?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">ヤマハ YAMAHA エレキギター PACIFICA112VM グレー PAC112VM GRY</a></p> <ul class="hatena-asin-detail-meta"> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E4%A5%DE%A5%CF">ヤマハ</a>(<a class="keyword" href="https://d.hatena.ne.jp/keyword/YAMAHA">YAMAHA</a>)</li> </ul> <a href="https://www.amazon.co.jp/dp/B085MTNGJP?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div> </div> </div> <p>雰囲気で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A1%BC%A5%D7%A5%CF%A5%A4%A5%D7">クリープハイプ</a>の曲を弾いてみたり、気まぐれに入門書を読んで練習してみたり、そういった活動を行っております。</p> <p>肉体を複雑に操作する活動をするのは久しぶりで脳が疲れる。<a href="https://www.amazon.co.jp/gp/video/detail/B00NF9WOV6/ref=atv_dp_share_cu_r">シャル・ウィ・ダンス</a> の序盤で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CC%F2%BD%EA%B9%AD%BB%CA">役所広司</a>がステップを踏めなくてテンパってるときに近い感情を日々味わっている。</p> <p>全体的に学生のときよりもちょっとだけ上達してきてる感触がある。</p> <h2 id="出社強化月間を自主的に開催した">出社強化月間を自主的に開催した</h2> <p>出社は良い(通勤時間を除けば)という結論である。</p> <p>今は全体の出社比率が低く、人口密度が低いために特にそう感じるということはあるかもしれない。</p> <h2 id="読んだ本">読んだ本</h2> <ul> <li> <p><a href="https://www.amazon.co.jp/dp/B07KVTV42B?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">ノルウェイの森 (講談社文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B00FGY4XJY?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">蹴りたい背中 (河出文庫)</a></p> </li> <li> <p><a href="https://www.amazon.co.jp/dp/B08YF4R7F4?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1">狭い部屋でも快適に暮らすための家具配置のルール</a></p> </li> </ul> <h2 id="大岳鍾乳洞">大岳鍾乳洞</h2> <p>鍾乳洞を見に行った。</p> <p><a href="http://ootakecave.com/page1.html">【公式】大岳鍾乳洞 大岳キャンプ場</a></p> <p>なかなか良い観光スポットでした。</p> <p>これは道中にあったエモいトンネルの写真です。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20230531/20230531231348.jpg" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>鍾乳洞本体は険しすぎて写真を取れなかった。</p> <h2 id="オーケストラのコンサートに行った">オーケストラのコンサートに行った</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.nhkso.or.jp%2Fconcert%2F20230507.html%3Fpdate%3D20230507" title="N響 × 青のオーケストラ コンサート | NHK交響楽団" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.nhkso.or.jp/concert/20230507.html?pdate=20230507">www.nhkso.or.jp</a></cite></p> <p>オーケストラのコンサートに行くのは多分人生で初めてだったんだけど思いの外良かった。</p> <h2 id="今月のまとめ">今月のまとめ</h2> <blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"> <p dir="ltr" lang="ja">現場では、実装がテストのバグを発見する!(カスのロシア的倒置法)</p> — 𝘼𝙧𝙧𝙖𝙮-𝙨𝙖𝙣 (@genya0407) <a href="https://twitter.com/genya0407/status/1658419941387939842?ref_src=twsrc%5Etfw">2023年5月16日</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> </p> threetea0407 ABC size を可視化し、闇を払う hatenablog://entry/4207112889942591399 2022-12-07T00:00:00+09:00 2022-12-07T01:06:14+09:00 ABC size を可視化する CLI ツールを実装しました。これにより、人々が rubocop の Metrics/AbcSize と適切に向き合えるようになることを祈ります。 <p>これは <a href="https://qiita.com/advent-calendar/2022/ruby">Ruby Advent Calendar 2022</a> の7日目の記事です。</p> <h2 id="TL-DR">TL; DR</h2> <p>ABC size を可視化する <a href="https://github.com/genya0407/abc_size_visualizer">abc_size_visualizer</a> という gem を作りました。 こんな感じで、メソッドの各行がどれだけ ABC size の増加に貢献しているかを可視化する <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> ツールです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20221205/20221205205411.png" width="1200" height="361" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>赤が代入、黄色がメソッド呼び出し、青が条件式の数を表しています。</p> <p>また、各行の末尾にある <code># &lt;0, 2, 1&gt;</code> のようなコメントも abc_size_visualizer が付加したもので、それぞれ代入、メソッド呼び出し、条件式の大きさを表しています。</p> <h2 id="使い方">使い方</h2> <p>以下のコマンドでインストールできます。</p> <pre class="code bash" data-lang="bash" data-unlink>$ gem install abc_size_visualizer</pre> <p>そして、以下のコマンドで ABC size の可視化を実行します</p> <pre class="code bash" data-lang="bash" data-unlink>$ visualize_abc_size some_code.rb ... # ABC size の情報が付与された some_code.rb の内容が出力される</pre> <h2 id="背景">背景</h2> <p>abc_size_visualizer を実装するに至った背景を説明します。</p> <h3 id="ABC-size-はどのように理解されているか">ABC size はどのように理解されているか</h3> <p>多くの <a class="keyword" href="http://d.hatena.ne.jp/keyword/Rubyist">Rubyist</a> は、以下のような警告によって ABC size を知ります。</p> <pre class="code bash" data-lang="bash" data-unlink>$ bundle exec rubocop (略) Metrics/AbcSize: Assignment Branch Condition size for auth is too high. [20.5/15]</pre> <p>このような警告に直面した人々は、インターネットを検索して回った後、おもむろに <code>.rubocop.yml</code> を編集し、ABC size に関する警告を「解決」します <sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">Metrics/AbcSize</span><span class="synSpecial">:</span> <span class="synIdentifier">Max</span><span class="synSpecial">:</span> <span class="synConstant">100</span> </pre> <p>このように、一般的に ABC size は、</p> <ul> <li>なんかよくわからないけど rubocop が怒ってくるもの</li> <li>(ホントは良くないと分かっているが... という disclaimer 付きで)無視するもの</li> </ul> <p>と理解されています。</p> <h3 id="ABC-size-とは何であるか">ABC size とは何であるか</h3> <p>では、そもそも ABC size とは何でしょうか?</p> <p>ABC size は、代入(assignment)、メソッド呼び出し(branch)、条件式(condition)の出現回数をそれぞれ2乗して和をとり、その<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CA%BF%CA%FD%BA%AC">平方根</a>を計算したもの<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup>であり、コードの「サイズ」を表す指標です<sup id="fnref:3"><a href="#fn:3" rel="footnote">3</a></sup>。</p> <p>定義を述べてもよくわからないと思うので、例を挙げます。以下のメソッドを考えると、</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">def</span> <span class="synIdentifier">some_method</span> <span class="synStatement">if</span> [<span class="synConstant">true</span>, <span class="synConstant">false</span>].sample <span class="synComment"># if による条件式(C)、sample メソッドの呼び出し(B)</span> puts <span class="synSpecial">&quot;#{</span><span class="synConstant">100</span> + <span class="synConstant">200</span><span class="synSpecial">}&quot;</span> <span class="synComment"># puts メソッドの呼び出し(B)、 `+` メソッドの呼び出し(B)</span> <span class="synStatement">end</span> <span class="synPreProc">end</span> </pre> <p>ABC size は、</p> <div class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Csqrt%7BA%5E2%20%2B%20B%5E2%20%2B%20C%5E2%7D%20%3D%20%5Csqrt%7B0%5E2%20%2B%203%5E2%20%2B%201%5E2%7D%20%5Csimeq%203.2%0A" alt=" \displaystyle \sqrt{A^2 + B^2 + C^2} = \sqrt{0^2 + 3^2 + 1^2} \simeq 3.2 "/> </div> <p>となります。</p> <h3 id="ABC-size-の問題点">ABC size の問題点</h3> <p>ABC size の問題点は<strong>分かりづらい</strong>ことにあります。</p> <p>例えば、以下のコードには「メソッド呼び出し(branch)」が何回出現するでしょうか?</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink>puts user_name </pre> <p>正解は..................「わからない」です!</p> <p>なぜなら、 <code>user_name</code> がローカル変数なのかメソッド呼び出しなのか、この一行だけでは判定できないからです。</p> <p>ABC size は、メソッド全体の文脈を加味しないと算出することができず、見た目が全く同じコード断片であっても、文脈によって ABC size は全く異なる可能性があります。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">def</span> <span class="synIdentifier">some_method1</span> puts user_name <span class="synComment"># user_name というローカル変数がないので user_name はメソッド呼び出しである</span> <span class="synPreProc">end</span> <span class="synPreProc">def</span> <span class="synIdentifier">some_method2</span>(<span class="synConstant">user_name</span>:) puts user_name <span class="synComment"># user_name というローカル変数が定義されているので user_name はメソッド呼び出しではない</span> <span class="synPreProc">end</span> </pre> <p>ほかにも、ABC size には非自明なケースがいくつも存在します。</p> <ul> <li>メソッドの引数は assignment としてカウントされるか?</li> <li><code>&amp;.some_method</code> は condition としてカウントされるか? branch としてカウントされるか?</li> <li><code>rescue SomeError =&gt; e</code> は condition としてカウントされるか? assignment としてカウントされるか?</li> <li><code>else</code> は condition としてカウントされるか?</li> <li><code>||=</code> は condition としてカウントされるか? assignment としてカウントされるか?</li> <li><code>&gt;</code> は condition としてカウントされるか? branch としてカウントされるか?</li> <li>デフォルト値付きのキーワード引数は assignment としてカウントされるか? condition としてカウントされるか?</li> </ul> <p>また、これらの非自明なケースはドキュメント等にも特に記載がありません。 そもそも、Rubocop の Metrics/AbcSize では ABC の内訳は表示されません。</p> <p>そのため、ABC size 削減チャレンジは、</p> <ol> <li>当てずっぽうで色々書き換える</li> <li>rubocop に怒られないことを祈る</li> <li>怒られたら 1 に戻る</li> </ol> <p>という方法で行われる<sup id="fnref:4"><a href="#fn:4" rel="footnote">4</a></sup>ことになり、不毛度が高いです。</p> <h3 id="abc_size_visualizer-で-ABC-size-の闇を払う">abc_size_visualizer で ABC size の闇を払う</h3> <p>abc_size_visualizer の存在する世界では、ABC size 削減チャレンジは</p> <ol> <li>ABC size を可視化し、ABC size が大きい原因を理解する</li> <li>問題となっている行を書き換える</li> <li>rubocop に怒られないことを確認する</li> </ol> <p>というステップで実施することができます。</p> <p>また、rubocop に鍛えられて <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> のベストプ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ティスを学ぶように、人間側が ABC size をより深く理解するようになり、abc_size_visualizer がなくても適切に ABC size を削減できるようになっていくことでしょう。</p> <h2 id="実装方法とその問題点">実装方法とその問題点</h2> <p>abc_size_visualizer は、rubocop の内部で利用されている AbcSizeCalculator クラスを流用して実装しています。</p> <ul> <li>abc_size_visuzalizer の実装 <a href="https://github.com/genya0407/abc_size_visualizer/blob/trunk/lib/abc_size_visualizer/abc_size_calculator.rb#L1">https://github.com/genya0407/abc_size_visualizer/blob/trunk/lib/abc_size_visualizer/abc_size_calculator.rb#L1</a></li> <li>元になったクラス <a href="https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb">https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb</a></li> </ul> <p>これは、rubocop の ABC size 計算と実装をなるべく一致させることを意図したものですが、今後 rubocop 側の実装が変更されたときに追従できない可能性が高いため、なんらかのうまいやり方を考える必要があるナアと思っています...</p> <h2 id="まとめ">まとめ</h2> <p>ABC size を可視化する <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> ツールを実装しました。 これにより、rubocop の Metrics/AbcSize の闇を払い、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rubyist">Rubyist</a> の皆さんをサポートすることができれば嬉しいです。</p> <p>また、この <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> ツール自体の実装には闇が多いため、どうにかマトモにしていきたいなと考えています。</p> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> <code># rubocop:disable Metrics/AbcSize</code> によって「解決」することもある。<a href="#fnref:1" rev="footnote">&#8617;</a></li> <li id="fn:2"> <a href="https://nacl-ltd.github.io/2016/02/23/ruby-abcmetrics.html">https://nacl-ltd.github.io/2016/02/23/ruby-abcmetrics.html</a> から。<a href="#fnref:2" rev="footnote">&#8617;</a></li> <li id="fn:3"> <a href="https://en.wikipedia.org/wiki/ABC_Software_Metric">https://en.wikipedia.org/wiki/ABC_Software_Metric</a> から。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>の「複雑さ」や「計算量」を表す値ではないことに注意。<a href="#fnref:3" rev="footnote">&#8617;</a></li> <li id="fn:4"> ABC size を完全に理解している人は別ですが<a href="#fnref:4" rev="footnote">&#8617;</a></li> </ol> </div> threetea0407 Minutus という mruby の Rust バインディングを作った hatenablog://entry/4207112889908682596 2022-08-14T20:24:58+09:00 2022-08-14T20:24:58+09:00 このところ、夏休みの自由研究として「mruby と Rust をいい感じにつなぎこむ」というのをやっていました。 github.com お盆休みのすべてを費やし、なんとか「実用可能」といえそうなレベル*1まで来たので、この記事で簡単に説明したいと思います。 (↓は Matz にリツイートされてとても嬉しかったツイート) めちゃくちゃいい感じに Rust から mruby の中身に手を突っ込めるようになった...(キモいという説はある) pic.twitter.com/WJzmSh2T8o — 𝘼𝙧𝙧𝙖𝙮-𝙨𝙖𝙣 (@genya0407) 2022年8月9日 Minutus とは Minutus… <p>このところ、夏休みの自由研究として「mruby と Rust をいい感じにつなぎこむ」というのをやっていました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgenya0407%2Fminutus" title="GitHub - genya0407/minutus" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/genya0407/minutus">github.com</a></cite></p> <p>お盆休みのすべてを費やし、なんとか「実用可能」といえそうなレベル<a href="#f-8b8c9ab7" name="fn-8b8c9ab7" title="割と限定された用途ではあるが...">*1</a>まで来たので、この記事で簡単に説明したいと思います。</p> <p>(↓は Matz に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C4%A5%A4%A1%BC%A5%C8">リツイート</a>されてとても嬉しかったツイート)</p> <blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"> <p dir="ltr" lang="ja">めちゃくちゃいい感じに Rust から mruby の中身に手を突っ込めるようになった...(キモいという説はある) <a href="https://t.co/WJzmSh2T8o">pic.twitter.com/WJzmSh2T8o</a></p> — 𝘼𝙧𝙧𝙖𝙮-𝙨𝙖𝙣 (@genya0407) <a href="https://twitter.com/genya0407/status/1556995353467322368?ref_src=twsrc%5Etfw">2022年8月9日</a></blockquote> <h3 id="Minutus-とは">Minutus とは</h3> <p>Minutus は、Rust と mruby を<em>いい感じ</em>に連携するためのライブラリです <a href="#f-b275d6d5" name="fn-b275d6d5" title=" Ruby の Rust バインディングである Magnus をめっちゃ参考にしました">*2</a>。</p> <p>Minutus を使うと、Rust の中で mruby の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を eval できます。以下のようなイメージです。</p> <p> <script src="https://gist.github.com/genya0407/191e3c6cc562639657cd6b59c051e6ee.js"> </script> <cite class="hatena-citation"><a href="https://gist.github.com/genya0407/191e3c6cc562639657cd6b59c051e6ee">gist.github.com</a></cite></p> <p><a href="https://github.com/genya0407/minutus/tree/trunk/minutus-test/src">minutus/minutus-test/src</a> を覗くと、他にも多くの例を見ることができます。<br />例えば、mruby のメソッドを Rust から呼べたりします。</p> <p>また、それとは別に、 mrbgem (mruby 用の ライブラリ) を Rust で作ることもできます。<br /><a href="https://github.com/genya0407/minutus/tree/trunk/examples/mruby-polars">minutus/examples/mruby-polars</a> はその例で、Rust のデータフレームである <a href="https://github.com/pola-rs/polars">polars</a> を mruby に(全く不完全な形で)ポートした mrbgem です。</p> <h4 id="Minutus-で達成したいこと">Minutus で達成したいこと</h4> <p>Minutus で達成したいことは2つあります。</p> <ol> <li>最新の mruby (3系) で動く</li> <li>利用者が難しいことを考えずに、とにかく mruby と Rust を接続できる</li> </ol> <p>これらを目指す理由を次で述べます。</p> <h3 id="mruby-と-Rust-を接続する際の問題点">mruby と Rust を接続する際の問題点</h3> <p>人類には「Rust で作ったツールに mruby の <a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a> を入れたい」という普遍的な欲求がある、ということは広く知られています。また、mruby のネイティブ拡張を Rust で書きたいという人類も多く存在していると思います。</p> <p>しかし、その実現には<strong>高いハードル</strong>があります。</p> <p>まず、新しい mruby (3系)で動く Rust / mruby の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">バインディング</a>は存在しません。例えば、<a href="https://github.com/anima-engine/mrusty">mrusty</a> がサポートする mruby のバージョンは 1.2.0 です。現在の mruby の最新のバージョンは 3.1.0 なので、これは相当古いと言えるでしょう<a href="#f-bd67caf8" name="fn-bd67caf8" title="2系に追従しようとしている人もいるが、PR がスルーされている Support mruby 2.1.0 by wordijp · Pull Request #100 · anima-engine/mrusty · GitHub">*3</a><a href="#f-ea30e526" name="fn-ea30e526" title="頑張って探したら他にもあるかもしれないが、サッと見た感じでは無さそう">*4</a>。</p> <p>かといって、mruby の C <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> を、 Rust から直<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%DC%BF%A8">接触</a>るのは茨の道です。</p> <ul> <li>避けられない unsafe 祭り</li> <li>自前で mruby をビルドし、リンクする必要がある</li> <li>mruby の C <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> に習熟する必要がある <ul> <li>つまり、Rust も mruby も書ける、というだけではダメ</li> </ul> </li> <li>mruby の C <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> はマクロの形態で提供されるモノが多く、bindgen が認識してくれない<a href="#f-57f63af8" name="fn-57f63af8" title=" see: Improve macro defined constants to recognize #define CONSTANT ((int) 1) · Issue #316 · rust-lang/rust-bindgen · GitHub">*5</a></li> <li>mruby から取り出した値を Rust の型に変換するのは面倒 <ul> <li>こういうコードを都度書く必要がある<br /><a href="https://github.com/genya0407/minutus/blob/fefbe38bff6e9a1614dda81dd8a34daa0d9d2acc/minutus/src/types/array.rs#L5-L18">minutus/array.rs at fefbe38bff6e9a1614dda81dd8a34daa0d9d2acc · genya0407/minutus · GitHub</a></li> </ul> </li> <li>Rust で作ったバイナリを mrbgem にするのは地味にハマりどころが多い <ul> <li>src/ 以下に C の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>がないと(おそらく) _init 関数が呼ばれない</li> <li>mrbgem のインストール時に <a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>以外のバイナリをビルドさせる正式な方法が(おそらく)ない<a href="#f-2711c507" name="fn-2711c507" title=" つまり、mrbgem をインストールするときに `cargo build --release` を実行させる方法がない ">*6</a></li> </ul> </li> </ul> <p>むかし私もこの問題にぶち当たり、Rust に mruby を埋め込むのを諦めたことがあります。<br /><a href="https://dawn.hateblo.jp/entry/rumap">Rubyで設定を書けるLinux用キーマッパー 「rumap」をRustで作った - さんちゃのblog</a></p> <h3 id="Minutus-の工夫">Minutus の工夫</h3> <p>これらの問題を受け、<strong>最新の mruby</strong> で動き、かつ <strong>mruby の C <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> に詳しくなくても使える</strong> mruby / Rust の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">バインディング</a>が必要だと感じました。</p> <p>特に後者に関しては、以下のような工夫をしています。</p> <ul> <li>mruby と接続するためのコードを生成するマクロを提供する</li> <li>mrbgem のテンプレートを生成する <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> ツールを提供する</li> <li>実装例を提供する  <ul> <li>前述の <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> ツール自体も Minutus の利用例となっています</li> </ul> </li> </ul> <p>これらの工夫により、mruby の C <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> に詳しくない人でも、気軽に mruby と Rust を接続できます。</p> <h3 id="Minutus-の課題">Minutus の課題</h3> <p>「実用可能」といえるレベルにはなったと思うものの、Minutus には様々な問題が残っています。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a> と向き合えていない <ul> <li>基本的には正しく実装できているはずで、利用中の値がうっかり<a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a>されることはおそらく無いが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%E2%A5%EA%A5%EA%A1%BC%A5%AF">メモリリーク</a>していない自信がない</li> <li>evaluate 関数の実装に使っている mruby_load_string の返り値に対する <a class="keyword" href="http://d.hatena.ne.jp/keyword/GC">GC</a> の挙動がよくわからない</li> </ul> </li> <li>mruby のメソッドを Rust から安全に呼ぶ方法がない <ul> <li>現状では、`define_funcall!` で定義した関数で問題が発生するとパニックするようになっている</li> <li>これはマクロを改良したらいい感じにできるはずなので、暇を見つけて直したい</li> </ul> </li> <li>キーワード引数のある mruby のメソッドを呼ぶ方法がない <ul> <li>Rust の関数にはキーワード引数がなく、`define_funcall!` に渡す<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%B0%A5%CD%A5%C1%A5%E3">シグネチャ</a>が Rust の文法からどうしても乖離してしまうため、マクロの文法をどうするか悩んでいる</li> <li>mruby 側に適当なメソッドを定義すれば、そのメソッド経由で呼び出すことはいちおう可能</li> </ul> </li> <li>マクロのエラーメッセージが雑すぎて文法ミスを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>できない <ul> <li>例えば `define_funcall! { fn <a class="keyword" href="http://d.hatena.ne.jp/keyword/hoge">hoge</a>(i64) -&gt; i64 }` は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>が失敗するが、`unexpected token` みたいなエラーメッセージしか出ないため、何が悪いのか全然わからない<a href="#f-fca39eac" name="fn-fca39eac" title="このケースでは、引数名を書き忘れていることが原因。そもそも引数を省略しても動くようにしろという話もある。">*7</a></li> </ul> </li> <li>Rust のモジュールシステムと組み合わせて動かせるのか未検証 <ul> <li>`define_funcall!` や `wrap` と、それらを利用するコードが同じファイルにまとまってるパターンしか検証しておらず、それらが別モジュールに分離したときにうまく動くか怪しい</li> </ul> </li> <li>パフォーマンス <ul> <li>mruby と Rust の世界で値をやり取りするときの関数呼び出しの回数がエグいし、inline 展開とかもしてない</li> </ul> </li> <li>`wrap` で生成したクラスに initialize_copy を実装してないので、たぶん <a class="keyword" href="http://d.hatena.ne.jp/keyword/dup">dup</a> すると死ぬ <ul> <li>やるだけなので暇なときにやる</li> </ul> </li> <li>実用的な例が存在しない <ul> <li>Rust の資産を mruby にポートするというようなことをやって、minutus 自体の欠陥を洗い出して洗練させつつ、minutus 利用者が参考にできるようにしたい</li> <li>例えば actix-web の mruby ラッパーを作りたいなと思っている</li> </ul> </li> </ul> <p>書き出してみると色々ありますが、暇を見つけて一つづつ潰していこうと思っています。</p> <h3 id="おわりに">おわりに</h3> <p>この記事では、mruby と Rust をいい感じに接続するためのライブラリである Minutus について説明しました。</p> <p>個人的には割と良いコンセプトのものができたと思っているので、興味のある方は使っていただけるととても嬉しいです。<br />こんなことはできないの?とか、ここがイケてないとか、ドキュメントが分かりづらいとか、なんでも良いのでフィードバックをいただけると泣いて喜びます。</p> <ul> <li> <p><a href="https://twitter.com/genya0407">https://twitter.com/genya0407</a></p> </li> <li> <p><a href="https://github.com/genya0407/minutus/issues">Issues · genya0407/minutus · GitHub</a></p> </li> </ul> <p>以下の記事で言及されている通り、mruby 界隈は、特に周辺のエコシステムが古くなりつつあり<a href="#f-0bbc91e1" name="fn-0bbc91e1" title=" mrusty が 1.2.0 しかサポートしていないのはその一例 ">*8</a>、厳しい気持ちになることもあるのですが、mruby 自体は触っていてとても面白いので、盛り上げていけたらいいなと思っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fudzura.hatenablog.jp%2Fentry%2F2021%2F03%2F11%2F232423" title="2021年にmrubyを始める皆さまへ - ローファイ日記" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://udzura.hatenablog.jp/entry/2021/03/11/232423">udzura.hatenablog.jp</a></cite></p> <p>以上</p> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p><div class="footnote"> <p class="footnote"><a href="#fn-8b8c9ab7" name="f-8b8c9ab7" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">割と限定された用途ではあるが...</span></p> <p class="footnote"><a href="#fn-b275d6d5" name="f-b275d6d5" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> の Rust <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">バインディング</a>である <a href="https://github.com/matsadler/magnus">Magnus</a> をめっちゃ参考にしました</span></p> <p class="footnote"><a href="#fn-bd67caf8" name="f-bd67caf8" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">2系に追従しようとしている人もいるが、PR がスルーされている <a href="https://github.com/anima-engine/mrusty/pull/100">Support mruby 2.1.0 by wordijp · Pull Request #100 · anima-engine/mrusty · GitHub</a></span></p> <p class="footnote"><a href="#fn-ea30e526" name="f-ea30e526" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">頑張って探したら他にもあるかもしれないが、サッと見た感じでは無さそう</span></p> <p class="footnote"><a href="#fn-57f63af8" name="f-57f63af8" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text"> see: <a href="https://github.com/rust-lang/rust-bindgen/issues/316#issuecomment-417836636" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;">Improve macro defined constants to recognize #define CONSTANT ((int) 1) · Issue #316 · rust-lang/rust-bindgen · GitHub</a></span></p> <p class="footnote"><a href="#fn-2711c507" name="f-2711c507" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text"> つまり、mrbgem をインストールするときに `cargo build --release` を実行させる方法がない </span></p> <p class="footnote"><a href="#fn-fca39eac" name="f-fca39eac" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text">このケースでは、引数名を書き忘れていることが原因。そもそも引数を省略しても動くようにしろという話もある。</span></p> <p class="footnote"><a href="#fn-0bbc91e1" name="f-0bbc91e1" class="footnote-number">*8</a><span class="footnote-delimiter">:</span><span class="footnote-text"> mrusty が 1.2.0 しかサポートしていないのはその一例 </span></p> </div> threetea0407 Ruby の拡張ライブラリを、Rust を使ってお手軽に実装する hatenablog://entry/13574176438101402092 2022-06-12T15:21:59+09:00 2022-06-12T15:21:59+09:00 magnus というcrate を利用すると、超簡単に Ruby の拡張ライブラリが実装できます。 具体的には、Rust 側の記述はこんな感じになります。 use magnus::{define_class, function, method, prelude::*, Error}; #[magnus::wrap(class = "Point")] struct Point { x: isize, y: isize, } impl Point { fn new(x: isize, y: isize) -> Self { Self { x, y } } fn x(&self) -> isize … <p><a href="https://crates.io/crates/magnus">magnus</a> というcrate を利用すると、超簡単に <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> の拡張ライブラリが実装できます。</p> <p>具体的には、Rust 側の記述はこんな感じになります。</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">use</span> <span class="synPreProc">magnus</span><span class="synSpecial">::</span>{define_class, function, method, <span class="synPreProc">prelude</span><span class="synSpecial">::</span><span class="synType">*</span>, Error}; <span class="synPreProc">#[magnus::wrap(class = </span><span class="synConstant">&quot;Point&quot;</span><span class="synPreProc">)]</span> <span class="synStatement">struct</span> <span class="synIdentifier">Point</span> { x: <span class="synType">isize</span>, y: <span class="synType">isize</span>, } <span class="synStatement">impl</span> Point { <span class="synStatement">fn</span> <span class="synIdentifier">new</span>(x: <span class="synType">isize</span>, y: <span class="synType">isize</span>) <span class="synStatement">-&gt;</span> <span class="synType">Self</span> { <span class="synType">Self</span> { x, y } } <span class="synStatement">fn</span> <span class="synIdentifier">x</span>(<span class="synType">&amp;</span><span class="synConstant">self</span>) <span class="synStatement">-&gt;</span> <span class="synType">isize</span> { <span class="synConstant">self</span>.x } <span class="synStatement">fn</span> <span class="synIdentifier">y</span>(<span class="synType">&amp;</span><span class="synConstant">self</span>) <span class="synStatement">-&gt;</span> <span class="synType">isize</span> { <span class="synConstant">self</span>.y } <span class="synStatement">fn</span> <span class="synIdentifier">distance</span>(<span class="synType">&amp;</span><span class="synConstant">self</span>, other: <span class="synType">&amp;</span>Point) <span class="synStatement">-&gt;</span> <span class="synType">f64</span> { (((other.x <span class="synStatement">-</span> <span class="synConstant">self</span>.x).<span class="synIdentifier">pow</span>(<span class="synConstant">2</span>) <span class="synStatement">+</span> (other.y <span class="synStatement">-</span> <span class="synConstant">self</span>.y).<span class="synIdentifier">pow</span>(<span class="synConstant">2</span>)) <span class="synStatement">as</span> <span class="synType">f64</span>).<span class="synIdentifier">sqrt</span>() } } <span class="synPreProc">#[magnus::init]</span> <span class="synStatement">fn</span> <span class="synIdentifier">init</span>() <span class="synStatement">-&gt;</span> <span class="synType">Result</span><span class="synStatement">&lt;</span>(), Error<span class="synStatement">&gt;</span> { <span class="synStatement">let</span> class <span class="synStatement">=</span> <span class="synIdentifier">define_class</span>(<span class="synConstant">&quot;Point&quot;</span>, <span class="synType">Default</span><span class="synSpecial">::</span><span class="synIdentifier">default</span>())<span class="synSpecial">?</span>; class.<span class="synIdentifier">define_singleton_method</span>(<span class="synConstant">&quot;new&quot;</span>, <span class="synPreProc">function!</span>(<span class="synPreProc">Point</span><span class="synSpecial">::</span>new, <span class="synConstant">2</span>))<span class="synSpecial">?</span>; class.<span class="synIdentifier">define_method</span>(<span class="synConstant">&quot;x&quot;</span>, <span class="synPreProc">method!</span>(<span class="synPreProc">Point</span><span class="synSpecial">::</span>x, <span class="synConstant">0</span>))<span class="synSpecial">?</span>; class.<span class="synIdentifier">define_method</span>(<span class="synConstant">&quot;y&quot;</span>, <span class="synPreProc">method!</span>(<span class="synPreProc">Point</span><span class="synSpecial">::</span>y, <span class="synConstant">0</span>))<span class="synSpecial">?</span>; class.<span class="synIdentifier">define_method</span>(<span class="synConstant">&quot;distance&quot;</span>, <span class="synPreProc">method!</span>(<span class="synPreProc">Point</span><span class="synSpecial">::</span>distance, <span class="synConstant">1</span>))<span class="synSpecial">?</span>; <span class="synConstant">Ok</span>(()) } </pre> <p>これをビルドすると、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 側に Point クラスが生えます。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> の世界 と Rust の世界 の間で相互に自動で型が変換されていてすごいですね。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink>point_1 = <span class="synType">Point</span>.new(<span class="synConstant">0</span>, <span class="synConstant">0</span>) point_2 = <span class="synType">Point</span>.new(<span class="synConstant">100</span>, <span class="synConstant">100</span>) point_1.distance(point_2) <span class="synComment"># =&gt; 141.4213562373095</span> </pre> <p>この記事では、渡された文字列をシャッフルする <a href="https://github.com/genya0407/shuffle">Shuffle</a> という gem の実装を通じて、Rust で拡張ライブラリを作る方法を説明します。</p> <p>なお、実行環境は <a class="keyword" href="http://d.hatena.ne.jp/keyword/macOS">macOS</a> Monterey 12.4、CPU は M1 Pro です。とはいえ、他の環境でも似たような手順で行けると思います。</p> <h2>gem を作る</h2> <p>拡張ライブラリを入れるための gem を作っていきます。</p> <h3>gem を初期化する</h3> <pre class="code lang-sh" data-lang="sh" data-unlink>$ bundle gem shuffle $ <span class="synStatement">cd</span> shuffle </pre> <h3>諸々のバージョンを揃える</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> のバージョンを 3.1.1 以上にする必要があります<a href="#f-5790a3b7" name="fn-5790a3b7" title="RubyGems 3.3.11 を利用するため">*1</a>。 2022/06/12 現在では、 3.1.2 を入れておけば良いと思います。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ rbenv install <span class="synConstant">3</span>.<span class="synConstant">1</span>.<span class="synConstant">2</span> $ rbenv <span class="synStatement">local</span> <span class="synConstant">3</span>.<span class="synConstant">1</span>.<span class="synConstant">2</span> </pre> <p>また、Rubyems のバージョンを 3.3.11 以上にする必要があります<a href="#f-6900569c" name="fn-6900569c" title="Rust extension support を利用するため [https://blog.rubygems.org/2022/04/07/3.3.11-released.html:title]">*2</a>。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ gem <span class="synSpecial">-v</span> <span class="synConstant">3</span>.<span class="synConstant">3</span>.<span class="synConstant">15</span> <span class="synComment"># 古い場合はバージョンを上げる</span> $ gem update <span class="synSpecial">--system</span> </pre> <h3>Rust をセットアップする</h3> <p><a href="https://rustup.rs/">rustup</a> を利用して適当に Rust をセットアップします。 1.57.0 以上をインストールしてください<a href="#f-1e2ea706" name="fn-1e2ea706" title="magnus がサポートしているのが 1.57.0 以上なので [https://github.com/matsadler/magnus#compatibility:title]">*3</a>。</p> <h3>gemspec を適当に埋める</h3> <p><code>shuffle.gemspec</code> の TODO になっている欄を適当に埋めます。埋めないとこの先の作業ができません。</p> <p>参考: <a href="https://github.com/genya0407/shuffle/commit/5563f9c0fd19078a3e69e222736997570aae3d9b">fix gemspec &middot; genya0407/shuffle@5563f9c &middot; GitHub</a></p> <h2>Rust で拡張ライブラリを作る</h2> <p>いよいよ拡張ライブラリを作っていきます。</p> <h3>拡張ライブラリをビルドする環境を作る</h3> <p>まず、拡張ライブラリをビルドするのに必要な gem を <a class="keyword" href="http://d.hatena.ne.jp/keyword/dependency">dependency</a> として追加します。 <code>shuffle.gemspec</code> に以下を書き加え、bundle install を実行します。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink> spec.add_dependency <span class="synSpecial">&quot;</span><span class="synConstant">rake-compiler</span><span class="synSpecial">&quot;</span> spec.add_dependency <span class="synSpecial">&quot;</span><span class="synConstant">rb_sys</span><span class="synSpecial">&quot;</span> </pre> <pre class="code lang-sh" data-lang="sh" data-unlink>$ bundle install </pre> <p>次に、<code>bundle exec rake compile</code> で拡張ライブラリをビルドするため、 <code>Rakefile</code> に以下を書き加えます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">rake/extensiontask</span><span class="synSpecial">&quot;</span> <span class="synType">Rake</span>::<span class="synType">ExtensionTask</span>.new(<span class="synSpecial">&quot;</span><span class="synConstant">shuffle</span><span class="synSpecial">&quot;</span>) <span class="synStatement">do</span> |<span class="synIdentifier">ext</span>| ext.lib_dir = <span class="synSpecial">&quot;</span><span class="synConstant">lib/shuffle</span><span class="synSpecial">&quot;</span> ext.source_pattern = <span class="synSpecial">&quot;</span><span class="synConstant">*.{rs,toml}</span><span class="synSpecial">&quot;</span> <span class="synStatement">end</span> </pre> <p>そして、 <code>ext/shuffle/extconf.rb</code> を作成します。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">mkmf</span><span class="synSpecial">&quot;</span> <span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">rb_sys/mkmf</span><span class="synSpecial">&quot;</span> create_rust_makefile(<span class="synSpecial">&quot;</span><span class="synConstant">shuffle/shuffle</span><span class="synSpecial">&quot;</span>) </pre> <p>最後に、 <code>shuffle.gemspec</code> に <code>extconf.rb</code> を認識させるため、以下を書き加えます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink> spec.extensions = [<span class="synSpecial">&quot;</span><span class="synConstant">ext/shuffle/extconf.rb</span><span class="synSpecial">&quot;</span>] </pre> <p>ここまでで、Rust 拡張をビルドする準備が整いました。</p> <p>参考: <a href="https://github.com/genya0407/shuffle/commit/61af06275596b49d2edc6e4d545ccfc40d3e8f0c">setup rake-compiler &amp;&amp; rb_sys &middot; genya0407/shuffle@61af062 &middot; GitHub</a></p> <h3>Rust を書く</h3> <p>まず、 <code>ext/shuffle</code> で Rust のプロジェクトを初期化します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ <span class="synStatement">cd</span> ext/shuffle $ cargo init <span class="synSpecial">--lib</span> $ <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">'</span><span class="synConstant">target</span><span class="synStatement">'</span><span class="synConstant"> </span><span class="synStatement">&gt;&gt;</span> .gitignore </pre> <p>そして、 <code>Cargo.toml</code> に以下を書き加えます。</p> <pre class="code toml" data-lang="toml" data-unlink>[lib] crate-type = [&#34;cdylib&#34;] # 拡張ライブラリを作るために必要 [dependencies] magnus = &#34;0.3&#34; rand = &#34;0.8.5&#34; # 文字列をシャッフルするのに使う</pre> <p>最後に、文字列をシャッフルするクラスを定義しましょう。 <code>ext/shuffle/src/lib.rs</code> に以下を記述します。</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">use</span> <span class="synPreProc">magnus</span><span class="synSpecial">::</span>{define_class, function, method, <span class="synPreProc">prelude</span><span class="synSpecial">::</span><span class="synType">*</span>, Error}; <span class="synStatement">use</span> <span class="synPreProc">rand</span><span class="synSpecial">::</span><span class="synPreProc">seq</span><span class="synSpecial">::</span>SliceRandom; <span class="synPreProc">#[magnus::wrap(class = </span><span class="synConstant">&quot;Shuffle&quot;</span><span class="synPreProc">)]</span> <span class="synStatement">struct</span> <span class="synIdentifier">Shuffle</span> { original: <span class="synType">String</span>, } <span class="synStatement">impl</span> Shuffle { <span class="synStatement">fn</span> <span class="synIdentifier">new</span>(original: <span class="synType">String</span>) <span class="synStatement">-&gt;</span> <span class="synType">Self</span> { <span class="synType">Self</span> { original } } <span class="synStatement">fn</span> <span class="synIdentifier">shuffle</span>(<span class="synType">&amp;</span><span class="synConstant">self</span>) <span class="synStatement">-&gt;</span> <span class="synType">String</span> { <span class="synStatement">let</span> <span class="synType">mut</span> rng <span class="synStatement">=</span> <span class="synPreProc">rand</span><span class="synSpecial">::</span><span class="synIdentifier">thread_rng</span>(); <span class="synStatement">let</span> <span class="synType">mut</span> v <span class="synStatement">=</span> <span class="synConstant">self</span>.original.<span class="synIdentifier">chars</span>().<span class="synIdentifier">collect</span><span class="synSpecial">::</span><span class="synStatement">&lt;</span><span class="synType">Vec</span><span class="synStatement">&lt;</span>_<span class="synStatement">&gt;&gt;</span>(); v.<span class="synIdentifier">shuffle</span>(<span class="synType">&amp;mut</span> rng); v.<span class="synIdentifier">into_iter</span>().<span class="synIdentifier">collect</span>() } } <span class="synPreProc">#[magnus::init]</span> <span class="synStatement">fn</span> <span class="synIdentifier">init</span>() <span class="synStatement">-&gt;</span> <span class="synType">Result</span><span class="synStatement">&lt;</span>(), Error<span class="synStatement">&gt;</span> { <span class="synStatement">let</span> class <span class="synStatement">=</span> <span class="synIdentifier">define_class</span>(<span class="synConstant">&quot;Shuffle&quot;</span>, <span class="synType">Default</span><span class="synSpecial">::</span><span class="synIdentifier">default</span>())<span class="synSpecial">?</span>; class.<span class="synIdentifier">define_singleton_method</span>(<span class="synConstant">&quot;new&quot;</span>, <span class="synPreProc">function!</span>(<span class="synPreProc">Shuffle</span><span class="synSpecial">::</span>new, <span class="synConstant">1</span>))<span class="synSpecial">?</span>; class.<span class="synIdentifier">define_method</span>(<span class="synConstant">&quot;shuffle&quot;</span>, <span class="synPreProc">method!</span>(<span class="synPreProc">Shuffle</span><span class="synSpecial">::</span>shuffle, <span class="synConstant">0</span>))<span class="synSpecial">?</span>; <span class="synConstant">Ok</span>(()) } </pre> <p>そしておもむろに <code>bundle exec rake compile</code> を実行すると、 <code>lib/shuffle/shuffle.bundle</code> というファイルが生成されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ bundle <span class="synStatement">exec</span> rake compile $ file lib/shuffle/shuffle.bundle lib/shuffle/shuffle.bundle: Mach-O 64-bit dynamically linked shared library arm64 </pre> <p>このファイルを <code>require</code> すると、 <code>Shuffle</code> クラスが使えます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ bundle <span class="synStatement">exec</span> irb irb<span class="synPreProc">(</span><span class="synSpecial">main</span><span class="synPreProc">)</span>:001:<span class="synStatement">0&gt;</span> require <span class="synStatement">'</span><span class="synConstant">./lib/shuffle/shuffle</span><span class="synStatement">'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">true</span> irb<span class="synPreProc">(</span><span class="synSpecial">main</span><span class="synPreProc">)</span>:002:<span class="synStatement">0&gt;</span> Shuffle.new<span class="synPreProc">(</span><span class="synStatement">'</span><span class="synConstant">abc</span><span class="synStatement">'</span><span class="synPreProc">)</span>.shuffle <span class="synStatement">=&gt;</span> <span class="synStatement">&quot;</span><span class="synConstant">bca</span><span class="synStatement">&quot;</span> </pre> <p>gem のお作法的には、 <code>lib/shuffle.rb</code> から require_relative しておくのが良いです。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># frozen_string_literal: true</span> <span class="synPreProc">require_relative</span> <span class="synSpecial">&quot;</span><span class="synConstant">shuffle/version</span><span class="synSpecial">&quot;</span> <span class="synPreProc">require_relative</span> <span class="synSpecial">&quot;</span><span class="synConstant">shuffle/shuffle</span><span class="synSpecial">&quot;</span> <span class="synComment"># これを追加</span> <span class="synPreProc">class</span> <span class="synType">Shuffle</span> <span class="synPreProc">class</span> <span class="synType">Error</span> &lt; <span class="synType">StandardError</span>; <span class="synPreProc">end</span> <span class="synComment"># Your code goes here...</span> <span class="synPreProc">end</span> </pre> <p>なお、 <code>bundle gem</code> 実行時に、<code>Shuffle</code> は module として宣言されているので、そのままだと「Shuffle は class じゃなくて module だよ」みたいなエラーが出ます。 これを回避するために、 <code>lib/shuffle.rb</code> や <code>lib/shuffle/version.rb</code> を修正しておきましょう。</p> <p>参考:<a href="https://github.com/genya0407/shuffle/commit/4389ef1411d091f57dbb7f077496fe4d735e7d6b">implement extension &middot; genya0407/shuffle@4389ef1 &middot; GitHub</a></p> <h3>作った gem を利用する</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/RubyGems">RubyGems</a> に publish するのも何なので、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a> からインストールしてみます。 先程の shuffle gem を <a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a> に push しておきつつ、以下のような <code>Gemfile</code> を作って <code>bundle install</code> してみましょう。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ cat Gemfile <span class="synComment"># frozen_string_literal: true</span> <span class="synStatement">source</span> <span class="synStatement">&quot;</span><span class="synConstant">https://rubygems.org</span><span class="synStatement">&quot;</span> gem <span class="synStatement">'</span><span class="synConstant">shuffle</span><span class="synStatement">'</span>, github: <span class="synStatement">'</span><span class="synConstant">genya0407/shuffle</span><span class="synStatement">'</span>, branch: <span class="synStatement">'</span><span class="synConstant">trunk</span><span class="synStatement">'</span> $ bundle install Fetching https://github.com/genya0407/shuffle.git Fetching gem metadata from https://rubygems.org/. Fetching rake <span class="synConstant">13</span>.<span class="synConstant">0</span>.<span class="synConstant">6</span> Installing rake <span class="synConstant">13</span>.<span class="synConstant">0</span>.<span class="synConstant">6</span> Using bundler <span class="synConstant">2</span>.<span class="synConstant">3</span>.<span class="synConstant">15</span> Fetching rake-compiler <span class="synConstant">1</span>.<span class="synConstant">2</span>.<span class="synConstant">0</span> Fetching rb_sys <span class="synConstant">0</span>.<span class="synConstant">9</span>.<span class="synConstant">4</span> Installing rb_sys <span class="synConstant">0</span>.<span class="synConstant">9</span>.<span class="synConstant">4</span> Installing rake-compiler <span class="synConstant">1</span>.<span class="synConstant">2</span>.<span class="synConstant">0</span> Using shuffle <span class="synConstant">0</span>.<span class="synConstant">1</span>.<span class="synConstant">0</span> from https://github.com/genya0407/shuffle.git <span class="synPreProc">(</span><span class="synSpecial">at trunk@f24fe1f</span><span class="synPreProc">)</span> Bundle <span class="synStatement">complete</span>! <span class="synConstant">1</span> Gemfile dependency, <span class="synConstant">5</span> gems now installed. Bundled gems are installed into <span class="synSpecial">`./vendor`</span> $ bundle <span class="synStatement">exec</span> irb irb<span class="synPreProc">(</span><span class="synSpecial">main</span><span class="synPreProc">)</span>:001:<span class="synStatement">0&gt;</span> require <span class="synStatement">'</span><span class="synConstant">shuffle</span><span class="synStatement">'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">true</span> irb<span class="synPreProc">(</span><span class="synSpecial">main</span><span class="synPreProc">)</span>:002:<span class="synStatement">0&gt;</span> Shuffle.new<span class="synPreProc">(</span><span class="synStatement">'</span><span class="synConstant">abcdefg</span><span class="synStatement">'</span><span class="synPreProc">)</span>.shuffle <span class="synStatement">=&gt;</span> <span class="synStatement">&quot;</span><span class="synConstant">gdecafb</span><span class="synStatement">&quot;</span> </pre> <p>このように、 gem として利用できることが確認できました。 なお、利用者側の環境にも Rust が必要である点に注意して下さい。</p> <h2>magnus に関する雑感</h2> <p><a href="https://crates.io/crates/magnus">magnus</a> の型変換がちゃんとしててすごいと思いました。 感動ポイントを箇条書きします<a href="#f-a78334e1" name="fn-a78334e1" title="筆者は拡張ライブラリを真面目に書いたことがないので、的はずれなことを言っている可能性も高いですが...">*4</a>。</p> <ul> <li>関数の型宣言を見て、勝手に型変換をやってくれる <ul> <li><a href="https://github.com/matsadler/magnus#type-conversions">GitHub - matsadler/magnus: Ruby bindings for Rust</a> の表のとおり、変換の対応付けが妥当。</li> <li>しかも、変換<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C9%D4%C7%BD">不能</a>な場合は IllegalArgument エラーを raise してくれる。panic したりしない。</li> </ul> </li> <li>型変換ができないときは手動でも変換できる <ul> <li>例えば、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/UTF-8">UTF-8</a> として invalid な <code>Vec&lt;u8&gt;</code> を <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> の String に変換するのも、<code>RString::from_slice(&amp;vec)</code> のように簡単にできる<a href="#f-68da676e" name="fn-68da676e" title="Rust の String は UTF-8 として valid な文字列しか入らない">*5</a>。 <ul> <li>see: <a href="https://github.com/genya0407/reing_print_image/blob/trunk/ext/reing_print_image/src/lib.rs#L32">reing_print_image/lib.rs at trunk &middot; genya0407/reing_print_image &middot; GitHub</a></li> </ul> </li> </ul> </li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> のオブジェクトと Rust の構造体の紐付けを勝手にやってくれる <ul> <li>C拡張みたいに TypedData_Wrap_Struct とかしなくていい。</li> </ul> </li> </ul> <p>全体的に、「そうなってほしい」という思いが達成されていて、おまじないが少ない作りになっているのが良いです。</p> <h2>まとめ</h2> <p>拡張ライブラリを実装したくなる機会は現状ではそれほどないと思いますが、このレベルまで簡単に実装できるようになったことで、Rust の拡張ライブラリを作るという選択肢が一般的になってくると <strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> の将来という観点でも面白い</strong> と思いました。</p> <p>例えば、 <a href="https://github.com/genya0407/reing_print_image">GitHub - genya0407/reing_print_image</a> は、 <a href="https://github.com/genya0407/reing_text2image">GitHub - genya0407/reing_text2image</a> を拡張 gem にしたものです。</p> <p><a href="https://github.com/genya0407/reing_text2image">reing_text2image</a> はもともと、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> で作った <a href="https://rreing.genya0407.link/">自作質問箱</a> でOGPの画像を生成するために作ったツールでした。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">Crystal 別にそんなに好きじゃないし業務で使ってないけど、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> ぽくて速いので競プロするときに使おうかなと思っている <a href="https://t.co/rb9kAJMXeM">https://t.co/rb9kAJMXeM</a> <a href="https://twitter.com/hashtag/reing?src=hash&amp;ref_src=twsrc%5Etfw">#reing</a></p>&mdash; 𝘼𝙧𝙧𝙖𝙮-𝙨𝙖𝙣 (@genya0407) <a href="https://twitter.com/genya0407/status/1530891482760376320?ref_src=twsrc%5Etfw">May 29, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>以前はこれを<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> ツールとして<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> から呼び出して、一度ディスクに画像ファイルとして書き出したものを <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> から read して send_data で返却するという、結構すごい作りをしていました。</p> <p>しかし、Rust を使った拡張 gem が手軽に作れるようになったことで、ファイルを介すことなく、<a class="keyword" href="http://d.hatena.ne.jp/keyword/png">png</a> のバイナリを直接 Rust → <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> で受け渡せるようになりました。</p> <p><a href="https://github.com/genya0407/reing_print_image/blob/trunk/spec/reing_print_image_spec.rb#L17-L21">reing_print_image/reing_print_image_spec.rb at trunk &middot; genya0407/reing_print_image &middot; GitHub</a></p> <p>このように、全体的には <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> や <a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> で実装しておき、無理なところだけ Rust に切り出す、ということができるようになったことで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> が関与できる世界が広がっていく<a href="#f-79548ba3" name="fn-79548ba3" title="あるいは縮小を緩和できる">*6</a>のではないかと思いました。</p> <p>Rust で拡張ライブラリを作るのは思いの外面白かったので、他にもネタを見つけてやっていきたいと思います。</p> <div class="footnote"> <p class="footnote"><a href="#fn-5790a3b7" name="f-5790a3b7" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a class="keyword" href="http://d.hatena.ne.jp/keyword/RubyGems">RubyGems</a> 3.3.11 を利用するため</span></p> <p class="footnote"><a href="#fn-6900569c" name="f-6900569c" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Rust extension support を利用するため <a href="https://blog.rubygems.org/2022/04/07/3.3.11-released.html">3.3.11 Released - RubyGems Blog</a></span></p> <p class="footnote"><a href="#fn-1e2ea706" name="f-1e2ea706" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">magnus がサポートしているのが 1.57.0 以上なので <a href="https://github.com/matsadler/magnus#compatibility">GitHub - matsadler/magnus: Ruby bindings for Rust</a></span></p> <p class="footnote"><a href="#fn-a78334e1" name="f-a78334e1" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">筆者は拡張ライブラリを真面目に書いたことがないので、的はずれなことを言っている可能性も高いですが...</span></p> <p class="footnote"><a href="#fn-68da676e" name="f-68da676e" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">Rust の String は <a class="keyword" href="http://d.hatena.ne.jp/keyword/UTF-8">UTF-8</a> として valid な文字列しか入らない</span></p> <p class="footnote"><a href="#fn-79548ba3" name="f-79548ba3" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">あるいは縮小を緩和できる</span></p> </div> threetea0407 スポットインスタンスで個人開発 Web サーバーを運用する技術 hatenablog://entry/13574176438085524723 2022-05-07T10:00:00+09:00 2022-05-07T11:11:48+09:00 最近の私の個人開発 Web サーバーの運用方法を簡単に説明します。 <p>趣味の Web アプリを廉価にデプロイしたい、という話題が最近盛り上がっています。</p> <ul> <li> <p><a href="https://laiso.hatenablog.com/entry/nope-sql">個人開発のコストはDB次第 - laiso</a></p> </li> <li> <p><a href="https://anond.hatelabo.jp/20220504211823">個人でWEB開発を15年くらいやってる者ですが</a></p> </li> <li> <p><a href="https://k0kubun.hatenablog.com/entry/surplus">個人開発を黒字にする技術 - k0kubun's blog</a></p> </li> </ul> <p>この記事では、最近の私の個人開発 Web サーバーの運用方法を簡単に説明します。</p> <h2>TL; DR</h2> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> のスポット<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を一台借りて、terminate されたときはいい感じに再起動する <ul> <li>データ永続化用の EBS を別途マウントする</li> </ul> </li> <li>そこに docker でアプリをたくさんデプロイして相乗りする <ul> <li>DB も docker で立てる</li> </ul> </li> <li>wildcard 証明書を取って、リバースプロキシに subdomain ベースでルーティング先のコンテナを振り分けさせる</li> <li>料金は諸々合わせて $13/月 ぐらい</li> </ul> <h2>詳解</h2> <h3>スポット<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a></h3> <p><a href="https://aws.amazon.com/jp/ec2/spot/">スポットインスタンス</a> は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>から廉価にEC2サーバーを借りる手段ですが、突然サーバーをシャットダウンされる可能性があります。そのため、スポット<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>1台だけでサービスを運用するのは、通常は許されない行為です<a href="#f-2032fd4b" name="fn-2032fd4b" title="実はオンデマンドインスタンスも突然死ぬことはあるので、オンデマンドインスタンス1台で運用するのもやってはいけないのだが...">*1</a>。</p> <p>しかし、利用者が少なくお金も取ってないような個人開発アプリケーションという文脈では、<strong>多少のダウンタイム(数分/週程度)は十分許容できます</strong>。つまり、スポット<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が落とされても、数分以内に新しい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が立ち上げられれば問題ありません。</p> <p>私は、個人開発サーバーをスポット<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>で運用し、以下のような lambda <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を5分おきに実行するようにしています。</p> <ul> <li>特定のタグがついた EC2 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が存在するかチェックする <ul> <li>存在しない場合は、適切な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>タイプを判定して新しく作る</li> </ul> </li> <li>そのEC2<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>に特定の EIP がアタッチされているかチェックする <ul> <li>アタッチされていない場合はアタッチする</li> </ul> </li> <li>そのEC2<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>に特定の EBS がマウントされているかチェックする <ul> <li>マウントされていない場合はマウントする</li> </ul> </li> <li>そのEC2<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>で dockerd が起動しているかチェックする <ul> <li>起動していない場合は起動する</li> </ul> </li> </ul> <p>これによって、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が terminate されても最長10分程で元の状態に戻ります。</p> <p>ちなみに、大体4ヶ月ぐらい運用していて、実際にこの処理が実行されたのは12回ほどでした。ただ、これには動作確認のため私が手動で terminate したものも含まれます。体感では、 t4g.small を使う限りでは<strong>月に一回程度 terminate されるかどうか</strong>、といったところです。</p> <p>また、Heroku と違って東京リージョンにサーバーを立てられるので、日本からアクセスしたときのレイテンシが体感できるレベルで速いです<a href="#f-9b8a60b1" name="fn-9b8a60b1" title="Heroku はアメリカとヨーロッパにしかサーバーが立てられないので、体感できるレベルでレイテンシがでかい">*2</a><a href="#f-a4fe47e4" name="fn-a4fe47e4" title="例えば  Reing あたりにアクセスすると速さを感じられると思う">*3</a>。</p> <h3>docker でアプリをデプロイ</h3> <p>以下の記事を参考にして、docker compose でアプリをデプロイしています。</p> <p><a href="https://blog.p1ass.com/posts/docker-context/">Docker Contextsを使ってDocker Composeをデプロイする際の注意点 - ぷらすのブログ</a></p> <p>たくさんアプリをデプロイしても、母艦となる EC2 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が汚れないので持続性が高いです。なお、概ね記事通りの構成ですが、いくつか差分があります。</p> <ul> <li>Docker Registry を自前で立てて、そこから image を落としてきてコンテナを起動する構成 <ul> <li>Registry なしの構成はなんかうまく行かなかったので断念</li> <li>Registry も「アプリ」の一つとして運用している</li> <li>ECR は高いので却下</li> </ul> </li> <li>諸々を楽にやるための <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> Tool を作った <ul> <li> <p><a href="https://github.com/genya0407/carrier">GitHub - genya0407/carrier</a></p> <ul> <li>`carrier init` で設定ファイルを生成</li> <li>`carrier release` で image を build / push</li> <li>`carrier deploy` でコンテナを起動</li> </ul> </li> <li>自分以外の人が使うことをあんまり想定してない作りです</li> </ul> </li> </ul> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/RDBMS">RDBMS</a> についても、docker コンテナの一つとしてサービスごとに立ち上げています。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> はメモリの消費量が多かったので<a href="#f-676fbd90" name="fn-676fbd90" title="設定を適切にすれば解決できる気がするが私には無理でした">*4</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/PostgreSQL">PostgreSQL</a> か <a class="keyword" href="http://d.hatena.ne.jp/keyword/SQLite">SQLite</a> を使うようにしています。</p> <h3>リバースプロキシ・<a class="keyword" href="http://d.hatena.ne.jp/keyword/SSL">SSL</a></h3> <p>前述のブログを参考にして、リバースプロキシには <a href="https://caddyserver.com/">Caddy</a> を使っています。適切に設定すると<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EF%A5%A4%A5%EB%A5%C9%A5%AB%A1%BC%A5%C9%BE%DA%CC%C0%BD%F1">ワイルドカード証明書</a>を勝手に取得してくれるすごいやつです。Caddy も docker コンテナとしてデプロイして、 <a href="https://christina04.hatenablog.com/entry/docker-service-discovery">Dockerの埋め込みDNSサーバを使ったService Discovery</a> を利用して各サービスにルーティングしています。</p> <h3>料金について</h3> <p>月々の料金は大体 $13 ぐらいですが、その内訳は、</p> <ul> <li>EBS:$7.5</li> <li>EC2:$4.5</li> <li>Secrets Manager:$0.5</li> <li>Route53:$0.5</li> </ul> <p>という感じで、データの永続化のために契約している EBS の料金が支配的です。</p> <p>EC2 に関しては、t4g.small というかなり弱めの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>タイプを利用しているということもあり、かなり安く抑えられていると思います。アプリが増えてリソースが足りなくなってきたら、もう少し強い<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>に乗り換えても良いかもしれません。</p> <h2>雑感</h2> <ul> <li>可用性を犠牲にし、アプリもDBも全部スポット<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>に立てるという意思決定が出来たのは良かった<br /> <ul> <li>RDS とか借り始めるといきなりお金がかかるようになるので厳しい</li> <li>DynamoDB 等を使うという選択肢もあるが、やはり <a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a> で開発したいという気持ちは強い</li> </ul> </li> <li>サーバーのスペックアップ・スペックダウンが自在に行える点は良い <ul> <li>万が一アプリがバズったときに札束で殴る余地がある</li> <li>さくらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/VPS">VPS</a>とかだと、スペックアップはできてもスペックダウンができないので、一時的なスペックアップという選択肢が取りづらい</li> </ul> </li> <li>arm64 のマシンが使えるのが良い <ul> <li>ローカルの <a class="keyword" href="http://d.hatena.ne.jp/keyword/mac">mac</a> が M1 なので、手元で docker image をビルドする関係上、サーバーも arm64 でないとビルドにかかる時間がえらいことになる</li> <li>前に使っていたさくらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/VPS">VPS</a>には arm64 マシンがなかったような気がする(今はあるかも)</li> </ul> </li> <li>スポット<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>再起動用の lambda をいい感じに作れたのが個人的には気に入っている <ul> <li>冪等なので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が存在するときに動かしても問題なく、定期的に実行すれば良いだけなので、考えることが少なくて済んでいる</li> </ul> </li> <li>EBS の料金が高いのはちょっと意外だった</li> <li>本当にアクセス数が少ないアプリしかないので、もう少しコンスタントにアクセスが来るようになったら成立しなくなる可能性は十分ある <ul> <li>そのときはそのとき考える</li> </ul> </li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/k8s">k8s</a> で似たようなことができるんじゃないかなと思ってるけど、やり方がよくわからないので勉強中です</li> </ul> <p>以上</p><div class="footnote"> <p class="footnote"><a href="#fn-2032fd4b" name="f-2032fd4b" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">実はオンデマンド<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>も突然死ぬことはあるので、オンデマンド<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>1台で運用するのもやってはいけないのだが...</span></p> <p class="footnote"><a href="#fn-9b8a60b1" name="f-9b8a60b1" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Heroku は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%EA">アメリ</a>カとヨーロッパにしかサーバーが立てられないので、体感できるレベルでレイテンシがでかい</span></p> <p class="footnote"><a href="#fn-a4fe47e4" name="f-a4fe47e4" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">例えば  <a href="https://rreing.genya0407.link/">Reing</a> あたりにアクセスすると速さを感じられると思う</span></p> <p class="footnote"><a href="#fn-676fbd90" name="f-676fbd90" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">設定を適切にすれば解決できる気がするが私には無理でした</span></p> </div> threetea0407 「ハイパフォーマンスブラウザネットワーキング」を読んだ hatenablog://entry/13574176438079406496 2022-04-03T15:48:55+09:00 2022-04-03T15:48:55+09:00 ISUCON11を振り返る - さんちゃのblog に書いたとおり、ブラウザ・HTTP周りの知識の少なさを感じていた。この問題を解決するために、「ハイパフォーマンスブラウザネットワーキング」を読んだ。 ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化 作者:Ilya Grigorik オライリージャパン Amazon この本は、単にベストプラクティスを列挙するのではなく、TCP / TLS / WiFi / 3G・4G回線などの通信経路の「原理」あるいは「メンタルモデル」を提供する。そのため、読者が自分で検討する能力を獲得することができる… <p><a href="https://dawn.hateblo.jp/entry/2021/09/05/001027#%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E5%91%A8%E3%82%8AHTTP%E5%91%A8%E3%82%8A%E3%81%AE%E7%9F%A5%E8%AD%98%E3%81%8C%E8%B6%B3%E3%82%8A%E3%81%AA%E3%81%84">ISUCON11を振り返る - さんちゃのblog</a> に書いたとおり、ブラウザ・HTTP周りの知識の少なさを感じていた。この問題を解決するために、「ハイパフォーマンスブラウザネットワーキング」を読んだ。</p> <div class="freezed"> <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/4873116767?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/51x2sA8N+TL._SL500_.jpg" class="hatena-asin-detail-image" alt="ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化" title="ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化" /></a> <div class="hatena-asin-detail-info"> <p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/4873116767?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化</a></p> <ul class="hatena-asin-detail-meta"> <li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/Ilya%20Grigorik" class="keyword">Ilya Grigorik</a></li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%E9%A5%A4%A5%EA%A1%BC%A5%B8%A5%E3%A5%D1%A5%F3">オライリージャパン</a></li> </ul> <a href="https://www.amazon.co.jp/dp/4873116767?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div> </div> </div> <p>この本は、単にベストプ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ティスを列挙するのではなく、<a class="keyword" href="http://d.hatena.ne.jp/keyword/TCP">TCP</a> / <a class="keyword" href="http://d.hatena.ne.jp/keyword/TLS">TLS</a> / <a class="keyword" href="http://d.hatena.ne.jp/keyword/WiFi">WiFi</a> / 3G・4G回線などの通信経路の「原理」あるいは「メンタルモデル」を提供する。そのため、読者が自分で検討する能力を獲得することができる。</p> <p>HTTP/2 や WebSocket、 WebRTC などの新しく導入された<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C8%A5%B3%A5%EB">プロトコル</a>についての解説も厚くされているのだが、その背景となる通信やデ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9">バイス</a>の特性とセットで示されるため、その必要性や意味するところ、導入するべき<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>などが想像しやすい。</p> <p>もちろん単に原理を述べるだけではなく、そこから導き出される具体的な種々のベストプ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ティスもしっかり記載されている。例えば、最新の<a class="keyword" href="http://d.hatena.ne.jp/keyword/TCP">TCP</a>の設定を有効にするためにサーバーの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A1%BC%A5%CD%A5%EB">カーネル</a>をバージョンアップすることの重要性とか、HTTPレイヤのキャッシュの設定方法とか、HTTP/2への乗り換えの案内とか。</p> <p>そのため、「今すぐ役立つパフォーマンス改善の手引」として読むこともできるし、読者の能力を拡張してくれる教科書として読むこともできる。</p> <p>この本を読むことで、自分が運用しているシステムの普段意識しない詳細について知りたくなるだろうし、また、これからソフトウェアを実装する際に意識できるポイントが大きく変わってくると思う。非常に学びが多かったと感じるし、現実的に読み切れる分量の技術書でもあるし、個人的にはオススメの本だ。</p> <p>なお、初版が2014年とやや古いことに起因するためか、2021年の第2版においても 5G や HTTP/3 (QUIC) などのトピックについての言及がないことはやや残念ではある。第3版を楽しみに待っている。</p> threetea0407 自宅の最強の加湿器、あるいは私は如何にして心配するのを止めて加湿器を起動するようになったか hatenablog://entry/13574176438075330600 2022-03-22T00:09:31+09:00 2022-03-22T00:09:31+09:00 最強の加湿器 世の中には「最強の加湿器」を作った人がいる。 最強の加湿器を作った from Arata Sato www.slideshare.net 曰く、「最強の加湿器」は湿度を自動で適切な値に維持してくれる*1。この加湿器が欲しくなったので作ることにした。 作成中に発覚した問題 数時間の作業の結果、自室の湿度を測定するのはラズパイにセンサーを付けたらできたし、その値をもとに加湿器が起動しているべきか停止しているべきかを判定するロジックを組むこともできた。 しかし、実際に加湿器を操作するロジックを組む段になって、問題が発覚した*2。 加湿器を操作する難しさ 自宅にある加湿器は、押し込みボタ… <h3>最強の加湿器</h3> <p>世の中には「最強の加湿器」を作った人がいる。</p> <p><iframe src="https://www.slideshare.net/slideshow/embed_code/key/avyaxVEZGjNdHk" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border: 1px solid #CCC; border-width: 1px; margin-bottom: 5px; max-width: 100%;" allowfullscreen=""> </iframe></p> <div style="margin-bottom: 5px;"><strong> <a href="https://www.slideshare.net/ssuser2a92b11/ss-135416868" title="最強の加湿器を作った" target="_blank" rel="noopener">最強の加湿器を作った</a> </strong> from <strong><a href="https://www.slideshare.net/ssuser2a92b11" target="_blank" rel="noopener">Arata Sato</a></strong></div> <p><cite class="hatena-citation"><a href="https://www.slideshare.net/ssuser2a92b11/ss-135416868">www.slideshare.net</a></cite></p> <p>曰く、「最強の加湿器」は<strong>湿度を自動で適切な値に維持してくれる</strong><a href="#f-75c1f480" name="fn-75c1f480" title="スライド中には他にもいくつか条件があるが、本記事ではこれを定義とする">*1</a>。この加湿器が欲しくなったので作ることにした。</p> <h3>作成中に発覚した問題</h3> <p>数時間の作業の結果、自室の湿度を測定するのはラズパイにセンサーを付けたらできたし、その値をもとに加湿器が起動しているべきか停止しているべきかを判定するロジックを組むこともできた。</p> <p>しかし、実際に加湿器を操作するロジックを組む段になって、問題が発覚した<a href="#f-5fd4f267" name="fn-5fd4f267" title="実際には問題が解決してから判定ロジックを組んでいるのだが、話をわかりやすくするために事実とは異なる時系列を採用している">*2</a>。</p> <h3>加湿器を操作する難しさ</h3> <p>自宅にある加湿器は、押し込みボタンでオンオフを制御するタイプの製品だ。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20220321/20220321225831.png" alt="f:id:threetea0407:20220321225831p:plain" width="214" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>このボタンは内部に状態(押し込まれているか否か)を持っている。そのため、加湿器をオンオフするためには、ソフトウェア側で<strong>ボタンの状態を管理</strong>する必要がある。</p> <p>例えば、加湿器が<strong>オフ</strong>になっているべきだと判定されたとしよう。ボタンが押し込まれて<strong>いる</strong>状態ならボタンを<strong>押す</strong>必要がある。一方で、ボタンが押し込まれて<strong>いない</strong>ならば、<strong>何もしてはいけない</strong>。</p> <p>このように、ボタン経由で加湿器の操作をする場合、「いま、ボタンは押し込まれているか?」という状態をソフトウェア上に保持しなければならない。これでは実装が面倒になる上に、ソフトウェアと実機上での状態が乖離する可能性もある。状態が乖離した場合、それをソフトウェアによって修正することは不可能である。</p> <div class="freezed"> <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07B7NXV4R?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/21w9UgDMp0S._SL500_.jpg" class="hatena-asin-detail-image" alt="SwitchBot スイッチボット スイッチ ボタンに適用 指ロボット スマートホーム ワイヤレス タイマー スマホで遠隔操作 Alexa, Google Home, Siri, IFTTTなどに対応(ハブ必要)" title="SwitchBot スイッチボット スイッチ ボタンに適用 指ロボット スマートホーム ワイヤレス タイマー スマホで遠隔操作 Alexa, Google Home, Siri, IFTTTなどに対応(ハブ必要)" /></a> <div class="hatena-asin-detail-info"> <p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07B7NXV4R?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">SwitchBot スイッチボット スイッチ ボタンに適用 指ロボット スマートホーム ワイヤレス タイマー スマホで遠隔操作 Alexa, Google Home, Siri, IFTTTなどに対応(ハブ必要)</a></p> <ul class="hatena-asin-detail-meta"> <li>スイッチボット(SwitchBot)</li> </ul> <a href="https://www.amazon.co.jp/dp/B07B7NXV4R?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div> </div> </div> <p>ボタンの押し込まれ具合やランプの色から現在の状態を判別することも可能かもしれないが、新しくセンサーを導入する必要があり、管理が煩雑になる。また、湿度の時間<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C8%F9%CA%AC">微分</a>の正負によって加湿器の状態を推定するといったことも検討したが、湿度は加湿器の状態のみに依存するわけではない<a href="#f-577479c5" name="fn-577479c5" title="例えば、室温が上がると湿度は下がる">*3</a>ということもあり、正確に推定するのはかなり難易度が高いと思われた。</p> <p>この問題を解決するために、加湿器のオンオフ操作をボタン経由ではなく、電源経由で行うことにした。</p> <h3>加湿器を操作する最強の方法</h3> <p>加湿器のオンオフ操作を電源経由で行うというのは、ボタンは「入」の状態で固定しておき、加湿器をオフにしたくなったらコンセントを抜き、オンにしたくなったら差す、ということである。</p> <p>実際、コンセントの仮想的な抜き差しを実現するデ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9">バイス</a>は Switch <a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a> から提供されている。</p> <div class="freezed"> <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07TVSGJT4?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31JkdzMuPQL._SL500_.jpg" class="hatena-asin-detail-image" alt="SwitchBot スイッチボット スマートプラグ Wi-Fi コンセント – タイマー 遠隔操作 音声コントロール Alexa Google Home IFTTT Siriに対応" title="SwitchBot スイッチボット スマートプラグ Wi-Fi コンセント – タイマー 遠隔操作 音声コントロール Alexa Google Home IFTTT Siriに対応" /></a> <div class="hatena-asin-detail-info"> <p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07TVSGJT4?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">SwitchBot スイッチボット スマートプラグ Wi-Fi コンセント – タイマー 遠隔操作 音声コントロール Alexa Google Home IFTTT Siriに対応</a></p> <ul class="hatena-asin-detail-meta"> <li>スイッチボット(SwitchBot)</li> </ul> <a href="https://www.amazon.co.jp/dp/B07TVSGJT4?tag=genya040704-22+&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div> </div> </div> <p>このデ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9">バイス</a>は、現在電源がついているかどうかに関わらず「電源をオフにする」と「電源をオンにする」という操作を行うことができる(そういう<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>が生えてる)。そのため、加湿器の現在の状態を気にすることなく、加湿器のオンオフ操作を行うことができる。</p> <h3>最強の加湿器</h3> <p>最強の加湿器が動作する様子がこちらだ。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20220321/20220321235032.png" alt="f:id:threetea0407:20220321235032p:plain" width="1200" height="386" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%B7%A4%AD%A4%A4%C3%CD">しきい値</a>を上回ったり下回ったりしたタイミングで加湿器のオンオフが切り替わり、湿度が反転している様子がわかる。</p> <h3>雑感</h3> <p>問題に気づいたときに、無理やりセンサーでどうにかしたり、煩雑な状態管理の処理を書くのではなく、シンプルかつ堅牢な方法を考えることで対処できたのは良かったと思った。</p> <p>まだ最強の加湿器を運用し始めて日も浅いので、今後も様子を見守りつつ、他のパラメータ(例えばCO2濃度)についても自動で制御されるようにしていきたいと考えている。</p><div class="footnote"> <p class="footnote"><a href="#fn-75c1f480" name="f-75c1f480" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">スライド中には他にもいくつか条件があるが、本記事ではこれを定義とする</span></p> <p class="footnote"><a href="#fn-5fd4f267" name="f-5fd4f267" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">実際には問題が解決してから判定ロジックを組んでいるのだが、話をわかりやすくするために事実とは異なる時系列を採用している</span></p> <p class="footnote"><a href="#fn-577479c5" name="f-577479c5" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">例えば、室温が上がると湿度は下がる</span></p> </div> threetea0407 ブラウザオンラインゲームを Ruby on Rails で作る hatenablog://entry/13574176438040521891 2021-12-21T00:00:00+09:00 2021-12-21T00:00:28+09:00 この記事は、CAMPHOR- Advent Calendar 2021の21日目の記事です。 Ruby on Rails に hotwire-rails という gem を導入すると、ブラウザ・サーバー間の双方向通信がかんたんに実装できます。 それを利用して、ある種のブラウザオンラインゲームを簡単に実装にできるという話と、その一例として、オンラインで対戦できるリバーシを作った話をします。 <p>この記事は、<a href="https://advent.camph.net/">CAMPHOR- Advent Calendar 2021</a>の21日目の記事です。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a> に <a href="https://github.com/hotwired/hotwire-rails">hotwire-rails</a> という gem を導入すると、ブラウザ・サーバー間の双方向通信がかんたんに実装できます。</p> <p>それを利用して、ある種の<strong>ブラウザオンラインゲーム</strong>を簡単に実装にできるという話と、その一例として、オンラインで対戦できる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D0%A1%BC%A5%B7">リバーシ</a>を作った話をします。</p> <h3>作ったもの</h3> <p>オンライン対戦できる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D0%A1%BC%A5%B7">リバーシ</a>を作りました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Freversible.genya0407.net" title="Reversible" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://reversible.genya0407.net">reversible.genya0407.net</a></cite></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20211210/20211210014346.png" alt="f:id:threetea0407:20211210014346p:plain" width="278" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>はこちら:<a href="https://github.com/genya0407/reversible">GitHub - genya0407/reversible</a></p> <p>この Web アプリでは、盤面の状態はサーバーで管理されています。そして、サーバー上で盤面の状態が更新されるたびに、その変更がブラウザに反映されます。例えば、自分が石を打つと、サーバー上の状態が更新され、即座に対戦相手の画面も更新されます。これ<span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;">を実現するために、<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> は一行も書いていません。</span>すべてのロジックは <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> と テンプレートエンジン、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>のみで記述されています<a href="#f-00c746ce" name="fn-00c746ce" title="hotwire-rails が提供するJavaScriptライブラリは動いています。ここではあくまで「私が」JavaScript を書いていない、ということを主張しています。">*1</a>。</p> <p>私のような、<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> をまともに書けないサーバーエンジニアでも、Hotwire を使えばブラウザオンラインゲームを雑に作ることはできるようでした。もっとも、アクション性の高いものを作るのは難しそうですが。</p> <h3>Hotwire とは</h3> <p>Hotwire について詳しく知りたい方は、公式サイトがよくまとまっているので、これを読むのをオススメします。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhotwired.dev%2F" title="HTML Over The Wire | Hotwire" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://hotwired.dev/">hotwired.dev</a></cite></p> <p>非フロントエンドエンジニアがゲームを作るという文脈において重要なのは、サーバー上のイベント起因でブラウザの状態を更新するという操作を、 <strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> を一切書かずに実現できる</strong>ということです。</p> <h3>Hotwire でオンラインゲームを実装する方法</h3> <p>Hotwire では、オンラインゲームを以下のように実現できます。</p> <ol> <li>参加者がゲームのページを開き、WebSocket のコネクションが確立される <ol> <li>hotwire-<a class="keyword" href="http://d.hatena.ne.jp/keyword/rails">rails</a> が提供するヘルパーを使うと1行で書けます</li> <li> <p><a href="https://github.com/genya0407/reversible/blob/trunk/app/views/players/show.html.slim#L11">reversible/show.html.slim at trunk · genya0407/reversible · GitHub</a></p> </li> </ol> </li> <li>参加者が自身の行動を表すHTTPリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トをサーバーに送信する <ol> <li>例えば 「(1, 5) の位置に黒石を置く」のようなPOST リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを送信する<a href="#f-7042fa56" name="fn-7042fa56" title="aタグをクリックしてPOSTが飛ぶのはちょっとトリッキーですが、Rails の仕組みを使って実現してます Rails学習者にrails-ujsの動作説明したら感動された話 - INODEVLOG">*2</a></li> <li> <p><a href="https://github.com/genya0407/reversible/blob/trunk/app/views/application/_board.html.slim#L53">reversible/_board.html.slim at trunk · genya0407/reversible · GitHub</a></p> </li> </ol> </li> <li>サーバーは、サーバー上の盤面の状態を更新する <ol> <li>盤面の状態は <a class="keyword" href="http://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a> や Redis で管理するのが良いでしょう</li> <li> <p><a href="https://github.com/genya0407/reversible/blob/trunk/app/controllers/moves_controller.rb#L24-L48">reversible/moves_controller.rb at trunk · genya0407/reversible · GitHub</a></p> </li> </ol> </li> <li>サーバーは、更新後の状態を元にHTML文字列を生成し、参加者全員の WebSocket コネクションに対して送信する<br /> <ol> <li> <p><a href="https://github.com/genya0407/reversible/blob/trunk/app/controllers/moves_controller.rb#L11">reversible/moves_controller.rb at trunk · genya0407/reversible · GitHub</a></p> </li> </ol> </li> <li>参加者のブラウザは、WebSocket 経由でHTML文字列を受け取り、それを画面に描画する<br /> <ol> <li>hotwire-<a class="keyword" href="http://d.hatena.ne.jp/keyword/rails">rails</a> が提供する <a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> のライブラリがいい感じに処理してくれます</li> </ol> </li> </ol> <p>つまり、ゲームの状態はすべてサーバーが管理し、それを元にHTMLを生成する処理もサーバー側で行います。ブラウザはそれを画面に描画し、操作するためのインターフェースに特化します。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20211210/20211210013049.png" alt="f:id:threetea0407:20211210013049p:plain" width="373" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>一般に、サーバーもフロントも自分で作るのにわざわざ <a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> を定義するのはダルいと言われています<a href="#f-a1194262" name="fn-a1194262" title="要出典">*3</a>。Hotwire なら、<strong>ロジックをサーバーに集約し、密に結合した一つの世界で、ブラウザを含めたすべてをコン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%ED%A1%BC%A5%EB">トロール</a>することができます。</strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> のこちらとあちらで同じロジックを再実装する必要はもうありません。</p> <h3>まとめ</h3> <p>Hotwire を導入することで、普通の <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a> アプリに双方向通信を容易に持ち込むことができます。それを利用し、ブラウザで遊べるオンラインゲームを気軽に作れることがわかりました。</p> <p>もう少し頑張れば、将棋やチェス、麻雀、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A5%BF%A5%F3">カタン</a>のような、より複雑な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DC%A1%BC%A5%C9%A5%B2%A1%BC%A5%E0">ボードゲーム</a>を作ることもできると思います。Hotwire でゲームを作るのは非常に新鮮で楽しいです。皆さんもぜひ体験してみてください!</p><div class="footnote"> <p class="footnote"><a href="#fn-00c746ce" name="f-00c746ce" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">hotwire-<a class="keyword" href="http://d.hatena.ne.jp/keyword/rails">rails</a> が提供する<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>ライブラリは動いています。ここではあくまで「私が」<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> を書いていない、ということを主張しています。</span></p> <p class="footnote"><a href="#fn-7042fa56" name="f-7042fa56" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">aタグをクリックしてPOSTが飛ぶのはちょっとトリッキーですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> の仕組みを使って実現してます <a href="https://www.inodev.jp/entry/2019/12/03/234210" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;">Rails学習者にrails-ujsの動作説明したら感動された話 - INODEVLOG</a></span></p> <p class="footnote"><a href="#fn-a1194262" name="f-a1194262" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">要出典</span></p> </div> threetea0407 ISUCON11を振り返る hatenablog://entry/26006613799795504 2021-09-05T00:10:27+09:00 2021-09-05T03:28:51+09:00 @ebiebievidence と @uni745e の2人と一緒に「ここにチーム名を入れる」というチームを組んで、ISUCON11に出場しました。 結果は予選敗退、最高スコアは45180、最終スコアは40952、公開Leaderboard での順位は 105位でした*1。 この記事では、準備したこと、当日やったこと、振り返りについて述べます。 *1:参考値内での順位は100位でした <p><a href="https://twitter.com/ebiebievidence">@ebiebievidence</a> と <a href="https://twitter.com/uni745e">@uni745e</a> の2人と一緒に「ここにチーム名を入れる」というチームを組んで、ISUCON11に出場しました。</p> <p>結果は予選敗退、最高スコアは45180、最終スコアは40952、<a href="https://portal.isucon.net/">公開Leaderboard</a> での順位は 105位でした<a href="#f-7d923769" name="fn-7d923769" title="参考値内での順位は100位でした">*1</a>。</p> <p>この記事では、準備したこと、当日やったこと、振り返りについて述べます。</p> <ul class="table-of-contents"> <li><a href="#準備したこと">準備したこと</a><ul> <li><a href="#リクルーティング">リクルーティング</a></li> <li><a href="#過去問演習">過去問演習</a></li> <li><a href="#役割分担">役割分担</a></li> <li><a href="#自習">自習</a></li> </ul> </li> <li><a href="#当日やったこと">当日やったこと</a><ul> <li><a href="#1000--1045-マニュアル読みアプリの動作確認">(10:00 ~ 10:45) マニュアル読み、アプリの動作確認</a></li> <li><a href="#1045--1145-諸々のセットアップ">(10:45 ~ 11:45) 諸々のセットアップ</a></li> <li><a href="#1145--1220-ベンチマークログメトリクスの分析方針相談">(11:45 ~ 12:20) ベンチマーク、ログ・メトリクスの分析、方針相談</a></li> <li><a href="#1220--1300-アプリからエラーログが出るようにしようとする">(12:20 ~ 13:00) アプリからエラーログが出るようにしようとする</a></li> <li><a href="#1300--1320-index-を貼る-">(13:00 ~ 13:20) index を貼る ①</a></li> <li><a href="#1320--1345-コードリーディング">(13:20 ~ 13:45) コードリーディング</a></li> <li><a href="#1345--1500-500エラーの原因を追う">(13:45 ~ 15:00) 500エラーの原因を追う</a></li> <li><a href="#1500--1600-index-を貼る-">(15:00 ~ 16:00) index を貼る ②</a></li> <li><a href="#1600--1630-LIMIT-1-をつける">(16:00 ~ 16:30) LIMIT 1 をつける</a></li> <li><a href="#1630--1800-latest_isu_condition-テーブルの導入">(16:30 ~ 18:00) latest_isu_condition テーブルの導入</a></li> <li><a href="#1800--1845-クロージング">(18:00 ~ 18:45) クロージング</a></li> </ul> </li> <li><a href="#振り返り">振り返り</a><ul> <li><a href="#慣れない言語は難しい">慣れない言語は難しい</a></li> <li><a href="#実装は難しい">実装は難しい</a></li> <li><a href="#ブラウザ周りHTTP周りの知識が足りない">ブラウザ周り・HTTP周りの知識が足りない</a></li> <li><a href="#古いログが混ざってしまった件">古いログが混ざってしまった件</a></li> <li><a href="#ロールバック可能性の担保">ロールバック可能性の担保</a></li> <li><a href="#エンバグへの対処">エンバグへの対処</a></li> <li><a href="#再起動試験できた">再起動試験できた</a></li> <li><a href="#ISUCON10-の反省を生かせた">ISUCON10 の反省を生かせた</a></li> <li><a href="#改修の筋道を立てられた">改修の筋道を立てられた</a></li> <li><a href="#解き方が不適切だった説">解き方が不適切だった説</a></li> <li><a href="#情報の集約">情報の集約</a></li> <li><a href="#手早さ">手早さ</a></li> </ul> </li> <li><a href="#総括所感">総括・所感</a></li> </ul> <h3 id="準備したこと">準備したこと</h3> <h4 id="リクルーティング">リクルーティング</h4> <p>ISUCON10 が終了した直後に、インフラエンジニアの @uni745e を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%AF%A5%EB%A1%BC%A5%C8">リクルート</a>しました。 これは、私も @ebiebievidence も<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>・インフラ周りの知識がなさすぎて、まともにチューニングができなかった、という反省を受けての行動です。</p> <p>@uni745e が参戦したことで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>のチューニングが去年に比べて高いレベルで行えたり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>のバージョンアップがスムーズに行ったり、構成変更をゴリゴリできたりといった良い効果がありました。</p> <h4 id="過去問演習">過去問演習</h4> <p>3人で集まって過去問を解きました。 合計で3年分ぐらいの過去問を解きました<a href="#f-884bbcf8" name="fn-884bbcf8" title="流石に8時間フルで解くのは難しいので、初動の数時間に絞ってやりました">*2</a>が、回数をこなしたことで、初動の整理・役割分担をうまくできたと思います。</p> <p>特に今回は @uni745e が ISUCON 初参戦だったため、回数をこなしたのは効果的だったと思います。</p> <h4 id="役割分担">役割分担</h4> <p>普段の仕事内容や、過去問演習の反省を踏まえ、初動・役割分担は以下のような形に落ち着きました。</p> <ul> <li>私 <ul> <li>初動:<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>をgit管理に持ち込んだり、デプロイ方法・ログローテーション方法を確立する</li> <li>役割:下回りを整える + <a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> のチューニング</li> </ul> </li> <li><a href="https://twitter.com/ebiebievidence">@ebiebievidence</a> <ul> <li>初動:<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を読んで、データの流れを把握しつつ、問題のありそうなコードに目星をつける</li> <li>役割:アプリのチューニング</li> </ul> </li> <li><a href="https://twitter.com/uni745e">@uni745e</a> <ul> <li>初動: /etc/hosts を配ったり、各種ログを出したり</li> <li>役割:<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>のチューニング、構成変更</li> </ul> </li> </ul> <p>なお、マニュアルの読解とアプリの動作確認は、全員で集まって画面共有をしながら、一番最初に行うことにしました。 また、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>の分析についても、下回りの整備が終わったタイミングで、全員で画面共有しながら行うことにしました。</p> <h4 id="自習">自習</h4> <p>@ebiebievidence はアプリのプロファイラ周りの勉強をし、@uni745e は <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>のパラメータチューニングを勉強したり構成変更の手順をまとめたりしていたようです。</p> <p>私は、 pt-query-digest の使い方を思い出したり、デプロイ・ログローテーション<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>の雛形を作ったりしていました。</p> <h3 id="当日やったこと">当日やったこと</h3> <p>私がやったことを中心に、当日の行動を時系列で述べます。</p> <h4 id="1000--1045-マニュアル読みアプリの動作確認">(10:00 ~ 10:45) マニュアル読み、アプリの動作確認</h4> <p>画面共有しながら全員で競技マニュアルを読み、その後、アプリのマニュアルを見ながらアプリの動作確認を行いました。</p> <h4 id="1045--1145-諸々のセットアップ">(10:45 ~ 11:45) 諸々のセットアップ</h4> <p>以下を行いました。全部合わせて1時間ぐらいかかりました。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>をgitで管理下に置く</li> <li>デプロイ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を書いて、デプロイできるようにする</li> <li>アプリが吐くログをファイルに出力するようにする <ul> <li>元は標準出力に出てたので見づらかった</li> </ul> </li> <li>fgprof / pprofを導入する</li> <li>ログローテーション<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を書いて、ログローテーションできるようにする</li> </ul> <h4 id="1145--1220-ベンチマークログメトリクスの分析方針相談">(11:45 ~ 12:20) <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>、ログ・メトリクスの分析、方針相談</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>を実行し、ログや各種メトリクスを読んだり、コードを読んだりして以下のことがわかりました。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のCPU使用率が100%である</li> <li>いくつかの<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>が、クライアントの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BF%A5%A4%A5%E0%A5%A2%A5%A6%A5%C8">タイムアウト</a>で切断されている(status code 499)</li> <li>postIsuCondition は9割のデータを保存せずに無視している</li> </ul> <p>これを受けて、以下の方針で行くことにしました。</p> <ul> <li>私 <ul> <li>各種READ系が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BF%A5%A4%A5%E0%A5%A2%A5%A6%A5%C8">タイムアウト</a>してるのをどうにかする</li> <li>ただし、 getTrend を改善して人が増えても捌ききれないので、getTrend は直さない</li> </ul> </li> <li>@ebiebievidence <ul> <li>postIsuCondition が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BF%A5%A4%A5%E0%A5%A2%A5%A6%A5%C8">タイムアウト</a>してるのをどうにかする</li> </ul> </li> <li>@uni745e <ul> <li>少しでもDBにCPUを使わせるため、Web と DB のホストを分ける</li> </ul> </li> </ul> <h4 id="1220--1300-アプリからエラーログが出るようにしようとする">(12:20 ~ 13:00) アプリからエラーログが出るようにしようとする</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トと紐付いた形でエラーログが出るようにしようと頑張ったが、echo の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>周りに不慣れだったことから沼ってしまい、最終的に諦めました。</p> <h4 id="1300--1320-index-を貼る-">(13:00 ~ 13:20) index を貼る ①</h4> <p>はじめは、エンドポイントをひとつひとつ改善していこうと思っていましたが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のCPUがサチってるので任意のクエリが重くなるのは必然だと思い直し、アプリ中の重いクエリに片っ端からindexを貼ることにしました。</p> <h4 id="1320--1345-コードリーディング">(13:20 ~ 13:45) コードリーディング</h4> <p>index を貼るためにクエリダイジェストを取ろうとしたところ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MariaDB">MariaDB</a> が吐くスローログのフォーマットが思っていたものと違っており、pt-query-digest に食わせてもうまく情報が出てくれませんでした。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MariaDB">MariaDB</a>でいい感じにスローログを出す方法がわからなかったので、 @uni745e にお願いして <a class="keyword" href="http://d.hatena.ne.jp/keyword/MariaDB">MariaDB</a> を <a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> に載せ替えてもらうことにしました。</p> <p>そして、pt-query-digest が使えるようになるのを待つ間、アプリのコードを少しでも読んでおこうと思ってコードリーディングをしました。</p> <h4 id="1345--1500-500エラーの原因を追う">(13:45 ~ 15:00) 500エラーの原因を追う</h4> <p>コードリーディングをしながら<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>を alp で分析していたのですが、@ebiebievidence の改修(postIsuConditionでbulk insertするやつ)が入ったあとから、 <code>POST /isu/condition/...</code> で大量の 500 エラーが出ていることがわかりました。 エラーの原因を調査するべく調査しましたが、原因がわからずに1時間ぐらい溶かしてしまいました。</p> <p>結局このエラーは、 @ebiebievidence が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>をしているときに出たエラーログが、正常なアプリのログに混じっていたのが原因だとわかりました。 実際、ログを消したあとに再度<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>を回したら500エラーはきれいに消えました...。</p> <h4 id="1500--1600-index-を貼る-">(15:00 ~ 16:00) index を貼る ②</h4> <p>このタイミングではまだ<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>への載せ替えは完了していなかったので、pt-query-digest を使うのは諦めて、コードを <code>grep 'SELECT'</code> で検索し、出てきたすべてのクエリをさっと見て、重そうなやつにindexを貼りました。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">CREATE</span> <span class="synSpecial">INDEX</span> isu_uuid_timestamp <span class="synSpecial">ON</span> isu_condition (jia_isu_uuid, `timestamp`); </pre> <p>この index は、以下のクエリを改善します。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> * <span class="synSpecial">FROM</span> `isu_condition` <span class="synSpecial">WHERE</span> `jia_isu_uuid` = ? <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> `timestamp` <span class="synSpecial">DESC</span> LIMIT <span class="synConstant">1</span> </pre> <p>このクエリは、 index を貼る前は type: ALLの実行計画になる上に、getIsuList の中にあるループの中で呼ばれているという激ヤバのクエリでした。</p> <p>index を貼ったことで、スコアは 8000 → 23000 に上がりました<a href="#f-a82e76f2" name="fn-a82e76f2" title="web と db を分離したり、Bulk INSERT を導入したりで 2000 → 8000 に上がっていた">*3</a>。</p> <h4 id="1600--1630-LIMIT-1-をつける">(16:00 ~ 16:30) LIMIT 1 をつける</h4> <p>getTrend の loop の中で、以下のようなクエリが実行されていました。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> * <span class="synSpecial">FROM</span> `isu_condition` <span class="synSpecial">WHERE</span> `jia_isu_uuid` = ? <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> timestamp <span class="synSpecial">DESC</span> </pre> <p>しかし、このクエリの結果は、なんと最初の1件しか使われていませんでした。つまり1件しか取得する必要がないということなので、以下のように書き換えました。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> * <span class="synSpecial">FROM</span> `isu_condition` <span class="synSpecial">WHERE</span> `jia_isu_uuid` = ? <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> timestamp <span class="synSpecial">DESC</span> LIMIT <span class="synConstant">1</span> </pre> <p>これによって、スコアは 23000 → 29000 に上がりました。</p> <h4 id="1630--1800-latest_isu_condition-テーブルの導入">(16:30 ~ 18:00) latest_isu_condition テーブルの導入</h4> <p>以下のような「ある椅子の最新の isu_condition を取得する」クエリが発行されている場所がいくつかありました。しかもこのクエリはforループの中で実行されていて、典型的なN+1クエリでした。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> * <span class="synSpecial">FROM</span> `isu_condition` <span class="synSpecial">WHERE</span> `jia_isu_uuid` = ? <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> timestamp <span class="synSpecial">DESC</span> LIMIT <span class="synConstant">1</span> </pre> <p>上記のクエリは、 直前に貼った isu_uuid_timestamp index によってある程度高速に実行されますが、ループの外で実行する(=N+1クエリをやめる)ためには、この構造のテーブルでは厳しいと思いました。</p> <p>この問題を解決するため、 <code>latest_isu_condition</code> テーブルを新たに導入することにしました。</p> <p><code>latest_isu_condition</code> は、ある isu に関する最新の condition を保持するテーブルであり、以下のように利用されます。</p> <ul> <li>postIsuCondition の中で latest_isu_condition に UPSERT する</li> <li>「ある椅子の最新の isu_condition を取得する」というクエリは latest_isu_condition を使って発行する</li> </ul> <p>このようにすることで、PRIMRY KEY に対する検索によって一発でデータを取得でき<a href="#f-b1ad7889" name="fn-b1ad7889" title="latest_isu_condition の PK は jia_isu_uuid にしておく">*4</a>、高速化につながるだろうと考えました。</p> <p>そして、2時間ほどかけて諸々の実装を終え、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>を回したところ、40000 → 45000 ぐらいまでスコアが上がりました<a href="#f-942f2f46" name="fn-942f2f46" title="MySQLのパラメータチューニングと構成変更で、30000 → 40000 に上がっていた">*5</a>。</p> <h4 id="1800--1845-クロージング">(18:00 ~ 18:45) クロージング</h4> <p>終了時刻が近づいてきたのでクロージング処理に移り、各種ログの出力を止め、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>ガチャを行いました。 ところがここで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>が fail し始めました。</p> <p>慌てながら色々と調査した結果、前述の latest_isu_condition が原因で落ちてそうだということがわかったため、latest_isu_condition が入る前までコードを巻き戻し、なんとか一回だけ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>を通し、試合終了となりました。</p> <h3 id="振り返り">振り返り</h3> <p>思いついたことをつらつらと書いていきます。</p> <h4 id="慣れない言語は難しい">慣れない言語は難しい</h4> <p>今回私は、デプロイ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%EB%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">シェルスクリプト</a>で書き、またGoのアプリの改修も行いましたが、どちらも不慣れな言語だったので問題がありました。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%EB%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">シェルスクリプト</a>に関しては、配列の扱いに不慣れだったため、2つのホストにデプロイしているつもりで、1つのホストにしかデプロイできていなかった、という致命的な問題が終盤に発覚しました。 Goに関しては、N+1を直すべくゴリゴリ書こうとしたのですが、普段書かない言語ということもあってかなり手こずりました。</p> <p>デプロイ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>に関しては<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%EB%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">シェルスクリプト</a>を使うのをやめて、使い慣れた <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> を使って Rake Task に書き直しました。 アプリに関しては、言語の選択を考えなおすことや、 @ebiebievidence との役割分担も含めて、色々と考え直したほうが良さそうな感はあります。</p> <h4 id="実装は難しい">実装は難しい</h4> <p>実装は難しく、バグが出ます。難しい実装は更に難しいので、バグが更に出ます。 実装せずに早くできる手段があるならそれを選ぶべきだし、簡単な実装で済ませられるならそうするべきだと思いました。</p> <p>そして、簡単な実装をするためには、そのための知識や準備が必要になるなあと思いました。</p> <h4 id="ブラウザ周りHTTP周りの知識が足りない">ブラウザ周り・HTTP周りの知識が足りない</h4> <p>お仕事でブラウザを直<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%DC%BF%A8">接触</a>ることがないので、キャッシュ周りの知識とかが皆無だということに今回気づいた。 何らか勉強しないとまずいなと思ったので、ひとまずこのブログを頭から読んでいます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.redbox.ne.jp%2F" title="キャッシュ屋ブログ | REDBOX Labo" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://blog.redbox.ne.jp/">blog.redbox.ne.jp</a></cite></p> <p>それに加えて、いま輪読会で読んでる本が読み終わったら、そういう本を一冊ぐらい読んでおこうと思っています。 このへんとか。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.oreilly.co.jp%2Fbooks%2F9784873116761%2F" title="ハイパフォーマンス ブラウザネットワーキング" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.oreilly.co.jp/books/9784873116761/">www.oreilly.co.jp</a></cite></p> <h4 id="古いログが混ざってしまった件">古いログが混ざってしまった件</h4> <p>追う必要のない500 エラーで時間を溶かした回の話。</p> <p>今回作ったログローテーション<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>は、「ログローテーション」と「ログの手元へのコピー」がワンセットになった<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>であり、ログのコピーが遅いために<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>全体が重くなっていた。 そのため、ログローテーションを行うには長い時間がかかり、ログローテーションを行わないモチベーションが生まれていた。 その結果として、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>中に出たアプリログが残ってしまい、正常な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>時のログと混ざってしまった。</p> <p>この問題は、以下の2つの対策によってある程度解決できるだろうと考えています。</p> <ul> <li>ログローテーションと、ログの手元へのコピーを独立して実行できるようにする <ul> <li>これにより、手軽にログローテーションを行えるようになる</li> </ul> </li> <li>デプロイ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>の中でログのローテーションを行うようにしておく <ul> <li>これにより、アプリをデプロイしたのであれば、ログはローテーションされている、ということを担保できます</li> </ul> </li> </ul> <h4 id="ロールバック可能性の担保"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%ED%A1%BC%A5%EB%A5%D0%A5%C3%A5%AF">ロールバック</a>可能性の担保</h4> <p>最後の最後でFailすることに気づいたが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%ED%A1%BC%A5%EB%A5%D0%A5%C3%A5%AF">ロールバック</a>をうまい単位で行えずに、ログを切るコミットも消してしまいました。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>を通った各コミットにタグを打つなどして、サクッと撤退できるようにしておくべきだったと思います。</p> <p>このことが、クロージングのバタつきの一因となったと思いました。</p> <h4 id="エンバグへの対処"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%D0%A5%B0">エンバグ</a>への対処</h4> <p>コードの修正を伴う改修は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%D0%A5%B0">エンバグ</a>確率が高いので、ベンチマーカーが数回通ることを確認してからマージするのが良さそうです。 なぜならば、以下のような場合、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%D0%A5%B0">エンバグ</a>は一発の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>で検出されないからです。</p> <ul> <li>確率で失敗するロジックが存在するケース</li> <li>2回目のデプロイ以降で死ぬようになるケース</li> </ul> <p>そのため、修正をマージする前に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>を数回実行し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%D0%A5%B0">エンバグ</a>している可能性を下げておくのが良さそうです。 ただし、index を追加するだけ、みたいなやつは1発ベンチを通してマージでも良いと思います。</p> <h4 id="再起動試験できた">再起動試験できた</h4> <p>去年は再起動試験をせずにフィニッシュしました。 今年は再起動試験をやって無事なことを確かめました。結果的には運営の人が書いたであろう restart=always があったので問題ありませんでしたが<a href="#f-f478c337" name="fn-f478c337" title="去年ももしかして restart=always 入ってたのかも">*6</a>。</p> <h4 id="ISUCON10-の反省を生かせた">ISUCON10 の反省を生かせた</h4> <p>ISUCON10 での反省点を解消するのが、ISUCON11 における自分の隠れ目標でしたが、解消できたものとできなかったものがあります。</p> <p>解消できたものはこの辺です。</p> <ul> <li>競技開始直後に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>を分析することはできたが、競技中に分析できず、アクセス傾向の変化に気づけなかった</li> <li>作業の依存関係の整理や優先度付けが下手で、ログ分析に着手するのが遅れた</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>のアップグレードに失敗する、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>の秘伝のタレを扱えないなど、インフラ面のスキル不足が露呈した</li> <li>再起動試験に失敗したであろう構成のままフィニッシュしてしまった</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DC%A5%C8%A5%EB%A5%CD%A5%C3%A5%AF">ボトルネック</a>を見つけるのにこだわりすぎて、改修に手を出すのが遅れた</li> <li>テーブルの構造を最適化するような大きな改修を行えなかった</li> </ul> <p>一方で、以下の点については今年も課題が残ったと思いました。</p> <ul> <li>複雑な実装に手を付けて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%D0%A5%B0">エンバグ</a>した</li> <li>見たことのない機能が使われているのを見て脳が停止した</li> </ul> <p>前者に関しては、ベンチを数回回してからマージするフローを採用することで問題を回避できそうな気はしてます。 後者は Conditional GET の話ですが、初手で「Conditional GET は誰もやったことないからやらないことにしよう」という判断をしてしまったのが駄目だったなあと思います。 時間制限がある中でも落ち着いて、新しい知識を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BB%C5%C6%FE">仕入</a>れながら使う、ということができるようになりたいものです。 またその一方で、Webエンジニアとして必要になる知識は、普段から幅広く<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BB%C5%C6%FE">仕入</a>れておく必要があるとも思いました。</p> <h4 id="改修の筋道を立てられた">改修の筋道を立てられた</h4> <p>alpで分析した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>と、マニュアルにあるスコア計算式、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%DE%A1%BC%A5%AF">ベンチマーク</a>終了後のDBの状態、そしてベンチマーカーの出力を睨みつつ、今どういうことが起きていて、それを解消するために、どのような順序で問題を解消していくべきか、という筋道を組み立てることができたのは良かったかなと思います。</p> <p>具体的には以下のような感じです。</p> <pre class="code" data-lang="" data-unlink>アクセスログを分析すると、以下のことがわかる 1. 椅子の状態登録APIで、多くのリクエストがタイムアウトしており、十分な数のデータを保存できていない。そして、新しいデータを保存できないと、得点に繋がらない。 2. 自分の椅子の情報を取得するほとんどのAPIで、リクエストがタイムアウトしており、得点が上昇していない。情報取得APIが直接の得点源であるため、これを改善することで得点が上昇する。 3. トップページのトレンドAPIがタイムアウトしており、新規ユーザーが増加していない。 また、ベンチマーク終了後のDBの状態を見ると、椅子やユーザーの数は少ない(数件〜数十件のオーダー)。 この人数を捌けないのであれば、トレンドAPIを改善して新規ユーザーを流入させても、得点にはつながらないだろう。 したがって、今やるべきことはトレンドAPIの改修ではなく、状態登録APIや情報取得APIの高速化である。 そして、それらのエンドポイントが十分高速化した暁には、トレンドAPIを改善して新規ユーザーを流入させることを考えるべきである。</pre> <p>このような筋道を最初に立てることで、やるべきことが明確になって取り組みやすくなったと思います。 一方で、これは次の項目にも関連しますが、立てた筋道の妥当性に関しては疑問が残ります。</p> <h4 id="解き方が不適切だった説">解き方が不適切だった説</h4> <p>競技終了後に社内でISUCON<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%B6%C1%DB%C0%EF">感想戦</a>をやりました。色々と学びはあったのですが、一番タメになったのは <strong>まずはリソースの飽和を解消すべきである</strong> ということでした。</p> <p>前述の「筋道」で自分は、</p> <pre class="code" data-lang="" data-unlink>トレンドAPIを改善して新規ユーザーを流入させても、得点にはつながらないだろう。 したがって、今やるべきことはトレンドAPIの改修ではなく、状態登録APIや情報取得APIの高速化である。</pre> <p>と書きました。これは、得点の増加という観点からは妥当だったと思います。 しかし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> のCPU使用率が100%になっている状態では任意の<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>が重くなるため、得点に直結する<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を改修しても十分な効果は得られないと考えられます。</p> <p>そのため、得点に直結する<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を直すより前に、リソースの飽和の原因となる<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>(この場合はトレンド<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>やgetIsuCondition)の改修に優先的に取り掛かるべきだったとも思えます<a href="#f-fcff3daf" name="fn-fcff3daf" title="今回は結果的に、リソースの飽和の原因となっているクエリを直すのを優先的にやってましたが">*7</a>。</p> <h4 id="情報の集約">情報の集約</h4> <p>今回は各人の作業状況を、こんな感じで <a class="keyword" href="http://d.hatena.ne.jp/keyword/Google%20drive">Google drive</a> に集約しながら進めました。 これにより、「次に自分が何をやればよいか」「他の人は今何をやっているか」をひと目で知ることができ、見通しが立てやすくなった(不安が小さくなった)と思います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20210829/20210829142651.png" alt="f:id:threetea0407:20210829142651p:plain:w300" width="879" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span></p> <p>また、1時間に1回ぐらいの周期で全員が手を止め、情報を共有したり、次の方針を決めたりしていて、これも良かったです。</p> <h4 id="手早さ">手早さ</h4> <p>行動ログを見るとわかるのですが、 12:20 ~ 15:00 あたりはかなり無をしています。タラレバですが、ここの時間が丸っと削れたとすれば、 <code>latest_isu_condition</code> の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>をすることもできたし、その上でさらにもう一つぐらい改修を入れられたと思います。</p> <p>スローログがうまく取れないという状況にはありましたが、結局スローログを見なくても index は貼れています。 多少効率が悪くなったとしても、もっと早いタイミングで「実質的な効果を持つ施策」に着手するべきだったかもなあと思いました。</p> <p>また、これ以上の順位を取るためにはindexを貼る程度の最適化では難しく、アプリ改修も含めた最適化に手がある必要があります。しかし、そのような最適化に手を出すためには、コードリーディングの速度やコーディングの速度が必要になってくると感じました。</p> <h3 id="総括所感">総括・所感</h3> <p>昨年よりは多少マシになっていて、少なくとも<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%B0%A5%EC">デグレ</a>はしてないと思います。しかし、一昨年→昨年の差分に比べると、昨年→今年の差分からは、自身の成長がスコア上昇に寄与している感覚が少なかったです。</p> <p>一昨年→昨年は、production-ready なWeb開発の経験がないところから、業務経験をバリバリ積んで index の知識が付いたことで、成長を特に感じられたと思います。一方で、昨年→今年はそういう技術的成長がなかったとも言えるかもしれません。もちろん何かしら成長してはいるのですが、ISUCONで役立つ技能はあんまり増えてないような気はしてます。</p> <p>技能が増えてないという感覚を得たことで、来年に向けて勉強したいという気持ちが去年よりも強くあります。とりあえず、今年の予選問題はハイスコアが出るまでチューニングし続けてみようと思いました。</p> <p>最後に、一緒にISUCONに出てくれた @ebiebievidence と @uni745e、ありがとうございました!</p> <div class="footnote"> <p class="footnote"><a href="#fn-7d923769" name="f-7d923769" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://isucon.net/archives/56021246.html">参考値内</a>での順位は100位でした</span></p> <p class="footnote"><a href="#fn-884bbcf8" name="f-884bbcf8" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">流石に8時間フルで解くのは難しいので、初動の数時間に絞ってやりました</span></p> <p class="footnote"><a href="#fn-a82e76f2" name="f-a82e76f2" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">web と db を分離したり、Bulk INSERT を導入したりで 2000 → 8000 に上がっていた</span></p> <p class="footnote"><a href="#fn-b1ad7889" name="f-b1ad7889" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">latest_isu_condition の PK は jia_isu_uuid にしておく</span></p> <p class="footnote"><a href="#fn-942f2f46" name="f-942f2f46" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のパラメータチューニングと構成変更で、30000 → 40000 に上がっていた</span></p> <p class="footnote"><a href="#fn-f478c337" name="f-f478c337" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">去年ももしかして restart=always 入ってたのかも</span></p> <p class="footnote"><a href="#fn-fcff3daf" name="f-fcff3daf" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text">今回は結果的に、リソースの飽和の原因となっているクエリを直すのを優先的にやってましたが</span></p> </div> threetea0407 「はじめて学ぶソフトウェアのテスト技法」を読んだ hatenablog://entry/26006613697541538 2021-02-28T19:20:26+09:00 2021-02-28T19:20:26+09:00 よく考えてみたら「テスト」について勉強したことなかったなと思って、本を読んでみた。 はじめて学ぶソフトウェアのテスト技法作者:リー・コープランド発売日: 2005/11/03メディア: 単行本 本の解説 「はじめて学ぶソフトウェアのテスト技法」は、バグを効率的に発見するためにテストをどう行えばよいかという問に答える本だ。 テストコードの保守性を上げるにはどうしたらよいか、という問には答えてくれないので、その問題意識を持つ人は別の本をあたったほうが良いと思う(良い本があったら教えてください)。 本書は、以下のブラックボックステストの技法を解説している。 同値クラステスト 境界値テスト デシジョン… <p>よく考えてみたら「テスト」について勉強したことなかったなと思って、本を読んでみた。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4822282511/genya040704-22%20/"><img src="https://m.media-amazon.com/images/I/51JSRX31MSL.jpg" class="hatena-asin-detail-image" alt="はじめて学ぶソフトウェアのテスト技法" title="はじめて学ぶソフトウェアのテスト技法"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4822282511/genya040704-22%20/">はじめて学ぶソフトウェアのテスト技法</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%A5%EA%A1%BC%A1%A6%A5%B3%A1%BC%A5%D7%A5%E9%A5%F3%A5%C9" class="keyword">リー・コープランド</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2005/11/03</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <h2>本の解説</h2> <p> 「はじめて学ぶソフトウェアのテスト技法」は、<b>バグを効率的に発見するためにテストをどう行えばよいか</b>という問に答える本だ。 テストコードの保守性を上げるにはどうしたらよいか、という問には答えてくれないので、その問題意識を持つ人は別の本をあたったほうが良いと思う(良い本があったら教えてください)。</p> <p>本書は、以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D6%A5%E9%A5%C3%A5%AF%A5%DC%A5%C3%A5%AF%A5%B9%A5%C6%A5%B9%A5%C8">ブラックボックステスト</a>の技法を解説している。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>テスト</li> <li>境界値テスト</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%B7%A5%B8%A5%E7%A5%F3%A5%C6%A1%BC%A5%D6%A5%EB">デシジョンテーブル</a></li> <li>状態遷移テスト</li> <li>ペア構成テスト</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>分析テスト</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>テスト</li> </ul> <p>我々がテストを書くときに無意識に実践している項目もある。例えば「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>テスト」は、ほとんどの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE%A1%BC">プログラマー</a>は無意識にやっているはず。 ただ、他のテスト技法に関しては、実践したことのないものばかりだった。</p> <p>これまでは、テストでバグを発見できる気がしなくてすごく気持ち悪かったんだけど、この本を読んだことである程度自信を持ってやれそうな気がしてきた。</p> <p>あと、すごく重要なことなんですが、この本はかなり<u>読みやすい</u>です。 昔似たような本を読んだときはとにかく退屈で、10ページぐらい読んでやめてしまったんだけど、この本は一人でもサクサク読みすすめられました。</p> <h2>面白かったところ</h2> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>・境界値</h3> <p>個人的には<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>と境界値が大事な発想だと思った。</p> <p>テストっていうのは、原始的にはありうる全ての入力パターンを試して正しく動くことを担保しないといけないと思うんだけど、現実的にはそれは無理だ。 入力が<a class="keyword" href="http://d.hatena.ne.jp/keyword/enum">enum</a>とかで有限のパターンしか取らないなら可能かもしれないけど、整数とか実数が入力パラメータに入ってくるとオシマイだ。 そこで導入される考え方が「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>テスト」と「境界値テスト」だ。</p> <p>振る舞いが「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C8%F3%C0%FE%B7%C1">非線形</a>」に変化する領域ごとに入力をグルーピングして、そのグループの「端の値」と「代表的な値」をテストしましょうね、というのが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>・境界値テストだと自分は理解した。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>・境界値の発想を理解することで、整数とか実数とかの、無数の値を取る入力に対しても網羅的にテストが書けることになる。 このことによって、逆に「網羅的にテストを書こう」という気持ちになれることを発見した。</p> <p>これまでテストを書くときは、以下のパターンでテストを書くことが多かった。</p> <ul> <li>網羅性を気にせず、ド正常系を1つと、思いつく限りの異常系をテストする</li> <li>正常系に属する範囲から入力をランダムに選んでテストする</li> </ul> <p>これは、どうせ網羅的にはテストを書けないのだから網羅性を担保するのを諦める、という発想が根底にある。</p> <p>網羅的にテストを行う手法を理解したことで、網羅的にテストを書こうという気持ちになれる。</p> <p>また、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>と境界値を前提として様々なテスト技法が存在している。 例えば、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%B7%A5%B8%A5%E7%A5%F3%A5%C6%A1%BC%A5%D6%A5%EB">デシジョンテーブル</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>と境界値を前提として入力の網羅性を担保したいときに使う技法だし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>分析テストは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>・境界値の拡張だ。 そういう意味でも、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%B1%C3%CD%A5%AF%A5%E9%A5%B9">同値クラス</a>と境界値を理解することは重要な雰囲気を感じる。</p> <h3>ペア構成テスト</h3> <p>ペア構成テストも面白い。 これは、単体では問題なく動く<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>同士を組み合わせたときにバグるのを発見する手法だ。 組み合わせる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の種類に対して、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の組み合わせの数は指数的に増えていく。 そのため、全ての<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の組み合わせパターンをテストするのは実質的に不可能だ。 この問題を解決するために導入される手法がペア構成テストだ。</p> <p>経験則として「組み合わせでバグるのは、2つの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を組み合わせたときにバグるのがほとんどで、3つ以上の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を組み合わせたときだけバグるというのはほとんどない」ということが知られているらしい。 つまり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の全組み合わせを調べる必要はなく、全ての<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>がペアを組んだことがあるような組み合わせに絞ってテストをすれば、それだけでかなりの確率でバグを発見できる、ということだ。</p> <p>「全ての<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>がペアを組んだことがあるような組み合わせ」を生成するソフトウェアが世の中には存在するので、それを使ってテストする組み合わせをリストアップすればいい。 これによって、全組み合わせをテストするのに比べて格段にテスト対象を減らすことができる。</p> <p>ただ、自動テストをするという観点からは、多くの場合は全組み合わせをテストすることは可能だと思う。特に正常系をざっとみるだけであれば、テストケースを自動生成してエラーがthrowしないことをチェックするのは簡単なはずだ。 一方で、各組み合わせに対して詳しい挙動をチェックしようと思ったら、ペア構成テストは自動テストにおいても有効な手法だと思う。</p> <h3>状態遷移テスト</h3> <p>これまで状態遷移をテストしたことなかったけど、いざやることになったら多分どこから手を付けたらいいかわからんとなっていたと思う。 この本には状態遷移をテストする方法がいくつか紹介されていて、それぞれどのぐらい偉いテストなのかということが書いてあってよかった。 重要な状態遷移なら気合を入れて偉いテストを書き、どうでもいいやつならざっくりテストを書く、ということができると思う。</p> <h2>まとめ</h2> <p> 「はじめて学ぶソフトウェアのテスト技法」は面白い本だった。 雑にテストを書いてるソフトウェア開発者の人は一度読んでみると面白いと思う。</p> <p>それはそれとして「いいテストコードの書き方」「テストが書きやすい設計をする方法」とかの知見はこの本からは得られないので、オススメの本がある人は教えてください(クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>は読みました)。</p> <p>これは読書ノートです: <a href="https://scrapbox.io/genya0407/%E5%88%9D%E3%82%81%E3%81%A6%E5%AD%A6%E3%81%B6%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E6%8A%80%E6%B3%95">&#x521D;&#x3081;&#x3066;&#x5B66;&#x3076;&#x30BD;&#x30D5;&#x30C8;&#x30A6;&#x30A7;&#x30A2;&#x306E;&#x30C6;&#x30B9;&#x30C8;&#x6280;&#x6CD5; - genya0407&#x306E;&#x30E1;&#x30E2;</a></p> <h2>NOTE:</h2> <p>本書は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D6%A5%E9%A5%C3%A5%AF%A5%DC%A5%C3%A5%AF%A5%B9%A5%C6%A5%B9%A5%C8">ブラックボックステスト</a>の手法だけでなく、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DB%A5%EF%A5%A4%A5%C8%A5%DC%A5%C3%A5%AF%A5%B9%A5%C6%A5%B9%A5%C8">ホワイトボックステスト</a>の手法も取り上げています。 しかし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DB%A5%EF%A5%A4%A5%C8%A5%DC%A5%C3%A5%AF%A5%B9%A5%C6%A5%B9%A5%C8">ホワイトボックステスト</a>の扱いは小さいし、「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DB%A5%EF%A5%A4%A5%C8%A5%DC%A5%C3%A5%AF%A5%B9%A5%C6%A5%B9%A5%C8">ホワイトボックステスト</a>に拘るな」みたいな記述があったし、本体コードにゴリゴリに依存したテストコードの保守性はものすごく低いことが予測され、実際に業務で書くことはあまり無いだろうと思ったので、読み飛ばした。</p> <p>とはいえ、「この分岐ってテストされてないけど大丈夫かな」のような発想でテストケースに気づけることもあると思うので、なんとなく意識するぐらいのことはしてもいいのかなと思った。</p> threetea0407 Rubyで設定を書けるLinux用キーマッパー 「rumap」をRustで作った hatenablog://entry/26006613661416222 2020-12-08T00:00:00+09:00 2020-12-08T16:50:12+09:00 この記事は、 CAMPHOR- アドベントカレンダー 2020の8日目の記事です。 Rubyで設定を書けるLinux用のキーマッパーをRustで実装した話をします。 <p>この記事は、 <a href="https://advent.camph.net/">CAMPHOR- アドベントカレンダー 2020</a>の8日目の記事です。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で設定を書ける<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>用のキーマッパーをRustで実装した話をします。</p> <h2>Rumap</h2> <p>Rumap は <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>で設定を書ける<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>用のキーマッパーです(正確には<a class="keyword" href="http://d.hatena.ne.jp/keyword/X%20Window%20System">X Window System</a>用)。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgenya0407%2Frumap" title="genya0407/rumap" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/genya0407/rumap">github.com</a></cite></p> <p>キーマッパーとは何かというと、karabinarみたいなやつです。 つまり、キーボードの入力をなにか別の入力に変換するアプリケーションです。</p> <p>例えば、以下のような設定ファイルを書いたとします。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink>remap <span class="synSpecial">'</span><span class="synConstant">Control-BackSpace</span><span class="synSpecial">'</span>, <span class="synConstant">to</span>: <span class="synSpecial">'</span><span class="synConstant">Delete</span><span class="synSpecial">'</span> </pre> <p>これを rumap に食わせて起動すると、 Control と BackSpace を同時押しすると、代わりにDeleteが入力されるようになります。</p> <p>また、キー入力を変換するだけでなく、キー入力をトリガーにしてコマンドを実行することもできます。 例えば以下のように書くことで、Alt と Shift と 4を同時押しすると <code>gnome-screenshot -a -d 0</code> が実行されます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink>remap <span class="synSpecial">'</span><span class="synConstant">Alt-Shift-4</span><span class="synSpecial">'</span>, <span class="synConstant">to</span>: execute(<span class="synSpecial">'</span><span class="synConstant">gnome-screenshot -a -d 0</span><span class="synSpecial">'</span>) </pre> <p>詳しい使い方やインストール方法は、<a href="https://github.com/genya0407/rumap">README</a>を参照してください。</p> <h2>設定ファイルが<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>である</h2> <p>rumapの設定ファイルは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>なので、いろいろと便利なことができます。</p> <p>例えばこういうことができます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synSpecial">%w[</span><span class="synConstant">r z x c v w t f Return</span><span class="synSpecial">]</span>.each <span class="synStatement">do</span> |<span class="synIdentifier">key</span>| remap <span class="synSpecial">&quot;</span><span class="synConstant">Alt-</span><span class="synSpecial">#{</span>key<span class="synSpecial">}&quot;</span>, <span class="synConstant">to</span>: <span class="synSpecial">&quot;</span><span class="synConstant">C-</span><span class="synSpecial">#{</span>key<span class="synSpecial">}&quot;</span> <span class="synStatement">end</span> </pre> <p>この設定ファイルを使うと、Alt と r や z や x...を同時押ししたとき、 <strong>Ctrlとの同時押し</strong> に変換されます。</p> <p>このように<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>を設定用の言語とすることで、複雑な設定も短く書き下すことができます。</p> <h2>なぜ作ったのか</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>で動いて、かつ<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で設定ファイルを書くことができるキーマッパーがなかったので作りました...なら理由としてカッコいいですが、実はそのようなキーマッパーは存在します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fk0kubun.hatenablog.com%2Fentry%2Fxkremap" title="Linux向けの最強のキーリマッパーを作った - k0kubun&#39;s blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://k0kubun.hatenablog.com/entry/xkremap">k0kubun.hatenablog.com</a></cite></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%BF%A5%C3%A5%AF%A5%B9">シンタックス</a>もほぼ同じです(というかrumapの<a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>はxremapのパクリです)。一時期は僕もxremapを使っていました。</p> <p>ではなぜ作ったのかという話なんですが、xremap には with_modifier オプションが当時存在しなかったからです(今はあるかも)。</p> <h3>with_modifier オプション</h3> <p>with_modifier オプションの説明をするために、with_modifier オプションがない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%A4%B3%A6%C0%FE">世界線</a>の話をします。</p> <p><code>Ctrl+hjkl</code> で矢印キーを入力したい気持ちになることが人類にはよくあると思います<a href="#f-268e28f8" name="fn-268e28f8" title="矢印キーまで指を伸ばすのはだるいので">*1</a>。 これをxremapの<a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>で表現するとどうなるでしょうか</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink>remap <span class="synSpecial">'</span><span class="synConstant">Control-h</span><span class="synSpecial">'</span>, <span class="synConstant">to</span>: <span class="synSpecial">'</span><span class="synConstant">Left</span><span class="synSpecial">'</span> remap <span class="synSpecial">'</span><span class="synConstant">Control-j</span><span class="synSpecial">'</span>, <span class="synConstant">to</span>: <span class="synSpecial">'</span><span class="synConstant">Down</span><span class="synSpecial">'</span> remap <span class="synSpecial">'</span><span class="synConstant">Control-k</span><span class="synSpecial">'</span>, <span class="synConstant">to</span>: <span class="synSpecial">'</span><span class="synConstant">Up</span><span class="synSpecial">'</span> remap <span class="synSpecial">'</span><span class="synConstant">Control-l</span><span class="synSpecial">'</span>, <span class="synConstant">to</span>: <span class="synSpecial">'</span><span class="synConstant">Right</span><span class="synSpecial">'</span> </pre> <p>こうなります。</p> <p>これでめでたく<code>Ctrl+hjkl</code>で矢印キーが入力できるようになったわけですが、ところでテキストを編集するときにマウスを使わずに範囲選択をしたくなることは、人類にはよくあります。 <code>Ctrl+hjkl</code> のことを考えなければ、 <strong>Shiftを押しながら矢印キーを押してカーソルを移動する</strong> ことで、上記を実現できます。</p> <p>「Shiftを押しながら」 なんだか怪しい雰囲気がしてきました。</p> <p>実際、当時のxremapで、 <code>Ctrl+hjkl</code> の設定を入れた状態でShiftを押しながら<code>Ctrl+j</code>を入力すると何が起こったかというと、何も起こりませんでした。 上の設定は <code>Ctrl+j</code> の設定であって、 <code>Ctrl+Shift+j</code> の設定ではないからですね。</p> <p>xremapの動作をもう少し詳しく説明すると、</p> <ul> <li>xremapは起動時に、Xのサーバーに対して<a class="keyword" href="http://d.hatena.ne.jp/keyword/watch">watch</a>するキーの組み合わせを設定する</li> <li>Xは、設定された組み合わせのキーが入力されたとき、その旨をxremapに通知する</li> <li>xremapはキー入力の通知を受けると、キーを変換してXに入力する</li> </ul> <p>のように動作するのですが、上の設定だと <code>Ctrl+j</code> の組み合わせしか<a class="keyword" href="http://d.hatena.ne.jp/keyword/watch">watch</a>しないので、<code>Ctrl+Shift+j</code>が入力されてもxremapに通知が来ません。</p> <p>これを解決するために、rumapで導入したのが with_modifier オプションです。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink>remap <span class="synSpecial">'</span><span class="synConstant">Control-h</span><span class="synSpecial">'</span>, <span class="synConstant">to</span>: <span class="synSpecial">'</span><span class="synConstant">Left</span><span class="synSpecial">'</span>, <span class="synConstant">with_modifier</span>: <span class="synSpecial">'</span><span class="synConstant">Shift</span><span class="synSpecial">'</span> ... </pre> <p>このように <code>with_modifier: 'Shift'</code> を指定すると、rumapは立ち上げ時に、<code>Ctrl+h</code> に加えて <code>Ctrl+Shift+h</code> も<a class="keyword" href="http://d.hatena.ne.jp/keyword/watch">watch</a>します。そして、 <code>Ctrl+h</code> を <code>←</code> に変換し、<code>Shift</code>も押されていた場合は <code>Shift+←</code> に変換します。つまり、 <code>Ctrl+h (+Shift)</code> を <code>← (+Shift)</code> に変換します。</p> <p>これによって、 <code>Ctrl+Shift+hjkl</code> を同時押しすることで、テキストの範囲選択が可能になります。</p> <h3>なんで本家にcontributeせえへんねん</h3> <p>当初は、xremapにpatchを当てることによって上記の問題を解決しようとしており。PRも出すには出しました。</p> <p><a href="https://github.com/k0kubun/xremap/pull/36">Pass unmatched modifiers by genya0407 &middot; Pull Request #36 &middot; k0kubun/xremap &middot; GitHub</a></p> <p>この実装は、上で説明した with_modifier オプションとは違って、 全ての modifier の組み合わせを<a class="keyword" href="http://d.hatena.ne.jp/keyword/watch">watch</a>します。 そして、通知されてきた入力のうち、設定されている部分について変換処理をします。</p> <p>例えば、 <code>remap 'Control-h', to: 'Left'</code> と設定していたら、<code>h</code>を含む全ての入力(<code>h</code>、<code>Ctrl+h</code>、<code>Ctrl+Shift+h</code>、<code>Ctrl+Alt+h</code>、、、)を<a class="keyword" href="http://d.hatena.ne.jp/keyword/watch">watch</a>し、<code>Ctrl+Alt+j</code> が入力されてきたら、 <code>Alt+↓</code>に変換する、といった感じです。</p> <p>これは設定が短くて済むという利点はあるのですが、PRを出した直後にバグに気づいたのでPRを取り下げました。</p> <p>もう記憶があんまりないのですが、たしか <code>Alt+j</code> → <code>Ctrl+j</code> みたいな設定をしていると、変換後の <code>Ctrl+j</code> も <code>j</code> を含んでいるため通知が飛んできて、xremapが無限ループして死ぬ、というようなバグだったと思います。</p> <p>この問題を解決するには、上で説明したような with_modifier オプションを<a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>に導入すればよいのですが<a href="#f-cc59fa96" name="fn-cc59fa96" title="厳密には、Ctrl+h → Ctrl+h みたいな設定をしてると無限ループは回避できないけど、そんな設定をする人類はいないと仮定する">*2</a>、</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>の文法についての交渉をするのが面倒だった<a href="#f-7a5f639d" name="fn-7a5f639d" title="よく考えると、後方互換性を保ったままDSLを拡張できるので割とすんなり行けたんじゃないかって気はする">*3</a></li> <li>既存のコードをいい感じに修正するのが大変そうだった(たしか)</li> <li>xremapをコードリーディングして実装を概ね把握したことで、勉強も兼ねて自分で一から実装したくなった</li> </ul> <p>などの理由から自分で実装することにしました。</p> <h2>その他、考えたことなど</h2> <p>作ってるときに考えたことなどをつらつらと書いていきます</p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/x11">x11</a>ライブラリ</h3> <p>Xの<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を叩くために、<a href="https://github.com/erlepereira/x11-rs">x11</a>というライブラリを使いました。これはXlibの非常に薄いラッパーで、Cの関数を呼ぶのでunsafeな操作ばかりです<a href="#f-1f09a14a" name="fn-1f09a14a" title="余談ですが、unsafeな操作は上で説明したコアロジックには存在しません。なぜなら、unsafeな操作をするのはX依存のモジュールだけだからです。">*4</a>。ただ、薄いラッパーであることによって、Xlibのドキュメントがそのまま使えたのは助かりました。</p> <p>一番困ったのはXlibのドキュメント(というか<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C1%A5%E5%A1%BC%A5%C8%A5%EA%A5%A2%A5%EB">チュートリアル</a>)がないことで、仕方がないので xremap の実装を何度も読み返しました。</p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>以外のOSに(理論上)拡張できるようにした</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>以外のOSに拡張できるように、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>(X)に依存する部分と、そうでない "コアロジック" を分離するようにしました。</p> <ul> <li>X依存の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>: <a href="https://github.com/genya0407/rumap/tree/master/linux">rumap/linux at master &middot; genya0407/rumap &middot; GitHub</a></li> <li>コア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>: <a href="https://github.com/genya0407/rumap/tree/master/mapper">rumap/mapper at master &middot; genya0407/rumap &middot; GitHub</a></li> </ul> <p>これによって、(理論上は)例えば<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>や<a class="keyword" href="http://d.hatena.ne.jp/keyword/macOS">macOS</a>でも使えるように、rumapを拡張できるはずです<a href="#f-fa17d6d5" name="fn-fa17d6d5" title="実際には、X固有だと思ってたロジックがコアロジックだったとか、他の環境だとコアロジックが使い物にならん、みたいなのはあるとは思いますが、そのあたりはやってみないとわからない">*5</a>。</p> <p>ただ、コアロジックと環境依存<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>をうまく分離できていないのか、あるいは分離の仕方が悪いからかもしれませんが、コア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>のコードに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B8%A5%A7%A5%CD%A5%EA%A5%AF%A5%B9">ジェネリクス</a>が大量に発生してしまい、非常に読みづらく、また<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B8%A5%A7%A5%CD%A5%EA%A5%AF%A5%B9">ジェネリクス</a>関連のコピペが多くなっています。</p> <p>他環境にも拡張しないとただ単にコードの複雑性を増しただけになってしまうので拡張していきたい。しかし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/mac">mac</a>にはkarabinarがあるし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>を使うことは基本的にないし、なかなかモチベーションが生まれないというのが正直なところです。</p> <h3>Cargoのworkspaceが便利</h3> <p>上に説明した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の分離を実現するために、Cargoのworkspace機能を使ったのですが結構便利でした。</p> <p>レポジトリを見てもらうと、 <code>linux</code> と <code>mapper</code> という2つの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リが存在し、それぞれが1つのcrateになっています<a href="#f-d8da9e85" name="fn-d8da9e85" title="Cargo.tomlもそれぞれ持っている">*6</a><a href="#f-6c108e88" name="fn-6c108e88" title="linuxがX依存部分、mapperがコアロジック">*7</a>。 そして、Cargo.tomlに以下のように記述することで、1つのworkspaceとしてまとめています。</p> <pre class="code toml" data-lang="toml" data-unlink>[workspace] members = [ &#34;mapper&#34;, &#34;linux&#34;, ]</pre> <p>この設定により、それぞれのcrateを、依存関係をもたせながら独立にビルドすることができます。</p> <p>例えば、 <code>linux/Cargo.toml</code> に以下のように記述することで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/linux">linux</a> crateからmapper crateを利用することができます。</p> <pre class="code toml" data-lang="toml" data-unlink>[dependencies] mapper = { path = &#34;../mapper&#34; }</pre> <p>また、以下のようにすることで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/linux">linux</a> crate (と、それが依存するmapper crate)のみをビルドすることができます。</p> <pre class="code shell" data-lang="shell" data-unlink>$ cd linux $ cargo build</pre> <p>今回の例では全てのcrateがビルドされてしまうのでありがたみがないですが、例えば <a class="keyword" href="http://d.hatena.ne.jp/keyword/macos">macos</a> crate を追加したとき、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>上では <a class="keyword" href="http://d.hatena.ne.jp/keyword/macos">macos</a> crate のビルドが通らないとしても、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/linux">linux</a> crate のみをビルドすることが可能です。</p> <p>共通機能を持ちつつもそれを利用したアプリケーションが複数あるような場合に、とても便利そうだなと思いました。</p> <h3>外部の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>コマンドに依存するのをやめたい</h3> <p>xremap の「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で設定が書ける」という特徴は革命的に便利なので、rumapでもこの特徴は引き継ぎたいと思っていました。xremapはmrubyで実装しているから<a class="keyword" href="http://d.hatena.ne.jp/keyword/ruby">ruby</a>のコードを評価するのは簡単ですが、Rustでそれをどう実現すればよいでしょうか。</p> <p>なんとrumapは、 <strong>外部の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>コマンドを実行する</strong> ことで、設定ファイルを評価しています。具体的には、引数に指定されたファイルをinstance_evalして結果を<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>文字列にする<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を書いて、rumap起動時にその<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を起動して設定ファイルを<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>に変換し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>を元にrumapを初期化するようにしています<a href="#f-ec1f6d16" name="fn-ec1f6d16" title="JSONを作れるならなんでもいいので、例えばPythonのDSLを作って組み込むことも可能なはずです。">*8</a>。</p> <p>外部の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>コマンドに依存するのはダサいし、外部要因で動いたり動かなかったりするのはつらいので、どうにかしたいと思っています。 この記事を書いてるときに見つけた以下のcrateを使えば、いい感じにできるはず。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fanima-engine%2Fmrusty" title="anima-engine/mrusty" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/anima-engine/mrusty">github.com</a></cite></p> <h2>まとめ</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で設定を書ける<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>用のキーマッパーを作りました。ほとんど xremap のパクりですが、微妙に機能が増えています。</p> <p>いろいろと実装上の反省点はあるのですが、自分が便利に使えているのでひとまずはいいかなと思っています。</p> <div class="footnote"> <p class="footnote"><a href="#fn-268e28f8" name="f-268e28f8" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">矢印キーまで指を伸ばすのはだるいので</span></p> <p class="footnote"><a href="#fn-cc59fa96" name="f-cc59fa96" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">厳密には、Ctrl+h → Ctrl+h みたいな設定をしてると無限ループは回避できないけど、そんな設定をする人類はいないと仮定する</span></p> <p class="footnote"><a href="#fn-7a5f639d" name="f-7a5f639d" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">よく考えると、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B8%E5%CA%FD%B8%DF%B4%B9">後方互換</a>性を保ったまま<a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>を拡張できるので割とすんなり行けたんじゃないかって気はする</span></p> <p class="footnote"><a href="#fn-1f09a14a" name="f-1f09a14a" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">余談ですが、unsafeな操作は上で説明したコアロジックには存在しません。なぜなら、unsafeな操作をするのはX依存のモジュールだけだからです。</span></p> <p class="footnote"><a href="#fn-fa17d6d5" name="f-fa17d6d5" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">実際には、X固有だと思ってたロジックがコアロジックだったとか、他の環境だとコアロジックが使い物にならん、みたいなのはあるとは思いますが、そのあたりはやってみないとわからない</span></p> <p class="footnote"><a href="#fn-d8da9e85" name="f-d8da9e85" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">Cargo.tomlもそれぞれ持っている</span></p> <p class="footnote"><a href="#fn-6c108e88" name="f-6c108e88" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a class="keyword" href="http://d.hatena.ne.jp/keyword/linux">linux</a>がX依存部分、mapperがコアロジック</span></p> <p class="footnote"><a href="#fn-ec1f6d16" name="f-ec1f6d16" class="footnote-number">*8</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>を作れるならなんでもいいので、例えば<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>を作って組み込むことも可能なはずです。</span></p> </div> threetea0407 標準入力からヒストグラムを描画するCLIツールを作った hatenablog://entry/26006613632069181 2020-09-24T23:59:49+09:00 2020-10-08T22:11:29+09:00 標準入力をいい感じにヒストグラムにするCLIツールを作りました。 GitHub - genya0407/hist インストール Releases · genya0407/hist · GitHub からお好きなバイナリをダウンロードして、適当なパスに展開してください 使い方 なんかこういう感じのテキストファイルがあるとする。 $ cat example.txt 4.486107060301375 4.400612185880518 3.1836054290123 1.8814038706949097 3.367418962291763 2.5550752855238943 2.76469696… <p>標準入力をいい感じに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%B9%A5%C8%A5%B0%A5%E9%A5%E0">ヒストグラム</a>にする<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>ツールを作りました。</p> <p><a href="https://github.com/genya0407/hist">GitHub - genya0407/hist</a></p> <h2>インストール</h2> <p><a href="https://github.com/genya0407/hist/releases">Releases &middot; genya0407/hist &middot; GitHub</a> からお好きなバイナリをダウンロードして、適当なパスに展開してください</p> <h2>使い方</h2> <p>なんかこういう感じのテキストファイルがあるとする。</p> <pre class="code" data-lang="" data-unlink>$ cat example.txt 4.486107060301375 4.400612185880518 3.1836054290123 1.8814038706949097 3.367418962291763 2.5550752855238943 2.7646969681590603 4.099374705165457 4.765991107086257 3.1929965581891215 ...(略)</pre> <p>これをhistコマンドの標準入力に食わせると、以下のようなAAが出力される。</p> <pre class="code" data-lang="" data-unlink>cat example.txt | hist 466.1| 468.3| 470.5| 472.7| 474.9|** 477.1|** 479.3|**** 481.4|******** 483.6|************* 485.8|************************ 488.0|****************************** 490.2|****************************************** 492.4|***************************************************** 494.6|******************************************************************* 496.8|********************************************************************** 499.0|******************************************************************************** 501.1|*************************************************************************** 503.3|************************************************************************* 505.5|***************************************************************** 507.7|******************************************************* 509.9|************************************** 512.1|*********************************** 514.3|******************* 516.5|*************** 518.6|********* 520.8|***** 523.0|** 525.2|* 527.4| 529.6| 531.8| +--------------------------------------------------------------------------------+ 995 times +----------------------------------------+ 497 times</pre> <p>縦軸が「値」で、横軸が「頻度」です。上の例だと、500ぐらいの値が出る頻度が一番高くて、その頻度は1000回ぐらいであることがわかります。</p> <p>ちなみに、横軸の高さを調整するオプションや、値をグルーピングする単位(いわゆる <b>bin</b>)を調整するオプションもあります。</p> <p>contributionは大歓迎です。あと、入力の行数が少ないときにバグっぽい挙動をするので注意してください。</p> <h2>モチベーション</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>とかDBの中身を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%B9%A5%C8%A5%B0%A5%E9%A5%E0">ヒストグラム</a>にしたいことは稀によくある。例えば「ユーザーあたりのブログポスト数ってどういう分布になってるのかな?」みたいなのを知りたいなど。</p> <p>継続的に見ていきたいメトリクスならGrafanaとかのキチンとしたツールで見たほうが良いと思うんですが、<b>今この瞬間</b>サッと見れるだけでいいんだけど、というニーズも少なからずあります。そういうときにいちいちjupyter notebookを立ち上げて、seabornの書き方を調べながら、フォント環境が壊れててイライラしたり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/gnuplot">gnuplot</a>でいい感じに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%B9%A5%C8%A5%B0%A5%E9%A5%E0">ヒストグラム</a>を書こうとして四苦八苦するのは不毛です<a href="#f-6be7ad52" name="fn-6be7ad52" title="もちろん、使い慣れてる人ならそんなに大変じゃないとは思いますけど、僕はあんまり使い慣れてないので...">*1</a>。</p> <p>見たい対象のデータがどこにあるのかによっても違ってきますけど<a href="#f-2f6a6339" name="fn-2f6a6339" title="例えば、elasticsearchにアクセスログが入ってるケースとかは、histコマンドで取り扱うのには不適切かも。素直にkibanaを導入したほうが良さそう。">*2</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>をテキストファイル形式で扱う場合や、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>にクエリを打ってデータ取ってくるときは、シェルからデータが生まれてくるわけで、シェルのなかでグラフ描画までできたら楽でいいですよね。</p> <p>というわけで、「シェルから生まれてきた数字列をそれっぽい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%B9%A5%C8%A5%B0%A5%E9%A5%E0">ヒストグラム</a>AAに起こす」という処理を例によって<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EF%A5%F3%A5%E9%A5%A4%A5%CA%A1%BC">ワンライナー</a>で毎度毎度書いてたんですが、ちゃんとした<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%B9%A5%C8%A5%B0%A5%E9%A5%E0">ヒストグラム</a>を書くのは意外にめんどくさいので<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>ツールにしちゃおと思ったのが、今回histコマンドを作ろうと思ったきっかけです。</p> <h2>メイキング</h2> <h3>Rustの良いところ</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>ツールがいい感じに作れてすごかった(小並感)。</p> <p>この argopt ってcrateがめっちゃ便利。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftanakh.hatenablog.com%2Fentry%2F2020%2F08%2F31%2F032946" title="argopt: Rust向けの宣言的なコマンドライン引数パーザー - 純粋関数型雑記帳" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://tanakh.hatenablog.com/entry/2020/08/31/032946">tanakh.hatenablog.com</a></cite></p> <p>詳しくは上のリンクを読んでもらったら良いんですけど、例えば以下のように書いて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>して実行すると、</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">use</span> <span class="synPreProc">argopt</span><span class="synSpecial">::</span>cmd; <span class="synPreProc">#[cmd]</span> <span class="synStatement">fn</span> <span class="synIdentifier">main</span>( <span class="synPreProc">#[opt(short = </span><span class="synConstant">&quot;b&quot;</span><span class="synPreProc">, long = </span><span class="synConstant">&quot;bin&quot;</span><span class="synPreProc">)]</span> bin: <span class="synType">Option</span><span class="synStatement">&lt;</span><span class="synType">f64</span><span class="synStatement">&gt;</span>, <span class="synPreProc">#[opt(short = </span><span class="synConstant">&quot;l&quot;</span><span class="synPreProc">, long = </span><span class="synConstant">&quot;bar-length&quot;</span><span class="synPreProc">, default_value = </span><span class="synConstant">&quot;80&quot;</span><span class="synPreProc">)]</span> bar_length: <span class="synType">i64</span>, ) { <span class="synComment">// 略</span> } </pre> <p>以下のような出力が得られます。</p> <pre class="code" data-lang="" data-unlink>$ hist --help hist 0.1.0 USAGE: hist [OPTIONS] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: -l, --bar-length &lt;bar-length&gt; [default: 80] -b, --bin &lt;bin&gt; </pre> <p>もちろんmain関数の引数にはいい感じに引数が渡ってきます。これはちょっと感動的に便利ですよね。 今回Rustで<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>作ろうと思ったきっかけの1/4ぐらいは、argoptの紹介記事を読んだことです。</p> <p>余談ですが、最新のバージョン(v0.1.1)のargoptは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/dependency">dependency</a>としてargoptを指定するだけじゃ動かなくて、structoptも指定してあげないとダメっぽかったので、issueを立てて報告しました(英語が拙い)。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftanakh%2Fargopt%2Fissues%2F1" title="Did not compile without structopt crate · Issue #1 · tanakh/argopt" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/tanakh/argopt/issues/1">github.com</a></cite></p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/Github">Github</a> Actionsがべんり</h3> <p>これもすごかった。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmotemen.hatenablog.com%2Fentry%2F2019%2F11%2Fgithub-actions-crossbuild-rust" title="GitHub ActionsでRustプロジェクトをクロスビルドしてリリースする - 詩と創作・思索のひろば" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://motemen.hatenablog.com/entry/2019/11/github-actions-crossbuild-rust">motemen.hatenablog.com</a></cite></p> <p>この記事を真似するだけで、</p> <ul> <li>タグをpushすると</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/macOS">macOS</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>用バイナリのビルドが走り</li> <li>各種プラットフォーム向けのバイナリ一覧を含んだリリースが作成される</li> </ul> <p>という魔法みたいな状態を構築することができます。当然ビルドされたバイナリは展開してPATHに置くだけで実行できる。シングルバイナリ最高です。今まで僕が<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>作って配布方法をウンウン悩んでたのは何だったんだろう...</p> <h2>まとめ</h2> <p>標準入力から<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%B9%A5%C8%A5%B0%A5%E9%A5%E0">ヒストグラム</a>を描画する<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>ツールをRustで作りました。Rustは<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>ツールを作成・配布する上でとても楽な選択肢であり、オススメです。</p> <div class="footnote"> <p class="footnote"><a href="#fn-6be7ad52" name="f-6be7ad52" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">もちろん、使い慣れてる人ならそんなに大変じゃないとは思いますけど、僕はあんまり使い慣れてないので...</span></p> <p class="footnote"><a href="#fn-2f6a6339" name="f-2f6a6339" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">例えば、elasticsearchに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>が入ってるケースとかは、histコマンドで取り扱うのには不適切かも。素直にkibanaを導入したほうが良さそう。</span></p> </div> threetea0407 scanコマンドというcliツールを作った hatenablog://entry/26006613586386940 2020-06-17T23:57:09+09:00 2020-10-08T22:08:50+09:00 scanコマンドというcliツールを作った。 GitHub - genya0407/scan scanコマンドは、標準入力の各行に対して正規表現を適用し、ほしい部分を取り出すコマンドだ。使い方は以下の通り。 $ scan --help Usage: scan [options] OUTPUT_FORMAT -p [PATTERN] specify regexp -d [DELIMITER] specify delimiter 使用例を見てもらったほうが早いだろう。 使用例 正規表現を適用する例 例えばこういうファイルがあったとする。 $ cat data.txt hogehoge_nyan h… <p>scanコマンドという<a class="keyword" href="http://d.hatena.ne.jp/keyword/cli">cli</a>ツールを作った。</p> <p><a href="https://github.com/genya0407/scan">GitHub - genya0407/scan</a></p> <p>scanコマンドは、標準入力の各行に対して<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>を適用し、ほしい部分を取り出すコマンドだ。使い方は以下の通り。</p> <pre class="code" data-lang="" data-unlink>$ scan --help Usage: scan [options] OUTPUT_FORMAT -p [PATTERN] specify regexp -d [DELIMITER] specify delimiter</pre> <p>使用例を見てもらったほうが早いだろう。</p> <h2>使用例</h2> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>を適用する例</h3> <p>例えばこういうファイルがあったとする。</p> <pre class="code" data-lang="" data-unlink>$ cat data.txt hogehoge_nyan hohho_nyan</pre> <p>これに対して、アンダースコアの左だけを取り出す<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>を適用するには、以下のようにする。</p> <pre class="code" data-lang="" data-unlink>$ cat data.txt | scan -p &#34;(.+?)_.+&#34; {1} hogehoge hohho</pre> <p>アンダースコアの左と右を、 <code>,</code> で繋いで出力したいときは以下のようにする。</p> <pre class="code" data-lang="" data-unlink>$ cat data.txt | scan -p &#34;(.+?)_(.+)&#34; {1},{2} hogehoge,nyan hohho,nyan</pre> <p>複雑な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>を適用するときは、名前付きキャプチャも使用できる。</p> <pre class="code" data-lang="" data-unlink>$ cat data.txt | scan -p &#34;(?&lt;name1&gt;.+?)_(?&lt;name2&gt;.+)&#34; {name1}:{name2} hogehoge:nyan hohho:nyan</pre> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>エンジンは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>標準のものをそのまま使っているので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>の詳しい仕様についてはリファレンスをあたってください。</p> <p><a href="https://docs.ruby-lang.org/ja/latest/doc/spec=2fregexp.html">&#x6B63;&#x898F;&#x8868;&#x73FE; (Ruby 2.7.0 &#x30EA;&#x30D5;&#x30A1;&#x30EC;&#x30F3;&#x30B9;&#x30DE;&#x30CB;&#x30E5;&#x30A2;&#x30EB;)</a></p> <h3>区切り文字を指定する例</h3> <p>また、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>を使うのではなく、区切り文字を指定することもできる。</p> <p>例えばこういうファイルがあったとする。</p> <pre class="code" data-lang="" data-unlink>$ cat hoge.csv aaa,bbb,ccc xxx,yyy,zzz</pre> <p>このとき、区切り文字として <code>,</code> を指定して、左から3番目のフィールドを切り出すためには以下のようにする。</p> <pre class="code" data-lang="" data-unlink>$ cat hoge.csv | scan -d , {3} ccc zzz</pre> <p>また、scanコマンドに何もオプションを指定しない場合、区切り文字として <code>\s+</code> つまり「1つ以上連続する空白文字列」が指定されていると解釈される。</p> <p>例えばこういうファイルがあるとする。</p> <pre class="code" data-lang="" data-unlink>$ cat hoge.tsv aaa bbb ccc xxx yyy zzz</pre> <p>このファイルを、オプションを何も指定しないscanコマンドに食わせると、空白文字を区切りと解釈して左からn番目の文字列を取り出すことができる。</p> <pre class="code" data-lang="" data-unlink>$ cat hoge.tsv | scan {2} bbb yyy</pre> <h2>インストール方法</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.6.3 以上が動く環境を前提として、以下のファイルをパスが通った場所に配置して、実行権限を付与すれば動く。依存ライブラリとかはなく、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>があれば動きます。</p> <p><a href="https://github.com/genya0407/scan/blob/master/scan">scan/scan at master &middot; genya0407/scan &middot; GitHub</a></p> <p>もう少しいい感じのインストール方法を模索していますが、ひとまずはこれで許してください。</p> <h2>なぜこのコマンドを作ったのか</h2> <p>仕事柄(?)、バグが出たときとかにサーバーのログを漁る必要にかられることがよくある。そういうときに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>は便利だ。</p> <pre class="code" data-lang="" data-unlink>$ cat server.log | (アクセスしたユーザーのIDをいい感じに取り出す正規表現) | sort | uniq -c 100 user_id_111 # user_id_111 が100回もアクセスしているのがわかる 10 user_id_222 ..</pre> <p>しかし、<b><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>をシュッと書いて値を抜き出す適用するツール</b>が見当たらなかった<a href="#f-732f754d" name="fn-732f754d" title="古参のエンジニアの皆さんはおすすめの正規表現ツールでも書いててください">*1</a>。多分<a class="keyword" href="http://d.hatena.ne.jp/keyword/awk">awk</a>とか<a class="keyword" href="http://d.hatena.ne.jp/keyword/perl">perl</a>とかの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EF%A5%F3%A5%E9%A5%A4%A5%CA%A1%BC">ワンライナー</a>で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>をかけるとは思うのだが、僕は<a class="keyword" href="http://d.hatena.ne.jp/keyword/awk">awk</a>も<a class="keyword" href="http://d.hatena.ne.jp/keyword/perl">perl</a>も使い方がわからないし、言語ごとに存在すると思われる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>の方言を覚えるのもなんだかなあという気持ちだった。</p> <p>僕が一番使える<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>なので、一時期は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EF%A5%F3%A5%E9%A5%A4%A5%CA%A1%BC">ワンライナー</a>を書くということをやっていた。</p> <pre class="code" data-lang="" data-unlink>$ cat server.log | ruby -ne &#39;puts $_[/user_id: (.+)\s+/, 1]&#39; | sort | uniq -c</pre> <p>これも割といい線行ってるとは思うが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>で値を抜き出したいだけなのに <code>ruby -ne</code> とか <code>puts</code> とか <code>$_</code> とか書くのはイケてない。</p> <p>次に使ってたのはrargsというツールで、これは限りなく正解に近い。</p> <p><a href="https://github.com/lotabout/rargs">GitHub - lotabout/rargs: xargs + awk with pattern matching support. `ls *.bak | rargs -p &#39;(.*)\.bak&#39; mv {0} {1}`</a></p> <p>rargsは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>を指定して文字列を抜き出すことができ、抜き出した文字列を使ったコマンドを実行することができる。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>の代わりに区切り文字を指定することもできる。</p> <pre class="code" data-lang="" data-unlink>$ ls *.bak | rargs -p &#39;(.*)\.bak&#39; mv {0} {1} $ cat hoge.csv | rargs -d , echo {2}</pre> <p>しかし、rargsはコマンドを実行する都合上、入力される行の数だけプロセスを立ち上げる必要がある。環境にもよるが、プロセスの立ち上げはそこそこヘビーな処理で、業務で使っている<a class="keyword" href="http://d.hatena.ne.jp/keyword/MacBook%20Pro">MacBook Pro</a>ではここがものすごく重かった<a href="#f-80f1c753" name="fn-80f1c753" title="Instrumentsでプロファイルしたらposix_nspawnでめちゃめちゃ時間喰ってた。おそらくMacBook Proが悪いのではなく、セキュリティソフトかなにかが原因で遅いのではないかと疑っている。お家で使ってるThinkipad上のLinuxでは全然重くなかった。">*2</a>。そのため、長めのログファイルをrargsに食わせると、ちょっと現実的ではないぐらい集計に時間がかかってしまう状態になっていた。</p> <p>私が望む用途(=ログの集計)ではコマンドを実行する必要はない。そのため、rargsからコマンド実行の機能を削り、高速に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>を適用するだけのコマンドを作ればよいだろう、という発想に至った。</p> <p>そして、今回説明したscanコマンドを作った。</p> <h2>まとめ</h2> <p>標準入力に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>をシュッと適用して、好きなフォーマットに整形して出力するコマンドである <code>scan</code> を作った。これは、ログの集計・整形などに使うことができ、実用的なレベルには高速であり、自身も便利に使っている。</p> <div class="footnote"> <p class="footnote"><a href="#fn-732f754d" name="f-732f754d" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">古参のエンジニアの皆さんはおすすめの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>ツールでも書いててください</span></p> <p class="footnote"><a href="#fn-80f1c753" name="f-80f1c753" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Instrumentsでプロファイルしたら<a class="keyword" href="http://d.hatena.ne.jp/keyword/posix">posix</a>_nspawnでめちゃめちゃ時間喰ってた。おそらく<a class="keyword" href="http://d.hatena.ne.jp/keyword/MacBook%20Pro">MacBook Pro</a>が悪いのではなく、セキュリティソフトかなにかが原因で遅いのではないかと疑っている。お家で使ってるThinkipad上の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>では全然重くなかった。</span></p> </div> threetea0407 画像入りzipを人物認識してエクセルに変換する「マイクロサービス」を作った hatenablog://entry/26006613572814310 2020-05-24T12:23:50+09:00 2020-10-08T22:09:08+09:00 概要 例の建築家の同期が、動画に映る人の位置を1秒ごとに目視で認識するという虚無作業をしていたので、自動化するWebアプリ的なものを作りました。 github.com 使い方 まず、人物認識したい動画をお好みの間隔(1秒毎とか)で画像に切り出し、適当なフォルダにいれてzip圧縮します。 そして、今回作ったWebアプリを開きます。以下はWebアプリのスクリーンショットです。 GCPのアクセストークンを頑張って取得して、「アクセストークン」という入力欄にコピペします。そして、先程のzipファイルを選択します。最後に「変換」ボタンを押すと、以下のようなエクセルファイルがダウンロードされます。 左端の… <h3>概要</h3> <p><a href="https://dawn.hateblo.jp/entry/teach-how-to-write-html">例の建築家の同期</a>が、動画に映る人の位置を1秒ごとに目視で認識するという虚無作業をしていたので、自動化するWebアプリ的なものを作りました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgenya0407%2Fboxboxbox" title="genya0407/boxboxbox" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/genya0407/boxboxbox">github.com</a></cite></p> <h3>使い方</h3> <p>まず、人物認識したい動画をお好みの間隔(1秒毎とか)で画像に切り出し、適当なフォルダにいれてzip圧縮します。 そして、今回作ったWebアプリを開きます。以下はWebアプリの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20200524/20200524011510.png" alt="f:id:threetea0407:20200524011510p:plain" title="f:id:threetea0407:20200524011510p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/GCP">GCP</a>のアクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンを頑張って取得して、「アクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ン」という入力欄にコピペします。そして、先程のzipファイルを選択します。最後に「変換」ボタンを押すと、以下のようなエクセルファイルがダウンロードされます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20200524/20200524010137.png" alt="f:id:threetea0407:20200524010137p:plain" title="f:id:threetea0407:20200524010137p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>左端の列が画像ファイルの名前を表し、その隣の2列は人物を囲う長方形の左上の点の座標を表し、その隣の2列は右下の点の座標を表しています。これによって、画像の中のどの位置に人間が写っているのかを知ることができます。</p> <p>イメージとしては、以下のような写真を入れると2つの赤丸の座標が取れるという感じです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20200524/20200524012615.png" alt="f:id:threetea0407:20200524012615p:plain" title="f:id:threetea0407:20200524012615p:plain" class="hatena-fotolife" itemprop="image"></span></p> <h3>どうやって実現しているのか</h3> <p>Cloud <a class="keyword" href="http://d.hatena.ne.jp/keyword/Vision">Vision</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>というのが世の中にはあって、物体認識をしてくれます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fvision%2Fdocs%2Freference%2Frest%2Fv1%2Ffiles%2Fannotate" title="Method: files.annotate  |  Cloud Vision API  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/vision/docs/reference/rest/v1/files/annotate">cloud.google.com</a></cite></p> <p>今回作ったWebアプリがやってるのは、</p> <ul> <li>zipを解凍して画像を取り出し</li> <li>画像を<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>に埋め込んで<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を叩き</li> <li>返ってきたデータを若干加工して<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSV">CSV</a>にする</li> </ul> <p>という処理だけです。つまり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%B3%D8%BD%AC">機械学習</a>的なものを自分で実装したわけではありません。</p> <h3>工夫した点</h3> <h4>動画から画像への変換をやらない</h4> <p>もともと同期の人がやりたかったことは、「ある街に定点カメラをおいて、その動画の中で人がどう動いているかを調べる」ということでした。</p> <p>今回は、動画から画像への変換はユーザーにやってもらい、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Web%A5%B5%A1%BC%A5%D3%A5%B9">Webサービス</a>は画像入りzipから人物を認識するだけ、というインターフェースを採用しました。</p> <p><figure class="figure-image figure-image-fotolife" title="ユーザーが動画から画像を切り出す必要がある"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20200524/20200524021034.png" alt="f:id:threetea0407:20200524021034p:plain" title="f:id:threetea0407:20200524021034p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ユーザーが動画から画像を切り出す必要がある</figcaption></figure></p> <p>しかしこれを例えば、ユーザーに動画をアップロードさせて、それをWebアプリ側で画像に変換し、その画像に対して人物認識をする、というようなインターフェースにすることも可能です。</p> <p><figure class="figure-image figure-image-fotolife" title="動画を直接変換できる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20200524/20200524021105.png" alt="f:id:threetea0407:20200524021105p:plain" title="f:id:threetea0407:20200524021105p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>動画を直接変換できる</figcaption></figure></p> <p>後者のほうがユーザーの仕事が減り、一見すると便利なように見えます。しかし、このインターフェースには問題があります。</p> <p>例えば、今までは1秒ごとに画像を切り出していたけど、5秒ごとに切り出すようにしたい、というような要望が出てくることは容易に想像されます。そのたびに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>に手を入れてパラメータを修正するのは不毛です。そのようなパラメータをWebアプリ上で設定可能にすることも考えられますが、その場合は設定項目が無数に増えていくことになるでしょう<a href="#f-888a240b" name="fn-888a240b" title="しかもそれらの設定項目のうち実際に使われるのは極少数であると予想します">*1</a>。</p> <p>また、一般にこの手の物体検出は、画像の中における物体の相対的な大きさによって、検出精度が大きく変わるようです<a href="#f-fad8f014" name="fn-fad8f014" title="この場合、画質の粗さは問題とならず、純粋に画像全体のなかで物体が相対的にどのぐらいの大きさで写っているのかというのが重要っぽい">*2</a>。</p> <p><figure class="figure-image figure-image-fotolife" title="右奥に写った小さな人物は認識されない"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20200524/20200524114452.png" alt="f:id:threetea0407:20200524114452p:plain" title="f:id:threetea0407:20200524114452p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>右奥に写った小さな人物は認識されない</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="右奥を拡大すると認識されるようになる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20200524/20200524114522.png" alt="f:id:threetea0407:20200524114522p:plain" title="f:id:threetea0407:20200524114522p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>右奥を拡大すると認識されるようになる</figcaption></figure></p> <p style="text-align: right;"> <span style="font-size: 80%"><a href="https://pixabay.com/ja/users/Joergelman-241223/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=347468">Jörg Möller</a>による<a href="https://pixabay.com/ja/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=347468">Pixabay</a>からの画像</span> </p> <p>そのため、「大きな画像に沢山の人が小さく写っている」というような画像を対象にするときは、画像を適当なサイズに分割して検出にかけた上で、その結果を統合する処理が必要になります。その場合、どのような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>で画像を分割するか、またどのような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>で結果を統合するか、ということを考える必要があります。</p> <p>このように「動画を画像に変換する」という領域は、要求が変化しやすい上に、真面目に考えるとかなり複雑です。</p> <p>こうしたことを考え、動画から画像への変換方法に自由度を持たせ、かつアプリケーションの複雑度を下げるため、現行のような「画像をzipで固めてアップロードする」というインターフェースを採用しました。これによって、「動画を画像に変換する」という処理はこのソフトウェアの対象領域外となり、実装も使い方も非常に簡単になりました。</p> <p>また、「動画を1秒ごとに画像に切り出す」というような処理は、それ用のソフトウェアがすでに存在します。そのため、このようなインターフェースを採用しても、ユーザーの手間はそれほど変わりません。</p> <h4><a class="keyword" href="http://d.hatena.ne.jp/keyword/Excel">Excel</a>形式で出す</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Excel">Excel</a>で開けるというのはかなり重要です<a href="#f-81ce4884" name="fn-81ce4884" title="Excelではなく正確にはCSVファイルを出力している">*3</a>。というのも、同期の人が使っているビジュアルプログラミング環境である<a href="https://www.applicraft.com/products/rhinoceros/grasshopper/">Grasshopper</a>に、「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Excel">Excel</a>からコピペでデータを貼り付けられるブロック」というのがあるからです<a href="#f-31f00958" name="fn-31f00958" title="一般的に、Excelからコピペでデータを貼り付けられるソフトウェアは結構多い">*4</a>。これにより、認識した人物の座標をGrasshopper上で処理することができます。</p> <p>また、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Excel">Excel</a>で開ける形式であるため、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C9%BD%B7%D7%BB%BB">表計算</a>によるデータの加工が可能です。例えば、画像ファイルに含まれる連番の番号を文字列処理で取り出して、「この人物は動画の何秒目に写っていたのか」というデータを取り出すことができます。プログラミングに不慣れな人にとっては、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C9%BD%B7%D7%BB%BB">表計算</a>のほうが敷居が低いため、これも便利な点です。</p> <p>同期の人がGrasshopper上で処理した例が以下です。これは、街並みの3Dモデル上に動画上の人物の位置を投影し、三次元空間上での人間の動きを分析したものです。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">やっとプログラムが完成したのだけど、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a>が提供する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%B3%D8%BD%AC">機械学習</a>のサービスや<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B0%A5%E9%A5%B9%A5%DB%A5%C3%A5%D1%A1%BC">グラスホッパー</a>などを使って、固定カメラの動画から人を検出し、3D モデル上でユーザーの位置を特定することで、場所の使われ方を、より正確で多量な空間・時間の情報から分析できるようになった! <a href="https://t.co/lTRuBNxeZU">pic.twitter.com/lTRuBNxeZU</a></p>&mdash; Tomi (@T0m12345) <a href="https://twitter.com/T0m12345/status/1263474486390722569?ref_src=twsrc%5Etfw">May 21, 2020</a></blockquote> <p> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <h4><a class="keyword" href="http://d.hatena.ne.jp/keyword/GCP">GCP</a>のアクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンを入力させる</h4> <p>これはCloud <a class="keyword" href="http://d.hatena.ne.jp/keyword/Vision">Vision</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>の利用料金を、利用者である同期に支払わせるための仕組みです。Cloud <a class="keyword" href="http://d.hatena.ne.jp/keyword/Vision">Vision</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>は非常に廉価に利用できますが、それでも60分の動画を1秒ごとに切り出した画像を全部物体検出する、みたいな処理をすると、無視できない料金が発生します。その料金を僕が負担するのは筋違いなので、何らかの手段で同期の人に料金を支払わせる必要があります。定期的に現金ないしはLINE Payとかでお金をやり取りしても良いですが煩雑です。</p> <p>そこで考え出したのが「アクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンを入力させる」というやり方で、これによって<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>の利用料金は同期の人のクレジットカードに直接請求されるため、私とのお金のやり取りが発生せずに済みます。なお、アクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンはサーバー上で保存しておらず、変換処理を行うたびに入力する必要があります<a href="#f-8faa3840" name="fn-8faa3840" title="実際にはブラウザの機能で自動入力されるようです">*5</a>。</p> <h3>技術的に特筆すべきこと</h3> <h4>Steepを使ってみた</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>に型をつける技術というのが最近できつつあるのですが、それを使ってみました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fsoutaro%2Fsteep" title="soutaro/steep" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/soutaro/steep">github.com</a></cite></p> <p>上のレポジトリから例をコピーしてきますが、以下のような型定義ファイルを<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>とは別に書いて、テストみたいな感じで型チェックを回すという感じになります。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">class</span> <span class="synType">Person</span> <span class="synIdentifier">@name</span>: <span class="synType">String</span> <span class="synIdentifier">@contacts</span>: <span class="synType">Array</span>[<span class="synType">Email</span> | <span class="synType">Phone</span>] <span class="synPreProc">def</span> initialize: (<span class="synConstant">name</span>: <span class="synType">String</span>) -&gt; untyped <span class="synPreProc">def</span> name: -&gt; <span class="synType">String</span> <span class="synPreProc">def</span> contacts: -&gt; <span class="synType">Array</span>[<span class="synType">Email</span> | <span class="synType">Phone</span>] <span class="synPreProc">def</span> guess_country: -&gt; (<span class="synType">String</span> | <span class="synConstant">nil</span>) <span class="synPreProc">end</span> </pre> <p>今回書いた型定義ファイルは以下のような感じです(抜粋)。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink>interface _BoxLocalizer <span class="synPreProc">def</span> localize: (<span class="synConstant">images</span>: ::<span class="synType">Array</span>[<span class="synType">Boxboxbox</span>::<span class="synType">BinaryImage</span>]) -&gt; ::<span class="synType">Array</span>[<span class="synType">Boxboxbox</span>::<span class="synType">Box</span>] <span class="synPreProc">end</span> <span class="synPreProc">class</span> <span class="synType">Boxboxbox</span>::<span class="synType">BoxLocalizer</span>::<span class="synType">GoogleVisionApiOnline</span> <span class="synIdentifier">@access_token</span>: <span class="synType">String</span> <span class="synIdentifier">@max_results</span>: <span class="synType">Integer</span> <span class="synIdentifier">@min_percentage</span>: <span class="synType">Float</span> <span class="synIdentifier">@max_retry</span>: <span class="synType">Integer</span> <span class="synIdentifier">@logger</span>: <span class="synType">Logger</span> <span class="synPreProc">def</span> initialize: (<span class="synConstant">access_token</span>: <span class="synType">String</span>, <span class="synConstant">max_results</span>: <span class="synType">Integer</span>, <span class="synConstant">min_percentage</span>: <span class="synType">Float</span>, <span class="synConstant">max_retry</span>: <span class="synType">Integer</span>, <span class="synConstant">?logger</span>: <span class="synType">Logger</span>) -&gt; untyped <span class="synPreProc">def</span> localize: (<span class="synConstant">images</span>: ::<span class="synType">Array</span>[<span class="synType">Boxboxbox</span>::<span class="synType">BinaryImage</span>]) -&gt; ::<span class="synType">Array</span>[<span class="synType">Boxboxbox</span>::<span class="synType">Box</span>] (略) <span class="synPreProc">end</span> </pre> <p><code>_BoxLocalizer</code> というインターフェースがあって、その実装として <code>Boxboxbox::BoxLocalizer::GoogleVisionApiOnline</code> というのがある、という気持ちです<a href="#f-cdb55afa" name="fn-cdb55afa" title="duck typingなので、明示的にinterfaceを実装しなくても、interfaceを満たしていれば実装したことになる">*6</a>。ここでI/Fを切ったのは、Cloud <a class="keyword" href="http://d.hatena.ne.jp/keyword/Vision">Vision</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を叩くときに叩き方がいくつかあるし、他の物体検出系のサービスもあるので、そこを差し替えられるようにしたかったからです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgenya0407%2Fboxboxbox%2Ftree%2Fmaster%2Fsig" title="genya0407/boxboxbox" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/genya0407/boxboxbox/tree/master/sig">github.com</a></cite></p> <p>Steepを使ってみた感想は以下です</p> <ul> <li>クラスの階層を全て明示した形で書かなきゃいけないので若干見づらい(型定義のネストができない)</li> <li>private methodも型定義書かなきゃいけないのでつらい</li> <li>標準ライブラリも型定義が存在しないものがあり、自分で書く必要があったりした</li> <li>型を書いてるとき、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>ファイルと独立した全然関係ないファイルを書くことになるので、地味に虚無な気持ちになる(慣れの問題かも)</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のダックタイピングをかなりいい感じに型に落とし込めてるという印象</li> <li>型がないライブラリを使うときに、自分で適当にシュッと型をつけてしまえるのは結構体験が良かった <a href="https://github.com/genya0407/boxboxbox/blob/v1.0.6/sig/json.rbs">例</a></li> <li>型がないライブラリの実装から型定義ファイルを自動生成するやつというのがあって、大体のケースはそれを使うとなんとなく型がついてくれるんだけど、たまになんか無理なやつがあってつらい (concurrent-<a class="keyword" href="http://d.hatena.ne.jp/keyword/ruby">ruby</a>というgemは無理だった)</li> </ul> <p>Steepを使う際には<a href="https://ruby-jp.github.io/">ruby-jp</a>の#typeチャンネルの皆さんに大変お世話になりました。ありがとうございました。</p> <h3>今後の課題的な</h3> <p>同期の人の要件を満たすソフトウェアは作れたので一旦は満足なんですが、一般に公開したら誰かしら使いたがる人はいる気がしていて、公開したいという気持ちがあります。ただ、いろいろ気になるところがあって公開を見送っています。</p> <p>例えば現状の実装だと、アップロードされてきたzipファイルはそのまま/tmp<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ以下に書き込まれるようになっており、大容量のファイルが送られてきたり、利用者が増えてきたりすると問題になりそうです。/tmp<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに置くのをやめてS3に上げるのも考えられるんですが、そうなるとファイルサイズに対して従量で料金がかかってくるわけで、テロされて<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>破産みたいな話もあり得るわけです。</p> <p>あとは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/GCP">GCP</a>のアクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンをサーバーに送信させてるわけですが、これって大丈夫なんだっけみたいな話はあって、僕個人との信頼関係がない人が、ここにアクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンを書き込みたいとは思わないでしょう。そもそも、Webでこれをやる必要は一切なくて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>のデスクトップアプリとして公開すれば良いはずです。今回サーバーサイドアプリケーションとして実装したのは、単純に僕にデスクトップアプリを作る技術がないからです。</p> <p>ただ、デスクトップアプリとして公開する場合も、<a class="keyword" href="http://d.hatena.ne.jp/keyword/GCP">GCP</a>のアクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンの取得手続きは利用者に踏んでもらう必要があり、ユーザーに一定程度難易度の高いタスクを要求することになります。そう考えると、サーバーでその処理をまるっと隠蔽してあげるというのはありうる選択肢で、ユーザーに対して適切に料金を課す手段が見つかってないことが問題なのだと考えることもできます。</p> <p>というわけで、今後あり得る展開としては</p> <ol> <li>フリーのデスクトップアプリにして一般公開する(アクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンは自分で取得させる)</li> <li>ユーザーに料金を課すスキームを確立し、一般公開する(アクセス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンの取得をさせない)</li> <li>需要がないためこれ以上何もしない。現実は非情である。</li> </ol> <p>という感じです。雑にユーザーに課金させるシステムをどなたかご存知でしたら教えてください。よろしくおねがいします。</p> <div class="footnote"> <p class="footnote"><a href="#fn-888a240b" name="f-888a240b" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">しかもそれらの設定項目のうち実際に使われるのは極少数であると予想します</span></p> <p class="footnote"><a href="#fn-fad8f014" name="f-fad8f014" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">この場合、画質の粗さは問題とならず、純粋に画像全体のなかで物体が相対的にどのぐらいの大きさで写っているのかというのが重要っぽい</span></p> <p class="footnote"><a href="#fn-81ce4884" name="f-81ce4884" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a class="keyword" href="http://d.hatena.ne.jp/keyword/Excel">Excel</a>ではなく正確には<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSV">CSV</a>ファイルを出力している</span></p> <p class="footnote"><a href="#fn-31f00958" name="f-31f00958" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">一般的に、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Excel">Excel</a>からコピペでデータを貼り付けられるソフトウェアは結構多い</span></p> <p class="footnote"><a href="#fn-8faa3840" name="f-8faa3840" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">実際にはブラウザの機能で自動入力されるようです</span></p> <p class="footnote"><a href="#fn-cdb55afa" name="f-cdb55afa" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">duck typingなので、明示的にinterfaceを実装しなくても、interfaceを満たしていれば実装したことになる</span></p> </div> threetea0407 駆け出し建築家にHTMLの書き方を教えた話 hatenablog://entry/26006613485834786 2019-12-19T00:00:00+09:00 2019-12-19T00:00:38+09:00 この記事は CAMPHOR- Advent Calendar 2019 19日目の記事です。 "駆け出し建築家" にWebサイトの作り方を教えた話をします。 <p>この記事は <a href="https://advent.camph.net/">CAMPHOR- Advent Calendar</a> 2019 19日目の記事です。</p> <p>"駆け出し建築家" にWebサイトの作り方を教えた話をします。</p> <h2>背景</h2> <p>僕にはTという友人がいる。高校生の時からの付き合いになる。彼は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%EC%B5%FE%B7%DD%C2%E7">東京芸大</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%FA%C3%DB%B3%D8">建築学</a>科で勉強していた人で、今は海外の大学院に留学している。このTという人が、Webサイトの作り方を教えてほしいという話を持ちかけてきた。</p> <p>Tは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%FA%C3%DB%B3%D8">建築学</a>科の学生だ。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%FA%C3%DB%B3%D8">建築学</a>科の学生は授業で「これこれのようなテーマで建築を考えなさい」というような課題をたくさん出されるらしい。そして彼らは、建築の模型やスケッチ、その説明文を制作して提出する。Tは、これらの模型の写真やスケッチ、文章を公開するためのWebサイトを作りたい、またそれとは別に、自分が書いた建築批評文や紀行文も公開したい、と考えていた。そして重要なことに、このWebサイトを作る際には、既存のブログサービスやWebサイト構築サービスは使いたくないというのだ。</p> <p>Tはすでにいくつかの文章をWeb上に公開していた。例えば<a href="https://issuu.com/">issuu</a>というPDF公開サイトで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DD%A1%BC%A5%C8%A5%D5%A5%A9%A5%EA%A5%AA">ポートフォリオ</a>を公開したり、<a href="https://note.com/">note</a>で批評文を書いたりしていた。そして、それらを一新し統合したいという気持ちから、<a href="https://ja.wix.com/">Wix</a>を使ってホームページを作った。しかし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wix">Wix</a>では十分にデザインがコン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%ED%A1%BC%A5%EB">トロール</a>できず、また有料プランに入らないと広告が表示されてしまうことから、納得行くWebサイトを作れずに悩んでいたのである。</p> <p>そのころ僕は、会社の同期の影響を受けて、「エモい図形」を創作するという遊びをしていた。そして創作した「エモい図形」を公開するために、以下のようなWebサイトを作っていた。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Femogallery.netlify.com%2F" title="Emotional Graphics" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://emogallery.netlify.com/">emogallery.netlify.com</a></cite></p> <p>見てもらえばわかるが、このWebサイトは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>などによる装飾のほとんどない、素のHTMLの無骨なデザインである<a href="#f-da1d1091" name="fn-da1d1091" title="私はデザインセンス、特に色彩に関するセンスが欠けているため、苦肉の策としてこのようなデザインを好んで使う">*1</a>。これを見たTは、凝った作りをしなくてもデザインとして成立させることができるのだから<a href="#f-25cbcaa5" name="fn-25cbcaa5" title="確か、このようなデザインのWebサイトを作るのはどのぐらい大変なのか、と事前に聞かれたような記憶がある">*2</a>、自分でHTMLを書くことでWebサイトを作ることも可能なのではないかと考え、僕に相談を持ちかけた<a href="#f-262de406" name="fn-262de406" title="T本人の言葉を借りると『(私の「エモい図形」のページに可能性を感じたのは)「ざっくり書いたな」という見た目そのままでも、デザインとして成立しているなと思ったから。逆の例としては、いかにホームページ作成サービスで「シンプル」で「ストイック」な見た目のものを作っても、そのコード(建築的にいうなれば工法)がシンプルでないことがあり、結果としてなにか嘘くさいものになってしまうように感じた。』">*3</a>。</p> <h2>考えたこと</h2> <p>僕はこの相談を受けて、ITに詳しくない人間に上記の条件を満たすWebサイトを作らせるにはどうしたら良いだろうか、ということを考えた。</p> <p>既存のサービスを利用するのが嫌だということなので、最終的には何らかの手段によってHTMLを生成して、Webサーバーにアップロードする必要があるだろう。継続的に記事を更新したいとのことなので、僕ならHTMLを全部手書きするのではなく、例えば<a href="https://jekyllrb.com/">Jekyll</a>を使って<a class="keyword" href="http://d.hatena.ne.jp/keyword/Markdown">Markdown</a>からHTMLを生成する仕組みを作っておいて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Markdown">Markdown</a>を<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>にpushするとビルドが走ってWebサイトが更新されるようにするだろう。</p> <p>しかし、Tは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%FA%C3%DB%B3%D8">建築学</a>科の学生であって、ITは専門外だ。一般的な人に比べればITのスキルはある方だとはいえ<a href="#f-c2894db5" name="fn-c2894db5" title="最近の建築学科の学生はプログラミングをしたり3Dプリンタを触ったりするらしい">*4</a>、我々Web<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE%A1%BC">プログラマー</a>と同じ技能を仮定してはいけない。彼の持ち物である<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a> PCで、どうやってJekyllを動かすというのか。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>は誰がインストールするのか。そもそもプログラミングができない人がJekyllをセットアップすることって可能なんだっけ?</p> <p>もしセットアップを僕が変わりにやってあげたとしたら、その手順はTには再現不可能なものとなり、Webサイトが存続する限り僕がメンテナンスする必要がある。いまJekyllのセットアップをしてあげるのは簡単だが、保守する責任が生まれ、将来に渡って僕の負担になる。これを防ぐために、Webサイトの保守責任はTにある、ということから出発して考え始めるべきだ。つまり、Tに理解可能な範囲の知識のみによって、Webサイトを構築する必要がある。<a href="#f-4840e79a" name="fn-4840e79a" title="Jekyllを使ってGitHub PagesでWebサイトを公開するには、Ruby、HTML、CSS、テンプレートエンジンという概念、Git、GitHubなどの知識が必要であり、これらをTに1から教えるのは現実的ではない。">*5</a></p> <p>よくよく考えてみると、当たり前のことながら、Webサイトを公開するのにJekyllもGitもプログラミングも必要ない。必要な知識は「HTMLと<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の書き方」と「HTMLファイルをWebに公開する方法」だけだ。HTMLと<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を手書きすることと、手動でHTMLをアップロードすることさえ厭わなければ、最小限の知識によってWebサイトを構築することができる。そしてその場合、Tから見て理解不可能な部分が少ないため、何かが壊れたとしても(基本的には)自分でトラブルシュートすることができるだろう。</p> <p>このようなことを考え、「多少の手作業は許容し、Tが理解できる知識の中でWebサイトを作る。HTMLと<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の書き方、HTMLファイルを公開する方法だけは僕が1から教える。」という方針で、Webサイトの作り方を考えることにした。</p> <h2>Webサイトの作り方</h2> <p>HTMLをまず教えた。タグというのがあって、例えばリンクを作りたかったら <code>a</code> タグに <code>href</code> を指定して文章を囲めばよいとか、<code>h3</code> タグというのがあって見出しにはこれを使うとか、画像を貼りたかったら <code>img</code> タグをつかって <code>src</code> に画像のURLを指定するとか。そして次に、URLの構造を教えた。まず<a class="keyword" href="http://d.hatena.ne.jp/keyword/https">https</a>/httpという文字列から始まり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>名というのが入り、後ろのスラッシュにより区切られた文字列は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構造と対応している、相対リンクと絶対リンクというのがあって...などなど。<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>はHTMLのタグの中の <code>style</code> 属性の中に直書きしてもいいし、 <code>style</code> タグの中にまとめて書いてもいい。HTMLタグには <code>id</code> と <code>class</code> というのを指定できて...云々。</p> <p>こうして書いたHTMLと<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を、<a href="https://www.netlify.com/">netlify</a> にアップロードして公開する方法も教えた。netlifyには、フォルダをドラッグ・アンド・ドロップで投げ込むと、それをそのまま公開してくれる機能があるので、Tはドラッグ・アンド・ドロップするだけで、Webサイトの動作確認もできるし、Webサイトを公開することもできる<a href="#f-4383fb4c" name="fn-4383fb4c" title="ステージング環境とかそういう難しいことは考えない">*6</a>。<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> Pagesにあるような、ブランチとかコミットとかの小難しい概念はない。</p> <p>自明かつ素朴だが、現代においては廃れてしまったように見える、古式ゆかしい "ホームページ" の作り方である。</p> <p>こうして苦労しながらも、TはWebサイトの作り方を学習し、Webサイトが公開された。</p> <h2>できたもの</h2> <p><a href="https://archi-tecture.netlify.com/works/works002">&#x3059;&#x304D;&#x307E;&#x306E;&#x30D3;&#x30EB;</a>は、Tが授業の課題で設計したビルの解説だ。写真がそれっぽいとWebサイトがそれっぽくなるということがわかる。この他にも、<a href="https://archi-tecture.netlify.com/works/works001">&#x3061;&#x3050;&#x306F;&#x3050;&#x306A;&#x30EA;&#x30A2;&#x30EB;</a>、<a href="https://archi-tecture.netlify.com/works/works011">&#x6C5A;&#x308C;&#x3068;&#x6B20;&#x3051;</a>など、Tが制作したものが公開されている。制作物の一覧ページもちゃんと作ってある:<a href="https://archi-tecture.netlify.com/">Archi-tecture</a>。</p> <p>また、建築作品とは別に、<a href="https://archi-pelago.netlify.com/articles/hk01">&#x5197;&#x9577;&#x306A;&#x90FD;&#x5E02;&#x3001;&#x6301;&#x7D9A;&#x3059;&#x308B;&#x904B;&#x52D5;</a>、<a href="https://archi-pelago.netlify.com/articles/hk02">&#x6C34;&#x306E;&#x3088;&#x3046;&#x306A;&#x3082;&#x306E;&#x306B;&#x6D78;&#x308B;&#x8857;</a>など、ブログのようなもの(紀行文?)も書き始めたようだ。この記事は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>を通じて "建築家界隈" に割と広く読まれたらしく、反響もそれなりにあったとのことだ。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a> cardも設定してあって(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>にリンクを貼って確かめてみよう!)、気合を感じる。</p> <p>全体的に、これはどうなんやというコードや、レイアウト崩れなどが散見されるが、動いてるしこれでよいのだと思う<a href="#f-c2a8ce95" name="fn-c2a8ce95" title="プログラマーは普通の人間に比べてtypo-sensitivityが高いということがわかる">*7</a>。Tはこれからもコンスタントに紀行文を書いていくつもりで、今は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Markdown">Markdown</a>で3つ目の文章を書くことに挑戦中らしい。</p> <h2>まとめ</h2> <p>このように、駆け出し建築家でありITの素人であるところの友人TにWebサイトの作り方を教えた。プログラミングができない人でもHTMLを手書きしてWebサイトを書けるようになるし、それは意義があることだ、という当たり前のことを再確認する結果となった。利便性を得るために既存のサービスを利用するのもよいが、自由を手に入れるという観点からは、このような基礎的な手法に立ち返るのも良いのではないかと思った。</p> <div class="footnote"> <p class="footnote"><a href="#fn-da1d1091" name="f-da1d1091" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">私はデザインセンス、特に色彩に関するセンスが欠けているため、苦肉の策としてこのようなデザインを好んで使う</span></p> <p class="footnote"><a href="#fn-25cbcaa5" name="f-25cbcaa5" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">確か、このようなデザインのWebサイトを作るのはどのぐらい大変なのか、と事前に聞かれたような記憶がある</span></p> <p class="footnote"><a href="#fn-262de406" name="f-262de406" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">T本人の言葉を借りると『(私の「エモい図形」のページに可能性を感じたのは)「ざっくり書いたな」という見た目そのままでも、デザインとして成立しているなと思ったから。逆の例としては、いかにホームページ作成サービスで「シンプル」で「ストイック」な見た目のものを作っても、そのコード(建築的にいうなれば工法)がシンプルでないことがあり、結果としてなにか嘘くさいものになってしまうように感じた。』</span></p> <p class="footnote"><a href="#fn-c2894db5" name="f-c2894db5" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">最近の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%FA%C3%DB%B3%D8">建築学</a>科の学生はプログラミングをしたり<a class="keyword" href="http://d.hatena.ne.jp/keyword/3D%A5%D7%A5%EA%A5%F3%A5%BF">3Dプリンタ</a>を触ったりするらしい</span></p> <p class="footnote"><a href="#fn-4840e79a" name="f-4840e79a" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">Jekyllを使って<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> PagesでWebサイトを公開するには、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>、HTML、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>、テンプレートエンジンという概念、Git、<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>などの知識が必要であり、これらをTに1から教えるのは現実的ではない。</span></p> <p class="footnote"><a href="#fn-4383fb4c" name="f-4383fb4c" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">ステージング環境とかそういう難しいことは考えない</span></p> <p class="footnote"><a href="#fn-c2a8ce95" name="f-c2a8ce95" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE%A1%BC">プログラマー</a>は普通の人間に比べて<a class="keyword" href="http://d.hatena.ne.jp/keyword/typo">typo</a>-sensitivityが高いということがわかる</span></p> </div> threetea0407 ブログを移転し、ブログを集約するページを作った hatenablog://entry/17680117127169703512 2019-05-26T21:50:44+09:00 2020-10-08T22:10:46+09:00 ブログを移転した ブログを移転しました。 https://genya0407.github.io/ Atomフィードもあるので、僕のブログを継続的に読みたい人がいたらRSSリーダーに登録してください*1。 https://genya0407.github.io/feed.xml この新しいブログは、JekyllとGitHub Pagesで作られています。 静的サイトであり広告などもないので、かなり高速にページを表示できています。 PageSpeed Insightsでトップページのスコアを算出すると100点でした。 新しいブログのスコア すでにいくつか記事も書いていますが、記事の作成に関しても… <h2>ブログを移転した</h2> <p>ブログを移転しました。</p> <p><a href="https://genya0407.github.io/">https://genya0407.github.io/</a></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Atom%A5%D5%A5%A3%A1%BC%A5%C9">Atomフィード</a>もあるので、僕のブログを継続的に読みたい人がいたら<a class="keyword" href="http://d.hatena.ne.jp/keyword/RSS%A5%EA%A1%BC%A5%C0%A1%BC">RSSリーダー</a>に登録してください<a href="#f-a0547529" name="fn-a0547529" title="そういえば2019年の現在にRSSリーダー使ってる人ってどのぐらいいるんですかね。僕は一応使ってますが、周囲の人が使ってる印象があんまりない。">*1</a>。</p> <p><a href="https://genya0407.github.io/feed.xml">https://genya0407.github.io/feed.xml</a></p> <p>この新しいブログは、Jekyllと<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> Pagesで作られています。 静的サイトであり広告などもないので、かなり高速にページを表示できています。 PageSpeed Insightsでトップページのスコアを算出すると100点でした。</p> <p><figure class="figure-image figure-image-fotolife" title="新しいブログのスコア"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20190526/20190526214433.png" alt="f:id:threetea0407:20190526214433p:plain" title="f:id:threetea0407:20190526214433p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>新しいブログのスコア</figcaption></figure></p> <p>すでにいくつか記事も書いていますが、記事の作成に関しても不便を感じたことはないです<a href="#f-4902dc84" name="fn-4902dc84" title="markdownで記事を書いてgithubにpushするとすぐに反映される">*2</a>。</p> <h2>移転の理由</h2> <p>ブログを移転した理由ですが、一番大きかったのは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%D6%A5%ED%A5%B0">はてなブログ</a>の表示が遅いということです。 遅いと言っても十分実用可能なレベルの遅さだとは思いますが、動作がキビキビしているとは言えません<a href="#f-7297eb39" name="fn-7297eb39" title="これははてな社の努力不足とかそういう話ではなくて、広告を表示したりしているのでその分遅くなるのは仕方がない。">*3</a>。 気にならない人が大半だとは思いますが、僕は結構気になっていました。</p> <p>実際、PageSpeed Insightsでスコアを算出しても、33点という低いスコアになります。</p> <p><figure class="figure-image figure-image-fotolife" title="このブログのスコア"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20190526/20190526214523.png" alt="f:id:threetea0407:20190526214523p:plain" title="f:id:threetea0407:20190526214523p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>このブログのスコア</figcaption></figure></p> <h2>ブログを集約するページ</h2> <p>そういうわけでブログを新しく作り、今のところは満足しているわけなんですが、将来的にはまた別の理由が発生してブログを移転したくなるかもしれません。移転するというか、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%D6%A5%ED%A5%B0">はてなブログ</a>には<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%D6%A5%ED%A5%B0">はてなブログ</a>の、Qiita には Qiita の、JekyllにはJekyllのいいところがあるので、使い分けていきたいという気持ちがあります。</p> <p>しかし、こういうことを繰り返すと、自分の書いた記事が色んな所に分散することになってしまいます。僕は自分の書いた記事の一覧をどこかに持っておきたいので、これは好ましくありません 。</p> <p>そういうわけで、僕が書いた記事を集約するページを作りました</p> <p><a href="https://articles.kuminecraft.xyz/">genya0407&#39;s articles</a></p> <p>裏では<a class="keyword" href="http://d.hatena.ne.jp/keyword/RSS">RSS</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/Atom">Atom</a>のフィードがいくつか登録してあって、これを15分毎にクロールしてページを更新するようになっています。 現状ではこのブログと、新しく作ったブログと、Qiitaのフィードが登録してあって、これらのブログサイトの記事が一覧で表示されます。 新しいブログを開設したとしても、そのブログのフィードを登録すればこのページに表示されるので、記事の分散を防ぐことができます。</p> <h2>まとめ</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%D6%A5%ED%A5%B0">はてなブログ</a>が遅いということに不満を抱き、Jekyllで高速なブログを開設しました。 またそれに合わせて、僕の書いた記事がブログをまたいで一覧表示されるページを作りました。</p> <div class="footnote"> <p class="footnote"><a href="#fn-a0547529" name="f-a0547529" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">そういえば2019年の現在に<a class="keyword" href="http://d.hatena.ne.jp/keyword/RSS%A5%EA%A1%BC%A5%C0%A1%BC">RSSリーダー</a>使ってる人ってどのぐらいいるんですかね。僕は一応使ってますが、周囲の人が使ってる印象があんまりない。</span></p> <p class="footnote"><a href="#fn-4902dc84" name="f-4902dc84" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a class="keyword" href="http://d.hatena.ne.jp/keyword/markdown">markdown</a>で記事を書いて<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>にpushするとすぐに反映される</span></p> <p class="footnote"><a href="#fn-7297eb39" name="f-7297eb39" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">これは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA">はてな</a>社の努力不足とかそういう話ではなくて、広告を表示したりしているのでその分遅くなるのは仕方がない。</span></p> </div> threetea0407 CAMPHOR-についてここらでひとこと言っておくか hatenablog://entry/17680117126999521434 2019-03-27T22:06:38+09:00 2019-03-29T01:06:49+09:00 京都のIT系学生コミュニティ「CAMPHOR-(カンファー)」に私が出入りするようになったのは2016年の冬なので,足掛け4年ほどCAMPHOR-に関わっていたことになります. この記事では,CAMPHOR-について説明した後,私がCAMPHOR-に感じた魅力を語ります. ※この記事はポエムです <p>京都のIT系学生コミュニティ「CAMPHOR-(カンファー)」に私が出入りするようになったのは2016年の冬なので,足掛け4年ほどCAMPHOR-に関わっていたことになります.</p> <p>この記事では,CAMPHOR-について説明した後,私がCAMPHOR-に感じた魅力を語ります.</p> <p>※この記事はポエムです</p> <h2>CAMPHOR-とは何か</h2> <p>CAMPHOR-とは,京都のIT系学生のコミュニティです.ここで「IT系学生」とは,主にエンジニアやデザイナーのことを指します<a href="#f-e0eb9cdb" name="fn-e0eb9cdb" title="歴史的にはこれに加えて「起業家」というロールの学生も多くいたようですが,ここ数年はあまり見かけません.というか,近頃の起業家を目指す学生はエンジニアになりがちという話な気もする.">*1</a>. コミュニティの活動の場として,京大のすぐ近くにある町家を利用して,<a href="https://camph.net/#house">CAMPHOR- HOUSE</a>という空間を提供しています<a href="#f-6225d0c2" name="fn-6225d0c2" title="京大のすぐ近くに拠点があるとはいっても,CAMPHOR-は京大生限定の団体ではありません.実際に,京都大学以外の大学の学生も多く出入りしています(See members).">*2</a>.</p> <p>CAMPHOR- HOUSEは,週に2~3日ぐらい,大体15:00~18:00ぐらいの時間帯に開館しており<a href="#f-a62e9e37" name="fn-a62e9e37" title="2019年3月現在">*3</a>,学生なら誰でも,事前の連絡なく自由に出入りできます. 開館時間には学生がやってきて,自分の作業をしたり,ITに関して雑談<a href="#f-9f978bb9" name="fn-9f978bb9" title="最近流行りの技術,最近作ったアプリケーションの話,分からない所を聞くなど">*4</a>をしたりします.</p> <h2>私がCAMPHOR-に関わるようになった経緯</h2> <p>私がCAMPHOR-に関わるようになった直接的な理由は,シバニャン(<a href="https://twitter.com/_6v_">@_6v_</a>)に誘われたことです.</p> <p>当時僕は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a>にハマっていて,<a href="https://kumano-dormitory.github.io/ryosai2016/">熊野寮祭の公式Webページ</a>を<a href="https://jaspervdj.be/hakyll/">Hakyll</a>という静的サイトジェネレーターで作っていました. これがCAMPHOR-のSlackの<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a>チャンネルで話題になり,どうやらこれを作った奴はシバニャンの知り合いらしいということで,開館日に連れてこられました.</p> <p>初めてCAMPHOR-に行った日は人が沢山いて楽しくお話をしたのですが,その中でも特に <a href="https://twitter.com/ymyzk">@ymyzk</a> が見せてくれた <a href="https://blog.ymyzk.com/2018/12/how-wsgi-lineprof-works/">wsgi_lineprof</a>に衝撃を受けました. それまで僕は簡単なWebアプリケーションぐらいは作ったことがありましたが,ライブラリは作ったことがなく,ましてやそれを人々が使える形で公開したことなどありませんでした. さらに<a class="keyword" href="http://d.hatena.ne.jp/keyword/wsgi">wsgi</a>_lineprof はCの拡張で記述されており,<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>が書ける自分でも内容が全く理解できませんでした. こんな高度な技術力を持つ人がいて凄いコミュニティだなあと素朴に感動したのを覚えています.</p> <p>また,これとは別に,プログラミングについて語り合えるコミュニティが欲しいと考えていた時期でもありました. というのも,それまで僕は一人でずっとプログラミングをやっていて,<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE%A1%BC">プログラマー</a>の知り合いというのがほとんどいなかったからです. そのため,自分がやってることが正しいのかわからないし,面白そうな技術について議論することもできないので,フラストレーションが溜まっていました.</p> <p>プログラミングのアルバイトもやっていて,色々教えてもらえて非常にためになりました<a href="#f-215a9df8" name="fn-215a9df8" title="雇ってくれた株式会社Spookies様には本当に感謝しています">*5</a>が,業務範囲外のことについて教えてもらえるわけではなかったし,アルバイト先の社員さんは社会人で基本的に忙しいので,満足がいくまでプログラミングについて語り合えるわけではありませんでした.</p> <p>そうした中でCAMPHOR-というコミュニティに誘ってもらい,強く興味を持ちました. そして,忘年会に参加したりして親交を深めた後,色々あって運営メンバーになりました.</p> <h2>CAMPHOR-の魅力</h2> <p>これは,人間の興味一般に言えることだと思いますが,人間の興味というのは,もともと自分ができたことや興味があったこと以外には広がりません<a href="#f-61240a85" name="fn-61240a85" title="少なくとも自分はそう">*6</a>. そして人間は,興味が無いことを自発的に勉強しようとは思いません. 新しいことに手を出すときというのは,何かしらの「外部の力」が働いたときです. CAMPHOR-は,そのような「外部の力」を僕に及ぼしてくれました.</p> <p>CAMPHOR-の魅力は,<strong>HOUSEでの雑談</strong> と <strong>個人での活動</strong> がいい感じに影響を及ぼしあって,結果としてコミュニティ自体も参加する個人もレベルが上っていくという構造にあると思います.</p> <p>実際に僕も,CAMPHOR-での雑談から様々な影響を受けました.例えば僕は, <a href="https://twitter.com/tomoyat1">@tomoyat1</a> に影響を受け,<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%B9%A5%C6%A5%E0%A5%D7%A5%ED">システムプロ</a>グラミングに興味を持ち,学びはじめました.その成果が表れたのが先日の「<a href="https://www.slideshare.net/yusukesangenya/ss-135407412">入門 シェル</a>」というタイトルの発表で<a href="#f-40383101" name="fn-40383101" title=" CAMPHOR- DAY 2019 ">*7</a>,主にOSの仕組みという観点から,シェルの作り方について解説しました.</p> <p>他にも,<a href="https://twitter.com/yu_i9">@yu-i9</a> に影響されて言語処理系に興味を持ち,<a href="https://github.com/genya0407/mal">LISP方言の処理系を実装</a>したり<a href="#f-3b5937d8" name="fn-3b5937d8" title="中途半端なところで終わっていますが...">*8</a>.<a href="https://twitter.com/ishiy1993">@ishiy</a> や <a href="https://twitter.com/ymyzk">@ymyzk</a>の影響で<a href="http://dawn.hateblo.jp/entry/2019/03/13/185112">Rustを使うようになった</a>り,<a href="http://tomokortn.hatenablog.com/">@tomokortn</a> に<a href="https://www.sbcr.jp/products/4797351422.html">良い入門書</a>を教えてもらってデザインを学んでみたり,と様々な影響を得て,自身の学習に繋げることができました.</p> <p>逆に,僕がCAMPHOR-に及ぼした影響<a href="#f-2beca8d5" name="fn-2beca8d5" title="ここに関しては完全に僕の想像なので,向こうがどう思ってるかはわからない.僕に何かしらの影響を受けたと思ってる人はその内容をブログに書いてほしい.">*9</a>としては,Web開発の知見を共有したということがあります. 特に,初心者がどうやってWeb開発を始めるべきか,ということについてかなりアド<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9">バイス</a>できたと思います. 例えば,<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>から始めて挫折してしまった人に,Webアプリを構成する要素 ― HTML,<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>,DB,HTTP,プログラミング ― を学んだ後に<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>を学ぶべきだと伝えたり<a href="#f-3311ee69" name="fn-3311ee69" title="その順序でないとRailsは理解できないと考えているため">*10</a>,実現したい要件によっては,静的サイトジェネレータの存在を教えたり,単に静的なHTMLファイルを作るところから始めてはどうかとアド<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9">バイス</a>したりしました.</p> <h2>CAMPHOR-の魅力を伝えることの難しさ</h2> <p>上でも書いたとおり,CAMPHOR-の魅力というのは,魅力的な学生の関係性から繰り出される雑談にあると思うんですが,これを説明するのって難しいんですよね.公式HPが分かりづらいっていう話はいろいろ聞いてはいるんですが,そもそもCAMPHOR-の活動自体が分かりづらいので仕方がない.</p> <p>だって「魅力的な学生の関係性から繰り出される雑談を活動として行っています」なんて公式HPに書けなくないですか?もともとデザインがシャレオツ過ぎて一部の人間に<a href="https://zunda3rd.hatenablog.com/entry/spiral_book">抵抗感を引き起こしている</a>ところに,「魅力的な学生の関係性から繰り出される雑談を活動として行っています」なんて書いたら,一発で意識高い系団体だと思われちゃうじゃないですかやだー<a href="#f-f991adef" name="fn-f991adef" title="実際大昔にCAMPHOR-の公式HPを見たことがあるのだが,その時は意識高い系団体がなんかやっとるなぐらいにしか思ってなかった">*11</a>.</p> <p>でも絶対いい場所だと思うんですよ,CAMPHOR-.B4とかM2になって来月から就職ですなんてタイミングで訪問してきて「もっと早く来ればよかった」って言う人も結構いるし,それはとても残念だと思ってます<a href="#f-e429a924" name="fn-e429a924" title="最近も新しいメンバーが一気に増えてきてさらに面白い感じになってきてるんですが,僕は4月から就職なのでもう関われないのが悲しいです.一周回って腹が立ってくる.2年前にやれ">*12</a>.</p> <p>CAMPHOR-を正しく理解してもらって,こういう悲劇をなくしていきたい.そのためには,CAMPHOR-のメンバーが自分が感じたCAMPHOR-の魅力を自分の言葉で発信していくというのが大事だと思う<a href="#f-beee103a" name="fn-beee103a" title="CAMPHOR-にはこういう人がおるでというのを知らせる意味でも個人の発信は大事(=ブランディング) ">*13</a>.そういう気持ちで今このブログを書いてる.</p> <p>でも結局CAMPHOR-の良さみたいなのは一回来てもらわなきゃわからんだろうという気持ちも一方ではあるんですよね.一回来てもらうための手段としてCAMPHOR-のメンバーの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D6%A5%E9%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">ブランディング</a>をやっていくというのは非常に有効な手段ではあると思うのだけれど.</p> <h2>まとめ</h2> <p>そういうわけでCAMPHOR-に興味を持った学生の皆さん,是非一度遊びに来てください! CAMPHOR- HOUSEの場所は<a href="https://www.google.com/maps/place/CAMPHOR-+HOUSE/@35.0249122,135.7766559,20.04z/data=!4m5!3m4!1s0x60010858be12d1a3:0x33f52187b90d65a4!8m2!3d35.024888!4d135.776916">ここ</a>で,開館時間は<a href="https://camph.net/schedule/">こんな感じ</a>です.</p> <p>どんな人がいるのか気になるという人は,<a href="https://camph.net/members/">Members</a>や<a href="https://camph.net/members_blog/">Members' Blog</a>をチェックしてみてください. きっと興味の近い学生がいると思います.</p> <p>CAMPHOR-よいとこ一度はおいで.</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcamph.net%2F" title="CAMPHOR- カンファー : 京都のIT系学生コミュニティ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://camph.net/">camph.net</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-e0eb9cdb" name="f-e0eb9cdb" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">歴史的にはこれに加えて「起業家」というロールの学生も多くいたようですが,ここ数年はあまり見かけません.というか,近頃の起業家を目指す学生はエンジニアになりがちという話な気もする.</span></p> <p class="footnote"><a href="#fn-6225d0c2" name="f-6225d0c2" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">京大のすぐ近くに拠点があるとはいっても,CAMPHOR-は京大生限定の団体ではありません.実際に,<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%FE%C5%D4%C2%E7%B3%D8">京都大学</a>以外の大学の学生も多く出入りしています(See <a href="https://camph.net/members/">members</a>).</span></p> <p class="footnote"><a href="#fn-a62e9e37" name="f-a62e9e37" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">2019年3月現在</span></p> <p class="footnote"><a href="#fn-9f978bb9" name="f-9f978bb9" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">最近流行りの技術,最近作ったアプリケーションの話,分からない所を聞くなど</span></p> <p class="footnote"><a href="#fn-215a9df8" name="f-215a9df8" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">雇ってくれた<a href="https://www.spookies.co.jp/">株式会社Spookies様</a>には本当に感謝しています</span></p> <p class="footnote"><a href="#fn-61240a85" name="f-61240a85" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">少なくとも自分はそう</span></p> <p class="footnote"><a href="#fn-40383101" name="f-40383101" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text"> <a href="https://blog.camph.net/event/camphor-day-2019/">CAMPHOR- DAY 2019</a> </span></p> <p class="footnote"><a href="#fn-3b5937d8" name="f-3b5937d8" class="footnote-number">*8</a><span class="footnote-delimiter">:</span><span class="footnote-text">中途半端なところで終わっていますが...</span></p> <p class="footnote"><a href="#fn-2beca8d5" name="f-2beca8d5" class="footnote-number">*9</a><span class="footnote-delimiter">:</span><span class="footnote-text">ここに関しては完全に僕の想像なので,向こうがどう思ってるかはわからない.僕に何かしらの影響を受けたと思ってる人はその内容をブログに書いてほしい.</span></p> <p class="footnote"><a href="#fn-3311ee69" name="f-3311ee69" class="footnote-number">*10</a><span class="footnote-delimiter">:</span><span class="footnote-text">その順序でないと<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>は理解できないと考えているため</span></p> <p class="footnote"><a href="#fn-f991adef" name="f-f991adef" class="footnote-number">*11</a><span class="footnote-delimiter">:</span><span class="footnote-text">実際大昔にCAMPHOR-の公式HPを見たことがあるのだが,その時は意識高い系団体がなんかやっとるなぐらいにしか思ってなかった</span></p> <p class="footnote"><a href="#fn-e429a924" name="f-e429a924" class="footnote-number">*12</a><span class="footnote-delimiter">:</span><span class="footnote-text">最近も新しいメンバーが一気に増えてきてさらに面白い感じになってきてるんですが,僕は4月から就職なのでもう関われないのが悲しいです.一周回って腹が立ってくる.2年前にやれ</span></p> <p class="footnote"><a href="#fn-beee103a" name="f-beee103a" class="footnote-number">*13</a><span class="footnote-delimiter">:</span><span class="footnote-text">CAMPHOR-にはこういう人がおるでというのを知らせる意味でも個人の発信は大事(=<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D6%A5%E9%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">ブランディング</a>) </span></p> </div> threetea0407 質問箱クローンをRustで作った話 hatenablog://entry/10257846132656070019 2019-03-13T18:51:12+09:00 2019-03-31T11:31:25+09:00 1年ぐらい前に質問箱(peing.net)を真似て匿名質問サービスを作成しました. これに関して技術的な話と技術的でない話をします.技術的な話というのはRustでWebサービスを作る知見で,技術的でない話というのは質問箱を自分で運用するとどういう感じになるかという知見です. <p>1年ぐらい前に質問箱(peing.net)を真似て匿名質問サービスを作成しました. これに関して技術的な話と技術的でない話をします.技術的な話というのはRustで<a class="keyword" href="http://d.hatena.ne.jp/keyword/Web%A5%B5%A1%BC%A5%D3%A5%B9">Webサービス</a>を作る知見で,技術的でない話というのは質問箱を自分で運用するとどういう感じになるかという知見です.</p> <ul class="table-of-contents"> <li><a href="#作ったもの">作ったもの</a></li> <li><a href="#背景">背景</a></li> <li><a href="#技術的な話">技術的な話</a><ul> <li><a href="#仕様策定">仕様策定</a></li> <li><a href="#実装">実装</a><ul> <li><a href="#Webフレームワーク-Rocket-に関する所感">Webフレームワーク "Rocket" に関する所感</a></li> <li><a href="#ORM-Diesel-に関する所感">ORM "Diesel" に関する所感</a></li> <li><a href="#画像の生成">画像の生成</a></li> </ul> </li> <li><a href="#デプロイ">デプロイ</a><ul> <li><a href="#Heroku">Heroku</a></li> <li><a href="#さくらのVPS">さくらのVPS</a></li> </ul> </li> <li><a href="#速度について">速度について</a></li> </ul> </li> <li><a href="#オマケ技術的でない話">オマケ:技術的でない話</a><ul> <li><a href="#質問してくるユーザーの数は非常に少ない">質問してくるユーザーの数は非常に少ない</a></li> <li><a href="#自演するやつがいる">自演するやつがいる</a></li> </ul> </li> <li><a href="#追記20190319">追記(2019/03/19)</a></li> </ul> <h2 id="作ったもの">作ったもの</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Freing.kuminecraft.xyz%2F" title="Reing" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe></p> <p>質問お待ちしてます!!!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgenya0407%2Freing" title="genya0407/reing" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe></p> <p>ソースは公開しており,気持ち程度にREADMEも書いてあるので,あなたも自分の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を立てることができます.</p> <h2 id="背景">背景</h2> <p>当初は <a href="https://peing.net/ja/genya0407">"本家質問箱"</a> を使っていたんですが,2018年の5月ぐらいにクッソ動作が重くなってストレスがすごく,また当時はRustでなんか作りたいという気持ちがあり,自前で作ったら快適になりそうだと思って作りました.</p> <h2 id="技術的な話">技術的な話</h2> <h3 id="仕様策定">仕様策定</h3> <p>私が作った質問箱には以下のような機能を実装しました.</p> <ul> <li>匿名で質問をPOSTする機能</li> <li>質問投稿の通知をメールで私に投げる機能</li> <li>回答者(=私)のログイン機能</li> <li>回答する機能</li> <li>回答されたときに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>にツイートする機能</li> </ul> <p>この質問箱は,1つのサーバーに複数人の回答者を登録するということができないように作っています.つまり,<b>1つのサーバーには回答者は1人(=管理者)しかいません</b>. この方針により,例えばユーザー登録機能を作らなくて良かったりするなど,機能の複雑度が大幅に下がっています.</p> <h3 id="実装">実装</h3> <p>Rustでサーバーサイドを作りました.RustでWebアプリを作るといいことがあるのかという話はもちろんあるが,そこについてはあまり考えずにやる.俺はRustが書きたかったんだよ!!! まあ真面目な話をすると,速いアプリケーションにしたかったが,Goは宗教上の理由で使えないのでRustになったという側面もあります.</p> <p>WebフレームワークとしてはRocket,ORMとしては<a class="keyword" href="http://d.hatena.ne.jp/keyword/Diesel">Diesel</a>を使用しました.また,質問文の画像を生成する部分は特に頑張って自作しました.</p> <h4 id="Webフレームワーク-Rocket-に関する所感">Webフレームワーク "Rocket" に関する所感</h4> <p>RocketはfastでsecureなWebフレームワークですが,flexibility, usability, type safetyも犠牲にせんぞということだそうです.</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Frocket.rs%2F" title="Rocket - Simple, Fast, Type-Safe Web Framework for Rust" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://rocket.rs/">rocket.rs</a></cite></p> <p>大体こういう感じでエンドポイントが生やせます:</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synPreProc">#[get(</span><span class="synConstant">&quot;/hello/&lt;name&gt;/&lt;age&gt;&quot;</span><span class="synPreProc">)]</span> <span class="synStatement">fn</span> <span class="synIdentifier">hello</span>(name: <span class="synType">String</span>, age: <span class="synType">u8</span>) <span class="synStatement">-&gt;</span> <span class="synType">String</span> { <span class="synPreProc">format!</span>(<span class="synConstant">&quot;Hello, {} year old named {}!&quot;</span>, age, name) } <span class="synStatement">fn</span> <span class="synIdentifier">main</span>() { <span class="synPreProc">rocket</span><span class="synSpecial">::</span><span class="synIdentifier">ignite</span>().<span class="synIdentifier">mount</span>(<span class="synConstant">&quot;/&quot;</span>, <span class="synPreProc">routes!</span>[hello]).<span class="synIdentifier">launch</span>(); } </pre> <p>URLのパラメーターをシュッと関数の引数に入れてくれるのが面白いですね. ここには書いてないですけど,POSTリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トのbodyをいい感じにパースして構造体に入れてくれたりもします. どちらの場合も,型が合致しないときはマッチする他のエンドポイントを見に行くようになっています.</p> <p>テンプレートエンジンのサポートがあるので,古典的にHTMLをサーバーサイドで合成して返すこともできるし,もちろん<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>を返して<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>として使うこともできるようになってます. テンプレートエンジンは<a href="https://github.com/Keats/tera">Tera</a>というのを使うことができて,こういう感じで書けます<a href="#f-ae3c5f9d" name="fn-ae3c5f9d" title="teraのREADMEから引用">*1</a>.</p> <pre class="code lang-django" data-lang="django" data-unlink>&lt;ul&gt; <span class="synPreProc">{% </span><span class="synStatement">for</span><span class="synPreProc"> user </span><span class="synStatement">in</span><span class="synPreProc"> users %}</span> &lt;li&gt;&lt;a href=&quot;<span class="synPreProc">{{ user.url }}</span>&quot;&gt;<span class="synPreProc">{{ user.username }}</span>&lt;/a&gt;&lt;/li&gt; <span class="synPreProc">{% </span><span class="synStatement">endfor</span><span class="synPreProc"> %}</span> &lt;/ul&gt; </pre> <p>このときコントローラはこういう感じになっているとします.</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synPreProc">#[derive(Serialize)]</span> <span class="synStatement">struct</span> <span class="synIdentifier">User</span> { <span class="synStatement">pub</span> url: <span class="synType">String</span>, <span class="synStatement">pub</span> username: <span class="synType">String</span> } <span class="synPreProc">#[derive(Serialize)]</span> <span class="synStatement">struct</span> <span class="synIdentifier">Index</span> { <span class="synStatement">pub</span> users: <span class="synType">Vec</span><span class="synStatement">&lt;</span>User<span class="synStatement">&gt;</span> } <span class="synPreProc">#[get(</span><span class="synConstant">&quot;/&quot;</span><span class="synPreProc">)]</span> <span class="synStatement">fn</span> <span class="synIdentifier">index</span>() <span class="synStatement">-&gt;</span> Template { <span class="synStatement">let</span> users <span class="synStatement">=</span> <span class="synIdentifier">fetch_users</span>(); <span class="synStatement">let</span> context <span class="synStatement">=</span> Index { users: users }; <span class="synPreProc">Template</span><span class="synSpecial">::</span><span class="synIdentifier">render</span>(<span class="synConstant">&quot;index&quot;</span>, <span class="synType">&amp;</span>context) } </pre> <p>完全にJinja2リスペクトだというのがわかります<a href="#f-27a3a93a" name="fn-27a3a93a" title="Teraという名前もJinja(神社)に対するTera(寺)ということなのだろう.そういえばTemplateってTemple(寺院)っぽい綴りですね.なんという偶然!">*2</a>. <code>Template::render</code> 関数には <a href="https://docs.rs/serde/1.0.89/serde/trait.Serialize.html">Serialize</a> traitを実装しているものなら何でも <code>context</code> として渡せるようです. 今回は構造体を定義してSerializeをderiveしたものを渡していますが,普通にHashMapとかも渡せます.</p> <p>この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>は速いのかということに関しては普通に高速だと思います.そもそも<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>ロジックが少ないしDB呼び出しも少ないから遅くなりようがないんですが.... ただ,RocketはRustの<b>nightly<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>を前提</b>にしているので,そこがちょっとつらいです. 2ヶ月ぶりに機能を追加しようと思ったら<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>のインストールから始まるみたいなのつらくないですか?僕はつらいです.</p> <h4 id="ORM-Diesel-に関する所感">ORM "<a class="keyword" href="http://d.hatena.ne.jp/keyword/Diesel">Diesel</a>" に関する所感</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Diesel">Diesel</a>はRustのORMです.今回は主にmigrationが欲しくて使いました.</p> <p><iframe src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fdiesel.rs%2F" title="Diesel" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="http://diesel.rs/">diesel.rs</a></cite></p> <p>DBアクセスをこんな感じで書ける<a href="#f-006716d4" name="fn-006716d4" title="http://diesel.rs/ より引用">*3</a>.</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synPreProc">#[derive(Insertable)]</span> <span class="synPreProc">#[table_name=</span><span class="synConstant">&quot;users&quot;</span><span class="synPreProc">]</span> <span class="synStatement">struct</span> <span class="synIdentifier">NewUser</span><span class="synStatement">&lt;</span><span class="synSpecial">'a</span><span class="synStatement">&gt;</span> { name: <span class="synType">&amp;</span><span class="synSpecial">'a</span> <span class="synType">str</span>, hair_color: <span class="synType">Option</span><span class="synStatement">&lt;</span><span class="synType">&amp;</span><span class="synSpecial">'a</span> <span class="synType">str</span><span class="synStatement">&gt;</span>, } <span class="synStatement">let</span> new_users <span class="synStatement">=</span> <span class="synPreProc">vec!</span>[ NewUser { name: <span class="synConstant">&quot;Sean&quot;</span>, hair_color: <span class="synConstant">Some</span>(<span class="synConstant">&quot;Black&quot;</span>) }, NewUser { name: <span class="synConstant">&quot;Gordon&quot;</span>, hair_color: <span class="synConstant">None</span> }, ]; <span class="synIdentifier">insert_into</span>(users) .<span class="synIdentifier">values</span>(<span class="synType">&amp;</span>new_users) .<span class="synIdentifier">execute</span>(<span class="synType">&amp;</span>connection); </pre> <p>あとmigrationがあったり,複雑なクエリが書ける<a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>があったり,型安全だったり,<b>データベースからテーブルの定義を導出</b>してくれたりという感じで結構便利です.</p> <p>一方で,<a class="keyword" href="http://d.hatena.ne.jp/keyword/DSL">DSL</a>が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>時に生成されるので,ドキュメントが検索しづらいという問題があります.あと導入が若干面倒です. また,Rocketと組み合わせて使うためにはコネクションプールをいい感じに取り扱ってやる必要があって,そこはかなり面倒でした. 具体的には,<a href="https://github.com/diesel-rs/r2d2-diesel">r2d2-diesel</a>というライブラリがあるので,これとRocketの<a href="https://rocket.rs/v0.4/guide/state/">state</a>を組みわせることで,Rocketで<a class="keyword" href="http://d.hatena.ne.jp/keyword/diesel">diesel</a>を使うことができます.</p> <h4 id="画像の生成">画像の生成</h4> <p>質問文を画像に埋め込んでポストするというのを実現したいというのがあって,これは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>の140字制限を回避しつつ質問文を全てツイートに入れたいからですね. つまり,こういう画像を生成したいわけです:</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20190313/20190313154853.jpg" alt="f:id:threetea0407:20190313154853j:plain" title="f:id:threetea0407:20190313154853j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>この機能はRustの<a href="https://docs.rs/image/0.21.0/image/">image</a>というクレートで画像を生成することで実現しました. 素朴に質問文を一文字一文字画像に埋め込んでいます. 文字の折返しもちゃんとやるようになっていて<a href="#f-611c9211" name="fn-611c9211" title="禁則処理は実装してないです">*4</a>,これが一番面倒だった.</p> <p>また,この部分だけは別のレポジトリに切り出していて,<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%DE%A5%F3%A5%C9%A5%E9%A5%A4%A5%F3">コマンドライン</a>から文字画像を生成できるようになっています.</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgenya0407%2Freing_text2image" title="genya0407/reing_text2image" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/genya0407/reing_text2image">github.com</a></cite></p> <p>こうすると</p> <pre class="code console" data-lang="console" data-unlink>$ cargo run -- &#39;5000兆円欲しい!!&#39; --brand &#34;ぼくがかんがえたさいきょうの質問サービス&#34; --rgb 00,00,00</pre> <p>こうなる</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20190313/20190313161632.jpg" alt="f:id:threetea0407:20190313161632j:plain" title="f:id:threetea0407:20190313161632j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>こういう感じで,テキスト,枠色,ロゴなどを自由に変えられるので,自分で質問箱を作りたいという方は使って下さい.</p> <h3 id="デプロイ">デプロイ</h3> <p>もともとHerokuでやってたんですが,高速化を試みていくとHerokuへの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%BB%FE%B4%D6">アクセス時間</a>のせいで主に遅くなってるっぽかったので,途中から<b>さくらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/VPS">VPS</a></b>に載せ替えました.</p> <h4 id="Heroku">Heroku</h4> <p>HerokuにRustのアプリケーションをデプロイするには,これを使っていました.</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Femk%2Fheroku-buildpack-rust" title="emk/heroku-buildpack-rust" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/emk/heroku-buildpack-rust">github.com</a></cite></p> <p>Herokuへの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%BB%FE%B4%D6">アクセス時間</a>が長いのは,FreeプランだとUSにあるサーバーを使うことになるからですね. 金を払えばTokyoリージョンのHerokuも使えるらしいが...</p> <h4 id="さくらのVPS">さくらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/VPS">VPS</a></h4> <p>フツーに80番ポートでnginxでリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを受けて,アプリケーションが待ち受ける別のポートにリダイレクトしています. Dockerとかもほとんど使ってない<a href="#f-b0b67774" name="fn-b0b67774" title="このサーバーはArchLinuxなんですが,ArchのパッケージマネージャでPostgreSQLを入れると勝手にバージョンアップされて悲惨なことになるので,PostgreSQLのバージョンを固定するためだけにDockerを使っている.">*5</a>.古き良きWebアプリケーションという感じ.</p> <p>さくらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/VPS">VPS</a>は(日本国内からの)アクセス速度が速いです.具体的な数字は忘れたんですが,Herokuからさくらに移行しただけでめっちゃ速くなったので感動した記憶がある.</p> <p><blockquote class="twitter-tweet" data-lang="HASH(0xe4f9d58)"><p lang="ja" dir="ltr">さくらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/VPS">VPS</a>にreing移転してからやたら早くなったなと思って計測したら、<a class="keyword" href="http://d.hatena.ne.jp/keyword/https">https</a>のレスポンス受け取るまでの速度がだいたい10倍ぐらい速くなってた。</p>&mdash; 𝑨𝒓𝒓𝒂𝒚-𝒔𝒂𝒏 (@genya0407) <a href="https://twitter.com/genya0407/status/1011073082448941056?ref_src=twsrc%5Etfw">June 25, 2018</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <h3 id="速度について">速度について</h3> <p>速いの?→速いぞ!!!</p> <p>まずこれがpeing.netの<a href="https://developers.google.com/speed/pagespeed/insights/?hl=ja">PageSpeed Insights</a>のスコアです.</p> <p><figure class="figure-image figure-image-fotolife" title="peing.netのPageSpeed Insightsの結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20190313/20190313170310.png" alt="f:id:threetea0407:20190313170310p:plain" title="f:id:threetea0407:20190313170310p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>peing.netのPageSpeed Insightsの結果</figcaption></figure></p> <p>そしてこれが私が作った質問サービスのスコアです.</p> <p><figure class="figure-image figure-image-fotolife" title="私が作った質問箱のPageSpeed Insightsの結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20190313/20190313170246.png" alt="f:id:threetea0407:20190313170246p:plain" title="f:id:threetea0407:20190313170246p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>私が作った質問箱のPageSpeed Insightsの結果</figcaption></figure></p> <p>まずRustで書いてるので速いというのと,あとpeing.netの方は質問の画像を表示しているのですが,こっちは質問文は文章だけを表示しており,画像は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>に投稿するときだけ使うようにしています.これらが主に速度に寄与していると思います.</p> <p>とはいえ,広告なし,利用者1人でやってたら早くなるのは当たり前ですね.</p> <h2 id="オマケ技術的でない話">オマケ:技術的でない話</h2> <p>質問してくるユーザーの<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>を保存するようにしているんですが,それによっていくつかわかったことがあります</p> <h3 id="質問してくるユーザーの数は非常に少ない">質問してくるユーザーの数は非常に少ない</h3> <p>このサービスを作成してから大体2900件ぐらいの質問が来てるんですが,これは色んな人からまんべんなく質問が来てるわけではなくて,少数のユーザーからの質問が主であるということがわかっています<a href="#f-63f51d74" name="fn-63f51d74" title="人にもよると思うが">*6</a>.</p> <p>例えば,質問してきた数が多い順に<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>を並べて質問数の累積和を取ると,上から10個目ぐらいで質問数が75%に達し,35個目ぐらいで90%を超えます. つまり,よく質問する35人で90%以上の質問をしているということになります<a href="#f-19290c41" name="fn-19290c41" title="IPアドレスの数自体は218ぐらいあるので,割と綺麗にパレートの法則を満たしている気がします">*7</a>.</p> <p><figure class="figure-image figure-image-fotolife" title="質問数の集計結果(IPアドレスは一応モザイク)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20190313/20190313174720.jpg" alt="f:id:threetea0407:20190313174720j:plain" title="f:id:threetea0407:20190313174720j:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>質問数の集計結果(<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>は一応モザイク)</figcaption></figure></p> <p>つまり,質問してくるユーザーの数は少なく,一部の熱心なユーザーが大量の質問をポストしてくるという構造がありそうということがわかります.</p> <h3 id="自演するやつがいる">自演するやつがいる</h3> <p>匿名インターネットで自演は当たり前のことなんですが,綺麗に自演が判明したことがあったので共有します.</p> <p>一時期女叩きするやつにめっちゃ粘着されてた時期がありました.</p> <p><blockquote class="twitter-tweet" data-lang="HASH(0xd981958)"><p lang="ja" dir="ltr">「Aという考えが存在するのでAだ」というのを本気で主張してるんだとしたら、流石にそれは「バカ」以外の何者でもないと思うんですけど、流石にネタですよね? <a href="https://twitter.com/hashtag/reing?src=hash&amp;ref_src=twsrc%5Etfw">#reing</a> <a href="https://t.co/eIG65keEnk">https://t.co/eIG65keEnk</a> <a href="https://t.co/Agp74xHxkh">pic.twitter.com/Agp74xHxkh</a></p>&mdash; 𝑨𝒓𝒓𝒂𝒚-𝒔𝒂𝒏 (@genya0407) <a href="https://twitter.com/genya0407/status/1023196069004337153?ref_src=twsrc%5Etfw">July 28, 2018</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p><blockquote class="twitter-tweet" data-lang="HASH(0xe4f9d58)"><p lang="ja" dir="ltr">そんなものないでしょ。「Aというデータが存在しないので¬Aだ」という論法は成り立ちませんからね。 <a href="https://twitter.com/hashtag/reing?src=hash&amp;ref_src=twsrc%5Etfw">#reing</a> <a href="https://t.co/cjhOVdDx6N">https://t.co/cjhOVdDx6N</a> <a href="https://t.co/KdGmfCn3J1">pic.twitter.com/KdGmfCn3J1</a></p>&mdash; 𝑨𝒓𝒓𝒂𝒚-𝒔𝒂𝒏 (@genya0407) <a href="https://twitter.com/genya0407/status/1023213023341600770?ref_src=twsrc%5Etfw">July 28, 2018</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p><blockquote class="twitter-tweet" data-lang="HASH(0xd981958)"><p lang="ja" dir="ltr">あなた女叩きの投稿を繰り返してますけど、こういうのは「ネチネチ根に持つ」とか「女々しい」とかって言わないんですかね。<br>それとも自分が「少数派」だという自覚があるんでしょうか? <a href="https://twitter.com/hashtag/reing?src=hash&amp;ref_src=twsrc%5Etfw">#reing</a> <a href="https://t.co/hMqlzuDAJv">https://t.co/hMqlzuDAJv</a> <a href="https://t.co/2j2M1HDVQf">pic.twitter.com/2j2M1HDVQf</a></p>&mdash; 𝑨𝒓𝒓𝒂𝒚-𝒔𝒂𝒏 (@genya0407) <a href="https://twitter.com/genya0407/status/1023215868392554496?ref_src=twsrc%5Etfw">July 28, 2018</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p><blockquote class="twitter-tweet" data-lang="HASH(0xd981958)"><p lang="ja" dir="ltr">なるほど <a href="https://twitter.com/hashtag/reing?src=hash&amp;ref_src=twsrc%5Etfw">#reing</a> <a href="https://t.co/AsNseiPCmR">https://t.co/AsNseiPCmR</a> <a href="https://t.co/9hnSbs2MYV">pic.twitter.com/9hnSbs2MYV</a></p>&mdash; 𝑨𝒓𝒓𝒂𝒚-𝒔𝒂𝒏 (@genya0407) <a href="https://twitter.com/genya0407/status/1023217073084743680?ref_src=twsrc%5Etfw">July 28, 2018</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p>この5倍ぐらい質問がPOSTされてたんですが多すぎるので割愛します. 僕は飛んできた質問はなるべく返すようにしているんですが流石ウンザリしてきたタイミングでした. ちなみにこれ全部同じ<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>から飛んできた質問です.</p> <p>このような質問に紛れて,以下のような質問が飛んできました<a href="#f-c5dc6986" name="fn-c5dc6986" title="もはや質問でもない">*8</a>.</p> <blockquote><p>当方女ですが、女の方が悪いです。 当然です。 男の人が優しい人が多い理由として、 男の人は 『他人から良い評価を得られる事に生き甲斐を感じている』 からでしょう。 男の人は周りからの目線をよく気にします。 どう、思われてるかな? モテたい、出世したいなど だから、人に対して優しく出来たり、 だから、義理人情に熱い人が多い →よって、自ずと性格の優しい人が増えるのだと思います。 対して女が性格悪いと言われる理由に 『いかに自分がお姫様扱いされるか』 に生き甲斐を感じれる人が多いからでしょう。 だから大して自分から動きもせずに 楽がしたい、 甘やかされてるから我が儘も言い放題だから その証拠に、 彼氏にしたい人=会話が面白い人でぇ~ 結婚したい人=年収500万以上でぇ~ なんていう私利私欲の極みと言えるゲス、ばっかりですよね。 だから男の人は性格が優しい だから女は全員性格が悪い そう叩かれるのは当然の結果だと思います。 女も良く思われたいなら努力しろ!! 女だからって甘ったれんな!! これを見てる人で自信を持って 『私はそんな事無い!!』 と言い切れるなら、 聞き流してくれて良いですよ。</p></blockquote> <p>突然女性を名乗る匿名の人からの質問が飛んできましたね. 「当方女ですが」の破壊力が高いのはさておいて,こんなこと言う女性おるんかいなという気持ちに当然なります<a href="#f-7fc029ba" name="fn-7fc029ba" title="これは女性は悪口言わないだろうみたいな話ではなく,同性のことをここまで悪し様に描写して異性を持ち上げるやつおるか?という気持ちでした.最近ではそのような男性・女性はしばしばいるということがわかってきたのでなんやという感じですが...">*9</a>. そこで質問者の<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>を見ると,この質問は上に貼った<b>女叩きの質問の<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>と同じ<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>から投稿されている</b>ということがわかりました. 女叩きの中で「自分は男である」と言っているので,これは明らかな自演です.</p> <p>そのことを回答で指摘したところ,女叩きの質問はピタッと止まりました<a href="#f-3108078c" name="fn-3108078c" title="ちなみにこのIPアドレスはその後もいくつか質問をしているのだが,「明日手術を受けるのですが、怖くてたまりません。何か勇気の出る言葉をください」という質問を最後に質問をしなくなっています.">*10</a>.</p> <p><blockquote class="twitter-tweet" data-lang="HASH(0xd981958)"><p lang="ja" dir="ltr">自演失敗してますよ。<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>がさっき自分を女々しい男だと言ってたやつとおんなじなんだよね。 <a href="https://twitter.com/hashtag/reing?src=hash&amp;ref_src=twsrc%5Etfw">#reing</a> <a href="https://t.co/HeQdZeklPq">https://t.co/HeQdZeklPq</a> <a href="https://t.co/wDFCQ53MF6">pic.twitter.com/wDFCQ53MF6</a></p>&mdash; 𝑨𝒓𝒓𝒂𝒚-𝒔𝒂𝒏 (@genya0407) <a href="https://twitter.com/genya0407/status/1023228796000907265?ref_src=twsrc%5Etfw">July 28, 2018</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p>みなさんも自演するときはプロキシなどを通して身元がばれないようにしましょうね.</p> <h2 id="追記20190319">追記(2019/03/19)</h2> <p>このブログを公開した後,以下のような質問が来た.</p> <p><blockquote class="twitter-tweet" data-lang="HASH(0xd981958)"><p lang="ja" dir="ltr">こんなのあるんすね〜。直します。 <a href="https://twitter.com/hashtag/reing?src=hash&amp;ref_src=twsrc%5Etfw">#reing</a> <a href="https://t.co/iXUi7EaQDi">https://t.co/iXUi7EaQDi</a> <a href="https://t.co/7SV9pmRb4V">pic.twitter.com/7SV9pmRb4V</a></p>&mdash; 𝑨𝒓𝒓𝒂𝒚-𝒔𝒂𝒏 (@genya0407) <a href="https://twitter.com/genya0407/status/1105990967469830145?ref_src=twsrc%5Etfw">March 14, 2019</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p><a href="https://observatory.mozilla.org/analyze/reing.kuminecraft.xyz">https://observatory.mozilla.org/analyze/reing.kuminecraft.xyz</a>は,ウェブサイトをチェックしてどこを直せばセキュリティが向上するか教えてくれるというサービスらしい. この質問が投げられたときは安全性は <strong>F</strong> というクッソ低いスコアだったんですが,nginxの設定をいじったりした結果,<strong>A+</strong>まで改善することができました.わいわい!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/threetea0407/20190319/20190319221215.png" alt="f:id:threetea0407:20190319221215p:plain" title="f:id:threetea0407:20190319221215p:plain" class="hatena-fotolife" itemprop="image"></span></p> <div class="footnote"> <p class="footnote"><a href="#fn-ae3c5f9d" name="f-ae3c5f9d" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://github.com/Keats/tera">teraのREADME</a>から引用</span></p> <p class="footnote"><a href="#fn-27a3a93a" name="f-27a3a93a" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Teraという名前もJinja(神社)に対するTera(寺)ということなのだろう.そういえばTemplateってTemple(寺院)っぽい綴りですね.なんという偶然!</span></p> <p class="footnote"><a href="#fn-006716d4" name="f-006716d4" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="http://diesel.rs/">http://diesel.rs/</a> より引用</span></p> <p class="footnote"><a href="#fn-611c9211" name="f-611c9211" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">禁則処理は実装してないです</span></p> <p class="footnote"><a href="#fn-b0b67774" name="f-b0b67774" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">このサーバーはArchLinuxなんですが,Archのパッケージマネージャで<a class="keyword" href="http://d.hatena.ne.jp/keyword/PostgreSQL">PostgreSQL</a>を入れると勝手にバージョンアップされて悲惨なことになるので,<a class="keyword" href="http://d.hatena.ne.jp/keyword/PostgreSQL">PostgreSQL</a>のバージョンを固定するためだけにDockerを使っている.</span></p> <p class="footnote"><a href="#fn-63f51d74" name="f-63f51d74" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">人にもよると思うが</span></p> <p class="footnote"><a href="#fn-19290c41" name="f-19290c41" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>の数自体は<a class="keyword" href="http://d.hatena.ne.jp/keyword/218">218</a>ぐらいあるので,割と綺麗に<a href="https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%AC%E3%83%BC%E3%83%88%E3%81%AE%E6%B3%95%E5%89%87">パレートの法則</a>を満たしている気がします</span></p> <p class="footnote"><a href="#fn-c5dc6986" name="f-c5dc6986" class="footnote-number">*8</a><span class="footnote-delimiter">:</span><span class="footnote-text">もはや質問でもない</span></p> <p class="footnote"><a href="#fn-7fc029ba" name="f-7fc029ba" class="footnote-number">*9</a><span class="footnote-delimiter">:</span><span class="footnote-text">これは女性は悪口言わないだろうみたいな話ではなく,同性のことをここまで悪し様に描写して異性を持ち上げるやつおるか?という気持ちでした.最近ではそのような男性・女性はしばしばいるということがわかってきたのでなんやという感じですが...</span></p> <p class="footnote"><a href="#fn-3108078c" name="f-3108078c" class="footnote-number">*10</a><span class="footnote-delimiter">:</span><span class="footnote-text">ちなみにこの<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>はその後もいくつか質問をしているのだが,「明日手術を受けるのですが、怖くてたまりません。何か勇気の出る言葉をください」という質問を最後に質問をしなくなっています.</span></p> </div> threetea0407 Pythonのコレクション操作をメソッドチェーンでやる hatenablog://entry/10257846132689799411 2019-02-27T13:15:47+09:00 2019-03-02T22:11:54+09:00 以前このような記事を書いた. dawn.hateblo.jp 詳しくはそちらを読んでいただくとして,Pythonのコレクション操作がイケてないという気持ちが僕にはある*1. しかし,Pythonには豊富な資産(numpy,pandas,networkx,scikit-learnなどなど...)があり,Pythonを使わざるをえないことがまれによくある. 上の記事でも書いたように,僕はRubyのコレクション操作のようにmapやfilterをメソッドチェーンするのが好きだ. Pythonでも同様のコレクション操作を実現できないか?というのがこの記事の主題である. *1:異論は認める <p>以前このような記事を書いた.</p> <p><iframe src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fdawn.hateblo.jp%2Fentry%2F2018%2F12%2F17%2F134744" title="ダラー演算子とメソッドチェーンとパイプライン演算子に対する気持ち - さんちゃのblog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="http://dawn.hateblo.jp/entry/2018/12/17/134744">dawn.hateblo.jp</a></cite></p> <p>詳しくはそちらを読んでいただくとして,<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>のコレクション操作がイケてないという気持ちが僕にはある<a href="#f-e0beafc4" name="fn-e0beafc4" title="異論は認める">*1</a>. しかし,<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>には豊富な資産(numpy,pandas,networkx,scikit-learnなどなど...)があり,<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>を使わざるをえないことがまれによくある.</p> <p>上の記事でも書いたように,僕は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のコレクション操作のようにmapやfilterをメソッドチェーンするのが好きだ. <a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>でも同様のコレクション操作を実現できないか?というのがこの記事の主題である.</p> <h2>既存のクラスを拡張する</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>では,既存のクラスに後から<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>メソッドを生やすことができる.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> networkx <span class="synStatement">as</span> nx <span class="synStatement">def</span> <span class="synIdentifier">hoge</span>(self): <span class="synStatement">return</span> <span class="synConstant">'Extended!'</span> nx.Graph.my_special_method = hoge G = nx.Graph() G.my_special_method() <span class="synComment"># =&gt; 'Extended'</span> </pre> <p>この調子で <code>list</code> クラスに <code>map</code> とか <code>filter</code> を生やせば,メソッドチェーンでコレクション操作が可能になりそうな雰囲気がする. しかし,<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>には<b>組み込みクラスに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>メソッドを生やせない</b>という制約がある. そして <code>list</code> クラスは組み込みクラスである. 従って, <code>list</code> クラスに <code>map</code> とか <code>filter</code> を生やすことはできない.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">map</span>(self, func): <span class="synStatement">return</span> [func(elem) <span class="synStatement">for</span> elem <span class="synStatement">in</span> self] <span class="synIdentifier">list</span>.<span class="synIdentifier">map</span> = <span class="synIdentifier">map</span> <span class="synComment"># =&gt; Error!</span> </pre> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%C6%A5%EC%A1%BC%A5%BF">イテレータ</a>ーをラップする新しいクラスを作る</h2> <p>既存のクラスの拡張が頓挫したので,別の手段を考える.</p> <p>一番単純なのは,適当に新しい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%C6%A5%EC%A1%BC%A5%BF">イテレータ</a>ーのクラスを定義してやり,そのコンスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%C6%A5%EC%A1%BC%A5%BF">イテレータ</a>ーを渡してやることである.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">i</span>(<span class="synIdentifier">object</span>): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, iterator): self.iterator = iterator <span class="synStatement">def</span> <span class="synIdentifier">map</span>(self, func): <span class="synStatement">return</span> i(<span class="synIdentifier">map</span>(func, self.iterator)) <span class="synStatement">def</span> <span class="synIdentifier">filter</span>(self, func): <span class="synStatement">return</span> i(<span class="synIdentifier">filter</span>(func, self.iterator)) <span class="synStatement">def</span> <span class="synIdentifier">to_list</span>(self): <span class="synStatement">return</span> <span class="synIdentifier">list</span>(self.iterator) i([<span class="synConstant">2</span>,<span class="synConstant">1</span>,<span class="synConstant">15</span>,-<span class="synConstant">4</span>,-<span class="synConstant">5</span>,-<span class="synConstant">12</span>]).<span class="synIdentifier">map</span>(<span class="synStatement">lambda</span> x: x*<span class="synConstant">2</span>).<span class="synIdentifier">filter</span>(<span class="synStatement">lambda</span> x: x &lt;= <span class="synConstant">10</span>).to_list() <span class="synComment"># =&gt; [4, 2, -8, -10, -24]</span> </pre> <p>つまり,<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>の組み込みリストをメソッドチェーンが可能な世界に "持ち上げ" て操作を行い,最後に <code>list</code> として取り出してやればよいのである. このやり方は一応うまくいく.しかし,問題点もある.</p> <h3>問題点1:メソッドチェーン中に改行できない</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>ではメソッドチェーン中に改行することができない.これは長いコレクション操作をするときにめんどくさいことになる.</p> <pre class="code lang-python" data-lang="python" data-unlink>i([<span class="synConstant">1</span>,<span class="synConstant">2</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">6</span>]).<span class="synIdentifier">map</span>(somefunc1) .<span class="synIdentifier">filter</span>(somecondition) <span class="synComment"># =&gt; Syntax error</span> .<span class="synIdentifier">map</span>(somefunc2) <span class="synComment"># =&gt; Syntax error</span> </pre> <p>この問題は,行末にバックスラッシュ<code>\</code>を入れることで一応回避できる</p> <pre class="code lang-python" data-lang="python" data-unlink>i([<span class="synConstant">1</span>,<span class="synConstant">2</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">6</span>]).<span class="synIdentifier">map</span>(somefunc1)<span class="synSpecial">\</span> .<span class="synIdentifier">filter</span>(somecondition)\ <span class="synComment"># =&gt; Ok</span> .<span class="synIdentifier">map</span>(somefunc2) <span class="synComment"># =&gt; Ok</span> </pre> <p><code>\</code>を入れ忘れたり消し忘れたりしてウォアアアーーとなることもあるが...</p> <h3>問題点2:持ち上げて戻すのが冗長</h3> <p>メソッドチェーンを3つぐらいつなげてると別に気にならないんですが,一回mapしたいだけのときとかだと冗長さが気になってくる.</p> <pre class="code lang-python" data-lang="python" data-unlink>i(lst).<span class="synIdentifier">map</span>(<span class="synStatement">lambda</span> x: x * <span class="synConstant">2</span>).to_list() <span class="synComment"># 冗長!</span> </pre> <h2>結論</h2> <p>郷に入っては郷に従え</p> <div class="footnote"> <p class="footnote"><a href="#fn-e0beafc4" name="f-e0beafc4" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">異論は認める</span></p> </div> threetea0407