技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

設計はトップダウンとボトムアップ

設計のトップダウン

コードが設計だとしても、全ての設計が不要になるわけではない。

もちろん、Excel 方眼紙に書いた誰も読まないコード書いた方が早い設計書ではない。

まずは機能設計までと名前空間(パッケージやディレクトリ構成)だ。
何の事はない、機能ごとにパッケージを作りなさいという話だ。

私は MVC 形状の Web アプリを作る機会が多いので、例えばこんな感じになる。

  • root
    • (処理の種類、controllers, models, entities, utils 等)
      • (機能カテゴリ名、account, administrators)
        • (機能名、login, logout, signup 等)

はっきり言えば、この時点では機能一覧みたいな感じだ。
文章で言えば、「このアプリにある機能一覧」とか、本で言えば「目次」みたいなものだ。

もう一つあまりにも複雑になりそうな場合は、メモ書き程度のクラス図、メッセージ図、シーケンス図や、ものによっては状態遷移図も書く。*1

何故これしか書かないのかと言えば、それ以上の粒度で書いたとしても、(保守性も考慮して)なるだけシンプルに実装しようとすればどうしてもメソッドもクラスも増える。
つまり、これ以上細かい粒度で書いた所で、ドキュメント保守という名の、生産性とは程遠いメンテナンス地獄が発生してしまう。

設計のボトムアップ

上記で既に半分述べてしまったが、機能を実現するためのコーディングを開始する。
しかし、可読性を考えたり、コードをシンプルにしようとすると、メソッドが増えてくる。

例えば、ログインであれば下記のような手続きがあるだろう。

  • 画面からのリクエストをバリデーションする
  • 該当するアカウントをテーブルから取得する
  • 取り出したハッシュ済みパスワードと、画面から入力されたパスワードを検証する
  • ログイン済みのセッションを作成する
  • 画面応答を作成して返す

といった手順だろう。
当たり前だが、これらは全部メソッド化すべきだ。

何故なら、一つのメソッドで記述すれば、処理の区切りを発見しづらくなる。
つまり追いづらくなり、変数を使っていればその寿命がわからなくなる。

バリデーションにしても、各フィールドの定義、長さ、使用可能な文字などの設定もある。
当然この定義とバリデーションの実行(リクエストからのデータの取得等)も分離するだろう。

そういった事を読みやすくしようと考えれば、当然ステップごとにメソッド化するだろう。

一つのクラスにメソッドが増え過ぎれば、クラス分離を考える。
例えばバリデーションに関する定義クラスを分離するなどだ。

適切な名前さえつけておけば、コードの意味が読み取りやすく、何かあっても十分に分離しているので、修正箇所は一箇所。
それも他に影響を考えなくていい。

クラスが増えれば、種類ごとにサブパッケージを考える。

すると、当初からメソッドが増え、クラスが増え、パッケージが増える。
それは可読性を上げるための手段であり、日本語で無理に書かれた仕様書より保守性を上げる手段だ。

しかもこれは、設計からは見えてこない可読性に根ざした修正だ。つまりボトムアップでしか発生しない。

WF の限界

WF ではトップダウンで全て流れるから、このボトムアップな設計は許容されないだろう。
WF あるあるの 1 メソッド 100 行超えの超大作なんかは、この仕様書メンテナンスコストを嫌うために発生しているものが多い。
WF の後戻り工数はそれくらい重い。 (もしくは設計通りに作れという政治的な圧力があるか、開発者から考える頭が欠如してしまったかだ)

この辺りが WF という手法の限界だ。

ここまで予測できるのなら WF もありだろう。
だが、C言語メモリリークやポインタ操作ミスでクラッシュを招くように、C言語プログラマに全能性を求めてしまっている。
同様に、WF の流れは設計者に全能を求めてしまう。

その他考えてる事

Martin Fowler 氏に言わせれば、 コードはドキュメントである ということだ。

この論文では、作成したものを表現するという点に関して、ソースコードは、その実現方法を含む完全な形で全てが記載されているという点があげられる。
また、この文章の中に、「コードは設計である」 という話もある。

