がんばるぞ

がんばります

1ヶ月間姿勢が悪かったのでPureScriptの勉強をしているよという話をします

この記事はスターフェスティバル Advent Calendar 2021の5日目です。


姿勢

1ヶ月間良い姿勢で仕事した結果、腰痛が治るわ睡眠効率が改善するわ最高!という話をするつもりだったのだけど、この一ヶ月間全くもって姿勢が良くなかった。 一体何が悪かったのだろうか...。いや、悪かったのは姿勢なんだけど、なぜ良い姿勢を保つことができなかったのか。

終わってしまったことを悔やんでも仕方がないので、今日はなにか別の話をすることにします。最近PureScriptで関数指向プログラミングの勉強をし始めたのでそこらへんの話を。 腰が痛いな

ちなみにやっとコードが読めるようになってきたくらいで関数指向とはみたいなところはほとんど理解出来ていないし、モナド周りでめっちゃ苦戦してます。 背中も違和感があるな

PureScript とは?

AltJSの純粋関数型言語Haskellから多くの影響を受けているらしいです。 同じくAltJSで関数指向のプログラミングが出来るElmという言語もありますが、サーバーサイドで動作させることも可能で言語機能も充実しているという点がElmとは異なるようです。

なぜPureScriptなのか

HaskellScalaあたりも候補だったのですが、

  • spagoというコンパイラ&パッケージマネージャが使いやすそうだったこと
  • npmとnodejsがあればとりあえず始められること
  • どうせやるなら純粋関数型まで振り切ってしまった方がいい気がした

あたりを理由にPureScriptにしました。

環境構築周りの話

こちらの記事が大変参考になる上にめちゃくちゃいい感じに要約されているので、こちらを読むとわかりやすいです。 qiita.com

面白かったところたち

PureScriptの解説がちゃんとできるほど理解できてないので、勉強していておもしろーいってなったところをつらつら紹介したいと思います

デフォルトで部分適用される関数

例えば以下のコードは、PHPerであれば one という変数に 1 を代入していると読むと思うのですが、実はこれは1という値を返す関数の定義になります

one = 1

引数が必要な場合は以下のように書きます。

add x y = x + y

また、型の書き方もPHPとは全然違います。 先ほどの add 関数であれば、型は以下の通りです。

add :: Int -> Int -> Int

最初の Int が 第一引数の型で、2つ目の Int が第二引数の型で、最後の Int が返り値の型です。 なんで Int Int -> Int じゃないんだ?と僕は思いましたが、当然理由があって、PureScriptでは全ての関数は1つの引数だけ受け取るようになっているので、引数が二つある関数は実際には「関数を返す関数」なんですね。引数が3つある関数は「関数を返す関数を返す関数」です。

具体的には add 1 3 などのようにadd関数を実行すると、 まず add 1 の部分が評価された結果 Int -> Int という関数が返り、その関数に 3 が適用されることで最終的に 4 という数値が手に入る。みたいな感じです。

replなどでその様子を確かめるとわかりやすいです( :t は型を調べるコマンドです)

> :paste
… add :: Int -> Int -> Int
… add x y = x + y
… 
> :t add                  
Int -> Int -> Int

> :t add 1
Int -> Int

> :t add 1 3
Int

> (add 1) 3
4

ここらへんの挙動もPHPでは考えられない世界観で面白いですね。

無理やりPHPで表すとこんな感じでしょうか

<?php
$add = fn (int $x) => fn (int $y) => $x + $y;

$add(1)(3);

たぶんこれのおかげで関数の合成や組み合わせがしやすくなるとか再利用生が上がるとかなんか色々メリットがあるのだと思われです。わかりませんけど。

中置演算子

+ などの記号は実際のところただの関数で、型は forall (a :: Type). Semiring a => a -> a -> a です。 ただ、普通の関数と違い引数を記号の左右に書くことができます。こういったものを中置演算子と呼ぶらしいです。

普通の関数と同じように扱うためには () で囲みます。

> (+) 1 3
4

また、逆に普通の関数を `` で囲むことで中置演算子として書くことができます。

> 1 `add` 3
4

infix という機能を使うと、この中置演算子を自由に追加することができるようです。

