はらへり日記

腹に弾丸

PHPUnitで例外を投げた後の処理をテストする

例えばこんなクラスコードがあるとする。

<?php

namespace App;

use Psr\Log\LoggerInterface;
use App\Util\DB;
use App\Exception\DbException;

class BlogService
{
    /** @var DB */
    protected $db;

    /** @var LoggerInterface */
    protected $logger;

    public function __construct(DB $db, LoggerInterface $logger)
    {
        $this->db = $db;
        $this->logger = $logger;
    }

    public function createNewPost(int $userId, string $title)
    {
        $this->db->begin();

        try {
            $postRow = $this->db->insert('posts', [
                'title' => $title,
            ]);

            $this->db->insert('post_user_relations', [
                'user_id' => $userId,
                'post_id' => $row['post_id'],
            ]);

            $this->db->commit();
        } catch (DbException $e) {
            $this->logger->warning($e->getMessage());
            $this->db->rollback();

            throw $e;
        }

        return $postRow['post_id'];
    }
}

ブログを投稿する簡単なクラス。

INSERT文を2つ発行するのでトランザクションを貼って、もしデータベース例外が発生したらloggingした上で例外を投げ直すというよくあるコード。

これに対してテストコードをPHPUnitで書いてみる。

テスト方針

スタンダードに以下のようなテストケースを書いてみる。

  • INSERT処理が2回とも通り、期待する結果を返せばpost IDを返す
  • INSERTがどちらかコケたらログを取った上で例外を投げる

上記のクラスはきれいにDIパターンで実装されているのでいずれのテストもモックを注入し、振る舞いを指定することでロジックの保証をするテストが書ける。

後者のテストを実装するならこんな感じ。

<?php

namespace App;

use Mockery as m;
use Psr\Log\LoggerInterface;
use App\Util\DB;
use App\Exception\DbException;

class BlogServiceTest
{
    /** @var DB */
    protected $db;

    /** @var LoggerInterface */
    protected $logger;

    public function setUp()
    {
        $this->db = m::mock(DB::class);
        $this->logger = m::mock(LoggerInterface::class);
    }

    /**
     * @expectedException DbException
     */
    public function testCreateNewPostSholdThrowExceptionWithLogging()
    {
        $this->db->shouldReceive('insert')->andThrow(new DbException());
        $this->db->shouldReceive('rollback')->once(); // Rollbackすることをテスト
        $this->logger->shouldReceive('warning')->once(); // Loggingすることをテスト

        $SUT = new BlogService($this->db, $this->logger);

        $SUT->createNewPost(1235, 'title');
    }
}

色々はしょってるけどだいたいこんな感じになると思う。

ただ、現実のサービスはこんな単純な仕様であることはほぼない。

例えばここに追加仕様で「例外を投げたらloggingし、rollbackし、エラーをテキストファイルに吐き出すようにする」みたいなものが来たとする。(いい例えが思いつかなかっただけで、現実的にはもっと別のケースがあるでしょう)

こんなコード。

<?php

    /** ~ */

    public function createNewPost(int $userId, string $title)
    {
        $this->db->begin();

        try {
            $postRow = $this->db->insert('posts', [
                'title' => $title,
            ]);

            $this->db->insert('post_user_relations', [
                'user_id' => $userId,
                'post_id' => $row['post_id'],
            ]);

            $this->db->commit();
        } catch (DbException $e) {
            $this->logger->warning($e->getMessage());
            $this->db->rollback();
            // ここが追加
            file_put_contents('path/to/file.txt', json_encode($e));

            throw $e;
        }

        return $postRow['post_id'];
    }

これに対して先程のテストコードだと「エラーをテキストファイルに吐き出す」ことの確認ができない。

しかしファイル書き込みロジックは外部注入していないのでモックすることもできない。

そこでこんなテストを書いてみるとする。

<?php

namespace App;

use Mockery as m;
use Psr\Log\LoggerInterface;
use App\Util\DB;
use App\Exception\DbException;

class BlogServiceTest
{
    /** @var DB */
    protected $db;

    /** @var LoggerInterface */
    protected $logger;

    public function setUp()
    {
        $this->db = m::mock(DB::class);
        $this->logger = m::mock(LoggerInterface::class);
    }

