|
漁師プログラマ 山崎敏 YAMAZAKI Satoshi
vol.02 |
|
|
●●ある日 |
|
|
約1年かけた大きな仕事がありました.ネットワーク系のシステム開発です.開発言語がC++であったのが幸いし順調にコードは仕上がり,半年後にはプログラムは動いていました.プログラムも動いたし「後はのんびりできるなぁ」と思っていたのですが,本当の仕事はこの後にやってきました.
その仕事とは「ドキュメント(仕様書)書き」です.厚さ5cmのバインダに約40冊(いや,もっとあったかも)を仕様書としてお客さんに納めるわけです.ハッキリ言ってたいへんな量です.それを残りの数ヶ月で書きまくるのです.いやぁ.もう,すごいですよ.ガンガン印刷して,使用済みの紙が崩れそうなほど高く詰まれていきます.最後の日にはプリンタが壊れて,もう大騒ぎ.新しいプリンタをどこからともなく奪ってきて徹夜で印刷したり.納品の当日には,宅配便で送っていたのでは間に合わないということで,タクシー2台に仕様書の入ったダンボール箱を積んで納品しに行くという暴挙に出るし.紙ですよ紙.タクシーに乗っているのが人ではなくて紙なんです.
なんで,そんなに紙にこだわるのでしょうか?電子媒体ならCD-ROM1枚に収まるほどの内容なのに….長年,プログラマやってきましたが,こんな仕事は初めてでした.これってオレの仕事なのか?オレってプログラマだよな?などと疑問を残しつつ仕事は終わるのですが,そんな辛い思いをしてまで作成したドキュメントは本当にお客さんの役に立っているのか?そんなことまで考えてしまいます.
|
|
|
●●ウォーターフォールモデルの終焉 |
|
|
別の仕事で「プログラマが足りない」ってことで駆り出されたのですが,その会社は,ソフトウェアの開発に「ウォーターフォールモデル」(図1)を適用している会社でした.仕様書はすでに数百ページも書いてあって,すべてのファイル名や関数名,関数の引数,そして機能のフローチャートまでしっかり書いてありました.あとは,この仕様書を元にコードを書くだけの単純作業で終わるはずでした.

