技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

カプセル化によるルールの強制

オブジェクト指向を適用すると、ソースコードの重複した記述を排除でき、生産性や品質を向上することができます。
それとは別に、他の開発者にルールを強制できるメリットもあります。
カプセル化を例にして説明するのがわかりやすいので、今回はカプセル化を例に挙げてルールを強制するメリットを書いていきます。
 
----
 
例えば、自分は商品の金額を計算するPriceCalcクラスを作成していて、他の開発者はPriceCalcクラスを利用するUserクラスを作成しているとします。
自分としては「このロジックで金額計算して欲しい」というのがあるのですが、他の開発者にそのロジックの実装をさせるようにしてしまうと、意図が上手く伝わらなかった時に誤ったロジックで計算されてしまいます。
この状況をソースコードで表すと下記のような形になり、PriceCalcクラスは計算に必要な変数をまとめるだけ、PriceCalc側でその変数を参照して独自に業務ロジックを実装するような形となります。
 
public class PriceCalc {

  public ArrayList<Integer> costs = new ArrayList<Integer>();
  public int taxRate = 0;
  :
  : 色々なメンバ変数定義
  :

}
 
public class User {

  PriceCalc priceCalc = new PriceCalc();
  :
  : priceCalcオブジェクトのメンバ変数を使った複雑な業務ロジック
  :
  
}
 
----
 
それでは困るので、業務ロジックもPriceCalcクラス側に実装することにします。
変数とメソッドをひとまとまりにして一つのクラスとして提供することで、Userクラス側ではメソッドを呼び出すだけであるべき業務ロジックに基づいた計算を行うことができ、Userクラス側で業務ロジックを意識する必要が無くなります。
 
しかし、メンバ変数がpublicだと、Userクラス側でその変数を参照して独自に業務ロジックを実装することもできてしまいます。
自分としてはそれでは困るので、「変数を参照して独自に業務ロジックを実装しないでください」と伝えるのですが、それが上手く伝わらないと独自に業務ロジックを実装されてしまう可能性があります。
自分一人でプログラムを作っている場合は全ての変数をpublicにした方が便利だったりもしますが、複数人で開発している場合は意思疎通がうまく行かなかった場合に意図通りではないコーディングをされてしまうリスクがあります。
 
public class PriceCalc {

  public ArrayList<Integer> costs = new ArrayList<Integer>();
  public int taxRate;
  :
  : 色々なメンバ変数定義
  :
  
  public void registNewCost(int cost) {
    this.costs.add(cost);
  }
  :
  : メンバ変数の設定用メソッド
  :
  
  public int getPrice(int index) {
    :
    : 複雑な業務ロジック
    :
  }

}
 
public class User {

  PriceCalc priceCalc = new PriceCalc();
  :
  : PriceCalcクラスのメソッドを使った設定
  :
  int priceOk = priceCalc.getPrice(index);
  
  // しかし用意したメソッドを使用せずに独自で計算できてしまう
  int priceNg = priceCalc.costs.get(index) * priceCalc.taxRate;
  
}
 
----
 
それを防ぐためには、変数のアクセスレベルをprivate等にして、変数に自由にアクセスできないようにするのが有効です。
こうすることで、変数を参照して独自に業務ロジックを実装しようとされた場合に、コンパイルエラーとして機械的に失敗させることができます。
これが、カプセル化によるルールの強制であり、意思疎通がうまく行かなかった場合に意図通りではないコーディングをされるリスクを減らすことができます。
 
public class PriceCalc {

  private ArrayList<Integer> costs = new ArrayList<Integer>();
  private int taxRate;
  :
  : 色々なメンバ変数定義
  :
  
  public void registNewCost(int cost) {
    this.costs.add(cost);
  }
  :
  : メンバ変数の設定用メソッド
  :
  
  public int getPrice(int index) {
    :
    : 複雑な業務ロジック
    :
  }

}
 
public class User {

  PriceCalc priceCalc = new PriceCalc();
  :
  : PriceCalcクラスのメソッドを使った設定
  :
  int priceOk = priceCalc.getPrice(index);
  
  // 下記はコンパイルエラー
  // int priceNg = priceCalc.costs.get(index) * priceCalc.taxRate;
  
}

