To be a professional

プログラミング関係の情報

Javaのサロゲートペアについて

前書き

暇だったので、Java Cookbook, 4th Editionを読んでたのですが、下記のような記述がありました。(適当翻訳)

Javaサロゲートペアを扱う場合は、String.codePointAt()が使えるよ。 for (int i = 0 ; i < someString.length(); i++) { System.out.println(someString.codePointAt(i)); System.out.println(someString.charAt(i)); }

for文の中で、i++していることに違和感を感じました。

サロゲートペアとは

下記はわかりやすいよう、すごくざっくり + 適当な説明です。 パソコンにおいて、文字は数字で管理されています。
画面に表示するときに、特的のルールに従って、数字を文字に変換しているのです。
(0だったらAを表示みたいなイメージです。)
数字から文字に変換するルールはいっぱいあります。
JavaにおいてはUnicodeというルールが使われています。
Unicodeは、全ての文字を表現できるようにしようとしました。
しかし当初用意していた数値の範囲が、全ての文字を表すのには不足していました。
そこで2つの数字の組みで、一つの文字を表すことを考えました。
これがサロゲートペアです。

何に違和感を感じたのか?

サロゲートペアは2つの数字で、1つの文字を表します。
なのでサロゲートペアを含んでいる場合、i + 2にしないといけないはずです。

for (int i = 0 ; i < someString.length(); i++) { System.out.println(someString.codePointAt(i)); System.out.println(someString.charAt(i)); }

試してみる

下記のコードを試してみました。

String a = "𠀋"
System.out.println(a.charAt(0)); // 該当する文字がないので'?'が表示される。
System.out.println(a.charAt(1)); // 該当する文字がないので'?'が表示される。
System.out.println(a.codePointAt(0)) // 131083が表示される。
System.out.println(a.codePointAt(1)) // 56331が表示される。

ここでコードポイントを取得したい場合、a.codePointAt(0)が適切です。 なので下記の例西違うと、期待と違った値を取得してしまいます。

for (int i = 0 ; i < someString.length(); i++) { System.out.println(someString.codePointAt(i)); System.out.println(someString.charAt(i)); }

正しいコード

下記が正しいと思います。

for (int i = 0; i < a.length(); ) { int tmp = a.codePointAt(i); System.out.println(tmp); i += Character.charCount(tmp); } ポイントはCharacter.charCount()を使用しているところです。
charCount()はサロゲートペアを引数にした場合、2を返してくれます。

結論

サロゲートペアを使う場合は、ちょっと違うAPIを使わないといけないですね!

参考

qiita.com

www.ne.jp

www.ne.jp

SMTPクライアントを作成して、Gmailを利用する

経緯

今年の10月より、ネットワークの勉強をしています。
使用しているのはComputer Networking: A Top-Down Approach, Global Editionです。 この本の2章に、「SMTPクライアントを作成しようぜ!」という問題がありました。
解いてみると、意外に苦労したので備忘録として残しておきます。

SMTPクライアントとは?

SMTPクライアントは大雑把にいうと、GmailMozilla Thunderbirdのようなメーラーです。 まずSMTPは、メールを送信するプロトコル(約束事)です。
メールは利用しているPC(クライアント)から、ネットでつながっている送信先のPC(サーバー)に送信されます。 この時に使用されるのがSMTPです。

どう実装するか

TCPUDP

まずクライアントと、サーバーは何らかの方法で通信しないといけません。
通信方法は2種類あります。

  1. TCP
  2. UDP

今回利用したのは、TCPです。(単純に指定されていただけ)
実はSMTPは、どちらを使用するかを規定していません。
ただUDPだと、通信内容がサーバー側に到達しない可能性があります。

SMTPについて

SMTPの内容については、RFC 5321という文書にまとまっています。
ただ適当に実装するだけなら、Wikipediaの内容で十分です。
つまり、詳細はWikipediaの内容を見ていただきたいのですが、下記コマンドを利用するだけでよいです。

  • EHLO
  • MAIL
  • RCPT
  • DATA
  • QUIT

コマンドをどう利用するのか

上記のコマンドは、引数が必要です。(引数の内容はWikipedia見てください。)
利用するコマンド名と引数がわかれば、次は簡単です。
コマンド名と引数をそのまま、サーバーに送信するだけです。

ソース

実装したソースは下記となります。(教科書の指定がPythonとなっているので、Pythonで実装してます。)

