人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

Javaに詳しい方教えて下さい JavaではExceptionが多く活用されますが なぜでしょうか?

C++の場合 Exceptionに使われる setjmp longjmp命令は他と比べて高コストのために エラー値を返しても十分な場合は エラー値を返すというのが基本設計かと思います。
Exceptionはエラーではなく例外である。という考え方。

他方 Javaの場合はエラー値で処理できる場合でもExceptionを使うことが多く見受けられます。

想像すると、これはVMという仕様上 Exceptionのコストが エラー値を返すとほぼ等価でVMが実装できるために
速度的なボトルネックにならない。という C++との違いなどがあるために生まれている事象でしょうか?

詳しい方お願いします。

●質問者: 心は萌え
●カテゴリ:コンピュータ
○ 状態 :終了
└ 回答数 : 4/4件

▽最新の回答へ

1 ● tdoi
●75ポイント

文化としか言いようがない気もしますが、敢えて理由付けするならばこんな感じかと思います。

まず、パフォーマンスに関して求められていることが違うかなと思います。

必ずしもそうではないですが、C/C++をつまりネイティブコードで実行させたいケースというのは、パフォーマンスを求めるからというのが1つの理由としてあります。その際に、少しでもコストがかかると感じているものは避けたいという気持ちがあるかと思います。

一方、Javaが遅いという意味ではなく、Javaの場合はそこまでのパフォーマンスを求められる時に採用されることは少ないと思っています。逆に、生産性を求められる際に採用されることが多い言語かと思います。
そういった事情であれば、実行時の、さらに、例外的な処理のパフォーマンスを多少犠牲にしても、コードの可読性をあげたいために、Exceptionを利用するのが一般的なのではないかと思います。


また、言語自体の歴史の問題もあるかと思います。
Javaは当初の言語仕様から例外機構が組み込まれていたはずです。また、その辺の入門書を手にとってもらっても例外処理はこう書きますってな感じで例外を多用していますし、Javaの標準ライブラリからして、例外を扱わずして使えない仕組みになっています。

一方、C/C++の場合は、Cで書かれたレガシーコードとの統合が必要であったり、Cで鍛え上げられた人が開発に関わっていたことも多く、ライブラリも例外を使わない仕組みも多くあります。確か、newも失敗時に例外をはくかどうかを設定でき、デフォルトは例外をはかずにNULLを返すんじゃなかったかなと思います。それらの流儀に合わせると、戻り値で成否を判定する方が一般的になった面もあるかと思います。
また、setjmp、longjmpによる制御を多用したコードは非常に可読性が低いので、その意識から敬遠されていたのもあるかと思います。


最後にリソース管理の言語的な仕組みも大きく影響していると思います。
Javaの場合は、例外もありますが、基本的にはメモリを初めとしたリソース管理は、GC任せなことが多いかと思います。また、例外を投げてもfinally節によるリソース解放が可能です。

一方、C++は基本的には自分でリソースを管理しなければいけませんし、finally的なことをするためには一工夫必要ですし、簡単にリソースリークを起こせます。また、リソースに余裕がないケースで利用されることが多いというのもあるかもしれません。


ざっと、僕が感じていることを挙げてみましたが、何かの参考になれば。


心は萌えさんのコメント
はい、C++の場合は 設定次第で newの失敗時はstd::bad_allocをthrowする可能性があります。 その割には・・・JITを初め C++と遜色のない速度と言い始める人もいたり、Javaは速度の事は気にしていないという事はないようなきもしているので聞いてみました。

tdoiさんのコメント
組み込み系の人を別とすれば、C/C++で例外が嫌いな人で、そこまでパフォーマンスを問題に例外を禁止にする人もいないんじゃないかと。それよりも、setjmp/longjmpによる制御構造の破綻や、それにともなうリソースリークが問題にされるかと。 この辺も文化というか、宗教ですが、関数の出口は1つじゃなきゃやだって人もいますから。 速度を気にしないまともなプログラマはいないと思います。 ただ、それはクリティカルなことに対してでしょう。トータルのパフォーマンスに影響する部分以外はそれほど気にしないでしょう。気にしてたら走るかどうか分からないJITや、いつ起きるか分からないGCを使うのは厳しいでしょう。 もちろん、C++erがそこまで完全に気にしているという意味じゃないですが。 ただ、全体の傾向として、そういうのはあると思います。 ようは適材適所です。 あと、答えておいてなんですが、Javaも使いますが、僕はC++erです。

心は萌えさんのコメント
えー 個人的な話ですが、トライアンドエラーが必要な仕事で テスト用のモジュールを30パターンぐらい用意する。動けばいい。というパターンの時に 論理的に使わない try catchをいちいち書かされて こんなに生産性の悪いw 言語は初めてという感じです。 実運用ならいいのでしょうが トライアンドエラー系には向かない言語ですね・・・

