モデルのプロパティのAttributeに(cipher = true)をつけるとテストが失敗する現象への対応

gen-modelで生成したモデルオブジェクトに、

@Attribute(cipher = true)
private String accessToken;

を付け、appengine-web.xml

<system-properties>
	<property name="slim3.cipherGlobalKey" value="hogehogehogehoge"/>
</system-properties>

を追記すると、ブラウザから動作させた場合は普通に動作し、Datastoreにも暗号化されて保存されるが、EclipseよりRun as->JUnit Testとすると、下記の例外が発生した。

java.lang.IllegalStateException: A cipher key is required.

appengine-web.xmlを読み込んでいない?


とりあえず、該当のテストクラスにて、setUpメソッドを実装。

@Override
@Before
public void setUp() throws Exception {
    super.setUp();

    Datastore.setGlobalCipherKey("hogehogehogehoge");
}


ただしこの方法、大量のテストクラスに対して上記の対応が必要になります。(該当のモデルを利用するController, Serviceのテストクラスなど)
もっとスマートなやり方がありそう。


ちなみに、ktrwjrであれば、上記の対応は必要ないみたいです。
でも、Eclipse上で完結させたいなー。

checkoutしたタイミングで任意の処理を実行する

前置き

仕事場では、gruntが導入されており、その生成物はバージョン管理の対象外となっています。
そのため、他の人がjsを修正した際に、ブランチを切り替えたあと、"grunt build"を実行しないと、古いjsのまま動かしてしまうことが何度かありました。*1
基本的に、自分ではjsを触らないこともあり、チェックアウトしたタイミングで、自動的に再生成されるようにしました。

手順

まず、チェックアウトされたタイミングで実行したい処理を記述します。
今回は kyouen-python にあるcoffeescriptを自動的にjsに変換する処理を書いてみます。
フォルダ構成としては、
kyouen-python/src/coffee:coffeescriptが保存してある
kyouen-python/src/js:生成されたjsを保存する
といった感じです。
なので、スクリプトは下記のようになります。

cd src/coffee
coffee -c -o ../js list.coffee


これを、"post-checkout"として保存します。(カレントディレクトリは"kyouen-python"の想定)

cd .git/hooks/
echo "cd src/coffee" >> post-checkout
echo "coffee -c -o ../js list.coffee" >> post-checkout
chmod +x post-checkout

あとは、通常通りチェックアウトすると、自動的にlist.jsが生成されます。*2

git checkout feature-XXXX


参考にしたのは下記です。
http://git-scm.com/book/ja/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF

雑感

"post-checkout"だけでなく、いろいろなフックがあるようです。
"pre-"とついたフックを書くことで、commitなどの行動に制約を課すことができるようです。(lintを実行し、エラーだったら拒否するとか)
チームで作業する際に、てきとーなコミットを許さない、とか出来そうですね。*3


.gitディレクトリ以下って、バージョン管理対象外ですよね?
この"post-checkout"ファイルはどうやって管理するのが普通なんだろう。。。

*1:本番・ステージング環境にデプロイするときはJenkinsのタスクに登録されています

*2:coffeeコマンドって、標準出力になにも出さないんですね。動いてるのかわからなかったorz

*3:でも、とりあえずコミットさせて、Jenkinsとかがフックして、違反があったらパトランプが回る、とかの方が面白いかなー

[Redis]Redisで作る更新通知

facebookの地球(?)のアイコンを押した時に表示されるようなものをイメージ。
自分の投稿に対して、いいねがついたことを通知する。

※コード書くのがめんどうだったので、redisのコマンドで記述してあります。

前提条件・要件

  • 元となるデータ(投稿、ユーザ、いいね)はRDBに管理されている
  • 新着件数、新着一覧を表示できる(ソート順は更新日時の降順)
  • 保持できる上限件数を99件とする(Redisのデータ容量を見積もることが可能)
  • 1つの投稿に対して、複数人のアクションをまとめて表示できる(○○さん他X人に・・・のような)

登録処理

  • user_id = 123の、

article_id = 234に、
user_id = 345が
2013/01/01 00:00:00(1356966000)にいいねを押したとき
→まず、既に存在しているかを確認する

HGET like:values:123 234

→emptyが返ってくるので、JSON文字列("{\"user_ids\":[345]}")を作成し、下記を実行

ZADD like:keys:123 1356966000 234
HSET like:values:123 234 "{\"user_ids\":[345]}"
ZADD like:new:123 1356966000 234
  • 同じ投稿に、

user_id = 456が、
2013/01/01 00:00:20(1356966020)にいいねを押したとき
→まず、存在確認

HGET like:values:123 234

→さっきのJSONが返却されるので、パースし、今回のuser_id(456)を追加する。
→そして、下記のコマンドを実行

ZADD like:keys:123 1356966020 234
HSET like:values:123 234 "{\"user_ids\":[345,456]}"
ZADD like:new:123 1356966020 234

同じユーザの、
article_id = 235に、
user_id = 345が
2013/01/01 00:00:40(1356966040)にいいねを押したとき
→まず、存在確認