    public function testCreateNewPostSholdThrowExceptionWithLogging()
    {
        $this->db->shouldReceive('insert')->andThrow(new DbException());
        $this->db->shouldReceive('rollback')->once(); // Rollbackすることをテスト
        $this->logger->shouldReceive('warning')->once(); // Loggingすることをテスト

        $SUT = new BlogService($this->db, $this->logger);

        // 例外投げられるとチェックできないからcatchする
        try {
            $SUT->createNewPost(1235, 'title');
        } catch (DbException $e) {
            $this->assertTrue(file_exists('path/to/file.txt'));
            return;
        }
   
        $this->fail();
    }
}

例外を投げられた後だとプログラムが続行できないので例外をcatchしてassertionする。

また、例外をcatchできなかったらテストをコケるようにして例外を投げることを保証する。

これで一件落着、と思いきやこの書き方には2つ問題がある。

  • テストの書き方によってはスタックトレースが表示されず何がなんだか分からない
  • テストの中にロジックが混ざって可読性が下がる

前者に関しては以下のようなテストの書き方をしてると起きる。

<?php

// 例外投げられるとチェックできないからcatchする
try {
    $SUT->createNewPost(1235, 'title');
} catch (\Exception $e) {
    $this->assertInstanceOf(DbException::class, $e);
    $this->assertTrue(file_exists('path/to/file.txt'));
    return;
}

こんな書き方しなきゃいいじゃんって話なんだけど、時たまやるしテストやロジックが複雑化していけばなおさら起きる。

とはいえこういうことは往々にして起きるのでもう少し違うアプローチを考えることにした。

テストを分ける

シンプルにテストを分けてコケたときのエラートラッキングを分けるようにする。

具体的には「意図した例外が投げられること」と「例外が投げられた後の状態が望みどおりであること」の2ケースに分ける。

イメージ的にはこんな感じ。

<?php

namespace App;

use Mockery as m;
use Psr\Log\LoggerInterface;
use App\Util\DB;
use App\Exception\DbException;

class BlogServiceTest
{
    /** @var DB */
    protected $db;

    /** @var LoggerInterface */
    protected $logger;

    public function setUp()
    {
        $this->db = m::mock(DB::class);
        $this->logger = m::mock(LoggerInterface::class);
    }

    /**
     * @expectedException DbException
     */
    public function testCreateNewPostSholdThrowExceptionWithLogging()
    {
        $this->db->shouldReceive('insert')->andThrow(new DbException());
        $this->db->shouldReceive('rollback')->once(); // Rollbackすることをテスト
        $this->logger->shouldReceive('warning')->once(); // Loggingすることをテスト

        $SUT = new BlogService($this->db, $this->logger);

        $SUT->createNewPost(1235, 'title');
    }

    public function testCreateNewPostSholdPutFileAfterThrowException()
    {
        $this->db->shouldReceive('insert')->andThrow(new DbException());

        $SUT = new BlogService($this->db, $this->logger);

        try {
            $SUT->createNewPost(1235, 'title');
        } catch (DbException $e) {
            // 例外をthrow後の状態をテストする
            $this->assertTrue(file_exists('path/to/file.txt'));
        }
    }
}

こうすることで意図しない例外が発生しても原因の切り分けが多少やりやすくなる。

また、もう少し改善する点としてこの2ケースは前者がコケた時点で後者をテストする必要がない。

そこで@dependsアノテーションを利用して前者がコケたら後者がコケるようにする。

<?php

namespace App;

use Mockery as m;
use Psr\Log\LoggerInterface;
use App\Util\DB;
use App\Exception\DbException;

class BlogServiceTest
{
    /** @var DB */
    protected $db;

    /** @var LoggerInterface */
    protected $logger;

    public function setUp()
    {
        $this->db = m::mock(DB::class);
        $this->logger = m::mock(LoggerInterface::class);
    }

    /**
     * @expectedException DbException
     */
    public function testCreateNewPostSholdThrowExceptionWithLogging()
    {
        $this->db->shouldReceive('insert')->andThrow(new DbException());
        $this->db->shouldReceive('rollback')->once(); // Rollbackすることをテスト
        $this->logger->shouldReceive('warning')->once(); // Loggingすることをテスト

        $SUT = new BlogService($this->db, $this->logger);

        $SUT->createNewPost(1235, 'title');
    }