> infixl 6 add as 🍎 
> 1 🍎 2
3

自由に演算子を追加できるという世界観もPHPには存在しないので面白いですね。

ただかなり気を付けないと可読性が悪化するだけになりそうなので、フレームワークを作るなどでない限り出番は無さそうな気がしています

パターンマッチ

PHPも最近はmatch式が導入されて便利ですが、パターンマッチはもっと便利です。

例えばEitherという成功(Right)あるいは失敗(Left)を表す型があるのですが、以下のような感じで成功時と失敗時の処理を分岐させることができます。 (関数のオーバーロードに少し似てますね)

data Err = ConnectionErr | Timeout

logError :: Either Err String -> Effect Unit
logError (Right _) = log "success"
logError (Left _) = log "error"

PHPで書くならこんな感じでしょうか

<?php

return match ($either::class) {
    Right::class => fn() => print 'success',
    Left::class => fn() => print 'error',
};

さらに、もっと細かく型を指定した書き方もできます。以下だとConnectionErrだった時だけ違うログメッセージ出すってかんじですね。

これくらいの分岐からPHPだと若干ごちゃついてきますが、PureScriptだととてもシンプルです。

logError (Right _) = log "success"
logError (Left ConnectionErr) = log "connection error"
logError (Left _) = log "error"

そして最高なのが、全てのパターンを網羅できていない場合はビルドできなくなることです。

PHPenumが入ったことだし、PHPStanとかを使えばEnumを使ったswitch文とかmatch式は全部のパターンを網羅できてないことを検知できるようにそのうちなりそうですね。もうなってるのかな

おわりに

ちょっと早い上にとりとめもない感じなのですが娘と寝る時間になったのでもう終わります。 純粋関数型言語の世界観はPHPとは全く違うので慣れるまで大変ですね。全体的な構文もそうですが、do記法とかモナドは意味がわからないし、関数合成も慣れないと挙動が想像しにくいです。

でもこれらを理解することで得られる学びにすごく興味があるので引き続きチビチビがんばっていきたいと思います。

あと来年こそは良い姿勢で過ごすことで体のコリを軽減し睡眠の質を改善したいです。 今年はもう諦めました。

買ってよかったもの2020

今年は色々と物を買った気がするので、買ってよかったものを振り返ってまとめようと思います。 トゥギャッターではリモートワーク手当として毎月3万円が支給される(!!!)ので、自宅の作業環境の改善による生産性の向上を口実に色々と利用させていただきました。(手当の使い方は自由なのだが、性格上気を抜くと貯金ばかりしてしまいそうだったので)

布団クリーナー

妻がハウスダストだかほこりだかのアレルギーなので購入しました。 実際に使ってみるとすごい。めっちゃホコリがとれる。 こんなホコリだらけの布団にくるまって寝てたんかい......って落ち込むくらいよく取れる。

僕は寝起きにくしゃみをよくしてしまうのだが、こいつを使うようにしてからくしゃみが激減した。 「寝起きはくしゃみが出るもの」程度に考えていたので、ホコリなどが原因だったことに初めて気がついた。妻さん、布団クリーナーを欲してくれてありがとう。

整腸薬

ヤクルトBL整腸薬 36包 [指定医薬部外品]

ヤクルトBL整腸薬 36包 [指定医薬部外品]

  • メディア: ヘルスケア&ケア用品

なんか妻が飲んでたので僕も試しに飲んでみたらアラ不思議。快便になる。 僕は基本便秘気味なのでたまに飲むようにするとこりゃあいいという感じ。リピ決定!!!!妻ありがとう。

冷凍餃子

妻にそそのかされるがまま買ってみると、ウマイっ!!!!!! 特に餃子鍋にすると良かった。 ご飯作るのダリ〜って時にとても便利なので、「俺のバックには大量の冷凍餃子がついてるから、いつご飯作るのダリ〜って思っても良いんだぜ」という心持ちになれるため日々のストレスが減った。 妻と冷凍餃子ありがとう。

カチューシャ

