adobeのスーパー解像度で刑事ドラマのやつの検証

転載元zenn.dev


 
刑事ドラマでよくある、なんとか犯人らしき人物を監視カメラで捉えたんだけど、どうしようもないボケボケの解像度で、サイバー対策室的なのに所属しているハッカーポジションの若い人材が「お待ちください。ッターーン」のワンクリで解像度上げて犯人の顔割り出すやつ。

現実的に詳細な画像=存在しない情報なので無理だろっていつも思ってたんですけど、adobeが現実のものにしたようです。

その名も"スーパー解像度"
わかりやすくていいですね。

blog.adobe.com

実際に使ってみて、ほんとに刑事ドラマみたいなのできるのか検証してみました。

やっていくぞ!

まずはraw画像を用意します。
(この時点で違和感覚えた方は目を瞑って未来に期待してください)

f:id:ezm_u:20210422122218j:plain
コロナ禍における閑散とした雑多な駅前商店街。趣があります

ベランダから撮りました。
住んでるとこバレそうですけど男の一人暮らしなので問題ありません。

写真が準備できたら最新版のPhotoshopで開きます。
raw画像は自動でCamera Rawが立ち上がるので、画像を右クリックして"強化"を選択します。
ダイアログが開くので、完了時間など確認しつつ"強化"しましょう。

f:id:ezm_u:20210422122340p:plain

f:id:ezm_u:20210422122357p:plain
のアクションボタンは普通のデザイナーなら"実行"とかにしそうですが、"強化"ってすごい。ゲームとかじゃなくて、現実世界でボタンひとつで何かを強化したことある人いますか?

強化完了!スーパー解像度!

強化した画像がこちらです。
このサイズだと全然わからないですね。
f:id:ezm_u:20210422122514j:plain
車のナンバープレートにフォーカスしてみましょう。
刑事ドラマ感がでてきました。

まずは元の解像度の画像から。
左上の漢字っぽいの不明瞭で読めないですね。

f:id:ezm_u:20210422122546j:plain
強化前の画像です。全然知らない人の車なのでナンバーの一部を隠しています

これをスーパー解像度で強化すると...

.

.

.

.

.

.

.

.

.

.

.

.

板橋!

なんとか読める状態に。
普通にすごい。どういう仕組みか全然わかりません。

f:id:ezm_u:20210422122651j:plain
強化後の画像です。全然知らない人の車なのでナンバーの一部を隠しています

Gifアニも用意したのでスーパー解像度による強化をお楽しみください。
f:id:ezm_u:20210422122722g:plain
以上です。

Supervisor を実際に操作してデーモン化に触れてみる

この記事は個人ブログと同じ内容です

www.ritolab.com


プロセス制御システム である Supervisor を実際に動かしてみて、彼のことを理解しようとしてみました。

Supervisor(スーパーバイザー)

Supervisor

supervisord.org

スーパーバイザーは、ユーザーがUNIXライクなオペレーティングシステム上の多数のプロセスを監視および制御できるようにするクライアント/サーバーシステムです。

プロセスを監視して、落ちたら起動し直してくれたりするツール。

Supervisor を起動すると、設定している管理対象も一緒に起動してくれる。

実行環境

linux 環境があれば良いので Docker で適当に作成

Dockerfile

FROM amazonlinux:latest

# EPEL インストール
RUN amazon-linux-extras install -y epel

# supervisord インストール
RUN yum install -y supervisor

# 設定ファイル設置
COPY nginx.ini /etc/supervisord.d/nginx.ini

イメージをビルドして

# イメージをビルド
docker build -t amazon_linux_20210417 .

コンテナを起動する

# コンテナ起動
docker run -d --privileged amazon_linux_20210417 /sbin/init

# コンテナ ID 出力
-> e17xxxxxxxxxxxxxxxxxxxxxxxx

出力されたコンテナ ID を指定してコンテナの中に入る

# コンテナの中に入る
docker exec -it e17 /bin/bash

Supervisor の起動完了

# バージョン確認
$ supervisorctl version
3.4.0

管理対象を設定する

nginx を管理対象にして操作していく。

nginx.ini

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true

.ini じゃなくて .conf が良ければ、supervisord.conf の最下部に include があるのでそこを変更する。

/etc/supervisord.conf

