殺人事件の解決までを Exception で再現してみる feat. ポートピア連続殺人事件

# これは何？
- 昔の有名な殺人事件を、Exception の try～catch で再現してみます。
- Exceptionの握りつぶしについて「完全に握りつぶす」「１つ以外を握りつぶす」「一切握りつぶさない」の３パターンを使って、徐々に事件の解決に近づきます。
- ソースコード(portpia.php)は最後に全部掲載します。

# はじまり
事件発生！
山川社長が何者かに殺害された。
刑事のヤスが事件解決の為、捜査を開始した。


# 山川社長、文江、ヤスを呼び出す。
new でオブジェクトを作ります。ここではクラス定義は秘密にしておきます。（最後に掲載します）

```php
$objs = [];
$objs[] = new yamakawa();
$objs[] = new fumie();
$objs[] = new yasu();
```

# 聴取１回目：自己紹介させます。
Exceptionに重要発言を持たせています。ここでは、自己紹介するだけなので、Exceptionを完全に握りつぶします。

```php
foreach ($objs as $obj) {
    print "\n";
    try {
        $obj->listening();
    } catch (Exception $e) {
        // 吐かないよう握りつぶす
        ;
    }
}
```

実行結果

```
山川(殺害された人)   >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました！

文江(山川の秘書)     >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです！

ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい！

```

# 聴取２回目：事情を聴いてみます。
重要発言（ようはException）を１つだけ聴いてみます。

```php
foreach ($objs as $obj) {
    print "\n";
    try {
        $obj->listening();
    } catch (Exception $e) {
        // １つだけ吐かせる
        $obj->say($e->getMessage());
    }
}
```

実行結果

```
山川(殺害された人)   >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました！
山川(殺害された人)   >>> 実は、昔、詐欺をやっていたという過去があります

文江(山川の秘書)     >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです！
文江(山川の秘書)     >>> 実は、ヤスは私の兄です

ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい！
ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります
```

# 聴取３回目：真実を吐かせてみる
全ての重要発言、ようは全てのExceptionを聴いてみます。ネストしたExceptionを全て展開しているだけです。

コード

```php
foreach ($objs as $obj) {
    print "\n";
    try {
        $obj->listening();
    } catch (Exception $e) {
        // 全部吐かせる
        do {
            $obj->say($e->getMessage());
        } while($e = $e->getPrevious());
    }
}
```

実行結果

```
山川(殺害された人)   >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました！
山川(殺害された人)   >>> 実は、昔、詐欺をやっていたという過去があります
山川(殺害された人)   >>> 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました

文江(山川の秘書)     >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです！
文江(山川の秘書)     >>> 実は、ヤスは私の兄です
文江(山川の秘書)     >>> 実は・・・兄の犯罪に加担していました

ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい！
ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります
ヤス(捜査している人) >>> 実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました
```

# 聴取の総括：どこで吐いたのか
事件は解決したんですが、ソースコード中のどこで重要発言があったのか(つまりExceptionが発生したのか)全部出力してみます。

```php
foreach ($objs as $obj) {
    print "\n";
    try {
        $obj->listening();
    } catch (Exception $e) {
        // どこで吐いたのかも含めて、全部吐かせる
        // print $e."\n"; でいいのだけど、出力が見づらいので行頭を少し加工
        print preg_replace('/^/m', '  |', $e)."\n";
    }
}
```

実行結果

```
山川(殺害された人)   >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました！
  |Exception: 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました in /home/ubuntu/20190227/portopia.php:31
  |Stack trace:
  |#0 /home/ubuntu/20190227/portopia.php(23): yamakawa->deepListening()
  |#1 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening()
  |#2 {main}
  |
  |Next Exception: 実は、昔、詐欺をやっていたという過去があります in /home/ubuntu/20190227/portopia.php:25
  |Stack trace:
  |#0 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening()
  |#1 {main}
：
（以降長いので省略、最後に全部載せてます）
```

# まとめ
- 犯人はヤス
- 重要発言、つまり Exception は聞き漏らさないようにしましょう。
    - `catch(Exception $e){ ; }` すると、重要発言は何も聞けない。
    - 重要発言を聞くために `catch(Exception $e){ print $e->getMessage(); }` としても、１つしか聞けない。
    - 重要発言を聞き漏らさない為には `catch(Exception $e){ print $e; }` とする。
- PHP版で力尽きました。もし気に入ってもらえたなら、ほかの言語でも事件を解決してみてください:wink:

# 添付：全ソースコード

