2019年12月08日

Visual Studio CodeでParaya MicroのWebアプリケーションを作る準備をする

☆☆☆はじめに☆☆☆

Java AdventCalendar 2019 8日目の記事だよ。

☆☆☆この記事のゴール☆☆☆

この記事では、Visual Studio Codeで、
Payara Microを使うWebアプリケーションを快適に作る環境を紹介することにしたよ。
ほら、なんか、Jakarta EE 8?って言うのが出たみたいだし。

というわけで、使うのはこの辺。

・Visual Studio Code(以降、VSCode)
・Java11
・Payara Micro
・Gralde

Windows10の環境で作るけど、
後で出てくるrobocopyコマンドをrsyncで置き換えれば、
他の環境でも動くんじゃないかなぁ。

あ、作った結果は、ここ(GitHub)にあるよ。

☆☆☆目標☆☆☆

で、まず、快適ってなんだろう?
ってことで、今回達成したい目標を上げておくね。
目標1.Gradleでビルドできること。
目標2.Gradleから実行できること。
目標3.VSCodeでビルドできること。
目標4.VSCodeから実行・デバッグできること。
目標5.VSCodeで起動したままreloadできること。
目標6.VSCodeで上の作業が楽にできること。

ほら、ソース書き換えて、すぐにコンパイルできて、
reloadできないとやってられないでしょ?
そこまではやらないとね!

☆☆☆本編☆☆☆

☆☆手順〜概要〜☆☆

さて、最終的なものもあるし、
VSCodeにJava11とJavaの拡張機能の設定まで終わっているって言う前提で、
記事としては細かい設定付近がメインに書いていくよ。それ以外はスルーで。

とはいえ、目標達成のための基本的な手順はこんなかんじ。
手順1.VSCodeのワークスペースを作ってGitリポジトリを初期化する。
手順2.Gradleの基本的な設定をする(目標1,2、3)。
手順3.GradleにVSCodeでのWebアプリ用の追加設定をする。
手順4.VSCodeのWebアプリ起動用の設定をする(目標4)。
手順5.VSCodeのWebアプリ更新用の設定をする(目標5)。
手順6.快適にするための設定をする(目標6)。

うん!簡単!って言うか、まぁ、ただの設定だしね。
GitHubのコミットは大隊この手順でコミットしてあるから参考にしてね。

☆☆手順1.VSCodeのワークスペースを作ってGitリポジトリを初期化する☆☆

ここで気をつけるのは、
.code-workspaceのファイルをプロジェクトのファイルと同じディレクトリに置くことかな。

RedHatの提供しているJavaの拡張機能のせいだと思うけど、
VSCodeのマルチワークスペースにうまく対応していないみたいで、
階層構造に制限があるみたいなんだよね。

実際、Javaのプロジェクト複数同じVSCodeのWindowで開くとうまく動かなくなったりするし。
あとは、.gitingnoreに、諸々の自動生成されるファイルを指定すれば完了!
この辺かな。
・Gradleの出力先のbuildディレクトリ。
・VSCodeの出力先のbinディレクトリ。
・Javaの拡張機能が自動生成する.project、.classpath、.settings。
・Gradleの本体の.gradleとローカル設定のgradle.properties。

☆☆手順2.Gradleの基本的な設定をする(目標1,2、3)☆☆

さて、実際に、Graldeの設定ファイルを置くよ。
まだ中身はGradle用のを書いているだけだからふつーだから、特筆するようなことは無いよ。
とりあえず、こんなかんじ。

plugins {
id "java";
id "war";
}

ext.moduleName = project.name;

repositories {
jcenter();
}

dependencies {
// use Payara Micro.
providedRuntime group:"fish.payara.extras", name:"payara-micro", version:"5.194";

// use JUnit test framework.
testImplementation "org.hamcrest:hamcrest:2.2";
testImplementation "org.junit.jupiter:junit-jupiter-api:5.5.2";
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.5.2";

}

tasks.withType(JavaCompile) {
options.encoding "UTF-8";
options.compilerArgs << "-parameters";
}

test {
useJUnitPlatform();
}

war {
baseName moduleName;
manifest {
attributes (
"Automatic-Module-Name": moduleName
);
}
}

javadoc {
source = sourceSets.main.allJava;
options.charSet "UTF-8";
options.encoding "UTF-8";
options.addStringOption("Xdoclint:none", "-quiet");
}
task javadocJar(type: Jar) {
baseName war.baseName;
classifier "javadoc";
from javadoc.destinationDir;
}
javadocJar.dependsOn(tasks.javadoc);
artifacts {
archives javadocJar;
}

task start(type: JavaExec) {
main = "-jar";
args = [configurations.providedRuntime.find { it.name.contains("payara-micro-") }.absolutePath, "--deploy", "\"${buildDir}/libs/${moduleName}.war\""];
}
start.dependsOn(tasks.war);

Gradleの設定を一通り置いたら、
『./gradlew build』とか、『./graldew start』とかで、
ビルドと実行の確認ができるはず!

あと、code-workspaceのsettingsに、

		"java.configuration.updateBuildConfiguration": "automatic"

を追加することをおすすめするよ。
VSCodeで、build.gradleを変更するたびに右下に更新する?って聞かれるから、
自動反映するならこの設定だね。別に聞かれて、Alwaysって選んでも同じ設定入るけど。

これで、.classpathが作られれば、VSCodeで、補完、コンパイルあたりができるようになるから、
目標1と目標2と目標3は達成だね!

☆☆手順3.GradleにVSCodeでのWebアプリ用の追加設定をする☆☆

さて、ここからが本番だよね。どうやって統合していくのかってところ。
ポイントになるのは、この辺。

・VSCodeのJava拡張がWebアプリに対応していない。
 →Graldeのプラグインで設定されるprovided〜の設定がimplmentionとかruntime扱いになる。
  →VSCodeで実行するには、ライブラリを明示的に指定しないとダメ。
 →webappが出力先にコピーされない。
・build.gradleでライブラリを追加とかしたときには、update projectしないと補完機能に反映されない。
 →でも、反映するときには、.classpathが上書きされて出力先がbin/mainに固定されてしまう。
  →でも、クラスファイルは『デプロイ先の』WEB-INF/classesに無いとクラスローダーに『正しく』読み込まれない。
   ※そうしなくても、一見動くけど、CDIとか(Java EE Security APIとかJerseyの実装とか)さわり始めると悲惨なことになる。
・JavaのDebugプラグインがWebアプリに対応していない。
 →Gradleのプラグインで設定されるprovided〜の設定がimplmentionとかruntime扱いになる。
  →勝手に実行時のクラスパスに追加される。
・JavaのDebugプラグインで起動時に指定するmainClassはclasspathRuntime上に存在しないと起動できない。
 →Gradleみたいに、mainClassを『-jar』とか指定できない。
・GradleでDLされたライブラリのパスはとてもわかりにくい。
 →明確なパスが得られないと困る。

このパズルを解けばok!

このために、Gradleさんには、実行時に必要なライブラリをコピーしてもらうことにするの。
copyRuntimeタスクとcopyLibrariesタスクを用意するよ。

copyRuntimeは、payara-microと、他に必要なものがあればって感じ。
DB接続用のjarとかはWEB-INF/libの方に入れてってどこかに書いてあった気がするから、
payara-micro-*.jarくらいな気がするけどね。だから、こんなかんじ。

task copyRuntime(type: Copy) {
into "${buildDir}/runtime/";
from configurations.providedRuntime - configurations.providedCompile;
}

copyLibrariesは、warを作るときに、warの中に入るライブラリを用意するよ。
implementationとruntimeが対象かな。と言っても、直接は取り出せないから、こんなかんじで……。

def libraries = configurations.runtimeClasspath - configurations.providedRuntime;
task copyLibraries(type: Copy) {
into "${buildDir}/libraries/";
from libraries;
}

で、
Javaのデバッグプラグインがクラスパスの自動生成をするのを抑制しなきゃいけない

