ソフトウェアドキュメント作法

こんにちは丸山@h13i32maruです。つい先日、devchat.fmというポッドキャストに出演して、「ドキュメント」というお題について話しました。なぜこんなニッチなお題について話したかというと、Ubie Discoveryに入社して5ヶ月の間にいくつか*1まとまったソフトウェアドキュメントを書いたので、自分の中でホットな話題だったからです。

これらのドキュメントは個人的にわりと良く書けたと思ってますし、他のソフトウェアエンジニア(以下SWE)からも「わかりやすい」「すごくいい」などの感想をもらいました。それに気を良くしてdevchat.fmでもべらべらと喋ったのですが、せっかくなのでちゃんとまとめておこうと思ってブログに書くことにしました。

今回はソフトウェアやシステムの内部構造を説明するドキュメントについての話です(システムドキュメント、ARCHITECTURE.mdなどと呼ばれるようなもの)。ユーザマニュアル、Design Doc、ADRなどのドキュメントについてはスコープ外です。それと僕はテクニカルライターとかではなく、ドキュメントを書くのが好きな普通のSWEです。なので的はずれなこと書いてたらごめんなさい。

# なぜドキュメントを書くのか

ドキュメントを書く理由は人によって様々かもしれませんが、僕の場合は「ドキュメントにより、そのシステムをスムーズに理解できて、コードの読み書きを効率的にできるようになるため」です。例えばドキュメントを読んでから、システムのコードリーディングをすると、所要時間が半分になるならすごい時間の節約になりますよね。そのシステムに関わる人が多ければ、ドキュメントを書くのに使った時間を回収することができると思います。

つまり、ドキュメントをアセット(資産)にすることで、生産性を上げたいということです。極論、一人で開発してるならドキュメントは不要なことが多いです。しかしチームで開発している場合は、ドキュメントがあるおかげで新しくチームに入った人がスムーズにシステムを理解できれば、生産性が上がるはずです。そしてシステムに触れる人が多ければ多いほど、そのドキュメントのRoIは良くなります。

# どんなときに書くと良いのか

なんでもかんでもドキュメントを書けばよいというわけではありません。前述したとおり、RoIが良いと予想される場合に書くべきです。具体的には以下のようなシステムや機能についてはRoIが良くなりそうだと思っています。

  • 社内のSWEにあまり実装経験が無いようなシステム
  • 事業ドメインのコアでいろいろなSWEが触るようなシステム
  • 世間一般に情報が無い社内独自のシステム

逆に以下のようなものはRoIが悪いかなと思っています。

  • Webや書籍などの情報が十分にあるシステム
  • 社内のSWEに実装経験が豊富なシステム
  • プロトタイプなど頻繁に実装や設計が変わるシステム

とはいえ、このあたりはまだ自分の中でもふわっとしてるところです。

# 意識してること

コードの読み書きを効率的に行えるようなドキュメントを書くためにいくつかのことを意識しています。特に「メンタルモデルの構築」は重要だと思っているので、そのあたりを中心に紹介していきます。Ubieで書いたドキュメントを公開できると良かったんですが、流石に内部向けドキュメントすぎるので断念。

## メンタルモデル

コードの読み書きをスムーズに行うには、脳内にそのシステムのメンタルモデルが構築されているかどうかにかかっています。

メンタルモデルとは「そのシステムの設計や実装について推測可能になる」ということです。例えば「ここがooってことは、多分あれはxxだろう」とか「この機能を追加するにはあのあたりのコードに手を入れればよさそう」などです。

このメンタルモデルというのは「コードを読む → 脳内で抽象化する → モデルとして組み立てる」という感じで構築されていきます。しかしこれをコードを読みながら行うのはすごく大変です。というのもコードは最も細かい粒度で書かれておりそれを読み解くのは脳内メモリとCPUをかなり使いますし、そもそもコードはメンタルモデルの構築を目的に書かれていません。

そこで、ドキュメントの出番です。メンタルモデルを構築することを目的に書かれたドキュメントを読むことで、コードを読みながら行うよりも遥かに効率的にメンタルモデルを構築できるようになります。

メンタルモデルの話は以前に書いた記事も参照してみてください。

以降の話はすべてこのメンタルモデルをスムーズに作れるようにというのを意識したテクニックです。

## 俯瞰

