Tallman

技術とか読書とかいろいろ

2020年を振り返る

2019年の振り返りはこちら

sasa5740.hatenablog.com

にかいめのふりかえり

1月

2020-01-01から1ヶ月間の記事一覧 - Tallman

Rustやりたみが増してたので書いていた。実はこの時いろいろあって大学院への願書がだせなかったりで結構落ち込んでいた

2月

2020-02-01から1ヶ月間の記事一覧 - Tallman

仕事でgraphql-rubyを使っていて直したいところが出てきたのでコントリビュートした。
このことも含めてそろそろ社外でなにか発表したいと思ってGinza.rbでLTもした。皆さん優しくて楽しかったおもひで。思えばこれがリアルで社外のエンジニアの人とであう最後の機会でした。
あとこのブログで一番読まれている沼の記事。僕はこれでコロナ期間中に10kg痩せたので皆さんも是非やって見てください。夏は腐るのでやめた方がいいです。

後はAtocderのためにスクリプトを書いてGemとして公開した。

qiita.com

これは2020年に書いたコードの中で多分一番人の役に立っている気がする。結構スターも来たし実際にforkして自分で改造して使ってくれている人もいて嬉しかった。自分で使用していても結構便利なのでもしRubyAtcoderに出ているという人がいたら是非使ってください。issueとかもお待ちしてます。

3月

2020-03-01から1ヶ月間の記事一覧 - Tallman

なんかゲームの記事書いてた...。LOLでダイアになるのは4年くらいかかっているので許してください。
N予備校Scalaのコースはかなり体験がよくてジェネリクスとか型パラメーターとか位相とかRubyだけやっているとあまり考えない概念もしれて良かった。プログラミングちゃんとやりたいぜって人にはかなりおすすめできる。UNIX哲学も短いながら普段お世話になっているOSの原点(?)の設計思想が学べて良かった。

4月

2020-04-01から1ヶ月間の記事一覧 - Tallman

Scalaが結構面白かったのでScalaの記事、それとTypeScriptでブログ作った記事がありますね。ちなみにブログは放置してます...。
仕事でBatchLoaderというGemを見つけたのですがかなりキレイな実装で感心したのは今でも覚えてます。

5月

2020-05-01から1ヶ月間の記事一覧 - Tallman

Atcoderやってた。といっても茶色になんとかなったくらいのレベル。自分でスクリプトとか書く割にはレートは低い....。やっぱり数学の素養がなさすぎるなぁと考えていた気がする。

6月

2020-06-01から1ヶ月間の記事一覧 - Tallman

社内でクリーンアーキテクチャの読書会をやってテンションあがった書いた記事がある。クリーンアーキテクチャはクリーンアーキテクチャという実装があるのではなく、抽象に依存せよ、とか依存の方向性を考えろ、とかソフトウェア設計に置ける鉄則を書にした本のように感じた。
Atcoder含めて少しアルゴリズムを使ったなにかが書きたくなったのでRustでBM法で全文探索を使ったstash検索ツールを書いた。これも結構便利で仕事でたまに使っている。この時あたりから型を含んだIDEでの開発体験が良すぎて仕事でも型を使いたくなってきていた。

7月

2020-07-01から1ヶ月間の記事一覧 - Tallman

先月に書いたRustのツールをbrewに公開するためにいろいろやっていた。後AWSによるクラウド入門は なぜクラウドなのか?なぜサーバーレスなのか?がちゃんと学べて良かった。

8月

2020-08-01から1ヶ月間の記事一覧 - Tallman

増補改訂版パRails良かったのでみんな買おう!

9月

2020-09-01から1ヶ月間の記事一覧 - Tallman

去年の振り返りでも書いたが大学院にいきたいと思っているので科目履修生になった。
仕事で良いプロダクトを作るってどういうことなんだろう??と悩んでいたので事業のドメインに関する本も読んでいた。お仕事では教育についてやっているんだけど僕は(自分がそうなんだけど)能力もやる気もない人間がどうやって一人前になるのかが興味があるので、他人を気持ちよく学ばせる仕組みというのは興味がある。ブログには書いてないが会社のnotionとかにも教育ドメイン周りの書籍の感想文を投稿したりしていた。