上でコピーしたものをクラスパスに入れないといけないんだけど、
Javaのデバッグプラグインだと、ワイルドカードが使えないんだよね。
だから、ファイルに設定を書き出して、それをクラスパスに指定することで、両方の問題を解決するよ。
そうすると、タスクはこんなかんじ。

task createArgfile {
def argfile = file("${buildDir}/argfile/argfile.txt");
def runtimeDirectory = file("${buildDir}/runtime/");
def librariesDirectory = file("${buildDir}/libraries/");
runtimeDirectory.mkdirs();
inputs.dir(runtimeDirectory);
if(libraries.isEmpty() == false) {
librariesDirectory.mkdirs();
inputs.dir(librariesDirectory);
}
outputs.files(argfile);
doLast {
argfile.parentFile.mkdirs();
argfile.write("\"" + (runtimeDirectory.listFiles() + librariesDirectory.listFiles()).join(";").replaceAll("\\\\", "\\\\\\\\") + "\"", "UTF-8");
}
}

これは、コピーの後に実施しなとダメだから依存関係をつけるよ。

createArgfile.dependsOn(copyRuntime);
createArgfile.dependsOn(copyLibraries);

Gradle側の設定はこれでおしまい。

☆☆手順4.VSCodeのWebアプリ起動用の設定をする(目標4)☆☆

続けて、これをVSCode側で受け取りながら実行できるようにするよ。
最終的に動かす構成としては、こんな配置にするね。

・/bin/main ← VSCodeがコンパイルしたクラスファイルとリソースが入る。
・/bin/application ← デプロイ先(名前はわたしが勝手に決めた)。
・/bin/server ← PayaraMicroのサーバーの場所(わたしが勝手に決めた)。

このために必要な設定は、以下の4つ。
・設定1./bin/mainを/bin/application/WEB-INF/classesの下に配置するタスクを作る。
・設定2./src/main/webappを/bin/applicationの下に配置するタスクを作る。
・設定3.GradleのVSCode用タスクと、設定1のタスクと設定2タスクを呼ぶタスクを作る。
・設定4.起動する設定を作る。

☆設定1./bin/mainを/bin/application/WEB-INF/classesの下に配置するタスクを作る☆

/.vscode/tasks.jsonに、こんなかんじで作るよ。

		{
"label": "CopyBinToApplication",
"type": "shell",
"command": "robocopy \"\"\"${workspaceFolder}/bin/main/\"\"\" \"\"\"${workspaceFolder}/bin/application/WEB-INF/classes/\"\"\" /e /purge /xo /nfl /ndl /np /njh /njs /r:1 /w:1; exit $(if($LASTEXITCODE -le 8) { 0 } elseif($LASTEXITCODE -eq 16) { if(Test-Path \"\"\"${workspaceFolder}/bin/application/WEB-INF/classes/\"\"\") { 0 } else { New-Item -Path \"\"\"${workspaceFolder}/bin/application/WEB-iNF/classes/\"\"\" -ItemType Directory } } else { $LASTEXITCODE })"
}

これでディレクトリが同期できる。

☆設定2./src/main/webappを/bin/applicationの下に配置するタスクを作る☆

設定1と同じ感じでこんなかんじ。

		{
"label": "CopyWebappToApplication",
"type": "shell",
"command": "robocopy \"\"\"${workspaceFolder}/src/main/webapp/\"\"\" \"\"\"${workspaceFolder}/bin/application/\"\"\" /e /purge /xo /nfl /ndl /np /njh /njs /r:1 /w:1 /xd WEB-INF /xf .reload; exit $(if($LASTEXITCODE -le 8) { 0 } elseif($LASTEXITCODE -eq 16) { if(Test-Path \"\"\"${workspaceFolder}/bin/application/\"\"\") { 0 } else { New-Item -Path \"\"\"${workspaceFolder}/bin/application/\"\"\" -ItemType Directory } } else { $LASTEXITCODE })"
}

でも、クラスファイルが入るWEB-INディレクトリFと、この後作る.reloadファイルを除外しているよ。
webappの中に、除外したディレクトリとファイルは作れなくなるけど、まぁ、作らないよね。
一応、リポジトリには、WEB-INFのディレクトリを同期するタスクもCopyWEB-INFToApplicationって名前で追加してあるよ。

☆設定3.GradleのVSCode用タスクと、設定1のタスクと設定2タスクを呼ぶタスクを作る☆

ここは迷うことなくこんなかんじ。

		{
"label": "PrepareLaunchApplication",
"type": "shell",
"dependsOn": ["CopyWebappToApplication", "CopyWEB-INFToApplication", "CopyBinToApplication"],
"command": "./gradlew copyRuntime copyLibraries createArgfile"
}

☆設定4.起動する設定を作る☆

ここからはlaunch.json。

		{
"type": "java",
"name": "Debug (Launch) - PayaraMicro",
"request": "launch",
"classPaths": ["@${workspaceFolder}/build/argfile/argfile.txt"],
"mainClass": "fish.payara.micro.PayaraMicro",
"vmArgs": "--add-modules java.se --add-exports java.base/jdk.internal.ref=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.management/sun.management=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.base/java.util.stream=ALL-UNNAMED",
"args": "--rootdir \"${workspaceFolder}/bin/server/\" --deploy \"${workspaceFolder}/bin/application/;\"",
"preLaunchTask": "PrepareLaunchApplication"
}

classPathsにGradleで作った引数のファイルを指定して、
JavaDebugの拡張機能のクラスパスの自動生成を抑制しながらクラスパスを指定するよ。
mainClassには、Payara Microを-jar指定したときに呼ばれるクラス名を指定する。
あとは、rootdirとdeployを指定すればokだね。
あ、deployは;を最後につけてルートコンテキストに配置されるようにしているよ。

と、ここまでで起動できるはず。VSCodeでF5でデバッグ起動してみてね。

これで目標の4を達成だね。

☆手順5.VSCodeのWebアプリ更新用の設定をする(目標5)☆

ここまできたら後は簡単。Payara Microのreloadの設定をするよ。
デプロイ先に.reloadファイルを作れば良いから、tasks.jsonにこんなかんじに作るよ。

		{
"label": "ReloadApplication",
"type": "shell",
"dependsOn": ["CopyWebappToApplication", "CopyWEB-INFToApplication", "CopyBinToApplication"],
"command": "if(Test-Path \"\"\"${workspaceFolder}/bin/application/.reload\"\"\") { Set-ItemProperty -Path \"\"\"${workspaceFolder}/bin/application/.reload\"\"\" -Name LastWriteTime -Value $(Get-Date) } else { New-Item -Path \"\"\"${workspaceFolder}/bin/application/.reload\"\"\" }"
}

うん、これだけ。
サーバーを起動してある状態でこのタスクを動かすと、再配置されるのがログに出るはず。
これで目標5も達成だね。

☆目標6.VSCodeで上の作業が楽にできること☆

最後に、いろいろ楽にできるように設定しようね。

実行は、F5でできるから、reloadも簡単にできるようにしたいよね。
というわけで、VSCodeでのビルドタスクにreloadを設定するよ。こんなかんじ。

		{
"label": "ReloadApplication",
"type": "shell",
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": ["CopyWebappToApplication", "CopyWEB-INFToApplication", "CopyBinToApplication"],
"command": "if(Test-Path \"\"\"${workspaceFolder}/bin/application/.reload\"\"\") { Set-ItemProperty -Path \"\"\"${workspaceFolder}/bin/application/.reload\"\"\" -Name LastWriteTime -Value $(Get-Date) } else { New-Item -Path \"\"\"${workspaceFolder}/bin/application/.reload\"\"\" }"
}

デフォルトのビルドに設定したから、Ctrl+Shift+Bでいけるようになるよ。
webappだけにしたいなら、Reloadapplicationタスクじゃなくて、CopyWebappToApplicaitonタスクをビルドタスクにすれば良いと思う。
他のキーとかに割り当てたければ、keybindingsとかに設定するのもありかもね。

後はログの出し方かな。
ここまでの状態だと、タスクのログが前に出てきて、サーバーのログが見にくいよね。

