『モチベーション3.0』を読んだ

あけましておめでとうございます。お正月休みに実家で読んだ。いまさら感&非技術書で書くほどのこともないけど、感想を晒す。

bookclub.kodansha.co.jp

いわゆるビジネス書の類でベストセラーになっているし、まずタイトルが胡散臭い。単純労働から創造的労働が主体となる社会において、モチベーションに対する考え方も変える必要がある、という話。著者によるTED動画があるのでみてみるとよいかも。

www.ted.com

TEDでも本書でも語られているが、創造的労働としてソフトウェアエンジニアが挙げられており、Google社やAttrassian社を例に紹介している。また本書はGitHub社でも参考にされているらしい*1。自分の立場から感想を書いてみる。

内発的動機づけ

モチベーション1.0を「生存や安心に基づく動機づけ」、2.0を「アメとムチに駆り立てられる動機づけ」と定義している。近代社会は2.0に基づいて設計されており一定の成果を上げてきたが、社会の変容により不整合が起きている。具体的には、報酬と制約によるマネジメントは、単純労働には向いているが、創造性のある労働には適合しない。例えば、一定の水準を超えた報酬は、モチベーションの向上に寄与しない。「成果が出れば給料を上げる」とマネージャから言われたからがんばる、というのは、外発的動機づけと呼ばれる。自分以外のところにモチベーションの源泉がある状態。これに対し、本人の内部から発生する内発的動機づけが必要だと述べている。

内発的動機づけは、曲解すると、「やりたいからやる」という状態。報酬はこれを阻害する可能性がある。例えば、自分はマンガを読むのが好きだが、「マンガを読むと1冊あたり100円を与える」と言われると、読みたくなくなる。らしい。たしかに短期的には喜んで読むかもしれないが、長期的にはそれを強いられると苦痛になりそう。読みたくて読んでいるからこそ、いつまでも読んでいられる(と感じる)。コードを書くのも、趣味だと楽しいのに仕事だと楽しくない、ということは往々にしてあるが、これはその内容だけでなく、(給与にかぎらず評価や称賛などの)報酬によって内発的動機づけが阻害されている可能性がある。

特にソフトウェアエンジニアリングは、おそらく他の職種と比して平均的に創造性のある労働に分類される。ソフトウェアエンジニアにとってだいたいの仕事は未知である。自分も同僚も、過去にやったことがないことをやる。そのため、内発的動機づけが重要になる。

自律性・熟達・目的

内発的動機づけのために必要な、三大要素的なやつ。まず「自律性」とは、何をやるか、いつやるか、どうやってやるか、だれとやるかなど、仕事に対して自分が決定権を持つこと。わかりやすくは、20%ルール*2というのがあり、実際にここから独創的な発想が生まれ、主力プロダクトになるケースも聞かれる。オーナーシップという言葉と似ているのかも。

次の「熟達」は、なにか価値のあることを上達させたいという欲求。体感としても一致していて、仕事の中で必要な情報を調べたり考えたりして、結論「わかった」という状態になったときが一番気持ちいい。「できた」とか「評価が上がった」ということに対しては、客観的にみて嬉しいという感情はあるけど、内発的な感動はない。ドゥエックは、人の信念が熟達の内容を決定づけると主張し、これを自己理論と呼んでいる。自己理論において、「知能は存在する分しか存在しない」という固定知能観と、その反対の拡張知能観に分かれる*3。熟達に必要なのは後者のマインドセット。脳科学領域における可塑性は一般に認められている。脳が気持ちいいと思うことをやっていきたい。

自律性をもって熟達を目指す人間は、「目的」をもつとさらに大きな成果をあげるらしい。外部から目的を与えればよいということではなく、あくまで内発的に目的を保つ必要がある。組織にいる以上は組織の目的が前提にあり、それと自身の目的が紐づく状況を作るとよさそう。そう考えると、OKRのようなツールがでてくるのも理解できる。ソフトウェアエンジニアにとっては、コードを書くのが楽しいから続ける、というだけではなく、それが組織の目的に合致して結果的に貢献できることは、評価とフィードバックに結びつき、中長期的には強い動機づけになりそう。

まとめ

報酬による画一的な動機づけだけでは、自分も含めてソフトウェアエンジニアのモチベーションを保てない。やりたいからやるという状態を、本人も環境も作っていかないといく必要がある。ただし、報酬は低くてもいいということではなく、市場や他社との比較も含めて「気にする必要がない」くらいには与えられる前提であり、本書はその先のレベルの話である。つまり給与は1000倍にしてほしい。とはいえ、シャの仕組みとか偉い人たちの主張について、多少は理解が進んだかもしれない。

ビジネス書は普段あまり読まないし、読んだとしてもブログには書いていなかった。今後は、おもしろかったら書いてみようかという気持ちになった。本を読むのも、それ自体が目的になっていいし、熟達に向かう快感がある。コードを書くのもそうで、別に勉強のための勉強でもいいと思う。熟達に向けてコードを書こう。

*1:https://speakerdeck.com/schacon/robots-beer-and-maslow?slide=122

*2:業務時間のうち20%を使って好きなことをやっていい、というやつ

*3:https://fs.blog/2015/03/carol-dweck-mindset/

2019年の振り返り

年末年始、帰省している。昨日まで暖かかったのに、今日はすごく寒い。冬かもしれない。昨年も振り返りをしていたので、今年も書いてみる。

エンジニアリング

シャでは、レガシーに始まりレガシーに終わった一年だった。CI周りからフロントエンドのパッケージ管理、データアクセスのライブラリまで、いろんな領域のレガシーに触れた。問題にぶつかり続けてかなり消耗したが、幸いにも周囲に助けられながら進めてくることができた。できてない。

レガシーという言葉を使ってきているが、自分の感覚としては広くソフトウェア自身が抱える課題の解決である。新規機能の開発プロジェクトとの違いは、顧客に価値が届く時間でしかない。2020年もレガシー改善をやることになりそうだし、よりソフトウェアにとってクリティカルな課題を解決できるとよさそう。