10 ~12 月

えー言い訳はできないのですが9月に入った大学院の課題がほぼ毎週あってそれにかまけていました。 あんまり残業もないリモートワークの独身一人暮らし男性ですらこんな大変なのにJAISTの人たちは本当にSUGOI。テスト60点+提出物60点の講義だったんだけど提出物は58点でした。テストもとりあえず提出はしているので落単ということは流石にないと思う。あと授業自体は本当に面白くて、Javaのメソッドディスパッチの仕組み、非同期処理のモデル検査、Javaを用いて簡易VM構文解析を実装してプログラミング言語作成、色々盛りだくさんな講義だった。

あと12月は私生活でメンタルが崩壊してた。

2021年

今年の仕事は人生初の新規プロダクトの開発でいろいろ大変だったけど充実してた気がする。チームの人が基本いい人過ぎて涙がちょちょぎれます。来年もバリューを発揮しつつ今度こそ科目履修生からステップアウトするぞ。

Design for How People learnを読んだ

米アマゾンでe-learningで検索すると一番上にでてくるこの本が気になっていたので読みました。業務との関連もあったので一度読んでおきたかった本です。

正しいことを教えただけでは人はスキルを獲得することはできないという前提のもとにどうしたら受講者の学習体験をよくできるかを豊富な例示とともに解説してある本です。 例示が豊富でイラストも多くかなりわかりやすい本になっていると思います。この本自体もDesign for How People learnを取り入れてますね。
面白かった所をいくつか上げてきます。

誰だって自分はバカとは感じたくない

本ではモチベーションに対する言及の中で誰も自分をバカとは思いたくないので、知っている事が全くない書籍よりも自分が知っている事が多少ある本の方が人は手に取るということが書かれていました。ゲームと同様に以下に人に「自分は賢い!」と思ってもらうかどうかが大事であるとのことです。 これは自分の中では結構納得していて、だいたい自分がハマったゲームは最初のプレイで結構うまいじゃん!とシステムや他人に褒められたことがあるゲームが多い気がします。
情報全てを相手に伝えるのが正しいわけではないというのも同意です。「正しくいうのであれば〇〇にはこういうケースがあり~~」等ってゲームを初心者に教えるときによく聞くワードですけどそんなに情報詰め込んでも聞いてる側はアップアップになってしまうのでは?という気がします。

すぐに役立つものに人は興味をもつ

人間の理性的な部分にだけ問いかけているだけでは十分ではなく、本能的な部分に問いかけないと人は興味をもたないという話。 楽しいものと結びつけるというよりは学習者が今まさに欲しているものに対して知識を教えるのがコツとのことです。人間すぐ手に入る報酬を大切にするだろうし、穴がないのにスコップの使い方教わってもあまり興味をもてないって話ですね。

環境

学習は教室や研修室だけで起きるものではなく、良いコミニュティーや仲間たちに支えられていますとのこと。もしコミュニティができそうならば全力で支援して上げるべしとのこと。 コミュニティとかいっしょに学ぶ人って大きいですよね。自分もプログラミング学ぶときにはプログラミングスクールに通ったんですがその時いっしょに頑張っていた人がいたのがかなり学習のやる気に直結していたのを覚えています。 この環境を以下に平等に用意できるかがいわゆるEdTechのキモなんじゃないかと個人的に思っています。

対例を使う

いい例だけでなく悪い例を出すと理解しやすいよという話。問題のある書き方を見せたほうが正しい理解になるのはなるほどという感じです。 これもゲームでもコードでもよく聞く話ですね。よくうまいプレイヤーの動画を見て学習したり、いい設計の方法なんかの情報がありますが、私的にはだめなコードやプレイにたくさん指摘がある情報の方が役にたつ情報だと思います。いいコードとかいいプレイって割と状況によって変わってくることが多いんですが、悪いコードやプレイって割と共通していて役に立てやすいことが多いです。

