conference on REPL: Rest-Eat-Program Loop https://blog.nsfisis.dev/tags/conference/ Recent content in conference on REPL: Rest-Eat-Program Loop Hugo -- gohugo.io ja-JP Sat, 27 Aug 2022 18:55:28 +0900 PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた https://blog.nsfisis.dev/posts/2022-08-27/php-conference-okinawa-code-golf/ Sat, 27 Aug 2022 18:55:28 +0900 https://blog.nsfisis.dev/posts/2022-08-27/php-conference-okinawa-code-golf/ はじめに

本日 PHP カンファレンス沖縄 2022 が開催された (らしい)。

カンファレンスには参加できなかったものの、懇親会の LT で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。

ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3

細かいレギュレーションは不明だったので、勝手に定めた。

  • コマンドライン引数の第1引数で受けとる
  • 結果は標準出力に出す
  • コンマの直後にはスペースを1つ置く
  • 末尾コンマは禁止
  • 数字でないものは入ってこないものとする
  • 負数は入ってこないものとする

書いたものがこちら:

[<?php $n=$argv[1];foreach([1e4,5e3,2e3,1e3,500,100,50,10,5,1]as$x)for(;$n>=$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>]

しめて 123 バイトとなった (末尾改行を含めずにカウント)。

こちらは改行とスペースを追加したバージョン:

[<?php

$n = $argv[1];
foreach ([1e4, 5e3, 2e3, 1e3, 500, 100, 50, 10, 5, 1] as $x)
  for (; $n >= $x; $n -= $x)
    $r[] = $x;
echo implode(', ', $r ?? []);

?>]

使用したテクニック

指数表記

割と多くの言語のゴルフで使えるテクニック。e を用いた指数表記で、大きな数を短く表す。このコードでは 10000500020001000 を指数表記している。

foreach や for の中身を1つの文に

foreachforif などの後ろには、通常 { を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、{} を省略できる。C言語などでも使える。

$r に初期値を入れない

PHP では、$r[] = ... のような配列の末尾に追加する式を実行したとき、$r が未定義だった場合は $r を勝手に定義して空の配列で初期化してくれる。これを利用すると、$r = []; のような初期化が不要になる。

ただし、プログラムに 0 が渡されるとループを一度も回らないので、$r が未定義になってしまい、implode() に渡すところでエラーになる。それを防ぐために $r ?? [] を使っている。

もし 0 が渡されたケースを無視するなら、これが不要になるので 4 バイト縮む。

PHP タグの外に文字列を置く

PHP では、<?php ?> で囲われた部分の外側にある文字列は、そのまま出力される。今回のケースでは、先頭と末尾に必ず [] を出力するので、そのまま書いてやればよい。

おわりに

最後になりましたが、めもりー さん、楽しい問題をありがとうございました。

]]>
PHPerKaigi 2022 https://blog.nsfisis.dev/posts/2022-05-01/phperkaigi-2022/ Sun, 01 May 2022 09:41:39 +0900 https://blog.nsfisis.dev/posts/2022-05-01/phperkaigi-2022/ はじめに

2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に、一般参加者として参加した。 弊社デジタルサーカス株式会社 はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。

昨年のレポートはこちら

感想

厳選おすすめトーク

多くの素晴らしいトークの中から、特におすすめのものを 5つ選んだ。是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。

予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント

PHP はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。

本講演では PHP 8.1 をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。

PHPのエラーを理解して適切なエラーハンドリングを学ぼう

PHPを使ってるとよく遭遇する Fatal error / Parse error / Warning / Notice 理解していますか?
これらのエラー文を理解することで、すぐにエラーの原因に気付き適切に対象できる様になります!
またそれらを理解した上でのエラーハンドリングを学びましょう。

エラー監視とテスト体制への改善作戦

毎日流れてくるエラーに皆さんはどう向き合ってますか?
エラーを出さない事が一番ですが、完全に塞ぐ事は難しいと考えます。
サービス運用の中で本番環境から発生するエラー(サーバー・クライアントサイド・サードパーティ起因のエラー)への監視体制と、
エラー・バグ防御のためチームで行っているテストコード文化づくりの話をします。

