PHP 8.0 の新機能を試してみよう

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

www.ritolab.com


2020 年 11 月 26 日に PHP 8.0 が正式リリースとなりました。

今回は PHP 8.0 の新しい機能を使ってみようと思います。

実行環境

Docker でコンテナを作成して PHP 8.0 を動作させます。

今回は適当な場所に php80 というディレクトリを作成してそこで行っています。

php80/
└─ src/
    └─ index.php

以下のコマンドでコンテナを起動

docker run \
    -v /path/to/php80/src:/var/www/html \
    -p 8080:80 \
    -d --name test-php80-container php:8.0-apache

起動したコンテナの PHP バージョン

$ php -v
PHP 8.0.0 (cli) (built: Dec  1 2020 03:24:11) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies

Named Arguments(名前付き引数)

関数のデフォルト値の一部を変更したい時とかに、この名前付きの引数を使用する事で引き数の指定が簡略化されます。

https://wiki.php.net/rfc/named_params

例えば以下のような関数があったとして

<?php

function sample(int $value = 1, int $value_int = 1, string $valueString = 'hoge')
{
    $number = $value + $value_int;

    return "{$valueString}: {$number}";
}

sample();
// => hoge: 2

3つの引き数にはデフォルト値が指定されているので、この関数を使用時に引き数を渡さなければそれぞれデフォルト値が代入されて、文字列 hoge: 2 が返ります。

この時に例えば「最後の引き数だけを指定したい」といった場合には 3 つ全ての引き数を指定してあげなければなりませんでした。

<?php

// 第三引数だけを変更したいけど第一引数と第二引数も渡してあげる必要がある
sample(1, 1, 'foo');
// => foo: 2

これを名前付き引き数を使用することで、第三引数のみを指定する事ができるようになりました。

<?php

// 第三引数の変数名を指定して引数として渡す
sample(valueString:'abc');
// => abc: 2

「最後の引数」というのは例なので、例えばこの関数の第二引き数のみを指定する事も可能です。

<?php

// 第二引数の変数名を指定して引数として渡す
sample(value_int: 10);
// => hoge: 11

複数指定することもできます。

<?php

// 複数指定する
sample(value_int: 5, valueString:'xyz');
// => xyz: 6

関数で定義されている引数の順番でなくてもいけます。

<?php

// 指定順がバラバラでもいける
sample(valueString:'AAA', value_int: 3, value: 4);
// => AAA: 7

Constructor property promotion(コンストラクタのプロパティ昇格)

クラスをインスタンス化する際にコンストラクタでセットするプロパティへの代入と定義が簡略化できるようになりました。

https://wiki.php.net/rfc/constructor_promotion

例えば、Member クラスをインスタンス化する際に id と name を渡すとすると、これまでは以下の記述で行っていました。

<?php

class Member
{
    public int $id;

    public string $name;

    public function __construct(int $id, string $name)
    {
        $this->id   = $id;
        $this->name = $name;
    }
}
  1. プロパティ(メンバ変数)を宣言する
  2. コンストラクタでそれぞれ id と name をプロパティへ代入する

これが PHP 8 からは以下のように簡略化して記述することができるようになりました。

<?php

class Member
{
    public function __construct(
        public int $id,
        public string $name,
    ) { }
}

コンストラクタの引数のところでプロパティを宣言しているようなイメージですね。

これで「プロパティの宣言」「コンストラクタで受け取る引数の型・順番の指定」「受け取った値をプロパティへ代入する」をまとめて行っている感じ。なんかすごいな。

Union types(Union型)

型宣言を複数記述することができるようになりました。

https://wiki.php.net/rfc/union_types_v2

関数の引数・戻り値、クラスのプロパティに型宣言が行えますが、これまでは型が1つに確定している場合のみ(もしくは ? で |nullを表現)記述できる状態でした。PHP 8 からは、複数の型を記述できるようになりました。

<?php

class Sample
{
    public int|float $a;

    public function set(int|float $a)
    {
        $this->a = $a;
    }

    public function get(): int|float|null
    {
        return $this->a;
    }
}

Match expression(match式)

match 式が追加されました。

switch 式と似ているけれど、より洗練された感じになっています。

<?php

$number = 1;


/* switch 式 */
switch ($number) {
    case 1:  $result = 'one';   break;
    case 2:  $result = 'two';   break;
    case 3:  $result = 'three'; break;
    default: $result = 'other';
}

echo $result;
// => one


/* match 式 */
$result = match ($number) {
    1       => 'one',
    2       => 'two',
    3       => 'three',
    default => 'other',
};

echo $result;
// => one

match が switch と違う点

  • 値を返す
  • 比較が厳密( == ではなく === )に行われる
  • フォールスルーしないので break を書かなくて良い

シンプルな分、間違いが起こりにくい印象を受けました。 比較部分を軽視してて予期せぬ挙動したりとか、単純に break 付け忘れて意図していない挙動するとか、そういうのは無くせそうですね。