    /**
     * @depends testCreateNewPostSholdThrowExceptionWithLogging
     */
    public function testCreateNewPostSholdPutFileAfterThrowException()
    {
        $this->db->shouldReceive('insert')->andThrow(new DbException());

        $SUT = new BlogService($this->db, $this->logger);

        try {
            $SUT->createNewPost(1235, 'title');
        } catch (DbException $e) {
            // 例外をthrow後の状態をテストする
            $this->assertTrue(file_exists('path/to/file.txt'));
        }
    }
}

これで完璧!!

テストを書くのは楽しいね。

PHPカンファレンス福岡2017に登壇しました

行ってきました

総じて最高でした。

トークをしました

JavaScriptの静的型解析ツールであるflowの話をした。

色々反省もありつつも、今年の目標として掲げていたPHPカンファレンストークでの登壇を達成できたこと。

トーク後の質問タイムで発表にお褒めの言葉を頂いたり僕が伝えたかったことが伝わっていることを質問から感じ取れる場面があり素直に嬉しかった。

30分で申し込みましたが結構見積もりが甘くて、実際は伝えたいことのいくつかを削った。

そこらへんの補足や+αの話は会社の技術ブログの方に投稿する予定なので投稿したらここに貼っつけます。

福岡は飯が最高

先日、弊社の福岡オフィスが新設されたのでそこの方との交流を目的に木曜日から福岡入りしていました。

なので観光のため業務の一環でいろんなご飯を食べてた。

これがもれなく美味い。全部美味い。

食べたのは博多ラーメン(2回)、うどん(2回)、魚介丼、ごま鯖、といった感じ。

まぁなにはともあれ私は食レポマンではないので、福岡に足を運ぶことがあればとりあえずここに足を運んで欲しい。

retty.me

retty.me

福岡というと博多ラーメンのイメージがあるが福岡の人に言わせると行列に並んでラーメン食べるならうどんを食えという話があるらしい。

事実、2回食べたうどん屋さんはどちらも美味しかった。

前者は駅から10分もかからないところに。後者はLINEさんが入ってるビルの地下にある。

どちらでもごぼ天肉うどんを食べたが本当に美味しいので福岡に行く人は騙されたと思って食べて欲しい。

参加してみて

当たり前の話だけどやっぱりカンファレンスに来るといろんな刺激がもらえるし、今回は東京から飛び出しての参加だったのでなお刺激的だった。

福岡のエンジニアさんはみんな優しくて、3次会まで飲みに行って色んな話をした。

Twitterでお見かけする人とコミュニケーションが取れたり、逆に「きりんさんですよね」と話しかけられることもあったりして人脈も前よりは広がってきてる感じがする。

今まで頑張ってアウトプットしていたかいもあったしこれからも続けて行きたい。勇気を出してトーク応募してよかった!

次は

トークはだいぶ慣れてきたのでもっと質を上げていくことを目標に頑張りまっす。(あとはPHPの話もしたいですね)

まとめ

福岡のみなさん本当にありがとうございました!また飲みましょう!

flowtypeからweakモードが無くなってた

weakモードとは

flowtypeの説明は割愛します。

知りたい方は福岡PHPカンファレンスに来て私のトークを聞いてください💕

冗談はさておき、ざっくり言うとflowtypeはFacebook製のJavaScript用静的型解析ツールです。

ファイルの頭に// @flowを書き足すことでそのファイルをflowtypeの解析対象とすることができます。

一昔前(と言っても2, 3ヶ月前程度)はこの時に// @flow weakと書くことによりweakモードでの解析が可能でした。

このweakモードは引数や返り値の型宣言が無いことを許容し、PureなJavaScriptを解析することができるものです。

このweakモードが現在は無くなっています。

なぜなくなったのか

詳しい経緯は以下のIssueを読んで下さい。

github.com

噛み砕くと

  • いつの間にか// @flow weakと書いてもweakモードで実行されなくなった
  • Issuer「なんで消えたの?便利だったのに!」
  • Maintainer「確かに一定の場面では便利なんだけどメンテナンスをちゃんとしてないこと、weakモードの時に何をチェックしてるか不明瞭だから無くしたよ」
  • Maintainer「とはいえGitHubで検索してみると4000以上のコードで使われてるし破壊的な変更はなるべくしないよ」