ISUCON11のPHP実装は、何を考え、どのようにして作られていたのか

昨年開催されたISUCON11にて問題(参考実装)のPHPへの移植を担当させていただきました。

最終的なソースコードこそシンプルなWebアプリケーションではありますが、その裏には
・「(私の思う)良い設計」を実現するための意思決定
・「ISUCONの問題」という位置付けに由来する取捨選択
・移植中に遭遇したトラブルとその解決策
といった文脈や葛藤が存在しています。

本発表はそれらを共有することで
・PHPアプリケーションの設計、実装事例として役立ててもらう
・ISUCONの言語移植に興味を持ってもらう
・ISUCON問題移植の「実装や設計の練習をする教材」としての可能性を知ってもらう
ことを目的とします。

チームの仕事はまわっていたけど、メンバーはそれぞれモヤモヤを抱えていた話──40名の大規模開発チームで1on1ログを公開してみた

サイボウズの大企業向けグループウェアのGaroon(ガルーン)は、PHPで開発されている20年目の製品です。ガルーン開発チームは日本で40名、ベトナムで50名の計90名ほどのチームになっています。また、コロナ禍でフルリモートでの活動がこの2年ほど継続してきました。

フルリモートになっても仕事はまわっており、継続的にリリースはしていましたが、一方でお互いの考えていることや感じている問題意識が見えづらくなり、モヤモヤを抱えているメンバーが増えていました。

このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。

トークン問題の作成

今回は、PHPer チャレンジ用に弊社のトークン問題を 3題作成した。こちらについては別途記事にしているので、そちらを参照されたい。

PHPer チャレンジ

1位になった。
また、賞品として Echo Show 15 をいただいた。

カンファレンス全体への感想

去年の参加レポ では、こんなことを書いた。

1つ個人的な反省点としては、(中略) Discord しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。
まあ初カンファレンスだし、とお茶を濁しておこう。

この反省を踏まえ、今年は積極的にほかの場 (公式の Discord サーバや、アンカンファレンス) にも参加した。
これにより、参加体験の質がはるかに向上した。特に Discord に関しては、登壇者ご本人による補足や、質問への回答などがおこなわれる (ことが多い) ため、特別な理由のない限り、発言はしないまでも参加はしておいたほうが良いと思われる。

なお、アンカンファレンスについては、1日目の終わりにトークン問題の解説放送もおこなった。

また、今年はオフラインとオンラインのハイブリッド開催であったが、去年の全オンラインと比べて、オンライン参加の体験が落ちていなかったのは、特筆すべきであろう。 今年は 3回目のワクチン接種が間に合わなかったこともあり現地参加は見送ったのだが、来年は是非オフラインで参加したい。

そして来年へ……?

PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の 4つを目標としたい。

  • プロポーザルを出す
  • PHPer チャレンジのトークン問題を 5題作成する
  • 現地に行く
  • PHPer チャレンジで圧勝する

最後になりましたが、PHPerKaigi のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。

ではまた来年。

]]>
PHPerKaigi 2022 トークン問題の解説 https://blog.nsfisis.dev/posts/2022-04-09/phperkaigi-2022-tokens/ Sat, 09 Apr 2022 21:50:19 +0900 https://blog.nsfisis.dev/posts/2022-04-09/phperkaigi-2022-tokens/ はじめに

本日開始された PHPerKaigi 2022 の PHPer チャレンジにおいて、弊社 デジタルサーカス株式会社 の問題を 3問作成した。この記事では、これらの問題の解説をおこなう。

リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens

第1問 brainf_ck.php

ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。

<?php

declare(strict_types=0O1);

namespace Dgcircus\PHPerKaigi\Y2022;

/**
 * @todo
 * Run this program to acquire a PHPer token.
 */

https://creativecommons.org/publicdomain/zero/1.0/

\error_reporting(~+!'We are hiring!');

