LAN内のPCにはIPではなくホスト名でアクセスしようと思った話

イメージ・マジックの安藤です。
最近誕生日を迎えました。20代でいられるのもあとわずかです。

今回は内製のデスクトップアプリケーション(以下アプリ)の運用にあたってネットワーク周りでの気づきの話です。

前置き

弊社では、生産性の向上や属人性の排除といった観点で生産工程の一部を自動化しています。
諸々の言及は避けますが、そのシステムの一環として加工を早く済ませるためにアプリを介してPC間とPC加工機間で通信を行う仕組みがあります。 イメージ 画像は何かを表しているようでほとんど何も表していないイメージ図です。

アプリの目的は加工機にデータを送ることです。アプリは内部で簡易的なサーバをたてているため、接続先を指定すればアプリ間でエンドポイントを通じて通信を行うことができます。PC間で何やかんやをして素早くデータを加工機に送るという仕組みがアプリにあります。

何が起こったか

前述の前置きを踏まえて本題なのですが、ある日現場の方からデータの送信が遅いという話があり実際に現場に赴き調査することとしました。

データの送信が遅くなっている原因はアプリ間の通信ができていないことにありました。そこで、通信ができない理由を探ったところ以下のことが分かりました。
  • 各PCのプライベートIPの割り当てがDHCPで管理されている
  • アプリに設定されている接続先がIPで指定されている
  • アプリに設定されているIPが実際に接続したいIPと異なっていた
以上のことから、これは接続先を正しく指定すれば一応の解決ができるわけですが、PCのIPが変わるたびにこのようなことが起こってはたまらないため対策をすることにしました。

Windowsにホスト名解決をさせる

現場で働いている方々は、PCには詳しくないことが多いのでトラブルの際に解決しやすい方向に倒すほうがいいです。
今回はアプリに設定する接続先をホスト名に変えることで対処することにしました。WindowsではPC名がホスト名にあたります。コマンドプロンプトかPowerShellでhostnameコマンドでも確認できます。
名前解決でもIPが変わった際にキャッシュによって正しい解決ができない可能性が残りますが、そこはDNSキャッシュクリアのコマンドを直接指示するかそういった機能をアプリに組み込むことで今後対処できるかなと思います。

おまけ:PC間のpingがWindowsのFWで防がれている件

Windows10のプライベートネットワークはデフォルトでICMPがブロックされるようになっているため、ファイアウォールでこれを許可する必要がありました。「ファイルとプリンターの共有 (エコー要求 – ICMPv4 受信)」「ファイルとプリンターの共有 (エコー要求 – ICMPv6 受信)」が該当する項目でした。
原因の調査中に引っかかりファイアウォールを無効化すると通ったため気づきました。

おわりに

Webアプリでは問題の再現と解決はしやすいことが多いですが、デスクトップのアプリだとその要因が個々のPC環境に依存して解決しにくいことが多いのでこれを解決する仕組みを更に考えていきたいと思いました。

Java14とJavaFXを使い普通のブロック崩しを作る

はじめに

安藤です。
世の中の情勢が厳しい昨今ですがいかがお過ごしでしょうか。私は元気です。
今回は3/17にリリースされたJava14を早速使ってみたという話です。

Java14について

全容はきしださんがまとめてくれています。いつもお世話になっています。
Java 14新機能まとめ
個人的に注目したいのが、ついに正式版になったPackaging Toolです。
およそ1年前にこちらの記事でも触れましたが、ようやく公式のパッケージャが登場しました。

今回作ったもの

本題です。
この記事のためだけにJavaFXを使ったシンプルなブロック崩しを作りました。
javafx-breakout
breakout.zip
実は前職で新卒研修時に暇だったので似たようなものを作ったことがあり、それを思い出しながら作っていたのですが、想像の3倍くらい大変でした。
新しい構文を使うことと、できるだけ内部状態の変更を起こさないことをコンセプトに作りました。

全体の構成

ProcessingのようにCanvasで画面を描画していきます。fxmlの記述はこれだけです。

