Why I love everything you hate about Java ≪ Magic Scaling Sprinkles
Why I love everything you hate about Java
なぜあなたがJavaに関して嫌っていることのすべてをわたしが好きなのか
If you're one of those hipster programmers who loves Clojure, Ruby, Scala, Erlang, or
whatever, you probably deeply loathe Java and all of its giant configuration files and
bloated APIs of AbstractFactoryFactoryInterfaces. I used to hate all that stuff too.
But you know what? After working for all these months on these huge pieces of Twitter
infrastructure I've started to love the AbstractFactoryFactories.
もしあなたが Clojure や Ruby、Scala、Erlang といったものが大好きなhipster (物知り、進
んでいる人) プログラマーの一人であるのなら、おそらくあなたは Java と、その巨大な
configuration ファイルや AbstractFactoryFactoryInterfaces のような bloated な API 群を
心から嫌っていることでしょう。わたしもまた、そういった stuff すべてを嫌っててきました。
でもあなたの知っていることって?この数ヶ月 Twitter のインフラの huge piecesで働いてみて、
わたしはこの AbstractFactoryFactories が好きになっていたのです。
Let me explain why. Consider this little Scala program. It uses “futures”, which are
a way to schedule computation to be done in parallel from the main flow of a program.
They are sometimes a natural way of modeling the most efficient scheduling of program
execution. Usually you schedule in advance some expensive work that can be done in
parallel and then you do something else in the meantime. Only when you really need the
result of the original computation do you block and wait (and hopefully only very
briefly since you scheduled the work way in advance!). Here is a “typical” Java-ish
Futures library used from Scala:
なぜなのかを説明させてください。ここでは小規模な Scala プログラムを考えます。そのプロ
グラムではあるプログラムのメインフローから並行で行う計算のスケジュールをする“futures”
を使っています。それがプログラム実行の最も効率的なスケジューリングモデリングの自然な方
法であるときもあります。通常あなたは並列に実行できる some expensive work をスケジュール
して
and then you do something else in the meantime.
元々の計算結果をあなたが本当に必要としているときにだけブロックとウェイトを行います
(and hopefully only very briefly since you scheduled the work way in advance!
そしてできれば)。これが Scala から使われる “典型的な”Java 的 Futures libraryです:
private val executor = new ThreadPoolExecutor(
poolSize, maxPoolSize,
keepAlive.inSeconds, TimeUnit.SECONDS,
new LinkedBlockingQueue[Runnable],
new NamedPoolThreadFactory(name))
val future = new FutureTask {
doSomeWork
}
executor.execute(future)
If you come from a dynamic language like Ruby or Python you will probably have a
visceral reaction like “Yeck! Look at all that horrible boilerplate. Convention over
configuration!” Wouldn't it be nice if you could just do something like:
もしあなたが Ruby や Python のような動的言語からやってきたのなら、
“Yeck! Look at all that horrible boilerplate. Convention over configuration!”
(おいおい、このhorrible boilerplate を見てみろよ。「規約は設定に勝る」ってなんだよ!)
のような visceral reaction をすることでしょう。
Wouldn't it be nice if you could just do something like:
val future = new Future {
doSomeWork
}
It seems nice but its nicety is just an illusion. All that boilerplate is really
important when you work at massive scale and where efficiency really matters. These
magic numbers like the thread pool size and the kind of queue you use to schedule work
can vastly impact the performance of your application. And the “right” configuration
depends entirely on the nature of the problem you're solving and how callers of this
code behave. What all of this weird boilerplate provides is a way to configure the
behavior of the system; it doesn't assume there's one right way of doing things. And
that is precisely how modular software behaves: modular code is code designed to grow
past the assumptions of just one user. Modularity really matters when your software
isn't a little throw-away program.
これは一見 nice なように見えますが、その nicety は単なる幻想にすぎません。こういった様々
な boilerplate はあなたが work at massive scale なときに同時に効率が重要な要素である
場合にはとても重要です。スレッドプールの大きさのようなマジックナンバーやスケジュールの
ために使うキューの種類といったものは、あなたの作るアプリケーションに広範な影響をもたら
す可能性があります。そして“正しい”コンフィギュレーションは、あなたが解決しようとして
いるその問題の性質やこのコードの呼び出し元の動作がどういったものであるかに完全に依存し
ています。この種の weird な boilerplate のすべてが提供しているのは、そのシステムの振る
舞いを configure する方法です。そのとき実行するためのただ一つの正しい方法があるとは想
定していません。そしてそれはまさに modular sottware がどのように振舞うのかということな
のです:modular code とは、ただ一人のユーザー (just one user) の asumptions を成長させ
るために過去に設計されたコードです。Modularity はあなたのソフトウェアが小さな使い捨て
プログラム(little throw-away program)でない場合には重要なことです。
Twitter recently open-sourced Querulous, a minimal database querying library for Scala.
We use it in several projects in Twitter, but it was designed principally to meet the
extreme demands FlockDB, our distributed, fault-tolerant graph database. FlockDB
demands extremely low-latency (sub millisecond) response times for individual queries.
Any excessive indirection from an ORM would be unacceptable. Furthermore, because
FlockDB processes tens of thousands of queries per second across dozens of shards,
FlockDB must collect extensive statistics on the performance and health of the various
shards in order to direct traffic to the most efficient place.
Twitter は Scala 向けの minimal database querying library であるQuerulous を最近オープ
ンソースとしました。わたしたちはこれを Twitter 内の幾つかのプロジェクトで使っていまし
たがそれは principally な設計がわたしたちの分散型でフォールトトレラントなグラフデータ
ベースであるFlockDBに extreme な demands をするようなものでした。FlockDB はこの query
に対するとことん低いレイテンシー (ミリ秒以下) のレスポンスタイムを要求しますORM からの
一切の excessive indirection は受け付けられませんでした。それに加えて、FlockDB のプロ
セスはdozen of shards を跨いだ毎秒一万件もの query を処理するので、FlockDB はパフォー
マンスについての extensive な統計情報と、最も効果的な場所へトラフィックを振り向けるた
めの health of the various shards を収集 (collect) しなければなりません。
So Querulous was designed for querying databases at low latency, massive scale, and
with easy operability. It has flexible timeouts, extensive logging, and rich
statistics. But as FlockDB became more mature and sophisticated, the demands grew
greater. We needed different health-check and timeout strategies in different contexts.
It became clear that Querulous would need to be made extremely modular and extremely
configurable to work at all.
そこで Querulos はデータベースに対する 低レイテシーで、massive scale, easy operability
な querying のために設計されました。この Querulos は flexible な timeouts、extensive
logging、rich な statistics を備えていました。しかし、FlockDB がより mature かつ
sophisticated されていったように要求は grew greater していったのです。わたしたちには別
の heelth-check と異なるコンテキストにおける timeout 戦略が必要になりました。ここで
Querulous は work at all のために extremerly に modular にし、かつextremerly に
configurable にする必要があることがクリアになりました
So we set about to re-write Querulous using my favorite modularity techniques:
Dependency Injection, Factories, and Decorators. In other words, everything you hate
about Java.
そこでわたしたちは、わたしの好きな modularity 技法である依存性注入(Dependency Injection)、
ファクトリー (Factories)、デコレーター (Decorators) を使って Querulous を書き換えること
を決意しました。これらは、言葉を換えればあなたが Java で嫌っているものすべて (everything)
です。
The design patterns of modularity (modularity のデザインパターン)
In order for code to be modular it must have few hard-coded assumptions. In
Object-Oriented software this means something very particular since the essence of an
Object-Oriented program is that its structure is organized around the types of objects.
Therefore, the most fundamental, anti-modular assumption in Object-Oriented software
is the concrete type of objects. Any time you write new MyClass in your code you've
hardcoded an assumption about the concrete class of the object you're allocating. This
makes it impossible, for example, for someone to later add logging around method
invocations of that object, or timeouts, or whatever isn't anticipated a priori.
コードを modular にするためにはハードコードされた仮定や想定 (hard-coded assumptions)の
数を少なくしなければなりません。オブジェクト指向ソフトウェアではこれは something very
particular を意味します。なぜなら、オブジェクト指向プログラムのエッセンス (essence) と
は、その構造がオブジェクトの型を中心として構成されていることだからです。したがって、オ
ブジェクト指向ソフトウェアにおける最も fundamental な anti-modular な assumption とは
オブジェクトの具体的な型 (concrete type of objects) です。あなたが自分のコードで new
Myclass と書いた時点であなたは自分が割り当てを行うオブジェクトの具体的なクラス
(concrete class) についての想定をハードコードしています。これはたとえばあとから誰かが
あるオブジェクトを invocation するメソッドに関するロギングを追加したりとかタイムアウト、
あるいは a priori に anticipated できなかったものすべてを追加することを不可能にしてし
まいます。
In a very dynamic language like Ruby, open classes and method aliasing (e.g.,
alias_method_chain) mitigate this problem, but they don't solve it. If you manipulate
a class to add logging, all instances of that class will have logging; you can't take
a surgical approach and say “just objects instantiated in this context”.
Ruby のような動的言語では、オープンクラス (open classes) とメソッドエイリアシング
(method aliasing。e.g., alias_method_chain) がこの問題を mitigate (軽減する、和らげる)
しますが解決はしません。もしあるクラスをロギング機能を追加するように操作すると、そのク
ラスのすべてのインスタンスがロギング機能を持つようになります。あなたは surgical
approach (限定されたアプローチ?) を採用して
“just objects instantiated in this context”
(このオブジェクトだけこのコンテキストでインスタンス化する)と指示することはできません。
There are standard design patterns to mitigate this, namely Dependency Injection,
Factories, and Decorators. By injecting a Factory (a function that manufactures
objects) as a parameter to a function that needs to create objects, you allow a
programmer to later change his mind about what Factory to inject; and this means the
programmer can change the concrete types of objects as his heart desires. And by using
Decorators, the programmer can mix and match functionality easily, stack one thing on
top of another like so many legos. Let's look at an example.
この制限を緩和 (mitigate) するための Dependency Injection (依存性注入)、Factories(ファ
クトリー)、 Decorators (デコレーター)と呼ばれるデザインパターンが存在します。オブジェ
クトを生成するのに必要となる関数をパラメーターとしてFactory (オブジェクトを構築する関
数) を注入することによりあなたはプログラマーがあとになって注入する Factory について心
変わりすることを許可します。そしてこれはプログラマーが自分の要望としてのオブジェクトの
具体的な型 (concrete types of objects)を変更できるようになることを意味します。さらに
Decoratorsを使うことでプログラマーは functionality を簡単に mix and match でき、たくさ
んのレゴのようにstack one thing on top of another できるのです。
では例を見てみましょう。
Here I have a Query object, with methods like #execute(). I want to add timeouts
around all queries. I start by creating a QueryProxy that routes all method
invocations through an over-ridable method: #delegate:
ここで #execute() のようなメソッドを持った Query オブジェクトがあります。わたしはすべ
ての qureries に対して timeouts を追加したいと思っています。オーバーライド可能なメソッ
ド #delegate を通じて起動するすべてのメソッドをルーティングするQueryProxy を生成するこ
とから始めます:
abstract class QueryProxy(query: Query) extends Query {
def select[A](f: ResultSet => A) = delegate(query.select(f))
def execute() = delegate(query.execute())
def cancel() = query.cancel()
protected def delegate[A](f: => A) = f
}
Then, to implement timeouts, I create a Query Decorator:
そしてタイムアウトを実装するために Query Decorator を作ります:
class TimingOutQuery(timeout: Duration, query: Query) extends QueryProxy(query) {
override def delegate[A](f: => A) = {
try {
Timeout(timeout) {
f
} {
cancel()
}
} catch {
case e: TimeoutException =>
throw new SqlTimeoutException
}
}
}
This Decorator delegates to the underlying query object the execution of the query,
but it wraps that execution in a Timeout.
この Decorator は query を実行する query オブジェクトに delegate していますが、
Timeout のある実行としてラッピングしています。
As an aside, it is interesting to note that the Decorator pattern is just the
Object-Oriented equivalent of function composition in a functional language. Scala
makes this especially explicit since everything is both an Object and a Function (it
is a function if it is an object that responds to the method #apply()). A Decorator
around an object that only implements #apply() is pure Function-composition as you
would see in Haskell, ML, and so forth. I might phrase this as: function composition
is a degenerate case of the Decorator pattern.
それはさておいて、オブジェクト指向におけるデコレーターパターンは関数型言語の関数合成
(function composition) と等価なものであることに言及しておきましょう。Scala ではすべて
が Object であると同時に Function でもあるために、これを特にはっきりとした形にしていま
す(あるオブジェクトが #apply() というメソッドに反応するのであればそれは関数です)。
#apply() しか実装していないオブジェクトに対するデコレーターは、Haskell や ML などに見
られる pure Function-composition です。このように言うこともできるかもしれません: 関数
の合成はデコレーターパターン (Decrator pattern) の degenerate case である。
The implementation of the Timeout function is shown for the curious. It uses threads
and is weird but cool.
Timeout 関数のこの実装は shown for the curious です。
スレッドを使っていて weird ではありますが cool です。
object Timeout {
val timer = new Timer("Timer thread", true)
def apply[T](timeout: Duration)(f: => T)(onTimeout: => Unit): T = {
@volatile var cancelled = false
val task = if (timeout.inMillis > 0) Some(schedule(timeout, { cancelled = true; onTimeout })) else None
try {
f
} finally {
task map { t =>
t.cancel()
timer.purge()
}
if (cancelled) throw new TimeoutException
}
}
private def schedule(timeout: Duration, f: => Unit) = {
val task = new TimerTask() {
override def run() { f }
}
timer.schedule(task, timeout.inMillis)
task
}
}
(An alternative implementation of Timeout could use Futures,
but that's a subject for another blog post)
(Timeout の alternatvie な 実装として Futures を使うことができますが、
それは別の blog post の subject になります)
Modularity and testing techniques (Modularity とテスト技法)
One of the principal advantages of (or stated another way, one of the principal
motivations for) writing Decorator-oriented code is how easy it is to write isolated
unit tests of that code. To test the timeout functionality of the TimingOutQuery we
don't need to interact with a database at all. We can write behavioral/mockish tests
like this:
デコレーター指向 (Decorator-oriented) なコードを書く主なアドバンテージの一つ(もしくは、
そうすることの主要な動機のひとつ) が、そのコードに対する isolate されたユニットテスト
の記述がいかに簡単であるかということです。TimingOutQuery の timeout functionality をテ
ストするためにデータベースなどとの interact を行う必要はありません。次のように
behavioral/mockish tests を記述できます:
val latch = new CountDownLatch(1)
val query = new FakeQuery(List(resultSet)) {
override def cancel() = { latch.countDown() }
override def select[A](f: ResultSet => A) = {
latch.await(2.second.inMillis, TimeUnit.MILLISECONDS)
super.select(f)
}
}
val timingOutQuery = new TimingOutQuery(query, timeout)
timingOutQuery.select { r => 1 } must throwA[SqlTimeoutException]
latch.getCount mustEqual 0
If the timeout functionality was just inlined into the #select() method of the source
code of the Query class, or “bolted on” as an alias_method_chain in Ruby (or added
as “advice” in some AOP shit) you could not write this test without talking to the
database and somehow finding a query that takes long enough that it will actually hit
the timeout. Because we instead use Decorators, to test the code we can use a fake
query that implements the Query interface but that doesn't talk to the database at all.
Here we use a CountDownLatch to “halt” execution for a bounded amount of time, thus
triggering the timeout.
もし timeout の functionality がただ単にQuery クラスのソースコード中の #select メソッ
ド中にインライン展開されていたものであったり、あるいは Ruby における
alias_method_chain のように“bolted on”されたもの(または 一部の×××××な AOP の
“アドバイス”として追加されたもの) であれば、あなたはデータベースとの対話をせずにこの
テストを記述することはできなかったでしょうし、タイムアウトを実際に起こすような充分時間
のかかる query を見つけだすようなことをしなければならなかったでしょう。ここではわたし
たちは代わりに Decorators を使っているのでコードのテストのために Query インターフェース
を実装している fake query を使えますが、それはデータベースなどとの会話は行いません。
わたしたちはここで時間制限された実行を“halt”するために CountDownLatch を使っているの
で、タイムアウトを引き起こします。
Tying it together with Factories (ファクトリーを一緒に結びつける)
Back to our original mission. So now we have a way of layering on timeout
functionality on top of a Query object. But how do we ensure that Timeouts get used
when we want them to? The thing that glues this all together is to make sure that
everybody that needs to instantiate a Query object never ever calls new Query directly.
We provide instead a Factory as a parameter to the method that needs to manufacture
the object. The programmer chooses which Factory to provide at runtime. Here is a
Factory that makes TimingOutQueries:
ここでわたしたちの original mission に戻ります。現在わたしたちは Query オブジェクトの
トップにtimeout functionality を layering on する手段を持っています。しかし、Timeouts
がわたしたちがそれを必要としているときに使われるということをどうやって保証 (ensure) で
きるでしょうか?これらすべてを解決するのは、Query オブジェクトのインスタンス化を行う必
要のあるすべてのひとがけっして new Query を直接呼び出さないようにすることですわたした
ちはオブジェクトを構築するのに必要となるメソッドをパラメーターとして受け取るファクトリ
ーを提供しています。プログラマーは実行時に提供するファクトリーを選択します。以下に示す
のが TimingOutQueries を作るファクトリーです:
class TimingOutQueryFactory(queryFactory: QueryFactory, timeout: Duration) extends QueryFactory {
def apply(connection: Connection, query: String, params: Any*) = {
new TimingOutQuery(queryFactory(connection, query, params: _*), timeout)
}
}
Since TimingOutQueries are decorators around regular Queries, to manufacture a
TimingOutQuery you have to first manufacture a regular Query. In this example, the
TimingOutQueryFactory takes another Factory as an argument. This could be a simple
QueryFactory or something more complex-allowing Factories to be composed indefinitely.
With this we stack together timeouts, logging, statistics gathering, and debugging
like so many pieces of legos. This smacks of the oft-ridiculed Java
AbstractFactoryFactoryInterface. But let me put it bluntly:
AbstractFactoryFactoryInterface's are how you write real, modular software-not little
fart applications.
TimingOutQueries は通常 (regular) の Queries を修飾するデコレーターなので、
TimingOutQuery を生産 (manufacture) するにはまず通常の Queryを構築する必要があります。
この例では、TimingOutQueryFactory は引数として別の Factory を取っています。これには
QueryFactory や、曖昧な合成 (composed indefinitely) を許す Factoriesのようなより複雑な
ものを単純にできる可能性があります。これを使ってわたしたちは timeouts, logging,
statistics gathering を一緒に stack して、たくさんのピースのレゴのようにデバッグします。
これにはたびたび嘲笑されている Java の AbstractFactoryFactoryInterface の雰囲気があり
ます。遠慮なく言わせてもらえば、AbstractFactoryFactoryInterface というのはreal で、
modular なソフトウェア、little fart でないアプリケーションをどのように書くかということ
です。
# bluntly 無遠慮に、ぶっきらぼうに
# fart 嫌な、莫迦な、屁
This seems like a bit of a mind-fuck because we here have Factory Decorators that take
Decorated Factories that make Decorated Queries. It's so meta! (Actually, “meta” in
Greek means nothing like “meta” in English. “Meta” plus the accusative means “
after” so Aristotle's Metaphysics is actually just a book “after [the book on]
physics”. Anyway.) So all these crazy FactoryFactoryDecorators sound kind-of scary at
first but it is just the kind of abstraction on top of abstraction and closure under
composition that allows complex software to be made simple. Manage complexity by
taking many things and re-conceiving of them as just one thing; this one thing is then
combined with many other things and the process is repeated up the ladder of
abstraction until you reach the Godhead.
Decorated Queries を作る Decorated Factories を引数にとる Factory Decorators がわたし
たちにはあるので、これは bit of a mind-fuck のように思われます。なんと meta なことか!
(実際のところは、ギリシア語でいう “meta”には英語の “meta”のような意味はありません。
“Meta” plus the accusative means “after” so Aristotle's Metaphysics is actually
just a book “after [the book on] physics”. Anyway.) 。これら crazy
FactoryFactoryDecorators すべては最初のうちは kind-of scary のように見えますが、
abstr0action のトップにある abstraction の一種であり、複雑なソフトウェアを可能にする合
成の下の closure をシンプルにするためのものです。
this one thing is then
combined with many other things and the process is
repeated up the ladder of abstraction until you reach the Godhead.
Godeheadにあなたが到達するまで抽象化の階段の繰り返されます
# taking many things による複雑さの管理と
# just one thing としての
# re-conceiving of them
Taking this to the next level (Taking this to the next level)
To take this even further, let's add a new feature: per-query timeouts. At one point
in the history of FlockDB, there was a global 3-second timeout. This was really stupid
given that our most common query has a latency of 0.5ms and a standard deviation of
2ms. If you have a global timeout you must set your timeout around your most expensive
query not your most common query (otherwise, your most expensive query will always
timeout!). But for a production system, cheap frequent queries, if they start
exceeding 2 standard deviations, can take down your site. So a sensible timeout for
these frequent queries is like 5ms. But we had it set to 3,000 ms!! Yikes. So let's
change it!
To take this even further,
新しい機能 per-query timeouts を追加することにしましょう。
FlockDB のヒストリー中のあるポイントで
三秒のグローバルなタイムアウトがありました。
This was really stupid
given that our most common query has a latency of 0.5ms and a standard deviation of 2ms.
これはとてもバカバカしいことです
わたしたちの一般的な query は0.5ミリ秒のレイテンシーで、
標準偏差が2ミリ秒
もしあなたがグローバルタイムアウトを持っているのなら、
最も一般的な query ではなく最も高価な query に対して
タイムアウトをセットしなければなりません
(さもなければ、最も高価な query が常にタイムアウトしてしまうでしょう!)。
しかし production system の cheap frequent な queriesに対して
start exceeding 2 standard deviations
であれば、あなたのサイトを take down してしまうかもしれません。
ですから、these frequent queries のための sensible timeout は
おおよそ 5ms なのです。
But we had it set to 3,000 ms!!
Yikes. So let's change it!
class PerQueryTimingOutQueryFactory(queryFactory: QueryFactory, timeouts: Map[String, Duration])
extends QueryFactory {
def apply(connection: Connection, query: String, params: Any*) = {
new TimingOutQuery(queryFactory(connection, query, params: _*), timeouts(query)) // YAY
}
}
That's it. We've now implemented a new Timeout strategy in one line of code! And to
wire it all together it is a piece of cake! Querulous makes no assumptions about how
best to implement a timing-out strategy, it doesn't even assume you'll want timeouts
(in fact, there are some cases you don't want any timeouts). Querulous achieves
modularity by providing an “injection point” for the programmer to layer on custom
functionality. It takes QueryFactories as a parameter to the method, which can return
arbitrarily decorated Queries.
まさにこれです。新しい Timeout の戦略をコード一行で実装しました!そして、全てをまとめて
しまうことは朝飯前 (a piece of cake)です! timing-out 戦略を実装するのにどうするのが最
善かどうかについてQuerulous では一切の仮定をしていませんし、あなたが timeouts を必要と
することすら想定しません(実際、一部のケースではあなたは timeouts を必要としないでしょ
う)。Querulous は custom functionality を重ねるプログラマー向けの“injection point”を
提供することによってmodularity を達成しています。この “injection point”は任意の
decorated Queries を返すことができるメソッドに対するパラメーターとして QueryFactories
を受け取ります
I love this example because it's so simple but yet it's no toy. It also emphasizes the
value of Dependency Injection more generally than just with Factories. We could have
written the TimingOutQuery with a static global constant (probably the most common
programming technique):
シンプルではあっても単なる toy (おもちゃ)ではないので、わたしはこの例が好きです。これ
はまた依存性注入の価値が、単にファクトリだけを使うものよりもさらに一般的なものであるこ
とを強調します。わたしたちは、(おそらくもっとも一般的なプログラミング技法である)
static な global constant を使ってもTimingOutQuery を記述できたでしょう:
class TimingOutQuery(query: Query) extends QueryProxy(query) {
val TIMEOUT = 3.seconds
But intead it is injected as a parameter to the constructor to the TimingOutQuery:
しかしここではコンストラクターのパラメーターとして TimingOutQuery に注入 (inject) されています:
class TimingOutQueryFactory(queryFactory: QueryFactory, timeout: Duration) extends QueryFactory {
def apply(connection: Connection, query: String, params: Any*) = {
new TimingOutQuery(queryFactory(connection, query, params: _*), timeout)
}
}
This enables the TimingOutQueryFactory to invoke a function to choose the appropriate
timeout for this query. In this case, we just look some shit up in a hash table
(timeouts(query)) and we're done.
これは TimingOutQueryFactory が関数を query に対する適切なタイムアウトを選択する
ために起動できるようにします。この場合、
we just look some shit up in a hash table (timeouts(query)) and we're done.
わたしたちはハッシュテーブル中の some shit up を見て、処理しているだけです。
Yes, all this FactoryFactory bullshit is exactly what you hate about Java. But it's
amazing not how just short this code is but that it could be configured by any
programmer anywhere, regardless of whether they have access to the source code that
actually instantiates and executes queries. Any user of Querulous can decide if she
want timeouts or not, and she can decide if they also want debugging, stats gathering,
and so forth?Querulous hard-codes no assumptions. So, yay modularity.
そう、この FactoryFactory という××××があなたが Java で嫌っているものなのです。コードが
これだけ短いということが驚きなのではなく、it could be configured by any programmer
anywhere, どんなプログラマーがどの場所からでも実際にインスタンス化を行ったりクエリを実
行しているソースコードにアクセスできるかどうかには関係なくconfigure できることが驚きな
のです。Querulous のユーザー誰でも、は自分がタイムアウトを望むのか否かや、デバッグを行
うか否かといったこと、統計情報を集めたり and so forth? ということを自分自身で決定できます。
Querulous hard-codes no assumptions.
So, yay modularity.