```php:portpia.php
<?php

abstract class investigation
{
    abstract function whatName();
    abstract function listening();
    function say($msg) {
        print $this->whatName() . " >>> " . $msg . "\n";
    }
}

class yamakawa extends investigation
{
    function whatName()
    {
        return '山川(殺害された人)  ';
    }

    function listening()
    {
        $this->say("社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました！");
        try {
            $this->deepListening();
        } catch (Exception $e) {
            throw new Exception("実は、昔、詐欺をやっていたという過去があります", 0, $e);
        }
    }

    function deepListening()
    {
        throw new Exception("実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました");
    }
}

class fumie extends investigation
{
    function whatName()
    {
        return '文江(山川の秘書)    ';
    }

    function listening()
    {
        $this->say("山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです！");
        try {
            $this->deepListening();
        } catch (Exception $e) {
            throw new Exception("実は、ヤスは私の兄です", 0, $e);
        }
    }

    function deepListening()
    {
        throw new Exception("実は・・・兄の犯罪に加担していました");
    }
}

class yasu extends investigation
{
    function whatName()
    {
        return 'ヤス(捜査している人)';
    }

    function listening()
    {
        $this->say("山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい！");
        try {
            $this->deepListening();
        } catch (Exception $e) {
            throw new Exception("実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります", 0, $e);
        }
    }

    function deepListening()
    {
        throw new Exception("実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました");
    }
}

$objs = [];
$objs[] = new yamakawa();
$objs[] = new fumie();
$objs[] = new yasu();

print "\n";
print "***\n";
print "*** 聴取１回目：自己紹介(Exceptionを完全に握りつぶす)\n";
print "***\n";
foreach ($objs as $obj) {
    print "\n";
    try {
        $obj->listening();
    } catch (Exception $e) {
        // 吐かないよう握りつぶす
        ;
    }
}

print "\n";
print "***\n";
print "*** 聴取２回目：事情を聴く(直近のExceptionのメッセージ１つだけ吐かせる)\n";
print "***\n";
foreach ($objs as $obj) {
    print "\n";
    try {
        $obj->listening();
    } catch (Exception $e) {
        // １つだけ吐かせる
        $obj->say($e->getMessage());
    }
}

print "\n";
print "***\n";
print "*** 聴取３回目：真実を吐かせる(ネストしているExceptionのメッセージも含め全部吐かせる)\n";
print "***\n";
foreach ($objs as $obj) {
    print "\n";
    try {
        $obj->listening();
    } catch (Exception $e) {
        // 全部吐かせる
        do {
            $obj->say($e->getMessage());
        } while($e = $e->getPrevious());
    }
}

print "\n";
print "***\n";
print "*** 聴取の総括：どこで吐いたのか(Exceptionのメッセージ以外の情報も全部吐かせる)\n";
print "***\n";
foreach ($objs as $obj) {
    print "\n";
    try {
        $obj->listening();
    } catch (Exception $e) {
        // どこで吐いたのかも含めて、全部吐かせる
        // print $e."\n"; でいいのだけど、出力が見づらいので行頭を少し加工
        print preg_replace('/^/m', '  |', $e)."\n";
    }
}
```

# 添付：全実行結果
PHP7.2で動作確認しましたが、他のバージョンでも動くと思います。

```shell-session
$ php --version
PHP 7.2.15-0ubuntu0.18.04.1 (cli) (built: Feb  8 2019 14:54:22) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.15-0ubuntu0.18.04.1, Copyright (c) 1999-2018, by Zend Technologies

$ php portopia.php 

***
*** 聴取１回目：自己紹介(Exceptionを完全に握りつぶす)
***

山川(殺害された人)   >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました！

文江(山川の秘書)     >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです！

ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい！

***
*** 聴取２回目：事情を聴く(直近のExceptionのメッセージ１つだけ吐かせる)
***

山川(殺害された人)   >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました！
山川(殺害された人)   >>> 実は、昔、詐欺をやっていたという過去があります

文江(山川の秘書)     >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです！
文江(山川の秘書)     >>> 実は、ヤスは私の兄です

ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい！
ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります

***
*** 聴取３回目：真実を吐かせる(ネストしているExceptionのメッセージも含め全部吐かせる)
***

山川(殺害された人)   >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました！
山川(殺害された人)   >>> 実は、昔、詐欺をやっていたという過去があります
山川(殺害された人)   >>> 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました

文江(山川の秘書)     >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです！
文江(山川の秘書)     >>> 実は、ヤスは私の兄です
文江(山川の秘書)     >>> 実は・・・兄の犯罪に加担していました

ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい！
ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります
ヤス(捜査している人) >>> 実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました

***
*** 聴取の総括：どこで吐いたのか(Exceptionのメッセージ以外の情報も全部吐かせる)
***

山川(殺害された人)   >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました！
  |Exception: 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました in /home/ubuntu/20190227/portopia.php:31
  |Stack trace:
  |#0 /home/ubuntu/20190227/portopia.php(23): yamakawa->deepListening()
  |#1 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening()
  |#2 {main}
  |
  |Next Exception: 実は、昔、詐欺をやっていたという過去があります in /home/ubuntu/20190227/portopia.php:25
  |Stack trace:
  |#0 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening()
  |#1 {main}

文江(山川の秘書)     >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです！
  |Exception: 実は・・・兄の犯罪に加担していました in /home/ubuntu/20190227/portopia.php:54
  |Stack trace:
  |#0 /home/ubuntu/20190227/portopia.php(46): fumie->deepListening()
  |#1 /home/ubuntu/20190227/portopia.php(137): fumie->listening()
  |#2 {main}
  |
  |Next Exception: 実は、ヤスは私の兄です in /home/ubuntu/20190227/portopia.php:48
  |Stack trace:
  |#0 /home/ubuntu/20190227/portopia.php(137): fumie->listening()
  |#1 {main}

ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい！
  |Exception: 実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました in /home/ubuntu/20190227/portopia.php:77
  |Stack trace:
  |#0 /home/ubuntu/20190227/portopia.php(69): yasu->deepListening()
  |#1 /home/ubuntu/20190227/portopia.php(137): yasu->listening()
  |#2 {main}
  |
  |Next Exception: 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります in /home/ubuntu/20190227/portopia.php:71
  |Stack trace:
  |#0 /home/ubuntu/20190227/portopia.php(137): yasu->listening()
  |#1 {main}
```