HGET like:values:123 235

→emptyが返ってくるので、JSON文字列("{\"user_ids\":[345]}")を作成し、下記を実行

ZADD like:keys:123 1356966040 235
HSET like:values:123 235 "{\"user_ids\":[345]}"
ZADD like:new:123 1356966040 235

参照処理

  • 新着の件数を取得する(user_id=123)
ZCARD like:new:123
  • 更新通知を10件表示する(user_id=123)

→まず、キー一覧を取得する

ZREVRANGE like:keys:123 0 9

→取得できたキー(235, 234)について、値を取得する

HGET like:values:123 235
HGET like:values:123 234

RDBより、投稿・ユーザの情報を取得する

SELECT * FROM article WHERE id = 235;
SELECT * FROM user WHERE id = 456;
などなど
  • 新着更新通知を10件表示する(user_id=123)

→取得するキーが keys -> new を変えて、更新通知を10件表示するを実行する。

追記が必要なこと

  • 上限件数を越えようとした場合

ざっくり書くと、下記のような感じ。

    • 100件目以降のkeysを取得(WITHSCORES)
    • そのkeyで、valuesを削除する
    • keysの100件目のscore以下のnewを、ZREMRANGEBYSCOREを利用して削除する

multi -> exec を利用するべきかと。

書いた後気づいた

公式にtwitter cloneのcase studyがあった。
こっちを見たほうがいいんじゃなかろうか。
http://redis.io/topics/twitter-clone

CocoaPodsをインストールしてみた。

Mac OS X 10.8.2
Ruby関連は、アップデートはしてないはず。


基本的には下記を参考。
http://d.hatena.ne.jp/Watson/20111204/mac_dev_jp_advent_calendar_cocoapods01
なので、下記コマンドを実行。

sudo gem install cocoapods
略
Installing ri documentation for i18n-0.6.4...
Installing ri documentation for multi_json-1.6.1...
Installing ri documentation for activesupport-3.2.12...