あとがき

自分はkindle cloud readerとDeepL使って翻訳しなが読みましたが、最初に書いた通り割と平易な英文が多いですし図も豊富なので翻訳使わなくても読める人は多いと思います。
本文でも書いたのですが、ゲームにつながる話が多かったのかなと。他人に楽しくなにかを学習させるっていうのはゲームも教育もいっしょですね。

北陸先端科学技術大学院大学の科目履修生になりました

タイトルの通り北陸先端科学技術大学院大学のソフトウェア設計論という講義の科目履修生になりました。 もともと情報系の学位がほしかったのと、課題を与えられる環境のありがたさを痛感しているので社会人大学院の門は叩きたいと思っていました。先立って体験しようと考えての科目履修です。
科目履修生というのは講義単位で選択して大学に入学することができる制度です。費用は入学金3万円、1講義大体3万円ほど。その他に検定料も1万円かかりました。
8月30日から授業は始まっていて、いきなり2時間の授業が2連続で始まって驚きました。学部は文系だったのですが、理系ってこれが当たり前なの?ってくらい課題があったので面食らっています。早速社会人大学院の大変さを感じているところです。 先生も「社会人で仕事をしながらこの課題は大変だと思いますが、私の方からは頑張ってとしか申せません!頑張ってください」と言っていて笑いました。
講義の内容はオブジェクト指向周りをより学術的な目線で解説していくようなものでした。プログラミング自体は素人ではないのでアトリビュートやらインターフェースが初耳ということはないのですが、これをプログラミング知らない人が受けてると思うと恐ろしいスピードで授業が進んでいるように思います。このスピードで数学色の強い講義についていけるかはかなり不安です。
講義資料はすべて英語で、ある程度OSの操作に習熟してないと予習等もままならなそうな雰囲気はあります。
昨日人生で初めての大学院の課題を提出したのですが、無事先生から満点をもらいました。満点といっても2点中2点ですけどね。
質問をすれば必ず返ってきますし、提出した課題は採点してもらえますし、本当に勉強をするためだけの場所に来たんだなという感じでワクワクしております。無事12月には単位取得して来年には入学したいと思っています。頑張ろう。

増補改訂版 パーフェクト Ruby on Rails の感想

みんな読んでるパーフェクト Ruby on Railsを読みました。

9章くらいまでは流し読みです。以下個人的に面白かったところを書きます。

10章 コンテナを利用したRailsアプリケーション

Gemfileのimageレイヤのキャッシュのところ参考になりました。この方法なら多少imageのレイヤーは増えるものの、本体コードに変更があってもGemfileに変更がない時にimage build中にbundle installしなくてすみますね。*1

11章 複雑なドメインを表現する

値オブジェクト

Railsでの値オブジェクトの具体的な活用例がのっていました。composed_ofを業務で使ったことはないのですが、ファットモデルでもう限界だってなってから使ったほうが良さそうな手法だと思いました。Rails開発だとできるだけ要件削って値オブジェクトにロジックがそれほどないくらいにシンプルにしたほうが良い気がします。 値オブジェクトってもう型っぽいしJVM言語とかGo使ったほうがいいのでは

サービスオブジェクト

何度実行しても結果整合性が取れる形にしたいですね(小並)

12章 複雑なユースケースを表現する