といった感じです。 実際、リリースタグを見るとv0.40.0このリリースではまだweakモードは消さないよと言っています。

で、今はどうなのか

具体的にどのバージョンで変更が加わったのか見つけられなかったのですが(知ってる人いたらこっそり教えてください)、現在はweakモードは存在しません。

当然ドキュメントにも書いてありません。

個人的な意見として、weakモードの嬉しいところは既存のコードを書き換えずにflowtypeの解析の恩恵を受けられる点だと思っていたのでプロダクションコードにflowtypeをねじ込もうとしてた身としては悲しかったのですが、安心してください。

現在は通常のモードでも型の明示的宣言を強いられることはありません。

なので従来通り、既存のコードに導入しやすいというメリットは残っていると思います。

やったぜ。

eslint-plugin-importでNODE_PATHにパスを足したい

NODE_PATHを足す

JavaScriptを書くとき、ディレクトリを掘って階層が深くなったりテストフォルダが別にあるとrequire('../../../../hoge');みたいになってつらい

なのでWebpackで固める場合はconfigで、サーバサイドjsの場合はapp-module-pathを使ってNODE_PATHを足している。

node_modules配下のフォルダ名を上書きしてしまう恐れもあるがまさかlodashなんて名前でフォルダは作らないだろうし今のところそういう意味で困ったことは無かった。

が、eslint-config-airbnbを利用した際、それに含まれるeslint-plugin-importを使う時にパスを上手く解釈してくれなくて詰みかけた。

その時のエラーはこんな感じ。

直し方

Pathを足すための設定が公式で用意されてた。

github.com

例えばsrc/をNODE_PATHに足しているなら.eslintrcにこう書く

{
  "settings": {
    "import/resolver": {
      "node": {
        "paths": ["src"]
      }
    }
  }
}

これで例えばsrc/hoge.jsrequire('hoge')していてもESLintがきちんと解釈してくれるようになる。

メルカリに入社して1ヶ月経った

1ヶ月過ごしてみて。ポエムです悪しからず。

楽しい

結論から言うと毎日楽しいです。

社内の雰囲気や制度については事前に社員さんとお話させてもらったり、mercanを読んで知ってたんだけど、入る前の想像とのギャップはほぼ感じずに過ごしてる。

強いて言うならコードベースはレガシーとまでは行かなくとも、死ぬほどキレイというわけではないなぁという気持ちはあるけどhirakuさんがmercari dayで発表していたようにプロダクトファーストの現れだと思うと全然違和感はない。

というよりむしろこれだけのスピードでやってきたらもっと崩壊しててもいいのでは?と思うくらいには普通に触れる。楽しい。

緊張感の所在の変化

転職して一番変わったのは自分がプレッシャーを感じるポイントかなと思っている。

メルカリの組織の回し方は"性善説"の一言に尽きていて、稟議や管理という概念がほとんどない(今のところ僕は出会ってない)。

そうすると何が起きるかって言うと、プロダクトをよくするために開発に集中するときの障害が一切なくなる。

会議も無いし説得も無いし細かい進捗管理もない。

これって一見、楽に見えるんだけど実際やってみるとプレッシャーがすごい。

一人のエンジニアとして完全に信頼されているからこの働き方をしていると思うと指示待ち人間に成り下がるのは論外だし、いかに短い時間の中で成果を出すのかを考えるようになる。ひたすら考える。

そしてそれがまだまだできていないなぁと焦る。

私は甘やかされると無限に甘える人間なので今のこの環境は本当に合っていて、毎日学びも多いしひとまず1人前になるために必死です。

スピード感がすごい

会社全体の流れを週一で共有する場があるんだけど、他のチームやUS, UKの動きを見てるとスピードがすごい。

し、現場のスピードもすごい。確か入社4日目くらいで自分の開発分をリリースできるぐらいには走る環境が整ってる。

そして入社してまだ1ヶ月なのにアメリカ出張に行くなど、ここらへんもすごい。

出張が決まったのは出張2週間前くらいで急いでパスポート取ったりした。

なんだかんだ、会社の規模は300人を越えているので多少はもたもたする部分もあるだろうと思ってたけど今のところ全く感じないし、なんというか社員がみんな優秀がゆえに成り立っているんだなとしみじみ感じる。

まとめ

おすすめのアメリカ観光地教えてください。サンフランシスコから2時間以内に行ける場所で。

