JavaScriptでHelloWorld(ブラウザ上に文字表示)

JavaScriptはCUIでも実行できますが、GUIで使うケースの方が多いので、GUIでHelloWorldを書いてみました。
JavaScriptのHelloWorldはログに出力したりポップアップ表示したりするものが多いですが、ブラウザ上に出力した方がJavaScriptっぽい気がするので、ブラウザ上に表示するサンプルを提示します。

【サンプルコード1】

・Sample.html

【結果1】

・Sample.htmlをChromeで開いた結果


サンプルコード1のようにJavaScriptはHTML内に書くことができますが、サンプルコード2のようにJavaScriptを別ファイルにするケースの方が多いと思いますので、別ファイルにするサンプルも提示します。

【サンプルコード2】

・Sample.html

・Sample.js

【結果2】

結果1と同じため割愛


いかがでしたでしょうか。

JavaScriptは広く使われている言語であり、面談時点でJavaScriptのスキルを求められない現場でもJavaScriptを使うことがあります。
他言語に精通している技術者であっても、JavaScriptには慣れておいた方が無難だと思います。

テーブル結合の失敗パターン

RDBMSでのSQLによる問い合わせにおいて、複数のテーブルを結合するパターンは比較的複雑な操作になります。
そのため、ミスも起こりやすいです。

今回は、注意喚起のため、私が実務で出くわしたミスを3つ挙げます。

1.外部結合の結合条件をwhere句に書いてしまい内部結合になってしまう

2.結合条件指定不足で意図せず直積結合してしまう

3.結合条件となる値のセットを誤り結合できなくなってしまう


1.外部結合の結合条件をwhere句に書いてしまい内部結合になってしまう

例えば、以下のようなテーブルがあるとします。

・ログイン日時管理テーブル

・ユーザ名履歴テーブル

ここで、

・ユーザがログインした時のユーザ名を知りたい

・ただし、ユーザがログインした時、ユーザ名が存在しない場合がある

(この場合、ユーザ名が存在していない旨を知りたい)

 

という要件があった場合を考えます。

正しいselect文と問い合わせ結果は以下です。

もし、ここで、誤って結合条件の一部をwhere句に記述すると、下記のようになります。
左側外部結合なのに、左側のテーブルにしか存在しないレコードが抽出されません。

誤ったSQL文については下記①②のような動きになるため、このような結果になります。

① on句に指定された条件で左側外部結合を行う

② ①の結果に対して、where句とselect句が評価される。

(where句で条件指定した場合、内部結合と同じようになる)


2.結合条件指定不足で意図せず直積結合してしまう

例えば、以下のようなテーブルがあるとします。

・社員マスタテーブル

・保有資格管理テーブル

ここで、会社コードが”001″の会社の全社員が保有している資格を取得したいとします。

正しいselect文と問い合わせ結果は以下です。

もし、ここで誤って結合条件に社員コードを含めないと、以下のように実際に保有している資格数よりも多く出力されてしまいます。

なぜこうなるのかは、select文で全カラム指定すればよくわかると思います。
社員マスタテーブル.社員コードと保有資格管理テーブル.社員コードが一致しないレコードについても結合してしまうため、意図せずに直積結合が起こり、実際に保有している資格数よりも多く出力されてしまいます。


3.結合条件となる値のセットを誤り結合できなくなってしまう

例えば、以下のようなテーブルが存在するとします。

・利用料金テーブル

・利用料金明細テーブル

結合する時に指定するキーは利用番号になるため、以下のように利用番号には同一の値を使用する必要があります。

しかし、利用番号がプログラム中で生成する連番であり、かつプログラム中で利用料金テーブルと利用料金明細テーブルで別々に連番を付与していた場合、利用番号で結びつかなくなってしまいます。

こうなると、利用料金テーブルと利用料金明細テーブルを結合するという使い方ができなくなり、後で困ることになります。
更に、既にサービスイン後であり過去データの補正が必要になった場合、その補正も困難になる場合があります。


いかがでしたでしょうか。

今回は失敗パターンの説明ということで、テーブル結合の失敗パターンを取り上げました。
失敗パターンは実務をこなす中で暗黙知的に知ることが多いと思いますので、文章に起こして共有できる形にしていきたいと思います。

リスコフの置換原則とは

リスコフの置換原則とはオブジェクト指向の設計の原則の一つであり、「基本クラスをサブクラスに入れ替えても問題なく動かなけらばならない」という原則です。
もしサブクラスに入れ替えると正しく動かなくなるのであれば、クラスを利用する側はサブクラスを使用してはならないというのを意識しながらコーディングしなければならず、保守性が低下します。

