スケジュールの役割 | アート・オブ・プロジェクトマネジメント

私たちは日々、スケジュールと共に生活しています。そして、頻繁に遅れます。 飲み会の開始時間、DVDの返却期限、クリスマスプレゼントの用意、そして仕事。 私などは自分が遅れることを知っていますし、遅れない計画の建て方もよくわからないので、ついスケジュールを立てることに消極的になってしまいます。

あの悩ましいスケジュールとは、一体なんなのでしょうか。また、どうやって立てればいいのでしょうか。

スケジュールの役目は、厳守されることだけではない

『アート・オブ・プロジェクトマネジメント』は、スケジュールの役割は3つあると言っています。

  • いつものごとが完了するのかを表す
  • チーム全体における個人の成果物の位置づけを理解させ、書くメンバーの協調を促進させる
  • 作業を管理可能な塊に分割するツールをチームに与える

です。 たしかに、状況が不確定すぎるからと仕事の完了期限を濁していて、チームメンバーに困った顔をされた経験があります。 状況が不確定でもいったん期限を切る、という行動の裏には、チームの協調を図るという目的もあったのです。 自信がないからといって叩き台にできるスケジュールも提供しないのでは、マネジメントもなにもできません。

じゃあどうやって、そのスケジュールを立てるのさ? 1/3の法則

といっても、守れもしない適当な期限でばかり合意したら、それこそアドレナリン・ジャンキーになってしまいます。現実的なスケジュールは、どうやったら立てられるんでしょうか。

本著はそこで「1/3の法則」を提唱しています。 1/3の法則とは、どんなスケジュールでもざっくり分ければ「計画 / 実行 / 検証」の3ステップに分けられるから、その3ステップのそれぞれにかかる時間をイメージしてみれば、そんなにズレのないスケジュールができるでしょ!という豪快な法則です。

この法則は、対象が自分1人でやる作業であったとしても、昔ながらのウォーターフォール型の開発モデルであったとしても、あるいはスクラム開発であったとしても、あてはめることができます。 自分1人の作業なら「何をするか、どうやってするか」を考え「実際に手を動かし」て「それが要件にあっていることを確認する」のが3つにわけられます。ウォーターフォールモデルでは対象が巨大になりますが同じです。そしてスクラム開発は、小さな1/3の法則の集合といえます。

そのスケジュールの正しさをどう担保しよう?

1/3の法則で作ったスケジュールは、どうして正しいといえるのでしょうか? 本著の著者、Scottさんは

初期計画時点でスケジュールを作成する場合、そのスケジュールに影響を与える、星の数ほどある意思決定は、まだ行われていないはずです。…(中略)… このため、でっち上げた数字と乱暴な予想で粗いスケジュールを作り上げ、それを角度の高いプロジェクト計画としてチームに引き渡すことになります。

と書いています。つまり、当たり前のことですが不確定な状況で正しいスケジュールは立てられないということです。 だから優れたスケジュールを立てるには、不確定な情報のない優れた設計が必要です。 優れたスケジュールは、見積もりを行うエンジニアが十分だと納得する程度に詳細な設計情報、エンジニアへの信頼、そしてなにより、チームメンバーが「スケジュールは確率と予想の掛け算であり、ブレ得るものだ」という前提に立っていることから生まれるのです。

本著に書いてあるけれど、本稿に書いていない具体的なこと

他にもこの章では、スケジュールを立てる時についつい見過ごしがちなチェックポイントのリストや(連休期間中の稼働時間は短めにしてあるか?)や、スケジュールを機能させるためにすべきことのリスト(リスクには先に対処する)が盛り込んであります。

http://amzn.asia/ieLFu8M

プロジェクトとは?マネジメントとは?そして、マネージャとは? | アート・オブ・プロジェクトマネジメント

  • 平易な言葉で書かれたマネジメント全体を説明する書籍