ここで WF の設計を考えると、その設計はそのままコードに置き換えられるか?不可能だ。
なぜなら、そこまでできる位なら、設計書をコンパイルした方が圧倒的に効率的だ。
そう考えて、かつて富士通は限定的とは言え 設計書をコンパイルして実行できるようにする 試みもしている。

現実的に広まったかは別として、面白い観点だ。
だがそれを WF の定義で見て設計と呼ぶのか?コンパイルして実行できるものを書く行為が?

結局の所、 コードを書くことと設計する事に差はほぼないという事だ

むしろ、エネルギーも「熱→水蒸気→(タービンにかけて)電気」と変換する上でエネルギーをロスするように、工程を増やして他人を介在すれば、それだけ原型の仕様が失われる。
そこを考えれば、設計/製造と分けることが現実と乖離していることがわかる。*2

*1:当たり前だが、納品する予定などない

*2:というか関数型のパラダイムシフトの中で、それを冗長化せずにラムダ入りのコードを日本語化できるならやってみろと言いたい

車輪の再発明は避ける

プログラマなら何度も聞いた筈。
でも、実際に車輪の再発明って結構やってるのでは?

車輪の再発明とは?

何のことはない、同じようなものを何回も書く事をいう。

例えば、AJAX API を作るという場合なら、XML/JSONシリアライズ/デシリアライズの環境を整えて、レスポンスヘッダを弄って、オブジェクトのシリアライズ結果をレスポンスのストリームに書き込む…

何言ってんだレベルだよね。

例えば Spring なら、コントローラに「RestController」をアノテートして、オブジェクトをそのまま返せば済む話だったりする。
Play なら、Json オブジェクトにクラスを食わせて結果を返す だね。

CSRF 対策だって、自前でセキュアなワンタイムトークンを作るとか阿呆な所業だ。

こういう技術の実現をより低レベルから構築することが車輪の再発明という。

何故避ける?

大体の事は はてなキーワード に書いてある。

標準で提供されているものは注意深く検討した上で作成されており、バグは少なく、処理も速いため、余程のことがない限り作成のために使った時間と金をロスするだけである。
商用システム上で車輪の再発明を行ってしまった場合は作成した後も、試験・運用などの開発フェーズでも面倒を見る局面がありコストは更に嵩む。
また担当SEやプログラマが入れ変わった場合に、再発明された機能のコードまで含めて勉強させなければならないため、無駄になるコストは意外とバカにならない。
ただ、あえて再発明をする必要がある時もある。例えば現状のプログラムがなんとか動いているのだがコードがスパゲッティになってしまい、それ以上の拡張性を期待できなくなった時などである。
しかし、多くの場合は設計がまずいことが多いため、リファクタリングを行ったほうが適切である。

誰が好き好んで スパゲティコード なんて弄るか!
ライブラリを使えば、増量ソースが少なくて済む。

たまに「その学習コストが…」なんていう阿呆が居るが、何も極めろなんて言ってないし、求められる事も普通ない。
「お前の学習コストは、JSON 一つ覚えるのに月単位もかかるのか?」と問いたい(むしろかかるならその学習能力のなさを驚きたい)。

後、ライブラリを使うことには意味がある。
Excelで帳票出して」と言われて、Excel のバイナリファイルの仕様を1から勉強して自力で実装するのと、 Apache POI 覚えるのどっちがいい?

つまりそういう事だ。

車輪の再発明ってどうやって避けるのか?

これには、まず自分がやろうとしている事が、本当に自分でやるべき事なのかを考えることから始まる。
だが、ここが一番の問題なんだ。

CSVパースだって機能を実現するためのやるべきことだ」なんて思ってると、車輪の再発明はなくならない。

まずこれの切り分けが無いと、何度でも車輪の再発明を繰り返してしまう。
この認識だと、「沢山のライブラリを知ってれば、簡単に解決できるものを選べるよね?」という解決法になってしまう
これでは 「ライブラリや OSS を探そう」という発想が生まれない