カチューシャはすごい!!!!!!!!!!!!!!!!!!!!いくら髪の毛が伸びても邪魔じゃない!!!!!!!!!!!!!!!!!!!! コロナで人と会うことも減ったし、髪の毛切るのめんどっち〜〜〜〜ってなっていたのでとても助かりました。

ちなみにこれは締め付けがきつい時はグイ〜〜〜て広げると広がってくれるところが良いです

断熱タンブラー

こいつにお湯/冷水を入れるとしっかり保温される。 朝に入れたコーヒーがお昼になっても暖かいままで感動した。 僕は基本的に水分をあまり摂取しない(というか忘れる)ので、仕事中などに「あっ、飲み物の存在忘れてた」となったときにこの保温力があるととても嬉しい。

サーキュレーター

これは衝撃の事実なのですが、僕の作業部屋にはエアコンがない。取り付けることができない。 コロナでリモートワーク化してから、この事実がシャレにならなくなったので藁にもすがる思いで購入。 隣の部屋の空気を作業部屋に送ることでなんとかなった! サーキュレーターありがとう!!!引っ越さなくて済んだ!!!

ウルトラワイドディスプレイ

トゥギャッターのリモート手当を2ヶ月ため込んで購入した。 こいつは94Wの給電もついてるのでMBPの充電ができる! さらにUSBポートもType-Cが1つとUSB3が2つ付いてるので、配線がめっちゃスッキリするという優れもの! 画面がでかいは正義!

Oura Ring

トゥギャッターのリモートワーク手当があまりに最高すぎるので、手当を有効活用して会社に貢献しなくてはと思い調べていたところ発見。 睡眠の質が上がれば生産性も上がるはず!!!!ということで睡眠トラッキングを始めました。 トラッキングをしてみたところ想像以上に睡眠の質がよくないことが発覚したため、諸々がんばって睡眠の質を改善した結果、集中力が以前より向上している...というか安定することが実感できました。

Oura Ringはいいぞ!

セパレートキーボード

肩こりが辛かったため購入。 普通のキーボードと比べて使用感がそこまで変わらず、肩こりが軽減されたのでかなり満足! 左右のキーボードをつなげるケーブルが少し短めなのがちょっと不満!

バロンチェア

腰痛になりつつあったのでオフィスバスターで2万程度で購入。安い!ありがとう!!!!!! 腰がめっっっっっっっっちゃくちゃ楽になったので高い椅子はすごい!!!!!ってなりました。

いかがでしたか?

いかがでしたか?

コンピュータシステムの理論と実装 Chap3

www.amazon.co.jp

これの三章です。

三章の概要

この章では順序回路について学び、フリップフロップレジスタ、メモリなどを実装する。

順序回路

順序回路とは時間に依存した回路のこと。
1章、2章で実装した回路は時間に依存しておらず、組み合わせ回路と呼ばれる。

クロック

時間の経過をモデル化したやつ
こいつが各回路に信号(タイムユニット)を送り続けることによって、時間に依存した回路を実装することができる。

フリップフロップ

順序回路の中で最も基本的な回路。
いくつかある種類のうち、本書では D型フリップフロップ(DFF) を採用している。
こいつのおかげで回路の同期的な計算が可能となる

DFF は 1bit の入力とクロック入力という2つの入力と1bitの出力があり、振る舞いとしては一つ前のタイムユニットの入力を出力するだけの回路である
この回路を元にさまざまな記憶素子を実装していく

レジスタ

データを格納したり読み出したりすることができる記憶装置
多ビットを記憶できるレジスタが持つ値はワードと呼ばれる。

メモリ(RAM)

任意の長さのワードを記憶できる装置。
物理的な記憶位置に関係なく、同じ時間で直接値にアクセスできる

カウンタ

タイムユニットが進むごとに任意の整数が加算されていく回路。
任意の整数は 1 が用いられることが多い。

順序回路の実装

2章までに実装した回路と予め用意されているフリップフロップ回路を用いて、レジスタ・メモリ・カウンタなどの順路回路を実装する。

以降は僕が実装したコードが出てくるので、ネタバレされたくない人は読まない方がよいです

1bitレジスタ

in と 制御ビットの load の2つを入力として受け取り、 load が1の場合に保存された値を読み込み、 load が 0 の場合に in の値を書き込めばOK