Railsにおける「レール」の正体「レール」の限界と向き合い方という2つの節がよく言われる「Rails辛い」を言語化してあったのが良かったです。
Railsにおける「モデル」は「記事の投稿が完了したらメールを送信する」といったユースケースロジックと、「記事には投稿者がいる」といったドメインモデルが全部「モデル」に置けることが「レール」の正体であり、Railsの初期開発においてまずファットモデルが良い方向だとされる*2のもこのためだと思います。
よくフォームオブジェクトやプレゼンターがRails開発で用いられていますが、改めてユースケースをモデルから分離するための技法の一つだと言語化できるとモデルに書くべきロジックとフォームオブジェクトやプレゼンターに書くべきロジックの区別がつけやすくなるのではないでしょうか。 クリーンアーキテクチャやDDDの本は当然アーキテクチャの基本や考え方が中心になって話が進むのですが、パRailsはあくまでRails中心で話が進むので実務でも活かしやすい章だと思います。

13章 複雑なデータ操作を実装する

Concernがモデルに密接に結びつくのはまずい。最近そんなコードを書きました!!!! すいませんでした!!!
自分は値オブジェクトとかサービスオブジェクトに分離する前にConcern化の方を先に考えていたので参考になりました。

*1:ドキュメントのこの節で詳しい挙動がのっていました Dockerfile のベスト・プラクティス — Docker-docs-ja 19.03 ドキュメント

*2:少なくとも僕の周りはそうだけど賛否両論あるかもしれない

東大計数工学科講義「AWSによるクラウド入門」の感想

四連休はツイッターで回ってきた東大計数工学化の講義でつかわれているらしい教材をやっていた。

AWSによるクラウド入門

学習用のDocker imageが既に用意されていてコンテナを立ち上げれば準備が全て終わるので便利。 内容も細かい技術の特徴というよりはなぜその技術が生まれたのか?を中心に書かれていて勉強になった。

なぜクラウドを使うのか?

設定一つでスケールアウト、スケールアップ共に可能。物理サーバーを管理する必要がない。

なぜサーバーレス?

サーバーフルクラウドとの対比で書かれていてわかりやすかった。
従来WebサービスだとAPIサーバーに送られるタスクの消費する計算資源は一定ではないのにもかかわらず、どんなタスクでもサーバーのインスタンスを一台割り当てなければいけない。サーバーの稼働率を高めてコストパーフォーマンスを最大化するのにもコストがかかる。
サーバーレスではサーバーの計算資源を丸々専有してプロセスを常駐させるのではなく、実行したいプログラムをクラウドに提出してクラウド側の大きな計算資源の一部を使って提出されたプログラムを実行する。この方式だと必要のない計算資源を使うようなことがなくなるのでコストパーフォーマンスを最大化しやすい。 S3もDynamoDBもサーバーレスの考え方が元になっているストレージサービスで、最初に決まった容量を買うのではなく使った容量でコストが決まる方式。
「従来のサーバーフルクラウドは賃貸、サーバーレスクラウドは電気・ガス・水道料金のようなもの」という解説もわかりやすかった。
サーバーレスのデメリットも解説があった。AWSならAWSに寄り添ったシステムになってしまうので、サーバーフルよりもプラットフォーム間の移動は難しくなる。クラウドプロパイダーとしては自社システムに依存してもらいたいのだろうということだった。

Hands-onを試してみて

この教材は最後に実際にVue.jsを使った静的アセットをS3において、LamdaにGET、POSTの関数を配置してDynamoDBでデータを永続化するWebサービスAWSに構成する。実際に自分がコードを書くことはなく、用意されたスクリプトを叩いて、結果をAWSコンソールから確認するという作業が主だった。 実際に複数のリクエストを同時にAPIGatewayに送ったりするスクリプトも用意されていて、本当に試しやすい教材でした。用語の説明も一つ一つ丁寧だった。こんな授業を受けられる学生が羨ましいですな。

GitHub Actionsで#<Errno::ENOTTY: Inappropriate ioctl for device>が出る

発生した経緯

