技術とか戦略とか

証券レガシーシステムを8年いじってから転職した普通の文系SEによるブログ。技術のみではなく趣味の戦略考察についても。

java:プリミティブ型とラッパークラスの暗黙の型変換

最近知ったのですが、intやcharに代表されるプリミティブ型と、IntegerやCharacterに代用されるラッパークラスの間では暗黙の型変換が行われるそうです。
プリミティブ型からラッパークラスへの暗黙の型変換をオートボクシング、ラッパークラスからプリミティブ型への暗黙の型変換をアンボクシングと呼ぶそうです。
 
例えば、intとIntegerを明示的に型変換すると以下のようになります。
 
【サンプルコード】
            // 変数定義
            int primitive = 1;
            Integer wrapper = null;

            // プリミティブ型からラッパークラスへの変換
            wrapper = new Integer(primitive);

            // ラッパークラスからプリミティブ型への変換
            // (変換が正しく行われればif文の中に入り変数の中身を出力)
            if (primitive == wrapper.intValue()) {
                System.out.println(wrapper.intValue());
            }
 
【結果】
1
 
しかし、以下のように、暗黙の型変換に頼ったコードでも通ります。
 
【サンプルコード】
            // 変数定義
            int primitive = 1;
            Integer wrapper = null;

            // プリミティブ型からラッパークラスへの変換
            wrapper = primitive;

            // ラッパークラスからプリミティブ型への変換
            // (変換が正しく行われればif文の中に入り変数の中身を出力)
            if (primitive == wrapper) {
                System.out.println(wrapper);
            }
 
【結果】
1
 
明示的に型変換しなくても動くのが個人的に気持ち悪かったのですが、それを可能とする仕組みがjavaに備わっているというのが調べてわかって少し安心しました。
(それでも私の場合は明示的に型変換すると思いますが。あくまでも異なる型だというのを意識するために。)

システム障害が起きた時に元請けSIerの中で起こること(体験談)

二次請けや三次請けで本当に開発しかしない職場だと、システム障害が起きた時にその対応がどれくらい大変なのかが伝わりにくいことがあります。
今回の記事は、システム障害が起きた時に元請けSIerでどのような対応を行う必要があるのかを書きたいと思います。
「緊急対応(手運用やプログラム修正)」「障害発生個所の復旧」「お客様向けの文書の作成」「再発防止策」といった対応が必要になるのですが、客観的なことを書いてもなかなかリアルさが伝わりにくいので、あえてシステム開発者である私の体験談を書くことにします。
(機密情報等があるので、ブログに書いても問題のない形に加工します)
 
システム開発者目線の体験談のため「なんか残業多そうで大変」ぐらいしか伝わらないかもしれませんが、障害を掴まされるお客様も大変な思いをします。業務が滞るためお客様もお客様で色々な所に説明しなければなりませんし、障害の復旧に付き合うことになります。主に経営レベルでお客様からの信頼も失いますし、システム障害を繰り返すと本当に顧客離反を招きます。
 
この記事が、品質向上の機運を高めるきっかけの一つになれば幸いです。
 
-----------------------
 
【n月m日(日)】
機能向上のため、既存システムの改修のリリースを実施。
 
【n月m+1日(月)~n月m+2日(火)】
リリース後の稼働確認を実施し、問題なしと判断。
 
【n月m+3日(水)】
いつも通りに始業した所、お客様窓口の担当者から連絡が来る。
「帳票を使ってるお客さんで、0円になってる項目がいつもより多いって言ってきてるんだけど、見てもらえる?」
 
調査を始める。
開発時のテスト結果の見直しや、再テストを行った所、n月m日のリリースで改修ミスがあったことを発見。
偉い人に報告する。当然怒られる。
そして、それをお客様に報告する偉い人もお客様に怒られる。
(機能向上になるはずが、今まで使えていた機能が使えなくなってしまい業務が滞ったので、お客様から見たら当然怒りたくなる)
 