WillCan面談で決めた目標を達成するために、上司であるところの @kawasima さんからいただいた書籍のひとつ、アート・オブ・プロジェクトマネジメントを読み始めました。 本著は、体系立っている一方で難しい言葉を使わずに、プロジェクトマネジメント全体について説明した書籍です。目次だけ見ても、歴史にはじまりスケジュールの引き方、ビジョンについて、コミュニケーションの問題、プロジェクトの終わらせ方に社内政治など、様々なテーマが詰まっています。 その分厚みもあり、ページ数なんと400P超え!息切れしないように、1周目である今回は適度に飛ばしつつ全体像を把握していこうとおもいます。

そして今回は1章を読んだので、それについて書きます。

「プロジェクト」は古今東西にある、共通した骨組みを持つモノ

プロジェクトは古今東西に偏在してきた概念です。言われてみればそうですね。プロジェクトには目標があって、予算があって、納期があります。それは正倉院を作るのだって宇宙船を作るのだって映画を作るのだって、あるいはクレジットカードの決済基盤を作るのだって同じです。 そしてそれらは共通しているために、比較可能なはずです。ということは、今自分が進めているプロジェクトは、今まで行われてきたすべてのプロジェクトから何か学ぶことができるはずです。しかし現実はそうなっていません。

その要因のひとつは、見た目があまりに違うとそもそも「比較できる」と信じ難いことです。実際私も、ピラミッドの建造とWordpressサイト構築を比べて何か学べと言われても、想像もつかないです。 だから、現代の他の産業のプロジェクトマネジメント手法と、ソフトウェア開発を比較しましょう。私は学生時代、公文の先生のバイトをしていました。とても素敵な教室だったので、今はあの教室の空気を職場で再現するのが人生の目標です。あの教室と今の職場で、違うのはなんでしょう?そして同じものはなんでしょうか。

プロジェクトマネージャはプロジェクトを成功に導くリーダー

プロジェクトマネージャは、あるコードを美しく動かすことではなくプロジェクト全体の面倒を見ることで成功をもたらすリーダーです。 プロジェクトマネージャはある時は情熱家、ある時は現実主義者でなければありません。またある時は明確なドキュメントを求め、別の時は口頭での曖昧な説明を求める必要があるかもしれません。

しかしその職務内容は曖昧です。ただの観測者ではいけないし、管理したがりも邪魔なだけ。必要なのは、チームメンバーを助け、アツさを持って良いものを作り上げようとすることだけ。具体的には…なにをしたらいいんでしょうか?

それは今後の章のお楽しみ。

平易な言葉のシンプルさと難しさ

私は本著を読み始めたところなのですが、「平易な言葉での説明を貫く」という方法はとても好きです。私が書くもの、話すことにおいてもかくありたいです。ご覧の通り、まだまだなわけですが…。

本著の中でも述べられているのですが、シンプルな本質を見つけることで集中を高められる一方で、シンプルであることは簡単であることと直結しません。マラソンは「42.195km、一番早く走りきる」というシンプルなルールでできた競技ですが、決して簡単な競技ではありませんしね。 そう思うにつけ、「プロジェクト」などという巨大で難解なものをシンプルな言葉で語ろうという本著が気に入ります。 
http://amzn.asia/i2LR6mg

『アドレナリンジャンキー』を笑えなくなったらヤバい

『アドレナリンジャンキー』を読んだ。

『アドレナリンジャンキー』はソフトウェア開発あるあるネタ

『アドレナリンジャンキー』は、他にもユーモラスな本をいくつも書いているトム・デマルコ氏の著作です。本著は彼が仲間と一緒に、ソフトウェア開発現場での「あるあるネタ」をたくさん集めて本にしてやったぜ!という、あるあるネタ集です。 「あーこれ見たことあるわ」というのから、「言われてみればそうだけど、いつかこういう状況に鉢合わせることもあるんやろな…」というようなハッとさせられるようなものまで、様々なあるあるネタが満載です。

最近Twitterで時々流れてきていた「悪い情報は上司に登るほど美化される」みたいのも、2009年に刊行された本著にバッチリ収録されています。

