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

iOS アプリエラー監視の設計とその効果

Kuniwak
November 20, 2017

iOS アプリエラー監視の設計とその効果

Kuniwak

November 20, 2017
Tweet

More Decks by Kuniwak

Other Decks in Programming

Transcript

  1. J04ΞϓϦΤϥʔ؂ࢹͷ

    ઃܭͱͦͷޮՌ

    View Slide

  2. "CPVUNF

    View Slide

  3. ,VOJXBL
    גࣜձࣾ%JWFSTFͷJ04ςοΫϦʔυɻ

    ͱ͋ΔΞϓϦͷϦχϡʔΞϧϓϩδΣΫτʹ

    ్த͔ΒՃΘͬͨ΋ͷͷɺςετࠔ೉ͳઃܭΛ

    ໨ͷ౰ͨΓʹͨͨ͠ΊɺཪϦχϡʔΞϧΛ։࢝ɻ

    ࠷ऴతʹςετΛಋೖ͠ɺ։ൃ଎౓Λഒʹͨ͠ɻ
    झຯ͸5%%ɻ

    View Slide

  4. Τϥʔ؂ࢹͱ͸

    View Slide

  5. w ΞϓϦͰൃੜͨ͠ΤϥʔΛαʔόʹૹΔͳͲͯ͠

    Τϥʔͷൃੜ਺ͳͲΛ؂ࢹ͢Δٕज़
    w ྫ͑͹ɺΞϓϦ͕Ϋϥογϡͨ͠ΒϨϙʔτΛ

    ඈ͹͢ΫϥογϡϨϙʔτ͸͜ͷҰछ
    w ΫϥογϡϨϙʔτղੳαʔϏεͰ͸

    $SBTIMZUJDT͕༗໊

    View Slide

  6. Τϥʔͷൃੜ਺΍66
    Τϥʔͷ*% ΤϥʔͷΧςΰϦ
    Τϥʔൃੜ਺ͷ࣌ؒతਪҠ
    Τϥʔൃੜ࣌ͷσόΠε Τϥʔൃੜ࣌ͷ04

    View Slide

  7. w ࣮͸ɺଟ͘ͷΫϥογϡϨϙʔταʔϏε͸

    ΫϥογϡҎ֎ͷΤϥʔ΋؂ࢹͰ͖Δ
    w ྫ͑͹ɺ8FC"1*ݺͼग़࣌͠ͷΤϥʔ΍ɺ

    ಺෦ঢ়ଶͷෆ੔߹ͳͲͷΤϥʔ΋؂ࢹͰ͖Δ
    ૹ৴ͷ࢓ํ͸ޙͰ঺հ͠·͢

    View Slide

  8. Τϥʔ؂ࢹͷಛੑ

    View Slide

  9. w Τϥʔ؂ࢹͷಛੑ͸ҎԼͷͭɿ
    w ඇৗʹ޿ൣͳόάΛݕ஌Ͱ͖Δ
    w Τϯόά͔Βൃݟ·Ͱͷ͕࣌ؒ௕͍

    View Slide

  10. ਤղ

    View Slide

  11. όάΛݟ͚ͭΔͨΊͷखஈ
    ߏจϋΠϥΠτ
    Ϧϯτ
    ੩తܕݕࠪ
    ୯ମɾ݁߹ςετ
    6*ςετ
    ςελʔʹΑΔಈ࡞֬ೝ
    Τϥʔ؂ࢹ
    Ϣʔβʔ͔Βͷ͓໰͍߹Θͤ

    View Slide

  12. όά9Λݟ͚ͭΒΕΔ৔ॴ
    όά9
    ߏจϋΠϥΠτ
    Ϧϯτ
    ੩తܕݕࠪ
    ୯ମɾ݁߹ςετ
    6*ςετ
    ςελʔʹΑΔಈ࡞֬ೝ
    Τϥʔ؂ࢹ
    Ϣʔβʔ͔Βͷ͓໰͍߹Θͤ

    View Slide

  13. όά9
    ߏจϋΠϥΠτ
    Ϧϯτ
    ੩తܕݕࠪ
    ୯ମɾ݁߹ςετ
    6*ςετ
    ςελʔʹΑΔಈ࡞֬ೝ
    Τϥʔ؂ࢹ
    Ϣʔβʔ͔Βͷ͓໰͍߹Θͤ
    ্ͷํͰࢭ·Δͱ
    ݪҼڀ໌ͷ͕࣌ؒ୹͍
    όά:
    ಛघͳΤϥʔ͸ԼͷํͰ

    ͔͠ݟ͚ͭΒΕͳ͍

    View Slide

  14. ߏจϋΠϥΠτ
    Ϧϯτ
    ੩తܕݕࠪ
    ୯ମɾ݁߹ςετ
    6*ςετ
    ςελʔʹΑΔಈ࡞֬ೝ
    Τϥʔ؂ࢹ
    Ϣʔβʔ͔Βͷ͓໰͍߹Θͤ
    Τϥʔ؂ࢹ͸ಛघͳ

    ΤϥʔΛݟ͚ͭΒΕΔ

    ΄΅࠷ޙͷࡆ
    όά:
    ͔͠͠ɺ্ͷ૚ΑΓ΋

    Τϯόά͔Βൃݟ·Ͱͷ

    ͕࣌ؒͱͯ΋௕͍

    View Slide

  15. Τϥʔ؂ࢹͰݟ͚ͭΔ΂͖

    Τϥʔͱ͸

    View Slide

  16. ߏจϋΠϥΠτ
    Ϧϯτ
    ੩తܕݕࠪ
    ୯ମɾ݁߹ςετ
    6*ςετ
    ςελʔʹΑΔಈ࡞֬ೝ
    Τϥʔ؂ࢹ
    Ϣʔβʔ͔Βͷ͓໰͍߹Θͤ
    ༧໿ޠλΠϙ
    ม਺໊λΠϙ
    ϓϩύςΟ໊

    λΠϙ
    ෼ذߟྀ࿙Ε
    ίϯϙʔωϯτ

    ࢓༷ͷෆ੔߹
    ࢓༷΁ͷޡղ
    ಛघͳ؀ڥͰ͔͠

    ࠶ݱ͠ͳ͍Τϥʔ
    ҋ
    ཧ૝ͷόάݕ஌ͷ࢟

    View Slide

  17. ߏจϋΠϥΠτ
    Ϧϯτ
    ੩తܕݕࠪ
    ୯ମɾ݁߹ςετ
    6*ςετ
    ςελʔʹΑΔಈ࡞֬ೝ
    Τϥʔ؂ࢹ
    Ϣʔβʔ͔Βͷ͓໰͍߹Θͤ
    ༧໿ޠλΠϙ
    ม਺໊λΠϙ
    ϓϩύςΟ໊

    λΠϙ
    ෼ذߟྀ࿙Ε
    ίϯϙʔωϯτ

    ࢓༷ͷෆ੔߹
    ࢓༷΁ͷޡղ
    ಛघͳ؀ڥͰ͔͠

    ࠶ݱ͠ͳ͍Τϥʔ
    ҋ
    όά͕Լͷ૚·Ͱ

    ಥ͖ൈ͚ͯ͠·͏ͷͰ

    ݪҼڀ໌ʹ͕͔͔࣌ؒΔ
    μϝͳόάݕ஌ͷ࢟

    View Slide

  18. ߏจϋΠϥΠτ
    Ϧϯτ
    ੩తܕݕࠪ
    ୯ମɾ݁߹ςετ
    6*ςετ
    ςελʔʹΑΔಈ࡞֬ೝ
    Τϥʔ؂ࢹ
    Ϣʔβʔ͔Βͷ͓໰͍߹Θͤ
    ༧໿ޠλΠϙ
    ม਺໊λΠϙ
    ϓϩύςΟ໊

    λΠϙ
    ෼ذߟྀ࿙Ε
    ίϯϙʔωϯτ

    ࢓༷ͷෆ੔߹
    ࢓༷΁ͷޡղ
    ಛघͳ؀ڥͰ͔͠

    ࠶ݱ͠ͳ͍Τϥʔ
    ҋ
    ཧ૝ͷόάݕ஌ͷ࢟

    View Slide

  19. ߏจϋΠϥΠτ
    Ϧϯτ
    ੩తܕݕࠪ
    ୯ମɾ݁߹ςετ
    6*ςετ
    ςελʔʹΑΔಈ࡞֬ೝ
    Τϥʔ؂ࢹ
    Ϣʔβʔ͔Βͷ͓໰͍߹Θͤ
    ༧໿ޠλΠϙ
    ม਺໊λΠϙ
    ϓϩύςΟ໊

    λΠϙ
    ෼ذߟྀ࿙Ε
    ίϯϙʔωϯτ

    ࢓༷ͷෆ੔߹
    ࢓༷΁ͷޡղ
    ಛघͳ؀ڥͰ͔͠

    ࠶ݱ͠ͳ͍Τϥʔ
    ҋ
    Τϥʔ؂ࢹͰݕ஌͢΂͖

    Τϥʔ͸͜ͷ͋ͨΓ

    View Slide

  20. ͜͜·Ͱͷ·ͱΊ
    w Τϥʔ؂ࢹ͸ಛघͳόάΛ

    ݕ஌͢ΔͨΊͷखஈ
    w ͳ͓ɺͦͷલͷखஈͰόάΛ

    ݟ͚ͭΒΕΔͳΒͦͷํ͕

    ݪҼڀ໌Λ଎͘Ͱ͖Δ

    View Slide

  21. Τϥʔ؂ࢹͷ࣮૷ํ๏

    View Slide

  22. Τϥʔ؂ࢹαʔϏεͷ"1*Λ೺Ѳ͢Δ
    ΤϥʔϨϙʔλʔΛΧελϚΠζ͢Δ
    ΤϥʔϨϙʔλʔΛ૊ΈࠐΉ

    View Slide

  23. Τϥʔ؂ࢹαʔϏεͷ

    "1*Λ೺Ѳ͢Δ

    View Slide

  24. Crashlytics.sharedInstance().recordError(error)
    $SBTIMZUJDTʹΤϥʔΛૹ৴͢Δίʔυ
    &SSPSͰ͸ͳ͘/4&SSPSͳͷͰ஫ҙ

    View Slide

  25. ΤϥʔϨϙʔλʔΛ

    ΧελϚΠζ͢Δ

    View Slide

  26. ౎౓/4&SSPSʹม׵͢Δͷ͸

    ໘౗ͳͷͰɺ4XJGUͷ&SSPSΛ

    /4&SSPS΁͍͍ײ͡ʹ

    ม׵ͯ͘͠ΕΔϥούʔΛॻ͘
    $SBTIMZUJDTͷ৔߹

    View Slide

  27. class CrashlyticsErrorReporter {
    private let crashlytics = Crashlytics.sharedInstance()
    func report(error: Error, from reporter: Any) {
    #if DEBUG
    let prefix = "DEBUG "
    #else
    let prefix = ""
    #endif
    let detailedError = NSError(
    domain: "\(prefix)\(type(of: reporter))",
    code: CrashlyticsErrorTracker.getNSErrorCode(bySwiftError: error),
    userInfo: [
    NSLocalizedDescriptionKey: "\(error)",
    ]
    )
    self.crashlytics.recordError(detailedError)
    }
    private static func getNSErrorCode(bySwiftError error: Error) -> Int {
    let bridgedError = error as NSError
    return bridgedError.code
    }
    }
    %(ϏϧυΛ۠ผͰ͖ΔΑ͏ʹ͓ͯ͘͠ͱ

    ຊ൪؀ڥͱ։ൃ؀ڥΛ۠ผ͠΍͘͢ͳΔ
    ΤϥʔϨϙʔλʔʹͲͷΑ͏ʹ

    ूܭ͞ΕΔ͔Λҙࣝ͢Δ
    ྫ͑͹ɺ$SBTIMZUJDT͸/4&SSPSͷ

    EPNBJOͱDPEFͷ૊ͰΤϥʔΛ

    άϧʔϐϯά͢ΔͷͰɺͦΕͧΕ

    Τϥʔ*%ͱൃੜݩΛࢦఆ͢Δͱ

    ៉ྷʹΤϥʔ͕·ͱ·Δ
    Τϥʔͷશจ͸άϧʔϐϯάʹ࢖Θͳ͍Α͏ʹ͢Δ

    ʢϢʔβʔ*%ͱ͔͕ೖΔͱݸผͷΤϥʔʹͳͬͯ

    ૯਺Λ೺ѲͰ͖ͳ͘ͳΔͷͰʣ
    $SBTIMZUJDT༻ͷΤϥʔϨϙʔλʔΛ࡞੒
    SFQPSUؔ਺ΛݺͿͱΤϥʔϨϙʔτ͕

    ૹ৴͞ΕΔΑ͏ʹ࣮૷͍ͯ͠Δ
    ΤϥʔͱҰॹʹൃੜݩ͕

    Θ͔ΔΑ͏ʹ͓ͯ͘͠ͱ

    ݪҼͷݟ౰͕͖ͭ΍͘͢ͳΔ

    View Slide

  28. ΤϥʔϨϙʔλʔΛ

    ૊ΈࠐΉ

    View Slide

  29. Α͘ΈΔ૊ΈࠐΈํ๏

    View Slide

  30. class UserApiRepository: UserRepositoryProtocol {
    private let api: GitHubApiClientProtocol
    func get(by id: GitHubUser.Id) -> Promise {
    return self.api
    .fetch(
    endpoint: GitHubApiEndpoint(path: "/user/" + id.text),
    headers: [:],
    parameters: []
    )
    .then { data -> GitHubUser in
    let response: GitHubUserResponse = try unbox(data: data)
    return response.user
    }
    .catch { error in
    ErrorReporter.shared.report(
    error: error,
    reporter: self
    )
    }
    }
    }
    Τϥʔ͕ฦ͖ͬͯͨΒ

    ΤϥʔϨϙʔτΛૹ৴

    ʢͭ·Γϋʔυίʔυʣ
    "1*ݺͼग़͠ͷίʔυ

    View Slide

  31. w ͔͠͠ϋʔυίʔυʹ͸໰୊͕ଟ͍ɿ
    w "1*ͷݺͼग़͠ʹؔ܎ͷͳ͍ίʔυ͕ೖΔͨΊ

    ୯Ұ੹຿ݪଇʹҧ൓͢Δ
    w ୯ମςετͰΤϥʔϨϙʔτ͕ૹΒΕͯ͠·͏
    w ςετ͚࣌ͩ༗ޮͳϑϥάΛ௥Ճͯ͠

    ΤϥʔϨϙʔτΛૹ৴͠ͳ͍Α͏ʹ

    Ͱ͖ͳ͘͸ͳ͍͕΍Γͨ͘ͳ͍

    View Slide

  32. ͦ͜Ͱ0CTFSWFSύλʔϯ

    View Slide

  33. w 0CTFSWFSύλʔϯͰ͸ɺ؂ࢹऀ͕؂ࢹର৅ͷ

    ঢ়ଶมԽΛ࡯஌ͯ͠ಈ࡞͢Δ
    w ΤϥʔϨϙʔτʹԠ༻͢Δͱɿ
    w ؂ࢹऀ͸ΤϥʔϨϙʔλʔ
    w ؂ࢹର৅͸Τϥʔ͕ൃੜ͠͏ΔΦϒδΣΫτ
    w 0CTFSWFSύλʔϯΛ࢖͑͹ϋʔυίʔυͷ

    ໰୊఺ΛղফͰ͖Δ

    View Slide

  34. Τϥʔͷൃੜݯ Τϥʔ͕ൃੜ͠·ͨ͠
    ΤϥʔϨϙʔλʔ
    ؂ࢹ

    View Slide

  35. Τϥʔͷൃੜݯ
    ΤϥʔϨϙʔλʔ
    ؂ࢹ
    Τϥʔ͕ى͖ͨͷͰ

    ΤϥʔϨϙʔτΛ

    ૹ৴͠·͢

    View Slide

  36. Τϥʔͷൃੜݯ
    ΤϥʔϨϙʔλʔ
    ΤϥʔϨϙʔτૹ৴

    ͷ੹຿
    Τϥʔ͕ى͜Γ͏Δ

    ॲཧͷ੹຿

    View Slide

  37. Τϥʔͷൃੜݯ
    ΤϥʔϨϙʔλʔ
    ςετͷͱ͖͸

    ΤϥʔϨϙʔλʔ

    ΛऔΓ෇͚ͳ͍

    View Slide

  38. w ͨͩ͠ɺ0CTFSWFSύλʔϯΛ࢖͑Δ৔ॴ͸

    ݶΒΕ͍ͯΔ
    w ؂ࢹର৅͕؂ࢹՄೳͳΠϯλʔϑΣʔεΛ

    උ͍͑ͯͳ͍ͱ͍͚ͳ͍ͨΊ
    w .7ΞʔΩςΫνϟͳΒɺ.PEFM͸؂ࢹՄೳͳ

    ΠϯλʔϑΣʔεΛ͍࣋ͬͯΔ͸ͣͳͷͰɺ.PEFMʹ

    ΤϥʔϨϙʔλʔΛ૊ΈࠐΈ΍͍͢͸ͣ
    w 'MVYΞʔΩςΫνϟͷ৔߹͸4UPSF͕؂ࢹՄೳͳ

    ΠϯλʔϑΣʔεΛ͍࣋ͬͯΔ͸ͣ

    View Slide

  39. .PEFM
    7JFX &SSPS3FQPSUFS
    ؂ࢹ ؂ࢹ
    Τϥʔ͕ൃੜ͠·ͨ͠

    View Slide

  40. .PEFM
    7JFX &SSPS3FQPSUFS
    ؂ࢹ ؂ࢹ
    ΤϥʔΛදࣔ͠·͢ ΤϥʔϨϙʔτΛ

    ૹ৴͠·͢

    View Slide

  41. func fetch() {
    switch self.currentState {
    case .fetching:
    return
    case .fetched:
    self.stateMachine.transit(to: .fetching)
    self.repository.get(by: self.id)
    .then { user in
    self.stateMachine.transit(to: .fetched(
    result: .success(user)
    ))
    }
    .catch { error in
    self.stateMachine.transit(to: .fetched(
    result: .failure(.unspecified(debugInfo: "\(error)
    ))
    }
    }
    }
    ͜ͷ.PEFM͸"1*ݺͼग़͕͠Τϥʔʹ

    ͳͬͨΒΤϥʔঢ়ଶ΁มԽ͢Δ
    ͦͷͨΊɺΤϥʔϨϙʔλʔ͔Β؂ࢹ͠΍͍͢
    .PEFM૚ͷ"1*ݺͼग़͠ͷίʔυ
    ͜ͷΑ͏ͳ.PEFM૚ͷઃܭͷৄࡉ͸IUUQTHPPHM1G'+-Λࢀর͍ͯͩ͘͠͞

    View Slide

  42. class UserModelErrorReporter {
    private let model: UserModelProtocol
    private let errorReporter: ErrorReporterProtocol
    private let disposeBag = RxSwift.DisposeBag()
    init(
    observing model: UserModelProtocol,
    reportingBy errorReporter: ErrorReporterProtocol
    ) {
    self.model = model
    self.errorReporter = errorReporter
    self.model.didChange
    .subscribe(onNext: { [weak self] (state: UserModelState) in
    guard let `self` = self else { return }
    switch state {
    case let .fetched(result: .failure(error)):
    self.errorReporter.report(error: error, reporter: self)
    default:
    return
    }
    })
    .disposed(by: disposeBag)
    }
    }
    .PEFM͕Τϥʔঢ়ଶʹͳͬͨΒ

    ΤϥʔϨϙʔτΛૹ৴
    .PEFMͷঢ়ଶભҠΛ؂ࢹ
    .PEFMΛ؂ࢹ͢ΔΤϥʔϨϙʔλʔΛ࡞੒

    View Slide

  43. ͜͜·Ͱͷ·ͱΊ
    w Τϥʔ؂ࢹͷ࣮૷ํ๏ʹ͸

    0CTFSWFSύλʔϯ͕ద͍ͯ͠Δ
    w .PEFM΍4UPSFΛ؂ࢹ͢ΔͱΑ͍

    View Slide

  44. Τϥʔ؂ࢹͷίπ

    View Slide

  45. Τϥʔ؂ࢹΛ։࢝͢Δͱ

    ࠔౕ͕ͬͨͪΐͪ͘ΐ͘ग़ͯ͘Δ

    View Slide

  46. Fatal Exception: SomethingError
    *** nil
    ৘ใྔ͕΄ͱΜͲͳ͍ʂ

    View Slide

  47. Τϥʔʹ͸ͳΔ΂͘

    ΫϦςΟΧϧͳ৘ใΛ٧ΊΑ͏

    View Slide

  48. func example(input: String) -> String? {
    guard validate1(input) else {
    return nil
    }
    guard validate2(input) else {
    return nil
    }
    guard validate3(input) else {
    return nil
    }
    return "OK! Hello \(input)"
    }
    ݪҼڀ໌Λ஗͘͢Δѱ͍ྫ
    ࣦഊͨ͠ݪҼ͕۠ผͰ͖ͳ͍

    View Slide

  49. enum ExampleError: Error {
    case validate1(debugInfo: String)
    case validate2(debugInfo: String)
    case validate3(debugInfo: String)
    }
    ͦ͏͍͏࣌͸FOVNͰΤϥʔΛ۠ผͰ͖ΔΑ͏ʹ͢Δ

    View Slide

  50. func example(input: String) -> Result {
    guard validate1(input) else {
    return .failure(.validate1(debugInfo: input))
    }
    guard validate2(input) else {
    return .failure(.validate2(debugInfo: input))
    }
    guard validate3(input) else {
    return .failure(.validate3(debugInfo: input))
    }
    return .success("OK! Hello \(input)")
    }
    ΤϥʔΛ۠ผ͠΍͍͢Α͍ྫ
    ࣦഊͨ͠ݪҼ͕۠ผͰ͖Δ
    ࣦഊͨ͠ࡍͷೖྗ΋ೖखͰ͖Δ

    View Slide

  51. Τϥʔ؂ࢹͷ݁Ռ

    View Slide

  52. w ϢʔβʔͷखݩͰൃੜ͍ͯ͠Δόάͷछྨ΍

    ن໛Λ೺ѲͰ͖ΔΑ͏ʹͳͬͨ
    w छྨ΍ن໛͕೺ѲͰ͖ΔΑ͏ʹͳΔͱɺόάमਖ਼ͷ

    τϦΞʔδΛ΍Γ΍͘͢ͳΔ
    w ՝ۚܥ͸࠷༏ઌͱ͔ɺΤϥʔ਺ͷଟ͍΋ͷ

    ͔ΒରॲͳͲ

    View Slide

  53. w ϢʔβʔͷखݩͰ͸༧૝֎ͷΤϥʔ͕ى͍ͬͯͨ͜
    w ςελʔʹΑΔಈ࡞֬ೝͱ͸؀ڥ͕

    ҟͳ͍ͬͯͨͨΊɺൃݟͰ͖ͳ͔ͬͨΑ͏ͩ
    w ໘ന͍͜ͱʹɺ"QQMFͷϨϏϡʔΞ͕ૺ۰ͨ͠

    όάͷݪҼڀ໌ʹ΋໾ཱͬͨ
    w 4BOECPYͷϨγʔτ͡Όͳ͍ͱ࠶ݱ͠ͳ͍

    όάͩͬͨ

    View Slide

  54. w خ͍͠෭࡞༻ͱͯ͠ɺςελʔͷૺ۰ͨ͠όάͷ

    ৄࡉ͕ೖखͰ͖ΔΑ͏ʹͳͬͨ
    w ςελʔͷಈ࡞֬ೝதʹΤϥʔϨϙʔτ͕

    ඈΜͰ͘ΔͨΊʢ%(ϏϧυͳͷͰ͙͢Θ͔Δʣ
    w σόοά͕͍͢͝ḿΓ·͢

    View Slide

  55. ·ͱΊ
    w Τϥʔ؂ࢹʹΑΓɺςελʔʹΑΔಈ࡞֬ೝͰ΋

    ݟ͚ͭΒΕͳ͔ͬͨΤϥʔΛൃݟͰ͖ΔΑ͏ʹ

    ͳͬͨ
    w ઃܭͷίπ͸ҎԼͷͭɿ
    w 0CTFSWFSύλʔϯΛ࢖͏
    w ͳΔ΂͘Τϥʔ৘ใΛΘ͔Γ΍͘͢͢Δ

    View Slide

  56. એ఻
    w %JWFSTFͰ͸৽نࣄۀͷJ04։ൃνʔϜͷ

    νʔϜϦʔμʔΛืू͍ͯ͠·͢ʂ
    w ੒௕தͷαʔϏεΛࣗ෼ͷྗͰಋ͍ͯΈ͍ͨͱ

    ࢥ͏ํʹɺͥͻ͖ͯ΄͍͠ͱࢥ͍ͬͯ·͢ʂ
    w ͝ڵຯ͕͋Γ·ͨ͠ΒɺҰॹʹϥϯνʹ

    ߦ͖·ͤΜ͔ʁ

    View Slide