うぃろぅ.log

140字で綴りきれない日々の徒然備忘録

【Rails 6.0】 rails 6.0で`rails s`したら`config/webpacker.yml`がないエラーで怒られた

うぃろぅです。

igarashikuniaki.net

Railsの教科書の復習のためにrails newしたらrails s実行時にエラーが出たのでメモ。

確認環境

macで実行。

Rails 6.0がリリースされてた

weblog.rubyonrails.org

知らなかった…。

rails newして生成されたGemfileが以下のようになっていた。

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "2.6.3"

# Bundle edge Rails instead: gem "rails", github: "rails/rails"
gem "rails", "~> 6.0.0"
# Use sqlite3 as the database for Active Record
gem "sqlite3", "~> 1.4"
# Use Puma as the app server
gem "puma", "~> 3.11"
# Use SCSS for stylesheets
gem "sass-rails", "~> 5"
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem "webpacker", "~> 4.0"
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem "turbolinks", "~> 5"
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem "jbuilder", "~> 2.7"
# Use Redis adapter to run Action Cable in production
# gem "redis", "~> 4.0"
# Use Active Model has_secure_password
# gem "bcrypt", "~> 3.1.7"

# Use Active Storage variant
# gem "image_processing", "~> 1.2"

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", ">= 1.4.2", require: false

group :development, :test do
  # Call "byebug" anywhere in the code to stop execution and get a debugger console
  gem "byebug", platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  # Access an interactive console on exception pages or by calling "console" anywhere in the code.
  gem "web-console", ">= 3.3.0"
  gem "listen", ">= 3.0.5", "< 3.2"
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem "spring"
  gem "spring-watcher-listen", "~> 2.0.0"
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem "capybara", ">= 2.15"
  gem "selenium-webdriver"
  # Easy installation and use of web drivers to run system tests with browsers
  gem "webdrivers"
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]

最初に気づいたのはgem "chrome-driver"gem "webdrivers"になったので警告されることがなくなったこと。

rails sでエラー

で、rails sしてみたらエラー。

以下のように出力されている。

1: from /Users/vviilloovv/Git/rails/books-app/vendor/bundle/ruby/2.6.0/gems/webpacker-4.0.7/lib/webpacker/configuration.rb:87:in `load'
/Users/vviilloovv/Git/rails/books-app/vendor/bundle/ruby/2.6.0/gems/webpacker-4.0.7/lib/webpacker/configuration.rb:91:in `rescue in load': Webpacker configuration file not found /Users/vviilloovv/Git/rails/books-app/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /Users/vviilloovv/Git/rails/books-app/config/webpacker.yml (RuntimeError)

RuntimeErrorで落ちている。config/webpacker.ymlがないらしい。

検索したら以下の記事がヒット。

qiita.com

rails webpacker:installを実行すればいいとのこと。
実行してみた1

r webpacker:install
Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/

yarnがないって怒られる。
Homebrewでyarnをインストール。

❯ brew install yarn
(中略)
==> Summary
🍺  /usr/local/Cellar/node/12.8.1: 4,629 files, 53.1MB
==> Installing yarn
==> Downloading https://yarnpkg.com/downloads/1.17.3/yarn-v1.17.3.tar.gz
==> Downloading from https://github-production-release-asset-2e65be.s3.amazonaws.com/49970642/114f8b80-a477-11e9
######################################################################## 100.0%

OK。
もう一回rails webpacker:installする。

r webpacker:install
RAILS_ENV=development environment is not defined in config/webpacker.yml, falling back to production environment
      create  config/webpacker.yml
(中略)
✨  Done in 10.69s.
Webpacker successfully installed 🎉 🍰

できたっぽい。
改めてrails sを実行してみる。

r s
=> Booting Puma
=> Rails 6.0.0 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

OK。

http://localhost:3000/ を開いてみる。

f:id:vviilloovv:20190817182235p:plain

やりました。

yarnがなかったことがそもそもの原因なような気がする。

ではまた。


  1. rbundle exec railsとしている。

【備忘録】 「メタプログラミングRuby」を読む

うぃろぅです。

少し間が空いてしまいましたが今日も本を読んでいきます。
暑さに負けるな。

今日の本

メタプログラミングRuby

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

Rubyの基本を理解したら読むといいらしい。
具体的には「ループ処理をeachで書けるなら読める」と書いてある。ほんとぉ??

構成

Rubyの各機能についてのことが1章ずつ、その後Railsについて記載されている。

1章 頭文字M

多分「イニシャルエム」と読むと思う。夏は豆腐が美味しい。

メタプログラミングとは何かについてのイントロ。

「コードを書くコード」をプログラミングするのがメタプログラミングとしている。

「実行時に定義されるメソッド」とか「実行時に定義されるクラス」とかそういったものをこれから学習していくよ、というような内容となっている。

2章 オブジェクトモデル

オブジェクトモデルの内部

Rubyではクラスもメソッドもオブジェクトである。
また、すでに定義されているクラスにメソッドや変数を追加することもできる。自由度の鬼。

ということは、自分しか使わないと思って定義したクラス名が他のgemのクラス名と被るということが起き得る。
そのため、名前の衝突を回避するために

module MyModule
  class MyClass
    (何か処理)
  end
end

こんな感じでクラスをネームスペースにラップすると良い。

メソッド探索

メソッド探索は「右へ一歩、それから上へ」となっている。
インスタンスから自分のクラスに一歩動き、それから繼承ツリーを上へ上へと登っていくイメージ。

[1] pry(main)> module M1; end
=> nil
[2] pry(main)> module M2
[2] pry(main)*   include M1
[2] pry(main)* end
=> M2
[3] pry(main)> module M3
[3] pry(main)*   prepend M1
[3] pry(main)*   include M2
[3] pry(main)* end
=> M3
[4] pry(main)> M3.ancestors
=> [M1, M3, M2]

また、同じモジュールを2回以上挿入しても読み込むのは最初の1回だけ。かしこめ。

3章 メソッド

動的メソッド

Module#define_methodを使えば動的にメソッドが生成できる。

[1] pry(main)> class MyClass
[1] pry(main)*   define_method :my_method do |my_args|
[1] pry(main)*     my_args * 3
[1] pry(main)*   end
[1] pry(main)* end
=> :my_method
[2] pry(main)> obj = MyClass.new
=> #<MyClass:0x00007fe2feed0ba0>
[3] pry(main)> obj.my_method(2)
=> 6

上記の例だとmy_methodがメソッド名、my_argsが引数。
my_methodの箇所を変数に変えればメソッド名は自由自在。
メソッド呼び出しもsendを使えば文字列で呼べる。ちょっと自由度高すぎんよー。

ゴーストメソッド

さらに「この名前で定義してね」とお願いするだけではなく「知らないメソッド名がきたらこの処理をしてね」ということもできる。

method_missingをオーバーライドすることでメソッド名が存在しない場合の処理を記載できる。

これを使う場合、respond_to_missing?メソッドもオーバーライドしてゴーストメソッドがrespond_to?に正しい値を返すようにするのがルール。ただ暗黙のルールとなっているため動的メソッドで済む場合はそちらを使った方がバグが入ってきづらい。

4章 ブロック

ブロックはクロージャ

スコープは

  • クラス定義
  • モジュール定義
  • メソッド

という単位で切り替わる。

class Hogedef fugaという記述によってスコープが切り替わるため、ここをClass.newdefine_methodと書くことでスコープが切り替わらなくなる。
キーワードによってスコープが切り替わると覚えておくとわかりやすい。

スコープが切り替わるキーワードをスコープゲートと呼び、スコープゲートを越えたいときにブロックが使われる。
なのでブロックはクロージャである、と。

呼び出し可能オブジェクト

ブロックはオブジェクトではない。
ブロックをオブジェクトにしたものはProcである。

Proc.newにブロックを渡してProcオブジェクトを生成し、Proc#callを呼び出すことでブロックを評価する。

他には

  • lambda
  • proc
  • ->(矢印ラムダ)
    p ->(x) { x + 1 }のような書き方になる

がある。

この辺りのことに関しては以下の記事が非常に詳しい。

qiita.com

とても助かる。

呼び出し可能オブジェクトとは、「評価ができて、スコープを持ち運べるコード」のことである。
呼び出し可能オブジェクトになれるのは以下。

  • ブロック
  • Proc
  • lambda
  • メソッド

DSLについては付録で詳しく学習することとする。

5章 クラス定義

オブジェクト指向といえばクラス、クラスといえばオブジェクト指向というくらい切っては切れないクラス定義について。

Rubyの場合、クラス定義にはあらゆるコードを記述できる。

class Hoge
  puts "こんにちは"
end

これでも動く。

動的にクラスを宣言する

class Hogeのようにclassキーワードを使わないでクラスを宣言してみる。

[1] pry(main)> c = Class.new(Array) do
[1] pry(main)*   def my_method
[1] pry(main)*     "Hello!"
[1] pry(main)*   end
[1] pry(main)* end
=> #<Class:0x00007faea61c9e20>

Class.newでOK。引数は継承元。
ただしこれだと無名クラスであるため、他の変数で使い回せない。
ではどうすればいいかというと

