プログラミングと英語

英語ができなかったら、プログラミングはできるとは言えないのではないか。英語ができなければ近いうちにソフトウェアエンジニアと名乗れなくなるのではないかと思う。私は英語ができない側の人間だから、こんなことを考えるのだと思いますが。

日本社会として今は空前の英語ブームです。本屋に行けば英語教材の棚の占める面積は大きい。昇進や入社に TOEIC テストの点数を課している企業も多いので、多くの人々が英語の勉強をするべく、教材を買っている。従業員に英語力を課す企業は英語が必要になる方に賭けるという経営判断をしたのだろう。日本人の会社員の皆が英語を読んだり書いたり話したりできる必要があるのかは、私には分からない。英語を課した企業が賭けに勝つのかどうかは、おいおい結果が出るだろう。

企業として英語化が良いのかは分からないが、個人としては良いことなのは間違いないと思っている。これだけ英語ブームだったら、英語ができたら給料が上がる。従業員に英語力を課した企業は当然英語ができる人の給料を上げなければならない。*1一方で英語なんてどうでもいいと思っている企業は安い給料で英語ができない人を雇えるのだから、人件費の削減になる。企業として英語ができる人を比較的高給で雇うか、できない人を安く雇うかのどっちが得かは、私には分からないが、個人としては英語を覚えた方が得だろう。

上記は会社員一般についての議論だが、ソフトウェアエンジニアもしくはプログラマに関しては、英語ができなかったらそもそも仕事にならないのではないかと思ったりする。理由は二つで、一つは英語が読めなかったらマニュアルが読めない;もう一つは英語の読み書きができなかったらソースコードの読み書きができないことです。

インターネットがこれだけ普及した現在、我々日本人がアクセス可能な情報であっても、ほとんどの情報は英語で書かれている。知っているか知らないか――エンジニアは専門職なのだから専門知識を有していることが職業的な優位性を担保している。同じ問題に直面している人は世の中には多いものなので、既に世の中には問題の解決策が存在していることがしばしばある。普通にマニュアルを読めば答えが書いてあったりするし、Stack Overflow にヒントが載っていたりすることもある。それを知っていれば問題は素早く解決できる。エンジニアってそのために雇われているのでしょう? もちろん、Stack Overflow や Blog ポストに載っていることが正しいこととは限らないので、裏取りが必要なのは言うまでもない。隣に座っている人の情報の信憑性も同程度ではあるので、正しい情報源としては結局マニュアルを当たる必要があり、マニュアルが英語だった場合はそれが読めなければ困ったことになってしまう。いわゆるオープン系のシステムは、知らない人が作った部品を使うことがその定義であり、ほとんどの場合英語で書かれているマニュアルを読めなければ、仕事にならない。

マニュアルに限らず、ソースコードだって英語で書くものだ。英語ができなくても coding つまり機械に読めるような符号を書くことはできるが、例えば整数に全て i をつけるようなことはできるが、それはプログラミングではない。符号化はコンパイラの仕事であって、プログラマがやることではない。私は、「設計=名付け」と考えていて、名付けができていなければ、設計をしていないと思っている。英語ができなければ名付けができないので、英語ができないならプログラミングができないという考えになる。ただ動くだけのものだったら、別に英語力なんて要らないが、ただ動くだけのソフトウェアが極端に組織の足を引っ張るのは、誰しも経験して知るところだろう。

私がソフトウェアエンジニアを雇うなら、英語ができることを課す。自分が英語を出来なきゃ、英語ができる人のマネジメントなんてできないので、ちょっとは勉強しなければならないなと強く思う今日このごろ。

*1:英語のできないおじさんの給料は下げられないが、若い人には語学力を求めるような、おかしな会社もあるかもしれないが、そんな会社はさっさと辞めた方がいいと思う。

Bloom Filter を作ってみた

Bloom Filter を実装してみた。簡単な実装なので、速度や空間効率は悪いです。

Bloom Filter というのは、確率的データ構造の一つで、ある要素が集合に含まれるかどうかを試験するものです。空間効率が非常に良いのが利点で、偽陽性、つまり集合に含まれない要素もあるとしてしまう場合があるという欠点があります。偽陰性はありません。

以下が実装例です。

import hashlib
import math

