Oracle 認定資格デジタルバッジ

8月から Oracle 認定資格を保有していることを証明する電子証明書デジタルバッジ』の提供が開始されたようです。保有資格をオンラインで公開できるものらしい。

education.oracle.com

というわけで、Oracle Certified Java Programmer のデジタルバッジを入手。

Oracle Certified Java Programmer, Silver SE 8
Oracle Certified Java Programmer, Gold SE 8

その他

Silver SE 8 を受験したときのメモはこちら。

kntmr.hatenablog.com

Gold SE 8 を受験したときのメモはこちら。

kntmr.hatenablog.com

サービスインと切り戻しについて

先日、お客様サイドで新規システムのリリース対応がありました。

が、新規システムに正しくデータが連携できなったようで、長時間の作業も虚しく、残念ながら切り戻しとなりました。ちなみに、切り戻しと言うのは、新規システムのリリースを取り止めて旧バージョンのシステムに戻すことです。

お客様サイドでは、不備(不整合)のあるデータを地道に直していたようなんですが、さすがに終わる見通しが付けられなかったようです。それでも、切り戻しの判断が下るまでかなり長い時間に亘って作業を続けていました。

なんとかリリースをやり切ってサービスインしようというような意思は感じましたが、もう少し早いタイミングで決断できなかったものか。

もしかしたら、見える範囲ではやり切れるという自負があったのかもしれません。しかし、見えないところに深い落とし穴はあるものです。

事前にリリース計画や作業手順は準備していても計画通りにいかないことはあると思います。計画にないタスクを手探りな状態で進めていると、次第に思考の冷静さを欠いていきます。その結果、重要な判断が先延ばしになり、ズルズルと長い時間に亘って悪い状況が続くことになります。

そうなる前に勇気をもってストップをかけられるか。切り戻しという決断ができるか。これが重要だと思いました。もちろん、事前に切り戻しの計画を立てていることも大事です。切り戻しで事故ったらどうしようもないので。

ただし、おそらく渦中のひとはこういう決断はなかなかできないので、マイルストーンを置いてこれに従うことを徹底するのがポイントだと思います。それか、当事者以外のひとがスケジュールを管理するか。

あと、システムは、切り戻し可能で、かつ切り戻し易いアーキテクチャにすることが大事だと思います。

自分もリリース対応の支援という形で28時間勤務(汗)となってしまいましたが、なかなか勉強になる1日でした。

Haskell を使ってみる 9 (高階関数1)

前回の続き

Haskell を使ってみる 8 (再帰) - kntmr-blog

引数に取ったり返り値として返せる関数を高階関数と呼ぶ。

カリー化関数

Haskell のすべての関数は引数を1つだけ取る。複数の引数を受け取るような関数はカリー化されている。関数を本来より少ない引数で呼び出すことを部分適用と呼ぶ。

Prelude> :t max
max :: Ord a => a -> a -> a -- a 型の値を引数に取る関数で、「a 型の値を引数に取って a 型の値を返す関数」を返す

これは、max :: Ord a => a -> (a -> a) と同義。

中置関数はセクションを使って部分適用する。

divideByTen :: (Floating a) => a -> a
divideByTen = (/10) -- 片側に値を置いて括弧で囲む

*Main> divideByTen 200
20.0

高階プログラミング

引数に関数を取ったり返り値として関数を返す関数の例。

 applyTwice :: (a -> a) -> a -> a -- 「a 型の引数を受け取り a 型を返す関数」を引数に取る
 applyTwice f x = f (f x)

*Main> applyTwice (+2) 10
14
*Main> applyTwice ("Hello " ++) "Haskell"
"Hello Hello Haskell"

ラムダ式

1回だけ必要な関数を作るときに使う無名関数をラムダ式と呼ぶ。主に高階関数に渡す関数を作るときに使われる。ラムダ式を宣言する場合は \ を使う。

Prelude> map (+1) [1,2,3,4] -- 部分適用
[2,3,4,5]
Prelude> map (\x -> x + 1) [1,2,3,4] -- ラムダ式
[2,3,4,5]

今回はカリー化と部分適用、ラムダ式まで。