Nullsafe operator(Null 安全演算子

JavaScript 等ではお馴染みな Null 安全演算子PHP でも使えるようになりました。

<?php

$country = $session?->user?->getAddress()?->country;

「?」をアローの手前に挟むやつです。

<?php

class User
{
    private int $id;

    private string $name;

    public function __construct(int $id, string $name)
    {
        $this->id   = $id;
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

class Member
{
    public User|null $user = null;
}

$member = new Member();


$member->user->getName();
// => Fatal error: Uncaught Error: Call to a member function getName() on null

$member->user?->getName();
// => null

上記の例では、Member クラスの $userに User クラスがセットされていない場合、getName() を参照できないので Fatal error が発生しますが、 Null 安全演算子で記述することで、$member->user が null の場合は getName() を参照せずに null を返してくれるようになります。

str_contains()

対象文字列に、指定した文字列が含まれているかどうかを調べる str_contains() 関数が追加されました。

https://www.php.net/manual/ja/function.str-contains.php

<?php

$target = 'I love Grape,Apple,Orange,Strawberry and Cherry.';

$search = 'Orange';

str_contains($target, $search);
// => true

これまで strpos とか preg_match を使っていたことを考えたらシンプルになって良いかなと思います。

str_starts_with() / str_ends_with()

以下2つの関数も追加になっています。

<?php

$target = 'I love Grape,Apple,Orange,Strawberry and Cherry.';


$start = 'I';

str_starts_with($target, $start);
// => true

$end = 'Cherry.';

str_ends_with($target, $end);
// => true

他にもいろいろ

ここで取り上げたもの以外でも様々な変更点があるので PHP マニュアルを参照してみてください。

PHP: PHP 7.4.x から PHP 8.0.x への移行 - Manual

日々の「ふりかえり」のために、タスクでやることやったことの履歴を残す

agent bank 開発チームのさかもとです。

最近チームで取り組み始めたアクションで、とても効果を感じれたことがあったので書きます。

ふりかえり大事

仕事をしていく上で振り返りを行うことは、とても重要なことです。

なぜ「ふりかえり」を行うのか。
個人やチームの成果が出るまでの過程をふりかえってみることで、良くなかった点、改善できる点の改善を行い、良かった点はそれを続けられるように強化をしていきます。

ふりかえりは、チームや個人の反省会ではなく、次を良くするために行われるものです。  

はなしたいこと

slackのワークフローを使って、タスク着手時に「やること宣言」と「やったこと履歴」残すのが、ふりかえりをする際やチームでタスクを進めていく上でとても効果を感じれた。

きっかけ

チームの現状説明(タスクの進め方や、振り返りのタイミング)

agent bank開発チームでは現在こんな感じで開発を進めています

  • フルリモート
  • スクラム開発(スプリントの単位は1週間)
  • discordのボイスチャットつなぎっぱで開発
  • ATLASSIANのJiraバックログ管理
  • 一日の朝・昼・夕のタイミングでチーム内の現状把握の場を設定している

リモートワークを円滑にすすめるための実践とツール - ROXX(旧SCOUTER)開発者ブログ こっち見るともう少し詳しくどんな感じで進めてるかわかるかと思います。

ふりかえりであがったproblem

一日に最大3回チームの現状把握の場を設定して、「計画通りに進めているか?」「想定外の事態が発生していないか?」などのチェックをして、計画の修正やアサインの調整などを行っているんですが、先日のスプリントの振り返りでこのようなproblemが上がりました。

f:id:skmtko:20201111142339p:plain
進捗の遅れから想定外のタスク、隠れた仕様・懸念点などを洗い出したい

背景としては、タスクに対してのポイントの付け方を以下のように、ストーリーにポイントが付いていればポイント付けを行い、タスクにポイントがつくのはストーリーに紐付かない場合のときだけにしているのが、関係してそうでした。

f:id:skmtko:20201111144255p:plain
タスクへのポイントの付け方

※リファインメントの際に、ストーリーにポイント付を行い、プランニングの際にポイントが紐付かないタスクにポイント付を行っています

結果として1日の中で現状把握の場を設けてはいるが、実際にタスクにかけるつもりだった時間と、実際に掛かった時間が見えずらい状態が続いていました。

深く記憶に残るような、よほどの大事故でもない限り、一日のふりかえりで振り返ることは難しいですね。「きのうの晩ごはん」を思い出すのも難しくなってしまった我々にとっては、タスクの中でいくつも発生する些細な出来事や、タスクの開始時間、経過時間を記憶だけですべて鮮明に振り返るのはおそらく不可能です。

「想定よりもタスクが大きかったが検知できるようになる」をゴールとしたアクションを出そうといういことで、今回の「slackにやること宣言する」をすることになりました。

やったこと

やったことはシンプルでこんな感じのことをやっていました

  1. チームのタスク進捗用のslackチャネルを作成した

  2. チームメンバーはタスク着手時に、slackワークフローを用いて、やること、着手開始時間、完了目標時間などを投稿

  3. タスクを進めながら、必要に応じて都度進捗をスレッドに投稿する

  4. タスクが完了したら、完了したことを書く

  5. 区切りの時間(昼休み後の昼会や夕会)の振り返りに、活用する

実際にはこんな感じ

ワークフローの入力

f:id:skmtko:20201111150053p:plain
ワークフロー入力のイメージ

終了時刻は任意にしているが、開始から終了までに2時間以上かかっていた場合には、ふりかえりの際に、なぜ時間超過してしまったのかを話すトリガーにしている。

ワークフロー投稿の活用イメージ

f:id:skmtko:20201111152909p:plain
こんな感じでスレッド作ってふりかえれるようにする

終了したことと、終了した時刻だけは必ずわかるようにして、作業中のスレッドは各自自由に投稿するようにしている

今のところ、進捗の投稿を行うかどうかは各自に任せて自由に投稿している。
 例)「〇〇完了した」「✗✗が判明したのでタスクを追加起票した」「△△が複雑で延長した」

他のタスクを実施しているメンバーが、共有や依頼を投げたりにも使ってみている
 例)「Aのタスクで演る予定だったけど、こちらで〇〇の実装もお願い!」

うれしかったこと

みんなが嬉しかったこと

実際に2週間運用してみたところ、メンバーの全員が好感触でした。こんなかんじ。

  • 忘れがちになタスク着手時の時系列を残せてふりかえれるのがよい
  • デザインタスクをひとりでやることが多いが、共有できて安心する(開発チームにデザイナーも一人います)

ふりかえりの材料が目に見える状態で残っているというのが、とても大きかったみたいです。
何もわからないことは、怖いですもんね。
slackのワークフローで最初に投稿するようにしたのが、着手時の投稿ハードルを下げたんですかね。着手宣言のアクションを初めて1日後にワークフローをすぐ作ってくれたメンバーに感謝です。

他の人はしらんけど俺はこれがうれしかったこと

ふりかえりの際の材料になることはもちろんとても良かったんですが、その他にも個人的に、以下のような嬉しさがありました。

  • 他のメンバーが何をやっているかがすぐ分かる
  • 着手時に何をすべきなのかが整理できる
他のメンバーが何をやっているかがすぐ分かる

ボイスチャットつなぎっぱといえども、皆がずっと話しっぱで、逐一何しているか話しているわけではありません。(たまにずっと喋りながらタスクを進めるメンバーがいたり、全員でモブですすめるみたいなケースはありますが...)

おおよそ誰がどのタスクを着手するかをわかるようにしていますが、計画通りに行くとは限りません。
誰が何をやっているのかすぐにわからない状態では、次何をするかを迷ってしまいがちでした(わたしが)

着手宣言と経過を見えるようにすることで、その日のたすくをより効率的にすすめるための判断がやりやすくなりました。
(どんどんタスクをすすめるべきか、ほかメンバーのサポートに入るべきか、計画を修正するべきかなどの判断がやりやすくなったとおもう)

 着手時に何をすべきなのかが整理できる

着手時に完了目標時間を入れることによって、タスク開始時に再度タスクのプランニングを行うことになります。
可能ならば30分とか10分単位で完了できることを整理し直すことで、タスクの中で明確に何をやるべきなのかが見えてくると思います。
タスクが大きすぎる場合は、ワークフローで投稿する際に宣言しなくとも、スレッド内に「次の30分で〇〇を終わらせる」などと宣言してタイムボックスを設定すれば、細かくやることを整理も可能でさらには、ふりかえりの際に、より詳細な出来事として振り返ることが可能です。

あと、タスクに紐づく共有をできたのも良かった

個人的には、15分スプリント*1にトライしてみたい感あったんですが(やれるイメージが、まだあまり浮かばないけど)、とりあえずはslackの「やること宣言」だけでも今の顕在化した課題は解決できそう。

さいごに

「slackにタスクのやることやったことの履歴を残す」といったシンプルなアクションだったんですが、現在のチームとしてはすごく効果を感じることができました。このアクションが、これからのふりかえり、改善のために役立って行くことと思います。

また続けていくうちに、違った方法になるかもしれませんが、もしそうなるとしたらチームがより良くなるための動きの一つだと思います。

今回のアクション自体も、実行の1日目から「slack投稿先の変更」「ただのslack投稿→ワークフローで投稿」「ワークフローの入力項目変更」などと、ふりかえりを重ねることに、今のチームがより良くできるように変化を重ねていました。

この調子で、さらにプロダクトをより良くできるチームになれるように、開発を進めていきたいところです。

おまけ

弊社では開発チームのメンバーをとても募集しています。 ちょっとでも興味があって、話くらいなら聞いてやるか!という方は私のtwitterにでも連絡くれると、とてもありがたいです。

こちら さかもとこうへい (@12kohe3i) | Twitter

Next.jsでStrict CSPを実現する

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

kotamat.com

業務では Nuxt.js を使用しているのですが、CSP の nonce の対応ができておらず、Next.js だとどうなんだろうと触ってみたらメチャクチャ簡単に設定できる事がわかったので、その方法について紹介します。

やりたいこと

CSP の対応のなかで Google が提唱しているやり方としてStrict CSPというのがあります。 これは簡単に言うと下記のように CSP の設定を行えば モダンなブラウザにおいて プロダクションレベルのセキュリティーが担保できるよというものです。

Content-Security-Policy:
  object-src 'none';
  script-src 'nonce-{random}' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:;
  base-uri 'none';

このなかで大事なものとしては strict-dynamic が挙げられます。これは、CSP Level3 から導入されたディレクティブで、ほかのドメインホワイトリストは無視した上で、'nonce-{random}'で指定された <script> タグのスクリプトを許可し、かつその先で生成された別のスクリプトに関しても同様に許可するというものです。

この挙動により、開発者はトップレベルのスクリプトに関して nonce をつければ良くなり、攻撃者はその nonce を奪えない限り XSS ができなくなるため、ホワイトリスト形式での設定より安全になるということで、非常に便利に対策ができるようになります。

注意点としては、モダンなブラウザでなければ strict-dynamic は解釈してくれないので、そういったブラウザでは脆弱性は防ぎきれないというところになります。 また Safari はまだ対応していません。(モダンなブラウザではないということですかね…?)

https://caniuse.com/?search=strict-dynamic

対応方法

/pages/_document.tsx にて、nonce の生成と、Head,NextScript への反映を行います。

import { randomBytes } from "crypto";
import Document, { Html, Head, Main, NextScript } from "next/document";

type WithNonceProp = {
  nonce: string;
};
export default class MyDocument extends Document<WithNonceProp> {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    const nonce = randomBytes(128).toString("base64");
    return {
      ...initialProps,
      nonce,
    };
  }
  render() {
    const nonce = this.props.nonce;
    const csp = `object-src 'none'; base-uri 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: http: 'nonce-${nonce}' 'strict-dynamic'`;

    return (
      <Html>
        <Head nonce={nonce}>
          <meta httpEquiv="Content-Security-Policy" content={csp} />
        </Head>
        <body>
          <Main />
          <NextScript nonce={nonce} />
        </body>
      </Html>
    );
  }
}

