すずけんメモ

技術メモです

PHPカンファレンスで話してきた

先々週のことですが、PHPカンファレンス2017で話してきました。初PHPカンファレンス。楽しかった!ひさびさにPHPの話をいっぱいできてよかったです。

会社のブログにもゆきみねがまとめてくれてます。 techlog.voyagegroup.com

発表資料はこちら。

speakerdeck.com

セッション動画も載ってます。

www.youtube.com

セッション内容としてはレガシーなシステムの段階的改善です。「こんなケース、ありませんか?」というのが今回の問い。会場で聴いていただいた方々にも多く該当される方がいたようで、このテーマで話をしてよかったなあと思いました。

f:id:suzu_v:20171018124930p:plain

コードを書くとき、すでにあるコードを変更することのほうが多いはずです。そして価値を出している。価値を出しているコードを、僕は尊敬しています。なのでそれらをより良くしていくためにどうしたらよいか?というのがこの1年間ほど取り組んでいたテーマの1つでした。

大きくなってしまったコードベースを読み解くのは大変です。コードを読むことは、書くことよりも時間を使います。コードは読まれる時間のほうが長いのです。Readabilityは最も重要です。ですがすでにReadabilityが低いコードである場合にはどうしたらよいのでしょう。小さい規模であればリファクタリングして地道につぶしていくのもいいかもしれませんが、それだと時間がかかりすぎてしまうかもしれません。

最近読んでいる本にレガシーソフトウェア改善ガイドがあります。レガシーソフトウェア改善ガイドの話は発表中にはしていないのですが、おすすめです。レガシープロジェクトの改善をどう進めていくかという観点から体系立てて書かれています。

www.shoeisha.co.jp

リファクタリングかリライトするかというのは、常に悩ましい問題です。しかし、リプレースという選択肢もあります。リファクタリングするのでもリライトするのでもなく、リプレースをする。書き直さずに、置き換える。サードパーティ製のソフトウェアで代替えできるのであれば、それは素晴らしいことです。

今日公開した ajito.fm/10 でもPHPカンファレンスについて話したので、よかったら聴いてみてください。 ajito.fm

カンファレンス運営者のみなさま、スポンサーのみなさま、そして参加者のみなさま、どうもありがとうございました!

「データ分析基盤構築入門」の執筆に参加しました

「データ分析基盤構築入門」の執筆に参加させていただきました。

gihyo.jp

概要は以下のとおりです。

「サービスのデザインはログのデザインから。」良いサービスを作り上げるには,ログデータを収集し,改善を続けるシステムの構築が必要です。本書は,ログデータを効率的に収集するFluentdをはじめ,データストアとして注目を集めているElasticsearch,可視化ツールのKibanaを解説します。本書を通して,ログ収集,データストア,可視化の役割を理解しながらデータ分析基盤を構築できます。2014年に刊行した「サーバ/インフラエンジニア養成読本ログ収集 可視化編」の記事をもとに最新の内容に加筆しています。

最初に執筆させていただいてから早3年、改訂しませんか?との話から始まった今回の執筆。ログ解析にまつわる環境にも変化がありました。前に書いた原稿を眺め、反省しつつ、2017年の今また改めてこの文章を書くならどう書くのが良いのだろうか?というのを自分なりに再解釈し、書いたのが今回の書籍になります。

もともとElasticsearch, Fluentd, KibanaのいわゆるEFKスタックの本として書かれていました。その間にETLの領域でも様々なツールが生まれ、実践され、よりよい運用が試みられてきました。そうした取り組みを改めて俯瞰し書く機会をいただき、嬉しく思っています。特に今回はy-kenさんにより、Embulk、Digdagに関しても多く加筆されています。

10/10夜にはピクシブ株式会社様にて刊行記念の勉強会が開かれます。お気軽に聞きに来ていただけると幸いです。

pixiv.connpass.com

PHPカンファレンス2017で話します