偉い方々を招集し、緊急で対応会議を行う。
その結果、問題のあったプログラムは緊急で修正・リリースし、翌日以降の影響を防ぐことにした。
問題のある帳票(3日分)は土曜日に再作成し翌週提供することとなった。
(日曜日は予備日)
 
同じ担当の開発者を集めて、プログラムの修正とリリース作業を当日中に緊急で行った。
それとは別にお客様への報告文書も必要になるため、それも作成した。
 
【n月m+4日(木)~n月m+5日(金)】
緊急リリースしたプログラムの稼働確認を実施。問題なし。
 
お客様への報告文書の作成や、土曜日の帳票修正作業の計画に追われる。
自分の担当だけでなく他の担当とも連携するシステムであるため、帳票修正作業の調整で時間がかかる。
他の担当の人から見れば巻き込まれた形なので、当然文句を言われる。
 
【n月m+6日(土)】
土曜日に出勤し、帳票修正作業を実施。
帳票修正作業は終了し、予備日は使わずに済んだ。
 
【n月m+8日(月)~】
お客様に対応完了の報告文書を提出した。
しかし、再発防止策を立てて実行し、その結果もお客様に報告しなければならない。
 
障害対応に追われて先週やるつもりであった開発作業ができなかったため、他のプロジェクトの進捗にも影響を及ぼすことになった。

「共通化の罠」を見た感想

数日前にTwitterで「共通化の罠」という動画がバズりました。

https://togetter.com/li/1354658

 
内容はリンク先の動画を見ての通りなのですが、一言でまとめると「複数のモジュールで同じロジックを使用していたので、そのロジックを共通モジュールとして外に切り出した。しかし、共通モジュールに個別の呼び出し元モジュールに特化した処理が保守対応で次々と入り込み、共通モジュールを呼び出しているモジュールが密に結びついてしまった(各モジュールを別々のシステムで動かすことが困難になった)。」という話です。
 
恐らく、上記で言う「同じロジック」というのは、「似ているビジネスロジック、ある時点では同じだったビジネスロジック」と書いた方が正確でしょう。
「似ているけど、それぞれの業務でロジックが微妙に違う。変化も多い。」というのはビジネスロジックあるあるなので、ビジネスロジックは安易に共通化せず、システム基盤となるような機能を積極的に共通化するのが良い設計方針だと思います。
 
しかし、似たようなビジネスロジックが点在しているのもそれはそれで問題(改修時に影響見落としやテスト工数増を招く)なので、ビジネスロジックも何らかの方法で共通化する必要はあります。
ビジネスロジックを共通化する設計方針としては、「これ以上分割できないという単位で機能毎に部品化・共通化する」というのが良い設計方針だと思っています(モジュールにするには1単位1単位が小さすぎるのであれば、共通関数でも良いです)。その共通モジュール(共通関数)を組み合わせればそれぞれの業務の処理ができる、という状態にするのが理想です。
 
例えば、RPGで魔法を使った時の処理を実装する際、ダメージ計算式は共通でも、魔法の種類毎に「命中判定の有無」「ダメージの振れ幅(乱数化)の有無」といった細かい違いがあったりします。そのような場合は、「ダメージ計算」「命中判定」「ダメージ乱数化」といった単位で共通モジュール(共通関数)を作成し、「魔法Aは命中判定有で乱数化無だから「ダメージ計算」「命中判定」を呼び出す」「魔法Bは命中判定有で乱数化有だから「ダメージ計算」「命中判定」「ダメージ乱数化」を呼び出す」といった形でそれぞれの魔法の処理が可能になります。
 
保守対応で業務毎に新たな差異が発生するようになる、というのは良くある話です。その場合は、新たな共通モジュール(共通関数)を作成し対応します。
RPGの例で言うと、新作で魔法Aと魔法Bで別々の命中判定式になった場合は、「命中判定」の共通モジュール(共通関数)を「命中判定A」「命中判定B」の二つに分け、「魔法Aは「命中判定A」、魔法Bは「命中判定B」を使用する」という形にして対応します。
 
