めあとるーむ日記帳

なんか書く

CircleCIでMavenを使ってGoogle AppEngineをデプロイするのを自動化したい

追記(2017-09-08)

解決してないのでググってたどり着いた人ごめん


タイトルの通り。

GAEのデプロイは2通りあって、appcfgを使うかMavenを使うか

Deploying a Java App  |  App Engine standard environment for Java  |  Google Cloud Platform

おススメ(recommended)なのはMavenらしいので、それを使っていたのだが、せっかくだし自動化したいなーと思ってCircle CIでデプロイコマンドに mvn appengine:update をそのまま入れたところ、まあ当然なのだが認証が通らず止まってしまった。デフォルトではOAuthで認証してキーを取ってきてそれを入れると通るのだが、当然入力する場所はないのでどうしようもない。


というところで止まっている。お盆が明けたら再開予定……

可能ならサービスアカウントを登録させるか引数で渡すべきなのだと思う……

ちなみにほかのデプロイ方法ではどうやらgcloudコマンド用の設定がCircle CIに用意されているらしい。


追記(2017-09-08)

ようやく追記(タイトルもちょっと修正)

とりあえず結論は出来なかった。できなかったというよりわからなかったが近い。

サービスアカウントの情報を渡して認証させてもどうやら maven appengine コマンドが読んでくれない。

認証関係のオプションを探してみたがそれらしいものはなかった。一応auth関係のオプションも探したがそれらしいものも見つけられなかった。

pythonコマンドを用いたデプロイやらコンテナベースのサービス?なら手段もそれなりにあるっぽいが、残念ながらわからないというのが実情。むなしい

値を返してから代入する

ちょっと前にTwitterで人が死ぬバグといって流れてきたツイートがそれなりに話題になっていたと思う。

これね。出典元は 非推奨だった bool 型に対するインクリメント演算子を削除 - cpprefjp C++日本語リファレンス

これは古いCのコードをもとにしていて、かつ当時はbool型もなかったのでこうなっていたようだが、配列の要素を並べて表示し、それぞれの要素間に,を表示するといったパターンではおそらく昔はよくあったコードだと思う。


この「配列の要素を並べて、各要素間にはコンマをうって表示する」というケースに類似した処理はよくあるものだと思う。

地味に面倒なことに、この場合「最初は飛ばすがそのあとからは毎回つける」というパターンで記述することが多い。