2017年10月8日(日)に開催されるPHPカンファレンス2017にて登壇します。

phpcon.php.gr.jp

https://joind.in/event/japan-php-conference-2017/session14-php--

session14: 広告配信管理システムを支えるPHP ~ レガシーシステムからの段階的移行戦略
13:35 4Fコンベンションホール梅 (手前側)

弊社管理画面の改善ネタです。下記の記事をみていただくと雰囲気が伝わるかと思います。

suzuken.hatenablog.jp

是非お越し下さいませ。会場でもPHPerの皆様とお話できるのを楽しみにしております :)

8/4 12:10- ランチセッションにて出張 #ajitofm します

builderscon, 今週ですね!弊社でもスポンサーさせていただいており、「ランチセッション枠もらったのでなんかやらない?」と話をふってもらったので出張 https://ajito.fm をすることにしました。8/4 12:10 - 12:40 イベントホールです。ランチでます!ゲストは @ajiyoshiさん、yowcowさん、そして初登場 トミールさんです。

builderscon.io

いつも通り(?)ゆるい雰囲気で話していく予定ですのでお楽しみに!8/4は終日日吉にいますので是非お話しましょう〜。

A Tour of Goを終えたあなたにおすすめのGoを勉強するためのリソース

今年も夏のインターンで学生にGoの講義をします。多く寄せられる質問が「A Tour of Goを終えたのですが、その後に何をやるのがおすすめですか?」というものです。学生に限らず、言語を学ぶ方はプログラミングそのものに対する慣れやバックグラウンドも違います。そこでなるべくいろんな方の参考になるように、おすすめな本なりページなり方法なりをまとめてみます。

わりと多くの人におすすめ

プログラミング言語Go」(Alan A.A. Donovan, Brian W. Kernighan著)です。通称GOPL。

http://www.gopl.io/

柴田さんによる日本語翻訳もあります。

https://www.amazon.co.jp/dp/4621300253

Go言語のイントロダクションから始まり、型・インタフェース・並列性の説明などが丁寧にかかれています。私がGOPLを良いと思う点は、例示が豊富なところです。Goのインタフェースはとても強力ですが、最初その有用性をどのように活かせばいいかというのはちょっとした応用のコツを抑える必要があります。GOPLでは例を交えながら説明されているので、とてもわかりやすく、実践的です。例えば7.9では簡単な評価器をつくります。以下のような簡易電卓を実装します。

sqrt(A / pi)
pow(x, 3) + pow(y, 3)
(F - 32) * 5 / 9

(7.9 Example: Expression Evaluator より抜粋)

これらの式に対するパーサをどのように実装していくか、というのがわかりやすく説明されています。単項演算、二項演算の型を定義し、式をインタフェースとして定義します。

type Expr interface{
    Eval(env Env) float64
}

この例では浮動小数点演算のみサポートしています。各演算はすべて Expr のインタフェースを満たします。これにより、評価器の環境モデルをわかりやすく記述できるようにしています。

評価器の実装はインタフェースの演習の一例です。機能性を知り、理解を深めるために良い例だと私は思います。またGOPLでは、テストの実践例、豊富なGoのツールセット、Reflectionなどについても扱われており、私が知る中で日本語で読める書籍だと最も網羅的に書かれている本です。

もっと言語自体を深く知りたい方におすすめ

言語自体の理解を深めるには golang.org に載っているコンテンツを読み込むのがおすすめです。Goチームはドキュメントをとても重視していて、どの文章もよく書かれています。godocもその文化の一例です。

Goチームの書いているブログはどの記事も言語機能及び応用に関する解説として秀逸です。

The Go Programming Language Blog

ymotongpooさんが日本語訳を公開してくださっています。

www.ymotongpoo.com

個人的に好きな記事をあげておきます。

また、Goの標準ライブラリを読み進めるのがおすすめです。言語仕様を読みつつ、「どう実装してるんだろう」と想像しながらコードを読み進めていくと楽しいです。Goの標準ライブラリはGoで書かれているので、読みやすいというのも嬉しいところです。