unrecognized option `--encoding'

For help on options, try 'rdoc --help'

とか出てましたが、とりあえず気にせずに下記を実行。

pod --help

結果:

Your RubyGems version (1.3.6) is too old, please update with: `gem update --system`

ということで、言われた通り、下記を実行。

sudo gem update --system

すると、大量にログが出力されましたが、気にせず再度実行。

pod --help

なんか、それっぽいヘルプが出てきたので、下記を実行。

pod setup

すると、下記の出力。

Setting up CocoaPods master repo
Cloning spec repo `master' from `https://github.com/CocoaPods/Specs.git' (branch `master')

Cocoapods 0.17.0.rc4 is available.

Setup completed (read-only access)

とりあえず、セットアップは出来たらしい。

なので、TumeKyouen.xcodeproj が存在するディレクトリで

vim Podfile

で、内容に下記を記載。

platform :ios
dependency 'AFNetworking'


それでは、インストール

pod install TumeKyouen.xcodeproj

怒られた。

[!] Unrecognized argument: `TumeKyouen.xcodeproj'
pod install

とすると、

[DEPRECATED] `dependency' is deprecated (use `pod')
Resolving dependencies of `./Podfile'
Updating spec repositories

Cocoapods 0.17.0.rc4 is available.

Resolving dependencies for target `default' (iOS 4.3)
[!] AFNetworking (1.1.0) is not compatible with iOS 4.3.

との表示。

Podfileに

xcodeproj 'TumeKyouen.xcodeproj'

を追記し、

pod install

を実行しても、特に変化はなし。


で、PodfileのdependencyをAFNetworkingから、下記に変更。

dependency 'AdMob'

で、再度インストール

pod install

で、結果、

[DEPRECATED] `dependency' is deprecated (use `pod')
Resolving dependencies of `./Podfile'
Updating spec repositories

Cocoapods 0.17.0.rc4 is available.

Resolving dependencies for target `default' (iOS 4.3)
Downloading dependencies
Installing AdMob (6.3.0)
Generating support files

[!] From now on use `TumeKyouen.xcworkspace'.
Integrating `libPods.a' into target `TumeKyouen' of Xcode project `./TumeKyouen.xcodeproj'.

[!] The target `TumeKyouen [Debug - Release]' overrides the `OTHER_LDFLAGS' build setting defined in `Pods/Pods.xcconfig'.
    - Use the `$(inherited)' flag, or
    - Remove the build settings from the target.

あれ、通った?
とりあえず、

open TumeKyouen.xcworkspace

を実行したら、エラーになってしまって、よく考えると、TumeKyouen.xcodeproj を開いたままだったので、Xcodeを全て閉じ、再度

open TumeKyouen.xcworkspace

とすると、無事、開いて、ビルドも成功。
とりあえず、今日はここまで。

  • 3/16 追記

もともと入れていた、AdMobのリソースを削除したところ、コンパイルエラーになってしまった。
そこで、TumeKyouenプロジェクト -> Build Phases -> Link Binary With Libraries に、libGoogleAdMobAds.a を追加したところ、コンパイルが通るようになった。

Developer Migration 2013に参加しました #devmi

http://www.pasonatech.co.jp/ca/devmi2013/

ハッシュタグ#devmi
https://twitter.com/search?q=%23devmi

開発者は仕事でリーダブルなコードを書けるのか?

http://slide.rabbit-shocker.org/authors/kou/devmi-2013/


例1と例2ではどちらがリーダブルなコードか。
例1の方が行数は少ないが、例2の方がリーダブル。
例1は、他のプロパティに対して、他の排他制御をするかもしれない。(mutex2で制御するとか)
例2であれば、このクラスのsynchronizeを呼んでいるため、クラス全体で排他制御をするように見える。


"書いた人の意図"をコードに表す


何しているか・何したいかがわからないコードを読む・変更するのはつらい


リーダブルなコードなら、変更の要求に対して、すぐに対応出来る
⇒オレってばスゲー感


検討を重ねて、必要なものだけを作るっていう考え方もあるが、
それは、コード書いてると時間がかかるから?
必要ないものに時間をかけたくないってことでは?
⇒簡単に試せるのであれば、考えてる間に書いて、動かせる(それで不要になっても、失った時間は少ない)


実サーバから仮想マシンに移行したことにより、追加・削除が容易になった
→コードだって同じ様に出来る(必要になったら、すぐに作る)


バッファを積むより、困ったらすぐ相談出来るような関係性を築いておく。
そっちのほうが、誠実。


リーダブルなコードなら、誰でも直せる
自分の担当コードだけで何とかしようとすると、DRYじゃないコードになる。
修正すべき箇所を修正するべき。
ただし、SIerでは、責任問題とかいろいろあって、難しいかも。


で、本題
"仕事で"リーダルブなコードが書けるのか?


コードレビューが必要といわれるが、読んでくれる人がいない
→まず、自分で読んで見ましょう
時間無いし
→1日15分とか、時間を決めてその時間だけ読む


クリアコードの事業の話
「みんながみんなのコードを読む」文化にする


B2D(Business to Developer)事業
B2CのCの気持ちを考えるより、Dの気持ちのほうが理解しやすいですよね?


読むことの必要性
読んだ経験がないと、どんなコードがリーダブルかわからない


"プロジェクト内のリーダブルなコード"を共有するためにコードレビューする
良いコードを見たら、真似してコミットする(いいね!ではなく行動する)


他の人のコードを読むことで、その人の力量・得意分野がわかる
コミットの間隔を見ることで、生存確認できる?


クリアコードにてサービスも提供していますが、、、
オープンソースソフトウェアの開発に参加してみれば、
そこでの体験を自分の言葉で説明できるのでは?

質疑応答

リーダブルなコードとは、チームのメンバーが同意できるコードということですか?
→YES


コードを読んで、指摘することで、負の感情を与えてしまうのでは?
→日本語で伝えるのが難しければ、コードで語る(こっちのコードが良いのでは?とコミットする)


リーダブルなコードを書くために気をつけていることは?(注:質問内容違ったかも)
→自分の書いたコードは忘れるようにしている
 忘れても、読めるようなコードを書く

あたりまえのアジャイルと、その先の世界

スライドは見つからなかったので、
http://hiroki.jp/2013/03/01/6786/


アジャイル開発のコンサルティングみたいなことをしている人


アジャイル開発が一般的になってきた。ただし、業種による。


アジャイル=not waterfall
それ以上の細かい説明は人それぞれになってしまう


ガラケー→スマホ
CD→iPod/iPhone
パッケージソフトを購入→AppStoreでダウンロード
上記は10年程度での変化⇒変化のスピードは速い


変化の無いものは捨てられていく
 新しくても、生き残れないものもあるけど


開発期間が短くなってきている
 年単位だったものが3〜6ヶ月とか
  1,2日で作った要件定義書から、実装して、みたいな。


技術も変化・拡大していく
 LAMPぐらいでサービスが作れる→とにかくたくさんの技術が必要になってきた


物事は変化するので、それに備えることが大切
→これを解決する一つの策として、アジャイルなプロセスがある


ソフトウェア開発プロセスとは、枠組みである
自分たちに合ったものを選べばよい


Scrum
アジャイルやるなら、まずはこれ。
詳しくは スクラムガイド を参照
理解は容易、しかし、習得が困難。
なぜなら、経験が必要→最初はつらい(経験主義)
Scrumは薄いフレームワークなので、それをやりながら、自分たちに合った手法を取り入れていけばよい。
本を読むことで経験を少しは補える。 サムライ・エピソード
 日本の開発者の経験が書かれている


Skill
Scrumは部門まで巻き込む話。
プログラマとして何をするべきか書いてあるのがXP
Scrumをやるためには必須


プログラマとして、最低限必要なこと
・Git
・言語知識:その言語っぽく(その言語らしい)コードが書けること
・言語設計(オブジェクト設計、オブジェクトデザイン):これを知らないと、スプリント毎につらくなる
テスト駆動開発
・CI:最初(イテレーション0)でやるべき
実装パターンリーダブルコード :パターンを知る
フレームワークの知識:知識がなければ、使わない方がまし
チームに一人は詳しい人が欲しい
デザインパターン
リファクタリング
・インフラのオートメーション化: Pro Puppet



経験からの話


アジャイルにはコーチが必要
半年は必要。3ヶ月とかでは無理。
会社・チームにあったコーチを招くのが理想。


とにかく、学ぶ


GitHubを使いこなす
 http://github-book.doorkeeper.jp/


アジャイルが浸透した会社などが次に目を向けているのはLean
今までの話は、フィードバックループ内のBuildの話だけ。
次にやるべきはMeasure、そして解析
どういうデータを提供すべきかを、エンジニアが見当つけられた方がよい。
なので、初歩的な統計学の知識とかも合ったほうがいいスキルになるだろう。
Leanを学んでみるのなら、 Running Lean

質疑応答

計測・解析について、データサイエンティストになる必要がある?(注:ニュアンス忘れました)
→今のところ、一般的な環境ではそこまでやる必要がない。企画者・プロデューサが理解できないだろうし。
 ツール・可視化はエンジニアがやるだろうから、それぐらいの知識は必要になってくるだろう。

2013年の開発環境で求められるエンジニアとは?


(注:下記はほぼメモのまま)


勉強するべきプログラミング言語・抑えるべき技術についてはディスカッションしません。

自己紹介と発表の内容を簡単に


>大石さん
AWSに特化したSIer
未来予測は意味ないよね。
予測は外れるし。
変化に対応できるようにする


>大塚さん
フリーのアジャイルコンサル
変化に対応するためにアジャイルが必要
それが当たり前になると、Leanへ。
スキル・アジャイルが必要。


>須藤さん
リーダブルなコードとは。
利点と、どうやって実現するか。


>安達さん
DevOpsについて
・採用はいいけど、何を解決したかったの?
・変化が激しい→スキルセットの変化も必要
インフラエンジニア→アプリケーション開発者


これまでの10年を振り返る

・クラウド登場前後での変化


1.クラウドサービスを知ったきっかけ
2.知った前後で何か意識の変化があったか
3.具体的にどんな変化か


>大石さん
2007年にAWSを知った。
まず、コピーしようとした。
S3は作れなかった。実装が意味わからん。
3年30億かけても追いつけない。その時点で枯れた技術だった。(Amazon内で利用していた)
2008年社内サーバ購入禁止令
使う側の技術を極めるように方向転換。


>安達さん
Saasから知った
インパクトが大きかったのはIaas
サーバが使い捨てのイメージに
1年もしたら、新しいのに乗り換えるみたいな。


>大塚さん
ディザスタリカバリがすげーことになったなぐらいー。
古いインフラ屋さん、(=不動産業)はつらいなー。
今では、あたりまえになってきた。


>須藤さん
あんまり覚えてない。Amazon, googleとかで知ったなー。
意識の変化とか、特に無く、
なんかあったら、使おうかなー、ぐらい。


・業務システムへのOSS活用に抵抗なくなったのはいつ頃?
業務システムもOracleばっかりじゃなくなってきた


>大石さん
2005年でMySQL提案したら、蹴られた
2007,8年でやたら高いサーバを買えなくなった。経済的な要因が大きい。


ソラリスとか、見かけなくなった。


>安達さん
2007年とか入社なので、最初から抵抗がなかった。
在籍した部署もOSSを推進するようなところだったし。

2013年以降の展望


・SIの今後


>大石さん
ボタン一発で、一括リプレースとかが可能になってきた。
SIのピラミッドの、下の方の人達が必要なくなってきた。
人がたくさんいるから、コミュニケーションスキルが大事だった。
→人がすくなければ、そんなに必要ないので、コミュニケーションスキル<技術となってきた。


>須藤さん
大きなピラミッドにかかわってきてない


>大塚さん
ピラミッドから1年で脱出しちゃった


>安達さん
自分のいた部署はそうじゃなかったが、周りの人達はピラミッド構造の中にいて、大変そうだった



・SIのビジネスモデル変化で今後意識した方がよいことは?


>安達さん
インフラチーム・アプリチームの分業だった
→一人のエンジニアが全てやるようになってきた
 いなくなった時のことを考えペアとかにはするけど、個人が一通り出来る必要がある


>須藤さん
開発・運用・監視とかは一通りやってる
SIのビジネスモデルから離れたところで仕事するのがいいのでは。(B2Dみたいに)


ストレッサー
=ストレス要因となるもの
 人によって異なる

・外部環境変化が「ストレッサー」にならなかったか?


>小山田さん
変化をわりと楽しめている
この進行役も緊張/ストレスは感じるけど、楽しめる


↓いろんな環境変化を経験してますよね?
>大塚さん
SIがストレス→やめる
不動産業ストレス→やめる
ソフトウェア開発がストレスだから、それを変えるためにアジャイルを進めている
ソフトウェア開発がストレス=納期・金額をどんぶり勘定で押し付けられる


>安達さん
自分が変わらないと、という危機感から変化する
違う強い思いから、外的変化がストレスを感じない


↓普通のSI→AWS特化への変化
>大石さん
ストレスにはなる
AWS特化になって、インフラのリーダーがいやになってやめた。
→ストレスだったんだろう。
その後戻ってきた
普通になってきたから?
だいたいのことは、命までは取られないから、1,2年我慢してみたら。
ストレス耐性高い=パフォーマンスが高い(一般論として)


↓ストレス耐性は、先天的にもってるもの?後天的に得られるもの?
>大石さん
自分はストレス耐性が無かった(学生時代のテストとか)
プレゼンも全く緊張しなくなってきた
 聴衆が、見てない・覚えてないことをわかったから
自分の覚えた言語が無くなったみたいなことも経験してきた
 経験・達観・前のあれみたいなやつ
 例:クラウド=タイムシェア・集中コンピューティング
学習により、ストレス耐性がつく


>須藤さん
どんな変化によるかで違う
 技術的な変化は楽しい
会社の経営はストレス、いろんなことを決めなくてはならない
しかし、周りの人にいろいろ教えてもらって、楽になってきた
判断基準を学んだ
好きじゃないものでも、ストレス対象にならなくなってくる


↓パネルディスカッションは初めてらしいですがストレス?
>須藤さん
ストレスというか、どうしたらいいかわかんない感じ


・ご自身なりの「キャリア戦略」をお持ちですか?


>小山田さん
コードが書けるキャリアコンサルタント
QuitaのiPhoneアプリを申請中
技術者と話すときに、共通言語で喋れる=相手の力量が計れる


>大塚さん
意識は特になく、自分が何が出来るか、何を提供できるかを明確に持っている、ぐらい


↓ブログのテーマ選択とかの基準は?
>大塚さん
ブログは仕事を意識して書いてはいない。

基本的に、経験したものしか、自信をもって提供できない


↓インフラ寄りの人はアウトプットの仕方が難しいですよね?
>安達さん
ブログは書けない(外にだせない)こともある
キャリア戦略としては、人と違うことをやってみる
DevOpsでインフラ・アプリが出来る
マーケティングが出来るエンジニア
デザインも出来るエンジニア
とか。


↓マーケティング・デザインなど、何を基準に次の領域を選ぶ?
>安達さん
一人で設計・開発・運用が出来るようになるにはを考えている
結びつかないような(他人がやらないような)技術領域にチャレンジ
インフラもデザインも出来る、とか。


↓安達さんみたいなひと(なんでもやる)が採用にエントリーしてきたら?
>大石さん
基本、キャリア戦略とか信用してない
会社としてはマルチタレントが欲しいので、好印象。
ただし、キャリア戦略よりも、今、会社内で評価されてるか
 過去・現実の評価があること
今の環境でベストを尽くせない人は、どこへ行ってもベストを尽くせない。


↓変わった採用方法を採ってますよね?
>須藤さん
http://www.clear-code.com/recruitment/:パッチ採用
戦略より、コード書けよ。


↓採用にかかる期間、長いですよね
>安達さん
採用に半年かかる
採用は結婚


↓フィーリングが合いそうな人とは?
>須藤さん
積み重ねが必要。(これさえクリアしたら、みたいなものではない)
コーディングスタイルを個々のプロジェクトに合わせる(不要な我を全面に押し出さない)
他の人も考えたコードを書く


>安達さん
同棲してみて、価値観をすり合わせる
積み重ねていく


↓一方的にアウトプットする人ではなく、協調出来る人?
>安達さん
会社が若いので、変化も体験してもらいながら、価値観をすりあわせていければ。


・なぜリーダブルなコードが必要なのか?
>須藤さん
リーダブルじゃないコードを見続けるのがつらい→ストレッサー
なんかあった時にすぐ対応できる→"変化"に対応


・リーダブルなコードのビジネス上の利点
↓「お客さんの要望に答えやすい」以外に
>須藤さん
開発者が死んで行かない
精神衛生上やさしい


↓こんな利点が、とかあります
>大塚さん
リリース後の方が時間が長い(短納期)
書く時間<読む時間 になってる
リーダブルなコードが必要なのではなく、リーダブルなコードを書ける人が必要
↑それには、リーダブルなコードを読む必要がある(鶏が先か卵が先か)


>大石さん
マイコンBASICマガジンというドMな雑誌が昔ありました。
昔、リーダブルより、メモリ節約だった。それを写経で覚えた。
新入社員にリーダブルなコードを写経してもらおうかと考えてる
 自分もやらされそうだけど。


・コード書けないエンジニアの立場ってどう思うか?
>安達さん
インフラエンジニアは昔はコーディングしなくてよかった。
インフラ構築のためにコードが必要になってきた
料理の作れない料理人(いらない人)=コードの書けないエンジニア になってくる


>大塚さん
ネットワーク・インフラとかは(コードを書けないエンジニアでも)必要では?
中途半端な領域の立場の人は危ういだろうけど、
DC1から作れますとか、極めたひとはコードは必要ないのでは


>安達さん
物理的なものに特化した人はコードが書けなくていい
 ネットワーク仮想化とかでコードで解決する技術領域が広がってくるとわからないが。


・「コード書けるようになりたい」という気持ちを持ったエンジニアに何かヒントを
>大石さん
コード書けばいいですよ。
写経。
コード書けないエンジニアも、前までは必要だった。
Excel書くとか、人と人をつなぐとか。
これからは、そういう人が減ってくる。
綺麗なコードを書ける人は、論理構造の綺麗な日本語を書ける。
ドキュメントもコードも言語なんだから、ちゃんと日本語書けた人は、コードも書けるはず。


>大塚さん
コード書いてる人向けに言うと、テストを書けるようになれ。
テストを書けるようになるには、テストコードを写経する。
常識の位置を変える。
テストを必ず書く、を徹底する
チーム内で自分だけ書いてる人もいる。
意識を変えていく。


↓テストを個人プロダクトで書けてない。モチベーションが上がらない。どうしたらいい?
>大塚さん
WEBアプリ作るときに、コード書く→F5 をやりたければ、それをやれば。
書かなければいけない、トレーニングを強制する。
とりあえず、1つ書いてみる。
仲間を作る。議論しあえる。


>大石さん
会社では、テスト通らないとYammerに報告される
→はずかしい



・参加者からの質問


・AWS以外にいいのありますか?
 ソフトレイヤーとかいいのでは?


>大石さん
Amazonの人いないですよね。。。下手なこと言うと、怒られちゃう。
IAASはAmazonでよいのでは?
あとは、それぞれの得意領域に合わせて選択していけばいい。(ストレージが安いところと、簡単にアプリを作れるところをつなぐ、とか)
組み合わせだけで、作らない開発ができるのでは。


・写経するのにおすすめのものはありますか?
 Ruby/JavaScriptとかで。
>須藤さん
自分が使ってるサービスをやってみれば。その方がイメージつきやすいし。
Redmineとか?
いいと思います。
とりあえずやってみればいいと思う。
で、いくつかやってみる。


>大塚さん
実装パターンを読んでみるとか。
コードは小さなパターンの組み合わせ。
元ネタがあると、実用的かどうかを判断しやすい。


・テストコード書く際に徹底させるべきこと
>須藤さん
とりあえず、リーダブルコードをまず読んでください。
自分のコードをリーダブルにする。


>大塚さん
計画が大事なのは当たり前だが、振り返りも大事。
定期的に振り返りを行う。
日々の積み重ねを行う。


・コード書ける人が、インフラを学ぶヒント
>安達さん
Rubyをやってるのであれば、サーバ内で動いているスクリプトをRubyで書いてみたら?


>大石さん
サーバワークスという会社がありますよ。今回は採用みたいな話は出来ないのですが。
個人でやるのは限られる。大きなものをやるには会社が必要。
上長を巻き込む・説得する。
経営的なメリットがあるらしい、と上長に訴え、ボトムアップでやる。

iPhoneアプリへのAdMob導入のために行ったこと


基本的には公式ドキュメントを参照
https://developers.google.com/mobile-ads-sdk/docs/ios/fundamentals?hl=ja


ライブラリのコピー

  • AppDelegateがあるフォルダ(グループ)に"GoogleAdMobAdsSdkiOS"フォルダを作成し、ダウンロードしたzip内のREADME.txt以外のファイルをコピー

(フォルダ階層については、他のサンプルなどを確認し、要変更)


エラーの解決

"_OBJC_CLASS_$_ASIdentifierManager", referenced from:

ビルド時に、上記のエラーが発生したため、
http://www.j7lg.com/archives/1497
を参考に、AdSupport.frameworkを追加

上記を設定後もビルド時に、

"_OBJC_CLASS_$_SKStoreProductViewController", referenced from

上記のエラーが発生したため、
http://stackoverflow.com/questions/12726640/build-error-while-add-revmob-add-in-ios-app
を参考にStoreKit.frameworkを追加

 -[GADObjectPrivate changeState:]: unrecognized selector sent to instance

が出力されていたため、下記を参考に、該当のTARGETSのOther Linker Flagsに"-ObjC"を設定(Debug, Release両方に設定される)
http://stackoverflow.com/questions/12635283/admob-crashes-with-gadobjectprivate-changestate-unrecognized-selector


最終的に追加したframework一覧

  • AudioToolbox
  • MessageUI
  • SystemConfiguration
  • CoreGraphics
  • AdSupport
  • StoreKit

Google App EngineのDataStoreに格納しているデータをローカル環境で利用する

=== 追記 2019/3/9 ===
2019年のやりかたで書き直しました。
本番のCloud Datastoreのデータを、ローカル環境にインポートする - Qiita

=== 追記終わり ===


試験データを作成するのが面倒だったので、ダンプしてローカルで利用しようと思い、
ググったら出てきたので、メモ。
(下記、appname=my-android-server です。)


基本的には、
http://kopipeprogrammer.blogspot.jp/2011/02/google-app-engine-bulk-loader.html
を参考に、
https://developers.google.com/appengine/docs/python/tools/uploadingdata?hl=ja
を参照しました。


実際に欲しかったのは、全てのデータだったので、

  • builtins ディレクティブを使用した remote_api のインストール
  • 全データのダウンロードとアップロード

を行ったのですが、

google.appengine.api.datastore_errors.BadRequestError: app s~my-android-server cannot access app my-android-server's data

と、エラーになってしまいました。
記号がある場合に変になるのかと思い、applicationパラメータの指定バッククォートで囲み、

appcfg.py download_data --application=`my-android-server` --url=http://my-android-server.appspot.com/_ah/remote_api --filename=./dump.datastore

に変更したところ、正常に動作しました。
ハイフンなどの記号がある場合、バッククォートで囲めばなんとかなるっぽいです。
取得したデータ量と、かかった秒数的には下記の通り。

16962 entities (4560394 bytes) transferred in 871.2 seconds

ちなみに、Datastore Read Operations は40%まで上がりました(通常は数%)


次に、起動時の"--datastore_path"パラメータに、dump.datastoreを指定したのですが、

ERROR    2013-02-12 14:08:51,099 dev_appserver_main.py:691] <class 'google.appengine.runtime.apiproxy_errors.ApplicationError'>: ApplicationError: 3 Could not read data from /Users/ishikuranoboru/workspace/python/kyouen-python/src/kyouen.datastore. Try running with the --clear_datastore flag. Cause:
ValueError('insecure string pickle',)