Mux(a=loop, b=in, sel=load, out=mux);
DFF(in=mux, out=out, out=loop);

16bitレジスタ

1bitレジスタの16bit版。
なので、1bitレジスタを16個並べるだけで良さそうだったのでそうした

Bit(in=in[0], out=out[0], load=load);
...
Bit(in=in[15], out=out[15], load=load);

RAM(8|64|512|4K|16K)

inload に加えて address の3つの入力を受け取り、 address の値によって読み書きするレジスタを選択出来ればOK。

各16bitレジスタの出力を Multiplexor で振り分ければいけそうだったのでそうした。 また、指定されたレジスタ以外に書き込みが発生するとまずいので、 入力の loadDemultiplexor で振り分けてる

以下のコードは RAM8 だけど、それ以外のRAMも同じようなロジックでいける。

DMux8Way(in=load, sel=address, a=load1, b=load2, c=load3, d=load4, e=load5, f=load6, g=load7, h=load8);
Register(in=in, out=out1, load=load1);
Register(in=in, out=out2, load=load2);
Register(in=in, out=out3, load=load3);
Register(in=in, out=out4, load=load4);
Register(in=in, out=out5, load=load5);
Register(in=in, out=out6, load=load6);
Register(in=in, out=out7, load=load7);
Register(in=in, out=out8, load=load8);
Mux8Way16(a=out1, b=out2, c=out3, d=out4, e=out5, f=out6, g=out7, h=out8, sel=address, out=out);

カウンタ

in と制御ビットである inc , load , reset の4つの入力を受け取り、 制御ビットに合わせて動作を変えればOK
inc が 1 の場合は格納されている値をインクリメントし、 load が 1 の場合は格納されている値を in で上書きし、 reset が 1 の場合は格納している値を 0 にする。
値の格納にはレジスタを使う

ALU と同じような実装で良さそうだったのでそうした。

// inc
Inc16(in=loop, out=incremented);
Mux16(a=loop, b=incremented, sel=inc, out=in1);

// load
Mux16(a=in1, b=in, sel=load, out=in2);

// reset
Mux16(a=in2, b[0..15]=false, sel=reset, out=in3);

Register(in=in3, load=true, out=out, out=loop);

感想

うお〜メモリを実装したぞ〜〜

コンピュータシステムの理論と実装 Chap2

www.amazon.co.jp

前回に引き続きこれです

2章概要

2進数、符号付き2進数、2の補数、ブール算術など理解し、加算器・算術論理演算機を実装する

2進数

0と1のみを使って数を表現するやつ

符号付き2進数

正の数と負の数を表現できる2進数

2の補数

符号付き2進数を実現するために使われているやつ。
最上位bitの状態で正か負かを判断できる。
補数を求めるための数式は 2**n - x ※ n= bit数

たとえば 8bitで -5 を表す場合は 2**8 - 5 = 256 -5 = 251 となり、2進数に直すと 11111011 となる
これに8bitの5である 00000101 を加算してみると 00000000 となり (繰り上がった9bit目はオーバーフローで無視) 答えが0になっているので補数であることが確認できる。

符号付き2進数において2の補数での表現を採用するメリットは、減算を加算と同じ手順で計算できるためシンプルな実装になることらしい。
たとえば4bitの 3-2 であれば 0011 + 1110 となり、計算結果は 0001 (繰り上がった5bit目は無視) なので、正しく計算できていることがわかる。

加算器とALUの実装

1章で実装した論理ゲートを用いて、いくつかの加算器と算術論理演算機(ALU=Arithmetic Logical Unit)を実装する。

以降は僕が実装したコードが出てくるので、ネタバレされたくない人は読まない方がよいです

半加算器

2つの入力の和を出力すればOK。
出力パターンは 00, 01, 10 の3つ

最下位ビットは2つの入力がそれぞれ 0 と 1 だった場合にのみ 1 を出力し、それ以外は 0 を返せばよく、これは Xor の挙動と同じなので Xor を使い、
最上位ビット(=繰り上がり) は2つの入力が両方とも 1 だった場合にのみ 1 を出力すればいいので And を使った。

