そのソースコードが汚い理由:共通した根源的な間違い
この内容には私も全面的に賛成で、クラスやフィールド、メソッド、名前空間など、とにかく文字として表れる名前には、必ず、例外なく、正しく誤解のない命名を徹底することが非常に重要だ。
http://blog.livedoor.jp/lalha/archives/50261226.html
先のエントリは、danさん*1やlalhaさんにまで言及いただき大変光栄で、なにより多くの人に読んでもらえた。多謝。
一方で、自分で読み直すと「先のエントリ」は、いくぶん観念的でいまいちよく分からないところもあるかなと思った。というわけで、より実践に結びつきやすいように、「何に気をつければいいのか」「どういう考え方でコードを書けばいいのか」を書いてみる。
lalhaさんがエントリで強調したかったという
- (1) 適当に書いたコードは後でとても大きな被害をもたらす可能性が高い
への包括的な対策であり、
- (2) たくさんのソフトウェアの「つくり」に触れ、APIを熟知しよう
と非常によく整合して親和する考え方だと思ってる。
プログラマの良心を自ら邪魔している、プログラマ自身の誤解に基づく行動、それは
プログラマたる者、誰だって自分のソースコードはきれいに書きたいと思っている、はず。プログラマはきれいにしたいと思っているのに、その希望に反してソースが汚くなるとき、またはきれいではなくなるとき、プログラマは決まってある事をやっている。
自分はこれが諸悪の根源で、かつ、ソースを汚くする原動力となる上にハマリによる時間の浪費を招き、バグをも増やしてコードの汎用性まで下げる、コーディングに現れる悪魔みたいなものと思ってる。それは、
「必要な動きにしようという考えで、ソースに手を入れる」という自己破壊
… (゚Д゚)ハア?
これを徹底的に排除する事で、様々な効果得られ、ソースコードも必然的に綺麗になる。
な… 何を言ってるのか わからねーと思うが(ry。
「そんなん、きれいとか汚いとか関係なく、やらなきゃしょうがないことだろ」と思う読者が多いかもしれない。でも、実際はそうではないこと、「必要な動きにしようという考えで、ソースに手を入れる」事を排除して、ソースコードを綺麗に書き、バグを減らして、変更に強いプログラムにできる事を以下で述べたい。
まず、「必要な動きにしようという考えで、ソースに手を入れる」と、どういう事が起きるか、から。
正しい名前、つまり「それは何?」という思考が抜ける・弱まる・後回しになる
名前の重要性は、前回書いた通り。「正しい名前」とは、それが何なのかを、そのスコープにおいて的確に表現している名前のこと。「それは何?」を一言で言うには、その名前以外に言い様がないような名前。
「正しい名前」へのこだわりは、名前そのものがよくなるだけでなく、「正しい名前」を考える事で「それは何?」という思考がはたらき、ソースの整理をうながし、設計まで改善される所にその威力がある。きれいなソースとは、すべてについて「それは何?」が明確となっていて整理されているソース。逆に汚いソースとは、「それは何?」という事が曖昧で整理もままならないようなソース。
「必要な動きにしようという考え」でソースをいじり、「必要な動き」を実現したらどうなるか。普通の人間なら、「ふう、できた。」とひとつの達成感が得られるだろう。しかしそのとき、「必要な動き」が出来ただけならば、ソースコードについて「正しい名前である事」が壊れている。「必要な動き」を実現する事自体には、「正しい名前」を維持する必要がまったくないのだから。
「必要な動き」がソースを破壊する
スーパープログラマーは分からないが、自分のような平凡なプログラマは、コーディングで大なり小なりの試行錯誤が入る。テスト段階でもNGが出たら直すし、使い始めてバグが出てもソースを直す。ソースは一度タイプしたらそれで完成するものではなく、どんどん書き直されるものだ。
だから、最初の時点ではすべてについて「それは何?」が明確となっていて整理されたきれいなコードを書いていても、「こうすれば必要な動きになる」という修正が入っていく事で、本来の「それは何?」を表していた名前と、実際の実態が少しずつずれていく。
- 「hogehoge」という関数・メソッドに、「ここでこれをやれば『必要な動き』になる」と、「hogehoge」には不要な処理が追加される。
- 「fugafuga」という関数・メソッドが、「ここでやる事を変えれば『必要な動き』になる」と、「fugafuga」じゃないものに変容する。
- 「pugepuge」という変数に、「ここでこういう値を入れれば『必要な動き』になる」と、「pugepuge」じゃない値が入れられる。
- 「geregere」というクラスに、「ここにこういうメソッドがあれば『必要な動き』を作れる」と、「geregere」の責任ではない機能が追加される。
こうやって、きれいだったソースも、だんだんきれいでなくなっていき、汚くなっていく。
これはそのコードを最初に書くときも同じ。「こういう動きが必要だな」という事を考えて、その動きをさせるべくソースを書いていくと、上記のようにめちゃくちゃなソースになる。
漠然とした「ソースをきれいにしたい」 は、強力な「必要な動き」 に必ず負ける
汚いソースは様々な害悪を振りまきながらも、そのときはなんとか「必要な動き」だけはやってくれる*2。そしてその場限りの労力を考えると、ソースをきれいに保つより汚く修正する方が非常に楽ちん。そして、「動き」は否応なく結果が出るのに比べて、「きれい」かどうかというのは人が判断する分、かなり曖昧。
だから「きれいに保ちたい」という漠然とした気持ちを持っていてそれに従って頑張ろうとしても、「必要な動き」という発想を基本思考としてソースを修正する限り、「動き」の強さに負けて基本的にソースは汚くなっていく。
「必要な動き」 から逃れられれば、ソースはきれいになる。
ソースをきれいにしたいと思って頑張っても、「必要な動き」を追う限り、ソースは汚くなる。しかし逆に言えば、「必要な動き」にさえ束縛される事がなければ、ソースをきれいにしたいと思いがあり「正しい名前」を維持する労力を払えば、当然ソースはどんどんきれいになる。ならば、解決方法は決まっている。
「必要な動き」 という発想を最初から捨ててしまえばいい
話は簡単。ソースをきれいにするには最初から「必要な動き」という発想を捨ててしまえばいい。
「プログラム=動きを書く物」 は集団幻想
プログラムを学び始めた初心者が読む本の多くは、Hello worldみたいな例から入って、if文やfor文などの説明に入っていき、「こういう書き方をするとプログラムがこう動く」という観点で説明が書かれる。自分が読んだのもそうだったと思う*3。そのため、ほとんどのプログラマは「プログラム=動きを書く物」という基礎認識を最初から持たされることになり、習熟して経験を積んでもその認識の影響から逃れることが難しい。
しかし、そのような認識は、初心者向けの方便に過ぎない。ソースをきれいにしたければ、そのような誤った認識から脱却しないといけない。じゃあプログラムとはどういうものと認識すればいいのか。
プログラミング=対象の分析、プログラム=対象の定義
プログラムとは、日本語や英語ではなくプログラミング言語を使って、「対象」=実現しようとしている事 の定義」を書いたもの。対象が大きければ、対象を分析して、定義に必要なプログラミング言語上の語彙を増やす。もちろん、その語彙とは名前。そして、名前だけじゃなくて、それが示すものの定義をプログラミング言語で書く。こうやって語彙を積み上げていって、実現しようとしている事の定義を書く事がプログラミングに他ならない。
プログラムを書き始めるときに使える語彙は、環境に用意されたライブラリしかない。その語彙を利用して組み合わせて、新しい語彙の定義を書く。そうやって新しく作った語彙、環境に用意されている語彙を組み合わせて、対象の定義を書くのに必要な新しい語彙を積み上げる。そうやって作った、対象にフィットした語彙を使って、実現しようとしていること全体の定義を書いた物がプログラム。
「正しい名前を付けて、正しい名前である事を維持」する事=「プログラム=対象の定義」という認識
このプログラムに対する認識は、大仰な書き方に見えるかもしれないけど、実際は発想の問題に過ぎない。「正しい名前を付けて、正しい名前である事を維持する」事を本当に至上命題としてプログラミングしていたら、実際にやる事はまったく変わらない。
- 対象を定義するのに必要な物を考える。
- それに名前を付けて、名前の定義を書く(=名前のついた物を実装する)。
- 実装を動きではなく意味で見直し整理・洗練させる。
- 名前を付けられる独立性がある部分は名前を付けて独立させる。
- 名前と定義(実装)が合っているか確認する。
- 単純に定義(実装)の方が間違っていたら定義(実装)を直す。
- 必要だと思っていた物(名前)が実は違っていて定義(実装)の方が必要なことを正しくやっていたのであれば、名前のほうを直す。
- 繰り返し
この認識で、プログラムのきれい・汚いは、
プログラムが汚いとは、語彙の意味があやふやで不安定であること
プログラムがきれいとは、語彙の意味が明確で一貫していること
という風に考えられる。
語彙の意味があやふやで不安定だとどうなるか
プログラミング言語でなくて日本語などの普通の言語でも、言葉はその意味が安定しているから使うことができる。「赤」は、波長700nm前後の光であると言うことに安定しているから、「リンゴが赤い」「真っ赤な血」「フェラーリの赤」のように様々な物の記述に使えて、その記述が意味をなす。
これが、「動き」という意味不明な物の都合のために、あるときは500nm前後の光を指し、またあるときは1000nm前後の光を指し、しまいには「よくわからないけどこれで必要な動きになるんですよ」といってリンゴを指したり、というようでは、そんな言葉でまともに何か書くことはできない。
プログラムも全く同じ。というよりもっともっと厳しい。日本語を読む人間は融通を利かせて意図を汲んでくれるが、コンピュータは一切融通が利かない。めちゃくちゃな言葉で書かれていたら、書かれたとおりのめちゃくちゃな結果を出す。たまたま、言葉がめちゃくちゃでも結果オーライになることは確かにある。しかし、それはそのときその場だけの結果オーライ。定義(プログラム)に手を入れるとき、使える言葉の意味がこの「赤」のようにめちゃくちゃであれば、そんな言葉を使ってまともな定義を書くことは、きわめて困難で無駄な労力を必要とするうえに、結果も予測困難な危険な作業になる。
バグ=語彙(名前)の定義記述の誤り
プログラムがプログラミング言語での対象の定義だとすると、それがおかしくなるのは、こういう時。
- 使ってる語彙の定義が間違ってる
- 「それ」を指しているはずの名前に対して、その定義が「それ」の定義としておかしい
- 語彙の使い方が間違ってる
- 「それ」をさしている名前を、「あれ」を指していると思って使っている
これがバグの目に見えない本当の姿。だからその修正は、「必要な動き」なんかを求めるんじゃなくて、
- 使ってる語彙の定義が間違ってる
- 「それ」を指しているはずの名前に対して、その定義を「それ」の定義になるように書き直す。
- 語彙の使い方が間違ってる
- 「それ」をさしている名前は「それ」として使う。「あれ」が必要なら「あれ」を使う。
- 「あれ」がなければ、「あれ」を使わない定義方法を考える
- もしくは、「あれ」を新しく定義する
- 「それ」をさしている名前は「それ」として使う。「あれ」が必要なら「あれ」を使う。
こうやる。
こういう発想でプログラミングをしていけば、「必要な動き」に足を絡め取られることなく、ソースはどんどんきれいになり、ハマりによる無駄が回避され、バグも減り、変更に強いプログラムになる。
API・ライブラリ=最初から用意されてる語彙。よい作り=うまい言い回し・文章構造
ここまで、ソースをきれいに書くということを中心に述べてきた。一方、効率よくには、なんでも自前で語彙を作るのではなく最初から用意されてる語彙を熟知して、うまく使うことが重要なのはいうまでもない。日本語の言葉を知らない作家が失格であるように、プログラミング言語に用意された語彙を軽視していてはプログラマとして問題だ。
わかりやすく整理された文章は、うまい言い回しやうまい文章構造を備えている。先人たちがたどり着いたうまい言い回しやうまい文章構造を学ばずに、全部自分でオリジナルに考えるというのは無謀に過ぎる。プログラミングにおいても、全く同様。「巨人の肩の上に立つ」のは、何事においても、よい成果を上げる必須条件だろう。
以上、当初公開時より若干推敲・改変・追記してます。疑問質問歓迎。
というわけで、Haskellやろうぜ!あと、Ozも。(おまけ)
「プログラム=動きを書く物」という認識は根強くて、頭の切り替えは楽ではない。しかし、できるだけ「プログラム=対象の定義」という認識を意識しつつ、なにより「正しい名前を付けて、正しい名前である事を維持する」という鉄の意志を持ってプログラミングに望むことで、だんだん慣れていく。これが普通だと思う。
ただ、ここでひとネタ。強制的にその認識を矯正する方法のひとつとして、Haskellをやってみるというのもよいかも知れない。なんたって、この言語には「動き」がない*4。まさに関数の定義を書いていって定義を積み上げた物がプログラム。定義の中身がどういう順番で動くか解らない。ただただ、正しく定義を書くだけ。
荒療治だが、他にも色々学べる事が多いと思う。
WebにHaskellの情報は結構あるけど、大半が玄人向け。「やさしいHaskell入門(A Gentle Introduction to Haskell)」というドキュメントがあるが、これは「天才な人にはやさしい」という意味なので要注意。ふつうの人には、この本がふつうの意味でやさしいくていい感じ。興味が湧いた人には、まずこの本を読むのがお勧め。
あと、同様に動く順番不明での書き方がある言語「Oz」。その言語とともに、プログラミングの色んなモデルを学んで視野を広げられる、通称「CTMCP(または「ガウディ本」)」も紹介しておく*5。
求められてなくても勝手にお返事
読んでくれた方、賛同してくれた方、ありがとうございます。皆さんが苦しんでいるように(?)自分も周りに十分理解を得られてるとは言えないので、疑問には答えたいと思っています。なので、一方的に勝手に答えてしまいます。ご了承あれ。
id:thesecret3 その「必要な動き」って例えばデータベースが極端に遅い場合に、同じデータを複数回読まないようにするためには・・とやると一気にソースは汚れるけど、やるなともいい難かったりする。
http://b.hatena.ne.jp/thesecret3/20090517#bookmark-13499672
最終的に動くのは当然の事実で、事実を無視しては正しい定義は確かに書けませんね。なのでそれはたとえば、「DBから○○を取ってきた結果」じゃなくて「このリクエスト処理のスコープにおける○○のデータ」というような語彙を用意してやって、それを使うというような解決になるのではと思います。あるいは、本当は○○のデータが要らないのであれば使わないに超したことはなく、それは定義から無駄を省くことに他ならないと思います。意識が必要なことは要件として対象として意識して、使う・用意する語彙を工夫して不要な依存(DBアクセス)を排除するという事自体が、「それは何?」でプログラムを書くことだと思います。
id:h_tksn 結論に絶望した。
http://b.hatena.ne.jp/h_tksn/20090517#bookmark-13499672
えええ〜〜〜っ!?な、なんで?というか、その「結論」って?。☆を付けてるid:polyamidも教えてください。
あ、HaskellとOzは、全然必須じゃないですよ。すごく役に立って面白いハズだけど、興味がある人だけで。興味がなければ、「プログラム=対象の定義」「正しい名前を付けて、正しい名前である事を維持する」という鉄の意志を持ってそれを実行してれば十分だと思ってます。紛らわしかったのではっきり「おまけ」と書き直しました。すみません。
id:Zephid 正しい名前を付け、その通りの処理内容にする。/でも自分は分割されまくったコード読むのが苦手。辞書のまた引き状態になって目的を見失う
(後半について)そこで「正しい名前」ですよ!「また引き」になるのは、関数・メソッドが出てくる度にその中身を読みに行くからですよね?中身を見に行かなくても名前を見るだけで「それは何?」のキモが解るようにしてあれば、中身を読みに行かなくても大体解るはず。さらに最初に読んでる関数・メソッド自体も、一言で名付けられる位短い物になっていれば、「先(使ってる関数・メソッドの中身)」を読みに行く事無しにその関数・メソッドの全体のキモ・概要を把握出来ます。
なので、まずは「先(使ってる関数・メソッドの中身)」を読みに行くことなくその処理のキモ・概要を把握して、それから必要に応じて詳細を確認するために「先」を見に行く、ってのがよいと思ってます。これが出来るようにソースを書いて、こういう読み方になれると、長々じっくりと大量のソースを読み込む必要なく、さっくりと処理のキモ・概要が把握する事も可能になるので、実際のプログラミングでは超絶便利です。