LINE Messaging API と Google Apps Script で LINE BOT を作ってみる

LINE Messaging APIGoogle Apps Script で LINE BOT を作ってみるメモ。今回は、LINEのグループに送信したメッセージをメールで転送するBOT

アカウント作成 / BOT設定

※事前にLINEアカウントを作成すること

「LINE Business Center > サービス」の Messaging API で、「Developer Trial を始める」から LINE Business Center アカウントを登録する。

アカウントを登録すると LINE@ MANAGER ページが表示されるので、「APIを利用する」をクリックする。

「アカウント設定 > Bot設定」で以下を設定する。

  • リクエスト設定 > Webhook送信 > 「利用する」にチェック
  • 詳細設定 > Botグループトーク参加 > 「利用する」にチェック

BOTを友達に追加したときや自動応答のメッセージが不要の場合は「自動応答メッセージ」「友だち追加時あいさつ」のデフォルトメッセージを削除する。

最後に自分のLINEアカウントでBOTを友達に追加する。

LINE Developers

「アカウント設定 > Bot設定」のステータス欄にある「LINE Developersで設定する」リンクから LINE Developers ページを表示して Channel Access Token を発行する。これは、Google Apps Script から Messaging API にアクセスする際に使う。

Google Apps Script

今回、サーバを立てる代わりに Google Apps Script を使う。Google アカウントがあれば使えるので便利。

LINE BOT のプロジェクトと、処理を記述するスクリプトファイルを作成する。作成したプロジェクトは Google Drive に保存される。

LINE BOT からは POST でメッセージが渡ってくるので、doPost 関数を定義する。今回は受け取ったメッセージと送信したユーザの名前をメールで転送する。

var CHANNEL_ACCESS_TOKEN = '<CHANNEL_ACCESS_TOKEN>';

function getUsername(userId) {
  var url = 'https://api.line.me/v2/bot/profile/' + userId;
  var response = UrlFetchApp.fetch(url, {
    'headers': {
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN
    }
  });
  return JSON.parse(response.getContentText()).displayName;
}

function doPost(e) {
  var messageText = JSON.parse(e.postData.contents).events[0].message.text;
  var userId = JSON.parse(e.postData.contents).events[0].source.userId;
  var username = getUsername(userId);
  
  MailApp.sendEmail('beatkent@gmail.com', 'Forwarded LINE Messages', 'From: ' + username + String.fromCharCode(10) + messageText);
  return JSON.stringify({});
}

LINE API Reference を参考。

作成したスクリプトは「Publish > Deploy as web app…」で Web アプリとして公開する。Who has access to the app は Anyone, even anonymous を選択する。デプロイすると URL が表示されるので、コピーして LINE Developers の Webhook URL に設定する。

まとめ

思いの外、簡単に LINE BOT が作れる。あと、複数人トークグループトークは違うので要注意。


追記

メッセージをリプライするだけのBOTを作る場合は以下。

var CHANNEL_ACCESS_TOKEN = '<CHANNEL_ACCESS_TOKEN >';

function doPost(e) {
  var replyToken = JSON.parse(e.postData.contents).events[0].replyToken;
  var messageText = JSON.parse(e.postData.contents).events[0].message.text;
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    'headers': {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'POST',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': messageText + '!!!!',
      }],
    })
  });
  return JSON.stringify({});
}

Haskell を使ってみる 8 (再帰)

前回の続き

Haskell を使ってみる 7 (ガード) - kntmr-blog

再帰

関数を再帰的に定義する場合は問題を同じ種類のより小さな問題に分解する。再帰を使わずに定義できる問題を基底部と呼ぶ。再帰を実装する場合は基底部から考える。

maximum' :: (Ord a) => [a] -> a
maximum' [] = error "empty list"
maximum' [x] = x -- 基底部 (単一要素のリストの最大値はその唯一の要素を返す)
maximum' (x:xs) = max x (maximum' xs)

replicate' :: Int -> a -> [a]
replicate' n x
    | n < 1 = [] -- 基底部 (繰り返しが0以下のときは空のリストを返す)
    | otherwise = x : replicate' (n-1) x