「これ以上分割できないという単位で機能毎に部品化・共通化する」という方針であれば、「各モジュールを別々のシステムで動かす」なんて話が出てきても容易に対応できます。各モジュール+各モジュールで呼び出している共通モジュールのみを別システムに持ち出せば良いだけになります(RPGの例で言うと、魔法Aなら「ダメージ計算」「命中判定A」だけ持ち出せば良い)。密に結びついたモジュールを大量にソース修正して無理矢理引きはがしたり(移植で大量の工数が必要になったり)、移行先システムでは使用しない機能まで丸ごと持ち出したり(移植先システムの保守性が悪化したり)することは無くなります。
 
情報処理技術者試験で出題される「モジュール強度(

https://akira2kun.hatenablog.com/entry/2018/07/07/144209

)」に似たような話が出てくるので、もう一度おさらいしても良いかもしれません。動画の例は、フラグ制御(どのモジュールから呼び出しているのかの情報を受け渡し)で結合する「論理的強度」のレベルになってしまった例である、と考えています。

過去のコミット情報が見れない、直近のコミットをrevertしたら全てのファイルが消えた→深さ1でクローンしているのが原因かも

表題が全てを表しています。
 
グーグルで調べても出てこなかったので正直焦りました。
深さ(depth)1でクローン(clone)して、直近のコミット履歴のみ取得したら、そりゃ過去のコミット情報も見れなければ、その直近のコミットをrevertしたら全てが消えますよね…。
最新のソースを見たいだけの時は深さ1でクローンした方が時間短縮になって良いのですが、過去のコミット履歴が必要な作業を行う時はNGです。

java:暗黙の型変換による意図しない小数点以下切り捨て

原因がわかるまでに手間取ってしまったので、記録として残しておきます。
double型やBigDecimal型の変数の初期値を分数(例:2/3)で定義する際、小数点を入れないと「int型変数/int型変数」と判断されてしまい、小数点以下が切り捨てられた状態で変数に格納されてしまいます。
「2.0/3.0」なら可なのですが、「2/3」は不可です。
 
以下、サンプルコードです。
 
【失敗例】
ソースコード
package main;

import java.math.BigDecimal;

public class Main {
    public static void main(String args) {

     // double型(実行速度重視)
     double x = 2/3;
     System.out.println("double型変数の場合");
     System.out.println("期待値:0.6666666…");
     System.out.println("実際の値:"+x);

     // BigDecimal型(丸め誤差を気にする場合)
     BigDecimal y = BigDecimal.valueOf(2/3);
     System.out.println("BigDecimal型変数の場合");
     System.out.println("期待値:0.6666666…");
     System.out.println("実際の値:"+y.doubleValue());

    }
}

・実行結果
double型変数の場合
期待値:0.6666666…
実際の値:0.0
BigDecimal型変数の場合
期待値:0.6666666…
実際の値:0.0

 

【成功例】
ソースコード
package main;

import java.math.BigDecimal;

public class Main {
    public static void main(String args) {

     // double型(実行速度重視)
     double x = 2.0/3.0;
     System.out.println("double型変数の場合");
     System.out.println("期待値:0.6666666…");
     System.out.println("実際の値:"+x);

     // BigDecimal型(丸め誤差を気にする場合)
     BigDecimal y = BigDecimal.valueOf(2.0/3.0);
     System.out.println("BigDecimal型変数の場合");
     System.out.println("期待値:0.6666666…");
     System.out.println("実際の値:"+y.doubleValue());

    }
}

・実行結果
double型変数の場合
期待値:0.6666666…
実際の値:0.6666666666666666
BigDecimal型変数の場合
期待値:0.6666666…
実際の値:0.6666666666666666

---------------------------------

