おでーぶでおでーぶ

いろいろ書く。いろいろ。

本や他人の言葉で主張を語られると意見の返しどころがない

文書を書くとき、「本や他人の言葉を引用して自分の主張に使うことはあっても、それらの言葉を自分の主張として語るべきではない」ということを肝に命じて記述している*1

正直言うまでもないことだが、本や先達の言葉を引用しつつ自分の意見を主張することには良い効果が多分にある*2

例えば他から言葉を借りてくることで自分の主張を補強できたり、複雑な背景や説明をあえて他のソースに委ねることで読み手・聞き手の前提を揃える助けになることもある。あるいは借りなかった場合、既存の語句を自分の言葉で再定義したが故に主張が伝わりづらくなってしまうこともあると思う。

一方で、本や他人の言葉を適切に借りるためにはテクニックと経験が必要になる。適切でない借用、例えば借りてきた言葉がそのまま自分の主張となってしまうような表現は誰にとっても良い効果がない。その主張に対して意見を返そうとしても、それは主張者(Aさん)ではなく他ソース(Bさん et.al)に対する意見になりがちだからだ。

A さんが「Bさんによると X という問題がある」「語句Yの定義をBさんのいうZとする」といった形で B さんという他ソースに丸投げした場合、問題 X があること自体・Zという定義に懐疑的な意見は一体「誰」に対する意見なのだろうか。多くの場合、B さんがその意見に直接答えることはできないため A さんが答えるしかない。しかし A さんは B さんの代理人ではないので、B さんの主張を勝手に広げる行為はできない*3。そもそも他人の主張を代弁すること自体、非常に難しいというか無理がある。もし意見のやり取りをする上でそのような状況に陥った場合、意見の向き先が迷子にならずとも孫引きや伝言ゲームのように情報が劣化・歪曲されてしまうことは想像に難くない。

ここで、特に本をソースとした場合は一定の(謎の)説得力を持ってしまう点も忘れてはいけない。さらにこの問題はその一定の説得力が効かないような踏み込んだ話であったり、ストーリー性に違和感が発生したときに表面化してしまう。最悪の場合だと、主張の根拠を失う結果になることもある。それでも指摘しないことは健全ではなく、指摘をすると空気が悪くなってしまう。誰も得をしない。全員が損をしている可能性すらある。

話は少し飛んで、ソフトウェア開発の中でもこのようなリスクが露出してしまった場面を時々見かけた。

  • コードを理解することなく Stack Overflow などから借用してきたため、修正の根拠を聞いても「Stack Overflow に書いてあったので」「分からない」と返される
  • 著名な設計に関する本で言及された設計思想やパターンを(言及されていることを以て)良い・正義とする
  • 語句を「〜本に出てくる」という覚え方をしているため、その語句の意味を説明しようとしても答えられない
  • J○ke が言ってた*4

勿論、一定の説得力の中で話が進めばそれはそれで問題がないのかもしれない。そんな場面が多いのか少ないのか、そして良いのか悪いのかは分からないが・・・。

なんにせよ、文書でもコードでも、「本や他人の言葉を引用して自分の主張に使うことはあっても、それらの言葉を自分の主張として語るべきではない」をこれからも意識していきたい。


とある文書を書いていたら下書きが全部吹き飛んでしまったので、正直いうと今は自分の主張を「この本(数冊)を読んでおいて」で済ませたくなっている。とてもつらい。

*1:剽窃とはまた別の話です。剽窃は駄目、ゼッタイ。

*2:マックにいる女子高生が政治経済を語るとすることで主張の印象を変えるテクニックとは別

*3:憶測でそういったことをする人がいることにはいる。しらんけど

*4:これは GitHub Issue や reddit で稀によく見る

変な話ィ 2021

SHIROBAKO Advent Calendar 2021 - Adventar ​4日目

変な話、君たち全部作りなおしだYo

f:id:jmatsu:20211204005904p:plain

事あるごとに「変な話ィ」という接頭辞でイライラさせてくれる茶沢信輔さん。TV版の四年後である劇場版での出番はRECAPのデフォルメ以外ありませんでしたが・・・2016年に引き続き今回はこの茶沢を取り上げたいと思います。

変な話ィ、やらかしが多すぎるんじゃないかなって思います!

TV版最終回では下っ端に降格されたのかカメラマン*1をやらされていることに胸がスッキリした方も多いでしょう。

f:id:jmatsu:20211204011537j:plain

さてそんな茶沢さんですが、テレビ版では原作者に話しを通さず「いいんじゃないですかね」「あーそれ良いと思いまっす」「それで進めちゃってください〜」などとのたまい制作を進めさせ、原作者が確認してNGを出すとすぐさま制作をやり直させようとする鬼畜を演じています。第三少女飛行隊の制作にあたって次が大きな手戻りでしょう。

  • キャラクターデザインへの勝手なGoサイン及び原作者希望のリテイク伝達
  • PV作製をギリギリになって言う
  • 原作が追いついていないために放映版のラストを創作したところ、制作が進んでからの原作者要望のリテイク伝達

現実問題としてこんな人が指示側にいたら間違いなくその作品は終わりを迎えるでしょう。

変な話ィ、茶沢に守れるものもあると思いまっす!

締め切りも常識も守れていないのですが、そんな茶沢でも 原作者の気持ちを無視した放映が行われないこと だけは守れているという揺るぎない事実があります。

仕事量を増やしたくなくて原作者への確認を怠ったり、話し合いの場を設けないようにしたり、色々と仕事に粗は目立つ茶沢のワークスタイルですが、あのいわば自業自得の場面から「おたくらGODに楯突く気ですか!?」という勢いだけで制作側をリジェクト出来る胆力は目を瞠るものがあります*2。普通の人間には無理でしょう。