輪読したら楽しそう

本著はどれも選りすぐりのあるあるネタなので、どのネタも心動かされること間違いなしです。だから「○○が一番おもしろかった」とは本著に関しては言えません…。 強いて言うなら、こんなに秀逸なあるあるネタがこんなに集まってること自体が最高の良い点です。

部内の先輩・後輩含めた幅広い年齢層で輪読、というかみんなでスライドに表示したKindleの画面を見ながら話すだけでも楽しそうだと思います。そしたら「反省会をしよう」と誰かが言った時、みんなで「あっ、これやり方に気をつけないとやばいやつ」と思えたりするんじゃないかなと思います。そういう共通の温度感を養う入り口に使ってみたいなと思いました。

ぜひ手にとってみてください

「テンプレートゾンビ」「魂の仲間」「隠れた美」「機能のスープ」…。どんな意味か想像がつきますか?きっと想像と、そんなに外れてはいないと思います。 シンプルでニヤッとできる寓話であるあるネタを楽しみたい人は、ぜひ手にとってみてください。きっと楽しめると思いますよ。

http://amzn.asia/dCDUGbD

『ハッカーと画家』から、富について学んだ

ハッカーと画家を読みました。

ハッカーと画家』はWebアプリの祖が書いたエッセイ

ポール・グレアム氏が、ソフトウェア開発やサービス開発、ベンチャー企業などについて綴ったエッセイ…なんでしょうか。

刊行されたのはずいぶん前で、しかしもうWebベースのアプリケーションが当たり前になった時代。それを最初に切り拓いたグレアム氏から見た、インターネットの過去・現在・未来を語ってくれます。

富は、それを生み出した人が手に入れる

この本には「いつか誰でもどこからでもインターネットに接続できるようになる。」「デバイスが小型化する」など、慧眼といえる未来予測も入っていて、それも面白いです。

しかし、僕が一番おもしろいと思ったのは、

「限られた人が大きな富を持つことは、『富は奪い合うのではなく、作り出すから手に入る』という前提が成り立つ社会では間違いではない」

という主張でした。

世の中では「人口の数%が世界の富の何%を独占している」というような主張が、とても悪いこととして喧伝されることがあります。僕も今まではなんとなくそれは良くないことのように思っていました。

ところがグレアム氏は、僕らがそう思うのは荘園制の時代の思い込みである、といいます。つまり、領主が無条件に農奴から富を奪い、農奴は富を奪われたから貧しいという状況なら、富の独占は悪いことと思われたはずだ、というのです。しかし現在はそうではない。MicrosoftやかつてのIBMがあんなに巨額の富を築いたのは、彼らが世の中にそれだけ大きな新しい富を作り出したからだ、というのです。

たしかに!と思いました。もしMicrosoftWindowsを作らなければ、僕らはいまだに紙に手書きでグラフを書いていたかもしれません。それなら、Microsoftが富むのは世界を良くした当然の報酬だと思いました。

ぜひ手に取ってください

ハッカーと画家』は、こんなワクワクし、ドキッとされられ、悩まされる話が詰まった本です。世界で初めてのWebアプリを作って公開し、Yahoo!に売却した彼が、どんな哲学を持ってそれを成し遂げたのか。

気になる方はぜひ手にとってみてください。きっと楽しめると思いますよ。

http://amzn.asia/6rxuQ17

SpringframeworkからはじめるSpringBoot Part.1 - 依存性注入

承前

SpringBootを触れるようになったけど仕組みが全然分からなくて応用できない!から、やっぱり内部が分からないとと思ってSpringframeworkから見直してみました。 誤った情報を広めたいわけではないのですが、それでも理解がおかしい部分はあるかもしれません。その時はコメントください。

この記事に書いてあること

  • springframeworkのDIとは?
  • xmlでDIを定義する
  • 実際に定義したDIが実行されていることを、サンプルコードで確認する

まずDI(依存性注入)

それでは、まずDIからはじめましょう。これがSpringframeworkの根っこです。