[2] pry(main)> Hello = c
=> Hello

これでmy_methodを持ったHelloクラスということになる。便利。

[5] pry(main)> k = Hello.new
=> []
[6] pry(main)> k.my_method
=> "Hello!"

これでインスタンスも生成できると。

特異メソッド

特定のインスタンスにのみメソッドを追加することもできる。これが特異メソッド。

こう書くと何か特別なメソッドなのかと考えるかもしれないが、何の事は無い。
クラスメソッドはクラスの特異メソッドであるから使ったことはある。

def obj.a_singleton_method; end
def MyClass.another_class_method; end

見た目からして同じに見える。
これを利用したものがアクセサ。

class Hoge
  attr_accessor :my_attribute
end

少し違うけれどざっくりプロパティみたいな。gettersetterの代わりと考えると覚えやすい。
このようなメソッドをクラスマクロと呼ぶらしい。

特異クラス

メソッドときたらクラス。
特異メソッドを宣言したとして、そのメソッドはどこに存在するのか。それがこの特異クラスとなる。

1] pry(main)> obj = Object.new
=> #<Object:0x00007f8c3c6d1648>
[2] pry(main)> singleton_class = class << obj
[2] pry(main)*   self
[2] pry(main)* end
=> #<Class:#<Object:0x00007f8c3c6d1648>>
[3] pry(main)> singleton_class.class
=> Class
[4] pry(main)> "abc".singleton_class
=> #<Class:#<String:0x00007f8c3c899200>>

class <<Object#singleton_classで確認することができる。 インスタンスを一つしか持てないため、シングルトンクラスとも呼ばれる。
特異クラスはメソッド探索時に自分自身のクラスより先に探索される。オーバーライドした場合はそちらが先。

オブジェクトモデルの7つのルール

  • オブジェクトは1種類のみ: 通常のオブジェクトかモジュールになる
  • モジュールは1種類のみ: 通常のモジュールかクラス、特異クラスのいずれかになる
  • メソッドは1種類のみ: モジュールに住んでいる
  • 全てのオブジェクトは「本物のクラス」を持つ: 通常クラスか特異クラスのどちらか
  • (BasicObject以外の)全てのクラスは1つの祖先を持つ: スーパークラスかモジュール
  • オブジェクトの特異クラスのスーパークラスはオブジェクトのクラス、クラスの特異クラスのスーパークラスはクラスのスーパークラスの特異クラス
  • メソッド探索はまず「本物のクラス」に向かって「右に」進み、その後継承チェーンを辿って「上に」進む

これをベースに考えれば大抵のことはうまくいく。特異クラスのところはややこしいからあとでまた読む。

クラスメソッド構文3つ

# その1
def MyClass.a_class_method; end

# その2
class MyClass
  def self.another_class_method; end
end

# その3
class MyClass
  class << self
    def yet_another_class_method; end
  end
end

どれも見たことがあったがその3の書き方にようやく合点がいった。なるほどこういう仕組みだったのね。

クラスメソッドを追加することをクラス拡張、オブジェクトに特異メソッドを追加することをオブジェクト拡張と呼ぶ。
先程までのように特異クラスをオープンしてメソッドを追加することももちろんできるが、使う機会が多いためObject#extendという拡張用のメソッドが用意されている。そういうことだったのか…。

クラス定義に関しては何回か読まないと難しそう。

6章 コードを記述するコード

コードは単なるテキストである。テキストであれば出力できる。だからコードを出力するコードが書けるよね、という論理。

Kernel#evalを使うと、コードの文字列を評価できる。

[1] pry(main)> arr = [10, 20]
=> [10, 20]
[2] pry(main)> element = 30
=> 30
[3] pry(main)> eval("arr << element")
=> [10, 20, 30]
[4] pry(main)> arr
=> [10, 20, 30]

もはやこうなってくるとなんでもあり感ある。

evalで実行するコードに別のスコープの変数を持ち込みたい場合、Bindingオブジェクトをevalに渡すことで実現できる。

[5] pry(main)> class MyClass
[5] pry(main)*   def my_method
[5] pry(main)*     @x = 1
[5] pry(main)*     binding
[5] pry(main)*   end
[5] pry(main)* end
=> :my_method
[6] pry(main)> b = MyClass.new.my_method
=> #<Binding:0x00007fd47001d118>
[7] pry(main)> eval "@x", b
=> 1

しかし何でもかんでも使えばいいということではない。実行時にはコードとして評価されるが、書いている / 読んでいる際はただの文字列なのである。そのため、シンタックスハイライトや補完が使えず、バグに気付きにくい。
さらに大きな問題として、悪意あるユーザーが悪意あるコードを実行できる危険性がある。

evalを使わないようにするのであれば、define_methodsendを使うことになる。適切なメソッド選択が大事。

社内でしか使わないからevalで書いた、という機能もリリースする可能性がある。そのため、最終的にはevalを使わないようにリファクタリングできる場合はすることも考えるべきである。

フックメソッド

オブジェクトに対して発生するイベントをキャッチすることができる。
イベント、というのは例えばクラスが継承されたりメソッドが定義されたりといったこと。

[1] pry(main)> class String
[1] pry(main)*   def self.inherited(subclass)
[1] pry(main)*     puts "#{self}#{subclass}に継承された"
[1] pry(main)*   end
[1] pry(main)* end
=> :inherited
[2] pry(main)> class MyClass < String; end
StringはMyClassに継承された
=> nil

こんな感じ。このように何かのイベントに対応して呼び出されるメソッドをフックメソッドと呼ぶ。

7章 エピローグ

メタプログラミングに必要な知識としては以上となる。
これ以降はRailsのコードを見ながら実際に使われている箇所を見ていく流れ。

これ以降は実際のコードについて見ていく箇所になるため、ここでのアウトプットに関しては以上とする。

まとめ

メタプログラミング、なんてかっこいい名前をつけられているが、Rubyの世界においてはそれもただのプログラミングに過ぎないよ、ということが丁寧に順を追って説明されている良書。

とりあえず知識のインデックスは作った。これからは実際に手を動かしてみる予定。

8章以降でRailsについて見たので次はRailsの本でも読もうかしら。

ではまた。

【ゴルメモ】 金色のガッシュベル!!のブラウザゲーの話

うぃろぅです。
お盆だし暑いしで仕事してる気分じゃないので今日はブラウザゲーについて書きます。
書いていたら楽しくなってきたため布教目的になりました。
以下常体。

目次

今日のゲーム

金色のガッシュベル!! Golden Memories

f:id:vviilloovv:20190813130611j:plain
スマホでの起動画面

金色のガッシュベル!! 1ブラウザゲーム。略してゴルメモ
プラットフォームはenzaシャニマス勢にはお馴染み。

あのガッシュのブラウザゲー、ワクワクしてこない??

ゲーム概要

公式サイトの説明を引用してみる。

■集めた記憶で戦う、魔本構築RPG!■ 「金色のガッシュベル!!」の数多くの名シーンが「メモリー」となって登場! 集めたメモリーで魔本をカスタマイズして、自分だけの魔本を作り上げよう!

■「ゴールデンメモリーズモード」で物語を追体験!■ 1人用の「ゴールデンメモリーズモード」では、ガッシュたちと様々な場所を探検しながら、作品のストーリーを追体験することができる! 様々な交流を経てガッシュたちを成長させ、ステージの最後に待ち受けるボスに挑もう!!

■手に入れたメモリーが動き出す!■ 集めたメモリーは「金色化」することで、イラストが変化し動き出す! お気に入りのメモリーを金色化して、自分の魔本をさらに豪華に彩ろう!

■数多の魔王候補に挑む対戦モードも!■ 全国のユーザーとのオンライン対戦も可能! ランキング戦「魔王を決める戦い」では、上位ユーザーに「王様の特権」が与えられる! 超豪華な特権を手に入れて、君の手で新たな魔界を作り出そう!

enza | 人気作品の新作タイトルが遊べるゲームプラットフォーム

何一つわからねぇ…。なのでどんなゲームなのか書いていく。

集めた記憶で戦う、魔本構築RPG

ざっくり言うと「運要素少なめのDTCG」。

基本的な流れは以下。

  1. モリー(いわゆるカード)を2枚ずつ組み合わせたデッキを作る
  2. 自分と対戦相手はメモリーを開示して、相手の体力を削る
  3. 先に体力を0にした人か、ファイナルラウンド(10ターン)経過後に残っている体力が多い人が勝利

わかりやすいね。順番に見ていく。

デッキを作る

f:id:vviilloovv:20190809114242j:plain
5秒くらいでなんとなく組んだデッキ

こんな感じでメモリー2枚を1セット(見開きと呼ぶ)として、それを12組用意する。これでデッキはOK。詳細は後述。

対戦

f:id:vviilloovv:20190809114300j:plain
対ブラゴ(CPU)

CPUや人と対戦。
ハートマークの横の数字が体力。かなり差があるように見えるがこれは後述。
組んだデッキの最上段の見開きが表示され、そのうちのどれかを選択する。