JavaScript:matchメソッドで文字列抽出を行う

JavaScriptのmatchメソッドは戻り値に特徴があり、戻り値を利用して文字列抽出もできるようになっています。
今回は、文字列抽出の方法を紹介していきます。
 
----
 
matchメソッド(String.match)では、引数で与えた文字列が含まれるかどうかを返すメソッドです。
引数で与える文字列には、正規表現を使用することも可能です。
 
引数で与えた文字列が含まれない場合には、以下のようにnullが返却されます。
 
【サンプルコード】
・test.js
const str = 'hoge1fuga2';
const ret = str.match(/piyo/);
console.log("■戻り値全体を参照");
console.log(ret);
 
【実行結果】
c:\tmp>node test.js
■戻り値全体を参照
null
 
----
 
含まれる場合は、以下のように色々な情報が返却されます。
配列で返却され、一致した箇所を抜きだした文字列が添字0の要素に格納されます。
また、indexキーには一致した文字列が何文字目に存在するか、inputキーには走査対象とする元々の文字列が格納されます。
groupsキーについては後述します。
 
【サンプルコード】
・test.js
const str = 'hoge1fuga2';
const ret = str.match(/fuga/);
console.log("■戻り値全体を参照");
console.log(ret);
console.log("■一致した文字列のみ参照");
console.log(ret[0]);
 
【実行結果】
c:\tmp>node test.js
■戻り値全体を参照
[ 'fuga', index: 5, input: 'hoge1fuga2', groups: undefined ]
■一致した文字列のみ参照
fuga
 
----
 
matchメソッドの引数では、gオプションを指定することが可能です。
gオプションを指定すると、一致する文字列を見つけても走査を続け、一致する文字列を全て抜き出すという動きになります。
戻り値の形式もgオプション指定時には変更され、以下のように一致した全ての文字列が添字0の要素、添字1の要素…に格納されます。
 
【サンプルコード】
・test.js
const str = 'hoge1fuga2';
const ret = str.match(/[0-9]/g);
console.log("■戻り値全体を参照");
console.log(ret);
console.log("■1つ目の一致した文字列のみ参照");
console.log(ret[0]);
 
【実行結果】
c:\tmp>node test.js
■戻り値全体を参照
[ '1', '2' ]
■1つ目の一致した文字列のみ参照
1
 
----
 
matchメソッドの引数で使用できる正規表現には、キャプチャグループというものが存在します。
キャプチャグループとは、"()"で囲った箇所をグループ化するというもので、抽出条件には影響しませんが、抽出結果をグループ分けすることができます。
キャプチャグループが存在する場合は、添字1の要素に1つ目のキャプチャグループ、添字2の要素に2つ目のキャプチャグループ…といった具合に、戻り値でキャプチャグループの箇所を抽出することができるようになります。
キャプチャグループを使用することで、柔軟な文字列抽出が可能になります。
 
【サンプルコード】
・test.js
const str = 'hoge1fuga2';
const ret = str.match(/(fuga)([0-9])/);
console.log("■戻り値全体を参照");
console.log(ret);
console.log("■2つ目のキャプチャグループのみ参照");
console.log(ret[2]);
 
【実行結果】
c:\tmp>node test.js
■戻り値全体を参照
[
 'fuga2',
 'fuga',
 '2',
 index: 5,
 input: 'hoge1fuga2',
 groups: undefined
]
■2つ目のキャプチャグループのみ参照
2
 
----
 
キャプチャグループには名前を付けることができます。
"(...)"を"(?<hoge>...)"とすることで、名前を付けることができます。
"<"と">"で囲った箇所が、キャプチャグループの名前になります。
名前付きキャプチャグループを使用している場合は、戻り値のgroupsキーで抽出が可能になります。
 
【サンプルコード】
・test.js
const str = 'hoge1fuga2';
const ret = str.match(/fuga(?<digit>[0-9])/);
console.log("■戻り値全体を参照");
console.log(ret);
console.log("■名前付きキャプチャグループのみ参照");
console.log(ret.groups.digit);
 
【実行結果】
c:\tmp>node test.js
■戻り値全体を参照
[
 'fuga2',
 '2',
 index: 5,
 input: 'hoge1fuga2',
 groups: [Object: null prototype] { digit: '2' }
]
■名前付きキャプチャグループのみ参照
2