tdoiさんのコメント
状況がよく分かりませんが、言語特性を理解しないとどんな言語を使っても生産性は低いかと。 そのケースに適していたかは知りませんが。

心は萌えさんのコメント
おっしゃるとおり、Javaは生産性がと仰られたので おっしゃるとおり、適切な特性を選べば生産性が高くなるだと思ってコメントしました。 たとえば、動画の処理など速度を必要とする者に対してJavaを使えば生産性は悪くなりますし JavaはGCがある。Object志向やエラーに対する強力な警告がある。という言語特性があるだけで、Javaだから生産性が高い低いという事はないかと。

tdoiさんのコメント
既にあなたの中で答があり、それ以外のことに興味はないような気もしますが、せっかくなので、もう一言だけ。 あなたが書いたコードが非生産的だったからと言って、あなたが使った言語を非生産的というのは如何なものかと。あなたが書いたコードが非生産的だったのは、あなたが言語特性を利用したコーディングができなかったからなのか、あなたが従わざるおえなかった設計がまずかったのか、それは知りませんが、原因は言語特性ではなく、言語特性にあったコーディングができなかったことではないかと。 この辺りは、適材適所と曖昧な言い方をした僕もよくないのですが。 あなたがどれほどのコードを書ける人なのかは知りませんが、その辺の適当のプログラマを捕まえてきて開発をさせたとした時に、C/C++で開発させた場合と、Javaで開発させた場合と、出てきた成果物の安全性はどちらがまさる可能性が高いでしょうか?その成果物のメインテナンスをしていくとしたら、どちらがやりやすい可能性が高いでしょうか? それが生産性かと。 もちろん、ちゃんとC/C++のコードを書ける人を集めて、共通認識を持って開発をすれば、Javaに負けない生産性の高いコーディングも可能だと思います。

心は萌えさんのコメント
あー実運用ならいいのでしょうが トライアンドエラー系には向かない言語ですね ということで、実運用なら 生産性が高い と言ったはずですが 何か問題がありますでしょうか? 読みたいところだけ文章を読まれても困ります。 その成果物のメインテナンスをしていくとしたら、どちらがやりやすい可能性が高いでしょうか? については 高い生産性を持ったコーダーを雇えるなら C++でしょう。 そうでないなら 一般雇用だけでいくなら、Javaです。 ちなみに、生産性は 求められる要求条件に応じてとなりますので サービスあたりの処理量が膨大で メモリ管理まで自分でやらないとならない場合などになってくると GCの制御がしにくい という理由からJavaの方が生産性が低下します。 他方 単純なアプリや 銀行系サービスなどであるならJavaでしょう。 いずれにしろJavaにおけるGCのチューニング・これはサーバー屋の仕事かもしれませんが がボトルネックになるケースはたまに聞きますので サービスから見て一貫した場合 Javaの生産性がどこまでほんとうに高いか?というのは、測ってみないとなんともと思います。 文字通り 用途に応じて 言語を選ぶべきかと思い 言語を選んで 用途を選ぶべきではないと思いますが?

2 ● 犬猫ハーフ
●75ポイント

例外機構は「正常系から異常系に移る」為の仕組みの一つで、これを採用するとコードが簡潔になるから、というのが私の考えです。

例えば、HogeクラスのインスタンスメソッドにdoFoo()とdoBar()があり、どちらも非0の返り値はエラーコードだとします。そうすると、実際のコードはこんな感じになります。

if( hoge.doFoo() != 0 ) {

 // ここでエラー処理

}

if( hoge.doBar() != 0 ) {

 // ここでもエラー処理

}

これに対して例外でエラーを通知する場合はこうなります。

try {

 hoge.doFoo();
 hoge.doBar();

} catch( Exception ex ) {

 // ここでエラー処理

}// try{...}...

上と下のコードを見比べてもらえば一目瞭然ですが、下の方は正常系は正常系だけ、異常系は異常系だけで構成されていますので、非常に見やすいです。

逆に、上のコードでメソッド呼び出しが何十・何百もあると、正常系の処理内容をざっと目で見て追うだけでも大変です。


>個人的な話ですが、トライアンドエラーが必要な仕事で
>テスト用のモジュールを30パターンぐらい用意する。動けばいい。というパターンの時に
>論理的に使わない try catchをいちいち書かされて

そういう場合は、全メソッドに「throws Exception」と書いておけば良いだけではないでしょうか。
私は「動けばいい」場合はそうしています。