take' :: Int -> [a] -> [a]
take' n _
    | n < 1 = [] -- 基底部 (取り出す個数が0以下のときは空のリストを返す)
take' _ [] = [] -- 基底部 (空のリストからは要素を取り出せないので空のリストを返す)
take' n (x:xs) = x : take' (n-1) xs

reverse' :: [a] -> [a]
reverse' [] = [] -- 基底部 (空のリストの逆順は空のリスト)
reverse' (x:xs) = reverse' xs ++ [x] -- tail の逆順の後ろに head を付ける

-- 基底部のない再帰で無限リストを作成する
repeat' :: a -> [a]
repeat' x = x : repeat' x

zip' :: [a] -> [b] -> [(a,b)]
zip' _ [] = [] -- 基底部 (空のリストを zip したときは空のリストを返す)
zip' [] _ = [] -- 基底部 (同上)
zip' (x:xs) (y:ys) = (x,y) : zip' xs ys -- head のペアの後ろに tail を zip したものを繋げる

elem' :: (Eq a) => a -> [a] -> Bool
elem' a [] = False -- 基底部 (空のリストは値を含まないので False を返す)
elem' a (x:xs)
    | a == x = True -- 渡された値と head が一致するか調べる
    | otherwise = a `elem'` xs -- head が一致しなければ tail を調べる

クイックソート

再帰を使ってクイックソートを実装する例。

quicksort :: (Ord a) => [a] -> [a]
quicksort [] = [] -- 基底部 (空のリストをソートしたときは空のリストを返す)
quicksort (x:xs) =
    let smallerOrEqual = [a | a <- xs, a <= x]
        larger = [a | a <- xs, a > x]
    in quicksort smallerOrEqual ++ [x] ++ quicksort larger

リストの先頭要素をピボットとする。リスト内包表記でピボット以下の要素のリスト (a <= x) とピボットより大きい要素のリスト (a > x) を取り出す。取り出した要素のリストには let 束縛で名前を付ける。それぞれのリストに quicksort 関数を再帰的に適用する。


今回は再帰について。

テスト駆動開発モドキ実践

過去のブログを掘り起こしたらこんなエントリを見つけました。

単体テスト(ユニットテスト/UT)で考えること - kntmr-blog

どうすれば、メンテし易く保守性の高いソースコードが書けるか、というようなことを考えていた頃のエントリだと思われます。所謂、『テスト駆動開発 (TDD)』と言えるレベルのものではないですが、改めてここで自分なりの方針を少し整理してみようかと。

前提

仕事では Java を使うことが多いため、Java/JUnit あたりのサンプルを記載していますが、これらの考え方は言語やフレームワークに依存するものではないと考えています。

背景

これまでに参画したプロジェクトにおいて、次のようなケースにたびたび遭遇した。

設計もそこそこに突貫で作るようなことが多く、結合試験のフェーズでモグラ叩きのようにデバッグしたり、デグレと闘いながら機能追加したりと、プロジェクトによってマチマチではあるが、かなり綱渡りな状況であったと思われる…。

モノによってはユニットテストコードはあるものの、1メソッド内にモック化や assert のコードが大量に書いてあったり、とても継続的にメンテできるようなものではない。そもそもテストとして機能していないことすらある。(丸々コメントアウトされてたり)

課題

早い段階でバグの芽を摘み、変更に強く、拡張に優しい開発を実現するにはどうすればよいか、開発中に抱える不安感をどうやって解消すればよいか。

そのような思いから、自分なりにきちんとユニットテストコードを書いて、品質が担保できていることをある程度自動化して保証することを目指してみようかと。

課題は、どうすればテストし易いコードになるか、ということ。

アプローチ

コードを書くときはテストし易い粒度でメソッドを分割する。

1つのメソッドの処理が長くなるとテストはやり辛くなる。テストし辛いコードはメンテすることが困難になる。メソッドの命名に悩んだり、メソッド名の文節が極端に多い場合、その処理には何かしらの複雑さが含まれている可能性があると考える。ただし、処理の目的を明確にするために細かく命名することは問題ない。

