isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

スターウォーズの感想。

ネタばれあります。

 

スターウォーズ EP8見終わったので感想を書く。

 

テーマについて

ストーリーはいろんな大人の事情の上に成り立っているものだと思うが、あえてそこには触れない。続編を作らないといけないとか誰々が活躍するとスポンサーが喜ぶとかそういうのはいったん置いておく。

今回のテーマは主に二つあると思う。

一つは「ヒーローにならなくていい」である。

ポー・ダロメンは最初の戦闘で降格させられた。

フィンとローズはトラッカーの停止に失敗した。

あのアル中野郎はヒーローになるよりお金を選んだ。

さらにフィンはキャノンの破壊に失敗した。

唯一英雄的な活躍をしたのは代理提督さんだけだった。

終始徹底して「戦う、倒す」よりも「逃げる、守る」が高く位置付けられた話であった。

もう一つのテーマは「血筋じゃない」だ。

スターウォーズ4~6は「血筋がすげえ奴が無双する」という話になりがちだった。

しかし、今回はそういうのがなかった。

レイがジェダイの島の鏡に親を見せてもらうシーンはとても緊張した。

あらゆる選択肢があったと思う。

オビワンの孫、アナキンに弟か妹がいた、パルパティーンの孫、クワイガンの孫、ウィンドゥの孫等々。

しかし、結局つまらない話だった。

もちろん自作以降で本当の親が明かされる可能性がある。

しかし、僕はこのままでいいと思う。

ローズもフィンも含めてただの名もなき労働階級生まれの子が自分の責任を果たすという話でいいと思う。

 

カメラワークについて

カメラワークが素晴らしかった。

僕は映画はほとんど見ないのでその手の芸術的要素についてはわからないが仕事でUI・UXを考えることが多いので「どう見せたら説明しづらいことが伝わるか」について悩むことが多い。

冒頭のレイア姫とカイロ・レンの目が合ってることがわかる構図やその後レイとカイロ・レンの目が合ってることがわかる構図、あるいはカイロ・レンとレイが一緒にスノークの護衛と戦ってるシーンでカイロ・レンには背後が見えてることがわかる構図などいろいろあった。

特に好きなのは、鏡の中で自分の分身と一緒に指を鳴らすシーンだ。

その中の、野球観戦中のウェーブのように後方の自分に合わせて指を鳴らすという行為。

ジェダイはめちゃくちゃ殺陣が強いが、その理由が「相手の動きが見えてるから」だそうだ。

これはなかなかジェダイにしかわからない感覚だと思う。

しかし、この動画ですごく納得がいった。

要するに戦ってる最中のジェダイには自分がどう動けばいいかがあんな感じで見えてるんだと思う。

後ろからくる自分に合わせてタイミングよく太鼓の達人感覚でライトセーバーを振るといい感じに敵が倒せるってことだろう。

この説明力はすごいと思った。

 

課題

僕がまだよく理解できてないこと。

まず、ストーリー上の不思議として

ジェダイって結局なんだ

・あの島はどこだ

・あの本には何が書いてあるんだ

・レイは何者か

などがあげらえる。

それとは別に、いまいちメッセージがつかみきれないことがある。

一つはヨーダはEP8において、あるいはスターウォーズ全体において何の役割を果たしんだ、ということ。

これは2ちゃんのコピペなどにもあるが、ヨーダは実際結構ポンコツである。

「突き詰めればジェダイが崩壊したのは全部ヨーダが悪くね?」と言ってしまえるほど役立たずである。

しかし、重要なキャラクターとしていまだに登場人物に道を示唆する役割を担っている、ように見える。でも話の中身がなさ過ぎていてもいなくても変わらない気がする。

きっと僕が見落としているだけで、本当は何か大事な仕事をしてたんだろう。

二つ目はルークの死に方だ。

僕を含めて全員がカイロがルークを切る瞬間、ローブを残して消えることを期待したと思う。

しかし、実際はもっと地味な死に方だった。

なぜルークはそれを選んだのか。

もちろん、ライトセーバーをレイが持って行っちゃったとかフェリーの定期便が3日に1回だとかいろいろ理由はあったと思うが、よくわからない。

結局フィンがルークを、カイロがベイダーを、ポーがソロを越えられないのと同じように、ルークもオビ=ワンを超えれないということなのか。

最後にカイロ弱すぎないだろうか。

もちろん十分強いんだが、スノークの護衛に囲まれたシーンでも、「あ~アナキンならこのくらいの人数10秒で蹴散らしてたな~」って思う。

前作からもカイロ弱い説はさんざん指摘されていたと思うが、今作で確信した。彼が弱いという事は何かの伏線である。

意図である。

自作では強いカイロを見たい。

 