心は萌えさんのコメント
異常系をthrowするのは良いですが 準正常系までIFがthrowしているという事です。 メモリーが足りないは異常系だと思いますが 入力値が正常でない というのは ユーザーは誤入力をするものですから 準正常系かと思います。 というレベルです。別段throwを使うな!といってるわけではなくC++でもthrowがあるように必要な場合はいいんじゃないですか? 空のcatchを書くのも throws Exception と書くのも 手間的には大差ない気がしました。

犬猫ハーフさんのコメント
>異常系をthrowするのは良いですが 準正常系までIFがthrowしているという事です。 では仮に、その準正常系の返り値をチェックし忘れたプログラマがいて、その結果アプリが誤動作すると困った事になりませんか? そうならない様に、エラー等は返り値ではなく例外で通知するというのが、Javaの設計者の哲学なんでしょう。 もしその哲学が気に入らないというのであれば、それはあなたがJavaを選んだのが間違いだったという事です。 >入力値が正常でない というのは ユーザーは誤入力をするものですから 準正常系かと思います。 例えばjava.lang.Integer.parseInt()メソッドですと、「エラーを表す値を返す」というのは不可能ですよね。 0も1も-1も1234567も、全て「ユーザが入力するかもしれない」値ですから。 こういう場合は、例外を投げるのが一番確実な方法だと思います。 >空のcatchを書くのも throws Exception と書くのも 手間的には大差ない気がしました。 空のcatchを書くのは、例外発生時にそれに気付く可能性が減りますので非常に危険です。 あなたがコーディングもアプリの操作も絶対に間違えないなら空のcatchでもいいですが、そうでないならthrows Exceptionをお勧めします。

犬猫ハーフさんのコメント
一行書き忘れてたんで、追加します。 「では仮に、その準正常系の返り値をチェックし忘れたプログラマがいて、その結果アプリが誤動作すると困った事になりませんか?」 の前に 「準正常系の場合は返り値でエラーが起きた事を通知する事にします。」 を加えて読んで下さい。

3 ● taroe
●75ポイント

>速度的なボトルネックにならない。という C++との違いなどがあるために生まれている事象でしょうか?

C++はCをベースにオブジェクト指向を導入された実用的な言語だたため
エラーを戻り地などで戻すのも許容されている文化です。

Javaの場合は、完全にオブジェクト指向言語で、
オブジェクト指向言語の初期の時代に、Exceptionを積極的に使うべきという
文化が形成されました。


もちろん実行コストもあるわけですが、文化の違いです。

ただし、Javaにおいても何でもExceptionで処理するのは非効率だということが
多くの人たちが気づきだしました。

まず間違いだったのは
実行結果の状態を返すために、エラーを使うべきでなかったということです。

たとえば、入力エラーとかがあるとする場合に
これをエラーを返すと考えてExceptionを投げて処理するのは
微妙だという考えです。

入力に対して、どういう状態になったか?という状態を返すべきではないか?という
概念が導入されたわけです。


■エラーと例外
JAVAで言うと
Exceptionから派生したものと
Errorから派生したものは違います。

前者が例外で、後者がエラーです。


あなたの言葉の定義は、一般的ではないと思います。
確かに、C++の文化では、
戻り値にエラー値を返すときにエラーを返すといいます。


重要なのは、エラーにもレベルが言うことです。
たとえば、継続可能か、継続付加か
ユーザーレベルかシステムレベルかということです。

こういうのを区別して設計されていない関数やクラスは
非常に使いづらいものです。

現在においては実行コストよりもわかりやすさを、可読性を需要視します。
それでも、一律にExcetionを投げるべきだという設計はダメだと今はされています。


心は萌えさんのコメント
はい、おっしゃるとおりだと思います。 重度なシステムエラーならば、Exceptionは妥当で 一般的なエラーの場合 エラー値を返すが、実運用上は効率がよいかと思います。 ただ、私が言っているのではなく 使っているライブラリ(Java.XX)がそういう設計なので なんでだろう と思った次第です。

newtaさんのコメント
自分のシステムでそれらがチェック例外である必要がなければ RuntimeExceptionでExceptionをラップするUtilクラスを作るべきです。 必要が無いなら業務処理中のそこいらじゅうに書くべきではないと思います。 その辺は作りたいシステムにあわせて正しく構造を設計して作るかだと思います。 言語仕様じゃなくて使い方の問題。 ライブラリを遣うのではなく遣われているだけと言うことではないかなと。

4 ● a-kuma3
●75ポイント

# 一週間が短い X-(

† 何故、java はエラー処理を例外で行うことが多いか