というエラーとなってしまいました。
エラーメッセージなどでググると、
https://groups.google.com/forum/?fromgroups=#!topic/google-appengine/H7hYxKHvmC0
が見つかり、

appcfg.py upload_data --url=http://localhost:8080/_ah/remote_api --application=`dev~my-android-server`  --filename=dump.datastore --num_threads=5

を実行したところ、インポートが始まりました。("--application"に、"dev~"+を指定)
投入にも同じぐらいの秒数がかかりました。

16962 entities (4211170 bytes) transferred in 848.2 seconds


実際の画面やSDK Consoleを確認すると、本番のデータが表示されました。


高負荷かつ長時間かかるため、頻繁には使えないと思いますが、本番である程度データが溜まった場合は、これを利用してローカルにテストデータを投入出来そうです。

facebookのアクセストークンの長さについて

元記事が変更されてました。(2013/5/3)
現在は、MySQLのTEXT型にしておいたほうが安全なようです。


facebookのOAuth認証を行うアプリを作る場合、access tokenを保存しなければならないかと思います。
テストアカウントで認証を行なっても、それぞれバラバラの長さで取得されるため、必要十分な長さがわかりませんでした。
本番で変わったユーザのみ認証が成功しないなど、気づきにくいバグの発生を誘発する恐れもあります。
(実際、varchar(200)で定義してて、1つのテストアカウントでは投稿できないということがありました。)