標準ライブラリを読むときはテストコードを読むとさらに理解が深まります。Goの標準ライブラリのテストコードは、そのライブラリをどのようにつかうかという視点でみたときによいサンプルになります。例えば、 encoding/json を深く知りたいときには、 https://golang.org/src/encoding/json/encode.go を読みつつ https://golang.org/src/encoding/json/encode_test.go を読むとわかりやすいです。 Examplesとして書かれたテスト はgodocにも載っているので、そちらも参考になります。

Webエンジニアで*1実践にGoを使ってみたいというあなたにおすすめ

Goの本ではないのですが、「Real World HTTP」(渋川よしき著)を個人的におすすめします。

www.oreilly.co.jp

内容紹介より引用します。

本書はHTTPに関する技術的な内容を一冊にまとめることを目的とした書籍です。HTTP/1.0、HTTP/1.1、HTTP/2と、HTTPが進化する道筋をたどりながら、ブラウザが内部で行っていること、サーバーとのやりとりの内容などについて、プロトコルの実例や実際の使用例などを交えながら紹介しています。 GoやJavaScriptによるコード例によって、単純なHTTPアクセス、フォームの送信、キャッシュやクッキーのコントロール、Keep-Alive、SSL/TLSプロトコルアップグレード、サーバープッシュ、Server-Sent Events、WebSocketなどの動作を理解します。 これからウェブに関係する開発をする人や、これまで場当たり的に学んできた人にとって、幅広く複雑なHTTPとウェブ技術に関する知識を整理するのに役立ちます。HTTPでは日々新しいトピックが登場していますが、本書によって基礎をしっかりと押さえることは、さまざまな新しい技術をキャッチアップする一助にもなるでしょう。

夏のインターンでも学生向けにGoの話だけではなく、Webに関わるエンジニアとしてHTTPに関する講義を設けています。Real World HTTPはHTTPについて仕様を紹介するだけでなく、実際に動かす例を示しながら読み進めることのできる稀有な本です。また、HTTPの進化の過程を知ることもできますし、セキュリティに関わるトピックも丁寧に説明されています。Real World HTTPのコード例はサーバ実装、クライアント実装ともにGoが使われています。もちろん主眼はHTTPを学ぶことにありますが、GoでどのようにこのHTTPの機能を使うのか?というときに逆引きするのにも便利だと思います。

実践的なTipsをもっと知りたい方におすすめ

手前味噌ですが去年執筆に参加させていただいた「みんなのGo言語」、おすすめです。*2

suzuken.hatenablog.jp

そして何より

どんどん手を動かしてコードを書きましょう :)


思いつくまま挙げてみました。他にももしおすすめの本やリソースなどがあればコメントいただけると幸いです。

*1:あるいはWebエンジニアになりたくて

*2:この流れで紹介しないわけには・・すみませんすみません

ポッドキャスト #ajitofm はじめました

ポッドキャストをはじめました。

ajito.fm

社内バーAJITOでよなよな技術的なことを話して楽しんでいるのですが、「ああこういうポッドキャストがあったら面白いかもなー」と思い始めてみました。職業エンジニアをやっていると身近な話題もつい技術ネタになってしまうもので、普段もご飯をたべながらこんな感じでゆるく技術ネタを話しております。楽しいです。

最新話はこんな感じです。

ajito.fm

最新話についてはRSS、もしくはiTunes後日対応予定 7/12追記 対応しました!)で。または以下のTwitterアカウントでもご案内しております。

twitter.com

気軽に楽しんでいただけると幸いです。

7/12追記

iTunesでの購読に対応しました。

ajitofm

ajitofm

  • Kenta Suzuki
  • Tech ニュース
  • ¥0

複雑さに潜り込む - 大規模PHPアプリケーションにおける例外・モニタリング・ロギング

みなさん、PHP書いてますか?ここ2ヶ月くらいPHPも書いていたのでその話を書きます。

