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

例外を投げるのをやめてみないか? あるいは受け入れてみないか? - How to use exceptions other than throwing

uzulla
January 13, 2024

例外を投げるのをやめてみないか? あるいは受け入れてみないか? - How to use exceptions other than throwing

at: PHPカンファレンス北海道 2024
date: 2024-01-13
talker: uzulla

uzulla

January 13, 2024
Tweet

More Decks by uzulla

Other Decks in Programming

Transcript

  1. ࣗݾ঺հ — id: uzulla — ৭ʑͰੜܭΛཱ͍ͯͯ·͢ — PHPͰΦϨΦϨFWΛ࡞ͬͨΓɺ ࢖ͬͨΓͯ͠·͢ —

    ͭ·Γ अಓͳPHP Λॻ͍͍ͯ· ͢(ʁʁʁ) — ʁʮԦಓͱ͸ʁʁʁʯ ࢲʮब৬Ͱ໾ཱͭΑ͏ͳ…ʯ
  2. 15෼͔͕࣌ؒ͠ͳ͍ͷͰ ଈअಓ ʂ — PHPͷException͸ɺnewͨ͠ΠϯελϯεͰ͢ɻ — ͭ·ΓɺValueͳͷͰ — returnͰ͖Δ͠ —

    Ҿ਺ʹ΋Ͱ͖Δ͠ — ഑ྻʹ٧ΊΔ͜ͱ΋Ͱ͖·͢ — ʮ͜Ε͸ศརʹ ѱ༻ ׆༻Ͱ͖ΔΑφʂʂʂʯ
  3. <?php // ҰԠɺී௨ͷಠࣗྫ֎͸͜Μͳܗঢ় class SomeException extends RuntimeException { public function

    __construct( string $message, int $code = 0, Throwable $previous = null, ) { parent::__construct($message, $code, $previous); } }
  4. <?php declare(strict_types=1); // ෳ਺ͷྫ֎Λॴ༗Ͱ͖Δྫ֎ class UserEditException extends RuntimeException { public

    function __construct( string $message, int $code = 0, Throwable $previous = null, public array $exceptions = [] ) { parent::__construct($message, $code, $previous); } }
  5. <?php declare(strict_types=1); $exceptions = []; if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { $exceptions[]

    = new InvalidEmailException('Invalid email address'); } if (strlen($_POST['password']) <= 8) { $exceptions[] = new InvalidPasswordException( "too short", InvalidPasswordException::SHORT ); // ͜Ε͸ޙͰઆ໌͠·͢ } if (preg_match("/\n/u",$_POST['password'])) { $exceptions[] = new InvalidPasswordException( "lf not allowed", InvalidPasswordException::AVOID_LF ); // ͜Ε͸ޙͰઆ໌͠·͢ } if (count($exceptions) > 0) { throw new UserEditException('Invalid input', 0, null, $exceptions); }
  6. ## Controllerͱ͔Ͱ function run(array $post){ try{ $exceptions = []; //

    ͖ͬ͞ͷίʔυͱಉ͡ͳͷͰলུ if (count($exceptions) > 0) { throw new UserEditException('Invalid input', 0, null, $exceptions); } return render_success_page(); }catch (UserEditException $e){ return render_error_page($e->exceptions); } }
  7. // ྫ֎ͷ഑ྻ͔Βɺࢦఆͷྫ֎͚ͩΛൈ͖ग़͢܅ function get_list_contain_exception(array $exceptions, string $exception_name): array { return

    array_reduce( $exceptions, function($carry, $e){ if($e instanceof $exception_name) $carry[] = $e; }, [] ): array; } <?php $password_exceptions = get_list_contain_exception($all_exceptions, InvalidPasswordException::class) ςϯϓϨʔτͷύεϫʔυೖྗཝͱ͔Ͱ… <?php if (count($password_exceptions)>0){ ?> <?php foreach($exceptions as $e){ ?> <div class="error"><?=$e->getMessage()?></div> <?php } ?> <?php } ?>
  8. ΋ͬͱྫ֎Λ֦ுɾ׆༻ɾ ѱ༻ ͍ͨ͠ʂʂʂ — (ࢲʮ͞Βʹअಓʹͯ͠ɺօ͞ΜΛ͓͖͟Γʹ͍ͯ͘͠ʯ) — ྫ֎͸σόοάͱ͔ௐࠪʹ࢖͏͔Β(ී௨)දࣔ༻ʹ͸࢖͑ͳ ͍… Ͱ΋ɺදࣔ༻ͷจষͱ͔ͬ͠Γඥ෇͚͍ͨɻ —

    ͞Βʹ͍͑͹ɺଟࠃޠରԠͱ͔͍ͨ͠ɻ — ʁʮಠࣗྫ֎Λ୔ࢁͭ͘Δͷ͕େม͗͢ΔΜͰ͕͢ʯ ࢲʮCodeͬͯ΋ͷ͕͋ΔͷͰɺͦΕ࢖͑Δʯ ʁʮCodeͬͯͳʹʁʯࢲʮ͋͞…ʁʯ
  9. <?php declare(strict_types=1); // Ұͭͷྫ֎Ͱɺෳ਺ͷΤϥʔΛ΋ͪͭͭɺI18Nͳϝοηʔδ΋΋ͭྫ֎ class InvalidPasswordException extends RuntimeException { use

    I18NMessageTrait; // ޙड़ const int SHORT = 1; const int AVOID_LF = 2; // ͜͜ΛENUMʹ͢Δͷ͸ѱखͱ͓΋͍·͢ const array MESSAGES = [ ExceptionLang::JA->value => [ self::SHORT => "୹͍Ͱ͢Ͷʙʙ", self::AVOID_LF => "վߦ͸ແཧʂ", ], ExceptionLang::EN->value => [ self::SHORT => "Too Short", self::AVOID_LF => "LF is not allowed", ], ]; }
  10. trait I18NMessageTrait // ͖ͬ͞use͍ͯͨ͠΋ͷ { public function getI18NMessage(ExceptionLang $lang) {

    return self::MESSAGES[$lang->value][$this->code] ?? throw new \OutOfBoundException( "Undefined message." . self::class ." code:{$this->code}" ); } } // -- enum ExceptionLang: string { case JA = 'ja'; // ผʹENUMʹ͠ͳͯ͘΋ɺจࣈྻͷjaͰΑ͍ͷͰ͸ʁʁʁ case EN = 'en'; }
  11. class UserEntity { public function __construct( public int $id, public

    string $name, public string $email, public string $password ) { } }
  12. # ྫ֎Λฦ஋ʹ࢖͏ѱ༻ʁྫ function getUserFromDB(int $id): UserEntity|null|\RuntimeException { try { $pdo

    = new PDO('mysql:host=localhost;dbname=test', 'root', ''); $stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id'); $stmt->execute(['id' => $id]); if(($res = $stmt->fetch(PDO::FETCH_ASSOC)) === false) return null; return new UserEntity(...$res); } catch (\Exception $e) { return \RuntimeException('ͳΜ͔DBͰΤϥʔ͕͓ͬͨ͜Θʙʙ', 0, $e); } } // --- $user = getUserFromDB(1); if (is_null($user)) { return show_notfound(); } elseif ($user instanceof \RuntimeException) { return show_internal_server_error($user/*== Exception*/); } return show_user_detail($user);
  13. ΢ϫʔοʂʂʂ — ͻͲ͍ίʔυͩͱ͓΋͍·͔͢ʁ => ͸͍ɻ — ͨͩɺ୔ࢁͷTry/CatchΑΓಡΈ΍͍͢(ओ؍) (͜Ε͙Β͍୹͍ͱಡΔ͕ɺtry͕௕͔ͬͨΓɺ୔ࢁtry/ catch͕͋ΔͱࡶͳPokemon HandlingʹͳΓ͕ͪ)

    — (͜ͷྫͩͱ)show_user_detail ͰҾ਺͕ UserEntity Ͱ͋Δ ͱ໌هͯ͋͠Ε͹TypeErrorͰམͪΔͷͰɺ ઌʹਐΉ͜ͱ͸ͳ͍ͷͰͪΐͬͱ҆શ
  14. # ී௨ͱअಓͷൺֱྫ ## ී௨ͷTry/Catch try{ $user = getUserFromDB(1); if (is_null($user))

    show_notfound(); return show_user_detail($user); # ͜͜Β΁Μ͕ΊʔͬͪΌͳͳ͕͍͜ͱ͕ଟ͍ }catch(SomeException $e){ return show_internal_server_error($e->getMessage()); # ୔ࢁCatch͕͋ͬͨΓ… }catch(\RuntimeException $e){ return show_internal_server_error($e->getMessage()); # ͷͰɺ͕͍͜͜͢͝ԕ͍ } ## ઌ΄ͲͷअಓྫɺૣظReturnͬΆ͍Ͷʁ $user = getUserFromDB(1); if (is_null($user)) { return show_notfound(); } elseif ($user instanceof \RuntimeException) { return show_internal_server_error($user); } return show_user_detail($user);
  15. // ϚʔΧʔɺத਎͸ۭͰΑ͍ interface MustLogging{} // ಠࣗྫ֎ class MyException extends RuntimeException

    implements MustLogging{} // ྫ֎ॲཧͷͱ͖ɺΠϯϓϦ͞Ε͍ͯΔ͔ΛݟͯॲཧΛม͑Δ catch(Exception $e){ if($e instanceof MustLogging) error_log($e->getMessage()); } MustLogging ͷ෇͚֎͠͸҆શʹͰ͖Δʂ ͨͩ݁ߏن໿ײ͋Γɺଞਓʹ͸ಡΈͮΒ͍͔΋ (FWͬͯͦ͏͍͏΋ͷͰ͸ʁ)
  16. ## ׆༻࣮ྫ(ʁ) interface BailoutWhenBatchTag{ } // όονͷ৔߹͸ඞͣఀࢭ͢ΔϚʔΫ interface IgnoreOnProductionTag{ }

    // ຊ൪ͩͱແࢹ͢ΔϚʔΫ interface InternalServerErrorTag{ } // 500ʹ͍ͨ͠ϚʔΫɺBadRequestͱ͔΋͋Δ
  17. ࣍ʹt-wada͞Μͷൃද — PHP7 Ͱݎ࿚ͳίʔυΛॻ͘ - ྫ֎ॲཧɺද໌ϓϩάϥϛ ϯάɺܖ໿ʹΑΔઃܭ / PHP Conference

    2016 — https://speakerdeck.com/twada/php-conference-2016 — ྫ֎ઃܭʹ͓͚Δେࡑ — https://www.slideshare.net/t_wada/exception-design- by-contract
  18. — Laravel ϚχϡΞϧ — https://laravel.com/docs/10.x/errors — ݁ߏྫ֎Λ׆༻͍ͯ͠Δ — ΠϕϯτόϒϦϯάΈ͍ͨʹɺͰ͖Δ͚ͩྫ֎͸Laravel(֎ ଆ)ʹ೚ͤΔελΠϧ

    — ϚʔΫΛ෇͚Δ͜ͱͰɺLaravelଆ͕Α͠ͳʹ͢ΔͳͲ͋Δ — ͚Ͳɺʮศརͳ෼ن໿͕͔ͳΓଟ͍ʯͷͰ֮͑Δͷ͸গʑ େม
  19. PDOExceptionΛྫʹͨ͠ɺඍস·͍͠ਓؒͷ੒௕ྫ — ʮͳΜ͔ɺઌഐ΍AI͕͜͏ॻ͚͍ͬͯͬͨͷͰॻ͍ͨʯ => \Exception͔͔ͭ͠Θͳ͍ظ — ʮDBॲཧ͕ݪҼͱղͬͯศརʂʯ => ྫ֎Λ஌Γ͸͡Ίͯສೳײظ —

    ʮ͸ʁDBॲཧͷͲ͜ͰࢮΜ͔ͩΘ͔ΒΜ͕ʁม׵͢Μͧʯ => ಠࣗྫ֎Ͱͩ͜ΘΓຍ࠿ظ — ʮϩʔϧόοΫͰ͖Ε͹͍͍ΘɺҰԠϩάͯ͠re-throwʯ => ఘ؍ظ