<?xml version="1.0" encoding="UTF-8"?>


<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.Group?>
<Group xmlns="http://javafx.com/javafx/11.0.2" xmlns:fx="http://javafx.com/fxml/1"
       fx:controller="jp.imagemagic.breakout.FieldController"
       onMouseMoved="#mouseMove" onMouseClicked="#mouseClick">
    <children>
        <Canvas fx:id="canvas" focusTraversable="true" width="400" height="600"/>
    </children>
</Group>

画面にはブロック、ボール、バーがありますが、統一的な計算のために以下を用意しました。
Recordはイミュータブルな推移をさせるためにうってつけです。

package jp.imagemagic.breakout;

import javafx.scene.paint.Color;

interface Drawable {
    Vector pos();

    Vector size();

    default DrawType drawType() {
        return DrawType.Rect;
    }

    default Color fill() {
        return Color.WHITE;
    }

    default Color stroke() {
        return Color.BLACK;
    }
}


package jp.imagemagic.breakout;

public record Vector(double x, double y) {
    public Vector move(double dx, double dy) {
        return new Vector(x + dx, y + dy);
    }

    public Vector move(Vector v) {
        return move(v.x, v.y);
    }

    public Vector scalar(double n) {
        return scalar(n, n);
    }

    public Vector scalar(double nx, double ny) {
        return new Vector(x * nx, y * ny);
    }
}

クラス構成の大枠は以下の通りです。これはtetrixの影響を受けた構成になっています。
  • 画面からの入力の処理と一定時間ごとに画面の推移を行うFieldController
  • 現在の状態を保持するState
  • Stateの推移ロジックをもつField
  • FieldControllerとStateを仲介するViewUnit

package jp.imagemagic.breakout;

import java.util.function.Consumer;
import java.util.function.UnaryOperator;

final class ViewUnit {
    private final Field field;
    private State state;

    ViewUnit(double w, double h) {
        field = new Field(new Vector(w, h));
        state = field.initState();
    }

    void trans(UnaryOperator<State> trans) {
        state = trans.apply(state);
    }

    UnaryOperator<State> moveBar(double x) {
        return field.moveBar(x);
    }

    UnaryOperator<State> moveBall() {
        return field.moveBall();
    }

    UnaryOperator<State> transStatus() {
        return field.transStatus();
    }

    void drawView(Consumer<Drawable> draw, Consumer<Status> statusDraw) {
        state.view().objects().forEach(draw);
        statusDraw.accept(state.status());
    }
}


// 一部抜粋
public class FieldController implements Initializable {
    @FXML
    private Canvas canvas;
    private GraphicsContext gc;
    private ViewUnit unit;

    public void mouseMove(MouseEvent e) {
        transAndDraw(unit.moveBar(e.getSceneX()));
    }

    public void mouseClick() {
        transAndDraw(unit.transStatus());
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        gc = canvas.getGraphicsContext2D();
        unit = new ViewUnit(canvas.getWidth(), canvas.getHeight());
        final var timeline = new Timeline(new KeyFrame(new Duration(10), e -> transAndDraw(unit.moveBall())));
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();
        drawView();
    }
}

補記: jpackageによるパッケージング

予めjlinkでランタイムの大きさを削っておくのがお勧めです。
インストーラを伴わないものを作ろうと色々と試した結果、このようなコマンドになりました。
jpackage -t app-image -n breakout -d dist -i app --main-class jp.imagemagic.breakout.Main --main-jar jp.imagemagic.breakout.jar --runtime-image jregram.min\ --java-options "--add-exports javafx.base/com.sun.javafx.reflect=ALL-UNNAMED --add-reads javafx.base=ALL-UNNAMED --add-reads javafx.graphics=ALL-UNNAMED --enable-preview"
appにjarファイルがあり、distにbreakoutというディレクトリができます。
jarファイルにしていますが、オプションを変えればクラスファイルでも行けると思います。

これからのJavaとの付き合い方を考える

はじめに