だから、タスクのログをエラーの時だけ出すようにして、
サーバーのログをTerminalじゃなくてDebug Console側に出すようにするよ。
PayaraはVT100風のログを書くみたいだから、そっちの方が見やすいしね。

まず、タスクのログをエラーの時だけ出すようにするには、こんなかんじにするよ。

		{
"label": "ReloadApplication",
"type": "shell",
"presentation": {
"reveal": "silent"
},
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": ["CopyWebappToApplication", "CopyWEB-INFToApplication", "CopyBinToApplication"],
"command": "if(Test-Path \"\"\"${workspaceFolder}/bin/application/.reload\"\"\") { Set-ItemProperty -Path \"\"\"${workspaceFolder}/bin/application/.reload\"\"\" -Name LastWriteTime -Value $(Get-Date) } else { New-Item -Path \"\"\"${workspaceFolder}/bin/application/.reload\"\"\" }"
}

これは、ReloadAppliactionの例だけど、
普段は見なくてもいいタスク全部に対して、presentation.revealをsilentに設定するよ。

これで、普段はログがでないけど、エラーの時は表示されるよ。
どうせ最終的にはサーバーのログでデプロイまで終わったか見るだろうから、これで良いと思うの。

で、あとは、サーバーのログだね。
launch.jsonでこんなかんじにするよ。

		{
"type": "java",
"name": "Debug (Launch) - PayaraMicro",
"request": "launch",
"console": "internalConsole",
"classPaths": ["@${workspaceFolder}/build/argfile/argfile.txt"],
"mainClass": "fish.payara.micro.PayaraMicro",
"vmArgs": "--add-modules java.se --add-exports java.base/jdk.internal.ref=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.management/sun.management=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.base/java.util.stream=ALL-UNNAMED",
"args": "--rootdir \"${workspaceFolder}/bin/server/\" --deploy \"${workspaceFolder}/bin/application/;\"",
"preLaunchTask": "PrepareLaunchApplication"
}

consoleをinternalConsoleにするだけだね。これで、ログがTerminalからDebug Consoleに移って、
色もつくはず。まぁ、この辺は好みかもしれないけどね。

別に、どっちでも、ブレークポイントとかはちゃんと止まるから、
標準入力をどうしたいかとかの好みで設定しても良いかもね。

ってことで、ここまでで、開発環境によらずに、Gradleでのビルドと実行をできるようにしながら、
VSCodeで簡単なキーバインドで起動と更新ができて、ログも確認しやすい状況が完成だよ!