これだけです。

Next.js では、SSR では当然のこと、SSG する際にもこの処理を適応してくれるため、例えば下記のようなページコンポネントがあったとしても

import { GetStaticProps } from "next";
import { FC } from "react";

type Props = {
  name: string;
};
const SSG: FC<Props> = ({ name }) => {
  return <div>{name}</div>;
};

export default SSG;

export const getStaticProps: GetStaticProps<Props> = async (ctx) => {
  return { props: { name: "hoge" } };
};

next export 後のファイルに nonce がちゃんと反映されています。 ただ、当然ですが、SSG だと nonce の本来の持つべき役割である 「リクエストごとに生成し直す」ことができないため、SSG を前提としたプロダクトでは攻撃者の攻撃にさらされる危険性があります。

まとめ

Next.js にて nonce を用いた StrictCSP を実装する方法を紹介させていただきました。 モダンではないブラウザが対応してくれれば、相当対策をしやすくなるので、ブラウザの対応に期待したいですね。

余談

Nuxt.js では、この辺で HTML タグが直書きされていることによって nonce を入れる余地がないため、今回のやりたいことはできない感じになっています。 その代わり inlineScript の hash 値を CSP に埋め込む形で実装されているため、アプリケーションのソースコードに閉じる限りはそれなりに硬い対策になるようになっているようです。

Laravel 8 で刷新された ModelFactory でテストデータを簡単に作成する

こちらのブログは個人ブログと同じ内容です

www.ritolab.com


Laravel には ModelFactory(モデルファクトリ)といって Eloquent のモデルを使って簡単に開発用のデータやテスト時のデータを作成できる仕組みがあります。

このモデルファクトリが Laravel 8 で刷新されなかなか良い感じになっていたので、今回は実際に使用してみたいと思います。

モデルとテーブルの作成

まずは ModelFactory を作成するために、モデルと DB のテーブルを作成しつつ仕様を決めておきたいと思います。

今回は、Book モデルを作成して、それについてのモデルファクトリを定義しようと思います。

まずは books テーブルのマイグレーションを作成

# books テーブル マイグレーション作成
php artisan make:migration create_books_table --create=books

マイグレーション(カラム)は以下のように定義しました。