import socket
import sys

SERVER_NAME = sys.argv[1]
SERVER_PORT = sys.argv[2]
YOUR_ADDRESS = sys.argv[3]
TO_ADDRESS = sys.argv[4]
MESSAGE = "\r\n I love computer networks!"
END_MESSAGE = "\r\n.\r\n"
SIZE = 1024


def send(client_socket, message):
    client_socket.send(message.encode())


def receive(client_socket, status_code):
    recv = client_socket.recv(SIZE).decode()
    if recv[:3] != str(status_code):
        print(str(status_code) + ' reply not received from server.')
        print(recv)

# Choose a mail server (e.g. Google mail server) and call it mailserver
mailserver = (SERVER_NAME, int(SERVER_PORT))
# Create socket called clientSocket and establish a TCP connection with mailserver
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clientSocket.connect(mailserver)
receive(clientSocket, 220)

# Send HELO command and print server response.
send(clientSocket, 'HELO Alice\r\n')
receive(clientSocket, 250)

# Send MAIL FROM command and print server response.
fromCommand = 'MAIL FROM: <' + YOUR_ADDRESS + '>\r\n'
send(clientSocket, fromCommand)
receive(clientSocket, 250)

# Send RCPT TO command and print server response.
rcptCommand = 'RCPT TO: <' + TO_ADDRESS + '>\r\n'
send(clientSocket, rcptCommand)
receive(clientSocket, 250)

# Send DATA command and print server response.
send(clientSocket, 'DATA\r\n')
receive(clientSocket, 354)

# Send message data.
send(clientSocket, MESSAGE)
# Message ends with a single period.
send(clientSocket, END_MESSAGE)
receive(clientSocket, 250)

# Send QUIT command and get server response.
send(clientSocket, 'QUIT\r\n')
receive(clientSocket, 221)

clientSocket.close()

Gmailの利用

実は上記のソースは、自宅のメールサーバー等では動きますが、Gmailでは動きません。
じゃあどうすれば良いのかというと、下記が必要となります。

  1. 暗号化
  2. 認証
  3. Gmail側の設定

暗号化

上記は通信内容を暗号化していません。
なのでまず暗号化が必要となります。
Pythonの場合、sslモジュールを使用することで暗号化ができます。

認証

上記のコードをみて、「あれっ」と思った方もいるかもしれません。
実は上記のコードは、認証処理(要はログイン)を行っていません。
認証処理をどうすれば良いのかというと、「SMTP-AUTH」(RFC 4954)を利用するだけです。
いろいろ方法はあるのですが、簡単そうな実装方法(Auth Loginコマンド)を選びました。
実装方法は下記となります。

  1. サーバーにAUTH LOGINを送信
  2. サーバーにユーザー名を送信
  3. サーバーにパスワードを送信

Gmailの設定

実はGmailはセキュリティを担保するため、いろいろ設定を行っています。
そのためユーザー名とパスワードをサーバーに伝えるだけでは、利用できません。
具体的には下記手順が必要です。

  1. SMTPで利用するアカウントを作成(なければ)
  2. SMTPで利用するアカウントの、「2段階認証」を有効にする
  3. SMTPで利用するアカウントの、アプリパスワードを作成する。

コード

実装したコードは下記となります。

import socket
import ssl
import sys
import base64
import time

SERVER_NAME = 'smtp.gmail.com'
SERVER_PORT = 465
USER = sys.argv[1]
PASSWORD = sys.argv[2]
FROM_ADDRESS = sys.argv[3]
TO_ADDRESS = sys.argv[4]
MESSAGE = "\r\n I love computer networks!"
END_MESSAGE = "\r\n.\r\n"
SIZE = 1024


def send(client_socket, message):
    client_socket.send(message.encode())


def receive(client_socket, status_code):
    recv = client_socket.recv(SIZE).decode()
    if recv[:3] != str(status_code):
        print(str(status_code) + ' reply not received from server.')
        print(recv)


