経験は何よりも饒舌

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

【DMM × CAMPHOR-】Webフロントエンド開発トークセッション参加メモ

camphor.connpass.com

トーク1: 横断組織から見るDMMのフロントエンド

  • jQueryとの付き合い
    • 技術強い会社でもあるあるなんだなぁ
    • jQueryに稼いでもらってる側面もあるあるかも
  • ドキュメント
    • コードのノウハウが細かくドキュメント化されていた
    • フロントエンドの評価項目がとても細かかった ex)SOLID原則の理解
  • フロントエンドエンジニア
    • フロントに限らずバックからインフラまで触れる風潮
    • 逆にフロント一本で他人と差別化するのは難しいのではと思った
  • CTO室
    • いろいろやってるみたい

トーク2:生まれ変わるDMM〇〇

  • 経験
    • リプレイスの知見を1つの会社で短期スパンで異なるプロジェクトに転用できるのは事業範囲がやたら広い会社でしか無理なのではと思った
  • 技術選定
    • Next.jsじゃなくReact にした
      • Next.jsは表示速度が速いは眉唾
      • SSRSEOの強みはCSRでもDynamic RenderingやLambda絡めたらいけるっぽい
      • SSRでのAPIコールは必要なかった
      • コードの書き分け、開発環境と本番環境の差異がだるい
      • Node.jsが重くて落ちる
      • GraphQLはノリと挑戦

トーク3: ビデオ通話システムに関するFE技術まわりの話

  • 開発環境
    • TS化を促す
      • チームで整合性とれる
      • null安全
      • 段階的にできる
    • Sentryでのフロントエンド監視は眉唾
    • Storybook良いが管理大変
  • 困りごと
    • 自動再生ができない
      • ハックしてがんばる

 

自然言語処理へのアプローチ

言葉が好きということを突き詰めたなら行き着く先はここではないかと思っている

とてもいい参考資料があった
cl.sd.tmu.ac.jp

とりあえずウェブマイニングから攻めていけばいいだろう

それだとやってる感覚がなさそうだから同時並行で読み始めるか

Googleを支える技術」以外は読み終わった。
文字コード技術入門」はほとんど理解できなかったからもう一度読むと思う。

1/14 追記



これが読み終わりそう

同時に数学を少しやった

wafuwafu13.hatenadiary.com

機械学習がなにをしたいのかがだんだんわかってきた。
数式を追うのに苦労してコードの概観はまだ掴めてないからUdemyでそこら辺は補足したらよさそう。

www.udemy.com


そして本命の本を読んでいく予定

これもついでに読んだらよさそう

はじめてのOSS貢献

ついさっきpushしたのでメモしておきます。

STEP1 OSS貢献に関する記事を読む

とっつきやすいとか、誰でもとか書いてありますが、やはり難しそう...
でも、今書いてる記事を読んだらとっつきやすさは伝わると思います。

qiita.com

qiita.com

tech.gunosy.io

STEP2 できそうなものを探す

github-help-wanted.com

自分の場合は、言語はJavaScriptとTypeScript、ラベルはgood first issueで調べました。

良さげなやつが見つかったのですが、「Attend a Hack for LA Remote Onboarding session via Zoom.」みたいな案内があって、時間がかかりそうだったので一旦保留しました。

次に見つかったやつは、jsonを書くだけだったので、いけそう!

STEP3 お作法に沿って環境構築、アサインをリクエス

アサインされてから環境構築に失敗したりして詰まったら嫌なので、先に環境構築したりコード眺めたりしておきました。
そして、CONTRIBUTING.mdに沿ってアプローチしたら、2分でアサインされました。

STEP4 コードを書く

自分の場合はコードをいうより0,1を書きました。
github.com

STEP5 お作法に沿ってプルリクエス

CONTRIBUTING.mdや他の人のプルリクを見て、ブランチ名や残しておくコメントをチェックしました。
実際プルリク出したときも、注意書きがあったので丁寧。

STEP6 マージされるのを待つ

CI/CDはお作法に沿ってたら通るはずなので気にしないで良いと思います。
github.com