あと、ルークの島に湾にルークの戦闘機が沈んでるのを見たとき、ルークがレイに戦闘機をフォースで持ち上げろと命じるシーンを期待したが、なかったね。

 

常に最新のcreate table文を用意しておこう

create table文は現在のTableの状況を説明する最も大切なドキュメントだ。テーブルのスキーマがわかるだけではない。foreign key文からリレーション関係が読み取れる。index文からパフォーマンスの要所がわかる。

実際のシステムのDBはalter table文でどんどん更新されていくものだが、その都度create table文をバージョンアップしてほしい。

デプロイを自動化しよう

デプロイのミスは多い。

例えば

などなど。

デプロイは必ず自動化しよう。デプロイのたびにFFFTPTeraTermを立ち上げていてはいけない。間違えるからだ。

また、準本番や開発環境へのデプロイが手間だと動作確認を怠りバグの混入したソースをあげる人が出てくる。

簡単なことならGulpでも十分対応可能だ。

頑張ろう。

インフラの設定を自動化しよう

今日はインフラの設定を自動化しようという話だ。

インフラの設定とは例えば

などだ。

インフラの設定で大事な価値観は「再現できる」「再現の手順が修正できる」だ。

まず再現できるとは、今現在動いている環境と同じ環境を別のサーバーやローカルパソコンに再現できるということだ。もし、本番サーバーに何度もログインして設定を変更しており、その履歴を残していなければ同じ環境の再現ができなくなる。すると、異常が発生したときに原因の特定が遅くなる。また、新規機能のための開発環境がすぐに用意できなければ開発が滞る。だから、再現の手順がきちんと残っておかなければいけない。

次に、再現の手順が修正できる必要がある。各OSや仮想化ツールにはスナップショットを取る機能が存在するのでそれを使えば再現自体は簡単だ。しかし、それだけではだめだ。設定を追加することはできるが、既存の設定内容を変更することができない。例えば、PHP5をインストールしていたサーバーをPHP7にアップデートするとしよう。もちろんUpdate自体は可能だしPHP5をアンインストールすることもできる。しかし、どうせならOSをクリーンインストールしてPHP7をインストールしたいだろう。

まず、手始めにできることとして再現手順書を作ることだ。しかし、これでは手作業でサーバーを設定しなければいけないし、日本語力が求められる。

もっといい方法がある。DockerやVagrantやChef(僕は使ったことがない)やAnsibleを使うといい。どれも本質的な機能は異なるがサーバーを自動で設定してくれる点とその設定内容を簡単なテキストファイルで表現できる点が一緒だ。その設定用のテキストファイルだけをgitで管理すれば上記の要件を満たしたインフラ自動化が達成できる。

 

switch case文の後に処理を続けないで

switch case文の中で変数に代入すると、非常に読みづらいコードになる。そのSwitch文の目的がわかりづらいしこのSwitch文がどの変数を書き換える可能性があるかを把握しづらいからだ。しかし、結局Switch文は何か値を返すために書いてある場合がほとんどである。であればその値を返す関数を作って欲しい。下のコードを見て欲しい。これは英語で入って来た曜日を日本語でPrintするコードだ。

void printJAString(String week) {
String ja;

switch (week) {
case "monday":
ja = "月";
break;
case "tuesday":
ja = "火";
break;
case "wednesday":
ja = "水";
break;
case "thursday":
ja = "木";
break;
case "friday":
ja = "金";
break;
case "saturday":
ja = "土";
break;
case "sunday":
ja = "日";
break;
}

print(ja);
}

 

 なんどもjaというコードに代入している。もしコードがもっと複雑になり、ja以外のいろんな変数に代入していたらどうなるだろうか。それよりはこのように最終的に一つの値を返す関数にして欲しい。

void printJAString(String week) {
String ja = toJAString(week);

print(ja);
}

String toJAString(String week) {
switch (week) {
case "monday":
return "月";

case "tuesday":
return "火";

case "wednesday":
return "水";

case "thursday":
return "木";

case "friday":
return "金";

case "saturday":
return "土";

case "sunday":
return "日";

}
}

 

 このようにすることで「どうせtoJAString関数は値を一個返すだけだ。ということは読み飛ばしてもいい」と判断できる。

ぜひ、Switch文で変数を代入するのではなく、Switch文を包んだ関数で値を返そう。

 

補足

本題と関係ないので省略したが、switch文のdefaultは必ず書こう。

現在時刻を外から入れよう

時刻によって動作が変わるプログラムを書いた時、どうやってテストしているだろうか。手作業で書き換えているかもしれない。