そこで、いろいろ調べてみたのですが、stackoverflowに書いてありました。
http://stackoverflow.com/questions/4408945/what-is-the-length-of-the-access-token-in-facebook-oauth2
元記事はこちら。
https://developers.facebook.com/blog/post/572/

なので、MySQLなどで保存する場合は
varchar(255)
としておけば問題ないそうです。
案外、検索しても出て来なかったので、メモ。

Read It Nowをリリースしました。

Read It Now

https://play.google.com/store/apps/details?id=hm.orz.chaos114.android.readitnow

Pocket (Formerly Read It Later) の非公式Androidウィジェットです。
・タグ・検索文字列などの条件で絞り込んだ件数を、ウィジェットに表示
ウィジェット毎に条件を保存可能
ウィジェットをタップすると、絞り込んだ一覧を表示

※指定できる条件
state(all or unread or Archive)
favorite(favorited or un-favorited)
tag(all or 入力したタグ名)
contentType(article or video or image)
sort(newest or oldest or title or site)


また、このアプリケーションも全てのソースを公開しています。
https://github.com/noboru-i/ReadItNow
技術的なトピックとしては
Pocket API の利用
ActionBarSherlock の利用

本アプリケーションは、Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) の下で提供されるソースコードを使用しています。


Pocket公式クライアントを利用していたのですが、あとで読もうと思って忘れてしまうことが多々ありました。
しかし、RSSGoogle Readerwidgetを利用していたので、Home画面に未読件数が表示されているので、ちょっとした空き時間に思い出して消化していくことが出来ました。