f:id:vviilloovv:20190809114546j:plain

適当な見開きを選択すると相手と自分の選択した見開きが表示され、そのターンの先行から順に効果を処理していく。先攻後攻はラウンド(以下R)ごとに入れ替わる。

体力とは別に「心の力」という数値があり(青いハートの中にある数値)、これによって効果が発動したり上昇したりするメモリーが存在する。心の力はRごとに1ずつ自然上昇。

f:id:vviilloovv:20190809131102j:plain
R2

選択した見開きは消費され、先ほどのデッキ画面の左上から右に向かって順番に補充されていく。そのため自分の選択する見開きに運要素は存在しない

f:id:vviilloovv:20190809114608j:plain
手なりでR7まで進めた

心の力を上げつつ適当に進めてR7。ガッシュは心の力に応じて火力が大幅に上がる王道キャラのためプレイしていて気持ちいい。

f:id:vviilloovv:20190809114639j:plain
とどめ演出

f:id:vviilloovv:20190809114705j:plain
俺の勝ち。なんで負けたか、明日まで考えといてください

体力を0にしたので勝利。お互いにやりたいことをしっかりできると判定まで行かないことが多々ある。

対戦の流れはこんなところ。細かいルール等は実際にプレイしてほしい。

「ゴールデンメモリーズモード」で物語を追体験

ストーリーモードのこと。ストーリークリア特典でメモリーが手に入ったり魔界石2が手に入ったりするので積極的にクリアするべし。

漫画の内容をなぞっている。懐かしい。詳細は割愛。

手に入れたメモリーが動き出す!

f:id:vviilloovv:20190809115312j:plain
なぜか金色化してなかった

モリーは使っていくうちに経験値がたまり、一定値を貯めると専用アイテムを消費して「金色化(こんじきか)3ができる。

f:id:vviilloovv:20190809115326j:plain
金色のザケル

金色化するとイラストが動く。シャドウバースで言うプレミアムカードだったりHearthstoneで言うゴールデンカードだったりのあんな感じ。他にもいくつか利点がある。

金色化の利点

  • イラストが動く
  • フレーバーテキストが読めるようになる
  • ストーリープレイ時に能力が上がる
  • 対戦時の初期体力が上がる
  • 一部のメモリーの効果が上がる

フレーバーテキストは漫画のセリフから。懐かしさがさらに上がる。

対戦時の初期体力が上がる、という効果が一番わかりやすく、金色化しているメモリー1枚につき初期体力が4上昇する。
そのため、全く金色化していないガッシュデッキの初期体力は150、全てを金色化したガッシュデッキの初期体力は(メモリー2枚で1見開き) * (12セット) = (4 * 2) * 12 = 96246となる。

まさにいいこと尽くし。どんどん金色化していこう。

数多の魔王候補に挑む対戦モードも!

(今のところ)2ヶ月に1回、いわゆるランク戦(レート戦)が行われ、1位になった人には

  • 追加されるプレイアブルキャラを選択する権利
  • 追加されるストーリーを選択する権利
  • イベントマッチの特別ルールを決定する権利

4が与えられる。つまり魔界の王がゲームの今後に関われるようになる。
CoJが以前似た様な事をやっていた5が、まさしくそんな感じ。

ゲーム概要としては以上となる。なんとなく伝わっていたら嬉しい。

デッキ構築について

いくつかルールがある。

専用メモリーと汎用メモリー

ガッシュならガッシュのデッキにしか入れられない専用メモリー、どのキャラのデッキにも入れられる汎用メモリーが存在する。シャドウバースやHearthstone等と同じ。

ラウンド指定メモリー

R指定モリーって書いたら違う意味になったのでやめた。

f:id:vviilloovv:20190809115001j:plain
なんでこれが雷メモリーなのか

モリーの下の方に「R3」や「R7」と記載してあるメモリーは、そのラウンドに手札になる箇所にしかセットできない。
使うラウンドの指定ではなく手に入るラウンドの指定。手に入った後ならいつ使っても良い。

これがいい味を出していて、「これとこれ両方使えたら強いのに指定ラウンド被ってるから使えない」や「R8のメモリー強いからそこまで耐久できるようにデッキ作ろう」といったデッキビルドの楽しさを生んでいる。

色指定

モリーには4種類あり、

  • 赤の左
  • 赤の右
  • 青の左
  • 青の右

となっている。
「赤」とか「青」というのはメモリーの名前が書いてある帯の色の事で、赤は赤同士、青は青同士でないと見開きにできない。

f:id:vviilloovv:20190809143926j:plain
「赤の左」と「青の右」で決定しようとしても…

f:id:vviilloovv:20190809143945j:plain
「ダメだよ」って怒られる

「あれが赤だったら」「これが青だったら」とこちらも悩む事が多く楽しい。

レジェンド枚数制限

モリーのレアリティも4種類ある。

  • ノーマル
  • レア
  • スーパーレア
  • レジェンド

レジェンドはデッキに5枚までしか入れられない。
その他のレアリティは金色化させるためのアイテムが分かれている、ガシャでの排出率、生成するときの生成アイテム必要量等いくつかの違いはあるが、デッキには何枚でも入れられる。

f:id:vviilloovv:20190809145558j:plain
レジェ6枚入れて怒られた例

これにより「このカードを入れたいけどそうしたらこれを入れられない…」と悩む事になる。楽しい。

他にもキャラクタースキルとかスキルレベルとかいくつかあるけれどそこは細かいので割愛。

良い点

スマホゲー戦国時代の現代日本において私がこのゲームを続けている理由を挙げていく。

デッキ構築が楽しい

「縛りが大きい」ということはつまり「考える事が多い」ということになる。
ラウンド指定、色指定、レジェンド指定…。ともすればこれ一生完成しないんじゃないのなんて考える事もある。
「こうすればこうなるはず」という構築をしてそのとおりにはまったときの快感はすごい。

運ゲー要素が少ない

先述したように、デッキに入れた見開きが決まった順番で使えるようになるため、「これを引いていれば勝てた」というモヤモヤがない。
これは思ったより大きく、「相手にこれを引かれなければ勝ち」なときに引かれて負けたりとか「キーカードが引けないまま負け」とかがないというだけでストレスフリー。
楽しむためのゲームでイライラなんてしたくない。

ポケモンバトル好きとしてはこれはかなり大きい。

「読み」がアツい

「このターンに心の力下げられたら困るな~~~」って考えながら見開きを決めたり、「相手の大火力に耐えられる見開きあるけどこのラウンド大火力出してくるかな~~~」って悩んだり。
同じデッキで同じ相手と戦ったとしても読みだけで結果が変わることがある、楽しい。

あれこれ実質ポケモンバトルでは???

プレイアブルキャラクターが多い

金色のガッシュベル!!」に出てくる魔物が順次プレイアブルキャラクターとして追加されていくため、自分の好きなキャラクター・デッキタイプで遊びやすい。

2019/8/13現在ではプレイアブルキャラクターは11人いて、それぞれ以下の特徴がある。

  • ガッシュ&清麿: 心の力を貯めて大ダメージ
  • ブラゴ&シェリー: 相手の攻撃力を下げる
  • キャンチョメ&フォルゴレ: FRまで耐えて大逆転
  • ウォンレイ&リィエン: 中盤から連続で高火力を出す
  • ウマゴン&サンビーム: 会心率を上げて会心狙い
  • ティオ&恵: 盾を張って耐え切る
  • バリー&グスタフ: お互いの火力を上げて殴る
  • キッド&ナゾナゾ博士: セット(いわゆるトラップ)メモリーを駆使
  • ビクトリーム&モヒカンエース: 我が美しさを股間の紳士に!!(チャージを貯めて大ダメージ)
  • レイラ&アルベール: 後半まで耐えて逆転(アルベールの意思を開放する原作再現)
  • ゾフィス&ココ: 心の力を下げて直接ダメージ

プレイアブルキャラクターが増えるたびにコンセプトが変わるため楽しい。
原作のキャラが立っているから納得もしやすくていい感じ。
逆にこれ以上どうバリエーションを増やすか、みたいな部分はある。回復メイン(コルル?)とか回避メイン(パピプリオ?)とかだろうか。パピーは心の力減少メインでもいいが。

8月中にはコルルが追加される見込み…らしい。追加されたらコルルメインにする予定。

環境が変わる速度が早い

このゲーム、数週間に1回は「新メモリーの追加」や「プレイアブルキャラの追加」で環境が変わる。
「全キャラ使いこなしたい」という熱心な人には大変かもしれないが、プレイアブルキャラが多い分飽きづらい、というのはいい点なのではないだろうか。
その分覚えることが多いので新規参入障壁が高いがまぁそれはどのゲームでも同じ。

ブラウザゲーだからアプデ不要

「なんでアプリじゃないんだ」って声が多いが、アプリはアプリでストアで更新が必要だったりスマホの容量を気にする必要があったりで気にする必要があることは多い。
ならたまにはブラウザゲーもいいんじゃないの、ということでどうだろうか。
アプデ不要は意外と快適で良い。
PCでプレイすれば他の作業もしながらできるし。
アプリでないと課金がしづらいという点は確かにあるが…ガッシュ好きな年代の人はカードくらい持ってるでしょ…もしくはちょコムのようなサービスを使うとかどうだろうか。

