PHP7で堅牢なコードにを書く
SQLアンチパターンから抜粋
- globalの変数を使う時にキー名を書き換えられる可能性があるので注意
- カラム名が変わる可能性がある
- バインディングのキー名とパラメータ数がずれている可能性がある
- 不具合の発見が遅れれば遅れるほど傷は深くなる
予防的プログラミング
- nullじゃない、arrayじゃない、パラメータ数があってない、を全てチェックする
- 良いインターフェースの条件
- 正しく使用するほうが操作ミスするより簡単
- 誤った使い方が難しいように設計する
- 値の制限(Enum)
- 関数のサイズを小さくする
- ひらくさんのQiitaみよう
- 責務の多すぎ問題
- 知りすぎないやりすぎない
- PHPはやや緩めの言語なので予防的な制約を設けることで安全にプログラミングできる
攻撃的プログラミング
- 何らかの疑いがあるのであればどのような場合でも速やかに停止させるべき
- そんなに気楽にシステムを落としていいの?
- 正当性と堅牢性
- 正当性: 正しくない答えが帰ってきたら死ぬ
- 堅牢性: ちょっとぐらい間違ってても動き続けて間違ってても結果を返す
- 戻り値の弱点
- コードが肥大化しがち
- 無視されやすい
- PDO::ERRMODE_EXCEPTION を使うときれいになる
- 例外の利点
- 無視できない方法でエラー状態を知らせる
- 例外を握りつぶすのは可能だが開発者として見逃してはいかんよ
- 表明プログラミング
- 起こるはずがないと思っていることは調べろ
- PHPのassert()
- php.iniでassert.exception=1にすると表明違反例外で落ちる
- コードを書いた時の前提をコードの読み手が理解しやすくなる
- assert入れすぎると遅くならない?
- PHP7では本番モードでチェックしないようにできる
- assertの中には実行に必要なコードを記述しない
- 副作用をうまない
- 障害を抱えて中途半端に動くよりも死んだプログラムのほうがダメージは少ない
契約プログラミング
- 事前条件違反は呼び出し側のバグ
- 事後条件違反は供給者側のバグ
- バグと例外を区別して誰の責任かも見分けられるようにする
まとめ
- バグが少ないから品質が高いのではなく気付きやすく直しやすさこそが品質の高さ
- 分かりやすく間違えるのが難しいプログラム設計にする
- 下手に動き続けず死にやすいシステムにする
- 責任の所存を分かりやすくする
- 動くプログラムと正しいプログラムの違いを認識する
flowtypeで始める型のあるJavaScript
近年のweb
- ちょっとリッチなWebサイト構築だけでは終わらなくなった
- つらみがおおくなってきた
flowって何
- JavaScriptのための静的型解析ツール
- Facebookが作ってる
- あくまで型解析ツール
- JavaScriptのバグは見逃してはいけない
- 必要最低限のバグ報告を行う
- 速度は精度と相反する
- Object型の定義はJavaScriptと相性がいい
実際に見てみよう
//@flow function add(a: number, b: number): number { return a + b; } add("1", 2);
- flowのチェックをすると文字列型を渡すなとエラーが起きる
どうやって使うの
- 形のある言語として使うための機能は大体サポートされてる
- 4ステップ5分で始められる
- babel導入とflowコマンド
- コードが肥大化してる時、実装が遅れている時に有効
- アノテーションを入れるだけなのでランタイム・コンパイルには関与しない
- flowコマンドの実行をやめるだけで良いので捨てやすい
- 小規模プロジェクトや型が必要か悩ましい場合はあまり向かない
- 新規プロジェクトならflowでなくTypeScript使ったほうが良い
実際に導入してみよう
- まずは // @flow を追加する
- 引数の型宣言を追加nullableも宣言可能(a: ?string)
- yarn flow
- typeキーワードでEnumを作る
- Object typeで型宣言して堅牢で保守性を高くする
- 型定義をパイプで挟むと定義していないキーをエラーにできる
君の選択は正しいのか?
技術選定について
- 使用する技術はエンジニアに決めさせることで技術とモチベーションを上げる
- 明確な意志で技術選定を行うべし
- 要件を満たすだけだと適当な判断で決めてはいけない
- 技術選定をするには制限が必ずある
- その中で最善の選択をするには…真剣に考えろ
僕達がやってきたレガシープロジェクトとの付き合い方
やってきたレガシープロジェクト
- PHP5.3系のお話
- 5年動いているブラウザゲー
- LAMP
- SVN
- PHPもCodeigniterもMySQLもサポート切れのまま動いてる
問題点
テストコードがまったくなかった(安全性)
- テストコード書きました
- svn upをフックしてJenkinsでテスト実行
- マスタデータ(.csv)、設定ファイル(.ini) に対するテストを書いた
- レガシープロジェクトへの導入にあたってはテストはあくまでも手段
エンジニアがini職人化していた(楽さ)
- エクセルファイルをみてiniをべた書きだった
- 細かい自動化でヒューマンエラーによるリスクを軽減
コードレビューの文化がなかった(モダンさ)
- プルリク文化を作りたかった
- SVNとgitで同期を取って既存の仕組みを壊さずにgitを導入した
- Gitで開発できるようになってプルリク文化になった
- Hubotを入れてDevOps,ChatOpsの推進
海外からのアクセスを制御する
mrubyで作る海外IPフィルター
- 突如レスポンス遅延が起こった
- 海外からの攻撃アクセス
- 海外IPフィルターを作った
- レスポンス速度は低下してはならない
- 通すべき海外からのアクセス許可
- Googleなどのサービスからのクローリングは通す必要がある
- nginxがリバプロしてアクセス先のサーバを選ぶ
- nginx_mruby を使って弾いてる
実際にやったこと
- IPと国コードを紐付けたMaxMindDBを利用して弾く
- UserAgentを見て通すものは通す
- リクエストパスをチェックするしてNGにする
- localmemcache->KVS->MySQLと速い順に問い合わせ
- 検索結果を上位のサーバにキャッシュする
1人から始める大規模webアプリケーションの言語バージョンアップ
どのようにバージョンアップするに至ったのか
- 10年以上かどうしているサービス
- 時が経ち大規模化・複雑化しバージョンアップのコストが膨大に
- 古い言語を使い続けるには課題が山積み
- セキュリティリスク・エンジニア雇用に影響するのでは
- ナレッジが古く開発者・サービスの成長を阻害する
- エンジニアの成長はサービスの成長
- 基盤改善だけを行う時間を作って徐々にやろうとするも差し込みが多く失敗
- バージョンアップを含む基盤改善の専任チームを作る
バージョンアップするには
- プルリクエストを細かく出す
- 迷ったら安全な方
- やりすぎない(最小限の変更でバージョンアップする)
- リーダブルな資料を残す
- Issueを立てて考えを書いておいて有識者に見てもらう
- 何をどこまでやるか決める
- どのバージョンまで上げるか、どの機能のバージョンを上げるか
- 不安要素をリストアップする
- 壁になりそうなものには何があるか
- 動作の保証方法をどう決めるか
- register_globalsとmagic_quotes_gpcが5.3→5.6で消えるのが問題だった
- PHPの設定においてOn/Offどちらでも振る舞いが変わらないようにする
- Vagrant5.6の動作環境を作ってほぼほぼ動く環境を用意した
- そもそもカバレッジが低かったので動作検証はテストを追加せず手動で確認…
- 本番サーバの1台だけをLBに入れて動作を確認していく
バージョンアップした後
- テックミーティングをひらいて他案件でも他案件でも少しずつ移行を推奨していく
- 一人でやりきったわけでは無いけど一人から始められた
- 全て5.6系にするまでに7ヶ月かかったけど…
Progressive Web Apps + AMP = PWAMP for PHPer
Webアプリの良さ
- 回線環境の悪い国ではアプリをDLすることに抵抗感が強い
- TwitterLiteで見ても初期のDL容量が圧倒的に少ない
AMPとは
- リッチで遅いよりシンプルで速いことに対する新提案
- AMPはモバイル専用ではない
- 実装するためには「できないことを理解すること」が重要
- JavaScriptは使えない
- 外部CSSが使えない
- AMP startやAMPの公式を除けば今日から実装できるくらい簡単
- ブログ、ニュースサイト、レシピサイト、などなどに相性がいい
PWAとは
- ホーム画面にアイコン追加、フルスクリーン、プッシュ通知、オフラインキャッシュなど…
- 段階的に導入できる
- 今既に動いているアプリケーションも徐々に対応可能
- コンテンツのキャッシュコントロールが難しい
- Safari早く対応しろ
- 大規模サイトを完全対応するのはそれなりに工数がかかる
- manifest.json, service worker, and more…
Service worker
- ブラウザないでいいかんじに起動/停止するJSworker
- 永続的なデータ保存ができないのでIndexedDBなどを使う
- DOM操作はできない
- 対象スコープ以下で動作し続ける
- SWはプログラマブルなプロキシである
- ページからのリクエストをインターセプトしコントロール
- 外からの通知をよしなに操作してページにレスポンスとして返す
App Shellモデル
- 最低限のコンテンツをオフラインで描画できるようにキャッシュする
- 動的コンテンツはキャッシュを利用しながらも最新の情報を返す
PWA開発のためのライブラリ/ツール
- Workbox
- sw-precacheに変わるSW用ライブラリ
- Lighthouse
- PWA度やパフォーマンス、アクセシビリティを評価
PWA + AMP
- PWAはSWインストール後が速い
- AMPは最初の読み込みが早い
- AMPページでSW読み込みたい
- amp-install-serviceworkerタグ
- JSが動かないはずのAMP内でもSWをインストールできる
- AMP to PWA→SafariでAMP、AndroidでPWAを表示できる
- Webサービスメインの案件にはPWA対応の価値が大いにある
- AppleはWebの進化を止め続けるのか対応早くしろ
PHPerに覚えて欲しい日本語の重要性
バリデーションメッセージ
- 未入力です、正しく入力してください、不正な値です…これは正しいメッセージですか?
- 未入力、ではどうすれば良いのか分からない
- 入力フォーマットを正しく判別する事が重要
- 下さいは実質動詞、ものをもらう時に使う
- くださいは補助動詞、お願いをする時に使う
- 僕の過去ブログ「校正について私が知っている9つの方法」にも似たようなこと書いてるよ
エラーと例外の再入門
エラーの定義
- HTTPでファイル取得する関数に例外を定義するなら
- 型が違う、URLが違う、ドメインが見つからない、3時間たっても動かない
- バグもあるしもらい事故もある
- PHPの立場でのエラーとは実行時に処理しているあいだに起こる何か悪いところ
- PHPはエラーの箇所が多い言語なので気を使うべし
エラーの拾い方
- エラーの発生パターンと後始末を分けて書くときれいになる
- set_exception_handlerで一切catchされなかったエラーを処理できる最後の砦
エラーと例外
- エラーに関する情報は文字列のみ
- エラーレベルによって動きが違う
- 例外は無視できない、エラーより高機能
- エラーは無視できるので、古い関数の撲滅などに役立つ
例外安全とオブジェクト指向
- コード中にエラー処理が登場しないのできれいになる
- 例外は何か不具合が起きた時に速やかに死んでくれる
- 例外がいるといつどこで死ぬか分からない
- ゾンビキャッシュが残ってしまう可能性があるので変数の寿命は気にする
- phpの組み込みの関数がそもそも例外安全じゃない
- ob_start、fopenなど
例外プログラミングイディオム
- RAII
- 広範囲でcatchしてre-throw
- DBの扱いの時に使ったりする
ポケモン問題
- Pokemon catching
- やたら広い例外でキャッチして何もしない
- 何をキャッチしたいのかわからない
- 例外は自分のために用意する&それのみをcatchするように書く
まとめ
電池がないのでLT大会はまとめられませんでした!><