Hello, Stupid World! -3ページ目

Hello, Stupid World!

いろいろとメモ代わりに書いていきます。

今回からいよいよRailsをやっていきます。
まずは gem install rails でインストール

普通にコマンドプロンプトからやったらエラーが発生したので
Rubyコマンドプロンプトでやったら成功。
(64bit版だから?)

その後、アプリケーションを作成する。
rails new アプリケーション名



こんな感じにたくさんのファイルが作成されます。




「DL is deprecated, please use Fiddle」と表示されますが
内部で使用しているライブラリが古くて警告表示されるらしいですが
動作には問題ないので無視しました。

Webサーバを起動して初期画面を表示させます。
アプリケーションのルートで rails server を実行すれば
Webサーバが起動するはずですが、エラーが発生。

「No source of timezone data could be found. (TZInfo::DataSourceNotFound)」
とある。
どうやら、このTZInfoというのが見つからないらしいです。



調べてみると、64bit版ではアプリケーションのルートにあるGemfileの中の
gem 'tzinfo-data', platforms: [:mingw, :mswin] という箇所を
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw] 
修正する必要があるそう。

修正後、bundle updateして



再度 rails serverした所、無事に起動



ここまで行えば、初期画面が表示できます。



次回はScaffoldという機能を使います。
今回でMavenの紹介は一旦終わろうと思います。
最後に紹介するのはMavenからJavaプログラムを実行する為の
exec-maven-pluginです。
Javaプログラムを呼べるので大抵のことは何でもできちゃいます。
勿論、他のpluginで出来ることはpluginでやった方が簡単ですが。

早速、設定方法です。
以下のようにpom.xmlを設定します。

[pom.xml]
 <plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>exec-maven-plugin</artifactId>
   <version>1.2.1</version>
   <configuration>
     <executable>java</executable>
     <mainClass>trap.co.jp.Main</mainClass>
   </configuration>
   <executions>
     <execution>
       <phase>process-classes</phase>
       <goals>
         <goal>java</goal>
       </goals>
     </execution>
   </executions>
 </plugin>

ここでは実行フェーズをprocess-classesという
コンパイルでできたファイルに対する処理を行うフェーズに
してみました。

実行クラスはmainClassタグ内で指定します。
勿論、mainメソッドが対象です。
引数等も指定できます。
詳細は公式サイトを見て下さい。

指定したフェーズによってはpom.xmlのexecutionタグが警告されます。
この時にカーソルを合わせると
「Plugin execution not covered by lifecycle configuration~」と
表示されますが実行には影響が無いので気にしなくてもOKです。

気になる場合は問題ビューで削除すれば良いでしょう。
クイックフィックスで対応すれば簡単に治せますが、pom.xmlが
余計複雑になるので今回は使いませんでした。

この指定のJavaクラス実行を使えば大抵のことは何でもできちゃいます。
これまでのコンパイル、ユニットテスト、Warなどの作成、デプロイと
いった一連の処理の他にJavaクラスを実行することで、より柔軟な
ビルド処理が行えます。
FlywayというDBマイグレーションツールをMaveから実行できるよう
にしてみました。

DBマイグレーションとはテーブル構造やテーブル内容を
バージョン管理できるようにしたものです。

もう少し具体的に言うとバージョン毎にCreate Table文やInsert文
等のSQLを持たせて、現在のバージョンより新しいSQLがあれば
実行するものと、考えてもらえればいいです。

これを使う事でプログラムだけでなくDB状態も
バージョン管理できるので、プログラムのバージョンを最新版に
したけどDBの状態が古かったとかが無くなります。

また、テストの為にプログラムとDBの状態を一世代前に戻す
とかもできます。
※Flywayではバージョンダウンが無いため、作り直して
 一世代前までバージョンアップするという手順

人が増えて、新しく開発環境作ったりも楽になります。

さらにMavenと連携する事でWarファイルを作ると同時に
テスト用にDBデータを初期化してテストを動かしたり
開発環境のDB状態を最新化したりが同時にできます。

一点、ハマったのですが
このFlywayをMavenから使う場合、JDK1.6以上じゃないと使えません。
http://flywaydb.org/contribute/code/setup.html に書いてありました。

では、Flyawayを使う為のpom.xml設定内容です。
pluginsの中で設定します。