いわゆる上流工程にあたる要件定義、計画、設計の重要さ・難しさを改めて痛感したし、そこにおいても基礎的な技術知識の不足をかんじた。技術力に関しては長年の悩みであり劣等感すらあったが、いいかげん自分の限界はなんとなくわかってきた気がする。スーパーエンジニアにはなれないし、周囲の人間と比して、ある側面で劣る部分も受け入れつつある。この人間を活かすにはどうするべきか、2020年は仮説検証してみたい。

ひとつあげると、他者に共有すること、あるいはそれに向けて情報を整理する過程は、自分にとって技術面の向上という意味で一定の効果があるように思える。昨年末に書いたシャブログをきっかけに京都オヒスでイベントをやったし、2019年版のシャブログも書いた。曖昧な部分も言語化するし、正しい情報を伝えるために改めて学習する。自分にとってかなりエネルギーを消費するが、これ自体もトレーニングだと思って、2020年は継続してみる。

採用・評価・コーチング

ポジションとして何か大きな役割を持っているわけではないが、メインの開発業務以外も地味にやっていた。新卒採用の一次面接官を3年くらいやってきて、何かがうまくできるようになったという感覚は皆無だが、つまるところ一次面接官の判断基準は、一エンジニアとして「一緒に働きたいかどうか」に集約されるといってもいいかもしれない。

今年の後半には評価に一部関わる機会があった。全員がやる360°評価的なやつとは別に、いわゆる職能に関わる評価において、第三者的・客観的視点を提供する役割だった。人間が人間を評価すること自体に無理があるというのは置いておいて、やるだけのことはやった気がする。同時に、自分より年次の低い他チームの同僚に対して、コーチング的に接する機会もあった。1on1をやるのか、成果・成長につながるのかは難しい問題ではあるが、できることをやるのがサラリーマンの務め。どうしても目に見える効果が出づらいぶん、自分には苦手な領域である。今後ももし機会があるなら、せっかくやるならば、エンジニアリングと同じく、言語化して伝えられるくらいには理解したい。

健康

2019年最大の目標だったが、満足のいく結果は残念ながら得られなかった。どうも近年は不定愁訴的な趣があり、運動や食生活などいろいろ試してはみたが、改善には至らなかった。自身の状態を観測するのが苦手らしい。30代の身体は一種のレガシーシステムであり、単発の対処療法ではなく、継続的な改善あるいは自律的にそれがなされる環境を構築する必要がある。さしあたり、体調の良し悪しを計測したい。開発生産性よりは計測しやすいかもしれない。

不定愁訴の一要因として、脳疲労と呼ばれる状態を自覚することが増えた。単に会社を休むとか体を休めるのとは違って、積極的休養が必要に感じる。そもそも自覚できるようになったことが進歩であり、2020年は改善に向けて計測と施策を講じたい。この類はインターネッツも書籍もエセ医学が蔓延していて、しんどいものがある。知見をお持ちの方は教えてください。

改善点を挙げるなら、昨年の扁桃切除手術により、風邪で喉がやられる回数は劇的に改善し無になった。乾燥に怯えることがなくなったぶん、精神的な負担も激減した。また、健康診断においてアレルギー検査をしたところ、ハウスダストアレルギーが発覚した。空気清浄機を導入し、毛布を廃止するなど対策を講じたところ、劇的に改善した。逆に、今までなぜやらなかったのかと考えると、やはり明示的に検査結果が出たというのが大きい。お金で解決できることは解決する。

海外旅行

昨年の振り返りに書いていた、海外旅行に行くことができた。人生で初めて国境を越え、オーストラリアのパースという土地にいった。初海外旅行にしてはニッチなところらしく、いい意味で観光地感がなかった。別世界の日常に入り込むかんじが楽しかった。

これを機に、2020年もまたどこかにいってみたい。海外の技術カンファレンスとかもよさそう。英語はまったくわからんけど。

趣味

とりたててツイートなどしていないが、例年どおりそこそこな数のお笑いライブにいった。唯一の趣味といってもいいかもしれない。とはいえ一時期に比べるとだいぶ回数は減った。なんとなくこのあたりの熱量も、年齢を経るにつれ減少していく気がする。2020年はさらに減るかもしれない。寂しい。

進捗としては、今年はライブに行ったことのない友人を連れて行くことにも成功した。今後行ってみたいという方は気軽にお声がけください。

2020年に向けて

何かを考えているふりしてなにも考えていないダメ人間の典型なので、年が明けたころにはだいたい忘れて、また年末に同じ振り返りをしているはず。というか、結局のところ健康が第一。あらゆる活動のボトルネックになる。にもかかわらず、近視眼的な対策ではどうにもならないことが今年を通じてわかった。2020年は戦略的に取り組む。まずは体重を増やしたい。

関わってくれたみなさま、ありがとうございました。よいお年を。すべて感謝。般若。

壁 Advent Calendar 2019の総括

壁 Advent Calendar 2019 25日目の記事です。

adventar.org

なぜか隔年で実施している壁カレンダーだが、今回で第3回となった。今年は絶対に開催せずしれっとなかったことにしようと目論んでいたが、気づいたら発生していた。制作者かつ最終日ということで、僭越ながら各記事の感想を書く。

感想

1〜5・7・8日目

www.evernote.com

2日目 3日目 4日目 5日目 7日目 8日目

全7回に渡る長編、最後に物理壁をぶち壊す話。東大ロールモデルであらせられるひぐ様が、東大ロールモデルに至る軌跡を記した伝記の一部。

おそらく後世にはこの記事がまとめられ、「ひぐ力」みたいなタイトルで新書として出版される可能性が高い。ちなみにひぐ様と会ったのは2回くらいで、こういうノリが許される関係性なのかすら忘れた状態でこれを書いている。美味しいビールをいただくのを楽しみにしています。

6日目

www.evernote.com

ボルダリングの壁。壁カレンダーの起源は発起人である私がボルダリングにハマっていたことであり、初回の壁カレンダーは半数がボルダリングの記事だった。それを蹂躙され、いまの多様性を許容する形式となった。そんな中でボルダリングの記事を書いてくれた。唯一の良心。今後もボルダリングをしっかりやっていってほしいし、京都に帰った際にはぜひセッションしたい。

9日目

note.com

中古住宅を購入してリフォームした話。この段階で壁との関連性はかなり薄まってきた。私からみると学生時代の研究室の先輩にあたる。ちょうど先日おじゃました際には、おもしろ社内システムを鑑賞したり、ヒロシちゃんねるを鑑賞したりした。