database/migrations/xxxx_xx_xx_xxxxxx_create_books_table.php

<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBooksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up(): void
    {
        Schema::create('books', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('author');
            $table->date('bought_at')->nullable();
            $table->date('started_at')->nullable();
            $table->date('ended_at')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down(): void
    {
        Schema::dropIfExists('books');
    }
}
  • id
    • 主キー
  • title
    • 本のタイトル
  • author
    • 著者
  • bought_at
    • 購入した日
  • start_at
    • 読み始めた日
  • end_at
    • 読み終わった日
  • created_at
    • 作成日
  • updated_at
    • 更新日

続いて、モデルを作成します。

# book モデル作成
php artisan make:model Book

app/Models/Book.php

<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    use HasFactory;
}

今回は factory を試すことがメインのため、特に事前の設定は不要なので生成されたままで記述は変えていません。

この Book モデルからテストデータについて考えると以下のような感じで作れたら良いなと考えます。

  • 本のタイトルと著者は入っていれば何でも良い
  • 購入日・読み始めた日・読み終わった日はそれぞれが関連するので、state で予め定義しておいて簡単にデータを作成できるようにしたい

前者はランダムで良いので Faker で作成すれば良いですね。

後者は、例えば「読み始めた日」に日付が入っているということは、当然既に購入済み(レンタルは想定しません)なので「購入日」にも日付が入っている必要がある。みたいなことなので、ここは state を定義しておいて、呼び出すだけで簡単にテストデータを作成したいねというお話です。

このあたりまで決めたら、factory の実装に移れそうです。

Laravel 7 までのモデルファクトリ

Laravel 7 までの場合をおさらいすると、以下のように定義していました。

laravel7/database/factories/BookFactory.php

$factory->define(Book::class, fn(Faker $faker)  => [
    'title'  => $faker->sentence(),
    'author' => $faker->name(),
]);

これを factory 関数を使ってテストデータを作成していました。

/** @var \App\Models\Book $book */
$book = factory(Book::class)->create();

factory state(ファクトリステート)と呼ばれる、データの値を予め定義しておく場合も、以下のように記述していました。

laravel7/database/factories/BookFactory.php

$now = CarbonImmutable::now();

/**
 * 購入済み
 */
$factory->state(Book::class, 'bought', [
    'bought_at' => $now
]);

/**
 * 読んでいるところ
 */
$factory->state(Book::class, 'started', [
    'bought_at'  => $now->subDay(),
    'started_at' => $now,
]);

/**
 * 読み終わった
 */
$factory->state(Book::class, 'ended', [
    'bought_at'  => $now->subDays(2),
    'started_at' => $now->subDay(),
    'ended_at'   => $now,
]);

そしてテストデータ作成時に state()(複数ある時は states() )メソッドをチェーンさせて呼び出します。

/** @var \App\Models\Book $book */
$book = factory(Book::class)->state('ended')->create();

ModelFactory 実装

ではここから Laravel 8 にてモデルファクトリを定義していきたいと思います。

まずは、artisan コマンドで生成した時点での BookFactory を見てみます。

laravel8/database/factories/BookFactory.php

<?php

namespace Database\Factories;

use App\Models\Book;
use Illuminate\Database\Eloquent\Factories\Factory;

class BookFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Book::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            //
        ];
    }
}

なんということでしょう。ModelFactory がクラス化されています。Laravel 8 になって刷新されたというのは、こういうことだったのですね。

では実装します。まずは基本部分から

laravel8/database/factories/BookFactory.php

public function definition(): array
{
    return [
        'title'  => $this->faker->sentence(),
        'author' => $this->faker->name(),
    ];
}

definition() メソッドに値のデフォルト値を定義することで、テストデータを作成できます。

利用側はこんな感じ

/** @var \App\Models\Book $book */
$book = Book::factory()->create();

factory 関数ではなく、モデルから static に呼び出すことでテストデータを作成していますね。

続いてステートの定義をしていきます。

laravel8/database/factories/BookFactory.php

/**
 * 購入済み
 *
 * @return BookFactory
 */
public function bought(): BookFactory
{
    return $this->state(fn ()  => [
        'bought_at' => CarbonImmutable::now()
    ]);
}

/**
 * 読んでいるところ
 *
 * @return BookFactory
 */
public function started(): BookFactory
{
    $now = CarbonImmutable::now();

    return $this->state(fn ()  => [
        'bought_at'  => $now->subDay(),
        'started_at' => $now,
    ]);
}

/**
 * 読み終わった
 *
 * @return BookFactory
 */
public function ended(): BookFactory
{
    $now    = CarbonImmutable::now();

    return $this->state(fn ()  => [
        'bought_at'  => $now->subDays(2),
        'started_at' => $now->subDay(),
        'ended_at'   => $now,
    ]);
}

メソッドを作成することで、それぞれの state を定義することができるようになっています。

こちらも、テストデータ作成時にメソッドをチェーンさせて呼び出します。

/** @var \App\Models\Book $book */
$book = Book::factory()->bought()->create();

state に引数を渡す

ModelFactory がクラス化され state がメソッド化されたことで、state 指定時に引数を渡せるようになっています。

laravel8/database/factories/BookFactory.php

/**
 * 読んでいるところ
 *
 * @param int $startDays 読み始めるまでの日数
 *
 * @return BookFactory
 */
public function started(int $startDays=1): BookFactory
{
    $now = CarbonImmutable::now();

    return $this->state(fn (array $attributed)  => [
        'bought_at'  => $now->subDays($startDays),
        'started_at' => $now,
    ]);
}

/**
 * 読み終わった
 *
 * @param int $endDays   読み終わるまでの日数
 * @param int $startDays 読み始めるまでの日数
 *
 * @return BookFactory
 */
public function ended(int $endDays=1, int $startDays=1): BookFactory
{
    $end    = CarbonImmutable::now();
    $start  = $end->subDays($endDays);
    $bought = $start->subDays($startDays);

    return $this->state(fn (array $attributed)  => [
        'bought_at'  => $bought,
        'started_at' => $start,
        'ended_at'   => $end,
    ]);
}

今回の Book モデルで具体的に説明すると、これまで state では単純に「購入日」「読み始めた日「読み終わった日」にそれぞれ 1 日違いで値を挿入し、データとして整合性がとれた状態のみを作り出していました。

上記の場合では、引数に日数を与える事にとって、任意の日数差にてデータを作成できるようにしています。