惜しむらくはたまにエラーで落ちる事。

f:id:vviilloovv:20190813130440p:plain
あぁ…ってなる

対戦中にこれが起きると大抵1R分はオートで進む。対策といては毎試合ごとにページをリロードする事…かなぁ…。

金色のガッシュ!!」が好き

まぁ結局はこれ。
カサブタ」は名曲だし原作漫画は面白いし。

現在「完全版」が発売されている。オマケ漫画もある。私は全巻購入して読破済み。
もしかして私はガッシュ大好き勢なのでは??

こうなると嬉しい

2019/3/26にリリースしたゲームのため、まだ4ヶ月少々しか稼動していない。そのため、いくつか「これはこうなってほしい」という点も存在するので挙げていく。

あくまで個人の感想であり、「現状がこうだからダメ」などという批判をしたいわけではない、ということをあらかじめ断っておく。

会心 / 回避について

先ほどから運要素が少なめと書いていたが、その少なめ要素がこれにあたる。
お互いの攻撃時に一定の確率(明示はされていない)で会心回避が発生する。
会心はダメージが1.5倍、回避はダメージが0となる。

ここまで読んでいただいている方々にはお分かりいただけると思うが、要はポケモンの急所と回避である。

会心はまだいい。これくらいならエッセンスとして丁度いい。
ウマゴンは改心メインのキャラだし完全に運要素がなくなると逆転の目がなくなるし。

  • 回避

問題はこっち。
「この攻撃は回避されない」と記載されていないメモリー全てに回避の可能性がある。
これは確かに文句が出る。「R8に150ダメージ出るから耐久」のコンセプトのデッキがあったとして、それが回避されるのである。そりゃスマホ投げたくもなるわな。

なので、こうしたらどうだろうか。もちろん妄想。

  • 基本的に回避率は0
  • 火力が出るメモリーには「このラウンド中、相手の回避率+10%」等のデメリットをつける
  • ラウンド中回避率が上がるメモリーを追加(最大20~30%程度)

お察しのとおりポケモン式である。
これまで「命中率」という表記がされたことはないため、「回避率」をベースに考えるとこうなるかなと。
ラウンド中回避率アップは運ゲーがしたい勢のために追加してもいいかなというところ。永続砂嵐環境の砂ガブは許さん

しかし回避ゲーになってしまうとそれはそれで厳しい。10Rまでしかないゲームで回避が起きると他の9Rで取り戻す必要が出る。これはさすがに短すぎる。難しいところ。

しかしこのゲーム、メモリーのバランス調整が行われた事がない。なんとかなってくれると嬉しいが果たして。

イベントマッチ報酬メモリーの再入手手段

「魔界の王を決める戦い」以外にもイベントマッチがあり、イベントマッチのたびにミッション報酬としてメモリーが配布されている。
このメモリーの中にはいわゆる「ガチデッキ」に入るものがあり、新規でゴルメモを始める人に対しての壁になってしまう。

例えば「ランダムなCPUと連戦して3連勝できたらSRの報酬メモリーと交換できるチケットプレゼント」とか…どうだろうか…。
レジェンドのほうは「ランダムな高難易度CPUと対戦して3連勝」とか…。
もちろんイベントマッチの直後ではなく、数ヵ月後とかで良い。何卒…。

放置勢への対処

ゴルメモは対戦ゲームであるため、見開きを決める制限時間が存在する。
この時間は概ね30秒ほどで、時間切れになると選択中の見開きが自動的に選出される。

そしてこのゲーム、他のDTCG系ゲーム同様ペアごとにレベルがあり、CPU戦や対人戦を行うと経験値がもらえる。
経験値が一定値集まるとレベルが上がり、メモリーを生成するためのアイテムや魔界石がもらえる。

俗に「博士周回」と呼ばれる「チュートリアル戦で1~2Rで勝利する事を繰り返して高速で経験値を稼ぐ」レベリング方法があるものの、チュートリアル戦で選べるペアが限られているため全員がこのレベリングを行うことはできない。

その結果どうなるか。デメリット効果として自傷ダメージを受けるメモリーでデッキを組んで高速で自滅していく自滅デッキが一定数生まれる。
それだけならまだ良い。「はいはい自滅デッキね」で終わるからだ。
しかし「放置しても対戦が進行する」という特性上、「対戦するボタンだけを押して放置する事で最低限の操作でレベリングを行う」という、いわゆる放置レベリングが生まれる。
中には「CPUにはギリギリ勝てる程度のデッキを組んで放置(勝つともらえるポイントが増えるため)」ような人もいて非常にたちが悪い。
「放置するのでテンポ良く戦いたかったら降参してね」みたいな人もいる。もはや嫌がらせである。

こうした人の対策として「2R連続で放置した場合は操作があるまで制限時間短縮」みたいな仕様を入れてはどうか。「2R連続」としたのはブラウザのエラーで1R飛ぶことは割と起きるため。

まとめ

どうにかして新規参入障壁を下げ、アクティブ数が増えるようにして欲しい。
というのもゲーム、アクティブ数がそこまで多くないため「マッチング帯を分ける」や「フレンド機能を充実させる」といった「アクティブ数が多くなければ実現できない」ことが多い。

お盆休み(がある方は)のお供にゴルメモはいかが?
楽しいゲームなのでみんなやろう!!ガッシュ好きな人!!!ぜひ!!!!

ではまた。

©雷句 誠/東映アニメーション
©2019 BANDAI NAMCO Entertainment Inc.


  1. 原作漫画は「金色のガッシュ!!」というタイトルなので若干の差異がある。

  2. 聖晶石だったりダイヤだったり呼び名は変わるが要は課金石。

  3. ガッシュなので「きんいろ」ではない。

  4. これが全てではない。

  5. 公式を貼ろうとしたらリンクが切れていた。これが稼動終了…。

【セトリ】 UNISON SQUARE GARDEN Live 「PROGRAM 15th」 セットリスト

2019/7/27に舞洲スポーツアイランド 太陽の広場で開催された
UNISON SQUARE GARDEN Live
「PROGRAM 15th」
セットリストです。
※アンコールなし

1: お人好しカメレオン
2: シャンデリアワルツ
3: 君の瞳に恋してない
4: 流星のスコール
5: instant EGOIST
6: リニアブルーを聴きながら
7: Invisible Sensation
8: 8月、昼中の流れ星と飛行機雲
9: オトノバ中間試験
10: カウンターアイデンティティ
11: Catch up, latency
12: プログラムcontinued
13: 黄昏インザスパ
14: 春が来てぼくら
15: 水と雨について
16: harmonized finale
17: cody beats
18: 10% roll, 10% romance
19: 天国と地獄
20: fake town baby
21: 徹頭徹尾夜な夜なドライブ
22: シュガーソングとビターステップ
23: さわれない歌
24: 桜のあと (all quartets lead to the?)
25: オリオンをなぞる
26: センチメンタルピリオド

f:id:vviilloovv:20190727204254p:plain

「天気の子」に「佐倉香菜」を幻視する

うぃろぅです。

tenkinoko.com

「天気の子」観てきました。
覚えているうちに色々書こうと思います。
とりとめはきっとないです。

※ネタバレあります。

f:id:vviilloovv:20190725093125j:plain

このシーンを見て「少女は、空に落ちる」というキャッチコピーを思い出しました。

GRAVITY DAZE、面白いので未プレイの方はこの機会にぜひ。

きっかけ

私はあまり映画を観に行かないタイプです。
行くのは舞台挨拶くらい。なのでこの作品も「レンタル開始したら観ればいいかなー」と考えていました。
ではなぜ観にいったのか。

  • TLの花澤勢が「香菜ちゃん出てたー」と言っていたので興味を持った
  • TLの有識者(映画オタク)が「面白かった」と言っていた

等ありますが一番大きかったのが多分これです。

cr.hatenablog.com