そのヒントは実は昔の論文にある。
以下はその抜粋だ

魔法のように、すぐに役に立ちプログラマの生産性を倍増させるような技術や実践 (特効薬) は、今後10年間(論文が著された1986年の時点から10年の間)は現れないだろう、と主張した。
ログラマの生産性の限界は「本質的な複雑性」(essential complexity)についてのみ当てはまると述べているのであり、「偶有的な複雑性」(accidental complexity)に対する挑戦については支持している。
* 偶有的な複雑性は、ソフトウェア開発者自身が発生させている解決可能な問題に関連する。例えば、アセンブリ言語のプログラムコードの記述、最適化、バッチ処理などによってもたらされる遅延は、偶有的な複雑性に由来する。
* 本質的な複雑性は、解決すべき問題によってもたらされるものであり、これを取り除くことはできない。もし利用者が30の異なることを行う1つのプログラムを望む場合、この30のことは本質的であり、開発するべきプログラムはこの30の異なることを実行しなければならない。

銀の弾丸などない –Frederick Phillips Brooks, Jr. 氏の論文

超平たく言うと、「本質的な複雑性(≒要件)は時代とビジネスで変わるんだから、特効薬なんてあるわけないよ。でも偶有的な複雑性(≒要件を実現するための手段)はどんどんやれ」って事だ。

つまり、作る前に考えればいい。

「これから書こうとしているコードは要件そのものなのか?それとも、要件実現のための技術の一つなのか?」 そこが切り分けられれば、ライブラリを探すことができる。

この切り分けがしにくいなら、そもそもメソッド化やクラス化の単位が分かってないか、何が一般的技術(XML,JSON,Ajax等)なのかを知らない。
そういう人はまだ設計を行うべきではない。

大丈夫だ、技術なんてものは、ライブラリなんてものは使う為に作られてる。
つまり勉強することはできるんだ。

勉強し続ける

前のエントリ でも書いたが、勉強はスキルを上げる唯一の手段だ。
加えて言えば、勉強する気もなく古い技術ですべて解決しようとする人、とはコードを改善したいとも思わない人とは、僕は仲良くできないだろう(きっと一緒に仕事をしたら喧嘩になるか、そいつにコードを書かせない)。

勉強は大事だ

これは古い技術を馬鹿にしている訳ではない。
パラダイム・シフトは多くの場合、古い技術をより洗練させた形で実現することでおこるからだ。

正に温故知新。

オブジェクト指向だって、C++/Java で一気に有名になったが、元を正せば Simula 言語のクラスを C 言語の struct の発展形として実装したのが広まっただけだ。
もっと言えばオブジェクト指向という言葉も smalltalk 由来だ(C++ファミリとは違ったオブジェクト指向の流派だ)。

http://d.hatena.ne.jp/sumim/20080415/p1

C# には 2008 年ごろから、Java は 8 で対応したラムダ式なんて、自分の知る限り LISP/ML/haskell にまで遡る。
マルチスレッドのより良い制御だって Java/Scala に Akka があるが、この Akka のアクターモデルなんて一体いつの発明だよって話だ。

だがこれは、新しいものを学びもせず、学習コストは嫌いだと拒絶するのは意味が違う。

何故新しいライブラリ、フレームワーク、言語が出るのかを考えた事はあるだろうか?

それは主に2つの理由があると考えている。

  1. ビジネスが時代と共に変化し、そのビジネス要件を容易に実現するため
    要するにやりたいことをより単純な手段で解決するためだ。
    クラウドが出て、分散処理が流行ると、Hadoop あたりからどんどん分散処理が増えた。
    現在 DeepLerning が大流行だが、早速ライブラリまで出ている。
    ビジネスの進化に追従した結果だ。
    今は落ち着いたが HTML5 当初は AltJS 言語がやたら出たのも記憶に新しい。
  2. No silver bullet - essence and accidents of software engineering:銀の弾などない— ソフトウェアエンジニアリングの本質と偶有的事項
    フレデリック・ブルックス.1986年著
    「本質的(ビジネス要件)な複雑性と、偶有的(要件のために行う手続き)な複雑性で考えると、本質的な複雑性に対する特効薬はない」という論文。
    逆を言えば、偶有的複雑性は簡略化し得る事を意味してる。
    今更フレームワークなしで Web システムを作るか?正気の沙汰ではない。

