ページ

2014/09/02

[java]自分で60点くらいの例外処理を考えよう (2)

どうエラーを返すか、の前に、もうちょっとチェック例外のことを考えたい。
今作ろうとしているのは、NfcAクラスに対するMifareUltralightクラスのようなもので、そのFeliCa Lite版だ。

  • close
  • connect
  • get
  • getMaxTransceiveLength
  • getTag
  • getTimeout
  • getType
  • isConnected
  • readPages
  • setTimeout
  • transceive
  • writePage

赤文字が、追加されたメソッド。
NfcAにあるgetAtqa()なんかがないのは、NfcAを継承してないから。

TagTechnology > BasicTagTechnology、という継承になってるんだけど、BasicTagTechnologyはpublicがついてないから、私から使うことができない(のよね? 勝手にandroid.nfc.techなんてパッケージ名付けてもだめだと思ってるが)。


私が追加したいのも、こういうやつで、FeliCa Liteのブロックにアクセスする処理だ。
迷っているのは、こういう内部のAPIを使って機能を実現するやつ。
transceive()を使うけど、それはIOExceptionを投げる。
じゃあ、使ったAPIもIOExceptionを投げた方がよいか、隠蔽した方がよいか。

MifareUltralightクラスは、ほぼそのままにしている。
transceive()の返すIOExceptionもあるが、checkConnected()の返すIllegalStateExceptionもそのまま。
実行時例外はスルーして上まで投げて、チェック例外はやった方がよいなら自分でチェックするし、そうじゃないと思ったらthrowsして上に投げる、という判断なのかな。

 

圧倒的に、Javaソースを眺めている経験値が足りないですわ-。
今はAndroidのNFCのソースしか見てないという、視野が非常に狭い状況だから、またどっかで混乱するんだろうな。
まあ、足場がないと混乱もできないから、以前よりはよくなったと前向きに考えよう。

[java]自分で60点くらいの例外処理を考えよう (1)

たぶん、数日考えたくらいでは、Javaの例外を私がどうさばくのかという方針は決まらないだろう。
とはいえ、これに時間をかけているわけにもいかないので、自分の判定で60点くらいの納得ができればよいことにしてしまいたい。

最初の書いておくと、昨日の段階で自作のクラスはたいがいがExceptionを返すように作り替えてしまった。
勢いでやったと思うんだけど、やりすぎなのでまた作り方を変えるつもり。
その作り方を、ここで書きながら考えていこう。


まず、チェックされる例外、と、チェックされない例外、というものがJavaにある。
チェック例外・非チェック例外、検査例外・非検査例外など、呼び名は色々あるようだ。
英語だと、checked exception / unchecked exception、のようだ。

メソッド名の後ろにthrowsをつけ、そこにずらずら例外を書くのが「チェック例外」だと勝手に思っていたのだが、そうではないとのこと。
http://www.ne.jp/asahi/hishidama/home/tech/java/exception.html#h_checked_exception

チェック例外は、RuntimeExceptionを継承していない例外で、メソッド外に投げたいならばthrowsをつけてないとできないとのこと。
「そんな話から書くんか!」と思われるかもしれないが、知らないのだから仕方がない。

ようやく、定義らしきところにたどり着いた。
みんな「使い分け」の方に力が入っていて、そもそも何よ?が出てこなかったのだ。
http://docs.oracle.com/javase/jp/7/api/java/lang/Exception.html
はい、さっき書いたのと同じことが書かれてた。
関連するリンクに、Errorというのがあった。
http://docs.oracle.com/javase/jp/7/api/java/lang/Error.html
ここら辺まで眺めて、ようやくEffective Java 項目58の「checked exception / runtime exception / error」という分類になっているかがわかったというところ。

  • 非チェック例外はRuntimeExceptionを継承したもの。
    そうじゃないものはチェック例外。
    異常だというのがエラー。
  • 非チェック例外=実行時例外=RuntimeExceptionを継承しててthrowsなしでも伝わっていく。
  • チェック例外=throws書いていないと伝わらないので、書かないならtry-catchで解決する。

これでよいのかな?


抽象的なのは疲れるので、今やっているところの話にしたい。

今使っているAPIは、こんな例外を投げてくるようだ。

  • java.io.IOException
  • android.os.RemoteException
  • android.nfc.FormatException

継承図を見ると、どれもRuntimeExceptionが入っていないので、チェック例外ということになる。

例えば、NfcFクラスだと、以下が投げてくる。

  • close()
    呼ぶと、他でブロック状態になっていた要求がIOExceptionを投げられてキャンセルされる。
  • connect()
    既に他でconnectされていたら投げる
  • transceive()
    I/O失敗か、キャンセルされたら投げる

close()はthrows対象になっているのは、close()処理がブロックされているときに他スレッドでclose()されたときに投げるからかな?

上記の例外は、けっこうどうしようもない感じだ。
自分のあずかり知らぬところで処理がキャンセルされたり、transceive()だと・・・何だろう?

 

NfcFクラスでは出てこないけど、その基底になるBasicTagTechnology
これが、けっこうRemoteExceptionを返している。
NfcFではこれが現れないので、NfcF内で吸収していることになる。

RemoteExceptionは、AndroidのBinder関係の例外基底クラスのようだ(その割りに、継承してる人がほとんどいない)。
でも、これはこれでどうしようもない気配が漂っている。

BasicTagTechnologyなんか見てると、transceive()でRemoteExceptionが発生するとIOExceptionに差し替えているし、close()なんかは発生してもログを出すだけだったりする。
もうどうしようもないから、普通の流れで変なことをせずに終わろうとしている、という感じか。