Xor(a=a, b=b, out=sum);
And(a=a, b=b, out=carry);

全加算器

3つの入力の和を出力すればOK。
出力パターンは 00, 01, 10, 11 の4つ

半加算器を2つ使用して最下位bitの和を求めて、どちらかで繰り上がりが発生していた場合に最上位bitに1を出力すれば良さそうと思ったのでそうした

HalfAdder(a=a, b=b, sum=tmp, carry=carry1);
HalfAdder(a=c, b=tmp, sum=sum, carry=carry2);
Or(a=carry1, b=carry2, out=carry);

16bit加算器

2つの16bitの入力の和を出力すればOK。
オーバーフローは検出しない

半加算器と全加算器を並べるだけで良さそうだったのでそうした

HalfAdder(a=a[0], b=b[0], sum=out[0], carry=carry1);
FullAdder(a=a[1], b=b[1], c=carry1, sum=out[1], carry=carry2);
...
FullAdder(a=a[14], b=b[14], c=carry14, sum=out[14], carry=carry15);
FullAdder(a=a[15], b=b[15], c=carry15, sum=out[15]);

インクリメンタ

与えられた16bitの入力に1を加算すればOK

先ほどの16bit加算器を使うと楽そうだったのでそうした

Add16(a=in, b[0]=true, b[1..15]=false, out=out);

ALU

2つの16bitの入力といくつかの制御ビットをもとに計算する。
出力は計算結果の out , out が 0 であるかどうかを表す zr , outが負の数であるかどうかを表す ng の3つ。

制御ビット 説明
zx 入力 x をゼロにする
nx 入力 x を反転する
zy 入力 y をゼロにする
ny 入力 y を反転する
f True であれば加算、 False であれば And演算 する
no 出力を反転する

If文使いてえ〜と思いながら Multiplexor を使ってそれぞれの制御ビットを適用した計算結果を選択していく感じで実装した。

out は制御ビットを適用したあとの数値をそのまま出力し、
zr は out の全てのbit が 0 だった場合に 1 を出力すればよいため Or8Way (8bitの中に1つでも 1 があれば 1 を出力する論理ゲート) を2つ使い
ng は最上位ビットが 1 であれば 1 を出力し、そうでなければ 0 を出力すればいいため、 out の15bit目をそのまま出力した

変数名が無になって辛い...。

// zx, nx
Mux16(a=x, b[0..15]=false, sel=zx, out=x1);
Not16(in=x1, out=x2);
Mux16(a=x1, b=x2, sel=nx, out=x3);

// zy, ny
Mux16(a=y, b[0..15]=false, sel=zy, out=y1);
Not16(in=y1, out=y2);
Mux16(a=y1, b=y2, sel=ny, out=y3);

// f
Add16(a=x3, b=y3, out=xAddY);
And16(a=x3, b=y3, out=xAndY);
Mux16(a=xAndY, b=xAddY, sel=f, out=f1);

// no
Not16(in=f1, out=notF1);
Mux16(a=f1, b=notF1, sel=no, out=out, out[0..7]=out0to7, out[8..15]=out8to15, out[15]=ng);

Or8Way(in=out0to7, out=zr1);
Or8Way(in=out8to15, out=zr2);
Or(a=zr1, b=zr2, out=zr3);
Not(in=zr3, out=zr);

感想

ALUはもうちょっと良い実装があるんだろうなーと思ったけど特に浮かばなかったのが残念。
模範解答的なやつがあれば見てみたいなー

コンピュータシステムの理論と実装 Chap1

www.amazon.co.jp

最近この本をやり始めたのでメモ

1章概要

ブール理論を理解し、本書から提供されているハードウェアシミュレーターを用いて数種類の論理ゲートを実装する。

論理ゲート

ブール関数を実装した物理デバイスのことらしい。が、実際に物理デバイスを作る必要はなく、ハードウェアシミュレーター上で論理ゲートを実装すればOK。

ブール関数とは And とか Or とか Not のように、 n個の入力値を元に新しいブール値を出力するような関数のこと