全く勉強しなければ取り残されるのは上記が理由だ。

学習は価値観を変える

この業界の個人生産性はアホのような差がある。
それをもたらすのが学習だ。

そして、学習は価値観も変える。

Java三項演算子を使ってるだろうか?

    final String displayName =
        user.getDisplayName() == null ? user.getAccountId() : user.getDisplayName();

言っておいて何だが、多くの職場がそうであるように、三項演算子は禁止としているところが多い。
それのどこが悪いのかなんてそうそう気にすることではないだろう。

だが、 scala とかの関数型言語にどっぷり浸かると意見が変わる。

    val displayName =
      if (user.displayName.isEmpty) user.accountId else user.displatName.get

scala関数型言語で、関数型言語は定例的に「if は文ではなくて式」なのだ。だから、if や下手すれば for にも返値がある。
言語仕様上それは「処理ブロックの最後の一行」なわけだが、この処理どこかで見なかったか?

そう、さっき見た 三項演算子 だ。

「条件分岐」なのではなく、「条件で式の結果が変わる」という考え方に変わる。
(余談だが、こうした言語に慣れると、「if で値が返せないとかダサい」と考えるようになる)

当たり前だが、こんなコードの中で複雑な条件を載せるとか、返値が複雑化するとかなら scala ユーザでも if で長々とは書かない。
簡潔に書ける事こそ是としているからだ。

だが、逆に簡潔に書くことができるなら、Java でも三項演算子の使用に何のためらいがある?

それはきっとこんなださいコードを書くより良いはずだ。

   String tmpName = null;
   if (user.getDisplayName() == null) {
     tmpName = user.getAccountId();
   } else {
     tmpName = user.getDisplayName();
   }

   final String displayName = tmpName;

こうしたコードの価値観だって、更に勉強すればより良い書き方が出てくるかも知れないのだ。
その時、僕の価値観はまた少し変わるかも知れない。

例えば JavaScript ならこんな書き方もある。

    const displayName = user.displayName || user.accountId;

null = false の扱いだから、null なら右の値が返る。
boolean 同士だったらまた意味論が変わってしまうこともあるし、名前が重要になってくる書き方だ。

まぁ書き方の是非は置いといても、学べばシンプルに物事が書ける事が多い。

だからこそ学ぶのだ。

プログラムを読みやすくするための個人的Tips

個人的に綺麗なコードを書くために心がけているもの。 数が多いので、効果があると個人的に思っているものだけ集めてる。

なぜ読みやすく書くのか

読みやすいことに興味がない人のコードで、本当に汚ければ遠慮なく指摘するし、そのコードを保守したいとも触りたいとも、仕事でなければ関わりたくすら無い。

一度だけ書いて、二度と保守もメンテも必要無い書捨てのスクリプトなら、とやかくは言わない。
しかし、多くのプロダクトがそうであるように、アプリケーションはメンテナンスする。
それはバグ改修だったり、性能向上だったり、機能追加だったりする。

その時、作りを理解するのは何か?
Excel 方眼紙に書いた、行単位では意味が読み取れないような日本語の文章か?

違うだろ?

結局のところ読んで、理解して、手を入れるのは コード なんだ。
加えて実際の修正量とコードを読む量どっち多い?

聞くまでもない。

間違いなく読む量だ

なら、読みやすいコードこそ把握しやすく、バグも埋め込みにくく、容易に修正できるものだと言えるのでは?

だから 綺麗に書かれたコードが正義なんだ と考える。

他にも、コードは仕様上でも重要なドキュメントだ。