2019/05/18 追記
「double x = 2d/3d;」のような書き方でも可でした。
むしろこの書き方の方が一般的だと思います。

「数学は役に立たない」論について思うこと

定期的に話題になるテーマなのですが、ちょうど考える機会があったので記事にします。
 
かく言う私も、理系が向いていると言われながら、学んでいた数学が何の役に立つのか本気でわかりませんでした。
高校時代に「数学=無味乾燥で難しいだけ」という印象を感じてしまったため、理系への興味を失い、役に立ちそうな文系に進みました。数学の授業についていけないというわけではなかったのですが、文系に進んだ時に役に立ちそうな分野以外は自分から進んで勉強する気になれませんでした。
よくやり玉に挙げられる三角関数も当時は何の役に立つのかさっぱり分からず、テストが終わると同時に勉強した内容を忘れました。
そんな三角関数がゲームのプログラムの分野やデザインの分野等で役に立つことを知ったのは大人になってからです。
何の役に立つのか高校時代に知っていれば数学を勉強するモチベーションも違ったでしょうし、もしかしたら今頃別の道を歩んでいたかもしれないので、そのような情報は誰かがもっと早く教えて欲しかったと思っています…。
 
…という苦い経験をしているので、私が何かを教える時は、「何の役に立つのか」ということをできる限り伝えるように心がけています。
情報処理技術者試験も「これ何の役に立つの?」と言われかねない設問が少なくないので、当ブログの記事では実務でどのように使っているのかをできる限り書くようにしています。
私の知識や経験が色々足りていないので気持ちだけが先行しているような状態ですが、、
それでもこの心構えは間違っていないと思うので、今後もこのスタイルを貫きたいと思います。
また、私には無い知識や経験をお持ちの方が人に何かを教える時に、それが何の役に立つのかも教えていただけると良いなぁ、とも思っています。

java:Hashmapで1つのキーで複数の値を結びつける方法

HashMapとは、「キー、値」の組み合わせでデータを保持することができるクラスのことです。
このクラスを用いれば、キーに紐づく値を検索することが可能になります。
 
しかし、キーに対応する値が複数の場合は、一工夫する必要があります。
私が趣味で作っているプログラムでそのようなケースがあったので、どのように工夫したのかをサンプルコードとして残します。
今回は、「値」をStringで定義し、区切り文字を使って複数の値を結びつけ、参照する際はStringクラスのsplit関数を用いて分割するということをしています。
 
ちなみに、値には配列等の構造を持つオブジェクトを指定することも可能です。
もう少し複雑な状況では、値としてそのようなオブジェクトを指定した方が良いでしょう。
 
【サンプルコード(変数名等を加工した後、該当箇所のみ抜き出し)】



/* 変数定義 */
String magicType;
int magicPower;
double magicHitProbability;
int magicMP;
String magicEffectCode;

/* HashMap定義(魔法一覧) */
HashMap<String, String> magicMap = new HashMap<String, String>();
magicMap.put("Fire1", "fire|40|90|3|00");
magicMap.put("Fire2", "fire|80|90|6|00");
magicMap.put("Fire3", "fire|120|90|10|00");



magicMap.put("BigBang", "fire|250|100|30|7D");
magicMap.put("AbsoluteTemperature", "ice|250|100|30|7E");
magicMap.put("AbsoluteLightning", "electric|500|70|30|7F");

/* 魔法の性能取得 */
String magicInfo = magicMap.get(magicName[i]);
if (magicInfo == null) {
    // 例外処理(省略)
} else {
    String[] magicInfoSplit = magicInfo.split("\\|", 0);
    magicType = magicInfoSplit[0];
    magicPower = Integer.parseInt(magicInfoSplit[1]);
    magicHitProbability = Double.parseDouble(magicInfoSplit[2]);
    magicMP = Integer.parseInt(magicInfoSplit[3]);
    magicEffectCode = magicInfoSplit[4];
}