And だったら2つの入力値が共に True であれば True を出力し、それ以外であれば False を出力するし、 Not であれば1つの入力値を反転させた出力をする。
出力値は1つとは限らず、例えば Demultiplexor と呼ばれるブール関数は複数の出力を持つ

論理ゲートの実装

Nand (=not and) という論理ゲートだけが予め用意されているので、 Nand を使って他の論理ゲートを実装していく。 実行効率についてはあまり考えずに、最初に浮かんだロジックをとりあえず実装して進める方針でやっていく。

以降は僕が実装したコードが出てくるので、ネタバレされたくない人は読まない方がよいです

Not

与えられた入力値を反転して出力すればいい。

Nandしか使えないので以下のような実装になった。

Nand(a=in, b=true, out=out);

PHPだとこんな感じになる

<?php
function not(bool $in): bool
{
    return nand($in, true);
}

And

入力値が2つとも True だったら True を出力し、それ以外であれば False を出力すればいい。

そもそも Nand!And なので、さらに否定すれば !!And となって And になるじゃんと思ったので以下のような実装になった。

Nand(a=a, b=b, out=nand);
Not(in=nand, out=out);

PHPだとこう

<?php
function and(bool $a, bool $b): bool
{
    return not(nand($a, $b));
}

Or

2つの入力値がどちらも False である場合に False を出力し、それ以外の場合は True を出力する。

Nand は 2つの入力値が True である場合のみ True を出力し、それ以外の場合は False を出力するという Or の動作と似てる感じだったので、入力値をそれぞれ否定すればいいんじゃねということで以下のような実装になった

Not(in=a, out=notA);
Not(in=b, out=notB);
Nand(a=notA, b=notB, out=out);

Xor

2つの入力値がそれぞれ異なる値の場合に True を出力し、そうでない場合に False を出力する。

OrNand がそれぞれ True を出力すれば2つの入力値が異なる状態であると言えると思うので、以下のような実装になった

Nand(a=a, b=b, out=nand);
Or(a=a, b=b, out=or);
And(a=nand, b=or, out=out);

Multiplexor

a,b,selという3つの入力値を受け取り、selが False であれば a を selが True であれば b を出力すればOK。

こういった分岐はif文を使うことが前提な思考になっていたので、少し戸惑ってしまった。
あと変数名をどうすればいいかわからなさすぎる

b の値を出力する場合は sel と b を And 関数に通せばいいが a を出力する場合はそうもいかない。
が、sel を反転すれば a も b と同じ扱いが出来るのではという感じで以下の実装になった。

Not(in=sel, out=notSel);
And(a=a, b=notSel, out=a1);
And(a=b, b=sel, out=b1);
Or(a=a1, b=b1, out=out);

Demultiplexor

in,sel という2つの入力値をとり、a, b という2つの出力をする。 sel が False であれば a=in, b=0 を sel が True であれば a=0, b=in を出力すればOK。

先ほどの Multiplexor を使えば簡単そうだったのでそうした

Mux(a=in, b=false, sel=sel, out=a);
Mux(a=false, b=in, sel=sel, out=b);

多ビットAnd, Not, Or, Multiplexor / 複数入力 Or, Multiplexor, Demultiplexor

他にも課題のゲートはいくつかあったが、新しい考え方は特になかったのでスキップ

感想

ブール関数だけで Multiplexor みたいなゲートを実装するのはすごく新鮮で面白かったなー

2019年まとめ

主な出来事

(やっぱり技術的な挑戦はあまりしてないな)

今年買った本

良かった本だけ掲載。

ザ・ファブル」と「悪魔のメムメムちゃん」以外の漫画は全てカバレッジを上げている期間に読みました。精神を病まないようにすることに必死でした。

うーん、技術書を全く読んでない。これはひどい

あっ、大事な本を忘れてました。
僕も寄稿させていただいたみんなのPHPは最高なので買ってください!

2019年の雑感

2019年、色々ありました。

技術的に大きく進歩した実感はあまりないけど(遊びすぎた)人間関係がすごく充実した良い一年だったと思います。

知り合いが増えた

一番の変化はやはり知り合いが増えたことでしょうか。
hamacoさんのおかげもあり本当に知り合いが増えた。死ぬほど増えた。
去年は雲の上だと思っていたような人ともTwitter上で会話が出来るようにもなりました。
本当に信じられない変化ですね。