# Create socket called clientSocket and establish a TCP connection with mail server
context = ssl.create_default_context()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as bare_client:
    with context.wrap_socket(bare_client, server_hostname=SERVER_NAME) as client:
        client.connect((SERVER_NAME, SERVER_PORT))
        receive(client, 220)

        send(client, 'EHLO example.com\r\n')
        receive(client, 250)

        send(client, 'AUTH LOGIN\r\n')
        receive(client, 334)
        send(client, base64.b64encode(USER.encode()).decode() + '\r\n')
        receive(client, 334)

        send(client, base64.b64encode(PASSWORD.encode()).decode() + '\r\n')
        receive(client, 235)

        # Send MAIL FROM command and print server response.
        fromCommand = 'MAIL FROM: <' + FROM_ADDRESS + '>\r\n'
        send(client, fromCommand)
        receive(client, 250)

        # Send RCPT TO command and print server response.
        rcptCommand = 'RCPT TO: <' + TO_ADDRESS + '>\r\n'
        send(client, rcptCommand)
        receive(client, 250)

        # Send DATA command and print server response.
        send(client, 'DATA\r\n')
        receive(client, 354)

        # Send message data.
        send(client, 'From user1 <' + FROM_ADDRESS + '>\r\n')
        send(client, 'To: user2 <' + TO_ADDRESS + '>\r\n')
        send(client, 'Date: ' + time.asctime(time.localtime(time.time())) + '\r\n')
        send(client, 'Subject: Test\r\n')
        send(client, '\r\n')
        send(client, MESSAGE)

        # Message ends with a single period.
        send(client, END_MESSAGE)
        receive(client, 250)

        send(client, 'QUIT\r\n')
        receive(client, 221)

ソースコードについて

ソースコードココで公開中です。

SIerを退職しました

はじめに

実は昨年の8月に所属していたSIerを退職しました。 よくよく考えてみると、退職ブログを書いていなかったので書いてみます。

なんで退職したのか

会社に対する不信感及び、会社に所属することのメリットがなかったからです。

何をやっていたのか

細かく言えばいろいろありますが、おおまかな感じであれば下記の通りです。

  1. VB.NETを使用しているシステムのローカリゼーション
  2. VB6からVB.NETへのコンバート
  3. 新人研修の講師
  4. Web系(ASP.NET, Java)システムの改修
  5. Web系(Java)システムの新規構築
  6. CI/CD環境の移行

SQL数十本をテストなしで修正しないといけなかったり、ビルドできないシステムを改修したり、誰も詳細を知らないシステムを改修したり、思い出がいっぱいありますね。

会社のよかったところ

実は所属していたのは悪名高い会社なのですが、社員はいい人が多かったです。
後なぜかスキルが高い社員がいたので、とても勉強になりました。
開発に関して基本的なことは全てその方々から学べました。 後、案件についてもある程度こちらの要望を聞いてもらえました。

なぜやめるのか

給料が低い上に、上がらない

給料は本当に低かったです。
どの会社に転職しても、必ず給料は上がるレベルでした。

炎上案件が多い

SIerあるあるですが、基本的に2回に1回は炎上案件です。 まあ残業しないと生活できないので、ある意味ちょうどいいのかもしれません。

スキルアップできない

SIerだと、10年以上やり方が変わっていないところが多いです。
例えばテストはなぜかいまだに手動です。

リーダーが蒸発した

実はある案件でチームのリーダーが蒸発しました。
どうも営業と顧客の板挟みになっていたようで、ある時から来なくなりました。
下手すると自分も同じ目に遭いかねないと思うと、辞める一択でした。

最後に

実は転職済です。
今はVB.NETの案件をやっていますが、開発チームの立ち上げが主な仕事です。 メンバー自体いないので、採用からとなります。
早めに立ち上げたいな…。

Ubuntuインストール覚書

きっかけ

ようやく開発環境としてThinkPad X1 Carbonを買いました。(ThinkPad X1 Carbon Gen 8)
ThinkPad X1 Carbon にUbuntuをインストールしようとしたところ、ハマったところがあったので、メモとして残しておきます。

Ubuntuのインストール

下記手順でUbuntuのインストールを行いました。

  1. Ubuntuをダウンロードし、USBにイメージとして保存
  2. ThinkPadにUSBを差込み、起動
  3. BIOSを起動し、USBから起動するようブート順を入れ替える。

今までUbuntuをインストールする場合、UEFIでなかったので上記手順でUSBから起動できていました。 しかしUEFIになった場合、下記手順を行えばUSBから起動できました。

  1. BIOSを起動する。
  2. Securityの項目を選択
  3. Virtualizationを探す
  4. DMA ProtectionをOffにする

ただし今回は試さなかったのですが、UEFIモードでUbuntuをインストールしている記事があります。
上記の手順通りにすれば、UEFIモードでインストールできる可能性はあります。