自作のRubyGemのテストとCIを整えていた時に遭遇。 パスワード入力の所でIO#noechoを使うように修正した所、GitHub Actions内で以下のようなエラーが起きた。

  1) GreenDay::Cli login valid name and password create cookie-store
     Failure/Error: password = STDIN.noecho { |stdin| stdin.gets(chomp: true) }.tap { puts }

     Errno::ENOTTY:
       Inappropriate ioctl for device
     # ./lib/green_day/cli.rb:19:in `noecho'
     # ./lib/green_day/cli.rb:19:in `login'
     # ./spec/cli_spec.rb:83:in `block (3 levels) in <top (required)>'
     # ./spec/cli_spec.rb:101:in `block (4 levels) in <top (required)>'

ローカルでは通ったのでCI環境での問題っぽい。

解決まで

"GitHub Actions Inappropriate ioctl for device"でググった所以下のissueを発見。

github.com

どうやらTTYなるもので無いのが駄目らしい。

ワークアラウンドらしいけどこのコメントで解決した

https://github.com/actions/runner/issues/241#issuecomment-577360161

scriptはデフォルトでtypescriptってファイルにターミナルのsessionをまるごと記録してくれるもの。 仕組みはよくわかってないけど実際にローカルでscriptを叩くとセッションが新しく始まって後述のttyコマンドの結果も変化したのでなにかしらttyに当たるものを作るとうまく行く原理なのだろうか。

travis ciでは問題が再現しなかったのでGitHub Actions特有の問題っぽい。Dockerコンテナの出力をキャプチャしているのが関係しているのかもしれない。

最終的にscript -e -cをつけてテストを実行するとエラーは起きなくなった。 https://github.com/QWYNG/green_day/blob/1e174b17201743723a98e7ae245de4fe27e4bbc6/.github/workflows/ruby.yml#L28

tty is 何

最後にttyというワードも初めて知ったのでメモ。 tty自体はUNIXコマンド

man tty

TTY(1)                                                   User Commands                                                   TTY(1)

NAME
       tty - print the file name of the terminal connected to standard input

ターミナルに入力していると思っていた標準入力は実際はこのファイルへ送っているらしい。

 ~ ps                                              2020年07月16日 23時00分57秒
    PID TTY          TIME CMD
  83617 pts/2    00:00:00 fish
  83739 pts/2    00:00:00 ps
 ~ ll /proc/83617/fd                               2020年07月16日 23時01分00秒
合計 0
lrwx------ 1 qwyng qwyng 64  7月 16 23:00 0 -> /dev/pts/2
lrwx------ 1 qwyng qwyng 64  7月 16 23:00 1 -> /dev/pts/2
lrwx------ 1 qwyng qwyng 64  7月 16 23:00 2 -> /dev/pts/2
lr-x------ 1 qwyng qwyng 64  7月 16 23:00 3 -> /home/qwyng/
lr-x------ 1 qwyng qwyng 64  7月 16 23:00 4 -> 'pipe:[727942]'
l-wx------ 1 qwyng qwyng 64  7月 16 23:00 5 -> 'pipe:[727942]'
lr-x------ 1 qwyng qwyng 64  7月 16 23:00 6 -> 'pipe:[728533]'
l-wx------ 1 qwyng qwyng 64  7月 16 23:00 7 -> 'pipe:[728533]'
lrwx------ 1 qwyng qwyng 64  7月 16 23:00 8 -> /run/user/1000/fish_universal_variables.notifier|
 ~ tty                                             2020年07月16日 23時01分19秒
/dev/pts/2
 ~                                                 2020年07月16日 23時01分35秒

RubyにもIOがttyか調べるメソッドが合った。 docs.ruby-lang.org

IO#nochoのソースコードもttyが前提のようなコードだった。

https://github.com/ruby/io-console/blob/a4e0a1d67b6bd61f47f37b810eb261fc18a70967/ext/io/console/console.c#L571

GitHubActionsでリリースとHomeBrewのformulaレポジトリ更新を一度に行う