クラスの入力・出力に目を向けると、以下の条件を満たすとリスコフの置換原則を満たすことができます。

1.サブクラスの入力のパターンは、基本クラスの入力のパターンよりも緩い

2.サブクラスの出力のパターンは、基本クラスの入力のパターンよりも厳しい
 

もし、サブクラスの入力のパターンが基本クラスの入力のパターンよりも厳しいと、利用する側はサブクラスを利用する際にサブクラスが許容する入力であるかどうかを意識する必要が出てきてしまいます。
また、サブクラスの出力のパターンが基本クラスの出力のパターンよりも緩いと、利用する側はサブクラスを利用する際にサブクラスが予期せぬ出力をしないか意識する必要が出てきてしまいます。

以下のサンプルコードは、割り算を行う基本クラスであるDivideクラスを、サブクラスのDivideBusinessクラス・DivideBad1クラス・DivideBad2クラスで入れ替えてみるサンプルです。

DivideBusinessクラスは、基本クラスが許容する入力パターンに加え分母=0のパターンも許容し、基本クラスが出力することがある負数を出力しません。Divideクラスと入れ替えても特に考慮するパターンが増えることはなく、リスコフの置換原則を満たした良いクラスです。
しかし、DivideBad1クラスは、基本クラスが許容する分子=負数のパターンを許容せず、基本クラスと入れ替えた場合は分子が負数になっていないか意識する必要が出てきてしまいます。
また、DivideBad2クラスは、基本クラスと異なりnullを出力するパターンがあり、基本クラスと入れ替えた場合は出力がnullになる場合を意識する必要が出てきてしまいます。

【サンプルコード】

・DivideMain.java

・Divide.java

・DivideBusiness.java

・DivideBad1.java

・DivideBad2.java

【結果】


いかがでしたでしょうか。

ポリモーフィズムを利用するとソースコードを簡潔にできるのですが、それを実現するためにはリスコフの置換原則を満たすことが必要です。
デザインパターン等で良い設計に触れている方なら自然に満たせるものかもしれませんが、今一度意識してみるのも良いかもしれません。

単一責任の原則とは

オブジェクト指向の設計ではいくつかの原則があります。
その中でも有名な原則の一つが「単一責任の原則」です。

この原則は、その名の通り「一つのクラスに持たせる責任は一つにする」という原則です。
この原則が何のために用いられるのかと言うと、複数の理由で1つのクラスを修正するのを防ぐためです。
ここで言う「理由」を「案件」、「クラス」を「ソース」と捉えると分かりやすいと思います。
複数の案件で1つのソースを修正する必要が出てしまうと、並行開発手順やバージョン管理等が煩雑になってしまい、品質に悪影響が及びます。

「一つのクラスに持たせる責任は一つにする」と捉えると具体的なイメージが浮かびにくいと思うのですが、「複数の案件で1つのソースを修正しないような粒度で設計する」と捉えると分かりやすいと思います。


以下では、javaのソースコードで例を出します。
商品の名前と値段を登録し、それを表示するという例です。

まずは悪い例からです。

【サンプルコード1】

・CommodityMain.java

・RegistShowCommodity.java

【実行結果1】


ここで、「商品の税込み価格を計算可能にする」という案件と、「表示を綺麗にする」という案件が発生したとします。
今の設計だと、どちらの案件も RegistShowCommodityクラスの修正となってしまいます。

そこで、商品の登録を行う RegistCommodityクラス、商品の価格計算を行う CalcCommodityクラス、価格表示を行う ShowCommodityクラスの3クラスに分割します。

【サンプルコード2】

・CommodityMain.java

・RegistCommodity.java

・CalcCommodity.java

・ShowCommodity.java

【実行結果2】


3クラスに分割した結果、「商品の税込み価格を計算可能にする」という案件は CalcCommodityクラスの修正、「表示を綺麗にする」という案件は ShowCommodityクラスの修正で対応できるようになり、別々の案件で1つのクラスを修正する状態を防ぐことができました。

【サンプルコード3】

・CommodityMain.java

・RegistCommodity.java

・CalcCommodity.java

・ShowCommodity.java

【実行結果3】


いかがでしたでしょうか。

オブジェクト指向の設計と言うと覚えることが多くて大変だという印象を持つかもしれませんが、いくつかの原則を抑えるだけでも良い設計ができます。
(デザインパターンも、原則に則って設計を考えると自ずと導き出されるものだと思っています)