String getAisatu() {
// var date = new DateTime.now();
var date = new DateTime(2017, 10, 28, 18, 0);
if (date.hour > 17) {
return "こんばんは";
}
return "こんにちは";
}

 

 もっといい方法を紹介しよう。プログラム言語のinterface機能を使ったことがあるだろうか。あったら申し訳ない。このブログは初心者を対象にしている。interfaceプログラミングの詳細な説明は省く。とりあえずおなじinterfaceを実装していれば中身をすげ替えてもいいというルールがある。これを使おう。CurrentTImeService interfaceと現在時刻を返すクラスとダミー日付を返すクラスを作ろう。Dartでinterfaceはabstruct classという宣言をすることになっているが、適宜読み替えて欲しい。

// 共通のinterface
abstract class CurrentTimeService {
DateTime getDateTime();
}

// 本当に現在時刻を返す
class ProdCurrentTimeService implements CurrentTimeService {

@override
DateTime getDateTime() {
return new DateTime.now();
}
}

// ダミー日付を返す
class DummyCurrentTimeService implements CurrentTimeService {
DateTime dateTime;

DummyCurrentTimeService(DateTime dateTime){
this.dateTime = dateTime;
}

@override
DateTime getDateTime() {
return dateTime;
}
}

そして、現在時刻を取得する関数は引数にCurrentTimeServiceを取り、それに現在時刻を問い合わせるようにしよう。

String getAisatu(CurrentTimeService currentTimeService) {
var date = currentTimeService.getDateTime();
if (date.hour > 17) {
return "こんばんは";
}
return "こんにちは";
}

そして、呼び出し側でProdとDummyを差し替えられるようにしておこう。環境変数なので自動で切り替えられるようにしておくといい。

void main() {
var currentTimeService = new DummyCurrentTimeService(new DateTime(2017, 10, 28, 18, 0));
// var currentTimeService = new ProdCurrentTimeService();

print(getAisatu(currentTimeService));

}

 

 これで時刻を色々指定して動作確認ができるようになった。この手法はHTTPリクエストや乱数の生成など、挙動が予想できないメソッドのテストに有効だ。

書き込み系のREST APIは、URLとBodyに無いデータを書き込まないようにしよう

REST APIは、要求を受理するか判定する部分と、要求に従ってデータを返信したりDBに書き込んだりする部分に分かれる。前者の受理判定部分はあらゆる情報を使っていい。しかし、後者の処理部分はリクエストに明記されたもの以外を使ってはいけない。使っていいのはURLとBodyだけだ。

例えば/myprofileというAPIがあったとしよう。このサーバーはCookieを使ってアクセスした人を特定できる。そこで /myprofile ではCookieの情報をもとにその人のプロフィール情報を返してあげていた。これはよくない設計だ。このように直してほしい。まずURLは/profiles/:user_id (:user_idは変数)に変更しよう。そして、メソッドの冒頭にCookieと:user_idを比較して一致しなければエラーを出してほしい。

void onMyProfile(HttpRequest req) {
// これで /users/:user_id の:user_idの部分が取れる
var userID = req.uri.pathSegments[1];
if (!isUserIDMatch(req.cookies, userID)) {
req.response.statusCode = HttpStatus.BAD_REQUEST;
return;
}

req.response
..statusCode = HttpStatus.OK
..write(getProfile(userID));
}

 前者と後者の違いは、前者はサーバーに忖度を求めているのに対し後者はサーバーにしてほしいことを明確に指定している。後者の考え方でシステム全体を統一することで設計の不明瞭さがなくなる。どのAPIも挙動が予想しやすく、変更をかけやすく、機能追加したい人がどうしたらいいかわかりやすい。もう一つの違いは、前者は処理が始まってから複数の成功パターンに分岐していくのに対して、後者は一種類の成功パターン以外はすべてエラーになるということだ。後者のほうがコードと仕様がきれいになる。

他に例を挙げると、掲示板の書き込みAPIはPostのBodyにMessageとUserIDを取ろう。Messageだけを受け取りUserIDをCookieから割り出してはいけない。

いい例
{
"message": "こんにちは",
"user_id": "user1"
}
悪い例
{
"message": "こんにちは"
}

 また、1年以上前から会員登録してくれていた人向けに特別サービスをするときのAPIについても考えよう。二種類考えられる。

案1

  • サーバー側で会員登録履歴を確認する
  • 1年以上前からの会員にサービス価格を適用する
  • それ以外の会員に通常価格を適用する

案2

  • 特別サービス対象者か確認できるGet APIを用意する
  • 購入APIではRequest Bodyで自分が対象者かどうか明記させる
  • 非対象者が対象者として購入APIをコールしたらエラーにする
  • 対象者が非対称者として購入APIをコールしたらエラーにする
  • それ以外は申告通りに適用する

この場合も、面倒だが案2を選ぼう。