そんなわけで、まず1つの基準。
RemoteExceptionはどうしようもないから、上に返さないでエラーにしてあげよう。

じゃあIOExceptionはどうするか。
これも、なるべく隠してあげる方がよいような気がしている。
今作ろうとしているのはNfcFっぽいクラスなんだけど、TagTechnologyを継承してthrowsがついているところは、そのままにして、そうじゃないで自分で作ったメソッドなんかは、なるべくエラー値で返してあげようか。

そうなると、どうエラーを返すか、とか、引数エラーはどうするか、とかが出てくる。
今日は力尽きたので、次回にしよう。

2014/09/01

[java]戻り値がよいか例外がよいか・・・

Cで作っていたライブラリを、Androidで動かそうと移植している。
まあ、NFC関係ですわ。
そうなると、Cでは戻り値でエラーを返していたところが、Androidでは例外で返すようになっているところがある。
そんなときは、例外をcatchしてエラー値に変換していた。

そしてエラーの戻り値としてenumを定義し、catchして変換をするようなことを書き始めて、ふっと思った。
「このまま例外で処理したらいいんじゃないの?」と。

例外を返すようにして、呼び出しの一番上でcatchするようにすると、ソースの見栄えが非常によろしい。

ret = proc1();
if (!ret) {
  return ERR1;
}
ret = proc2();
if (!ret) {
  return ERR2;
}

とか、

ret = proc1();
if (ret) {
  ret = proc2();
}
if (ret) {
  ret = ....

とかが、

try {
  proc1();
  proc2();
  proc3();
  ...
}
catch(xxxException e) {
  xxx;
}
catch(yyyException e) {
  yyy;
}

みたいになって、ロジックとエラー処理を別々に書ける。
まあ、エラーの種類によって動作を変えることもあるだろうけど、見通しはよくなると言うのはわかる。

 

そうなってくると、エラーが起きたときに、戻り値で返すのか例外で返すのか、がだんだんわからなくなってきた。
C++では、例外のコストがけっこう高いのと、組込みのC++は例外が外されてることもあったので、実はそんなに使ったことがない。
Cでは、gotoとラベルを使って、(ソースの見た目上は)似たようなしくみを実現しているのを見かけたことはあるが、私自身は戻り値で知らせる派であった。

ただ、例えば引数がNULLだったり範囲外だったりしたら、お仕事ではチェックしてエラーを返したりするけど、自分でやるときはassert()で落としたりしている。
これがJavaだと、NullPointerExceptionとかIllegalArgumentExceptionがあるので、それを返すだけになっている。
assert()だとNDEBUGで無効になるし、なによりアプリが落ちる以外の選択肢しかないのが厳しい。
「ダメなんだけど、何も死ぬことはない」という状態だ。


この辺については、自分の中で整理をしないといかんですな。

2014/08/31

[android]Log.d()はそのまま出る仕様なんだ!

いやあ、知らないって事は恐ろしいですな。

AndroidManifest.xmlに、確かdebaggableとかいう設定があったと思う。
だから私は「これをfalseにすれば、デバッグ出力のLog.d()は出力されないんだろう」と何の根拠もなく思い込んでいた。

偶然、「ログをリリース時にオフにする方法」みたいな記事があったので見てみると、全然そんな仕様じゃないことがわかった。
debaggableじゃなくて他の設定だろうと思って調べたけど、どうもそうではない模様。
つまり、Log.d()はそのまま出るんだ!


Javaのところは、ここ。
最後はprintln_native()が呼ばれている。
https://android.googlesource.com/platform/frameworks/base/+/android-4.3_r2.1/core/java/android/util/Log.java

 

Nativeは、これでよいのかな。
https://github.com/android/platform_frameworks_base/blob/master/core/jni/android_util_Log.cpp

__android_log_buf_write()が呼ばれるようだ。
もう、誰にも止められない・・・

JNIなんだな、ログって。
けっこう重たい動きになるんだ。


なんか、一括して、しかもバイナリレベルで呼び出されないくらいのことをしてくれんかと思って探すと、ProGuardで消すというのが一番多かった。

proguard-rules.proに、

-assumenosideeffects class android.util.Log {
    public static int v(...);
    public static int d(...);
    public static int i(...);
    public static int w(...);
    public static int e(...);
    public static int wtf(...);
}

みたいなのを書いて、build.gradleに、

buildTypes {
    release {
        runProguard true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}

みたいな変更をして、Signed APKを作るようだ。 

[android]Android Studio初心者 (2)

"@string/foobar"みたいな記述をしている箇所を、自動的に該当する文言で表示してくれる。
便利と言えば便利なのだが、Android勉強中の私からすると「あれ、strings.xmlにまだ入れてなかったっけ」と思って、カーソルを当ててbackspaceで削除し始めてしまうことがしばしば。
慣れるまでは、入力したものをそのまま表示してほしい。

Settingsの、IDE Settingsの、Editor > Code Foldingの中に「Android String References」があるのでチェックを外すとよい。
できれば、動的に変更できるとよいのだが・・・。


昨日、ArrayAdapterのextendsか何かを書いていたときだったと思うけど、表示が「<~>」みたいになってた。
書き間違えたんかねー、とカーソルを当ててbackspaceで削除し始めて、それが省略表記だったことに気付く、ということがしばしば。
今の私は、自動的に省略してくれることのありがたみが、まだわかる段階に来ていないのだよ・・・。

たぶんこれも、上記のCode Foldingのところにあるんじゃなかろうか。
でも、どれかよくわからないので、Importのとこだけチェックして、残りは外してしまった。
いつの日か、このチェックをデフォルトに戻せる日が来ることを信じて・・・。