Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPによる"非"構造化プログラミング入門 -本当に熱いスパゲティコードを求めて- by きん...

PHPによる"非"構造化プログラミング入門 -本当に熱いスパゲティコードを求めて- by きんじょうひでき

hideki kinjyo

March 21, 2025
Tweet

More Decks by hideki kinjyo

Other Decks in Programming

Transcript

  1. 登壇者挨拶 <?= ͜Μʹͪ͸ʙʙʂ ?>  Fatal error: Uncaught Error: Undefined

    constant "͜Μʹͪ͸ʔʂ" in php-wasm run script:1 ⏳estimate: 大 00 01 /20
  2. イントロ  <?php for ($i = 10; $i >= 0;

    $i--) { echo $i . PHP_EOL; } echo '🚀 ϩέοτൃࣹʂ'; こういうのが、 ⏳estimate: 大 01 02 /20
  3. イントロ  <?php for ($i = 10; $i >= 0;

    $i--) { echo $i . PHP_EOL; } echo '🚀 ϩέοτൃࣹʂ'; こういうのが <?php $i = 10; COUNTDOWN: if ($i <= 0) goto FINISH; echo --$i . PHP_EOL; goto COUNTDOWN; FINISH: echo '🚀 ϩέοτൃࣹʂ'; こう! ⏳estimate: 大 01 02 /20
  4. イントロ  <?php declare(strict_types=1); use ImafuApp\App; require dirname(__FILE__, 2) .

    '/bootstrap.php'; (new App())->run(); index.phpだって、 ⏳estimate: 大 01 02 /20
  5. イントロ  <?php declare(strict_types=1); use ImafuApp\App; require dirname(__FILE__, 2) .

    '/bootstrap.php'; (new App())->run(); index.phpだって、 <?php declare(strict_types=1); $dbo = mysqli_connect( getenv('DB_HOST'), getenv('DB_USER'), getenv('DB_PASS'), getenv('DB_NAME'), ); if (!$dbo) goto db_error; /* ϦΫΤετͷॲཧ */ $action = filter_input(INPUT_GET, 'action'); if ($action === 'detail') goto detail_message; if ($action === 'new') goto new_message; if ($action === 'register') goto register_message; goto list_msgs; /* ϝοηʔδҰཡ */ list_msgs: $page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT); goto check_page; set_default_page: $page = 1; goto end_page_check; check_page: if ($page >= 1) goto end_page_check; goto set_default_page; end_page_check: $totalCount = 0; $msgs = []; $result = mysqli_query( $dbo, 'SELECT COUNT(*) FROM messages', ); if ($result !== false) goto list_msgs_page; $totalCount = (int)mysqli_fetch_column($result); $limit = 10; $offset = ($page - 1) * $limit; $stmt = mysqli_prepare( $dbo, 'SELECT * FROM messages ORDER BY id DESC LIMIT ? OFFSET ?' ); mysqli_stmt_bind_param($stmt, 'ii', $limit, $offset); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); $msgs = mysqli_fetch_all($result, MYSQLI_ASSOC); $msgCnt = count($msgs); $totalPage = ceil($totalCount / 10); list_msgs_page: ?> <!DOCTYPE html> <html> <head> <title>఻ݴҰཡ</title> <link rel="stylesheet" href="assets/geo-bootstrap/swatch/ bootstrap.min.css"> </head> <body> <div class="container" style="padding-bottom: 5rem;"> <div class="page-header"> <h1>఻ݴҰཡ</h1> <a class="btn btn-info" href="/?action=new">৽ن౤ߘ</a> </div> <div class="row"> <div class="span12"> <ul class="list-group mb-4"> <?php $i = 0; msg_loop: if ($i >= $msgCnt) goto msg_loop_end; $message = $msgs[$i]; ?> <li class="list- group-item"> <a href="/? action=detail&id=<?= $message['id'] ? >"> <?= htmlspecialchars($message['title']) ?> </a> by <?= htmlspecialchars($message['author']) ?> </li> <?php $i++; goto msg_loop; msg_loop_end: ?> </ul> </div> <div class="span12"> <div class="pager"> <ul> <li class="page-item"> <a class="page-link" href="/">࠷ॳͷϖʔδ</ a> </li> <?php goto check_prev_page; prev_page: ?> <li class="page-item"> <a class="page-link" href="/?page=<?= $page - 1 ?>">લͷϖʔδ</a> </li> <?php goto check_next_page; check_prev_page: if ($page <= 1) goto check_next_page; goto prev_page; check_next_page: if ($page >= $totalPage) goto end_pagination; next_page: ?> <li class="page-item"> <a class="page-link" href="/?page=<?= $page + 1 ?>">࣍ͷϖʔδ</a> </li> <?php goto end_pagination; end_pagination: ?> <li class="page-item"> <a class="page-link" href="?page=<?= intdiv($i, 10) + 1 ?>">࠷ޙͷϖʔδ</a> </li> </ul> </div> </div> </div> </div> </body> </html> <?php goto end; /* ϝοηʔδৄࡉ */ detail_message: $id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT); $stmt = mysqli_prepare($dbo, 'SELECT * FROM messages WHERE id = ?'); mysqli_stmt_bind_param($stmt, 'i', $id); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); if (mysqli_num_rows($result) === 0) goto redirect_home; $message = mysqli_fetch_assoc($result); ?> <!DOCTYPE html> <html> <head> <title><?= htmlspecialchars($message['title']) ? ></title> <link rel="stylesheet" href="assets/geo-bootstrap/swatch/ bootstrap.min.css"> </head> <body> <div class="container" style="padding-bottom: 5rem;"> <div class="page-header"> <h1><?= htmlspecialchars($message['title']) ? ></h1> <a class="btn btn-info" href="index.php">Ұཡʹ໭Δ</a> </div> <div class="row"> <div class="span12"> <article> <header> <p> <span class="label label-inverse">౤ߘऀ</ span> <?= htmlspecialchars($message['author']) ?> </p> <p> <span class="label label-inverse">౤ߘ೔</ span> <?= $message['created_at'] ?> </p> </header> <p class="well"> <?= nl2br(htmlspecialchars($message['body'] )) ?> </p> </article> </div> </div> </div> </body> </html> <?php goto end; /* ৽نϝοηʔδ */ new_message: ?> <!DOCTYPE html> <html> <head> <title>৽ن఻ݴ࡞੒</title> <link rel="stylesheet" href="assets/geo-bootstrap/swatch/ bootstrap.min.css"> </head> <body> <div class="container" style="padding-bottom: 5rem;"> <div class="page-header"> <h1>৽ن఻ݴ࡞੒</h1> <a class="btn btn-info" href="index.php">Ұཡʹ໭Δ</a> </div> <div class="row"> <div class="span12"> <?php if (empty($errors)) goto new_message_form; ?> <div class="alert alert-error"> <ul> <?php $i = 0; $errorCount = count($errors); error_list_item: if ($i >= $errorCount) goto error_list_end; $errorField = array_keys($errors)[$i]; ?> <li><?= $errors[$errorField] ?></li> <?php $i++; goto error_list_item; error_list_end: ?> </ul> </div> <?php new_message_form: ?> <form method="post" action="/?action=register" class="form- horizontal well"> <div class="control-group"> <label class="control-label">λΠτϧ</label> <div class="controls"> <input type="text" name="title" maxlength="50" required> </div> </div> <div class="control-group"> <label class="control-label">ຊจ</label> <div class="controls"> <textarea name="body" maxlength="500" rows="10" required></textarea> </div> </div> <div class="control-group"> <label class="control-label">౤ߘऀ໊</label> <div class="controls"> <input type="text" name="author" maxlength="50" required> </div> </div> <label> : </label><br> <button type="submit" class="btn btn-primary"> ొ࿥</button> <button type="submit"></button> </form> </div> </div> </div> </body> </html> <?php goto end; /* ϝοηʔδొ࿥ */ register_message: if ($_SERVER['REQUEST_METHOD'] !== 'POST') goto redirect_new; $postMessage = [ 'title' => filter_input(INPUT_POST, 'title'), 'author' => filter_input(INPUT_POST, 'author'), 'body' => filter_input(INPUT_POST, 'body'), ]; $errors = []; validate_title: if ($postMessage['title'] && mb_strlen($postMessage['title']) <= 50) goto validate_author; $errors['title'] = 'λΠτϧ͸50จࣈҎ಺Ͱ ೖྗ͍ͯͩ͘͠͞'; validate_author: if ($postMessage['author'] && mb_strlen($postMessage['author']) <= 50) goto validate_body; $errors['author'] = '౤ߘऀ໊͸50จࣈҎ಺ Ͱೖྗ͍ͯͩ͘͠͞'; validate_body: if ($postMessage['body'] && mb_strlen($postMessage['body']) <= 500) goto validate_end; $errors['body'] = 'ຊจ͸500จࣈҎ಺Ͱೖྗ ͍ͯͩ͘͠͞'; validate_end: if ($errors) goto new_message; $postMessage['created_at'] = date('Y-m- d H:i:s'); $stmt = mysqli_prepare($dbo, 'INSERT INTO `msgs` (`title`, `author`, `body`, `created_at`) VALUES (?, ?, ?, ?)'); mysqli_stmt_bind_param($stmt, 'ssss', $postMessage['title'], $postMessage['author'], $postMessage['body'], $postMessage['created_at']); mysqli_stmt_execute($stmt); if (mysqli_stmt_error($stmt)) goto end; $id = mysqli_stmt_insert_id($stmt); header("Location: /? action=detail&id={$id}"); goto end; /* ΤϥʔϋϯυϦϯά */ db_error: echo "σʔλϕʔε઀ଓΤϥʔ", PHP_EOL; goto end; redirect_home: header("Location: /"); goto end; redirect_new: header("Location: /?action=new"); goto end; end: goto program_exit; /* ϓϩάϥϜऴྃ */ program_exit: このくらい じゃないと! ⏳estimate: 大 01 02 /20
  6. 自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / 𝕏

    : @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • 最近はPodcastをやっています • ハッシュタグ: #readlinefm • 🆕 パンフ記事も書いているので見てください!  ⏳estimate: 大 01 02 /20
  7. 伝言掲示板の機能①: 伝言一覧  Styled with: Geo for Bootstrap, a Timeless

    Theme by Divshot https://code.divshot.com/geo-bootstrap/ ⏳estimate: 大 04 05 /20
  8. 伝言掲示板の機能②: 伝言の表示  Styled with: Geo for Bootstrap, a Timeless

    Theme by Divshot https://code.divshot.com/geo-bootstrap/ ⏳estimate: 大 04 05 /20
  9. 伝言掲示板の機能③: 伝言の登録  Styled with: Geo for Bootstrap, a Timeless

    Theme by Divshot https://code.divshot.com/geo-bootstrap/ ⏳estimate: 大 04 05 /20
  10. スタイル③を味わうためのルール ① いつもの ② 構造化プログラミング(手続き型) ③ "非"構造化プログラミング  クラスなし 関数なし

    「ブロック」なし 分岐は条件付きジャンプのみ可 高階関数・式のネストなし ファイルの分割なし ⏳estimate: 大 05 06 /20
  11. ③ルール1: クラスなし  readonly class User { public function __construct(

    private string $name, private int $age, ) { } } ✕ ⏳estimate: 大 05 06 /20
  12. ③ルール3: 「ブロック」なし  foreach ($array as $key => $value) printf('%s

    => %s', $key, $value); ✕ if ($a) { echo 'a'; } elseif ($b) { echo 'b'; } else { echo 'c'; } ✕ ⏳estimate: 大 05 06 /20
  13. ③ルール4: 条件付きジャンプのみ可  if ($me !== 'taro') goto honshori; echo

    'Taro is here!'; honshori: ◯ if ($me === 'taro') { echo 'Taro is here!'; } ✕ ⏳estimate: 大 06 /20
  14. ③ルール5: 高階関数・式のネストなし  array_map( fn ($x) => $x + 1,

    $values, ); mb_substr( $title 0, mb_strlen($displayLimit), ); ✕ ✕ ⏳estimate: 大 06 /20
  15. いつもの姿 • 素朴なMVC2にして、 こんな感じの構成に • 小さい部品が多数ある • ノリが分かれば、 目当ての処理を 見つけやすそう

     . ├── Action │ ├── BaseAction.php │ └── ViewMessage.php ├── App.php ├── Driver │ └── DBO.php ├── Entity │ └── Message.php ├── Exception ├── Repository ├── View ├── public │ ├── assets │ └── index.php └── templates └── view_message.php ※ファイル数が多いので 一部省略 ⏳estimate: 大 07 /20
  16. 構造化(手続き型)の姿 • 似たようなロジックを 1つのファイルに集約 • ファイル数はぐっと減る  . ├── assets

    ├── index.php ├── lib │ ├── actions.php │ ├── db.php │ └── helper.php └── templates ├── list_msgs.php ├── new_message.php └── view_message.php ⏳estimate: 大 07 /20
  17. 俺たちはここまで来たぞ  . ├── Action │ ├── BaseAction.php │ └──

    ViewMessage.php ├── App.php ├── Driver │ └── DBO.php ├── Entity │ └── Message.php ├── Exception ├── Repository ├── View ├── public │ ├── assets │ └── index.php └── templates └── view_message.php . ├── assets └── index.php ⏳estimate: 大 07 /20
  18. 構造化プログラミング • リクエストから`?page=`を INTで矯正して取り出し、 不正な範囲だったら`1`で 上書きする • 今っぽいスタイルでも手続 き型のスタイルでも同じ •

    FWを使ってればリクエスト内 容に生で触ることは無さそう かな?くらいの違い $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT, [ 'options' => [ 'default' => 1, ] ] ); if ($page < 1) { $page = 1; }  ⏳estimate: 大 08 /20
  19. code review $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT, [ 'options'

    => [ 'default' => 1, ] ] ); if ($page < 1) { $page = 1; } if文を使っている => 条件付きジャンプに変える 必要がある if ($page < 1) { $page = 1; }  ⏳estimate: 大 08 /20
  20. 構造化プログラミング • あるいは、`max()` で値を 丸めてしまうことも $page = max(1, filter_input( INPUT_GET,

    'page', FILTER_VALIDATE_INT, [ 'options' => [ 'default' => 1, ], ] )); $limit = 10; $offset = ($page - 1) * $limit;  ⏳estimate: 大 08 /20
  21. code review $page = max(1, filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT, [

    'options' => [ 'default' => 1, ], ] )); $limit = 10; $offset = ($page - 1) * $limit; $page = max(1, filter_input( 式のネストをしている (`max()` の中で `filter_input()` を使っている)  ⏳estimate: 大 08 /20
  22. • ここからが • 俺達の本気だ!! $page = max(1, filter_input( INPUT_GET, 'page',

    FILTER_VALIDATE_INT, [ 'options' => [ 'default' => 1, ], ] )); $limit = 10; $offset = ($page - 1) * $limit; code review これを非構造化したら、どうなる?  ⏳estimate: 大 08 09 /20
  23. 非構造化すると… $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT ); goto check_page;

    set_default_page: $page = 1; goto end_page_check; check_page: if ($page >= 1) goto end_page_check; goto set_default_page; end_page_check: チェックポイント ✓条件分岐はジャンプで ✓式の中で別の式を呼んでい ない ※ 味わい深さを優先して、 やや汚いコードにしています  ⏳estimate: 大 08 09 /20
  24. 非構造化すると… $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT ); goto check_page;

    set_default_page: $page = 1; goto end_page_check; check_page: if ($page >= 1) goto end_page_check; goto set_default_page; end_page_check: `$_GET`のparamを取得して $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT );  ⏳estimate: 大 09 /20
  25. 非構造化すると… $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT ); goto check_page;

    set_default_page: $page = 1; goto end_page_check; check_page: if ($page >= 1) goto end_page_check; goto set_default_page; end_page_check: 値の範囲をチェックする 処理にジャンプ goto check_page; check_page:  ⏳estimate: 大 09 /20
  26. 非構造化すると… $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT ); goto check_page;

    set_default_page: $page = 1; goto end_page_check; check_page: if ($page >= 1) goto end_page_check; goto set_default_page; end_page_check: 指定されたのが1以上なら 何もしないで次の処理へ if ($page >= 1) goto end_page_check; end_page_check:  ⏳estimate: 大 09 /20
  27. 非構造化すると… $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT ); goto check_page;

    set_default_page: $page = 1; goto end_page_check; check_page: if ($page >= 1) goto end_page_check; goto set_default_page; end_page_check: 正常範囲に含まれなければ デフォルト値のセット処理へ goto set_default_page; set_default_page:  ⏳estimate: 大 09 /20
  28. 非構造化すると… $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT ); goto check_page;

    set_default_page: $page = 1; goto end_page_check; check_page: if ($page >= 1) goto end_page_check; goto set_default_page; end_page_check: `$page` に デフォルト値をセット $page = 1;  ⏳estimate: 大 10 /20
  29. 非構造化すると… $page = filter_input( INPUT_GET, 'page', FILTER_VALIDATE_INT ); goto check_page;

    set_default_page: $page = 1; goto end_page_check; check_page: if ($page >= 1) goto end_page_check; goto set_default_page; end_page_check: `$page`関連の処理は コレで終わり。次の処理へ goto end_page_check; end_page_check:  ⏳estimate: 大 10 /20
  30. いつもの 取得部分(コントローラー) • 「取っ て」「仕込ん で」「表 示して」みたいな感じです • (特に説明したいことナシ) $msgs

    = $this ->repository ->getMulti($limit, $ofset); $payload = new Payload( msgs: $msgs, ); $this->render($payload);  ⏳estimate: 大 11 /20
  31. いつもの 表示部分(プレゼンテーション) • メッセージのコレクション を、foreachで回す • その中で素朴にechoしている のみ <ul class="list-group

    mb-4"> <?php foreach ($msgs as $m): ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m->id ?>" > <?= h($m->title) ?> </a> by <?= h($m->author) ?> </li> <?php endforeach; ?> </ul>  ⏳estimate: 大 11 /20
  32. 構造化プログラミング 取得部分(手続き) • 処理が単位ごとに切り出され ているので、OOPをベースに 書いたコードと骨組みは変わ らない印象 • クラス-メソッドの仕組みが ないので、関数同士の関連性

    が見抜きにくい気もする • それも、namespaceや命名の工夫 で支援できそう $dbo = getDbConnection(); $msgs = getMessages( $dbo, $page ); render( compact('msgs'), 'list_msgs.php', );  ⏳estimate: 大 11 /20
  33. 構造化プログラミング 表示部分(プレゼンテーション) • オブジェクトが連想配列に なったくらいで、ほとんど何 も変わっていない • そもそもテンプレートに複雑 なロジックを持っていなけれ ば、出てくるのはループや簡

    単な分岐くらい <ul class="list-group mb-4"> <?php foreach ($msgs as $m): ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m['id'] ?>" > <?= h($m['title']) ?> </a> by <?= h($m['author']) ?> </li> <?php endforeach; ?> </ul>  ⏳estimate: 大 11 /20
  34. 構造化プログラミング 表示部分(プレゼンテーション) • オブジェクトが連想配列に なったくらいで、ほとんど何 も変わっていない • そもそもテンプレートに複雑 なロジックを持っていなけれ ば、出てくるのはループや簡

    単な分岐くらい <ul class="list-group mb-4"> <?php foreach ($msgs as $m): ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m['id'] ?>" > <?= h($m['title']) ?> </a> by <?= h($m['author']) ?> </li> <?php endforeach; ?> </ul> これを非構造化したら、どうなる?  ⏳estimate: 大 11 /20
  35. 非構造化すると… $msgs = []; $msgCnt = 0; $stmt = mysqli_prepare(

    $dbo, 'SELECT * FROM messages ORDER BY id DESC LIMIT ? OFFSET ?' ); mysqli_stmt_bind_param( $stmt, 'ii', $limit, $offset ); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); if (!$result) goto db_error; $msgs = mysqli_fetch_all( $result, MYSQLI_ASSOC ); $msgCnt = count($msgs); まずは伝言の取得部分  ⏳estimate: 大 12 /20
  36. 非構造化すると… $msgs = []; $msgCnt = 0; $stmt = mysqli_prepare(

    $dbo, 'SELECT * FROM messages ORDER BY id DESC LIMIT ? OFFSET ?' ); mysqli_stmt_bind_param( $stmt, 'ii', $limit, $offset ); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); if (!$result) goto db_error; $msgs = mysqli_fetch_all( $result, MYSQLI_ASSOC ); $msgCnt = count($msgs);  初期化はやっておくと便利 やっておきましょう $msgs = []; $msgCnt = 0; ⏳estimate: 大 12 /20
  37. 非構造化すると… $msgs = []; $msgCnt = 0; $stmt = mysqli_prepare(

    $dbo, 'SELECT * FROM messages ORDER BY id DESC LIMIT ? OFFSET ?' ); mysqli_stmt_bind_param( $stmt, 'ii', $limit, $offset ); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); if (!$result) goto db_error; $msgs = mysqli_fetch_all( $result, MYSQLI_ASSOC ); $msgCnt = count($msgs);  PDOや`$stmt->fetch()`などの OOPスタイルを使えないので、 すべて「手続き型インター フェイス」を利用。 それ以外は普通のコード $stmt = mysqli_prepare( $dbo, 'SELECT * FROM messages ORDER BY id DESC LIMIT ? OFFSET ?' ); mysqli_stmt_bind_param( $stmt, 'ii', $limit, $offset ); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); 参考: PHP: 手続き型とオブジェクト指向イン ターフェイス - Manual https://php.net/manual/ja/mysqli.quickstart.dual- interface.php ⏳estimate: 大 12 /20
  38. 非構造化すると… $msgs = []; $msgCnt = 0; $stmt = mysqli_prepare(

    $dbo, 'SELECT * FROM messages ORDER BY id DESC LIMIT ? OFFSET ?' ); mysqli_stmt_bind_param( $stmt, 'ii', $limit, $offset ); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); if (!$result) goto db_error; $msgs = mysqli_fetch_all( $result, MYSQLI_ASSOC ); $msgCnt = count($msgs);  失敗時はジャンプで脱出 if (!$result) goto db_error; ⏳estimate: 大 12 /20
  39. 非構造化すると… $msgs = []; $msgCnt = 0; $stmt = mysqli_prepare(

    $dbo, 'SELECT * FROM messages ORDER BY id DESC LIMIT ? OFFSET ?' ); mysqli_stmt_bind_param( $stmt, 'ii', $limit, $offset ); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); if (!$result) goto db_error; $msgs = mysqli_fetch_all( $result, MYSQLI_ASSOC ); $msgCnt = count($msgs);  ここは 「普通」な処理と変わらない $msgs = mysqli_fetch_all( $result, MYSQLI_ASSOC ); $msgCnt = count($msgs); ⏳estimate: 大 12 /20
  40. 非構造化すると… if (!$result) goto db_error; $msgs = mysqli_fetch_all( $result, MYSQLI_ASSOC

    ); $msgCnt = count($msgs); ?> <!DOCTYPE html> <html> <head> <title>఻ݴҰཡ</title> <link rel="stylesheet" href="…"> </head> <body> <div class="container"> 取得部分→表示部分  ⏳estimate: 大 12 /20
  41. 非構造化すると… if (!$result) goto db_error; $msgs = mysqli_fetch_all( $result, MYSQLI_ASSOC

    ); $msgCnt = count($msgs); ?> <!DOCTYPE html> <html> <head> <title>఻ݴҰཡ</title> <link rel="stylesheet" href="…"> </head> <body> <div class="container"> 取得部分→表示部分 • 「ファイルの分割ができな い」を真っ当にやるため、 ロジックの中にテンプレー ト部分も埋め込む形にした  <!DOCTYPE html> <html> <head> <title>఻ݴҰཡ</title> <link rel="stylesheet" href="…"> </head> <body> <div class="container"> ⏳estimate: 大 12 /20
  42. 非構造化すると… <ul class="list-group mb-4"> <?php $i = 0; msg_loop: if

    ($i >= $msgCnt) goto msg_loop_end; $m = $msgs[$i]; ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m['id'] ?>"> <?= htmlspecialchars($m['title']) ?> </a> by <?= htmlspecialchars($m['author']) ?> </li> <?php $i++; goto msg_loop; msg_loop_end: ?> </ul> 表示部分(プレゼンテーション) • パッと見で、 印象はどうでしょうか? • やっていることは、さっきま でのコードと同じではある…  ⏳estimate: 大 13 /20
  43. 比べてみる 「制御構文」を用いて 構造化した場合と比較 <ul> <?php foreach ($msgs as $m): ?>

    <li> </li> <?php endforeach; ?> </ul>  <ul class="list-group mb-4"> <?php $i = 0; msg_loop: if ($i >= $msgCnt) goto msg_loop_end; $m = $msgs[$i]; ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m['id'] ?>"> <?= htmlspecialchars($m['title']) ?> </a> by <?= htmlspecialchars($m['author']) ?> </li> <?php $i++; goto msg_loop; msg_loop_end: ?> </ul> <ul class="list-group mb-4"> <?php $i = 0; msg_loop: if ($i >= $msgCnt) goto msg_loop_end; $m = $msgs[$i]; ?> </li> <?php $i++; goto msg_loop; msg_loop_end: ?> </ul> ⏳estimate: 大 13 /20
  44. 非構造化すると… <ul class="list-group mb-4"> <?php $i = 0; msg_loop: if

    ($i >= $msgCnt) goto msg_loop_end; $m = $msgs[$i]; ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m['id'] ?>"> <?= htmlspecialchars($m['title']) ?> </a> by <?= htmlspecialchars($m['author']) ?> </li> <?php $i++; goto msg_loop; msg_loop_end: ?> </ul>  $i = 0; ループカウンターを 初期化して ⏳estimate: 大 13 /20
  45. 非構造化すると… <ul class="list-group mb-4"> <?php $i = 0; msg_loop: if

    ($i >= $msgCnt) goto msg_loop_end; $m = $msgs[$i]; ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m['id'] ?>"> <?= htmlspecialchars($m['title']) ?> </a> by <?= htmlspecialchars($m['author']) ?> </li> <?php $i++; goto msg_loop; msg_loop_end: ?> </ul>  反復処理の開始部分 msg_loop: ⏳estimate: 大 13 /20
  46. 非構造化すると… <ul class="list-group mb-4"> <?php $i = 0; msg_loop: if

    ($i >= $msgCnt) goto msg_loop_end; $m = $msgs[$i]; ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m['id'] ?>"> <?= htmlspecialchars($m['title']) ?> </a> by <?= htmlspecialchars($m['author']) ?> </li> <?php $i++; goto msg_loop; msg_loop_end: ?> </ul>  反復回数と最大回数を比較 if ($i >= $msgCnt) goto msg_loop_end; msg_loop_end: 最後の伝言の処理が 終わったのが確認されたら、 反復処理を抜ける ⏳estimate: 大 13 /20
  47. 非構造化すると… <ul class="list-group mb-4"> <?php $i = 0; msg_loop: if

    ($i >= $msgCnt) goto msg_loop_end; $m = $msgs[$i]; ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m['id'] ?>"> <?= htmlspecialchars($m['title']) ?> </a> by <?= htmlspecialchars($m['author']) ?> </li> <?php $i++; goto msg_loop; msg_loop_end: ?> </ul>  カウンターを使って イテレーション $m = $msgs[$i]; ⏳estimate: 大 13 /20
  48. 非構造化すると… <ul class="list-group mb-4"> <?php $i = 0; msg_loop: if

    ($i >= $msgCnt) goto msg_loop_end; $m = $msgs[$i]; ?> <li class="list-group-item"> <a href="/?action=detail&id=<?= $m['id'] ?>"> <?= htmlspecialchars($m['title']) ?> </a> by <?= htmlspecialchars($m['author']) ?> </li> <?php $i++; goto msg_loop; msg_loop_end: ?> </ul>  カウンターを更新して、 反復処理の開始地点に戻る $i++; goto msg_loop; msg_loop: ⏳estimate: 大 13 /20
  49. 自由に飛び回るコードとは? gotoプログラミングでも、 「意味的なまとまり」は もちろん存在する  $i = 0; loop_start: if

    ($i >= $cnt) goto loop_end; sub_loop: $item = $items[$i]; // தུ sub_loop_end: $i++; goto loop_start; loop_end: ⏳estimate: 大 14 15 /20
  50. 自由に飛び回るコードとは?  $i = 0; loop_start: if ($i >= $cnt)

    goto loop_end; sub_loop: $item = $items[$i]; // தུ sub_loop_end: $i++; goto loop_start; loop_end: loop_start: ここが「ループ処理」の まとまり loop_end: ⏳estimate: 大 14 15 /20
  51. 自由に飛び回るコードとは? が!! 読み手が見出すしかない (強制力がない)  $i = 0; loop_start: if

    ($i >= $cnt) goto loop_end; sub_loop: $item = $items[$i]; // தུ sub_loop_end: $i++; goto loop_start; loop_end: ⏳estimate: 大 15 /20
  52. 自由に飛び回るコードとは?  $i = 0; loop_start: if ($i >= $cnt)

    goto loop_end; sub_loop: $item = $items[$i]; // தུ sub_loop_end: $i++; goto loop_start; loop_end: goto sub_loop; sub_loop: 「まとまり」の外から 直接的に内に進入できる ⏳estimate: 大 15 /20
  53. 順接実行の世界  foreach ( $messages as $message ) { ///

    தུ } 「ブロック」を用いる 「順接実行」の世界なら こうしたワープが防がれる ⏳estimate: 大 15 /20
  54. 順接実行の世界  foreach ( $messages as $message ) { ///

    தུ continue; } 入口に戻るか (ブロックの内側の移動) continue; foreach ( ⏳estimate: 大 15 /20
  55. 順接実行の世界  foreach ( $messages as $message ) { break;

    /// தུ } 出口に向かうか (ブロック脱出の唯一の方法) break; } ⏳estimate: 大 15 /20
  56. ∴「自由に飛び回れない」とは?  foreach ( $messages as $message ) { break;

    /// தུ } 「特定の場所にジャンプする」 というより 「ループを進める・終了する」 という操作の概念になる ⏳estimate: 大 16 /20
  57. ∴「自由に飛び回れない」とは?  foreach ( $messages as $message ) { break;

    /// தུ } これによって 「意味的なまとまり」は 「ハードな制約」になっている ⏳estimate: 大 16 /20
  58. 両者を比較してみると  件名を表示 本文を表示 投稿日時を表示 1件取り出す 1件ごとの 逐次処理 件名を表示 本文を表示

    投稿日時を表示 1件取り出す どこからでも・どこへでも ブロックを境に出入りを制限 ⏳estimate:
  59. 制御フローの抽象化  伝言を取得 1件ずつ処理 件名を表示 本文を表示 投稿日時を表示 次の伝言へ 1件取り出す foreach

    ( $messages as $message ) { /// தུ } 「まとまり」が 明確に 構文として現れる ⏳estimate:
  60. コールスタック? • サブルーチンや関数の「今呼ばれたもの」「呼び出し元(戻り先)」を 積み上げて管理する • スタックを減らす = 「入口〜出口」の 処理が終わった時に、呼び出し元に戻す •

    スタックオーバーフロー = 呼び出しが 深くなりすぎてスタックが・・・状態 • gotoだと「終わった時に戻る場所」 を特定できないので、 コールスタックも積まれない  ⏳estimate:
  61. 「構造化(あるいは非構造化)」の威力 • 構造化 = ブラックボックス化の力 • そのために「順序どおりに」の秩序が欠かせない • そにれよって、複数のレイヤーの「流れ」「関心」を切り離した •

    構造化のためのサブルーチン • 「入口」は一箇所だけで「出口」は呼び出し元に固定 • 「呼び出された側」が「外の世界の、具体的な位置を気にする」という 相互的な依存状態も解消される。疎結合化を促進する。  ⏳estimate:
  62. 抽象 • 組み合わせ: 具体を積み上げて 抽象を支える • 分解: 各モジュールの外からは その中身は干渉できない •

    単純化: 問題と解法の「正しさ」は レイヤーごとに保証する 抽象 具体 アルゴリズムや I/O制御 ユースケースや モデル ⏳estimate:
  63. お便りのコーナー: 私から皆さんへ・・・ • 変わっていくものと変わらないものがある • 「問題が複雑になり、解決の陳腐化が早くなる」はこれからも加速していくはず • だから「易しくしたい」も強くなっていく • ただし「生身の人間がどこまで介入するか」は変わっていく

    • では「GOTOが悪: なぜなら読みにくいし混乱の元だから」は変わらない? • 60年前に「GOTOはいらない、3つのルールでプログラミングしよう」と言われたのは、「最低限そのルールだ けあれば十分だから」と数学的な証明があったから • 「GOTOが読みにくい」は本当に変わらない? • 読み相手が「人間でなくAI」になったらどうか? • 「どこでキャッチされるかわからないthrow Exception」と「明示的に行き先が示されているgoto」という 見方もできるのでは • (静的なラベリングが前提ではあるが)より明示的に依存が示される効果 • 「上から全部読まないと解釈が難しい」「構造化されていれば、目次を使って必要なところだけ詳細に読 める」が真でも、「素直に上から読んでいけば全部追える」が優勢になったら大局は変わるのも 
  64. 参考書籍 • E.W.ダイクストラ, C.A.R.ホーア, O.-J.ダール 共著ほか. 構造化プ ログラミング, サイエンス社, 1975,

    (サイエンスライブラリ. 情報電 算機 ; 32) • ドナルド・E.クヌース 著ほか. 文芸的プログラミング, アスキー, 1994.3. • 葉田善章 著. コンピュータの動作と管理. 改訂版, 放送大学教育振興 会, 2017.3, (放送大学教材). 978-4-595-31739-2. https:// ndlsearch.ndl.go.jp/books/R100000002-I027926799 
  65. PHPのgoto • マニュアル: https://php.net/goto • (それでも乱用するものでもないが)従来的な問題に対して、大きく制約を加え て安全な利用を促進している • 導入は5.3から。名前空間、遅延的束縛、無名関数などが入ったバージョン •

    GOTO in PHP 5.3: is it Really That Evil? | PHP Architect • https://www.phparch.com/2009/06/goto-in-php-5-3-is-it-really-that-evil/ • 追加の経緯や考察が述べられている記事。よくまとまっていて面白い 
  66. 伝言のバリデーション • フィールドごとに ifで検証 • 何の変哲もないですよね? • エラーがあったら、 `$errors` にエラーメッ

    セージを格納する $errors = []; if ( $input['title'] === null || mb_strlen($input['title']) > 50 ) { $errors['title'] = '50ࣈҎ಺Ͱೖྗ͍ͯͩ͘͠͞'; } if ( $input['author'] === null || mb_strlen($input['author']) > 50 ) { $errors['author'] = '50จࣈҎ಺Ͱೖྗ͍ͯͩ͘͠͞'; } // தུ return $errors; 
  67. 非構造化 • もう読めますね?? • 今回のルールである「ifの 中に入れられるのはジャン プだけ」を守った形  $errors =

    []; validate_title: if ($input['title'] && mb_strlen($input['title']) <= 50) goto validate_author; $errors['title'] = 'λΠτϧ͸50จࣈҎ಺Ͱೖྗ͍ͯͩ͘͠͞'; validate_author: if ($input['author'] && mb_strlen($input['author']) <= 50) goto validate_body; $errors['author'] = '౤ߘऀ໊͸50จࣈҎ಺Ͱೖྗ͍ͯͩ͘͠͞'; // தུ validate_end: if ($errors) goto new_message;
  68. 非構造化  validate_title: if ( $input['title'] && mb_strlen($input['title']) <= 50

    ) goto validate_author; $errors['title'] = 'λΠτϧ͸50จࣈҎ಺Ͱೖྗͯͩ͘͠͞ ͍'; validate_author: if ($input['author'] && … if ( $input['title'] && mb_strlen($input['title']) <= 50 ) データの形式をチェックして
  69. 非構造化  validate_title: if ( $input['title'] && mb_strlen($input['title']) <= 50

    ) goto validate_author; $errors['title'] = 'λΠτϧ͸50จࣈҎ ಺Ͱೖྗ͍ͯͩ͘͠͞'; validate_author: if ($input['author'] && … goto validate_author; OKだったら次のチェックへ validate_author:
  70. 非構造化  validate_title: if ( $input['title'] && mb_strlen($input['title']) <= 50

    ) goto validate_author; $errors['title'] = 'λΠτϧ͸50จࣈҎ ಺Ͱೖྗ͍ͯͩ͘͠͞'; validate_author: if ($input['author'] && … $errors['title'] = 'λΠτϧ͸50จࣈҎ ಺Ͱೖྗ͍ͯͩ͘͠͞'; ジャンプされなかったら エラー時の処理に入る
  71. Composerでの例 • ユーザーのキー入力を待つ 対話的な部分 • 「その操作は実行できない よ」というケースでデフォ ルトの処理(=help表示)に 飛ばす・・という処理 •

    switchの中で、別のブランチ からdefaultに飛ばしている  <?php while (true) { switch ($this->io->ask(...)) { case 'y': $this->discardChanges($path); break 2; case 's': if (!$update) { goto help; } $this->stashChanges($path); break 2; // লུ case '?': default: help : $this->io->writeError(...); if ($update) { $this->io->writeError(' s - stash changes and try to reapply them after the update'); } $this->io->writeError(' ? - print help'); break; } } src: https://github.com/composer/composer/ blob/2.8.6/src/Composer/Downloader/