エンジニアの頭の中

フリーランスエンジニアが書く技術系ブログです。

Kindle Oasis のディスプレイが7インチになった!

f:id:mitsu3204:20171108231018j:plain

 

ついに7インチのKindle Oasisが発売された!

基本的に書籍は電子書籍で購入し、Kindleで読んでいます。

mi2.hatenablog.com

で、つい先日、Kindle Oasis の新型(2017年版)が発売されたので、早速購入しました。買ったのは、8GB、キャンペーン情報(広告)無し、Wifiのみのモデルです。

 

Kindle Oasis (Newモデル) 8GB、Wi-Fi、電子書籍リーダー


2016年版の Kindle Oasisのディスプレイは、6インチでしたが、今回発売された2017年版の Kindle Oasis は、ディスプレイが7インチとなりました。

 

しばらくの間、Kindle Voyageを使用していて、特に不満もなく使用していたため、初代Oasisは購入していませんでしたが、今回発売された Kindle Oasis の新型は、画面サイズが大きくなったこともあり、即ポチりました。 

 

Kindle Voyageの6インチのディスプレイと比較

旧型のKindle Oasisは持っていないので、Kindle Voyageとディスプレイサイズを比較してみました。

左がKindle Voyage(6インチ)で、右がKindle Oasis(7インチ)です。どちらも、文字サイズは下から3番目に設定してあります。

f:id:mitsu3204:20171108230532j:plain


当然ですが、ディスプレイが大きくなった分、同じ文字サイズでも、1ページ内によりたくさんの文字を表示できるようになりました。

 

他に変わった点

防水仕様になった

Kindleとしては初めての防水仕様(IPX8等級)となりました。ただ、私の場合は、浴室やビーチで読書することは今までもないですし、恐らく今後もそんな機会はないので、そちらはあまり気にしてません。

ストレージ容量が大きくなった

8GBと32GBから選べるようになりました。私は、8GBを購入しましたが、漫画をたくさんダウンロードする人は、容量を結構食うと思うので、32GBの方が良いかと思います。

漫画をダウンロードしない人なら、8GBあれば十分すぎるでしょう。私の場合、元々使っているKindle Voyageの容量は4GBですが、Kindleで漫画を読むことはないので、2年くらい使った今でも容量には余裕があります。

 

 

以上です。歴代のKindleの中では、変更点が目立ったアップデートでした。

新しいKindle Oasisで、今後の読書がますます捗りそうです。

 

Kindle Oasis (Newモデル) 8GB、Wi-Fi、電子書籍リーダー

 

SQLAlchemyでMySQLのINSERT .. DUPLICATE ON KEY UPDATE (UPSERT)を使用する

SQLAlchemyでUPSERTしたい

最近、PythonMySQLデータベースへ接続するコードを書いていたのですが、直接SQLを書くのが嫌になり、SQLAlchemyを使用することにしました。

既存のMySQLへの接続処理を、SQLAlchemyを使用するコードに置き換えていた際に、MySQLへのレコード登録時にキー重複が発生した場合は、該当レコードを更新する「UPSERT」をする必要があり、元々は、MySQLduplicate on key update機能をSQLを使用していたのですが、SQLを書かずにSQLAlchemyで実現する方法を調べてみたので、メモとして残しておきます。

ON DUPLICATE KEY UPDATEはSQLAlchemy 1.2系でサポートされている

SQLAlchemyでは、1.2系から同機能をサポートしているようです。

以下は、bitbucketのチケットです。

zzzeek / sqlalchemy / issues / #4009 - implement MySQL ON DUPLICATE KEY UPDATE — Bitbucket

  こちらは、公式ドキュメント What’s New in SQLAlchemy 1.2? — SQLAlchemy 1.2 Documentation

1.2系は、現時点(2017/11/5)では、プレリリース版です。私が使用しているSQLAlchemyは、1.1系でしたが、この機能を使用したかったので1.2系へ更新してみました。

SQLAlchemy 1.2系をインストール

pipで検索します。

$ pip search SQLAlchemy

1.2.0b3(ベータ版)が出てきました。

SQLAlchemy (1.2.0b3) - Database Abstraction Library
INSTALLED: 1.1.15
LATEST: 1.2.0b3

SQLAlchemyを1.2.0b3のバージョンに更新します。pipでベータ版へアップデートする場合は、通常のアップデート時とは異なり、--preオプションを付与して、install -Uを実行します。

$ pip install SQLAlchemy -U --pre
Collecting SQLAlchemy
Downloading SQLAlchemy-1.2.0b3.tar.gz (5.4MB)
100% |████████████████████████████████| 5.4MB 331kB/s
Installing collected packages: SQLAlchemy
Found existing installation: SQLAlchemy 1.1.15
Uninstalling SQLAlchemy-1.1.15:
Successfully uninstalled SQLAlchemy-1.1.15
Running setup.py install for SQLAlchemy ... done
Successfully installed SQLAlchemy-1.2.0b3