[pom.xml]
 <plugins>
   <!-- flyway-maven-pluginの設定 -->
   <plugin>
     <groupId>com.googlecode.flyway</groupId>
     <artifactId>flyway-maven-plugin</artifactId>
     <version>2.2</version>
     <configuration>
       <
url>jdbc:h2:~/test</url>
       <
user>sa</user>
       <
password>sa</password>
     </configuration>
     <dependencies>
       <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
         <version>1.3.170</version>
       </dependency>
     </dependencies>
   </plugin>
 </plugins>

H2Databaseを使う為にdependency下にH2のドライバを指定します。
configuration以下にはJDBC URLとユーザ名、パスワードを指定します。
(緑部分)
ユーザ名やパスワードを別ファイルに指定する事もできるらしいです。

設定は以上。

以下のコマンドがあります。
init:Flyway用のテーブルを作成
clean:全テーブル削除(Flywayで作ったもの以外も)
migrate:マイグレーション実行
repair:マイグレーション失敗したものを削除
info:バージョン情報を表示

例えばDBを再構築してからテストしたい場合は
「Maven Flyway:clean Flyway:migrate Test」
DBのバージョンアップだけしたい場合なら
「Maven Flyway:migrate」
といった感じです。

Flywayで使うSQLファイルはクラスパスが通っている場所に
db/migrationフォルダを作成し、Vバージョン番号_説明.sqlという形式で
作成します。
例)V1_create_table_items.sql

次回はMavenから自作のJavaプログラムを呼び出す方法を説明します。
Mavenを使ってTomcatにデプロイしてみます。
pom.xmlの内容は以下です。

[pom.xml]
 <project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0  http://maven.apache.org/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>パッケージ名</groupId>
   <artifactId>プロジェクト名</artifactId>
   <packaging>war</packaging>
   <version>0.0.1-SNAPSHOT</version>
   <name>名前</name>
   <url>http://maven.apache.org</url>
   <dependencies>
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.0</version>
       <scope>compile</scope>
     </dependency>
   </dependencies>
   <build>
     <finalName>ファイル名</finalName>
     <plugins>
         <!-- For Juit 4 -->
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <version>2.4.2</version>
       </plugin>
         <!-- snip -->
       <plugin>
         <groupId>
org.apache.tomcat.maven</groupId>
         <artifactId>
tomcat7-maven-plugin</artifactId>
         <version>2.2</version>
         <executions>
           <execution>
             <id>
exec-tomcat</id>
             <phase>
package</phase>
             <goals>
               <goal>
redeploy</goal>
             </goals>
             <configuration>
               <path>
/mavenWeb</path>
               <server>
tomcat-localhost</server>
               <url>
http://localhost:8080/manager/text</url>
             </configuration>
           </execution>
         </executions>
       </plugin>
     </plugins>
   </build>
 </project>

pluginにtomcat7-maven-pluginを追加しています。
また、executionタグを設定し、packageフェーズで自動的に
再デプロイされるようにしてみました。

フェーズは順番にcompile→test→package→install→deployと
実行されていきます。
テスト後に実行されるようpackageにしてみました。

再デプロイをしたくなければ、goalにdeployを指定します。

configuration以下には設定を記述します。
pathはデプロイ先、urlはTomcat ManagerのURLを指定します。

また、tomcatへログインする為の設定ファイルが必要です。
設定ファイルはユーザのフォルダ直下にある.m2フォルダに
用意します。

[.m2/settings.xml]
 <settings>
   <servers>
     <server>
       <id>tomcat-localhost</id>
       <username>
tomcat</username>
       <password>
tomcat</password>
     </server>
   </servers>
 </settings>

Tomcat Managerにアクセスする必要があるので
Tomcatのmanager-script権限を持ったユーザの
名前とパスワードを指定します。

今回までの設定によりMavenのpackageフェーズ以降まで
実行すればコンパイル、ユニットテスト、Warファイルの作成、
Tomcatへのデプロイといったことが自動的に行われます。


勿論、ユニットテストでエラーがあればWarファイルの作成や
デプロイは行われません。
一度、設定してしまえば、これらの作業が自動的に
行われるので、かなり便利ではないでしょうか。
Mavenでユニットテストを自動化するにはpom.xmlを変更する必要が
あります。