イメージ・マジックの安藤です。
皆さんは、GWはどのように過ごされたでしょうか? 私はひたすら配信サイトで映画を探して見ていました。小さい頃に何となくテレビで見た映画を改めて見るのもまた面白いものだと感じています。


さて、今回はユーザーエンドのJava、いわゆるPublic JREと近年のJavaを取り巻く環境の変化についての話です。
話の大元となるのはこちらの記事です。

JDKのリリースモデルとライセンスの変更

Java9リリース後、JDKは9月と3月の6ヶ月ごとにリリースされるようになりました。Oracleによれば、大きな機能の進捗に引っ張られて中小機能のリリースが滞ることを危惧してとのことです。
JDKのライセンスも変更になっています。ライセンスはBCLからGPL v2.0に変更されており、加えてOracle JDKとOpenJDKの機能的な違いはJava11からはなくなりました。Oracleが配布するJDKが終了することばかりが注目されていますが、この変更の本質はJRE/JDKを容易に再配布可能にすることにあるようです。
Oracleは、現在のJavaが「JREをPCにインストールして画一的に実行する」方式であることにいくつかの弊害があるとしており、JREをアプリケーションにバンドルするための方式の整備を進めています(Java9で新たに登場したモジュールもそのための改善の一環です)。

Java11以降のPublic JREの廃止と新たなアプリケーション配布方式

前述の通り、OracleはJREのアプリケーションバンドルを徐々に推し進めています。そのような中で、Java11からはPublic JRE、つまりJavaの公式サイトで入手できるJREは廃止されています。現在配布しているのはJava8ですが、個人使用のユーザに対しての無償アップデートは2020年12月末まで、それ以外の無償サポートは2019年1月に終了しました。
Java11ではjlinkというバンドル用JREを作成する機能が付いています。アプリケーションに必要なモジュールを抽出するためのjdepsという機能も付いています。今年9月にリリースされるJava13にはjarからマルチプラットフォーム向けのアプリケーションやそのインストーラを作成するjpackageという機能も追加される予定です。
バンドル用JREの作成に際してはこちらのサイトを参考にさせていただきました。

業務での方針を考える

弊社で開発中のアプリケーションではPDFの帳票印刷にJavaを使用しています。現在は実行可能JarをPCにインストールされているJavaで実行していますが、JREを配布して実行できるような仕組みを追々作成していく必要があるでしょう。 テストとしてバンドル用のJREを作成してみましたが、40MBほどの容量になりそうです。
配布するために一度圧縮する必要やアップデートの方法など検討事項がいくつかありそうなので少しずつ進めていく予定です。

DLL+EXEからDLLの使い方を解析する[後編]

はじめに

イメージ・マジックの安藤です。
今年は雪が降ることはないのかなと思っていたらまさかこの時期にとは思いもしませんでした。ともあれ、平日でなくて良かったと思っています。私は地元が北海道なのですが、そちらは今年も順調に雪が積もっているようでした。
今回は前回の話の続きです。

あらすじ

前回はコンパイル時にDLL内のメソッドを暗黙的にリンクするまでの手順を説明しました。今回はメソッドの引数と戻り値の解析です。

.NETアプリケーションを逆コンパイルする

JavaやC#などコンパイル時に中間言語に翻訳されるような言語は、特殊な対策が施されていない限りかなり可読性の高い状態で逆コンパイルすることが可能です。 .NETアプリケーションを逆コンパイルするソフトはいくつかありますが、今回はILSpyというソフトを使用しました。

データのマーシャリング

逆コンパイルしたソースからDLLのメソッドの使い方がわかれば万々歳で全てが解決するかと言われるとそんなことはありません。.NET Frameworkにはマネージコードという概念があり、その外にあるコード(アンマネージコード)を呼び出すにはデータのマーシャリング(変換)が必要になります。
つまり、C++で使われると型とC#で使われる型はかなり違うということです。
大体の変換対応表はMSの公式ドキュメントに記載されています。
この中でも特にマルチバイトの文字列はC++で扱うのはだいぶ辛いです。それから、上記のドキュメントには書いていませんが、関数ポインタを渡したいときはdelegateが使われたり、参照(例えばWIN32APIにあるHANDLE型の参照はLPHANDLE)を渡したいときはrefキーワードが使われたりします。ちなみに、System.IntPtrとref System.IntPtrは別物です。

