Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

JavaScriptで静的変数

f:id:fa11enprince:20200513231943j:plain 例えば次のような処理を考えてみます。
名前と身長と体重を与えると、その人のBMIがメソッドにより出力されるというものです。
さらに、食料foodNumがあって、食べると1つ減って、
代わりに1kg増えるものとします。食料はあらかじめ、貯蔵量に限界があって、減る一方とします。
一人の人のを都度処理するなら下記のような感じで書くと思います。
Vueとか使ってるとよく使うオブジェクトリテラルっす。

// 小数第N位で四捨五入
function roundFloat(number, n) {
    var _pow = Math.pow(10 , n);
    return Math.round(number * _pow) / _pow;
}
(function() {
    // Object Literal
    var person = {
        name: 'Bob',
        height: 180,
        weight: 65,
        foodNum: 10,  // classにしたときにこれを静的変数にしたい…
        bmi: function() {
            if (!this.height || !this.weight) {
                return 0;
            }
            return roundFloat(this.weight / Math.pow(this.height / 100, 2), 2);
        },
        show: function() {
            console.log('name: ' + this.name + ", bmi: " + this.bmi() + ", foodNum: " + this.foodNum);
        },
        eat: function() {
            if (this.foodNum != 0) {
                this.foodNum--;
                this.weight += 1;
            }
        }
    };
    person.show();
    person.eat();
    person.show();
})();

出力結果はこんな感じです

name: Bob, bmi: 20.06, foodNum: 10
name: Bob, bmi: 20.37, foodNum: 9

ただ、複数人のを一度に処理したいときにclass化してかつ、foodNumは共有資源で誰かが食べると、
みんなの分が減るものとします(なんか怖いですが)。
そうした時に、global変数以外では静的変数にしたくなります。
しかし、JavaScriptには静的変数がありません…。というのでこうやってしまおう!というもの。

ES5版

function roundFloat(number, n) {
    var _pow = Math.pow(10 , n);
    return Math.round(number * _pow) / _pow;
}
(function() {
    // Class
    var Person = function(name, height, weight) {
        if (!(this instanceof Person)) {
            return new Person(name, height, weight);
        }
        this.name = name;
        this.height = height;
        this.weight = weight;
    };
    Person.prototype.bmi = function() {
        if (!this.height || !this.weight) {
            return 0;
        }
        return roundFloat(this.weight / Math.pow(this.height / 100, 2), 2);
    };
    Person.prototype.show = function() {
        console.log('name: ' + this.name + ", bmi: " + this.bmi() + ", foodNum: " + Person.foodNum);
    };
    Person.prototype.eat = function() {
        if (Person.foodNum && Person.foodNum != 0) {
            Person.foodNum--;
            this.weight += 1;
        }
    }
    Person.foodNum = 10;  // static variable

    var person1 = new Person('Bob', 180, 65);
    person1.show();
    person1.eat();
    person1.show();
    var person2 = new Person('Alice', 170, 50);
    person2.show();
    person2.eat();
    person2.show();
})();

出力結果

name: Bob, bmi : 20.06, foodNum: 10
name: Bob, bmi : 20.37, foodNum: 9
name: Alice, bmi : 17.3, foodNum: 9
name: Alice, bmi : 17.65, foodNum: 8

Person.foodNumと単にオブジェクトにプロパティを設定してあげているのがポイントです。 JavaScriptっぽいですね。JavaScriptに慣れてない人はなんだこの変態文法と思うはずです。
そんな人はES2015版を見てもらえばいいかもしれません。ただし、実運用で使う場合はBabelを忘れずに…。

ES2015版

const roundFloat = (number, n) => {
    const _pow = Math.pow( 10 , n );
    return Math.round( number * _pow ) / _pow;
}
(() => {
    // ES2015 Class
    class Person {
        constructor(name, height, weight) {
            this.name = name;
            this.height = height;
            this.weight = weight;
        }
        static foodNum = 10;  // static variable
        bmi() {
            if (!this.height || !this.weight) {
                return 0;
            }
            return roundFloat(this.weight / Math.pow(this.height / 100, 2), 2);
        }
        show() {
            console.log(`name: ${this.name}, bmi : ${this.bmi()}, foodNum: ${Person.foodNum}`);
        }
        eat() {
            if (Person.foodNum != 0) {
                Person.foodNum--;
                this.weight += 1;
            }
        }
    }
    const person1 = new Person('Bob', 180, 65);
    person1.show();
    person1.eat();
    person1.show();
    const person2 = new Person('Alice', 170, 50);
    person2.show();
    person2.eat();
    person2.show();
})();

