ひしだまの変更履歴

ひしだまHPの更新履歴。
主にTRPGリプレイの元ネタ集、プログラミング技術メモと自作ソフト、好きなゲームや音楽です。

僕が考えた最強でもないJavaじゃんけんプログラム(後編)

2014-12-27 13:49:37 | PG(Java)

よこなさんが作ったじゃんけんプログラムの話の続きです。(→前編
同様のじゃんけんプログラムを自分でも書いてみました。

なお、メソッド名と乱数についてはさくらばさんのじゃんけんプログラムを参考にさせていただきました。

プログラムを3つ公開しますが、作っていった過程を示している感じです。

  1. じゃんけん1(とりあえず出来た)
  2. じゃんけん2(メソッド分割した)
  3. じゃんけん3(キー定義を統一した)

自分のプログラムのポイントは、じゃんけんの手をenumにし、勝敗の判定メソッドもenumに持たせたところです。
(compareToで比較できるようにしようかとも思ったけれどもw、推移律(a<bかつb<cならa<c)を満たさなくなるので駄目な気がした))


1つ目のプログラムでは、loopメソッドの中に全処理をべたーっと書いています(苦笑)
これではテストのしようもないので(元々コンソール入力のあるプログラムのテストを書くのは面倒ですが)、せめて判定部分だけでもメソッド分割を…と思って出来たのが2つ目です。
入出力も別メソッドに切り出せたので、loopメソッドはとてもシンプルになったと思います。
ただし、loopメソッド内に収まっていたローカル変数がフィールドになってしまいましたが^^; 

あ、ちなみに、OptionalのifPresentメソッドを使って判定している箇所があります。
「ifPresentを使ったら敗北だ」という人に、この場合どうすればいいかぜひ教えていただきたく(笑)


3つ目のプログラムは、入力するキーの文字列もenumに持たせました。
2つ目までは、入力するキーの情報があちこちに散らばっていたので、例えばキーをa,b,cに変更しようと思ったら大変です。

キーのMapを作ったりする箇所でStreamを使いました。こういうのはほんと便利です(笑) (同じループを何回も行う富豪的プログラミングですが…)
ただ、やっぱりメソッド参照を使うと分かりにくい気はしますねぇ。慣れれば問題ないかなぁ。

ちなみに、enumにキーの情報を持たせたけれども、それが良いかというと、実は悩ましい。
例えばEclipseのように実行中にキーを設定できるようにするなら、enumに持たせるのはありえないです。


なお、privateでよさそうなメソッドがほとんどprotectedになっているのは、趣味です(爆)
フレームワークやユーティリティーと呼ばれるものがガチガチにprivateで作られていて拡張できないことに日頃からうんざりしているので、自分で作るものは基本的にprotectedにします!(きっぱり)
ちなみに、privateメソッドにしていたとしたら、privateメソッドのテストがしたい問題が再発ですw

あー、あと、Randomのフィールドがクラスの途中にあります。
標準的なコーディング規約では、クラスの先頭にフィールドがあり、その後はメソッド定義が続くことになります。
が、個人的には、あるメソッドの中でしか使わないようなものは、このように使うメソッドの直前に置いたりします。
(Javaでは無理だけど、メソッドの中で永続的な変数を定義できると良かった)

(getComputerHandメソッドのテストを行うことを考えたら、randomフィールドも差し替えられるようにパッケージプライベートにしておいた方が良かったかも?)

4つ目は作っていませんが、PlayerクラスとComputerクラスを作って入力方法をそこに隠蔽する形には出来そうですね。 


そういえば、さくらばさんのプログラムでSystem.console()というのが使われていて、初めて知りました。(Eclipse上で実行したらnullが返ってきたけど(爆) コマンドプロンプトで実行したら大丈夫だった)
一点気になったのが、コンストラクターで全部処理していることですね。JavaFXのサンプルプログラムなんかでもたまに見かける方法ですが、個人的には不安です。
コンストラクターの初期化処理が終わっていない状態がずっと続くことになるわけですが、(JavaVMあるいはJava仕様的に?)大丈夫なのでしょうか。 

それにしても、よこなさんのじゃんけんプログラム、とても良い題材でした。 

コメント
  • Twitterでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

僕が考えた最強でもないJavaじゃんけんプログラム(前編)

2014-12-27 12:44:15 | PG(Java)