state は本来、決まったデータのパターンを定義するものと私は理解しているので、基本的には可変的に設定するシーンは多くはないと認識はしているものの、それでもあるケースのテストを行いたい場合に、たまにこういうの欲しくなる事もあります。

もちろん、make() や create() の引数で値を指定すれば済む話ではあるのですが、テストやテストデータ作成の数が多くなってくると、この辺の記述が結構膨れてきて設定の意図がわかりづらくなり、他の値の設定とかも入ってきて可読性が下がるのが微妙だなと思っていました。

なので外から値を投げられるのは結構うれしいなと感じました。

// 購入日から 3 日後に読み始め、読み始めてから 10 日後に読み終わる
/** @var \App\Models\Book $book */
$book = Book::factory()->ended(10, 3)->create();

今回の state の場合はサンプル的に作っているのでニーズから作成したものではないですが、例えば、普段は日にちさえ入っていればそれで良いのだけれど、あるテストケースで「いつ読んだ」とか「読むのに何日以上かかった」あるいは日付範囲で絞る。みたいな検索・抽出の機能のテストを書く際のテストデータがほしいなって思った時とかに使えるかな。

state() に渡すクロージャの引数

ちなみに、$this->state() に渡すクロージャには、引数として array $attributed が渡りますが、ここには、この state が実行されるまでに設定された値が入ってきます。

ですので definition() で作成されたデータ + この state が実行されるまでに実行された state のデータが渡ってくる事になります。

例えば以下を実行した場合に、

Book::factory()->bought()->ended()->create();

ended() の ファクトリステートで渡ってくる array $attributed は以下のようになります。

$attributed => Array
(
    [title] => abcd
    [author] => ABCD
    [bought_at] => 2020-11-03 10:14:17
)

Laravel 8 と Laravel 7 以前のモデルファクトリの違い

これまで部分的にしかコードを記していなかったので、最後に Laravel 8 と Laravel 7 以前のモデルファクトリの違いを全体感で俯瞰できるように、今回作成したそれぞれのコードをここで表示します。

Laravel 7 以前の BookFactory

<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Book;
use Carbon\CarbonImmutable;
use Faker\Generator as Faker;

$factory->define(Book::class, fn(Faker $faker)  => [
    'title'  => $faker->sentence(),
    'author' => $faker->name(),
]);

$now = CarbonImmutable::now();

/**
 * 購入済み
 */
$factory->state(Book::class, 'bought', [
    'bought_at' => $now
]);

/**
 * 読んでいるところ
 */
$factory->state(Book::class, 'started', [
    'bought_at'  => $now->subDay(),
    'started_at' => $now,
]);

/**
 * 読み終わった
 */
$factory->state(Book::class, 'ended', [
    'bought_at'  => $now->subDays(2),
    'started_at' => $now->subDay(),
    'ended_at'   => $now,
]);

Laravel 8 の BookFactory

<?php

declare(strict_types=1);

namespace Database\Factories;

use App\Models\Book;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * Class BookFactory
 *
 * @package Database\Factories
 */
class BookFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Book::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition(): array
    {
        return [
            'title'  => $this->faker->sentence(),
            'author' => $this->faker->name(),
        ];
    }

    /**
     * 購入済み
     *
     * @return BookFactory
     */
    public function bought(): BookFactory
    {
        return $this->state(fn (array $attributed)  => [
            'bought_at' => CarbonImmutable::now()
        ]);
    }

    /**
     * 読んでいるところ
     *
     * @param int $startDays 読み始めるまでの日数
     *
     * @return BookFactory
     */
    public function started(int $startDays=1): BookFactory
    {
        $now = CarbonImmutable::now();

        return $this->state(fn (array $attributed)  => [
            'bought_at'  => $now->subDays($startDays),
            'started_at' => $now,
        ]);
    }

    /**
     * 読み終わった
     *
     * @param int $endDays   読み終わるまでの日数
     * @param int $startDays 読み始めるまでの日数
     *
     * @return BookFactory
     */
    public function ended(int $endDays=1, int $startDays=1): BookFactory
    {
        $end    = CarbonImmutable::now();
        $start  = $end->subDays($endDays);
        $bought = $start->subDays($startDays);

        return $this->state(fn (array $attributed)  => [
            'bought_at'  => $bought,
            'started_at' => $start,
            'ended_at'   => $end,
        ]);
    }
}

クラス化されたことで各々のファクトリがしっかりすみ分けできた感じがいいなあ。

Laravel <= 7 から 8 への移行

さて、Laravel 8 にて刷新されたモデルファクトリですが、Laravel 7 以前の記法とは互換性がありません。

なので ver. 8 へアップグレードをするためにはここを全て書き直さないといけないわけですが、調整箇所が多いと物量との戦いになるため、なかなか辛くなります。

ただ、モデルファクトリを使用している部分は開発用のデータやテストデータの作成部分であり、直接的にプロダクトとして価値を提供している部分ではないので、ここの移行で ver. 8 へのアップグレードがスタックするのはなるべく避けたいところです。

そこで、Laravel 8 でも、旧式のモデルファクトリの記述を使えるように、パッケージが提供されています。

laravel/legacy-factories
https://github.com/laravel/legacy-factories

ある程度運用されているシステムであれば、基本的にアップグレードで変更や調整が必要となる箇所はモデルファクトリだけではないと思うので、ひとまずこのパッケージを導入してモデルファクトリ以外を Laravel 8 に対応させてしまい、その後でこの部分だけを対応させていくという流れが良いかなと思いました。

ちなみに試してみたところ、Laravel 8 でも以前のモデルファクトリが問題なく動作しました。ただし旧記述と新記述を共存させることはできなかったので、新しい記述に対応する際にはパッケージを削除して一気に全てのモデルファクトリを修正する必要がありそうです。

まとめ

Laravel 8 で刷新されいい感じになったモデルファクトリ。開発用のデータやテストデータの作成って作成元も利用側も散らかり傾向になりがちなので、これですっきりまとめて見通し良く開発していきたいですね。

リモートワークを円滑にすすめるための実践とツール

こんにちは皆さん。 niisan-tokyoです。

現在の新型コロナの脅威の中で、弊社も当然のごとくリモートワークに移行しまして、引きこもり傾向のある私としては特に苦もなく移行は出来たのですが、チームとしてリモートワークを進めるには、やはりいろんな困難があるわけです。 今回は、我々agent bank 開発チームが、リモートワークを円滑に進めるために実践していることと、それに伴って使っているツールなどを紹介します。