読みづらいドキュメントを誰が訂正するんだ?誰も触りたくなんて無い。
だから学ぶんだ。

美しく書くには

コードの美醜と言うものを知らなければできない…と経験で上がってきた人は居ると思う。
それも方法の一部であることは事実である。

美しいとは経験だ。当然ながら無垢な3歳時が名画を見て「すごい」とは思っても「美しい」とは思わない。
それは経験のなせることだからだ。

そうした意味では OSS のソース、それも多くのマージがあったものが良いかもしれない。
(複数人がメンテする必要があるということは、それ相応に読めるように出来ているだろうと予測はできる)

だが、もう一つ学ぶ方法がある。

それは「 原則を学ぶ 」だ。

原則自体は、書籍「プリンシプル オブ プログラミング」を見ていただくとして、特に効果のあった思考を順におっていこうと思う。

本にもなっている。気になる人はこのエントリの下の方参照。

普段気にしてるもの一覧

項目が多いので、別のエントリで色々書こうと思う。

参考

Playframework2.5 で Slick3 弄ってみる

まずはアプリケーション仕様

今回は特に何も考えず、Evolution で突っ込んだ文字列を画面に表示するだけ。
ジンプルなことは良いことだ。

教科書はこれ

PlaySlick - 2.5.x

Playframework プロジェクト作成

いつものこと

    activator new

play-scala を選択する。
現時点で正式公開なのは、Play 2.5 なので、2.5 のテンプレができる。

厳密には 2.5.10 ができた(2017/3/9 22時現在)。

まずは写経してみる

liblaryDependencies に突っ込むところからとな。
当然 evolution も突っ込む。

build.sbt にて

val H2_VERSION = "1.4.193"

libraryDependencies ++= Seq(
  jdbc,
  cache,
  ws,
  "com.typesafe.play" %% "play-slick" % "2.0.0",
  "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0",
  "com.h2database" % "h2" % H2_VERSION,
  "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test
)

次は DB 設定。
application.conf に下記を追加。

slick.dbs.default.driver="slick.driver.H2Driver$"
slick.dbs.default.db.driver="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:mem:play"

db ディレクティブは参照しないらしい。
そっかー的な感覚。

そして、ここまでで起動と。

sbt run

するとエラー

1) A binding to play.api.db.DBApi was already configured at play.api.db.slick.evolutions.EvolutionsModule.bindings(EvolutionsModule.scala:15):
Binding(interface play.api.db.DBApi to ConstructionTarget(class play.api.db.slick.evolutions.internal.DBApiAdapter) in interface javax.inject.Singleton) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1).
  at play.api.db.DBModule.bindings(DBModule.scala:25):
Binding(interface play.api.db.DBApi to ProviderConstructionTarget(class play.api.db.DBApiProvider)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)

写経してこれとか不親切が過ぎんだろ。
これに対する答えが

PlaySlickFAQ - 2.5.x

It is very likely that you have enabled the jdbc plugin, and that doesn’t really make sense if you are using Slick for accessing your databases. To fix the issue simply remove the Play jdbc component from your project’s build.

なんか「Slack使ってんのになんでJDBCなんぞ有効にしてんだよ…」的なニュアンスを感じるが…。
まぁいい、build.sbt を修正

val H2_VERSION = "1.4.193"

libraryDependencies ++= Seq(
  cache,
  ws,
  "com.typesafe.play" %% "play-slick" % "2.0.0",
  "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0",
  "com.h2database" % "h2" % H2_VERSION,
  "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test
)

Evolution を設定する

Play Slick supports Play database evolutions.

とのことなので、早速ページを見る。

Evolutions - 2.5.x

  1. conf/evolutions/default ディレクトリを作成
  2. (番号).sql を作成
# Example table schema
# --- !Ups

CREATE TABLE IF NOT EXISTS `messages` (
    `id`         bigint(20) NOT NULL AUTO_INCREMENT,
    `message`    varchar(255) NOT NULL,
    `created_at` datetime NOT NULL,
    `updated_at` datetime NOT NULL,
    PRIMARY KEY (id)
);