全目標達成かな?(゜▽、゜

ここまででプロジェクトの名前が入っているのは、
ルートとプロジェクトのディレクトリ名と、
settings.gradleとcode-workspaceくらいかな。

他のOS版とかカスタマイズも、ここまで来れば結構簡単にできそうだよね。
robocopyをrsyncにするくらいかな?

以上!お疲れ様でしたなのだ!('-^*/

posted by すふぃあ at 00:00| Comment(1) | TrackBack(0) | プログラム

2017年12月23日

スパゲッティとライフサイクルとドメイン

この記事は、〇〇勉強してみた Advent Calendar 2017の23日目(12/23)の記事だよ。
次の記事はtosssaurusさんの『canvasを1ヶ月勉強してみた』みたい。

☆☆☆スパゲッティコードとの出会い☆☆☆

最近は、Javaじゃなくて、C#とかTypeScriptとか使ってるんだよね。
ほら、最近のアプリケーションって非同期処理が必須じゃない?
そうすると、スパゲッティなコードを割と自然に解消できるasync/awaitってすごいなぁって思うんだよね。

と言うか、ここのところ、いろんな人のコードをいっぱい見る機会があって、
async/awaitじゃなくても、『そういった構成』にしてないからスパゲッティになっているってコードを結構見かけて、
スパゲッティなコードってこういう風に作られていくんだなぁって思ったから、まとめてみようかなって。

Wikipediaとかみても、

・goto文の濫用
・多重継承の濫用
・スコープの拡大
・技術不足

とかしか書いてないし、
これって、特に最後の技術不足って、わかりません。って書いてあるようにしか思えないしね。
少しでも、スパゲッティなコードの理解と回避につながればいいなって思うの。

☆☆☆スパゲッティの概念的な定義☆☆☆

いつものことだけど、わたしの勝手な考えだから、鵜呑みにしておかしなことになっても知らないよ!

ってことで、いきなりわたしの答えから書いちゃうと、
次の2種類の書き方をしているプログラムをスパゲッティって言うんだと思う。
A. ライフサイクルに関する処理をライフサイクルの層から外してプログラムを構成する。
B. ドメイン重視のコンポーネント単位ではなく、アクション(1連の処理の流れ)としてプログラムを構成する。

ちなみに、これはだめな書き方だから、じゃぁ、どう書けば良いの?って観点で行くと、
『ライフサイクルやドメインにそった開発をする』っていうことかな。
そういう意味では、DDDな書き方している人はスパゲッティなコード書くことないのかもしれないね。

まぁ、どう書くべきか、みたいなのは、時間とともに変わっていくと思うけど。

☆☆☆スパゲッティの具体な成長例☆☆☆

じゃ、わかりやすい例を。
例だから、現実的かどうかとかは大目に見てもらえると助かる感じ?
わかりやすさ重視で!

一番わかりやすいのはこう言うのかな……。
『画面のボタンを押して、処理が終わるまで押せなくする』みたいな。
あんまりしない気もするけど例だから気にしないっ。

1.処理の配置間違えた。

private void Button_Click(object sender, RoutedEventArgs e) {
this.Button.IsEnabled = false;
this.doSomething();
}

private void doSomething() {
// なが〜いなが〜い処理。
Thread.Sleep(2000);
this.Button.IsEnabled = true;
}

あ、えっと、一応、UIにボタン(Button)があって、押すとButton_Clickが呼ばれるってことで。
ここの例は、VisualStudioでWPFのプロジェクトとテンプレで作って、MainWindowにButtonを配置してイベントを作っただけだよ。

さて、こんなコード書かないよ!って?いや、まぁ、普通はそうだとわたしも思うんだけど。
と言うか、そもそもボタンの切り替え動かないし……っていうのは置いといて。
特に、メソッドを切り出すような処理がなければ、こういう書き方することはあり得ないね。

これは、定義のAになっちゃっているのがよくわかると思うんだよね。
切り替えている処理が別々のメソッドに分散しちゃってるから。

じゃ、次はもうちょっと、書きそうな例ってことでちゃんとボタンが切り替わるように非同期にするよ。

2.非同期にした(非同期場所間違えた)。

private void Button_Click(object sender, RoutedEventArgs e) {
this.Button.IsEnabled = false;
this.doSomething();
}

private void doSomething() {
// なが〜いなが〜い非同期処理。
Task.Run(() => {
Thread.Sleep(2000);
this.Dispatcher.Invoke(() => {
this.Button.IsEnabled = true;
});
});
}

このくらいになると、書く人がちょっとだけいそうな気がする。

え?あ、うん、いや、まぁ、元の問題と対応があってないのはわかってるんだけど、
この流れだと、こう考えて書く人多そうだなってことでよろしく!
JavaScriptなWebだと、元に戻すのにわざわざDispatcherとかの行はいらないから、シンプルになって、余計に書く人多そう。

#1で書かないって人は、もちろん、#2も書かないと思うんだけど。
じゃぁ、次でやっとUIの処理まとめる気になったことにする。
実際は、doSomethingとかがさらに複雑になるとか。呼び出し元の処理が複雑になると気づくはずだよね。

3.UIの記述場所をまとめたつもり。

private void Button_Click(object sender, RoutedEventArgs e) {
this.Button.IsEnabled = false;
this.doSomething(() => {
this.Button.IsEnabled = true;
});
}

private void doSomething(Action callback) {
// なが〜いなが〜い非同期処理。
Task.Run(() => {
Thread.Sleep(2000);
this.Dispatcher.Invoke(callback);
});
}

さて、これは、さらに書く人多そうな形だと思うんだよね。
コールバックっていうんだっけ。なんかすごく好きな人がいるみたいだよね。
わたしは、あまり使うの好きくないんだけど。

でも、doSomethingのメソッドがUIでの後処理を意識したメソッドになってしまっているね。
もうちょっと、ちゃんとUIを寄せてみようかな。

4.コールバックでUIと分ける。

private void Button_Click(object sender, RoutedEventArgs e) {
this.Button.IsEnabled = false;
this.doSomething(() => {
this.Dispatcher.Invoke(() => {
this.Button.IsEnabled = true;
});
});
}

private void doSomething(Action callback) {
// なが〜いなが〜い非同期処理。
Task.Run(() => {
Thread.Sleep(2000);
callback();
});
}

うん、まぁ、何となく良くなった感じ?
じゃぁ、ここで、なが〜いなが〜い処理、ずっと適当にSleepとか書いてきたんだけど、
まぁ、普通は通信処理とかになったりするわけで、当然例外とかあり得るわけだよね。
そうしたらどうなるかなぁって。

はっ、#2でおかしい対応していたことがばれてしまう!?(いまさら

5.コールバックでUIと分ける。

private void Button_Click(object sender, RoutedEventArgs e) {
this.Button.IsEnabled = false;
this.doSomething(() => {
this.doSomething(
() => {
this.Dispatcher.Invoke(() => {
this.Button.IsEnabled = true;
});
},
(message) => {
this.Dispatcher.Invoke(() => {
MessageBox.Show(message);
});
}
);
});
}

private void doSomething(Action callback, Action errorHandler) {
// なが〜いなが〜い非同期処理。
Task.Run(() => {
try {
Thread.Sleep(2000);
// throw new Exception("hoge");
} catch(Exception ex) {
errorHandler(ex.Message);
} finally {
callback();
}
});
}

もうすでに、これはない……って感じになってきているわけだけど、
たぶん、少しずつ『処理を追加していってる』とか、『全体の構成じゃなくて一部だけ見直している』とかだと、
こうなっちゃう人、割といるんじゃないかなぁ?
ここでは、finallyにcallback置いたけど、finallyじゃなければ、Button_Clickの実装はまた変わってくるよね。
doSomethingの作りに非常に強く影響受けている感じ。
ほんと、ちょっとした変更も一蓮托生で、動き全部把握しておかないと即死って感じ?(物騒

まぁ、もうこのくらいでいいかな。
というわけで(?)、こういう風にコードが成長していくとスパゲッティになっていくって言うのは伝わったかなって。

じゃぁ、どうすればいいのかっていうと、次のような感じ。
まずは、Taskを戻すよ。

6.制御を戻す。

private void Button_Click(object sender, RoutedEventArgs e) {
this.Button.IsEnabled = false;
Task task = this.doSomething();
Task wait = Task.Run(() => {
try {
task.Wait();
} catch(AggregateException exs) {
this.Dispatcher.Invoke(() => {
exs.Handle((ex) => {
MessageBox.Show(ex.Message);
return true;
});
});
} finally {
this.Dispatcher.Invoke(() => {
this.Button.IsEnabled = true;
});
}
});
}

private Task doSomething() {
// なが〜いなが〜い非同期処理。
Task task = Task.Run(() => {
Thread.Sleep(2000);
// throw new Exception("hoge");
});
return task;
}

スパゲッティになった状態ではっきりわかる最大の問題は、
『非同期の制御を元に戻していない』ことだから、そこを直した感じ。

Taskを作ってる箇所が複数になって、もう#2あたりでのこともはっきり目に見えちゃうね。

じゃ、ちょっと、見にくいから、async/await使うよ。

7.async/awaitで。

private async void Button_Click(object sender, RoutedEventArgs e) {
this.Button.IsEnabled = false;
try {
await this.doSomething();
} catch(Exception ex) {
MessageBox.Show(ex.Message);
} finally {
this.Button.IsEnabled = true;
}
}

private Task doSomething() {
// なが〜いなが〜い非同期処理。
Task task = Task.Run(() => {
Thread.Sleep(2000);
// throw new Exception("hoge");
});
return task;
}

どうかな〜。だいぶわかりやすくなったかな〜。
async/awaitを使うと、非同期関係のところは、正しいライフサイクルに導いてくれる気がするんだよね。
補助してくれるというか。

まぁ、別にasync/await使おうってことじゃなくて、
非同期はTaskを戻すのがライフサイクルとかドメインを考える上で重要ってこと。
そして、ライフサイクルとかドメインを考えないとスパゲッティになっていくってこと。

どんどん処理を追加していくんじゃなくて、
ちゃんと、ライフサイクルとかドメインを考えて構成しないと、スパゲッティになるよってことっ。

たぶん、スパゲッティコードが生まれる理由は、
コンポーネント単位でプログラムを書かないで、
アクション単位でコードをがんがん追加しちゃうのが原因じゃないかな。
言葉の定義怪しいけど。

今回も、Button_ClickにUIに関する制御&表現を、doSomethingに実際に実行&実現したいことを、って、
分けていれば、最初から#6とか#7の方向に進んでいくはずだしね。

ただ、ライフサイクルは、割と簡単だけど、
実際には、ドメインにそって適宜?分割していくのはすごく難しいと思う。

個人で開発していると、ドメインが1個に見えてきちゃうし、
チームで開発していると、ドメインの内部の関係性を把握しにくい感じがする。

☆☆☆スパゲッティを探す☆☆☆

さて、視点をちょっと変えて、すでにスパゲッティになっているのをどう見つけるか、ちょっと考えてみる。
・引数を、使わずに次のメソッドにただ渡している。
 使われているところから発生源を追うんじゃなくて、変数がnewとかされてから、いったいどこまでそれが持ち運ばれていくかってみていく。
 そうすると、だいたい、さっきみたいにUIの最後の処理をするためーとかで、UIそのものをドメイン側の処理に横流ししているとか見つけることが……ある?
 まぁ、あったのっ。すっごく驚いたけど。
・フィールド経由でたまたまそこにある呼び出し先になるメソッドに値を渡す。
 たまに見かけたコードだと、ドメインのメソッド、さっきの例だとdoSomethingは別のクラスとかに分けたりするわけだけど、
 省略のために規模が小さければクラス分けないこともあると思うんだよね。
 と言うか、最初は小さいから分けないことも多いと思うの。

 で、引数で渡すのはおかしい!みたいなことをすると、フィールド経由で渡したりして……、もちろん、これもアウトだよね。
 あとで、クラス分けるとき困っちゃう。

 他のケースだと、全く別のメソッド呼び出していって、
 コールバックで戻ってきたときに使うから、と言う理由で、フィールドに保存しておくみたいな考えっぽいもの。
 これも結構すごかった。

 かければ何でもいいとはわたしは思わないのだ。

・クラスに関係ないインターフェースを実装している。
 これも結構あるかな。さっきの引数で持ち回るとき用でやっていたりするコードを見かけることがあるよ。
 たぶん、ラムダ式とかがわからなかったり、クラスの責任とかを考えなかったり、っていうことだと思うけど。
 かるく、同じアプリケーションコードの中でVisitorパターン発生してるよねきっと。

 インターフェースをクラスに実装している例がいっぱいネット上に転がっているからなのかな?関係ない?

・Task、Future、Promiseを作って、returnしていない(コントロールが手放されている)。
 定番でわかりやすいよね!例としても使わせてもらったし!
 ちなみに、UIとかのもともと戻り値の型がvoidなところで保持する必要がある場合は、
 フィールドに維持して管理するしかない……よね。

☆☆☆まとめ☆☆☆

長々書いてきたけど、結局、最初に書いた定義のAとBによってスパゲッティはできていくと思うの。
ここまで来たら、定義のAとBの違いは、もうわかっちゃっていると思うんだけど、
プラットフォームとかシステムに依存した仕組みとか制限によるものか、
アプリケーションのドメインによる表現とかによるものかの違いかな。

Aがわかるけど、Bがわからないひとは、BをAに強引に当てはめて恐ろしいスパゲッティを作ることが多い気がする。
両方わからない人はいろいろ警戒するから、そこまで元々巨大なスパゲッティは積極的には作らないかな。

つまり、自分がいったいどんなドメインを扱っているのかしっかりとらえるのが重要ってこと。
プログラムは人によって違う書き方になるっていうけど、わたしはあまりそう思わない。

ドメインの設計は人によって結構大きく変わるけど、そこが落ち着いたら、
それを表現する最適なコードは割と誰が書いても同じになると思っているよ。
まぁ、設計っていうか構成を考えるのとプログラムを一緒にすると、人によって違うっていえるかもだけど。

posted by すふぃあ at 11:35| Comment(0) | TrackBack(0) | プログラム

2017年12月02日

コメントの書き方〜すふぃあ流コメント術〜

☆☆☆はじめに☆☆☆

この記事は、個人開発 Advent Calendar 2017の2日目(12/02)の記事だよ。
前の記事は、kappy0322さんの『個人開発における開発プロセスを公開してみる (2017年冬)』だよ。
次の記事はturanukimaruさんの『ファイアーエムブレムヒーローズの戦闘結果計算ツールをKotlinでDDD的に作ってみた』みたい。

☆☆☆経緯とか☆☆☆

kappy0322さんから、Advent Calendarの招待を受けて、あれ、なんで招待されたのかな〜、とか戸惑いながら、
そういえば、前から書こうかなーって思っていた、コメントの書き方、この際だから書こうかな!と思い立って書くことにしたの。
ここで言うコメントって言うのは、ドキュメントコメントではないコメントのことね。

わたしは、プロの教育とか受けてないから、コメントの書き方も、当然、自己流なんだけど、
最近、いろんな人のソースコードを見る機会があって、
『何でこんなコメント書いてるの?』って思うのをいっぱい見てびっくりしたんだよね。たぶん、プロの人のも結構あったと思うんだけど……。

☆☆☆コメントの書き方の問題☆☆☆

で、どうも教育を受けたことがある人に聞くと、
『そのコードを書いた理由をコメントで書きなさい』
って、教えられているみたい。

実際に、ググってみると、本当に、理由を書きなさいみたいなのがいっぱい出てくる。
うん、いや、そうなんだけどさ、なんでそんな、てけとーな教え方してるの……?って思っちゃった。
いや、だってさ、こういうコメント書かれても困るでしょ。

// データが複数あるのでfor文で回す。
for(int i = 0; i < elements.length; ++i) {
// 略
}
// 処理を分けるために分岐する。
if(something.getID() > 0) {
// 略
}

極端な例ではあるけど、教えられたとおりに、『理由』を書いているよね。
でも、良いコメントだね!……ってならないよね?
だから、『理由を書く』っていうのは不十分だと思うの。

って、こういうところから、何が問題かとか、分析していってもいいんだけど、
わかっている人にはわかっていることだろうし、あんまりおもしろくないから、
その辺は省略して、わたしがコメントを書くときの考え方を紹介していくよ。

別に、書くのがめんどくさくなってきたとかそういうわけじゃない……から、ね?

あと、何度も書くけど、わたしはそういう系の教育受けてないから、これが本当によい方法かどうかはわからない〜。
いろいろ比較とか研究とかできるわけでもないし。
ただ、個人開発で困っている人に、少しでも役に立てば良いな、とは思っているよ。

☆☆☆すふぃあ流コメント術☆☆☆

最大のポリシーは、
『あとで変更するときの判断基準を書く。』
ということ。

だって、システムとかアプリケーションを、作って〜リリースして〜書き換えて〜ってやっていると、
コメントが必要な時って、『そういうとき』だよね?

このポリシーから、コメントを書く、より具体的な方針は、、
『普通ではない書き方をしたところには、その根拠を示す。』
ということになると思っているよ。

コメントを書いていないところは、コードそのままの意図で、おかしな動きをしていたら、それはバグとして直されたり、最適化されたりする。
コメントがあるところは、その妥当性をコメントをみて判断してから、変更や修正ができるってわけ。

たとえば、

// このメソッドの定義より、要素の最後には、○○を含めて返す必要がある。
// ○○は、××の特性を含むため、△△の処理は行ってはならないので除外されるように条件を調整している。
for(int = 0; i < elements.length - 1; ++i) {
// 略
}
return elements;

まぁ、サンプルだから、もっと別のコード書きなよっていうのはあると思うんだけど、そこはそれ。
でも、こうすれば、なんで-1しているの??っていう理由と、それが妥当かどうかの判断ができるはず。
実は、この見解が間違っているとか、見解が変わったとかなら、よりどころとなっているコメントごと直すって感じ。

単に、『理由』ではなくて、変更するときの『判断基準』となることを書いていくのが重要ってこと。
ほんと、ただ、それだけだよ?みんなが知りたいことってそういうことじゃないのかな?

コメントが長くなるじゃん!って言うのもあるかもしれないけど、
必要なことは書くべきだと思うの。
くだらないことじゃないわけだからね。

かなり自分でも困ったソースコードの付近には、15行超えるようなコメント書いたこともあったかな。
まぁ、稀だけど。
読んで判断基準にならないコメントなんて、だからなんなの!って叫びたくなるだけじゃない?

☆☆☆弱点とか☆☆☆

この書き方の最大の弱点は、『普通』っていうのを共有しないといけないこと。
基本的には、必要十分なコードで、設計ポリシーにあっていれば問題は起きないはず。

裏を返すと、実力に大きな差があるチームだと、失敗すると思う。
まぁ、足手まといな人がいると、きっと別の意味で失敗するだろうから、大丈夫な気もするんだけどね。
その辺は想像だから、よくわかんない。

もう一つは、設計が微妙だとコメントがすっごく増えること。
だから、作っているときに気づいた、設計が微妙なところはすぐに直していかないとダメかも。
その設計がおかしいと言うコメントを大量に残すことになるからね。

って、この書き方じゃなくてもコメントが増えること自体は同じかもしれないけど。

☆☆☆よくありそうな質問と回答☆☆☆

Q. 難しいコードに説明コメントつけるのはどうなの?
A.
難しいって、普通じゃないってことでしょ?それは書き換える基準も含めてコメント書かないとだめじゃない?
まさか、難しいのが普通になって……ないよね?
もし、どこもかしこも難しくてコメントだらけになりそうって言うことなら、
それは設計がおかしいんだと思うよ。

Q. 必要なコメントが書かれていなかったら、困ったことになるんじゃないの?
A.
それは、つまり、書き換えるときに、必要な変更の判断基準が書かれてなくて、
さくさく書き換えた結果、間違った書き換えになっちゃうんじゃないか、って言うことだよね。

良いんじゃない?だって、前書いた人(過去の自分含む)さえ、それに気づいてなかったんでしょ?
気づけて良かったじゃない。
と言うだけだと思う。

てけとーに書かれていたのが、たまたま、動いていただけで、それが壊れるだけのこと。
壊れるのは、困るけど、それは、元を書いた人の問題だと思う。

このコメントの書き方をしていると、書き換える元にコメントを残さなかったことが悪いってことになんじゃないかな。
いや、まぁ、どっちが悪いとか言っても慰めにもならないんだけどさ。

なんか、どんなひどいコードでも、動いていれば正義みたいな考え方もあるみたいだけど、
それが『絶対』だとはわたしは思わない。もちろん、動いていることも大事だけどね。
動いていても、手抜きを許すわけじゃないし、ミスはありえることだと思う。

『過去のわたし!しっかりしろ!』って思うこと、よくある。
そこには、きっと、コメント以外の対策みたいなのが必要なんじゃないかと思ってみたりするよ。

Q. 書き直す予定がないときもコメント書くべきなの?
A.
書き直す予定がないって言うのは、バグが絶対にないってことかな。
それとも、バグがあったら、そのソースコードを全く参照しないで作り直すって言うことかな。
というわけで、そんなことは、まず、あり得ないと思うから、書いた方が良いと思う。

ただ、ちょっとしたスクリプトとかまで毎回書くかとかは知らないっ。
でも、やっぱり、あとでスクリプトを発掘すると、
あれ?これなんでこうなっているんだっけ?みたいなことは、わたしもよくあるけどね?(゜▽、゜

☆☆☆まとめ☆☆☆

さぁ、『理由』じゃなくて、『判断基準』をコメントに書いていってみようよ!
コメントを書くときに後の人のことを想像して考えることがやりやすくなるんじゃないかな。
わたしは、何にも責任とれないけどね!('-^*/

posted by すふぃあ at 12:10| Comment(0) | TrackBack(0) | プログラム

2010年02月28日

Tomcat6をJavaEE6もどきにする〜その2〜

Weldの1.0.0.SP1と1.0.1-CR2がダウンロードできなくなって、1.0.1-Finalだけになった。
あとEclipseLinkも2.0.1になってる。
というわけで新しくTomcatで動く組み合わせを確認してみたよ(゜▽、゜

jarの名前+Versionどこから?
jsf-api-2.0.2.jarMojarra 2.0.2
jsf-impl-2.0.2.jarMojarra 2.0.2
javax.inject-1.0.1-Final.jarJSR-299 TCK 1.0 1.0.1-Final(Weldの側から)
cdi-api-1.0.1-Final.jarWeld 1.0.1-Final
weld-api-1.0.1-Final.jarWeld 1.0.1-Final
weld-core-1.0.1-Final.jarWeld 1.0.1-Final
weld-servlet-1.0.1-Final.jarWeld 1.0.1-Final
weld-spi-1.0.1-Final.jarWeld 1.0.1-Final
weld-tomcat-support-1.0.1-Final.jarWeld 1.0.1-Final
javax.persistence_2.0.0.v201002051058.jarEclipseLink 2.0.1
eclipselink-2.0.1.v20100213-r6600.jarEclipseLink 2.0.1
validation-api-1.0.0.GA.jarHibernate Validator 4.0.2.GA
hibernate-validator-4.0.2.GA.jarHibernate Validator 4.0.2.GA
jp.empressia.jsf.scope.view-1.0.1.20.jarEmpressia
jp.empressia.logging-1.0.0.12.jarEmpressia
jstl-api-1.2.jarJSTL(GlassFishのサブプロジェクト)
jstl-impl-1.2.jarJSTL(GlassFishのサブプロジェクト)
javassist-3.11.0.jarJavassist 3.11.0(Weldのため)
google-collect-1.0.jarGoogle Collection 1.0(Weldのため)
dom4j-1.6.1.jarネットから探して(Weldのため)

ログ周りは、Weldのservlet.jarかな。に入ってるので別に持ってくる必要はないのだ。
あとEJBのもぽいぽい!ってことで、GlassFishV3から持ってきてたのも取り除いてみた。
あと、WeldのSP1の時は、servet-intが必要だったけど、今回はservletになってるね。
うーむ、20個まで減った!(゜▽、゜いいことだ
WeldはbeforeShutdownが呼ばれるようになったし本当に良かった……(ρ_;)
でもやっぱりGlassFishが良いデス。

posted by すふぃあ at 14:03| Comment(0) | TrackBack(0) | プログラム

Weld(CDI)が1.0.1-Finalになったので対応

Weld(CDI)が1.0.1-FinalにVersionがあがってたので、
ViewScopedのjp.empressia.jsf.scope.view.jarもバージョンを上げて対応したよ。

それにしてもAPI変わりすぎ(笑)
影響されて、1.0.1.19と1.0.1.20で大分変わっちゃった。

1.0.1.19がWeld 1.0.0.SP1用で
1.0.1.20がWeld 1.0.1-Final用ね。

GlassFishV3は、まだVersionがあがってないみたいだから、1.0.1.19で大丈夫そうね。
posted by すふぃあ at 13:53| Comment(0) | TrackBack(0) | プログラム

2010年02月21日

Tomcat6をJavaEE6もどきにする〜その1〜

いろんなサイトで、「JPAをTomcatで」とか。「JSFをTomcatで」とか。
まぁ、色々あるけど、全部一緒に動かす例って無いんだよね。
整合性保つのが大変なのかな?

jp.empressia.scope.view.jarをテストするために色々放り込んで動く環境を確認したので、
これなら動く!って言うのをメモっておくのだ。
Tomcatは6.0.24ね。

JSF2.0(mojarra)+CDI1.0(Weld)+JPA2.0(EclipseLink)と
BeanValidation1.0(Hibernate Validator 4)を入れるのだ。
あとWeld用ViewScopedもね!
ちなみに、jarのバージョン番号とかはわたしが勝手につけた部分もあるので注意。

jarの名前+Versionどこから?
jsf-api-2.0.2.jarMojarra 2.0.2
jsf-impl-2.0.2.jarMojarra 2.0.2
javax.inject-1.0.1-CR1.jarJSR-299 TCK 1.0 1.0.1-CR1(Weldの側から)
cdi-api-1.0.0.SP1.jarWeld 1.0.0.SP1
weld-api-1.0.0.SP1.jarWeld 1.0.0.SP1
weld-core-1.0.0.SP1.jarWeld 1.0.0.SP1
weld-logger-1.0.0.SP1.jarWeld 1.0.0.SP1
weld-servlet-1.0.0.SP1.jarWeld 1.0.0.SP1
weld-servlet-int-1.0.0.SP1.jarWeld 1.0.0.SP1
weld-spi-1.0.0.SP1.jarWeld 1.0.0.SP1
weld-tomcat-support-1.0.0.SP1.jarWeld 1.0.0.SP1
javax.persistence_2.0.0.v200911271158.jarEclipseLink 2.0.0
eclipselink-2.0.0.v20091127-r5931.jarEclipseLink 2.0.0
validation-api-1.0.0.GA.jarHibernate Validator 4.0.2.GA
hibernate-validator-4.0.2.GA.jarHibernate Validator 4.0.2.GA
log4j-1.2.14.jarHibernate Validator 4.0.2.GA
jp.empressia.jsf.scope.view-1.0.1.18.jarEmpressia
jp.empressia.logging-1.0.0.12.jarEmpressia
jstl-api-1.2.jarJSTL(GlassFishのサブプロジェクト)
jstl-impl-1.2.jarJSTL(GlassFishのサブプロジェクト)
javassist-3.11.0.jarJavassist 3.11.0(Weldのため)
google-collect-1.0.jarGoogle Collection 1.0(Weldのため)
dom4j-1.6.1.jarネットから探して(Weldのため)
javax.ejb-glsasfishv3.jarGlassFishV3の中
weld-osgi-bundle-glassfishv3.jarGlassFishV3の中
slf4j-api-1.5.10.jarSLF4J 1.5.10(Hibernate Validatorと他の調整のため)
slf4j-ext-1.5.10.jarSLF4J 1.5.10(Hibernate Validatorと他の調整のため)
slf4j-log4j12-1.5.10.jarSLF4J 1.5.10(Hibernate Validatorと他の調整のため)
cal10n-api-0.7.2.jarネットから探して(Hibernate Validatorと他の調整のため)

全部で29個……( ̄▽ ̄;;)
Tomcatでこれだけめんどくさいの調整するくらいならGlassFishかなぁ(笑)
それぞれのバージョン上げるのも大変そうだね、これ(゜▽、゜

そうそう、これだけがんばってもEL式でメソッド#{bean.method()}みたいなのは使えませんw
EL式使えるようにするには、Tomcat改造しないと無理っぽいから……。
さすがにそんなことするくらいならGlassFishV3でしょ!

あ!これで安心しないように!
DB使うならそれのjarも必要だからね!あわせてTomcatのlibに投入('-^*/⌒°

で、設定ファイルはねー。
Tomcatのconf/context.xmlでしょ〜。WeldのBeanManagerとDBのDataSourceを設定!
WEB-INF/faces-config.xmlでしょ〜。JSF2.0で設定!1.2にしないに注意だよ!
WEB-INF/beans.xmlでしょ〜。からっぽでもは忘れないようにっ!
META-INF/persistence.xmlでしょ〜。RESOURCE_LOCALでnon-jtaだね!
WEB-INF/web.xmlでしょ〜。FacesServletとかいろいろ!
ふー、設定完了(゜▽、゜起動してみてね!
え?動かない!?(ρ_;)イイモン わたしはこれで動くもん

2010/02/28追記:weld-servlet-1.0.0.SP1.jarは、いらなかった!表の中は抹消線つけておいた!ごめん!
posted by すふぃあ at 20:03| Comment(0) | TrackBack(1) | プログラム

JSFとCDI(Weld)の実現するものはWPFに似ている

JSFとCDI使ってWebアプリをいじっていたら、やっとわかったきがした(゜▽、゜
なにがって、なんていうか、実現する構成の方向性みたいなのが。

JSFはStrutsと違ってコンポーネントベースって言われるフレームワークなのね。
Strutsは、URLへのRequest(引数)に対して対象のAction(関数)を呼んで、画面(戻り値)を返す感じ。
JSFは、URLに対応する画面(オブジェクト)があって、画面のアクション(イベント・メソッド)で、状態を変化させていく。
まぁ、どっちもまともに使ったこと無いけど(笑)

Strutsは関数指向。JSFはオブジェクト指向な感じだね。
関数指向は処理が分散しがちでメンテナンスはちょっと気を抜くと大変なことになる。
オブジェクト指向は、正しく作ればメンテナンス性は高くなるけど、処理を分散しにくい。

そうなると、棲み分け的には、
クライアントのアプリとかは、オブジェクト指向で作って、
最近よく聞く、クラウドっていうのは、関数指向で作ることになるんだろうね。
まぁ、オブジェクト指向は、関数指向を受け入れない訳じゃないから、
ハイブリッドとかもあり得るだろうけど。
まぁ、普通の(?)Webアプリ作るならJSFの方が管理とかはずっと楽って言うこと〜。

そして、クライアントとサーバーをまとめて見下ろすと、なんか別の世界が見えてきた。
「あ、これってWPFと同じMVVMじゃん。」
結局、『.NET』も『Java』も行き着くところに行き着いた。という感じなのかな?

MVVMって言うのは、MVCの派生みたいなの。Model-View-ViewModelって感じのやつ。
難しいことはよくわかんないけど、わたし的解釈は、下(↓)に書いたような感じかな。
「画面(View)と画面に出すもの(ViewModel)の関係は、
View→ViewModelでしかもバインドとして疎結合であるべき。
Modelからの依存は許さない。」
バインドってことはその外側でバインディングを管理している人がいるわけなのをお忘れ無く!

MVVM_001.PNG
フレームワークViewViewModelModel
WPF.xamlファイル.xaml.csファイルその他
JSF+Weld.xhtmlファイル.xhtmlに対応した(Managed)Beanその他

結局、どっちもViewに対してViewModelを割り付けて、
Viewで発生したイベントをViewModelで拾ってModelに流してるのね。

JSFだけだと、Modelを完全に切り離すのがめんどくさそうなので、Weldの力を一緒に使うのが良さそう。
っていうことで、Webアプリを作るならJSF+CDI(Weld)が楽っぽいなー。
特にわたしはクライアントアプリ中心にオブジェクト指向でやってきたし(笑)

posted by すふぃあ at 11:58| Comment(1) | TrackBack(0) | プログラム

2010年02月14日

Weld(CDI)好きのJavaEE6なGlassFish(JSF+CDI+JPA)なアノテーション一覧

よく使う?とわたしが独断で思うやつを勝手に一覧化(゜▽、゜
他にどんなのよく使うのかな〜?(・_・?)ハテ
あと、こっそりEmpressiaライブラリも入ってたりする(゚ー゚)ニヤニヤ
画像版↓
JavaEE6_Annotations_Part_20100214.PNG

製品仕様(依存)アノテーションパッケージ個人的分類期待効果
WeldCDI@Namedjavax.injectInject対象管理Inject管理対象としてマーク。
WeldCDI@Injectjavax.injectInject対象管理Inject先のフィールド、Injectするための特殊処理を挟むメソッドを指定する。
WeldCDI@Qualifierjavax.injectInject対象管理InjectとProducesを結びつけるマッピング。
WeldCDI@Producesjavax.enterprise.injectInject対象管理Inject元のィールド、Inject元を生成するメソッドを指定する。
WeldCDI@Disposesjavax.enterprise.injectInject対象管理破棄される時に呼ばれるのを指定。
WeldCDI@ApplicationScopedjavax.enterprise.contextInjectスコープ管理アプリケーション単位での生成と破棄。
WeldCDI@SessionScopedjavax.enterprise.contextInjectスコープ管理セッション単位での生成と破棄。
WeldCDI@RequestScopedjavax.enterprise.contextInjectスコープ管理リクエスト単位での生成と破棄。
WeldCDI@ConversationScopedjavax.enterprise.contextInjectスコープ管理業務単位での生成と破棄。
WeldCDI@Dependentjavax.enterprise.contextInjectスコープ管理Injectされる元に依存した生成と破棄。
WeldCDI@NormalScopedjavax.enterprise.contextInjectスコープ管理新しいスコープを定義するために使う。
EmpressiaWeld@ViewScopedjp.empressia.jsf.scope.viewInjectスコープ管理JSFの画面単位での生成と破棄。
Common AnnotationsCommon Annotations@PostConstructjavax.annotationInject生成管理Inject生成完了した時に行う処理。
Common AnnotationsCommon Annotations@PreDestroyjavax.annotationInject生成管理Inject破棄する前に行う処理。
EclipseLinkJPA@Entityjavax.persistence永続化管理EntityManagerの管理対象としてマーク。
EclipseLinkJPA@Idjavax.persistence永続化管理主キーを指定する。ID指定された対象のEntityが複合主キーを持っている場合は、ばらしたフィールドを別途用意して、@Idをつける必要がある。
Id関連は@Columnとかで名前を指定した方が良いかも。@JoinColumnとか必要になってくるし。
EclipseLinkJPA@OneToManyjavax.persistence関連数指定1対多の関係のあるフィールドに指定する。主にListのフィールドに指定する。相手側からの依存が基本の場合はmappedBy属性を指定する。
EclipseLinkJPA@ManyToOnejavax.persistence関連数指定多対1
EclipseLinkJPA@OneToOnejavax.persistence関連数指定1対1
EclipseLinkJPA@ManyToManyjavax.persistence関連数指定多対多
EclipseLinkJPA@MapKeyjavax.persistence関連指定主にMapなフィールドに指定する。※詳細は確認してない。
EclipseLinkJPA@Embeddablejavax.persistenceテーブルマッピングフィールドの形で同じテーブルに組み込むための定義側指定。
@PrePersist等は動かない。@MappedSuperclassと@Entityのみで動作する。
EclipseLinkJPA@Embeddedjavax.persistenceテーブルマッピングフィールドの形で同じテーブルに組み込むための指定。
EclipseLinkJPA@MappedSuperclassjavax.persistenceテーブルマッピングこれを定義したクラスをEntityで継承すると継承元も永続化対象になる。
@Embed〜系と違って、@PrePersistを定義しておくと動作する。
EclipseLinkJPA@PrePersistjavax.persistence永続化対象管理Insert前に行う処理を指定。
EclipseLinkJPA@PreUpdatejavax.persistence永続化対象管理Update前に行う処理を指定。
EclipseLinkJPA@Temporaljavax.persistence詳細指定日付系に指定する。
EclipseLinkJPA@OrderByjavax.persistence詳細指定順序指定。
EclipseLinkJPA@Transientjavax.persistence詳細指定永続化対象外マーク。
mojarraJSF@ManagedBeanjavax.faces.bean画面対象管理Weld使う時は使用しない。
mojarraJSF@ManagedPropertyjavax.faces.bean画面対象管理Weld使う時は使用しない。
mojarraJSF@ApplicationScopedjavax.faces.beanスコープ管理Weld使う時は使用しない。
mojarraJSF@SessionScopedjavax.faces.beanスコープ管理Weld使う時は使用しない。
mojarraJSF@RequestScopedjavax.faces.beanスコープ管理Weld使う時は使用しない。
mojarraJSF@ViewScopedjavax.faces.beanスコープ管理Weld使う時は使用しない。
mojarraJSF@CustomScopedjavax.faces.beanスコープ管理Weld使う時は使用しない。
mojarraJSF@NoneScopedjavax.faces.beanスコープ管理Weld使う時は使用しない。
mojarraJSF@FacesConverterjavax.faces.convert画面管理独自クラスをプルダウンとかチェックボックスとかに使う場合の定義に使用する。
mojarraJSF@FacesValidatorjavax.faces.validator画面管理独自の入力のチェックを定義する。フロントエンド用。
posted by すふぃあ at 13:10| Comment(0) | TrackBack(0) | プログラム

2010年01月29日

MySQL5.0系の致命的な問題

MySQLひどいなー。これ、DBとして失格じゃない?
3人以上だともっと楽しいことが出来るかもね!
-- フェーズ1
-- 前提処理
CREATE TABLE SIMPLE_TEST (
ID CHAR(8) NOT NULL,
NAME VARCHAR(64) NOT NULL,
PRIMARY KEY (ID)
)
ENGINE = InnoDB;
INSERT INTO SIMPLE_TEST (ID, NAME) VALUES ('0001', 'オリジナルネーム1');
INSERT INTO SIMPLE_TEST (ID, NAME) VALUES ('0002', 'オリジナルネーム2');

-- フェーズ2
-- Aさん作業
-- Aさんトランザクション開始
SELECT * FROM SIMPLE_TEST;
UPDATE SIMPLE_TEST SET NAME = '名前上書き' WHERE ID = '0001';
-- Bさん
-- Bさんトランザクション開始
SELECT * FROM SIMPLE_TEST;

-- フェーズ3
-- Aさん作業
-- Aさんコミット

-- フェーズ4
-- Bさん作業
SELECT * FROM SIMPLE_TEST;
-- ↑Aさんがコミットした値が見えない。
UPDATE SIMPLE_TEST SET NAME = '名前上書き' WHERE ID = '0001';
-- ↑UPDATE件数が0件で正常に終わる。
SELECT * FROM SIMPLE_TEST;
-- ↑UPDATEされたと思っている値が見えない。
-- Bさんコミット
-- ↑仮にCさんが別作業でコミットしててもBさんがやろうとした更新は何も反映されない。

トランザクション内でSELECTすると、作業コピー作って、それを見るみたいなんだけど、
「UPDATE時に作業コピーではなくてマスターのコミット済み領域を見てUPDATE結果が変わらなそうなら何もしない」とか言う制御してるっぽい。
DBとして失格な気がする(゜▽、゜
posted by すふぃあ at 12:45| Comment(0) | TrackBack(0) | プログラム

2009年12月27日

WeldにViewScopedを追加するライブラリ〜その1〜

JavaEE6がでて、おもしろそうなのでいろいろ実験してたんだけど、
Context and Dependency Injection(CDI)って言うのがおもしろいね。
実装は、Weld。JavaSEでも動くみたいだし!

JSF2.0のmojarraとWeldを使ってて、なんだろ、InjectionはやっぱWeldの方がいい(゜▽、゜
さすがって感じ?(なにがっ
でもでも、JSF2.0にはViewScopedがあるんだけど、
Weldには無いんだよね。
かといって、CDI(Weld)のConversationScopedは、JSF2.0にはない……。
ネット見てたら、やっぱ作るしかないらしい……。

というわけで作った!
ダウンロードページ
下の2個のライブラリをlibの中にでも放り込めばOK
jp.empressia.jsf.scope.view.jar
jp.empressia.logging.jar
あとは管理対象に、SessionScopedとか、ConversationScopedと同じように、ViewScopedを書くだけ〜☆
あ、ViewScopedは、JSFの方じゃなくて、jp.empressia.scope.view.ViewScopedね。
便利になった(゚ー゚)(。_。)(゚-゚)(。_。)ウンウン

まだ微妙におかしいところがあるかな……。基本的な動きは大丈夫っぽいので、そこだけ修正しないとダメかな?φ(。。めもめも
↑2009/12/27 追記:1.0.0.15で対応済み♪

posted by すふぃあ at 14:03| Comment(0) | TrackBack(1) | プログラム

2009年09月13日

WPFのRoutedCommandとCommandBinding

ふつーに、WPFなアプリを作ってて、行き詰まりというか、悩んでたことが、解決した。
解決した内容とかは、他のWebサイトとかにも載ってる内容なのでそっちを参考にするっとφ(。。
気が向いたら概要を図にまとめてもいいかな、と思う。
どちらにしても、そこにたどり着いた道筋は、残しておこう。
結局、いろんなサイト見たけど、失敗ケースをやるまで良くわからなかったから。


例1:アプリでUIイベント処理したい。
☆対応1:
 ・通常のWindowに対してメソッド作成した。
 ・Windowに操作対象Objectを保持した。
 ・Windowの中のButtonからメソッドを呼んだ。
 結果:
  できた。
 問題点:
  ButtonとWindowが切り離せない。
WPFCommandBinding01.png


例2:例1に対してUIイベント処理を非同期化したい。
☆対応1:
 ・メソッドに対してdelegateを宣言、代入した。
 結果:UIに対する結果反映処理が別スレッドからの処理となりエラー。
☆対応2:
 ・BackgroundWorkerを使った。
 結果:
  できた。
 問題点:
  操作対象objectがWindowにあるので、以下のどちらかにする必要がある。
  1.thisアクセスする
  2.イベントのResult等にobjectを入れて引き回す
  1の場合は、非同期処理をするメソッドと、後処理のメソッドは同クラスから切り離せない。


例3:例2に対して、別の場所から操作対象を処理したい。
☆対応1:
 ・別ユーザーコントロールを作った。
 ・ユーザーコントロールに例1と同様にobjectを保持した。
 ・ユーザーコントロールに例2と同様にメソッドを作成した。
 結果:
  できた。
 問題点:
  処理は出来たが、他のUIに処理結果が反映できなかった。


ここで、前から疑問に想っていたICommandとRoutedCommandとCommandBinding言うものが良くわかった。
作りながらも3回くらい、調べては、あきらめを繰り返してきたけど、
これらを(これらも)解決できる仕掛けの提供になっているんだね。
もっと洗練できそうな気もするけど。

ちょうど、想像してた仕掛けの解説っぽい記事を発見したので張っておこうっと。
antsk blog : WPFのCommand
自分の見たかった図とかが載ってた。
これを基準にPreviewExecuteとかExecutedを利用すればよさげ。
うみゅ、満足(゜▽、゜ねむいゾ
posted by すふぃあ at 13:03| Comment(0) | TrackBack(0) | プログラム

2008年08月02日

StateパターンとStrategyパターン

んーと、なんかよくわからないので整理してみる。

状態とかタイプとかで、処理が分岐するときに使うパターン。

・状態の数が固定・可変(後から追加)。
数が固定の場合は、当然Enumで表現できる。
数が可変の場合は、クラスの継承で表現する。

数が固定っていっても、状態の数が変わるかも!とか考え出すとEnum使えないんだよね。
ここで言う追加って言うのは、他の人が追加するってこと。
自分で追加……は、えっと、許されるんだよね?(汗)
Enumの拡張とか継承みたいなのってあり得ないのかなぁ。

話を戻すと……、処理を状態に含めないなら上だけで済むんだけど、
実際には、処理も放り込みたいんだよね。
というわけでさらに以下の場合によって実装ってかわるっとφ(。。

・状態別の処理が固定・可変(?)。

数が固定で、状態別の処理が固定なら、当然Enumに放り込むよね。
数が可変で、状態別の処理が固定なら、状態別のクラスに実装すればOK。

数が可変で状態別の処理が可変の場合は、状態と処理を分離して実装すればOK。

問題は数が固定で状態別の処理が可変……って、これは数は固定だけど、実はEnumにするものじゃないんだよね、きっと。

うーん、なんか抽象的な話だなぁ……。

まとめると
状態数が固定なら、Enum。ただし状態数が拡張可能な場合はEnumジャダメ。
処理が可変な場合は、Strategyパターン?で分離しておくって感じ?
posted by すふぃあ at 15:17| Comment(0) | TrackBack(0) | プログラム