$z = fn($f) => (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
$id = \spl_object_id(...);
$put = fn($c) => \printf('%c', $c);
$mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));

$👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
$👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
$👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
$👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
$📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
$🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
    +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
      $b => $loop(++$pc, ++$n),
      $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
      default => $loop(++$pc, $n),
    })($pc, -![])],
    default => [$mp, ++$pc],
};
$🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
    +!![] => [$mp, ++$pc],
    default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
      $e => $loop(--$pc, ++$n),
      $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
      default => $loop(--$pc, $n),
    })($pc, -![])],
};
$🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
  isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
)($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);

$🐘([
  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
  $🤡,
    $👉, $👍, $👍, $👍,
    $👉, $👍, $👍, $👍, $👍, $👍,
    $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
    $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
    $👈, $👈, $👈, $👈, $👎,
  $🎪,
  $👉, $👍, $👍, $👍, $👍, $👍, $📝,
  $👎, $👎, $📝,
  $👉, $👎, $👎, $👎, $📝,
  $👉, $👎, $👎, $👎, $📝,
  $👎, $👎, $📝,
  $👎, $📝,
  $👈, $📝,
  $👉, $👉, $👎, $👎, $📝,
  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
  $👈, $👎, $👎, $👎, $👎, $📝,
  $👈, $📝,
  $👉, $👍, $👍, $📝,
  $👉, $👎, $📝,
  $👈, $📝,
]);

この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。

解説

絵文字

まず目につくのは大量の絵文字だろう。 PHP は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。

プログラム全体

Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck とは、難解プログラミング言語のひとつであり、ここで説明するよりも Wikipedia の該当ページを読んだ方がよい。

https://ja.wikipedia.org/wiki/Brainfuck

なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。

+ + + + + + + + + +
[
  > + + +
  > + + + + +
  > + + + + + + + + + + + +
  > + + + + + + + + + +
  < < < < -
]
> + + + + + .
- - .
> - - - .
> - - - .
- - .
- .
< .
> > - - .
+ + + + + + + .
< - - - - .
< .
> + + .
> - .
< .

実行結果はこちら: https://ideone.com/22VWmb

それぞれの絵文字で表された関数が、各命令に対応している。

  • $👉: >
  • $👈: <
  • $👍: +
  • $👎: -
  • $📝: .
  • $🤡: [
  • $🎪: ]

, (入力) に対応する関数はない (このプログラムでは使わないので用意していない)。

なお、$🐘 はいわゆる main 関数であり、プログラムの実行部分である。

絵文字の選択

おおよそ意味に合致するよう選んでいるが、$🤡$🎪 は弊社デジタルサーカスにちなんでいる。 また、$🐘 は PHP のマスコットの象に由来する。

strict_types

declare 文の strict_types に指定できるのは、01 の数値リテラルだが、 0x00b1 のような値も受け付ける。 今回は、PHP 8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。

URL

ソースコードのライセンスを示したこの部分だが、

https://creativecommons.org/publicdomain/zero/1.0/

完全に合法な PHP のコードである。 https: 部分はラベル、// 以降は行コメントになっている。

リテラルなしで数値を生成する

ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 PHP では、型変換を利用することで任意の整数を作り出すことができる。

assert(0 === +!![]);
assert(1 === +![]);
assert(2 === ![]+![]);
assert(3 === ![]+![]+![]);
assert(10 === +(![].+!![]));

[]! を適用すると true が返ってくる。それに + を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int への型変換が走り、10 が生まれる (コード量に頓着しないなら、1 を 10 個足し合わせてももちろん 10 が作れる)。

また、error_reporting に指定しているのは -1 である。 これは、! によって文字列を false にし、+ によって false0 にし、さらにビット反転して -1 にしている。

if 文なしで条件分岐

三項演算子ないし match 式を使うことで、if を一切書かずに条件分岐ができる。 また、&& / || も使えることがある。 遅延評価が不要なケースでは、[$t, $f][$cond] のような形で分岐することもできる。

whilefor 文なしでループ