CTF for ビギナーズ Finalに参加してきました(Write up)

CTFはかじったことはあったけどどんな解き方があるかとか全然わからなかったので参加してきた。

2016.seccon.jp

講義のおかげか、運良くぽんぽん問題が解けて3位になれました。

講義も演習も楽しく取り組むことができました。運営の皆様お疲れ様でしたm( )m

Write up

自分が解けて覚えてるものだけ書いておきます。

解けなかったのはファイル持ち帰ったので時間あるときに取り組んでまたWrite up書きたいと思います。

解けた問題はこんな感じ。

f:id:sota1235:20170129233111p:plain

Misc 100 Welcome

解くだけ。サービス問題。

Misc 200 CountUp Game

問題文の案内通り、サーバに接続するとゲームが始まる。

お互い、3つまで数字を言って21を言ったら負けなゲーム。

ただし必ず相手が先攻で始まる。

問題数が多かったらプログラムで自動化しないと無理だったけど10問だったので手動で解いた。

基本的に相手に5, 9, 13, 17を言わせるようにすれば必ず勝てるのでその通りに解いていく。

Web 100 Classical Injection

ユーザ名とパスワードを入れるテキストフォームが表示される。

データベース系と見てシングルクォーテーションを入れるとエラーが表示される。

そこで以下のようなSELECT文が実行されると仮定してみる。

$sql = "SELECT * FROM users WHERE loginid = '${loginid}' AND password = '${password}'";

そこでユーザ名のテキストフォームに' OR 1 = 1;--と打ち込んで後半の条件文を無視するようにインジェクションするとログインができてflagが表示される。

Web 100 May the extensions be with you.

問題忘れてしまいました…誰かWrite up頼むorz

Web 200 もぐもぐ(・~・)

アプリにアクセスすると検索フォームが表示される。

f:id:sota1235:20170129233921p:plain

適当に文字を打って検索すると検索結果がないと言われる。

SQLインジェクションを疑いシングルクォーテーションを入れるとPHPのエラーが表示される。

エラーから読み取るにSQLの実行結果からループで結果を取って表示しようとしてるっぽい。(たしかfetchArray()でエラーがでてた)

とりあえず講義で教わったようにテーブルスキーマを盗めないか試す。

以下の文字列を検索フォームに入れる。

' UNION SELECT null, null, null, null; #

データベースが何かの特定はエラー分からは推測できないのでMySQL, PostgreSQL, SQLiteの可能性を順番に試した結果、MySQLぽかった。

ここでnullの値を変えていくと(確か)4つでページにデータが表示された。

ので次に以下でテーブル一覧を取る。

' UNION SELECT null, table_name, null, null FROM INFORMATION_SCHEMA.COLUMNS; #

これによりsecret_umasugiというテーブルがあることがわかる。

ちなみにtable_nameを2番めにしてる理由は、1番目だと画面に描画されなかったからです。(もしかしたらソースにはあった?)

というわけで以下のテキストで検索してフラグゲット。

' UNION SELECT * FROM secret_umasugi; #

ちなみにたまたまカラム数が一致したので通ったけどもしこれで通らなかったらカラムの名前を調べたりGROUP_CONCAT()あたりで頑張ることになりそう。

Web 300 Easy SQL Injection

ページにアクセスするとLogin ID, Display name, Passwordを入力できる。

また、ログインボタンと登録ボタンが存在する。

とりあえず普通に登録してみるとログインできることがわかる。

問題名が問題名なのでひとまずシングルクオーテーションを入れてみるとDisplay nameのときだけバリデーションエラーのメッセージが微妙に違うことがわかる。

そこでこんなSQLが書かれていると仮定する。

$sql = "INSERT INTO users VALUES ('${loginId}', '${displayName}', '${password}')";

そこでこんな文字列をDisplay nameのフォームに入れて登録を試す。

', 'pass'); --

これで登録が通り、パスワードがパスワードのテキストフォームに入れたものでなく、'pass'になる。(確か)

ちなみにこの時にコメントアウト#にして試せばMySQLでないことがわかるのでSQLitePostgreSQLのどちらかに絞れる。

ここにSQLインジェクションできることはわかったので次にこのDisplay nameにデータベースから抜いた情報を入れられないか試す。