JavaScript:オブジェクトの中身をコピーする方法(deepcopyライブラリ使用)

JavaScriptにもJavaC#と同じように参照型変数が存在し、オブジェクトや配列、関数が参照型変数です。
そして、JavaC#と同じように、単純に「=」で代入するだけでは参照先(オブジェクトのメモリ領域を示すポインタ)しかコピーできず、コピー先の変更がコピー元に影響してしまいますし、その逆にコピー元の変更がコピー先に影響してしまいます。
(このようなコピーを「シャローコピー(浅いコピー)」と呼びます)

これを避けたい場合には、参照しているメモリの中身を丸ごとコピー(新たにメモリ領域を確保し書き込み)する必要があります。
(このようなコピーを「ディープコピー(深いコピー)」と呼びます)
JavaScriptでは標準でディープコピーを行うための関数が用意されていないので、自力で実装するか、外部のライブラリを使用するかする必要があります。
今回は、deepcopy(https://www.npmjs.com/package/deepcopy)というライブラリを使用します。
(なお、ディープコピーをサポートするライブラリは他にもあり、用途に応じて使い分けた方が良いです。JSONシリアライズ・デシリアライズする方法もありますが、関数やundefinedがコピーされない等の問題があるので注意が必要です。)
 
以下、サンプルコードです。
Node.jsを使用して検証します。
 
【事前準備】
・Node.js command prompt を起動
・作業フォルダ(今回は"C:\tmp\")に移動
cd c:\tmp\
・deepcopyライブラリをインストール
npm install deepcopy
 
【サンプルコード】
サンプルコードは作業フォルダ直下に作成します。
 
・ShallowCopy.js
let item1 = {id:1, name:"Sword"};
let item2 = item1;
item2.id = 2; // オブジェクトのコピー
item2.name = "Shield";
console.log(item1);
console.log(item2);
 
・DeepCopy.js
const deepcopy = require('deepcopy'); // コピー用ライブラリ読込
let item1 = {id:1, name:"Sword"};
let item2 = deepcopy(item1); // オブジェクトのコピー
item2.id = 2;
item2.name = "Shield";
console.log(item1);
console.log(item2);
 
【実行結果】
c:\tmp>node ShallowCopy.js
{ id: 2, name: 'Shield' }
{ id: 2, name: 'Shield' }

c:\tmp>node DeepCopy.js
{ id: 1, name: 'Sword' }
{ id: 2, name: 'Shield' }

c:\tmp>

HelloWorldを書く意義

職業柄、実務で使った経験のない言語やフレームワークをある日突然現場で使うようになることが多いです。
そのような場合、HelloWorldをプライベートで書くだけで、現場の実務に挑むようなこともあるのですが、HelloWorldを書くだけでも大分違うと感じます。
 
この記事では、私の実感を元に、HelloWorldを書く意義について書いていきたいと思います。
 
1.開発環境を正しく構築できたことの確認ができる
HelloWorldが動くということは、開発環境を正しく構築できたということを意味します。
プライベートのPCに開発環境を構築できていれば、勉強が必要な文法が出てきた時にすぐに試すことができるので、キャッチアップの速度が早まります。
この利点は、Webで入門者向けの手順を探してそれをそのまま実施するだけでも得られるメリットです。
 
2.言語やフレームワークが持つ特徴や利点を垣間見える
言語やフレームワークの特徴を下調べした後、1つ1つの手順や記述内容の意味を考えることで、言語やフレームワークが持つ特徴や利点を垣間見ることができる場合もあります。
HelloWorldだけでは意味のあることはできませんが、記述内容を少しずつ付け加えていくことで、意味のある作業やアプリケーション作成を行うことができるようになります。
言語やフレームワークが持つ特徴や利点を経験から知っていれば、意味のある作業やアプリケーション作成を行う上での基礎力を身につけることができます。
 
例えば、以下は私がC#のHelloWorldを行った時の記事です。
Visual Studio Communityの構成を意識することで、構成マネージャーやNugetといった応用的な話を理解しやすくなりましたし、Windows標準でコンパイラが用意されていることを知ることで簡単なツールをC#で作れるようにもなりました。
https://cyzennt.co.jp/blog/2020/04/18/c%E3%81%A7%E3%81%AEhelloworld%EF%BC%88%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%EF%BC%89/
 
また、以下は私がVue.jsのHelloWorldを行った時の記事です。
変数の値を監視するという少し難しい方法でHelloWorldを試すことで、MVVMモデルの利点を垣間見ることができましたし、その後の実務で使われるコードの理解にも役立ちました。
https://akira2kun.hatenablog.com/entry/2021/01/03/120000

npmで公開されているパッケージをローカルに落とさず使用する

npmで公開されているパッケージは、UNPKG(https://unpkg.com/)で公開されています。
UNPKGで公開されているパッケージをscriptタグ等で指定することで、ローカルにライブラリを落とすことなくライブラリを使用することができるようになります。
インターネット接続していることが前提となりますが、Webシステムでは便利なサービスです。
(このようなサービスをCDNサービスと呼びます)
 
以前に試したVue.js(https://akira2kun.hatenablog.com/entry/2021/01/03/120000)も、インストールすることなく以下のような形で使用することができます。
(今回は触れませんが、バージョン指定も可能です)
 
<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="UTF-8">
 <title>Vue.js - Hello world!</title>
</head>
<body>

<div id="app">
 <input type="checkbox" id="checkbox1" v-model="checked1">
 <label for="checkbox1">
  {{ message }}
 </label>
</div>

<script src="https://unpkg.com/vue"></script>
<script>
 var app = new Vue({
  // elで指定した値はdivタグのidと対応
  el: '#app',
  // 変数の初期値定義
  data: {
   message: '',
   checked1: false
  },
  // 変数の値を監視するイベントを定義
  watch: {
   checked1: function(newVal, oldVal) {
    this.message = (newVal) ? 'Hello World!' : '';
   }
  }
 })
</script>
</body>
</html>

Jestを試してみた

Jestとは、JavaScriptのテスト用のフレームワークです。
ここでは、Node.jsがインストールされていることを前提に、Jestを試してみます。
公式ドキュメントであるGetting Started(https://jestjs.io/docs/ja/getting-started)を参考に、説明が割愛されている箇所を補いながら説明していきます。
 
【前提】
・WindowsOSで作業を行います。
・Node.jsを事前にインストールし、"Node.js Command Prompt"でコマンド発行します。
・今回の確認では、C:\tmp\ で作業を行います(以下「作業フォルダ」)。
 
【インストール】
・cdコマンドで作業フォルダに移動します。
cd C:\tmp\
 
・npmコマンド(ライブラリのインストール用のコマンド)を発行しJestを取得します。
npm install --save-dev jest
 
・コマンドを発行すると、以下のように出力されます。
C:\tmp>npm install --save-dev jest
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated request-promise-native@1.0.9: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN saveError ENOENT: no such file or directory, open 'C:\tmp\package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^2.1.2 (node_modules\jest-haste-map\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.1: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
npm WARN enoent ENOENT: no such file or directory, open 'C:\tmp\package.json'
npm WARN tmp No description
npm WARN tmp No repository field.
npm WARN tmp No README data
npm WARN tmp No license field.

+ jest@26.6.3
added 511 packages from 351 contributors and audited 512 packages in 92.123s

23 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities


C:\tmp>
 
・作業フォルダ直下に、以下のようなフォルダが作成されれば成功です。
C:\tmp>dir node_modules\jest
ドライブ C のボリューム ラベルは  です
ボリューム シリアル番号は  です

C:\tmp\node_modules\jest のディレクト

2021/01/06 21:45 <DIR> .
2021/01/06 21:45 <DIR> ..
2021/01/06 21:44 <DIR> bin
2021/01/06 21:44 <DIR> build
1985/10/26 17:15 1,086 LICENSE
2021/01/06 21:45 <DIR> node_modules
2021/01/06 21:45 2,084 package.json
1985/10/26 17:15 551 README.md
3 個のファイル 3,721 バイト
5 個のディレクトリ バイトの空き領域

C:\tmp>
 
※なお、yarnコマンドでも取得できます。
 
【実行準備】
・作業フォルダに、"package.json"を作成します。内容は以下の通りです。
{
 "scripts": {
  "test": "jest"
 }
}
 
※これで、npmコマンドの引数に"test"を指定すると、コマンド"jest"が発行されるようになります。
 
【テスト対象のスクリプト作成】
・作業フォルダに、"sum.js"を作成します。内容は以下の通りです。
function sum(a, b) {
 return a + b;
}
module.exports = sum;
 
【テストコード作成】
・作業フォルダに、"sum.test.js"を作成します。内容は以下の通りです。
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
 expect(sum(1, 2)).toBe(3);
});
 
【実行】
・npmコマンドによりテストを実行します。
npm test
 
・以下のように、作業フォルダ直下のテストコードが実行されれば成功です。
C:\tmp>npm test

> @ test C:\tmp
> jest

PASS ./sum.test.js (7.241 s)
√ adds 1 + 2 to equal 3 (10 ms)

Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 23.654 s
Ran all test suites.

C:\tmp>

JSONフォーマットとは

JSONとは、データ構造を記述するフォーマットの一種です。
CSV等とは異なり、階層構造を記述することができます。
階層構造を表現できるフォーマットとしてはXMLも挙げられますが、JSONフォーマットはXMLよりも簡易的に記述できる、JavaScriptとの親和性が高い、といった特徴があります。
電文でのデータ通信で多く使われる印象があります。
 
以下では、JSONについて、フォーマットの簡単な説明と、JSONの生成・読込を行う簡単なサンプルコードを取り上げます。
 
----
 
JSONのフォーマットの簡単な説明】
■データの開始と終了
開始は"{"、終了は"}"で示されます。
 
■データの構造
以下のように、変数名と値を":"で区切って表記します。
変数名はダブルクォーテーションで囲います。
(値については後述します)
{ "height" : 170 }
 
変数名と値のペアが複数ある場合は、ペアを","で区切って表記します。
{ "height" : 170, "weight" : 58.8 }
 
人に読ませることを意識する場合、以下のように改行を入れてわかりやすくすることが多いです。
{
 "height" : 170,
 "weight" : 58.8
}
 
■値の種類
JSONでは以下の値を使用することができます。
 
・数値
数値を記述することで、数値としてみなされます。
{ "height" : 170 }
{ "weight" : 58.8 }
 
・文字列

ダブルクォーテーションで囲うことで、文字列としてみなされます。
{ "str" : "hoge" }
 
・true/false
trueと記述することで、真としてみなされます。
falseと記述することで、偽としてみなされます。
{ "flag1" : true }
{ "flag2" : false }  
 
・null
nullと記述することで、nullとしてみなされます。
{ "flag3" : null } 
 
・オブジェクト
"{"と"}"で囲うことで、オブジェクトとしてみなされます。
これにより、階層構造の記述が可能になります。
{
 "name": {
  "firstName":"miku",
  "lastName":"hatsune"
 }
}
 
・配列
"["と"]"で囲うことで、配列としてみなされます。
{"numList": [ 1, 2, 3 ]}
 
文字コード
文字コードUTF-8(BOM無し)を使用します。
 
■その他詳細
RFC8259(https://tools.ietf.org/html/rfc8259)がJSONの正式な仕様となりますので、こちらをご参照ください。
 
----
 
JSONの生成・読込を行うサンプルコード】
ここでは、JavaScript(Node.js)でのサンプルコードを提示します。
以下のように、JSONの生成・読込を行うための機能が用意されており、それを利用することで容易に生成・読込が可能になります。
 
■生成のサンプル
・コード(stringify.js)
let worker = {
 skill: "singing",
 name: {
  firstName: "miku",
  lastName : "hatsune"
 }
};
let json = JSON.stringify(worker)
console.log(json);
 
・実行結果
C:\tmp>node stringify.js
{"skill":"singing","name":{"firstName":"miku","lastName":"hatsune"}}

C:\tmp>
 
■読込のサンプル
・コード(parse.js)
let json = '{"skill":"singing","name":{"firstName":"miku","lastName":"hatsune"}}';
let worker = JSON.parse(json);
console.log("skill: " + worker.skill);
console.log("firstName: " + worker.name.firstName);
console.log("lastName: " + worker.name.lastName);
 
・実行結果
C:\tmp>node parse.js
skill: singing
firstName: miku
lastName: hatsune

C:\tmp>