経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

denodrivers/deno_mongo で作った my good first issue

この記事はDeno Advent Calendar 2021の19日目です。

deno_mongo で取り組んだ my good first issue を紹介したいと思います。

README.mdに情報を足す

リンクに飛んだ方がわかりやすいと思ったのでそうしました。
docs: Improve README by wafuwafu13 · Pull Request #294 · denodrivers/mongo · GitHub

フォーマットを統一する

varが使われているなど、VSCode上で警告の出ている箇所がちらほらあったので、CIでdeno lintを走らせるようにしました。
chore: Run `deno lint` in CI by wafuwafu13 · Pull Request #298 · denodrivers/mongo · GitHub

TODOコメントを拾う

TODO: add test casesを拾いました。
test(02_connect): Add `runCommand` test by wafuwafu13 · Pull Request #295 · denodrivers/mongo · GitHub

非推奨のAPIを使わない

非推奨になったassertThrowsAsyncが使われていたので書き換えました。
chore: `assertThrowsAsync` is deprecated by wafuwafu13 · Pull Request #313 · denodrivers/mongo · GitHub

テストの方法を改善

MongoのCURD操作のテストをしているファイルがあるのですが、最初に実行されるテストから最後まで同じコレクションが使われており、テスト間に依存関係がある状態でした。
その状態では、最後の方にあるテストを読もうとしたとき、最初の方のテストで何がinsertされたのかを順に追っていかないといけないので辛いです。
そのため、各テストの実行後のコレクションの中身をコメントで追記するという作業をしました。
chore(test/03_curd): Improve readability by wafuwafu13 · Pull Request #302 · denodrivers/mongo · GitHub
しかし、テストを追加する際にコメントも編集しないといけなくなる、そもそも依存関係をなくした方が良いという議論になりました。
それはそうだと思ったので、各テストの実行後にコレクションの中身を削除する関数を用意し、依存関係のないテストを新たに書きました。
test(03_curd): Write tests that do not depend on each other by wafuwafu13 · Pull Request #311 · denodrivers/mongo · GitHub




機能の追加やバグ修正はしていませんが、それ以外にもできることが結構ある場合があります。
good first issue といえども、コードの全体像を把握した上での good first issue なので、まずは自分なりの my good first issue に取り掛かるのもありかもしれません。

DBスペシャリスト試験不合格記

令和3年度秋期のデータベーススペシャリスト試験を受けた。

結果


動機

時間があった。
基本情報も応用情報も受けてないけど、高度試験は面白そうだったから、ちょうど時期が合ったDBを受けてみた。

対策

受ける前からバックエンドの経験は積んでいたので元々読んでいた本があった。

対策を進める上で『SQLアンチパターン』をさらっと参照することが1回あるかないかくらい、『実践ハイパフォーマンスMySQL』はモニターのかさ増しのままだった。

この本をメインに対策を進めていった。

午前Iと午前IIで採点がストップするのはどうしても避けたかったから、本からダウンロードできる過去問でかなり対策していた。

午前I 12年
午前II 8年

午後はそれぞれ5年分くらいやった。

感想

午前IIはかなり手応えがあったけどギリギリだったのが意外だった。
午後Iは時間が足りなくなってしまった。
空白が結構あったのに49点あったのが意外。
午後IIは時間に余裕があって、きちんとカラム名入れて線をひけていた記憶があるので、採点まで到達できなくて残念。


合格しようというモチベーションはなくてもう受けないけど、受けて損はなかったと思う。
特に正規形に関する部分をあらためて深められたのと、既存に組み立てられた割と大規模なリレーションを理解して解いていくのは実践的でよかった。
高度試験の雰囲気を知れたし、午前Iの免除があるから、次はネットワークスペシャリストを受けようと思う。

Architecture of nodejs/node and denoland/deno_std/node

I contribute to denoland/deno_std as a hobby, and I've been looking at denoland/deno_std/node a lot, so I thought I'd take a quick look at the architectural differences between the nodejs/node and denoland/deno_std/node APIs at this point in time (2021/12/16).

Where to implement the API

nodejs/node

It is implemented in node/lib.
Using Query string as an example, the logic is implemented in node/lib/querystring.js and exported at here.
In addition, internal code that does not need to be exported is implemented in node/lib/internal.
In Query string, functions such as encordStr are exported, and imported at node/lib/querystring.js.

denoland/deno_std

It is implemented in deno_std/node.
APIs that are compatible with the logic exported in nodejs/node/lib/querystring.js in deno_std/node/querystirng.ts are exported here.
Also, in deno_std/node/internal, an implementation equivalent to nodejs/node/lib/internal has been created.