実装にコードを書き始めるときは、最初にスケルトンコードを書いて全体構成を整理する。メソッドのアクセス修飾子はデフォルト (パッケージプライベート) にして、プロダクションコードのクラスとテストクラスを同じパッケージに配置する。

TDD の世界では「テストから先に書く」ということを言われているが、そのあたりのアプローチがきちんとイメージできていない…。とりあえず、プロダクションコードを書いてからテストを書くようにしている。ただし、プロダクションコードに合わせてテストを書いていたら身も蓋もないので、テストコードを書くときは設計書なりをインプットにすること。

テストコードでは基本的にすべての分岐を通す。また、テストコードはテスト対象のクラス内で閉じるようにする。テスト対象クラスが他のクラスに依存する場合、そのクラスはモックにする。入力値として、境界値や空、および null のケースをテストする。

原則、1つのテストメソッドは、3フェーズ、もしくは4フェーズで構成する。(大抵は tear down を除いた3フェーズで事足りているが…)

  1. set up
  2. exercise
  3. verify
  4. tear down

モック化や assert が極端に多い場合には、プロダクションコードのリファクタリングを検討する。

修正や機能追加

修正や機能追加でプロダクションコードを変更する場合、以下のアプローチを取る。

  1. テストを実行してパスすることを確認する
    (まず、この時点でテストが正しく動作することを確認する)
  2. テストコードを目的の仕様に合わせて変更する
  3. テストを実行して落ちることを確認する
    (想定通りに落ちることを確認する)
  4. プロダクションコードを変更する
  5. テストを実行してパスすることを確認する

ここで気になるのは、テストコード自体が間違っていないか、ということ。人間なのでどうしてもミスをすることはある。(コピペミス然り…)

ミスを起こしにくくする、もしくはミスがあってもすぐにリカバリーできるように、テストコードやプロダクションコードをシンプルに保つ。テストし易いコードを書くように常に意識することが大事。

特長

このようなアプローチを取ると開発にテンポが生まれる。(ような気がする)
また、テストがパスしている間は大丈夫、という安心感を得られることは大きい。

その他

『学習テスト』という考え方があります。

gihyo.jp

テスティングフレームワークを使って、API やライブラリの使い方を調べるというアプローチです。例えば…

Boolean.valueOf("true"); // => true
Boolean.valueOf("TRUE") // => true?
Boolean.valueOf(""); // => false?
Boolean.valueOf(null); // => false? ぬるぽ?

こういうときに簡単なテストコードを書いてみます。

@Test
public void test() {
    assertTrue(Boolean.valueOf("true"));
    assertTrue(Boolean.valueOf("TRUE"));
    assertTrue(Boolean.valueOf("True"));

    assertFalse(Boolean.valueOf("false"));
    assertFalse(Boolean.valueOf("FALSE"));
    assertFalse(Boolean.valueOf("False"));

    assertFalse(Boolean.valueOf("hoge"));
    assertFalse(Boolean.valueOf(""));
    assertFalse(Boolean.valueOf(null));
}

昔は、検証用に Eclipse のプロジェクトを用意して、main から叩いて標準出力とかで確認したりしてましたが、今は JUnit を使ってやっています。

上の記事にも書いてありますが、API やライブラリの使い方を調べる以外に、メソッド単位でロジックを切り出してテストコードから検証します。ある程度、検証できたものはそのままプロダクションのコードに移したりします。

Java 9 を使えば、API やライブラリの検証程度なら JShell で十分なのかもしれません。(たぶん)

まとめ

当然、これが正解ではないですが、自分でも何が正解なのか分からないんですね…。とりあえず、自分がやれることを考えてやってみた結果です。

ただ、もっといいやり方がないものか、とは思います。以上です。

JSUG勉強会 2017年その4 ~ Springを使ったバッチアプリケーション特集 に行ってきた

JSUG勉強会 2017年その4 に行ってきました。簡単に所感をまとめます。

jsug.doorkeeper.jp

今回のテーマは Spring を使ったバッチアプリケーション特集です。