そこで、Pocketの未読件数もHome画面に表示しようと思ったのですが、Pocket公式クライアントにはwidgetがありません。
無いものは作ろうと思い、作ったのがこれです。
stateがunreadのものだけ表示しておけば、未読件数がHome画面に表示され続けるので、Google Readerwidgetと同じような使い方が出来るかと思います。


とは言いつつ、未読を既読にする機能が未実装なので、Pocket公式クライアントを利用しないと管理は面倒です。
こちらについては、アプリ内で更新も出来るように改善予定です。


ちなみにこのアプリ、Night_Hack_Cero_v01参加中に公開しました。
Ver2.0もあるようなので、参加を検討中です。

タップしたらActivityが起動するwidgetを作成しました

全てのソースコードは下記より

https://github.com/noboru-i/ReadItNow/tree/tap-action-widget

最終的に、下記のように比較的すっきりしました。
プログラミング途中では、思ったように動かず、それを解決するためにもっとコード量が多かったのですが、
アンインストール→インストール→widgetの配置
を毎回するようにすると、うまいこと動きました。
変更し、上書きインストールなどを行うと、AppWidgetProvider#onUpdateが呼び出されたりして、
想定とは異なった動きになっていたようです。

実装

まず、AndroidManifest.xmlwidgetを定義します。