テーブル情報を引き抜くのを講義資料を元にSQLite, PostgreSQL両方試し、結果的にSQLiteだった。

こんな感じ。

'||(SELECT sql FROM SQLITE_MASTER), 'pass'); --

こうしてログインが成功すると最初はDisplay nameに何もでないがもう一回ログインし直すとusersテーブルのスキーマ情報が抜ける。

あとはそれを元にもう一度インジェクションし、ログインし直して情報を抜いてフラグゲット。

ユーザ名をadminにした理由は勘なのでこれが外れたらLIMIT, OFFSET句あたりを使って一行ずつ抜いていくとかになるんですかね。

'||(SELECT password FROM users WHERE loginid='admin'), 'pass'); --

Forensics 100 みつけてみよう

pcapファイルなので中身をWireSharkで開くとHTTP通信を何回かしている。

とはいえ回数多くて全部読むのがめんどくさくてstrings for100.pcap | grep ctf4bしたら1つだけ中身のあるフラグがあった。

Forensics 200 漏洩した情報を特定せよ

この問題が個人的には面白かった。

まずpcapファイルをWiresharkで開き、ざっと見てみる。

特に怪しいものはなかったのでHTTP通信にフィルタするとこんな感じになる。

f:id:sota1235:20170129235815p:plain

通信の流れを見るとクライアントが

  • GET /c2/xor_cipher_key/
  • POST /c2/leak_info/

としてるのがわかる。問題のシナリオを推測すると情報を漏洩させた人は

  • 情報を他社にリークする
  • その際、情報を暗号化して送信した
  • 暗号化の手段はXOR演算

と推測できる。

まずGET /c2/xor_cipher_key/のレスポンスを見るとこんな感じ。

f:id:sota1235:20170130000023p:plain

ffとだけある。これがXOR演算のためのキーだと仮定する。

その後のPOST /c2/leak_info/のリクエストボディを見る。

f:id:sota1235:20170130000111p:plain

���˝��ϊ������̛̛��ύ��̜�����ϑ��というURLエンコードされた値を送信していることがわかる。

先程のシナリオから逆算するとこの値は

  • 情報にffを用いてXOR演算する
  • その値をURLエンコードする

ことによって得られたと仮定できる。

なのでこれを読み解くには逆のことをすればいい。

超適当スクリプトを書いて実行する。

<?php

$data = '���˝��ϊ������̛̛��ύ��̜�����ϑ��';
$bytes = explode('%', $code);

$answer = '';

foreach ($bytes as $b) {
    $num = hexdec($b);
    $const = hexdec('ff');

    $result = $num ^ $const;

    $answer .= '%'.dechex($result);
}

echo $answer;

これで元のデータをURLエンコードした文字列が得られるのであとはこんな感じでコマンドを叩いてフラグゲット!

f:id:sota1235:20170130000601p:plain

なぜかcが抜けてたけど足してSubmitしたら通った。

Binary 100 HiddenFlag

これを終了3分前に焦って解いて、結果的に3位になれた。

3分で解いたという時点でお察しで、こんなコマンド叩いて解きました。。。

strings bin100_1 | grep ctf4b

stringsコマンド便利すぎた。

参加してみて

CTF4Bはすごく前から参加してみたくて、今回予定も合って参加できてよかった。

短い時間の中で講義も丁寧にやってもらって資料も充実していて、演習ではそれが生かせる問題ばかりだった。

とはいえまだまだビギナーの身なので大会か何かあればちょろっと参加してみたい気持ちです。

参加した皆様、お疲れ様でした!

macOS環境下で複数ファイルの先頭に一括で文字を挿入する

やりたかったこと

既存コードにflowを導入したかったのでとりあえず全JSの先頭行に// @flow weakを挿入したかった。

shell

find ./app/assets/js -type f -name '*.js' -exec sed -i "" -e $'1s/^/\\\/\\\/ @flow weak\\\n/' {} \;

詳しい解説はshellで疲弊したので省略します。 とりあえず気をつけなきゃいけないことは

  • macOSsedBSD系なので使い方がCentOSとかのものと全然違う
  • xargsでpipeするとなぜかうまく動かない

という点です。 xargsがおかしくてもう諦めかけたところでfindコマンドの-execオプションを知って解決した次第です。最高。

まとめ

もうしばらくshellは書きたくないね。