サンプルコードを読む

公式ドキュメントの、Dialect Improvements and Changes - MySQLの項には、upsertを実行するコードとして、以下のサンプルが掲載されています。

from sqlalchemy.dialects.mysql import insert

insert_stmt = insert(my_table). \
values(id='some_id', data='some data to insert')

on_conflict_stmt = insert_stmt.on_duplicate_key_update(
data=insert_stmt.inserted.data,
status='U'
)

conn.execute(on_conflict_stmt)

insert(my_table)関数の引数に指定されているmy_tableは、sqlalchemy.Table型のオブジェクトです。 肝心なのは、insert_stmt.on_duplicate_key_updateの部分です。 on_duplicate_key_updateの引数として、datastatusというキーの引数が指定されていますが、これらは、レコードのキー重複が発生した際に、「どのカラムをどんな値で更新するか」を指定するためのものです。

上記のサンプルの場合だと、以下のように解釈できます。

  • dataという名前の列を、insert文のdata列に指定した値で更新する。(つまりvalues関数の引数であるdataに指定された'some data to insert'で更新せよという意味)
  • statusという名前の列を、'U'という値で更新する。

というものです。 datastatusという引数は、on_duplicate_key_update関数特有の引数ではなく、更新対象とするカラムの名前と値を、key=valueの形式で表している点に注意してください。

実際に書いてみる

サンプルに従い、私が使っていたテーブルに合わせて書いてみたコードです。テーブル定義のコードとセットで載せておきます。

# table定義
sample = Table('sample', metadata,
    Column('context_id', Integer, nullable=False),
    Column('name', String(256), nullable=False),
    Column('amount', BigInteger, nullable=False),
    Column('created_at', TIMESTAMP, nullable=False, server_default=text('current_timestamp')),
    Column('updated_at', TIMESTAMP, nullable=False, server_default=text('current_timestamp'), server_onupdate=text('current_timestamp')),
    UniqueConstraint('context_id', 'name')
)

# insertを作成
insert_stmt = insert(sample).values(
    context_id=<context_idの値>, name=<nameの値>, amount=<amountの値>)

# ユニーク制約であるcontext_idとnameの重複が発生した場合は、既存レコードのamount列をinsert文のamountに指定した値で更新する
on_conflict_stmt = insert_stmt.on_duplicate_key_update(
amount=insert_stmt.inserted.amount)

# upsert実行
conn.execute(on_conflict_stmt)

以上です。簡単に書けました。

【2017年10月版】WealthNaviでお金は増えるのか?ロボアドバイザー投資の運用実績を公開!

以前、WealthNaviの運用状況を記載しましたが、しばらく期間が経ったので、最新の状況を公開します。 

前回は、2017年8月上旬時点での運用状況について記載しました。

mi2.hatenablog.com  

資産残高の推移

前回の記事で記載した通り、運用開始時点の初期投資額は100万円です。

その後の資産残高の推移は以下の通りです。 

年月 月末の資産残高 元金から損益 入金された分配金 支払った手数料
2017年5月 999,367 -633(-0.06%) 0 0
2017年6月 1,018,424 +18,424(+1.84%) 3,991 89
2017年7月 1,023,160 +23,160(+2.32%) 569 894
2017年8月 1,028,116 +28,116(+2.81%) 1,181 943
2017年9月 1,060,208 +60,208(+6.02%) 2,970 930
  • VTIなどのETFの価格が、順調に上昇しているため、資産残高は着々と増えています。
  • 9月末に急激に資産残高が増えていますが、これは、為替が円安に動いたことが要因として大きいです。
  • 6月と9月の分配金が多くなっているのは、VTIなどのETFの分配金は、四半期ごとに支払われるためです。

 

10月に資金を追加で投入

今までは、お試しのつもりで100万円だけ資金を投入して、様子を見守っていたのですが、運用状況も悪くないですし、少しづつ資金を追加しても良いかなという気になってきたので、10月に入り、クイック入金で10万円を追加で入金しました。

 

入金後は、早速購入処理が実行されていました。

f:id:mitsu3204:20171014171721p:plain

今後も気が向いたら少しづつ資金を追加していこうと思っています。

 

最新の資産残高

資金追加も行い、2017年10月14日時点の最新の資産残高は以下の通りです。日本円とドルとそれぞれ載せておきます。

日本円

f:id:mitsu3204:20171014164417p:plain 

USドル 

f:id:mitsu3204:20171014164429p:plain

 

今後もロボアドバイザーには注視して行きます。運用実績は、また公開します。

  

 

Python3で使用するMySQL用ライブラリのインストールしようとした時の問題