リモートワークを円滑にすすめる実践

チーム全員リモートにする

これは意外に大事なのですが、リモートワークをする場合は、全員をリモートにし、一部の人だけリモート、みたいな状況を作らないほうがいいです。 リアルで顔を合わせるメンバーがいると、それ以外のメンバーをどうしてもないがしろにしてしまいますし、リモートメンバーはその状況を改善するために、余分に自分の状況をアピールする必要があり、その結果としてメンバー間のコミュニケーションに齟齬が生まれたり、疲労が溜まったりします。

働き方改革なんかで、リモートか出社かを選べる状況もあるかとは思いますが、少なくともチーム単位では、全員リモートか全員出社かのどちらかにしたほうが、楽だと思います。

一つのツールにのみ依存しないようにする

リモートワークを進める上で、ボイスチャットなどのツールを使うと思うのですが、一つのツールに依存してしまうと、それが使えないとなったときに業務が止まりますので、複数のツールで業務ができるようにしておくべきです。

例えば、我々のチームだと、基本的にはボイスチャットに使用しているdiscordで画面共有もやっているのですが、discordの画面共有と何故か相性の悪いメンバーがいて、そのメンバーたちは画面共有を google meet でやっていたりします。

情報共有の場を意図的に設定する

開発チームの仕事は開発なのですが、開発するにあたっては、仕様・実装のイメージ・運用のイメージ・現在の成果状況といったものについて、メンバー間で共通認識が取れていないと、開發が滞ったり、手戻りが発生したりして、効率も品質も落ちます。

我々のチームだと主に朝会と夕会で現状の認識を合わせるようにしています。

朝会のアジェンダは以下のようになっています。

  • 新規タスクの確認: 前日からの持ち越し・分離タスク、差し込まれたタスクなどの確認
  • その日一日でやることの設定: 何を、どのように進めて、どこまでのタスクをこなすかを決める

一方、夕会のアジェンダは以下のとおりです。

  • タスクの実施状況: 朝会で設定した内容に対しての進捗と現在の成果状況を確認、遅延・想定外状況の発生したタスクのチェック
  • 振り返り: 翌日以降の開発を良くするための案出し
  • ゴール決め: 各メンバーの当日作業の終了目標を決める。持っているタスクが大きすぎれば、タスクを分割してその日終わらせるものと翌日に持ち越すものを分離

このように、定期的に場を設けて、少なくとも一日に一回は現状把握する時間を作っています。

リモートに役立つツール

ここからは我々のチームで使っているツールを紹介し、どんなふうに使っているのかを簡単に見ていきます。

discord

discord.com

ゲームとかでよく使われているボイスチャットのアプリです。 画面共有もできるようになっており、ペアプロや夕会などの定期的なミーティングにもこれ一つで行けてます。 基本ボイスチャットなので、メンバーの顔を見たい場合はgoogle meet のほうを利用しています。

肝心の使い方ですが、基本的には以下のようにしています。

  • 基本的に全員同じチャンネルで作業
  • 一日中繋ぎっぱなし
  • 違うタスクをしているメンバーの音量を下げる
  • ドライバーは、画面共有できる場合はする

全員同じチャンネルで作業しているのは、情報の齟齬が発生したときに、それをキャッチしやすくするためであるのと、互いに質問や状況を聞きやすくするためです。チャットを切る必要もないので、休憩時間以外は常につないでいます。

f:id:niikura23:20201104123614p:plain
別タスクメンバーの音量を下げる

ただし、現在のタスクとは違う内容の会話が混じると進めにくいので、違うタスクのメンバーについては音量を下げ、会話が混じらないようにしています。

なお、PC側で重たい処理( ビルドとか ) が走っている状況だと、discordの音声処理が遅延するのか、音が遅れたり、ハウったりするので、そういう場合は一旦マイクを切って、重たい処理が終るのを待ちます。

Google Meet

apps.google.com

Google Meetはリモート会議をするためのツールで、ブラウザで動かすことが出来ます。 音質はdiscordのほうがいい感じですが、Google Calendar と連携させて、インスタントな会議を設定することが出来ます。 このため、我々は主に他部署のステークホルダとのリモート会議で使用しています。

f:id:niikura23:20201104125345p:plain
カレンダーとmeetの連携

カレンダーをおさえて、あとは、meetに来てくれればオッケーみたいな感じです。

あと、前述したとおり、discordで画面共有が出来ない場合、meetで画面だけ共有して、音声はdiscordでやってます

Miro

miro.com

Miroはオンラインで使えるホワイトボードです。 前回も紹介しましたが、我々はストーリー詳細化という、開発するべきことを詰めていく作業があるわけで、この作業において、ホワイトボードのように使えるMiroを使っています。

techblog.roxx.co.jp

Miroは可視化ツールとしてはかなり優秀で、おかげで代替が難しい面もあります。 我々のチームでも多くのケースで使われており、実例を上げてしまうと、ストーリー詳細化などは全面モザイクになったりしますので、難しいですが、使用例の一部を紹介しておきます。

振り返り

f:id:niikura23:20201104131842p:plain
振り返りの例

振り返りでもMiro使ってます。 我々が現在使っている手法は普通のKPTAになっています。

ワイヤーフレームの作成

ストーリー詳細化の際にワイヤーフレームを作ることがあり、そこでもMiroは活躍しています。 特に、既存のUIに対する変更においては、クリッピングが役に立ちます。

f:id:niikura23:20201104143541p:plain
クリッピングの様子

クリッピングした画像同士の間に、新しい要素を入れると、簡易的ですが既存UIに対する追加を表現できます。

まとめ

リモートワークに移行してはっきり認識できたことは、チーム内、もしくは関係者間で共通認識を持てるかどうかが、物事をすすめる上では大事であるということです。 本記事の実践していることにおいても、使っているツールにおいても、いかにお互いの認識を合わせられるかを主眼にしています。

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

おまけ

弊社では開発チームのメンバーを募集しています。 ちょっとでも興味があって、話くらいなら聞いてやるか!っていう方は私のtwitterにでも連絡くれると、とてもありがたいです。

twitter.com

PhpStormを使ってリモートでペアプロを実現する(Code With Me)

こちらのブログは個人ブログと同じ内容です

www.ritolab.com


