値を返してから代入する
ちょっと前にTwitterで人が死ぬバグといって流れてきたツイートがそれなりに話題になっていたと思う。
人が死ぬバグってあるんだな...今まで平和なプログラミングしかしたことないや。https://t.co/8EtfrW0pvp pic.twitter.com/zF9ZNLfUtu
— Daiius (@DaiiusJmst) 2017年7月17日
これね。出典元は 非推奨だった 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++の場合演算子オーバーロードでは前置か後置かの判定で描き方も面倒になっている。ついでに言うと += などの書き方も正直あまりいい物ではないと思う。
省略することが良いとは限らないし、現在なら基本はすべて記述すべきだと思う。
追記(2017年11月24日)
文字列を合体させて要素の間にコンマを入れたいケースとかは String.join() とかつかうといいよ(なおJavaは8以降にしかないらしい)
こういう多数の人間が必要になりそうな機能はだいたい供給されている(たまにされてない)のでAPIをどんどん調べていこうと思った
コンストラクタの引数がいいのか、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でガノンを討伐した
- 出版社/メーカー: 任天堂
- 発売日: 2017/03/03
- メディア: Video Game
- この商品を含むブログ (13件) を見る
このゲームがいかに素晴らしいかはたぶんもう聞き飽きたのではないかというぐらい聞いたと思う。
いろんな要素について各ゲームサイトや個人サイト、それこそ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)
中身は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)
ほかにも設定関係はここで変更してあげればいいっぽい
MavenでCheckstyle
開発環境にEclipseではなくVisual Studio Codeを用いていて、ビルドツールはコマンドラインでMavenのコマンドをたたいている。
で、Pythonを書いているとpep8なるものがありコーディング規約をチェックしてくれたり、Goだと言語仕様レベルでコーディング規約を定めてくれていることもあり、Javaでもそろそろコーディング規約のチェックツールを導入するかなーと思った。
Javaでのコーディング規約チェックツールにはCheckstyleというツールがある。これを使えばいいことまではすぐにわかったのであるがMavenを使っていることだしMavenで実行させたい。
Maven用のCheckstyle Pluginはちゃんとある。
Apache Maven Checkstyle Plugin – Introduction
しかし日本語資料はこれでもかというぐらい見つからなかった。厳密にはあったのだが、Maven2での資料でMaven3ではなかった。
なのでここに軽く示す。
Apache Maven Checkstyle Plugin – Usage
示すって言った直後にあれだけどここのページの下部にあるExampleを大体コピーで構わない。バージョンには気を付けるのと、設定関係の値(configuration下)は考えた方がいい。
configLocation値とかは書かなくても動くし(Checkstyle自体がデフォルトでSunの規約となっているのでおそらくそれで動いてくれる)
で、走らせた結果日本語が文字化けしてめちゃくちゃエラーを吐いている。つらい。5471個もエラーが出た。つらい
でも日本語使ってるからエラーというわけではなく、出力がUTF-8だからこうなってっぽい。
一応CHCPコマンドで文字コードを変えることはできるが、それでも一部文字が上手く表示されないようだった。
出力をShiftJISにするか、あるいはエラーメッセージを英語で出力するかの解決法しかないと思われる。一時ファイルに出力させてそれをエディタで見るのも一つではあるが、それもそれで面倒。
一番楽なのは出力を英語にするやつだと思う。でもどうすればいいかわからない……