f:id:mitsu3204:20170815173648j:plain

Python3からMySQLを使用するため、mysqlclientをインストールしようとしたのですが、OSError: mysql_config not foundだのIndexError: string index out of rangeだのと、エラーが発生して苦労したので、メモを残しておきます。

環境情報

作業内容

ライブラリ選定

pythonmysql用ライブラリはいくつか存在しますが、どのライブラリも特に決定打となるような点も無いようなので、とりあえず、Python3に対応しているmysqlclientを使おうと決めました。

mysqlclientのインストールしようとして失敗

mysqlclientをpipでインストールしようとしたところ、以下のようなエラーが出ました。

$ pip install mysqlclient
Collecting mysqlclient
  Using cached mysqlclient-1.3.10.tar.gz
    Complete output from command python setup.py egg_info:
    /bin/sh: mysql_config: command not found
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/vf/1vzq715d34gb5rd58qmb2bv80000gn/T/pip-build-umfa1hq7/mysqlclient/setup.py", line 17, in <module>
        metadata, options = get_config()
      File "/private/var/folders/vf/1vzq715d34gb5rd58qmb2bv80000gn/T/pip-build-umfa1hq7/mysqlclient/setup_posix.py", line 44, in get_config
        libs = mysql_config("libs_r")
      File "/private/var/folders/vf/1vzq715d34gb5rd58qmb2bv80000gn/T/pip-build-umfa1hq7/mysqlclient/setup_posix.py", line 26, in mysql_config
        raise EnvironmentError("%s not found" % (mysql_config.path,))
    OSError: mysql_config not found

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/vf/1vzq715d34gb5rd58qmb2bv80000gn/T/pip-build-umfa1hq7/mysqlclient/

mysql-connector-cをインストール

OSError: mysql_config not foundで、mysql_configが無いと言われています。 で、ググった結果、mysql-connector-cをインストールすれば解決すると知り、Homebrewでmysql-connector-cをインストールします。

$ brew install mysql-connector-c

こちらは、無事にインストール完了しました。インストールしたmysql_configのバージョンは、6.1.11です。 これで、mysql_configが使えるようになります。

$ mysql_config
Usage: /usr/local/bin/mysql_config [OPTIONS]
Options:
        --cflags         [-I/usr/local/Cellar/mysql-connector-c/6.1.11/include ]
        --cxxflags       [-I/usr/local/Cellar/mysql-connector-c/6.1.11/include ]
        --include        [-I/usr/local/Cellar/mysql-connector-c/6.1.11/include]
        --libs           [-L/usr/local/Cellar/mysql-connector-c/6.1.11/lib -l ]
        --libs_r         [-L/usr/local/Cellar/mysql-connector-c/6.1.11/lib -l ]
        --plugindir      [/usr/local/Cellar/mysql-connector-c/6.1.11/lib/plugin]
        --socket         [/tmp/mysql.sock]
        --port           [0]
        --version        [6.1.11]
        --variable=VAR   VAR is one of:
                pkgincludedir [/usr/local/Cellar/mysql-connector-c/6.1.11/include]
                pkglibdir     [/usr/local/Cellar/mysql-connector-c/6.1.11/lib]
                plugindir     [/usr/local/Cellar/mysql-connector-c/6.1.11/lib/plugin]

異なるエラーが発生して、mysqlclientのインストールに失敗

改めて、mysqlclientをインストールしようとしたところ、今度はまた異なるエラーが発生しました。

$ pip install mysqlclient
Collecting mysqlclient
  Using cached mysqlclient-1.3.10.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/vf/1vzq715d34gb5rd58qmb2bv80000gn/T/pip-build-mz9gtse9/mysqlclient/setup.py", line 17, in <module>
        metadata, options = get_config()
      File "/private/var/folders/vf/1vzq715d34gb5rd58qmb2bv80000gn/T/pip-build-mz9gtse9/mysqlclient/setup_posix.py", line 54, in get_config
        libraries = [dequote(i[2:]) for i in libs if i.startswith('-l')]
      File "/private/var/folders/vf/1vzq715d34gb5rd58qmb2bv80000gn/T/pip-build-mz9gtse9/mysqlclient/setup_posix.py", line 54, in <listcomp>
        libraries = [dequote(i[2:]) for i in libs if i.startswith('-l')]
      File "/private/var/folders/vf/1vzq715d34gb5rd58qmb2bv80000gn/T/pip-build-mz9gtse9/mysqlclient/setup_posix.py", line 12, in dequote
        if s[0] in "\"'" and s[0] == s[-1]:
    IndexError: string index out of range

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/vf/1vzq715d34gb5rd58qmb2bv80000gn/T/pip-build-mz9gtse9/mysqlclient/

今度は、IndexError: string index out of rangeとか言われています。

mysql_configを修正