<receiver
    android:name=".appwidget.CountWidget"
    android:exported="false" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <!-- This specifies the widget provider info -->
    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/widgetinfo" />
</receiver>
  • android.appwidget.action.APPWIDGET_UPDATE

ウィジェットの更新を取得するために必ず必要となります。

  • widgetinfo

xmlフォルダに定義します。

widgetinfo.xml

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minHeight="40dp"
    android:minWidth="40dp"
    android:resizeMode="vertical|horizontal"
    android:updatePeriodMillis="1800000"
    android:configure="hm.orz.chaos114.android.readitnow.ui.SettingActivity" />

widgetのレイアウトを指定します。

https://sites.google.com/a/techdoctranslator.com/jp/android/practices/ui_guidelines/widget_design
より、1セルは40dpのようなので、40dpを指定しています。

には、widget配置時に起動するActivityを指定しています。パッケージ名を含め、全てを記載します。

widget_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="@dimen/widget_margin"
    android:background="@drawable/widget_background"
    android:orientation="vertical"
     ></LinearLayout>

https://sites.google.com/a/techdoctranslator.com/jp/android/practices/ui_guidelines/widget_design
より、8dpぐらいマージンを取ったほうがよさそうなので、"@dimen/widget_margin"を指定しています。
values/dimens.xml に定義しました。