しかし,実際には我々部外者のプログラマが借り出されるほどの大きな問題になっていたのです. 「やはりねー」と思った方,漁師プログラマですね. そうなのです.その答えは単純なのです.仕様書のとおりにコードを書いても,そのコードでは動かないのです.仕様書はその内容が正しいかどうか検証されずに書かれているのです. いや,正しいと思って書いてはいるはずなのですが,人は機械ではないのでミスが入ります.長い間,プログラマという職業をしていると,人の考えることにはミスが多いことに気づかされます.ウォーターフォールモデルに沿って開発する場合,トップダウン式に機能が細分化されていきます.概要が決められ,外部仕様や内部仕様が決まります. ですが,それが正しく動くかどうかは誰も検証していないのです.ウォーターフォールモデルでは,きちんと仕様書が作成されていれば,人が変わっても開発作業は続行できるとしています.しかし,ソフトウェア開発にはあてはまらないのです.実際には,紙に残らない情報が多く,最初に書いた人の思想は受け継がれずに,紙だけが次の人に移動していきます.その人も真の意味を知らずに新しい紙を作成していきます. こうして紙は増え,内容は希薄になり,書けば書くほどミスが入り,最終的にコードに起こすプログラマにしわ寄せがきます.とたんに日程遅延をしはじめ「プログラマを増員しろ!」となるのです.ウォーターフォールモデルのパターンなのでしょう.
漁師プログラマの立場から言わせていただけるなら,仕様書は概要で十分です.細かく書いてあると,逆に本来実現したい機能が見えません.「木を見て森を見ず」とでも言いましょうか.結局この仕事は,コーディングが遅れ,その後の仕様書へのフィードバックという膨大な作業を発生し,決して品質が良いとは言えない状態で終わりました(俗に言うデスマーチですね).では,ウォーターフォールモデルの何が問題だったのでしょうか?
|
|
|
●●ペーパーレスって死語? |
|
|
あなたがプログラマなら,思い出してみてください,仕様書が役に立っている場面を.何か思い当たりますか?おそらく,仕様書に関しては良くないイメージが多いのではないでしょうか.誰が見るのかわからない紙を徹夜しながら書いているとか.仕様書が多くなってしまい書類棚に入りきれなくなったり.大量に印刷するので,再生紙とはいえいったいどこが地球にやさしいのだろうかと悩んでみたり.締め切り間際の一番忙しいときに限って,プリンタのトナーがなくなり,ガチャガチャと手荒に扱ったら故障してしまい印刷できなくなってしまったり.助けがほしくて周りを見渡しても夜中の2時では警備のおじさんしかいなかったり. そうまでして書き上げた仕様書なのに,誰の目にもふれることなく,ひっそりと倉庫の片隅でカビだらけになり異臭を放っていたりと,あまり良いイメージはないと思います.
結論を先に言うと仕様書は必要です.しかし,設計のときというより,むしろ比重はメンテナンス(保守)のときにあります.設計のときにも必要ですが,設計時の仕様書は大まかなモノで良いのです. フローチャートのレベルまで噛み砕いて細かく書いてある必要はないのです.そこまで細かく書いてしまうと,逆に困ったことが起こります.必ず仕様書と実際のコードの間に不一致が出てくるのです. この場合,仕様書を直さなければならなくなります.もしくは,コードを無理やり仕様書に合わせるよう強引に書くこともできますが,この場合,コードが読みにくくなり理解しがたいコードになってしまいます.最悪なケースでは,仕様書とコードが一致しなくなり,どちらを信じて良いのかわからなくなるのです. そうなってしまうと,最終的にはコードを信じることになると思うのですが,仕様書の内容がどこまで合っていて,どこからが合っていないのか,全体を調べなければならないので,余計な作業がかかり,仕様書がないほうがましなのでは,という状態になります.作業を軽減する目的で作成した仕様書が逆に作業を増やしてしまうわけですからこれでは本末転倒です. 何のためにその紙が必要なのか考えたことはありますか?紙を書くことが仕事だと思い込んでいませんか?むしろ,紙を減らすことがあなたの仕事ではないのでしょうか.
|
|
|
●●コードの寿命 |
|
|
皆さんはソフトウェアのコードの寿命について考えたことがありますか?ソースコードでもオブジェクトコードでもどちらでも良いのですが,たとえば今作っているオブジェクトコードはどのくらいの期間使われるのでしょうか.何人の人間が使うのでしょうか.国外にまで広がるのでしょうか.では,ソースコードのほうはどうでしょうか.どのくらいの期間メンテナンスすることになるでしょうか.あなたが今読んでいるソースコードは何年前に書かれたコードでしょうか.
数年前「2000年問題」というソフトウェアのつくりの問題が指摘され,ちょっぴり騒ぎになりました.
この問題の根本的な原因はおそらく「そんなに長い間(2000年を超えるまで)このソフトウェアを使うとは思わなかった」という理由が多かったと思います.このように,ソフトウェアの寿命は皆さんが思っているより長いというのが現実ではないでしょうか.ということは,逆にソフトウェアの寿命が長いことをふまえることで,メリットを増やせるのではないでしょうか.
たとえば,ソフトウェアの性能向上にはそれなりの工数や費用がかかります.一方,ハードウェアは同じ価格で1年間で2倍程度の性能の向上しています.これは,ソフトウェアの性能を向上しなくても,ハードウェアの恩恵をそのまま受け続けて性能を向上することができることを意味しています.そして同時に,それだけの期間コードのメンテナンスをすることにもなるわけです.このメンテナンス作業が軽減できれば,ハードウェアの恩恵を受け続けるメリットが増えるわけです.
ソフトウェアの寿命は意外にも長いものです.その場しのぎのコードを書くのはやめましょう.自業自得ではありませんが,自分で書いたコードを自分で読み直すことになる可能性は高いと思います.もしかしたら自分の子供たちが自分の書いたコードを読むかもしれません.そんなとき,子供に何と言われるでしょうか?できることなら「パパってすごいね」と言われてみたいと思いませんか?少なくとも「ダサい」とは言われたくありませんよね.
|
|
|
●●メンテナンスは地獄? |
|
|
コードのメンテナンスとは,ある一部の人にはたいへん重要な問題であり,また別の一部の人にはまったく無関係の問題だったりします.人によって両極端な問題だと思います.メンテナンスとは,ある人にはバグ退治であったり,また別の人には顧客からのクレーム処理であったり,機能追加であったり,はたまたコード再利用のためであったり,さまざまな理由でメンテナンスをすると思います.このような仕事の多くは,すべてを1人で行うことは珍しく,複数人の間を渡り歩くのが普通です.むしろ渡り歩くので問題になるとも言えるわけです. 良くも悪くも人の考えていることにはそれぞれ個性があり,世の中にまったく同じ考えを持つ人はいないでしょう.この考え方に違いがあるために,メンテナンスはたいへんな作業になります.コードが読めても考え方が理解できないとメンテナンスできないからです.コードに手を加えることはできるのですが,その修正が正しいのかどうか判断が難しいのです.全体の流れを判断して,設計時の考え方に沿っているのかが問題になります. たとえば,あるシステムではエラー処理を共通モジュール化していても,別のシステムでは各モジュールで独自にエラー処理をしているかもしれません.そのようなシステム全体の考え方の違いがメンテナンス作業を混乱させ,最後には「地獄だ」という結論にまで到達させてしまうようです.
仕様書には「なぜなのか?」や「どういう理由で何をしたいのか?」という概要を書くべきなのです.
より詳細なレベルである「1を代入する」とか「0と比較する」では,全体として何がしたいのかがわからないし,それが正しい記述なのかが判断できないのです.「ログに実行日時を出力する」とか「データベースからユーザ名を取得する」とか大きな流れを記述する必要があるのです. そもそもメンテナンスに必要な情報とは,それほど多いモノではなくて,最近の言語であれば,ほとんどソースコード内にコメントとして記述できます.ソースコードに入りにくい情報として「全体の流れ」があります.機能の流れは比較的記述されやすい情報なのですが,とくにデータの流れは見えにくいです.
|
|
|
●●フローチャートとデータフロー図 |
|
|
簡単に説明すると,フローチャートとは「機能の流れを書いた図」であり,データフロー図とは「データの流れを書いた図」(図2)になります.