class BloomFilter:

    def __init__(self, num_item, false_positive):
        assert 0 <= false_positive <= 1
        # Optimal number of bit array size and hash function
        # https://en.wikipedia.org/wiki/Bloom_filter#Optimal_number_of_hash_functions
        log2 = math.log(2)
        logp = math.log(false_positive)
        size = - num_item * logp / (log2 * log2)
        num_hash = size / num_item * log2

        self.size = int(math.ceil(size))
        self.num_hash = int(math.ceil(num_hash))

        self.array = self.BitArray(self.size)
        self.hash_functions = [self.HashFunction(i, self.size)
                               for i in range(self.num_hash)]

    def add(self, item):
        for hash_func in self.hash_functions:
            i = hash_func(item)
            self.array[i] = True

    def __contains__(self, item):
        return self.contains(item)

    def contains(self, item):
        for hash_func in self.hash_functions:
            i = hash_func(item)
            if self.array[i] is False:
                return False
        return True

    class BitArray:

        def __init__(self, length):
            # Allocate an object per a bit for simplicity
            self.array = [False for _ in range(length)]

        def __getitem__(self, index):
            return self.get(index)

        def get(self, index):
            return self.array[index]

        def __setitem__(self, index, bit):
            return self.set(index, bit)

        def set(self, index, bit):
            if bit > 0 or bit is True:
                self.array[index] = True
            else:
                self.array[index] = False

    class HashFunction:

        def __init__(self, seed, maximum):
            self.seed = seed.to_bytes(
                seed.bit_length() // 8 + 1, byteorder='big')
            self.maximum = maximum

        def __call__(self, byte_buffer):
            return self.call(byte_buffer)

        def call(self, byte_buffer):
            # MD5 is enough because I don't want to oneway hash function
            b = hashlib.md5(byte_buffer + self.seed).digest()
            i = int.from_bytes(b, byteorder='big', signed=False)
            return i % self.maximum

要素の数と偽陽性の確率を指定します。

>>> bf = BloomFilter(num_item=1000, false_positive=0.01)

アイテム数 1000 で偽陽性率が 1% で、たったの 9856 bit = 1232 bytes にデータが収まります。

>>> bf.size
9586

要素を入れて、入っているかどうかが判定できる。

>>> bf.add(b'1')
>>> b'1' in bf         # 必ず正しい
True
>>> b'2' in bf         # たまに間違う
False

面白いですね。

異常時にちゃんと止まるシェルスクリプト

自動化は大切ですが、自動化するときは異常時にちゃんと止まるようにしなければなりません。トヨタ自動車でいうところのニンベンのついた「自働化」である。なぜ自働化が必要かというと、エラー時に止まってくれない機械は稼働中に人が張り付いて見守っていなければならないからです。また、エラーが起こったのちに機械が動き続けるならば、異常なアウトプットが出力され続けることになり、異常なアウトプットを処理しなければならないというムダが発生します。自動化のためのスクリプトを書くときは、エラー時には止まるように作らなければなりません。

しかしながら、シェルスクリプトは恐ろしいことにエラーが起きても既定ではそのまま処理を続けていきます。異常時にちゃんと止まる自働化スクリプトとするためには以下をスクリプトの先頭に書くと良いでしょう。

set -eu -o pipefail
trap 'echo "ERROR: line no = $LINENO, exit status = $?" >&2; exit 1' ERR
set -e
set -e
echo 1
false
echo 2

上記のシェルスクリプトを実行すると、以下のように false のところ、つまり 0 以外で exit したところで止まります。

$ bash sete.sh
1
$ echo $?
1
trap

しかし、上記ではどこで止まったのかが分からないので、どこで止まったのかを trap を用いて表示するようにします。

set -e
trap 'echo "ERROR: line no = $LINENO, exit status = $?" >&2; exit 1' ERR
echo 1
false
echo 2

trap に第1引数で指定されたコマンドがエラー発生に実行されます。 LINENO 変数には現在の行番号が格納されています。実行と以下のようにエラー発生時の行番号と exit status が標準エラー出力に表示されて止まります。

$ bash traperr.sh 
1
ERROR: line no = 4, exit status = 1
$ echo $?
1
pipefail

set -e だけだとコマンドをパイプでつないだ場合に上手く止まらない場合があります。

set -e
trap 'echo "ERROR: line no = $LINENO, exit status = $?" >&2; exit 1' ERR
echo 1
false | true
echo 2

を実行すると、 false コマンドが失敗しているはずなのに、止まりません。

$ bash pipefail1.sh 
1
2
$ echo $?
0

set -o pipefail をつけるとパイプの途中のコマンドが失敗しても、止まります。

$ cat pipefail2.sh 
set -e -o pipefail
trap 'echo "ERROR: line no = $LINENO, exit status = $?" >&2; exit 1' ERR
echo 1
false | true
echo 2
$ bash pipefail2.sh 
1
ERROR: line no = 4, exit status = 1
$ echo $?
1
set -u

set -u を指定すると宣言してない変数が使用されるとエラーになります。

$ cat setu.sh 
set -u
echo 1
echo $AAA
echo 2
$ bash setu.sh 
1
setu.sh: line 3: AAA: unbound variable

自働化は運用時の話なので、タイポなんてのは運用前になくなっているはずなので、運用時にはなくてもいいですが、間違っているのに動いちゃっているという状態にならないためにつけておくべきでしょう。

プログラムのレイヤーは降りれても登れない

プログラムにはレイヤーというものがあります。レベルとも呼びます。プログラムというのは人間の理解を超えて複雑なものなので、全容を知るということは人間には出来ません。理解できない複雑なものを理解するために、人間には便利な思考法が備わっています:抽象化という思考法です。抽象思考がどういうものなのかは、説明が長くなるので、ここでは省きますが、プログラムの世界もレイヤー化という一種の抽象思考によって驚くほど複雑なものが作られてきました。一般に、カーネルなど生の機械を触るプログラムを低レイヤー(低レベル)と言い、アプリケーションの画面など人間に近いプログラムを高レイヤー(高レベル)と言います。

ソフトウェアエンジニアとざっくり呼ばれますが、レイヤーごとに仕事をしている人が異なります。高レイヤーのエンジニアはアプリケーションエンジニアと募集されていることが多いですし、比較的低レイヤーのプログラミングは最近ではシステムプログラミングと呼ばれます*1。同じプログラミングではあるけれども、求められている知識やスキルが微妙に異なるので、レイヤー毎にエンジニアの棲み分けが行われています。

低レイヤーのエンジニアは、高レイヤーのエンジニアより自分は偉いと思っていることがしばしばあります。私が知っているカーネルの開発をしている人は、職業欄に “Kernel Developer” とわざわざ書いています。俺は特別だと思っているのでしょう。鼻には付きますが、自分の仕事に誇りを持つことは悪いことではないです。

しかしながら、そのよく分からない高慢がエンジニアのキャリアや企業の収益の可能性を制限してしまっているのではないかと思います。

低レイヤープログラマは「低レイヤーができたら高レイヤーもできるが、逆はできない」と言うのですが、これは嘘です。使う人の気持ちになれないからです。作ったけど何に使うかは分からないと、恥ずかしげもなく言う程度の低い人がいる。そういう人々は、自分の仕事は難しいのだと偉そうにしているけれども、低レイヤーのプログラミングすら実はまともにできないのだ。

さらに、現在では、低レイヤーのソフトウェアを作ることが大して金にならなくなっている。Linux のように OSS として公開されているものが、選ばれるようになっているからだ。それに、例えば Google みたいな低レイヤーの製品で利益を得ていない企業が、自分らが使うために作ったが、隠し持っていても競争力に繋がらないし、広く使われるほうが有利だと OSS を公開している。

使い道を分かっている人が作って無償で公開しているものがあるのに、どうして使い道も考えずになんとなく作っている製品を買う人がいるのだろうか。無償で配布されているものにすら及ばないものしか作れないなら、その仕事に1円の価値もないのではないかと思う。

そんな事業辞めて、もっと高いレイヤーに移れば良いのにと思うが、それができない。売るレベルのものが作れないとしても、一回覚えたことにこだわってしまうものだ。それに程度の低いプログラマが高レイヤーのプログラムを書くとそれはそれはひどいものができる。なんとなく動くし、性能はいいのかもしれないが、抽象度が整っていない難読でメンテ不可能な代物になる。また、使い道が分かっていないというのは変わらないので、作ったものに商品性はない。レイヤーを移るのは無理なので、仕事を維持するための仕事をして1円の価値もないものを作り続けるしかないのだろう。

お前らはそれしかできないかもしれないが、俺は他のことができるのに、どうしてお前らの仕事を守るためだけのことをしなければならないのか――と思っている人は、さっさと金になるレイヤーに移った方がいいと思う。使い道が分かるようになったら、再びレイヤーを降りてプロダクトマネージャー的なことをやったら良い。その方がキャリアもそうだし、社会のためにもなると思う。

*1:システムプログラミングをする人のことは何と呼ぶのだろう?インフラエンジニア?

正しい人間になりたい

このところずっと心に思っていることがある、ちょっとはマシな人間になりたい。恥の多い生涯を送って来ましたが、そろそろお天道様に顔向けできるような人生を歩みたいです。

マシな人間とは正しいことをする人のことです。正義を信じ、正義を貫ける人に私はなりたい。私は絶対に正しいのだという自信を持った行いをしたい。

何をもって正義とするか。もちろん世界には多様な価値観があって、それぞれがそこでの生活体系の中では正しかろうということは認めている。私の正義は、私が持っている価値観の体系で正しいことである。私が正しいと思うことが私の暮らす共同体の中でも正しいかどうかを疑ったら話が進まないので、私は道徳を知っていると信じる。

むしろ年を取ると、正しいことを知っているはずなのに、分からなくなってしまうのが怖い。世の中には「馬鹿には見えない服」が多い。裸の王様はやっぱり裸であると、自分の感性には自信を持つべきだ。私も含めて言われた後に「そんなの当たり前だ」みたいなことを口にするのだが、経験だとか知識だとかが付いてしまうと当たり前のことにさえ言われるまで気付かなくなってしまう。

すべての正義を認識できていないのかもしれないが、現状知りうる正義をもってして、私はそれに従っているのか。恥ずかしながら不正義な行いをしている。悪行はしていないが、今日お前は悔いのない生を過ごしたかと尋ねられれば、確信をもって肯定することはできない。遅くに起きたし、頭ボサボサで、部屋は散らかったままだし、やろうと決めていたこともできていない。こんなちっぽけなことだけではなくて、一生後悔するようなこともこれまで多くした。10年近く無駄に過ごしている。生きているからいいのだけれど、余生もこのまま過ごすは嫌だわ。

正しい行いをしたい。私は絶対に正しいと確信をもって、日々を過ごしたい。お天道様にも世間様にも、恥ずかしくない生き方をしたいものです。

不正義な行いの何が良くないかというと、それを一旦してしまうと、どんどん悪い方に向かっていくところだ。ひとつ後ろめたいことがあったら、ふたつめもみっつめも不正義が気にならなくなってくる。そして常に良くない行いを選択するようになってしまう。また、ちょっとでも後ろめたいことがあったら、正しいことをする際の行動力まで下がる。私は断じて間違っていない・私は正しいと信じている人間は強い。

不正義というのは軽微であっても許すことができない割れ窓なのです。常に正しいことをするという、ゼロ・トレランスの精神で戦わなければならない。戦うといって、誰と戦うかというともちろん自身とである。まあ、そんな気張るようなことでもなくて、迷いだとか躊躇だとか怠けだとかをせずに、ただ自分の正しいと思うこと・当たり前だと思うことをするだけです――それが出来ないわけですが。

また、周囲とも戦わなければならない。自分の正義を貫くなんて、わがままな生き方です。お前らが間違ったことをしていようが俺は正しいことしかしない、そしてお前らも正しくあれ、と言っているわけですから。後ろめたさがちょっとでもあったらそんなことも言えないので、自由が効く範囲では隙のない正義を実行している必要はあります。わがままだとは思うのだけれど、後悔はしたくないので正しいわがままは通そうと思った。

ほとんど全員がこれは正しくないのではないかと思っていても、なあなあで正しくないことを続けているところってあるじゃないですか。「言っていることは正しい、でも……」だとか、「正論を言っても仕方ないじゃないか」*1とか、金輪際聞かない。今後は怒るようにしたい、そして無視するようにしたい。面倒くさいみたいな理由で私もそういうこと言ったことがあるけれど、それで何の進歩もないような場所には居たくない。もう決して言わない。

正義を貫く――これを格率として、ちょっとはお天道様に顔向けできるような日々を過ごしたいと思う今日このごろ。

心の欲する所に従いて矩を踰えず

こんな意識高いようなことを考えなくても、自然と実行できる境地に達したら、立派な人間になれたと言えると思うが、それは無理かな。

*1:一般的には正しいが個別の場合には正しくないならそれは正論でない。

inline_table ― ソースコードに ASCII テーブルを埋め込むための Python モジュール

inline_table という名前のソースコードに ASCII テーブルを埋め込むための Python モジュールを作ってみました。

GitHub - fjkz/inline_table: Python module for embedding text tables into source-code

以下のように reStructuredText で書かれたテーブルをソースコードに埋め込むことが出来ます。

>>> import inline_table
>>> t1 = inline_table.compile('''
...     === === ====
...      A   B   AB
...     === === ====
...      1   1  '1'
...      1   2  '2'
...      2   1  '2'
...      2   2  '4'
...     === === ====
...     ''')
>>> t1.select(A=1, B=2)
Tuple(A=1, B=2, AB='2')

if 文を並べるよりコンパクトで読みやすいと思うのですが、どうでしょう?

テーブルに値を渡すこともできます。関数なども渡せるので便利かと思います。

>>> t2 = inline_table.compile('''
...     === =====
...     key value
...     === =====
...      1   a
...      2   b
...     === =====
...     ''',
...     a='A', b='B')
>>> t2.select(key=1)
Tuple(key=1, value='A')

ソフトウェアの拡張と劣化

デカルトの『方法序説』に面白いことが書いてあった:

たくさんの部品を寄せ集めて作り,いろいろな親方の手を渡ってきた作品は,多くの場合,一人だけで苦労して仕上げた作品ほどの完成度が見られない.たとえばよくあることだが,一人の建築家が請け負って作りあげた建物は,何人もの建築家が,もともと別の目的で建てられた古い壁を生かしながら修復につとめた建物よりも,壮麗で整然としている.同じく,はじめは城壁のある村落にすぎなかったのが時とともに大都市に発展していった古い町は,一人の技師が思い通りに平原に線引きした規則正しい城塞都市にくらべると,ふつうひどく不揃いだ.

400年前に言われたことだがこれはデカルトの時代に限った話ではなく,現代でも正しい.どの時代でも成り立つ普遍的な事実なのであろう.しばしばソフトウェアは建築に例えられるが,ソフトウェアでもデカルトが言っていることが成り立つ.一般に一人で組んだソフトウェアの方が,多くの人が拡張していったソフトウェアよりも完成度が高い.多くの人が手を加えれば加えるほど,ソフトウェアは醜いものになっていく.

手を入れる人の数が多いと設計の一貫性が保てなくなるからである.良いソフトウェアは設計が一貫している.設計が一貫していない乱雑なソフトウェアは悪いソフトウェアだ.たった一つの設計思想のもとに整然と組まれたソフトウェアは美しい.設計思想がないパッチワークのようなソフトウェアは美しくない.

ただし,手を加える人もソフトウェアを醜くしようと思って手を加えているわけではない.古い設計のままでは新しい要求を叶えられないから,仕方なく手を加えているのである.要求が一貫しているなら設計も一貫したものになるだろう.だが,要求は一貫したものではなく,時々に応じて変わっていく.変化する要求に応じて随時ソフトウェアを更新していけば,ひどい異臭を放つ代物になっていくのも仕方のないことだ.まして,一度書いたところは触ってはいけない・デグレードは絶対に許さないといった縛りがあるならば,なおのことである.古いコードは新陳代謝なしに残っていて,癌細胞のように拡張されるコードとのキメラを形成する.手を加えた人に対してコードを汚したと非難するのは酷だろう.

では,一切何にも手を触れなければソフトウェアが劣化しないかというと,そうでもない.ソフトウェア自体は変わらなくても,周囲の世界の方が変わっていくから,相対的にソフトウェアは劣化していく.陳腐化ともいう.ソフトウェアに対する要求というのは変わっていくので,手を加えなければ,要求を満たさなくなっていく.Windows 2000 でしか動かないアプリケーションがあったとしても,そんなものにはもはや価値はない.

ソフトウェアが劣化していくのは避けようがない.デカルトのように,すべてを一旦全否定して,全く新しく自分が信じられるものを作り上げると野心的なことが言えれば良いのだろうけれども,そういうわけにはいかないのだ.そのため,いかにしてソフトウェアの劣化を緩やかにして,ソフトウェアを延命させるかということが課題になる.

銀の弾丸はないのだけれども,効果的な方法というは知られている.次回はそれについて考えてみたい.