(前書き)カオスは続くよ、どこまでも。
スマフォが普及する以前、人類がリーマンショックを経験する前、Adobe Flashベースで「リッチクライアント」というものを実現するFlexというものがあった。リッチクライアントとは、クロスブラウザでリッチなユーザー体験を実現するというもの。ActionScriptで実装されたFlexがもしもそのまま進化していれば、例えば、React NativeやGoogle Flutterのようなものになっていたに違いない。
時々にヒューマンインターフェース技術は進歩していく。リッチクライアントがスマフォになり、さらにAlexaをはじめとする音声入力インターフェースも普及も進む。
そうした中、今年は、開発が始まってから10年を超えるシステムのフロントエンドをReact&React Nativeを使って刷新するというプロジェクトで、フロントエンド開発界隈がもんのすごく混乱しているのを目にすることとなった。10年ちょっと前に、ActionScriptを実装言語にFlexを用いて行われた、けっこう複雑なUI/UXを持つプロジェクトが阿鼻叫喚の混乱をきたした様を思い出した。こうした場合、サーバ側で待っている人間も辛い。
このままでは、これからも世界各所でカオスな実装が生み出されていくに違いない。フロントエンドが、そして、バックエンドが、カオスに陥らないためにはどうしたら良いのか。
実は10年以上前から、ベストプラクティスは存在していた。それは、UIコンポーネント内での循環参照を避けること。循環参照を避けろと口で言うことは簡単でも、それを実践し続けることは(しばしば納期との関係で)人類には難しいらしい。カオスな実装は生み出され続けている。
だが、既に人類はカオスへの闇落ちを避けるための幾つかの武器を手にしている。
その一つは、Dartだ。
Dartを画像検索してみるだけで、そのまっすぐな飛び方が目に浮かんでくる。
出典 http://www.dartslive.com/
ここでは、この新たな武器を人類が誤用しないための心構えをポエムとして書いておく。
カオスへと闇落ちしないための武器として、Dartを学ぶにあたっての心構え
① Elmを知れ。
かつて、Googleの威光の下、ブラウザ界隈(?)を牛耳ろうとして失敗したことのあるDart。その頃、とある良心的なエンジニアが個人的に開始したプロジェクトが近年のブラウザ界隈で、ちょっとした話題だ。その名を、Elmと呼ぶ。
同じAltJSながら、DartとElmは、かなり対照的だ。Googleの精鋭エンジニアがゴリゴリと開発を進めるFlutterはじめ、JSやAndroid JavaさらにはApple Swiftのかなりのところを置き換えてやろうという力技で政治的なDartが、マッチョな感じを受けるのに対し、とある個人の手で開発が開始されたElmは中世的なミニマリストの印象を受ける。基本的にブラウザ環境専用と、禁欲的である。
ヨーロッパ世界ではElmとGrapeと並んで良縁の象徴とされる。ワイン作りの葡萄は基本的にElm製の棚で支えられている。
The Elm and the Vine
ジェンダー的にどうかは別にして、勤勉な夫と貞節な妻、そして、子沢山がイメージされるものらしい。
さて、なぜDartのアドベントカレンダーなのに、Elmに言及したのか。
それは、Dartは良い言語だと思うが、ミニマリストのElmと異なりカオスにに陥るわながあると思ったから。「(Dartの)FlutterのWidgetには、StatelessWidgetクラスを継承したものとStatefulWidgetクラスを継承したものがあります。はい、そこの人、今回はどちらを使いますか?」と聞かれた時、Elmの設計思想を知らない人は、ついつい早く作れるほうを選びそうだ。そうした方は、納品日まで間がない場合の武器として、Elmを知っておいた方がよさそうだ。
そう、Haskellに出自を持つElmは、immutableな純潔のマリア。状態(state)を持たないことがディフォルトゆえ、テストが容易になる。
もちろん、純潔さが嫌いな人とか宗教上の理由でJavaっぽくなければだめといった方にはおすすめできないが。自分がElmに合うかについては、簡単なチェックシートがある。
ともあれ、これからdartを書こうという人は、保守性という観点からstateless/immutableに特化し、覚えることの少ないElmの良さを知っておいても良い。
② better Javaと思うな。
Dartのコードを初めて見た人の多くは、Javaっぽい、と思う。そしてJavaより簡潔な言語だということも知る。だが、Dartを学び書くにあたって、Dartをただの"better Java"と思うことは危険だ。
どんな言語を使おうとダメなシステムはダメだ。逆に良いものは良い。JavaとJavaScriptを使った素晴らしいシステムは世にいくらでもある。他方で、時々に新しい技術を使った結果、学習コストが高く、コピペ乱発のコードに、場当たり的なメソッドの命名、そして、いくつもの循環参照がなされているシステムも生み出されているのもまた事実。
Dartは、ぱっと見Javaっぽい。が、Dartを学び始めるにあたっては、StreamControllerといった、リアクティブな作りを支える仕組みがはじめから備わってることに、注目すべきだ。
10年ほど前に"better Java"として売り出され、Javaほどには使われることはないが、Reactive/Stream界隈で一定の勢力を持っている言語にScalaがある。だが、Scalaを、"better Java"として使う使い方は、さして流行らなかった。"better Java"に思え言語を乗り換えるとしても、Javaにありがちな実装アプローチをそのまま使ったのでは、scalaを使うメリットはあまりない。Dartも、Dartなりの実装アプローチをはじめから学んでおくべきだ。
ここから先は、以下のようなDart先達の良記事を読むのが良いのだと思う。
- ① 長めだけどたぶんわかりやすいBLoCパターンの解説
- ② FlutterのBLoC(Business Logic Component)のライフサイクルを正確に管理して提供するProviderパッケージの解説
先達の記事①から、一方向的な関係の図を引用する。
出典 長めだけどたぶんわかりやすいBLoCパターンの解説
イチゴから苺への流れ(flow)はあるが、逆はない。
BLoCは、Javaの一般的な実装とは発想が異なる。そう思い、Dartを学ぶのがベターだよと。
このあたりに関して、クリーンアーテキクチャ本的(?)な補足をしておく。このネタ投稿では、《矢印》のような一方向的な作りの大切さだけを強調しているんだけれど、これはシステムの挙動をチェックし始める際の視点(どんなふうに影響範囲があるんだろうか etc.)。
その次には、限定された影響範囲の中での各オブジェクトの挙動やライフスタイル管理などを捉えていくことになる。
再び先達の記事①から、ビジネスロジックコンポーネント(BLoC)を含む図を引用させていただき、このあたりを注記しておく。
先達の記事②の方は、実際にBLoCを採用した実装を行う際に「作りを破綻させない」ための、Widget とProviderの継承関係について述べたもの。以下のように、Blocは複数のサブツリーに属するwidgetたちと情報をやり取りできる。実装の推奨パターンが固まらないままだと、混乱した実装がなされそうで怖い。
出典 FlutterのBLoC(Business Logic Component)のライフサイクルを正確に管理して提供するProviderパッケージの解説
具体的な実装の推奨の話は先達の記事に譲るとして、以下、サーバ側から見ることになってしまった混乱したクライアント実装や周辺システム(主にはメモリリークな何かだけどね)の怖い話をネタ的・戯画的に。。
世の言語はJavaかbetter Javaであるべきだと決めつけるエンジニアが多数派を占める世界の、ディストピア
~元はと言えば、コードがややこしくて長くなるのがいやなおっさんたちが、Javaを誤用したこが罪深いのかもしれないけれども。~
① モノリスなシステムがくたびれちまった経緯。
15年前に生み出され、コピペしまくりの結果、今ではコード量が100万行を超えてしまった、モノリスでくたびれた、とある自社システムがある。本システムは、もともとは物販コールセンター向けに作られたシステムであったが、web物販システムとしての大改修を経て、今に至る。このシステムのこれまでの貢献は大きく、今では、システムによる年商は100億円を超えてくれている。
だが、ビジネスが急成長しシステムにやっつけの改修が入るたびに、次々とシステムには技術的負債は蓄積されていった。エンタープライズ・システムではよくあることなのかもしれないが、お客様の登録ワークフローを変更する(例 カード支払いに加え各社のQRコード払いにも対応)、商品の提示方法を変更する(例 賃貸不動産の紹介ページに、最寄り駅からの距離を近い順に自動で上位3件まで追加する)、商品の販促キャンペーンを個別設定する管理者画面の追加、はたまた、コールセンター側の連携システム構成が変化したり、などなど。
そうした中、度重なる改修に疲れ果てた、メインの実装担当者が退職してしまった。
あなたは、このシステムの保守を一人で任された、とする。引継ぎの文書は少なく、改修時の仕様書などは存在しない。やっつけでこのシステムへの改修を加えることになったあなたは、さまざまな闇を見ることになった。
そんなある日、部長が、あなたに「ちみちみ、これからはうちの商品もIoT対応するよ。Amazon Alexaなんかに話しかけると、商品の状況をぱぱっとレポートしてくれる感じにね。いや~、頑張って予算取っといたから、よろしくね。」と嬉しそうに言った...
② とある学園都市の話として、ラノベ風に。
以上は、実話を戯画化したものであり、究極に手続き型アプローチで作られたシステムは今日も、実際に稼働している。
が、もともとはPHP4ベースの実装から始まった実システムを中途半端に戯画化すると生々しいので、私が15年ものの本システムに垣間見た、過度に実装が具体的になっていまっている闇たちを、以下では、とある未来の学園都市の話として、ラノベ風に記しておこう。
なお、以下には、Hub-of-All-Things(HAT)なる、実在するマイクロサービス(AGPL3ライセンス)が登場するが、HATの実際のアーキテクチャなどサーバ側の話は、そのうち別の回で取り上げることとする。
ラノベ風ポエム 「とある《矢印》のベクトル変換」
[1] 兵庫県のとある学園都市の生徒たちの放課後の学びの多様化を支援・管理する《システムⅠ》
平成が終わりを遂げた、2019年のゴールデンウィークの11連休明け、ギークを気取る若きあなたは、兵庫県のとある都市にある学園都市(以下、<学園都市Ⅰ>)に配属された。配属先であなたは、少子高齢化が進む我が国において、学園都市という名に恥じず、国内外の10万人を超える学生たちの学びの多様化を支援するシステムのメインの開発者である。あなたの上司にあたる、最高技術責任者(CTO、ボス)は、うれしいことに、アーキテクチャの選定から自由になるスクラッチ開発を許してくれた。
イングランドから来たボスは、クイーンズ・イングリッシュ交じりの英語を話す。DeepLearningの研究で博士号を取っており、もともとは貴族階級の出だという。経済学部出身ながら、つぶしの効く仕事としてプログラマになったあなたは、実装についての経験はボスより上だ、と自負しているものの、情報工学の知識の深さなど、ボスには尊敬すべき面もあると思っている。
ボスは、システムのコンセプトを「ユーザー(学生)が学びの楽しさを自ら見つけていくことを支援するハブとなること」とあなたに伝える。そして、参考になる事例として、大学時代の知人がイギリスで立ち上げたプロジェクト『THE HUB OF ALL THINGS』をintroduceしてくれた。
cf. https://www.hubofallthings.com/main/what-is-the-hat/
本プロジェクトのソースコードは、githubで公開されている。
https://github.com/Hub-of-all-Things/HAT2.0
ざざっとコードを見たあなたはつぶやく
実装はscalaベースで、外部とのインターフェースではPlayFrameworkが用いられており、データベース周りはslickライブラリ経由でPostgres決め打ちだが、両者の境界にはアダプターがakkaを用いられたreactiveな作りになっていて、それぞれを切り離すことは可能だ。
Haskellerなあなたは、アカデミアな雰囲気の<学園都市Ⅰ>にふさわしく、haskellベースでシステムを書いていきたかったが、ボスの顔を立てて、GPLなHAT2.0をfolkして、scalaで書いていくこととした。scalaならエンジニアも探しやすいしな、たぶん、と思いつつ。haskellに未練があったので、フロントエンドはelmを使っていくことにした。
どっかのおっさんがPlayFramework+elmでお試しを書ているようだし、play周りの設定で困ることはあまりなさそうだ。開発コード名は、HATとelmをつなげて、『はてるむ(hat-elm)』としておこう。たぶん、検索機能は必要になるから、elastic4sライブラリあたりを使って、検索エンジン対策を進めるか,うんぬん...
あなたは、開発コード名「はてるむ」の《システムⅠ》の設計に取り掛かる。まずは、システムのステークホルダーにヒアリングを行い、システムの主なユースケースを以下のように整理した。
- 「はてるむ」では、ユーザー登録時に、今関心があることをユーザーに書いてもらう。
- 「はてるむ」を訪問したユーザーには、それぞれの関心事に応じ、いくつかの学習コースが提示される。
- 「はてるむ」の学習コースは、リアルな教室の公開講座と、web上のeラーニング講座とからなる。
- 「はてるむ」の学習コースには、受講状況やアンケートの結果から、関心分野別にランキングが用意される。
- 「はてるむ」では、マイナーな講座を見つけやすいよう、検索機能が用意される
- 「はてるむ」では...(以下、略)
あなたは、とあるステークホルダーから、「はてるむ」では、当面の間、ユーザーの属性はさほど変更されない一方で、学習コースの講座の方は、IoTを用いる、新いた体験学習講座が企画されていることを知った。この新講座では、ビデオ録画と四肢に装着したセンサー情報を組み合わせ、講師と生徒の動きの一致度を計測し、生徒の学びに役立てることができるようになるという。生徒たちに人気のダンスレッスンに新講座が用意されると、受講者が殺到しそうだ。
あなたは、設計段階から、新たな方式の学習講座の追加を念頭にシステムのインターフェース設計を行っていった。あなたは、システム改修時に、haskellベースの新興フレームワーク『となとな』をこっそりと導入したりと、<学園都市Ⅰ>でのエンジニア生活を満喫していった...
[2] 15年後の分散マイクロサービス群《システムⅢ》
2035年、あなたは、東京都下にある学園都市<学園都市Ⅲ>に転属となっていた。かつて<学園都市Ⅰ>で生み出された、《システムⅠ》は、<学園都市Ⅲ>に住まう200万人以上の生徒たちの日常生活を広く管理・監視するハブシステム、字義通りに《The-Hub-of-All-Things》という役割を果たす《システムⅢ》へと発展していった。「はてるむ」の実装を開始してから、はや15年。システムには数多くのシステム改修が行われていった。中でも改修量が大きかったのが、VR型のMMORPG(Massively Multiplayer Online Role-Playing Game;大規模多人数同時参加型オンラインRPG)への対応を伴う、《システムⅠ》から《システムⅡ》へのアップデートであった。
VRMMORPGでは、生徒たちは空中浮遊などリアルワールドで不可能な動作を学ぶことができる。そんな現実世界に存在しない動きを学んで何に役に立つのだろうかと、当初思っていたあなたは、突如、埼玉県の自衛軍の飛行場の近くにある<学園都市Ⅱ>へと転属される。どうやら、急速に発展を遂げるVRMMORPGを、これからの機動隊や自衛軍レンジャー部隊の候補生たちの学びの場とする構想があるらしい。VRMMORPGによって、ゲーム感覚で、剣技や銃器の取り扱いの基礎を学べるようになっているのは事実だ。
<学園都市Ⅱ>は、近隣の脳科学研究を行う厚労省系の独立行政法人と提携関係にある。あなたのミッションは、CTOとして、VRMMORPGシステムのログと、VRMMORPGプレイヤーたちの脳波を図る独立行政法人側のシステムとを統合解析できる機能を実現する《システムⅡ》の構築を統括すること。あなたの指導の下、投入されたエンジニアたちの尽力の結果、比較的モノリシックな《システムⅠ》は、いくつかの言語で記述されたマイクロサービスベースの《システムⅡ》へと発展していった。頭部に接続することになるVRMMORPGの端末には高度なリアルタイム応答性が求められる。そこで、VRMMORPG端末では、応答性に優れたZirconカーネルを用いたFuchsiaが採用されていた。Fuchsiaは長らく愛用されてきたAndroid OSの事実上の後継という扱いがなされている。大規模な開発となったVRMMORPG端末の開発チームのエンジニアたちはDart、C++、nimを駆使して、脅威的に高機能なUXを実現していった。腕利きのエンジニアたちにあなたは実装をまかせっきりになった。
<学園都市Ⅱ>の生徒は数の上では数千人にすぎないが、2年近くの間、VRMMORPGシステムにログインしたままであったという特殊な子たちで、中には...(以下、略)。
ともあれ、今の問題は、転属先の<学園都市Ⅲ>の《システムⅢ》だ。すっかり中年のおっさんとなったあなたは、《システムⅢ》の技術顧問。15年前に自らコードを書き始めたシステムとはいえ、当初は想定していなかった改修を加えていったことで、システムのコードの多くは、あなたが知らないAI支援型ドメイン特化言語Helmesで記述されるようになっていた。《システムⅢ》から《システムⅢ》への改修は、実装パラダイムの変更を伴った。かつて、scalaやelmなどのコードはIDEやエディタで手打ちされていたのに対し、AI支援型DSLでは、プログラマが脳内に浮かんだコードをAIが対話的に補完していきリポジトリへとコミットしていく。これによりプログラマは職業病である腰痛や腱鞘炎にかかるおそれは軽減されたものの、プログラミングの生産性は必ずしも向上したわけではなかった。一説では、AI支援型DSLの導入は、コードをAIを通じ集中管理することで、《システムⅢ》内の機密情報を<学園都市Ⅲ>を外に漏らすことかないようにする狙いがあるとされていた。
正直、あなたにとって、AI支援型DSLのHelmesは性に合わない。確かに、元になったelmの特徴は残っているものの、DOMの操作言語から脳波情報の汎用記述言語へと記述目的が変わったことは大きい。
こうなったのも、<学園都市Ⅱ>のオーナーである自衛軍が、<学園都市Ⅲ>のオーナーである統括理事会の下にある「暗部」に敗北したことが大きい。オーナーシップの変更に伴い、《システムⅡ》と《システムⅢ》は目指すところが全く異なっている。恐るべきことに、《システムⅢ》では、《システムⅡ》が蓄積してきた脳波情報を〇〇〇することで、200万人を超える生徒たちを「能力者」へと変えていくことを目的とするらしい。システムの目的の変更も大きい。加えて、CTOを外さられたあなたには、システムの機能〇〇〇や△△△などが開示されなくなっていた。《システムⅢ》の技術顧問という肩書きは、実のところは、レガシーなscala/akka周りのコードを時折保守するおじさんという意味合いしかなかった。
そんなあなたのところに、とある統括理事から「暗部」の一組織を通じた依頼が入る。それは、《システムⅢ》上の統括理事会向けフロントエンドからの検索から漏れている、「液状被覆超電磁砲」に関する情報をシステムのレガシーなデータベースから抜き出すことと、こうした検索漏れを起こさないようにすること、だ。あなたは「液状被覆超電磁砲」がいかなるものかは知らないが、<学園都市Ⅲ>中に分散配置されている《システムⅢ》のサブシステムから、「液状被覆超電磁砲」に関する情報を抽出するべく調査を開始する。
[3] リファクタリングは、《矢印》のベクトル変換で。
あなたの主な担当範囲は、バックエンド・システムのうちscalaとhaskellで記述された部分だ。かつて、あなたが書いたコードも含まれるが、現在のコード量ははるかに大きい。非開示の部分もあるため全体はわからないが、あなたが、CTOを務めていた《システムⅡ》の頃に、すでにコード量は1億行を超えていた。かつての<学園都市Ⅱ>では豊富な国防予算がつぎこまれて、大規模な開発チームが構成されたが、<学園都市Ⅲ>での開発体制はそれを上回る。どうやら、その間、あるサブシステムでは、アカデミア系あるあるで、謎のこだわりをもっていたり、時々の流行に流されやすい老若男女のコーダーたちに、時として「暗部」の人間までが加わって、イマドキのHelmesや、古のhaskellやscalazの形式で、黒魔術コードを埋め込んでいた。また、AI支援型DSLが生み出す白魔術コードの中には、人間が読めたものではないものもあった。他方で、《システムⅡ》の頃にオフショア開発に出され、納期を遅延するうちに納品先が、<学園都市Ⅲ> へと変更された別のサブシステムでは、目を覆いたくなるようなコードのコピペが乱発されていた。
結果、検索漏れ問題の特定は長期に及んだ。
疲れ果てたあなただったが、コピペ乱発のとあるサブシステムで、ついに原因を突き止める。
それはかつてどこかで見たようなクソコードだった。とりあえず、いったん落ち着かせるために、テストコード代わりにコメントに置かれていたコードをREPLで走らせてみる。
scala> val 現在の学園都市レベル5たち = "一方通行、垣根帝督、御坂美琴、麦野沈利、食蜂操祈、(未判明)、削板軍覇".split("、")
現在の学園都市レベル5たち: Array[String] = Array(一方通行, 垣根帝督, 御坂美琴, 麦野沈利, 食蜂操祈, (未判明), 削板軍覇)
scala> val register5 = new CreateLevel5Users
register5: CreateLevel5Users = CreateLevel5Users@1435103b
scala> for (name <- 現在の学園都市レベル5たち) register5.addToRepository(name)
scala> register5.showAll
User(現在第1位 : 一方通行)
User(現在第2位 : 垣根帝督)
User(現在第3位 : 御坂美琴)
User(現在第4位 : 麦野沈利)
User(現在第5位 : 食蜂操祈)
User(現在第6位 : (未判明))
User(現在第7位 : 削板軍覇)
あなたは、学園都市のレベル5という変数やCreateLevel5Usersなるクラスに見覚えはなかった。だが、継承元のCreateUserを書いたのは、15年前のあなただった。
あなたはわなわなと震える。
class CreateLevel5Users extends CreateUser{
private val myUserRepository = new UserRepository
private var order = 0
def addToRepository(name:String) = {
order += 1
val registname = name match {
case "一方通行"=> name+("(より強力な黒い翼,白い翼もあり")
case "御坂美琴"=> name+("(より強力な食蜂操祈の合体技の液状被覆超電磁砲もあり。黒子とも合体するか。)")
case _ => name
}
myUserRepository.add(s"現在第${order}位 : ${name}") // sは,補間 (string interpolation) を意味
}
def showAll = {
val users = myUserRepository.getAll
users.foreach {u => println(u)}
}
}
なんの理由でこんなの書いたの。だめじゃん、この作りだと、能力者レベル5の持つ能力の応用に関する排他的検索のところから落ちちゃうじゃん。だが、あなたの怒りの真の向き先は、クリーンアーキテクチャにならって書いていた《システムⅠ》のコードのビジネスロジックが、CreateUserの実装に依存してしまっていることだった。このままでは、特定の検索条件では、液状被覆超電磁砲は御坂美琴にも食蜂操祈にも帰属しないという結果を返してしまう。そして、もうひとつ怖いのが、一方通行の黒い翼,白い翼やらが、能力者以外の属性を持つ者との関係で不具合を起こしていないかということだった。
技術顧問であるあなたの数少ない部下のひとりに影響範囲を調べてもらったところ、潜在的な影響範囲は懸念した通り大きいことが分かった。あなたと年老いてくたびれはてた部下たちとの、とっちらかった実装のクラス間の依存関係の《矢印》を本来あるべき一方通行に戻すべく、ベクトル変換に勤しむ日々の始まりであった。しかも、カオスはサーバ側に留まらない。Fuchsiaの後継OSに持ち込まれたコードにも、カオスは仕込まれていた。テキストベースの言語になじみがすくない若い人に、そうしたカオスを扱わせるのは無理というもの。昔々読んだ、クリーンアーキテクチャ本のという《矢印》を思い出し、「悪いな、ここから先は一方通行なんだよ。」、あなたは小さな声で呟くと、レガシーなエディタを使い、またひとつ循環参照を起こしているコードを直すのであった。しこしこと、しこしこと。
それはほとんどがJavaやScalaのサーバエンジニアであったおっさんたちにとっては、人生初の苦行なのであった。
(後書き)
ちなみに、実システムの方では、都道府県に関する情報はデータベースに入っていたのに対し、政令指定都市の一部に関する情報がハードコーディングされていた。これにより、某県に、同じ名前を持つ政令指定都市が誕生した後のシステム改修でバグが発生した(型付けが弱いまま暗黙の変換があるらしいPHPでの癖のある書き方に起因するバグ)。だが、そんな笑える不具合がおきる一方で、よりシャレにならないユーザー様にご迷惑をおかけするときがあるのに不具合を起こしている箇所が特定しきれないものもあるのだった。恐るべし、循環参照とハードコーディングのコンボ技(それ以外にも問題は多いのだけれども)。
15年間って、長いんだな(遠い目)。
ちなみに、10年ちょい前にオフショア先からやっとのことで納品されてきた循環参照だらけのFlexの巨大なコード群を見た時のトラウマがあってか、少し前に納品されたreactのコードもちょっと怖くてまだ開いていない。現実逃避でクリーンアーキテクチャな心構えか何かを書こうと思ったら、こんなポエムになってしまった。