ソースコードには主に機能の流れに沿って書きますから,フローチャートを書くほうがメリットがありそうに思えます.しかし,実は違います.ソースコードと同じことをフローチャートとして書いて残してもあまり意味がないのです.
『達人プログラマ−システム開発の職人から名匠への道』(アンドリュー・ハント,デビッド・トーマス著/村上雅章訳/ピアソン・エデュケーション/ISBN:4894712741)という本に,「同じことを2度書くな」という言葉が書いてあります.ソースコード上でコピペをするなということなのです. たとえば,共通に書けるコードを数ヵ所にコピーしたとします.その後,コピーしたコードにバグが見つかり修正することになった場合,前にコピーした所すべてに対して同じ修正を正確に行わなければなりません.コピーした本人が修正するならまだしも,他人の書いたコードがそうなっていた場合,すべてを正確に修正するのはとても困難になるのです.
このたとえはソースコード上での話ですが,私はソースコードとフローチャートの関係でも同じような問題が起こると思っています.フローチャートと同じコードが正しく動けば問題はありませんが,コードを修正した場合フローチャートも修正する必要が出てきます.

これが,データフロー図のようにソースコードの記述とは違う方向から書かれている場合,無駄な修正しなければならないケースは少なくなると思うのです.「PAD(Problem Analsys Diagram)」や「NSチャート」でも同じような理由から「同じことを2ヵ所に書いている仕様書」になると思うのです.ですから,仕様書には「データフロー図」や「シーケンス図」(図3)のような,ソースコードからは読み取りにくいデータの流れに関する情報を優先して書くべきであり,残すべきであると思うわけです.
|
|
|
●●メンテナンスは設計から |
|
|
設計がうまくいかないと,メンテナンスも大変になります.たとえば,あなたが部屋でインターネットを楽しみたいと思ったとします.しかし,電話線を引こうにも壁に穴をあけられなかったり.電源コンセントが少なかったり,遠かったり,そもそもパソコンを置くための机がなかったり,いろいろと細かい作業が発生したりします. 同じようなことは,ソフトウェアのメンテナンスにも起こります.ある場所であるデータが必要になったりするのですが,設計がダサいとあちらこちらに穴をあけパッチを当てることになるのです.プログラマであれば,コードが変更されたとき,どの程度の修正が必要になるのか,気になるところだと思います. 設計がうまくいっているコードでは,驚くほど少ない行数の修正ですむ場合があります.最悪でも数ヵ所のですむのです.ところが,設計のダサいコードでは,ほぼすべてのモジュールにまんべんなく修正が入り,データを延々と引き回してパッチを当てます.パッチというのは,ある条件が成立した場合のみ特別な処理を行うという例外のような分岐処理が多いのですが,やはりこういった例外的な分岐が多くなると,全体の見通しが極端に悪くなります.
ここで,こういうコードがすばらしいのだ!とお見せできないのがもどかしいのですが,少なくともそういうコードは存在します.その違いが微妙で難しいところなのですが,スマートなコードは余計なコードが少なく簡潔に書かれているのに対して,凝ったコードは不必要なコードが多いと言えます. おそらく将来のことを考えて,必要のないコードがたくさん入っていると思います.そのような肥大化したコードでは,やはりメンテナンスに向いていないと思うのです.必要最小のコードで,最大の効果(可読性や保守性)を上げるコードがスマートなコードなのです.
スマートなコードを書くには,コードの設計に長い時間をかけるべきでしょう.とくに,いきなりコードを書くのではなく,まずは紙の上に設計方針を落書きすることをお勧めします.
|
|
|
●●メンテナンス工数の図 |
|
|
プログラミング言語を大きく2種類に分けると,
@ 型の概念が薄くて情報隠蔽度の低い言語
A 型の概念の濃くて情報隠蔽度の高い言語
に分けることができると思います.@にはPerlやBASIC,AにはJavaやC++のような言語が分類されるでしょう. @のように型の概念が薄くて隠蔽度の低い言語では,比較的容易に書きやすく,規模の小さなシステムを開発する場合には威力を発揮します.しかし,規模が大きくなるにつれて,その言語の持つ容易に書きやすいという特徴が逆に理解しにくいという工数を発生させてしまうように思えます. 一方,Aのように型の概念が濃くて情報隠蔽度の高い言語では,小さな規模でもある程度の記述量が必要になり,それなりの工数がかかってしまいます. しかし,規模が多くなってもなかなか工数が増えないという特徴があると思います.
これら2つのタイプのメンテナンス工数の違いを比べると,図4のようなグラフになります.この図は,対象となるシステムの規模によって,メンテナンス工数が変わるので開発言語のタイプを慎重に選ぶべきである,ということを示しています.