しょぼちむさんと並んで最近Twitter上でよく見かけるよこなさんが、『何も作ったことない若者がじゃんけん作ったはなし』というブログで、じゃんけんのプログラムをJavaで作っていました。
初めて1からJavaプログラムを書いたようで、初めてでこれだけ出来るのはすごいと思います。
というのと、初めての人が書くとこうなるんだ!(自分ではこういう発想は出来ないぞ)という意味で、すごく面白いプログラムです。
自分が書いたプログラムを公開するのもマサカリを受け止めると書くのもすごく勇気がいるので、素晴らしいの一言に尽きます。

という訳で、マサカリとかコードレビューとかではないですが、自分が気になったポイントを少し挙げてみたいと思います。


14~16行目、定数定義

Javaの場合、定数(固定値)を定義する場合は、static finalにすることが多いです。

ただ、定数→変数という順序で定義されているのは良いと思います。


21~22行目、フィールド定義

可視性(privateとか)が何も付いていないのが気になります。
クラス内で何も付いていない場合はパッケージプライベートであり、自分の場合、パッケージプライベートにするのは「本来はprivateだけれども内部クラスあるいはテストクラスからアクセスする」という場合だけです。
今回の場合だと、テストクラスからこれらにアクセスできる必要性は無さそうな気がします。 

あと、resultsという変数名だと、じゃんけんした結果を保持するような感じがします。
(以降、メソッド名や変数名については言及しません。「じゃあどういう名前にすべきか」という答えに自信が無いので(爆))

BufferedReaderのインスタンス生成をフィールドの初期化で行っているのもちょっと違和感がありました。
仮にファイルから生成するような場合だと、インスタンス生成過程でFileNotFound等の例外が発生する可能性があります。つまりIOExceptionを処理する必要があるのですが、これはコンストラクターにthrowsを書くことになります。たぶん知らないとハマりますので、自分はそういう処理ではフィールドでインスタンス生成しません。

ただ、このBufferedReaderのクローズをしてないのは良いですね。
普通はBufferedReaderインスタンスを生成するとクローズしないといけないのですが、今回はSystem.inを使っているので、これをクローズするとそれ以降でコンソール入力できなくなってしまいます。
System.inという特殊ケース専用の話ですが。


31行目、decideShapeメソッド

乱数を使っているので、コンピューター側の手を返すメソッドですね。
返している値が1~3のStringなので、じゃんけんの手をそういうIDで管理しているのかと思いました。
(SIerだと、コードをStringで扱うのもよくやるので^^;) 

で、じゃんけんの手のように有限の場合は、IDはenumで定義するのが常套手段だと思います。


36行目、transferNumメソッド

switchの変数にStringを使っているのが偉い(笑)
JDK1.7から入った機能なので、昔からJavaをやっている人なら逆にやらない(知らない)可能性が高いです。
(ちなみに今回は関係ないですが、switchに渡す値がnullだとNullPointerExceptionが起きるので注意が必要です) 

ここで返している値はIDに相当する使い方をしているので、クラスの先頭で定義しているROCK, PAPER, SCISSORSという定数を返すようにすべきでしょう。

また、引数が仕様の範囲外の値だった場合にIllegalArgumentExceptionを投げるのは一般的ではありますが、この例外を投げなくてはいけない状況というのは、バグがある時です。(値を渡す側が悪い、という場合に使う)
今回のようにユーザーの入力値の精査(バリデーション)を行う場合は、IllegalArgumentExceptionは相応しくないと思います。 

(なお、IllegalArgumentExceptionを使う場合は、例外メッセージ内に入力値を含めるべきだと思います。そうしておけば、例外メッセージを見ただけでどんな値を入れたのか分かります。
(ただし、業務アプリであれば、ユーザー名やパスワード等を出力してしまうとログに残るので、出していいかどうかはポリシー次第かなー))

で、さっきのdecideShapeは1~3の文字列を返し、transferNumでそれをさらにグー・チョキ・パーというIDに変換しているので、余計なステップが入っているように感じます。decideShapeが直接IDを返せばいいのに、と。
まぁ、decideShapeの仕様が「(人間がキー入力するのと同じ感じで)コンピューターのキー入力結果を返す」のであれば、これでもいいのかもしれません。


49行目、isTiedメソッド

変数を使った文言(メッセージ)をコンソールに出力したい場合、個人的にはprintfを使う方が好みです。

System.out.println("あなたは"+ formedShape +"、わたしも"+ selectedShape +"。あいこ!もう1回!");

System.out.printf("あなたは%s、わたしも%s。あいこ!もう1回!%n", formedShape, selectedShape);

もっと言うと、人間に見せるためのメッセージはMessageFormatを使っておくと、メッセージであることが明確になります。(多言語対応もしやすいw)