心を許せるレベルの知り合いも複数人出来ました。
すごい幸運。ラッキーボーイ。みなさんいつもありがとうございます。

そして、勉強会などで会ったことのない人から「Twitterで顔だけ見たことあります」みたいなことを言われるようになった。
どうかしてる。

コミュニティは本当に大事だなと実感した1年だったので、来年も無理のない範囲でコミュニティに貢献できたらなと思ってます。

みんなのPHPに寄稿した

商業誌ってずっと目標にしてたんですよね。まさかこんなに早く関わることが出来るとは。 みんなのPHP、もっと売れるといいな〜〜!

次は単著出せるようにがんばるぞ〜。

遊びすぎた

本当に遊びすぎた。 仕事でカバレッジを20%から90%に上げたりはしたけど、やれば誰にでも出来ることだし、何にも挑戦してなくてヤバい。

未熟さを思い知らされた

すごい人ってすごくて(語彙力)、会話をするたびにレベルの違いを思い知らされます。

中でも印象に残っているのはDDD Meetupの懇親会の時のことで、すごい人たちが繰り広げているモデリングの議論にまっっっっっったくついていけなかったことです。

レベルが高すぎるゆえに質問することすら出来なくて、とてつもない悔しさと「この人たちスゲェ」という興奮が綯い交ぜになった感情を抱きました。

このように、すごい人たちに囲まれていると「このままじゃヤバい」という感情がムクムクと湧いてくるので、来年はネタキャラとしてだけではなく、Webエンジニアとしてもみんなと会話が出来るようにならないとなーと思っています。
とくに今年はみんなと知り合えた嬉しさからか遊びすぎた(はしゃぎすぎた)ので、来年は遊びはほどほどにしてたくさん勉強しようと思います。

カバレッジを上げて燃え尽きてる場合じゃない。

来年の抱負

  • Laravel JP Conference 2020やるぞ!
  • 設計勉強するぞ!
  • 会社のサービスを伸ばすぞ!
  • フロントエンド方面も勉強するぞ!
  • 個人でWebサービスリリースするぞ!
  • 勉強するぞ!
  • 勉強するぞ!
  • 英語勉強するぞ!
  • 音楽もやるぞ!
  • 勉強するぞ!
  • 実装速度を早くするぞ!

やるぞ〜〜〜〜〜〜〜!!!!!!!!!

*1:僕よりも前から焼肉の人

*2:由緒正しい焼肉の人

*3:リファクタリングコストの都合上、不本意な(中途半端な)実装で妥協せざるを得なかったクラス

PHP Conference Japan 2019で登壇してきました

かなり時間が経ってしまいましたが、PHP Conference Japan 2019 にて登壇してきました。

togetter.com

20%もなかったカバレッジを90%まであげたので、そのためにしたことの話をしました。

動画はそのうちYoutubeに上がるのかな?上がったらこの記事に貼っておきます。 動画あげてもらいました! www.youtube.com

この記事は登壇後記という体のただの乱文です。

ポエム

いやぁ、普通に疲れました。
登壇が終わってから、ブログを書いてる今この瞬間も完全に燃え尽きてます。

一つのエンドポイントにテストを追加するとだいたい0.04%〜0.08%くらいはカバレッジが上がってくれるんですが、 途中 0.01%〜0.03%しかカバレッジが上がらないみたいなことが連発していた時はだいぶ精神的に追い詰められました。

けどなんとか登壇までに90%まで上げることが出来てよかった。

カバレッジをあげたかった理由は登壇の最初でも話したんですが、それ以外にも 「テストがあれば防げたようなバグを僕がデプロイしてしまったから」ってのがあるんですよね。

C0レベルのテストでも防げたようなしょうもないバグで、でもめちゃくちゃ重要な機能にバグを放り込んでしまって めっちゃくちゃ情けなかったんです。

この情けないミスの償いと、同じミスを僕以外のエンジニアにさせないためにカバレッジを上げなければと強めに決心しました。

