Amazon Linuxの言語設定を変更した

言語設定が英語のUTF-8になってたので、日本語設定しておいた。

$ cat /etc/sysconfig/i18n
LANG=en_US.UTF-8

でもlocaleは日本語になってた。

$ locale
LANG=ja_JP.UTF-8
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER="ja_JP.UTF-8"
LC_NAME="ja_JP.UTF-8"
LC_ADDRESS="ja_JP.UTF-8"
LC_TELEPHONE="ja_JP.UTF-8"
LC_MEASUREMENT="ja_JP.UTF-8"
LC_IDENTIFICATION="ja_JP.UTF-8"
LC_ALL=

まぁ、どっかの rc かなんかで設定してるんかなと推測。

$ echo $LANG
ja_JP.UTF-8

とりま変えとく

$ sudo vi /etc/sysconfig/i18n
#LANG=en_US.UTF-8
LANG=ja_JP.UTF-8

DebianのLocal timeをJSTに変更した

EC2でDebian/Ubuntuベースのインスタンスを立ち上げたときに、だいたい UTC になってて9時間ほど前の時間になってると思う。
それはちょっと面倒なので、 JST に変更します。

$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
  • timezoneの確認
$ timedatectl
               Local time: Thu 2020-07-09 03:56:56 UTC
           Universal time: Thu 2020-07-09 03:56:56 UTC
                 RTC time: Thu 2020-07-09 03:56:57
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: inactive
          RTC in local TZ: no
  • 東京のtimezoneの値を確認
$ timedatectl list-timezones|grep Tokyo
Asia/Tokyo
  • timezoneを東京に変更
$ sudo timedatectl set-timezone Asia/Tokyo
  • 東京の時間になったか確認
$ timedatectl
               Local time: Thu 2020-07-09 13:02:27 JST
           Universal time: Thu 2020-07-09 04:02:27 UTC
                 RTC time: Thu 2020-07-09 04:02:28
                Time zone: Asia/Tokyo (JST, +0900)
System clock synchronized: yes
              NTP service: inactive
          RTC in local TZ: no

インスタンスを再起動しても、Local timeが JST のままだったので、とりあえずよしとしました。

Bitnamiの右ロゴリンクを無効にした

Bitnami Redmineでもほぼ同じだった。

  1. sshでログイン
  2. コマンド実行
$ sudo /opt/bitnami/apps/wordpress/bnconfig --disable_banner 1
  1. httpdリスタート
$ sudo /opt/bitnami/ctlscript.sh restart apache
Unmonitored apache
Syntax OK
/opt/bitnami/apache2/scripts/ctl.sh : httpd stopped
Syntax OK
/opt/bitnami/apache2/scripts/ctl.sh : httpd started at port 80
Monitored apache

お前のパソコンを構成管理してやろうか

この記事は GMOペパボ Advent Calendar 2018 の14日目の記事です。

qiita.com

 さっむいですね。。
前回の記事が「あっついですね」で始まっていたのにもう寒くなってしまいました。寒暖差が体に堪えても、普通に会社に出て普通に仕事しています。

 最近ですね、お仕事してる時にこう言われることが多くなってきたんです。

  • 開発環境が壊れて開発すべきページが見れなくて困っている
  • テンプレートのデザインしたので、コミットしておいてもらえませんか

 あれ?何かが違う気がする。。。

気がついたら、そのたびに小一時間、下手したら丸一日かかりっきりでパソコンを直してました。

  • Ruby のバージョンが違う
  • コンテナが buzy で止まってる
  • pid ファイルがあるのにサーバが止まってる
  • Python3 にしないと動かないはずなのに動かない
  • Composer がエラー吐く
  • PuppetLabs の VM イメージが古すぎて GnuPG の署名が通らなくなってた
  • rbenv の git リポジトリが変わってるみたいで Puppet が止まる
  • 会社でホストしてた yum リポジトリがなくなってた

 などなど、そもそも構築方法を見直さなければならないものから、手順自体を見直さなければならないものまで、多種多様の「動かなくなった」案件が舞い降りました。
 僕はわりと壊れたものを直すのが好きなので、時間がかかっても最終的に直してしまっていたのですが、ちょっと時間取られすぎかなって思ったり、いやいや、そもそもちゃんとした開発環境もないのにちゃんと開発できてるんか?生産性とか開発のテンポってみんなどう感じてる?みたいな思いが頭の中でグールグルしていました。
 そんな家でビールを飲んでテレビを見てうとうとしていた時に、なぜかデーモン閣下が僕にこうささやいていました。