不動点コンビネータを使って無名再帰する (詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に Z コンビネータとして知られるものを使った ($z)。

実際のところ、$🤡$🎪$🐘 は、一度 Scheme (Lisp の一種) で書いてから PHP に翻訳する形で記述した。

なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) ので、 あまりに長い brainf*ck プログラムを書くとスタックオーバーフローする。

第2問 riddle.php

ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。

<?php

/*********************************************************
 * This program displays a PHPer token.                  *
 * Guess 'N'.                                            *
 *                                                       *
 * Hints:                                                *
 * - N itself has no special meaning, e.g., 42, 8128,    *
 *   it is selected at random.                           *
 * - Each element of $token represents a single letter.  *
 * - One letter consists of 5x5 cells.                   *
 * - Remember, the output is a complete PHPer token.     *
 *                                                       *
 * License:                                              *
 *   https://creativecommons.org/publicdomain/zero/1.0/  *
 *********************************************************/
const N = 0 /* Change it to your answer. */;
assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);

$token = [
  0x14B499C,
  0x0BE34CC, 0x01C9C69,
  0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69,
  0x01C1C66, 0x0FC1C47, 0x01C1C66,
  0x10C5858, 0x1E4E3B8, 0x1A2F2F8,
];
foreach ($token as $x) {
  $x = $x ^ N;

  $x = sprintf('%025b', $x);
  $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
  $x = implode("\n", str_split($x, length: 5));
  echo "{$x}\n\n";
}

さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 トークンを得るためには、ソースコードを読み、定数 N を特定する必要がある。

ここでは、私の想定解を解説する。

読解

まずはソースコードを読んでいく。

$token = [
  // 略
];

数値からなる $token があり、各要素をループしている。

  $x = $x ^ N;

まずは排他的論理和 (xor) を取り、

  $x = sprintf('%025b', $x);

二進数に変換して、

  $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);

0 を空白に、1 を # にし、

  $x = implode("\n", str_split($x, length: 5));

5文字ごとに区切ったあと、改行で結合している。

ヒント

次に、ソースコードに書いてあるヒントを読んでいく。

  • N それ自体は、42 や 8128 といったような特別な意味を持たず、ランダムに決められている
  • $token の各要素は、1文字を表す
  • 1文字は 5x5 のセルからなる
  • 出力されるのは、完全な PHPer トークンである

ここで、PHPer トークンは必ず # 記号から始まることを思いだすと、 $token の最初の数字 0x14B499C は、変換の結果 # になるのではないかと予想される (なお、このことは、リポジトリの README ファイルに追加ヒントとして書かれている)。

解く

ここまでわかれば、あと一歩で解ける。すなわち、0x14B499C# に変換されるような N を見つければよい。

N は高々

assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);

なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。

<?php

$x = 0x14B499C;

$x = $x ^ N;

$x = sprintf('%025b', $x);
$x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
$x = implode("\n", str_split($x, length: 5));

assert($x ===
  " # # \n" .
  "#####\n" .
  " # # \n" .
  "#####\n" .
  " # # ");

この一連の変換に対する逆変換を考えると、次のようになる。

<?php

$x =
  " # # \n" .
  "#####\n" .
  " # # \n" .
  "#####\n" .
  " # # ";

$x = implode('', explode("\n", $x));
$x = str_replace(search: [' ', '#'], replace: ['0', '1'], subject: $x);
$x = bindec($x);

$n = $x ^ 0x14B499C;

echo "N = $n\n";

これを実行すると、N が得られる。

第3問 toquine.php

ソースコードはこちら。

<?php

// License: https://creativecommons.org/publicdomain/zero/1.0/
// This is a quine-like program to generate a PHPer token.
// Execute it like this: php toquine.php | php | php | php | ...