Implementation Language

nodejs/node

It is implemented in JavaScript and C++.
Using URL as an example, some C++ code is called by the internalBinding in JavaScript implementations such as node/lib/url.js and node/lib/internal/url.js.
For example, the domainToASCII implementation is in node/src/node_url.cc.
Incidentally, I was wondering why it is not implemented in JavaScript, and asked Why domainToASCII is written by C++ not JS? in the help repository, and got the answer "for perf reasons".
Also, primordials are used in many places.
You can read more about primordials in the suggestions around prototype pollution and primordials.js(Japanese article).

denoland/deno_std

It is implemented in JavaScript and TypeScript.
For example, Query string is implemented in querystring.ts, and Buffer is implemented in node/buffer.js, and node/buffer.d.ts is specified in @deno-types of node/buffer.ts.
The equivalent of Node's internalBinding is implemented by TypeScript in node/internal_binding.
The primordials are implemented in denoland/deno, but not in denoland/deno_std.

Tests

nodejs/node

It can be found in node/test.
For example, URL testing is implemented in node/test/parallel/test-url-*.js.
The execution method and other information is summarized in pull-requests.md.

denoland/deno_std

In node/_tools/config.json, specify the file name of the test in nodejs/node/test, retrieve it in node/_tools/setup.ts, and place it in node/_tools/suites.
The execution method and other information is summarized in node/README.md.

nodejs/node と denoland/deno_std/node のアーキテクチャ

English version: Architecture of nodejs/node and denoland/deno_std/node - 経験は何よりも饒舌


この記事は Advent Calendar 2021 Deno の16日目です。
趣味でdenoland/deno_stdにコントリビュートしており、特に denoland/deno_std/node をよく見ているので、nodejs/nodedenoland/deno_std/node の現時点(2021/12/16)でのNode APIにまつわるアーキテクチャの違いをざっくり書いていこうと思います。

APIの実装場所

nodejs/node

node/libに実装されています。
Query stringを例にすると、node/lib/querystring.jsでロジックが実装されていて、ここでexportされています。
また、exportする必要のない内部のコードがnode/lib/internalで実装されています。
Query stringではencordStrなどの関数がexportされてnode/lib/querystring.jsでimportされています。

denoland/deno_std

deno_std/nodeに実装されています。
deno_std/node/querystirng.tsnodejs/node/lib/querystring.jsでexportされているロジックと互換性のあるAPIここでexportされています。
また、deno_std/node/internalで、nodejs/node/lib/internal相当の実装がされています。

Implementation Language

nodejs/node

JavaScriptC++で実装されています。
URLを例にすると、node/lib/url.jsnode/lib/internal/url.jsといったJavaScriptの実装の中で一部、internalBindingによってC++のコードが呼び出されています。
例えばdomainToASCIIの実装はnode/src/node_url.ccにされています。
ちなみに、なぜJavaScriptで実装されていないのかと疑問を持ち、helpレポジトリでWhy domainToASCII is written by C++ not JS?と伺ったところ、for perf reasonsという回答をいただきました。
また、各所でprimordialsが使われています。
primordialsについてはプロトタイプ汚染周りの提案と primordials.jsが詳しいです。

denoland/deno_std

JavaScriptとTypeScriptで実装されています。
例えばQuery stringはquerystring.tsで実装されていますが、Buffernode/_buffer.jsで実装されており、node/buffer.ts@deno-typesnode/_buffer.d.tsが指定されています。
NodeのinternalBindingに相当する実装はnode/internal_bindingでTypeScriptにより実装されています。
primordialsは、denoland/denoには導入されていますが、denoland/deno_stdには導入されていません。

テスト

nodejs/node

node/testにあります。
例えばURLのテストはnode/test/parallel/test-url-*.jsで実装されています。
実行方法などはpull-requests.mdにまとまっています。

denoland/deno_std

node/_tools/config.jsonで、nodejs/node/testにあるテストのファイル名を指定し、node/_tools/setup.tsで取得し、node/_tools/suitesに配置しています。
実行方法などはnode/README.mdにまとまっています。

Scrapboxを使って技術書を真剣に読む


23卒のエンジニア職志望向けAdvent Calendar Advent Calendar 2021の13日目です。