INSERT INTO `messages` (`message`, `created_at`, `updated_at`)
  VALUES ('Example message', '2017-03-09 13:03:41', '2017-03-09 13:03:41');

# --- !Downs

DROP TABLE IF EXISTS messages;

いざ実行!

Database 'default' needs evolution!

よっしゃ〜!!……?
いつまでたってもEvolution が終わらない…(汗

ログみてなるほど、Evolution 実行後にアプリケーションが再起動してる…つまりそのタイミングでインメモリデータベースも…
設定書き換え。

slick.dbs.default.driver="slick.driver.H2Driver$"
slick.dbs.default.db.driver="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:./db/play"

これで Evolution 通過。

DBアクセス

ということで、スキーマと Dao 作る

package models

import java.sql.Timestamp
import javax.inject.{Inject, Singleton}

import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.driver.JdbcProfile
import slick.lifted.ProvenShape
import scala.concurrent.duration._

import scala.concurrent.{Await, Future}

case class Message(
  id: Long,
  message: String,
  createdAt: Timestamp = new Timestamp(System.currentTimeMillis()),
  updatedAt: Timestamp = new Timestamp(System.currentTimeMillis())
)

@Singleton
class Tables @Inject() (override protected val dbConfigProvider: DatabaseConfigProvider)
  extends HasDatabaseConfigProvider[JdbcProfile] {

  import driver.api._

  class Messages(tag: Tag) extends Table[Message](tag, "messages") {
    def id      = column[Long]("id", O.PrimaryKey, O.AutoInc)
    def message = column[String]("message")
    def createdAt = column[Timestamp]("created_at")
    def updatedAt = column[Timestamp]("updated_at")

    override def * : ProvenShape[Message] =
      (id, message, createdAt, updatedAt) <> (Message.tupled, Message.unapply)
  }

  val messages = TableQuery[Messages]

  def getAllMessages(): Seq[Message] = {
    import dbConfig.driver.api._
    Await.result(
      db.run(messages.sortBy(_.id).result),
      10 seconds
    )
  }
}

そしたらController から読んでやる。

package controllers

import javax.inject._

import models.Tables
import play.api.mvc._

@Singleton
class HomeController @Inject() (
                               tables: Tables
                               ) extends Controller {

  def index = Action {
    Ok(views.html.index(tables.getAllMessages().map(_.message).mkString(",")))
  }
}

さぁって…と思って試して見ると

JdbcSQLException: テーブル "messages" が見つかりません

What's!?

仕方ないので、ファイルを H2 Database Engine よりダウンロードしてコンソールを起動。
そこで疑問氷解。

どうにもテーブル名やカラム名が大文字になってる。当然、見つからないと言われたSQL(ログから取得) select "id", "message", "created_at", "updated_at" from "messages" order by "id" も通らない。
何これヒデエ(TT

試しにコンソール上で大文字かして見る。

> select ID, MESSAGE, CREATED_AT, UPDATED_AT from "MESSAGES" order by ID;

ID      MESSAGE     CREATED_AT      UPDATED_AT  
1  Example message 2017-03-09 13:03:41.0   2017-03-09 13:03:41.0
(1 行, 9 ms)

FUUUUxxKK!!

OK わかったよ。

@Singleton
class Tables @Inject() (override protected val dbConfigProvider: DatabaseConfigProvider)
  extends HasDatabaseConfigProvider[JdbcProfile] {

  import driver.api._

  class Messages(tag: Tag) extends Table[Message](tag, "MESSAGES") {
    def id      = column[Long]("ID", O.PrimaryKey, O.AutoInc)
    def message = column[String]("MESSAGE")
    def createdAt = column[Timestamp]("CREATED_AT")
    def updatedAt = column[Timestamp]("UPDATED_AT")

    override def * : ProvenShape[Message] =
      (id, message, createdAt, updatedAt) <> (Message.tupled, Message.unapply)
  }

  val messages = TableQuery[Messages]

  def getAllMessages(): Seq[Message] = {
    import dbConfig.driver.api._
    Await.result(
      db.run(messages.sortBy(_.id).result),
      10 seconds
    )
  }
}

これで実行成功...自宅で落ち着いてやると案外すんなり行くんだよねこういうの。

Playframework2.5.x でプロジェクト分離してみた

サブプロジェクト自体は、Playframework2.3 から存在していて、当時はあまり使い道も思い浮かばなかったが、今では大分使いやすくなってたのでメモ。

完全独立した sbt プロジェクトを取り込む

このケースでは、完全に独立した sbt モジュールを取り込みます。
用途的には、コンソールプログラムとWebアプリでロジックを共有するために切り離すとか、そういう用途だ。

因みに、それ自体が独立してコンパイルできる sbt プロジェクトであればいいので、おそらく、Playframework アプリケーションでもいけると思う。
ただし、個人的にはそれは失敗した。(設定も面倒だし、後述の「Play サブプロジェクトを作成する」を参照した方がきっといい)

下記の例は単純に sbt のライブラリを取り込む設定だ。
ディレクトリ構成的には下記。

  • project_root
    • build.sbt
    • modules
      • common_lib
        • build.sbt

common_lib がsbt プロジェクトだ。

project_root/build.sbt を下記のようにする。

// ① common_lib という形で、サブプロジェクトがあることを宣言
lazy val common_lib = project in file("modules/common_lib")

lazy val root =
  (project in file("."))
    .enablePlugins(PlayScala)
    .dependsOn(common_lib) // ② common_lib に依存しているという

root 定義に dependsOn で common_lib に依存していることを表す。
これだけなので、とりあえずこれで、common_lib 内のコードを取り込んだ側で利用可能になる。

Play サブプロジェクトを作成する

プロジェクトを機能ごとに分離したい場合に使用。
コンパイルはプロジェクト単位で行うので、コンパイル時間をどうにかしたい場合(変更のあったプロジェクトと、その依存プロジェクトのみ差分コンパイルが走る)や、機能間依存を極力減らしつつ機能の充実化をする(要するにプロジェクト整理)ために使用する。

ディレクトリ配置は下記のようにしてみた。

  • project_root
    • build.sbt
    • app : アプリケーションソース
    • modules
      • view_template
        • build.sbt
        • app : 共有ソース

project_root/build.sbt の側はこれでOK

lazy val viewTemplate =
  (project in file("modules/view_template"))
    .enablePlugins(PlayScala)

lazy val root =
  (project in file("."))
    .enablePlugins(PlayScala)
    .dependsOn(viewTemplate)
    .aggregate(viewTemplate)

enablePlugins(PlayScala) を食わせると、サブプロジェクト側も Play のプロジェクトとして扱われるようになる。

次に、 modules/view_template/build.sbt 側の中身は下記。
注意が必要なのは、scalaVersion を本体に合わせないとコケる。

name := "view_template"

organization := "net.white-azalea"

version := "0.1-SNAPSHOT"

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
  jdbc,
  evolutions,
  cache,
  ws,
  filters,
  specs2 % Test,

  /* database definition */
  "com.h2database" % "h2" % "1.4.192"
)

この時、サブプロジェクト側のディレクトリ構成もは、Playframewrok アプリ同様のディレクトリ構成になる。

Controller だけでなく、URL も部分切り出しできるので、それをするなら routes 分離(公式ドキュメント) 見ればいいと思う。

サブプロジェクトの記述を簡単にしたい

続きを読む

プログラミングの経験と信仰

経験は全ての物事の元となるもので、とても重要なものだと思う。
でも、経験を『信仰』にした瞬間、それは害悪となる。

プログラマは誰しも経験した記憶があるだろう。
「それならコレでもできるよ」と長ったらしい作りをした人を。
かく言う私も、これをかつてしたことがある。今では恥じるばかりだが。

あるコボラー達の話をしよう。
これは大学時代の友人から数年前に聞いた話である。

COBOLメインだった会社が生き残りをかけて、Java を主軸にしようとした話だ。
一般の方に説明すると、COBOL 言語とは、全くプログラムを知らない人用(事務方用)に設計され、非常に少ないルールでのみ記述するプログラム言語だ。(今は若干機能が増えた方言がいくつかあるが、やはり先細り中)
ここだけ聞くと何が間違っているかわからないだろうが、ルールが少ないと言う事は覚えやすい反面、できることが少ないと言うことだ。これは平仮名だけで小説を書くに等しい。当然分量は増え、冗長で誰も読みたくない。

ビジネスは変化する。それはアプリだってバージョンアップする必要があると言うことだ。
ひらがな(COBOL)の小説に続編を書くなんて、浪費でしかない。人件費だけがかかりすぎるのだ。
だが、現場は猛反発。「それならCOBOLでもできる!」の一点張りだったそうだ。
最終的に「Javaやるか会社辞めるか選べ」と言って、トップダウンで強硬策までしてやっと乗り換えたと言う話だ。

彼らにとって、これまでの成功経験は絶対的な正義であり、変えるべきではない『信仰』だったと言える。
それを変えようと言うのは、彼らにしてみれば『背信行為』なのだ。

彼らが間違っていたことは、成功体験が信仰となり、経験と混同していたことだ。

経験は猛毒だ。
たとえどれだけ理不尽であり得ない論理であっても、経験してしまえばそれはその人の真実となる。
まして成功体験は、その真実を信仰に補強してしまう。

経験とは応用し、より良い結果を生み出すものだが、信仰とはそれを受け継ぎ、変化を妨げる。
変化も良し悪しはあるだろう。だが少なくとも現代のプログラミングに信仰は無用だ。

オープンソースがメジャーとなり、GitHub がそれを加速させた結果、『誰かの経験』は短期間で幅広く共有でき、『皆の経験』でドンドン進化する。
それは個人程度が持つ『経験』など、霞むくらいの経験の集合知だ。
個人の成功経験で書いたコードと、多くの人の成功経験、失敗した経験をフィードバックして書かれたコード、価値はどっちにあるだろう?

『経験』とは『知っている事柄だから、新しいやり方も咀嚼して理解できる』だったり『知っているからより良くする方法を考えられる』。『知っているから効率のいい運用を考えられる』と言うことではないだろうか?
それは変化を促すものであって、変化をせき止めるものであってはならない。

信仰を捨てれないなら、COBOL のように先細って茹でガエルになりましょう。

しかし、この業界は変化が早い。
実は上記の理由以外にも『変化をせき止める。押し返そうとする』勢力は実はいる。
それも入社仕立てや、30代の若い世代にも大量にいる。

IT の変化が早すぎて、追従し切れない人たちだ。
多くの場合、仕事で使った、説明された技術のみ覚えていく人たちだ。

追従できなくも、仕事を変える事ができないのはある種、可哀想ではあるが…
彼らに言える事は、仕事の外でも勉強するしかありませんねと言うこと位だ。

仕事で使う技術についていけないなら、仕事の外でも学ぶ事だ。
良書と言われる本を読み、知っている人の講義を聞き、有名なOSSのドキュメントを端から端まで読み、コードを読み、テストを読もう。
どう勉強していいかわからない人もいるだろう。コードが自分の知識と離れすぎて読めない人もいるだろう。どの本を読むべきか分からない人もいるだろう。
そんな時は、年下だろうが後輩だろうが頭を下げて教わろう。僕は下げた。凄い奴は凄い。割り切ろう。

好き嫌いの問題では無い、プライドが許さないと言うなら、猫のモフモフより価値のないプライドは猫にでも食わせればいい。
気位の高い猫のことだ、きっとお気に召してくれるはずだ。

IT はルネサンス期。
天動説を強要したキリスト教は地動説への転向を余儀なくされた。
人の造物ごときに神はいない。『信仰』を捨てる心の準備は?皆の経験は本当に『経験』か?