最高の家だったし、よくよく聞くとリフォームの過程で壁をぶち抜いていたため、本カレンダーにふさわしい。

10日目

www.evernote.com

リングフィットの壁。他に類をみない有益記事であり、壁カレンダー以外の然るべき場所で発表されていれば、インターネッツで大いに評価されたに違いない。リングフィットアドベンチャー自体、同僚の間でもめちゃくちゃ流行っており気にはなっていたため、雰囲気がすごくわかった。センサの精度とかゲームとしてのおもしろさに関しては、顧客が本当に欲しかったものみたいなかんじで、ものづくりの観点で良さみを感じた。

11日目

yukara-ikemiya.github.io

シンガポールの壁。というか、シンガポールのビアバーが紹介されている。ビールのことはまったくわからないので、このあたりをさらに深堀りした解説を、プロであるひぐ様に書いてほしい。はやめに。

12〜14・21日目

13日目 14日目 21日目

タルトタタン、アイスクリーム、フィナンシェ、マドレーヌの作り方。どうがんばっても壁には結びつかない。手書きメモをツイしURLを貼り付けるという超絶手抜き。ガッキーだから許される。ちなみに9日目の中古住宅におじゃました際に持ってきてくれたのでいただいた。美味かった。スイーツで一旗あげ、第二のロールモデルを目指して邁進してほしい。

15日目

yukara-ikemiya.github.io

自宅をホームシアターにする話。めちゃくちゃ有益。ホームシアター化を画策する一人暮らし人間におかれましては、インターネッツに巣食うアフィブロガーの記事をくぐり抜け、この記事にたどり着くことを願うばかりである。

16〜19日目

www.evernote.com

17日目 18日目 19日目

ある意味で、壁カレンダーを象徴する作品。16日目のラップは圧巻だし、最終日にはゲームが用意されているので、ぜひ挑戦してみてほしい。感想とかはうまく言語化できない。もうわけがわからん。

20日目

sorami-chi.hateblo.jp

ドイツ語の壁。ドイツ語だけでもまったくわからんのに、さらに派生(?)であるSwiss Germanという、フランス語Remixみたいな言語について紹介されている。著者のブログには他にもチューリヒ長期滞在についての有益実体験記事があるので、チューリヒ長期滞在予定の方は参考にされたい。

22日目

pankz.hatenablog.com

天井裏の配管が破裂して水漏れした話。いいかんじに破裂しビチャビチャになったとのことで、本当に心から良かったと思っている。ポタポタとかそういうレベルではない様子がよくわかる。本当に良かった。

23日目

www.evernote.com

次元の壁を超えようとした話。かっこよすぎ。これについて私が何かを言うのはおごがましい。ただ最高。

24日目

yukara-ikemiya.github.io

23日目の挙式の参加者が、当日の写真たっぷりに紹介した記事。ガチ感が本当に最高。現時点で「追記予定」となっているが、すでに最高みが溢れている。

総括

シンプルに各記事がおもしろく、とても楽しめた。それ以上の感想は正直ない。ほとんどなにも考えてない。各自が思い思いの「壁」について文章を生成しており、人間の多様性が垣間見える。ちなみに参加者は自分の知る限り(知らない人もいる)自分以外はほぼ研究者であり、これは従来どおり。ただ所属が変わっている人が多く、人生ってかんじがある。

いまどきインターネッツ界隈でもアドベントカレンダーの慣習はこなれてきて、技術やキャリアのまじめなやつや、定番のやつが目立つ。わけのわからんやつはだいたい個人の思いつきで、カレンダーが埋まらず終了しているものがほとんど。そんな中で、壁カレンダーは自由の文化を維持しつつ隔年で3回、全日程を埋めて完成している。再来年はどうなるかわからないが、今度こそ絶対に途絶えさせたい。

f:id:kyabatalian:20191224171017p:plain

『リファクタリング(第2版)』を読んだ

かの古典的名著『リファクタリング』が全面改訂された第2版の翻訳書が発売されたので、読んだ。ちなみに第1版は読んでいない。

www.ohmsha.co.jp

初版との違いとして「サンプルコードがJavaからJavaScriptに変更になった」ということが目立つが、根本的には、クラスを中心とした設計を減らしていくという大方針に従って技術選定した結果と思われる。時代ですね。

The reorientation towards a less class-centered view is a large part of this.
The Second Edition of "Refactoring"

説明の分解能

各リファクタリングの手法について、動機・手順・例を詳細を、他にないほど丁寧に説明している。例えば、「変数名の変更」をする際の手順が以下のように紹介されている。

  • 変数が広く使われている場合、カプセル化を検討する
  • 変数への参照を探し、それらすべてを変更する
    • 変数が別のコードベースから参照されている場合 ...(略)
    • 変数が変更されない場合 ... (略)
  • テストする

プログラミングを仕事でやっている人で、「変数名の変更のやり方がわからない」という人はほぼいないと思う。そのため、ここに書かれていることはかなり冗長に感じる。一方で、変数名の変更を「常に最適な方法でやっている」と自信を持って言える人は少ない気がする。本書の価値はそこにありそう。例えば、新規サービスを開発するプロジェクトにおいて、どうやって作るか、なぜその方法を選択するのかについて、説明を求められることは普通にある。ただそこからどんどん粒度を細かくしていったとき、どこまでその合理性を説明できるか。

本書はなにげなくやっているリファクタリングについて「自分がいま何をやっているのか、なぜそうするべきなのか」を言語化し、説明可能な状態にしている。これはまた、熟練者の「感覚でやっている、慣れろ」という謎の説明放棄に苦しむ初学者にとっても、助けになるかもしれない。同時に、初学者の側も、「なにがわからないのか」を分解して言語化する努力をすべき。というかそれができたら実は問題が解けている、ということが往々にしてある。説明の分解能は理解度に直結している。その意味で、著者はリファクタリングをめっちゃ理解している。

タスクの細分化