そもそもSpringframeworkにとってのDIって何?

DIというものについてのもっと良い情報は世の中に山ほど公開されています。しかしここでは改めて解説させてください。 ちなみに今調べたら他の資料としては猿でも分かる! Dependency Injection: 依存性の注入 - Qiitaなどが見つかりました。猿でも分かるそうなので人類ならばきっと理解できると思います。

Springframeworkの公式ドキュメントでDIについて言及している部分にはこう書いてあります。

IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method.(引用元は公式ドキュメント)

(これは実はIoCについての記述なのですが)IoCは依存性注入(DI)という名前でも知られています。これはオブジェクトが自分の依存性(動作に必要な別のオブジェクト群)を、自分がインスタンス化されたりファクトリーメソッドから返却された後で、コンストラクタやファクトリーメソッドの引数、オブジェクトのインスタンスの変数のみによって定義する手段です。

つまりあるオブジェクトが使う別のオブジェクトを、なにかより柔軟な方法で定義できる、ということのようですね。

その後の方には、

  • 普通はBean(オブジェクト)自身が、自分が使うオブジェクトを規定するはずなのに、ここではDI ContainerがBeanを構築する時にそいつの使うオブジェクトを決める。これって逆転してるよね。だからIoC(Inversion of Control, 制御の逆転)っていうんだよ。
  • SpringframeworkではIoCを実現するためにorg.springframework.beansorg.springframework.contextってパッケージを使うよ。
  • BeanFactoryはオブジェクトの設定を行う基本的な機能を提供して、ApplicationContextでより実用的な機能を提供するよ。
  • ちなみにさらっと出てきてたけどBeanっていうのはIoCコンテナによってインスタンス化され管理されるオブジェクトのことだよ。

みたいなことが書いてあります。どれも目にしたことのある言葉ばかりですね。結構読みやすいのでぜひ一度目を透してみてください。今後ググった時にSpringの公式ドキュメントが出てきた時にビビらずに済むようになることでしょう。

さて、これでDIについてピンと来たでしょうか?私は当時全く何もピンと来ませんでした。 ですから、手を動かしてみましょう。

DIだけ試してみよう

ここで「ではSpringBootのプロジェクトを作ってAPIを作って…」みたいなことをやると、いろいろややこしいものが入りすぎて何がなんだかわからなくなるので、まずはDIだけを試してみましょう。

SpringframeworkでDIを試すには、 spring-context というライブラリを使います。 公式ドキュメントによればspring-contextは、

it is a means to access objects in a framework-style manner that is similar to a JNDI registry.(引用元はこちらの第2段落)

JNDIレジストリみたいなフレームワークのお作法に則ったやり方でオブジェクトにアクセスする手段です。 この「アクセスする手段」こそがDI(依存性注入)というわけですね。 spring-contextはその依存ライブラリに

  • spring-core
  • spring-bean

などを持っています。上で出てきたorg.springframework.beansパッケージが含まれていそうですね。

1.プロジェクトの準備

さて、では本当に手を動かしていきましょう。 まず元となるプロジェクトを作りましょう。

mvn archetype:generate

対話式でgroupIdなどを尋ねられるので好きなように設定してください。終わったらIntelliJなどのIDEに取り込みます。 古めのJUnitの依存が入ってますが使わないので消してあげましょう。

  <!-- pom.xml -->
  </properties>

  <dependencies>
-   <dependency>
-     <groupId>junit</groupId>
-     <artifactId>junit</artifactId>
-     <version>3.8.1</version>
-     <scope>test</scope>
-   </dependency>
  </dependencies>
</project>

src/test配下も使わないので削除しておいてください。

そしてspring-contextの依存を追加してあげましょう。

  <!-- pom.xml -->

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+   <spring.version>4.2.4.RELEASE</spring.version>
  </properties>

  <dependencies>
  </properties>

  <dependencies>
+   <dependency>
+     <groupId>org.springframework</groupId>
+     <artifactId>spring-context</artifactId>
+     <version>${spring.version}</version>
+   </dependency>
  </dependencies>