以下、サンプルです。

 <project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven- v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>パッケージ名</groupId>
   <artifactId>プロジェクト名</artifactId>
   <packaging>war</packaging>
   <version>0.0.1-SNAPSHOT</version>
   <name>名前</name>
   <url>http://maven.apache.org</url>
   <dependencies>
     <dependency>
       <groupId>
junit</groupId>
       <artifactId>
junit</artifactId>
       <version>
4.0</version>
       <scope>
compile</scope>
     </dependency>
   </dependencies>
   <build>
     <finalName>ファイル名</finalName>
     <plugins>
         <!-- For Juit 4 -->
       <plugin>
         <artifactId>
maven-surefire-plugin</artifactId>
         <version>
2.4.2</version>
       </plugin>
     </plugins>
   </build>
 </project>

MavenはデフォルトではJUnitは3系が動くので
4系で使う場合は上記のようにpluginにmaven-surefire-pluginを
dependencyにjunitを追加します。

テスト対象のファイルはsrc/main/java以下に
テストコードはsrc/test/java以下に置くのがふつうです。
Mavenのアーキテクトをwebappにすると、その二つが
ないのでソースフォルダとして作っておきます。

で、その状態で「実行」→「Maven Test」とすることで
Compile後にユニットテストが実行されます。



もし、下記のように



「Unable to locate the Javac Compiler in:~\tools.jar」
というメッセージが表示されるならば
「ウィンドウ」→「設定」→「インストール済みのJRE」を選択して
「編集」→「外部Jar追加」で使用しているJavaのlib配下にある
tools.jarを追加して下さい。

今回と前回の設定によりWarファイルを作成すると同時に
ユニットテストが動くようになりました。

次回はさらに、できたWarファイルをデプロイするよう設定します。

はるか昔に紹介したMavenですが、また使い始めたのでメモ。

Mavenとはかんたんに言えば
JarやWarの作成、テストやアップロード等の
作業をまとめて一気にやってくれるツールです。

まとめてやってくれるので一つずつやらなくていいし
一部の作業を忘れたり順番間違えたりしなくなる。

Eclipseから使う為に「m2eclipse」というプラグインをインストール。
インストールするにはEclipseの「ヘルプ」メニューから
「新規ソフトウェアのインストール」を選択。



このように検索欄に「m2e」と入力すれば表示されます。
あとはいつも通り選択していけばインストールされます。

インストールしたら早速プロジェクトを作ってみます。

「新規」→「プロジェクト」→「Maven」→「Maven Project」
アーキタイプ選択画面で「maven-archetype-webapp」を選択。
※一覧の表示に時間がかかります


アーキタイプというのは作りたいプロジェクトの種類になります。

アーキタイプのパラメータ画面では
グループId, パッケージはパッケージ名を入れるのが一般的で
アーティファクトIdはプロジェクト名を入れるのが一般的らしい。


で、完了。

ここまででプロジェクトが作成されるのですが
最初から簡単な設定がされているので既にMavenが使える状態。


プロジェクトを選択して「実行」→「Maven Install」を実行した所。
無事、成功しています。

もし、実行して下図のように
「[ERROR] Unable to locate the Javac Compiler in:」と
エラーが出る場合は



「ウィンドウ」→「設定」→「Java」→「インストール済みのJava」→
「追加」でJDKを追加してあげれば上記エラーが出なくなります。



Maven Installで作成されたWarファイルをデプロイすると
このようにHello World!が表示されます。


「Maven Install」を実行した事でコンパイル、テスト、パッケージング、
ローカルリポジトリへの登録という一連の処理が行われています。

コンパイルはファイル別にEclipseが自動的に普段行いますし
テスト用クラスが無いので今回はパッケージング(War作成)
とローカルリポジトリへの登録だけしか行われません。
その為、便利に感じないでしょうけど、今後は色々な機能を
紹介していきます。

よく使うEclipseのショートカットを割と忘れがちなのでまとめてみる。

■ショートカット一覧の表示(Ctrl + Shift + L)
これだけ覚えておけばある意味OK。
登録ショートカットの一覧が表示されます。


■補完(Ctrl + Space)
これを一番使う人も多いんじゃないでしょうか?
ソースの補完です。
複数の補完候補がある場合は以下のように選択できます。


■コメントアウト(Ctrl + /) 

これも便利。現在の行をコメントアウトします。
もう一回やれば元の状態に戻る。