|
|
|
●●コーディング規約とマニュアル化社会? |
|
|
私はしっかりとした仕様書は書かないほうなので,ソースコードに頼ることになります.コードにしっかりコメントが書いてあればそれほど問題にはならないと思います.しかし,コメントはただ書けば良いというモノではありません.コードと同じ内容を書くのは意味のないことですし,混乱の元になるだけなので避けるべきです.単純に多ければ良いものでもないし,少なくてもダメ.変数名などのコーディング規約などと同じで,つねに良い書き方を模索する姿勢が大事だと思います. コーディング規約などがとくにそうなのですが,ギチギチに規則を決めてしまうケースがあります.
しかし,それは逆効果だと思います.ここで言いたいことは「規則を決めてそれを守れば良い」というのは,同じモノを大量に生産するような工場であればあてはまることかもしれませんが,プログラムのように同じモノは2つとないような常にクリエイティブな生産作業では,規則にあてはまるような「同じことの繰り返し」という作業はほとんどないと思うのです.答えが1つしか考えられないような問題であれば,規則どおりの手順によって効率化することもできるでしょうが,答えが複数存在するような問題に対しては逆に規則どおりの手順では非効率的であり,1つの解答にたどり着くことさえも難しいでしょう.
若い人はよく「どうしたら良いか教えてください」と言います.また「答えのない問題は解けない」とも言います.社会でも同じような問題も少しはあります,しかし,多くの問題は「答えのない問題」なのです.その答えのない問題の中から,自分で見て,感じて,判断して,開拓して,ときには人の知恵も借りて無理矢理にでも解決していくしかないわけです.本当に必要とされているのは,答えのない問題からいかにして答えに近づくか,その方法を模索する姿勢とその結果なのです.
脱線気味なので話を戻します.筆者は規則はできるかぎり少ないほうが良いと思っています.規則が少ないほうが,それだけ環境の変化に対応(順応)するマージンが広いと思っているからです.あるとき,画期的な方法を閃いたとします.ところが,規則が決められていてそれを実行できなかったとしたら,それは明らかに損失です.規則がなかったら効率が上がったかもしれません. もちろん,逆に失敗して下がる可能性もあります. しかし,「常に考え続ける姿勢」があれば,失敗した場合であってもそれを吸収し次に活かせるはずです.コーディング規則やコメントの書き方をギチギチに規則で固めてしまうのは,そういった「考える姿勢」を束縛しますし,何より「無駄な単純作業」になってしまいがちです.無駄な作業になると「いかにして楽をするか?」というネガティブなループに入り,生産品の質も悪くなってしまうでしょう.
最後に,規則を決める立場の人に一言言わせてください.正解を求める姿勢も大事ですが,正解はつねに1つではないことも忘れないでほしいのです. 世の中は常に変化しています.その規則を決めることは変化への追従を無視することにならないでしょうか?
|
|
|
●●やっぱり可読性? |
|
|
メンテナンスしやすいコードの書き方というか設計の方針として,私が普段心掛けていることに「データ指向」という考え方があります.「もっとデータに着目しよう」という考え方なのですが,簡単に説明するとこうなります.みなさんが食事をするときのことを考えてみてください.まず食べ物を用意して食べます.そして食べ終わったら片付けるでしょう.これを1週間分の食べ物をすべてテーブルの上に乗せて,食べ終わった皿などもそのまま放置しておく人はいないでしょう.ここで言いたいのは衛生面の問題ではなくて,まだ食べていない物と,すでに食べおわった物とがゴチャゴチャになって混乱してしまう状態を問題にしているのです. 日常の食事ではこのようなことをしない人でも,ソフトウェアのコード上ではしていたりします.関数の先頭やグローバルなエリアに,必要となるすべてのデータを並べておいて実際には最後の最後で使ったり,あるいは条件によっては使わなかったりします.
Pascalのように,データの宣言は必ず先頭で行うという規則になっている言語もありましたが,最近の言語ではデータの宣言はどこでも可能になっています.C言語でもスコープで区切ればどこでも宣言は可能です.必要なモノは必要なときに確保し,必要のなくなったモノはできるだけ早く破棄しましょう.人が一度に記憶することのできる数は7項目が限界であると言われています.7という数字に疑問があるかもしれませんが,項目数は少ないほうが記憶しやすいのは事実ですよね.
この話をコードにするとリスト1,2のようになります.2が望ましい例です.
| リスト1 可読性向上前 |
void foo()
{
// すべてを一度に宣言
int a ;
int b ;
int c ;
int d ;
int e ;
... // その後,いろいろな処理が続く
} |
| リスト2 可読性向上後 |
void foo()
{
int a ; // ←全体をとおして必要な宣言
{
int b ; // ←必要なぶんのみ宣言
int c ;
...
} // ←必要なくなったらすぐに破棄
{
int d ;
...
}
{
int e ;
... // ←ここではa とe についてのみ考える
}
} |
現場のプログラマの方々には,メンテナンス性の低いコードを仕方なくメンテナンスしなくてはいけないというケースも多いのではないでしょうか.そういった場合には,ソースコードを読みながら,ソースコードの可読性を上げていくと良いと思います.他人のコードに手を入れるのはできる限り避けたいと思われるかもしれませんが,可読性が向上すると思考の具合も向上しますし,全体の流れを把握しやすくなり,最終的にはバグを入れずにすむようになります.
具体的な作業としては,まず変数をよりローカルな位置に移動しましょう.グローバル変数はファイルローカルな変数に,ファイルローカルな変数は関数ローカルな変数に,関数ローカルな変数はよりローカルな変数に移していくのです.また,変数名をわかりやすい名前に変えて,スペースも入れて見やすく並べます.この2つの作業をするだけでも,かなり可読性が上がります. この場合,関数(機能)に対して変更するわけではないので機能の内容を深く知らなくても変更できます.どの変数が,どこで参照されて,どこで更新されているのか,ソースコードからデータの流れを把握するわけです.データのアクセス可能なスコープを短くすることで,大まかなデータの流れが把握できるようになるのです.このデータの流れを把握することでメンテナンスがしやすくなるのです.ぜひ一度,試してみてください.
|
|
|
●●最後に |
|
|
メンテナンスに対しても「考え続ける姿勢」と「可読性」が重要であるという,前回(バグに対して)と変わらない結論になってしまいました.もう少し違ったアプローチを期待していた方,ごめんなさい.
次回は「コードの再利用」について書いてみたいと思います.お楽しみに.
|
|
|
コラム●80対20の法則 |
|
|
この法則(パレートの法則ともいうらしい)には例がたくさんあります.たとえば,
・80%の人は,地球上の20%の土地に住んでいる
・お金の80%は,全人口の20%の人が持っている
とかなんとかと言われています.
この法則はソフトウェア開発の世界にもあてはまる法則でして,
・バグの80%は,全体の20%のプログラマが作りこんでいる
・プログラムの処理時間の80%は,全体の20%のコードで動いている
・ユーザの80%は,20%の機能しか使わない
といったことが言われます.この法則のキモは「その20%を重視しなさい」ということだと思います.バグを減らしたいのであれば,全体の80%をしめる20%のコードについて対策をするべきです.コードの速度を最適化するにも同じように20%のコードについて対策すべきなのです.すべてのコードを対象とする必要はないということなのです.
|
|
|
コラム●スペースの効用 |
|
|
可読性って,難しく考える必要はまったくなくて,スペースをうまく使うだけでぐんと可読性が上がります.逆に言えば,もっとスペースを使いましょうということです.
たとえばリストAは普通の宣言ですが,これもリストBのようにすると型名,変数名,初期値ときちんと並んで見えます.見た目にもきれいですが,このかたまりが「宣言のかたまり」であることを主張するわけ
です.このようにスペースを入れるだけで可読性はずいぶん変わるものなのです.みなさんもこのあたりから「可読性」にこだわってみてはいかがでしょうか?
効用は保証しますよ.
| リストA スペースに注意を払う前 |
int count = 0 ;
char read_buff[256] = "" ;
const bool ok = true ;
|
| リストB スペースに注意した後 |
int count = 0 ;
char read_buff[256] = "" ;
const bool ok = true ;
|
|
|
|
コラム●if文の可読性 |
|
|
if文の可読性の話をしまましょう.
リストCの書き方では,OKの場合の処理なのか,NGの場合の処理なのか,瞬間すぐに判断できませんね.
そもそも“=”ではなくて,“==”の誤りではないか?と迷いますよね.if文には条件文を書きましょう.そんなわけで,リストDのように書いたほうが良いと思いませんか?
でも,1行に複数の処理を入れるのは可読性が悪いです.それに,FILE*fp;に初期値が入っていないのは精神的に良くありません.そこで,リストEのように書きます.ここまで書ければまずは合格でしょう.
| リストC OK の処理かNGの処理かわかりにくい |
FILE * fp ;
if ( fp = fopen( ... ) )
{
... // ←OK の場合の処理?NG の場合の処理?
}
|
| リストD 改善例 |
FILE * fp ;
if ( ( fp = fopen( ... ) ) != NULL )
{
... // ←OK の場合の処理とわかる
}
|
| リストE さらに改善した例 |
FILE * fp = fopen( ... ) ;
if ( fp != NULL )
{
... // ←OK の場合の処理とわかる
}
|
|
|
|
コラム●Cでループの話 |
|
|
C言語やC++のコーディングでループを書く場合,リストFのように書くか,リストGのように書くかで悩んでいる人もいるでしょう.漁師プログラマからのアドバイスは「for文を使いなさい」です.
とくに,難しい薀蓄はありません.単純に文字数が少ないからという理由でも良いと思います.ここで重要なのはwhileを使ったりforを使ったりコロコロと変えないことです.「ループにはfor文を使う」これで良いと思います.whileを使うと,条件文を省略できないので,とくに無限ループなどで
while (1){...}
という初心者にとって可読性の低いコードを書くことにもなってしまいます.無限ループはやはり
for (;;){...}
でしょう.
| リストF forを使う例 |
for ( int i=0 ; i<max ; ++i )
{
...
}
|
| リストG whileを使う例 |
int i = 0 ;
while ( i < max )
{
...
++i ;
}
|
|
|