劇場版では三女2なるものが冒頭に出ており、中学生のようなエロ要素を適用したストーリーに変更されています。制作がヤバすぎたのか、新しい担当がエロさえやればいいと思ったのか制作の無理を断れなかったのか、真実はわかりませんが少なくとも茶沢がいれば原作者の意志に反することはなかったでしょう。まあ野亀先生がネタに詰まってエロに走った場合は茶沢でも無理なんですが、変な話。

まとめ

茶沢の印象として「変な話ィ」や「おたくらGODに楯突く気ですか!?」などと宮森たちの障害としての側面が目立ちますが、原作者の意志に沿わないことはキッチリと跳ね除け、要求をなあなあで通さないという点では一部職責を全うすることのできるスキルの持ち主です。そして劇場版に茶沢がいたら*3、三女2に「No」を伝えることができたかもしれません。

この記事を読んで茶沢のことが気になって胸焼けが止まらない方はNetflixでTV版を、それ以外の方はU-NEXTで劇場版を見ましょう。

おまけ

「Noを伝える技術」を学びたい方は pmconf 2021 のセッションをご覧ください。

https://2021.pmconf.jp/sessions/pkikYIU5

*1:使ってるカメラのモデルは恐らく Nikon D4 でしょう。ディスプレイではなくファインダーを通し、キャリブレーションはズーム大きめでそこから引いて調整するであろう辺りに技を感じますね。

*2:尊敬するとは言ってない

*3:実はトラップに茶沢がいて企画を通していなかったのは茶沢なのでは・・・? とか思うと闇が深くなりますね

「チームで育てるAndroidアプリ設計」を読んだ

ダイレクトマーケティングです。(PR) アフィじゃないリンクは一番下に置いておきます。

peaks.cc

全体的な所感

アーキテクチャのモチベーションからアーキテクチャとチームの育成、将来を加味した運用方針について、非常に分かりやすく構成されています。新規プロジェクトへのアーキテクチャの導入、既存アーキテクチャの方向性の手直しや作り直し、既存アーキテクチャの思想読み込みといった よく必要とされる行為でありながら、あまり言語化されていないもの をどういったモチベーションや理念を持って実行するのか、そして どうすれば独りよがりにならず、チーム・組織として運用することができるのか を学べる良い本だと思います。

本書は順番に読み進めるとテンポよく進んでいく章立てになっています。その一方で気をつけなければならないこととして、本中で利用されるアーキテクチャチームのスコープで使用する汎用的な設計方針 と第一章で定義されるなど、その曖昧な語句の意味は本中で順を追って定義されます。前半で定義し終わるようにうまく構成されているとはいえ、頭から読み進めることで共通認識を得ることに変わりはなく、用語定義表などがあるわけではないので斜め読みをすることはあまり推奨しません。どうしても斜め読みをする場合、他開発者(著者)にとってアーキテクチャはどういう立ち位置であるかを理解するために第一章と第二章は確実に読んでおくことをおすすめします。

また少しエモみの溢れる内容から、想定読者(?)は以下のように感じました。

  • ✅ 新たにアーキテクチャを導入したり、既存のアーキテクチャを補正・刷新するにあたって「アーキテクトとしての言語化の補助」「チームメンバー間でのアーキテクトに対する意識の統一」をするときに読むといい
  • アーキテクチャの選択や導入手順に悩んでいるときに読むといい
  • ✅ アーキテクトでもチームメンバーでもどっちであっても読むといい
  • ❌ 何かしらの具体的なアーキテクチャの実装例を求めて読むといい

意図的な構成を感じたので、具体的な実装例についてはきっと新しい本の執筆が進んでいることでしょう*1

ちょっとずつ細かく

自分が本を読むとき、二周目移行に役立つよう章立てをガン無視してラベリングしたりするのでその構成でどんなときに読んでおくと良さそうか、を書きます。

アーキテクチャ導入を説明するときのモチベーションと運用方針

パート: 第一章、第三章、第四章

第一章では新規開発を例にとり、新規開発で求めたいものからアーキテクチャを用意することの必要性が語られます。とはいえ、新規開発でなくとも、現在のチームにその求めたい点が足りていないのであれば同様の考えを展開することは可能なので、自分たちのフェーズが新規開発でなくとも読んでおくと良さそうです

第三章と第四章はアーキテクチャ例に関係なく読めました。アーキテクチャをチームにどうやって浸透させ運用していくか、それを大きな組織内で横展開するにはどうやればいいのか、著者の経験も含めて語られています。アーキテクチャを導入しても「将来的に腐る運用」であればそのアーキテクチャの寿命は短く、早晩に毒となりえます。自身の運用に自信がなくなったり、迷いが出てきたら非常に良い指針となる章でした。

選択などに悩んだとき用。既存アーキテクチャのまとめ

パート: 第二章

チームメンバーの学習強度といった外的要因から始まり、アーキテクトが大体ぶち当たるであろう、早期に抑えるべき点が列挙されています。また多層アーキテクチャなどの既知のアーキテクチャでカバーできることも丁寧にまとめてあるため、あとで選択に迷ったら読むと良さそうです。

アーキテクトの意思決定の理解の一助となるはずなので、チームメンバーにはぜひ読んでおいてもらいたいですね。

実運用フェーズで考慮すべきコードや運用のメタなポイント

パート: 第五章

Android チームだけではなく、バックエンド他のプラットフォームとの協調も考えなければいけない実例を元に話が進みます。一定の規模までプロダクトや企業が成長したら確実に遭遇するとまでは言いませんが、特に複数の協調するプロダクトを持つ会社ではありそうなパターンです。

その特徴から、開発効率性と堅牢性の双方を重要視するという話は非常に共感出来ます。ただそれを実現するのは非常に難しいので、どのように合意形成をしていくのかであったり、ドキュメント文化であったり、泥臭いことも含めて整備していくことの重要性ややり方が書かれています。