人間の脳はシングルコアであり、マルチタスクには向かない。例えば、リファクタリングと、他の目的(機能追加やチューニング)を混同して同時にやろうとしてはいけない。前述と同じで、「いまなにをやっているのか?」と聞かれてシャープに答えられる状態が理想。リファクタリングをするためには、計画が必要。既存のコードに手を入れる場合に「まずリファクタリングし、そのあとの機能追加する」というように計画に組み込まないと、片手間にやろうとしてもだいたい失敗する。あるいは、片手間にすらやろうとせず既存のコードに手を入れてしまうと、レガシーコードに工数が飲み込まれる。

そもそもタスクを細分化することが必須の場面もある。なぜなら、だいたいのレガシーシステムは肥大化し、「つくりなおす」みたいな非現実的な夢を語ることしかできなくなる。これは明確な思考停止状態。段階を踏めないか、と考え、まずプロジェクトを割れるだけ割ってみる。細かなところでいうと、上述の変数名の変更の例では、手順の1つ目に「変数が広く使われている場合、カプセル化を検討する」とある。変数がいろんなところから参照されているなら、それらを一気に変更する必要がある。副作用や漏れがあるかもしれない。カプセル化すれば、参照もとの変更は、一気にやらずひとつずつ段階的に移行できる。コストとリスクのトレードオフでもあるが、選択肢があると戦略を練る余地がある。

また、計画段階でリリースを細分化できることが多い。そうするとバグなどのリスクを低減できるし、featureブランチが不必要に育っていくという状態を防げる。自分がこれまでにやったプロジェクトを振り返っても、うまくいかない部分はだいたいこの計画段階に集約される。プロジェクトの要件と仕様がある程度わかった段階で、どうやってリリースするかを具体的に想像すると幸せになれそう。

設計と計画だいじ

「デザインスタミナ仮説」ははじめてきく言葉だった。内部の設計を入念にやれば、ソフトウェア開発をより長い期間、より速い速度でできる、という仮説。仮説なので、もちろん実証はなく、著者のこれまでの経験に即しているらしい。

martinfowler.com

会計的なメタファを使うと、設計償却線が存在する。そして、この設計償却線が、一般に想像するよりもはるかに下にあるのでは、ということをいっている。つまり、一般に設計が足りていないという主張をしている。前述のスコープを刻むというところにも即するが、設計のための調査と、それに基づいた実装は、意識して区別したほうがいい。ついコードを書き始めたくなる気持ちはすごくあるけど、情報が不足している状態で実装を始めると、だいたいろくなことにならない。

また、本書では自動テストについても触れている。テストコードを書く段階をいつ設けるかはそれぞれの事情があるけど、すくなくとも新たなインターフェイスを定義し、それに期待する振る舞いがある場合は、テストを書く前提で設計したほうがいい。

手動テストははらわたがちぎれるほど退屈 (p.91)

まったくもって同意。テストを書くことは、品質を担保するだけでなく、これから何を作るかを明確にできる。どんなテストを書いていいのかわからなければ、それは何を作っていいのかわかっていないという状態なので、作り始めてはいけない。既存のコードでテストが書きづらいものもあるが、だからこそリファクタリングが必要とされる。計画し、必要ならしっかり議論しましょう。

PostgreSQLのクエリプロトコルと起こりうる問題

Postgres Advent Calendar 2019の11日目。クエリプロトコルについておしごとで調べる機会があったため、にわか知識でさわりを紹介してみる。.NETのコードやライブラリを例に出すが、PostgreSQLの仕様に関するおはなしのはず。

アプリケーションがRDBMSと通信するとき、多くの場合、クライアントライブラリを使って通信を抽象化している。そのため、おそらくほとんどのソフトウェアエンジニアは日常的に通信のプロトコルを意識することはない。ただし、クライアントライブラリを実装する場合や、後に述べる特異なケースにおいて、プロトコルの仕様が重要になる。

簡易クエリと拡張クエリ

PostgresSQLはフロントエンド(クライアント)とバックエンド(サーバ)の通信に、メッセージベースのプロトコルを使う。このプロトコルには、通信の開始・終了や、データのやりとり(クエリ)、コピー、レプリケーションといった用途に応じて、複数のサブプロトコルが存在する。本記事ではそのうち、クエリ用のプロトコルに触れる。PostgreSQLのクエリプロトコルは、大きく簡易クエリ拡張クエリの2種類がある。

詳しい挙動の違いは公式ドキュメントにあるが、ものすごくざっくり書いてみる。簡易クエリは、パラメータをテキストとしてSQLの文字列に埋め込み、送信する。PostgreSQLは受け取ったSQLを実行する。それに対し、拡張クエリでは、クエリの解析・パラメータバインド・実行という一連の処理が、それぞれ別のメッセージとして送信される。クエリの解析結果は使い回すことができるため、例えば同一セッションにおいて*1同じSQLでパラメータだけ異なるクエリを大量に実行する場合、性能が向上する。機能的にはPREPAREと等価であり、実際内部的にもPREPARE/EXECUTEに相当する処理が実行されている。

各プロトコルの検証

クライアントライブラリによって、使っているプロトコルが異なる。どちらのプロトコルを使っているかを知るには、仕様・実装をみる、postgresのログをみる、パケットを覗くなどある。今回はPostgreSQLのログをみてみる。.NETのPostgreSQLクライアントライブラリであるNpgsqlは、メジャーバージョンが2のときに簡易クエリ、3以上では拡張クエリを使っている。比較にちょうどいいので、このライブラリを使って、それぞれのバージョンで同一のコードを実行し、PostgreSQLのログの差異を確認してみる。

using (var conn = new NpgsqlConnection(connnectionString))
{
    conn.Open();

    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = "SELECT @test;";
        cmd.Parameters.AddWithValue("test", NpgsqlDbType.Integer, 100); // 型を指定してパラメータをセット
        cmd.ExecuteScalar();
    }
}

このコードをNpgsqlの旧バージョン新バージョンで実行する*2。postgresql.confにおいてログレベルを最大の log_min_messages = debug5 としておく。

旧バージョンにおいて、クエリの実行に関するログは以下。 @test のところに 100 が文字列として埋め込まれ、かつintegerにキャストされている。簡易クエリでは、パラメータをSQLに文字列として埋め込み、かつ型情報を使ってキャストされることがわかる。

2019-12-08 19:33:39 JST LOG: 実行 SELECT (('100'::integer))