この問題は、既にGIthubにissueが挙がっていました。

github.com

解決方法を提示されていたので、それに従い、mysql_configを書き換えます。

$ which mysql_config
/usr/local/bin/mysql_config
$ vim /usr/local/bin/mysql_config

mysql_configの114行目に記載されているlibsの値を以下の通りに書き換えます。

変更前

libs="$libs -l "

変更後

libs="$libs -lmysqlclient -lssl -lcrypto"

再度mysqlclientをインストールを実行、そして成功

再度インストールを実行したところ、今度は成功しました。

$ pip install mysqlclient
Collecting mysqlclient
  Using cached mysqlclient-1.3.10.tar.gz
Installing collected packages: mysqlclient
  Running setup.py install for mysqlclient ... done
Successfully installed mysqlclient-1.3.10

動作確認

動作確認として、PythonからMySQLサーバに接続して、show tablesでテーブルの一覧を表示させてみました。 正常に動作しました。

import MySQLdb

conn = MySQLdb.connect(
        user='ユーザー'名,
        password='パスワード',
        host='DBホスト名',
        db='データベース名')
cur = conn.cursor()
cur.execute('show tables')
print(cur.fetchone())

これで作業完了になります。

WealthNaviでお金は増えるのか?ロボアドバイザー投資の運用実績を公開!

WealthNaviでロボアドバイザーによる投資を始めました

今年(2017年)の5月から、WealthNaviでロボアドバイザーによる投資を始めました。

以前から、株式投資はやっているのですが、ロボアドバイザーというのも面白そうだなと思い、試してみたくなったのです。

まだ運用期間が短いですが、実績を公開してみます。

www.wealthnavi.com

軍資金は100万円

元手となる資金は100万円を入金しました。

というのも、WealthNavi の最小の投資額は、100万円だったからです。現在(2017年8月時点)では、30万円から始められるようになっている様です。

入金は、Wealthnavi上のメニューから実行します。複数の銀行に対応しています。私は、三菱東京UFJ銀行から入金しました。

f:id:mitsu3204:20170730195301p:plain

プラン

最初にいくつかの質問に答えて、リスク許容度を決定します。

WealthNaviでは、このリスク許容度によって、何をどの程度の割合で保有するか、ポートフォリオの構成が異なってきます。私の場合、リスク許容度は5段階中4でした。

 

今までの投資実績

2017年5月 

初めての購入処理

 入金後、同日中に早速購入処理が行われました。内訳は以下の通りです。

f:id:mitsu3204:20170730195325p:plain

何をどの程度購入するかは、ユーザーごとのプランによって異なると思います。私の場合は、リスク許容度は4/5でしたが、VTIの購入割合が多いようですね。

 

翌日に追加で購入処理が発生していました。

この購入処理が完了した時点で、現金は数千円のみ残った状態でした。手数料の徴収があるので、多少は残すのでしょうか?

f:id:mitsu3204:20170730195427p:plain

5月末資産残高 

5月31日時点の資産残高です。

¥999,367($9,040)

円と米ドルと両方で確認できます。ドルでは増えているのですが、為替が円高となったため、その影響で日本円に換算すると、入金時点より減っています。 

2017年6月

手数料の支払いが発生

初めての手数料の支払いとなります。運用開始日が、5月22日なので、5月分は約10日分の手数料になると思います。手数料は、運用額に対して、年率1%となるのですが、89円をどうやって算出したのかは謎です。

f:id:mitsu3204:20170730195612p:plain

分配金が入金される

保有している各商品の分配金がそれぞれ入金されました。源泉税を2割徴収されるため、税引後の金額で合計4,340円です。

f:id:mitsu3204:20170730200354p:plain

6月末資産残高 

6月末は増えています。初期投資した資金からプラスになりました。

¥1,018,424 ($9,063.96)

 

2017年7月

米国債券(ACG)と、不動産(IYR)の二つで、二度目の分配金の入金が発生しました。金額はどちらも前回とほぼ同様です。

また、手数料の支払いが発生しています。前回の手数料は、運用期間が丸々一ヶ月分なかったので、金額がもっと小さかったですが、今回は6月の丸々一ヶ月分に対する手数料となります。

金額は、894円となってますが、100万円を一ヶ月運用した時の手数料はだいたいこれくらいになるようです。

f:id:mitsu3204:20170730200849p:plain

7月末資産残高

7月末の資産残高は、¥1,023,160で、6月末よりさらにプラスになりました。

まとめ

今までの月末時点の資産残高の推移です。

年月 資産残高 元金からの差額
2017年5月 999,367 -633(-0.06%)
2017年6月 1,018,424 +18,424(+1.84%)
2017年7月 1,023,160 +23,160(+2.32%)

 

以下は、現在(2017年8月5日時点)のポートフォリオです。