(もっと言うと、Javaには文字列内に値を埋め込む構文が無いからなぁ)


58行目、printResultメソッド

throws NullPointerExceptionが書かれていますが、普通はNullPointerException(というかRuntimeExceptionのサブクラス)はthrowsには書きません。
特にNullPointerException(略称NPE)はシステムが出力するものなので、基本的には自分で扱わないようにすべきです。(NPEを投げたくても、別の例外(業務アプリの仕様で定められた例外)にすべき) 


70行目、matchメソッド

Javaの場合、ローカル変数をメソッドの先頭で宣言だけすることは(普通のコーディング規約では)しません。
使う箇所で定義します。
ってゆーか、inputTextはそうなってるじゃんw

BufferedReaderのreadLineメソッドは、EOF(end of file、入力の終了)になるとnullを返します。
したがって、nullチェックが必須です。
コンソール入力の場合でもEOFは起きます。WindowsだとCtrl+ZでEOFになります。UNIXだとCtrl+DでEOFになると思います。

それから、matchメソッド内のメソッド呼び出しのレベルに違和感がありました。
引き分けかどうかの判定はしているのに、勝ち負けの判定が見当たらないんです。
引き分けでなかったらいきなり結果表示(printResult)を呼んでいるので、あれ?となりました。 

最後に、matchメソッドはじゃんけんを繰り返し行う場合のループも兼ねているようですが、ループのさせ方に感激しました!
再帰呼び出しを使ってループさせる方法は、関数型プログラミングだとこういう例がよく出てきますが、自分は(知識では知っているけれども)まず思い付きませんorz
ただ、残念なことに、Javaでは末尾再帰最適化は行われないのです。つまり、延々と繰り返しじゃんけんを行っていると、どこかでスタックオーバーフローが発生してしまうでしょう。
(そうでなくとも、途中で例外が発生したら、スタックトレースが酷い事になります^^;) 


105行目、mainメソッド

mainメソッドの引数が可変長引数で定義されているのが良いですね。通ですね(笑)
(JDK1.5から書けるようになった方法なのです)

例外のマルチキャッチを使っているのもいいですね。(JDK1.7から書ける方法)
ただし、メッセージだけ出して、スタックトレースを出していないのは駄目です。
一番単純にはe.printStackTrace()でスタックトレースを表示できます。

って、よく見たらNullPointerExceptionをキャッチしているんだ^^;
NPEは普通はキャッチしなくていいと思います。(個人的には、NPEが発生するのは実行を続けるのが不可能なバグだと思うので、mainの外まで伝播させてしまってよいと考えます)

また、IllegalArgumentExceptionをキャッチしていますが。
これはtransferNumで発生したものを処理する想定のようですが、キャッチする場所が良くないですね。
transferNumの呼び出し箇所でキャッチすべきです。
mainでキャッチすると、そのままプログラム終了ですよね^^; キーの再入力に戻る方がいいでしょう。
(あと、もし仮にdecideShape(コンピューターの手を返す)が1~3以外を返したらtransferNumでIllegalArgumentExceptionが発生しますが、今のメッセージだと、それも人間が入力した手のように誤解しちゃいますよね) 

そういえば、mainメソッドをクラスの先頭に書くか末尾に書くかはJava界でも定まっていないような気がしますが(決まってたら教えてちょ)、それ以外のメソッドに関しては、並び順が概ね標準化されています。
aメソッドがbメソッドを呼び出しているとき、a→bの順に定義します。(aのすぐ下にbを書く)
C言語だと、先に宣言してある関数しか呼べない為、b→aの順に書くことも多いですが。(aの前にbを書く)


さて、よこなさんのプログラムに触発されて、さくらばさんもじゃんけんプログラムを書いています(笑)

自分も、ちょっと書いてみました。(後編へ続く)

コメント
  • Twitterでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

AsakusaFW本体のビルド方法

2014-12-24 00:07:56 | PG(分散処理)

Asakusa Framework Advent Calendar 2014の24日目です。

今日はAsakusaFW本体のビルド方法を紹介します。

Asakusaアプリケーションの開発方法(ビルド方法)は、以前はMavenを使っていましたが、今はGradleになっています。
が、さすがにAsakusaFW本体はMavenのまま変わっていません。
普通のMavenの使用方法と同じです。 

ソースはGitHubで公開されているので、ダウンロードも普通にGitHubからフォークしてクローンするだけです。

以上、なんてお手軽(笑)