</project>

これでSpringframeworkのDIが使えるようになりました。

2.Beanオブジェクトの実装

今回は、文字列を標準出力するサービスクラスを用意して、それを呼び出してDIを体験してみましょう。

sayHelloメソッドを定義するHellointerfaceと、それを実装して文字列を標準出力するクラスを2つ、用意しましょう。

package jp.blackawa.examples;

/**
 * 文字列を標準出力するメソッドを定義するインターフェース.
 */
public interface Hello {
    void sayHello();
}
package jp.blackawa.examples;

public class HelloBoys implements Hello {
    public void sayHello() {
        System.out.println("Hello, Boys!");
    }
}
package jp.blackawa.examples;

public class HelloGirls implements Hello {
    public void sayHello() {
        System.out.println("Hello, Girls!");
    }
}

Helloを使用する( = Helloに依存する)サービスクラスも用意しておきましょう。SpringBootではビジネスロジックをよくサービスクラスに書きますよね。

package jp.blackawa.examples;

/**
 * 挨拶を出力するサービスクラス.
 */
public class HelloService {
    private Hello hello;

    // Getter, Setterを用意しておく.
    public void setHello(Hello hello) {
        this.hello = hello;
    }

    public Hello getHello() {
        return hello;
    }
}

Hellointerfaceの実装は2つ用意されています(HelloBoys, HelloGirls)が、ここまでではHelloServiceがどちらに依存するかは決まっていません。 なんだかDIっぽくなってきましたね。そんな感じがしませんか?

3.Bean定義の用意

では、このプロジェクトがどっちのHellointerfaceの実装を使うのか、決めてあげましょう。 それにはxmlファイルを使います。懐かしいですね。 xmlファイルによるBean定義は、SpringBootが採用しているアノテーションJavaベースの設定よりも冗長で記述量も多く、エラーにも気づきにくいです。しかしその分xml定義の方が何が起こっているか明確に分かるはずです。

src/main/resources配下にbeans.xmlという名前でxmlファイルを用意してください。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

ここからが重要なので、段階的にいきましょう。上記の空ファイルができたら、次にBeanを登録しましょう。

Beanとはなんでしたっけ?SpringframeworkのDIコンテナで管理するオブジェクトでしたね。今回はHellointerfaceの2つの実装クラスをDIによって使い分けたいので、これらを登録すれば良さそうです。

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd">
+    <bean id="helloBoys"
+          class="jp.blackawa.examples.HelloBoys"/>
+    <bean id="helloGirls"
+          class="jp.blackawa.examples.HelloGirls"/>
</beans>

2つのBeanにIDとそれに紐づくclassが定義されているのが一目瞭然ですね。DIっぽくなってきましたね。

そして最後に、HelloServiceクラスが使用(依存)するのがどちらのHellointerfaceの実装クラスなのかを定義してあげましょう。

    <bean id="helloGirls"
          class="jp.blackawa.examples.HelloGirls"/>

+    <bean id="helloService"
+          class="jp.blackawa.examples.HelloService">
+        <property name="hello" ref="helloBoys" />
+    </bean>
</beans>

HelloServiceクラスをBeanとして登録しました。 そしてHelloServiceクラスの変数(property)であるhelloの参照を、HelloBoysに定義します。 なんと、あるクラスの変数が保持するインスタンスをBean定義のxmlファイルから定義してしまっています!IoC、制御の逆転というのがなんとなく感じられますね!

4.HelloServiceを使ってみよう

では動かしてみましょう。SpringBootを通して、このようなBeanが思い通りのオブジェクトを使って動いてくれるのは知っていると思いますが、こういうのは実際に動かしてこそ面白いものです。

mainメソッドからHelloServiceを呼び出して、変数として保持しているHellointerfaceの実装を取得してみましょう。

package jp.blackawa.examples;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main( String[] args ) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        HelloService service = (HelloService) context.getBean("helloService");
        Hello hw = service.getHello();
        hw.sayHello();
    }
}

