Java8の機能を実務でどこまで使うか比較的まじめに考える。(Optional)
Java8の機能を実務でどこまで使うかを比較的まじめに考えてみようと思う。
地味だけどまずはOptionalから。
SI業務においては新機能を何でも取り入れるというのは現実的ではないと思うので、使いどころとか、それはアプリケーション担当が意識するか?という観点も入れていきたい。
先にまとめるとOptionalはアプリケーション担当も意識しておいた方が良い知識だと思う。
個人的感想としてOptionalには二つの意義があって、一つは、型がOptionalであることにより、nullが含まれる可能性を明瞭に示せること。もう一つは、nullの場合の操作を提供してくれること。
実益はnullガード戦法の抜けによるNPE発生トラブル抑止になることが期待されていると思うのだが、そもそもnullがありうると気づいていればnullガード戦法でも何とかなるので、その気づきを増やすことが大事なのではないかと思った次第。
あまり細かく説明しませんが、勉強用に書いたコードを晒しておきます。
package java8_study.optional; import java.util.Optional; /** * APIがレガシーな例。 * @author akiraabe */ public class Sample1 { public static void main(String[] args) { // ---(1) 直接toUpperCaseを呼び出す。 String str1 = "abe"; System.out.println(str1.toUpperCase()); // 値がnullだった場合・・・ String nullStr = null; //下記文は当然ながらNPEになる。 //System.out.println(nullStr.toUpperCase()); //したがってnullガード戦法を取る if (nullStr != null) { System.out.println(nullStr.toUpperCase()); } // ---(2) toUpperCaseをラップしたnullセーフなメソッドを使う String str3 = "akira"; System.out.println(myUpper(str3)); // 値がnullだった場合でもラッパーを呼びだせばとりあえずOK System.out.println(myUpper(nullStr)); // とはいえ、myUpperメソッドからもnullが返ってくることには変わらない。 // --(3) レガシーAPIを前提にOptionalで包み込む // 参考:http://www.slideshare.net/kawasima String upperdString = Optional .ofNullable(myUpper(nullStr)) .orElse("<NULL>"); System.out.println(upperdString); } /** * 引数を受けてUpperに変換するnullセーフなメソッド。<br> * Optionalは使っていないレガシーなAPI. * * @param str 文字列 * @return 大文字変換された文字列(引数がnullならnullを返却) */ private static String myUpper(String str) { if (str == null) { return null; } else { return str.toUpperCase(); } } }
続いて2作目!
package java8_study.optional; import java.util.Optional; /** * API(ここではprivateメソッドのこと)自体がOptionalに対応。 * * @author akiraabe */ public class Sample2 { public static void main(String[] args) { // ---(1) toUpperCaseをラップしOptionalが返ってくるメソッドを使う String str1 = "akira"; System.out.println(myUpper(str1)); String ret1 = myUpper(str1).orElse("<NULL>"); System.out.println(ret1); // 値がnullだった場合でもラッパーを呼びだせばとりあえずOK System.out.println(myUpper(null)); String ret2 = myUpper(null).orElse("<NULL>"); System.out.println(ret2); // しかし、これだとアプリのプログラマがOptionalの使い方を習得する必要がある //(習得しろよ!という説はあるが) // というのと、結局nullの場合の動作が所与のものとして決められるならば、 // それをAPI側の責務にしてもよいと思う。 // --(2) なのでもう少しAPIに責務を寄せてみる String ret3 = mySmartUpper(str1); System.out.println(ret3); String ret4 = mySmartUpper(null); System.out.println(ret4); String ret5 = mySmartUpper(null, "**"); System.out.println(ret5); } /** * 引数を受けてUpperに変換するnullセーフなメソッド。<br> * Optionalとして返却するAPI. * * @param str 文字列 * @return 大文字変換された文字列(引数がnullならnullを返却) */ private static Optional<String> myUpper(String str) { if (str == null) { return Optional.ofNullable(str); // emptyを返した方が親切? } else { return Optional.of(str.toUpperCase()); } } /** * 引数を受けてUpperに変換するnullセーフなメソッド。<br> * 一部の処理をmyUpperに委譲し、Stringを返す。 * * @param str 文字列 * @param initVal nullの場合の初期値指定 * @return 大文字変換された文字列(引数がnullなら指定の初期値を返却を返却) */ private static String mySmartUpper(String str, String initVal) { if (str == null && initVal != null) { return initVal; } return myUpper(str).orElse("<NULL>"); } /** * 上に委譲する。 */ private static String mySmartUpper(String str) { return mySmartUpper(str, null); } }
第一次の落としどころは、Sample2の(1)のレベルかと思う。
ラップは部品側でしてあげて、アプリケーション担当はOptionalから取り出す操作を実装する。
その操作があまりにも定型であれば、定型部分をprivateメソッドやHelperに括りだし、Sample2(2)のような形にする。ただしこれはアプリケーション担当の仕事と捉えた方が良いと思う。(ケースバイケースかもしれないが、これが定型になるのかどうかは仕様に依存するから)
Sample1(3)は、ラップしつつ受け取るというのをアプリケーション担当が実装するのだとすると、ちょっと敷居が高い気がするが、これはチームの技術力とか要件によってはありかと。
最後になりますが、この辺りの記事を参考にしました。ありがとうございます。
http://qiita.com/oohira/items/9c13f92815266cc5112c
http://www.slideshare.net/kawasima/java-62447735
Java EE7色々
業務都合でJava EE7にキャッチアップ。
大昔J2EEはやったし、Sunが悔い改めてEoDを掲げたEJB3?のハンズオンセミナーに参加したことはあるが、一定の距離を置いていた。SpringとかHibernateやってればどうにかなるだろうと思っていたが、書籍をざっと見ると知らないことが多々あることに気づく…。
そうなるとやってみるしかない!
環境は、Win7上で、NetBeansに全てを委ねるという堕落した感じ。DBはMySQLがたまたま入っているという理由で選択。
GlassFishの管理コンソール上で、jdbcのConnection Pools設定で早速試練がw
HTTP Status 500 - Internal Server Error
type Exception report
messageInternal Server Error
descriptionThe server encountered an internal error that prevented it from fulfilling this request.
exception
java.lang.IllegalStateException: getOutputStream() has already been called for this response
note The full stack traces of the exception and its root causes are available in the GlassFish Server Open Source Edition 4.1.1 logs.
これはGlassFishの既知の問題らしく、以下の方法で対処が可能とのこと。
C:\Program Files\glassfish-4.1.1\bin> asadmin create-jdbc-connection-pool --datasourceclassname com.mysql.jdbc.jdbc2.optional.MysqlDataSource --restype javax.sql.DataSource mySqlPool
JDBC connection pool mySqlPool created successfully.
Command create-jdbc-connection-pool executed successfully.
C:\Program Files\glassfish-4.1.1\bin>
各種プロパティを上記のコマンドで設定する方法が良くわからなかったので、コマンド実行後にGlassFishの管理GUIでプロパティを追加した
PortNumber 3306
Password *******
User root
serverName localhost
DatabaseName MySQL
AndroidアプリからFirebaseを使う。
AndroidアプリからFirebaseを使ってみたので覚え書き。
なんやかんや説明するよりもコードを紹介した方が話は早いのでとりあえずGitHubに載せた。
https://github.com/akiraabe/HelloFirebase
Firebaseって何?という方は、この辺をご覧ください。
Gihyoの記事:
http://gihyo.jp/dev/serial/01/firebase
本家サイト:
https://www.firebase.com/
本家サイトは当然英語ですが、読みやすいです。
クライアントはWeb(Js)、Andoroid、iPhoneなどがサポートされていますが、ドキュメントやサンプルが結構そろっています。
今回は、題材としては(勉強用なので実用性はおいておいて)チャットアプリとしました。
ポイントは、オフラインでも使える(Firebaseが面倒みてくれる)、Push型で他のクライアントから書き込んだ内容が自動的に表示されるといった点ですかね。
アーキテクチャ的にはFirebaseはデータ管理だけなので、ロジックはクライアントサイドに書くことになり、Webアプリ(Servletなど)をやっているとちょっと発想の転換を要求されます。
このようなアーキテクチャで美しい設計というのはどういうパターンになるのか研究材料として興味があります。
自前チュートリアルで「おみくじアプリ」を作るのでそれのスケルトンをアップ。
自前チュートリアルで「おみくじアプリ」を作るのでそれのスケルトンをアップ。
共同制作者(プログラミング初心者)にソースを共有するためにアップしたので、このコード自体が世のため人のためになることは一切ないと思われます。
import javax.swing.JFrame; import javax.swing.JOptionPane; /** * * チュートリアルのおみくじアプリケーションです。 * * @author akira_abe * */ public class OmikujiMain { /** * @param args */ public static void main(String[] args) { // Swingコンポーネントのインスタンスを生成します。 JFrame parent = new JFrame(); // 名前の入力を求めるダイアログを表示します。入力結果は変数resultに格納されます。 String result = JOptionPane.showInputDialog(parent, "Your name please!"); int conf = JOptionPane.showConfirmDialog(parent, "いいっすか?"); if (conf == JOptionPane.NO_OPTION) { System.out.println(JOptionPane.NO_OPTION); System.out.println("なんでやねん!??"); } else if (conf == JOptionPane.YES_OPTION) { System.out.println("よっしゃーーー!"); } // 特に意味はないが無限ループ for (;;) { System.out.println("ぬるふふふ...."); } // 入力値を表示します。(呼び捨てw) //System.out.println("おい! " + result); } }
Automated Business Logicを試した。
Grailsのサイトで「Automated Business Logic」というOSSがあるのを知りちょっと試してみた。
詳しくはこちらを参照。
http://www.automatedbusinesslogic.com/architecture/framework-integration/framework-integration---grails
一言で表すと、お決まりのビジネスルールの定義をモリモリとコードを書くのではなく、アノテーションで定義するというもの。
Tutorialをやるのは面倒だったので、demoをダウンロードして実行してみた。これはGrailsアプリの形式になっていて、解凍して"grails run-app"で直ぐに動かせるので雰囲気をつかむのにはよい。
感想:
コード生成自動化の対象がいわゆるボイラーコードやお呪いコードだけでなく、ビジネスロジックにまで及ぶ時代になった訳ですねぇ。この他にも「ルールエンジン」などで変化しやすいビジネスロジックをパラメータ化する技術も実用化されている訳ですから、従来型のプロセスで重厚に要件定義して、大量の設計文書を介して上流から下流へパスするというやり方がますます通用しなくなる気がします。
BA(ビジネスアナリスト)や業務SEも技術にもっと目を向けて、簡単なものなら自分で作れるくらいで無いといかんと思います。
なお、ツールのコンセプトはドメインクラスとは別に、HogeLogic.groovy(Hogeがドメイン名)というファイルにビジネスロジックを書くというものなので、イマイチ好みには合いませんでした。(ビジネスロジックはドメインクラスに書くのが好み)
まぁ、DDDのSpecificationパターンだと思えばよいのかもしれませんが。
また、デモの題材はEric EvansのDDD本のAggregateの章に出てくるものを使っているようです。
SPRING SECURITY WITH GRAILS
http://blog.springsource.com/2010/08/11/simplified-spring-security-with-grails/
を参考にGrailsアプリにSpring Securityを導入。
基本的にはサイトの手順どおりでOKだが、注意する点は以下。
Update As of version 1.2 of the Spring Security Core plugin, the generated User class automatically encodes the password when an instance is saved. Hence you no longer need to explicitly use SpringSecurityService.encodePassword()
とあるように、version1.2以降では、「SpringSecurityService.encodePassword()」を使う必要なし。
未解決課題:
Spring Securityを導入したら、日本語が文字化けするようになった。
回避策はあるのだろうか?(なきゃ困るがw)
追記:
Grails2.0.0にアップグレードすることにより解決しました。
Seleniumひとめぐり(FireFox)
Seleniumには色々な機能があるがSeleniumIDEしか使ったことがなかったので、その他の機能を使ってみた。業務で使うことを考えるとIEを克服?しないといけないのだが、なぜか自宅のPC(Vista)ではIEが安定稼動しないので、今回はFireFoxのみとした。
参考URL:
@IT
http://www.atmarkit.co.jp/fjava/rensai4/devtool07/devtool07_1.html
本家
http://seleniumhq.org/
今回試した機能(プロジェクト):
◆Selenium RC◆
Selenium RCにも色々な機能があるのだろうが、今回はTestSuiteをコマンドラインから実行するという部分に絞った。
・まずは、SeleniumIDEの操作記録機能でTestCase、TestSuiteを作成する。
・Selenium RCは、ここ(のSelenium Server (formerly the Selenium RC Server))からダウンロードする。
・コマンドの説明は、前述の@ITの記事にもあるので詳細は書かないが、ここでは、以下のようなコマンドを入力した。
java -jar selenium-server-standalone-2.13.0.jar -htmlSuite *firefox http://localhost:8080/finance/ c:\practice\grails\finance\testsuite.html result01.html
testsuite.htmlというのがSeleniumIDEで作成したTestSuiteで、result01.htmlはテスト結果の出力先。
感想:
firefoxの部分を*chromeとか*operaとかに変えると他のブラウザでのテストができる(ハズ)だが、本日のところは残念ながらFireFoxでしか正常稼動しなかった・・・。複数ブラウザをお手軽にテストするというのが狙いだったので残念。
◆Selenium WebDriver◆
WebDriverを用いると、Java、Ruby、PythonなどからSeleniumのテストを実行することが出来る。
この中では一番馴染みがあると言う理由でJavaを選択。
・まずは、Selenium WebDriverをセットアップしたeclipseプロジェクトを作成する。
手順としては、本家のDocumentに沿って、maven2を使った。
参考 http://seleniumhq.org/docs/03_webdriver.html#setting-up-a-selenium-webdriver-project
さほど難しい英語ではないので、読めば分かると思うが、適当なフォルダに、以下の内容のpom.xmlを記述する。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>MySel20Proj</groupId>
<artifactId>MySel20Proj</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
</project>そのうえで、以下のmavenコマンドを発行。
mvn clean install
Buildが成功したら、以下のコマンドでeclipseプロジェクトに変換する。
mvn eclipse:eclipse
・テストコードの作成
SeleniumIDEにより、JUnit4形式のテストコードを生成できる。
但し、このコードを実行すると、
Could not start Selenium session: You may not start more than one session at a time
というエラーが出た。
ググッた結果でこのサイトを参考にやっつけ仕事でコードを修正した。
結果のコードがこれ。(package、import宣言は省略してある)
public class TestCaseByJava2 {
DefaultSelenium selenium = null;
@Before
public void setUp() throws Exception {
WebDriver driver = new FirefoxDriver();
String baseUrl = "http://localhost:8080/";
selenium = new DefaultSelenium(new WebDriverCommandProcessor(baseUrl, driver));
}
@Test
public void testTestcase_java() throws Exception {
selenium.open("/finance/");
selenium.click("link=finance.BorrowerController");
selenium.waitForPageToLoad("30000");
selenium.click("link=Borrowerを新規作成");
selenium.waitForPageToLoad("30000");
selenium.type("id=code", "000001");
selenium.type("id=name", "borrower_01");
selenium.type("id=description", "test_01");
selenium.click("id=create");
selenium.waitForPageToLoad("30000");
selenium.click("link=ホーム");
selenium.waitForPageToLoad("30000");
selenium.click("link=finance.LoanController");
selenium.waitForPageToLoad("30000");
selenium.click("link=Loanを新規作成");
selenium.waitForPageToLoad("30000");
selenium.type("id=time", "6");
selenium.type("id=contractNumber", "L01");
selenium.type("id=loanType", "FLAT");
selenium.type("id=principal", "50000");
selenium.type("id=rate", "4");
selenium.select("id=startDate_day", "label=20");
selenium.select("id=startDate_month", "label=11月");
selenium.select("id=startDate_year", "label=2011");
selenium.click("id=create");
selenium.waitForPageToLoad("30000");
// verifyTrue(selenium.isTextPresent("582"));
// この入力例だと利息額が582円となるのでその値を検証している。
assertEquals(true, selenium.isTextPresent("582"));
}
@After
public void tearDown() throws Exception {
selenium.stop();
}
}ここでは、DefaultSelenium というオブジェクトを使っているが、これ以外にも、WebElementというオブジェクトを操作する方法もある。詳しくは、本家のサイトのドキュメントを参照。
感想(ならびに今後の課題):
プログラミング言語からSeleniumを操作できると色々と可能性が広がる。(当たり前か)
- テストケースのシート(Excel)などからpoiを使って操作内容を動的に生成するとか
- DBの状態に応じて処理を切り替えたり
たぶんこの手のことは誰かが実践済みなんだろうけど。この辺りを今後リサーチしてみたい。
◆追記
WebDriver経由でJUnitのテストコードから画面キャプチャする方法を調査したので掲載しておく。
参考 http://stackoverflow.com/questions/3422262/take-a-screenshot-with-selenium-webdriver
public class TestCaseByJava2 {
DefaultSelenium selenium = null;
WebDriver driver = null;
@Before
public void setUp() throws Exception {
driver = new FirefoxDriver();
String baseUrl = "http://localhost:8080/";
// Selenium selenium = new WebDriverBackedSelenium(driver, baseUrl);
// selenium.start();
selenium = new DefaultSelenium(new WebDriverCommandProcessor(baseUrl, driver));
}
@Test
public void testTestcase_java() throws Exception {
selenium.open("/finance/");
selenium.click("link=finance.BorrowerController");
selenium.waitForPageToLoad("30000");
selenium.click("link=Borrowerを新規作成");
selenium.waitForPageToLoad("30000");
selenium.type("id=code", "000001");
selenium.type("id=name", "person_01");
selenium.type("id=description", "test_01");
// captureEntirePageScreenshotが稼動しないので苦労した。。。
// selenium.captureEntirePageScreenshot("c:/practice/grails/finance/capture01.png", "");
captureScreenShot("c:\\practice\\grails\\finance\\capture_001.png");
selenium.click("id=create");
selenium.waitForPageToLoad("30000");
captureScreenShot("c:\\practice\\grails\\finance\\capture_002.png");
selenium.click("link=Borrowerを新規作成");
selenium.waitForPageToLoad("30000");
selenium.type("id=code", "000002");
selenium.type("id=name", "person_02");
selenium.type("id=description", "test_02");
captureScreenShot("c:\\practice\\grails\\finance\\capture_003.png");
selenium.click("id=create");
selenium.waitForPageToLoad("30000");
captureScreenShot("c:\\practice\\grails\\finance\\capture_004.png");
selenium.click("link=ホーム");
selenium.waitForPageToLoad("30000");
selenium.click("link=finance.LoanController");
selenium.waitForPageToLoad("30000");
selenium.click("link=Loanを新規作成");
selenium.waitForPageToLoad("30000");
selenium.type("id=time", "6");
selenium.type("id=borrowerCode", "000001");
selenium.type("id=contractNumber", "LOAN01");
selenium.type("id=loanType", "Flat");
selenium.type("id=principal", "50000");
selenium.type("id=rate", "4");
selenium.select("id=startDate_day", "label=20");
selenium.select("id=startDate_month", "label=11月");
selenium.select("id=startDate_year", "label=2011");
captureScreenShot("c:\\practice\\grails\\finance\\capture_005.png");
selenium.click("id=create");
selenium.waitForPageToLoad("30000");
captureScreenShot("c:\\practice\\grails\\finance\\capture_006.png");
// verifyTrue(selenium.isTextPresent("582"));
assertEquals(true, selenium.isTextPresent("582"));
selenium.click("link=Loanリスト");
selenium.waitForPageToLoad("30000");
captureScreenShot("c:\\practice\\grails\\finance\\capture_007.png");
}
private void captureScreenShot(String filePath) throws IOException {
File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(scrFile, new File(filePath));
}
@After
public void tearDown() throws Exception {
selenium.stop();
}
}