おまけ

LAPRASでOSSシルバーコントリビューターがもらえる

Understanding the difference of Unit Test and Integration Test

Understanding the difference of Unit Test and Integration Test by testing add function and total function in App.js, using Jest and I quote Unit Test vs Integration Test: What's the Difference?.

export const add = (x, y) => x + y

export const total = (shipping, subtotal) => {
    return '$' + add(shipping, subtotal)
}

Unit Test

Test code and result is below.

import { add } from './App'

test('add', () => {
    expect(add(1, 2)).toBe(3)
    expect(add(3, 6)).toBe(9)
    expect(add(3, 6)).toBe(10)
})


The idea behind Unit Testing is to test each part of the program and show that the individual parts are correct.

It means this test show that add function is correct.

It is kind of White Box Testing

It means this Unit Test show that why test is not correct (3 plus 6 is not 9 but 10).

Integration Test

combine modules in the application and test as a group to see that they are working fine

It means this test show that total function that add function is combined with is correct.

The test written below passes.

import { total } from './App'

test('total', () => {
    expect(total(5, 20)).toBe('$25')
})

But if the add function behaves unintentionally like below, this test is not pass.

export const add = (x, y) => x - y  // oops!!

export const total = (shipping, subtotal) => {
    return '$' + add(shipping, subtotal)
}

Also if the total function behaves unintentionally like below, this test is not pass.

export const add = (x, y) => x + y

export const total = (shipping, subtotal) => {
    return '#' + add(shipping, subtotal) // oops!!
}


It is kind of Black Box Testing

It means you can't know which function is responsible for not passing the test(which function has bugs).
In this case, two functions is easy so it is easy to know it, but the bigger the code, the harder.


In this case, if change App.js like below, you just have to do Unit Test.

export const add = (x, y) => x + y
export const doller = () => '$'

export const total = (shipping, subtotal) => {
    return doller() + add(shipping, subtotal)
}
import { add, doller, total } from './App'

test('add', () => {
    expect(add(1, 2)).toBe(3)
})

test('doller', () => {
    expect(doller()).toBe('$')
})

// If add and doller pass, it will definitely pass
test('total', () => {
    expect(total(5, 20)).toBe('$25')
})

Rethinking about role of Babel and webpack

I rethink about role of Babel and webpack.
Initial state of my code is here.

First, don't use webpack or Babel.
It means edit index.html as below.

<!DOCTYPE html>
<html>
...
<body>
<script src="../src/js/app.js"></script>
</body>
</html>
```

When previewed it in the browser(Chrome), get the following error.

This error can be resolved by applying the module to HTML as follows.
JavaScript modules - JavaScript | MDN

<!DOCTYPE html>
<html>
...
<body>
<script type="module" src="../src/js/app.js"></script>
</body>
</html>
```

But, got different error as below.

This error occured because violates the restrictions of the same origin policy.

It means file:///Users/tagawahirotaka/Desktop/webpack-tutorial/public/index.html that displayed in the browser and file:///Users/tagawahirotaka/Desktop/webpack-tutorial/src/js/app.js that call JavaScript violates the restrictions of the same origin policy.

Same-origin policy - Web security | MDN
https://developer.mozilla.org/en-US/docs/Archive/Misc_top_level/Same-origin_policy_for_file:_URIs


Second, use webpack and don't use Babel.
It means edit index.html as below.