この記事はVOYAGE GROUP techlog / Advent Calendar 2016の記事です。

例えば以下のような話に身に覚えはありませんでしょうか。

  • 例外がどこかで握りつぶされており、例外的状況なのにエラー表示がまちまち。レスポンスステータスも一貫性がない。エラーログが適切に出ていない。
  • エラーログ出力用コードがいろんなところで散乱している。エラー文字列整形のための適当なヘルパメソッドがクラスごとに実装されている。
  • エラーごとにエラー表示のためのメッセージを設定するのが面倒になり、「システムエラーが起きました」とだけ表示されるようになってしまった。
  • 例外ハンドリング周りのコードは考えるのが面倒なのでコピペだらけになっている。
  • オブジェクトの依存関係がクラスのプロパティに大量に埋め込まれている。これを初期化するためのロジックがコンストラクタないし初期化のためのメソッドに組み込まれている。これが親クラスとなり、継承されたクラスには暗黙的に大量のプロパティが継承されている。またそのためテストコードから大量の初期化処理が必要になる。

そんな問題を解決すべく、ここ最近取り組んでいた話をします。

環境

  • PHP 5.3.x / CodeIgniter 1.7.x
  • 5,6年ほど運用されているPHPアプリケーション / だいたい10万行くらい
  • 社内 / 社外のユーザが利用する管理用のアプリケーション
  • php-cs-fixerでほとんどのコードの体裁は整っている
  • Class LoaderはCodeIgniterとcomposerのものが併用されている / たまに require_once もある

兎にも角にもモニタリング

アプリケーションの課題は何か?をチームで考え始めたとき、以下の点が話し合われました。

  • 何がどこで失敗しているかわかりづらい
  • 既存コードを読み解くコストが高い / 不必要に複雑になっている
  • よく使われているコードに費やす時間が少なく、新しい機能に時間が割かれがちになる
  • 当時メンテナンスしていたエンジニアがいなくなっており、すぐに構造について聞ける人がいない

どれも大事ですが、個人的にはアプリケーションエンジニアの行動指針として特に3番目は大事だと思っています。新機能はもちろんビジネスの目玉になるかもしれませんし、戦略上重要な場合も多いでしょう。しかしながら、既存コードは価値を生み出し続けており、事業を支えているという場面は少なくありません。費やす時間が少なくなればコードの保守は疎かになります。エンジニアチームとして保守フェーズに入っている機能をどのように質を保ちつつメンテナンスしていくかというのは課題です。

そこでまず、よく使われているコードで問題が起こっている箇所を効率よく知る方法はなんだろうかと考えました。結論としてはNew Relicでアプリケーションモニタリングをはじめました。

Application Performance Management & Monitoring | New Relic

f:id:suzu_v:20161215100141p:plain

New Relicでは以下のことができます。

  • 時系列でのPHPランタイム状況の把握
  • 実行に時間のかかっているコードのプロファイリング
  • 遅いクエリの抽出 / 1つのHTTP requestごとに発生したクエリの本数と所要時間などの把握
  • エラー状況のモニタリング / HTTP Requestごとに発生したエラー内容の把握

いま着手しているアプリケーションはCodeIgniterの1.7系をベースとしつつ一部のAPIではSlim Frameworkが利用されています。そのため、特定のフレームワークによるプロファイリングツールではなく、PHPランタイムの情報からトレースしてくれるようなツールを探していました。New Relicの場合は今回のユースケースにあったため、利用を開始しました。DB側でもスロークエリログを出力していますが、New RelicからだとどのHTTP Requestのパターンにおいて何本クエリが発行されておりレスポンスタイムを長くする要因になっているか、ということを把握しやすくなっています。このあたりはOR Mapperを改良して例えばSQLコメントにHTTP requestごとのIDを埋め込み、traceabilityを確保する方法もあります。私達の実装ではOR Mapperが必ず使われているわけではなかったので、モニタリングツール側でトレースできることは役立ちました。