めっちゃ大変だったんだな、と想像しながら読みました(小並感)。

大きいプロダクトからかかる制約例 - モジュラモノリス

パート: 第六章

モジュラモノリスを新規開発段階で考える機会はそう多くはないと思いますが、参考になるのはもちろんのこと単純に面白いです。

多くの既存アプリや社内SDKと協調するプロダクトの開発経験に関する知見を得られます。これはアーキテクチャを主導する人だけではなく、参加する人にもかなり有益じゃないでしょうか? 単にプロダクトの大小に関する1軸ではなく、組織規模も含めた2軸以上を考慮したアーキテクチャはその複雑度や裏にある思想が多くなりがちです。それらをプロダクトに参加して即把握するなど超人以外には不可能なので、この章で知見を得ておくと将来役立つかと思います。

機能開発の効率増加やDX改善といったフィードバック運用方針

パート: 第七章、第八章

すごくエモい方法論の第七章と具体的なクラス特性を改善しつつ説明する第八章のセットです。アーキテクチャを運用していると確実にぶち当たる壁で、これを蔑ろにしているとそのアーキテクチャは腐ってしまいます。ここで紹介された運用手順を導入した場合でも、定期的に見返すことで運用の健全性を考えていくと良さそうですね。

退職と人事異動というコラムが最高だったのでぜひ買って読みましょう。

新規開発と大規模開発で意識する点まとめ

パート: 第九章

新規開発でいきなり複雑なアーキテクチャを導入する必要性はないように思うことも多いですし、逆に大規模開発では多様なレベルの関係者が増えることで制約を増やした方が全体的に健全になることもあります。そういった違いをぱっと見るにはこの章をあとで見返すことになると思います。

蛇足

個人的に本書で書かれたアーキテクチャ周りのモチベーションや思想はかなり近いものがあります。著者陣の二人と仕事をしたこともありますし、設計談義を良くするのですでに意見を知っているとは言え、改めて言語化されたことで再認識する点もありました。

執筆お疲れ様でした。

https://peaks.cc/books/architecture_with_team

*1:しらんけど

2020 買ってよかった気がするもの

めちゃくちゃ雑に書きました。お金を使ってしまったなぁ、という自戒も込めて。微妙だったものも書こうか悩んだんですが今回は書きません。

仕事編

Dell - U3419W

34 inch の曲面モニターです。今回買ってよかったものに入れていますが、購入当初は結構ネガティブなことを言ってました。

mobile.twitter.com

このときは曲面モニターを iMac Retina 5k と併用していたので駄目評でしたが、今は Macbook とこの曲面モニターを繋ぎ、曲面モニターをメインディスプレイにしています(iMac は別で単体利用)。 画面左右半分で分けて、普通のディスプレイなら1枚でしか表示出来ない領域を2つ横並びで表示する形で利用しつつ、MacOS の Workspace 機能を併用して用途ごとにうまく切り替えてます(そしてその切り替えをゲーミングマウスのキーに当ててます)。また Slack のような watch する系のアプリは全部 Macbook 側に配置して、このディスプレイは作業のみに集中出来るようにしています。元々の問題は iMac との解像度の差だったので、今の構成だとそのデメリットは全てなくなり幸せです。

値段相応の価値があるかどうかに関しては「今の運用ならある」という感じで、運用次第で無価値にもなりそうだなという感覚です。曲面ディスプレイに慣れるまではそもそも作業効率に響く可能性は高く、単純に平面複数の方が嬉しい人も多いはずです。

佐藤ショップ - モニター台

スマホ台は使っていませんが、大きさが変化する点や最大幅であれば Realforce がシュッと入る点が気に入ってます。耐荷重的にも足的にも iMac をちゃんと置けます(耐震マット等はちゃんと対応しましょう)。小物が多くなる人にはオススメです。なお配線はどうにもならないので諦めました。

生活用品編

キッチン・食品

熊本製粉 - グルテンフリー ケーキミックス

米粉ベースなグルテンフリーのパンケーキがレンジで作れます。ボウルなどを必要とせず、そのままレンジで作れる時点で最高 of 最高なのは日頃パンケーキを作る民にとって自明だと思いますが、こいつはグルテンフリーな上に中々美味いです。僕はプレーンに低糖質ココアプロテインバーと牛乳をぶち込み、ココナッツオイルと蜂蜜突っ込む魔改造ブレンドが好みです。グルテンフリーなので、何を入れてもノーカロリーロー糖質です(ガバガバ計算)。

EMEBAY - 包丁 & まな板立て

少し足があり、安定感のある以下の包丁立てつきまな板置きを買いました。コンパクトで結構軽く、思ったとおり安定感もあって非常に便利です。これもそんなに足が長いタイプではありませんが、これ以上足が短いタイプを使っていたときに抱いていた不清潔だなーという感想がなくなって良い感じです。

岩崎 - スクリュートップキーパー

かの有名な保存容器です。スクリュートップできっちり閉まる、ちょうどいいサイズ感、清潔感のある見た目と中身がちゃんと見えるクリア度、レンジも食洗機もOKと最高最強の保存容器です。ダースで買いましょう。前からいくつか持っていましたが、今年小型のものをナッツケースなどにする目的もあり、全て新調しました。

霜鳥製作所 - カヌレ焼型

銅型・・・はさすがに高すぎるので、テフロン加工のもので縁巻き加工してあるものを選びました。ヤスリで削ってあっても、縁巻き加工していないものは手を怪我しかねないのでオススメしません。この製品は縁巻き溶接はされていないので、そこに汚れが入ると頑張って落とさないといけないです。とはいえ、お菓子作りの型はカヌレ型に限らず基本ブラシで洗うようにしてるので問題としていません。

mobile.twitter.com

健康・スポーツ

LPN - ストレッチポール