■クイックフィックス(Ctrl + 1)

見づらいけどCtrlと 1 ( イチ )です。
豆電球のアイコンをクリックするのと同動作になります。



■呼出し階層を開く(Ctrl + Alt + H)

メソッドやメンバ変数上で「呼出し階層を開く」を実行する事で
呼出し先の階層が表示されます。(上図)
また、表示された呼出し先のメソッドをクリックする事で
そのメソッドへ飛ぶことができます。(下図)



■宣言を開く(Ctrl + クリック  or F3)

上記、呼出し階層を開くの丁度逆の動作で呼出し側でなく
呼び出されている側に飛ぶことができます。

■型を開く(Ctrl + Shift + T)

小さいウィンドウが表示され、クラスの検索が行えます。



■検索(Ctrl + F)

これは人によっては一番使うかもしれない。
検索や置換を行うショートカットです。



■検索ダイアログ表示(Ctrl + H)
細かく色々な指定で検索できます。
これもよく使う。


■エディタの切替え(Ctrl + E)
エディタの一覧が表示されて選択できます。



■アウトラインの表示(Ctrl + O)
これを覚えておけばアウトラインのビューは消しておいても良さそう。
選んだメソッドに飛べます。



■実装を開く(Ctrl + T)
インタフェースのメソッド名で実行すれば実装をツリー表示できます。



■インクリメンタルサーチ(Ctrl + J, Ctrl + Shift + J)
入力した文字列で検索する。
1文字入力するたびに検索されるので途中で見つけやすい。
Googleみたいな感じです。
Ctrl + Shift + Jは逆方向を検索する。



■インデントの訂正(Ctrl + I)
インデントがずれていた場合、修正してくれる。

これが

こうなる。


単純なインデントと違って、この状態でもう一回やっても
変化はしない。あくまでも訂正です。

■行削除(Ctrl + D)
現在行を削除

■行追加(Ctrl + J)
カーソル位置に関係なく下に行を追加

■セーブ(Ctrl + S)
自分はこれを一番使ってる気がします。
落ちたときの為にちょくちょくセーブ。

インターフェイス分離の原則(ISP)とは簡単に言ってしまえば
機能を持ち過ぎたインタフェースを作ってはいけないと言う事です。

インタフェースに存在するメソッドを実装するクラスは
全てのメソッドを実装する必要があるので、適切でない
インタフェースを実装した場合、変更による影響を
受けてしまいやすいという原則です。

例えば

[Storage.java]
 public interface Storage {
   public void save();
 }

[DBStorage.java]
 public class DBStorage implements Storage {
 @Override
 public void save() { ~ }
 }

[FileStorage.java]
 public class FileStorage implements Storage {
 @Override
 public void save() { ~ }
 }

上記のようにStorageインタフェースを実装したDBStorage、FileStorage
クラスがあります。
さらにDBStorageクラスでrollbackメソッドが欲しくなったとします。

この時、下記のようにStorageインタフェースにrollbackメソッドを
追加してしまうと本来、FileStorageクラスでは必要無かった
Rollbackメソッドを実装する必要がでてしまいます。

[Storage.java]
 public interface Storage {
   public void save();
   public void rollback();
 }

そのような無駄なメソッドを実装している状況がインタフェース分離の
原則から外れてしまっている状況になります。

もし、多くの機能が必要になった場合には複数のインタフェースを
実装するか、インタフェースを別のインタフェースが継承したものを
作る事でカバーするのがよいです。

継承したものを作った場合が下記です。

[StorageIF.java]
 public interface StorageIF {
public void save();
 }

[DBStorageIF.java]
 public interface DBStorageIF extends StorageIF {
 public void rollback();
 }

[FileStorageIF.java]
 public interface FileStorageIF extends StorageIF}

[FileStorage.java]
 public class FileStorage implements FileStorageIF {
 @Override
 public void save() { ~ }
 }

[DBStorage.java]
 public class DBStorage implements DBStorageIF {
 @Override
 public void save() { ~ }

 @Override
 public void rollback() { ~ }
 }

インタフェースを継承し、個々のクラスに合う形のインタフェースを
用意しています。

今までクラスの継承は問題が起きることも多いと解説してきましたが
インタフェースの場合はあまり問題が起きないので継承も使いやすいです。
(インタフェース同士で委譲はできませんし)

