ソフトウェア設計の最初の一歩

本記事はQiitaのAdvent Calendar 2019として書かせて頂いたものです。
初めてのAdvent Calendar…!

TL;DR

  • ソフトウェア設計の肝要の一つは「巨大な泥団子」を「部品の集合体」に分割する作業
  • 部品に形を与える第一歩は「名前を与える」こと
  • 0から考えるときはペンとノートを使う(個人的なススメ)

対象読者

ソフトウェア設計という言葉は極めて意味が広いので、この記事の仮想読者をもう少し具体化してみます。

  • ある程度大きなソフトウェアを書こうとしている。
  • いきなり実装しだすのが良くないことは分かっているが何を整理すべきか分からない。
  • あるいは上司から「設計書を書いて」と指示されたが何を書くべきか分からない。

1万LOC以下のプログラムであれば、何も設計せずいきなり書き出して問題ないことも多いです。
あるいは個人で開発するのか共同開発するのか、使い捨てなのか資産として長く使用するのか、といった要素に応じて
どの程度「ソフトウェア設計」すべきかは変わります。

ここでは、どんなソフトウェアを作るにしてもこれくらいは理解しておいたほうが良いと思っていることを解説できたらと思います。
私は組み込み系の仕事をしているため、暗黙のうちに組み込みC++を前提にしています。Web系などは話が当てはまらないかもしれません。

ソフトウェア設計は何故行うのか

ソフトウェアは一般的に複雑で、様々な機能があります。最初は「こんな感じのソフトウェア」というイメージだけが頭の中にあります。
ここでいきなり実装し始めると、一般的には「巨大な泥団子(Big Ball of Mud)」と呼ばれるものが出来上がります。

巨大な泥団子のイメージは難しくないと思います。要は以下のようなソフトウェアです。

  • 300行を超える関数が散在
  • 過剰に機能が詰め込まれたクラス(いわゆる神クラス)が存在
  • 似たようなコードが散在
  • まったく抽象度の異なる処理が隣り合って書いてある

エンジニアリングの基本のひとつは「難しい問題は分割せよ」 です。
ソフトウェアに適用する場合は以下をする必要があります。

  • 「こんな感じのソフトウェア」をモジュール(部品)に分割する
  • 部品同士をどう繋げるかを決める

強調します。ソフトウェア設計の骨子は、分割することです。
分割すると1つ1つがシンプルになり、以下のようなメリットがあります。

  • 1つ1つのモジュールについては考えることが減り実装ミスが減る、可読性が向上する
  • 単体テストがしやすい
  • コードの再利用がしやすい
  • 多数のメンバで並行開発できる、一部の作業を切り出して外注できる

最後のやつは誰しも1度はネガティブな経験があると思います。
ソフトウェアの開発作業を分担するって言うほど簡単じゃないよね。

適切に分割できているか?

モジュールに分割しモジュール同士の繋げ方を決めるということは、ソフトウェアの構造を定めるということです。
そして、ソフトウェアの構造に絶対唯一の正解はありません。極端に言えば一種のアートであり、設計者の個性が出ます。

しかしながら、適切な構造とそうでない構造があるのも事実です。
適切な構造の目安の最も基本的なものは、言い古されたことですが「モジュールの責務が単一であるか」です。

でも「責務が単一かどうか」と言ってもピンときませんね。こういう時は名前をつけます
様々な本でも名前の重要性は紹介されていますが、それでもまだ強調し足りないです。
適切な名前が付けられたら設計の大部分は完了していると言ってもいいくらい、本当に重要です!

  • 良い名前かもしれない例

    • DataConverter (データを変換するだけの子)
    • InputValidator (入力を検証するだけの子)
    • InputReader (入力を受け取るだけの子)
    • HogeGenerator (Hogeを生成するだけの子)
    • HogeEvaluator (Hogeを評価するだけの子)
  • 悪い名前かもしれない例

    • DataConverterAndWriter (データを変換して書き出す)
    • InputReaderAndValidatorAndTransmitter (データを受け取り、検証して、送信する)