リモートワークが増えたので肩甲骨を開いたりするためにストレッチポールを買いました。毎日10-20分くらい寝起きにやったり、疲れたなーってときに設計や仕様を考えながら延ばしてます。

腰や背中を伸ばすということもあり、何も考えずに乗っていると怖いので整体士と一緒にストレッチポールを組み入れたストレッチメニューを考えたりしています。

イケヒコ・コーポレーション - い草 畳マットレス

もう実家を出て10年になるんですが、未だにベッドが好きになれません(実家は和室に布団)。かといって和室の物件を探すと築年数が中々のものなので、今回ベッドフレームを捨ててそのまま畳のマットレスに切り替えました。この上に整体マットレスを敷き、寝く形です。

折りたたみ式で使ってないときはコンパクトに出来ることが売りっぽいんですが、割と重いので数日おきに起こして風通しをする以外は基本敷きっぱなしで生活しています。そのため休みの日はこの畳の上にクッションを敷き、胡座をかいてコーヒーを飲んだり日本酒をこぼしたりして幸せな時間を過ごしています。

アウトドア編

自転車

PIRELLI - P ZERO VELO 700-25C

今まで FELT F85 に 700-23C のタイヤ(標準)を装備していたんですが、パンク頻度やすり減り、それと70km超えのときの疲労が深刻になってきたので25Cにあげて付け替えました。めちゃくちゃ柔らかくて取り付けは簡単だし、街乗りには十二分の性能を発揮してくれます。

単純に 23C -> 25C になったことで振動も減り、その疲労は減ってます。ただ取り付けに寄与してるその柔らかさは普段の走りにも出てくるので、正直120km超えた辺りから加速がしんどくなりそうだなという感じ。限界圧もそこまで高いわけではないので、好みは分かれると思います。

キャンプ

ヘリノックス - チェアツー

元々名前も知らない安い小さめの折りたたみ椅子を持っていましたが、今回大きめの椅子を買いました。安定のヘリノックスです。座り心地はかなり良く、生地も丈夫で安心感があります。でかいモデルなだけあって、188cm 78kg の僕でもゆったり座る事ができます。

ついでにロッキングフットも買いました。地面がある程度しっかりしている必要がありますし、ないと利用出来ないとかいうわけではないので必須ではありません。ただ焚き火に当たりながらゆらゆらしたいという希望がある人は買うべきです。

Mozambique - キャンプテーブル(ロールタイプ)

キャプテンスタッグの例の王道テーブルはもちろん持っているんですが、かなり位置が低いので椅子から無理なく届く高さで、丈夫、そして軽いテーブルを探したところこのテーブルがヒットしました。

コンパクトにまとまり、出し入れが非常に簡単です。また布なので軽量さは言うまでもないです。それでいてテーブル面がしっかり平らになり、かなり安定感があります。めちゃくちゃ便利なので、普段から室内で時々利用してます。

バイク

KABUTO - CF-1W ピンロックシート と チークパッド

今年バイクの免許を取ったので、ヘルメットに KAMUI3 を選んだんですが何回か走りにいって以下の2つは必須だなと思いました。

CF-1W はクリアタイプの曇り止めシートで、ちゃんと装着すれば本当に曇らないです。ただ正直言って、何をもって「ちゃんと装着した」となるのか良く分からなかったので、部屋でヘルメットして作業して曇りを確認してました。絶対確認して調整しましょう。

チークパッドは 20mm を追加購入しました。デフォルトが 30mm なんですが、結構な圧迫感があります。太ったら終わるな・・・という危機感と戦っても良かったんですが、装着時間が40分超えた辺りから脱着後の痛みが発生したので諦めて買いました。

リモートワークでの足元ヒーター三銃士

買ったもの

出力切り替え型セラミックヒーター

このタイプには最大1000W、下は300-500W帯が多いです。

期待していたことは

  • 初期コストの低さ
  • 強弱の切り替えで十分

出力ダイヤル可変セラミックヒーター

下は200W辺りから、ダイヤルで任意の出力に設定出来ます。最大1000W超えが意外とあります。

期待していたことは

  • 初期コストの低さ
  • 細かく設定出来ること
  • キャンプでの利用

足元を囲うタイプの遠赤外線ヒーター

色んな種類がありますが、下有りで上が空いてるタイプを選びました。また殆どがダイヤル式で任意の出力に切り替えられます。

期待していたことは

  • 局所性のなさ
  • 足裏への対応

評価

出力切り替え型セラミックヒーター < 出力ダイヤル可変セラミックヒーター = 足元を囲うタイプの遠赤外線ヒーター << 靴下 & 裏起毛ズボン & 座布団 (or スリッパ)

とても悲しい結果になりましたね・・・結局今は以下の構成の上で仕事をしています。(足裏汗っかき & 夏場を考慮しているので、湿気取りシートは必須です)

<足元のみミニカーペット>
<薄いカーペット>
<断熱シート>
<湿気取りシート>
<フローリング>

また窓枠には断熱シートを必ずしましょう。これだけで冷たい微風が発生しなくなるのでだいぶ楽になります。

出力切り替え型セラミックヒーター

  • 👍 軽くて片付けたり取り出したりが楽
  • 👍 出力は十分高い
  • 👎 局所性の関係で弱い出力だと周りが寒く、強い出力だと暑すぎる部分ができてしんどい
  • 👎 ホコリが溜まりやすく、溜まってると臭い
  • 👎 消し忘れると怖かった
  • 👎 蹴る、熱い、痛い、寝落ち危ない

出力ダイヤル可変セラミックヒーター

  • 👍 軽くて片付けたり取り出したりが楽
  • 👍 出力は十分高い
  • 👍 柔軟な温度調整が出来るので、局所的なヒーティングに関してはかなりよい
  • 👍 テント内のように閉じた空間であれば高出力でヒーティングできる
  • 👎 ホコリが溜まりやすく、溜まってると臭い
  • 👎 消し忘れると怖かった
  • 👎 蹴る、熱い、痛い、寝落ち危ない

