強制的にその認識を矯正する方法のひとつとして、Haskellをやってみるというのもよいかも知れない。なんたって、この言語には「動き」がない*4。関数の定義を書いていって定義を積み上げた物がプログラム。定義の中身がどういう順番で動くか解らない。荒療治だが、他にも色々学べる事が多いと思う。詳細を見るWebにHaskellの情報は結構あるけど、大半が玄人向け。Haskell)」というドキュメントがあるが、これは「天才な人にはやさしい」という意味なので要注意。ふつうの人には、この本がふつうの意味でやさしいくていい感じ。興味が湧いた人には、まずこの本を読むのがお勧め。あと、同様に動く順番不明での書き方がある言語「Oz」。その言語とともに、プログラミングの色んなモデルを学んで視野を広げられる、通称「CTMCP(または「ガウディ本」)」も紹介しておく*5。読んでくれた方、賛同してくれた方、ありがとうございます。皆さんが苦しんでいるように(?)自分も周りに十分理解を得られてるとは言えないので、疑問には答えたいと思っています。一方的に勝手に答えてしまいます。その「必要な動き」って例えばデータベースが極端に遅い場合に、同じデータを複数回読まないようにするためには・・とやると一気にソースは汚れるけど、やるなともいい難かったりする。http://b.hatena.ne.jp/thesecret3/20090517#bookmark-13499672最終的に動くのは当然の事実で、事実を無視しては正しい定義は確かに書けませんね。それはたとえば、「DBから○○を取ってきた結果」じゃなくて「このリクエスト処理のスコープにおける○○のデータ」というような語彙を用意してやって、それを使うというような解決になるのではと思います。本当は○○のデータが要らないのであれば使わないに超したことはなく、それは定義から無駄を省くことに他ならないと思います。意識が必要なことは要件として対象として意識して、使う・用意する語彙を工夫して不要な依存(DBアクセス)を排除するという事自体が、「それは何?」でプログラムを書くことだと思います。http://b.hatena.ne.jp/h_tksn/20090517#bookmark-13499672えええ〜〜〜っ!?な、なんで?というか、その「結論」って?。☆を付けてるid:polyamidも教えてください。あ、HaskellとOzは、全然必須じゃないですよ。すごく役に立って面白いハズだけど、興味がある人だけで。興味がなければ、「プログラム=対象の定義」「正しい名前を付けて、正しい名前である事を維持する」という鉄の意志を持ってそれを実行してれば十分だと思ってます。紛らわしかったのではっきり「おまけ」と書き直しました。正しい名前を付け、その通りの処理内容にする。/でも自分は分割されまくったコード読むのが苦手。辞書のまた引き状態になって目的を見失う(後半について)そこで「正しい名前」ですよ!「また引き」になるのは、関数・メソッドが出てくる度にその中身を読みに行くからですよね?中身を見に行かなくても名前を見るだけで「それは何?」のキモが解るようにしてあれば、中身を読みに行かなくても大体解るはず。最初に読んでる関数・メソッド自体も、一言で名付けられる位短い物になっていれば、「先(使ってる関数・メソッドの中身)」を読みに行く事無しにその関数・メソッドの全体のキモ・概要を把握出来ます。「先(使ってる関数・メソッドの中身)」を読みに行くことなくその処理のキモ・概要を把握して、それから必要に応じて詳細を確認するために「先」を見に行く、ってのがよいと思ってます。これが出来るようにソースを書いて、こういう読み方になれると、長々じっくりと大量のソースを読み込む必要なく、さっくりと処理のキモ・概要が把握する事も可能になるので、実際のプログラミングでは超絶便利です。*1:こっちから勝手にリクエストしたんだけど^^*2:やってくれなかったらきれい汚い以前の問題*3:遙か昔で忘れてしまったが、ナイコン(年がばれるw)の身で図書館で借りたBASICの本*4:乱暴な言い方だけど*5:紹介しておきながら、自分は積ん読状態...。「構造のきれいなプログラムを書けるようになるためにはどうすればいいのか?」という質問を受けたので、「はて?どうしているだろうか?」と考えてみました。あ、形式知にきちんとなっているようなテクニックみたいなもんじゃなくて、モノローグなので、あまり凝ったものは期待しないように。http://blog.shibu.jp/article/28983162.html自分なりにもっと凝縮版を。渋川さんが言っている事全体もその通りとは思うけど*1、もっと簡単で、しかも射程が広い、と自分が思っている事。渋川さんはちょろっと触れてるだけだけど、自分はこれが最も基本的で汎用的、かつ、ソースをきれいにする原動力となる上にバグをも減らしてコードの汎用性まであげる、コーディングのエンジンみたいなものと思ってる。「すべてに正しい名前を付けて、そして、正しい名前であることを維持する」という鉄の意志クラス、モジュール、メソッド・関数、変数(モジュール、クラス、インスタンス、ローカル、etc)など、プログラムで名前がつくものすべてについて、「正しい名前」を付ける。「正しい名前」であることを維持する。維持するためには、コードの方を変える事も、名前の方をより正しい新たな名前に変える事も、どちらもやる。その言語では名前が付けられないような、ブロック、ループ、数行のコードの固まり、などなどにも、正しい名前を付けたがる姿勢まで含む。パラノイア、などと言ってもいいかも知れない。「正しい名前」とは、それが何なのかを、そのスコープにおいて的確に表現している名前のこと。「それは何?」を一言で言うには、その名前以外に言い様がないような名前。それ自体は、至極シンプルな事。それを徹底的に突き詰める事で、まったく単純ではない様々で莫大な効果得られ、ソースコードも必然的に綺麗になる。なんで「正しい名前」でソースコードが綺麗になるのか。※本エントリに関連して、実践するために気を付ける事や考え方みたいのを書きました。よろしければこちらも。違うもちろんそれはそれで非常に大事な効果。意味がない名前や実態と合ってない名前が溢れてると、ソースを見ても、何をやってるか分からなかったり、勘違いを招く。それがなくなれば、ソースは大幅に読みやすくなる。大きなメリットであり非常に重要ではあるけど、それは効果のほんのごく一部に過ぎない。「正しい名前付け、正しい名前の維持」で生まれるのは、そんな当たり前の事だけではない。「正しい名前」を付けるために、コードの方も直すのだから。「すべてに正しい名前」を求める意志は何を生むか偉そうなことをいっておいて、実は全然整理されてないけど*2、思いつくところを順不同で挙げていく。抜けてる物・説明もたくさんあると思う。それぞれは完全に独立したものではなく、多分にオーバーラップしつつ、全体で上記に挙げたメリットに繋がる。そのコード・その部分がやることが明確になるたとえば、メソッドや関数。適当な名前で「なんとなく」で書き始めてなんとなく動いたら終わり、では、目的不明確でコードがヨレがち。ヨレてフラフラしたコードが綺麗な訳がない。その各部分(ブロックなりループなり、メソッド・関数内の数行の塊なり)に注目しても、単に不完全な断片にしかならない。まじめに的確に名前を付けようとすれば、それが何をする物なのか明確に考えなくちゃならない。明確になったら名前としていったん固定され、実装ではその固定された「やる事」をぶれずに書き下す。実装中に名前の方が実は間違ってる事に気が付いたら、名前の方を直す。書き上がってからも、名前と実態が合ってるか、名前にそぐわぬコードが混ざってないか見直す。これにより、無駄やヨレがなくなり、コードは綺麗になり、バグも減る。実際に言語では名前を与えられなくても、各部分部分について的確な名前を考え、コードもそれに合わせるように直していけば、部分についてもやる事が明確になり、無駄・ヨレの排除でコードが綺麗になり、バグも減る。クラスでもモジュールでも、話は同じ。その変数は何なのかきちんと考えて固定すれば、役割が明確になる。役割が明確なら、役割に合わない値を入れたり、役割と違った目的で値を使う事がなくなる。最初考えた役割の方が間違ってたら、正しい役割を表す名前に直して、その役割と使い方がすべて合ってるか見直す。これも、ヨレを無くし、バグを減らす。「それは何なのか」が一貫する適当な名前で何となく書かれた関数なりクラスなり変数なりは、曖昧がゆえに実装の部分部分や使われる場所によって、それがどういう物なのかが微妙にまたは大きく揺らぎが生じる。これもヨレや無駄、そしてバグの元。的確な名前を付けるようにしていれば、その実装やその使い方がそこから外れる事が無くなる。実装や使い方が、その名の通りに一貫される。スコープが最適になるなんとなく処理に必要な変数を用意して使っていると、それが必要な範囲も漠然としてしまう。変数に最も的確な名前を考え、コード内の部分(ブロックなりループなり、メソッド・関数内の数行の塊なり)についても的確な名前を考えていくと、変数がその名前にふさわしい役割で必要とされている範囲がはっきりしてくる。スコープで適切な名前にしようとすると、自然と本当に必要な範囲だけのスコープの変数になっていく。スコープが最適になると言う事は、それが本来無用の場所でも間違って使われたり、読むときも無用に意識する必要がなくなるという事。ソースは綺麗になり、バグも減る。プログラムが「とにかくそう動く」ではなく、意味レベルで整理・洗練されるそのコードが何なのか一貫した認識がなく、またそのコードで使ってる変数や関数も何なのか曖昧だと、「ああなってこうなって...、えーと...、ウン、よし、動くな」と、動き中心でコードが書かれがち。それでは、何とかそのときは動くコードにはなるが、コードは洗練されない。的確な名前を付けようとすると、動きでなく意味で考えざるを得なくなる。意味で考えて意味通りのコードにしようとすれば、コードはただ動くではなく、洗練の方向が明らかになるし、洗練させずには意味通りのコードにはならない。コードの凝集度が上がるそのコードが何なのか一貫した認識がなく意味レベルで整理されていなければ、「動く」という理由で、関係の深い物も浅い物もバラバラに配置されたままになる。意味で考えられていて、コードを意味に合わせていれば、関係ある物はどうしたって集まってくるし、関係が浅い物は離れていって別メソッド・関数や別クラスなどに分離されていく。これはコードの凝集度が上がる事に他ならない。凝集度があがると、コードは綺麗だし、変更の影響範囲が狭い範囲に閉じるし、コードの依存事項も小さくなり、いい事づくめ。設計が本当に必要な最適解に近付いていく名前と実装の絶え間ない改善のスパイラルは、実装を書いてみて気が付いた大事な事を吸収しながら、ソースコードを明確な意味達の集まりに洗練していく。これはソースコードを、事前設計では得られない正しい設計に導く。それぞれの意味が明確に名前で表されていれば、必要なものとのズレも見付けやすくなる。これによって、そのズレを直すべく設計をさらに洗練させる事になる。複雑さが名前で表現できる範囲に押さえられる名前は名前であり、それなりに簡潔であるべき物。簡潔な名前の意味にあったコードにするには、そもそもダラダラと長く複雑なコードではいられなくなる。長いコードは、その部分(ブロックなりループなり、関数内の数行の塊なり。メソッドの集まりであったり)についてもそれが何であるのか名前を付けようと意味を明確にしようと考える事で、部分の役割が明確になって、関数やクラスに独立させる事になる。コードがシンプルになる正しい名前にするために意味で整理して凝集度が上がることで、メソッド・関数なりクラスなりは小さな単位になっていく。これは、一つ一つの役割は小さくなり、小さな役割について意味を明確にしようとして、その明確な意味通りのコードにしようとすれば、それは自然に短くシンプルになる。コードの矛盾・ズレ・間違いが排除される使ってる物(変数・メソッド・関数・クラス、など)の正しい名前・意味が曖昧なままだと、使ってる物に何か変更が入ったときも、もともとの意味が曖昧なので使い方に矛盾・ズレ・間違いがあっても目立たない。使ってる物の名前・意味が明確ならば、使い方の矛盾・ズレ・間違いは目立つし、かつ名前・意味を明確であり続けさせようとすると、そのような矛盾・ズレ・間違いは排除しない訳に行かない。依存の少ない変更に強いコードになる使ってる物(変数・メソッド・関数・クラス、など)や部分(ブロックなりループなり、関数内の数行の塊なり。メソッドの集まりであったり)の名前・意味が曖昧なまま整理されないと、それらが依存する(影響を受ける)ものも必要以上に増えた状態になる。そのため、別の部分を修正したときも、本来関係ないはずであっても影響を受けてしまいやすくなる。的確な名前を考え意味で整理され無駄が無くなっていると、本当に必要な物だけが使われるので、関係ないものへの依存が無くなり、変更に強くなる。コードの重複が排除されるその部分の名前・意味が曖昧で依存が広く塊も大きいと、似たような事をやるにもちょっとした違いのためにそれを利用できず、また部分的にはほとんどそのまんまでも、部分を切り出す事が困難になり、似たような別のコードを書かなくてはならなくなる。明確な名前と意味で整理されたコードは、自然と凝集度が高く独立性が高い小さな単位(メソッド・関数、クラス、など)の集まりになる。役割が小さく意味が専門化させていくと、そもそも「似た処理」の本当の共通の性質部分そのものの意味になりやすい。「似た処理」が必要と思ったとき、その部分について本当に的確な意味として既に切り出されて専門化されている。汎用な物が抽出され、それを利用する事で、コードがさらに簡潔になる前項とほとんど同じだが、部分部分の小さな単位に的確な名前を付けて独立させると、それは小さく、そして元の場所のしがらみから離れて汎用的な物になる。やって汎用的な物がどんどん独立していき、そしてそれらの塊にも適切な名前を与えるべく専門の塊にしていくことで汎用性を持つライブラリが出来ていく。そのようなライブラリを他からも使用する事で、コードは全体としてさらに簡潔に書きやすくなっていく。大事なのは、常に正しい名前を求める意志であり、場合によっては必ずしもその通りの名前を付けなくてもいいもちろん普通はその名前を付けた方がいい。いいけど、上記のメリットは正しい名前を求める意志・思考自体によるところが大きい。言語でのお約束として、たとえば自明なレベルではループカウンタはi,j,k,...を使うなら実際のコードではそれを使ってもいいし、とにかく簡潔な名前を使う慣習の言語では、名前に若干の説明不足が生じても仕方ない*3。その先ちゃんと名前を付けるってのは、基本と言えば余りに基本的な事。それは、その一言ではとても尽くせない、広大な汎用性を持つ超重要事項。それに本気になればなるほど、それに見合ったご利益が得られる、と思う。このエントリでは、一般的な「きれいな」ソースコードのための超絶最重要事項として正しい名前を挙げた。これは自分なんかが言わなくても、実践できてる人も多いかも知れない。いう人は、もう一段上の「美しい」コードについて、ハッカー達の濃い思いをじっくり味わうのもよいかも知れない。「美しい」から「かっこいい」へ)。詳細を見る追記コメントやブクマを見て、説明不足だったかもと思ったところを補足。重要なのは、しゃれた名前とか、縁起のいい名前とか、知的な名前とか、詩的な名前とか、かわいい名前とか、そういうのじゃない。「それは何?」という事を考え抜き、コードに対して「そのまんま」な名前を考える。名前そのまんまのようにコードを書き、そのまんまになるように直す。センスがある人は(邪魔にならない限り)そう言う要素を混ぜてもいいかも知れないけど、必要なのはもっと愚直に考える事。「それはどんなかんじ?」じゃなくて、「それは何?」たとえば、手に負えないようなぐちゃぐちゃなソースに対して、"spagetti"なんて名前を付けても、まあ確かにある意味そのまんまかも知れないけど正しい名前じゃない。「中身がどう」じゃなくて「中身が何か」を表さなくては意味がない。「こんなのに正しい名前なんて付けられない」じゃなくて、正しい名前を付けられるように整理して分割する俗に「ひとつの関数・メソッドは、25行(1画面)以内」というプログラミングの格言がある。自分がプログラミングをまともに始めた頃は、そんな無茶なという認識だった。その頃は、それに一言で正しい名前なんて付けられないようなのを書いてた。正しい名前を付ける事を強く意識して内容を一言で言える単位毎にまとめて整理・分割していくと、いつのまにか25行以上なんて関数・メソッドはごく例外的なものだけという状況になっていた。曖昧にダラダラ書いた関数・メソッドでは、正しい名前は付けられない。そこをうんうんムリに考えるんじゃなくて、関数・メソッドの方を整理して分ける。これは、クラスやモジュールでも同じ。いつの間にデカくなってきたクラスやモジュールは、「正しい名前であること」を維持できているか見直して、整理して分割して、正しい名前を付け直す。大事なのはソースをよくする事であって、名前はそのきっかけや手掛かりに過ぎないとも言える。目的はソースを見やすくするだけじゃなくて、構造や設計を含めてきれいにする事だし。役割がシンプルで単一なものになるように整理分割されてないと、一言でそのまんま言い表せる名前は付けられない。中身を一言でうまく言えないソースは、部分部分にそれぞれ正しい名前を付けられるように整理して正しい名前で分割する。これをしなければ、名前だけそこそこの物を考えても、「名前でソースをきれいに」としては全くの片手落ちで、前のエントリで書いた効果の多くが得られない。「正しい名前」の実現は、ソースを書き換えてきれいにしていくための方法であり、考え方。漠然とソースを書き換えてもきれいになるとは限らない。その羅針盤が、「正しい名前」と「名前通りのソース」を追い求める事。正しい名前を考え、ソースもきちんと直すには、確かに時間はかかる。目の前の10行だか100行だかを、ただとりあえず動くだけのレベルで書くのに比べたら、余計に時間はかかる。その程度の規模で、動いたらもうメンテせずに棄ててしまうプログラムならば、こだわってもご利益に見合わないかもしれない。プログラミングは、目の前の10行だか100行だかを書いて終わりじゃない。実際は、全体としてもっともっと沢山のコードを書き、そしてその後それをメンテナンスしなくてはいけない。そこで、「きれいなソース」のために投じた時間に、莫大な利子が付いて返ってくる。整理されてなくて、何やってるかも不明確で、ちょっと条件が変わると動かなくなるようなソースが一部分でもあると、それに関係するプログラムを書くときに何かにつけてあれやこれやの無駄な時間を食われる。それが一部分じゃなくて、大部分のソースがそんな状態だと、もはや手枷足枷目隠しをされてプログラミングするのと同じだ*5。それが何かを徹底的に考え、徹底的にプログラムを整理する事は、こだわった分だけ、ほかならぬそのプログラマ自身のスキルを着実に向上させる。プログラムをただ書くのではなく、ちゃんと考えて整理するために仮にいまは10の時間が掛かっても、それをやった分、次は9の時間でそれができ、その次は8の時間で、というふうに、投資に必要な時間がどんどん短くなっていく。投資額は小さくなっていくが、リターンは減るどころかどんどん大きくなる。こんなにおいしい投資をしないのは、あなたの時間とあなたの誇りを自ら捨ててる事と同義ではないか。-疑問・質問歓迎-疑問・質問・その他、歓迎。わかりません」の末尾で(求められてなくても勝手に)回答。*1:ただし、引きこもりがきれいなコードをかけないということは全くないと私は思います*2:なので、後で断り無く記事をブラッシュアップするかも。→実際ちょっと加筆修正してます(おもに、すこし「部分」を強調。あと設計にも触れた)。*3:そういう場合は、長い「正しい名前」はコメントとして付しておくといいかも*4:あと関数型言語とかだと、抽出したものが余りに抽象的でまんまの意味となる的確な名前を簡潔になんて付けようもなかったりするけど、そういう言語ではそう言う物は既に標準ライブラリに大抵入ってるし、大事なのはそう言う思考って事で*5:恐ろしいのは、大なり小なり、プログラミングとはそう言うものだという認識がまかり通っている組織が往々にして存在する事。わかりませんというエントリで、Yコンビネータ(不動点演算子)と再帰の絵解き解説をしました。Yコンビネータ自身は、結局のところ再帰を産み出してくれるだけです。関数(正確にはλという単純な文字列変換ルール)だけで出来て、プログラミングに関するいろんな原理の研究を可能にするのが凄い訳です。その辺のさわりを、きしださんが解説されています。単なる再帰なら、実際のプログラミングではYコンビネータなんて使わなくても出来ます。Yコンビネータとか不動点とかは、偉い学者さんとかが研究に使えばいいもので、普通のプログラマには何の意味もないモノなのでしょうか?というわけで、今回はポジティブに、Yコンビネータや不動点で出てくる考え方を、理論だけじゃなく、実際のプログラミングに応用する例を見てみましょう。今回、プログラムの例をJavaScriptで書きます。Yコンビネータ(不動点演算子)は、amachang先生の定義を拝借します*1。無駄な再帰を省略して爆速化まず、Yコンビネータや不動点の考え方の応用は、「メモ化(memoization)」という汎用関数です。きしださんも例に用いていた、フィボナッチ数という数を計算する関数を考えます。Yコンビネータとか関係なく、普通に再帰するバージョンです。}}fib(1)は1、fib(2)は2、fib(3)は3、fib(4)は5、fib(5)は8、fib(6)は13が返ってきます。これでちゃんと計算できるし、なにより、フィボナッチ数の定義通りのコードでとても解りやすいです。実はものすごい無駄があります。fib(x-2);ここです。xが5のときは、fib(4)とfib(3)の両方を計算します。その前者fib(4)の計算では、同様にfib(3)とfib(2)の両方を計算します。fib(3)は、fib(5)の計算でもfib(4)の計算でも、どちらでも計算されます。答えは同じなのに2回計算されます。同じ感じに、fib(2)は3回、fib(1)は5回計算されます。xが大きくなるにつれて、この無駄は凄い事になります。自分のポンコツマシンとOpera9.64でfib(30)を実行してみたところ、結果は1346269で、1秒188msec掛かりました。fib(35)だと結果は14930352で、なんと14秒547も掛かりました。