FM牛鍵屋本舗

プログラマ(弱)の日々精進系ブログ

Mac+Intellijで"Created by ${USER}"を変更するよ!!

うっかりリアルイニシャルが記載されたままGistにあげてしまったので変更方法をメモするよ。

/Applications/IntelliJ IDEA CE.app/Contents/binidea.vmoptionsというファイルがあるので

これに-Duser.name=${変えたい名前}を追記してIntellijを再起動するよ。

stackoverflow.com

Javaでファイルを読むよ、でもこれは覚書だよ

Java8の試験でやったはずなんですけど、

すっかり忘れてましたね。

Files.linesでStreamを返せます。

でも、何度検証してみても

BufferedInputStream is = BufferedInputStream(FileInputStream(new File("/nhome/hoge.list")));

ByteArrayOutputStream os = new ByteArrayOutputStream();

byte[] bytes = new byte[1024 * 10];
int res = -1;
while ( (res = is.read(bytes, 0, bytes.lengh())) != -1) {
  os.write(bytes, 0, res);
}
String result = new String(os.getByteArray(), StandardCharset.UTF_8);

これの方が高速なんだ…うーむ。

Windowsでシンボリックリンクを作るよ!!

エディタはkaoriyaさんのところのvim(gvim)を使っています。

最近、grepをvimgrep(遅い)から変えようとjvgrepを入れました。

github.com

既存ソースの調査をしなければならなくなり、ふと思いました。

Eclipseでいうワーキングセットみたいなディレクトリ作っといたら楽じゃないか、と。

そのためには…そうだ、シンボリックリンクを作ろう!!Windowsだけど!!

管理者モードでコマンドプロンプトを立ち上げて

mklink /D <作成するリンク> <対象ディレクトリ>

Dスイッチはディレクトリを対象にするものです。

また、他にも

  • J : ジャンクション(簡易版のディレクトリリンク、ソフトリンク)
  • H : ハードリンクを作成する。管理者モードでなくても使用できる。

などがあります。

しめしめ、これでワーキングセットが作れてウハウハやで…

と、思っていたら、jvgrepはシンボリックリンクディレクトリとして解釈しないようです。 具体的に言うと、go言語の FileInfo.IsDirectoryでtrueを返さない。

がっかり。

落としてきてその部分を修正してビルドするのも手だけど 一旦諦めてjvgrepのドキュメントをよく読むと

あ、検索対象、可変長だ

:jvgrep "regex pattern" ./hoge/**/*.* ./moge/**/*.*

一旦はこれで…

"Web開発者のための大規模サービス技術入門"を読んだよ

"Web開発者のための大規模サービス技術入門"を読んだよ

紹介

Web**+DB Press Plusの持ちやすいサイズ。 はてなの方が書かれた書籍で、インターン研修をベースにしているようです。

インターンレベル高っ。

実際のはてなや他の大規模サービスの事例をもとに、 インフラやアプリケーションが大規模サービスを構築する際に 考慮するべきことがわかりやすくかかれています。

また、一部アルゴリズムやツールの紹介もあって楽しく読み通せました。

10年前くらいの本なので、クラウド周りの状況が変わっているかな、とは思いますが、 そのあたりにも触れています。ELBが出た当時。

雑感

はてなの人レベル高っ。

もともと大規模なシステム関わったことがなく、そこそこのエンタープライズシステムばかりで ある程度のリソース負荷であればほとんど考えてこなかったのですが、 この本で紹介されている「圧縮」とか「検索エンジン」とかは流用できるなー。

相変わらず計算量オーダーのlogがよくわからない…。

それにしてもわくわくしてなにか作りたくなる気分になる一冊でした。

メモ

  • ディスクI/Oを減らし、極力オンメモリにする
  • DBの負荷分散、冗長化
    • テーブルの役割毎に異なるホストへ分割(論理的、派生して参照中心か更新があるか)
    • 複数HOSTにまたがるのでJOINを避ける
  • ロードアベレージ重要
  • 用途特化型のインデクシング
  • 圧縮
  • ベイジアンフィルタ
  • Trie
  • AC法
  • システム安定化に対する「地雷」
    • 特定条件下で発動
  • システム安定化に関わる「自律化」

アルゴリズムとOS、ジョブキューとキャッシュについて調べたくなりました。どうしても。

# Javaで簡易テンプレートを置換するよ!!

Javaで簡易テンプレートを置換するよ!!

仕事でテンプレートのプレースホルダーっぽいのを置換することになりました。

~テンプレートエンジン使えよ~

プレースホルダーはある特定の文字列から始まり、特定の文字列で終わります。

置換する文字列は元を辿れば画面入力、なので上記の特定の文字列が入力されても置換されないようにする必要があります。