CountWidget.java

package hm.orz.chaos114.android.readitnow.appwidget;

import hm.orz.chaos114.android.readitnow.R;
import hm.orz.chaos114.android.readitnow.ui.MainActivity;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.RemoteViews;

public class CountWidget extends AppWidgetProvider {
	private static final String TAG = CountWidget.class.getSimpleName();

	public static final String EXTRA_APP_WIDGET_ID = "appWidgetId";

	@Override
	public void onEnabled(Context context) {
		Log.d(TAG, "#onEnabled");
	}

	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {
		Log.d(TAG, "#onUpdate");
		Log.d(TAG, "appWidgetIds.length = " + appWidgetIds.length);
		Log.d(TAG, "appWidgetIds[0] = " + appWidgetIds[0]);

		for (int appWidgetId : appWidgetIds) {
			RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
					R.layout.widget_layout);
			Intent intent = new Intent(context, MainActivity.class);
			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, 0);
			remoteViews.setOnClickPendingIntent(R.id.text_view, pendingIntent);
			appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
		}
	}

	@Override
	public void onDeleted(Context context, int[] appWidgetIds) {
		Log.d(TAG, "#onDeleted");
	}

	@Override
	public void onDisabled(Context context) {
		Log.d(TAG, "#onDisabled");
	}
}
  • #onUpdate

複数個のwidgetを配置し、再インストールした際など、
引数のappWidgetIdsには複数件入ってくるようなので、ループで処理しています。
PendingIntent を作成し、RemoteViews#setOnClickPendingIntent に渡します。
第2引数については、
http://y-anz-m.blogspot.jp/2011/07/androidappwidget-pendingintent-putextra.html
を参考にidを指定しました。

SettingActivity.java

package hm.orz.chaos114.android.readitnow.ui;

import hm.orz.chaos114.android.readitnow.R;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.util.Log;
import android.view.View;

public class SettingActivity extends PreferenceActivity {
	private static final String TAG = SettingActivity.class.getSimpleName();

	private int mAppWidgetId;

	@SuppressWarnings("deprecation")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		addPreferencesFromResource(R.layout.query_preference);

		// Find the widget id from the intent.
		Intent intent = getIntent();
		Bundle extras = intent.getExtras();
		if (extras != null) {
			mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
					AppWidgetManager.INVALID_APPWIDGET_ID);
		}

		// If they gave us an intent without the widget id, just bail.
		if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
			finish();
		}

		ButtonPreference preference = (ButtonPreference) findPreference("complete_button");
		preference.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				finishConfigure();
			}
		});
	}

	private void finishConfigure() {
		Log.d(TAG, "#finishConfigure");

		// Make sure we pass back the original appWidgetId
		Intent resultValue = new Intent();
		resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
		setResult(RESULT_OK, resultValue);
		finish();
	}
}
  • #onCreate

起動時のIntentより、APPWIDGET_IDを取得しています。
複数のwidgetを配置した場合に、識別するためのキーとなります。

  • #finishConfigure

SettingActivityに配置したボタンを押下した際に呼ばれるよう、onCreate で指定しました。
http://y-anz-m.blogspot.jp/2011/06/androidappwidget.html
を参考に、widgetのidを入れ、RESULT_OKを指定しました。