足元を囲うタイプの遠赤外線ヒーター

  • 👍 ブランケットを併用するとシッティングだけならかなり暖かい
  • 👍 コロコロを使えばいいので掃除しやすく、匂いもない
  • 👎 案外温度調整が難しく、出力を下げても保温されてるので冷やす必要があり、だからとってブランケットを外すと一気に冷えてしまう
  • 👎 かさばるし、意外と重量があるので結構邪魔に感じる
  • 👎 膝辺りがちょうどいい出力だと足裏暑すぎて汗かく
  • 👎 足元の遊びが減って、ちょっと動かすとぶつかる(意外と強い)ので集中力をかく

靴下 & 裏起毛ズボン & 座布団 (or スリッパ)

  • 👍 シッティングにもスタンディングにも対応出来る
  • 👍 そのままコンビニにいける
  • 👍 温度調整は装備の脱着や他手法との併用で対応出来る
  • 👍 椅子の上であぐらかいたり出来る自由度の高さ
  • 👎 座布団はしっかりしたもの、またはマットにしないとスタンディングのときに危ない
  • 👎 他に比べるとちょっとコストが高くなりがち
  • 👎 睡眠への誘惑が高い

反省点

  • そもそも局所的なヒーティングや空気を暖めるタイプは今回の用途に向いてなかった
  • スタンディングとシッティングを切り替える人の場合、どちらもカバーしたいなら服での対応は前提条件
  • 足裏はヒーターじゃなくて靴下やスリッパで頑張らないと汗をかく(もちろん体質による)
  • スポーツ用のシャカシャカズボン(アレなんて言うの)を好んでいるので、ヒーターから距離を取る必要があるし、ヒーターがそもそも向いてなかった疑惑がある
  • セラミックヒーターって安物が多く、安全装置が信用ならないと感じてしまって使う気を失っていった
  • 全部買ったせいで初期コストが全く安くなかった
  • 電気代は軒並み普通に上がったのでそこまで差は感じなかった

Javaからの利用を視野に入れたKotlinコードで何をするべきか

(2015年に書いたものをコピペ)

Kotlin Advent Calendar 2015 10日目.

TL; DR

Javaからの見た目を考慮して,アノテーションと修飾子を使って整形しましょう

  • @file:JvmName@JvmStatic@JvmOverloadsをつけよう
  • constopen 修飾子は適切に
  • interfaceのdefault/static methodの扱いには注意しよう

Javaからの利用を視野に入れるということ

Null-safeの恩恵は強いし,拡張関数は便利だし,他にも色々機能はあるし,多分金髪美少女だし,Kotlinは非常に扱いやすい可愛い言語です1

さらに,普段からJavaを用いて開発しているひとにとって馴染みの深いbuild toolを用いた開発が可能です2

つまりはbuild toolさえ動けば良いので,jitpack.ioなどのpackage repositoryサービスでもKotlinの利用が可能になっています.となると,Kotlinライブラリの配布・利用を行う壁は非常に低いことが分かりますね.

Kotlinの特徴を用いたライブラリの利点は KotlinからでもJavaからでも非常に高いと思っています.Kotlinだけをターゲットとしたライブラリであれば必要ないのでしょうが,JVM上で動作する便利ライブラリとして配布していくとしたら,最低限Javaからの利用は考慮する必要があるでしょう.

それらの考慮のうち,ひとの手による調整が必要/適切だと思われるものをあげていきたいと思います.

Staticメソッド周り

Kotlinで NameSpace.method() と呼び出せるメソッドを定義する際,companion objectnamed objectPackage-level function が挙げられます3

例えば以下のような定義をKotlinでしてみましょう.

package daruma

fun debu() = true

class RedDaruma {
  companion object {
    fun fat() = true
  }
}

object Jmatsu {
  fun yaseru() = throw NotImplementedError()
}

Kotlinからであれば daruma.debu()daruma.RedDaruma.fat()daruma.Jmatsu.yaseru() で呼び出せるこのメソッドたちですね.

しかし,Javaから呼び出す際はそれぞれ daruma.DebudarumaKt.debu();daruma.RedDaruma.Companion.fat();daruma.Jmatsu.INSTANCE.yaseru(); と書くことになります.

KotlinではPackage-levelでのfunction定義が可能.
特に指定がない場合, Packege-level functionは "${filename}Kt"クラスのstaticメソッドとして充てがわれる.

でも "${filename}Kt" とか Companion ってださいしあんまり良くないですよね.そこで fileに対する@JvmName と methodに対する@JvmStatic アノテーションを利用します.

@file:JvmName("Majide") // Use Majide instead of DebudarumaKt

package daruma

fun debu() = true

class RedDaruma {
  companion object {
    @JvmStatic fun fat() = true // Don't need Companion
  }
}

object Jmatsu {
  @JvmStatic fun yaseru() = throw NotImplementedError() // Don't need INSTANCE
}

とすると,Javaからでも daruma.Majide.debu();daruma.RedDaruma.fat();daruma.Jmatsu.yaseru(); で呼び出せるようになります. ※ object の場合,インスタンスメソッドであって欲しいときには関係ありません

Kotlinを知っている人からすると xxxKtCompanion といったクラスが補完で出てきたら色々と察するところではあると思うんですが,そうじゃない人たちにとってこの部分はなかなかの障害になるかと思います.

拡張関数について

拡張関数は定義した位置のstaticメソッドとして生え,レシーバーとして第一引数に充てがわれます.つまり上記のstaticメソッドのケースが当てはまります.ということで同様の対策を行いましょう. ついついPackage-levelの位置で宣言しがちですので,@JvmName を充てましょう.

Interfaceに載せる系の実装

Interfaceのstaticメソッド