必要とするものだけを作り、無駄なものを実装しない。
Keep It Simple, Stupid(シンプルにしておけ、間抜け!)※KISSの原則
が重要ですね。
Sakura Editorは知名度の高い純国産エディタです。
IDEが重いときや簡単な作業等で重宝します。

以下で配布されてます。
http://sakura-editor.sourceforge.net/

このエディタは多彩な機能を持っていますが、使いこなせていない人も
多いので今回から少しずつ機能を紹介していこうと思います。

まずは地味ながら使い勝手に直結しやすい範囲選択です。


1.ALT+ドラッグ
これは四角形に文字を選択できます。
できないエディタも多いので便利。



2.CTRL+クリック(ダブルクリック)
単語を選択します。よく使う。



3.ATL+A(4回クリック)
ファイル全体を選択します。
上二つよりは使用頻度少ないでしょうが便利です。



今回はこれだけ。今後も少しずつサクラエディタの記事を書いていきます。

「継承よりコンポジション」とは有名なJava本、EffectiveJavaの言葉
ですが最近、共感する事が多かった為、改めてまとめてみます。

継承とは知っての通り、親の特性(メソッド、メンバ)を引き継ぐ事です。
これがメリットにもデメリットにもなり得ます。

親の持つ特性を利用できるというメリットは分かりやすく
大抵の継承の説明でされています。

■問題点
デメリットの説明はあまりされていません。
デメリットはPrivate以外全ての特性を引き継いでしまうという事です。
(引き継ぐ対象を指定できない)

これが問題を引き起こします。

■例

 public class FileLog {
   protected String fileName;

   protected String message;


   public FileLog(String fileName) {
     this.fileName = fileName;
   }


   public void changeFile(String fileName) {
     this.fileName = fileName;
   }


   public void print(String message) throws IOException {
     PrintWriter pw;
     pw = new PrintWriter(new BufferedWriter(new FileWriter(new File(fileName))));
     pw.println(message);
     pw.close();
   }
 }

上記のようなファイル出力用クラスがあったとします。
また、それを利用した以下のような、ファイル名に日付を自動的につける
子クラスをつくったとします。
※サンプルなんでクラス設計がおかしいことは気にしないで下さい

 public class TimeFileLog extends FileLog {
   public TimeFileLog(String fileName) {
     super(fileName);
     SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyMMdd_");
     this.fileName = sdf1.format(new Date()) + fileName;
   }
 }


実はこの時、欠陥が存在します。

 TimeFileLog timeFile = new TimeFileLog("test.txt");
 timeFile.changeFile("test2.txt");
 timeFile.print("Hello");


本来はファイル名に日付をつけるはずが親クラスのメソッドを利用する
事で子クラス日付がつかないファイル名とする事ができてしまいます。
もちろん、この動作を子クラス作成者が理解できていれば問題は
ありません。

しかし、このようにオーバライドしなかったメソッドを利用された場合の
動作を想定するのは難しいことで、気づかずにこのような状況に
なってしまう事が多いです。

子クラスを作るときに気付くのでは無いかと思う人もいるでしょう。
しかし、子クラスを作成してから親クラスが拡張されて上記のような
欠陥が生まれてしまう事も多いですし、メソッドが多くなってくると
見逃す可能性も増えます。

つまり、継承を使う事で子クラスが意図しないメソッドまでを
引き継いでしまう事が問題の原因です。

■解決策
解決策はコンポジションです。
子クラスにあたるものをコンポジションを利用する事で
作成者が意図した特性のみを引き継げます。
前述のTimeFileLogというクラスをコンポジションを利用するよう
変更したものが下記です。

 public class TimeFileLog {
   private FileLog fileLog;

   public TimeFileLog(String fileName) {
     SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyMMdd_");
     String _fileName = sdf1.format(new Date()) + fileName;
     this.fileLog = new FileLog(_fileName);
   }

   public void
print(String message) throws IOException {
     
fileLog.print(message);
   }
 }

拡張しないメソッドについては元クラスのメソッドに
そのまま引数を渡すだけです。(printメソッドのように)

このように作っておけば親クラスが知らない間に拡張されようが
上記の問題が生じません。

使用するものを全部、明示的に記述するのは多少の手間ですが
その分、動作をしっかり保障したものとなります。