『リファクタリング(第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』のほうが好きで、日本語の「脱却」とは少し違うニュアンスを感じる。「うぅ…抜け出したい…」よりも「我々の戦いはまだ始まったばかり!」みたいな。始まっていきましょう。

『エンジニアのためのマネジメントキャリアパス』を読んだ

更新が滞っていました。リハビリとして今日読んだ本の感想を書きます。

www.oreilly.co.jp

本書は、技術系マネージャーとそれを目指すエンジニアに向けて、IT業界の管理職に求められるスキルを解説する書籍です。テックリードからCTOになった経験を持つ著者が、管理職についたエンジニアが歩むキャリアパスについて段階をおって紹介します。

特段昇進を熱望しているわけではないですが、プロジェクト単位でマネジメントしたりメンター的なことをしたりする日々の中で、マネージャの視点を体系的に読めそう、読みやすそう、評判が良さそうな本書に手を出しました。テックリードから始まり、章が進むに連れて徐々に広い範囲の役職について言及され、最後にはCTO、そして組織文化のマネジメントまでと、マネジメントキャリアのラダーにそって書かれています。

原著者のCamille Fournierさんは、Rent The Runwayというアメリカのベンチャー企業のCTOです。今年春に大型の資金調達をしていました

感想

「いいマネジメントとはどんなものか」や「上位のマネージャが何を考えているか(考えるべきか)」の一例をみることができました。著者の過去の失敗を開示していて、理想論に閉じず、かといって個別の事象にもよりすぎず、一般論を現実的にわかりやすく説明されていました。訳も自然で読みやすかったです。

また、実際にマネージメントをする人に限らず、マネジメントされる側の視点においても、「なんとなくやりにくい」と感じてモヤっている人や、逆に「いまのマネジメントにまったく不満がない」という人こそ一読の勝ちがあるかもしれません。課題が言語化され、自分がパフォーマンスを最大化する環境について考えるきっかけにもなりそうです。

自分はまず技術をがんばりますという気持ちになりました。

技術だいじ

マネージャの職位があがるにつれ、勤務時間に占めるコーディング時間は減っていきます。それでもなお、あくまでエンジニアのマネージャであるかぎりは小さくともコードを書き続けるべきだとし、また技術力不足でキャリアが頭打ちになるケースを実体験として挙げています。「まだ早いと思うなら引き受けるな、技術に専念せよ」という旨まで書いてあります。

職位が上がる、つまりマネジメントする対象が広がるに連れて、課題に対する抽象度が上がり、意識すべき責任範囲が広がるということです。技術に関する知識・理解の深さと対立するものではなく、(自分の体感としても)正の相関に近いのかもしれません。

オープンドアより1on1

メンバーとマネージャの間で、プロジェクトあるいはその他の相談、質問、議論のインターフェイスとして、オープンドアと1on1を対比して後者を推しています。オープンドアというのは、「いつでもなんでも聞いてOK」と門戸を開いておくスタイルで、1on1は、定期的にミーティングを設けるスタイルです。

オープンドアは一見なんとなく良さそうですが、エンジニアも人間なので、上司にネガティブな話をするのは心理的負担が少なからずあります。プロとして必要ならやるべきという考え方もあるかもしれません。でも、心理的負担は小さいに越したことないです。

三大美徳

エンジニアならだれもが知る、Larry Wall大先生が唱えた三大美徳「怠惰」「短気」「傲慢」についてです。これらがマネジメントにおいても有効だと説明されています。もちろん「短気=すぐに怒る」みたいなことではないです。プロジェクトに対して、「なるべく楽に、速く終わらせるにはどうすればいいか?」という視点を持つべきです。

行動規範として皆さんに今すぐ実践してほしいのが「今すぐ重要なことを見極める」と「帰宅する」なのです。(p.160)

パワープレイで問題を解決しようとしたり、考える前に行動して時間を無駄にすることは、当人にとってもマネージャにとっても会社にとっても望ましくないです。勤務時間という制約の中で最大の成果を出すことを目指し、そのために考え抜くのが知的労働におけるプロフェッショナリズムといえます。

好奇心を大切に

本書では一貫して、「好奇心を持て」と説かれています。「本書を通じて」というところが重要で、メンター、テックリードからCTOに至るまで、(少なくともエンジニアに対する)マネジメントのあらゆるフェーズで、好奇心が価値を持つとのことです。

メンターは、メンティーをサポートする立場ですが、逆にメンティーの視点を通して世界をみることで好奇心を刺激してもらう機会でもあります。メンティーの悩みをとおして組織の課題に気づいたり、質問をとおして自身の理解不足に気づくかもしれません。また、テックリード以上の職位においては、ある技術領域において自身よりもメンバーのほうが深い知識を持っていることが往々にしてあります。その場合に理解を諦めるのではなく、メンバーに時間をとってもらって教えてもらうなどでプロジェクトの計画精度を高めると同時に、エンジニアとしての成長機会にもなります。

参考

ググるといい書評がいっぱいでてきたので貼っておきます。このへんをみるとだいたい内容はわかります。さらに気になったら本を読んでみるといいと思います。

京都でレガシーシステムのはなしをした

2/22(金)にLegacy Meetup Kyotoで発表してきました。

speakerdeck.com

ここ1年くらいでやってきた、レガシーシステムの改善に関する3つのプロジェクトを紹介し、その過程で考えたことを少し整理してお話しました。レガシーシステムと相対する人類にとって少しでも参考になれば幸いです。

Q&A

懇親会でいくつか質問をいただきました。まともに話す時間もなく答えきれなかったものを、書いておいてみます。本人に届く可能性は極めて低くとも。

Q:受託開発の場合、顧客をどう説得するか?

自分は経験がなく想像もあまりできないのでわかりません。というと終了するので、想像で答えると、説得しなくてよいのではないでしょうか。どうしても説得したければ、説得に足る情報を集めて判断してもらえばよいのでは。発表の中にある「合意」に必ずしも正しさ・清さを包含しません。人間はわかりあえないという前提のもと、自分にとって都合がよく、かつ後々不利益を被らない情報を恣意的に提供すればいいと思います。

Q:巨大すぎる技術的負債にどう立ち向かうか?

立ち向かわなくてよいのではないでしょうか。疲れるし。どうしても立ち向かいたい理由があるなら、まずはどれくらい巨大かを把握しようとしてみては。本当に巨大だったら諦めもつくでしょう。意外とそんなに巨大ではないかもしれません。そしたらラッキーですね。

Q:技術的負債はまだ残っているのか?

個人ブログなので詳しくは書きませんが、言えるとすれば、今回の内容は「レガシーシステムの端の端の末端の端っこを少しなおしてみた」ということでしかないです。「コードは書いた瞬間から技術的負債になる」ってだれかが言ってました。

様子

会場は古き良き町家をリノベーションした建物です。リノベーションといっても、畳敷きの和室はそのままです。長机と座布団を並べたその空間は、寺子屋あるいは習字教室を彷彿とさせました。「皆様、足を崩して楽な姿勢でどうぞ…」のアナウンスをもって完全に法事と化しました。

発表者の立場としては、とても話しやすかったです。東京で社内/社外、お仕事/プライベートでいくつか発表をしたことがありますが、今回はとりわけ快適でした。勉強会のテーマが特定の技術によらないエンジニアリングの共通項だったことに加え、前述のとおりリラックスできる場の共有が少なからず寄与していると思います。初対面なのに内輪ともいうべき不思議な雰囲気でした。

他の登壇者の発表もおもしろかったです。ヤフーの山本さんの発表からは新たな技術や未知を楽しむ""つよさ""を感じましたし、はてなの id:t_kyt さんの発表からは並々ならぬ""やっていき""を感じました。資料は勉強会のconnpassページにリンクがあるので、ご興味ある方はそちらをどうぞ。

懇親会では各自のレガシーどうよみたいな話をできて、「みんな大変そう」という圧倒的に深い考察を得ました。おぢさんたちが和室に集ってレガシーシステムを追悼する様子は、まさに法事そのものでした。

余談

本題です。

勉強会が金曜だったので、翌日は京都をぶらぶらし、もう一泊してから帰りました。京都オヒス近辺(徒歩5分圏内)でお店を発掘しました。オススメ3選を共有します。使命感の為せる技です。

  • Weekenders Coffee : コーヒースタンド。パーキングの奥のほうに茶室みたいな外見の建物があります。自分が学生だったころは元田中にあったのが、移転したらしいです。
  • Tato : スペイン料理。ビールがたくさんあって、スペインの家庭料理が激ウマです。狭めの店内に、スタンディング席とテーブル席があります。店主が異常に陽気です。
  • OIL : バー。雑居ビルの奥の奥にあって心理障壁がすごいです。中は意外と広くてゆっくりできます。メニューがないわりに良心的なお値段です。

f:id:kyabatalian:20190302183524j:plain
Tatoで食べたパエリア

そのほか、高倉二条のラーメン、わたなべ横丁とかキセノンという定番立ち呑みから、翌日サウナの梅湯築地など、サブカル定番コースでまったりしました。

まとめ

なにも予定がなく、なにもやりたくないとき、ひとまず川にいける京都。最高。

『レガシーソフトウェア改善ガイド』を読んだ

本を読むと何かをやった気になってしまって良くないです。でも、新たな知識を得るのは楽しいです。娯楽だと思うことにします。

どんな本?

レガシーなソフトウェアを改善するためのガイドです。たぶん。

www.shoeisha.co.jp

著者はThe GuardianのSenior DeveloperであるChris Birchall氏。訳者の吉川邦夫さんという方は、元SEで現在は翻訳を主に手がけられているっぽいです。翻訳リストをみると心なしかMS周りの技術書に多く関わられているっぽいです。

おなじ翔泳社から出ているレガシーコード改善ガイドとシリーズ物かと思いきや、全然別物です*1

なぜ読んだのか

勤務先の技術ブログに、レガシーシステムの改善に関する記事を書きました。その中で本書を紹介していたのですが、その段階でまったく読んでいませんでした。まったく読んでいない本を紹介することに対し、一定の罪悪感が拭えなかったため、渋々読むことにしました。

上記の記事で書いたこと以外にもいわゆる技術的負債と言われる領域に手をつけていく中で、もうちょっとまじめに取り組んだほうがいいよなあ、という思いが強くなってきました。やってきたことやより大きな技術的負債に対してどう立ち向かっていくべきか、指針を得られればと思いました。また、前述の『レガシーコード改善ガイド』ではなくソフトウェアのほうを選んだのは、コードベースを改善するにはまずそれ以前にソフトウェア全体やその開発プロセスの土台が必要と感じているからです。富士山よりも高い意識。

感想など

3部構成になっており、それぞれ「なにが課題なのか」「どうやって解決するか」「そのための環境づくり」について言及されているように感じました。

まず、ソフトウェアの実装に関することは想像以上に、というかほぼ書かれていません。レガシーは人間から生まれるので、必然的にそうなるのかもしれません。ビッグリライトはやめとけ(意訳)とか、マイクロサービスに走るな(意訳)とか、安易にレガシーを正す主張を並べるのではなく、理路整然と価値ある判断ができるように導いてくれています。レガシーソフトウェアと対峙し感情が荒ぶりがちなエンジニアは、一度エディタを閉じて深呼吸し、帰宅して本書を一読することをおすすめします。

具体的なレガシーソフトウェアの例を示し、改善に至るまでの考え方をストーリー仕立て紹介されている箇所があり、大変わかりやすいです。「何がダメで、どうしたいのか、そうすると何が嬉しいのか」を、十分すぎるくらいに説明してくれます。知識を詰め込んだ技術書というよりは、読者にとって読みやすいように配慮されていると感じます。

最後の第3部では主に自動化を主眼に、いかにレガシーを生み出さないかに言及されています。その中で、Ansible、Vagrant、Jenkinsといった具体的なツールを使った環境整備が書かれています。このへんは疲れてきてあんまり読んでません。すみません。個人的には本書は第2部までで十分その価値を持つ、第3部は必要な人だけ読んだらいいという印象です。直近のお仕事でCIをがんばりたい所存なので、まあ、がんばろうという決意を新たにしました。

本書を通じて私は「レガシー」(legacy)を、何か悪い言葉のように扱ってきたが、そうする必要はない。望むと望まないとに関わらず、我々は次世代の開発者に遺産(legacy)を残すのだから、できるだけのことをして、誇らしい遺産にしよう。

やっていきましょう。以下ではときめいた箇所とそれぞれ思ふことをメモっておきます。

「負債」には「利子」がつく

技術的負債がやっかいなのは、ただ存在することがデメリットであることです。テストがなく仕様が曖昧なコードは、その周辺の挙動を知るために読み解く手間がかかります。また、設計が使用に沿わずレガシーとなったデータベーススキーマが存在すれば、それに適応するためにアプリケーションコードが必要以上に複雑になり、新たなレガシーを生み出します。レガシーなソフトウェアを放置すると、エンジニアや組織は利子を払い続けることになります。その意味において、技術的「負債」という言葉は、事象のメタファーとして非常に優れていると感じました。

レガシーカルチャをリファクタする

レガシー「ソフトウェア」というタイトルから、レガシーコードよりは広い領域だろうな、という想像をしていましたが、本書ではさらに開発プロセスや組織のカルチャにまで言及されています。レガシーなカルチャとして、「変化への畏怖」と「知識の孤立」を挙げています。それぞれについて、個別に取りうる方策を説明しています。使われていないはずのコードを消す恐怖を和らげるためにはアクセスログをみればいいですし、いま記録されていないならまずログを出すところが第一歩です。

本書では言及されていませんが、自分の経験からいうと、技術的負債の返却に対して、組織的・長期的なメリットだけでなく、個人のインセンティブとリスクを取れる文化が必要と感じます。新規開発のプロジェクトと比較して、ユーザよりも開発者にとっての課題のため、それに取り組むモチベーションが内発されにくいです。組織における評価者がソフトウェアの状況を理解し、長期的な価値をふまえた高度な評価をする必要があります。

まずは触ってみるしかない

この「未知への恐れ」を克服する最良の方法は、そのコードに飛び込んで、いじり始めることだ。

レガシーなソフトウェアをみた人間に内発されやすい感情として、「恐怖」と「フラストレーション」が挙げられています。自分の経験とも一致します。これらが開発効率やソフトウェアの品質にもたらす悪影響を評価することは極めて難しいですし、少なくとも定量化は困難と思われます。少なくともいい状態ではないのでなんとかしたいわけですが、なぜ自分が怖かったりイライラしたりするかと考えてみると、「知らないから」というのが多分にあります。なので、そのコードを読んで、何をやっているかを把握することが、解消に繋がります。

そのために本書では、「調査的リファクタリング」が紹介されています。何をやっているかを把握しながら、簡単なリファクタを進めていく、という手法です。人間の心理として調査自体は何かを生産している気にならずコストにみえますが、そのついでに少しでもソフトウェアを改善することで、ある意味で正当化できます。例えば、もう使っていないメソッドや参照を削除することでも、それが残り続けることに比べれば大きな前進です。まずは、小さなリファクタを始めましょう。使っていないクラス、メソッド、変数、参照をみつけたら、削除しましょう。

レガシーの源泉を紐解く

結局、最初は「醜悪な実装」に見えたコードが、実は「複雑な仕様」だった、という場合が多く、それについては、大きな改善が望めない。

複雑なコードが存在するとき、それが必要な複雑さなのかは考える必要があります。そもそもの仕様が複雑ならその実装が(エンジニアにとって)複雑であっても、妥当な複雑さです。さらに一段上のレイヤを考えて、実現したいことが複雑ならその仕様の複雑さも妥当です。ただし、ほとんどの場合、過剰な複雑さだと思います。その場合は、要件の掘り下げが足りていない可能性があります。その場合、リファクタすべきはコードではなく仕様です。

段階的にできるものはそうする

モノリスなアーキテクチャを運用するチームは、世の潮流と自身のギャップを感じ、「マイクロサービスにするぞ!」と士気が高まりがちです。しかしその間はとんでもない距離があり、いざやるとなると不相応にしんどいです。まずはフロントエンドとバックエンドを切りわける、その次にSOAに取り組む、その上でさらに必要ならマイクロサービスを検討する、というふうに進めていけるはずですし、ほとんどのケースでマイクロサービスまでやる必要はないはずです。「マイクロサービス化」を目的としてしまうと、本来の課題が解決できない恐れがあります。

また別の観点として、機能の改善とリファクタリングは切り分けるのが定石です。機能をなおしてからリファクタするのか、リファクタしてから機能をなおすのか、どちらになるかはプロジェクトの性質に依存しますが、いずれにしても同時にやるべきでないことは確かです。

すべてはユーザのため

最も重要なのは(コードについて言えることの、どれよりも、はるかに重要なのは)ユーザーにとって価値のあるソフトウェアを作ることだ。

技術的負債が負債たりうるのは、ユーザに害があるからです。エンジニアがストレスを感じ、必要以上に時間を浪費し、開発速度が落ちて新たな価値を届ける速度が落ちます。また、バグの温床にもなります。ユーザのために技術的負債を返却せねば。

*1:原著のタイトルはそれぞれ"Working Effectively With Legacy Code"と"Re-engineering Legacy Software"であり、出版社も異なる