学生時代の行動指針としてインフラに長期間触れてみたいという思いがあり、いろいろ本を読んだりして準備していたのですが、いざインフラに常に触れられる状態になると、読んだ内容を詳細に思い出せないことに気がつきました。
自分の強みである、既存のコードを読む力はアプリケーションを開発するときにもインフラを構築するときにも役立っていますが、概念の習得、例えばアプリケーションでいうとDOMとは、インフラでいうとDNSとは、とかいう部分は長期間触れ続けないと忘れる、自分の場合は特にインフラの概念を忘れる傾向にあります。しかもそれは既存のコードを読む力の前提となる部分であるから、確固とした知識を入れておかないといけないと感じました。
技術書は一回読むだけではなく繰り返し読むことが多いですが、早く読み終わりたいという意識が無意識に働き、重要な概念を読み飛ばしていることが早期の忘却につながっているのではないかという仮説を立て、今一度じっくり技術書を真剣に読みなおすという方針を立てました。
そこで用いたのがScrapboxで、まずは『ネットワークはなぜつながるのか』の重要な部分を書き起こしながら読むことと、登場した概念をリンクにすることを実践しています。

今のところかなり効果を感じています。
読んだ内容を脳内で整理してからまとめて書くことにより理解が深まるし、後で追いやすいです。また、読み流して無理矢理前に進むことがなくなりました(1周目に関しては読み流してでも本の全体像を把握することは有用だと思うが、2周目、3周目で読み流すのは避けたい)。
学部時代に独学で習得すべきジャンルは既に全て一回は読み流しているはずなので、これからはじっくりまとめながら確固とした知識を吸収し続けたいです。

統計検定1級受験記

統計検定1級の「統計数理」と「統計応用(社会科学)」を受験したのでいろいろまとめておく。

モチベーションと数学力

モチベーションとしてはいくつかあって、まずは時間があった。
夏の終わりから11月にかけてアルバイトを控えていたので、今までしてこなかった資格勉強をしようという気になった。
勉強したい資格として、統計検定、データベーススペシャリストネットワークスペシャリスト、TOIEC、簿記2級、AWS認定資格があった。
ネットワークスペシャリスト、簿記2級に関しては受ける時期がまだ先だったので手をつけなかった。
TOIECに関しては一回受けたことがあるしいつでも受けられるので手をつけなかった。
AWS認定資格は学習期間にも費用がかかり続けると思ったのでモチベーションが湧かなかった。
データベーススペシャリストはちょうど秋にあったので10月に受けてきた(結果が出たらまとめる)。

そもそもなぜ統計検定を受けたかったかというと、まずは数学をきちんと勉強したかった。
ソフトウェアエンジニアとしてアルバイトやインターンをする上では数学力不足に悩んだことはなかったが、趣味でHaskellを深めるためにTaPLを手に取ったり、社会科学と因果分析に興味をもったり、OSSのもっとコアな部分を理解しようとした時に数学の壁があった。
社会学部で、そこそこちゃんとした統計の講義やゼミを受けたり、般教で線形代数を取ったことはあるけれど、あくまで文系生徒を対象としたもので、一般の理系学部生がどれほどの理解力を求められているのかを知るためにも統計検定は有用だと思った。
あとは機械学習がアプリケーションに組み込まれることがスタンダードになってきているように見えていて、機械学習分野の動向を全く無視して過ごすのも自分の中ではうーむという感じがしおり、まずは数学への圧倒的理解力不足を解消したかった。
記述式の対策をした方が数学を解く練習になりそうだからと、統計応用の社会科学に興味があったからという理由で1級を選んだ。

今まで数学を全くしてこなかったわけではなかった。
数学1と数学2は高校生の時に学んでいたし記憶もあったので、青チャートを眺めてこんな感じだったと思い出せる状態ではあった。
今年に入ってからゼロから作るDeepLearningを読み始めたり数学3の教科書をSymPyで解き始めたりしていた。
なんとなく高校数学を思い出したり、数学3をカバーしたり、線形代数をちょろっと理解している状態ではあったが、数学の問題を解くという練習や行為は受験以来全くしてこなかったので、本格的に対策をすることにした。

対策

まずはネットで情報を集めた。
統計検定 1 級に合格する方法 - Qiita
文系で統計検定1級に合格した|khosoda|note
統計検定1級の勉強法|蒼天|note
統計検定一級受検体験記-ぐぐりらにっき
|統計検定:Japan Statistical Society Certificate

情報を踏まえて参考書をいろいろ検討して、まずはマセマで基礎を固めることにした。
マセマの学習フローチャートでは確率統計に入る前に、微分積分があったからまず微分積分をすることにした。
また、他の記事では線形代数の知識もあったほうがいいと書いてあったので線形代数もすることにした。
9月から資格勉強をする時間がまとめて取れて、10月中盤にデータベーススペシャリストがあったから、10月中盤までに微分積分線形代数の基礎を固めることにした。
マセマは解説が非常に丁寧で理解しやすかった。
多少詰まるところはあったけど気にせず曖昧なままで最後まで進めた。
数学の問題を解くという行為に慣れるため、演習問題の解答を隠して紙に書いて解いてみるということもした。