個人でHomeBrew通してバイナリを公開する時、formulaを作成してレポジトリを立ち上げる必要があります。
そこにはバージョンやリリースしたいファイルのURL、SHA256 checksumを含めなくてはならないのですが、バイナリをアップデートしたい時にソースコードのレポジトリだけでなくformulaのレポジトリも更新しなくてはならず面倒です。
そこでGitHubActionsで2つのレポジトリを連携してリリースとformulaの更新を一度に行える様にしてみました。

対象レポジトリ

Rustで書いたCLIアプリであるGitHub - QWYNG/harvest: stashes grep tool with rustとそのformulaである GitHub - QWYNG/homebrew-harvestが対象レポジトリです。

workflow

まずリリースする側のレポジトリのworkflowの設定です。長いので中略してます。

name: Release

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macOS-latest]
        rust: [stable]

    runs-on: ${{ matrix.os }}

    steps:
~~~ snip ~~~
      - name: Set version
        id: set_version
        run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\//}
        env:
          GITHUB_REF:  ${{ github.ref }}
      - name: Build for linux
        if: matrix.os == 'ubuntu-latest'
        run: |
          cargo build --release --target=x86_64-unknown-linux-musl
          zip -j harvest-${VERSION}-x86_64-lnx.zip target/x86_64-unknown-linux-musl/release/harvest
        env:
          VERSION: ${{ steps.set_version.outputs.version }}
      - name: Build for macOS
        if: matrix.os == 'macOS-latest'
        run: |
          cargo build --release --target=x86_64-apple-darwin
          zip -j harvest-${VERSION}-x86_64-mac.zip target/x86_64-apple-darwin/release/harvest
        env:
          VERSION: ${{ steps.set_version.outputs.version }}
      - name: Set SHA256
        if: matrix.os == 'macOS-latest'
        id: sha_256
        run: |
          echo "::set-output name=sha::$(shasum -a 256 harvest-${VERSION}-x86_64-mac.zip |awk '{print $1}')"
        env:
          VERSION: ${{ steps.set_version.outputs.version }}
      - name: Release
        id: release
        uses: softprops/action-gh-release@v1
        with:
          files: '*.zip'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Dispatch homebrew Repo update
        if: matrix.os == 'macOS-latest'
        uses: peter-evans/repository-dispatch@v1
        with:
          token: ${{ secrets.HOMEBREW_REPO_GITHUB_TOKEN }}
          repository: QWYNG/homebrew-harvest
          event-type: update
          client-payload: '{"version": "${{ steps.set_version.outputs.version }}", "sha": "${{ steps.sha_256.outputs.sha }}" }'

Build for macOSではビルドとzipへの圧縮を行っています。
GitHubActionsは::set-output name={name}::{value}の形で後のstepでsteps.<step id>.outputs.{name}として値を使用できます。これを利用してSet versionSet SHA256でバージョンとzipファイルのchecksumをセットしています。
最後に取得したバージョンとchecksumをrepository-dispatchをつかってformulaレポジトリに投げます。普通にcurlで発火させることもできますが、今回は

GitHub - peter-evans/repository-dispatch: A GitHub action to create a repository dispatch eventが便利そうだったので使ってみました。

dispatchを受け取るformulaレポジトリ側のworkflowは以下

name: Release

on: repository_dispatch

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master
        with:
          persist-credentials: false
          fetch-depth: 0
      - name: Create local version change
        run: |
          truncate version.rb --size 0
          echo 'VERSION = "${{ github.event.client_payload.version }}"' > version.rb
          echo 'SHA = "${{ github.event.client_payload.sha }}"' >> version.rb
      - name: Commit version change
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git commit -m "update version" -a
      - name: Push changes
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

dispatchで受け取ったペイロードを使ってversion.rbを書き直してコミットしてプッシュしてます。 f:id:sasa5740:20200706221128p:plain 無事自動でformulaレポジトリを更新できました。

まとめ

愚直な実装なのでHomeBrewに詳しければもう少しスマートにできるかもしれないです。
harvestをgit diffではなくgit stash show -pの結果で検索するように改良し、更に標準出力にページャを導入しました。大分使いやすくなったので満足。