[include]
files = supervisord.d/*.ini

supervisorctl コマンド

スーパーバイザーを操作するには supervisorctl コマンドを使っていく。

Running supervisorctl - Supervisor 公式

http://supervisord.org/running.html#supervisorctl-command-line-options

管理対象のプロセスの状態を確認する

supervisorctl status ... で管理対象のプロセスの状態を確認できる

status <name>      単一プロセスのステータスを取得する
status <gname>:*   グループ内のすべてのプロセスのステータスを取得する
status <name> <name>   複数の名前付きプロセスのステータスを取得する
status       すべてのプロセスステータス情報を取得する

nginx の状態を確認してみます。

$ supervisorctl status
nginx                            RUNNING   pid 136, uptime 0:34:04

RUNNING しっかり動いてる。

管理対象を再起動する

supervisorctl restart ... でプロセスの再起動ができる

restart <name>     プロセスを再起動します
restart <gname>:*  グループ内のすべてのプロセスを再起動します
restart <name> <name>  複数のプロセスまたはグループを再起動します

nginx を再起動してみます。

# 再起動を実行
$ supervisorctl restart nginx
nginx: stopped
nginx: started

# ログ
2021-04-17 07:12:27,752 INFO waiting for nginx to stop
2021-04-17 07:12:27,767 INFO stopped: nginx (exit status 0)
2021-04-17 07:12:27,771 INFO spawned: 'nginx' with pid 152
2021-04-17 07:12:37,814 INFO success: nginx entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)

再起動されました。

プロセスを落としてみる

プロセスを何度か落としてみましたが、何度でも這い上がってくる感じがさすがデーモン化といったところ。

f:id:ro9rito:20210419201222p:plain

これがスーパーバイザーの力か。恐るべしデーモン閣下

設定を変更して反映する

nginx の管理設定項目を変更してそれを適用させてみます。

設定ファイルを変更しただけでは適応されないので反映してあげる必要がある。

/etc/supervisord.d/nginx.ini

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true

# 起動後設定値(秒)より早くプロセスが終了した場合は起動失敗とする
startsecs=10

# 起動失敗時のリトライ回数。この回数を超えると FATAL 状態となり起動施行をストップする
startretries=2

下の 2 を新たに設定値に加えました。

startsecs 起動後設定値(秒)より早くプロセスが終了した場合は起動失敗とする startretries 起動失敗時のリトライ回数。この回数を超えるとステータスが FATAL になって起動をストップする Section Settings http://supervisord.org/configuration.html#program-x-section-settings

変更を反映するには update を使う。プロセスが再起動されて新しい設定値も読み込まれる。

$ supervisorctl update nginx
nginx: stopped
nginx: updated process group

一度 remove してから再度 add する

nginx を Supervisor の管理対象から外したのち、再度管理対象にしてみます。

stop でプロセスを止めます。

$ supervisorctl stop nginx
nginx: stopped

remove で管理対象から外します。

$ supervisorctl remove nginx
nginx: removed process group

この状態から start でプロセスを開始させようとすると、管理対象から外れているのでエラーになります。

$ supervisorctl start nginx
nginx: ERROR (no such process)

再度管理対象とするには add を使います。

$ supervisorctl add nginx
nginx: added process group

再度 nginx が起動します。

ちなみに、remove していようが Supervisor 自体を再起動したら起動します。

ステータスが FATAL になったものを再度起動させる

起動に失敗して FATAL になった管理対象ってどうやって復帰させるんだろう。という事で確認。

現在、FATAL の状態

$ supervisorctl status
nginx                            FATAL     Exited too quickly (process log may have details)

手動で起動させるには start で行う

start <name>       プロセスを開始します
start <gname>:*       グループ内のすべてのプロセスを開始します
start <name> <name>    複数のプロセスまたはグループを開始する
start all         すべてのプロセスを開始します

nginx を起動してみます。

$ supervisorctl start nginx
nginx: started

FATAL ステータスの nginx を起動できました。

まとめ

Supervisor 実際に触ってみたら操作はとてもシンプルでした。

supervisorctl コマンドは add/start とか、reload/reread/update みたいな、シンプルゆえに雰囲気で触ってると意外とどれがどういう動きをするのかわからないなと思ったので、実際に触ってみて確認して理解が深まったことは収穫でした。

みんながきっと1万回は聞いている、VS Code Remoteでコンテナ開発をやる方法

この記事はzenn.devで書いたやつなんで、そっちの方も見てやってくださいな。

zenn.dev


こんにちは皆さん。

いやね、皆さんこう言いますよ。 「それもう何番煎じやねん!前も聞いたわ!」 ってね。

でも、こうも思うじゃないですか。 「たくさん記事があるってことは、ひょっとしてめっちゃくちゃ大事なことなのでは?」 とね。

とういうわけで、毎回まるで前提のごとく使っていたVS Code Remote Containerによるコンテナ開発について、自分が使っているやり方について簡潔に書いてみます。 マニュアルなぞっても面白くないですしね。

VS Code Remote

VSCではリモート環境に入ってエディタを起動することができます。 イメージ的にはこんな感じで、リモート環境上で立ち上げたエディタを、外部から遠隔操作するようなイメージですね。 VSC Remote VS Code Remote自体は随分前からあったのですが、WindowsのDocker開発の環境が整うまでに結構かかったのですよね。 私のプライベートマシンがWindowsであることもあって、完全にRemote Containersができるようになったときは、無駄に感動したものです。

このエクステンションは、このリモート環境をどこに用意するかで、3つに分かれています。 普通の外部サーバで実行しSSHでアクセスするもの、WindowsのWSL上で実行するもの、そして、立ち上げたコンテナ上で実行するものです。 今回解説するのは、コンテナにアクセスするものです。

VS Code Remote Containers

VSCをコンテナ上で実行できるパッケージですが、その実行方法がいくつかあります。各々について解説していきます。

パッケージインストール

何をやるにもVSCの Remote Containerが必要です。さっさと入れちゃいましょう。 https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers 入れるのは一瞬ですが、前提として、docker for mac もしくは docker for windowsが必要です。 私の環境ではdocker for windows の WSL2ホスティングを使っています。

attatch container

まずは手っ取り早くコンテナ上でVSCを実行してみましょう。 例として私の環境でPHP8の開発をしたいと考えたとき、まぁ、PHP8をそもそも入れていないわけですよ。Windowsですし。

> php
php : 用語 'php' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。名前
が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してください。
発生場所 行:1 文字:1
+ php
+ ~~~
    + CategoryInfo          : ObjectNotFound: (php:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

ただ、dockerはあるので、そいつでphpを立ち上げてみます。

docker run -it --rm --name php-testing php:8 bash

終わったら、後でexitして落とします。

さて、コンテナが立ち上がってい状況で、VSCで以下のような手順を踏みます。 1. 左下のアイコンを押す 2. Remote-containers: Attatch to runnning container を選択する 3. 立ち上がっているコンテナを選択する

これだけで、コンテナ上で実行するVSCが立ち上がります。 実際に見てみたのはこんな感じです。 (PHP古いな。。。更新しよう) これでPHP8の入った環境中でのVSCを使った開発が始められるようになります。 もちろん、PHP intelephense などのエクステンションをインストールすることもできます。 しかし、注意すべきは、エクステンションのインストールは、環境ごとになるという点です。普段ホストで開発していると気にしないですが、コンテナ開発をする場合、基本的にコンテナは実行終了とともに破棄することが多いので、その場合、いちいちエクステンションをインストールしなおさなきゃいけない点に留意しましょう。 面倒な場合は、コンテナを破棄せず、使いまわすようにしましょう。

devcontainer

複数人での開発を考えると、構築する環境定義をプロダクトコードの中に入れておくのが楽です。 VSCのRemote Containerでは、動かしたいコンテナ環境を、docker-composeと少々に設定で定義することができます。 その方法を解説していきましょう。

.devcontainer

まず、プロダクトリポジトリのルート配下に以下のようにディレクトリとファイルを作ります。

.devcontainer
  ┗ devcontainer.json
  ┗ docker-compose.yml

超単純には、docker-compose.ymlがVSCで開発するうえで必要な環境を用意する部分で、devcontainer.jsonが実際にVSCをどこでどのように起動するかを定義する部分です。 docker-composeを使わないで、dockerのイメージを直接指定するやり方もありますが、今回はdocker-composeを使った方法の解説だけにします。

コンテナの定義

私がよくLaravelの記事を書くときに使っているテンプレを例にしてみましょう。 大体の場合、PHPを動かす環境とデータベースがあれば事足りますので、docker-compose.ymlは以下のようになる場合が多いです。

version: "3"

services:
    workspace:
        build: workspace
        command: sleep infinity
        volumes:
            - ../:/var/www/
        ports: 
            - 8000:8000

    db:
        image: mysql
        environment:
            - MYSQL_ROOT_PASSWORD=secret
            - MYSQL_USER=niisan
            - MYSQL_DATABASE=niisan
            - MYSQL_PASSWORD=secret

ここで、workspaceという未定義のコンテナが出てくるので、いつものdocker-composeで環境を立ち上げるのと同じやり方で、workspaceのコンテナを定義しておきます。

.devcontainer
  ┗ devcontainer.json
  ┗ docker-compose.yml
  ┗ workspace
    ┗ Dockerfile

仕事じゃないので、たいして派手な内容ではないですが、以下のようなDockerfileを使っています。

FROM php:8

RUN apt-get update && apt-get install -y git unzip libonig-dev libzip-dev && \
docker-php-ext-install mbstring pdo pdo_mysql zip && \
pecl install xdebug && docker-php-ext-enable xdebug

COPY --from=composer /usr/bin/composer /usr/bin/composer

とりあえず、composerとテストできる環境があればいいや的なやつですね。

devcontainer.json

最後に、devcontainer.jsonを定義しましょう。

{
    "name": "oauth-laravel",
    "dockerComposeFile": "docker-compose.yml",
    "service": "workspace",
    "workspaceFolder": "/var/www",
    "settings": {
        "editor.tabSize": 4
    },
    "shutdownAction": "stopCompose"
}

このなかで、dockerComposeFileserviceは必須で、どのcomposeファイルを使って、どのサービス上でVSCを動作させればいいかを判断します。 ここまでできれば、あとはVSCを再起動させるだけです。 左下のアイコンを押して、Remote Containers: Reopen in Container を選択します。これで、開発環境に入った状態で、VSCが再起動します。 ちょっとわかりにくいかもですが、コンテナ環境上でエディタが起動しています。

おまけ

エクステンションの指定

devcontainerで起動させる場合、エクステンションについては少しありがたい機能があります。上に挙げたdevcontainer.jsonでextensionsという項目があるので、ここにあらかじめ入れておきたいエクステンションを記録しておけばよいです。jsonに簡単に追加する機能もあります。 そうすると、こんな感じになります。

{
    "name": "oauth-laravel",
    "dockerComposeFile": "docker-compose.yml",
    "service": "workspace",
    "workspaceFolder": "/var/www",
    "settings": {
        "editor.tabSize": 4
    },
    "shutdownAction": "stopCompose",
    "extensions": [
        "felixfbecker.php-intellisense"
    ]
}

とりあえず、PHPで必要そうなエクステンションはあらかじめ入れるようにしておきましょう。

デバッガ

デバッガもリモート環境中で動かせます。 ...動かせているはず。 試しにPHPxdebug動かしてみましょう。 とりあえず、以下のiniファイルを/usr/local/etc/php/conf.d/docker-php-ext-xdebug.iniに突っ込みます。

zend_extension=xdebug
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.client_port = 9000

自動でやるんならDockerfileで入れてあげればいいかなって思いますが、今回は普通に編集したのを突っ込みました。 普通に動きますね。

まとめ

というわけで、VS Code Remote Containersの自分流のやり方を書いてみました。 もし、もっと深く知りたいとかであれば、マニュアルサイトを見に行ってくれればと思います。

今回は今すぐに始めるとしたら、どういう感じで始められるかな?という観点でやってみました。 開発環境をリモートの隔離された環境に作り、ホストをきれいに保つというのは、私にとっては割と重要事項ですので、同じ考えを持つ方々にとって良い記事となっていれば幸いです。

今回はこんなところです。

参考

マニュアル
devcontainer.jsonの仕様

Chart.js , vue-chart-jsで、データのフィルターを外部のinputから行う

この記事は個人ブログと同じ内容です

zenn.dev

概要

chart.jsのデータセットの絞り込みをLegend(凡例)のクリックではなくて、chart.jsのコンポーネント外のインプットから行うサンプルを作ってみた。

まとめ

chart.js の表現を超えた凝った表現をする場合は、なかなか面倒そうだということがわかった。
(今回のサンプル以外に、やりたいことあったけど、調べてるうちに作るより、デザインや受け入れ要件と調整するほうが楽やろ!もしくはスクラッチで作ったほうが早いやんか!となったので)
本来の目的は楽することなので、それを忘れないようにしたい。

本題

chartjsのデフォルト

chartjsのデフォルトだと、上記のような凡例をクリックする事により表示項目を絞ることができるが、
下記のように絞り込みのUIを chartjs のコンポーネントから引き剥がしたかったため、試しに作ってみた。
おまけで、カテゴリごとに表示項目絞るみたいなこともできるようになった。
(野菜だけ表示するみたいなことができる)

今回作ったもの

コードはこちらにおいてます
https://codesandbox.io/s/chartjs-toggle-datasets-outer-input-mh0r6

外部から、Legendのフィルタ情報を渡してdatasetをフィルタすることはできなかった。

chartjs にデフォルトで用意されてるだろうと調べてみたけど、入ってなかった。

凡例からのフィルタらしきものをoptions.legend.labels.filter見つけたが、これは、凡例のラベルをフィルタするものであって、凡例のラベルをクリックしたときのdatasetのフィルタには全く関係がなかった。

https://www.chartjs.org/docs/latest/configuration/legend.html#legend-label-configuration

propsで datasets を渡す際に、 外でフィルタした状態のdatasetsを渡す方針に変更

chartjsに用意されてない昨日だとわかった & chartjsを拡張するほど力をかけたくないので、簡単そうな方法「そもそもフィルタしたdatasets渡したらええやん」にきりかえた。
(「力かけたくないなら デフォルトの凡例クリックでのフィルタでいいじゃん」には耳をふさぎます。)

できたもの

やってることは単純で、

  1. filter用の配列(filterItem)をdataに持つ
  2. filterItemはinputや、methodで変え放題
  3. 表示したいdataをchartのコンポーネントに渡す際に、filterItemでフィルタして渡す

https://codesandbox.io/s/chartjs-toggle-datasets-outer-input-mh0r6

<template>
  <div>
    <button @click="fillData()">Randomize</button>

    <hr />
    <button @click="filterByCategory('vegetable')">野菜だけ</button>
    <button @click="filterByCategory('fruit')">果物だけ</button>
    <hr />

    <template v-for="item in dataSetsKey">
      <label
        :for="`check_${item.index}`"
        :key="item.label"
        :style="`color: ${item.color}; margin: 8px;`"
        ><input
          :id="`check_${item.index}`"
          type="checkbox"
          v-model="filterItem"
          :value="item.index"
        />{{ item.label }}</label
      >
    </template>
    <BarChart
      :chart-data="filteredDataCollection"
      :options="{
        responsive: true,
        maintainAspectRatio: false,
        labeling: { display: false },
        legend: {
          display: false,
        },
      }"
    />
  </div>
</template>

<script>
import BarChart from "./components/BarChart.vue";

export default {
  name: "SandBox",
  components: {
    BarChart,
  },
  data() {
    return {
      datacollection: {},
      filterItem: [],
    };
  },
  created() {
    this.fillData();
    this.filterItem = this.datacollection.datasets.map((_s, i) => i);
  },
  computed: {
    filteredDataCollection() {
      const collection = {
        ...this.datacollection,
        datasets: this.datacollection.datasets.filter((_s, i) => {
          return this.filterItem.includes(i);
        }),
      };
      return collection;
    },
    dataSetsKey() {
      return this.datacollection.datasets.map((s, i) => ({
        label: s.label,
        index: i,
        color: s.backgroundColor,
      }));
    },
  },
  methods: {
    filterByCategory(category) {
      this.filterItem = this.datacollection.datasets
        .map((s, i) => {
          if (s.dataCategory === category) return i;
          return -1;
        })
        .filter((s) => s >= 0);
    },
    fillData() {
      this.datacollection = {
        labels: ["1月", "2月", "3月", "4月", "5月", "6月"],
        datasets: [
          {
            label: "ほうれん草",
            backgroundColor: "#4cc36b",
            dataCategory: "vegetable",
            data: [
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
            ],
          },
          {
            label: "なす",
            backgroundColor: "#456dfe",
            dataCategory: "vegetable",
            data: [
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
            ],
          },
          {
            label: "りんご",
            backgroundColor: "#f44b81",
            dataCategory: "fruit",
            data: [
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
            ],
          },
          {
            label: "みかん",
            backgroundColor: "#f48817",
            dataCategory: "fruit",
            data: [
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
            ],
          },
        ],
      };
    },
    getRandomInt() {
      return Math.floor(Math.random() * (50 - 5 + 1)) + 5;
    },
  },
};
</script>

最後に

chartjsで調べたり書いてみたりしていたけど、ライブラリの表現を超える表現をする場合にはやっぱり、なかなかパワーいるなと思った。
楽したくて使ってるのに、カスタマイズ重ねて結局スクラッチのほうが楽だったねってならないように気をつけようとおもた。

PHPerKaigi 2021 ガヤ担当の感想

こちらの記事は個人ブログの転載です

akki-megane.hatenablog.com

まずは 今年も無事にPHPerKaigi 2021 開催できたこと、本当にありがとうございます!

f:id:akki_megane:20210329185249j:plain

スタッフとして

PHPerKaigi 2020 に引き続き、コアスタッフとして参加させていただきました。 当日はTrack-A の担当として、配信の設定やAsk The Speaker のがや(ファシリテーション)をしてました。

Ask The Speaker ・・・登壇後にスピーカー方へ質問や感想を視聴者が言う場です。 今回はDiscord で実施しました。

Ask The Speaker 楽しかった

3日間 Ask The Speaker をしましたが、配信会場的に1部屋しかなく、
他の音が入らないように部屋の隅っこのほうでブツブツPCに喋っている状況でした。

Ask The Speakerは基本的は視聴者方がスピーカーへ質問や感想を述べる場ですが、慣れない状況やオンラインということもあり、
最初に質問等をするにはややハードルが上がっている状況だったので、
取っ掛かりになるように、自分が積極的に質問やディスカッションをするように心がけました。

話が盛り上がると質問が活発になったり、別の参加者から補足事項が出てき新しい知見があったり、
本来カンファレンスの懇親会でするようなざっくばらんな話をこの場でできたのはとても楽しい体験でした。

スピーカー方からも、「Ask The Speaker 素敵な体験でした」、「Ask The Speaker で新しい発見がありました」等好意的な意見を頂いたので、部屋の隅っこで3日間喋り倒した甲斐がありました。

アンカンファレンス 今年も無限LTをした

実は2019年から、毎年恒例で無限LTという狂気のLT回を実施しているんですが、 今年もおかしょい (@okashoi) | Twitter が発起人となり実施しました。

fortee.jp

突発でしたが、20人ぐらい参加してくれて、7人LTが実施できました。
参加者のみなさんありがとうございました!

自分は、技術的負債を返し続ける取り組み というタイトルで発表しました。
当日におかしょいさんから依頼があったのでスタッフ業の合間を縫ってこそこそ作ってました。

18:20~ 開催だったためスタッフは解散し部屋を追い出され、
途方にくれた結果、近くの喫茶店に入り隅っこでコソコソ話してました。
f:id:akki_megane:20210329195716p:plain

無限LTの説明
speakerdeck.com

感想

今年も最高な PHPerKaigi でした!

素晴らしいセッションもたくさん聞けて、明日からのモチベーションをかなりもらいました!

オンラインということもあり、多くの人とはコミュニケーションが取れず、ちょっとさみしい気持ちもありますが、とても楽しかったです。

来年は懇親会でみんなで酒を飲みたいな!!!!

Vue3のSuspenseを使ってみた

Vue3のSuspenseについて興味があったので、試しに触ってみた内容をまとめます

Suspenseって?

非同期処理が解決されるまで、コンポーネントの代わりにフォールバックコンテンツをレンダリングする特別なコンポーネントです。 今まで、computedで変数を定義して、v-ifで表示制御していたのを簡単に書けるようにしたもののようです。

実際に書いてみた

親コンポネ

親コンポネでは以下のように書きます。

<template>
  <Suspense>
    <template #default>
      <ArticleList/>
    </template>
    <template #fallback>
      Loading...
    </template>
  </Suspense>
</template>

<script lang="ts">
import {defineComponent} from 'vue'
import ArticleList from "./components/ArticleList.vue"

export default defineComponent({
  components: {
    ArticleList
  },
})
</script>

Suspenseは、2つのスロットを持っています。

default

最終的にレンダリングするコンテンツ

fallback

defaultに定義したコンテンツの非同期処理が完了するまでのコンテンツ


今回作ったサンプルだと、子コンポネの非同期処理が終わるまではLoadingと表示されます。

子コンポネ

非同期処理を行う子コンポネでは、以下のようにかきます。

<template>
  <div class="card-wrapper">
    <div class="card" v-for="(article, key) in articles" :key="key">
      <h1>{{ article.title }}</h1>
      <div>{{ article.content }}</div>
    </div>
  </div>
</template>

<script lang="ts">
import {defineComponent} from "vue";

export default defineComponent({
  async setup() {
    const sampleArticles = [
      {title: '記事A', content: '記事Aの内容'},
      {title: '記事B', content: '記事Bの内容'},
      {title: '記事C', content: '記事Cの内容'},
    ]

    const fetchArticles = () => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(sampleArticles)
        }, 3000)
      })
    };
    const articles = await fetchArticles();

    return {
      articles
    }
  }
});
</script>

通常ではAPIから何らかのデータを取得して、取得したデータを表示すると思いますが 今回は、簡潔にするためにsetTimeoutで擬似的に非同期処理にしています。 ブログの記事一覧を引っ張ってくるAPIを叩いているイメージで書いています。


このように書くことで非同期処理が終わるまではLoadingと表示され、終わったら記事が表示されるようになります。 f:id:ryonnsui1201:20210329004426g:plain

エラーが発生した際のハンドリング

非同期処理が失敗することもあると思います。
その場合は、onErrorCapturedでエラーを補足し、エラーを表示します。
onErrorCapturedは子孫コンポーネントからエラーが捕捉されるときに呼び出されるライフサイクルフックです。

<template>
  <div class="card-list">
    <div v-if="error">
      {{ error }}
    </div>
    <Suspense v-else>
      <template #default>
        <ArticleList/>
      </template>
      <template #fallback>
        Loading...
      </template>
    </Suspense>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, onErrorCaptured } from 'vue'
import ArticleList from "./components/ArticleList.vue"

export default defineComponent({
  components: {
    ArticleList
  },
  setup(){
    const error = ref(null);

    onErrorCaptured((e) => {
      error.value = e
      return true;
    });

    return {
      error
    }
  }
})
</script>


f:id:ryonnsui1201:20210329011320g:plain

まとめ

以上、Suspenseの使い方でした。 とてもシンプルに非同期処理の際の表示処理を書けるので便利ですね。

参考

Suspense - new feature in Vue 3 - Vue.js Tutorials

ふりかえりの設計からファシリを最近2回したのでふりかえる(熱気球とStory of Storyやった)

f:id:skmtko:20210318185506p:plain

この記事は個人ブログと同じ内容です

sakamotoko.hatenablog.jp


agent bank開発チームのさかもとです。タイトルの通り、最近やったふりかえりに関して書きます!

話したいこと

先週と先々週にそれぞれ毛色の違ったチームふりかえりを行った。
さらにその2つのふりかえりに対してふりかえりの設計と、ファシリテーションを担当したので、そのことについて個人的に整理をする。

やったこと、わかったことを整理してみる。

本題

やったこと(大まかに)

まず、「毛色の違ったチームふりかえりって何やねん」ですが、

  • ① 3名で約1ヶ月分くらいのふりかえり
  • ② 8名で1スプリント(1週間)のふりかえり

を実施しました。

各ふりかえりの前には、「ふりかえりの後にどうなれていたらよいか」を上げるなどして、ふりかえりの手法の選定と、ふりかえりを実施する場所を準備するなどした。
ふりかえりの当日には、ファシリテーター役として、ふりかえりの進行を行った。

① 3名で約1ヶ月分くらいのふりかえり

自分を含めた開発チームのメンバーx2, 副業メンバーx1 の3名で行った。
どういう集まりかとういうと、約1ヶ月ほど前から、副業メンバーにジョインいただき、プロダクトのe2eテスト導入を進めてもらっているので、それに関わっているメンバーの集まりだった。

以下のような属性を含むので、さすがにふりかえりちゃんとしたほうがいいでしょうと思い、ふりかえりを計画 - 新しい試み - 新しくジョインしたメンバー - 副業メンバー - なかなか複雑なプロダクト

ふりかえりは、「熱気球」という手法をメインに取り入れて行った。

② 8名で1スプリント(1週間)のふりかえり

自分のいるagent bank開発チームではスクラム開発を採用しているので、スプリント(うちでは1スプリント=1週間でやってる)の節目に、次のスプリントをより良くするためにという目的で、ふりかえりを行っている。

最近は、「KPTA」という手法を用いてふりかえりを行っているが、この前の回のときに「他の手法でもやりたい!」と自分が発言したのがきっかけで、この回ではいつもとはいつもとは違う手法でやってみることになった。
結果的に「Story of Story」をやってみた。

やったことと、わかったこと(詳細に)

2回のふりかえりでやったことをそれぞれ説明する

① 3名で約1ヶ月分くらいのふりかえり

上に書いていたものを再度整理する。こんな感じのチームでのふりかえり。

  • 自分を含めた開発チームのメンバーx2, 副業メンバーx1 の3名
  • プロジェクトとして
    • 新しい試み
    • 新しくジョインしたメンバー
    • 副業メンバー
    • なかなか複雑なプロダクト
  • メンバージョインから1ヶ月たった

やったこと

このふりかえりの後にどうなっていたいか?を出す(ふりかえりの目的)

スクラムマスター(以降SM)の協力を得て、自分を含めた開発チームのメンバーx2 + SMの3人でふりかえりの設計を行った。
まず、手法を選定するために、ふりかえる期間にどんな事があって、ふりかえりをした結果、我々がどういい状態になっていたいかを出しあった。

f:id:skmtko:20210318114017p:plain

割と、コミュニケーションや、お互いの状況把握の点に関して少し改善の余地がありそうな印象だったので、上のようなアイデアが出た。

これをベースに、どんな手法を採用するかを考えていった。

ふりかえり手法の選定

ふりかえりチートシートや、https://www.funretrospectives.com/ を参考に、ふりかえり手法を選定していった。 結果的には、「何度かやったことがある」「楽しい」を理由に熱気球を採用することになった。あとで書くが、結果楽しかった。

参考: ふりかえりチートシート qiita.com

参考: 熱気球 blog.engineer.adways.net

ふりかえりの構成を考える

手法の選定も、ふりかえりの構成にはいるだろうなあとは思うけど、細かいことは気にしない。

大きな構成としては、以下のようになる

  • 場の設定 
  • データ収集(熱気球に決定)
  • イデア出し(熱気球に決定)
  • 次やることの決定
  • ふりかえりのふりかえり

それぞれどのようにすすめるかを決めていった。

ふりかえりの目的として、「現状の進捗把握」も上がっていたので、熱気球の前に現状把握のステップを追加した。

結果として、 以下のような構成になった

  • 場の設定 → ハピネスレーダー(顔文字でこの1ヶ月を表現)
  • データ収集① → 現状把握
  • データ収集②、アイデア出し → 熱気球
  • 次やることの決定 → 熱気球で出たアイデアから、投票などして決める(その場の状況次第)
  • ふりかえりのふりかえり → 雑談形式

MTGを設定していた時間が1時間だったので、それに収まりきれうように、書くステップに必要な時間を割り振ることまでした

事前にデータ収集

各ステップの時間を設定したところ、「現状把握の時間」そんなに時間取れないな、というのが判明した & おそらく0からやると半端なく時間がかかるのが予想された。
解決策としては、「前もって聞けば分かるっしょ」ということで、slackで事前に質問して回答をもらっておいた。

結果聞いててよかった、ふりかえりの時間では読み合わせて認識合わせる&わからんこと質問する に集中することができて時間を効率よく進めれた。

ふりかえりに必要な物理的な場を作る

以下のツールを使用してふりかえりを行うことを決定した

Miroには、ふりかえり用のボードを用意しその中に、各ステップでやること、必要な時間、熱気球のイラストを事前に設置して、スムーズに始めれるように準備した。

ここまでしてやっとふりかえりの準備 DONE

実際にふりかえりを実施

SMにファシリテーターをお願いしたかったがきれいに予定がかぶっていたので、自分がファシリテーターをしつつ、ちょこちょこふりかえりにも参加するのをやった。

ちなみに、熱気球やった形跡はこんな感じになりましたf:id:skmtko:20210318124420p:plain

次やることの決定で、ほぼすべてのアイデアを次やることにした

出たアイデアの実施難易度が低いものが割と多かったので、結果的に出たアイデア全てに対して、具体的にどういうアクションが取れるかを決めて、次やることとして整理できた。 アイデア整理などして、5個の次やるアクションが出せた。

10分オーバー

1時間のMTGであったが、すべて完了するまでに10分オーバーしてしまった。今回は、幸い副業メンバーの方の後の予定が空いていたため、最後まで全員参加で、ふりかえりのふりかえりまで実施できた。

わかったこと

少人数だとスムーズに進む

いつもやっているチームのスプリントふりかえりだと、だいたい5-8名ほどで実施してるが、今回は3名だけだったので、凄くスムーズに進んだ気がする。

というのも、話す人少ないので、必然的に参加者が自発的に発言しやすい状態になっていたのかもしれない。
また、大人数だと、全員分話し聞こうとして物量てきに時間食っちゃうみたいなことになる。

ハピネスレーダー面白い

場の設定に使ったハピネスレーダーが意外と面白かった。
ネガティブそうな顔文字を貼っている人がいて不安になりながら話を聞いてみると、「なるほど、そんなことあったんか〜」みたいな予想外の展開あった。

貼られたのはこれで、「ヤバイドウシヨウ」ってなった。 f:id:skmtko:20210318123443p:plain
聞いてみたら深刻な内容ではなかったので一安心した

ふりかえり始めての体験の人だったが、楽しく質の高い場にできた

副業メンバーが、今回やったようなふりかえりをやるのが、初めてとのことだったが、とても楽しかったとの感想を貰えることができた。

次回1.5ヶ月くらい立ってから再度やりたいねとなったので、ほんとにちゃんと準備かいがありました。
よかったーーーーー


8名で1スプリント(1週間)のふりかえり

上の熱気球の文でだいぶ、力尽きた感あるので、こっちはシンプルな文章になりそう...

やったこと

いつもと違うふりかえり試したい発言

この回の前の回のふりかえりのふりかえり最後のSMの「なんにかやってみたいことあります?」問に対して「いつも違う(KPTAじゃない)ふりかえり手法でやってみたい」と発言したことにより、今回設計からファシリまでを務めることになりました。

SMの支援を受けながら進めた。

ふりかえり手法の選定

まず、ここでもふりかえりチートなどを参考に、どんな手法を取るかというのを考えていきました。

毎日の終了時に夕会としてその日あったことや次何やるかを、YWTを使って簡単に振り返ってます。

こんな感じ f:id:skmtko:20210318151935p:plain

縦長の四角が一日ごとのフレームでその中で上から「Yやったこと」「Wわかったこと」「Tつぎやること」をためています 付箋の色は、赤→ポジティブ、黄→ニュートラル、青→ネガティブ を表現してもらってます

このスプリントでは、新しくジョインしたメンバーが開発に本格的に参加し始めた & メンターと一緒に進めている様子がなかなかうまくいっているようだったので、良い学びが多かったのでは、YWTの結果もポジティブな付箋多めかな?という肌感だった。

良い学びを伸ばす系のふりかえりにしてみようというのと、毎日のYWTの内容を生かしてできないかなということで、Story of Storyを採用してみることにした

ちなみにふりかえりチートシートには、こんなふうに書いてた

f:id:skmtko:20210318170112p:plain 「良いところを伸ばす◎」なるほど良さそう。

Story of Story の参考webでみっけたの英語のページしかなかった...

www.funretrospectives.com

ふりかえりの構成を考える

結果的に以下のような構成に落ち着いた

  • 場の設定 → ハピネスレーダー(①でやったときに楽しかったので、味をしめたため)
  • データ収集① → 毎日ためてたYWTのYだけさらっと眺める
  • データ収集② → Story of Story
  • イデア出し → Story of Storyで集めた
  • 次やることの決定 → 出たアイデアから、Effort x Value という手法を使って、絞り込み、やることの具体案を決める話をする
  • ふりかえりのふりかえり → 3人くらいに話してもらう

次やることの決定に関しては、いつもは投票によって、アクションを決める対象を絞り込んでいるが、ここもチャレンジしようということで、Effort x Value という手法を取り入れてみた。 簡単に説明すると、Effort(必要な努力)と Value(得られる価値)の2軸のマトリックスに対して、アイデアマッピングする。より少ない努力で得られる価値の大きいアイデアに対してアクションを考えるというもの。
マッピングする中で、どうやったら少ない努力にできるかとか、アイデアの共通認識を合わせる的なやつです

www.funretrospectives.com

ふりかえりに必要な物理的な場を作る

Miroにこんな感じのボードをつくった、 f:id:skmtko:20210318173310p:plain

会話はDiscordで音声チャットをしながらおこなう。 また、①のときと同様にスッテプで何をするのかと、各ステップでどのくらい時間を使うかを見積もって記した。

実際にふりかえりを実施

実際にStory of Storyやった形跡はこんな感じになった

f:id:skmtko:20210318173938p:plain

わかったこと

ふりかえり慣れている人達がやっても、ハピネスレーダー楽しい

前回味をしめて、今回もやってみたがやっぱり楽しかった。 これからも困ったらこれやりそう

Story of Story 結構時間かかる

ステップとして、以下があるが、なかなかステップが多かったのと、すべてのステップで少しづつ予定の時間をオーバーしてた。ざっくり2倍づつくらい。

  • 期間の間に起きたことを書き出すステップ(以下を書き出す)
    • 事実
    • コミュニケーション・コラボレーション
    • 続けること
    • 避けること・やめること
  • 書き出したものを皆で共有する
  • 上で書き出したものから以下と書き出す
    • 個人の学び・成長
    • チームの学び・変化

事実の付箋がとても大量に書き出された事により、各ステップで、全部を見ながら次の付箋を貼るのがかなり負担だったみたい。 起きた出来事が多かったり、参加者が多いふりかえりに使う場合は、注意が必要かなあとおもった。 もしくは、テーマを絞るとかしても良かったのかな?

プロブレムに焦点が当たりづらかった

毎回やっているKPTAに比べると、プロブレム(課題)に対して焦点が当たりづらかあったという感想があった。
確かに、割と良いコミュニケーションや、良い学びに対して焦点が行きがちで、「このモヤモヤに対して話そうぜ」てきな雰囲気に行きづらかった感がある。

また、データの量が多すぎてアイデア出すのが大変みたいな感想もあった。

Effort x Value の使い方工夫必要そう

イデアの絞り込みにEffort x Value を使ってみたが、これもプロブレムに焦点が行きづらい要因だったかも。 プロブレムの解決って、大体すごいパワー必要そうなことが多い気がするので...

なので、ここぞというときに使えるように使い所を探ってみようと思う。

大人数疲れる!

6人+ファシリ(俺)+SM(俺の支援)みたいな感じだったんですが、みんなの出したアイデアを全部みたり、話を深ぼったりすると単純に時間がかかるし、超パワーがいりますね。

まとめ

2つの毛色のちがうふりかえりの設計から、ファシリまでやってみたんですが、 人数や、期間、目的によって適したやり方全く違うんだなと、実感した。

手法によって、得意不得意があるっぽいので、どんな手法がどんなときに効果的かみたいなことを実践で覚えていきたいなとおもった。
また、既存の手法も目的によってカスタマイズしながら進めれるようになりたい。

オンライン、オフライン関係なく少人数のほうが進めやすいんだろうなとは思うが、オフラインだと、途中で少人数に分離して合流的なことするにも、ツールとかの準備必要でめんどいなあ。ガッツリやるときは、zoomとかGoogleMeetのブレイクアウトセッション準備したりなんしたりするのだろうなあ...

最後に定期的に行っているスプリントのふりかえりで、実験的な事ができたのは、チームとしてとても良かったなと思う。
引き続きチャレンジしていきたい!