おわりに

やってみて思いましたがDLLの解析はノーヒントだとだいぶ辛いですね(探してもあまり情報が出なかったので)。なのでその軌跡をここに残すことにしました。

DLL+EXEからDLLの使い方を解析する[前編]

はじめに

イメージ・マジックの安藤です。
先日から季節が急速に真冬の様相を呈していますね。風邪やインフルエンザが流行りだす時期なので皆様におかれましても十分ご用心ください。ちなみに、私は去年の大晦日から今年の三が日にかけてインフルエンザを発症するという何とも言えない新年スタートを切っているので気を付けます。
今回は題名の通りDLLの解析をしたという話です。

DLLとは

多分この説明要らないんじゃないかなぁと思いつつ書いておきます。
DLLとは、プログラムのある機能を再利用できるようにプログラム本体と切り分けたライブラリのことを指します。Windowsならば.dll、Linux系では.soという拡張子が使われます。
なお、今回の話はWindowsの.dllで、一般的な、Cで記述されたDLLという話です。

コンパイルを通すのに必要なもの

DLLの利用方法には暗黙的リンクと明示的リンクが存在します。今回は完全に個人的にDLLを使いたいだけなので暗黙的リンクでコンパイルするものとします。
以下が必要になります。

  • インポートライブラリ(.lib)
  • ヘッダファイル(.h)
更にメタ的に言うとこうなります。

  • エクスポートされている関数名
  • 関数の引数と型

初期状態

DLLがどんな状態で使われていたのかを少しだけ書いておきます。
DLLとDLLの機能をラップしたEXEがあり、EXEに引数を渡して実行してあげるとDLLの機能が使えるという構成になっていました。最終的にはこのEXEを介さずにDLLの機能を呼び出すのがゴールです。

インポートライブラリを用意する

第一の関門として、インポートライブラリが存在していません。ただ、これに関してはVisual Studioを使えばDLLから同等のものを用意することができます。
まず、以下のコマンドを実行します。

dumpbin /exports hoge.dll


C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools>dumpbin /exports VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64\c1.dll
Microsoft (R) COFF/PE Dumper Version 14.16.27024.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64\c1.dll

File Type: DLL

  Section contains the following exports for c1.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           3 number of functions
           3 number of names

    ordinal hint RVA      name

          1    0 000DDDB0 AbortCompilerPass
          2    1 000DDE50 CloseTypeServerPDB
          3    2 000055FC InvokeCompilerPassW

  Summary

       32000 .data
       17000 .pdata
       F3000 .rdata
        7000 .reloc
        1000 .rsrc
      138000 .text

このコマンドでDLLからエクスポートされている関数の一覧がテーブル上に表示されます。MSVCに存在する適当なDLLを表示してみます。 ここでordinal~nameの列に注目すると関数名が分かります。この関数名を以下のように羅列し、拡張子を.defで保存します。これはモジュール定義ファイルと呼ばれるものです。

EXPORTS
AbortCompilerPass
CloseTypeServerPDB
InvokeCompilerPassW

作成したモジュール定義ファイルは以下のコマンドでインポートライブラリに変換できます。なお、64bitアーキテクチャを想定しています。

lib /DEF:hoge.def /MACHINE:X64 /out:hoge.lib

ヘッダファイルを用意する

ヘッダファイルでは以下のように書くことでDLLの関数を宣言できます。

#define DLLImport __declspec(dllimport)

#ifdef __cplusplus
extern "C"{
#endif

    DLLImport void funcName1();
    DLLImport int funcName2(int arg);

#ifdef __cplusplus
}
#endif


以上を見るとわかりますが、関数の戻り値と引数がわからないとDLLの機能は使えないことが分かります。

以降、今回のケースで関数定義をどう解析したかは次回の私の番で書いていきたいと思います。

僕らの96bit戦争

はじめに