続いて、新バージョンのNpgsqlにおいて、クエリの実行に関するログは以下。「解析」「バインド」「実行」と、分割されていることがわかる。

2019-12-08 18:52:26 JST DEBUG:  解析 <unnamed>: SELECT $1
(略)
2019-12-08 18:52:26 JST DEBUG:  バインド<unnamed>: <unnamed>
2019-12-08 18:52:26 JST LOG:  実行 <unnamed>: SELECT $1
2019-12-08 18:52:26 JST 詳細:  パラメータ: $1 = '100'

「解析」がPREPAREに相当する処理で、内部的には以下のSQLと等価になる。unnamedとなっているのは無名のPREPAREであることを意味するが、これは拡張クエリを介してのみ許可されている。

PREPARE unnamed (integer) AS (SELECT $1);
EXECUTE unnamed ('100');

簡易クエリではパラメータの型情報を使ってキャストしていたのに対し、拡張クエリではPREPARE文の宣言時に使われる。

影響をうける実例

クライアントライブラリがどちらのプロトコルを使っているかは、アプリケーションの視点からは隠蔽されている。そのため、日常的には意識する必要がないが、特異なケースのおいて問題になる。先述のNpgsqlを例に挙げて紹介する。上記のプロトコル変更は、リリースノートにおける以下の破壊的変更の要因になっている。

Parameter types have become more strict. Previous versions allowed to you pass arbitrary value types, such as writing CLR string to int columns, or anything that implemented IConvertible. Although some implicit conversions are still supported (e.g. long -> int, short -> int), some have been removed.
Npgsql 4.0 Release Notes | Npgsql Documentation

型が厳密になった、と書かれている。これがプロトコルの変更起因ということを理解していないと、何をどう対応していいかわからない。例えば以下のコードは旧バージョンでは動くが、新バージョンでは実行時エラーになる。

using (var conn = new NpgsqlConnection(connnectionString))
{
    conn.Open();

    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = "SELECT @test;";
        cmd.Parameters.AddWithValue("test", DBNull.Value);
        cmd.ExecuteScalar(); // -> Npgsql.PostgresException: '42P18: could not determine data type of parameter $1'
    }
}

先述の例と異なり、NpgsqlDbTypeを指定していない。その場合、Npgsqlは値のオブジェクトから型を推定するのだが、値がDBNull.Valueなので、型を推定する情報がない。結果、Npgsqlはパラメータを「型情報なし」でPostgreSQLに送信する。PostgreSQLは型情報のないパラメータを受けとると、不明型( unknown )としてPREPAREを実行する。

PREPARE <unnamed> (unknown) AS (SELECT $1);

このときさらにPostgreSQLは、リテラル文字列に対するやり方と同じ方法で $1 の型を推定しようとする。しかし、$1がnullの場合、型が解決できずエラーになる。

おこりうる問題

上記のコードは SELECT NULL するだけであり無意味すぎるため、実際に困ることはない。もう少し入り組んだ例で、実際に起こる可能性のある問題について考えてみる。改めて、拡張クエリを使う場合、パラメータの名前・値・型情報をセットで渡す必要がある。型情報はPostgreSQLが管理するoidを渡すため、クライアントライブラリは事前にシステムカタログを参照し、型とoidのマップを取得する。このようなハンドシェイクのために、型情報なしのクエリを許容している。型情報があれば、 SELECT @test も問題なく実行できる。

逆を返せば、型情報が明示できない場合に問題がおこりうる。例えば、さらに別の外部ライブラリにより、パラメータの型指定が隠蔽されている場合がある。.NETにおいて、DapperというORMを例に挙げる。Dapperは、コネクションクラスを拡張し、in/outのパラメータをマッピングしてくれる。例えば、最初に紹介したNpgsqlCommandを使ったクエリ実行は、以下のようにシンプルに書け、値を取得できる。パラメータの型はDapperが内部で動的にセットしてくれる。すごく便利。

using (var conn = new NpgsqlConnection(connnectionString))
{
    conn.QueryFirstOrDefault<int>("SELECT @test;", new { test = 100 });
}

以下のコードを考える。 inputUserIds はコレクションが渡される想定だが、これがnullのとき、Npgsqlの新バージョン(拡張クエリを実装したバージョン)ではエラーになる。

using (var conn = new NpgsqlConnection(connnectionString))
{
    // ユーザIDが指定されているときは、該当するユーザの名前を取得する
    // 未指定(NULL)のときは、全ユーザの名前を取得する
    var sql = @"
SELECT user_name
FROM user_table
WHERE @targetUserids IS NULL OR user_id = any(@targetUserIds);
";

    var userNames = conn.Query<string>(sql, new {  targetUserIds = inputUserIds }); 
    // ... 
}

これはコレクションのパラメータ特有の問題である。パラメータの型がintやstringやDateTimeなどであれば、値がnullだとしても、Dapperが適切な型情報をNpgsqlに渡してくれる。コレクションの場合には、型情報を渡せない。この問題は、以下2点の競合により発生している。

  • Dapperは任意のRDBMSに接続するインターフェイス拡張を提供するライブラリである
  • PostgreSQLにおける配列型は、SQL標準ではない拡張仕様である

配列型はSQL標準にはなく、PostgreSQLの特徴的な機能でもある。そのためDapperは配列型に対応するCLRの型、すなわちコレクションを「知っている必要がない」のだが、例外的にコレクションのパラメータをサポートしている。上のコードでinputUserIdsがコレクションのオブジェクトである場合、Dapperは型情報として汎用的な DbType.Object をセットし、Npgsqlに渡す。Npgsqlは、受け取ったパラメータの型は不明(汎用型)だが、値がコレクションであることから、配列型の型情報を付与してPostgreSQLに送信する。PostgreSQLは配列型として処理できる。ただしinputUserIdsがnullの場合が問題になる。Npgsqlは値から型を推定する手段がなく、型情報なしで送らざるを得ない。そのため、 @targetUserIds IS NULL がPREPARE時に型が解決できずエラーとなる。

簡易クエリを実装した旧バージョンのNpgsqlであれば、PREPARE文は実行されず文字列として埋め込むだけなので 'null' IS NULL となり、問題なく実行される。それを知らずにNpgsqlのバージョンを移行すると、思わぬところで不具合が発生する。かもしれない。

