マスターテーブルとenum
昨日、三浦カズヒトさんと Twitter でやりとりさせてもらった。
俺、設計とか技術とかそういうのに疎いから…うまく言語化できるか解らんけど。「マスター(リソースとも言う?)テーブルで「コードと名前」くらい持ってるけど、結局「そのコードの意味はプログラムが理解せねばならん」から「マスタと同数のenum持ってる」とか、許すならenumに落としたい。
— 三浦カズヒト(英語9点の生きにくさ) (@kazuhito_m) 2016年5月26日
あまり深く考えずにリプライしてしまい中途半端にしか答えられなかったので、ツイッターの文字数制限に縛られない場所で自分の考えをまとめてみた。
問題
データベースのマスターテーブルにキーと名称のペアがあったとして、それと同じペアをenumや定数でプログラムに持つのが、果たして良いことなのか。
どうせマスターにペアを追加するときはそれに対応するプログラムをデプロイするし、マスターとプログラムの両方を手直しするならマスターなんて持たずにプログラムだけで対応すれば良いのではないだろうか。
マスターに名称を持つ価値は、せいぜい急な名称変更に対応出来る程度なのではないか。
マスターとenumの両方を持つ理由
マスターには値の名称を持ち、enumではそのマスターに応じた処理を分岐させるための定数とするよう、両方を持つのだと思う。
例えば、マスターに以下のようなものがあったとする。
種別番号 | 種別名 |
---|---|
0 | 大 |
1 | 中 |
2 | 小 |
それに応じたenumを用意すると、こうなる。
public enum Kind { DAI, // 種別番号 0「大」 を表す CYU, // 種別番号 1「中」 を表す SYO // 種別番号 2「小」 を表す }
で、種別ごとに何らかの処理を実装すると、こうなる。
public void someProcess(Kind kind) { switch (kind) { case DAI: // 種別番号 0「大」 の時の処理 break; case CYU: // 種別番号 1「中」 の時の処理 break; case SYO: // 種別番号 2「小」 の時の処理 break; } }
このような実装をしていたら、マスターに種別を増やした場合にそれに応じたenumを増やして、更にそのenumを使っている箇所に処理を追加しないとならない。
例えば「極小」を追加したらマスターは下記のようになる。
種別番号 | 種別名 |
---|---|
0 | 大 |
1 | 中 |
2 | 小 |
3 | 極小 |
それに応じてenumの「GOKUSYO」も増やす。
public enum Kind { DAI, // 種別番号 0「大」 を表す CYU, // 種別番号 1「中」 を表す SYO, // 種別番号 2「小」 を表す GOKUSYO // 種別番号 3「極小」 を表す }
さらに「case GOKUSYO」の処理を増やす。
public void someProcess(Kind kind) { switch (kind) { case DAI: // 種別番号 0「大」 の時の処理 break; case CYU: // 種別番号 1「中」 の時の処理 break; case SYO: // 種別番号 2「小」 の時の処理 break; case GOKUSYO: // 種別番号 3「極小」 の時の処理 break; } }
種別が変更になるたびにマスターとenumと処理を増やすのは、正直しんどい。
だったらマスターを捨ててenumだけで良いのでは?というのも分かる。
マスターだけにする
種別に応じた処理をマスターに基いてやるようにすれば、enumも処理も不要になるケースがある。
例えばマスターに処理をするのに必要なパラメータを登録するというやり方。
種別番号 | 種別名 | パラーメータ1 | パラーメータ2 |
---|---|---|---|
0 | 大 | 789 | JKL |
1 | 中 | 456 | GHI |
2 | 小 | 123 | DEF |
3 | 極小 | 0 | ABC |
処理はこのようになる。
public void someProcess(int kind) { int param1 = getParam1(kind); // マスターから種別番号に応じたパラメータ1を取得 String param2 = getParam2(kind); // マスターから種別番号に応じたパラメータ2を取得 // param1とparam2を基に処理する }
処理は同じだが種別によってパラメータが違うという場合なら、これで十分対応が可能と思う。
これならマスターだけをメンテすれば万事OK。
マスターもenumも持たない
マスターがキーと値のペアだけだったら、マスターを持たないという選択もありだと思う。
かと言って、enumと処理がバラバラだとそれはそれでしんどいので、もうマスターもenumも持たないようにしたい。
マスターをなくしたので、種別に応じた種別名を取得する仕組みを作る。
ひとつの答えはインターフェースかと思う。
public interface Kind { public String getName(); // 種別名を返す }
で、種別に応じた実装クラスを用意する。
public class Kind0 implements Kind { public String getName() { return "大"; } } public class Kind1 implements Kind { public String getName() { return "中"; } } public class Kind2 implements Kind { public String getName() { return "小"; } } public class Kind3 implements Kind { public String getName() { return "極小"; } }
これで元々マスターにあった「キーに応じた値」を得ることは出来る。
せっかくインターフェースにしたなら、冒頭にあった
public void someProcess(Kind kind) { switch (kind) { case DAI: // 種別番号 0「大」 の時の処理 break; case CYU: // 種別番号 1「中」 の時の処理 break; case SYO: // 種別番号 2「小」 の時の処理 break; } }
この種別が増えるたびに分岐が増える汚コードもすっきりさせられる。
インターフェースに、この時にするべき処理を定義する。
public interface Kind { public String getName(); // 種別名を返す public void someProcess(); // するべき処理 }
で、種別に応じた「するべき処理」を実装する。
public class Kind0 implements Kind { public String getName() { return "大"; } public void someProcess() { // "大"の時にするべき処理 } } public class Kind1 implements Kind { public String getName() { return "中"; } public void someProcess() { // "中"の時にするべき処理 } } public class Kind2 implements Kind { public String getName() { return "小"; } public void someProcess() { // "小"の時にするべき処理 } } public class Kind3 implements Kind { public String getName() { return "極小"; } public void someProcess() { // "極小"の時にするべき処理 } }
そうすれば、汚コードもすっきりする。
public void someProcess(Kind kind) { kind.someProcess(); }
呼び出し元はインターフェースしか見えないので実際に何が実行されるかは、渡ってきたKindの実装クラスに応じて変わってくる。
変数kindはKind1のインスタンス参照かもしれないし、Kind2のインスタンス参照かもしれない。
実装はすっきりしたので、あとはインスタンスの生成だが、ここは局所化すれば汚コードでも良いと思う。
public class KindFactory { public static Kind create(int id) { switch (id) { case 0: return new Kind0(); case 1: return new Kind1(); case 2: return new Kind2(); case 3: return new Kind3(); } throw new IllegalArgumentException(id+"って何よ?"); } }
マジックナンバーが嫌だというなら、内部的なenumを使うのも良いかもしれない。
public class KindFactory { private enum K { DAI, // 種別番号 0「大」 を表す CYU, // 種別番号 1「中」 を表す SYO, // 種別番号 2「小」 を表す GOKUSYO // 種別番号 3「極小」 を表す } public static Kind create(int id) { if (K.DAI.ordinal()==id) { return new Kind0(); } else if (K.CYU.ordinal()==id) { return new Kind1(); } else if (K.SYO.ordinal()==id) { return new Kind2(); } else if (K.GOKUSYO.ordinal()==id) { return new Kind3(); } throw new IllegalArgumentException(id+"って何よ?"); } }
いやいやそれでも条件分岐が多くて汚コードは嫌だと言うなら、idに応じたクラスをプロパティファイルに書いて、リフレクションでインスタンスを作ると条件分岐がなくて尚良しかと。
まとめ
そうは言っても状況によって求められることは違うので、ここで書いたことが絶対に正しいとも思わないし、プロダクトによって答えは変わってくるかと。
今更聞けない基礎英語
中学レベルに立ち返って基礎英語をまとめている。
このエントリーを随時更新する予定。
インデックス
be動詞、一般動詞
be動詞
一般動詞
疑問文、否定文
be動詞の疑問文、否定文
一般動詞の疑問文、否定文
疑問詞
命令文
命令文「~しなさい」
否定命令文「~するな」
命令文(依頼)「~てください」
命令文(勧誘)「~しましょう」
三人称単数現在
sのつけかた
現在進行形
動詞のing形
疑問文
否定文
助動詞
疑問文
否定文
be動詞、一般動詞
be動詞
「~である」「~です」のように、主語と動詞以降の文がイコールであることを表現するのに用いるのがbe動詞。
■語順:
主語 + be動詞
■例:
・I am a travel journalist.
→ 私は旅行記者です。
・You are a student.
→ あなたは生徒です。
・He is a boss.
→ 彼はボスです。
be動詞には以下の8種類がある。
be動詞 |
---|
be |
am |
are |
is |
was |
were |
been |
being |
be動詞というだけあって、beが原形で、主語や時間軸などによって使い分ける。
be been being was were は一回忘れて、それ以外の am are is は主語によって下記のように使い分ける。
主語 | be動詞 |
---|---|
I | am |
you | are |
その他 | is |
■例:
・I am a travel journalist.
→ 私は旅行記者です。
・You are a student.
→ あなたは生徒です。
・He is a boss.
→ 彼はボスです。
疑問文、否定文
be動詞の疑問文、否定文
be動詞を用いた疑問文はbe動詞を前に持ってくる。
■語順:
be動詞 + 主語 + ・・・?
■例:
・Are you a student?
→ あなたは生徒ですか?
be動詞を用いた否定文はbe動詞の後ろにnotを入れる。
■語順:
主語 + be動詞 + not
疑問詞
疑問詞には以下の8種類がある。
疑問詞 | 意味 |
---|---|
how | どのように |
what | 何 |
when | いつ |
where | どこ |
which | どちら |
who | 誰が |
whose | 誰の |
why | なぜ |
疑問詞は文の先頭に使用する。
■語順:
疑問詞 + ・・・
命令文
命令文には「~しなさい」と強い言い方から「~してください」や「~しましょう」なども含まれる。
共通して言えるのは「主語がない」ということ。
また命令文の否定形もある。
命令文「~しなさい」
主語を取り除き、動詞の原形が先頭に来る。
■語順:
動詞の原形 + ・・・
■例:
・Study English.
→ 英語を勉強しなさい。
be動詞の場合は、be動詞の原形のbeになる。
否定命令文「~するな」
Do notを先頭につけて、Do not + 動詞の原形にする。
■語順:
Do not + 動詞の原形
■例:
・Do not study English.
→ 英語を勉強するな。
be動詞の場合でも、Do not を先頭につける。
命令文(依頼)「~てください」
Pleaseを先頭につけて、Please + 動詞の原形にする。
■語順:
Please + 動詞の原形
三人称単数現在
一般動詞は、主語が三人称で、単数で、現在文の時だけsがつく。
(いわゆる「三単現のs」)
代名詞だと、He や She や It で、一般の名詞だと代名詞にした時に He や She や It になるものが対象。
■例:
・He plays basketball.
→ 彼はバスケットボールをする。
・My sister studes English.
→ 私の妹は英語を勉強する。
(My sister は代名詞にすると She になる)
・Mr.Foo speaks English.
→ フーさんは英語を話す。
(Mr.Foo は代名詞にすると He もしくは She になる)
以下の例は三人称単数現在ではないのでsはつかない。
現在進行形
現在行っていることを表す。
主語 + be動詞 + 動詞のing形
※be動詞が入るのに注意
■例:
・I am studying English.
→ 私は英語を勉強している。
・You are speaking English.
→ あなたは英語を話している。
・He is playing basketball.
→ 彼はバスケットボールをしている。
動詞のing形
動詞に ing をつければ良い。
■例:
・study
→ studying
・speak
→ speaking
・play
→ playing
最後が e で終わる動詞は、その e を取って ing をつける。
疑問文
be動詞を前にする。
■語順:
be動詞 + 主語 + 動詞のing形 + ・・・?
助動詞
主語と動詞の間に置き、動詞の意味を助ける働きをする。
■語順:
主語 + 助動詞 + 動詞の原形
助動詞には以下のようなものがある。
助動詞 | 意味 |
---|---|
can | ~できる |
may | ~かもしれない |
will | ~する(未来形) |
must | ~するべき |
■例:
・You can speak English.
→ あなたは英語を話すことができる。
・You may speak English.
→ あなたは英語を話すかもしれない。
・You will speak English.
→ あなたは英語を(将来的に)話す。
・You must speak English.
→ あなたは英語を話すべき。
疑問文
助動詞を先頭に持ってくる。
■語順:
助動詞 + 主語 + 動詞の原形 + ・・・?
否定文
助動詞の後ろに not を入れる。
■語順:
主語 + 助動詞 + not + 動詞の原形
■例:
・You can not speak English.
→ あなたは英語を話すことができない。
・You may not speak English.
→ あなたは英語を話さないかもしれない。
・You will not speak English.
→ あなたは英語を(将来的に)話さない。
・You must not speak English.
→ あなたは英語を話すべきではない。
Windows10にアップデートされないよう非表示にするWindowsUpdate更新プログラム番号(Windows7版)
以下を非表示に。
・KB2952664
・KB3021917
・KB3035583
・KB3112343
SQLiteのジャーナルモード
2つのSQLiteのデータベースファイルがあり、それぞれ更新すると一方は「-journal」ファイルが作成されて、もう一方は「-shm」と「-wal」ファイルが作成されるという違うファイルが作成される理由がわからず調べたら、ジャーナルモードが違ったというお話。
ジャーナルモードの大分
SQLiteのジャーナルモードは大きく分けて「delete」と「wal」がある。
「delete」の時は「-journal」ファイルが作成され、「wal」の時は「-shm」と「-wal」ファイルが作成される。
例えば「foo」というデータベースファイルがあると、ジャーナルモードが「delete」の時は「foo」というファイルの他に「foo-journal」というファイルが作成され、ジャーナルモードが「wal」の時は「foo-shm」と「foo-wal」というファイルが作成される。
どちらもジャーナルファイルなので、本体のデータベースにコミットするまでのデータが書き込まれるという意味では同じだが、動作は若干異なる。
ジャーナルモードの詳細は本家のサイトを参照。
http://www.sqlite.org/pragma.html#pragma_journal_mode
deleteモード
トランザクション開始から終了までの更新内容を「-journal」ファイルに書き込み、コミットしたときに本体のデータベースファイルへ更新内容を反映し、反映したら「-journal」ファイルを削除する。
最後の「-journal」ファイルを削除する動作に因んで「deleteモード」と呼んでいる。
コミットした時点でデータベースファイルを更新するため、大きなデータベースファイルの場合は書き込みに時間がかかるかもしれない。(未検証)
walモード
トランザクション開始から終了までの更新内容を「-shm」ファイルに書き込み、コミットした時に「-wal」ファイルへ更新内容を書き込む。
コミットした時点では本体データベースファイルには更新内容を書き込まないため、本体に比べて小さいファイルの更新でトランザクションを完了できるメリットがある。
なのでデータ更新が多いシステムではwalモードの方が有利と言える。(未検証)
ただしデータが本体データベースファイルとwalファイルに分散されるため、データの取得には時間がかかるかもしれない。(未検証)
walファイルはデータベース接続を閉じる時に、その内容を本体データベースファイルに反映する。
つまりクローズ処理はdeleteモードに比べて時間がかかる。
永続性の違い
先の通り、deleteモードはコミットした時点で本体データベースに更新を反映する一方、walモードではコミットしても本体データベースに更新を反映しない。
そのため、もしコミット後にシステムが異常終了等した場合、walモードでは本体データベースに更新を反映するためのリカバリが必要になる。
その他のジャーナルモード
上の2つ以外にも以下のようなジャーナルモードがある。
truncateモード
deleteモードの亜種で、コミットしても「-journal」ファイルを削除せず、0バイトのファイルを残しておく。
ファイル作成時の権限等のリスクを減らすには有利かもしれない。
データベースを閉じて再度接続するとdeleteモードになる。
persistモード
deleteモードの亜種で、コミットしても「-journal」ファイルの内容を削除せずに取っておく。
データベースを閉じて再度接続するとdeleteモードになる。
memoryモード
ジャーナルをファイルではなくメモリで管理する。
とにかく動作を速くしたい場合は選択する価値があるかもしれないが、メモリの容量に注意が必要かもしれない。
データベースを閉じて再度接続するとdeleteモードになる。
offモード
ジャーナリングしない・・・ということか?(未検証)
まとめ
deleteモードかwalモードはそれぞれメリット・デメリットがあるので、システムの特性に合わせて適切に使い分けましょう。
DAG (Directed acyclic graph) - 有向非巡回グラフ
先日、JJUG CCC 2015 Fallに参加して、灰色のサイトで有名なひしだまさん(ひしだま (@hishidama) | Twitter)の「GH-6 Java8 Stream APIとApache SparkとAsakusa Frameworkの類似点・相違点」を聞いた際に出てきた「DAG (Directed acyclic graph) - 有向非巡回グラフ」が面白そうだったので調べてみた。
DAGとは
Wikiによると、
グラフ理論における閉路のない有向グラフの事。
有向グラフは頂点と有向辺(方向を示す矢印付きの辺)からなり、辺は頂点同士をつなぐが、ある頂点 v から出発し、辺をたどり、頂点 v に戻ってこないのが有向非巡回グラフである。
となっている。
DAGの例
Wikiの例はわかりづらいので、JJUG CCC 2015 Fallのひしだまさんのスライドを見ると、18枚目に例が載っている。
左は向きがない(無向)なのでDAGではない。
中央は菱型の条件分岐で上に戻っている(巡回)のでDAGではない。
右は向きがあって(有向)条件分岐があるが循環していない(非巡回)のでDAGであると言える。
ひしだまさんの発表は、DAGで例題を示し、Stream APIとSparkとAsakusaFWの相違点について説明するという流れになっていた。
説明も聞いていたが、自分はこのDAGが気になってしまって、心の半分がDAGに持って行かれていたことは内緒としておきたい・・・。
DAGの活用
DAGを使うと、今まで言葉でしか説明していなかったものや、なんとなく図式化していたものを明確に説明できるのではないだろうか。
例えば
社員一覧画面は、社員マスタからデータを取得し表示する。
ただし所属は所属マスタから取得し表示すること。
のようなものは下記のように表せる。
うん。わかりやすい。
もう少し複雑な例だと日締め処理のバッチなど。
その日一日の取引内容を集計する。
集計結果はPDF化し担当部署長宛にメールにて通知する。
のようなものは下記のように表せる。
ここまでやってて、もっとDAGを用いるのに適した分野があることに気付いた。
そう、料理のレシピだ。
DAGを用いた料理のレシピ
例えばみんなのきょうの料理に掲載されている「かじきのガーリック炒めレシピ」だと、このようになる。
ちなみにカレーのレシピはこのようになる。
このように、DAGの応用範囲は広いということがわかった。
料理のレシピはDAGでも併せて記述するべきだと思う。
JavaのStream API(IntStream)を触ってみた
調子に乗ってIntStreamも使ってみた。
UNIX/Linux系アプリにありがちな、実行時引数に「-」を使って色々なパラメータを渡すアレを受け取る処理を書いてみた。
例題として以下のパラメータを使用する。
- -c → 設定ファイルの指定
- -i → 入力ファイルの指定
- -o → 出力ファイルの指定
例えば以下のようにパラメータを指定する。
FooApplication -c configFile -i inputFile -o outputFile
当然、順番を変えて以下のように指定しても同じ意味となる。
FooApplication -i inputFile -c configFile -o outputFile
Javaの場合、mainメソッドの引数「String[] args」にそれぞれの値が配列に格納されて渡ってくるので、今回はそれをIntStreamを使って処理をする。
処理結果はMapに詰めて、キーにスイッチの値( -c など)を、バリューにそのスイッチの値 (configFile)を詰めることにする。
コードで書いた処理結果のイメージは以下の通り。
Map<String, String> map = new HashMap<>(); map.put("-c", "configFile"); map.put("-i", "inputFile"); map.put("-o", "outputFile");
Stream APIを使わなかった場合は以下の通り。(諸々のエラー処理は省略)
Map<String, String> map = new HashMap<>(); for (int i=0; i<args.length; i++) { if (args[i].startsWith("-")) { map.put(args[i], args[i+1]); } }
これがStream APIを使うと以下の通り。(諸々のエラー処理は省略)
Map<String, String> map = new HashMap<>(); IntStream.range(0, args.length) .filter(i -> args[i].startsWith("-")) .forEach(i -> map.put(args[i], args[i+1]));
・・・たぶん、IntStreamはこういう使い方をするためのものではないよね。
うん。知ってて使ってみた。
JavaのStream APIを触ってみた
読み込んだテキストファイルのうち「#」で始まる行と空行を除外するという処理を、せっかくだからStream APIで実装してみた。
Stream APIを使わなかった場合は以下の通り。
List<String> result = new ArrayList<>(); // 対象のテキストファイルを読み込んで、各行の内容を含んだListを受け取る List<String> lines = readAllLines(); for (String line: lines) { // #から始まる行は読み飛ばす if (line.trim().startsWith("#")) { continue; } // 空行も読み飛ばす if (line.trim().length()==0) { continue; } result.add(line); }
これがStream APIを使うと以下の通り。
// 対象のテキストファイルを読み込んで、各行の内容を含んだListを受け取る List<String> lines = readAllLines(); List<String> result = lines.stream() .filter(line -> !(line.trim().startsWith("#"))) // #から始まる行は読み飛ばす .filter(line -> !(line.trim().length()==0)) // 空行も読み飛ばす .collect(Collectors.toList());
実にシンプル。
これは気持ち良いかも。
for文禁止にしたい気持ちが、今更ながらわかってきた。