こんにちは、イメージ・マジックの安藤です。

先日の台風24号で私の住んでいるアパートは何度も大いに揺らされ、今にも倒壊するのではないかという勢いでした。
本当に大変だったのは翌日の電車でしたね。私の使用している沿線は早朝の段階では止まっていました。

そんなわけで、今回の話題は文字表現とビット演算の話です。

情報を管理する媒体について

当社で日々製造されている製品は、実際にお客様のお手元に届けられるまでに実に様々な工程を経ています。
しかも、それらは大量生産されているようなものばかりではなくお客様ごとのオンデマンド製品も多分に扱っています。
これらの製品を間違いなくお客様にお届けするためには、注文や製品ごとに情報をきっちりと管理していく必要があります。
そしてこれらの製造を支える在庫の管理・調整も効率的に向上を回していくうえで欠かせないものです。

そこで重要になってくるのが情報を管理する媒体です。最も簡単に実現できるものとしては、バーコードを印刷したラベルシールがあります。

ところで、皆様はユニクロのセルフレジを利用したことがありますでしょうか?
商品を置くだけで何をどのくらい買うのかを瞬時に判断してくれるので初めて使われた方は驚かれるかと思います。

実は、このレジにはRFID(非接触型ICタグ)という仕組みが使われており、一度に複数の商品を読み取ることができます。

当社においても、RFIDを実用していくために現在様々な検討をしています。

RFIDに書き込む、RFIDを読み込む

RFIDにはEPC Class1 Generation2という規格があり、4つのメモリバンクで構成されています。

  • RESERVED
  • EPC
  • TID
  • USER

データを読み書きする部分は主にEPCメモリで、通常は96bitを使います。USERメモリも自由に読み書きできますが、使用するチップによっては実装されていないこともあり容量も特に決まっていません。
TIDメモリはRead Onlyで32bit以上のチップの識別情報が書き込まれています。

一言でまとめますと、96bitに書きたい情報を詰めろということです。

96bitの使い道を考える

96bitでできることは何か考えてみましょう。

まず、これをビットボートとして扱った場合は96の状態を表現できますが、RFIDの使い道としては微妙すぎます。

次に、考えることとしては数字として使うことでしょう。32bitの整数であれば3つ分表現できますが、やはり不便です。

そこで行き着くものとしては、やはりASCII文字になるかと思います。この場合、1文字が8bitで最大12文字まで使用できます。

ところで、ここで一つ問題があります。
日本で使われているバーコードの規格であるJANは13桁で規定されているのです。もっとも、13桁目はパリティビットとなっているため導出が不可能というわけではないですが様々な場面で余計な手間がかかってしまいます。

様々な検討の結果、最終的にASCIIの余計な文字を削って16字まで表現できるようにするという方式を取ることにしました。

96bitで16文字を表現する

96bitで表現できる文字数を増やすには、1文字に使用できる選択肢を減らせばいいです。
1文字を6bit(=64種類)とすると96/6=16となり16文字分使えることとなります。
この際、64文字は好きなように選ぶことができます。

今回は0-9A-Zの半角英数大文字と括弧など一部の半角記号を除いた55文字を採用することとしました。

エンコード/デコードを実装する

諸般の事情により、上記の実装はnode.jsで行いRFIDリーダライタとのやり取りはネイティブモジュールを挟むことで実現しています。

読み取られたデータは16Bのデータとなっており、最初の4BはTIDです。
デコード時は12Bを6bitごとに分解する必要があり、エンコード時は最大16文字を6bitで表現し12Bに詰めなおす必要があります。

const codeMap = []; // 省略 最大64種類の文字に置き換えられる

const bitMap = [1, 2, 4, 8, 16, 32, 64, 128];

const calc = arr => arr.map((n , i) => n ? bitMap[i] : 0)
    .reduce((sum,  n) => sum + n,  0);

const toBit = (b, arr = [], index = 0) => index === 6 ? arr :
    toBit(parseInt(b / 2, 10), arr.concat(b % 2), index + 1);