で、その場合(下のコードはJava

boolean first = true;
for(Object obj : objList){
    if(!first){
        System.out.print(",");
    }else{
        first = false;
    }
    System.out.print(obj.toString());
}

みたいなコードが必要になったりする。

はっきり言って面倒である。

ちなみにC++では上のリンクであるように昔はインクリメントがあった。bool型(boolean型)のインクリメントはJavaにはないし、C++も今はない。

後述するがインクリメントははっきり言ってわかりにくいものなので最近だとSwiftでは方にかかわらずインクリメント自体がなくなった。

ではこのインクリメントなしで書くにはどうするか、というと、まあ上のように一つフラグを用意してあげてとなるのだが、C++では割と便利な関数ができていた。

それがstd::exchangeというので、std::exchange - cppreference.comにある。

これは「値を返してそのあと代入する」というものである。

ただこれはJavaにはない。Javaにそんな便利なものがあると思ったら大間違いだ。欲しければ作れ。という感じでこんな感じになるだろうか

public class Exchanger<T>{
    T value;
    public Exchanger(T value){
        this.value = value;
    }

    public T get(T newValue){
        T oldValue = value;
        value = newValue;
        return oldValue;
    }
}

使うときはこう

Exchanger<Boolean> first = new Exchanger<Boolean>(true);
for(Object obj : objList){
    if(!first.get(false)){
          System.out.print(",");
    }
    System.out.print(obj.toString());
}

ちょっとはキレイになった。

問題点はJavaなんでプリミティブ型が使えないこととか、Integer型のときに +1 で回せないという点あたりか。後者の問題はそのままの値を返すget()メソッドで値を取って+1したものをnewValueとしてget(T newValue)に渡す感じにするだろうか。ジェネリクスを使う以上あまり入り込んだ処理は行いたくない。

いずれにせよJavaだと面倒。ほかの言語であればだいぶ楽(C#とかならLINQ使ったりできるかもしれない)なので調べる価値はあるはず。


ところでインクリメント(と、デクリメント)について。

いわゆる++だが、先述の通りはっきり言ってややこしい。i++++iの違いなど意識していては可読性が下がるしバグの温床になりかねない。

SwiftではSwift2からインクリメントとデクリメントがなくなったし、正直forループの処理以外で使うこともほとんどないだろう。可能な限り使わないに越したことはない。

C++の場合演算子オーバーロードでは前置か後置かの判定で描き方も面倒になっている。ついでに言うと += などの書き方も正直あまりいい物ではないと思う。

省略することが良いとは限らないし、現在なら基本はすべて記述すべきだと思う。

コンストラクタの引数がいいのか、setterを使った方がいいのか

クラス変数やメンバ変数を置くとき、当然その変数に何らかの値を入れるものである。

で、その変数になにか値を入れるタイミングはコンストラクタで入れるべきなのか、それとも呼び出しもとでsetterを呼び出して入れるべきなのか、どちらがいいのかわからなくなった。わからないなりになにか文章に直そうと思ってこの記事を記す。

わからないので、もしこの記事で「バカジャネーノ」とか思った方がいれば教えてほしい。教えて。

コンストラクタで渡す場合

要は

Object obj = new Object(hoge);

みたいなケースである。

これが強制されるケースはJavaであればfinal修飾子を、C#であればreadonly修飾子を付けた変数はコンストラクタで値を決めるか、変数宣言時に値を決定する必要がある。ちなみにC#のconstは宣言時に値を決定しなければならない。

逆に言えばコンストラクタで値を決定できるともいえる。

メリット

  • setterを作らないことでクラス変数やメンバ変数のアクセス制御ができる
  • 呼び出し側に記述を作らない(ケース次第)

setterで渡す場合

コードに直すと

Object obj = new Object();
obj.setHoge(hoge);

というケース。

これコンストラクタが強制されるケース以外特に制限はない。

メリット

  • setterで制限や値の調整ができる
  • 呼び出し側に記述を作る(ケース次第)
  • 外部からの値の操作ができる(あんまりよくない)

比較

各々のメリットの欄に書いたが、呼び出し側に記述を作ることが場合によっては良かったり悪かったりする。

例えばゲームプログラミングで敵キャラを生成するときにHPなどの各ステータスをいちいちsetterで渡すのは正しいとは言えない(もっともこの辺は敵の種類を渡したら一発で処理できるようにすべきだし、そうであってもコンストラクタに渡せばいけるはずである)

Enemy enemy = new Enemy(enemyType);
// もっと正しくするにはcreateEnemy(enemyType)とかFactoryパターンやBuilderパターンを使うべきだと思う

こういったケースならコンストラクタに渡すべきだろう。

また、逆に言えば呼び出し側が呼び出すべき内容であればsetterでおくべきだということである。

外部からの操作に関していえば、setterであっても制限もできる。

void setHoge(Object hoge){
    if(hoge!=null){
        this.hoge = hoge;
    }
}

とすれば、nullでない場合だけ入るという形にできる。要は値が設定されていない場合だけ入るという形で、あとから外部で値を変更するというのは通じないことになる(どこかでnullになるような処理でない限り)。

総括

もっと言えば、極論はコードの書き方次第だしある程度の思想が統一された状態で書けばどちらでもいいのだと思う。

思うのだが、いかんせんなんかいい感じの資料とかもないのでいまいちどうやればいいのかわからないという感じはする。

Java Servlet ではPOSTでステータスを受け取れるContentTypeはapplication/x-www-form-urlencodedだけ?

ServletRequestインターフェースのgetParameter()メソッドでパラメータを取り出すとき、GETはできるのにPOSTにした瞬間うまくいかないことがあった。

理由を調べたところ、getParameter()あるいはそれに類する処理をPOSTメソッドのRequestからの場合、content-type=application/x-www-form-unlencoded でなければ読み込めないらしい。

ちなみにgetInputStream()とかを用いて生のデータを読めば別にこのメソッドを使わなくても読めるらしい。つまりBody部に入っているが、パース出来ないか読めないか読まないかそんなところのようだ。 ただしgetInputStream()とかとgetParameter()を併用するとデータがおかしくなることもあるとか。

もともと(というか本来?)POSTはフォームの入力のデータを扱う場合に使うことが多いからその影響かもしれない。もしServletでWebAPIみたいなことしたい、POSTでデータ送信したいってケースで引っかかることがあるかもしれない。

application/json や text/plain なども通らないってのはちょっとつらい。JSONはまあはやってる割に素で対応できる環境が少ないからまあいいけど、うーん。

まあapplication/x-www-form-urlencodedに偽装して送り付けるのが一番楽かな


参考資料

HttpServletRequest (Servlet 2.4 API 仕様)

厳密にはServletRequestインターフェースに実装されてるのでこっちだけど

ServletRequest (Servlet 2.4 API 仕様)

これのgetParameter()に書いてる

ファイル名が違ってgitの状態がおかしくなる

原因もなぜそうなったのかもよくわからない問題だけど一応記す。

git + github で readme ファイルを修正してコミットしてプッシュしてマージした。したが、どうもおかしく変更が適用されていないっぽい。

コミット忘れかと思ったがコミットしているし、readme ファイルそのものは変更されていてリポジトリのトップに表示される readme だけ変更されていなかった。

あららおかしいぞと思いつつ VSCode の git を見るとコミット対象に README がある。

> git status をしてみたところコミットされていないしインデックスに登録もされていないとのこと。

おかしいぞと思いつつ > git diff をしたところ修正前のものと修正後のものが出た。ただ、修正自体はコミットしたはずである。

実際、>git add -A>git commit -am "commit" とかやってもコミット対象はないよ、加えるファイルもないよと出る。

VSCodeの機能でコミットしようとすると今度はエラーが発生したと出た。

よく見たところ追加対象の “README” と実際にファイルとして存在する “readme” は違うファイルのようで、どういうわけか二つの名前のファイルが1つの存在として扱われているため起きていたみたいだった。詳しいことはわからん。でもファイル名が違っていた。

とりあえず対処法としては

> git reset --hard HEAD

で戻して

> git status

で確認。この時点で変更はないと出る。たぶん修正前に戻っている。

そしたら

> git mv README readme

で名前変更。あとは

> git add -A
> git commit -am "commit"

でコミットして

> git status

あたりで確認して、問題なければプッシュ。とりあえずこれでどうにかなった。

原因はわからないが、ファイル名に気を付けるといいと思われる。

ゼルダの伝説Breath of the Wildでガノンを討伐した

このゲームがいかに素晴らしいかはたぶんもう聞き飽きたのではないかというぐらい聞いたと思う。

いろんな要素について各ゲームサイトや個人サイト、それこそTwitterなどでも言われるように、このゲームはとても素晴らしいゲームであった。

具体的に言うと、このゲームには明確な欠点があるのに点数を出すときには満点になってしまうというぐらいすごいゲームなのである。

実際、

  • 割と多いフレームレートの低下、いわゆる処理落ちやプチフリーズ
  • 基本的に道順が存在しないためかなりの強敵からザコ敵までがごった煮になっておりややおおざっぱなバランス調整
  • 複雑なユーザーインターフェース
  • 動物要素の希薄さ。犬とか馬を撫でられないとか、猫がいないとか

という欠点はある。あるにはあるが、そんなものがあったところでこのゲームは神ゲーなのだ。本当に神ゲーなのだ。

  • このゲームは素晴らしく、間違いなくゲーム史に残る傑作である

という美点一つでひっくり返る。


いろいろやったが、現在のクリア率は30%を切っている。

ぶっちゃけオープンワールドゲームでこういった要素を100%にするのはあきらめているので、50%ぐらいを目指そうかなぁとも思っているがやや葛藤中でもある。

いっそ試練の祠・祝福の祠のコンプリートでもいいかもしれない。

ちなみにタイトルが「ガノンを討伐した」になっているのはそのため。とてもじゃないが、クリアしたとは言いにくい。おそらくまだ踏んでいないハイラルの大地があり、見ていない敵や生物や植物があり、知らないものだらけだろう。

このゲームをクリアしたというには、まだまだ遊び足りない。

とりあえず、祠探しの旅を続けながらたまに大妖精の泉を復活させたり敵を屠り去ったり料理を作ったりしながらハイラルの大地を駆け回ることにする。

しかしそうやってるとゼルダ姫の語り掛けに申し訳なさを感じる。感じざるを得ない。

リンク……まだですか……?って感じがする。ごめん、もうちょっと遊ばせて


どうでもいい(よくはない)けど、今作は追加ダウンロードコンテンツもあるんだよね。

なんでも追加コンテンツは裏ゼルダ(ダンジョンだけ新しくする+ダメージ増加)と、追加シナリオらしい。

ダンジョンは1つ当たりが小さめで、既存のゲームでいうとゲームエンジン的な特性を使ってる当たりPortalに近いし、ネタ自体は問題なさそう。

追加シナリオは……どうなるんだろうか。

オープンワールドゲームの問題の一つにシナリオをなぞる都合上どうしても広い世界を一本道にたどるというケースが多い。今作のゼルダはその辺もうまく解決していたけど、追加シナリオがどうなるか。

そういえばシナリオで思い出したけど今作ってゼルダシリーズの時系列のどこにあたるんだろうね。

風タクの世界に近そうだけど、あの世界一度海に沈んでるし、沈む前なら沈む前でガノンの様子とかがかみ合わない気もする。

まあそこまで重要な要素ではないか。


とにかく、いいゲームだった。

次回作もDLCも楽しみだし、20年後、再びこれを越えていくようなゲームが生まれるかもしれないと思うとこの先も楽しみになる。

素晴らしいゲームは、その作品だけでなくゲームそのものの未来も照らすいい例だったと思う。

Cloud SQL のタイムゾーンを変える

SQLの current_time とか now() はサーバ側のタイムゾーン設定に依存してるのでCloud SQLなどではタイムゾーンが日本と違ったりすることがある。

Cloud SQLも例にもれずデフォルトだとこういう設定になっている

mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.34 sec)

UTC世界標準時なので日本時間から9時間遅れである。

中身はMySQLだが設定ファイルを直接いじったりは出来ないのでCloud SQLの管理画面からフラグを操作すればいい。

詳しくはここ。これが公式ドキュメント。

Configuring Cloud SQL Flags  |  Cloud SQL for MySQL  |  Google Cloud Platform

タイムゾーンdefault_time_zoneを +09:00 にしてあげればJSTになる。

ちなみに変更するとこんな感じになる。

mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | +09:00 |
+------------------+--------+
2 rows in set (0.15 sec)

ほかにも設定関係はここで変更してあげればいいっぽい