ペアプロといえば1つのキーボードをペアで共有してコーディングを進めていったりしますが、リモートワークが加速している昨今では、メンバーが離れていて同じ場所にいない事も多々あると思います。

リモートでもペアプロを実現するために何か手はないかと探していたところ、JetBrains の IDE で実現できるとのことで試してみました。

今回は、PhpStorm を使ってリモートでペアプロを実現してみたいと思います。

Code With Me

PhpStorm を使ってリモートでペアプロを開始するためには、2020 年 9 月にリリースされた Code With Me というツールを使用します。

https://plugins.jetbrains.com/plugin/14896-code-with-me (執筆している 2020/10/20 時点では「早期アクセスバージョン」という取り扱いになっているようです)

今回の目的はペアプロですが、モブプロ含め、共同編集を可能にするためのツールという位置づけになっています。

  • IDE のバージョンは 2020.2.x(2020.2.1以降)から使用可能
  • 早期アクセスバージョンだからうまく動作しないところもあるかもしれないので自己責任で利用する
  • 早期アクセスバージョンは無料で使えるが将来的には有料になるかもしれない

導入手順については、以下のリリース記事を参考にして、こちらの手順で行っています。 https://blog.jetbrains.com/ja/idea/2020/09/code-with-me-eap-ja/

ペアプロなので、2台の PC があるとして、それぞれ、ペアプロを行うためのソースコードを持っている PC をホスト、そのソースコードについてペアプロで参加する PC をゲストと呼んでいます。

IntelliJ Client を使ってホストのプロジェクトへゲストが接続することで共同編集が可能になるようです。(ホストがゲストを招待する)

Code With Me の導入

まずは、ホスト側 IDE に Code With Me プラグインをインストールします。ゲスト側は、IDE が入っていればプラグインがなくても大丈夫なようです。

phpStorm の設定画面( Preferences > plugins )にて MarketPlace から Code With Me をインストールします。 f:id:ro9rito:20201020152651p:plain

インストールが完了すると、IDE の再起動を促されるので PhpStorm を再起動します。 f:id:ro9rito:20201020152729p:plain

再起動が完了し画面が開くと、右上に Code With Me のプルダウンが表示されるようになっているので、これで Code With Me のインストールは完了です。

招待リンクの作成

次に、ホスト PC から、ゲスト PC をペアプロに招待するための招待リンクを作成します。

Code With Me のプルダウンから、Enable Access and Copy Invitation Link を選択します。 f:id:ro9rito:20201020152802p:plain

確認画面が表示されるので、Continue ボタンを押下するとリンクが生成され、クリップボードにコピーが行われます。 f:id:ro9rito:20201020152824p:plain

コピーされた通知が若干わかりにくいのですが、再度、コピーしたい場合は、Code With Me のプルダウンから Copy Invitation Link を押下すれば再度 URL をコピーできます

これで招待リンクを作成できました。これをゲストへ共有して、ペアプロのための接続を進めていきます。

ゲストからプロジェクトへ接続する

ホストが生成した招待リンクをゲストで受け取ったら、ホストのプロジェクトに接続するためのリクエストを送ります。

ゲストにて、ブラウザから招待リンクにアクセスします。 f:id:ro9rito:20201020153255p:plain

ホスト側のプロジェクトに接続するために、ターミナルでコマンドを実行するように促されるので、表示されているコマンドをコピーし、ターミナルで実行します。

コマンドを実行すると、IntelliJ Client のダウンロードや接続のリクエストが行われます。このタイミングで、ホストの IDE に接続を許可するかを確認するダイアログが表示されます。 f:id:ro9rito:20201020153315p:plain

OK の場合は Accept ボタンを押下すると、ゲスト側の IntelliJ Client が起動して、ホストのプロジェクトに接続されます。

これでホストとゲストが繋がりました。

動作確認

つながったホストとゲストで動作確認をしてみます。 f:id:ro9rito:20201020153354p:plain

画像なのでちょっとわかりづらいですが、自分のカーソルとは別に相手のカーソルも出てきていて、共同でファイルを編集できるようになっています。

これで、リモートでペアプロを実現する事ができるようになりました。

まとめ

リモートでもペアプロができるなんて素敵な世の中になりました。しかも PhpStorm で簡単に実現できるのはうれしいですね。

JetBrains の IDE ならできるのだと思うので、WebStorm とかでもおそらく同じ手順でいけるんじゃないかと思います。

いくつかの詳細な操作や設定は以下に記載があったので併せて確認すると良いと思います。
https://www.jetbrains.com/help/idea/code-with-me.html

2020 年の 12 月に Code With Me が JetBrains IDE v2020.3 に含まれる的な事も書いてありました。
https://youtrack.jetbrains.com/issue/IDEABKL-708#focus=Comments-27-4365766.0-0

まだこの環境を使って共同作業をがしがし行ってはいないので、実際に使いながら不便に感じる点も出てくるのかなとは思いつつ、有料化される頃には色々改善されたりしていそうだし、これだけ便利なら課金もありだなと思いました。

ストーリー詳細化を交えた agent bank 開発チームの現在の開発手法

こんにちはみなさん ROXXのagent bank開発チームのniisan-tokyoです。

開発手法は、それこそ各チームで千差万別でだと思います。そこには、メンバーのスキルの特性や事業・プロダクトの性格など様々な事情が入り込んでいます。 メジャーなフレームワークであるスクラムも、スクラムガイドとセレモニーはあれど、細かい進め方は各チームによって変わるのではないかと思います。 今回は我々のチームの現在の開発手法を解説しますので、皆様の参考になればと思います。

現在の開発の流れ

現在の我々は一週間をスプリントの単位としており、その流れは以下のようになっています。

f:id:niikura23:20201014151453p:plain
1スプリント(一週間)の開発の流れ

ある程度はスクラムの流れを踏襲しているのですが、割と特徴的なやり方がふくまれているので、その点を解説します。

ストーリー詳細化

これはバックログにあるユーザストーリーをタスク分解できる状態に持っていく作業となります。 ここで詳細度を上げたストーリーを金曜日のプロダクトバックログリファインメントで扱うようにします。

ストーリー詳細化の詳しい説明はあとに回しますが、基本的には月曜日と火曜日に実施し、水曜日と木曜日は予備日に当てます。 担当者はスプリントごとに変わりますが、月曜・火曜で完了したのであれば、スプリントタスクに合流します。

スプリントのタスク

基本的には実装のタスクとなります。 ここで実現されたユーザーストーリーを、インクリメントとしてスプリントレビューに持ち込みます。