const encode = src => {
    const chunk = src.split('') // 1文字ごとに分ける
        .map(s => toBit(codeMap.indexOf(s)))
        .reduce((a , b) => a.concat(b), []);
    return Array(12).fill(0)
        .map((_, i) => calc(chunk.slice(i * 8, (i + 1) * 8)));
};

const decode = src => {
    const chunk = src.slice(4) // TIDをスキップ
        .map(b => Array(8).fill(0).map((_, i) => b >> i & 1))
        .reduce((a , b) => a.concat(b), []);
    return Array(16).fill(0)
        .map((_, i) => codeMap[calc(chunk.slice(i * 6, (i + 1) * 6))])
        .join('');
};

おわりに

エンコード/デコードの実装が意外と面倒で2時間ほどかけてしまいましたが、これで16文字の範囲で自由にRFIDを書き換えることが可能になりました。
ちなみにネイティブライブラリは当社別メンバーの力作です。

参考

20160129-UHF帯RFID標準コード体系ガイドライン初版

PHPにおける配列作成・分解マジック

はじめに

こんにちは、イメージ・マジックの安藤です。

オフィスに置いてあるウォーターサーバーを最も頻繁に使用している気がします。水が美味しいのは素晴らしいことです。

さて、今回は現在開発しているシステムで採用されているPHP7に関して、配列の中身を複数の変数にしたり複数の変数を配列にしたりする方法についてです。

PHPにおける配列とは

本題に入る前にPHPでの配列の扱いについて軽く触れておきます。

PHPの公式リファレンスには配列について以下のように説明されています。

PHP の配列は、実際には順番付けられたマップです。マップは型の一種で、 キーに関連付けます。

つまり、PHPには厳密には連想配列しか存在しないということです。

配列のキーには数字(整数)か文字列のみ使用でき、「数字として妥当な文字」がキーに指定された場合に数字に変換されるなどの変換法則がいくつかあります。

詳しくは公式リファレンスをご覧ください。

配列の作成

基本的にarray()を使用することで任意の配列を作成することが可能です。

PHP5.4からは[]を使うこともできます。

$data = [
 [1, 'noel'],
 [2, 'michael'],
];
$data = [
 ['id' => 1, 'name' => 'noel'],
 ['id' => 2, 'name' => 'michael'],
];

既に連想配列の値が変数として用意してあり、変数名と同じキーの配列を作りたい場合はcompact()を使うことができます。

$company = 'IMAGE MAGIC';
$state = '東京';
$city = '千代田区内神田';
print_r(compact('company', 'state', 'city'));
Array
(
  [company] => IMAGE MAGIC
  [state] => 東京
  [city] => 千代田区内神田
)

配列の分解

PHP5.xやPHP7.0では通常の配列はlist()、連想配列はextract()を使用することで実現できました。

$data = [
  [1, 'noel'],
  [2, 'michael'],
];
// $id = 1, $name = 'noel'
list($id, $name) = $data[0];

foreach ($data as list($id, $name)) {}

$data = [
  'company' => 'IMAGE MAGIC',
  'state' => '東京',
  'city' => '千代田区内神田',
];
/*
 * $company = 'IMAGE MAGIC';
 * $state = '東京';
 * $city = '千代田区内神田';
 *
*/
extract($data);

PHP7.1ではlistの代わりに[]を使うことができます。

$data = [
  [1, 'noel'],
  [2, 'michael'],
];
// $id = 1, $name = 'noel'
[$id, $name] = $data[0];

foreach ($data as [$id, $name]) {}

$data = [
  'company' => 'IMAGE MAGIC',
  'state' => '東京',
  'city' => '千代田区内神田',
];

/*
 * $company = 'IMAGE MAGIC';
 * $state = '東京';
 * $city = '千代田区内神田';
 *
*/
[
  'company' => $company,
  'state' => $state,
  'city' => $city,
] = $data;

そのほか便利な操作

  • array_keys
    連想配列のキーだけを取り出して配列にする
  • array_values
    配列を0からの添え字の配列にする
  • array_key_exists
    指定したキーが配列に存在するか調べる
  • in_array
    指定した値が配列に存在するか調べる