ひとつは、java の出自によると思います。
c++ では無い新しい言語を作ろう、という動機で開発されたものですから、c++ のやり方を踏襲しなければならない理由は一つもありません。
ヒープの GC もそうですが、開発者が注意してプログラムを作らなければいけない、ということを嫌ったフシがあります。
java の場合は、RuntimeException から派生した例外じゃなければ、catch するか throws 句を書かないとコンパイルの時点でエラーになりますから、起こりうるエラーの考慮が漏れていた、という間違いが少なくなるだろうと。
それを実現するという形で、標準の API でも、そのような実装(例外を多用)をしたものが多いのだと思います。
java に入ってきた人たちは、標準の API の流儀にならって、自前のライブラリを作っていくのは自然な流れだと思います。

† 何故、c++ ではエラー処理に例外が使われることが少ないか

c++ に直面していた人たちは、c を使い倒してた人たちでした。
c の前には、Fortran や COBOL といった手続き型の言語で腕を磨いた人たちです。
仕事なんかでプログラムを組む際に、「はっきり言って、オブジェクト指向なんかどうでも良い。うごくものができれば良いのだ。今までだって、きちんと動くプログラムを組んでいた。」という論調は、とても多かったです。
はっきり言って、オブジェクト指向についていけて無かっただけなんですが、プロジェクトの方向性を決めるのは、その昔の人たちです。
c++ を better c として使おう、なんてのも、よく見ました。
また、資産としての c のライブラリもたくさんありましたし、そういう人たちが例外を導入する積極的な理由は無かったのだと思います。

例外という概念が理解できて無かったわけでは無いです。
現に、MFC なんかは、WIN32API の薄いラッパーみたいなメソッドは、戻り値でエラーを判定しなければいけませんが、例外として送出されるエラーもたくさんありました。
ライブラリがそのように実装されていれば、素直に使うことはできていたはずです。

† 例外処理に関するコスト

c++ をやってたときに、ちょっと気が付いたことがあります。
例外処理はコストが高いみたいな話はありますが、現実にどこでコストがかかっているかというと、例外を送出するところでは無く、スコープが変わるところでした。

 // (A1)
 try {
 // (A2)

 ...

 // (B1)
 } catch (...) {
 ...
 }
 // (B2)

例えば、上記のコードで、A1?A2 や B1?B2 のところで、妙に時間がかかることを経験しました。
例外を送出して無ければ、B1?B2 では何も動いていないはずなので、最初は (?_?) です。
ソース上は、コードを書いていませんが、コンパイラは、ここにコードを吐き出します。
c++ には、スタックに積まれるインスタンスがあるので、スコープが変わるたびに、後始末(デストラクタ呼び出しなど)と、その準備のようなコードが入ります。

# 今どきは、通用しない話かもしれません ^^;

java の場合には、インスタンスがヒープにしか無いので、どれだけ細かく try ? catch でくくっても、デストラクタ呼び出しにかかるコストが無いので、とても気軽に例外が使えるんだなあ、と、後で java をやったときに感じた覚えがあります。

† じゃあ、エラー処理をするうえで、戻り値での判定と、例外の補足のどちらが良いのか

とかいう話では無いと思うんですね。
大切なのは、正しく設計されたコードと、それによってもたらされるコードの可読性だと思います。
戻り値によるエラーの判定をするときにありがちなのは、エラーの内容をログに残すだけの単純な処理なのに、そちらの方のコードが多くなって、何をやってるのか分からなくなってしまっているコード。

 ret = processA(...);
 if (ret != SUCCESS) {
 message = ...;
 log(message);
 return ERROR_CODE_A;
 }

 ret = processB(...);
 if (ret != SUCCESS) {
 message = ...;
 log(message);
 return ERROR_CODE_B;
 }
 // というようなコードが延々と続く

しかも、惰性に任せて書いていると、エラーが起きていることはログに残っているものの、何が原因でエラーが起きているか、全く分からないログしか出てないという...

例外のオブジェクトが、エラーの情報を正しくカプセル化できているなら、こんな感じに書けることが多いです。

 try {
 processA(...);
 processB(...);
 ...
 } catch (Exception e) {
 log(e);
 }

例外の設計を間違うと、こんな感じのコードになってしまうので、例外を使うのが良い、というわけではない、ということを踏まえて質問されてるんでしょうね、きっと =)

 try {
 processA(...);
 } catch (ExceptionA e) {
 message = ...;
 log(message, e);
 throw new ExceptionA();
 }

 try {
 processB(...);
 } catch (ExceptionB e) {
 message = ...;
 log(message, e);
 throw new ExceptionB();
 }

 // というようなコードが延々と続く

うまくまとめて書けないのですけれど、やっぱり参加したくって。
ガチャガチャした回答で、ごめんなさい。

関連質問

●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