$s = <<<'Q'
<?cuc
// Yvprafr: uggcf://perngvirpbzzbaf.bet/choyvpqbznva/mreb/1.0/
// Guvf vf n dhvar-yvxr cebtenz gb trarengr n CUCre gbxra.
// Rkrphgr vg yvxr guvf: cuc gbdhvar.cuc | cuc | cuc | cuc | ...
%f$f = %f;
$f = fge_ebg13($f); $kf = [
%f,
];
$g = ahyy.snyfr; sbe ($v = 0; $v <= vagqvi(__YVAR__-035,6); ++$v) vs (!vffrg($kf[$v])) oernx; ryfr
$g .= vzcybqr("\a", fge_fcyvg(fge_ercynpr(['0','1'], ['  ','##'], fcevags(pue(37) . '025o', $kf[$v])), 012)) . "\a\a";
$jf = neenl_znc(sa($j) => vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10));
cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf));
Q;
$s = str_rot13($s); $xs = [
0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7,
0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F,
0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7,
];
$t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else
$t .= implode("\n", str_split(str_replace(['0','1'], ['  ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n";
$ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10));
printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));

コメントにもあるとおり、次のようにして実行すれば答えがでてくる。

$ php toquine.php | php | php | php | ...

実際にはもう少しパイプで繋げなければならない。

解説

プログラム全体

コメントにもあるとおり、これは quine (風) のプログラムになっている。 Quine とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。

このプログラムは、実行すると自身とほとんど同じプログラムを出力する。 異なるのはトークンになっている部分のみである。

トークン

$xs がトークンに対応している。変換のロジックは riddle.php とほぼ同じなので省略する。

状態保持

トークンの何文字目まで出力したかを、ソースコードを変えずに (quine なので) 覚えておく必要がある。 このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、__LINE__ から情報を取得している。

ROT 13

Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。 これがあまり美しくないので、toquine.php では、ROT 13 変換を使って難読化した。

それにしてもなぜこんなものが標準ライブラリに……。

おわりに

解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。

今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、 来年は 5問、より面白い問題を持っていきます。

実はもう作りはじめているので、どうか来年もありますように……。

]]>
PHPerKaigi 2021 https://blog.nsfisis.dev/posts/2021-03-30/phperkaigi-2021/ Tue, 30 Mar 2021 23:22:40 +0900 https://blog.nsfisis.dev/posts/2021-03-30/phperkaigi-2021/ PHPerKaigi 2021 参加レポ

2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に一般参加者として参加した。 弊社デジタルサーカス株式会社 (今年1月から勤務) はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。

このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。

発表はトラック A、B に分かれていたのだが、今回はすべて A トラックを視聴している (切り替えるのが面倒だっただけ)。

凡例

発表・スライドのメモ (引用ではない)

感想など

Day 0 前夜祭 (2021/03/27)

17:30 [A]

PHP で AWS Lambda

Rails のプロジェクトを PHPer のメンバのみでメンテ →他のメンバもわかる PHP にリプレースを検討

サーバレス

  • サーバ・インフラの管理が不要
  • アプリケーションコードの知識だけで保守可能

ゼロベースで作れる案件が (Railsの件とは別に) あるため、そちらで試験的に導入?

AWSの学習 AWS のドキュメント DevelopersIO

AWS Lambda のカスタムランタイムで PHP を動かす

サーバのセットアップや維持管理を気にしなくて良い サーバーレスで PHP を動かすツールがすでにある サーバーレス構築はすんなり

今は Laravel がルーティングしている Laravel Livewire を Lambda に載せられないか? デプロイ方法は? バッチ処理は? (Lambda は 15分の制限)

Lambda でコンテナイメージがサポートされるように

抽象化されたもの「だけ」しか知らないよりも具象の理解は助けになる

AWS Lambda のような Function as a Service はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に web サービスを作る具体的なイメージがまだ見えない (注: すべて for me として書いている)。

PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。

勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP + Laravel などでは動かなさそう) だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。

18:10 [A]

大規模サイトの SEO

大規模サイト (100万ページ以上) Google の基準

クロールバジェットを意識したSEO

大規模サイトでコンテンツが中頻度 (1回/週) で更新 OR 中規模サイト (10,000以上) でコンテンツが目まぐるしく変更される これを満たさないなら、クロールバジェットを考えなくてもいい