JavaだとJava8からinterfaceにstaticメソッドを定義できるようになりました.

Kotlinでは companion object を使うことでinterface上にstaticメソッドが生えたように見せることが可能です.(Kotlinから見た場合) しかしそのとき,Javaから見るとCompanionオブジェクトが生えることは上記で説明しました.

では @JvmStatic をつければよいのでしょうか? 答えはNOです.(そもそもつけられません.) つけられるとしたらそれはJava8相当の動きになることを表していますね.

結論として,前述したstaticメソッドと同等の挙動を実現することは不可能です.

そこで妥協策としては named object で実装することで Companionという名前を回避することになります.

interface Foo {
  fun foo(): String
  object Bar {
    @JvmStatic
    fun baz() = 10
  }
}

とすれば,Javaからは Foo.Bar.baz() として呼び出すことが可能です.

Interfaceのdefaultメソッド

JavaだとJava8から,interfaceのメソッドにdefault修飾子を指定することで初期実装を与えることができるようになりました. 詳細は割愛しますが,主な特徴として,interfaceを継承したクラスにおいてデフォルト実装を持ったメソッドをOverrideをするかしないかは任意となっています.

Kotlinのinterfaceではdefault修飾子は必要なく,通常のメソッド定義と同様にメソッドボディを持たせることができます. これをKotlinはJVMに解釈させるためにどう変換しているのかが問題になります. 以下の例を見てみましょう.

interface A {
  fun foo(): String = "foo"
}

class B : A {
  // Overriding 'foo()' is not needed
}

同様の実装をJava8で書くなら

public interface A {
  default String foo() {
    return "foo"
  }
}

class B implements A {
  // Overriding 'foo()' is not needed
}

となりますね.

ではKotlinで定義したinterface AJava側で継承し,自動生成してみましょう.

public class B implements A {
  @Override
  public String foo() {
    return null; // 'foo()' must be implemented!
  }
}

はい,残念ながらJava8のdefault methodと同様の動きとはいきません. では消えてしまったのでしょうか? JVM上で動いてる以上,そんなわけはないですね.

代わりに A.DefaultImpls.foo(A $receiver) が定義されています.

interface内部に定義されたstatic finalなclassにおいて,staticメソッドとして移植されるわけですね.

これに関してはKotlinから(アノテーション等で)どうこうできる問題ではありません. Javadocに「デフォルト実装がある」と書いておくくらいしか出来ません4. DefaultImplsという名前も変えることは自分ではできませんでした.また変えてしまうとデフォルト実装がどこに置かれるかがわかりづらくなるため,これに関してはそのままで良いという感じもします.

このケースではJava側が注意する必要があります. つまりKotlinのinterfaceを継承したクラスではDefaultImplsがあるかどうか,あるなら中でdecorateするといった行為が必要になるでしょう.

public class B implements A {
  @Override
  public String foo() {
    return DefaultImpls.foo(this);
  }
}

Staticでfinalなフィールド周り

Staticでfinalなフィールドはまた違う問題があります.Kotlinは優しいのでフィールドにはgetter/setterをつけてくれます5.つまり何も指定をしなければカプセル化をしてくれるということですね.そう,それが例えstaticでfinalであっても・・・

package daruma

val debu = true

class RedDaruma {
  companion object {
    val fat = true
  }
}

object Jmatsu {
  val yaseru = null // :bow::bow:
}

とすれば,

  • daruma.DebudarumaKt.getDebu();
  • daruma.RedDaruma.Companion.getFat();
  • daruma.Jmatsu.INSTANCE.getYaseru();

が推奨されたアクセスになります.この表現から分かると思いますが,

  • daruma.DebudarumaKt.debu;
  • daruma.RedDaruma.fat;
  • daruma.Jmatsu.yaseru;

は非推奨のアクセスになります.※ ちゃんとstatic&&finalで宣言されてます

こちらはアノテーションではなく,const 修飾子を使います.

package daruma

const val debu = true

class RedDaruma {
  companion object {
    const val fat = true
  }
}

object Jmatsu {
  const val yaseru = null // :bow::bow:
}

ちなみにこちらだと先ほどの推奨と非推奨が逆転した形になります.

デフォルト値を用いたメソッド・コンストラク

Kotlinを使っていると結構使うのがデフォルト値.便利の一言に尽きます.

class GitApi {
  fun push(remote: String = "origin", branch: String = "master", force: Boolean = false) {
    // do something
  }
}

実はこれ,Javaから見ると GitApi#push(String, String, boolean) しか定義されていません.割と困ります.そこで @JvmOverloads アノテーションを使って解決しましょう.

class GitApi {
  @JvmOverloads fun push(remote: String = "origin", branch: String = "master", force: Boolean = false) {
    // do something
  }
}
  • GitApi#push(String)
  • GitApi#push(String, String)
  • GitApi#push(String, String, boolean)

@JvmOverload アノテーションにより自動で各メソッド(今回だと追加で2種類の計3種類)が定義されます.引数が全て揃ったメソッド以外は中でdispatchするだけの存在になります.

実はこれコンストラクタにも使えるんですよね,ということで

Androiderの方には下記のコンストラクタ定義が結構おすすめ

import android.content.Context as Ctx // 横に長過ぎたので
import android.util.AttributeSet as AS // 同上

class CustomView : View {
  @JvmOverloads constructor(ctx: Ctx, attrs: AS? = null, style: Int = 0): super(ctx, attrs, style) {
    // init action
    attrs?.apply {
      // load the attributes
    }
  }
}

クラス継承とOverride

9日目で @RyotaMurohoshi さんがちょうどこの周りの話をしてくださいました6

Kotlinでは継承に関して未指定の定義 == Javaでいうfinalで修飾された扱い となります.

これに関しては存在そのものになど賛否両論あるかと思います.ただ賛否どちらでも変わらない事実として,「このクラスは継承させたら困るからfinal」と考えていた人は「このクラスは継承してもいい/されることもあるからopen」という逆の考え方をしなければなりません.

