PHP
初心者
ネタ
exception
新人プログラマ応援

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


これは何?


  • 昔の有名な殺人事件を、Exception の try~catch で再現してみます。

  • Exceptionの握りつぶしについて「完全に握りつぶす」「1つ以外を握りつぶす」「一切握りつぶさない」の3パターンを使って、徐々に事件の解決に近づきます。

  • ソースコード(portpia.php)は最後に全部掲載します。


はじまり

事件発生!

山川社長が何者かに殺害された。

刑事のヤスが事件解決の為、捜査を開始した。


山川社長、文江、ヤスを呼び出す。

new でオブジェクトを作ります。ここではクラス定義は秘密にしておきます。(最後に掲載します)

$objs = [];

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


聴取1回目:自己紹介させます。

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

foreach ($objs as $obj) {

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

実行結果

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

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

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


聴取2回目:事情を聴いてみます。

重要発言(ようはException)を1つだけ聴いてみます。

foreach ($objs as $obj) {

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

実行結果

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

山川(殺害された人) >>> 実は、昔、詐欺をやっていたという過去があります

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

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


聴取3回目:真実を吐かせてみる

全ての重要発言、ようは全てのExceptionを聴いてみます。ネストしたExceptionを全て展開しているだけです。

コード

foreach ($objs as $obj) {

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

実行結果

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

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

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

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


聴取の総括:どこで吐いたのか

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

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(); } としても、1つしか聞けない。

    • 重要発言を聞き漏らさない為には catch(Exception $e){ print $e; } とする。



  • PHP版で力尽きました。もし気に入ってもらえたなら、ほかの言語でも事件を解決してみてください:wink:


添付:全ソースコード


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

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

print "\n";
print "***\n";
print "*** 聴取3回目:真実を吐かせる(ネストしている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で動作確認しましたが、他のバージョンでも動くと思います。

$ 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

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

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

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

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

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

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

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

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

***
*** 聴取3回目:真実を吐かせる(ネストしている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}