Optional型活用のすゝめ
はじめに
Streamの話を↑でしましたが,コードレビューで指摘する内容として「Optional型を上手く使いましょう」という話もよくしています。Optional型もJava8から登場した比較的新しい文法になりますが,nullを扱う場合に便利になるので積極的に活用していきたいところです。
Optional型=nullかもしれない値
Optional型はJava8から追加された新しいクラスです。どういうケースで使うといえば,nullを返却する可能性があるメソッドの戻り値型として採用すると良いでしょう。まず,従来の文法で説明してみます。
private MyEntity convertToMyEntity(String name) { if (name.equals("Apple")) { return new MyEntity("name"); } else { return null; } }
引数で与えられる文字列が"Apple"の時以外はnullを返します。このメソッドを使う側は,処理結果としてnullが返ってくるかどうかをどうやって判定するのでしょうか? 呼び出し先のソースコードがあるならば,そちらを見れば良いでしょうが対象クラス・メソッドがjarファイルになっているケースもあります。なにより毎回ソースコードを当たらないとnullが返るかどうか分からないというのは面倒です。そういう時に使ってほしいのがOptional型なのです。
private Optional<MyEntity> convertToMyEntity(String name) { if (name.equals("Apple")) { return Optional.of(new MyEntity("name")); } else { return Optional.empty(); } }
こんな感じで戻り値型をOptional型でラッピングして返却します。”これ,なんの旨みがあるの?”と思いますよね。Optional型のミソはラッピングされた値を取り出すところにあります。Optional型から元のMyEntity型を取得するコードは以下のようになります。
Optional<MyEntity> entity = convertToMyEntity("Apple"); entity.ifPresent(e -> { //e=MyEntity });
値を取り出す際にnullチェックを強制出来るという点が最大の特徴です。このルールをチームメンバーで共有しておけば,少なくとも自前で作成したコード部分については
- Optional型=nullが返ってくる可能性がある
- 非Optional型=nullは返ってこない
といった方針でコーディングが出来るのではないでしょうか。※getメソッドでチェックすることなく強制的に中身を取り出すことも出来ますが,例外が飛ぶので禁止しましょう。コードレビューでの注視ポイントだと思います。
個人的には最初は↑の箇条書きの方針で採用していけば良いと考えていますが,ある程度Optional型に慣れてきたらもう一検討して欲しいです。最初に述べた通り,Optional型は相手にnullチェックを強要出来ます。逆に言うとnullでも構わないようなケースは別にOptional型である必要はないでしょう。nullを扱うためにいちいちアンラップするのも面倒ですから。検査例外と非検査例外……みたいですね。
戻り値以外でOptional型を使うのはNG
「Optional型,便利じゃん!」ってなってきましたか? 便利なOptional型ですが使う場所は限定しましょう。Optional型に慣れてきた頃にやりがちなミスですが,フィールドの型としてOptional型を使ったりメソッド引数としてOptional型を使うのは止めるべきです。
private Optional<String> option; private void doSomething(Optional<String> args) { //なんかの処理 }
理由は2点です。
- 再代入できる様式だと考慮すべきパターンが増える
- 引数でOptional型を与えるメリットが誰にもない
前者「再代入できる様式だと考慮すべきパターンが増える」はフィールド型として使う場合の話です。再代入可能なOptional型を定義するとそれを参照する側は
- Optional型オブジェクトは存在するが,中身が空(null)である
- Optional型オブジェクトは存在するが,中身が入っている
- Optional型オブジェクトが存在しない(=nullで代入された)
という3つのパターンを考慮しないといけません。これでは逆にデメリットが増えてしまいます。採用するのであればgetterメソッドの型だけをOptionalにする方が無難でしょう。
後者「引数でOptional型を与えるメリットが誰にもない」は説明が難しいのですが,メソッドを作る側の立場になって考えてみましょう。引数型がOptional型以外でも結局のところnullチェックって実装するのではないでしょうか? 非Optional型で来たからといって信用できないかなという気がしています。なので,個人的には引数にはOptional型を使わない方が良い?と思っています。基本,Optional型は受け取った場所(メソッド)で中身を精査して扱うというのが王道なのでしょう。
Oracleの中の人も「Optional型は戻り値だけで使ってねん」と言ってた気がします。
Streamを活用すると綺麗に書けるJavaパターン
はじめに
ここ数ヶ月くらいJavaのコードレビューをする機会が何回かありました。Javaは社内でも知っている人が多い言語ですが,歴史が長いだけあって昔々の文法を貫いてコード書く人が結構居ます。なかなか新しい要素を勉強し続けるのも辛いと思うのですが,やはり新しいものは良いものが多いので地道に布教活動を続けていきたいと思っています。今回は,特に指摘するケースが多いJava8から使えるようになったStream文に関する内容を整理してみます。
単純なループ
下のようなコード結構見かけませんか?
List<String> nameList = Arrays.asList("1st", "2nd", "3rd"); for (int i = 0; i < nameList.size(); i++) { fetchSomeInformation(nameList.get(i)); }
いちいちリストの件数を取ってループするのも直感的ではありません。拡張for文を使うのもGoodですが,Streamで置換するのも良い手段だと思います。
List<String> nameList = Arrays.asList("1st", "2nd", "3rd"); nameList.stream().forEach(n -> fetchSomeInformation(n));
ループ中の条件判定
前の例は単純過ぎたのでもう少し実践的なケースを……。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); nameList.forEach(n -> { if (n.length() == 5) { fetchSomeInformation(n); } });
リストの中身を判定して特定のケースだけで処理を行うコードです。よくやりますよね?こういう処理こそStreamで置換すると効果が大きいと思います。これを置き換えると……。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); nameList.stream() .filter(n -> n.length() == 5) .forEach(n -> fetchSomeInformation(n));
こんな感じになります。実行条件と実行内容がそれぞれfilterとforEachに分かれることで処理も見やすくなります。仮に条件が増えた場合も……。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); nameList.stream() .filter(n -> n.length() == 5) .filter(n -> n.startsWith("A")) .forEach(n -> fetchSomeInformation(n));
すっきり書けちゃいます。
リストの変換
リストでループを回しながら,独自のオブジェクトを作る下のようなコード……よくありますよね。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); List<MyEntity> entities = new ArrayList<>(); for(String name : nameList) { entities.add(new MyEntity(name)); }
こういうケースではmapメソッドが大活躍です。mapはStreamを別の形式に変換する機能を提供しています。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); List<MyEntity> entieies = nameList.stream() .map(n -> new MyEntity(n)) .collect(Collectors.toList());
かなりスッキリしたコードになりますね!
Token認証対応のAPNs ProviderをJavaで実装する
久しぶりのiOSネタです。iOSデバイス相手のPUSH通知を実現するApple Push Notification Services(APNs)ですが、JWT認証の方を使ったことがなかったので今回はそちらに挑戦してみようと思います。
おさらい
APNsサーバは、Providerとの交信方法を2種類提供しています。
「バイナリ Provider API」は、iOS黎明期からサポートされてきた昔ながらの交信方法です。APNsサーバとProviderサーバ間でソケット通信を行い、所定フォーマットのバイナリデータで通知を依頼します。自前で実装する場合、ペイロード(依頼内容)のバイナリ変換が面倒くさい印象があります。AppleでもこれからProviderサーバを実装するのであれば、バイナリProvider APIは非推奨……と言っています。その内、サポート切られるのでは?
「APNs Provider API」は、WWDC2015で発表された新しい交信方法です。HTTPのGET/POSTメソッドでAPNsサーバに対して通知依頼を出すことが出来るので、非常に簡便にプッシュ通知を実現できるようになりました。また、通知失敗時もHTTPリクエストのレスポンスで詳細な原因が返却されますので、再通知やデバッグが捗る点も魅力的ですね。
交信方法やエラーハンドリングの簡便さ以外に「APNs Provider API」の大きな特徴が、APNsサーバとの認証方式です。
APNsサーバとの認証方式
「APNs Provider API」は以下の2種類の認証方式をサポートしています。
- 証明書認証
- JWT認証
「証明書認証」は、Member Centerから発行したAPNs用SSL証明書を使った認証です。証明書の有効期限が1年で失効されるため、定期的に証明書を再生成して差し替えないと通知が送れなくなるというデメリットがあります。また、証明書はアプリ単位に作成するので管理も煩雑になりがちです。受託開発案件をやっている人は、結構面倒くさいのではないでしょうか?保守料を取れるという見方もありますが。ちなみに、バイナリProvider APIは、証明書認証だけしかサポートしていません。
「JWT認証」は、Member Centerから発行したAPNs Auth Keyを使った認証です。APNs Auth Key自身には有効期限が存在せず、このAPNs Auth Keyを使ってAPNsサーバに認証トークンを要求する点が大きな特徴であり、これらの認証トークン取得手続きは全てProviderサーバのプログラムで行う必要があります。その代わり、1度処理を実装してしまえばAPNs Auth Keyを手動で失効させない限り、ずっとプッシュ通知を送り続けられる環境が実現できます。証明書認証と異なり、通知先のアプリに依存する要素が少ない点も嬉しいです。
JavaでProviderサーバ(JWT認証)を実装する
本題です。フル自前実装でも良いのですが、OSSを活用しましょう。
(1) pom.xmlの編集
Mavenに登録されているので、Providerサーバ用プロジェクトのpom.xmlに依存関係を追記します。
<dependency> <groupId>com.relayrides</groupId> <artifactId>pushy</artifactId> <version>0.9.3</version> </dependency>
あと、ポイントとしてはログ出力用のライブラリも追加した方が無難です。通知に失敗したときの詳細挙動がログから追えるからです。Java標準のエラー出力だと原因調査が難しいので、ぜひ設定しましょう。
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
(2) 通知処理の実装
あとは簡単です。
try { // 通知クライアントを作成 final ApnsClient apnsClient = new ApnsClientBuilder().build(); apnsClient.registerSigningKey(new File("[APNs Auth Keyが置かれた場所までのファイルパス]"), "[TeamId]", "[APNs Auth Keyの認証コード]", "[通知先アプリのAppID]"); // ペイロードを作成 ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder(); payloadBuilder.setAlertBody("hoge"); String payload = payloadBuilder.buildWithDefaultMaximumLength(); // 開発用APNsサーバに接続 Future<Void> connectFuture = apnsClient.connect(ApnsClient.DEVELOPMENT_APNS_HOST); // 接続完了まで同期で待つ connectFuture.get(); // 通知オブジェクトを作成 SimpleApnsPushNotification pn = new SimpleApnsPushNotification("[デバイスToken]", "[通知先アプリのAppID]", payload); // 通知実行 apnsClient.sendNotification(pn); // APNsサーバから切断 apnsClient.disconnect(); } catch (SSLException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
BigCalendarが表示されないときの覚書
最近,仕事の都合でReact.jsを触っています。規模が大きいアプリを作る際にコンポーネント指向の恩恵を感じており,(コンポーネント単位で)品質を維持しながら開発を進められる点が魅力的ですね。Reduxなどの状態管理ライブラリも触りたいのですが,今関わっているプロジェクトを何とか終えなければ......というところで,詰まった点を覚え書きしておきます。
1. Big Calendarがちゃんと表示できない!
React.jsの便利なUIライブラリに「Big Calendar」という部品があります。その名の通り,カレンダー(≒スケジューラ)を表示するためのOSSライブラリで,以下のようなカレンダーを簡単に表示できるようになります。
ただ,初めて使った際は↓のような感じで正しく表示されず,対応に困りました。CSSのリンクを外すと日付は表示されるので,原因はその辺りにありそうだな......と思いながら数刻が過ぎ。
2. サイズ指定が要る
解決しました。公式ページに以下の記述があります。
The default styles use height: 100% which means your container must set an explicit height (feel free to adjust the styles to suit your specific needs).
コンテナに高さ指定が要るということですね。
<div className="Main"> <BigCalendar events={this.state.events} culture='ja-Ja' defaultDate={new Date(2017, 1, 2)} className="Cal" /> </div>
.Main .Cal { height: 600px; }
出た......!
GradleでWebアプリ
今更ながらJavaのビルドツール「Gradle」を触ってみました。
Mavenに比べるとビルドスクリプトが手軽に書けるのが良いですね!
(pom.xmlの構文エラーとの戦いを経験している特に)
今回はEclipseのGradleプロジェクト形式で作成したプロジェクトを
Tomcat上で動かすところまでの手順を整理してみます。
手元の環境はこんな感じです。
1. Gradle(STS)プロジェクトを作る
予めEclipseにGradle(STS)プラグインをインストールしておきましょう。
ヘルプのEclipse Marketplaceから入手可能です!
「Java Quick start」でシンプルにプロジェクトを作成します。
2. Servlet-APIを入手する
今回は単純なJavaServletで動作を確認します。
build.gradeを編集し、Servlet-APIを依存ライブラリに追加します。
dependencies { compile group: 'commons-collections', name: 'commons-collections', version: '3.2' compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0' testCompile group: 'junit', name: 'junit', version: '4.+' }
依存ライブラリを追加したらプロジェクトを選択して
「Refresh Dependencies」を実行すれば依存ライブラリが入手可能です。
3. プロジェクトをWTPに変換する
GradleプロジェクトのままではTomcatにデプロイできないので
WTPプロジェクトに変換したいと思います。
apply plugin: 'war' apply plugin: 'eclipse-wtp'
「war」プラグインと「eclipse-wtp」プラグインを追加で導入します。
上記の通りbuild.gradeファイルの先頭に記述を追加します。
導入後に
gradle eclipse
を実行すればWTPプロジェクトに変換されます。
4. Servletを実装する
ここは普通にServletを実装します。
@WebServlet(name="MyServlet", urlPatterns={"/test"}) public class MyServlet extends HttpServlet { /** * */ private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter writer = resp.getWriter(); writer.append("<html><body><h1>Hoge</h1></body></html>"); writer.flush(); } }
5. 実行する from Eclipse
実行→Run on Serverで起動します。
http://localhost:8080/test で画面が表示されます。
6. WARファイルを作成する
gradle buildコマンドを実行すればwarファイルも作成可能です。
MultipeerConnectivityで実現する近距離無線通信:序
ふと思い立って,掲題のMultipeerConnectivity.frameworkを触ってみました。
近距離でのLINE風チャットアプリなんかが簡単に作れそうで楽しいAPIです……!
今回はMultipeerConnectivity.frameworkについて情報を整理してみました。
1. MultipeerConnectivityことはじめ
MultipeerConnectivity.frameworkは,iOS7から使えるようになった近距離
無線通信を実現するためのフレームワークです。具体的には,Wi-FiとBlutoothを
使って,近くのiOSデバイスと通信するための基本機能をAPIで提供しています。
このフレームワークを使うと↓のようなアプリが作れます。
- トランシーバーアプリ
- チャットアプリ
- 会議資料共有アプリ
- ゲームの対戦機能
2. 機能のイメージ
近距離通信するデバイスですが,大きく「ホスト」「クライアント」の2種類
存在しており,それぞれで微妙に役割が異なっています。「ホスト」は,1つの
通信グループ(同じサービス名を持つ通信)で1人だけ存在します。「ホスト」以外の
接続ユーザーは全て「クライアント」です。
そして,クライアントは他のクライアントと直接通信できません。
クライアントが通信できるのは,ホスト1人だけ。そのため,接続ユーザー全員で
状況共有が必要な場合は,ホストが各クライアントに情報をブロードキャスト
する必要があります。
また,1つの通信グループは最大でも8人までしか同時接続できないので
無尽蔵に大規模なグループは作れない……という点にも注意が必要です。
MultipeerConnectivity.frameworkとは,言うなれば各接続ユーザに
「ホスト」「クライアント」の役割を与え,ユーザ間での通信を行うための
機能を提供するフレームワークといえます。
3. 各接続ユーザを識別する情報「Peer」
次にPeerという概念について紹介します。Peerとは,接続ユーザーを
区別するための識別情報です。「ホスト」「クライアント」に関わらず
全てのユーザーがユニークなPeerを保持しています。
MultipeerConnectivityの世界では,Peerを表現するためのクラスとして
MCPeerIDというクラスが用意されています。
4. 交信機能を提供する「Session」
続いて,ユーザ間のメッセージングを実現するSessionについて説明します。
Peerと同じくSessionも各ユーザが1つ持っています。そして,自分が保持する
Sessionを使って他のユーザと交信(データ送信,データ受信)を行います。
ちなみに,Sessionは内部属性として,前述のPeer,つまり誰がそのSession
の持ち主か?という情報と,サービス名という情報を持っています。サービス
名というのは,通信グループの識別名称と理解してください。Session同士が
交信を行うためには,サービス名が同じである,という前提条件があるので
注意してください。
MultipeerConnectivityの世界では,Sessionを表現するためのクラスとして
MCSessionというクラスが用意されています。
5. 存在を主張する「Advertiser」
ここまで,ユーザの識別情報を保持する「Peer」と,ユーザ間での交信を
担う「Session」について説明しました。では,実際のユーザ間の接続は
いったいどういう流れで行われるのでしょうか?そこには,「Advertiser」
という要素が絡みます。Advertiserとは,サービスの存在を主張する機能です。
ユーザ間で交信をするにしても,最初はまずサービスへの接続が必要です。
そのためには,他のユーザに対してホストが,サービスの存在を主張する
必要があります。その存在主張をAdvertiserを通して行うのです。
MultipeerConnectivityの世界では,MCAdvertiserAssistantクラスが
Advertiserの機能を提供しています。
6. 次回予告
今回は概念的な話で近距離通信を説明したので,次回は同じ話を実装レベル
で解説してみたいと思います。
今年個人的に注力したい技術
あけましておめでとうございます。
今年も月に最低1件の技術ネタ投稿を目標にやっていきたいです。
さて,今年の抱負...ではないですが,今年は未経験の以下の技術について
調査しながら力を付けていきたいです。
・node.js
・Golang
・データベース一般知識
去年初めて良かったことは継続し,反省すべきことは反省しで
今年も頑張っていきたいと思っています。
今後共よろしくお願いします。