f:id:mitsu3204:20170805022921p:plain

 

運用期間はまだ二ヶ月ちょっとですが、現時点で既に3%弱の利回りとなっているので、実績としては好調です。銀行に預けておいても、金利はほぼ無いのと変わらないので、比較するとかなり良いですね。

ただ、まだ運用期間も短いですし、今まで相場が大きく崩れるシーンもなかったので、これからそういう事態が発生した際にどうなるかは注目するところです。

最近は、ロボアドバイザーがどんどん話題になってきていますね。この辺の話題は気になるところなので、今後も注視して、知識は深めていきたいと思います。

 

 

また実績溜まったら公開します。

プログラマなら絶対読んだ方が良い本「UNIXという考え方―その設計思想と哲学」(要約・書評)

f:id:mitsu3204:20170806141825j:plain

どんな本?

UNIXという考え方―その設計思想と哲学」は、プログラム開発におけるUNIXの思想について述べた本です。

少し古い本ですが、その内容からは、共感できる点や学ぶ点が多いです。その考え方は、現代においても有効な点が多々あると思います。 こちらは、日本語に翻訳されたもので、原著は「The UNIX Philosophy」になります。

この本の要点

この本では、UNIXの考え方として重要な点を、9つの「定理」として語られています。

定理

  1. スモール・イズ・ビューティフル
  2. 一つのプログラムには一つのことをうまくやらせる
  3. できるだけ早く試作を完成する
  4. 効率より移植性
  5. 数値データはASCIIフラットファイルに保存する
  6. ソフトウェアの梃子を有効に活用する
  7. シェルスクリプトを使うことで梃子の効果と移植性を高める
  8. 過度の対話的インタフェースを避ける
  9. すべてのプログラムをフィルタにする

それぞれの定理とされているものについて、要点を以下にまとめてみました。 あくまでも、これらは、著者の主張を断片的に抽出したものです。実際の書籍では、細かい説明や、具体的な実例が豊富に記載されているので、ぜひ書籍を読んでみることをお勧めします。

定理1.スモール・イズ・ビューティフル

プログラムは、本来の目的のみを実現する小さなプログラムであるべきで、関連はするものの、本来の目的とは異なる機能を持った、巨大なプログラムを作るべきではないと述べています。

例として、ファイルをコピーするプログラムを挙げており、コピー元からコピー先へ、ファイルの内容をコピーする処理(目的の処理)の前後に必要になるファイルの存在有無のチェックなどの処理は、他の小さなプログラムに任せるべきということです。UNIXの実例として、ファイルの存在有無にはtestコマンドが使用されている点を挙げています。

小さなプログラムのメリット

  • わかりやすい
  • 保守しやすい
  • システムリソースにやさしい
  • 他のプログラムと組み合わせやすい

定理2.一つのプログラムには一つのことをうまくやらせる

一つのプログラムでは、一つの機能のみを提供すべきです。そのためには、以下の様な点を確認すべきです。

  • ユーザーとの対話が必要か?パラメータをファイルやコマンドラインから渡すだけでは足りないのか?
  • 入力データは、特殊なフォーマットが必要なのか?フォーマット変換は、他のプログラムでできるのではないか?
  • 出力データは、特殊なフォーマットが必要なのか?ASCIIではダメなのか?
  • 似たような機能を持った、他のプログラムを使い回す事が出来ないか?

この「一つのプログラムには一つのことをうまくやらせる」という考えかたから外れてしまったUNIXにおける実例が、lsコマンド であり、純粋なlsコマンドは、ファイルとディレクトリの一覧を順不同の一列で表示するものですが、その後のバージョンのlsは、表示内容を複数列に整えて表示したり、様々な機能を提供するために、大量のオプションがある点を指摘しています。

定理3.できるだけ早く試作を作成する

とにかく、早く試作せよとのこと。 アプリケーションの設計を固めてから、実装にかかるのではなく、コードを書き、早く試作することによって、うまくいく点、うまくいかない点を早く知り、リスクを減らし、結果として製品のリリースは早まるとしています。 具体的なプロセスとして、以下の3点を繰り返します。詳細な仕様書は、以下の繰り返しが終了した後、もし必要であれば作成します。

  1. 短い機能仕様書の作成
  2. コードを書く
  3. テスト

定理4.効率より移植性

プログラムは、効率が良く高速であることよりも、移植性の高さを重視すべきと述べています。また、データにおいても、移植性の高さを重視しています。