責務が単一でないと、端的な名前が付けられず”…And…And…And…”という長ったらしい名前にせざる得なくなります。
長い名前になってしまう時は分割が十分か検討すべきです。

分割アイデアを練る

先程も言いましたが、ソフトウェアの分割方法に絶対の解はありません。置かれている状況にも依ります。例えば、

  • 開発メンバが多数いる場合は、できるだけ平行開発できるように分割粒度を細かくする(と良いかもしれない)
  • 開発チーム数に合わせてモジュール分割する(と良いかもしれない)
  • 急いで成果を出さなければいけない場合は粗い分割で作らざるを得ない(かもしれない)

そういうことを踏まえて分割アイデアを練る場合、紙とペンを使うのが個人的におすすめです。
特にA3サイズのノートとシャープペンシルです(シャープペンシルは製図用のを使うとテンション上がる)。
スケッチブックに絵の構想を練る画家のごとく思索するのです。

最終的な構造図をどのような形式でドキュメントにするかは私はあまり重要ではないと思っています。
UMLにするならPlantUMLを私は使いますが、中規模のソフト開発でメンバが2,3人ならパワポのお絵かきだけでも十分だと思います。

設計しすぎない/設計は必要最小限に

設計するということはソフトウェアに一種の制約を加えることです。
ソフトウェアは柔軟で何でも出来ます。何でもできるが故に、何も制約がないと収拾つかなくなります。
だから設計でカタチを、制約を加えます。

しかしながら、制約をつけるということは出来ることの選択肢が減るということです。
早すぎる設計は損です。
私も良かれと思って設計内容に盛り込んだ内容が、後になって悪い意味で制約になり、撤回した経験が何度かあります。

設計する内容は、その時の開発上の意思決定に必要な最小限の内容にとどめます。
設計に入れなくても意思決定に問題がない内容は省きます。
それは、そのモジュールの設計/実装にアサインされた人が考えるべき内容です。
それをやる頃には時間が経過し、状況や要件がより明確になっているはずなので、今現時点よりも優れた設計が出来る可能性が高いのです。

設計対象の抽象レベルはその時に意思決定する内容によって変わります。多くの場合、最初の分割レベルはレイヤ(モジュールの集合)です。
その次はモジュールです。その次はクラスです。その次はメソッドまたは関数です。

その他の色々

設計って分割するだけではないでしょ?

その通りです。しかしながら、適切に分割さえしていれば、つまり「混ぜるなキケン」さえクリアしていれば、
その他のことはある程度修正が効くことだと思っています。一部分の修正が他の部分に影響を与えないからです。
逆に混ぜるなキケンを守れていない場合、修正しようすると全部修正が必要ということになってしまい、詰む恐れがあります。作り直したほうが早いよねというやつです。
なので、本記事で書いたことは、どんなプログラムを作る上でも必要という意味で強調しました。

上で1関数あたり300行を超えるのが悪いってあるけど、何行なら良いの?

もちろん明確な決まりはありませんが、個人的には80行を超えたら書いてるコードを疑います。

部品を作っている時に全体像が見えないと不安じゃない?

モジュールが全部揃ったら本当に欲しいソフトウェアができるんだよね?という漠然とした気持ちはあります。
設計を信じて淡々と実装します。

設計スキルが上がって良かったことは何か?

周囲の人に適切に仕事が振れるようになるのが一番大きいです。あとは単体テスト・メンテが楽なのでストレスが全体的に減ります。

他の解説ページ見ると基本設計や詳細設計で分かれているけど何が違うの?

それらの意味は職場や文脈によって大きく変わるのが現実だと思うので、私はあまり気にしていません。
(組み込み系だからそう思うだけで、SEやWeb系の世界ではそうではないかもしれません)

皆様のHappy Hackingの一助となれば幸いです。