日本語入力

購入したThinkPad英語キーボードなのですが、下記でツールをインストールしないと日本語が入力できません。

% sudo apt-get install mozc-utils-gui

その後、下記手順でキーを設定する必要があります。

  1. Ubuntuの入力リソースを日本語に設定
  2. mozcのツールを起動
  3. ctrl + space になっている項目を削除
  4. Hankaku/Zenkakuの項目を適当なキーに変更

GitHubSSH鍵を登録

GitHubSSH鍵を登録する場合、下記手順を行う必要があります。

  1. ssh-keygen -t rsa -C "メールアドレス"を実行
  2. cat .ssh/id_rsa.pubを実行し、表示された内容をコピー
  3. GitHubにログインし、Settingsを選択
  4. SSH and GPG keysを選択
  5. SSH keysのNew SSH keyを選択
  6. コピーした内容を貼り付ける
  7. Add SSH keyボタンをクリック
  8. ssh -T git@github.comで応答が返ってくるくることを確認する

ポイントはssh -Tであってsudo ssh -Tではないことです。
sudo ssh -Tにしてて、結構時間を無駄にしました。

参考

sedコマンドまとめ

動機

久しぶりにsedコマンドを使おうとしたのですが、忘れてました。 復習のため、infoコマンドで調べた内容を適当にまとめておきます。

sedとは

まずsedとはなんでしょうか? infoコマンドによると

'sed' is a stream editor.
A stream editor is used to perform basic text transformations on a input stream(a file or input from a pipeline).

とのことです。 つまり簡単に言うと、テキスト処理(特定の行を削除、置換など)ができるコマンドです。

使い方

infoコマンドをみると、下記のように記されています。

sed SCRIPT INPUTFILE

SCRIPTについては、下段を参照してください。

使いそうなオプション

-iオプション

sedはデフォルトだと、実行結果を標準出力に表示します。 実行結果をファイルに保存したい場合、- iを使用することができます。

-nオプション

sedはデフォルトでは、指定された入力をどのように処理したかを表示します。 -nオプションを使用すれば、表示されません。

-fオプション

ファイルから実行するコマンドを読み込む場合に使います。

--follow-symlinksオプション

-iオプションのみを使用した場合、symbolic linkの参照先ファイルを更新できません。
--follow-symlinksオプションを使用すれば、symbolic linkの参照先ファイルを更新できます。

-eオプション

実行するコマンドを追加します。

SCRIPT

sedのSCRIPT(s/hogehoge/foo/gとか)とは、下記の形式で表せるもののことです。

[addr]X[options]

[addr]は、オプションの行アドレス、Xsedコマンドを表します。 例えば下記だと、

sed '30, 35d'

30, d5が[addr]であり、dがsedコマンドです。

よく使いそうなsedコマンド

コマンド 内容
d 文字を削除します。
s/REGEXP/REPLACEMENT/[FLAGS] 指定した正規表現に一致する文字列を置換します。
y/src/dst/ srcで指定したいずれかの文字を、dstで対応する文字に変換します。

後書き

ざっとですが、sedコマンドを紹介しました。
info sedで書かれている内容はもっとありますが、あまりの量に心が折れたので、今回はここまでとしておきます。

JUnitをつくってみる

動機

「何かを勉強するには作ってみるのが良い」という言葉を聞いたことがあるでしょうか?
業務でJUnitを利用することはあっても、具体的にどう作られているのかは知りませんでした。
会社を辞めて転職活動中のため、時間があるので、実際に独自バージョンのJUnitを作ってみることにしました。

参考にするJUnitのバージョン

JUnitソースコードを探したのですが、JUnit4とJUnit5しか見つかりませんでした。(公式以外なら、JUnit4とJUnit5以外にも存在します。)
JUnit4のソースコードjunit4/src/main/java/配下には2つのディレクトリが存在します。

  1. junit
  2. org/junit

中をみてみると、どうも別々のソースが格納されているみたいです。
とりあえずjunitの方を参考にすることにしました。理由は下記の通りです。

  1. Kent Beckの"Test Driven Development: By Example"に近い
  2. コードがシンプル

JUnitの大まかな流れ

どのように調査したか

最初はjunitのソースを1つ1つ読んでいましたが、下記の理由で主要な流れを把握するだけにしました。

  1. クラスの量がそこそこある。
  2. クラスの関係を整理するために、ツールが欲しくなったがいいのがなかった。
  3. ずっと読んでいるだけだとしんどい。