そのため、単純なreplaceではだめで、どうしようかなーと考えた結果、一旦プレースホルダーを残したままsplitで配列化したあとに、ループ処理で置換することにしました。

~最初の仕様では置換箇所一つだったから問題なかったのに…~

さあどうやってsplitしてやろうか!!

apache-commonsのStringUtils

困ったときのStringUtils。

JavaDocを舐めてみましたが、置換文字列を残すのは難しそう。

java.util.StringTokenizer

コンストラクタだけで仕様満たせるとか神ってる…!!と思ったら

StringTokenizerは、互換性を維持する目的で保持されているレガシー・クラスであり、新規コードでは使用が推奨されていません。この機能の使用を考えているなら、Stringのsplitメソッドまたはjava.util.regexパッケージを代わりに使用することをお薦めします

Oops.

java.lang.String

ずばりそのものはない。

正規表現で後読みとか駆使すれば出来なくはない(と思う)けど、これはまたの機会に。

力技

もともとテンプレートエンジン使えよってところからスタートしているので、どう転んでも車輪の再発明

それなら力技でやってやる…!!

インターフェース

すでにプレースホルダーが複数Enumに定義されていたのでインターフェース作成。

後述の通り、こいつが開始文字列と終了文字列を持っていてもいいのかもしれない。

package sample.template;

public interface ReplacementHolder {
     String getReplacement();
}

置換文字列の実態を保持しているEnum

package sample.template;

public enum PlaceholderName implements ReplacementHolder {
    /** 猫の名前 */
    CAT_NAME("#{catName}#")
    /** 犬の名前 */
    , DOG_NAME("#{dogName}#")
    ;
    /** コンストラクタ */
    PlaceholderName(String replacement) {
        this.replacement = replacement;
    }
    private String replacement;
    public String getReplacement() {
        return this.replacement;
    }
}

分割した各トークンを表すクラス

package sample.template;

public class Token {

    /** トークン */
    private String token;

    /** 置換対象か */
    private boolean replaces;

    /** コンストラクタ */
    public Token(String token) {
        this(token, false);
    }

    /** コンストラクタ */
    public Token(String token, boolean replaces) {
        this.token = token;
        this.replaces = replaces;
    }

    /** 更新 */
    public void update(String token) {
        if (this.replaces && token != null) {
            this.token =  token;
        }
        this.replaces = false;
    }
    public boolean replaces() {
        return this.replaces;
    }
    public boolean notReplaces() {
        return !this.replaces;
    }
    public String getToken() {
        return this.token;
    }
}

実際に処理するクラス

package sample.template;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Replacer<T extends Enum<?> & ReplacementHolder >{

    /** 開始マーカー */
    private String startMarker;
    /** 終了マーカー */
    private String endMarker;

    /** コンストラクタ BeanValidatorとかで契約的にしたほうがよいかも */
    public Replacer(String startMaker, String endMarker) {
        if (startMaker == null || endMarker == null) {
            throw new IllegalArgumentException("不正な引数");
        }
        this.startMarker = startMaker;
        this.endMarker = endMarker;
    }

    /** 置換 */
    public String replace(String template, Map<T, String> replacements) {
        // 前提条件チェックコードは省略
        List<Token> tokens = this.tokenize(template);
        for (Token token : tokens) {
            if (token.notReplaces()) {
                continue;
            }
            replacements.keySet().stream()
                    .filter(key -> key.getReplacement().equals(token.getToken()))
                    .findFirst() // 同じキーが二度指定されている場合は事前にチェック処理を入れるしかない
                    .ifPresent(key -> token.update(replacements.get(key)));
        }
        return this.serialize(tokens);
    }
    /** 直列化 */
    public String serialize(List<Token> tokens) {
        // 前提条件チェックコードは省略
        return tokens.stream().filter(Token::notReplaces).map(Token::getToken).collect(Collectors.joining());
    }
    /** トークン化 */
    public List<Token> tokenize(String src) {
        List<Token> tokens = new ArrayList<>();
        if (src == null) {
            return tokens;
        }
        int start;
        while(0 <= (start = src.indexOf(startMarker))) { // 開始マーカーが存在する間ループ
            /* 終了位置を取得 */
            int end = src.indexOf(endMarker, start);
            if (end < 0) {
                end = src.length();
            } else {
                end += endMarker.length();
            }
            if (start != 0) { // 先頭に開始文字列がある場合は分割しない
                tokens.add(new Token(src.substring(0, start)));
            }
            final String parts = src.substring(start, end);
            tokens.add(new Token(parts, parts.endsWith(endMarker)));
            src = src.substring(end);
        }
        if (src != "") {
            tokens.add(new Token(src));
        }
        return tokens;
    }
}

試す

package sample.template;

import java.util.LinkedHashMap;
import java.util.Map;