読んで字の如く、

  1. クラスパス上のxmlファイルからApplicationContextを取得する。(ApplicationContextってなんでしたっけ?)
  2. そのApplicationContextからhelloServiceを取得する。
  3. HelloServiceの中に入っているHellointerfaceの実装クラスを取得し、文字列を出力させる

をしているように見えますね。そして実際にそうしています。

では…お持ちのIDEでこのmainメソッドを実行してみてください! コンソールにHello, Boys!と出力されますね。

おめでとうございます。これがDIです。

beans.xmlの定義を変えてみましょう。

    <bean id="helloService"
          class="jp.blackawa.examples.HelloService">
-        <property name="hello" ref="helloBoys" />
+        <property name="hello" ref="helloGirls" />
    </bean>
</beans>

Javaソースコードを一切いじらないままに出力内容がHello, Girls!に変わりますね。

おめでとうございます。

ちなみに、mainメソッド内で直接HelloServiceを呼ぶことも可能ですが、そうするとBean登録されていないサービスクラスを使うことになるので、getHelloから何も取得できません。だってどこでもその変数の初期化をしていませんからね。 普通ならHelloServiceインスタンス化する時に引数としてHellointerfaceの実装クラスを渡すはずなのに、おかしいですね。制御、逆転していますね。

肌で感じられましたか?

さて、ここまででSpringframeworkのうちDIだけを試してきましたが、肌で感じられましたか? これでSpringBootのうち@Autowired@Component@Serviceなどは怖くなくなりましたね! アノテーションをつけると、今日書いたような設定がうまいことやってもらえてControllerクラスから好きなServiceクラスが使用できるんですね。

次回はただの文字列を出力するのではなく、Webアプリケーションを作ってみましょう。まだxmlでの設定はまだ続きますよ。 今回もそのおかげで仕組みがよく分かりましたしね。

参考資料

SpringFrameworkから始めるSpringBoot Part.0

SpringBootってよく分からない?

みなさん、SpringBoot、つかってますか?

使ったことがない方へ。 最低限Getting Started Guideをいくつかやったことがないと、この先は読み進めても面白くないです。今すぐコーヒーを淹れて、1時間ほどSpringの世界を楽しんできてください。それから次の行を読んでください。

SpringBootは簡単でシンプル?

さて、みなさんはSpringBootにどんなイメージをお持ちですか?私は仕事で使ってみるまで、 開発に必要な機能が全部揃ってて簡単に始められてシンプルでJava開発ではよく聞くWebフレームワークみたいなイメージでした。

@SpringBootApplication // こう書いとけばSpringBootだってことになる
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
@Controller // こう書いとけばリクエストを受け付けるクラスになる
public class UserController {
    @RequestMapping("/user/hoge") // こう書いとけばこのURLにリクエストが来た時このクラスが動く
    public String index() {
        return "user/index"; // こう書いとけばあるディレクトリのHTMLファイルが勝手に呼び出される
    }
}

「こう書いとけば○○」って便利ー!シンプルですごいなー!って私は思ってました。

でもこれが通用するのはチュートリアルをやっている間だけでした。

HTMLの置き場を変えなければいけなくなったらどうしたらいいの? DB接続の方法を変えなければいけなくなったらどうしたらいいの? UTF-8以外の文字コードが使いたい時どうしたらいいの? どこにも何も書いてないのに変な挙動をする... etc...

その他すべての、便利でシンプルな方法では解決できない問題を、私は解決できませんでした。

どうやらSpringBootは、少なくとも簡単ではないようです。

SpringBootは巨大なブラックボックス

たったひとつのアノテーションをつければ期待どおりに動くSpringBootが、なぜ少し道から外れたことをしようとすると途端に手も足も出ない複雑さで牙を向くのでしょうか。

答えは歴史が知っています。SpringBootはもともとWeb開発専用ではないライブラリをいくつもくっつけて、それらを隠して簡単に使えるようにした巨大なブラックボックスなのです。