サーチコンソール 「カバレッジ」の「除外」 多すぎるのは問題→クロールバジェットを浪費している

  • クエリの順番を決める
  • 空の値のルールを決めておく
  • リダイレクトすればインデックスはうまくいく
  • リンクが存在する限りクロールはされる

リニューアル前のURL

インデックスは移行される リンクのURLが存在する限り、別のURLとしてクロールされる リダイレクトされるとはいえ、リニューアル前のURLは移行した方が良い リニューアルで無視されるようになったパラメータも注意

robotes.txt で拒否しているのにクロールされる 一時的に拒否を外して 404 や 301 を読ませる 内部リンクを確認する JS でのリンクに書き換え

クエリパラメータからURLのパスに /tokyo?area=HOGE/tokyo/HOGE

URL 設計だいじ

SEO (Search Engine Optimization) は大して知らないので新鮮な話が多かった。その分語れることも少ない……。

18:50 [A]

知覚可能 操作可能 理解可能 堅牢 ちゃんとしたHTMLを書く (閉じタグ・入れ子構造など)

  • 標準の HTML を適切に使う

  • WAI-ARIA

  • キーボードフレンドリー

  • マシンフレンドリー

  • SEOフレンドリー

button タグ →キーボード h1 タグ →スクリーンリーダー・クローラ a タグ

WAI-ARIA HTML では表現できないセマンティクスを追加する

  • ロール
    • 何をするのか?
    • ユーザーアクションによって変化しない
  • プロパティ
    • 関連づけられたデータ
  • ステート
    • 現在の状態

まずは標準の HTML 要素で解決する 何でもかんでも WAI-ARIA を使えばいいというものではない

マウスホバーでツールチップが出てくるが、キーボード操作では出てこない

VoiceOver

全ての属性を使う必要はない あくまでアクセシビリティを上げるための方法の一つにすぎない

つい最近 WAI-ARIA についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが) いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。

19:30 [A]

PHP で FUSE

個人的に楽しみだった発表。

VFS (virtual filesystem) vs 具体的なファイルシステム

最適な実装方法は状況により異なる

アプリケーションに見せるAPIは変えずに実装を隠蔽する→VFS

カーネルのプログラムを作るのは難しい

  • 権限がデカすぎる
  • システム全体がクラッシュ
  • セキュリティリスク
  • 開発サイクルを回しづらい
  • ネイティブコードにコンパイルされる言語である必要がある

Filesystem in USEr space (FUSE)

  • 特定の C の関数を呼ぶことで filesystem が作れる
  • FFI を持つ言語なら FUSE が使える

SSHFS / s3fs / Docker Desktop

Linux 以外でも使える

  • dokany (on Windows)
  • osxfuse

VFS: システムコールが呼ばれると、ファイルシステムによってコール FUSE: カーネル空間からユーザ空間へ通信

高レベルなラッパで型をつける

PHP 以外では Wordpress を FUSE にマウントする実装がある (C, Python など)

  • grep できる
  • sed できる
  • 編集できる