効率の良いコードは、ハードウェアの性能を活用して、高速な処理を実現するが、同時にグラフィクスアクセラレータやキャッシュメモリなどを活用するため、移植性が犠牲にされてしまいます。単一のアーキテクチャでしか動かないプログラムでは、利用機会が狭められ、マーケットが限られてしまうというデメリットだと主張しています。また、ハードウェアは、時間と共に進化して高速になっていきます。ハードウェアの性能向上により、現時点で低速だったプログラムは、十分高速に動かせる可能性があります。そのため、わずかな処理時間の短縮のために、コードを書き換えるようなことは時間の無駄です。 そうなった時に、プログラムが新しいハードウェアでも稼働できるよう、移植性が重要になります。

高い移植性 を実現するための、具体的な手段の例として、プログラムをシェルスクリプトで書くことを挙げています。 シェルスクリプトで書いたコードは、C言語で書いたコードより高い移植性を持ちます(OSにもよりますが)。

定理5.数値データはASCIIフラットファイルに保存する

データは、人が読めてかつ簡単にエディタで編集ができるASCIIで保管するべきで、バイナリや特殊なファイルシステムは使用するべきではないと述べています。

ASCIIであれば、多くのUNIXコマンド(awk、diff、grepsed、sort、tail … )でもデータを扱うことができます。 ASCIIであることによって、バイナリと比較して、効率が落ちる可能性は否めないでしょうが、高い効率を求められるプログラム以外では、ほとんど問題とはならないですし、また、マシンの性能の向上によっても、この点は改善されていくでしょう。

データをASCIIテキストで保管してく事は、アプリケーションを移植する際に、そのデータの移植も簡単にします。

定理6.ソフトウェアの梃子を有効に活用する

梃子を有効に活用するために、以下の点について、それぞれの重要性を語っています。

  • コードを書くのではなくコードを借りる
  • 独自記述に陥らないようにする
  • 他人にコードを使わせる
  • 自動化する

「よいプログラマはよいコードを書く。偉大なプログラマはよいコードを借りてくる」と始まり、他人の仕事の成果を自分の仕事に取り入れるべきで、それこそが、効率よくソフトウェアを作成する方法であり、借りられたコードも価値を高めるため、借りる側、借りられた側共にメリットがあると述べています。

車輪の再発明による無駄や、コードの移植性を下げることになりかねないことから、独自技術に進んでいくことに注意を促しています。コードの梃子を有効に活用にするには、他人のコードを借りるだけではなく、自分のコードを他人にも使わせるべきで、UNIXが成功した原因も、コードを自由に使えたことにあると言っています。

また、ソフトウェアの梃子を活用するには、人が作業するのではなく、とにかくマシンを働かせるべき、自動化すべきとしています。

定理7.シェルスクリプトを使うことで梃子の効果と移植性を高める

プログラムの梃子の効果を最大限得るためには、シェルスクリプトを効果的に使用しなければならないと述べています。

シェルスクリプトは、他人の書いたプログラム(シェルスクリプトC言語で書かれたプログラムなども)を、容易に使う事が出来ます。たった1行のスクリプトコードでも、パイプ「 | 」で連結して、複数のコマンドを連携して処理させる事が出来ます。

定理8.過度の対話的インタフェースを避ける

プログラムを起動して、ユーザーと対話しながら処理を行っていくインタラクティブなインタフェースのことを、「拘束的インタフェース」と表現しており、これに対して批判的な考えを示しています。 拘束的インタフェースのデメリットとして、以下の点を挙げています。

  • コンピュータが、人による入力を待っている時間は無駄であり、コンピュータの処理能力を活かしきれていない。
  • コマンドパーサーが大きく醜くなる。(他の定理に反することになる)
  • 対話的インタフェースは、UNIXの長所である「プログラム同士の会話」を行う事が苦手であり、シェルスクリプトに組み込む事が困難になるため、他のプログラムと結合する事が難しく、プログラムの梃子の効果を得る事が出来ない。
  • プログラムを繰り返し実行する事が困難で、スケーラビリティに欠ける。(拘束的インタフェースであるadduserコマンドで数千人のユーザー追加を行う事が現実的でない事を例に挙げている)

定理9.すべてのプログラムをフィルタにする

全てのプログラムは、何かしらの入力を受け付け、処理した結果を出力するフィルタであるとの考えを示しています。 また、データはコンピュータが作るのでなく、人間が作るもので、コンピュータはデータを変換するものだと述べています。

プログラムをフィルタ化するために、設計として、守るべき点は以下のとおりです。

  • データの入力にはstdinを使用する。
  • データの出力にはstdoutを使用する。
  • 帯域外情報の出力にはstderrを使用する。

プログラムをフィルタとして作成することを意識しておけば、拘束的インタフェースを避ける事ができ、梃子の効果をえる事ができます。

感想

「定理1.スモール・イズ・ビューティフル」の思想は、UNIXに限らず、現代のソフトウェア開発においても、大いに役に立つ考え方だと思っています。特に、保守のしやすさや、他のプログラムと組み合わせやすいという点は、重要な点だと思っています。