「よく使われるコードとはどこか」という観点はもちろんのこと「ユーザにとってストレスのかかっている画面 / URLはどこか」というのがアプリケーションパフォーマンスモニタリングによって開発者間で共有できるようになったのは大きな変化でした。これにより、パフォーマンスに問題のある箇所のissueを明確にできたのはいうまでもありません。

catch (Exception $e) の香り / アプリケーションベース例外の設計とハンドリング

ここで質問です。MVCにおけるコントローラクラスのあるメソッドで以下のコードがありました。このコードの好ましくない点はどこでしょうか。

<?php
try{
    // DBにつないで結果返したり
    // 権限チェックしたり
    // とにかく色々な処理
} catch (Exception $e) {
    error_log($e->getMessage());
    $this->tpl->display('error.html', array('message' => $e->getMessage()));
}

色々ありそうですね。単品でもよくないのですがこんなコードが300箇所弱ありました。私が最初これを見たときに思ったのは以下の点です。

  • ルート例外で catch しているのはなぜ?
  • スタックトレースはどこにも出力していないけどここで catch するまえにどこかで出しているのだろうか・・?
  • $this->tpl がいろいろ握ってそうな気配がある
  • エラー画面に例外のメッセージそのまま出してるけど、ログと画面表示に同じメッセージ出して後から原因追えるのだろうか?
  • (ぱっと見わからないけど)これレスポンスステータス返していないのでは?
  • catch 句の中身がだいたいコピペ

他にも細かい点は色々ありますが、ぱっと思ったのはそういうところです。結果的にきれい汚いだとかコードスタイルだとかを置いておいて以下の点がよくありませんでした。

  • スタックトレースがないので原因究明がしづらくなっており、論理例外が起きても対応が放置されている。開発環境でのみスタックトレースを表示するという制御ができない。
  • アプリケーション例外が throw Exception("処理に失敗しました") のようにルート例外にメッセージだけ設定して throw されている箇所が多々ある。
  • エラーログの内容が少ないため、いつどこで何が要因で失敗しているのかがあとからトレースできない
  • エラー表示のための tpl がクラスのプロパティ(実際には親コントローラのプロパティ)に依存しているのでコードの分離がしづらくなっている。そのためエラー表示のためのコードをコントローラに入れざるを得なくなっていた。
  • エラーログ出力のためのコードが各 catch の中で書かれているため、たまに出力が忘れられており、エラーが起きていることすら把握できない状況になっている部分があった。

これらの課題にアプローチするためにはアプリケーション構造から着手するのが効率がよいと考えました。そこで、今定期的にコンサルに来ていただいている @t_wada さんとも相談しつつ、以下の方針を立てました。*1

  • 大域の例外ハンドラを実装する。例外を throw すれば適切にエラー画面が表示されるようになること。また例外に応じて適切にHTTP response statusが設定されること。これをうけて、ユーザの各実装において try catch は必要最低限にすること。
  • 独自定義の例外はすべてベース例外を継承すること。ベース例外には画面表示向けのエラーメッセージとシステム向けのエラーメッセージを分けて保持できること。
  • 例外を catch して挙動を変更する場合にも、特別な場合*2以外には例外を投げ直すこと。例外を投げ直す場合にはスタックトレースを第三引数で引き継ぐこと。

ベース例外は以下のような設計になっています。

<?php
// 注: Throwableは独自実装になっていて、PHP 7のためのpolyfillです。
class BaseException extends \Exception implements Throwable
{
    protected $userMessage;

    /**
     * @param string $message
     * @param int $code
     * @param \Exception|NULL $previous
     * @param string $userMessage
     */
    public function __construct($message = "", $code = 0, \Exception $previous = null, $userMessage = "")
    {
        parent::__construct($message, $code, $previous);
        $this->userMessage = $userMessage;
    }
}