データベーススペシャリストが終わったので残り1ヶ月で統計をどんどん進めることにした。
まずはマセマの確率統計を同じように多少詰まるところはあったけど気にせず曖昧なままで最後まで進めた。
これで微分積分線形代数、確率統計の大体の概要は把握できたから、マセマの次の本を検討した。

他の記事を参考にして現代数理統計学の基礎現代数理統計学のどっちかにしようと決め、京阪三条あたりの丸善でパラパラと眺めて自分に合っていそうな現代数理統計学の方を選んだ。
実際読み進めてみると、文章が多めで丁寧だということは伝わるが、各章の初めの方しか理解できず、各章の後半あたりは読み流すだけになってしまった。
理解度がかなり浅かったので、マセマと現代数理統計学の間を埋めるため、明快演習 数理統計を購入した。
各項目に例題がついているので、現代数理統計学で理解できなかった項目が例題の中で登場する場合は、使い方とか解き方がわかったりしてよかった。
ただマセマと比べたら解説が理解しにくく、繰り返し解くということはせずにリファレンスとして目を通して雰囲気を理解するために使った。
明快演習 数理統計でも理解できなかった現代数理統計学の項目は、はじめての統計学というYouTubeチャンネルの解説を紙にまとめて理解しようとした。
マセマの理解度に比べて明快演習 数理統計と現代数理統計学の理解度は格段に低かったが、とりあえず過去問に目を通してみることにした。
有意に無意味な話というサイトから2015/2018/2019の統計数理の過去問を3問ずつ取ってきて、2016/2017の公式問題集を購入した。
まずは2015/2018/2019の統計数理の過去問を解いてみたが、各問の小問1すら解けず、小問3もしくは小問4以降からは解説を読んでもわからないという状態だった。
関連する項目を現代数理統計学で深めようとしても、そもそも現代数理統計学もあまり理解できていないからほんの少し理解が進むだけだった。
とりあえず小問1,2に手をつけられるようにするため、マセマの説明文や解法を0から導出できる練習をした。
具体的には、演習問題をすらすら解けるようにする他、例えば「正規分布の確率関数からモーメント母関数を導出し、それを用いて正規分布の期待値と分散を求めよ。」というようにマセマの解説途中の説明文も自分で問題化してスラスラ解けるように練習した。
ある程度暗記が必要だったから4~5周くらいしてやっと何も見ずに解けるようになった。
2016/2017の過去問も解いてみて、統計数理に関してはだいたい小問1,2に手をつけられる状態、小問3以降は解説を読んでも理解が曖昧という状態になった。
統計応用に関しては人文・社会科学の統計学に一通り目を通して2016/2017の過去問を解いてみて、同様にだいたい小問1,2に手をつけられる状態、小問3以降は解説を読んでも理解が曖昧という状態になった。

ちなみに統計検定の申し込みは同志社大学でやった。
確か文化情報学部は無料になってその他の学部は少し割引が効いた。
申し込みのために半年ぶりくらいに今出川キャンパスに足を運んだ。
販売機の押すボタンを間違えてしまって初めて金融課にお世話になった。

本番

申し込みの時に会場を指定できたかは覚えておらず、会場はまあまあ遠い御堂筋ホールだった。
最寄りから1時間程度でOsakaMetro御堂筋線なんば駅に到着して駅内徒歩1分のビルで受けた。
会場はかなり広くて1テーブルに1人でゆとりのある状態で受けれた。

解いた感触は過去問を解いた時と一緒で、だいたい小問1,2に手をつけれて、小問3以降は手が出せなかった。
モーメント母関数を導出するのはそのまま出たので、マセマの解説途中の説明文まで解けるようにする対策は良かったと思う。

反省、抱負

受ける前からまあ受からんだろうなとは思っていたし、合格点に届く分量の解答を書いていないから受からないことは確定している。
対策時間がもっと長かったらという反省もないというか、対策時間をもっと長くとれば小問3以降とか現代数理統計学の理解が進んで合格に近づいたとかは思えなくて、対策時間という次元じゃなくて年単位の精進ではないかと思っている。
とりあえず統計に関してはgaccoなどの機会や他の参考書をあたってみたりして理解を進めていこうと思う。
1級はもう受けないけど準1級は学生の割引が効く間に取っておきたいから多分受ける。
数学を本格的に勉強する機会を設けれたことはかなり良かったと思う。
次は英語とアルゴリズムやっていくぞー。