バッチは地味だが役に立つ

  • 処理モデル
    • タスクレットモデル
      • 1ステップに対して1タスクレットの構成
      • シンプル
      • 処理をまとめたい場合など
    • チャンクモデル
    • 処理モデルはジョブごとに選択する
  • 起動方法
    • 同期実行
      • スクリプトなどからジョブを叩く
      • 複数のジョブを組み合わせるケースなど
    • 非同期実行
      • Webコンテナ経由でジョブを叩く
      • バッチ実行に即時性が求められるケースなど (ユーザ操作とか)
  • データ入出力
    • ファイル/DB
    • Bean定義の中でファイル読み込みやDB書き込みの定義が書ける
    • MyBatis
  • フロー制御
    • Job と Step でフローを制御する
    • シーケンシャルフロー
      • 後続ステップを指定してステップを繋げていく
      • リターンコードで分岐を定義できる
    • step の同時実行 (並列処理/多重処理) が可能
  • ジョブ管理機能
    • 障害発生時のリスタート制御
    • 二重起動防止
  • Webコンテナによる非同期実行の課題
  • ファイルアクセスにおける課題
    • Spring Batch では1行を1レコードとして扱う
    • マルチバイト文字が正しく処理できない
    • CSVエスケープ処理してない (クォートで囲った中のカンマが区切りとして見なされる)
  • TERASOLUNA Batch Framework

  • 業務DBを別途用意する場合、2フェーズコミットが必要になるか

    • ジョブの実行結果は JobRepository に入っているのでどこまで処理できたかは分かるはず
    • 2フェーズコミットにしても不整合が発生する可能性はある
    • 2フェーズコミットは使わず、不整合が発生した場合の運用対処を用意するのもアリ

www.slideshare.net

  • JJUG CCC 2017 Spring の再演
  • 現在のレポートシステムは第3世代
    • RabbitMQ + Spring Cloud Stream で構成されている
    • 第2世代が Spring Batch アプリだったので Spring Boot アプリに移行し易かった
    • Spring Cloud では Spring Boot アプリであることが前提
  • Spring Cloud Stream
    • 各サービスがマイクロサービス
      • サービス間はメッセージで連携する
    • Publisher / Subscriber
  • ポーリング型からイベントドリブンにすることでスループットが向上
  • Source (Publisher) -> RabbitMQ -> Sink (Subscriber)
    • output と input 両方で同じ destination を指定する
      • ただし、このままだとスケールしたすべての Sink にジョブが送られてしまう
    • consumer groups でグループ化する
      • グループ内の1つの Sink にジョブが送られるようになる
  • 大量のジョブを処理すると、Job管理テーブルに同時に大量のINSERTが発生する
    • RDBがハングした結果、RabbitMQにメッセージが戻ってくる
      • 空いてる Subscriber に再送する
    • デフォルト3回リトライしてだめだったらメッセージが消える
      • とりあえず上限値を5に上げる
    • Dead Letter Queue
      • 5回リトライしてだめならエラー処理用のサーバにメッセージを送る
      • Dead Letter Queue からメッセージを再送信する
  • API, Batch の Spring Boot 化
    • API : war -> jar (with Jetty)
    • Batch : 1アプリを1jarにする (機能単位でjarにする)
      • 常駐バッチは spring.batch.job.enabled=false にする (起動時にジョブを実行しない)
    • Spring Boot アプリを Web アプリとして起動しない
      • ポートの管理が大変
      • 1サーバに複数プロセスとする
      • spring.main.web-environment=false を設定する
        • Spring Boot 2.0 以降は非推奨
        • 代わりに WebApplicationType を使う

TERASOLUNA は、フレームワーク本体だけでなくドキュメントも充実してるので、とても助かります。これまで Tasklet 構成のバッチしか作ったことがないので、機会があればチャンクモデルも使ってみたいです。Spring Batch は、その処理モデルに合わせて設計するのがキモな気がする。(特にチャンクモデルの場合)

Spring 徹底入門の Spring Batch 編をダウンロードしたけど、まだちゃんと読めてないので勉強しよう。

これまで、Spring Cloud を使ったことがないので、JSUG の勉強会で Spring Cloud 関連のテーマになると理解が追い付かない…。