public class ReplacerTest {

    public static void main(String[] args) {
        Replacer<PlaceholderName> replacer = new Replacer<>("#{", "}#"); // ジェネリクスから取得できるようにしてもいいかも
        Map<PlaceholderName, String> map = new LinkedHashMap<>();
        map.put(PlaceholderName.CAT_NAME, "小太郎");
        map.put(PlaceholderName.DOG_NAME, "シロ");

        System.out.println(replacer.replace("#{catName}#ちゃんと#{dogName}#ちゃんは仲良しです#{hoge", map));
    }

}

小太郎ちゃんとシロちゃんは仲良しです#{hoge

うーん、あんまりスマートじゃないなあ。

VagrantのGetting Startを読んでみたよ!!

VagrantのGetting Startを読んでみたよ!!

諸元

なんか仮想化したいなーと思って。

序文

Vagrantは単一ワークフローで仮想マシン環境を構築・管理するツール。簡単なワークフローと自動化に注力してて、「僕の環境なら動くのに」という例のアレを過去の遺産にするよ。

仮想マシンの提供元としてVirtualBox(デフォルト)、VMWareAWSなどなどを選べるし、ShellScriptとかChefなどは仮想環境上で、自動的にソフトウェアのインストールや設定ができますよ、と。

他のツールとの比較

VS. CLIツール

仮想化ソフトウェアにもCLIツールは入ってるけど、それら自体に依存している。Vagrantも内部的にはそれらを使ってるけどね。 Vagrantとそれらの違いは、Vagrantはその上に一貫したワークフローとして構築されていること。ようするにアダプタというかFacadeになっているわけか。 仮想化ソフトウェアのバージョンごとの違いもVagrantが解決するよ。

VS. Docker

Vagrantは複数のプラットフォームにまたがって一貫した開発環境のワークフローを提供することに注力していて、Dockerは存在するコンテナ化システムの一貫した実行をコンテナ管理する。つまり、開発環境に注力しているのと、コンテナの実行に注力している違いがあるわけですね。たぶん。

コンテナは仮想マシンに比べて軽量で、開始と実行が速いよ。 Microserviceとかには向いてるね。

DockerもVagrantも選べるようにコミュニティが作ったライブラリを落とせるよ

VS. Terraform

どちらもHashiCorpが作っているけど、Terraformはインフラ構築。Infrastracture as a Code。

インストール

https://www.vagrantup.com/downloads.htmlから適したバイナリをダウンロードしてインストールしてね。 終わったらvagrantコマンドを打ってみて確認してね。 パスが通っていたらOKだよ。 システムのパッケージマネージャでインストールしないでね。

プロジェクトの設定

どんなVagrantプロジェクトでも一番最初にするのはVagrantfileの作成だよ。このファイルの目的は2つだよ。

  • vagrantのルートディレクトリを決めるためだよ
  • どんなソフトウェアをインストールするのか、どのようにそれにアクセスするのかなどの資産や(仮想)マシンの説明をするためだよ

適当なディレクトリを作って、vagrant initを実行しよう。 これでVagrantfileは作成されるよ。

Vagrantfileはバージョン管理下におけるから、だれの環境でも共通で使えるね。

ボックス

時間がかかって面倒くさい手段で最初から仮想環境を作る代わりに、Vagrant仮想マシンを高速でクローンするためのベースイメージを使うよ。それはboxとして知られていて、それを指定することがVagrantfile作成後の次のステップになるよ。

ボックスのインストール

vagrant box add hashicorp/precise64 これはCloud Box Catalogからのダウンロード。 ローカルやURLの指定も可能。

Vagrantfileにこんなふうに書いてもできるよ。

Vagrant.configure("2") do |config|
  config.vm.box = "hashicorp/precise64"
  config.vm.box_version = "1.1.0" # これはオプション
end

実行と接続

vagrant up

起動するけどUIはないよ。

起動する端末に接続するのは以下だよ。

vagrant ssh

同期されたフォルダー

Vagrantfileが配置されているフォルダと仮想環境の/vagrantが同期されているよ。

vagrant sshで接続すると/home/vagrantにいるけど、これとは違うよ。

供給

apacheでwebserverを立ててみよう。

まずShellScriptを作るです(bootstrap.sh)。

#!/usr/bin/env bash

apt-get update
apt-get install -y apache2
if ! [ -L /var/www ]; then
    rm -rf /var/www
    ln -fs /vagrant /var/www
fi

みればわかるけど、apacheいれて、ドキュメントルートを同期フォルダに変えているよ。

Vagrantfileを書き換えるよ。

Vagrant.configure("2") do |config|
  config.vm.box = "hashicorp/precise64"
  config.vm.provision :shell, path: "bootstrap.sh"
end