JUnitの実行

JUnitは下記の流れで実行します。業務で使用する場合は、統合開発環境がよしなに実行してくれます。 詳細はJUnit公式ページでご確認ください。

  1. テストクラスを作成する。
  2. テストクラスをコンパイルする。
  3. TestRunnerを実行する。

テストクラスの作成方法および、テストクラスのコンパイルの説明は省略します。

TestRunnerを実行する。

下記のようなコマンドを実行します。

java -cp 必要なライブラリ テストを実行するクラス(JUnit3ならjunit.textui.TestRunner) テスト対象クラス

重要なことは、テストを実行するクラス及び、テスト対象クラスを指定していることです。

JUnitの解析

JUnitはどのように処理を実行しているのでしょうか?
何か特別な仕組みが存在するのでしょうか?
結論から言うと、何か難しいことをしているわけではなくReflectionを使用しているだけです。

エントリポイントをみてみる

テストを実行するクラスをしていることから、そのクラスがエントリポイントだとわかります。
実際にjunit4/src/main/java/junit/textui/TestRunner.javaを見てみるとmain()メソッドが存在します。
つまりjunitは単純にmain()メソッドを読んでいるだけです。何か特別なことをしているわけではありません。

テストメソッドをどのように実行しているか

エントリポイントはわかりましたが、ではどのようにしてテストメソッドを実行しているのでしょうか?
先ほどのmain()メソッドからはいろいろメソッドが呼ばれていますが、実際にメソッドを実行しているのは下記となります。

junit4/src/main/java/junit/framework/TestCase.javaのrunTest()メソッド

中身をみてみるとmethod.invoke()しているだけです。(methodはMethod型)
これはReflectionの機能です。

Reflectionとは

Reflectionとは簡単に言うと、実行時にクラスやメソッドの情報を読み取ったり実行したりできる機能です。
結構昔ですが、黒魔術として有名でした。
Reflectionの長所は、柔軟にクラスやメソッドを操作できることです。
短所は長所の裏返しです。
実行時にJVMで情報を集めてくる必要があるため、実行速度が遅くなります。またprivateメソッドも実行できてしまうため、使用方法を間違えるとエラいことになります。

実装をしてみる

クラス構成

上記でJUnitを作ってみるのに、最低限必要な知識は得られました。 最低限の機能を実施するとして、必要なクラスを考えてみました。

  • Assert(assertEqualsを提供する)
  • TestResult(テスト結果を保持する)
  • TestRunner(エントリポイント)
  • TestCase(テストクラスのスーパークラス)

コード

GitHubを参照してください。 なお現在は、クラス構成が多少異なります

やってみてどうだったか

やってみてよかった点は下記の通りです。

  1. クラス設計とか久しぶりで、頭の体操になった。
  2. なんとなくコードリーティングするよりモチベーションが保ちやすい

Software Debuggingまとめ3

Ch3 Simplifying Failures

エラーを単純化することによって得られるメリット

  1. すぐに理解できる。
  2. 説明する手間が減る。

どうしたら単純化できるのか?

ある問題に対し、単純化させることを考える。

  1. ある要素が、その問題が発生することに関して関係があるかをチェックする。
  2. 関係がなければ、削除する。
  3. 1, 2を繰り返す。

単純化に必要なもの

エラーの単純化は面白くないので、コンピュータに行わせる。

コンピュータに行わせるためには以下の2つが必要。

  1. 戦略
  2. 単純化が成功したかチェックするためのテスト。

戦略の例

  • デルタ・デバッギング
  • ファジ・テスト

デルタ・デバッギング

  1. 入力をn個に分ける。(最初はn=2)
  2. n個に分けた入力の内、エラーを返すものがあれば、それをさらに分割する。(n = max(n-1, 2))
  3. もしエラーを返さなければ、nを大きくし(n = 2n)、1から行う。

デルタ・デバッギングの欠点

  • 最悪テスト回数が、入力文字列の2乗になる。
  • テストを行う機能が複雑であるということ。(エラーのテストを繰り返し行うには、ビルド機能の自動化及びバージョン管理システムが必要。)

ファジ・テスト

ランダムに入力を行うテストの事。

セキュリティのチェックなどに使われる。

テスト後のチェック

テストを行った後は以下をチェックする必要がある。

エラーの修正方法。

エラーの修正を行った後、エラーが起こらないこと。