Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Laravel.shibuya

1,608 views

Published on

Laravel.shibuya

Published in: Engineering
  • Login to see the comments

Laravel.shibuya

  1. 1. バリデーション駆動開発(仮称)で プロジェクトメンバー全員を幸せにした話 Laravel/オレオレ両対応
  2. 2. アジェンダ 話すこと 開発フェーズに特有の「厄介ななバグ」 「バリデーション」の適用範囲を考える php/Laravel での実装例 テスト駆動開発との併用 話さないこと Laravel のValidation
  3. 3. 自己紹介 @KentarouTakeda サーバサイド php / node.js / TypeScript Laravel / express / AWS Lambda PostgreSQL / MySQL インフラ RHEL / CentOS / AmazonLinux / Chef / Ansible フロントエンド TypeScript Angular / jQuery / Hexo
  4. 4. 心がけていること 担当業務の範囲にとらわれず、 幅広い視野で全体の効率アップを図りたい。 API / ネットワーク/ インターフェース 人/ 部署/ 会社
  5. 5. 「厄介なバグ」とは? 影響範囲が広い 再現が取れない 利用者への影響が大きい
  6. 6. 事例「ソシャゲのガチャ機能」 開発は「サーバ」「アプリ」「デザイナー」の3名 抽選はサーバ側の「ガチャAPI」が行う ガチャAPIの返却に応じてアプリ上で入手演出 素材は随時追加・デザイナー単独でcommit
  7. 7. バグ報告 「回す」ボタンを押した瞬間にクラッシュ。 演出まで進まない=画面には何の情報も無い 頻度は100回に1回程度。 外部スタッフからの報告 クラッシュログ入手困難 最初に誰に相談するか?
  8. 8. 考えられる原因 特定条件で「サーバ」が規格外のレスポンス 特定条件で「アプリ」が未定義プロパティを参照 特定の「素材」が破損している? 最初に誰に相談するか?
  9. 9. 些細なバグの裏で、こんなことが起きている? 「誰に相談すれば良いんだろう?」 「自分ではありません!」 「相談したら怒られた…」 「例のバグ、進捗どうですか?」 「全員に相談、するしかないな…」 「また人のバグで時間取られるのか…」
  10. 10. 「厄介なバグ」例えばこういう指針 困るのは誰か? 自分<自分以外の誰か 遭遇するのは誰か? 自分<自分以外の誰か 原因箇所を特定できるのは誰か? 自分以外の誰でも<自分のみ
  11. 11. 「厄介なバグ」をどう対処するか? 人海戦術で原因探し! 出さないように気をつける! あらかじめ可能性を潰しておく。
  12. 12. 今回はどこに問題があったのか?
  13. 13. 今回はどこに問題があったのか? 特定条件で「サーバ」が規格外のレスポンス というバグが存在したこと。
  14. 14. 今回はどこに問題があったのか? 特定条件で「サーバ」が規格外のレスポンス というバグが存在したこと。 というバグの可能性が存在したこと。
  15. 15. 可能性を潰す
  16. 16. IT用語辞典バイナリ バリデーション https://www.sophia-it.com/content/バリデーション 入力されたデータが、あるいはプログラミング言 語やマークアップ言語の記述が、規定された文法 に即して、または要求された仕様にそって、適切 に記述されているかどうかを検証することであ る。 “ “
  17. 17. バリデーションが必要な値とは? 外部から入力された値 信頼できない値
  18. 18. 信頼できない値
  19. 19. 自分の書くプログラムに 100%の信頼を持てますか?
  20. 20. JSON Schema https://json-schema.org/ JSONへの型定義 JSONが特定の型を満たしているか検証 JSON Schema is a vocabulary that allows you to annotate and validate JSON documents. “ “
  21. 21. ガチャAPI gatcha: # ガチャAPIは type: "object" # オブジェクトを返却する properties: cardIds: # cardIds プロパティは type: "array" # 配列型を取り items: type: "integer" # それぞれの値は整数 minimum: 1000 # 最小値は1000 maximum: 2999 # 最大値は2999 minItems: 1 # 配列の要素数は最小で1個 minItems: 3 # 最大で3個(1~3個のカードが出てくるガチャ) bonusId: # bonusId というプロパティは type: "integer" # 整数を取り minimum: 3000 # 3000以上 maximum: 4999 # 4999以下 required: # 必須プロパティは - "cardIds" # cardIds のみ(ボーナスは無い時もある) additionalProperties: false # 上記で定義された以外のプロパティは存在
  22. 22. PHP での実装 $ composer require justinrainbow/json-schema $validator = new Validator(); $validator->validate($response, $schema, $options); if(true !== $validator->isValid()) { throw new Exception("サーバ側のバグ!"); } echo json_encode($response); status=200 であれば必ず安全 安全でない場合必ず必ずstatus=500
  23. 23. 可能性を一つ潰した
  24. 24. 事例「Webアプリのマイページ」 Laravel案件、2名体制。 サーバエンジニア ルーティングやコントローラーを担当 Webデザイナー Bladeテンプレートへhtmlをコーディング
  25. 25. バグ報告 プロフィールに「氏名」を登録した状態でマイページ にアクセスすると以下のエラーが表示されます。 ErrorException (E_ERROR) Undefined variable: firstName (View: /var/www/resources/views/mypage/index.blade.php)
  26. 26. 考えられる原因 テンプレート側のtypo 正しくは$ rst_name だった コントローラー側のバグ $ rstName の初期化漏れ
  27. 27. 今回はどこに問題があったのか?
  28. 28. 今回はどこに問題があったのか? コントローラー側のバグ $ rstName の初期化漏れ というバグが存在したこと。
  29. 29. 今回はどこに問題があったのか? コントローラー側のバグ $ rstName の初期化漏れ というバグが存在したこと。 というバグの可能性が存在したこと。 ※コーダーはプログラマーの想像以上にエラー画面に ビビってます。
  30. 30. ビューに渡す値を検証 class MypageController extends Controller { public function index(Request $request) { /* ビューに渡す値を準備する */ /* ここでバリデーション? */ return view('mypage.index', $data); } } 何の保証にもならない。 (仕組み化して一括で防ぎたい)
  31. 31. コントローラーではオブジェクトを返 却するのみ class MypageController extends Controller { public function index(Request $request) { /* ビューに渡す値を準備する */ return $data; } }
  32. 32. ビュー適用 class ApplyViewMiddleware { public function handle($request, Closure $next) { $response = $next($request); /* 実際は abort() や RedirectResponse の際の処理が必要 */ $name = Route::currentRouteName(); // ビュー名 = ルート名 $response = new Response(view($name, $content)); return $response; } }
  33. 33. バリデーション class ValidationMiddleware { public function handle($request, Closure $next) { $response = $next($request); $name = Route::currentRouteName(); // バリデーション名 = ルート $isValid = $this->valication($response,$this->getSchema($nam if($isValid) { abort(500, "コントローラー側のバグ!") } else { return $response; } }
  34. 34. ミドルウェア適用 afterミドルウェアなので定義は逆順。 namespace AppHttp; use IlluminateFoundationHttpKernel as HttpKernel; class Kernel extends HttpKernel { protected $middlewareGroups = [ 'web' => [ /**/ AppHttpMiddlewareApplyView::class, // ビュー適用が先 AppHttpMiddlewareValidation::class, // バリデーションが後 ], ] }
  35. 35. バリデーションとテスト
  36. 36. テスト駆動開発(TDD) [DO03] 50 分でわかるテスト駆動開発 https://www.slideshare.net/decode2017/do03-50/9
  37. 37. TDDのサイクル 1. 次の目標を考える 2. その目標を示すテストを書く 3. そのテストを実行して失敗させる(Red) 4. 目的のコードを書く 5. 2.で書いたテストを成功させる(Green) 6. テストが通るままでリファクタリングを行う (Refactor) 7. 1~6を繰り返す
  38. 38. TDDと黄金の回転 Red, Grren, Refactor https://www.slideshare.net/decode2017/do03- 50/10
  39. 39. 出力の検証=テスト
  40. 40. バリデーション駆動開発 1. 次の目標を考える 2. その目標を示すテストバリデーションを書く 3. そのテストバリデーションを実行して失敗させる 4. 目的のコードを書く 5. 2.で書いたテストバリデーションを成功させる 6. テストが通るままでリファクタリングを行う 7. 1~6を繰り返す
  41. 41. 「ガチャAPI」
  42. 42. 目標「ガチャAPIが存在する」 // ApiTest.php class ApiTest extends TestCase { /** @test */ public function gatcha() { $this->post('/api/gatcha', [])->assertStatus(200); } }
  43. 43. テストをパスする最小限のコード # api.schema.yml gatcha: // ApiController.php class ApiController { public function gatcha() { } }
  44. 44. 次の目標「返却はcardIdsというプロ パティを持つ」 gatcha: + type: "object" + properties: + cardIds: class ApiController { public function gatcha() { + return [ + 'cardIds' => null, + ]; } }
  45. 45. 次の目標「cardIdsは配列」 gatcha: type: "object" properties: cardIds: + type: "array" class ApiController { public function gatcha() { return [ - 'cardIds' => null, + 'cardIds' => [], ]; } }
  46. 46. 次の目標「cardIdsの各要素(略)」 gatcha: type: "object" properties: cardIds: type: "array" + items: + type: "integer" + minimum: 1000 + maximum: 2999 + minItems: 1 + minItems: 3
  47. 47. class ApiController { public function gatcha() { return [ - 'cardIds' => [], + 'cardIds' => [1000, 1001, 1002], ]; } }
  48. 48. 開発体験 最小限のテストコード Red -> Green -> Refactor 仕様・実装・テストが同時に出来上がっていく 共有可能な成果物
  49. 49. 「ガチャAPI」
  50. 50. 今回はどこに問題があったのか?
  51. 51. 今回はどこに問題があったのか? 特定条件で「アプリ」が未定義プロパティを参照 というバグが存在したこと。
  52. 52. 今回はどこに問題があったのか? 特定条件で「アプリ」が未定義プロパティを参照 というバグが存在したこと。 というバグの可能性が存在したこと。
  53. 53. TypeScript https://github.com/bcherny/json-schema-to- typescript
  54. 54. { "title": "Example Schema", "type": "object", "properties": { "firstName": { "type": "string" }, "lastName": { "type": "string" }, "age": { "description": "Age in years", "type": "integer", "minimum": 0 }, "hairColor": { "enum": ["black", "brown", "blue"], "type": "string" } }, "additionalProperties": false,
  55. 55. export interface ExampleSchema { firstName: string; lastName: string; /** * Age in years */ age?: number; hairColor?: "black" | "brown" | "blue"; }
  56. 56. C# https://www.newtonsoft.com/jsonschema
  57. 57. { "$schema": "http://json-schema.org/draft-03/schema#", "title": "Country", "description": "A nation with its own government, occupying a "type": "object", "properties": { "flag": { "$ref": "flag.json" }, "cities": { "type": "array", "description": "A large town", "items": { "$ref": "city.json" }, "uniqueItems": true }, "population": { "type": "integer", "description": "The number of people inhabiting this count "minimum": 1000,
  58. 58. namespace generated { using System; using com.cvent.country.entities; using generated; using System.Collections.Generic; using Cvent.SchemaToPoco.Core.ValidationAttributes; using System.ComponentModel.DataAnnotations; // A nation with its own government, occupying a particular public class Country { // Used as the symbol or emblem of a country public Flag Flag { get; set; } // A large town public HashSet<City> Cities { get; set; } // The number of people inhabiting this country [Required()] [MinValue(1000)]
  59. 59. クライアントへの適用 ビルドが通っていれば確実に安全 安全でない場合ビルドで落ちる
  60. 60. 心がけていること 担当業務の範囲にとらわれず、 幅広い視野で全体の効率アップを図りたい。 API / ネットワーク/ インターフェース 業務/ 人/ 部署/ 会社 仕組み化で解決。 サーバサイドは仕組み化に取り組みやすい立ち位置。

×