期待通りの興味深い発表だった。FUSE 自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。 この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ (ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。

Day 1 (2021/03/27)

10:50 [A]

ATDD

  • ユーザーストーリー
  • ユニットテスト
  • CI/CD

ユーザストーリーの受け入れ条件が曖昧になりがち デグレチェックがユニットレベルでは収まらない場合、手動で同じシナリオをテストしている

Q2の強化 アジャイルテストの4象限

技術面/ビジネス面 開発チーム支援(コーディング前・コーディング中)/製品批評(コーディング後)

  • Q1: 技術面 & チーム支援
    • TDD
    • ユニットテストなど
  • Q2: ビジネス面 & チーム支援
    • ATDD
    • ビジネス面の受け入れテストで駆動する

Agile Alliance ユーザストーリーのスキルレベルを高める

テストピラミッド

  • UI Tests

  • Service Tests

  • Unit Tests

  • 異なる粒度のテストを書く

  • 高レベルになるほど、持つべきテストは少なくなる

    • ピラミッド型になる

高レベルテストが多すぎる→アイスクリームコーン アンチパターン

ATDD (Acceptance TDD) API経由・UI経由での高レベルテスト E2E test

ストーリ受け入れテスト

入れ子のフィードバックループ ATDD(外側) と TDD(内側)

外部品質・内部品質

バーティカルスライスのデリバリー

  • cucumber
  • gauge
  • behat

ユビキタス言語 手動テストもspecに書く 自動化は可能だがコスパが悪い 失敗することがわかっているテスト(レッドテスト)はCIから外す 失敗時の原因究明が難しい 饒舌なエラーメッセージ 状況のスナップショット

Continuous Testing

User Acceptance Test (UAT) くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。 高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。

11:50 [A]

型解析を用いたリファクタリング

型のある世界で生きてきた身として大いに楽しみにしていた発表。

  • PHPStan
  • Phan
  • Psalm

autoload も認識できる bootstrapFiles

編集箇所と利用箇所を CI でチェック ルールレベルを徐々に引き上げていく 警告が多すぎると見落としてしまう・無視されやすくなる

型がついていないことによるエラーが多い

型よりも詳細な検査 Util_Assert::min

SQL を静的解析 placeholder の型付け

警告レベルを低いレベルから導入 タイプヒントを積極的に書いていく PHPStan の拡張を追加する

昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。 今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で Ruby の typeprof には注目している。

12:30 [A]

昼食をとっていた。事前に何か食料を買っておくべきだった。

13:10 [A]

Documentation as Code

この発表も以前から非常に楽しみにしていた。

開発開始までのオーバーヘッド 新規にチームにジョイン 担当範囲外の機能を理解 オンボーディングのコスト

PHPerKaigi 2020 で発表あり

継続的にシステムの理解を助けるドキュメント

継続的ドキュメンテーション システムとドキュメントの乖離

書いてあることが間違っている・足りない

  • 徐々にずれていく
  • システムの更新タイミングとドキュメントの更新タイミングに差がある

システムとドキュメントは対応関係がある

  • 間違ったドキュメント
  • 存在しないドキュメント

システムとドキュメントの乖離を定量化する 継続的に システムの更新に近いタイミングで ドキュメントを更新し続ける

Documentation as Code

コードと同じツールでドキュメントを書く

  • issue tracker
  • vcs
  • plain text markup
  • automation

開発者 システム ドキュメント 構造化データ ソフトウェア

システムから構造化データを抽出する PHPDoc OpenAPI

ビュー 関心ごとに合わせてアーキテクチャを一つ以上の側面(断面)で説明する

ビューの単位でドキュメントに

スタックトレースからのドキュメント生成

ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な (乖離しない) 情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。

14:10 [A]

cookie による session 管理

全体的に基本的な話だったので特に触れない。Cookie やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。

14:50 [A]

PHP のエラーと例外

エラー PHPエンジンがエラーを通知する 例外 プログラムが投げる

PHP7-8とエラー

PHPエンジンのエラーの一部が \Error に変換されるようになった → try-catch で捕捉できる

\Error は例外とは異なる

PHP8 でエラーレベルの引き上げ

  • 捕捉すべきもの
    • recoverable
  • 捕捉すべきでないもの
    • unrecoverable
    • 開発時に対処できるもの

例外

  • 捕捉して事後処理
  • 捕捉せず(or 捕捉した上で)さらに上に是非を問う

開発段階で例外を把握し、ハンドリングを考えておく

\Throwable \Exception と \Error

\Error はキャッチすべきでない

  • \Error

    • 本番で起きてはいけない
  • \LogicException

    • 本番で起きてはいけない →生じないのだから捕捉もしない
  • \RuntimeException

    • 起こるかもしれないので本番環境でも考慮する

捕捉して対応するのではなく、未然に防ぐ

独自例外を使う \Exception を投げてしまうと、 catch (\Exception)せざるを得ない →catch 範囲が広すぎる

SPL の例外を使う

例外翻訳 上位のレイヤが下位のレイヤの例外を捕捉し、上位レイヤのAPIに「翻訳」する 下位レイヤの知識に依存させない

@throws 捕捉してほしい例外を書き連ねておく

呼び出しもとに負わせたい責任

PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で PHP を書き始めてから 4ヶ月ほどになる)。