DIライブラリから始まった

詳しくて正確な歴史はここでは必要ないのでより詳しい方にお任せしますが、SpringBootの元となっているのは、SpringFrameworkというDI用ライブラリです。

みなさん、SpringBootを使ってて@Autowiredとか@Serviceとか書いたことありませんか?あれがDIで、あれこそがそもそもSpringFrameworkが担っていたものです。

その後、そのDIライブラリを元にしてWebフレームワークやバッチフレームワーク、そして今やSNS連携やクラウド、マイクロサービスにも対応するようになった巨大なライブラリ群が、Springなのです。

そしてSpringBootはこれらのうち基礎的なWebアプリ開発用フレームワークを簡単に使えるようにしたツールです。その中にはもちろんDIが入っており、DB接続部品が入っており、Web開発用ライブラリも入っています。

こうしてみると、SpringBootが巨大なブラックボックスであることが少しずつ分かってきます。SpringBootは、もともとWeb開発専用に作られたわけではないツールをたくさん内包しているのです。

だから、少し違うことをしようとすると私たちはSpringFrameworkの中身と向き合うことになります。これが私が感じていた複雑さの原因でした。

避けてはもったいない!

さて、SpringBootはどうやら必ず簡単というわけではなさそうです。 でもこれはとてもワクワクすることだと思いませんか? SpringBootが隠してくれていた複雑さをそっと紐解いて眺めることができれば、きっともっとSpringBootと仲良くなれることでしょう。 HTMLテンプレートのディレクトリを自由に変える方法を知っていれば、胸を張ってコードを書いてよく眠れるかもしれません。

それに、これはJavaでのWeb開発やSpringFramworkが時代と一緒に試行錯誤してきた成果を少しだけ垣間見るチャンスにもなりそうです。

だから、「こう書いとけば○○する」の一歩先へ進んでみましょう。

というか進んできました

半年ほど、たまたま仕事でSpringBootを使う機会があって、SpringBootを構成するライブラリ群を少しずつ眺めてきました。 諸先輩方に比べればたいしたことのない道程ではありますが、それは楽しい時間だったので、少し時間は経っていますが改めて記事におこしてみようと思います。

次回は、SpringFrameworkからはじめましょう。

SpringBoot x Thymeleafでページングのパーツを作る

Webアプリケーションにそこそこよくあるものの一つに一覧表示とページングがあります。 SpringBootでは、JPAなどを使用すれば簡単にページング処理を使用してレコードを取得することができます。 しかしそのページングの操作をUIとして提供してくれる機構はザックリ調べた限り見つかりませんでした。 Railsならkaminariなどのページング用のコンポーネントがあるようですが…。

そこで、今後自前で実装する時に再利用できるように、コピペすればザックリ動くスニペット化したものを作りました。今日はその紹介をします。

まず実装イメージ

f:id:blackawa:20160320220940p:plain

このtableの上下の

  • n件中n~n件表示中
  • ページングのバー

が、今回作成した部分です。では構成を見ていきましょう。

今回のサンプルはgithubに、すぐに実行可能な状態で上がっていますのでそちらもご確認ください。

全体の構成

サーバサイドからは、ページングバーを描画するための情報や件数の情報を、専用のオブジェクトをThymeleafに渡して描画を行っています。

@Data
public class PagingView {
    private int totalRecordNum;
    private int fromRecordNum;
    private int toRecordNum;

    private int currentPageNum;

    private int recordPerPage;

    private boolean canGoNext;
    private String nextHref;

    private boolean canGoPrevious;
    private String previousHref;

    private boolean canGoLast;
    private String lastHref;

    private boolean canGoFirst;
    private String firstHref;