自分のKotlinコードであればエラーが出た際に修正する程度で構わないのかもしれませんが,他人のライブラリ,あるいは自分が他人へ提供するライブラリではそんなことも言えないので,気をつける必要があるでしょう.

まとめ

  • Package-level declaration は @file:JvmName で空間名の指定
  • 静的メソッドは @JvmStatic で生やす場所の指定
  • 静的な不変値は const
  • デフォルト値は @JvmOverloads で自動多重定義
  • open の指定は適切に
  • interfaceのstaticメソッドは named object & @JvmStatic
  • interfaceのdefaultメソッドはJava8とは違う挙動になり,DefaultImpls

Kotlinは非常に便利ですが,Java100%相互運用という利点を活かそうと思うと,やはりひとの手である程度の補正を行う必要が出てきますね.

おまけ

IntelliJ上でのsealed classの扱い

Kotlin M13よりsealed classというものが追加されました7

sealed という修飾子をつけると,そのclassを継承する際に制限をかけることができます.その制限とは,「sealed classを継承するクラスはそのsealed classの内部クラスであること」になります.

sealed class A {
  class B : A() // ok
}

class C : A() // error

Javaから使った場合もちゃんとコンストラクタ周りで怒られて継承はできません.正しい挙動ですね.

ただ現状,IntelliJだと以下のコードにエラーがエディタ上に表示されません.

package daruma

sealed class A
public class D extends daruma.A {
    public D() {
        super(); // 本来なら private access ということで怒られるはず
    }
}

勿論コンパイル時にはエラーとして検出されるので,Runtimeでは全く問題がないのですが.

Kotlinのコードをjarにし,Eclipseで書いてみるとちゃんとエラー扱いになります.ただこのケースでもIntelliJだとやはりエラーが見えないという・・・ Javaでprivateコンストラクタのみを宣言したクラスに対する継承の場合はちゃんとエラーが出るので,Kotlinプラグインの問題なんですかね.

Javaから見るドキュメント

KDocを書いたときのJavaからの見た目ですが,特に差異はないです(違いを見つけていないだけかもしれませんが).


下記の環境の元,書かれました.

  • Kotlin 1.0.0-beta-3595
  • Android Studio 1.5.1 #AI-141-2456560 w/ plugin IJ141-11
  • IntelliJ IDEA 15.0.1 #IU-143.382 w/ plugin IJ143-26

  1. 個人の感想です

  2. 現状はAnt,Maven,Gradleの3種類を公式がサポート.SBTプラグインを書いてる有志も.

  3. 他にあったらぜひ教えてください

  4. 何かしらの方法をご存知の方は教えてください

  5. propertyなので

  6. 著者 @RyotaMurohoshi さん,Qiita Kotlinのクラスの継承とメソッドのオーバーライド、あとポエム

  7. 筆者 @ngsw_taro さん,算譜王におれはなる!!!! Kotlin M13で追加されたsealed class

Cron で動かすスクリプトの開発方法例

(2015年に書いたものをコピペ)

普通の使い捨てスクリプトと同じ作り方をしてませんか?

そういう人がよくぶち当たるのが,cronで実行したらコマンドが見つからないだとか環境変数が設定されてなくてもうダメという状況.

そんな状況を回避するには以下の2つの手法を取りましょう.

  • ジョブ実行前に環境変数を設定する
  • cronでの実行環境を再現してジョブを作成する

結論だけ見る方はこちらのテンプレート

ひとまず,環境変数の確認方法

環境変数を出力するジョブを追加します.

* * * * * env >/tmp/cron_env

/tmp/cron_envの中身を見てみましょう.下記は筆者の端末(Mac)で実行した場合です.

SHELL=/bin/sh
USER=jmatsu
PATH=/usr/bin:/bin
PWD=/Users/jmatsu
SHLVL=1
HOME=/Users/jmatsu
LOGNAME=jmatsu
_=/usr/bin/env

少ないなんてレベルではありませんね. PATH に至っては正直使い物になりません.コマンドが見つからないわけです.

cronで実行するときのみ有効になるよう環境変数を設定する

なければ設定してしまえば良い,という形です. 複数の方法があり,それぞれ少しずつ特徴があります.

ログインシェルとしてシェルを起動し,それぞれのprofileを読み込む

lオプションを渡してログインシェルとしてシェルを起動させ,~/.bash_profile 等を読み込ませる方法です.

* * * * * /usr/local/bin/bash -l -c 'env > /tmp/cron_env_2'

自分馴染みの環境変数を設定することが可能になります.

ただしそのジョブごとに必要最小限の環境変数を設定するといった行為はできませんし,他人と共有するジョブで取る手段としては良いと言い切れない部分があります. 当然のことながら,lオプションでログインシェル化できないシェルは利用できません.

また読み込みに行く設定ファイルはBashだと /etc/profile, ~/.bash_profile, ~/.bash_login, ~/.profileであり,明示的に呼び出さない限り ~/.bashrc は読み込みにいきません. インタラクティブシェルとログインシェルの違いですね.

crontabに直接環境変数を設定する

こちらも各ジョブに対する設定ではなく,全体になります.

PATH=/usr/local/bin:/usr/bin:/bin
SOME_VAR="cron_env_3 and cron_env_4"

* * * * * env > /tmp/cron_env_3

SOME_VAR2="cron_env_4 only"
* * * * * env > /tmp/cron_env_4

明示的に指定することができ,影響範囲も前述の方法と違って完全にcrontab内に収まる形です. しかしながら,PATH=/usr/local/bin:$PATHPATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin と記述することはできません. まだ変数が設定されていないため・・・とかではなく,そもそもシェルスクリプトではないからです.

コマンド実行時に環境変数を明示的に指定する