単一責任の原則は代表的な原則の一つなので、覚えておいて損はないと思います。

「現行機能の踏襲」という要件定義のまずさと、苦し紛れの対応策

私自身は幸運なことに、要件が「現行機能の踏襲」であるプロジェクトに携わったことはないのですが、要件がこれに近いプログラムをプライベートで作成しているので、参考までにその経験を記事として残します。


私が作成しているプログラムはとあるゲーム(RPG)の戦闘部分の計算を行うシミュレータであり、ゲーム攻略のために作成しています。
要件としては下記であり、同じ計算が行われるということ自体に価値があるプログラムです。

・ゲームで行われる計算と同じ計算を実装する

・諸事情によりゲームを解析することはできない

・ゲームには場当たり的な対応やバグが多数含まれており、それも仕様として実装する

 

これを実務に置き換えると以下のような要件であり、限りなく「現行機能の踏襲」に近い状況です。

・現行システムと同じ機能を実装する

・諸事情により現行のソースコードを入手できない

(他ベンダーが開発、ソースコード紛失、等の理由で)

・場当たり的な保守・運用が多数行われており、それも仕様として実装する


私が作成しているプログラムで最も複雑な部分は、攻撃手段毎のダメージ計算式です。
攻撃手段は数百種類存在し、それぞれ異なった計算を行う必要があります。

本来であれば、各々の攻撃手段毎で共通するルールが存在するため、そのルールを抽象化して実装することで保守性を確保することができます(図「本来あるべき姿」)。
(「本来」というのは、「シリーズ初期は」や「運用当初は」といった言葉で置き換えてください)

しかし、実態としては、場当たり的な対応が行われる過程でそのルールを逸脱する計算が行われる攻撃手段が存在します(図「実態1」)。
また、実装の誤りにより、攻撃手段によっては一部計算式が異なる物もあります(図「実態2」)。

ゲームを解析することができれば、既存のソースコードをそのままコンバートしたり、ロジックを真似ることで現行のソースコードと同程度のレベルで抽象化したりすることができるのですが、今回はその手は使えません。
更に、実装を始めた段階では現行のゲームの全ての挙動が判明しているわけではなく、プログラムを作ってみて初めて判明する挙動というのも存在するため、このことも開発の難易度を高める要因となっています。
つまり、十分な抽象化が行えない状況で、将来の変更(新たに見つかった挙動への対応)を見据えたプログラミングを行う必要があります。

私が作成しているプログラムでは、下記のような対応をとっています(図「対応策」)。

1.攻撃手段毎にクラス分けする

一番避けなければならないのが、攻撃手段の分岐を計算式中で多様することでソースコードが複雑化し、保守困難になることである。
そこで、ルールに従った抽象化を行わず、攻撃手段毎にクラス分けした。
重複した記述は多くなるが、ある攻撃手段に対する変更が他の攻撃手段に影響を及ぼすのを避けることはでき、攻撃手段毎の処理内容を追うことも容易になる。
(なお、小規模な開発である場合、「クラス」は「関数」に置き換えても良い)

2.各々のクラスで使用する計算式は部品化する

ダメージ計算式は分解可能であるため、分解した計算式を部品(共通関数)として用意し、その部品を組み合わせることで各々のクラスの実装を可能とした。
攻撃手段毎の微妙な計算式の違いを、部品の組み換えや追加で吸収することが可能になり、保守性が向上する。

このプログラムは公開してから(使い物になる状態になってから)数年経っていますが、今の所は保守困難な状態にはなっていません。
しかし、十分に抽象化を行えなかった影響で、保守性は正直良くありません。
攻撃手段の数だけコピペが必要になるような対応も何回か発生しています。


「現行機能の踏襲」という要件はビジネス上の観点から見ても問題がある(業務改善ができない)と思いますが、実装上も

・共通ルールを見つけるのが困難で、十分な抽象化を行えない

・作ってから初めて明らかになる仕様があり、変更を見こさなければならない

 

という困難さがあり、実装を工夫したとしても十分な保守性を確保するのは難しいです。
保守性が悪いということは、将来の開発でQCDを確保することも困難になります。
「現行機能の踏襲」という要件は、開発コストの低減やリスクの低減に繋がらないと考えた方が無難だと思います。
今回の例のように、現行機能通りにすること自体に価値があるのであれば別ですが、そうでなければ、要件を「現行機能の踏襲」とすることには慎重になる必要があります。


プライベートでもプログラムを作っているので、その中で仕事に活かせそうなスキルや経験が得られることがあります。
また何かあれば、記事にしていきたいと思います。