[https://twitter.com/vviilloovv/status/1154018563310493696:embed#PS2版天気の子を俺たちは遊んだことが有る気がしてならないんだ。 - セラミックロケッツ! https://t.co/eJrUEPl9IW この記事読むためだけに観に行った説、実はあります]

ジョーク記事なのですが、こんなに面白そうなジョークを楽しむためには観なきゃと。
楽しそうなことは全力で享受していきましょう。

何を観にいったか

私は声優オタクなので花澤香菜さんの出演シーンを観に行きました。

そうしたら「カナ」役で花澤香菜さん、「アヤネ」役で佐倉綾音さんが出演されていたから普通に笑っちゃいました。

花澤香菜さんの出演シーンとしては「カナ」役だけかな、と思っていたのですがどうやら「キュアブラックのコスプレをしている人」もそうだったんじゃないか、とのことらしい。
私は気付かなかったので何人かの意見を聞いてみたいところ。

佐倉綾音さんの出演シーンは

  • 「アヤネ」役
  • 「都市伝説(晴れ女だったと思う)の取材でファミレスで話した女性」
  • 「花嫁(式場を晴れにして欲しい、みたいな依頼をしていた女性)」役
  • ラブホ行く前の電車で「どうしよう帰れないよ」みたいなことを言っていた女性

だったかな、と記憶しています。本当に何を観にいっているのだろうね。

観終わって

観終わって最初に私が言った言葉は

絶対佐倉香菜でしょ

でした。佐倉綾音さんが「花澤綾音」なのだから花澤香菜さんは「佐倉香菜」です。
佐倉綾音さんは花澤香菜さん大好き勢なので多分このシーン無限にニヤニヤしていたと思います。「花澤綾音」は違和感があまりないのに「佐倉香菜」は違和感があるのは何でなのでしょうね。個人の感想ですが。

次に思ったのが

新海誠、媚びたな~~~~~~

でした。
3年間の雨に沈んだ東京を映してエンドロール、でも満足できる作品だと思ったんですよ。 それを納得できそうな締めにつなげたり。でも3年後も祈っている陽菜ちゃんは可愛かったです。
宮水三葉の顔をなかなか映さなかったり、立花瀧がちょい役とは思えない存在感を発揮していたり。神木くんは本気で2次元の存在になろうとしている気配がすごい。

作品としては、「君の名は。」と比較しても登場人物の行動に納得しやすかったため、すんなりと流れを追っていくことができたと感じています。警察絡みのシーンはうーんともなりましたが。

後は佐倉綾音さんの出演シーンを忘れないように小声で「都市伝説の花嫁…あと電車…」とかつぶやいていました。絶対近づきたくない

見えないものを見ようとして

この作品、観た人に任せる部分が非常に大きいように感じました。

「佐倉香菜」もそうですし、「エロゲ版 天気の子」もそう。
「選択肢」が見えるんですよね。帆高が何を選ぶのか、見ている私なら何を選ぶのか。

え?見えない?
それでいいと思います。
かつてノベルゲームが大流行していたころ、その類のゲーム(主にエロゲ)を嗜んでいた人たちはゲームであった演出を幻視します。
RADWIMPS早回しには既読スキップを幻視して、ラブホにある軽食の自販機の隣には大人のおもちゃの自販機があって、その自販機でひと悶着がある様を幻視する。多分CG回収用の選択肢があります。
寝る前に「凪にばれない様にエッチするシーンがある」なんてことも幻視してしまうわけです。
これ本当にいくらでも見えてくるのでここまでにしておきます。幻視する速度に手が追いつかない。

これは「見えなければ損」ということではなく、「ある種の人に勝手に見えてしまった」というだけ。お嬢様と言えば金髪縦ロール、というのと同じで「お約束だからわかる」というやつですね。
そんな人が多かったからこそ集団幻覚にかかっているのだと思います。あいつら未来に生きてんな。いや過去か。

帆高が選択した結果、周囲の人たちが何を選択するのか。多分1つ違うだけで物語は様変わりします。
そして「少女か世界か」の選択肢から「少女」を選択した。
物語としては「その結果こうなりました」で終わらせていいんです。

でも終わらなかった。普段ゲームをやっていたら見えなかった「その先」を新海誠監督は描いた。
オタクは好きな作品だと思います。

観賞後のエレベーターの右奥隅で話していた30前後に見える男性のグループは「あと5回くらいは観てもいい」って話しをしていて、それが聞こえたのか左奥隅にいた20過ぎくらいに見える女性のグループは「価値観は人それぞれだよね…」と小声で話しをしていました。
私には男性グループの高校大学時代のゲーム遍歴が見えました。見えないもの、見ちまったな。

閑話休題しようとしても閑話しかなかった

このままだと際限なくとりとめがなくなってしまうのでここまでとします。

花澤香菜さんのオタクと佐倉綾音さんのオタクは出演シーンを意識すると楽しめると思います。
言の葉の庭」が好きな私としては「そこまで見せてくれなくてもいいのに…こんなに続いていいの…?」という気分になりました。「大丈夫」ではなくて「救われていた」でいいのです。

エロゲオタクは今すぐ観てくれ。

君の名は。」はそこまで刺さらなかった(円盤は買った)んですが、「天気の子」はめっちゃ楽しめました。また劇場で見てもいいかな。

高井刑事のリーゼントな件に関しては「傷だらけの天使」を見れば幸せになれると思います。親が水谷豊オタクで得をした瞬間です。

あと終盤の陽菜の名前を連呼するシーンで「ジゴゴゴ」思い出してごめん。

ではまた。

【備忘録】 「SQLアンチパターン」を読む

うぃろぅです。

今日も本を読みます。目指せ技術書の虫。

今日の本

SQLアンチパターン

SQLアンチパターン

SQLアンチパターン

SQLに関係ある人、恐らくWeb関係者の大半が読むべき本として有名な本。
「こんなDBはイヤだ」を集めた本。歴史に学ぼう。

構成

アンチパターンの種類を4つに大別している。

DB自体の設計、SQL発行の仕方、多言語からSQLを発行する際、と徐々に本体から遠ざかっていく形になっている。DBが良くても呼び出し方がね、とならないための構成。

1部 データベース論理設計のアンチパターン

1章 ジェイウォーク(信号無視)

カンマ区切りで複数データを持ってしまうやつ。
交差テーブル(交差点)を避ける行為であることから。

外部キー設定するべき箇所をVARCHAR型にしてカンマを許容してしまったらさあ大変。
その外部キーを用いて検索、なんてことが非常に困難になる。

商品ID 商品名 倉庫ID
1 声優パラダイス 1,2
2 声優アニメディア 2,3
3 声優グランプリ 1,4
倉庫ID 倉庫名
1 大宮
2 浦和
3 与野
4 岩槻

例えばこう。申し訳程度の声優要素。倉庫名がローカル。
「大宮倉庫に保管している商品」を引っ張ってきたい場合が地獄。
さらにはカンマを許容するために文字列型にしているため、倉庫IDに「春日部」みたいな文字列を入れることもできてしまってもうそれは阿鼻。

解決策は簡単で、「交差テーブルを避けてないでちゃんと作れ」ということである。
上記の例であれば保管テーブルでも作ればいいんじゃないかな。

保管ID 商品ID 倉庫ID
1 1 1
2 1 2
3 2 2
4 2 3
5 3 1
6 3 4

こんな感じ。保管日等は直接関係していないため省略している。

2章 ナイーブツリー(素朴な木)

掲示板のコメント欄のような「木コメ」に「枝コメ」や「葉コメ」がぶら下がる場合のコメントテーブルがあるとして、各コメントが「親コメント」しか持っていないやつ。

「葉コメ」から「木コメ」のような2つ以上離れた関連を辿って集計する際の処理が煩雑になってしまう。

例として適当なコメントを探そうかと考えたときに思い浮かんだのが某スマホゲーWiki某サーヴァントのコメント欄。少し前まで荒らしが横行していたが最近落ち着きを取り戻したらしい。落ち着きの定義が問われる。

それっぽい箇所を抜粋する。

コメントID 親コメントID コメント内容 コメント日時
1 (null) やっと、本当に久々に、平和なナーサリー板が戻ってきた(感涙)。記念にナーサリーちゃんprpr 2019-07-01 03:32:33
2 1 真ナが妙な変異起こしてて不安だけどな… 2019-07-01 07:38:12
3 2 インド帰りなんだろ 2019-07-02 09:44:10
4 2 じゃあ、いつもみたいに消しちゃう?不出来で不要になった真ナーサリーも? 2019-07-02 09:56:47
5 4 真ナーサリー「やってみろ」(ニチャア 2019-07-02 11:30:03
6 2 真ナが変なのって荒しなんじゃねえの? 2019-07-02 19:34:37
7 6 わざわざ管理人さんの規正報告に枝付けして意味不明なこと書いているあたりそれっぽいよな… 2019-07-03 10:16:42
8 1 (誰か木主に、お前がprprしてるのはナーサリーではなく叔父貴だという事を教えてあげて…) 2019-07-01 09:43:00

隣接リスト(Adjacency List)と呼ばれる設計。
コメントID2の枝を削除しようとする場合、外部キー参照の整合性制約を満たすため、2の子孫を辿っていって終端から逆順に削除していかなければならない。2を最初に削除すると346親コメントIDが不正になってしまうからである。
これはちょっと大変。

解決策はいくつかある。

  • 経路列挙(Path Enumeration)
  • 入れ子集合モデル(Nested Set)
  • 閉包テーブル(Closure Table)

全て詳しく書くのは冗長なため書籍参照。

例として経路列挙だけ書くと

コメントID コメントパス コメント内容 コメント日時
1 1/ やっと、本当に久々に、平和なナーサリー板が戻ってきた(感涙)。記念にナーサリーちゃんprpr 2019-07-01 03:32:33
2 1/2/ 真ナが妙な変異起こしてて不安だけどな… 2019-07-01 07:38:12
3 1/2/3/ インド帰りなんだろ 2019-07-02 09:44:10
4 1/2/4/ じゃあ、いつもみたいに消しちゃう?不出来で不要になった真ナーサリーも? 2019-07-02 09:56:47
5 1/2/4/5/ 真ナーサリー「やってみろ」(ニチャア 2019-07-02 11:30:03
6 1/2/6/ 真ナが変なのって荒しなんじゃねえの? 2019-07-02 19:34:37
7 1/2/6/7/ わざわざ管理人さんの規正報告に枝付けして意味不明なこと書いているあたりそれっぽいよな… 2019-07-03 10:16:42
8 1/8/ (誰か木主に、お前がprprしてるのはナーサリーではなく叔父貴だという事を教えてあげて…) 2019-07-01 09:43:00

こんな感じになる。
文字列になってしまうため、ジェイウォークと同じような扱いづらさがあることが欠点と言える。

閉包テーブルがおしゃれでいい感じ。深さも付与した閉包テーブルを作成すればノードの追加や移動があったとしてもソートしやすそう。

「絶対にこれ」ではなく各々の利点と欠点を理解して使い分けることが重要。

3章 IDリクワイアド(とりあえずID)

全てのテーブルに自動生成のIDをつけるやつ。
これは賛否が分かれる。

とりあえずidではなくhoge_idのように意味のあるIDを振るのが大事。命名が大事なのはどの言語でも一緒である。

例はなし。わかるし。

4章 キーレスエントリ(外部キー嫌い)

読んで字のごとく、外部キーを設定するべき箇所に設定していないやつ。
サポートしていないなら仕方ないにしてもあるものは使うべき。

外部キー参照がないとレコードの削除時に不整合が起こる。
外部キー設定時にデータ更新が面倒になることもあるかもしれないが、整合性がないテーブルよりはるかにコストが低い。

解決策としては外部キーを設定する。そりゃそうだ。

5章 EAV(エンティティ・アトリビュート・バリュー)

DBなのに行列でデータを持とうとしてしまうやつ。
文字だけではわかりづらい。

掲載されていた例はこちら

簡単なところでは健康測定のテーブルとして

出席番号 測定対象 測定結果
334 身長 170.2
334 体重 65.5
334 座高 85.6
334 視力(右) 0.8
334 視力(左) 0.4
334 最低血圧 95
334 最高血圧 130

こんな感じにしてしまう。主キーは出席番号測定対象の複合。
NHKとかガチャ目すごいとかちょっと足長めかなとかそういうことではない。

これをしてしまうと、「この測定結果は整数でこの測定結果は少数」のような制約をつけることができない。さらに「医師面談結果」みたいな測定対象があったとして測定結果が「異常なし」だとすると文字列になってくる。考えるだけで地獄が見える。

こういった運用をしたいのであればそれはSQLではなく別のDB管理方法をとった方が良くなる。リレーショナルである意味がなくなるからである。
今時こんなことしないやろって思っていたが以前携わったプロジェクトにこれを採用しているテーブルがあった。うーむ。

解決策としては

  • シングルテーブル継承
    属性名を列にする
  • 具象テーブル継承
    属性ごとにテーブルを作る
  • クラステーブル継承
    オブジェクト指向の継承のようにテーブルを継承する
  • 半構造化データ
    追加や削除が多い属性をXMLJSONで取り扱い、それを格納する列を設ける

といった方法がある。
属性の更新頻度や量などで総合的に判断すべし。

6章 ポリモーフィック関連

子テーブルに対して親テーブルの選択肢が複数ある場合に、親テーブルの名前をレコードとして持つやつ。

解決策の1つとしてポリモーフィック関連があるが、フレームワークでサポートしている等の理由ではない限り避けた方が無難。
Railsではサポートしているが、用法容量を守って正しく使おう。
親のクラスによって出し分ける、なんてあってはならない。

ポリモーフィック関連を避けた解決策として

  1. 参照を逆にする
  2. 複数の交差テーブルを作成する
  3. 交差点に交通信号を設置する
    複合キー+そのうちの1つにユニーク制約を付ける
  4. 両方の「道」を見る
    複数の交差テーブルをjoinする
  5. 「道」を合流させる
    unionで結合してビューにする
  6. 共通の親テーブルを作成する

という手法がある。4以降はどれかを選択する。

共通の親テーブルを作成すれば良さそうに見える。Kernelモジュールみたいな。

7章 マルチカラムアトリビュート(複数列属性)

1つのデータに複数の同系統データが関連するとき、複数の列を作成してしまうやつ。
これも文字だけではわかりづらい。

(私にとって)身近な例で言えばTwitterのアンケート機能。

最近投げたアンケ。シュタゲゼロ今観てるよ。消滅都市は観ると記憶が消滅するから見なくていいよって言われたけれど果たして。
閑話休題、この選択肢は2~4個までの間で可変。

このアンケートテーブルを

アンケートID ツイートID 回答期限 選択肢1 選択肢2 選択肢3 選択肢4
101 123 1:00 消滅都市 五等分の花嫁 シュタインズ・ゲート ゼロ スター☆トゥインクルプリキュア

こうしちゃダメだよということ。
(この例だとやらなさそうだけれど)値が「消滅都市」の選択肢を検索する際、選択肢1~4を全て検索しなければならないため、効率が悪い。
更に選択肢を2つのみにする場合、値が入らない箇所はnull値となる。わかりやすい感想としては気持ち悪い1
また、選択肢を5個まで増やせるように機能が変更されたとしたらテーブルに列を追加する必要が出てくる。これはとてもコストがかかる。

解決策は従属テーブルを作成する。
この例だと選択肢テーブルを作れば良い。
ジェイウォークと同じような対処法となる。

8章 メタデータトリブル(メタデータ大増殖)

メタデータ」はテーブルや列のこと。行はデータ。
これがトリブルのように無限増殖すること。海外のTVドラマに出てくる毛玉みたいなキャラクターの名前らしい。

年ごとや月ごとににテーブルを増やす、列を追加する、とかそういうこと。
テーブルの名前を変えてコピーする場合、コピー元のテーブルにバグがある場合そのバグごとコピーされることになる。列に変更がある場合は全てのコピペ先に適応しなければならない。コピペプログラマみたいだな…。

過去データを分離して保存しておく場合は例外的にこれもあり。変更しないで参照するだけ、とか制約を付けておくとなお良し。

解決策はパーティショニングと正規化。

lets.postgresql.jp

qiita.com

RDBMSがサポートしているパーティショニングを用いてテーブルを分割できる。
物理テーブルが分かれていたとしても内部でうまいこと振り分けてくれる仕組み。

1部は以上となる。プロジェクトで見た覚えのあるアンチパターンがいくつかあって心が痛い…。

2部 データベース物理設計のアンチパターン

9章 ラウンディングエラー(丸め誤差)

小数を扱う導出項目のデータ型をfloatにすると、精度が落ちてしまう。
JavaC#ではDecimal型があるためそれを使えば概ねなんとかなる2

解決策はnumericdecimalを使う。SQLにもあるのね。

正確な計算はどの言語でも気を遣うよね。金融系のシステムなんかでは必須。

10章 サーティワンフレーバー(31のフレーバー)

特定の値しか入れたくない列があるとする。例えば[大宮 浦和 与野 岩槻]のどれかしか入力できないようにしたい、みたいな。
この値を「列を定義するタイミング」で指定してしまうのがアンチパターン
サーティワンフレーバーというのは、バスキン・ロビンス3が当初は「月によって変化する31のフレーバーの中から好きな味を選べる」していたが、現在では31を超えて数多くのフレーバーを同時に取り扱うようになっていることから。

新たに選択肢を追加する場合は列の修正が必要となる。
配列やハッシュ等を使わずにインスタンス変数を新たに宣言するイメージ。
修正や削除のときも同様。

解決策は外部キー制約を付けて選択肢をデータ管理すること。

create table さいたま市構成市名(
  旧市名 varchar(20) primary key
);

insert into さいたま市構成市名 values('大宮');
insert into さいたま市構成市名 values('浦和');
insert into さいたま市構成市名 values('与野');
insert into さいたま市構成市名 values('岩槻');

create table 住所(
  (何か他の列),
  旧市名 varchar(20),
  foreign key(旧市名) references さいたま市構成市名(旧市名) on update cascade
);

こんな感じ。テーブル名とか列名はわかりやすさ重視のため日本語とした。
これなら「やっぱり与野は外そう」と考えてもすぐ外せる^4し、「上尾と蕨も合併しよう」と考えてもすぐに追加できる。

11章 ファントムファイル(幻のファイル)

まず名前がかっこいい物理ファイル自体をレコードとして追加するのか、物理ファイルのパスをレコードとして追加するのか問題。

たびたび議論の対象となるが、この本の筆者の立場は反対。
理由を挙げる。

  • レコード削除時に外部ファイルが残ってしまう
  • 物理ファイルを削除や変更した場合、トランザクション管理できずに変更が他ユーザーに反映される
  • (ファイルも同時に削除するとして)削除処理をロールバックする際に物理ファイルはロルバ対象外のため削除されたままとなってしまう
  • RDBMSのバックアップにDB外の物理ファイルを含めることが困難
  • 外部ファイルに対するアクセス権限はDB内の権限とは別物
  • ファイルはデータ型ではない(パスの場合DB内では文字列型となる)

なるほど。こう見ると結構ある。
逆に言えばこれらへの対応がしっかり練られていればそれはそれで問題ない。

解決策はblob型を採用すること。
最近のDBは容量に余裕があるのである程度のファイルサイズなら許容できるでしょ、という考え方。

ファイルの読み込みをサポートしているRDBMSも多いため、不自由には感じないはず。

12章 インデックスショットガン(闇雲インデックス)

禁書目録さんがショットガン片手に近づいてくる光景が脳裏をよぎったが絶対違う。

DBのパフォーマンスを向上させるために有効な手段の一つとしてインデックスを効果的に使用することが挙げられるが、その効果的な使用法について。
名簿であれば「あ行」「か行」みたいな。

ソート済みでなければインデックスは機能しないため、データ追加や更新のたびにソート処理が走る。そのため定義するとオーバーヘッドは生じる。
そのオーバーヘッドを上回る効用があるよねと。

だからといってたくさん貼ればいいわけではない。これが今回のアンチパターン
例えば以下はバッドプラクティス。

  • IDにインデックス
    ほとんどのRDBMSは主キーのインデックスを自動的に作成するため冗長。
  • 説明文等の長い文章にインデックス
    これを対象にselectする機会があまりなく、オーバーヘッドの方が大きくなりがち。
  • 所要時間などの付帯要素
    こちらも検索条件になりにくい4
  • 複合インデックス
    利点はあるが、定義した順番どおりに使用しなければ意味がなかったり、使用頻度が低くなりがちだったりと欠点も多い。

解決策は適切なインデックスを作成すること。わかりやすい!!
…で、どうすればいいのよと。そりゃそうなる。

適切なインデックス作成のためのチェックリストとして「MENTOR」が挙げられている。

  • Measure(測定)
    ボトルネックを調べる。
  • Explain(解析)
    クエリ実行計画(後述)のレポートを取得して解析する。
  • Nominate(指名)
    解析結果から原因を特定する。
  • Test(テスト)
    修正が適当か確認する。
  • Optimize(最適化)
    キャッシュに割り当てるメモリ量を調整し、頻度の高いインデックスをキャッシュにロードする。
  • Rebuild(再構築)
    定期的にメンテナンスする。

の頭文字をとったもの。

クエリ実行計画って?

Query Execution Planの頭文字をとってQEPとも。
クエリ実行にどのインデックスを使うかを判断する計画のこと。

RDBMSによって多少差はあるが、概ね

  • クエリに関連するテーブル
  • インデックスの選択理由
  • テーブルへのアクセス順序

が含まれる。
RDBMSごとに何が吐かれるかを調べるが吉。

2部は以上。インデックス張ってないDBは見たことがある…。

3部 クエリのアンチパターン

13章 フィア・オブ・ジ・アンノウン(恐怖のunknown)

名前がかっこいい。

住所の「マンション名・部屋番号」のように入力必須ではないデータを含む結果を得ようとする際、null値を一般値のように扱うことがアンチパターン。またはその逆。
null値を使うことがアンチパターンというわけではない。扱い方が問題。

SQLではnullを特殊な値として扱う。0でもfalseでも空文字でもない。
上記の例で住所を結合して出力したい際、「番地」と「マンション名」を結合しようとしたときに「マンション名」がnullの場合出力はnullとなってしまう。
いやー困っちゃうね。
だからといって自然数が入ることが想定されている列に-1を入れるようにすると集計処理の際-1を除く処理をしなければならなくなる。うおお面倒…。

null == nullの結果がnullになる理由は「年齢不詳の人と年齢不詳の人が同い年かどうかはわからないでしょ」である。わかりやすい。

解決策はnullを考慮した熟語を用いること。

  • is null
  • is not null
  • is distinct from

is distinct fromはサポートしていないことがあるため注意が必要。
あとはnot null制約をつけることだろうか。無論全てにつくわけではないが。

14章 アンビギュアスグループ(曖昧なグループ)

group byを利用してグループ内の最大値や最小値といった値を取得し、その行の他のデータを取得したい際に、グループ化していない列を参照すること。

以前書いたSQL文で最初そんなことが起きていた。
これはサブクエリを使うのでは?と期待しつつ読み進める。

グループ化していない参照値がユニークでない場合にこういったことがある。
SQLの仕様理解が大事。相手の気持ちを汲み取っていこう。

関数従属性がある列になら使っても良い。この言葉が出てくるのは第二正規系の時だっけ…?
助けて過去の私。

vviilloovv.hatenablog.com

しかも第三だった…。ちなみに第二は複合キーを分ける。

解決策はいくつかある。

  • 関数従属のある列のみにクエリを実行する
  • 相関サブクエリを使用する
  • 導出テーブルを使用する
  • 外部結合を行う
  • 他の列もグループ化する
  • グループごとにすべての値を連結する

いっぱいある。それだけ多くの人がこのアンチパターンにお世話になったということだろう。かく言う私もその1人であることだし。

15章 ランダムセレクション

ランダムな値が必要とされる際、ランダムにソートした最初の行を取得する。

少量のデータをランダムに取得するだけなのに全体をランダムソートする、というだけで大変なことをしていることがわかる。

データが少ないなら問題ない。それが増えないならなおのこと。

解決策はいくつかある。

  • 1から最大値までのランダムなキー値で抽出
    主キーが1から始まり、欠番がないことが条件。論削する方針のDBならいけそう。
  • ランダムなキー値で抽出 + 欠番の場合その次の値で抽出
    物削する方針のDBならこちら。
  • 主キーのリストをアプリ側で取得し、その中からランダムに1つ抽出
    リストが大きくなりすぎないのであれば有用。2回DBとやりとりするので若干冗長かも。
  • オフセットを用いてランダムに行を選択する
    ヒットした件数のうち何番目をとってくるか、をアプリ側でランダムに指定してとってくる。良さそうだがlimitをサポートしていないことがあるので注意。
  • ベンダー依存の解決策
    RDBMSにサンプル機能があるならそれ使えばいいよね。

乱数周りはパフォーマンスに影響が出やすい。よく調べて実装するのが吉。

16章 プアマンズ・サーチエンジン(貧者のサーチエンジン)

貧者と見るとカルナさんが思い出される。インド異聞帯最高だったよ。

全文検索を行う際、パターンマッチ述語を使用する。
正規表現は便利だが、データ全体に対して行うとデータ量に比例してパフォーマンスが低下してしまう。

単純なデータに正規表現を用いる分には問題ない。積極的に利用していこう。

最善の解決策はSQLを使わずに全文検索エンジンを使うこと。
次善の解決策は検索結果を保存して使いまわすこと。

これに関してはRDBMSを頼るのが良い。全文検索することは多いだろうからだいたいあるはず。
全文検索が必要となったときに調べる、ということが必要ということを覚えておくのが大事。

SQLが苦手なことはSQL以外にやってもらう。餅は餅屋精神。

17章 スパゲッティクエリ

SQL世界のスパゲッティプログラム。まぁどこの世界にもあるよね。あってほしいわけではないが。

合言葉は「全部1回で解決しようとするな」で。
とりあえず結合、としてしまうと場合によってはクロス結合することがある。
データ量が多いテーブル同士のクロス結合は地獄。

アンチパターンを用いても良い場合、という箇所で「上司を説得して複数に分けろ」と書いてあったのが面白い。要は使うなと。

解決策は処理を分けること。単一責任。

18章 インプリシットカラム(暗黙の列)

「暗黙の」ってついている時点でいやな予感しかしない。

ワイルドカード指定や暗黙の列指定(insert intoの列指定をしない等)を行うこと。

例えばinsert文の列指定を省略した場合、列追加をするとinsert文がエラーになる。列の数と値の数が合わないからである。列を削除した場合も同様。

怠惰と保守性とのせめぎあい。書き捨てではない場合さぼらず書こう。

解決策は列名を明示的に指定すること。
サンプル以外でSQLを発行する場合はワイルドカードを使用しない、と制約を付けてもいいかもしれない。

クエリのアンチパターンは以上。 スパゲッティクエリだけは絶対にダメ。

4部 アプリケーション開発のアンチパターン

19章 リーダブルパスワード(読み取り可能パスワード)

パスワードを平文で格納する。
絶対ダメ。攻撃された場合はもちろん、悪意のある内部の人間が絶対にいないとは言い切れない。

また、認証時にも平文同士で比較することになり、たとえユーザーが間違ったパスワードを入力していたとしても非常に危険となる。他の認証に使いまわすことがあるからである。

解決策はパスワードのハッシュにソルトを加えて格納すること。
パスワード文字列を不可逆な暗号化文字列に変換して比較するようにすれば良い。

不可逆な変換の簡単なものは剰余の計算である。
(適当な数値) % 3 = 2であるとき、この適当な数値が何かわからない。5でもいいし8でもいい。「結果から初期値がわからない」のが不可逆な変換。

ソルトは適当な文字列のこと。ユーザーのパスワードに適当な文字列を付与してからハッシュ化することで元の文字列がさらに判別しにくくなると言う寸法。

この辺の技術に関しては「体系的に学ぶ安全なWebアプリケーションの作り方」に詳しく載っているらしい。この前買ったから今度読む。

また、ハッシュ化する処理をSQLで行う場合、平文で扱う瞬間が存在してしまうため、アプリ側でハッシュ化する処理を実装したい。
また、パスワードがわからなくなった場合はリカバリーするのではなくリセットするのが絶対。

詳細は上記の本を読むべし。

20章 SQLインジェクション

情報処理試験でもよく見るあれ。ユーザーにSQL文を発行されて実行しちゃうやつ。

select *
from Bugs
where bug_ig = (入力された値)

という動的SQLを発行する際、入力値が1234; delete from Bugsだった場合に

select *
from Bugs
where bug_id = 1234;
delete from Bugs

というSQLが発行され、Bugsテーブルの中が消し飛ぶ。

解決策はいくつかあり、どれか1つで全ての攻撃が防げるわけではない。

  • 値のエスケープ
    文字列型の列には有効。
  • プリペアドステートメント
    絞り込むパラメータが単一の際は非常に有効
    列名や識別子等も入力されたパラメータは文字列として扱うため、select以外で使いたい場合想定した結果が得られない。
  • ストアドプロシージャ
    安全とは言い切れない。
  • データアクセスフレームワーク
    安全とは言い切れない。

状況に応じて適切に使い分けることが重要。
誰も信用してはならない。
「誰も」には自分自身も含まれている。
充分にテストされた関数のみを使い、自分で実装しようとは思わないことを意識するべきである。

21章 シュードキー・ニートフリーク(疑似キー潔癖症)

連番で振っているIDに欠番が出た際に欠番を別のデータで埋めちゃうやつ。

キモチワルイ気分になるのはわかる。わかるがやらない方がいい。

欠番に新しいデータを入れていく場合でも欠番を詰める場合でも、同時に2つの命令があった場合処理ができなくなる。
また、振りなおす前の主キーをどこかが実数値で持っていた場合、そことの整合性がおかしくなる。

解決策は欠番を埋めないこと。そりゃそうだ。

重要なことは上司を説得すること。コミュニケーションだ。

22章 シー・ノー・エビル(臭いものに蓋)

デバッグする際、自分のコードは正しいと思い込んで戻り値やエラーメッセージを無視すること。
「よくわからないけど動かないからバグ」みたいな。

qiita.com

エラーメッセージをよく読もう。
あとは使う前にドキュメントをよく読もう。

解決策は例外と戻り値をしっかりチェックすること。
それとステップ実行。どこからおかしくなるかを特定できれば対応も素早くできる。

23章 ディプロマティック・イミュニティ(外交特権)

アプリのスースコードにおいてはベストプラクティス5を採用することを良しとするが、SQLを特別視してSQLにはベストプラクティスを採用しない。

「DBはアプリとは別」という考え方があることに起因している。
アプリ設計者とDB設計者が別だったり、DB用のIDEが広く普及していなかったり、アプリとインフラの部門が分かれていたり。原因は色々挙げられる。
かく言う私の会社も「アプリ」と「ITインフラ」で部が分かれている。なるほどこれかぁ。

書き捨てならこの限りではない。このパターンいくつかあるな。

解決策はDBも品質管理の対象に入れること。

  • 文書化
  • バージョン管理
  • テスティング
  • ブランチ管理

SQL以外では常識とも言える。それをSQLに適応するだけ。

24章 マジックビーンズ(魔法の豆)

MVCの全てのモデルクラスがアクティブレコードを継承するべき、という考え方。
便利な機能を全員が使えるようにすることでジャックの魔法の豆のようにいい感じにプロジェクトが育つなんて気のせいだよ、みたいな。

どこからでもDBを触れると逆にどこに任せればいいかがわからなくなっちゃうよ…ということなのかな。ちょっと私の理解が足りていない。

解決策はモデルにアクティブレコードの管理を任せること。

ドメインモデルの責務とすることでDBの取り扱いを単一責任にするのが大事。
シンプルに取り扱う方法を追求していくといいんじゃないかな、といったところ。

25章 砂の城

想定不足。砂上の楼閣。「これが起きたら大変なことになる」に対する対応策がない状態。

対応策は知識を付けてちゃんと検討すること。
例外処理やバックアップ、ベンチマークテストの実施等を徹底しようね。

おわりに

読んでいると割と「あーあったあった」ってなってしまった。

この本を読むことによって「ジェイウォークにあたるからこの設計はやめましょう」のような指摘をしっかりとした根拠の上でできるようになった、ということが一番大きな収穫といえる。
デザインパターン同様、共通認識として会話するためには最高の良書。

逆に一番問題なのは私の周囲の人がこの本を読んでいないこと
会社が買ってくれればいいのだけれど。まぁ無理よね。次は何を読もうかね。

ではまた。


  1. ちゃんとした理由としては更新処理の際にnullチェックをかませなければならないため処理が煩雑になる、というものがある。

  2. 非常に大きな値と非常に小さな値との計算、桁数が多い場合などはDecimalでも無理。

  3. いわゆるサーティワンアイスクリーム。「B」の右側と「R」の左側色を変えて31に見せるロゴは誰でも見たことあるはず。

  4. ただの例であってバッドイメージを持っているわけではないことを明記しておく。

  5. バージョン管理やテストの自動化、ドキュメントの整備等。

一夜明けて

うぃろぅです。
何かを書かずにはいられない気分なので書きます。
推敲はせず、起承転結も意識しません。

昨日はあえて情報を入れないようにしていた。

人の命は平等、それはそうかもしれない。だが、人が生み出すものの価値は平等ではないのだ。
私や私の友人が共有できる、その未来の可能性の一部が焼却されてしまった。

アニメではなくドラマでもいい。演劇でもいい。もちろん映画でも、音楽でもいい。
誰かが生み出したものを私は享受し、そこから何かを得ている。
人が情熱をかけて生み出したものは、私の何かを変えるのだ。

高校の時は、「昨日のドラマ観た?」と同じノリで「昨日のけいおん観た?」という会話をしていた。
けいおんOP曲の歌詞がなんと言っているかを議論して、歌いながら外周走をした。
体育祭の余興でハレ晴レダンスを踊り、放課後にらき☆すたを観ながら工芸の提出物を作った。
物理のレポートを書きながらCLANNADの話をして、AFTER STORYの終盤では放送の次の日に体調不良で休んだ友人を笑った。休んだ友人は真っ赤な目をしていた。
浪人中、友人にシュタゲを薦められるまま観て、その息抜きに日常を観た。予備校では焼きサバと囲碁サッカー部の話で盛り上がった。
氷菓の原作を買い、そこから派生してインシテミルも読んだ。ChouChoさんは誕生日が同じ日で、勝手に親近感を覚えている。

こんなことばかり思い出していた。京アニの作品は私の人生に寄り添っていた。
私の人生に影響を与える作品を生み出した人たちが失われてしまったのだ。
今はただ、虚無感を抱えている。

私の通っていた高校は関東にあり、修学旅行先は海外や沖縄ではなく京都である。
「夕食の時間までに帰ってくればUSJ以外ならどこに行ってもいい」というルールのもと、各班は自由に行動計画を立てられた。
大阪に食い倒れに行った班があった。新幹線で広島に遊びに行った班があった。明石海峡大橋を渡って四国に行った班があった。

私の班は京都アニメーションの第一スタジオにアポを取り、職場見学をさせてもらった。

礼儀作法もなっていなかったであろう高校生の私たちに職員のかたは丁寧に対応してくださり、「他言無用」としてペン入れの様子を見せてくださった。「涼宮ハルヒの消失」だった。
応接室で質問を受けてくださり、ここでは書けないような内容から些細な内容まで、社内機密に当たらない質問には全て答えてくださった。

「どこかの会社の知らない人が亡くなった」では、ないのだ。
あの時に会話してくれた人たちが、昨日のあの場所に、いたかもしれないのだ。
悲しいや悔しいではなく、ただただ何もない。

この前のセールでPS4GOD OF WARを購入し、エンディングまで進めた。
主人公のクレイトスが息子のアトレウスとともに亡くなった妻を弔うための旅をする、という物語だ。
クレイトスは不器用な父親で、アトレウスに「母上が死んで悲しんでいるように見えない」と言われてしまう。
クレイトスはアキレウスに「生意気な口をきくな」と怒鳴り、叱咤するのだ。

「大声で嘆くだけが弔いだと思うな」
「私には私の弔い方がある」

今回の件に関してブログに書くのはこれが最初で最後とする。

ヴァイオレット・エヴァーガーデンの円盤を買おう。放送から10周年を迎えたけいおんへの愛を大いに語ろう。こなたのことを思い出しながらカラオケで木枯らしに抱かれてを歌おう。
元バイト先の仲間たちに「I'm proud歌って」と言われることがある。「メガネから声が出ている」と言われる。らき☆すたのEDで知ったこの曲をこれからも歌おう。
京アニの作品とともに、これまでとこれからを楽しく過ごそう。

私の弔い方をしよう。

ではまた。