こちらはシェルでお馴染みの記法です.

* * * * * PATH="/usr/local/bin:$PATH" env > /tmp/cron_env_5

各ジョブごとに設定することが可能ですし,シェルに解釈させる部分に記述できるので非常に書きやすいでしょう. 個人的にも正攻法だと思いますが,記述が長くなってくるとどうしても厳しいものがあります.

コマンド実行前に指定した設定ファイルを読み込んでおく

ジョブごとにファイルを用意すれば,柔軟に環境変数を定義しておくことが可能です.

export SOME_VAR="Hey"
# ;の次にスペースを入れると正しく動作しません.

* * * * * . ~/cron.env;env > /tmp/cron_env_6
# or
* * * * * source ~/cron.env;env > /tmp/cron_env_7

外部ファイルに必要な変数を分離しておくと非常に管理がしやすいです. 特にVCS管理下であれば,同じリポジトリ上に置いておくだけで非常に共有しやすいでしょう.

指定した外部設定ファイルを読み込んでから実行するジョブにする

上記の記法ではスペースを入れると動かないといったヒューマンエラーが避けられませんね.

ならばその自体をスクリプトとしてラップしましょう. 個人的に最良だと思っている選択肢がこれです.

* * * * * ~/run_with_env.sh > /tmp/cron_env_8
#!/bin/sh

# 外部ファイルから環境変数を読み込む
source ~/.some_profile

# 環境変数の設定を記述する
export SOME_ENV_VAR="Hey"

# job
env

cronで実行するときの環境変数を再現してデバッグする

前提

  • cronジョブ実行時の環境変数を前述した方法で取得し,/tmp/cron_env(任意)に保存しておくこと

また実行したジョブは ~/somejob.sh で実行できるとします.

直接再現する

env - $(cat /tmp/cron_env) ~/somejob.sh

として起動することで直接cronにおける環境変数状況を再現することができます.

これは最初に環境変数を空にし,

env -

次にcronジョブ実行時の環境変数を読み込むよう設定し,

env - $(cat /tmp/cron_env)

その環境変数の状態でジョブを実行することを表します.

その上で足りない環境変数は上記で紹介した いずれかの方法で設定する必要があります.

インタラクティブシェル env - $(cat /tmp/cron_env) /bin/sh 上でのデバッグやaliasの設定を行うと楽でしょう.

ただしこれはジョブを実行するステートメントで効くものであり,使い勝手が良いとは個人的には思いません.

間接的に再現する

環境変数をcronジョブ実行時と同様のものに設定し,なんらかの方法で環境変数を設定,ジョブ実行をするようなハブとなるスクリプトを用いてデバッグしましょう.

#!/bin/sh

# reset env
while read line; do
    eval "$line"
done < <(diff <(env) /tmp/cron_env|grep "^[><]"|grep -v " _="|sed -e 's/^< \([^=]*\)=.*/unset \1/' -e 's/^>/export/'|sort -r)

~/somejob.sh

環境変数リセット部分は以下の方針になります.

  1. 現在の環境変数とcron実行時の環境変数の差分を取る
  2. 差分行情報はいらないので実差分情報だけ取得する.また _ は除いておく.
  3. 差分形式を置換する.現在の環境変数をすべてunsetし,cron実行時の環境変数をすべてexportする形に整形する
  4. 先にunsetするようにソートする
  5. 整形した文字列をwhileに流し込み,1行ずつevalすることでexportあるいはunsetする.

この形式でデバッグを行うと,実行時には環境変数リセットの部分を除いた hub.sh をcronのジョブとして指定すれば良いわけですから非常に楽ですね.

直接的な再現と間接的な再現の差異

外部ファイルからの環境変数設定を考えます.

間接的に再現するケースでは,外部に環境変数用のスクリプトを用意し,source あるいは . による読み込みを行うことで実現されます.

#!/bin/sh

# reset env
while read line; do
    eval "$line"
done < <(diff <(env) /tmp/cron_env|grep "^[><]"|grep -v " _="|sed -e 's/^< \([^=]*\)=.*/unset \1/' -e 's/^>/export/'|sort -r)

source ~/.some_profile

~/somejob.sh
export EXT_VAR1="one"
export EXT_VAR2="two"

これは完全にシェルスクリプトですね.必要なように実行シェルをカスタマイズするわけです.

直接的に再現するケースでも当然のことながら不可能ではありません.

env - $(cat /tmp/cron_env) $(cat ~/ext_vars) ~/somejob.sh
EXT_VAR1="one"
EXT_VAR2="two"

しかしながら外部ファイルの中身の記述を見れば分かる通り,これはシェル変数の記述となります. つまり実行コマンドと同時に設定する方法以外では,環境変数用のファイルとしてそのまま用いることができません. まあ一手間加えて,source <(awk '$0="export " $0') のようにすればできますが.

間接的再現の仕組みの方が記法としても環境変数として直感的ですし,個人的に軍配は間接的再現側にあがると思っています.

結論

~/somejob.shというジョブを実行するという前提.

デバッグ時の環境

% ~/for_debug.sh

#!/bin/sh

# reset env
while read line; do
    eval "$line"
done < <(diff <(env) /tmp/cron_env|grep "^[><]"|grep -v " _="|sed -e 's/^< \([^=]*\)=.*/unset \1/' -e 's/^>/export/'|sort -r)

# 外部ファイルから環境変数を読み込む
source ~/.some_profile

# 環境変数の設定を記述する
export SOME_ENV_VAR="Hey"

~/somejob.sh

実行時の環境

* * * * * ~/for_runtime.sh

#!/bin/sh

# 外部ファイルから環境変数を読み込む
source ~/.some_profile

# 環境変数の設定を記述する
export SOME_ENV_VAR="Hey"

~/somejob.sh

何か間違いやアドバイス等ありましたらコメントにてお願い致します.