コツコツと自分の関わった範囲にテストを追加していくような手段も取れたんですけど、 派手にカバレッジを上げ、それをネタに登壇することで広報的な効果も追加出来るんじゃないかなーと思って、一気に90%まで上げることにしました。

ほら、テストってただでさえユーザーに対して直接価値を届けられるようなものではないじゃないですか。(主観)
なので、テストに大きくリソースを割くことに抵抗というか葛藤があったんですが、広報的な効果を追加すれば会社的なメリットも増えるし、まぁいいんじゃないかなーと。

ですが実際に登壇した結果、広報的な役割をしっかり果たせたのかというと

  • ポジティヴな印象を与えることが出来たのかわからん
  • PHP界隈の話題にすることが出来なかった

という点でかなり微妙なところではあるので、もうちょっと技術的に面白い話が出来たらよかったかなぁという反省があります。

カバレッジを上げて得たもの

チームメンバーへの感謝の心

僕がテストを書くことだけに集中出来るようにチームメンバーのみなさんがだいぶ気を使ってくれたので、本当に感謝しています。
具体的には、ほぼフルリモートで働くことを許可してくれたり、なるべく僕にテスト以外の作業が発生しないように配慮してくれたりですね。
みんなの配慮がなければ絶対に登壇までに90%は間に合わなかったと思います。

この場を借りてお礼を言います。ありがとうございました。

デプロイの安心感

C0レベルのテストは90%の箇所に書かれているので、とりあえず正常系であればテストが通っていればほぼほぼ動くのではって感じです。
ただ、実装をみて仕様を想像しながらテストを書いてるだけなので、漏れてるケースはちょこちょこありそうだなー。

カバレッジを追い求めることの無意味さ

おい!!!!!!!って感じなんですけど、カバレッジを追いかけるのはマジで虚無だなという感想を得ることが出来ました。

あ、いや、今回カバレッジを上げたが無意味だったという話ではないです。念の為。
今回カバレッジを上げたことは大きな意味がありました。が、カバレッジをKPIにする必要はないなという話です。

登壇の最中にも軽く話したんですが、弊社のサービスの場合は75%や80%を超えたあたりでだいたい必要そうな箇所にはテストをかけたなーという感覚があって、 それ以降は登壇のタイトルで90%って言ってしまったから仕方ねぇって感じで、「書かなくてもいいかなー」って感じのユニットテストをもりもり追加するような感じになっていました。

おそらくサービスごとに適切なカバレッジがあって、それは必要な箇所にテストを書いた結果自然と導き出されるものなので、カバレッジをKPIにしてテストを書いていくと不必要なテストが発生するんじゃないかなと思います。

みんなカバレッジとか気にせずにちゃんとテスト書いていこうな。

とはいえ、ローカルのPHPを7.4にあげてみた時にバグを発見してくれたのは「このテスト書かなくてもいいんじゃないかなー」って思って書いたユニットテストだったりしたので「このテストは必要ない」って判断するのめっちゃむずいよねって感じなんですが・・・。

また別の話ですが、テストは最終的には負債でしかないっていう意見もあって、僕も割と共感してます。

PHPは動的型付言語だし型の制約が強いわけではないしって理由でテストを書かないという選択をするのはだいぶ難しいと思うんですが、テストを書かなくて良いようなコードをかけたらよいなぁと思ってます。

PHPStanやPhanという静的解析がだいぶ強くなっているので部分的には可能かなぁ、どうだろう。

テストの責務についての考え方

Unitテストや機能テスト、受け入れテストでそれぞれのテストが担保すべき内容って違うんですよね。

機能テストで実行されているコードはUnitテストが必要ないのかというと絶対にそんなことはなくて、 テストの関心ごとが違うからそれぞれテストしないといけないと思うわけです。
機能テストの関心ごとじゃないところまで機能テストで担保しようとするとテストが大きくなりすぎちゃいますしね。

漠然と今までも理解していたんですが、今回死ぬほどテストを書きまくった結果そこらへんの考えがより明確に(深く?)なったので、どこかで改めてまとめたいなーと思ってます。

おわりに

はい、こんな感じかなー

テストは書いたので、次はこのテストたちを武器にリファクタリングに励みたいと思います。

おわり