$userMessage (このネーミングももっといいものがあっただろうとは思いますがひとまずはこれで・・)にはユーザに見せるための文字列を設定します。コンストラクタは PHP: Exception - Manual のコンストラクタに第4引数を追加しており、ここにユーザ表示用文字列を設定できるようにしています。独自例外を定義する場合にはこのユーザ向けメッセージを各例外のデフォルト値として設定できますし、あるいは例外を throw する際に適宜生成して埋め込むこともできるという設計になっています。またあとでも出てきますが、ベース例外の $code についてはHTTP response status codeとして利用されるようになっています。これは「Webアプリケーションの例外コードはHTTP response status codeとするのが明確なのではないか」という考えからです。このあたりは @t_wada さんと議論していて気がついたことですが、PHPの例外コードは案外使われていないという話になり、それならHTTP response status codeとしてしまうのが見通しがよさそうですね、という話をしました。

PHPでは大域の例外ハンドラを設定するために PHP: set_exception_handler - Manual を利用できます。 set_exception_handler を利用すると、すべての throw されてきた例外についてのハンドラ関数を設定できます。*3これはフレームワーク側での仕事でなければなりません。私達がこのアプリケーションで利用しているCodeIgniter 1.7系では大域例外ハンドラはありません。そこでフレームワーク初期化時に set_exception_handler によって大域例外ハンドラを設定するように変更しました。*4

これでめでたく大域例外ハンドラが仕事をしてくれる・・と思ったわけですが、まだ工夫をする必要がありました。大域例外ハンドラからもエラー表示用のテンプレートエンジンを利用する必要がありました。このテンプレートエンジンの初期化処理はコントローラの親クラスのコンストラクタで初期化されており、コントローラからは利用できない、という状況になっていました。*5そこでDIコンテナを導入しました。

DIコンテナの導入

大域例外ハンドラでテンプレートエンジンを利用するためにまだ問題がありました。親コントローラのコンストラクタに大きく依存しているアプリケーション初期化処理がありました。

Webアプリケーションの入り口はどこか?というとHTTP Requestです。なのでフレームワークのユーザからするとコントローラの初期化時、すなわちコンストラクタに色々書きたくなってしまうのでしょう。SlimやSilexなどのマイクロフレームワークだとコントローラではなくRouterなので初期化処理はDIコンテナでやるというのは自然かもしれません。とはいえDIコンテナのインタフェースが用意されていない場合、コントローラ初期化時にアプリケーションで利用する様々なコンポーネントの初期化処理を書くという選択はありがちだと思います。例えばセッションを読み取りつつ権限をチェック、テンプレートエンジンの初期化しつつ便利メソッドを生やす、ヘッダー・フッターなど共通部分のコンポーネントをログイン情報をもとに初期化する、DBに接続しコネクションを保持する、などです。これはWebアプリケーションのtestability、あるいはコントローラのtestabilityを下げる要因でもあります。コントローラが状態を持ちすぎているためです。

そこで私達は段階的にこの依存関係の箱を移していこうと考えました。今の親コントローラに初期化処理を依存した状態ではコントローラ以外からセッションをつかったコードを書いたり、テンプレートエンジンを利用するコードを書くことができません。これはアプリケーションの自由度を増やしますが、ある意味で複雑度を増す選択です。この選択には慎重でしたが、DIコンテナは軽量かつdisposableなコンポーネントとして利用できると考え、進める判断をしました。*6

DIコンテナを作り、親コントローラのコンストラクタにおける初期化ロジックを移植しました。これはアプリケーションのどこからでも利用できるようになっています。具体的にはCodeIgniterはルートオブジェクト*7&= get_instance() によってアプリケーションのどこからでも取得できます。この仕組みに乗っかり、DIコンテナもルートオブジェクト経由で取得できるようにしました。このあたりはチームメンバーががんばってくれました。

<?php
$ci =& get_instance();
$container = $ci->pimple->container;