Jestのカバレッジ計測について(モック編)

Jestのカバレッジ計測について - 経験は何よりも饒舌 で、「JestはJestのAPIを用いたテストに対してカバレッジを計測しているのではなく、テストで呼び出されたか否かでカバレッジを計測している」ことが分かったけど一応モックの場合も調査してみる。
モック自体は Jest のモック関数を整理する - 経験は何よりも饒舌 で整理している。

次のコードで調査していく。

const obj = {
  random: function () {
    return Math.random();
  },
  randomPlusOne: function () {
    return Math.random() + 1;
  },
};

module.exports = obj;

まずはモックを使わずにテストしてみる。

const obj = require("./sample");

describe("sample", () => {
  it("random", () => {
    expect(obj.random()).toBe(1);
  });
  it("randomPlusOne", () => {
    expect(obj.randomPlusOne()).toBe(2);
  })
})

予想通りテストは通らないが、カバレッジは100%になる。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 FAIL  ./sample.test.js
  sample
    ✕ random (4 ms)
    ✕ randomPlusOne (1 ms)

  ● sample › random

    expect(received).toBe(expected) // Object.is equality

    Expected: 1
    Received: 0.0018626031125137388

       6 | describe("sample", () => {
       7 |   it("random", () => {
    >  8 |     expect(obj.random()).toBe(1);
         |                          ^
       9 |   });
      10 |   it("randomPlusOne", () => {
      11 |     expect(obj.randomPlusOne()).toBe(2);

      at Object.<anonymous> (sample.test.js:8:26)

  ● sample › randomPlusOne

    expect(received).toBe(expected) // Object.is equality

    Expected: 2
    Received: 1.732370688737492

       9 |   });
      10 |   it("randomPlusOne", () => {
    > 11 |     expect(obj.randomPlusOne()).toBe(2);
         |                                 ^
      12 |   })
      13 | })
      14 |

      at Object.<anonymous> (sample.test.js:11:33)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 sample.js |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 failed, 1 total
Tests:       2 failed, 2 total
Snapshots:   0 total
Time:        0.537 s, estimated 1 s

ここで、一旦モックから離れて、ロジックに全く関与していない次のようなテストを試してみる。

describe("sample", () => {
  it("1+1", () => {
    expect(1 + 1).toBe(2);
  });
})

予想通り、カバレッジは0%になる。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ 1+1 (2 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |       0 |        0 |       0 |       0 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.526 s, estimated 1 s
Ran all test suites.

次に、前回のテストにrequireだけしたテストを試してみる。

const obj = require("./sample");

describe("sample", () => {
  it("1+1", () => {
    expect(1 + 1).toBe(2);
  });
})

すると、ロジックに全く関与していないのにrequireしただけでカバレッジは上昇している

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ 1+1 (2 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |      50 |      100 |       0 |      50 |                   
 sample.js |      50 |      100 |       0 |      50 | 3-6               
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.433 s, estimated 1 s
Ran all test suites.

次はモックを実装して試してみる。

const obj = require("./sample");
jest.spyOn(obj, 'random').mockImplementation(() => 1);
jest.spyOn(obj, 'randomPlusOne').mockImplementation(() => 2);

describe("sample", () => {
  it("random", () => {
    expect(obj.random()).toBe(1);
  });
  it("randomPlusOne", () => {
    expect(obj.randomPlusOne()).toBe(2);
  })
})

カバレッジの結果は前回と同じであり、つまり、モックはカバレッジに関与していないことがわかる。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ random (2 ms)
    ✓ randomPlusOne (1 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |      50 |      100 |       0 |      50 |                   
 sample.js |      50 |      100 |       0 |      50 | 3-6               
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.571 s, estimated 1 s
Ran all test suites.

次のように、JestのモックではなくSinon.JSを用いてテストしてみる。

const obj = require("./sample");
const sinon = require("sinon");

const randomStub = sinon.stub(obj, "random");
randomStub.returns(1);
const randomPlusOneStub = sinon.stub(obj, "randomPlusOne");
randomPlusOneStub.returns(2);

describe("sample", () => {
  it("random", () => {
    expect(obj.random()).toBe(1);
  });
  it("randomPlusOne", () => {
    expect(obj.randomPlusOne()).toBe(2);
  });
});

カバレッジの結果は前回と同じであり、つまり、モックの方法もテストに関与していないことがわかる。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ random (2 ms)
    ✓ randomPlusOne

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |      50 |      100 |       0 |      50 |                   
 sample.js |      50 |      100 |       0 |      50 | 3-6               
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.635 s

まとめ

  • モックの実装やモックの方法はカバレッジに関与していない