    private List<PagingViewElement> pagingViewElements;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PagingViewElement {
    private String name;
    private String href;
}

これをThymeleafに渡して、オブジェクトをバラしながら描画を行っていくことになります。

<!-- 件数表示 -->
<p th:text="
${paging.totalRecordNum} + '件中'
+ ${paging.fromRecordNum} + '~'
+ ${paging.toRecordNum} + '件表示中'">100件中1~10件表示中</p>
<!-- ページングバー -->
<div class="ui pagination small menu">
  <a class="icon item"
     th:classappend="!${paging.canGoFirst} ? 'disabled'"
     th:href="${paging.firstHref}"><i class="angle double left icon"></i></a>
  <a class="icon item"
     th:classappend="!${paging.canGoPrevious} ? 'disabled'"
     th:href="${paging.previousHref}"><i class="angle left icon"></i></a>
  <a class="item"
     th:each="element : ${paging.pagingViewElements}"
     th:href="${element.href}"
     th:text="${element.name}"
     th:classappend="${element.name} == ${paging.currentPageNum} ? 'disabled'"
  >1</a>
  <a href="#" class="icon item"
     th:classappend="!${paging.canGoNext} ? 'disabled'"
     th:href="${paging.nextHref}"><i class="angle right icon"></i></a>
  <a class="icon item"
     th:classappend="!${paging.canGoLast} ? 'disabled'"
     th:href="${paging.lastHref}"><i class="angle double right icon"></i></a>
</div>

サーバサイド

サーバサイドでは、件数を渡すのは簡単なのですがページングバーに表示する文字列とリンクの値を算出するのが少し複雑でした。 ページングバーのスペックは以下としました。

  • 現在のページがなるべく中央に表示される
  • 最初のページ、最後のページ、前ページ、次ページへのリンクが表示される。ただし遷移できる時にだけクリックできる
  • ページングバーのうち現在ページはクリックできない
  • ページングバーの長さは呼び出し元から指定できる

この要素を計算するロジックが以下です。

protected static List<PagingViewElement> generatePagingViewElements(
        int currentPageNum,
        int totalPageNum,
        int length,
        String preAppendPageNum
) {
    /* 偶数個のリストが要求された場合は現在のページが前寄せになる。
       例) [] がついているのが現在ページ
         << < 1 2 [3] 4 5 6 > >>
    */
    int backSpan = (length - 1) / 2;
    int forthSpan = (length - 1) - backSpan;

    int startIndex;
    int endIndex;

    if (currentPageNum - backSpan < 1) {
        // 表示幅に従うと存在しないページ(0ページ以下)が生成されるので、1ページから始める
        startIndex = 1;
        endIndex = length < totalPageNum ? length : totalPageNum;
    } else if (currentPageNum + forthSpan > totalPageNum) {
        // 表示幅に従うと存在しないページ(最終ページ以降)が生成されるので、表示領域を最終ページから逆算する
        startIndex = totalPageNum - (length - 1) > 1 ? totalPageNum - (length - 1) : 1;
        endIndex = totalPageNum;
    } else {
        // その間なので、中央にcurrentPageNumがくるように配置する。
        // ページのリストの端に当たっていないので、単純に中央にくるような両端を考えればよい。
        startIndex = currentPageNum - backSpan;
        endIndex = currentPageNum + forthSpan;
    }
    return IntStream.range(startIndex, endIndex + 1)
            .mapToObj(n -> new PagingViewElement(String.valueOf(n), preAppendPageNum + n))
            .collect(Collectors.toList());
}

クライアントサイド

クライアントサイドでは、渡された値を整理して描画を行います。

オブジェクト自身は今自分が何ページにいるかという情報のみを持っていてページングバーのどこをクリックできなくするかは知りません。そこで、ページングバーの要素を描画するたびに現在ページと突き合わせてクラス指定を制御しています。

th:classappend="${element.name} == ${paging.currentPageNum} ? 'disabled'"

まとめ

これらを組み合わせれば、ページングバーのフロントエンドを簡単に埋め込むことができます。

本当はjarとして配布してみたりしたいですが、htmlとjavaの混成をThymeleafで呼べる形で配布できるのでしょうか?

本格的に再利用を考えるならそのあたりも勘案する必要がありますね。