「お前のパソコンを構成管理してやろうか!」

 というのは嘘なんですが、これは結構大事なんじゃあなかろうかと思いました。
 確かに僕たちが開発してるものは、維持しなければならない技術スタックが多くなってきてまして、それらを維持するための解決案もあってケアされていると思っていましたが、それをブラッシュアップしきれていない状況でした。
このことについては、我らが偉大なるリーダーも課題感を持っており、会社ブログで書いてました。

tech.pepabo.com

 ただ、僕としては DB を除く全てのロールをコンテナ化したいと思っていて、そのコンテナから社内 OpenStack のイメージを作成してサンドボックス環境化して行きたかったんですね。
で、環境作るときはとりあえず docker-compose up しといて!って言ったら終わる感じにしておきたかったんですが、今の puppet どうする? capistrano どうする?みたいなよくある構成管理はどのツールに請け負わす?みたいな解決案を持っていませんでした。
 docker-compose.ymldockerfile に全部書けるぐらいなら管理も楽だしそれがいいんですが、ちょっとそんなに単純でもないしな。かと言って本番の Terraform からキックされるクックブックとは違う開発環境のクックブックをブラッシュアップしていくことにどれほどの意味があるのか。 mitamae か確かに便利そうだなぁ、割と Ruby っぽいレシピ書くっぽいけどこれ自体を維持していけるかな。あでもなんか mruby 使ってるだけあって速いらしいけど、今のやつは Fabric と混ざってる??
 うーんうーん、と悩んでたんですが、結局、まずまずは目の前の環境が壊れている子を助けることが主眼なんだから、今手元でずっと温め続けてきた ansible-playbook をもうちょっとブラッシュアップして使ってもらったほうが早いのでは?と思い直し、とりあえずみんなの環境ができて、お隣に行ってガチャガチャ直す時間が減ってからベストプラクティスを定義しても遅くはないのではなかろうか?どうせコンテナ化したらまたバックグラウンドが変わるだろうし。と思って社内で公開しました。

 構成はベストプラクティスに寄せたつもりです。

$ tree -L 2
.
├── ansible.cfg
├── dev.yml
├── hosts
├── log
│   ├── debug.log
│   roles
│   ├── composer
│   ├── docker
│   ├── homebrew
│   ├── hosts
│   ├── migration
│   ├── node
│   ├── pip
│   ├── ruby
│   ├── sidekiq
│   ├── styleguide
│   ├── vagrant
│   ├── vagrant.puppet
│   └── workspace
└── workspace
    ├── repository A
    ├── repository B
    └── repository C

 抽象化した作業をロールで分解して、使いたいタスクを dev.yml のコメントを外して使ってもらって好きな playbook を使ってもらえるように意識したつもりで、例えば好きなロールのサーバの起動だけしたいとか、 homebrew でインストールしたアプリのアップデートだけしたいとか、ユースケースに併せて使えるように意識しました。今後、使ってもらっていって、固定化されたユースケースが浮き上がってきたらそれ用の playbook を作ったらいいんだし、再利用性を考慮したつもりなんですけど、こういう設計って答えがなくて無限に悩めますね。
(ちなみに、これが全てではなくて、ロール固有の処理とかビジネスロジックに依存するタスクは割愛しております。

 Ansible 自体は前職のゲーム作っていた時に本番のサーバプロビジョニングで使っていたので、記法や勘所はすぐに思い出せたのですが、個人のパソコンをプロビジョニングする時にとっても悩んだ点を紹介します。
サーバのプロビジョニングはベースとなる構成が同じなので、各個人の macOS でベースが違うプロビジョニングはかなりハードルが高いと思いました。例えば

  • 使っているシェルが zsh や fish 、 bash など異なる
  • ndenv 、 nodebrew など、言語のバージョン管理ツールが各個人で異なる
  • sudo する時のパスワード入力
  • python の実行パスが各個人で異なる
  • インストールパスに各個人の趣向が異なる
  • 既にインストール済みのモジュールのエラー回避
  • ロールの設計

 また、普通にハマったことはこんなもの

  • 変数にハイフン( - )を使ったら見つからなくなった
TASK [test : debug] ***************************************************************
Tuesday 11 December 2018  17:00:54 +0900 (0:00:01.294)       0:00:01.437 ******
ok: [localhost] => {
    "ps-test": "VARIABLE IS NOT DEFINED!"
}

 こんな感じで、 VARIABLE IS NOT DEFINED! になってあれ?って思ったんですけど、ハイフン使わなかったら出ました
( yml かなこれは

[local]
localhost ansible_python_interpreter=/usr/local/bin/python3

hosts に ansible_python_interpreter を指定して Python3 のインタプリタをフルパス指定したら動きました

 個人 mac なんで、 NOPASSWD とか指定してませんから、 sudo が必要なタスクで --ask-become-pass を指定しておかないと権限がなくてエラーになります。
パスワードの入力自体はいいんですけど、NOPASSWD を記述してる VM 内の操作にそぐわなかったので、まぁ、playbook を分けるか mac に NOPASSWD を記述します。
 でも、セキュリティ上、良くないので、 vault で解決できたらそれがいいんですが、ちょっとそこまで行きませんでした。。

  • rbenv はバージョンが切り替わるけど、 ndenv は切り替わらなかった

 これは、今でもよくわかっていないのですが、 shell モジュールの実行時、 .node_version での node バージョンの切り替えがうまくいきませんでした。
rbenv の場合、 .ruby_version に記載のバージョンに切り替わるので、 chdir を指定したらバージョンが切り替わったのですが、なぜか ndenv の場合、 .node_version のバージョンに切り替わりませんでした。
 lookup を使って .node_version を参照して node global xxx で切り替わったのでそうしましたが、もうちょっと上手いことやれる気がしてます。、

感想など

 やっぱり自動化は面白い!って思いました。
Ansible は、エラーが実行したコマンドのエラーそのまま出ますので、えー、これ使ってるんや!とか、なんでこのファイルのここをコメントアウトしてるんだ・・・とか発見が多かったです。
 多分、普通にアプリ実行環境(本番とかステージング環境)の構築に Ansible を使っているだけではぶつからないエラーに引っかかるので、ちょっと Ansible 力が上がった気がします。

 ということで、明日の GMOペパボ Advent Calendar 2018@litencatt さんです。

WebExtensions で Firefox Add-ons を公開してみました

 あっついですね。。
前の記事からだいぶサボってました。。
ブログ記事にするようなことがなかったわけではないのですが、なぜだか間が空いてしまいました。
資格試験の勉強をしていたのですが、だんだん飽きてきてしまったので、そういえばブログだな〜っていうテンションで書いてます。
公開してから結構経ってしまったのですが、初めて WebExtensions に触れて公開したので、メモ書きします。

 今年のはじめ、QAチームのエンジニアさんというポジションに配属したのですが、問い合わせ業務や改善作業や障害対応もいろいろ関連付けて仕事していたら、なんだか何でも屋さんになってしまっていて、結局僕はなんていう名前のポジションなんだろうって考えてまして、あえて言うなら最近流行りの SRE( Site Reliability Engineering )や CRE( Customer Reliability Engineer )かな〜って思ってたんですが、まぁそんな大それた感じでもないか〜って思ってました。


 で、いろいろな作業をやるうちの一つに、内部脆弱性検査を実施するお仕事もありまして、 OWASP ZAPBurpSuite を触っていたのですが、エンジニアが少ないQAチームメンバーでも効率的に脆弱性を検査できるツールで VAddy を使おうということになりました。
このツール、素晴らしく脆弱性を検査してくれます。アップデートが早いし、どんどん高機能になっているので、僕は今後もお世話になっていくことになるでしょう。 中の方が茅場町に来てくださったので、上役とお邪魔してきました。

vaddy.doorkeeper.jp

 ただ、自動テストを想定しているせいか、手動でのテストは割と手間がかかるかなぁという印象です。
自動テストは僕がやりたい領域のトップに入るのですが、残念ながら今、それに注力できる時間が少なく、そもそも、環境からじゃね?ってなっていまして、いろいろ遠回りするしかなくてなかなかテストスクリプトを書く段階まで至らず、でもその間にも脆弱性は検知したいので、どうしても手動によるテストや探索的テストに大きく頼らざるを得ません。
そういう時、以下のマニュアルに沿って手で進めるのですが、大量にクロールデータ(テストシナリオ)を作る作業で時間がかかってしまうなぁと感じました。

support.vaddy.net

 まぁ、とはいえ手順が多いとか、複雑とかではなく、やることは至って単純なのです。
上記はクロールデータを作成する手順でして、ブラウザのプロキシを設定して、 begin URL(開始) を叩いて、画面操作して、 commit URL(終了)を叩けば終わりです。
あとは僕が作ってくれたシナリオをスキャンするのに、ボタンポチポチするか、スクリプトでガーッとやるかなんですが、クロールデータを作成するのは、僕ではなかったりしました。テスト担当者や、 CS のメンバーにお願いしていたため、どうしてもどれのあとにこれをやって、でどうなってっていう説明が難しくてですね。
説明が難しくても、厚めにフォローしてたら解決できる範囲だったのでいいのですが、 begin URL を叩いたら、変なエラーがでたとか、うまく操作してるつもりだったんだけど、クロールデータができなかったとかありまして、そのときにでたエラーを1つ1つ解消するのに苦労しました。


 なんでだろうな〜、って思っていたのですが、間違って begin URL を2回叩いていたとか、クロールデータ作成中に他のメンバーが begin URL を叩いてしまって、中断されてしまったとかでした。
複数メンバーで一気にクロールデータ作成を行っていたために起きていた問題と、そもそも操作ミスが原因で起きていた問題に切り分けられました。
前者は検証環境を増やすことで解消できたのですが、操作ミスは依然残ってしまっていて、どうしたらスムーズに進行してもらえるかな〜と考えていまして、その結果、 Add-ons を作ることにしました。
要するに、開始 URL と終了 URL を1回ずつ叩けばいいので、ブラウザの右端にアイコン作ってあげて、操作ミスを軽減したらいいのではないかと。
で、開始 URL を叩いたタイミングでテスト対象の URL を開いてあげたら更にいい感じになるのではと思ったので、それぐらいなら速攻できそうだなと思われました。

Easy VAddy Proxy Crawling – Firefox 向けアドオン

 はじめ、 Chrome を想定して作っていたのですが、割と出来上がってきたタイミングで、あ、Chromeのプロキシってシステムのプロキシ利用するんだったってことに気づいて、あぁあぁぁぁ。。ってなりました。
確かにマニュアルにも近しいこと書いてるなぁ。なんかそういうのなかったっけって調べたんですが、 sudoChrome 叩いて、引数に Proxy を渡したら、どうやらいい感じに動いたんですが、いや、流石にちょっとPCを管理している感じの人に怒られそうだな、じゃぁ、そのままシステムのプロキシ使ってもいいんじゃない?って思ったんですが、あれ? Slack... ってなったんで、やっぱ Firefox しかねぇって方向転換しました。
最近、 Firefox も WebExtensions になったみたいだし、書いたコードも流用できるでしょうと思ってたのですが、案外 Firefox の WebExtensions の作例が少なかったので、 MDN 読んでたら結構なボリュームだったんで、読むのに時間かけてしまいました。。。 ^^;

github.com

 作りながら、うーん、このタイミングで Notification を出したいな〜とか、このタイミングのときは Icon を変えたくないなぁとか、 Option ページは bootstrap とか使ってそれなりのデザインにしたいなぁとか欲求がでてきまして、速攻とか言ってましたけど、 Add-ons を作ること自体が楽しくなってきてしまって2、3日かけてしまいました。。


 まだ、ちょっと使いづらいし、例外ハンドリングが甘いのですが、僕たちがクロールデータを手動で作成するワークフローに合うように作れたと思います。
チームメンバーに使ってもらって、フィードバックを得たいですが、初めてブラウザのアドオン作ったので、いい勉強になりました!

Androidアプリ公開しました!

 かつてより作ってみたいな〜と思っていたAndroidアプリをGooglePlayに製品版を公開しました!

play.google.com

 WebViewで作られていたソシャゲを担当していた時、ネイティブの機能を使ってあれやりたいこれやりたいと思ったり、このJavascriptはどういう仕組で動いているんだ?と疑問に感じていたので、なんでもいいからスマホアプリを自分で手がけたいと悶々と思っていました。
 まぁ、それまでアプリプラットフォームチームのコードを読んだりもしていたのですが、そもそも今の組織に入ったとき、流行り始めているスマホアプリを開発したかったという思いもあって、6年越しに一通り自分で管理できる機会に恵まれました。

 作ったアプリは、HTML5canvas で生成した画像をスマホのストレージに保存する機能しか持たない簡単なアプリですが、なかなかはまりました。お遊びでアプリは作っていたのですが、ちょっと込み入ったことをすると途端にはまるのはいつまで経っても変わらないな。。
 普通のWebViewのアンカーリンク(A要素)から画像ファイルをダウンロードするのであれば、特にはまらなかったかもしれませんが、今回はbase64エンコードされた文字列をアプリで受け取って、画像ファイルにデコードして保存という流れです。というのも、WebView上の canvas に文字やスタンプ画像を好きに配置して、 toDataURL() メソッドで data:URI を生成するためです。
 この場合、 DownloadListener で受け取れるのか、 WebViewClient.shouldOverrideUrlLoading(WebView view, String url) で受け取れるのか挙動が分からずに色々悩みました。素人同然のため、API Levelで挙動が違うのか、端末依存なのか、WebViewのバージョンで違うのかが全く分からず、なんだかできたと思ったら他の端末で動かない、それを直したら他で動かないみたいなモグラたたき状態に陥っていました。
 結局、この記事に行き着き、 DownloadListener ではなく shouldOverrideUrlLoading でもないブラウザのセキュリティの更新が原因だったみたいでした。 JavascriptInterface を用意して、 data:URI をWebViewのJSから渡してもらう方法に軌道修正して期待通りの動きにできました。なんとなくアンカーリンク(A要素)だし初アプリ実装だから、王道のメソッドで受け取りたかったんだろうなと自分にかかったバイアスの強さを感じました。

pokkurutime.hatenablog.com

 だから端末によってイベントをキャッチできたりできなかったりしたんだろうなぁと振り返って思いました。他にもはまったポイントや知り得たことを忘れないように書いておこう。

  • 端末の権限の取り方
    • Android 7.0以降は onRequestPermissionsResult をOverrideしてユーザの操作をハンドリングする
    • これをOverrideしていないと権限無い状態で先の処理が走ってしまってアプリが落ちる
  • ProgressDialog の実装方法
    • Thread で非同期にしておかないと Activity で管理?していないダイアログになってアプリが落ちる
  • JSのAlertみたいなダイアログの実装方法
    • AlertDialog.Builder でボタンの挙動だったり表示文字をセットできる
  • 画面回転した場合、WebView の更新処理が走る
    • restoreState()savedInstanceState を保存しておくメソッドをOverrideしておく
  • Webアプリじゃないので、戻るボタンを押されたときの挙動を実装しておく
    • onKeyDown() をOverrideして実装しておかないと、トップページに戻った時に再度戻るボタンを押したら別のページに遷移したりする
    • この挙動がネイティブアプリっぽくないから、適切に押下を無視するとか finish() とかを実装しておく
  • アプリからWebViewのHTMLを変えたい
    • onPageFinished() とかをOverrideして loadUrl() でJSを即時実行させると動く
  • リファレンス機は必要
    • エミュレータで確認していたのですが、どうやらWebViewの更新が追いついていなかったのかな?動かなかった。。
    • Nexux5xも息が長いけど、公式リファレンス実機の確認は絶対やったほうがいい

 実装はとても楽しかったので、最後にアプリの紹介をば。 これは所属する会社のお産合宿に誘われたのがきっかけで始まったことでした。

osan.pepabo.com

 さらっと canvas で画像ファイルを・・・と話しましたが、それはアイドルのコンサートなんかで光らせるキングブレードというペンライトに装飾をするための画像ファイルです。キングブレードの内紙にバッチリ合う画像ファイルをコンビニで印刷できる(コンサートへ向かう最中でも推しメン用にカスタマイズできる)ことが売りです。
 そちら方面はまったく詳しくないのですが、 @souseiji さんや @_mmmix さんの話を聞く内にチャンスなのではないかと感じて参戦させていただいた次第です。

www.amazon.co.jp

 途中、まったくわからないキーワードに出会ったり、自分用のキングブレード買ったり、帰りにスターマリーのコンサートに連れて行ってもらったりと、開発以外にも色々初体験が多い期間で楽しかったです。

starmarie.com

 今回は、金銭的にも期間的にもAndroidアプリに絞って作りましたが、会社の人たちはiPhone率が高いので、空いた時間にちょびちょび作ってしれっとiOSアプリも公開しとこうかな〜

コードレビューのおきもち

 今日、わりとふと感じたことがあったので、一応メモしておこうかと思って書いてます。 チームでペアレビューの話が軽く出て、あぁ、そうかと納得したのですが、レビューってどう感じてますか? レビューって、以下のようなことを目的としてやってるかと思います。

  • 多角的な視点でコードを見て、不具合を減らす
  • タイポの防止
  • メンバー間のコミュニケーション
  • 人に説明できるコードを書けているかの確認

 よく言われてるから、今更どうとかではないし、上記を考えるとレビューをやる意義があると考えています。


 ただ、まだ駆け出しの頃は、レビューをするのもされるのも苦手でした。 振り返ってみて、なんで苦手だったかは、既に自分の中で答えが出ていました。

なんかケチつける or つけられてるみたいでマイナスなおきもち

 どんなにメンバーの関係が良好でも、言葉を選んでも、1%はそういう意味合いが僕にあったし感じていました。(関西人だから、自意識が高いのかなw?)
でも多分、それは今でも変わっていないので、だから、今でもする時もされる時も気を使っています。
 で、その気を遣うっていうのは、間違っていないんだとも思っています。 コードレビューじゃなくても、普段の生活をしていて、誰かに指摘や注意をする時って、気を遣いますよね。 それは、関係性をうまくし続けるために、必要な人間的活動なんだと思います。

 じゃあ、そのもやもやした意識を持ち続けて今後レビュー活動に勤しんでいても良いものかと、多分漠然と思っていたんでしょう。自分が。 でですね、やっと感じていたもやもやを打ち勝たせる魔法の考えがふと頭をよぎったのでした。


 コードレビューに対話やコミュニケーションの意義を見出すことに近いのですが、多分、僕が今後、レビューやペアレビューをするときは以下の思いを持って接していると思います。

僕の思想や考え方をあげるからちょうだい

 レビューをですね、いろんな人とすることによって、ペアになった人の思考や思想がどんな形であれ、自分の中に流れてきますよね。 それを受け取ったら、どのような感情を抱いても自分の中には入ってきてるんですよね。多分。そういう自分の考えじゃないものをいっぱい受け取って気に入ったものを反芻することで、自分の感性がブラッシュアップされていくんだと思うんですよね。
 今、僕が立っている場所はそういう活動を活発にしようとしているから、若いエンジニアは、広い視野を持ったり、知識を得る機会に恵まれて早く成長できるんだろうなと思ったんですよ。
 良いアイデアなのかそうでないのかを相対的に判断する必要はなくて、受け取った人の感性で継承していけばいいんじゃないのかな。それを個々人がコミュニティでやっていれば、自然淘汰されて良いアイデアが残っていくのではないか。自分のクローンは作らなくていいけど、いわゆる上位のエンジニアレベルの人間をできるだけ早く作るために、良いアイデアの生存競争を活発にすれば良いのではないかと。


 まぁ、であるならば生存競争の機会はレビューじゃなくても良いんだけど、レビューはわかりやすい場であるかな。 そう思えば、多少苦手でも、苦にならないなと自分は思った次第です。


 なんで今さらそんなことをふと思いついたのかはよくわかりませんが、WEB+DB PRESS を執筆させて頂く機会に恵まれた事は関係していると思います。 エンジニアになってから、よく目にする機会があった本に執筆させて頂く機会を与えてくださり、ありがとうございました。
 報告することが遅れてしまって申し訳ありませんでしたが、自分の知識の棚卸しを本にするという、なかなか体験できない経験をさせて頂いた技術評論社 池田様や、弊社メンバーに感謝しています。

gihyo.jp