おわりに

PHPでは配列が作りやすいので色々と便利ではありますが、mapやfilter,reduceなどが使いにくいというが少々残念なところです。
省略できる記法は省略して可読性を上げていきたいですね。

Immutableという概念について

はじめに

こんにちは、イメージ・マジックの安藤です。

社会人歴は2年目に突入し、入社して1ヶ月ほどが過ぎました.

日々バグ対応を行っているのですが、弊社のシステムに関する私の理解がまだ浅いので苦戦することも多々あり、そこが仕事の面白みかなともまた思っている次第です。

今回はコードを書く上でのワンポイントとしてImmutableという概念について少し触れればと思います。

なお、以下サンプルコードはJavaScriptで記載します。

Immutableとは

ある値がImmutableであるとはどういうことでしょうか?

この記事では、「ある値の状態が変わることがない」ということをImmutableとしたいと思います。

普通に考えると、Immutable=不変なので値が変わらないということから定数のようなものを思い浮かべるかもしれませんが、その違いとは何でしょうか。

まずは単純に数字同士の足し算を考えてみましょう。

数字同士の足し算はすべてImmutableになり、以下の例ではnum1やnum2はconstなので直接書き換えることもできないためこれらの値の状態が変わることはありません。

const num1 = 1;
const num2 = 3;
const num3 = num1 + num2;
console.log(num1, num2, num3); // 1 3 4

では、値の状態が変わる(mutable)場合の例を見てみましょう。

以下ではarrはconstとなっていますが、sort関数で昇順にソートすることでarr自体が変更されていることがわかるでしょうか。

const arr = [1, 3, 2, 5, 4];
arr.sort((a, b) => a - b);
console.log(arr); // [1, 2, 3, 4, 5]

何故このようなことが起こるのかというと、少し難しい話になりますが、constはオブジェクトのインスタンスが変わらないことしか保証しないからです。

上記の例ではarrはArrayクラスのインスタンスになっており、オブジェクトの内部で状態が変更されています。

そして、arrが見ているインスタンス自体は変更されていないため、arrがconstであるかどうかに関わらず値の状態を変更することができてしまいます。

余談ですが、sortのような呼び出したオブジェクト自体が変化するメソッドを破壊的メソッドと呼びます。

なお、同じArrayクラスでも以下のように破壊的でないものもあります。JavaScriptに限らないですが、自分の使おうとしているメソッドが破壊的か否かはきちんと確認するべきでしょう。

const arr = [1, 2, 3, 4, 5];
const added = arr.map(n => n + 1);
console.log(arr, added); // [1, 2, 3, 4, 5] [2, 3, 4, 5, 6]

最後に、Immutableな配列への要素追加とmutableな配列への要素追加の対比の例を載せておきます。

const arr = [1, 2, 3, 4];
const arr2 = arr.concat(5); // immutable add
console.log(arr, arr2); // [1, 2, 3, 4] [1, 2, 3, 4, 5]
arr.push(5); // mutable add
console.log(arr, arr2); // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]

Immutableであるメリットとは

ここまでImmutableとは何かということに触れてきましたが、Immutableであることのメリットに触れていきたいと思います。

Immutableであることの最大のメリットはオブジェクトの破壊的変更を気にする必要がなくなるという点に尽きます。これはプログラムを書いていくうえで想定外の変更が起きにくくなり、バグを抑制することができるということでもあります。

また、Immutableを適切に意識することでプログラム上でどの値がどのように変更されていくのかが追いやすくなるという効果もあるでしょう。

Immutabeで注意すべき点

Immutableにしようとする値の状態が頻繁に変更される場合や変更可能なデータを多く持っている場合、Immutableは適切でないことが多いため注意が必要です。

また、非破壊的メソッドを使っているとメソッドチェーンを繋げやすくなりますが、過剰なメソッドチェーンは可読性を下げることになるので注意しましょう。

参考

Immutableの利便性、大きなメリットについて。

イミュータブル – Wikipedia