基本的にプロダクトの一部となるコードは、将来に渡ってメンテナンスしていくものです。最初にコードを書いた本人が、将来もそのコードのメンテナンスをし続けていくとは限りません。現在のためだけでなく、将来の担当者のメンテナンスのしやすさのためにも、小さなプログラムの作成を心がけるのは良いと思います。

「定理6.ソフトウェアの梃子を有効に活用する」では、「コードを借りるべき」という主張があります。効率や作り込むリスクを考えれば、当たり前のことではあるのですが、公開されているオープンソースのライブラリや、使用しているフレームワーク、プロジェクト内で作成済みのライブラリなどで、すでに提供されている機能を、「コードを借りる」事をせず、自前で作成してしまうというケースをよく見かけます。(逆によく理解せずに無闇に他人コードを借りて問題が起きるケースもありますが・・・)

「コードを借りる」事の実現のしやすさのためにも、「小さなプログラム」である事は重要です。コードを借りることができず、類似のコードを書く羽目になると、開発工数の増加や、不具合リスクの増加、保守性の低下など、良くないことばかりですが、ソフトウェア開発の現場では、頻繁に起きている問題だと思っています。

その他の「定理」としているものについても、大体は納得のいくもので、現実の多くのシーンにおいて通じるものではないかと思います。

ちなみに、定理については6章までで語られていて、その後の 「第7章 さらなる10のUNIXの考え方」では、実際のUNIX人が、どのようなシーンでどのように考え行動しているかを、著者の経験から紹介しているのですが、この章もなかなか面白いです。

総評すると、知識を深めるという点でも、面白さという点でも、とても充実した本だと思います。 ページ数は多くないですが、内容は濃く、値段も安いと思います。

日頃、UNIXに触れるかどうかに関わらず、エンジニアの人には、ぜひ読んでもらいたい一冊です。

書籍情報

表紙

※原著はKindle版が販売されていますが、翻訳版は紙の本しか販売されていないようです。

目次

  • 第1章 UNIXの考え方:たくさんの登場人物たち
  • 第2章 人類にとての小さな一歩
  • 第3章 楽しみと実益をかねた早めの試作
  • 第4章 移植性の優先順位
  • 第5章 これこそ梃子の効果!
  • 第6章 対話的プログラムの危険性
  • 第7章 さらなる10のUNIXの考え方
  • 第8章 一つのことをうまくやろう
  • 第9章 UNIXとその他のオペレーティングシステムの考え方

SeleniumとPythonを使用してネットプリントへの書類登録を自動化

http://www.printing.ne.jp/support/common_v5/src/jpn/images/nps-logo.png

あらすじ

仕事の取引先とは、見積書や請求書などの書類を、郵送でやりとりしなくてはならないのですが、印刷するためのプリンタを持っていないため(たまにしか印刷しないので所有したくない)、紙の書類を印刷する必要がある時は、ネットプリントというウェブサービスを使用しています。

www.printing.ne.jp

ネットプリントは、ウェブ上で登録した文書をセブンイレブンのプリンタで印刷できるサービスです。

月に数枚程度しか印刷しないので、これで十分なのですが、毎月使用していると、ブラウザから印刷するためのファイルを登録する作業さえも面倒に感じるようになり、自動化しようと思いました。

自動化のイメージとしては、印刷したいファイルのファイルパスを自動化プログラムに渡して起動すれば、あとはプログラムがネットプリントにファイルを登録してくれるというものです。

外部から印刷用ファイルを登録するための、APIが公開されていれば、手っ取り早いのですが、調べてみた結果、APIは公開されていませんでした。 そのため、普段手作業で行なっていたブラウザの操作を、Seleniumを使って自動化するためのコードを書きました。

使用した Python のバージョンは、3.5.1 です。(別に3系である必要はありません)

前準備

Seleniumをインストール

Seleniumを使用して、ウェブブラウザを操作するため、pipを使用してPythonSeleniumライブラリをインストールします。

$ pip install selenium

$ pip show selenium
---
Metadata-Version: 2.0
Name: selenium
Version: 3.3.3
Summary: Python bindings for Selenium
Home-page: https://github.com/SeleniumHQ/selenium/
Author: UNKNOWN
Author-email: UNKNOWN
Installer: pip
License: Apache 2.0
Location: /Users/mi2/.pyenv/versions/3.5.1/lib/python3.5/site-packages
Requires:
Classifiers:
  Development Status :: 5 - Production/Stable
  Intended Audience :: Developers
  License :: OSI Approved :: Apache Software License
  Operating System :: POSIX
  Operating System :: Microsoft :: Windows
  Operating System :: MacOS :: MacOS X
  Topic :: Software Development :: Testing
  Topic :: Software Development :: Libraries
  Programming Language :: Python
  Programming Language :: Python :: 2.6
  Programming Language :: Python :: 2.7
  Programming Language :: Python :: 3.3
  Programming Language :: Python :: 3.4
  Programming Language :: Python :: 3.5
  Programming Language :: Python :: 3.6

