OmniAICreator/Qiita-1.07M huggingface dataset:
Robot Frameworkの技術カンファレンス、RoboCon 2018について

この記事は「[Robot Framework Advent Calendar 2017 - Qiita](https://qiita.com/advent-calendar/2017/robotframework)」の25日目の記事です。

ある技術が好きな方々が増えてくると、その技術カンファレンスが開催されることがあります。

Robot Frameworkでは `RoboCon` になります。直近では2018年に開催予定です。
[RoboCon 2018](https://robocon.io/)

RoboCon 2018の開催内容は

- 日時：2018/1/18の9:00~18:00
- 場所：フィンランドの首都ヘルシンキ
- 参加費用：470€
- Googleグループでの情報
  - [RoboCon 2018 - Google グループ](https://groups.google.com/forum/#!topic/robotframework-users/ZxbGYf5nGoU)

です。

また、前日には前夜祭、翌日にはコードスプリントが開催されるようです。

　  
RoboCon 2018のスピーカーを見ると、GitHubでRobot Framework関係のコントリビュータとして見るような方々が並んでいます。

チケットの販売状況は、2017/12/22時点で 210/250 のようですので、まだ購入できそうです。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">210/250 RoboCon 2018 tickets sold! Get yours quick if you want to join us! <a href="https://t.co/YdrK6bMAE2">https://t.co/YdrK6bMAE2</a></p>&mdash; Robot Framework (@robotframework) <a href="https://twitter.com/robotframework/status/944179707377078275?ref_src=twsrc%5Etfw">2017年12月22日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

そのため、気になる方は参加されてみてはいかがでしょうか。

　  
なお、このカンファレンスはRobot Frameworkコミュニティ主導で開催されるようです。

カンファレンスを開催するくらいコミュニティ活動は活発なようですので、今後のRobot Frameworkの発展が楽しみです。
LastPassの2段階認証よりも、1Passwordのシークレットキーのほうがセキュアかもしれない

会社でパスワードマネージャーを導入する際、数多のパスワードマネージャーから採用候補を選ぶ必要がありました。

ここで絞り込んででてきたのが、おそらく最もスタンダードな [1Password](https://1password.com/) と [LastPass](https://www.lastpass.com/) でした。

今回目的としていた利用用途はクラウドベースのパスワード管理だったので、機能面ではどちらを選んでも致命的な差はありません。しかし今回重要視していたセキュリティーの面で、両者には大きな違いがありました。

## セキュリティー・ホワイトペーパー

1PasswordとLastPassはどちらもセキュリティー設計についてのホワイトペーパーを公表しています。

- [1Password Security Design (PDF)](https://1password.com/teams/white-paper/)
- [LastPass Technical Whitepaper (PDF)](https://enterprise.lastpass.com/wp-content/uploads/LastPass-Technical-Whitepaper_Sept17_Final-1.pdf)

## 暗号鍵がひとつか、ふたつか

1PasswordやLastPassなどのパスワードマネージャーは登録したパスワードを暗号化し保存してくれるのですが、その際暗号鍵としてユーザーにひとつの「マスターパスワード」を入力してもらって、それがなければ暗号化された登録情報を読めないという仕様になっています。

そして1PasswordやLastPassに共通するセキュリティー仕様として「暗号化や復号の処理はクライアント上でのみ行い、サーバーは一切復号に関知しない・できない」ようになっています。

つまりこれらのパスワードマネージャーに登録された情報は、マスターパスワードがなければどうやっても読み出すことができないという安全仕様になっているのです。

## 多要素認証に重要な違い

さて、ここでホワイトペーパーを見るとどちらも同じようなセキュリティー機構を採用しているのですが、両者が備える多要素認証、つまりパスワードの他に別の認証要素を要求する機構に特徴的な違いがありました。

この多要素認証は、ご存知の方には耳にタコかもしれませんが「パスワードは漏洩するかもしれない」という前提の元、ユーザーの持つデバイス（スマートフォンやPCなど）自体を追加の認証要素にしよう、パスワードと一緒にそのデバイスが手元にないと認証できないようにしようという仕組みです。

この他要素認証においてLastPassが一般的な多要素認証をそのまま採用する中、1Passwordは「シークレットキー」と呼ばれる128bit以上の共通鍵を使用する特殊な方法になっていました。この仕様は実際のところ多要素認証とは趣を異にするものであり、1Passwordも実際に「多要素認証のかわりにシークレットキーを採用した」としています。

## 「認証要素」としての多要素認証

多要素認証は複数の方式がありますが、近年様々なサイトで見かけるようになった30秒ごとに書き換わるコードを入力して認証する [Time-based One-time Password (TOTP)](https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) を筆頭に、その仕様はいずれも「サーバー側に認証情報の登録を必要とする」ものになっていました。たとえばTOTPではコード生成用の鍵をサーバー・クライアントの双方に保存しておく必要があり、サーバーはそれを認証時に読み取れる必要があるのです。

## 「追加の暗号鍵」としてのシークレットキー

反面シークレットキーはユーザーごとに固有の固定値であり、この値そのものはサーバー側に保存される必要はありません。クライアントサイドでマスターパスワードと合わせて計算した認証鍵によって認証が行われ、サーバーはシークレットキーを知る必要がないのです。

ユーザーもまたこのシークレットキーを覚える必要はなく、各デバイスにはログインする際に1Passwordアプリケーションにシークレットキーが保存されます。そのためユーザー目線では引き続きマスターパスワードの入力だけで情報を開けるようになります。

さて、では1Passwordはこのシークレットキーを何に使っているのかというと、認証に加えて暗号化を強化するために使っているのです。パスワードなどの登録情報を暗号化する鍵を生成する際、マスターパスワードとシークレットキーを掛け合わせてより強力な鍵を生成しているのです。

## なぜシークレットキーがよいのか

この特性は原理上LastPassの多要素認証では実現できないものであり、現にLastPassのホワイトペーパーでも暗号鍵の生成にはマスターパスワードのみが利用されています。

この場合、暗号を破るのにLastPassではパスワードのみがあればいいようになっているので、暗号データとパスワードが流出してしまうともう手立てはありません。

反面シークレットキーの仕組みであれば、暗号化の解除にシークレットキーの所有も欠かせなくなるため、より安全性を確保できるのです。

## おわりに

ここでは最も顕著な違いであったシークレットキーについて取り上げました。冒頭で述べたパスワードマネージャーの選定ですが、セキュリティーを検討する上で実際にこのシークレットキーの仕様が決め手のひとつとなり1Passwordの採用へと至りました。

他にも両者にはアカウントのリカバリー（パスワードを忘れたアカウントの復活）などにも違いがあり、ホワイトペーパーで細かな仕様が紹介されているのでぜひご一読をおすすめします。特に1Passwordのホワイトペーパーは様々なセキュリティーリスクへの対策や限界などについても詳らかにされており読み応えがあります。
Herokuについてまとめてみた

この記事は [株式会社Y's アドベントカレンダー](https://qiita.com/advent-calendar/2017/ysinc) 21日目の記事です。
#ご挨拶
どうも、[@T_igarashi](https://qiita.com/T_igarashi)です。
[前回](#)はAWSについて触れてみましたが、今回は以前に使ったことのあるものについてまとめてみたいと思います。
#何を話すのか
[Heroku](https://www.heroku.com/)について話してみようと思います。
以前使ったときは、[Slack](https://slack.com/intl/ja-jp)でbotを作ってみたいなと思い、そのファイルの置き場所としてHerokuを使っていましたのと、今は[Crowi](http://site.crowi.wiki/)を動かす用に使用しています。
#Herokuとは
開発したWebアプリケーションなどを公開することができるもので、PaaS(Platform as a Service)と呼ばれるサービスになります。
今は

1. Node.js
1. ruby
1. Java
1. PHP
1. Python
1. Go
1. Scala
1. Clojure

に対応しています
>※Paas(Platform as a Service)
>OSとアプリケーションフレームワークを提供するクラウド

#heroku-toolbeltをインストール
[こちら](https://devcenter.heroku.com/articles/heroku-cli)に載ってある手順で(英語です…頑張りましょう)
インストールするとPCのターミナルで`heroku`のコマンドを使用できるようになります。
その後アカウント認証を行えば使用できるようになります。

#アプリケーションを作成、デプロイ
後はアプリケーションを作成したり、Crowiをデプロイすることによって使い始めることが可能です。

#終わりに
今はbotとかcrowiを動かすだけに使っていますが、もっと色々なアプリケーションを作成するときには利用できそうなので、定期的に触っていければと思います。

次は、22日目[@chockn](https://qiita.com/chockn)さんの記事になります。
Node.jsでClassを別ファイルに分割する(2017)

``module.exports``の書く場所に注意しなければならない。情報が錯綜していてなかなかうまく行かなかった。

## How to
```javascript:classess.js
module.exports = class Animal{
    constructor (baw) {
         this.baw = baw;
    }
    say (){
        console.log(this.baw);
    }
}
```
module.exportsに直接classを代入していることに注意。

```javascript:app.js
const Animal = require('./classess.js');
var inu  = new Animal('わんわん');

inu.say();   // → わんわん
```

以上。
box2d.dart が strong mode に対応


![12月-25-2017 02-11-54.gif](https://qiita-image-store.s3.amazonaws.com/0/54192/20a2af91-eebe-6e8f-87ef-0c883d28cf6d.gif)

https://plus.google.com/u/0/+filiphracek/posts/JPEQWiiDRLJ

Dart の　box2d.dart が strong mode に対応したそうです。

https://github.com/google/box2d.dart
https://filiph.github.io/box2d.dart/


# Flutter でも使えるか?

https://github.com/google/box2d.dart/blob/master/pubspec.yaml

https://github.com/google/box2d.dart/search?utf8=%E2%9C%93&q=dart%3Ahtml&type=

dart:html に依存していないので、使えそう


2015年あたりは、box2d package を使っていたよう

https://pub.dartlang.org/packages/flutter_sprites
https://github.com/flutter/flutter/commit/3118a6fba11590eeed0cf237e33a64fb7e1c86e5

なので、　 Html5 よう、Flutter よう と
どちらでも使えそう。
both of real entry and whiteout foundというエラーが発生

ログローテーションのタイミングで下記のエラーが発生。

```
# aufs au_lkup_dentry:***:logrotate[****]: I/O Error, both of real entry and whiteout found, term.log, err -5
```


どうやら、削除したはずのファイルと、whiteout(ファイルの削除情報)の両方が存在しているという状態らしい。

* aufs
unit mount形式のファイルシステム

* unit mount
複数のストレージを統合し、単一のファイルシステムとしてマウントすること
http://d.hatena.ne.jp/dayflower/20080714/1216010519

* whiteout
読み込み専用のファイルシステムのファイルを削除したかのように見せる機能。

unit mountシステムにおいて、マウント元(下位層)のファイルシステムが読み込み専用だった場合、
マウント元のファイルを削除を実施しても、マウント元にはファイルは残ったままになるが、
マウント先(上位層)ではファイルが削除される。。
代わりに、マウント先にはwihteoutというファイルの削除情報が隠しファイルとして残ることになる。

このようにマウント元の実ファイルは残ったままだが、マウント先からはファイルが隠され、
削除されたように見えるようにする機能をwhiteoutという。

視界が見えなくなるほどの猛吹雪が由来だと思われる。

https://askubuntu.com/questions/39826/what-does-whiteout-mean-in-a-copy-on-write-filesystem
お名前.com の Dynamic DNS を Linux+Wine で更新する

__DISCLAIMER: この内容はお名前.com によって公式に認められた方法ではありません。 本記事によって生じた損害について私は一切責任を負いません。__

お名前.com は Windows 用に Dynamic DNS クライアントを公開しています。

https://www.onamae.com/option/dnsrecord/ddnsclient/

しかし Linux にはありません。 これを、 Linux 上で [Wine](https://www.winehq.org/) を使って動作させる方法について説明します。

# インストール

上記クライアントは ClickOnce というweb上からダウンロードする形式のソフトウェアなので何もダウンロードすることなくコマンド一発でインストールできます。

```shell
wine rundll32 dfshim.dll,ShOpenVerbApplication http://dom.onamae.com/onamae/apl/ddns3/OnamaeDynamicDNS.application
```

## Wine を使っていない人向けのざっくり前提

上記が動作する正確な環境はわかりませんが、私の Wine 環境だと以下のとおりセットアップしていました．

```shell
sudo apt install --install-recommends winehq-devel
export WINEARCH=win32
wget  https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
chmod +x winetricks 
./winetricks cjkfonts dotnet40
```
バージョンは wine-3.0-rc2 です。


# 実行

```shell
wine start 'c:/users/ユーザー名/Start Menu/Programs/GMO Internet Group/お名前.com ダイナミック DNS クライアント.appref-ms'
```

# 設定ファイルの位置

```
.wine/drive_c/users/ユーザー名/Local Settings/Apps/2.0/Data/RQ9K044H.EC3/RARW8N51.QQC/onam..tion_febef47b6701ab64_0004.0000_0ae46297211c9e4a/Data/
```

RQ9K044H.EC3 などの文字列はインストールによって変わると思います。

# 雑感

インストーラ (本体も?) は ClickOnce てできているのでこのような方法になるようです。 

安定的に動かすなら自鯖で BIND 等を立ててお名前.com から参照させるほうが確実だと思いますか、そもそBINDを運用できるような固定 IP のサーバーを建てられるなら DDNS を必要とはしないはずです。
やはりお名前.comには今時らしい Web API 等を公開してほしいところですね。
timeout killing というログについて

# timeout killing /sbin/blkid -o udev -p /dev/mm
ブートパーティションの情報を表示しようとして、タイムアウト。

* udev
カーネルからの命令を受けて デバイスファイルを作成、消去するデーモン。

* UUID(Universally Unique Identifier)
世界で一つだけの重複しないID。

生成方法はいくつかあり、以下のようなものがある。
1. コンピュータの識別子(MACアドレスなど)と生成日時・時刻の情報を連結したもの
2. 任意のデータに対するハッシュ値

* blkid
ディスクパーティションの設定、表示するコマンド
https://linuxjm.osdn.jp/html/e2fsprogs/man8/blkid.8.html
-> -o udev : udevルールに合わせた形式で表示する 
-> -p : low level probeモード

* プローブ
コンピュータ・ネットワークの状況を調べたり監視したり、その情報を管理者に通知する機器や仕組み。

* /dev/mmcblk0boot**
ブートパーティションのこと。
※ 機種による

* ブートパーティション
ブートに必要なデータ、カーネルイメージなどがインストールされる領域。


どうも下記のログファイルが破損したため、タイムアウトが発生していたようだ。
```
# ls -la /out_storage/var/log/apt/
total 16
drwxr-xr-x  2 root root 4096 Sep  4 13:15 .
drwxr-xr-x 11 root root 4096 Sep  8 16:03 ..
-r--r--r-- 26 root root    0 Jan  1  2000 .wh.term.log
-rw-r--r--  1 root root    0 Sep  4 13:15 history.log
-rw-r--r--  1 root root   59 Jul 22  2016 history.log.1.gz
-rw-r-----  1 root adm     0 Oct 12 09:50 term.log
-rw-r-----  1 root adm    62 Jul 22  2016 term.log.1.gz

# ls -la /var/log/apt/
ls: cannot access /var/log/apt/term.log: Input/output error
total 16
drwxr-xr-x  2 root root 4096 Sep  4 13:15 .
drwxr-xr-x 13 root root 4096 Sep  8 16:03 ..
-rw-r--r--  1 root root    0 Sep  4 13:15 history.log
-rw-r--r--  1 root root   59 Jul 22  2016 history.log.1.gz
-?????????  ? ?    ?       ?            ? term.log
-rw-r-----  1 root adm    62 Jul 22  2016 term.log.1.gz

```
Ansible で PostgreSQL の initdb 処理

検証用環境の構築作業で Ansible に手を出してみた。
まだ ansible歴3日目だ。

CentOS7環境に PostgreSQL 10 を入れようとした。
手順は [PostgreSQL のダウンロードサイト](https://www.postgresql.org/download/linux/redhat/)
に従った。

あっちこっち参考にして、 yum でPostgreSQL のリポジトリの登録と

* postgres
* postgres-server
* python-psycopg2

のインストールまで Ansible Playbook  で行った。

で、次は initdb なのだが、ダウンロードサイトの手順に従うなら 
`/usr/pgsql-10/bin/postgresql-10-setup initdb`
とするところである。

以下のようにしてみた。

```
---

- hosts: postgresql,zabbixproxy
  become: yes
  tasks:
  - name: "Setup Database Cluster"
    shell: "/usr/pgsql-10/bin/postgresql-10-setup initdb"
    args:
      creates: /var/lib/pgsql/10/data/postgresql.conf

```

コツは `creates` オプションを指定すること。これで冪等性を保証し、すでにインストールしてある場合はスキップされるようになる。
[Ansible の Shell Module](http://docs.ansible.com/ansible/latest/shell_module.html)
※ なお、このオプションは [Command Module](http://docs.ansible.com/ansible/latest/command_module.html) にもある。

このオプションを見つける前は ignore-error して直後の fail の when条件を stdout が「Data directory is not empty!」でないとき・・・ってやってみたりした(^^;

なお、`postgresql-10-setup`スクリプトは直接 initdb コマンドを実行する場合と違い、postgresユーザーで実行する必要はなく、 root で実行できる。

次はサービス起動か・・・。

なお、この記事は Ansible 3日目の人間が書いたものなので、間違いがあれば鉞投げてください。
Ruby でメソッドの引数の値を確認する

思いついたので書いてみました。

```ruby
method(__method__).parameters.map { |t, v| [v, eval(v.to_s)] }.to_h
```

使い方

```ruby
def hoge(a = 3, b = 'hello', *test)
  p method(__method__).parameters.map { |t, v| [v, eval(v.to_s)] }.to_h
end

hoge #=> {:a=>3, :b=>"hello", :test=>[]}
```
よく使うターミナルのコマンド（Mac版）

## 1. cd ディレクトリの移動
```
$ cd 指定したディレクトリ
```
## 2. ls ファイルの表示
```
$ ls
```
## 3. mkdir ディレクトリ新規作成
```
$ mkdir　ディレクトリ名
```

## 4. touch ファイル新規作成
```
$ touch　ファイル名
```

## 5. vim vimエディタ起動(ファイルの編集)
```
$ vim　ファイル名
```

## 6. open ファイルの実行
```
$ open 実行するファイル名
```

## 7. mv [file] [new file] ファイル名(ディクトリ名)の変更
```
$ mv 現在のファイル名 変更するファイル名
```

## 8-1. cd 前いたディレクトリに戻る
```
$ cd -
```

## 8-2. cd 最初に開いたディレクトリに戻る
```
$ cd
```

## 9. mv [file] [dir] ファイルを移動させる
```
$ mv ファイル名 ディレクトリ名
```

## 10. rm ファイル削除
```
$ rm ファイル名
```

## 11. rm -r ディレクトリ削除
```
$ rm -r ディレクトリ名
```

## 12. pwd 現在のディレクトリの表示
```
$ pwd
```

## 13. cp ファイルをコピーする 
```
$ cp コピーしたいファイル　新しくできるファイル
```

## 14. 起動時の「Owner@〇〇-no-macbook:」の部分を消す
```
$ export PS1='\w : '
```
rubocop --auto-correctの補正結果を自動でPRにする

[rubocop](https://rubygems.org/gems/rubocop)は構文解析ツールです。
例えばrubyでString型を定義するときは、シングルクォートを使うようにするのが標準的な規約なので、`rubocop`は違反した部分のコードを教えてくれます。
ただシングルクォートになっていないといった簡単に修正できるものは、教えてくれるよりも修正してくれるほうが便利なので`--auto-correct`オプションを付けて自動補正するのが便利です。

今日は、CircleCIで`rubocop --auto-correct`を実行して、補正した差分をPull Requestにしてくれるgemを公開したので紹介します。

## rubocop-automata

[rubocop-automata](https://rubygems.org/gems/rubocop-automata)は、CircleCIで`rubocop --auto-correct`を実行してその補正結果をPull Requestにしてくれます。

<a href="https://gyazo.com/520933d9496c3271117d4f8d025db872"><img src="https://i.gyazo.com/520933d9496c3271117d4f8d025db872.png" alt="https://gyazo.com/520933d9496c3271117d4f8d025db872" width="400"/></a>

## install

`Gemfile`に追加して`bundle install`または、`gem install`します。

```ruby
gem 'rubocop-automata'
```

or

```
$ gem install rubocop-automata
```

## GitHub Personal access token

CircleCIからPull Requestを作れるように[Personal access token](https://github.com/settings/tokens)を発行します。

<a href="https://gyazo.com/c949d3821fcc9308694ba950e451651c"><img src="https://i.gyazo.com/c949d3821fcc9308694ba950e451651c.png" alt="https://gyazo.com/c949d3821fcc9308694ba950e451651c" width="400"/></a>

`Generate token`ボタンを押したあとのaccess tokenをクリップボードにコピーします。

## CircleCI Environment Variables(BUILD SETTINGS)

[BUILD SETTINGS] -> [Environment Variables] -> [Add Variable]で`GITHUB_ACCESS_TOKEN`をキーにaccess tokenを登録します。

<a href="https://gyazo.com/9a8cd90ac2173966cd583524b763b04b"><img src="https://i.gyazo.com/9a8cd90ac2173966cd583524b763b04b.png" alt="https://gyazo.com/9a8cd90ac2173966cd583524b763b04b" width="400"/></a>

## CircleCI Configuration

GitHubに登録してあるアカウントのemailとusernameを引数にして、`rubocop-automata`コマンドを実行します。

```yaml
version: 2

jobs:
  build:
    working_directory: /usr/src/app
    docker:
      - image: ruby:2.4.2-alpine3.6
    steps:
      - checkout
      - run: apk --update --upgrade add --no-cache git openssh curl
      - run: bundle install -j4 --retry=3
      - rubocop-automata <github email> <github username>
  
```

## 補正結果からPull Requestを作る

<a href="https://gyazo.com/e79900ff68c22bba0f6bfab4b258dd73"><img src="https://i.gyazo.com/e79900ff68c22bba0f6bfab4b258dd73.png" alt="https://gyazo.com/e79900ff68c22bba0f6bfab4b258dd73" width="400"/></a>

まずは、CircleCIで構文解析して自動補正した結果をPull Requestできるgemを作りました。
今後は別のCIサービスでも利用しやすいようにしたり、検証対象のコードをコミット範囲に絞れるようにしたいと思っています。
Ktor の FreeMarker で auto-escaping を有効にする

# Auto-escaping
テンプレートエンジンを使用している場合に、データベース等から取り出した値を展開させることは多々あると思いますが、エスケープが漏れていてはXSSなどに悪用される危険性があるため、自動でエスケープしてくれると安心です。

FreeMarker には auto-escaping という機能があります。
これは、`${name}`などのテンプレート部分を展開する際に、テンプレートファイルの拡張子から出力ファイル形式を推測して自動的にエスケープを行う機能です。

FreeMarker のドキュメントによると、HTML形式で出力したい場合には `.ftlh` 、XML形式で出力したい場合は `.ftlx` を拡張子に利用することが推奨されており[^1]、これらを利用することで auto-escaping を利用することができるようです。

auto-escaping を使用しなくても `${name?html}` のように escape することはできますが、万が一忘れていれば大変ですし、こちらは auto-escaping がデフォルト設定で有効になった 2.3.24 以降では deprecate されています。

# Ktor FreeMarker Feature
Kotlin 製サーバアプリケーションフレームワークの Ktor には標準で FreeMarker をサポートする Feature (プラグインのようなもの）があります。

この記事を執筆している2017年12月現在ではバージョン [2.3.20, 2.4) の範囲のものを利用するようになっているようです。
手元で確認したところ、 2.3.27-incubating が利用されていました。

# auto-escaping を有効にする
2.3.27 が使用されているということは、一見 auto-escaping が有効かのように見えます。

先述の通り HTML 形式の出力であることを明確にするためには `.ftlh` という拡張子を使用すればよさそうです。

しかし、実際これでは auto-escaping は有効になりませんでした。

FreeMarker Feature のソースコード[^2]を見ると、 FreeMarker の Configuration に `Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS` で定義されたバージョンが使用されていることがわかります。
この定数は `2.3.0` を指しているため、そのままでは auto-escaping が利用できなかったのです。

auto-escaping を明示的に有効にするには、 Configuration の `recognizeStandardFileExtensions` を true に設定します。

```kotlin
install(FreeMarker) {
    // ...
    recognizeStandardFileExtensions = true // enable auto escaping
}
```

この設定を行うことで、 `.ftlh` という拡張子がついたテンプレートファイルは HTML 形式として出力されることが明確になり、 FreeMarker は変数展開時に auto-escaping を行ってくれるようになります。

[^1]: https://freemarker.apache.org/docs/pgui_config_outputformatsautoesc.html
[^2]: https://github.com/ktorio/ktor/blob/master/ktor-features/ktor-freemarker/src/io/ktor/freemarker/FreeMarker.kt#L23s
GoでSlackのemoji用に画像を分割するツールを作成した

# はじめに
これは[Slack Advent Calendar 2017](https://qiita.com/advent-calendar/2017/slack)の23日の記事です。
遅くなってしまい申し訳ありません。

今回はこちらの記事([Slackで大きな絵文字を使う](https://qiita.com/gam0022/items/f24f582958c615d13533))を見て、Golangで出来ないものかと思いツールを作成しました。
まだ連携部分が出来ていないのですが、[Golangで作成したドラッグ＆ドロップでemojiをアップロードできるツール](https://qiita.com/from_Unknown/items/c6d0f16fe208d382404b)と組み合わせれば、ドラッグ＆ドロップ一発で登録することも出来そうです。

## 概要
以下がレポジトリです。
[picture_cutter_go](https://github.com/from-unknown/picture_cutter_go)
README.mdが書けていなくて申し訳ありません。
`go run picture_cutter.go image_file`で実行可能です。

実行すると以下のことを行ってくれます。

1. 画像の読み込み
2. 画像サイズの判定
3. 画像の縦か横が 128x4 = 512 を超えていたら比率はそのままで512まで縮小する
4. 128x128 最大4x4 にカットし、ファイルに`ファイル名_row_column.拡張子`で書き出す

上記で生成された画像をemojiに登録して順番通りに並べると隙間なく大きなemojiを貼り付けることが可能です。

## はまった点

### 隙間ができる
最初は画像を分割してそのままファイルに書き出していました。
しかし実際にemojiとして並べてみると隙間ができます。
正方形で登録しないと中心に寄ってしまうため、隙間が出来てしまうということが分かりました。

### 黒くなる
隙間が出来てはいけないという事が分かったため、対策として128x128に満たない部分は周りを埋めて128x128まで広げる事にしました。
そのために以下の様なコードを書きました。

```go
// 128x128のRectangleを作る
tmpRect := image.Rect(0, 0, int(imageMax), int(imageMax))
// 新しいRGBAの画像を作る
tmpImage := image.NewRGBA(tmpRect)
// subImageには切り出した画像が格納されている
// 左上からsubImageを描画する
draw.Draw(tmpImage, tmpRect, subImage, image.ZP, draw.Src)
```

しかし、上記コードだと出力された画像が真っ黒になってしまうことがあります。
調べてみるとどうやらjpeg画像をdecodeすると色のモデルがYCbCrとなるため、黒くなってしまう様でした。
なので、一度変換をかけてRGBAにしてから実行するとうまくいきました。

```go
// 128x128のRectangleを作る
tmpRect := image.Rect(0, 0, int(imageMax), int(imageMax))
// 新しいRGBAの画像を作る
tmpImage := image.NewRGBA(tmpRect)
// subImageには切り出した画像が格納されている
// subImageのRectangleを取得する
b := subImage.Bounds()
// 同サイズのRGBAの画像を作成
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
// draw.Drawを行うことでRGBAで出力される
draw.Draw(m, m.Bounds(), subImage, b.Min, draw.Src)
// pngとgifは透過色で塗りつぶす
// jpgには透過色がないため、白で塗りつぶす
if ext == ".jpg" || ext == ".jpeg" {
	draw.Draw(tmpImage, tmpRect, &image.Uniform{color.White}, image.ZP, draw.Over)
} else {
	draw.Draw(tmpImage, tmpRect, &image.Uniform{color.Transparent}, image.ZP, draw.Over)
}
// 左上からRGBAに変換したsubImageを描画する
draw.Draw(tmpImage, tmpRect, m, image.ZP, draw.Src)
```

こうして再度みると変換をかけなくても`draw.Draw`を通せばうまくいくような気もするのですが…
時間があったら調べてみたいとは思います。

## 感想
画像関連は何度かやっているため段々と分かってきた部分も多くなってきましたが、それでも毎回何かしらつまづいて学ぶことがあります。
今後も作ったら面白そうだなと思ったら積極的に作ってアウトプットして行こうと思います。
pythonでOCRがしたい

#背景
大学の授業で、プリントやスライドに書かれていることをそのまま打ち込むことが必要な場合が何度かあったので欲しいな、と思い書いてみました。
#内容
画像データ読み込み、２値化処理、OCRをpythonを用いて行います。

##OCR前処理
画像をただ読み込むだけでは読み取り精度が取れなかったので、OpenCVを用いて2値化する処理を行います。2値化に関する閾値は手動で行ってもいいのですが、背景に述べた通り、半自動化を目指すので、適応閾値処理(addaptive thresholding)を行います。openCVの[ドキュメント](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html)を参考にします。

```python3
import cv2
im = cv2.imread('写真.png')
im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
im_blur = cv2.GaussianBlur(im_gray, (5, 5), 0)

th1 = cv2.adaptiveThreshold(im_blur, 255, 
    cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
    cv2.THRESH_BINARY, 11, 2)

```

##OCR
tesserract-ocrというOCRエンジンをpyocrを用いて利用します。
pyocrの[ドキュメント](https://github.com/openpaperwork/pyocr)にあるInstructionに従えば問題なく利用できます。
ちなみに、PILは画像処理ライブラリであり、様々な処理が可能です。
[チートシート](https://qiita.com/pashango2/items/145d858eff3c505c100a)がとても参考になります。
が、今回は2値化処理のためにopenCVを使っているため、PILは必要ありません。

```python3

import sys
import pyocr

tools = pyocr.get_available_tools()
if len(tools) == 0:
    print('No OCR tool found')
    sys.exit(1)

tool = tools[0]

txt = tool.image_to_string(
    th1,
    lang = 'eng+jpn'
)

print(txt)
```

#課題
きれいな画像に関しては、かなりの読み取り精度が取れたが、(誤字はそれなりにあったが、それは学習データの問題かと...)、少しでも質の悪い画像(傾き、影)だとまったく読み取れない。
前処理部のコードに残っているブラーをかけるコード
また、適応閾値アルゴリズムの変更を試したが大きな改善にはならなかった。
膨張、縮小処理を試す。
ベトナムのオフショア開発・連携について

#はじめに
ある程度の試行錯誤の上でようやく軌道に乗ってきたので
実際に体験した事柄から開発を進めるコツを簡単に紹介します。

オフショア先はベトナム（以下VN）で、チームとしてはVN側にはブリッジが１名あとはメンバーという構成です。
日本にいる私がVNにいるブリッジとやりとりして開発を進めています。

#やったこと
##最初
何はともあれキックオフです。
VNまで足を運んでキックオフです。
実際に顔を合わせて仕事することで以後のコミュニケーションに響くのではないかと思います。

VNに行くにあたっては日本語のできるブリッジの方いるので高校程度の英語力があればあとは身振り手振りでなんとかなりました。

恐らく単身で行っても英語だけで何とかなります、
一般人もフレンドリーですし治安も良いです。あとご飯も美味しい。

私はキックオフと環境構築だけ済ませて帰国しました。

##ベトナム側の開発レベル
エンジニアとしてのレベルは未知数でしたが日本と大きく変わらない印象でした。
エンジニア歴が5年であればそれ相応といった感じです。

ただし日本と大きく変わるポイントはRubyの知識でしょう。
日本では浸透しているRubyですがチームで経験しいるメンバーはいないという実情でした。
恐らく私のチームだけでなく、VN全体で日本と取引のある企業に属していた方以外はまともにRubyを触っていないでしょう。
なのでRubyを書いて欲しい場合は学習コストが掛かるでしょう。

##開発スタイル
基本的にチャットツールを用いてコミュニケーションをとりました。

開発は日本側で仕様書を用意してブリッジに渡し、ベトナム語に訳してもらいます。
訳した後にその内容で実装してもらい、ある程度実装が終わったらレビューして指摘していくというよくある開発スタイルです。

レビューや指摘については日本語と英語・コードを混ぜて行います。
というのも日本語で長文になりがちな指摘事項などはどうしても微妙な誤訳があったりしたので、英語で同じ意味の文章を書いておくことにより誤訳を減らそうとしていました。
英語だとVN側のメンバーもある程度読めますし。

それでも伝わらない時のためにコードで会話すれば問題ないです。
コードは国を越えてもエンジニアの共通言語です。


##コミュニケーション
英語レベルはチャット上での意思疎通は難しくないという印象でした。
基本的に日本語でブリッジを通じてメンバーとの意思疎通を行うわけですが、個別に話したい時は英語で話しかけるようにしています。

ブリッジと話す際に気をつけなければならないのは和製英語と言い回し、難しい単語です。
和製英語で印象的なのはレビュー完了後にfixと言って通じなかった時ですね。

言い回しは日頃から意識しないと特に影響があると思います。
>その実装はあまり推奨されていないけど納期的にはやぶさかではないかな。

このような曖昧な言い回しは誤解を生みやすいです。
文章の前半で否定しておきながら最後に肯定するなどは伝わりにくい印象でした。

##資料は嚙み砕く
これはVNに限りませんが
社内ドキュメントなどは社内で通用するように書かれていることが多いので分かりやすく訳す必要があります。
社内用語の翻訳が一番骨が折れるので、開発前に噛み砕いた訳を準備していた方が良いです。


#オフショアを安定させる
ためにはコミュニケーションが一番大きいように感じました。
開発スタイルは慣れなので暫くすれば特に問題なく回るようになります。
一番大変なのはコミュニケーションで、言葉の言い回しは中学生でわかるレベルに落とし、英語もある程度理解している必要があります。
物理的距離もあるのでどうしても距離感を感じてしまいます。

なのでそれを解決するために毎週Skypeでの雑談会を行っています。
雑談を通じて個性を知ったりフレッシュしたりできるのでコミュニケーションを円滑にできています。

* 訳しやすい日本語でコミュニケーションする。
* 定期的に雑談する。
 
この２つが大事だと思います。
Elixirでrouting tableを作った

Elixirでsoft ware routerを作っているので、今のところできているrouting tableについて実装をメモ的に残します。

## 動作環境

- docker container
    - OS: debian 8

- elixir : 1.5.2

## 実装

routing tableの構成

|name|description|
|---|-----|
|id|routing tableのレコードの識別|
|destination|送信先IP|
|netmask|net mask|
|nexthop|次の宛先。直接つながってる場合は「connected」が入る|
|interface|送信するinterface|
|metric|メトリック|
|routeType|スタティックかダイナミックか|
|sourceOfRoute|ルーティングプロトコル|
|routeAge|レコードが追加された時間|
|routeInformation|ルーティングの情報|
|mtu|MTU|

保存先はETSを使う。データのIOでどれぐらいかかるか検証する必要あるかもしれないですけど、とりあえずETSで。
初期化時に直接接続されているネットワークをrouting tableに追加します。

```
defmodule Routing do
    def init() do
    table = :ets.new(:routing_table, [:set, :protected, :named_table])
    :interface.get_interfaces()
    |> Enum.each(fn({name, interface}) -> :ets.insert_new(:routing_table, {name,
        {:netmask, Tuple.to_list(interface[:netmask])}, {:dest, Tuple.to_list(interface[:addr])}, {:nexthop, :connected}, name, 0, "static", "link", :calendar.local_time(), "-", 1500}) end)
end
```

interfaceの取得はErlangで取得してる。interfaceは"eth"がつくものに限定しています。

```
get_interface() ->
    {ok, IfLists} = inet:getifaddrs(),
    Listen = lists:filter(fun(Elm) -> interface_list(Elm) end, IfLists).

interface_list(Elm) ->
    {Name, _} = Elm,
    string:find(Name, "eth") =/= nomatch.
```

routing tableから送信先のIPから次に送信する宛先を決定します。

```
:ets.match(:user_lookup, {:"_", :"$1", :"$2", :"$3", :"_", :"_", :"_", :"_", :"_", :"_", :"_"})
            |> Enum.map(fn(record) -> [{:address, List.zip([dest, record[:netmask], record[:dest]])}, {:nexthop, record[:nexthop]}] end)
            |> Enum.filter(fn(record) -> record[:address]
              |> Enum.all?(fn(octed) -> (elem(octed, 1) &&& elem(octed, 0)) == (elem(octed, 1) &&& elem(octed, 2)) end)
            end)
            |> Enum.map(fn(record) -> [{:dest, (for octed <- record[:address], do: elem(octed, 0))}, {:nexthop, record[:nexthop]}] end)
```

複雑になってしまったんで、軽く説明を付けておきます。

宛先IPとnetmaskとrouting tableに登録されている宛先のIPでオクテットごとにTupleを作っていきます。

```
# 例
# 宛先IP [192, 168, 0, 10]
# netmask [255, 255, 255, 0]
# routing tableに登録されている宛先IP [192, 168, 0, 0]（直接接続されているnetwork）
# result : [{192, 255, 192}, {168, 255, 168}, {0, 255, 0}, {0, 0, 0}]
|> Enum.map(fn(record) -> [{:address, List.zip([dest, record[:netmask], record[:dest]])}, {:nexthop, record[:nexthop]}] end)
```

まとめたTupleから宛先とrouting tableに登録されている宛先のIPがnetmaskでマスクした結果が一致しているものを取得

```
|> Enum.filter(fn(record) -> record[:address]
              |> Enum.all?(fn(octed) -> (elem(octed, 1) &&& elem(octed, 0)) == (elem(octed, 1) &&& elem(octed, 2)) end)
            end)
```

宛先にマッチしたrouting tableのrecordでrouting tableに登録されている宛先のIPとnexthopを返します。

```
|> Enum.map(fn(record) -> [{:dest, (for octed <- record[:address], do: elem(octed, 0))}, {:nexthop, record[:nexthop]}] end)
```

これで宛先が決まりました。まだ実装してないですが、netmaskのロンゲストマッチ、コストでソートができてないため、それは今後実装予定。

とりあえず、これでETSにrouting tableのレコードの追加、検索ができるようになりました。
次は、宛先がARP tableに次に送信するホストの情報があるか調べて、なかった場合はARPのrequestを送って登録処理を実装します。
Division by zero, numpy.nan and numpy.inf

```python
N = np.array(0)
print(N)
print(N / N)
print(N // N)
N = [np.nan, np.inf]
print(N)
X = np.array(N)
print(X)
print(X.astype(int))
print(X.astype(float))
print(X.astype(int).astype(float))
```
* Example

```
0
test.py:6: RuntimeWarning: divide by zero encountered in divide
  print(N / N)
0
test.py:7: RuntimeWarning: divide by zero encountered in floor_divide
  print(N // N)
0
[nan, inf]
[ nan  inf]
[-9223372036854775808 -9223372036854775808]
[ nan  inf]
[ -9.22337204e+18  -9.22337204e+18]
```
# Refs.
* [演算子](http://www.tohoho-web.com/python/operators.html)
* [numpy.ndarray.astype](https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.ndarray.astype.html)
[Xcode, c++]Working directory設定

# 基本生成場所
- /Users/"UserName"/Library/Developer/Xcode/DerivedData/"ProjectName"-cvanicthangqmpeomwhkmoijhrfw/Build/Products/Debug/

# Working directory設定
1. Product->Scheme->EditScheme選択
2. "options"タブを選択
3. "working directory"を設定（コード上作成したファイルが保存される）
なぞの FutureOr

dart:async の の中に、 FutureOr　という機能があるのだが、
使い方の解説も見ないでの、試して見た。


https://api.dartlang.org/stable/1.24.3/dart-async/FutureOr-class.html


```dart
import 'dart:async';

main() async {
  {
     int start = new DateTime.now().millisecondsSinceEpoch;
     for(int i=0;i<500;i++) {
       await doubleAsync(i);
     }
     int end = new DateTime.now().millisecondsSinceEpoch;
     print("#A# ${end -start}");
  }

  {
     int start = new DateTime.now().millisecondsSinceEpoch;
     for(int i=0;i<500;i++) {
       FutureOr f = doubleOr(i, m:1);
       if(f is Future) {
         await f;
       } 
     }
     int end = new DateTime.now().millisecondsSinceEpoch;
     print("#B# ${end -start}");
  }
 }

Future<int> doubleAsync(int i) async{
	return i+i;
}

int doubleSync(int i) {
	return i+i;
}

FutureOr<int> doubleOr(int i, {int m:0}) {
  if(m == 0) {
    return doubleAsync(i);
  } else {
    return doubleSync(i);
  }
}

```

https://dartpad.dartlang.org/　
で試すと、結構速度が違う。


# なんとなく


```
main() asyncOr {
  {
     int start = new DateTime.now().millisecondsSinceEpoch;
     for(int i=0;i<500;i++) {
       awaitOr doubleOr(i, m:1);
     }
     int end = new DateTime.now().millisecondsSinceEpoch;
     print("#A# ${end -start}");
  }

```

みたいに、かけたら良いのに.
デバッガーを自動で起動する方法

# Visual StuidoのDebuggerを自動で起動してアタッチ
Visual Studioのデバッガー機能は便利です。
業務中には動いているアプリケーションやサービスにアタッチしてデバッグしているのですが、アタッチしにくいケースに出会います。
例）
- 起動後すぐに流れてしまう処理で、アタッチしたときには終わっている
- そもそもアタッチするまえにプロセスが落ちてしまう

通常のアタッチでは
![atouch.png](https://qiita-image-store.s3.amazonaws.com/0/193480/6394b533-04f6-bb1b-a736-30b7be339b94.png)

のようにVisual Studioのデバッグメニューから選択しますが、上記の例ではそれでは対応できません。

下記のコードを入れておくと、読まれたときに自動的にデバッガーが起動しアタッチを行えます

```C#:test.cs
System.Diagnostics.Debugger.Launch(); 
```

![s_debug.png](https://qiita-image-store.s3.amazonaws.com/0/193480/cdfcfa58-391e-2f46-6fe2-2a3844b6ec73.png)



if...thenディレクティブを使えばDebugビルドのみデバッガーを自動で起動することができて作業が捗ります。
新たに加わるエンジニアをもてなすための施策 - オンボーディング

この記事は[Goodpatch Advent Calendar 2017](https://qiita.com/advent-calendar/2017/goodpatch)の19日目の記事です。

私の開発チームでは現在、数十人規模で開発を進めているのですが、エンジニアが増員される中でどのようなインプットがあると、スムーズにプロジェクトに入ってもらえるかを色々と考えてました。今回は新たにエンジニアが加わる際に考慮しておくと良さそうな事を書きたいと思います。
ちなみに私はフロントエンドエンジニアなので、フロントエンド＆Webアプリケーション開発に多少寄った内容になってしまう事をご了承ください。

# サービス理解

まずは何と言ってもサービス・プロダクトの説明が必要です。
例えば、以下のような情報をもとに、対話形式で説明していくと理解してもらいやすいかと思います。

- サービスのコンセプト、提供価値
- サービスの歴史
- ターゲットユーザー
- KGI・KPI
- 競合他社、競合サービス
- 開発ロードマップ
- 開発体制、組織図

# 仕様・規約の共有

ドキュメントをしっかり作成している開発現場は少ないかもしれませんが、もし以下の中で準備できるものがあれば、”開発まとめ”ページを作成して各情報へのリンクを貼っておくと分かりやすいと思います。

- システム構成図
- サイトマップ
- 対応環境（ブラウザ、機種、バージョン）
- アプリケーション実行環境
- 画面設計書
- API仕様書
- ER図
- テスト仕様書、テストコード
- コーディングガイドライン
- 利用フレームワーク、ライブラリ

仕様については大枠だけを伝えて、細かい部分は開発に入った後に徐々に共有していく形でも良いかもしれません。

# 開発準備

まずは開発で利用しているツールを共有して、ソフトウェアのインストールやアカウントの作成を行ってもらう必要があります。例えば、以下のようなものです。

- 開発ツール（Editor、Gitなど）
- デザインツール（Sketch, Photoshopなど）
- コミュニケーションツール（Slack, Chatworkなど）
- 課題管理（Backlogなど）

あとは、GitリポジトリのREADMEに環境構築手順を書いておいて、ローカルで動作確認できるようにしておけると良いかと思います。

# コードレビュー

コードレビューは重要だと思いますが、Linterなどを入れていると細かい部分は気にしないで済むので楽になります。コードレビューはPullRequestを受け取って、レビューする形が一般的だと思います。

レビューのタイミングについて、最初の方は特にWIP形式の方がお互い楽かもしれません。WIP形式のPullRequestについては以下の記事が参考になります。

[Work in ProgressパターンによるPull Requestを利用した開発フロー - Qiita](https://qiita.com/numa08/items/b676e38e3dbabfd39d18)

また、過去のコードレビュー指摘一覧のようなものがあると、同じようなミスが発生しにくくなるかもしれません。

# その他オプション

もし戦力化するまでに時間がかかりそうであれば、以下のような施策を打つのもアリだと思います。

- 勉強会
- 開発チュートリアル
- ペアプログラミング

また、新しく参加したメンバーは最初は遠慮しがちなので、しばらくしたらKPTや1on1など振り返りの時間を持つとコミュニケーションが促進されると思います。

今回は私の思いつく範疇で書き出してみましたが、もっと良くできる部分があると思いますので、アドバイス頂けると幸いです。
nvidia-dockerを実業務で使った話

本記事は[Docker Advent Calendar2017](https://qiita.com/advent-calendar/2017/docker)の25日目の記事です。

今年は、ディープラーニングに関連する案件に多く関わることになり、その際にnvidia-dockerを使用する機会があったので、
nvidia-dockerを使った事例と、難しかったところなどに触れたいと思います。

## nvidia-dockerについて

nvidia-dockerとは何かと言うと、NVIDIAが公式でOSSとして公開しているDockerのUtilityです。
インストール方法や詳細な使い方については[公式のGithub](https://github.com/NVIDIA/nvidia-docker)や、qiitaにもnvidia-dockerについて書かれている記事は色々あるのでそちらを参考にしてください。ここではnvidia-dockerの特徴だけ掲載します。

* ホスト側にはNVIDIAのGPUドライバさえインストールしておけば良い
* DockerコンテナからNVIDIAのGPUにアクセスできる
* ベースとするコンテナを切り替えることで、OSやCUDAのバージョンを自由に変更できる


## 事例その1 案件で使用するディープラーニングモデルの調査用として

最初は、案件で使用するディープラーニングモデルの環境構築方法や学習方法、使い方などの調査する環境を
作るためにnvidia-dockerを使いました。

**開発当初、自由に使えるGPUマシンがなかった**

nvidia-dockerをつかうことになったきっかけは、開発当初、開発用のマシンがすぐに用意できず、ディープラーニングの勉強用として社員で共有しているGPUマシンを間借りすることなったことです。

間借りした環境に極力影響を与えないようにかつ、OSや、使用するライブラリを自由に選択するにはDockerがいいのではないかと思い、「Docker GPU」とかで検索したらまさにドンピシャのもの(nvidia-docker)がヒットしたので使ってみることにしました。

**新人に環境構築をやらせてみた**

今回の案件では、今年入社した新人にpyenvやOpenCVの環境構築から、使用するモデルのデモプログラムを動かすところまでをやらせました。

実機インストールであれば、環境構築がうまくいかなかったらOSの再インストールから初めないといけないので、いきなり新人にやらせるのは難しいかもしれないですが、Dockerであれば適宜コンテナをコミットしてバックアップを取らせながらやらせれば、手戻りが少ないので新人でも安心して任せることができました。



## 事例その2 様々なディープラーニングモデルを組み合わせる

事例その2は、ディープラーニングのモデルを複数組み合わせてシステムを構築した時にnvidia-dockerを使うことにしました。

ディープラーニングのモデルを複数使うにあたり、下記の問題がありました。

* GPU割り当てをうまくやる必要がある
* そもそも複数のモデルをシングルプロセスで展開できない(※)

> ※複数のモデルを使うことができるかはものにもよると思う。
> 特に今回の案件ではCaffeを使ったモデルを複数使っていて、しかもそのCaffeは独自に
> 改造したものだったのでライブラリが競合してしまったことが大きな原因

上記2つの問題を解決するために、nvidia-dockerを使うことにしました。
具体的には下記の方針で設計しました。

* ディープラーニングモデルごとにDockerコンテナを用意
    * モデルごとに環境を用意できるのでライブラリの競合などの心配がない
* コンテナ起動時に重みをロードしてサービスとして展開する
    * モデルのロード時間はコンテナ起動時のみなので比較的早く処理を行うことができる
    * 今回は１つのアプリでしか利用していないですが、やろうと
* nvidia-dockerのdocker-compose版であるnvidia-docker-composeを使ってコンテナを管理
    * docker-composeなのでコンテナ管理が楽
    * GPUの割り当てについてもdocker-compose.ymlで管理できるので楽ちん

## nvidia-dockerを業務で使ってみて

nvidia-dockerを実業務で使ってみた所感を述べたいと思います

**コンテナの容量が大きくなりがち**

OpenCVとか、Caffeとか、Tensorflowとか、インストールするものも多いので、Dockerのイメージが大きくなりがちでした。

**作成物がroot権限になるので取り扱いが難しい**

Dockerって通常rootユーザで動くので、前処理プログラムとかで大量の画像データを作成したりすると
全てrootユーザになってしまうので、chownコマンドを適用するにしても数時間、数十時間かかったりとかして
取り扱いが難しかったです。

**デスクトップ環境が欲しくなるが、、、**

OpenCVとか使っていたりすると、highguiを使って画像を表示したいところですが、Dockerコンテナなので難しいです。
一応、Dockerコンテナでもデスクトップ環境を構築できるので、highguiをつかうこともできますが設定とかすごくめんどくさいです。
Operators and expressions testing for Python 

```python
def n27(Z):
 print(Z**Z)
 print(2 << Z >> 2)
 print(Z <- Z)
 print(1j*Z)
 print(Z/1/1)
 print(Z<Z>Z)

Z = 3
print("Z:"+str(Z))
n27(Z)
Z = np.random.randint(0,9,(2,3))
print("Z:"+str(Z))
n27(Z)
```
* Example

```
Z:3
27
4
False
3j
3
False
Z:[[2 5 4]
 [6 8 4]]
[[       4     3125      256]
 [   46656 16777216      256]]
[[  2  16   8]
 [ 32 128   8]]
[[False False False]
 [False False False]]
[[ 0.+2.j  0.+5.j  0.+4.j]
 [ 0.+6.j  0.+8.j  0.+4.j]]
[[2 5 4]
 [6 8 4]]
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    n27(Z)
  File "test.py", line 10, in n27
    print(Z<Z>Z)
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
```
# Refs.
* [数値を文字列に変換(str)](https://www.pythonweb.jp/tutorial/string/index9.html)
* [関数(def)](http://www.tohoho-web.com/python/function.html)
* [Python で複素数を扱う](https://qiita.com/shuhei/items/f5cf6c83fcfb5dd24c2d)
* [The python <- operator: what does it mean?](https://stackoverflow.com/questions/19336941/the-python-operator-what-does-it-mean)
開発環境メモ

個人の開発環境を構築したときのメモです。
随時、アップデートしていく予定です。
おすすめのツールや設定があればコメントいただけるとありがたいですmm

# Host
- OS : Windows 10 Pro
- App : VMware Workstation 14 Player

# Guest
- OS: Lubuntu

## OS
### システムアップデート
```bash
$ sudo apt-get update
$ sudo apt-get -y dist-upgrade
```
### パーミッション厳格化
```
$ sudo chmod 0750 /home/$USER
$ sudo sed -i "s/DIR_MODE=0755/DIR_MODE=0750/g" /etc/adduser.conf
```
### 日本語入力
```bash
$ sudo apt-get purge ibus-anthy
$ sudo apt-get install ibus-mozc
$ sudo apt-get install ibus-gtk ibus-gtk3
```
Preferences > IBus Preferences > Input Method 
- English- English
- Japanese - Mozc

## Utility
### Synapse
http://linuxg.net/how-to-install-synapse-0-2-99-on-ubuntu-15-04-ubuntu-14-10-ubuntu-14-04-and-derivatives/
```
$ sudo apt-get -y install synapse
```
### Guake
```
$ sudo apt-get -y install guake
```
### cURL
```
$ sudo apt-get -y install curl
```
### nkf
```
$ sudo apt-get -y install nkf
```
### jq
```
$ sudo apt-get -y install jq
```

## Browser
### Google Chrome
https://www.google.co.jp/chrome/browser/desktop/index.html

## Editor
### Atom
\https://atom.io/beta
### vim
```
$ sudo apt-get -y install vim
```

## Java
### OpenJDK
```
$ sudo apt-get -y install openjdk-8-jre
```
### Maven
```
$ sudo apt-get -y install maven
```
### IntelliJ IDEA
https://www.jetbrains.com/idea/download/#section=linux

## Node.js
### nvm
```
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
$ vim ~/.bashrc
#以下を追加
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
$ source ~/.bashrc
```
### node.js
Latest LTSをインストール

```
$ nvm install `nvm ls-remote | grep 'Latest LTS' | tail -1 | awk '{print $2}' | sed 's/v//g'`
$ nvm use`nvm ls-remote | grep 'Latest LTS' | tail -1 | awk '{print $2}' | sed 's/v//g'`
$ nvm alias `nvm ls-remote | grep 'Latest LTS' | tail -1 | awk '{print $2}' | sed 's/v//g'`
```

## Ruby
### rbenv
```
$ sudo apt-get install -y build-essential libssl-dev ruby-build
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ git clone https://github.com/sstephenson/ruby-build.git .rbenv/plugins/ruby-build
$ echo '' >> ~/.bashrc
$ echo '# ruby' >> ~/.bashrc
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(rbenv init -)"' >> .bashrc
$ source ~/.bashrc
```
### ruby
```
$ rbenv install -s `rbenv install -l | grep -v - | tail -1 | tr -d " "`
$ rbenv rehash
$ rbenv global `rbenv install -l | grep -v - | tail -1 | tr -d " "`
```
マルバツゲームをごり押しで作ってみた(python)

pythonを選択し、学び始めたばかりの初心者のアウトプット記録です。
今回は、user vs user 型のマルバツゲーム(3目並べ)を作ってみました。

テキストで出力し、数字で座標を管理します。

```python3:game.py
print('以下の数字で座標を指定してください')
text = """
1|2|3
-----
4|5|6
-----
7|8|9
"""
print(text)
```

座標管理のためのリストを用意します。

```python3:game.py
candidates = [1,2,3,4,5,6,7,8,9]
pre_list = []
pos_list = []
n = 0
```

勝敗判定のための関数を定義します。
良い方法が思いつかなかったのでゴリゴリに押していきます。

```python3:game.py
def vf(lis):
    if [1, 2, 3] == list(set([1, 2, 3]) & set(lis)):
        return True
    if [4, 5, 6] == list(set([4, 5, 6]) & set(lis)):
        return True
    if [7, 8, 9] == list(set([7, 8, 9]) & set(lis)):
        return True
    if [1, 4, 7] == list(set([1, 4, 7]) & set(lis)):
        return True
    if [2, 5, 8] == list(set([2, 5, 8]) & set(lis)):
        return True
    if [3, 6, 9] == list(set([3, 6, 9]) & set(lis)):
        return True
    if [1, 5, 9] == list(set([1, 5, 9]) & set(lis)):
        return True
    if [3, 5, 7] == list(set([3, 5, 7]) & set(lis)):
        return True
    return False
```

前段階で用意したものを使って勝敗がつく、または、あいこで終わるまで座標の入力を求めることと、座標の表示を繰り返します。

```python3:game.py
while True:
    if n % 2 == 0:
        pre = input("先攻の座標は？")
        try:
            pre = int(pre)
        except:
            print("正しい座標を入力してください")
            continue
        if pre in candidates:
            text = text.replace(str(pre), "o")
            candidates.remove(pre)
            pre_list.append(pre)
            print(text)
            if vf(pre_list):
                print("先攻の勝ち")
                break
        else:
            print("正しい座標を入力してください")
            continue
        n += 1
        print(n)
    else:
        pos =input("後攻の座標は？")
        try:
            pos = int(pos)
        except:
            print("正しい座標を入力してください")
        if pos in candidates:
            text = text.replace(str(pos), "x")
            candidates.remove(pos)
            pos_list.append(pos)
            print(text)
            if vf(pos_list):
                print("後攻の勝ち")
                break
        else:
            print("正しい座標を入力してください")
            continue
        
        n += 1
        print(n)
    if n == 9:
        print("あいこです。")            
        break
```

数字以外が入力されたときの対処はtry, errorで、あいこは思考の回数で判定するようにしてみました。

#改善点

勝敗判定についてはbit演算を使ってより簡潔に書けるようです。
[こちら](http://fujishinko.exblog.jp/7312504/)が参考になります。
こちらのほうが、処理も早く、簡潔ですね。
今回はテキストエディタ上で表示する形になりましたが、PyQt5などを使ってGUIで表示することに挑戦するのも良いかもしれません。
kubernetes1.7でkubectl applyでinit containerを再作成する

# はじめに

kubernetesにはinit containerという機能があります。init containerはpodの起動前に実行されるコンテナを定義することができ、例えばpodの作成前にassetsなどの静的ファイルを共有ボリュームに格納させて、nginxで配信するみたいな事ができます。このinit containerですが、kubernetesの1.8以前だと、設定を変更して再度applyしても再作成されないという問題がありました。

# 対策

以下のissueに書いてあるまんまですが、annotationsに`pod.alpha.kubernetes.io/init-containers: null`と`pod.beta.kubernetes.io/init-containers: null`を入れることで解決します。
https://github.com/kubernetes/kubernetes/issues/47264

例をあげると以下のような感じになります。

```yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
      annotations:
        pod.alpha.kubernetes.io/init-containers: null
        pod.beta.kubernetes.io/init-containers: null
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: workdir
          mountPath: /usr/share/nginx/html
      # These containers are run during pod initialization
      initContainers:
      - name: install
        image: busybox
        command:
        - wget
        - "-O"
        - "/work-dir/index.html"
        - http://kubernetes.io
        volumeMounts:
        - name: workdir
          mountPath: "/work-dir"
      dnsPolicy: Default
      volumes:
      - name: workdir
        emptyDir: {}
```

ちなみに、この状態だとkubectl applyする際にvalidateに引っかかるため`--validate=false`のオプションを追加することで回避できます。
kubernetes1.8だと解消してるみたいなので、最新のクラスタ使ってる方は上記の回避策は不要です。
OctoberCMSプラグイン作成：他プラグインのモデルにリレーションを追加する


OctoberCMSのドキュメントでは言及されていないが、他のプラグインのモデルのリレーションも拡張することができる。[ここ](https://octobercms.com/docs/plugin/extending#extending-user-model)でプラグインのモデルを別のプラグインから拡張する方法を説明しているが、モデルオブジェクトが渡されているならもしや、と思ってやってみたらあっさりできた。

下記では既存の`belongsToMany`フィールドに項目を追加することでリレーションの定義を追加している。例では`belongsToMany`だが、他のリレーションタイプでも同じはず。

```php
class Plugin extends PluginBase
{
    public function boot()
    {
        Product::extend(function ($model) {
            $model->belongsToMany['parts'] = [
                    'Pikanji\Custom\Models\Part',
                    'table' => 'pikanji_custom_products_parts',
                    'key' => 'product_id',
                    'otherKey' => 'part_id',
                ];
        });
    }
```

## おまけ
下記のようにモデルクラス内で定義するように変数に代入してしまうと、既存のリレーションが定義されている場合、上書きしてしまうので注意。見ればわかりそうなもんだが、なにせ記述しているプラグインが別なので、拡張される側に後からリレーション定義を作成した時にこのミスで数時間ハマった。

```php
            $model->belongsToMany = [
                'parts' => [
                    'Pikanji\Custom\Models\Part',
                    'table' => 'pikanji_custom_products_parts',
                    'key' => 'product_id',
                    'otherKey' => 'part_id',
                ],
            ];
```

下記の様なエラーが出て、モデルを拡張した覚えがあるなら、チェックしてみる価値はある。

```
Model 'Pikanji\Ec\Models\Product' does not contain a definition for 'categories'.
```
OpenFOAM Google Groupに投稿する際に抑えるポイント

# はじめに
日本ではOpenFOAMのGoogle Groupで質問を投げかけることで、直面している問題に対してアドバイスをもらえることがある。

OpenFOAM Google Group 
https://groups.google.com/forum/?nomobile=true#!forum/openfoam

ただ、回答されない質問もちらほら増えてきている。それは質問の内容が初歩的すぎるとかではなく、単に情報が足りなくて回答のしようがない時が多い。

今回はOpenFOAMに関して質問するときに何を書いたらいいのか考えて見た。

# 何をしようとしているのかを書く
どんな計算なのかを簡単に説明する。趣味でやっているのでなければ、自分の計算について説明できないことも多いと思う。その場合は別の問題に置き換えて書いてみる。
あと、あまり専門的なことは書かない。それは質問のスレッドが伸びてきてからでいいと思う。

# どんなモデルのなのかを示す
最近よく抜けているのがこれだと思う。境界条件だけ書かれても位置関係が分からなければ、その情報はあまり意味がない。

例えば、外部流れで計算が収束しないという質問で、その原因がOutletと対象物の距離があまりに近いからだとかの場合、境界条件だけではその問題の解決策にはすぐには至れない。

自分のモデルを示すことができなければ、手書きでもいいので簡単な矩形や丸とかで絵をかいてどの位置にどんな境界条件を入れているのかを見せる。これが回答を得るのに重要だったりする。

無視されているスレッドは、たいていエラーメッセージや言葉だけの境界条件しか書いていない。回答者はサポートではないので、いちいちキャッチボールを楽しむつもりはない。スっと回答したい。

# 使っているソルバは書く
これも忘れてはいけない。OpenFOAMは多様なソルバを抱えているため、ソルバによって固有の問題だったり、そもそも解こうとしているモデルに対して適切でないソルバを選んでいるがために問題が発生することもある。

# どんな流体なのかを書く
ニュートン流体とか非ニュートン流体、単相流・混相流などを書く。

乱流モデルとかも書く。

# それでもってエラーを書く
エラーのメッセージは最後に書くくらいでちょうどいい。というのも計算しようとしているモデルが分かっていないと、エラーの原因が絞り込めないことがある。

後、エラーメッセージはなるべくそのまま転記する。あなたの考え二の次。回答者はメッセージそのものと出たタイミングが知りたいのである。

たとえば、以下のメッセージはメッシュを切る前に計算を実行した際に出るメッセージであるが、この場合は自分が打ったコマンドを順番に示すことで、メッシュを切れていないことがわかる。

```
/*---------------------------------------------------------------------------*\
| =========                 |                                                 |
| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
|  \\    /   O peration     | Version:  v1706                                 |
|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
|    \\/     M anipulation  |                                                 |
\*---------------------------------------------------------------------------*/
Build  : v1706
Arch   : "LSB;label=32;scalar=64"
Exec   : simpleFoam
Date   : Dec 24 2017
Time   : 23:56:21
Host   : "LAPTOP-OUB5D812"
PID    : 804
Case   : /mnt/c/Users/hamme/Documents/vmshare/pitzDaily
nProcs : 1
sigFpe : Enabling floating point exception trapping (FOAM_SIGFPE).
fileModificationChecking : Monitoring run-time modified files using timeStampMaster (fileModificationSkew 10)
allowSystemOperations : Allowing user-supplied system call operations

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
Create time

Create mesh for time = 0



--> FOAM FATAL ERROR:
Cannot find file "points" in directory "polyMesh" in times 0 down to constant

    From function Foam::word Foam::Time::findInstance(const Foam::fileName&, const Foam::word&, Foam::IOobject::readOption, const Foam::word&) const
    in file db/Time/findInstance.C at line 202.

FOAM exiting
```

# ファイルを出せるものは出す。
手っ取り早くという話であれば、これも重要な手段。単なるタイポの場合、ものを見ないとこちらからは指摘ができない。

あと、回答者が手元で再現できると回答が早くされるかもしれない。

# おわりに
質問をするのは個人の自由だし、質問の仕方にルールがあるわけでもないです。

ただ、より早く回答を得たいのであれば、どんな情報が必要で、どうやってそれを伝えるかが必要になります。回答者はエスパーではなく、それぞれの分野でそれぞれの計算をしている人たちなので、普段扱っているモデルも自分とは異なります。

まずは自分のやっている計算がどんなのかを示して、分野の違いによるギャップをなくすのが、質問をする際には必要だと思います。

質問者も回答者もさっとやり取りできるようになったらいいなと思います。
試験項目NG文言5選

# はじめに
本投稿は、[ソフトウェアテストの小ネタ](https://qiita.com/advent-calendar/2017/software-testing-koneta)アドベントカレンダー24日目になります。

試験項目は「テスト実行者が10人実行したときに10人同じ手順、確認結果を得られること」が一つの要件だと思っています。
そこで、これまでの現場で見てきた試験項目に書いてあって困った文言たちをここでは紹介します。
本件は、筆者の個人的な経験に基づく、考えです。異論、疑問はコメントにお願いします。

#NG文言集
## 1. 正しいこと
正誤を判断するための条件が書かれているのが試験項目であって、「正しい」という文言は書いてはいけない。
あなたの「正しい」は、テスト実行者の「正しい」とはイコールではない可能性がある。
>【NG】 メッセージの文言が正しいこと。
>【OK】 メッセージが「サーバのエラーです。時間がたってから再度お試しください。」であること。

現実には、「正しい」と書かれた試験項目がまかり通っている。~~見かけたら破り捨ててほしい。~~
ちなみに、「仕様通りであること」も、同様にNG。理由は「正しい」同じ。

## 2. または
条件分岐を生む、「または」という言葉を試験条件や試験結果の確認したいことに書いてはいけない。
なぜなら、試験者は「または」という表現に対して、片方の条件のみ実施することが想定され、
結果的に実施されていない条件が実施漏れとして残る可能性があるため。
どうしても、同一ケースに対して複数の試験条件を入れたい場合は、ケースを分割すること。

>ログイン画面でIDに"taro"または"Hanak0"と入力する

であれば、以下のように分けましょう。

>ケース1: ログイン画面でIDに"taro"と入力する
>ケース2: ログイン画面でIDに"Hanak0"と入力する

## 3. 遅い/早い/すぐに
形容詞はNG。仕様書に規定されている数値で記載する。

>NG:「実行」ボタンクリック後、**すぐに**インジケータが表示されること
>OK:「実行」ボタンクリック後、**1秒以内**にインジケータが表示されること

## 4.入力例/出力例
理系の人間は例を使いたがるが、テストにおいて「例」では、困る。
例だけ、入力すればそのテストは十分なのか？

具体的な条件を記載すること。
もし、ここでどういったパタンがあるかわからない、複数出てくるといったことがあるのであれば、テストケースの洗い出しが
不十分である可能性が高い。


## 5. TBD/TODO/要確認
試験に必要な情報が埋まっていないパターン。
試験するタイミングで確認を始めてしまうと非常に非効率なため、NG。
試験項目を開いた時点で、こういった文言がないかgrepしておくと、手戻りが少なく進められるかと思われる。
> 確認項目「テロップに"**(TBD)**"と表示されること

実際には、結構こういう文言が残っている試験項目を見かけることが多いかもしれない。。。

#最後に
小ネタということで、網羅性や体系化されていない部分ではありますが、
こうした小さな積み重ねが、試験の精度や品質の良しあしにかかわってくると考えています。
これを読んだうえで、なんらかの気づきや、改善につながれば幸いです。

では、良いクリスマスを🍍
D言語ネイティブHTTPクライアントdlang-requestsの基本的な使い方

# はじめに

[D-man bot](https://twitter.com/d_man_bot)で使われている[twitter4d](https://code.dlang.org/packages/twitter4d)は[std.net.curl](https://dlang.org/phobos/std_net_curl.html)を使っているためlibcurlに依存しています。
今回、std.net.curlから[dlang-requests](https://code.dlang.org/packages/requests)に変更するために調べた使い方をまとめます。

# dlang-requests

読んで字のごとく[python-requests](http://docs.python-requests.org)インスパイア系のHTTP/FTPクライアントライブラリです。
APIは3つのレベルに分かれています。

* 最も高水準な`getContent`と`postContent`
* `Request`を用い`Response`構造体を返す`Request.get()`, `Reuest.post`, `Request.exec!"method"`など
* 最も低水準な`HTTPRequest`と`FTPRequest`

今回は2つ目の`Request`と`Response`の使います。

## `Request`

### GET

```d
Request rq = Request();
Response rs = rq.get("https://httpbin.org/");
assert(rs.code==200);
```

std.net.curlは2xx以外のコードを受け取ったとき例外を返しますが、dlang-requestはハンドリングできます。

### POST

POSTもGETと同様に`Response`を返します。
また、`Request`には`addHeaders`でヘッダーを付加できます。

```d
auto rq = Request();
rq.addHeaders(["User-Agent": "test-123", "X-Header": "x-value"]);
auto rs = rq.post("http://httpbin.org/post", `{"a":"b"}`, "application/x-www-form-urlencoded");
```

## `Response`

レスポンスの詳細を表します。

* `code` - HTTP/FTPのレスポンスコード
* `responseBody` - レスポンスのボディ部
* `responseHeaders` - レスポンスのヘッダー部


## [Streaming server response](https://github.com/ikod/dlang-requests/blob/master/README.md#streaming-server-response)
`useStreaming`を`true`にすることで逐次レスポンスを受け取ることができます。

```d
auto rq = Request();
rq.useStreaming = true;
rq.verbosity = 2;
auto rs = rq.get("http://httpbin.org/image/jpeg");
auto stream = rs.receiveAsRange();
while(!stream.empty) {
    writefln("Received %d bytes, total received %d from d ocument legth %d", stream.front.length, 
    rq.contentReceived, rq.contentLength);
    stream.popFront;
}
```
`byChunkAsync`とほぼ同等の挙動をします。


# さいごに
コミット[[1]](https://github.com/simdnyan/twitter4d/commit/a363ba87149ca7806b9e85f289bddaefe19c5523)[[2]](https://github.com/simdnyan/dmanbot/commit/b073b6a933838782bb450fd5e1dbd14b191187b2)しました。
GistBoxが2017年11月で終わってしまったのでCacherに乗り換えた話

# はじめに
以前、GistBoxというツールが便利ですよ〜っていう記事を書きました。

こちら→[Tips保管に便利！GitHubと連携できるGistBoxの使い方](https://qiita.com/maimax/items/2bf4234f4fa4020823b0)

しかし、この記事を書いた翌月、なんとGistBox廃止というお知らせが・・・

ああ、記事に補足しなければ、と悶々としているうちに、いつのまにかGistBoxにログインできなくなっていました。

<img width="1220" alt="スクリーンショット 2017-12-24 21.27.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/173732/476346c8-85b0-8313-ce99-c5260de68e79.png">


# Cacherに乗り換える

誘導されるがままに遷移しますと、Cacherのダウンロードページへ移動します。
OSにダウンロードするも良し、Web上で使ってみるも良しです。

<img width="1192" alt="スクリーンショット 2017-12-24 21.30.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/173732/dc0854a6-c0db-6b2d-5541-cf2c7ae0bf27.png">


何はともあれ、ログインはこれまで通りGitHub連携みたいなので、ログインしてみることに。

<img width="763" alt="スクリーンショット 2017-12-24 21.43.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/173732/13a5cef3-f675-4896-7eb2-7e9ee1954213.png">

無事にログインできました！
データもちゃんと移行されている模様！！

がしかし！！！！

**今までつけたラベルが外れていました・・・**悲しい・・・

<img width="1415" alt="スクリーンショット 2017-12-24 21.45.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/173732/d629cefd-31e9-e74c-9dba-2f028b5e1290.png">



# 使い心地

### ＊ UIについて
大きくは変わっていないので、使用感はあまり変化ないです。
ラベルだけでなく、スニペットの言語ごとにタグがつくので、探すのが少し便利になったかも。

### ＊ Chromeの拡張機能は？
探してみましたが、今のところ便利そうな専用拡張機能は出ていない模様。
以前のGistBox用の拡張機能も使えなさそうです。

調べ方が甘いだけかもしれないので知っている方は教えてください♪


# 参考
詳しいことは、このあたりのブログを読んだら良いと思うのです。

- [GistBoxがCacherという新サービスになるらしい](http://takezoe.hatenablog.com/entry/2017/09/15/192129)
- [gistboxが終わりだなんて！あわててcacherに移行したメモ](http://kinaccco.hatenadiary.com/entry/gistboxtocacher)
- [GistBoxがいつのまにか別サービスに変わっていた（Cacher）](https://nekoshaku.com/wp-content/cache/all/web-service/gistbox-changed-to-cacher/index.html)


# おわりに
そもそも、最近Gistboxをあまり使っていなかったので、意外となにも影響なかったのでした。

今は[boostnote](https://boostnote.io/)というプログラマ向けのメモアプリが気になっている！
使ってみて比較してみようと思います。