DIコンテナを経由することで、大域例外ハンドラで利用可能な初期済みのテンプレートエンジン及びセッション情報が取得できるようになり、従来通りのエラーページが表示できるようになりました。ちなみにDIコンテナにはPimpleを使っています。

大域例外ハンドラの実装は概ね以下のようになりました。フレームワーク初期化時に post_controller_constructor Hookからこれを呼ぶことで大域例外ハンドラを設定しています。

<?php
class ExceptionHook
{
    public function setExceptionHandler()
    {
        set_exception_handler(array($this, "handler"));
    }

    /**
     * @param Exception $e
     */
    public function handler(Exception $e)
    {
        $ci =& get_instance();
        $container = $ci->pimple->container;
        $twig = $container['twig'];
        log_message("適当なエラーメッセージがいい感じにここに埋め込まれる秘密の仕組み", $e->getMessage());
        if ($e instanceof BaseException) {
            // 例外コードをHTTPレスポンスステータスコードとして埋める
            header('HTTP', true, $e->getCode());
            $twig->display('error.html', $e->getUserMessage);
            return;
        }
        header('HTTP', true, 500);
        $twig->display('error.html', $e->getMessage());
    }
}

これでようやく例外の足回りが整いました。 throw すればいい感じにHTTP responseが生成され、エラーログが出力される世界になりました。

とはいえ、例外に関するのコードはアプリケーションコード全体からみるとごく一部です。メンテナンス性を高めるにはやることがまだまだあります。

使われていないコードを消すことはメンテナンス性を高める

知らないコードを読むとき、どのように読みますか?私は知らないコードを読むとき、関数であればまずinputとoutputを意識して読み解きます。副作用がないならこれはさくっと終わります・・が、もし関数外部の状態が変更されていれば頭の中のスタックが溢れそうになってしまいそうになりながら、状態を片隅に置きつつ別のコードを読み進めます。その状態は初期状態がどこで定義されているのか、あるいは場合によって状態の設定というのは方法が変わってくるのか。ああこのプロパティは外部からもどうやら変更されているようだ、お手上げだ・・などと思いつつ読み進めます。まあようするにコードを読むというのは体力のいる作業ですし、できればさっさと結論を知りたいのです。このセンテンスのように。

いらないコードは消しましょう。使われていないコードは消しましょう。コードが短いことは、将来のチームメンバーの時間を節約します。

dead code、つまり呼ばれていないコードをまず消すことを考えるでしょう。しかしながらこれを将来に渡って習慣づけて続けていなかければまた雑草がぼうぼうに生えた花壇のようになってしまいます。そこでコーディングガイドラインに「コードの消し方」を書きました。sebastianbergmann/phpdcd をdead code検出のために試用しましたが、CodeIgniterの独自loader環境においては限定的なdead codeしか検出できなかったため、PhpStormのinspectionを使ってdead codeを探すというのをメインでやりました。

使われていない関数やクラスがある場合、私達が取りうる手段は2つあります。*8

  • さっさと消す
  • @deprecated にする

継続的に使われていない関数やクラスを消していくためにはどうしたらいいか?明らかに呼び元がない関数は消して良いでしょう。しかしPHPの場合、実行時にはじめて呼ばれるとわかる場合もあります。例えば PHP: call_user_func - Manual をつかって $callback を動的に指定されている場合、静的解析でどの関数がコールされるかを推定するのは難しいです。もし消すのが不安な場合には PHP: trigger_error - Manual をつかって E_USER_DEPRECATED エラーを生成しましょう。

<?php
/**
 * @deprecated
 */
function d()
{
    trigger_error("function d() is deprecated since 2016/12/14. use f() instead.", E_USER_DEPRECATED);
}

関数をdeprecatedにする場合には上記のようにすることをコーディングガイドラインにいれています。*9 私たちはこれをエラーハンドラからロギングすることにより、非推奨な関数が呼ばれているかどうかをモニタリングできます。またPhpStormなどのPHPDocがサポートされたエディタをつかっていれば呼び出し側のコードにwarningを出せるため、実装の手助けにもなります。