ドキュメントにはまず対象システムの俯瞰から書き始めます。その俯瞰も詳細には書きません。読む人がほぼほぼ知ってそうなレベル、わかりきってるレベルから書き始めます。

そうすることで読者の現在の理解とのギャップを最小限にして、スムーズに読み始められるようにします。そしてまずは最も抽象度の高いレベルのメンタルモデルを構築できます。

システムドキュメントでよくありがちなのが、いきなり詳細な設計図、ER図、フローチャート、UMLなどから始まるものです。これは読者の現在の理解とギャップが大きすぎるので、理解が難しくなります。それに、そのレベルで詳細な場合はコードを読むのと変わらずメンタルモデルの構築も難しくなります。書き手の頭にはすでにメンタルモデルが構築されているので、いきなり詳細から書き始めたい欲求はありますがぐっと我慢します。

## レイヤー

俯瞰した内容から徐々にブレイクダウンした内容を書いてきます。アーキテクチャ、コンポーネント群、処理フローなどなど。このときに1つのセクション内で書く話題はレイヤー(抽象度)を揃えることを意識します。

例えばあるウェブサービスの設計についてのドキュメントがあるとします。その中のコンポーネントについて書いたセクションで、

  • APIサーバ(Rails)
  • セッションサーバ(Redis)
  • マスターデータ(Postgresql)
  • FooService(Rubyのクラス)

と並べてしまうと、FooServiceだけ別レイヤーのコンポーネントで浮いてしまいます。

他にもテキストエディタの設計についてのドキュメントがあるとします。その中の処理フローについて書いたセクションで

  • ユーザ入力
  • キャレット制御
  • 入力テキスト取得
  • パース
  • HTML更新

と並べるとキャレット制御だけ他のものに比べて詳細すぎます。

このように1つのセクション内に別レイヤーの話題が入ってくると、そのセクションの理解が難しくなり、結果としてメンタルモデルの構築が阻害されてしまいます。

この考えはプログラムを書いてるときと同じ考えです。レイヤードアーキテクチャだったり、SLAP(Single Level of Abstraction Principle)と呼ばれるものですね。そしてここからわかることは、レイヤーをちゃんと分けてドキュメントを書くためには、そのシステムの設計自体もまた正しくレイヤーを分けて作られている必要があるということです。なので、もしドキュメントを書いていて、うまくレイヤーに分けて書けないなと感じた場合は、システムの設計になにか問題があるかもと思ってチェックしてみると良いかもしれません。

## 推測

メンタルモデルを「そのシステムの設計や実装について推測可能になる」と説明しました。人が何かを推測するときは「因果関係から推測(演繹的)」と「パターンから推測(帰納的)」があります。なのでドキュメントにもそれらの要素を入れることで、メンタルモデルを構築しやすくなります。具体的に僕が意識して書いてることは以下のような点です。

因果関係から推測(演繹的)
設計やアルゴリズムについて「その設計・アルゴリズムにした理由」や「他の選択肢と比較したメリット・デメリット」を書くことを意識しています。例えば最近作ったカルテエディタではインクリメンタルビルドという仕組みを採用しました。このことについては以下のような補足をドキュメントに書いています。

  • エディタ上にReactコンポーネントをマウントする必要があるので、パフォーマンスが必要
  • インクリメンタルビルドはフルビルドと比べたときにパフォーマンスが良い
  • 一方で、実装は複雑になりがちで潜在的な不具合があるかもしれない

この補足により「エディタのパフォーマンス改善をするにはインクリメンタルビルド周りに手を入れる必要があるかも」「テキストを編集中に特定条件下だと不具合が起きるのはもしかしたらインクリメンタルビルド周りが怪しいかも」などの推測ができるようになります。

パターンから推測(帰納的)
これはレイヤーの話と表裏一体の内容です。つまり同じレイヤーのものを並べて書くということはそれ自体がパターンになります。例えばUbieで開発してる問診サービスについてのシステムドキュメントでは、質問選定フローを以下のように書きました。

  • 未回答の質問チェック
  • 主訴の質問チェック
  • 症状の深堀り質問チェック

これによって「病院固有の質問はこれらと並列に実装されてそうなので、多分あの辺りから読めば良さそう」「ooという質問を追加したいけど、これらの質問と並列ではなさそうだから、症状の深堀り質問の中にいれるのがよさそうかも」というような推測ができるようになります。