ちなみに、.NET的には(というか一般的に)そもそもコレクションの実体がnullになる実装自体がDO NOT。「プロトコルの変更によって、動いていたものが動かなくなる」例として挙げた、あくまでも思考実験です。

X DO NOT return null values from collection properties or from methods returning collections. Return an empty collection or an empty array instead.
Guidelines for Collections - Framework Design Guidelines | Microsoft Docs

おこりうる小問題

SQLクライアントアプリケーションは人間がSQLを実行する用途のため、大量のユーザが利用するアプリケーションやバッチ処理と比べて、大量のクエリを撃ちまくる用途ではない。そのため、実装のシンプルさを優先して、簡易クエリを使って実装されがち。PostgreSQLに限らず、ユーザ向けアプリケーションと手元のSQLクライアントで挙動が違う場合には、プロトコルの違いを疑ってみてもいいかも。

まとめ

プロトコルはほとんど関係ない内容になってしまった。プロトコルの差異とソフトウェア間の仕様の競合を組み合わせて、コーナーケースを紹介してみた。たまにはこういう抽象化の隙間を考えるのもおもしろい。ソフトウェアは思ったとおりには動かないけど、書いたとおりに動く。

クライアントライブラリを実装してみて、性能検証などしてみようかとも思ったが、思いついたときには時間が足りなかった。また、Npgsql以外の実装、また他のRDBMSのプロトコルもいくつかみてみておもしろかった(設計思想の違いがみえる)が、そのあたりも別の機会に。来年からは、時間がないという言い訳をしない人生を歩んでいきたい。よいお年を。

*1:PREPARE文のライフタイム

*2:PostgreSQLのバージョンは9.6を利用

レガシーシステムと人生

Sansanアドベントカレンダーの3日目。

10月にシャのブログに記事を書いたのだが、そのとき「技術的負債」「レガシー」のどちらの言葉を使うかでけっこう迷った。 ためしにググっていろいろな記事を漁ってみても、これらを区別していないケースのほうが多い。 結局、実際の世の中では誰も気にしていない、という結論に至って「レガシー」を採用した。そんへんから書き始めて、レガシーのつらみから救われたい話とか、つらい人へ向けてポジティブな話を書いてみる。

技術的負債という比喩

「技術的負債」という言葉は、Ward Cunningham*1によってOOPSLA '92で発表された。1992年といえば、スーパーファミコンマウスが発売された年らしい。これはまったく関係ない。

ウォードは開発コストの増大を説明するために、会計的な「負債」という比喩を導入した。これがバズった(しらんけど)。

Shipping first time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite. Objects make the cost of this transaction tolerable. The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt. Entire engineering organizations can be brought to a stand-still under the debt load of an unconsolidated implementation, object- oriented or otherwise.
出典: http://c2.com/doc/oopsla92.html

雰囲気、だいたい以下のようなことを言ってる。

  • 最初にコードを出荷する行為は、負債を負うようなものである
  • 多少の負債は、適切に書き直せば(=負債を返済すれば)、開発スピードを向上させる
  • 返済されないままでいると、「正しくないコード」によって時間(=利子)が費やされる

その後Martin Fowler*2が、技術的負債を4象限に分類した。

To my mind, the question of whether a design flaw is or isn't debt is the wrong question. Technical Debt is a metaphor, so the real question is whether or not the debt metaphor is helpful about thinking about how to deal with design problems, and how to communicate that thinking.
出典: https://martinfowler.com/bliki/TechnicalDebtQuadrant.html

ウォードの報告やファウラーの記事においても、「技術的負債」という言葉は「メタファ」と読んでいる。メタファって雰囲気で比喩のことと知っているけど、実際はなにか。

メタファーの本質は、ある事柄を他の事柄を通して理解し、経験することである。
出典: https://www.taishukan.co.jp/book/b196366.html

メタファが本来持つ情報を付与することで、対象のある側面を説明することができる。注意すべきは、「ある側面」の射影であり、決して対象全体を表さないということ。仮に対象全体を表すとしたら、それは対象そのものやん、となる。

「技術的負債」においては、(本来の)会計的負債がもつ「利子」という構造を使って、技術的な情報を用いず技術的な課題を説明することになる。そのため、技術的負債というメタファがもっとも価値を発揮するのは、「技術的な課題は理解できないけど、(会計上の)利子という概念は理解できる」人間に対して、開発コストの消費を伝えたい場合になる。一般にその人間は経営層やマネージャだったりする。「このままだと利子を払い続けることになりますよ!!」みたいな。

ファウラーはそれを前提とした上で、「利息を払うことを選ぶか元本を返済することを選ぶかという判断」を委ねる際のさらなる情報として、技術的負債の分類を試みている。どういう経緯で生まれたものなのかがわかっていたほうが、判断しやすい。

objectmentor.booked.net

ここまでで明らかなとおり、「技術的負債」という言葉は、偉い人に技術の言葉を使わずにつらみを伝えるのに便利。一方で、エンジニア同士が問題の解決に向けて会話するときに使うと微妙。情報の欠損が激しすぎる。また、メタファはどこまでいってもメタファであって、コミュニケーションのための言葉でしかない。「技術的負債」に変わる新たな「技術的〇〇」という言葉がでてきても、一側面しか表さない。問題そのものを表す情報は「技術的」という部分に丸められているから。コストを示したいなら「負債」はすごくいいし、実際に使い続けられている理由だと思う。

レガシーという暗示

技術的負債と並んで、「レガシー」という言葉もよく使われる。こっちは原典があるわけではなく、単にレガシーという単語自体の意味から、古くからあるシステムやコードの修飾語として使われているっぽい。レガシーも一種のメタファといえる*3。レガシーという言葉自体は、「歴史の一部」や「昔から残っているもの」みたいな意味を持っている。

something that is a part of your history or that remains from an earlier time
出典: https://dictionary.cambridge.org/dictionary/english/legacy