さっきと同じです。ES5版のSyntax Sugarっすね。Java/C#っぽくなりました。TypeScriptだとなお近くなります。

ぼやき

JavaScriptとNode力が足りないのでたまにES5で書くとthis.hoge = function() {}と書かねばならないところを、
var foo = function() {}とやって自爆しまう雑魚でした。
ECMAScript 6 compatibility table を見ると、もう最近はIEを捨てると、ちょっとしたJSを書くときではBabeらずにES2015で書いてしまってもいいかもしれない…と思った。 JSの辛いところは古くからあるライブラリはやはりES5以前の知識が必須なところですかね。

参考リンク

Static variables in JavaScript - Stack Overflow

上限付き境界ワイルドカードについて

f:id:fa11enprince:20200511185745j:plain Javaを使っていると、Clazz<Foo extends Bar>とかClazz<? extends T>というような
あまり見た目にやさしくない呪文を見かけると思います。
これはなんだ?って話です。
これは上限付きワイルドカード(upper bounded wildcard)といいます。

そもそもなんでこれが必要なのか

メソッドやクラスを使う場合を考えてみると
どんな型でも受け入れたい場合どうすればよいか。
その場合はObjectを使います。。。もしくは?(unbounded wildcard)を使います。。。
ってのは冗談で、そうすると使いたいメソッドがObjectにないことが多くて
警告でcastしてないよ!!ってのがしょっちゅう出てきます。 そこで出てくるのがジェネリクス(Generics)です。
List<E>とかがその典型ですね。
List<Integer>とすればInteger型でListが扱えます。
List<String>とすればString型でListが扱えます。

