Table of Contents
このエントリでは、Yegor Bugayenkoによる記事、Seven Deadly Sins of a Software Projectを紹介する。 (Yegorから和訳と転載の許可は得た。) 以下はその全文の和訳だが、意訳超訳が混じっているので、もとのニュアンスを知りたければ元記事を読んでもいいし、読まなくてもいい。
保守性は近代ソフトウェア開発において最も重要な美徳だ。 保守性は基本的に、新規開発者が本格的な修正を始める前に必要な学習時間で測ることができる。 学習時間が長いほど保守性は低い。 必要な学習時間が無限に近いプロジェクトもあるが、これは文字通り保守不能だ。 私はソフトウェアを保守不能にする7つの基本的で致命的な罪があると考えている。 それらについてここに書く。
アンチパターン
不幸にも、我々が使っているプログラミング言語は柔軟すぎる。 可能なことが多過ぎ、禁止されていることは少なすぎる。 例えばJavaは、数千のメソッドを持った単一の「クラス」でアプリケーション全体を記述することに何の反抗もしない。 このアプリケーションは技術的にはコンパイルして実行できる。 しかしこれはゴッドオブジェクトと呼ばれるよく知られたアンチパターンだ。
つまり、アンチパターンは技術的には設計に取り入れることができるが、一般的には取り入れるべきではないとされている。 言語ごとに多くのアンチパターンがある。 プロダクトに使われているアンチパターンは、生きている有機体の中の腫瘍のようなものだ。 いったん成長し始めると止めるのは非常に難しい。 やがて体全体が死に至る。 やがてソフトウェア全体が保守不能になり、書き直さなければならなくなる。
ひとたびアンチパターンを使ってしまうと、その量は次第に増え、「腫瘍」は育つばかりだ。
これは特にオブジェクト指向言語(Java、C++、Ruby、Python)に当てはまる。 これらが手続き型言語(C、Fortran、COBOL)から多くを引き継いでしまっているからだ。 また、OOP開発者が手続き型で命令的な思考をする傾向にあるからだ。残念なことに。
ところで、既存の有名なアンチパターンのほかに、私は以下のものもダメなコーディング法だと考えている。
- NULL参照
- ユーティリティクラス
- 可変オブジェクト
- GetterとSetter
- オブジェクト関係マッピング(ORM)
- シングルトン
- Controllers、Managers、Validators
- Public Static メソッド
- キャスト
私ができる実践的な提案は、読んで学ぶということだけだ。 ここに挙げた本か私の著書「“Elegant Objects」が多分助けになるだろう。 常にソフトウェアの品質を疑い、「動く」ということだけで満足してはいけない。 ちょうど癌のように、診断が早ければ早いほど生き残る可能性が大きい。
追跡不能な変更
コミット履歴を見るとき、全ての個々の変更に対して、何を、誰が、なぜ変更したのかがわからないといけない。 さらに、これら3つの情報を得るのにかかる時間は秒単位で計測しないといけない。 殆どのプロジェクトがこのようにできていない。 以下に実践的な提案を示す。
常にチケットを使う
プロジェクトやチームがどんなに小さくても、例え一人だけでも、修正しようとしている全ての問題に対してチケット(GitHub issues)を作れ。 チケットに問題の簡単な説明とそれに対する考えを記述しろ。 このチケットをその問題に関する全ての情報の一時的なストレージとして使え。 将来、他の誰かがその「不可解なコミット」が何であるかを理解するために参照する可能性のある全ての情報をそこに書け。
コミットからチケットを参照する
言うまでもないが、全てのコミットにはメッセージが付いていないといけない。 メッセージのないコミットはまったくひどい悪習だ。議論の余地はない。 しかしメッセージだけでは不十分だ。 全てのメッセージはチケット番号で始まらないといけない。 GitHub(君ももちろん使っていると思うが)は自動でコミットとチケットをリンクし、変更の追跡可能性を高めてくれる。
何も消さない
Gitは「強制」push、つまりサーバに既にあるブランチ全体を上書きするpushを許している。 これは開発履歴を破壊する方法の例のひとつだ。 また、GitHubのチケットを「きれい」にするためにコメントを削除するのをよく見るが、これはまったくの間違いだ。 何であれ決して消すな。 履歴がどんなに汚く(または乱雑に)見えても、そのまま残しておくことだ。
アドホックリリース
全てのソフトウェアはエンドユーザに届けられる前にパッケージングされなければいけない。
Javaのライブラリであれば.jar
ファイルにパッケージングされリポジトリにリリースされないといけない。
ウェブアプリケーションであればプラットフォームにデプロイされないといけない。
プロダクトの大きさにかかわらず、テスト、パッケージング、デプロイする正規の手順は常にあるべきだ。
理想的な解決策はこの手順を自動化し、コマンドラインから単一のコマンドで実行できるようにすることだ。
$ ./release.sh
...
DONE (took 98.7s)
ほとんどのプロジェクトはこれに程遠い。 そのリリースプロセスにはマジックが含まれていて、担当者(DevOpともいう)はあちらこちらのボタンをクリックしないといけない。 どこかにログインして、いくつかの指標をチェックして、など。 このようなアドホックリリース手順は、ソフトウェアエンジニアリング業界全体でいまだに典型的な罪である。
ここで私ができる実践的なアドバイスはひとつだけだ。自動化しろ。 私は自動化にrultor.comを使っているが、好きなのを使えばよい。 重要なのは、手順全体が完全自動化されていてコマンドラインから実行できることだ。
自発的静的解析
静的解析はコードの見た目を良くしてくれる。 見た目がよくなると、必然的に上手く動くようになる。 しかしこれは、チームの全員が静的解析ツールに指示されたルールに従うことを強制(!)されているときだけ有効だ。 私はこれについてStrict Control of Java Code Qualityに書いた。 私はJavaプロジェクトではqulice.comを使い、Rubyではrubocopを使うが、他にも似たようなツールがほとんど全ての言語にある。
どんなツールを使ってもいいが、強制しなければいけない! 静的解析ツールを使っているほとんどのプロジェクトで、開発者は単に見栄えのいいレポートを生成するだけで、コードの書き方を直そうとはしない。 そのような「自発的な」アプローチはプロジェクトにとって何のメリットもない。そればかりか、品質への錯覚を生む。
私が言いたいのは、静的解析は開発パイプラインの中の必須ステップでなければいけないということだ。 もし静的解析ルールがひとつでも破られたら、ビルドを成功にしてはいけない。
未知のテストカバレージ
簡単に言うと、テストカバレージはソフトウェアがユニットテストや統合テストでテストされた度合いだ。 カバレージが高いほど、テスト中に実行されたコードの量が多い。 カバレージ高いのは明らかに良いことだ。
しかし、多くのプロジェクトで開発者は単にカバレージを知らない。 この指標を計測しないのだ。 テストは書いているかもしれないが、それがソフトウェアのどの程度深くまで行き渡っているか、どの部分がテストされていないのか、誰も全く知らない。 このような状態よりは、カバレージが低くても、計測されて皆にレポートされている状態の方がかなり良い。、
高いカバレージは高い品質を保証するものではない。 これは明らかだ。 しかし、テストカバレージが未知であることは保守性に問題があるという明確な印だ。 プロジェクトに入った新規開発者は、修正がどの程度カバレージに影響を与えるかを確認できなければいけない。 理想的には、テストカバレージは静的解析でチェックされ、事前に決められた閾値(普通80%位)を下回ったらビルドが失敗するようになっているべきだ。
ノンストップ開発
ここでノンストップが意味するのは、マイルストーンもリリースも無いということだ。 書いているソフトウェアの種類によらず、頻繁にバージョニングとリリースをしないといけない。 明確なリリース履歴が無いプロジェクトは保守不能なガラクタだ。
これは概ね、保守性とは私が君のコードを読んで君を理解できるかということだからだ。
私がソースとそのコミットとリリース履歴を見るとき、開発者の意図が何で、プロジェクトが一年前に何をしていて、今どこに向かっているのか、ロードマップは何か、といったことを説明できなければいけない。 こうした情報の全ては、ソースコード中とGit履歴(こちらがより重要)に入っていなければいけない。
GitタグとGitHubリリースはそうした情報を残すための強力な道具だ。 これらをめいっぱい使え。 また、それぞれのバージョンのバイナリは直接ダウンロードできるようにしておくことを忘れるな。 プロジェクトが今バージョン3.4を開発していたとしても、即座にバージョン0.1.3をダウンロードしてテストできなければいけない。
ドキュメントに載っていないインターフェース
全てのソフトウェアは、その機能を使うためのインターフェースを持っている。 RubyのGemであれば、エンドユーザが利用できるクラスとメソッドがある。 Webアプリケーションであれば、エンドユーザが参照して操作できるWebページがある。 全てのソフトウェアプロジェクトはインターフェースを持ち、そのインターフェースは入念にドキュメント化されていないといけない。
これまでに挙げた全ての項目のように、これも保守性に関することだ。 プロジェクトの新規プログラマは、ソフトウェアをインターフェースから学び始める。 そのソフトウェアが何をするものなのかを理解して自分で使ってみる、ということができなければいけない。
私はここでユーザに対するドキュメンテーションの話をしている。開発者に対するものではない。 一般的に、ソフトウェア内部のドキュメンテーションには反対だ。 私はアジャイルソフトウェア開発宣言に完全に同意している。 動くソフトウェアは包括的なドキュメントよりもはるかに重要だ。 しかしそれは、(開発者ではなく)ユーザが読むための「外部」ドキュメントのことを指しているわけではない。
要は、エンドユーザとソフトウェアとの間の相互作用はドキュメントに明記されていなければいけない。
ライブラリであれば、エンドユーザはそれを使うソフトウェア開発者だ。 コントリビュータではなく、それを「ブラックボックス」として単に使うだけの開発者だ。
以上がYegorの記事。
一般的に認知されているベストプラクティスやアジャイル感に沿った、Yegorにしては丸い内容だ。 むしろ、現在は既にアジャイルは完全に浸透して、その次のステップとしてDevOpsをめざす時代になっているので、開発サイドだけに言及したこの内容だと少々保守的で古臭く感じさえする。
ただ、改めてだけど、「追跡不能な変更」に書いてあることはいいプラクティスだと思う。 GitHubで開発するときは、全てのコミットがIssuesかPull Requestsに紐付いていて、相互に導出可能であると便利そう。 面倒だからやったことないけど。
「ドキュメントに載っていないインターフェース」に書いてあることはちょっと引っかかる。 アメリカ人ってのは、分厚い細かいドキュメントなんか書いても誰も読まねーよ、ってのが基本のスタンスだと思っていた。 Steve Jobsだったら、ドキュメントが必要なくなるまでUIを洗練させろとか言いそうだ。 もしくはユースケースベースのざっくりとしたマニュアルをメインにしたり。
全てのインターフェースを入念にドキュメントしろっていうのはなんだかとても日本的だ。 そうしてくれた方が使う方は助かるんだけど、作る側はドキュメントの保守が大変だ。かなり頑張って気を使っても、ドキュメントと実装のずれってのは本当に簡単に頻繁に起こる。特に大きい会社の大きいプロジェクトで、開発チームとは別にマニュアルチームがあるような場合、このずれはほとんど全く避けられない。
自然言語でのプログラミングへの希望が大昔からあるようだけど、そんなものより、プログラミング言語で書いたものから自然言語のマニュアルを生成してくれるもののほうがよっぽど価値があると思う。