レガシーソフトウェア改善ガイド』では、ソフトウェア開発の文脈におけるレガシーという言葉を、「保守または拡張が困難な既存のプロジェクト」と定義している。 前述のとおり、「レガシーソフトウェア」という言葉自体がもつ意味は「昔から残っているソフトウェア」ということでしかない。我々エンジニアが暗に持っている「昔から残っているコードは、保守または拡張が困難である」という知識を使って、レガシーという言葉を再定義している。これは、修辞学的には「暗示」と呼ばれる技法になる。

比喩は一部分を切り取るのに対し、暗示は対象そのものの表現により近い。ただし、暗示に使った言葉について、情報を共有する人間同士で共通の知識が必要になる。例えば、前述のようにある限定された領域で「古い=保守または拡張が困難」という定義が共有されていれば、同じ対象を想起できる。技術的負債と比べると、実際に起きている事象を表現していそう。ただ問題は、定義が十分に共有されていない場合、あるいは定義自体の解釈が人間によって異なる点である。極端にいうと、レガシーという言葉を原義どおりに理解する人は、「古いコードは悪」という短絡的な思考になっても不思議はない。不思議ではないというか、人間は忘れる生き物だし、そういうことは普通にある。

古いコードはつらい、みたいな発想は、プログラミングという行為が製造工程ではなく設計工程であることに起因していそう。例えば、「車をメンテナンスする」という行為は、車が古くなってきたら掃除したり一部パーツをつけなおしたり、みたいな印象がある。対して、ソフトウェアは論理なので経年劣化しない。車の例えでいうと、設計図を作りなおしながらつかっていくことに相当する。製造はコンパイラがやる。

工学的なみかた

比喩や暗示で表現する方法は、具体的ななにかに射影することで、説明しやすくなる。これは文学的アプローチっぽい。対して、エンジニアが対象の問題を解決するためには、実際に起きている事象を明らかにする必要がある。これはモデル化、抽象化、であり、工学的アプローチっぽい。技術的負債を計測する試みはそこかしこでやられているっぽい。理想のこの問題を本来的に何と呼ぶかは、新たな言葉が必要だと思う。変更可能(必要?)性と変更困難性の両方を含意する言葉があればいいのかもしれない。

ちょうど最近、以下の記事をみた。

ソフトウェアは数年単位で書き直しが行われる。これは多くのリソースを消費するが、これによって市場の需要と変化に迅速に適応する企業の能力が保証される。コードを書き直すことで、蓄積されたコードのレガシーと複雑さを軽減すると同時に、知識とコードの所有権を新しいチームメンバーに移転することが可能になる。 出典: www.infoq.com

書き直す投資意識と技術力、さすがGoogleと思う一方で、ひねくれた見方をすれば、Googleをもってしても「負債にならないコードを書く」ことができていないことを示唆している。「負債を作らないようにつくる」ことはすごく大事だしその精神で最高の設計をすべきだけど、未来の不確実性はでかすぎる。成長するプロダクトだと特に。過去のだれかが設計をミスっているわけではなくて、それを継ぎ足し継ぎ足しされてきた結果でしかない。

アカデミックっぽいところでいうと、技術的負債をテーマにした国際会議もある。2020年で3回目らしい。

2019.techdebtconf.org

これからは工学的アプローチがどんどん進んで、つらみから開放されたい。とはいえ、2018のをざっとみると、一部限定条件下でモデル化を試みている研究もあるものの、大半は静的解析やツールの紹介に留まっている。ソフトウェアの本質的な複雑性がある以上、今後数十年はつらみが続く気がする。

キャリア的なみかた

レガシー改善ばっかりやっているというと、心配されることが多いけど、自分としてはレガシー改善だからしんどいみたいなことはあまりない。うまくいかないときはレガシー改善だろうが新規開発だろうがしんどい。要求されるメンタリティは違う気がする。 しんどそうと思う人がなぜそう思うか考えてみると、「先人が好き勝手にやったあとをなおす、損な役回り」的な印象があるっぽい。少なからず「負債」という言葉の功罪かもしれない。

自分と周辺の環境にかぎっていえば、そういうことはなくて、他のプロジェクトと同等に評価してもらっている雰囲気がある。ウェブっぽいことをあまりやっていないかんじもあるけど、ILを読んだり、わからなすぎてOSSのコントリビュータに凸ったりもしなければならない。大前提、技術力は必須。変更しやすくするには、変更しやすいとはどういうことか知っている必要があるし、現在の状態からあるべき姿へ進む戦略も含めると、相当な能力が必要とされる。その点自分は無力中の無力。全然できてないから、できるようになりたい。

先述のとおり、Google様でもつらみがあるし、学問が解いてくれるのはまだしばらく先になりそうな状況。加えて市況的にも、ソフトウェアの内製化が進みそう。

www.sbbit.jp

これまでは外部品質が品質の大半を占めていたが、内部品質の重要性がさらに高まるかもしれない。例えば、人間のモチベーションという貴重な資産もこれまで以上に評価されるかも。DX(Developer Experience)と呼ばれたりする。変更しやすいシステムにすることができれば、中長期的な事業価値になるのはもちろんだし、同僚エンジニアや自分がその価値を感じられる。 これらをふまえると、「変更しやすくする」技術の価値はこれから高まる気がするし、ソフトウェアの本質的な課題に向き合うおもしろみもある。がんばってください。

さいごに

雰囲気で書き殴ってしまった。正直、レガシーがどうこうよりも、前半の修辞学のところが調べていくとおもしろくて、興味がわいた。以下が金字塔的な書籍らしい。タイトルもパクった。

www.taishukan.co.jp

こういうお気持ち文章をあまり書いたのは久しぶりな気がするけど、すごい苦手なことを痛感した。普段から自分のお気持ちを言語化していきたい。内容に関しては感想とかもらえると嬉しい。

参考

*1:Wikiの開発者であり、デザインパターンやXPの提唱者のひとり。すごい人。

*2:めっちゃすごい人。

*3:というか、『レトリックと人生』によれば、ほとんどのことはメタファらしい

『レガシーコードからの脱却』を読んだ

お仕事でレガシー改善っぽいことばかりやっていたら、他部署のエンジニアから「レガシーの人」と呼ばれた。老害の化身みたいな印象を刷り込まれつつある。そんなわけで、レガシーにまつわる本を読んだ。

www.oreilly.co.jp