個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell などのエラーを「値として」扱う言語だと思っている。try-catch は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる (C のそれはまともな型付けではない。念のため)。

PHP のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。

15:30 [A]

Laravel のメール認証

Laravel の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。

16:10 [A]

gRPC

Unary RPCs Server streaming RPCs Client streaming RPCs Bidirectional streaming RPCs

Protobuf

実装とAPIが乖離しにくい 自動生成 複数言語でも相互に使える

マイクロサービスのサービス通信 スマホアプリ ゲームサーバ

PHP では?

PHP ではストリーミングが難しい リクエストごとにプロセスが使い捨て

PHP ではgRPCのクライアントしか対応していない

gRPC-Web ブラウザで扱うためのJSライブラリ+プロトコル

HTTP/1.1 でも使える Unary RPC と Server streaming RPC のみ

Envoy Nginx などで相互に gRPC と gRPC-Web で変換

Amp イベント駆動な並行処理のフレームワーク

HTTP/2 対応

C#のgRPC-Webが楽

(発表の中でもまさに同じことをおっしゃっていたが) PHP 以外の方が向いているだろう、というのが第一の感想である。gRPC はそれ自体というよりも Protobuf というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。

16:50 [A]

アーキテクチャテスト

Independent Core Layer Pattern

開発初期のアーキテクチャが崩れる アーキテクチャ観点のコードレビューができない

どこにクラスを置けばよいか? ドキュメントがない

アーキテクチャ設計に関する知識が属人化・暗黙知化

ガイドライン

  • 最初にルールを決めるのは簡単
  • ルール通り作り始めるのも簡単
    • →維持するのが難しい、人が決めたものゆえ壊れやすい

PHP の特性

  • クラスは public
  • 可視性の制御が public / protected / private のみ
  • 依存関係の制御が困難

アーキテクチャテスト クラスの依存関係や実装ルールをコードとして表現し、自動テスト化する

  • deptrac
  • phpat

Independent Core Layer Pattern

アーキテクチャテストの失敗

  • 実装誤り
  • or アーキテクチャが適切でない
    • 開発の過程でフィードバックしていく

モジュラーモノリス→マイクロサービスへ

Day 2 (2021/03/28)

冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。

残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。

全体の感想

Day 2 にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも (特に初参加者として) 嬉しいポイントだった。

今回、雑談/登壇者への質問等向けに Discord サーバもあったのだが、こちらは参加こそしたものの ROM のままになってしまった。発表に1ウィンドウ、メモを書くのに1ウィンドウ、Discord 表示に 1ウィンドウで私にはもう脳のリソースとディスプレイのスペースが追いつかなかった (さらにいうと Zoom でアンカンファレンスもやっていたようだ。こちらはまったく参加していない)。

1つ個人的な反省点としては、一つ一つのセッションを真剣に聞き過ぎたというものがある。もっと適当に聞いておけばよかった。これだけだと大変語弊があるのだが、言い方を変えると、Discord しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。 まあ初カンファレンスだし、とお茶を濁しておこう。

さて、カンファレンスで一つ気になったことがある。それは、Discord という書き込み場所が増えたことでニコ生のコメントの流量が吸い取られてしまったのではないか、という点だ。ニコニコだけ見ていると過疎っているかのように見えた発表も、Discord の方では盛り上がっている、というのを何度か見かけた。ニコニコのコメント方式は盛り上がりを如実に反映するが、逆もまたしかり。Discord があったこと自体はプラスだったと思うが、この点はマイナスだったのではないかと感じる。


最後になりましたが、毎年の PHPerKaigi 開催にご尽力されている皆様、スピーカーの皆様、楽しい3日間でした。ありがとうございました! (ずっと常体で書いてしまったのでいきなり仏頂面から笑顔になったようで気持ち悪い)

ではまた来年。

]]>