Chrome Driverをインストール

今回は、ウェブブラウザは、Google Chrome を使用します。 Seleniumで、Chromeを操作するために、Chromeのwebdriverが必要になります。 Homebrew でインストールします。

$ brew install chromedriver

$ chromedriver --version
ChromeDriver 2.29.461585 (0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b)

実装

Seleniumを使用して、Google Chrome の操作を自動化するためのコードを、Pythonで書きます。

upload_to_netprint.py

#!/usr/bin/env python
# coding: utf-8

import sys
import time

from selenium import webdriver


def upload(username, password, file):
    driver = webdriver.Chrome(executable_path="/usr/local/bin/chromedriver")
    try:
        driver.implicitly_wait = 3  # ページロード待ち時間(秒)
        driver.delete_all_cookies()  # Cookieを削除

        # ログイン画面取得
        print("[Step 1] Get login display.")
        driver.get("https://www.printing.ne.jp/usr/web/NPCM0010.seam")
        driver.find_element_by_id("NPCM0010:userIdOrMailads-txt").send_keys(username)
        driver.find_element_by_id("NPCM0010:password-pwd").send_keys(password)

        # ログイン実行
        print("[Step 2] Login.")
        driver.find_element_by_id("login").click()

        # create-document
        print("[Step 3] Open create-document display.")
        driver.find_element_by_xpath('//*[@id="NPFL0010"]/section[1]/div[1]/a').click()

        # プリントの設定
        print("[Step 4] Input printing preferences.")
        driver.find_element_by_xpath('//*[@id="NPFL0020"]/table/div/tbody/tr[3]/td[2]/div[1]/label[1]/span[2]').click() # 用紙サイズはA4を指定
        driver.find_element_by_xpath('//*[@id="NPFL0020"]/table/div/tbody/tr[5]/td[2]/div[2]/label[3]').click() # カラーモードは白黒を指定
        driver.find_element_by_xpath('//*[@id="NPFL0020"]/table/div/tbody/tr[6]/td[2]/div/label[2]/span[2]').click() # 予約番号タイプは「数字のみ」を指定
        driver.find_element_by_xpath('//*[@id="checkBox3"]/div[1]/label[2]/span[2]').click() # 登録結果通知は「設定する」を指定
        driver.find_element_by_xpath('//*[@id="upload-document"]').send_keys(file) # アップロード対象ファイルを選択
        driver.find_element_by_xpath('//*[@id="update-btn"]/span[2]/span').click() # アップロード実行

        time.sleep(2)

    finally:
        driver.close()


if __name__ == "__main__":
    if len(sys.argv) != 4:
        print("Usage: $user $pass $file")
        exit(1)
    upload(username=sys.argv[1], password=sys.argv[2], file=sys.argv[3])
    exit(0)

webdriverの初期化時にchromedriverのパスを指定する

executable_pathにchromedriverのパスを指定しています。

driver = webdriver.Chrome(executable_path="/usr/local/bin/chromedriver")

予めchromedriverにパスが通っている場合は、driver = webdriver.Chrome()と、引数無しで書いても問題ありません。

操作対象の要素の指定

画面の要素は、XPathで指定しています。

driver.find_element_by_xpath('//*[@id="NPFL0020"]/table/div/tbody/tr[3]/td[2]/div[1]/label[1]/span[2]').click() # 用紙サイズはA4を指定

印刷設定を変更したい場合は、コードも書き換える必要があります。(最終的には、印刷設定をコードと切り離して指定できるようにしたい) 画面の要素のXPathを調べて、書き換えてください。XPathの簡単な取得方法は、以下の記事でも紹介しています。

mi2.hatenablog.com

実行

ネットプリントにログインするため、ネットプリントのアカウントを持っている必要があります。 実行時に、引数でネットプリントのアカウントの認証情報と、登録対象のファイルのパスを渡します。

upload_to_netprint.py $username $password $filepath

引数の内容は、以下の通りです。

driver.find_element_by_xpath('//*[@id="checkBox3"]/div[1]/label[2]/span[2]').click()で、登録結果を通知するよう指定しているため、プログラムが正常終了した後は、登録結果がメールで通知されます。

セブンイレブンへ行って、専用の端末にこのメールに記載された番号を入力してプリントする事ができます。

まとめ

このプログラムによって、ネットプリントにファイルを登録するために、ブラウザを操作は自動化できましたが、プログラムの実行の際に、ファイルパスを渡してプログラムを実行するのは、若干面倒です。 次は、このプログラムを実行するためのトリガーを仕掛けて、自動起動を実現したいと思います。