インターネットをみているかぎり評判が良さそうで、ネクストバイブル的な扱いを受けていた。読んでいて思ったことなどを雑に書き留める。

感想たち

全体的にとても読みやすく、整理されている。訳も自然。キーワードを挙げるなら、アジャイル、TDDあたり。タイトルからは少しずれる印象かもしれないが、レガシー関係なく良いコードを書く方法にかなりページを割いている。その理由は、最終章の以下文で端的に説明されている。

良いものがどんなものか知らなければ、レガシーコードをリファクタリングしてもより良いものにしようがないのだ。(p.241)

「レガシー改善やるぞ」とそれ自体が目的になってしまいがちで、それもふまえてより大局的に「良いコード」を志向している点に、本書の価値を感じる。書かれていること自体も理論と実践のバランスよく、ソフトウェア開発者が日々意識しやすい言葉を使っていて、ジュニアからシニア、マネージャまで、ソフトウェア開発者と呼ばれる人々に広く示唆を与える内容となっている。

失敗のコスト

本書の前半は、広くソフトウェア業界が抱える問題意識を示している。

主にアメリカのソフトウェア業界をターゲットとして、権威ある調査機関が行った調査について、定量的に紹介されている。CHAOSレポートはその中の代表的な一つで、いろんなところで引用されている。

note.mu

1994年時点でのプロジェクト成功率は16%であり、それが2004年には29%になったこと、及びその理由としてアジャイル開発手法の普及を挙げている。この数字は、ソフトウェア開発者以外からみると、信じられないくらい低いと思う。というか、ソフトウェア開発者からみても低い。「成功」「問題あり」「失敗」の3状態の定義として、例えば「予算がオーバーした」は「問題あり」に分類される。そう聞くと、ソフトウェア開発者としては納得できる。

この感覚の差に内在するのはソフトウェアの複雑性だと思う。NISTの調査から、以下のコメントが紹介されている。

ソフトウェア開発者はすでにほぼ80%の開発コストを障害の特定と修正のために使っている。

そして、ソフトウェア障害がアメリカだけで年間600億ドルもの損失になっていることを紹介している。600億ドルといえば、現時点のレートで6.5兆円程度。「ポケモン」の市場規模が約6兆円らしいので、それを超える勢い。よくわからん。

技術的卓越性

自分はアジャイルを全然知らないのだが、本書はアジャイルの根底にある「余計なものはないほうがいい」という思想のもと、レガシーコードを生み出さないことに繋がることを説明している。たぶん。マネジメントプラクティスが先行しすぎていることを指摘し、技術プラクティスの重要性を強調している。たぶん、より開発生産性の高い状態を目指したらアジャイルチックなスタイルになっているのが理想なのだと思う。アジャイルマニフェストの起案者は、「今後は技術的卓越性やで」といってるらしい。

www.infoq.com

技術的卓越性とはなにか、という点については、本書では十分には説明されていない。アジャイルの聖典であるPrinciples behind the Agile Manifestoにある以下。

Continuous attention to technical excellence and good design enhances agility.

「技術研鑽を常に意識せよ」ということだと思うが、アジャイルにおいてそれが明文化されていることに意味がありそう。各自の解釈で。

変更しやすく

仮想世界は物理世界とは異なり、物理法則がない。それもあってか、ソフトウェア開発において「第一原理」となるものはまだない。オブジェクト指向や単一責務の原則のようないわゆる設計原則がそれに近しい位置にはいるが、それが不要なソフトウェア開発もまた存在する。そのため、「良いコード」の定義は様々あるとした上で、「変更のしやすさ」を大きな要素として挙げている。変更のしやすさはソフトウェアの内部品質であり、内部品質は結果ではなく原因であり、良いソフトウェアが備えているものである、としている。

高凝集・疎結合にしたり、ポリモーフィズムを駆使したり、オブジェクト指向の原則に従った開発を行うことや、テストでふるまいを書くことが「良い」とされている。その「良い」と感じる根源は、変更のしやすさであり、より根源的には不確実性、わからなさに対する戦略だと思う。コードは読み手のために書くという意識は大切だし、その人の不安をなるべく減らしたい。ホスタビリティ。おもてなし。

品質の定義

品質を高めるには?みたいな文脈で、「まず品質を定義しろ」みたいなことが書いてあった。気がする。当然のことながらケースバイケースであり、本書でも品質を定義するやり方については書かれていないし、従来の品質管理の手法はソフトウェアに適用できない的なことも書いてある。

内部品質に関しては、ひとつはコードの複雑性、例えば循環複雑度のようなものは計測ツールが色々あるので、なにも決めていない場合はそのあたりから初めて、徐々にチューニングしていけばいいかもしれない。ある組織においてソフトウェア開発者を評価するとき、外部品質(機能要件、非機能要件含む)が目立つが、内部品質の評価はあまり議論されていない気がする。外部品質の目的がユーザ体験なら、内部品質の目的は開発者体験、世にいうDXというやつなのかもしれん。であれば、その観点で評価をするべきだし、まさに技術的卓越性が評価観点になること、端的には「技術力」で評価されることは一定納得がいく。

リファクタリングのモチベーション

もちろん機能拡張や改良のコスト削減に寄与することが前提。それに加えて、一開発者の視点でもモチベーションになりうる。

コードのリファクタリングは、コードを書くときにしてはいけないこと、代わりにすべきことを学ぶ上で最速の方法のひとつなのだ。(p.227)

ソフトウェア開発において、大なり小なりリファクタリングのスキルは求められる。自分の体感とも一致する。一発目に書いたコードをそのままプッシュしてプルリクエストを作成することはほぼない。自分の場合、まずテストコードを書いてからテストをとおすコードを書く。その時点ではテストをとおすことを考えていて、コードの品質については考えていないので、ひどいコードになっている。それをある程度リファクタリングする。

そういえば、かの名著『リファクタリング』の第2版が出ているが、まだ読んでいない。と思ったらもうすぐ邦訳が出そうな雰囲気なので、折に触れて読んでみる。

www.hanmoto.com

まとめ

原題の『Beyond Legacy Code』のほうが好きで、日本語の「脱却」とは少し違うニュアンスを感じる。「うぅ…抜け出したい…」よりも「我々の戦いはまだ始まったばかり!」みたいな。始まっていきましょう。