この方法は「今すぐには消せないけど将来的には置き換えたいコード」を示すためにも良い方法です。

次のステップ

という感じにざーっと進めてきたのがこの2ヶ月ほどの取り組みでした。ひとまずアプリケーションとしての足回りを整えてきました。とはいえこれが今までチームの中で見過ごされてきていたのが最大の課題だなと思っており、継続的にアプリケーションを改善できるようにするにはどうしたらいいものかというのが私の最近の関心ごとです。

解決できていない課題も残っています。しかしながらこれらは一気に解決できるものではなく、組織でアプローチすべき課題だと考えています。今回これとは別に以下を進めました。

  • PHP 5.3系からPHP 5.6系へのバージョンアップのためのコードの書き換え (コンストラクタ書き換え / static callの対応 などなど)
  • require_once を消してcomposerのautoloadに移植。あるいはCodeIgniterのLoaderからcomposerのautoloadへの段階的なライブラリの移植。
  • 既存のSelenium baseのブラウザテストと別にさくさくとHTTP response statusをテストするための並列HTTPテストツール作成*10
  • 使っていない use 句の削除 / 使ってないライブラリの削除・あるいは同機能のライブラリへの統一
  • @property アノテーションを利用して簡易的にCodeIgniterのloader経由で呼ばれているクラス及びメソッドもinspectionを可能にする

課題は見えたところからコツコツ解消していかなければなりません。組織としてこうした課題に対してどのように対応するか、あるいはどのように時間を割いていくのか。個人的には複雑なアプリケーションが実装されるのはある程度しかたないないと考えています。そしてcomplicatedなアプリケーションはある程度技術的なアプローチによって改善可能であるし、事業を支えるプロダクトに改めて息を吹き込むというのはやりがいのあるテーマだと思っています。

もしそんなアプリケーション開発の話をしたいという方がいらっしゃれば是非 #ajiting しましょう!お気軽にお声がけくださいませ。 voyagegroup.com/recruit/

明日のアドベントカレンダーの記事もお楽しみに。


P.S. 某原稿の締め切りが伸びたので気合い入れて記事を書こうと21時ごろからスーパードライ片手に書き進めていたら一万字を超えてしまい反省しています

*1:t_wadaさんにはテストや例外設計のことであったり、開発チームの課題の掘り起こしであったり、ペアプロをチームメンバーとしていただいたりしています。

*2:たとえばretry可能な例外などの場合には例外の投げ直しをせずに通常の処理をretryするなど

*3:もちろん、握りつぶされている例外については大域の例外ハンドラで捕捉することはできません。

*4:ちなみにCodeIgniter 1.7系ではPHPエラーのための大域ハンドラがありますが、例外のための大域ハンドラはありません。

*5:ログインしているときとしていないときとでエラー表示を分けたい、という考えからそういう設計になっているようです。ユーザからの使い勝手を考えるとここは挙動を変えないほうがよいだろうと判断し、そのまま踏襲しました

*6:ちなみに私がよく書くGoの世界ではDIコンテナはあまり一般的ではありません。typeとinterfaceが大概の依存関係を明示してしまうからです。というのは横道にそれるので注釈で・・

*7:CodeIgniter用語でいうとベースオブジェクトになるかもしれません。CI_Baseのことです。

*8:放置するというのをいれると3つですね

*9:このアプリケーションに関して言えば、コーディングガイドラインがいままでなかったのです。この2ヶ月の取り組みの中であわせて、アプリケーションとチームにあったコーディングガイドラインを作っています。これはまだ作成途中ですが、レビューの指針にもなりますし、何よりチームのPHPに対する知見を集約しレベルを引き上げようという意図があります。

*10:これはGoの net/http を使い、ログイン後のセッションを持ち回しつつGo 1.7のsubtestをつかってparallelにさくさくとHTTP responseに対してテストをする、というツールです。