vagrantが起動していたらvagrant reload --provisionを実行するよ。 vagrant upでもいいけど、realoadのほうが速いよ。

まだネットワークの設定していないから、仮想環境内部から確認するよ

vagrant ssh
vagrant@precise64:~$ wget -qO- 127.0.0.1

ネットワーキング

ホスト側のポートフォワーディングで仮想端末に飛ばすよ。

Vagrantfileを書き換えるよ。

Vagrant.configure("2") do |config|
  config.vm.box = "hashicorp/precise64"
  config.vm.provision :shell, path: "bootstrap.sh"
  config.vm.network :forwarded_port, guest: 80, host: 4567
end

reloadupを使って更新するよ。

http://localhost:4567を開くと…わお。

他にも設定次第でIPを指定したりも出来るけど、別の説明箇所を見てほしいんだよ。。。

共有

Vagrant Shareと呼ばれる原始的な機能でこの環境を共有できるよ。

でも、商用レベルでは使えないから気をつけてね。

vagrant share

URLが表示されるんだけど、それを開くとさっきのapacheのページが表示されるよ。すごいね。

落とす

開発環境をクリーンする方法は3つあるよ。

  • vagrant suspend
    • 再起動
    • すごく速いよ
    • メモリもHDDも確保したままだよ
  • vagrant halt
    • 再起動
    • クリーンに停止するよ
    • ゲストマシーンはDISKスペースを確保しているよ
  • vagrant destroy
    • guest側のハードディスクも削除するよ
    • やっぱりvagrant upで起動するよ
    • 時間がかかるよ

再構築

vagrant up

仮想マシンの提供者

今までVirtualBoxでやってきたけど、upのオプションで仮想マシンの提供元を変えられるよ。

VMWare:

vagrant up --provider=vmware_fusion

AWS:

vagrant up --provider=aws

GoogleAppsScriptをローカルで編集できるclaspを試してみるよ!!

GoogleAppsScriptをローカルで編集できるclaspを試してみるよ!!

https://github.com/google/clasp

Develop Apps Script projects locally using clasp (Command Line Apps Script Projects).

とあるように、claspを利用することで(Google)AppsScriptプロジェクトをローカルで開発出来るようになります。 そんなCLIツール。

ここのReadmeを適当に進めてみる。

インストール

前提としてnpmが必要っぽい。

ターミナルから sudo npm i @google/clasp -g を実行します。

それから、AppsScriptAPIをhttps://script.google.com/home/usersettingsで有効化します。

機能

  • ローカル開発:claspはApps Script Projectのローカル開発を可能にします。それによりソースコード管理、他の開発者とのコラボレーション、好きなツールでの開発が出来ます。要するにVimでコーディングできてGithubが使えますよ、といったところか。
  • 開発バージョン管理:複数の開発段階の作成、更新、閲覧が出来ます。_要するにGithu(ry
    • と思ったのですが、GoogleAppsScriptはデプロイっぽいことするたびにバージョンと説明を入力するので、そのことかな。
  • コードの構造化:scriptをホストしているGoogleのフラットなプロジェクトから自動的に(ローカルの)フォルダに変換します。

コマンド

clasp

推測だけど、Command Line Apps Script Projectかな

最初

clasp login

ブラウザでGoogleのログイン画面が表示され、認証と認可が求められる。

Gmailサードパーティにメール読まれるみたいな話もあるのでちょっと怖いですが、あとには引けない。

これを行うとホームディレクトリに.clasprc.jsonが生成されるようです。これがcredentialになる。。。はず。

オプション 効果
--no-localhost ローカルサーバーを使わず、手動でコードを入力(というか登録??)する
--ownkey 上記.clasprc.jsonの保存先をカレントディレクトリにする。 複数のアカウントで開発している場合はこっちかな。

clasp logout

credentialsを削除することでログアウトする

clasp create

新しいプロジェクトを作成する。

どちらもオプションで、第一引数はプロジェクトのタイトル。第二引数は親プロジェクトID。

親プロジェクトIDとは何かというと、要するにGoogle SheetやGoogle DocのID。

指定しないとstandaloneプロジェクトになるよ、と。

例:clasp create "My Scripts" "ID_Gxyv****"

clasp create "My Sample1"を実行したら   appsscript.jsonが出来た。何コレ。

{
  "timeZone": "America/New_York",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER"
}

なんか設定ファイルでした。

timezoneは果たしてこれでいいのか。

clasp push

appscript.jsonがあるところで vim sample.js

function myFunction() {
  Logger.log('Hello, World');
}

clasp push

GoogleAppScriptを見に行くと上がってる!!

clasp deploy

clasp deployでデプロイ出来る

versionとメッセージを引数に取れるけど、

なくてもversionは勝手にインクリメントしてくれるみたい。