これら因果関係やパターンからの推測はそのシステムに詳しいSWE(= メンタルモデル構築済み)なら、コードを読まなくても大体のあたりをつけるという感じで自然と行っているものだと思います。

## その他


図は積極的に入れるのを意識しています。テキストだと一次元の情報ですが、図だと二次元の情報になることで情報量が多くなるにもかかわらず、理解しやすいからです。これは脳内のメンタルモデルが立体的な構造をしてるのでそれに近いほうがメンタルモデルの構築に便利だからだと思います。とはいえ図を書くのは面倒ですが、最近はmiroを使って書くことが多いです。キャンバスが広いのとちょうどよいコンポーネントが揃っているのでおすすめです。

f:id:h13i32maru:20210815111059p:plain

レビュー
ドキュメントもコードと同じように他の人にレビューしてもらうとよいです。というのも、書き手はすでにメンタルモデルを構築済みなので、どうしても何か前提や説明を飛ばしてドキュメントを書いてしまう場合があるからです。そこで、まだメンタルモデルを構築していない人に読んでもらうことで、そのバイアスに気づくことができます。また社内にそのシステムについてすごく詳しい人がいればその人にも見てもらいましょう。コードからだけでは読み取れなかったことや間違いを指摘してもらえると思います。

# ドキュメントの問題

最後にドキュメントにつきまとう「メンテナンスの問題」と「保存場所の問題」についてです。ただし、僕はどちらについてもベストプラクティスは持って無くて、どうしたらいいんだろうな〜と思いながらやっている状況です。

## メンテナンス

ドキュメントは書かれたその瞬間から、コードや設計に対して古くなっていきます。これ自体は仕方のないことですが、問題はそれをメンテナンスしていくのが大変ということです。この問題に対して僕は現時点では2つ対策をとっています(が、どちらも完璧ではない)。

ドキュメントにコードレベルの詳細を書かない
コードと一対一で紐づくような内容であればあるほど、ドキュメントが古くなるスピードが早いです。なぜならコード自体はどんどん変わっていくからです。一方で、アーキテクチャ、コンポーネント、コアのロジックと言ったものは安定しており、変更頻度は個別のコードに比べるとゆっくりしています。

つまりドキュメントには安定している構造(かつメンタルモデルの構築に役立つもの)を中心に書き、よく変更されるもの(コードレベルの詳細)は極力書かないようにします。サンプルコードや枝葉の実装解説などもできるだけ少なくします。これでメンテナンスの頻度自体を少なくできます。

スナップショットだと割り切る
前述した通り、ドキュメントにはコードレベルの詳細な内容は書きません。そうすることでドキュメントとシステムの乖離を極力抑えます。それができると、そのドキュメントは「yyyy年mm月時点でのスナップショット」だと割り切ることができます。そもそも頻繁に更新が必要なドキュメントはコードに近い詳細な内容を書きすぎていないかチェックしたほうがよいでしよう(あえてそうしてる場合は別ですが)。

それでももしドキュメントとシステムに乖離がおきたら、そのドキュメントを読んだ人にその乖離を積極的に直してもらうようにします。というのも、ドキュメントを読んだということは実際のコードを直近読む可能性が高いはずです。なのでドキュメントの乖離に気づきやすいというわけです。

それと最初からある時点でのスナップショットだと明記しておけば、古くなったドキュメントは信頼性が低いという前提で読者に読まれるはずです。そして古すぎてもう実際のシステムと違う点が多いものは、デメリットの方が大きいのでさっさと捨てましょう。

## 保存場所

ドキュメントを保存する場所はどこがよいのか?正直今のところどこが良いのかわかっていません。選択肢としては3つぐらいありそうです。

  • システムと同じリポジトリ
  • 社内情報ストックサービス(notion、google docsとか)
  • 色々なドキュメントを保存するドキュメント専用のリポジトリ

個人的には「ドキュメント専用のリポジトリ」に保存するのが良さそうなので、試してみようと思っています。理由はこんな感じです。

  • SWEが慣れているレビューシステム(GitHubのPull Request)を使える
  • ドキュメントの実例がすぐ近くにある
  • リポジトリをまたいだシステムのドキュメントを保存できる

というわけで、ソフトウェアのドキュメントについて思っていることをつらつらと書きました。ドキュメント書くのは楽しいので興味ある人は是非書いてみるといいと思います!ではでは。

*1:カルテエディタ、問診フロー、主訴検索についてのドキュメント