某Sparkのコントリビューター(SparkMeetupでSpark SQLのCatalystを説明していた人)なら「これでもうコントリビュートできますよね!」と言うところですが、自分はそこまでは言いません(笑) 

コメント
  • Twitterでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

DMDL EditorXの2014年の新機能

2014-12-23 13:27:23 | PG(分散処理)

Asakusa Framework Advent Calendar 2014の23日目です。

今日は、DMDL EditorXで今年作った機能の一部を紹介します。細かいのを含めると多すぎなので、大雑把に(笑)


データモデルの階層ビューは2月に作った機能です。

階層ビューはデータモデルを参照している箇所を探してツリー状に表示する機能です。(Javaのクラス階層と同様)

また、アウトラインビューはDMDL上で定義されているプロパティーだけしか表示しませんが、階層ビューはそのデータモデルに含まれる全プロパティーを表示するので、合成したモデルのプロパティーを確認することにも使えます。


「テストデータのExcelファイルを作る機能」に関しては今年のアドベントカレンダーの20日目でちょっと触れましたが、「ジョブフローやフローパートのテストクラスを作る機能」はゴールデンウィークの頃に作っていたみたいです。

演算子メソッドの雛形を生成するウィザードとか@Keyのgroup,orderの入力補完とかもその頃みたいですね。

もう結構経つんだなぁ。


あと、いつ作ったか覚えてないですが、去年のアドベントカレンダーでは未対応と書いていた@namespaceや@auto_projectionも対応済みです。


一番最近作った機能が、右クリックで開くコンテキストメニューの「Asakusa DSL References」です。(今日コミットしたw)

これは、データモデルやプロパティーがAsakusa DSL(Javaソース)のどこで使われているかを検索するものです。
dmdlファイルやJavaソース上のデータモデルやプロパティーに相当する箇所から使います。 

また、Operatorクラスで定義されている演算子メソッドで使うと、フロークラスで使われている箇所を検索します。

(ちょっと前までこの2つは「DataModel References」と「Operator References」という別々のメニューでしたが、検索元がかぶらないので(片やデータモデル・プロパティー、片や演算子メソッドなので)、メニューを統合してしまいました)

これけっこう便利だと思います(笑)


まだ改善していきたい点は色々あるのですが、今一番大きいのは、テストデータのExcelファイルの変更機能です。
入出力ポートの増減やデータモデルのプロパティーの増減に応じてExcelファイルを変更したいのですが、Apache POIのセルのコピー機能が貧弱なので、簡単に出来ません(苦笑)
(セル内の計算式の扱いが面倒orz これさえ無ければそんなに大変ではないはず)


それにしても、エディターの機能は言葉で説明しても分かりにくいので、どこかでデモンストレーションできるといいかもしれませんねぇ。

コメント
  • Twitterでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

OperatorのコーディングのTips

2014-12-22 02:42:09 | PG(分散処理)

Asakusa Framework Advent Calendar 2014の22日目です。

今日はOperator DSLをコーディングする際の注意点を2つ紹介します。


まずは、データモデルオブジェクトの使い回しについて。

ConvertExtractCoGroup等では、入力とは異なるデータモデルを新たに出力することが出来ます。
そこで使用する出力用のデータモデルオブジェクトは、Operatorクラスのフィールドに定義しておいて使い回すことが出来ます。

ただし、一旦出力する(Resultのaddに渡す、あるいはConvertでreturnする)と、内容が破壊されます。(厳密には「破壊されることがある」というだけですが、必ず破壊されるものと思っておいた方が無難です)
そのため、データモデルオブジェクトをフィールドに保持することは出来るのですが、使い始める箇所で毎回初期化(resetメソッドを呼び出すか、copyFromで別のオブジェクトから全コピー)するべきです。


2つ目は、Optionクラスを退避させる方法について。

データモデルのプロパティーの値を一時変数に退避させ、そのプロパティーを更新し、退避した値を別に使いたい、というような場合があると思います。
しかしAsakusaFWのOptionクラスは可変オブジェクトなので、退避をOptionクラスで行うと、プロパティーを更新したときに退避したつもりのOptionの内容も一緒に変わってしまいます。
Java8のOptionalScalaのOptionは不変オブジェクト、つまり一旦入れた内容を変更することが出来ないので、こういう問題は起きません)
(AsakusaFWのOptionはインスタンスを使い回すことでインスタンス生成コストを抑えるという考えなのだと思います。それなりの大量データを相手にするので)


注意していないと、なんとなく出来そうに見えるのですが、実際にやるとハマりますorz 

コメント
  • Twitterでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする