IkaDB! と 個人サービス開発の楽しさと学び
はじめに
本記事は Splatoon Advent Calendar 2015 - Adventar の12日目の記事です。
一時は枠が埋まっていたカレンダーですが、ふと見ると1日だけ枠が空いていたのでノータイムで飛び込んだ所、「深夜リリース」->「RubyKaigi1日目」->「今日」というコンボをくらい自爆してしまいました。
そんな訳で、今日は個人で開発したサービス「IkaDB!」と、開発時の楽しさ/学び をいくつか紹介します。
IkaDB!とは
IkaDB!は以下の機能を備えたWebサービスです。
- ブキの条件検索ができる!
- 「ダイオウイカかバリア持ちのブキ」「射程が#{自分の使うブキ}以上のブキ」など、知りたい条件のブキを一覧化できます。
- ギアの条件検索ができる!
- 「メインがマーキングガードでサブに攻撃力アップがつきやすいギア」「Ver2.3.0で新しく増えたギア」など、こちらも知りたい条件のギアを一覧化できます。
- ギアパワー影響を含めたダメージが計算ができる!
- 「攻撃力アップ」「防御力アップ」によるダメージ増減を計算し、何発当てれば相手を倒せるかを計算できます。
- 倒すのに必要な弾数が増減するギアパワー数が分かるため、「ギアパワーを何個積むのがいいか」の考察に利用できます。
主にwikiが取り扱っている情報の引き出しを楽にする機能を搭載しています。
月間のセッション数は約3万、ユーザー数は1万ちょっとまで伸びており、当初の考えよりも多くのユーザーさんにご利用いただいております。
作った理由
Splatoonは発売直後に会社の休憩室でプレイ(WiiUは先輩の私物)しドハマリし、その後ヨドバシに直行して本体ごと購入して日がな一日プレイする生活を送っていました。
※その時のわたし
イカに屈したルニ君 本体ごと購入するの巻 pic.twitter.com/3NU9I1J8sJ
— ルニ@IkaDB! (@lni_T) 2015, 6月 6
その頃(ゲーム発売後の6月頃)、装備についての情報収集源といえばwikiくらいしか存在せず、「量が多いしCtrl+Fで探すの大変!」という状況でした。
その時の「じゃあなんとかするかー」の精神で生まれたのがIkaDB!というサービスでした。
他にもいろいろ理由はあった気もしますが…
- こういう系のサービスが登場するのは時間の問題だな…!なら今すぐ作らねば先を越される!
- 就職して以来ほとんどできていなかった趣味開発がしたい!そろそろ何か自前でサービス作ってみてもいいんじゃない!?
- 会社に閉じこもって、働いていればそれでいいの…?
彼女にフラレたので現実逃避先が欲しいー
元々自分の困ったを解決するためのもので、深く考えず勢いで作ってしまった感があります。
こうして、IkaDB!は6月末にサービスを開始しました。
ゲーム発売のだいたい1ヶ月後で、振り返ってみるともう半年近く前で驚いています。
使っている技術/知識
開発面
- バックエンド: Ruby on Rails + PostgreSQL
- フロントエンド: AngularJS
- インフラ: Heroku
Railsに関しては業務でもメインで使っているので慣れていたのですが、AngularJSやHerokuに関しては全く分からんマンでした。
とはいえ、せっかくなので触ったことのないものも触っていきましょうスタイルで開発しています。
ゲーム面
ブキ/ギア検索
スプラトゥーンの装備品のデータ構造はごくごくシンプルです。
- ブキ
- メインウェポン(射程とか威力とかの情報含む)
- サブウェポン
- スペシャルウェポン
- ギア
- ギアパワー
- ブランド
- (付きやすいギアパワー)
- (付きにくいギアパワー)
上記で全てが完結してしまうので、どちらかというと実装よりデータのインポートの方が大変という程のシンプルさです。
IkaDB!では、それら装備品各要素のレコードが入ったDB、検索可能なAPI、APIにリクエスト送信/レスポンスを表示するJSで完結する簡素な構造になっています。 ゲーム1つ程度の情報量なら最初に全て読み込んでおいてフロント側で解決するほうが画面操作スピードは早くなりそうですが、 データの追加/修正がたびたび発生することを懸念して、画面更新不要で常に最新の情報を表示すべく、 検索操作毎にAPIサーバーにGETリクエストを投げる方式をとっています。
ダメージ計算
ダメージ計算式についてはここが詳しいのですが、本記事にも記載しておきます。
(計算式を解明した @acple 氏には足を向けて寝られません…)
スプラトゥーンの攻撃のダメージ量は、ブキの基本ダメージ × ダメージ倍率(ギアパワーにより変化) により求める事ができます。
が、このダメージ倍率の計算がかなり面倒で、じゃあここをJavascriptでやってしまおうかというのがこの機能になります。
ダメージ倍率は以下の計算で求めることができます(wikiの記載を借用)。
# ギアパワー数の合算 a = [攻撃メイン] * 10 + [攻撃サブ] * 3 d = [防御メイン] * 10 + [防御サブ] * 3 # 攻撃防御それぞれの倍率の計算 A = ((0.99 * a) - (0.09 * a)^2) / 100 D = ((0.99 * d) - (0.09 * d)^2) / 100 # 差分から最終倍率の計算 a >= dの場合(攻撃側が大きい場合) 倍率 = 1 + (A - D) a < dの場合(防御側が大きい場合) 倍率 = 1 + (A - D) / 1.8
こうして得られたダメージ倍率にブキの基本ダメージを乗算することで最終ダメージを算出することができます。
ただし、各ブキにはダメージ上限があり、相手を倒すのに必要な弾数が減らないギリギリの値でダメージが上昇しなくなります。
例えば、わかばシューターは基本ダメージ28で相手(HP100)を倒すのに4発攻撃が必要になります。
ここで相手に防御力アップをメイン2つ・サブ2つ積まれてしまうとダメージが24.847となり、倒すのに5発攻撃が必要になります。
しかし、攻撃力アップをたくさん積んでもダメージ上昇は33.3で止まるため、3発で倒せるようにはなりません。
こう書くと攻撃力アップが無意味そうですが、そういう訳ではなく、「防御を積まれても必ず相手を4発で倒せる状態」となるため、相手の防御力アップを無効化するためには有効な手段となります。
わかばシューター以外にもこの現象が発生します(.52ガロン系やスプラシューター系など)
その他にもメイン直撃+クイックボムカス当たりの合計ダメージをキル圏内まで引き上げたりと、合計ダメージ100の境界で熾烈なギアパワーの読み合いが行われています。
この計算はどうしても機械に頼らざるを得ないせいか、提供している機能の中でもダントツの人気を誇っています。
サービス開発時にやって良かったこと
有識者との協力・使ってくれるユーザーの所へ持っていく
サービスできたての頃はまず知り合いのSplatoonプレイヤーや昔のギルドメンバーに見せていましたが、「ふーん」程度のリアクションでした。
使って貰える人を見つけようと考えた所、「自分は元々wikiを見ていたのだから、wikiユーザーに使ってもらえば良いのでは?」ということで、
スプラトゥーン(Splatoon) for 2ch Wiki*管理人である@prairial氏とコンタクトを取り、wiki内ページからリンクを貼らせてもらえることとなりました。
おかげでユーザー数も大きく伸びる結果となりました。
また、ゲームのアップデート仕様についてはprairial氏よりたびたび情報提供を受けており、IkaDB!の運営に大きく協力頂いております。
この場を借りて御礼申し上げます。
ページのアクセス解析
Google Analytics
IkaDB!ではページビューをGoogleAnalyticsで収集しています。これがかなりモチベーションアップにつながりました。
リリースした機能が使われているかどうかがらくらくリアルタイムで検知できるのでとてもニヨニヨできます。Fluentdとかの導入よりずっと敷居が低い。
ページ表示時以外にもJS内の任意のタイミングでトラッキングイベントを送信することでフロント側の任意の処理回数を集計することもできるのがGood.
リファラスパム排除
解析開始当初はアクセスきてるぜひゃっほーいと浮かれていたのですが、 どうもトップページ以外へのアクセスが少なく不審に思って調べてみると 「リファラスパム」(リファラ付きでアクセスしてそれを見たサイト管理者にアクセスさせる)がほとんどで人間のアクセスはほぼ0という悲しい事件がありました…
サラッと調べるとGoogleAnalyticsは「フィルター」機能で特定条件のアクセスを弾けるようで、「これで下記URLのリファラを弾け!」といった記事が多く見つかりました。
が、これだとイタチごっこ感がすごいので、「OSが(not set)」「言語が(not set)」とかの人間ぽくないアクセスを弾いて対応しています。
単体テスト
ド正常系が通るだけのテストコードでも書いておくと随分役に立ちます 書いてないとこういうことになります。
@OpPulse バグだわ。直す
— ルニ@IkaDB! (@lni_T) 2015, 8月 8
痛恨のnull落ち
— ルニ@IkaDB! (@lni_T) 2015, 8月 8
いろいろな事件を受けて一応、以下の単体テストフレームワークを導入しています。
- RSpec (バックエンド用)
- Jasmine (フロントエンド用)
が「c0なにそれおいしいの」レベルでカバレッジ低い(計測すらしてない)のが現状です…
とはいえ、テスト書いてる部分は通る事が保証されるので、幾分心安らかに開発を進めることができています。
また、Jenkinsによる自動テストの仕組みも導入を進めています。この辺は社内のCI環境がJenkinsなのでそれの勉強も兼ねています。
俺のボケナスコミットが怖いので、最近はJenkinsでゆかりさんに自動で単体テストを回してもらうようにしました。しあわせCI環境ですね。(素材は https://t.co/UUwki0UqWE より借用) pic.twitter.com/aDMB37tM0z
— ルニ@IkaDB! (@lni_T) 2015, 11月 14
サービス開発時にやるべきだったこと
検索性の考慮
IkaDB!の致命的な弱点の1つとして、「ページの検索性が悪い」という点が挙げられます。
これは当初なんとかしようとしたのですがどうしてもGoogle上位に持ってこれず、検索結果 -> はてブやnaverのリンク頼りになっています。
原因の1つとしてはサービス名が悪かったのかなあ…というのがあります。
初期の検索結果では、英字入力(ikadb)だとGoogleにタイプミスではないかと疑われ、かといって日本語入力(イカDB)だと淫夢ネタに汚染されており…と散々な状態でした。
SEO周りはどうすればいいか今もよく分かってない状況です。
不具合情報の収集
一応動作確認はしているつもりなのですが、どうしてもバグだったりデータミスだったりが偶に混入します。にんげんだもの。
その辺からの学びの一つとして
- 「直接バグ報告してくださるユーザーさんは貴重」
ということがあります。基本的に不具合は気にされないか、Twitterでぼやかれて終わってしまうようです。
サービス提供開始からしばらくの間はこの辺がおろそかになっていて、新機能をリリースしたものの、
メンション等で報告もらうまでしばらくバグに気付かず、気づくと既に何人ものぼやきツイートが流れていた…などという事故が多発していました。
- 情報収集 大事
個人開発の楽しさ/嬉しさ
普段使わない技術を使える
普段の業務においては納期や保守について考慮する必要が有るため、ホイホイと新技術突っ込んだりもできず、またそういった時間が取れないことも多いのですが、
個人開発ではそういった制約もなく、のびのびと自由に開発することができました。
業務グラミングと趣味グラミングはやはり別物ですね。
普段はRailsでB2Bの業務アプリケーション開発を行っているので、事前知識で持っていたのはRailsとDBの知識のみで、 AngularJSやHeroku、その他諸々突っ込んだものはほぼ知識0の状態からのスタートでした。 触ったことのないものを制約なくぽこぽこ触れるのは楽しいものです。
おかげで「ふろんと全く分からん」から「社内での議論に参加はできる」程度にはウデマエアップしたので、いい感じに実利も得られたのではないかなと思ってます。
感想がもらえるのは嬉しい
「自分用に作った」とは書いたものの、感想や改善提案がもらえるのは嬉しいものです。夜中に部屋でひとりニヨニヨできます。
なんだかんだ単純なもので「便利!」や「すごい!」などの声が聴こえるとウホホホーイとなるものです。
直接送信は気がひけるのであればツイートするだけでも作者に届いて糧になったりします。
万が一の事態が起きても…?
不具合発生などの失態をしても、業務に比べると遥かにダメージが少ないです。
もちろん発生後は落ち込むし反省もしているのですが、業務での失態の場合はどうしてもお金や信用、同僚や上司、その他諸々の要素に多大な影響があり、本当に胃の痛い事態になります。
それに比べればなんと楽しくお気楽でスピーディーな開発なのでしょうか。
ちなみに以下の様なことも起きました。
やばいIkaDBバグったのデプロイしちゃった(ガバガバproduction環境)
— ルニ@IkaDB! (@lni_T) 2015, 8月 4
常日頃BtoBで神経すり減らしてるんだからちょっとくらいいいじゃん
— ルニ@IkaDB! (@lni_T) 2015, 8月 4
あっあっあっ巻き戻せませんあっあっ
— ルニ@IkaDB! (@lni_T) 2015, 8月 4
おいなんでこんな時にseedデータがばぐっとんじゃリセットできんやろ
— ルニ@IkaDB! (@lni_T) 2015, 8月 4
うわあああActiveUser増えてるるるお前らちょっと待てえええええええええ
— ルニ@IkaDB! (@lni_T) 2015, 8月 4
業務ならハラキリ不可避な事象が起きていますがどこか楽しそうです。
また、この後無事復旧して何食わぬ顔で運営を続けられています。
普段会社で大量の問題処理票消化や再発防止策提出に頭を悩ませている人は、
個人サービス開発で運用環境フッ飛ばしてカタルシスを感じてみるのもいいんじゃないでしょうか。
何を得て、何を失ったか
個人開発により得られたもの
強さ
IkaDB作っててイカゲーで遊んでない医者の不養生状態
— ルニ@IkaDB! (@lni_T) 2015, 8月 27
Sが遠すぎる
— ルニ@IkaDB! (@lni_T) 2015, 11月 22
はいはいウデマエダウンウデマエダウン
— ルニ@IkaDB! (@lni_T) 2015, 11月 22
AdventCalendar用のウデマエ晒し #Splatoon #WiiU pic.twitter.com/S7739bwhv7
— ルニ@IkaDB! (@lni_T) 2015, 12月 9
カンストどころかSにも届かねえ… (ヽ´ω`)
個人開発により失ったもの
- 健康
仕事開発10:プライベート開発10みたいなことしてると元々体力が無い俺みたいな奴は体調崩すのでアカン(会社休んだ所でとめた)
— ルニ@IkaDB! (@lni_T) 2015, 11月 14
- 本業とうまく両立しましょう
おわりに
いろいろありましたが総じて楽しいことばかりだったので、日々モヤモヤを抱えている人は それを開発にぶつけて鬱憤を晴らしてみるのはいかがでしょうか!何か状況が変わるかも!
さらっと書くつもりが、IkaDB開発の総括のような内容となり、かなり長くなってしまいました…
最後までお付き合い頂きありがとうございました。
明日は13日目 @bkzenさんの記事となります。
Raspberry Pi2 に最新版のJenkinsを導入する
- ラズベリーパイ2にJenkinsを導入する。apt-getで入れられるらしい。
事前準備
- リポジトリを追加する。初期状態だとaptでは古いJenkinsしか入らない。
wget -q -O - https://jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add - sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'
手順
sudo apt-get update sudo apt-get install openjdk-7-jre sudo apt-get install jenkins
- まずはJava7をインストールすること。この手順を飛ばすとJava8がインストールされてしまった
- 当環境ではJava8でJenkinsは動作せず
Rails+AngularJSのアプリをJasmineでテストする
概要
- Rails + AngularJS + Jasmine + CoffeeScript
- Rails + AngularJSはこちら: Rails単体でAngularJSとTwitterBootStrapを組み合わせた環境構築 - Qiita
- Jasmineさいつよ
参考資料
- 以下の2つを組み合わせる
手順
jasmine-railsのインストール
- ここは参考資料通り
# Gemfile group :development, :test do gem 'jasmine-rails' end
で
bundle install
rails g jasmine_rails:install
したら一式インストール完了- 楽ちんすぎてばかになりそうだ
angular-mocksのインストール
- Jasmineでテストしようとすると、
angular-mocks
が必要になる。 - 入ってないとこれ以降の手順で以下のエラーが出る。
ReferenceError: module is not defined
- application.jsでrequireする
- なんらかの手段でangular-mocksのパッケージは入れておくこと
//= require angular-mocks
実際のテスト
- 先のjasmine-railsのインストールで、テスト用のページは作られているのでSpecRunner.htmlなどは不要で、そのままテストを書けばよい。
describe "HogesController", -> beforeEach -> module('myApp') beforeEach -> inject ($controller, $rootScope, Hoge) -> @scope = $rootScope.$new() @ctrl = $controller("HogeController", { $scope: @scope }) spyOn(Hoge, "query").and.callFake -> return [] it "search()", -> @scope.search() expect(@scope.hoges).toEqual([])
myApp
にHoge
サービスと、HogesController
が定義されており、HogesController
のsearch()
をテストすることとする。最初のbeforeEachでmoduleを準備する。
- 次のbeforeEachで
@scope
とテスト対象のコントローラを定義する。- ついでに
Hoge
サービスのquery()
で返ってくる値をspyOn
で代役を立てておく。これで実際に通信しにいくことはない。 spyOn
はJasmineのSpy機能の1つ
- ついでに
- itでテストを行う。
HogesController
でsearch()
が実行された後、scopeのhoges
にHoge
サービスのquery()
の戻り値が入っていることを期待する。
ひとりプログラミング合宿へ
突発的なここではないどこかへ欲
どこかで1人合宿したいなあ
— ルニ (@lni_T) 2015, 8月 23
そして合宿へ
計画
リリースやインターンシップのや炎上案件が重なり、取得できていなかった夏季休暇があったので今週取得して合宿へ。
特に場所の希望はなかったのでざっくりと決めていく
- 近場or遠出
- →あまり遠いと移動で合宿の体力が尽きるので関東近郊で
- 海or山
- ここは直感で海へ
ここまでくるとある程度しぼれる
- 千葉or神奈川
- 神奈川は来週別の用事で行くので違う場所へ行こうということで千葉へ
というわけで千葉に行くことに…
事前調査
- とりあえずるるぶで下調べ
- が特に行きたい場所なし
- というわけで適当に宿とって温泉はいってもくもくしましょう
- るるぶトラベルで宿探して予約
決行
- というわけで高速バスで1時間ほど揺られて木更津へ
- 到着しての感想
- 「田舎だ…」「駅前に人がいねえ…」「何もない…」
- 人が居ないので落ち着いて合宿できそうです。
- ひとまず宿のチェックインが16時なので喫茶店で時間つぶしがてら目標の記載を行う。
合宿の目標
- という訳で目標設定
スケジュール
- 14:00 目標設定
- 15:00 下調べ
- 15:45 ホテル移動
- 16:00 ホテルチェックイン
- 16:15 第一次もくもくタイム
- 18:00 近場の温泉へ(大浴場付きのホテルの予約が取れなかった)&晩飯
- 19:45 ホテル戻る
- 20:00 第二次もくもくタイム
- 21:30 休憩
- 22:00 第三次もくもくタイム
- 23:30 休憩
- 24:00 第四次もくもくタイム(眠気が来るまで)
実施内容
- もくもくと何か
意気込み
- 進捗出すぞー
本当の旅の目的
【悲報】ワイ将 ついに部屋にGの侵入を許す
— ルニ (@lni_T) 2015, 8月 23
敵をロスト!!!(家爆破したい)
— ルニ (@lni_T) 2015, 8月 23
おわあああああああ見つからねーーーーーーー
— ルニ (@lni_T) 2015, 8月 23
引っ越すか
— ルニ (@lni_T) 2015, 8月 23
Rails4でActiveAdminを使う備忘録
Railsで管理画面を作るのに使ったActiveAdminの備忘録
基本
↓に従って進める github.com
手順
導入
- Gemfileに記載してbundle install
gem 'activeadmin', github: 'activeadmin' gem 'devise'
- rails gで各種生成
$ rails g active_admin:install
- 特に何もしていしなければAdminUserというdeviseを使ってログインできるユーザーが作成される
- 日本語化したければしておく
- 試しにログインするとかっこいい管理画面が表示される。
モデルを操作対象に
- 対象のモデルに対して以下のようにrails generate
rails g active_admin:resource ModelName
- これで画面に表示されるが、まだ編集はできない。
- アソシエーション系も勝手に表示される!スゴイ!
app/admin/model_name.rb
で操作可能な属性を指定する- belongs_toは :hogehoge_idを指定すると操作可能になる。
追記 全属性を操作可能にする
config/initializer/active_admin.rb
に以下を追加。
ActiveAdmin::ResourceController.class_eval do # Allow ActiveAdmin admins to freely mass-assign when using strong_parameters def resource_params [(params[resource_request_name] || params[resource_instance_name]).try(:permit!) || {}] end end