<!DOCTYPE html>
<html>
...
<body>
<script src="js/bundle.js"></script>
</body>
</html>
```

When previewed it in the browser, get the results you want.

Because js/bundle.js is file that starting from the specified file (src/js /app.js), the files are connected in a Imozuru formula by relying on the import statement, and they are combined into one JavaScript file by webpack.

One question arises here.
I wonder why import works without Babel when it should be ES2015(ES6) statement.

It because webpack resolves import dependencies.
Modules | webpack

In contrast to Node.js modules, webpack modules can express their dependencies in a variety of ways. A few examples are:

  • An ES2015 import statement

...

In other words, there is no need to rewrite import to require with babel.
Babel · The compiler for next generation JavaScript

So, when Babel plays a role?
Edit src/js/modules/addition.js as below.

const add = (num1, num2) => {
    return num1 + num2;
}

export default add;

Arrow function expressions used in this code is an ES2015(ES6) statement.
When bundle this code by webpack without babel, get the results you want.

This is because Chrome supports the arrow functions in ES2015(ES6) statement.
Arrow function expressions - JavaScript | MDN


In the conclusion, I understand two things.

  • webpack combine the scattered modules into one file based on the import statement.
  • Babel rewrites ES2015(ES6) statement to earlier statement, but import is supported by webpack, and Chrome and other browser is also compatible with ES2015(ES6) statement.

複数行副問合せを復習するついでにSQLアンチパターンについて考えた

SQL文に自信がなかったので1年前くらいに買ったスッキリわかるSQL入門 第2版 ドリル222問付き! - インプレスブックス(以下、本A)を復習した。
特に第7章「副問合せ」の練習問題7-3の3を解いてみて、あー、なるほどと思ったのでメモする。

まずは、カラムなどは本と違うけど、データを用意する。
ちなみに、名前の作成は、日本人名前自動生成機 -- 高樹凱.COMで行った。

create table family (
  id int,
  name varchar(10), 
  profession varchar(10), 
  parent_id int
);

中身はこうなっている。

select * from family;

やりたいことは、

「母親の職業がミュージシャンである人のidと名前を抽出したい」

である。

つまり、答えは以下のようになる。


ここで普段ほとんどアプリケーションのコードしか書かないので混乱する。

脳内でアルゴリズムを考えると、まず professionミュージシャン である人のid配列に格納しparent_idがその配列に格納されている人のnameを出力する、という感じ。

けど、SQL文に配列はない。
ここで登場するのが、副問合せ、特に複数行副問合せ である。

実際に書いてみると、こうなる。

select id, name from family 
where parent_id in (select id from family 
                    where profession = 'ミュージシャン');


複数行副問合せとは、本によると、以下のようなものである。

検索結果が複数の行からなる単一列(結果がn行1列)の値となる副問合せをさします。...
SQL文中で「複数の値を列挙」するような場所に、その代わりとして記述することができます。
具体的には、IN, ANY, ALL演算子を用いた条件式が代表的な事例です。

今回の例では、単一列は、idの列で、結果は「2, 3, 5」の3行である。
また、「複数の値を列挙するような場所」とは、さっきの脳内アルゴリズムの配列のことである。


問題の回答はここで終わりだけど、最近はO'Reilly Japan - SQLアンチパターン(以下、本B)も読んでいるため、作ったテーブルの妥当性を軽く検証する。

create table family (
  id int,
  name varchar(10), 
  profession varchar(10), 
  parent_id int
);

まずそもそもこのテーブルは、母親と子供の関係だけを格納していて、有用性があまり無い。(本Aの問題では母牛だった)
例えば、後から父親の情報も入れたい、となったときはどうだろう。
parent_idを消してmother_id, father_id のカラムを追加したとしても、1つのテーブルで親子関係管理するのは無理があることは想像しやすい。
そうならないように、テーブルを分割する必要がある。

create table child (
  id int,
  name varchar(10), 
  profession varchar(10), 
  parent_id int
);
create table mother (
  id int,
  name varchar(10), 
  profession varchar(10)
);

ここで、本Bの「4.2 アンチパターン: 外部キー制約を使用しない」に当てはまらないよう、外部キーを追加する。

create table child (
  id serial primary key,
  name varchar(10), 
  profession varchar(10), 
  parent_id int,
  foreign key (parent_id) references mother(id)
);
create table mother (
  id serial primary key,
  name varchar(10), 
  profession varchar(10)
);

こうすると、先ほどの副問合せは、

select id, name from child
where parent_id in (select id from mother
                    where profession = 'ミュージシャン');

となる。

次に、本Bの「10.2 アンチパターン:限定する値を列定義で指定する」について考える。
今回の場合、職業として選択されているのは、「公務員」と「ミュージシャン」である。
このような選択肢だけのアプリケーションは存在しないだろうけど、「学生」「公務員」「アルバイト」...といったような選択肢がプルダウンボックスで表示されるアプリケーションは多々あると思う。
そして、今のままのテーブルでは、

insert into child (profession) values ('ばなな');

というようなprofessioへの意味不明な挿入がエラーで弾かれない。
無効なデータ入力を拒絶するために、check制約やenum制約をつけることがある。

create table child (
  id serial primary key,
  name varchar(10), 
  profession enum('公務員', 'ミュージシャン'), 
  parent_id int,
  foreign key (parent_id) references mother(id)
);

しかし、これは本Bではアンチパターンとされていて、professionの追加や廃止に弱い。
解決策とされているのは、参照テーブルを新たに作成することである。
結果、以下のようになる。

create table child (
  id serial primary key,
  name varchar(10), 
  profession varchar(10), 
  parent_id int,
  foreign key (parent_id) references mother(id)
  foreign key (profession) references profession(profession)
);
create table profession {
  profession varchar(20) primary key
}

insert into profession (profession) values ('公務員'), ('ミュージシャン')
create table mother (
  id serial primary key,
  name varchar(10), 
  profession varchar(10)
  foreign key (profession) references profession(profession)
);

こんな感じで、正しいかどうかはさておき、独学中も自分であれこれ考えられるようになった(気がする)。

はてなサマーインターン2020に参加した

はてなリモートインターンシップ2020 に参加しました。

長文になると思います。
成果発表のスライドを貼っておくのでこれだけでも見てください。

speakerdeck.com

参加するに至るまで

はてなは近くて遠い会社でした。
エンジニアとしての職を探しさまよっていた時期も、京都、しかも通学経路に本社があることは知っていたのですが、技術力が相当高い会社であることも知っていたので、受かるわけがないと思いバイトも受けていませんでした。
今回のインターンシップも、これまでインフラの業務経験があるわけでもなく、趣味でDockerやk8sをゴリゴリ使っているわけでもなかったのですが、一縷の望みをかけてdocker run --rm -it hatena/apply-for-internship-2020:latestコマンドを叩いた結果、面接の機会をいただきました。
面接は、今までで一番自己肯定感が上がった面接でした。
アプリをストアに2本リリースしていたり、Qiitaで記事を70記事程度書いたり、atcoderのメモを https://wafuwafu13.hatenablog.com/ にまとめていたりと、結構アウトプットが溜まっていたので、そこを中心に話が進み、評価されたのだと思います。
また、自分のエンジニアとしての素質にも触れていただき、自信が湧いた感覚を今でも覚えています。
はてなのことをずっと知っていて志望動機が高かったのも伝わったと思います。
気持ちよく面接が終わった2時間後くらいに、サマーインターン内定の連絡が来て、まじかと思いました。
その勢いに乗っかって、バイトの面接も受けることにしました。
インターンの面接時に、バイトもしたいということを言っていたので、たぶんいけるだろうという自信と、先のインターンの5日間のパフォーマンスがめちゃくちゃ悪くて自信がなくなってしまうという事態も考慮して、絶対今受けた方がいい、と思ったのもあります。
バイトの面接では、夏休みが暇になることは確定していたため、インターンの前にバイトを始めることにちょっとした違和感も覚えつつ、8月からいけるという話をさせていただいたところ、8月から入社させていただく流れになりました。
うまく言語化できないですが、他のインターン生に対して、インターン開催の前にはてなに在籍しているという若干のずるさ的な感覚を若干感じていたのですが、自己紹介ページに actions-gh-pages の中の人や、アメリカから参加される方など、錚々たるメンバーが揃っていたので、ずるさ的な感覚が畏怖で消え去ると同時に、バイトはバイトで、インターンインターンで自分のやれることを精一杯やろう、という気持ちになりました。
先ほど記述した通り、これまでインフラの業務経験があるわけでもなく、趣味でDockerやk8sをゴリゴリ使っているわけでもなかったので、講義があるといえど、さすがに知識の補強をしないとまずいと思い、発表スライドの最後のページに記述した予習などをしておきました。
これはほんとにやってて良かったと思います。
他におすすめの本や動画があったら教えてください。

1日目

講義がありました。
普段は情報系の学部にいないため、技術の講義を受けるのは新鮮で、内容もわかりやすく、非常に勉強になりました。
ただ、事前に詰め込んでいた前提知識だけでは理解できない部分も少なからずあり、思考が停止していた時間もありました。
後にも書きますが、やっぱりネットワークやインフラの基礎知識が全然足りてなくて、今まではコマンド叩けてるだけだったことを痛感しました。
全体を通して言えるのですが、あとでしっかり復習したいと思いました。
話題のDockerQuizは、brew install aquasecurity/trivy/trivy の終了を待っていたら時間切れになりました。

講義が終わったあとの1時間くらい、ちょっとだけ実装する時間がありました。
しかしその時間は、Istio導入後の再起動時に、make up でサービスが立ち上がらない、という環境構築の問題を解決する時間となってしまいました。
k8sのエラーログとは初めましてで、どうしたらよいか分からず、Reset Kubernetes Cluster を押して make up叩いて祈る、みたいなことを繰り返しては落胆していました。
インターンの事前課題は、make upでサービスを立ち上げましょう、という課題で、その時は立ち上がっていたため、その時の状態に戻すしかないと思い、zoomでの歓迎会をしているときに、Docker Desktop for Mac の再インストールや git clone をしていました。(話はちゃんと聞いていた)
この環境構築でつまづくインターン生は他にもいたようで、成果発表の時にきちんと原因を突き詰めて発表していた方もいたので、それができるようになりたいです。

2日目

1つ目の課題である、基本的な記法の実装と、独自記法の実装をしました。
これから出てくるライブラリの説明やリンク等々は成果発表のスライドにまとめてあるので見ていただければと思います。
基本的な記法の実装は、goldmarkの力ですぐにできました。
とはいっても、レポジトリのファイル構成が今まで経験したことのない構成だったので、どこをいじればどう動くのかを掴むのが大変でした。
このときはまだマイクロサービスを構成している、という理解がまだできていませんでした。
独自記法の実装ですが、これもまた難しかったです。
ASTやParserに関しては申し訳程度の知識があるのですが、短い期間で自作Parserを作れる気もせず、とりあえずgoldmarkのソースコードを読んでみることにしました。
2時間程度ソースコードを読むために費やし、なんとなく仕様が掴め、既存のコードに自分なりの記法を足すことにしました。
そこで生まれたのが「xyz記法」です。
おなじみの マークダウンのチェックボックス記法 - [x]のnodeを拡張し、- [y]- [z] にも対応させてみよう、と思い実装しました。

// NewTaskCheckBox returns a new TaskCheckBox node.
func NewTaskCheckBox(checked bool, ranged bool, colored bool) *TaskCheckBox {
	return &TaskCheckBox{
		IsChecked: checked,
		IsRanged: ranged,  // 追加
		IsColored: colored, // 追加
	}
}

実装に取りかかってからは詰まった部分はあったものの、正規表現をゴリゴリ書いたり、ASTの構成を変える必要はなかったため、ソースコードを読み解くほど難易度は高くありませんでした。
しかしその分、拡張することが目的となってしまい、独自性や実用性は薄れてしまったのがトレードオフでした。
なにはともあれ、goldmarkのextensionのコードを流用したり、testutilを利用してテストをサックリ書いたりと、既存のコードをうまく生かして2日目を食らいつけたのはよかったです。

3日目

2つ目の課題である、タイトルの自動取得機能の実装をしました。
URLからタイトルを取得する機能は、goqueryを使ってサクッと実装できたのですが、その独立した機能を他のマイクロサービスとgRPCによって統合するのがとても難しかったです。
他のrendererやblogなどのサービスがどのように実装されているのかを参考にしつつ、理解半分、コピペ半分で実装を進めました。
この実装は、podを増やしたことによってmake upが再びコケるなど、結構時間がかかってしまい、翌日に縺れ込むことになりました。
ちなみに最近は質の良い睡眠を心がけているため、寝る前にパソコンを開けることはしていません。
しかしインターン期間中は周囲がレベチであるが故の不安やら新技術に対する興奮やらで3/5日睡眠導入に失敗しました。

4日目

statusCheckDeadlineSecondsを伸ばしてなんとかmake upを成功させたのですが、gRPCによる通信がなぜかうまくいかなかったので、メンターさんと一緒に画面共有でトラブルシューティングすることになりました。
いろいろデバッグをしても原因究明には至らず、諦めかけていたのですが、ふとした瞬間に試してみると上手くいく、という、よくありがちな気持ちの悪いトラブルシューティングとなりました。
原因は究明できていないですが、podの再起動に時間がかかっていた、というのが一番有力です。
しかし、デバッグの仕方や、バグ発見の勘所などを、その時間を通して得られたので、貴重な経験になりました。

2つ目の課題である、タイトルの自動取得機能の実装と統合が終わったので、発展課題を進めることにしました。
これは自身の弱さが一番露呈したところなのですが、並行処理や、リバースプロキシ、キャッシュの実装に対する知見、経験がなかったため、robots.txt を解析してクロール禁止サイトに対するスクレイピングをしないようにする、という機能を実装することにしました。

robots.txt の解析に便利なpythonモジュールがあったため、せっかくのマイクロサービスだし別言語での実装ができたらかっこ良さそうというノリで実装をはじめました。
Dockerfileやcompile、gRPCの定義を、コピペではなく自力で考えて実装することで、システム全体に対する理解度が上がりました。

mkdir -p python/inspect
docker run -v "$(pwd):/pb" -w /pb --rm hatena-intern-2020-protoc-python \
    python3 -m grpc_tools.protoc -I./ --python_out=./python/inspect --grpc_python_out=./python/inspect inspect.proto
mkdir -p ../services/inspect/pb
cp -r ./python/inspect ../services/fetcher-go/pb
cp -r ./python/inspect ../services/renderer-go/pb
cp -r ./python/inspect ../services/inspect/pb

protoファイルからpythonのコードを生成することはできたのですが、ファイルが2種類生成され、「あれ?Goと全然違うじゃん」となり困惑していたら時間切れになってしまいました。
今振り返ってみると、gRPCをpythonで実装するコストの大きさや、講義でも紹介されていた、サービスを分割する基準の失敗パターンである、「サービス内の概念ごとに無条件で分けまくる」、「一つの機能を出すのに多数のサービスをデプロイ」に当てはまりかねない設計だったため、Goでパースした方がよかったのかなぁ、とも思います。

5日目

成果発表がありました。
独自記法や実装方針が十人十色で、とても聞いていて楽しかったです。(発表がトップバッターでよかった)
ASTの構造まで踏み込むことや、並行処理、リバースプロキシ、キャッシュの実装やCI/CDの構築に対する知見、経験の差が出たかなぁと思います。
2人のインターン生に対してメンターが1人つき、Discordの部屋でそれぞれ作業する、インターン生は作業のログをScrapboxに残し、それがDiscordの別チャンネルに流れてくる、という形式でした。
他のインターン生のログや発表内容が高度すぎて理解できない部分があり、経験年数の違いがあるといえど、3日間の実装でここまで差が出るのは悔しいという感情と、そこまで引けを取っていないという自信とでちょうどいい感情になりました。
5日間の成果であるスライドやこのブログを10年後に見ても、頑張ったな〜となると思います。(同時に社会学部にいたことを忘れている)

感想と今後の展望

5日間で進化した点を簡潔にまとめると、

  • 事前に補強した知識を生かすこと
  • ソースコードを読みに行ってアイデアを絞り、自分で手を加えること
  • k8sやgRPCといった技術を、開発を通してキャッチアップすること
  • システムや実装の設計を俯瞰で捉えること

があげられると思います。


今後は、はてなで戦力になることを最優先の目的としつつ、学生生活もまだ長いため、自分の実力を試していけたらいいなと思っています。

5日間を共にしたインターン生やメンターの方々、ありがとうございました。
これからもよろしくお願いします!