List<T>だけで十分ではないの?って思うと思います。 たいていはそれで充分なのですが、なんかたまに
? extends T
ってのを見かける。これJava特有だし。(C#にはwhereなるものがあるが…) これライブラリでよく見ると思いますが、自分での実装で使う機会はそう多くないため、
いつ使うんだ?ってなりがちです。

具体例

例えばですが、配列を使っていてソートをするのに
挿入ソートのクラスでどんな型でも受け入れたいってことにしたい場合、
こいつが必要になってきます。
すごい単純なクラスを作ってみます。
単にInsertionSorter<T>となっていないところがポイントです

import java.util.Arrays;

public class InsertionSorter<T extends Comparable<T>> {
    T[] data;

    public void sort(T[] data) {
        this.data = data;
        isort(data);
    }

    private void isort(T[] data) {
        for (int left = 1; left < data.length; left++) {
            for (int right = left; right > 0
                 && data[right - 1].compareTo(data[right]) > 0; right--) {
                swap(data, right - 1, right);
            }
        }
    }

    public void swap(T[] arr, int pos1, int pos2) {
        T temp = arr[pos1];
        arr[pos1] = arr[pos2];
        arr[pos2] = temp;
    }
    
    public static void main(String[] args) {
        Integer[] data = { 5, 4, 3, 2, 6, 7, 8, 1, 9 };
        InsertionSorter<Integer> sorter = new InsertionSorter<>();
        sorter.sort(data);
        Arrays.stream(data).forEach(i -> System.out.print(i + ","));
    }
}

オンラインで実行したい方はこちら: http://tpcg.io/bTn8mbKH

sortをinterfaceのメソッドにしてなくてStrategyパターンになれないから微妙ですが…例なのでシンプルに。

<T extends Comparable<T>>
Comparableを継承するTって意味です(extends継承だけでなくimplements継承も含まれます)。
要はComparableを継承するものに制限しています。
上記のクラスではcompareTo(T o)を使っています。
compareTo(T o)を使えるのはComparableな型でなくてはなりません。
ところが単にTとしてしまうとcompareTo(T o)なんてメソッドありませんとエラーになってしまいます。
ピンとこない方は、上記のURLでInsertionSorterのGenericsの部分を単にTに変えてみてください。 実行しようとするとエラーになると思います。 それを解決するためにこのT extends Comparable<T>というものがあります。
こうすると、「ほうほう、このTはComparableなんだな」とコンパイラがわかってくれてエラーにしなくなります。

参考リンク

https://www.ibm.com/developerworks/jp/java/library/j-jtp07018.html

Express 4 + EJS + Socket.IOでWebSocketをやってみる

f:id:fa11enprince:20200416000912j:plain WebSocketをNode.js(Express 4 + EJS + Socket.IO)で復習がてら実装しました。 ハマりポイントはbin/wwwを修正しないと動いてくれないところ。
Expressってシンプルで綺麗な設計のフレームワークなんですが、
結構はまりやすいポイントが多いと思います。

Nodeおよびライブラリのバージョン

Node.js   12.14.1
Express    4.16.1
socket.io  2.3.0
EJS        2.6.0

最終成果物

次のと同じものを作る手順を記載します。

ひな形を作成

まずexpress-generatorをグローバルインストールをします。

$ npm install express-generator -g

次にひな形生成をします。いわゆるスキャフォールディングです。 プロジェクトのフォルダはexpress-ws-ejsとします。 テンプレートはEJSを選択します

$ cd express-ws-ejs
$ express --ejs

ソースコードを修正

まずsocket.ioを使えるようにします。

$ npm i socket.io --save
$ npm i

/models/chat.js

WebSocketのサーバ側です。 新規作成します。modelsフォルダを作り、chat.jsを作成します。

const socketio = require('socket.io');

function chat(server) {
    const sio = socketio.listen(server);
    sio.on('connection', function(socket) {
        socket.on('chat-message', function(msg) {
            console.log('Send message to client');
            sio.emit('chat-message', msg + '💛');
        });
        socket.on("disconnect", function() {
        });
    });
};

module.exports = chat;

■補足
いくつかあるemitの違い

const sio = socketio.listen(server);
sio.on('connection', function(socket) {
    socket.emit('chat-message', 'message'); // 送信元クライアントだけに送信
    socket.broadcast.emit('chat-message', 'message'); // 送信元を除く全クライアントに送信
    sio.emit('chat-message', 'message'); // 接続されている全クライアントに送信
}

他にも似たようなのがありますが、これだけ押さえておけばよいでしょう。
javascript - Send response to all clients except sender - Stack Overflow

/bin/www

ここでsocket.ioのlistenをしている/models/chat.jsを呼ばないといけません。
chat.jsはのちほど作成します。 app.jsでこれを呼ぶことはできません。というのもcreateServerをしているのが
/bin/wwwだからだと思われます。 ちなみにですが、varじゃなくてconstがいいのですがexpress-generatorvarで作るので、
varのままにしていたりします。

@@ -5,7 +5,8 @@
  */
 
 var app = require('../app');
-var debug = require('debug')('express-ws-ejs-o:server');
+var chat = require('../models/chat');
+var debug = require('debug')('express-ws-ejs:server');
 var http = require('http');
 
 /**
@@ -28,6 +29,7 @@
 server.listen(port);
 server.on('error', onError);
 server.on('listening', onListening);
+chat(server);
 

/app.js

usersのrouterはいらないので消します

@@ -5,7 +5,6 @@
 var logger = require('morgan');
 
 var indexRouter = require('./routes/index');
-var usersRouter = require('./routes/users');
 
 var app = express();
 
 @@ -20,7 +19,6 @@
 app.use(express.static(path.join(__dirname, 'public')));
 
 app.use('/', indexRouter);
-app.use('/users', usersRouter);
 
 // catch 404 and forward to error handler
 app.use(function(req, res, next) {

/controllers/index.js

これは新規作成します。controllersフォルダを作り、index.jsを作成します。

exports.index = function(req, res) {
    res.render('index', { title: 'Express' });
}

/routes/index.js

var express = require('express');
var router = express.Router();
var index_controller = require('../controllers/index');

router.get('/', index_controller.index);

module.exports = router;

その他リソースおよびview

主にクライアント側です。
Node使ってるとクライアント側なのかサーバ側なのかたまに混乱します。
/public/javascripts/index.js

$(function() {
    const socket = io('http://localhost:3000');
    $('form').submit(function() {
        console.log($('#m').val());
        socket.emit('chat-message', $('#m').val());
        $('#m').val('');
        return false;
    });
    socket.on('chat-message', function(msg) {
        $('#messages').append($('<li>').text(msg));
        window.scrollTo(0, document.body.scrollHeight);
    });
});

/public/stylesheets/index.css

* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
#messages { margin-bottom: 40px }

/views/index.js

@@ -2,10 +2,18 @@
 <html>
   <head>
     <title><%= title %></title>
-    <link rel='stylesheet' href='/stylesheets/style.css' />
+    <link rel='stylesheet' href='/stylesheets/index.css' />
   </head>
   <body>
-    <h1><%= title %></h1>
-    <p>Welcome to <%= title %></p>
+    <ul id="messages"></ul>
+    <form>
+        <input id="m" autocomplete="off">
+        <button type="submit">Send</button>
+    </form>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
+    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
+        integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="
+        crossorigin="anonymous"></script>
+    <script src="javascripts/index.js"></script>
   </body>
 </html>

Expressの起動

最後にExpressを起動すれば終わりです。

$ npm start

http://localhost:3000 にアクセスし、ブラウザのウィンドウを2つ以上立ち上げると動作確認ができます。 f:id:fa11enprince:20200416002210p:plain

参考

https://socket.io/get-started/chat/
https://liginc.co.jp/web/programming/node-js/132081
https://developer.mozilla.org/ja/docs/Learn/Server-side/Express_Nodejs
https://www.gitignore.io/api/node

WindowsでGitLabを構築(Docker)

f:id:fa11enprince:20200413013004j:plain
Docker DesktopによるGitLab構築方法の紹介です。
一応、初心者向きにはなっていますが、
Dockerの基礎知識は知っている前提の記事です。
Windows + Docker Desktop (旧版 Docker for Windowsでも可)で構築します。
Windowsでも動けば、Mac/Linux等でも動くでしょう。
Windows固有のハマりポイントはマウントの設定でしょうか

Dockerは気づくとITエンジニアほぼ必須の知識になってしまいました。
正直ここまで広まるとは思っていませんでした。
一部環境ではVT-x(インテル 仮想化支援機構)がONにするのを許されていなくて、
手元でDocker使えない、不便だーなんてことが起きてますが、
その場合はあきらめて開発サーバーを建てましょう(わたしもご多分に漏れず…)。
Docker使う場合は基本的にはLinuxがベターではあります。
とはいえどういうわけか手元にはWindowsマシン(Windows 10 Pro Edition)しかないみたいなときとか役に立つかと。

この記事について

docker-composeにてサクッとGitLabを立ててみようという記事です。
ただ、デフォルトのままではメール設定どうすんの?
とかWindowsの場合、ちょっとハマりがあったりとか、
そもそも遅いとか言った問題があるかと思います。
GitLabで使うデフォルトはPostgreSQLです。
https://docs.gitlab.com/omnibus/settings/database.html

こだわりがなければこのまま使います。 ちなみに、Dockerを使っている人はご存知かと思いますが、 Docker内のデータは一度止めてしまうと基本的にすべて消えてしまいます。
そのために、ホスト側にデータ領域をマウントし、再起動時にそこから読むことで永続化を行っています。

前提条件

私の環境は以下です。多少異なっていてもおそらく大丈夫かと思います。
かなり古い場合でなければ、特にDocker for Windowsでも大丈夫かと。

OS/ソフト バージョン
Windows 10 1903
Docker Desktop community 2.2.0.4

設定方法

Docker Desktopおよびホスト側の設定

Settings > Resources > File Sharing でCドライブを選びます

docker volume の作成

Windowsでは起動時に権限がらみのエラーが発生してしまうので、
マウントするのに名前付きボリュームを利用します。 docker volume lsでその一覧があとで参照できます。
実際にどこにマウントされているかはDocker DesktopのDashBoardのコンテナのInspectから参照することもできます。

docker-composeの設定

ドキュメント(Documents)ディレクトリ配下
%USERPROFILE%\Documents
docker\gitlab
というフォルダを作り、そこにdocker-compose.ymlを作成します
今回はPostgreSQL使用、
メールはGmail使用
としました。 この辺りの設定(GITLAB_OMNIBUS_CONFIG [/etc/gitlab/gitlab.rb])は
gitlab-ctl reconfigureで設定できる内容です(詳細割愛)。
GitLab的にはGmailは非推奨のようですが、例として使いやすいのでGmailで説明します。
https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md
下記に示した内容をいくつか置き換えて社内メール等を設定すれば環境構築できるかと思います。
大抵のケースの場合SMTP周りの設定をしておけばいいと思われます。

version: "3.6"

services:
  web:
    image: 'gitlab/gitlab-ce:latest'
    restart: always
    hostname: '127.0.0.1'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://127.0.0.1:8929'
        gitlab_rails['gitlab_shell_ssh_port'] = 50022
        gitlab_rails['smtp_enable'] = true
        gitlab_rails['smtp_address'] = "smtp.gmail.com"
        gitlab_rails['smtp_port'] = 587
        
        gitlab_rails['smtp_domain'] = "smtp.gmail.com"
        gitlab_rails['smtp_authentication'] = "login"
        gitlab_rails['smtp_enable_starttls_auto'] = true
        gitlab_rails['smtp_tls'] = false
        gitlab_rails['smtp_openssl_verify_mode'] = 'peer'

        gitlab_rails['smtp_user_name'] = "<your gmail address>"
        gitlab_rails['smtp_password'] = "<your passowrd>"
        
        # Gitlab tuning
        unicorn['worker_processes'] = 2
        unicorn['worker_timeout'] = 600
        postgresql['shared_buffers'] = "512MB"
        
    ports:
      - '8929:8929'
      - '50022:22'
    volumes:
      - 'gitlab_config:/etc/gitlab'
      - 'gitlab_log:/var/log/gitlab'
      - 'gitlab_data:/var/opt/gitlab'

volumes:
  gitlab_config:
    external: true
  gitlab_log:
    external: true
  gitlab_data:
    external: true

1点注意事項があります。 2段階認証プロセスを使っている場合、あれれ?メールが飛ばないなんてことがあります。

Gmail を使って Net::SMTPAuthenticationError が出力される場合の解決法 https://karur4n.hatenablog.com/entry/2015/03/07/234600

この場合上記に従い、アプリパスワードを使います。

社内メールを使用する場合

仕事で使う場合は、社内メールだと思いますので
doc/settings/smtp.md · master · GitLab.org / omnibus-gitlab · GitLab
あたりの設定が参考になるのではないでしょうか。

パフォーマンスチューニングについて

GitLabは結構マシンパワーを要求するのでpostgresql['shared_buffers']を大きめにとったほうが良いかと思います。
できれば2GB以上あったほうがサクサク動きます。
unicorn['worker_processes']も多いほど良いです。

名前付きボリュームの作成

コマンドプロンプトにて初回のみ

docker volume create --name gitlab_config
docker volume create --name gitlab_data
docker volume create --name gitlab-logs

として名前付きボリュームを作成します。

サーバー立ち上げ

コマンドプロンプトにて

cd %USERPROFILE%\Documents
docker-compose up -d

で起動します。

立ち上がりはマシンによっては遅いのでカップラーメンを食べながら待ちます。
Dockerのタスクトレイ内のアイコンを右クリックして、
Dashboardにアクセスすると、起動状態が確認できます。

GitLabにアクセス

しばらくたつと、 http://127.0.0.1:8929/
にアクセスします。

初回はrootのパスワードを求められます。

ユーザを作成した時にメールが飛べば成功です。

あとは煮るなり焼くなり。
GitHub同様使いやすいかと思います。

参考リンク

https://docs.gitlab.com/omnibus/docker/
https://mikoto2000.blogspot.com/2018/06/gitlab-docker-image-https.html https://myamada9999.hatenablog.com/entry/2018/11/04/204613
https://qiita.com/TomoyukiSugiyama/items/b17800d4e142e3dc549b
https://qiita.com/comefigo/items/2066dd02e2a0f3b470ca

チューニング系参考リンク

http://www.sakutyuu.com/technology/?p=1979
https://qiita.com/k_nakayama/items/9f083a4700915d02104a

名前付きボリューム関連

https://tan-y.hatenablog.com/entry/2019/02/26/003000
https://mikoto2000.blogspot.com/2018/06/gitlab-docker-image-https.html

Docker Desktopのマウント問題

https://qiita.com/kikako/items/7b6301a140cf37a5b7ac
https://stackoverflow.com/questions/41036273/disable-autostart-of-docker-compose-project

docker volume削除

https://qiita.com/Ikumi/items/b319a12d7e2c9f7b904d

メール関連

https://h-otter.hatenablog.jp/entry/2015/07/31/220511
https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md

GitLab CE Omnibus の基本操作手順

https://fumiyas.github.io/gitlab/install-omnibus.html