スプリントレビュー

実現できたストーリーをステークホルダに見せて、出来た機能と運用のイメージを解説し、フィードバックをもらいながらリリースのタイミングについて相談します。

振り返り

スプリントを振り返り、次のスプリントをより良くするために取るべきアクションを決めます。 我々のチームでは、オーソドックスな KPTA(Keep, Problem, Try, Action) を採用しています。

リファインメント

プロダクトバックログリファインメントのことですが、我々のチームではユーザーストーリーの詳細度を上げ、プランニングレディな状態に持ち込むためのイベントになっています。 我々のチームではマルチチームリファインメントというやり方を導入しており、この手法は以下のようなタイムボックスを設定して進められます。

  • 15min: ストーリーごとに小チームに別れ、ストーリーについての質問事項と、ストーリーを実現するためにやるべきことのリストを作成する
  • 20min: すべての小チームが集まり、各小チームごとに質問をPOに行い、やることを解説して他のチームからの質問・指摘を受け付ける
  • 15min: 再び小チームに別れ、ストーリーに対するやるべき事の追加・修正を実施し、新しい質問事項を集める。また、可能であればこの時点でストーリーポイントをつける
  • 20min: 再びすべての小チームが集まり、各小チームごとに質問をPOに行い、やることを解説して他のチームからの質問・指摘を受け付ける
  • 10min: やるべきことの修正を行い、まだであればストーリーポイントを付け、ストーリーを実現するためのタスクを作成する

プランニング

次のスプリントで、どのくらいストーリーを実現できるか、どの程度のタスクを実行できるかを見積もり、どのように進めるかを議論します。 これは、以下のように進めています。

  • POによる優先度に基づく並べ替え ( ざっくりストーリーごと単位 )
  • ストーリーに紐付かないタスクにポイントを付ける ( ストーリーから分割したタスクはポイントを付けない )
  • タスクをより効率に進めるために、優先度を壊さないレベルでの並べ替えと進め方の議論
  • 各日にちごとのチェックポイントを作成

チェックポイントは実際のタスクとして作っておいて、各日毎の進捗の様子を可視化できるようにします。

f:id:niikura23:20201014155952p:plain
Jira上でチェックポイントの作成

ユーザーストーリーの詳細化

我々の開発で大きな特徴があるところとすると、スプリント内にこのユーザーストーリーの詳細化を入れているところです。 これは設計のような作業で、なにをやればいいかの、どうなったら完了になるかを定義し、ユーザーストーリーをリファインメント可能な状態にする作業になります。

ユーザーストーリー

ユーザーストーリーとは、プロダクトに追加したい機能を、「〇〇が□□する」というユーザーの行動で記述したものになります。 例えば、プロダクトにメモ機能を導入したい場合は「ユーザーはプロダクト上でメモを残す」のようなストーリーが作られます。

我々のプロダクトにおいて、ユーザーストーリーは述語だけでなく主語もとても大事です。 というのも、我々のプロダクトである agent bank は、求人DBという性格上、主語となりうる人物が、エージェント様、求人企業の人事様、そして弊社の運営事務局と、少なくとも3者以上いるわけです。 そして、主語が誰になるかで作るものもガラリと変わります。 例えば、通知機能一つとっても、エージェント様であればメールでの通知になりますが、運営事務局だとslackになるなどですね。

なぜユーザーストーリーの詳細化の時間をとるのか

本当に大雑把なストーリーはPOが作ります。 しかし、現在POはほとんどコードをさわらないのと、圧倒的に時間が足りないということから、ある程度実装イメージが付いて、どこまで詳細化すればリファインメントできるかがわかる開発チームが、詳細化を担当することになります。 この作業にはまとまった時間が必要になりますので、開発チームから数人選出して、ストーリーの詳細化を集中的に実行するようになりました。

ユーザーストーリーの詳細化の作業内容

ユーザーストーリーの詳細化の方法は、ストーリーによって大きく変わりますが、リファインメントするのに必要な作業としては以下のようなものになります。

  • 背景の整理
  • 受け入れ要件の作成 ( 完了条件の定義 )
  • 画面イメージの作成
  • 指標の策定
  • ステークホルダとの事前共有

背景の整理

ストーリーを実現すると、何がいいのかというのを整理します。これをやっておかないと、どういった戦略に基づくものなのか、どのようなことができればよいのか、誰がステークホルダなのかが何もわからないです。 あらゆる作業の土台になるため、凄まじく重要です。

受け入れ要件作成、画面イメージの作成

受け入れ要件は、ユースケースのリストで定義され、これがすべて満たされれば、ストーリーを実現できたとみなせるものです。 例えば、「ユーザーはメモを残せる」というストーリーの受け入れ要件を考えると、

  • ユーザーはメモ作成画面でメモを入力し送信する
  • ユーザーはメモ閲覧画面でメモを閲覧できる
  • ユーザーはメモ閲覧画面からメモ編集画面に移動できる
  • ユーザーはメモ編集画面でメモを編集できる

といった感じです。 また、これらとともに画面イメージも作成し、実際にユーザがどのように使うかをシミュレートできるようにします。

指標の作成

ストーリーが実現できても、ちゃんとユーザーが使って価値を届けられているかわからないといけないので、上手く行ったかどうかを評価するための指標を作成する必要があります。 先のメモを残すであれば、メモを残しているユーザーが全体の何%いるか、などですね。

ステークホルダとの事前共有

これからこんな物を作りますよ、というのをステークホルダの方々に共有します。 作ったはいいが、いざレビューで「なにこれ?」ってなったら大変なので、実装する前段階でステークホルダと共有し、フィードバックをもらっておきます。

現在の所感とまとめ

というわけで、現在我々が開発の流れを、大雑把に解説しました。

今のところはいい感じにワークしているようにも思いますが、これがベストかと言われると全くそういうわけでもなく、なにか新しいトライがでてくればどんどん試していきたいと思っています。

とはいえ、ベストの手法なんて、本当にあるのでしょうかね?私としては、各チームごとの特性に従って、改善を繰り返して限りなくベストに近づけていくというのが、やはり常道だなって思っています。

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

おまけ

今の状況下でも弊社はエンジニアの採用もやってます。 リモートでカジュアル面談出来ますので、興味ある方は私のtwitterにDMなりメンションつけてコメントしてくれれば、つなぎますので、お気軽にどうぞ。

twitter.com