きれいなソースコードを書くために必要な、たったひとつの単純な事
「構造のきれいなプログラムを書けるようになるためにはどうすればいいのか?」という質問を受けたので、「はて?どうしているだろうか?」と考えてみました。あ、形式知にきちんとなっているようなテクニックみたいなもんじゃなくて、モノローグなので、あまり凝ったものは期待しないように。
http://blog.shibu.jp/article/28983162.html
自分なりにもっと凝縮版を。渋川さんが言っている事全体もその通りとは思うけど*1、もっと簡単で、しかも射程が広い、と自分が思っている事。
渋川さんはちょろっと触れてるだけだけど、自分はこれが最も基本的で汎用的、かつ、ソースをきれいにする原動力となる上にバグをも減らしてコードの汎用性まであげる、コーディングのエンジンみたいなものと思ってる。それは、
「すべてに正しい名前を付けて、そして、正しい名前であることを維持する」という鉄の意志
クラス、モジュール、メソッド・関数、変数(モジュール、クラス、インスタンス、ローカル、etc)など、プログラムで名前がつくものすべてについて、「正しい名前」を付ける。そして、「正しい名前」であることを維持する。維持するためには、コードの方を変える事も、名前の方をより正しい新たな名前に変える事も、どちらもやる。さらに、その言語では名前が付けられないような、ブロック、ループ、数行のコードの固まり、などなどにも、正しい名前を付けたがる姿勢まで含む。意志なんてヌルいものではなく、名前原理主義、名前パラノイア、などと言ってもいいかも知れない。
「正しい名前」とは、それが何なのかを、そのスコープにおいて的確に表現している名前のこと。「それは何?」を一言で言うには、その名前以外に言い様がないような名前。
それ自体は、至極シンプルな事。でも、それを徹底的に突き詰める事で、まったく単純ではない様々で莫大な効果得られ、ソースコードも必然的に綺麗になる。では、なんで「正しい名前」でソースコードが綺麗になるのか。
※本エントリに関連して、実践するために気を付ける事や考え方みたいのを書きました。よろしければこちらも。
「そのソースコードが汚い理由:共通した根源的な間違い」
「名前が正しければ、そりゃソースは読みやすくなる」 は違う
もちろんそれはそれで非常に大事な効果。意味がない名前や実態と合ってない名前が溢れてると、ソースを見ても、何をやってるか分からなかったり、勘違いを招く。それがなくなれば、ソースは大幅に読みやすくなる。大きなメリットであり非常に重要ではあるけど、それは効果のほんのごく一部に過ぎない。「正しい名前付け、正しい名前の維持」で生まれるのは、そんな当たり前の事だけではない。「正しい名前」を付けるために、コードの方も直すのだから。
「すべてに正しい名前」を求める意志は何を生むか
偉そうなことをいっておいて、実は全然整理されてないけど*2、思いつくところを順不同で挙げていく。抜けてる物・説明もたくさんあると思う。それぞれは完全に独立したものではなく、多分にオーバーラップしつつ、全体で上記に挙げたメリットに繋がる。
そのコード・その部分がやることが明確になる
たとえば、メソッドや関数。適当な名前で「なんとなく」で書き始めてなんとなく動いたら終わり、では、目的不明確でコードがヨレがち。ヨレてフラフラしたコードが綺麗な訳がない。その各部分(ブロックなりループなり、メソッド・関数内の数行の塊なり)に注目しても、単に不完全な断片にしかならない。
しかし、まじめに的確に名前を付けようとすれば、それが何をする物なのか明確に考えなくちゃならない。そして、明確になったら名前としていったん固定され、実装ではその固定された「やる事」をぶれずに書き下す。もちろん、実装中に名前の方が実は間違ってる事に気が付いたら、名前の方を直す。書き上がってからも、名前と実態が合ってるか、名前にそぐわぬコードが混ざってないか見直す。これにより、無駄やヨレがなくなり、コードは綺麗になり、バグも減る。
部分についても同様。実際に言語では名前を与えられなくても、各部分部分について的確な名前を考え、コードもそれに合わせるように直していけば、部分についてもやる事が明確になり、無駄・ヨレの排除でコードが綺麗になり、バグも減る。
クラスでもモジュールでも、話は同じ。
変数であっても同じ。その変数は何なのかきちんと考えて固定すれば、役割が明確になる。役割が明確なら、役割に合わない値を入れたり、役割と違った目的で値を使う事がなくなる。というか、なくす。もちろん、最初考えた役割の方が間違ってたら、正しい役割を表す名前に直して、その役割と使い方がすべて合ってるか見直す。これも、ヨレを無くし、バグを減らす。
「それは何なのか」が一貫する
適当な名前で何となく書かれた関数なりクラスなり変数なりは、曖昧がゆえに実装の部分部分や使われる場所によって、それがどういう物なのかが微妙にまたは大きく揺らぎが生じる。これもヨレや無駄、そしてバグの元。
一方、かならず的確な名前を付けるようにしていれば、その実装やその使い方がそこから外れる事が無くなる。実装や使い方が、その名の通りに一貫される。使い方を間違えない。
スコープが最適になる
なんとなく処理に必要な変数を用意して使っていると、それが必要な範囲も漠然としてしまう。
変数に最も的確な名前を考え、コード内の部分(ブロックなりループなり、メソッド・関数内の数行の塊なり)についても的確な名前を考えていくと、変数がその名前にふさわしい役割で必要とされている範囲がはっきりしてくる。そして、スコープで適切な名前にしようとすると、自然と本当に必要な範囲だけのスコープの変数になっていく。
スコープが最適になると言う事は、それが本来無用の場所でも間違って使われたり、読むときも無用に意識する必要がなくなるという事。ソースは綺麗になり、バグも減る。
プログラムが「とにかくそう動く」ではなく、意味レベルで整理・洗練される
そのコードが何なのか一貫した認識がなく、またそのコードで使ってる変数や関数も何なのか曖昧だと、「ああなってこうなって…、えーと…、ウン、よし、動くな」と、動き中心でコードが書かれがち。それでは、何とかそのときは動くコードにはなるが、コードは洗練されない。
きちんと的確な名前を付けようとすると、動きでなく意味で考えざるを得なくなる。意味で考えて意味通りのコードにしようとすれば、コードはただ動くではなく、洗練の方向が明らかになるし、洗練させずには意味通りのコードにはならない。
コードの凝集度が上がる
そのコードが何なのか一貫した認識がなく意味レベルで整理されていなければ、「動く」という理由で、関係の深い物も浅い物もバラバラに配置されたままになる。
意味で考えられていて、コードを意味に合わせていれば、関係ある物はどうしたって集まってくるし、関係が浅い物は離れていって別メソッド・関数や別クラスなどに分離されていく。これはコードの凝集度が上がる事に他ならない。凝集度があがると、コードは綺麗だし、変更の影響範囲が狭い範囲に閉じるし、コードの依存事項も小さくなり、いい事づくめ。
設計が本当に必要な最適解に近付いていく
名前と実装の絶え間ない改善のスパイラルは、実装を書いてみて気が付いた大事な事を吸収しながら、ソースコードを明確な意味達の集まりに洗練していく。これはソースコードを、事前設計では得られない正しい設計に導く。
さらに、それぞれの意味が明確に名前で表されていれば、必要なものとのズレも見付けやすくなる。これによって、そのズレを直すべく設計をさらに洗練させる事になる。
複雑さが名前で表現できる範囲に押さえられる
名前は名前であり、それなりに簡潔であるべき物。簡潔な名前の意味にあったコードにするには、そもそもダラダラと長く複雑なコードではいられなくなる。長いコードは、その部分(ブロックなりループなり、関数内の数行の塊なり。あるいはメソッドの集まりであったり)についてもそれが何であるのか名前を付けようと意味を明確にしようと考える事で、部分の役割が明確になって、関数やクラスに独立させる事になる。
コードがシンプルになる
正しい名前にするために意味で整理して凝集度が上がることで、メソッド・関数なりクラスなりは小さな単位になっていく。これは、一つ一つの役割は小さくなり、小さな役割について意味を明確にしようとして、その明確な意味通りのコードにしようとすれば、それは自然に短くシンプルになる。
コードの矛盾・ズレ・間違いが排除される
使ってる物(変数・メソッド・関数・クラス、など)の正しい名前・意味が曖昧なままだと、使ってる物に何か変更が入ったときも、もともとの意味が曖昧なので使い方に矛盾・ズレ・間違いがあっても目立たない。
使ってる物の名前・意味が明確ならば、使い方の矛盾・ズレ・間違いは目立つし、かつ名前・意味を明確であり続けさせようとすると、そのような矛盾・ズレ・間違いは排除しない訳に行かない。
依存の少ない変更に強いコードになる
使ってる物(変数・メソッド・関数・クラス、など)や部分(ブロックなりループなり、関数内の数行の塊なり。あるいはメソッドの集まりであったり)の名前・意味が曖昧なまま整理されないと、それらが依存する(影響を受ける)ものも必要以上に増えた状態になる。そのため、別の部分を修正したときも、本来関係ないはずであっても影響を受けてしまいやすくなる。
的確な名前を考え意味で整理され無駄が無くなっていると、本当に必要な物だけが使われるので、関係ないものへの依存が無くなり、変更に強くなる。
コードの重複が排除される
その部分の名前・意味が曖昧で依存が広く塊も大きいと、似たような事をやるにもちょっとした違いのためにそれを利用できず、また部分的にはほとんどそのまんまでも、部分を切り出す事が困難になり、似たような別のコードを書かなくてはならなくなる。
明確な名前と意味で整理されたコードは、自然と凝集度が高く独立性が高い小さな単位(メソッド・関数、クラス、など)の集まりになる。役割が小さく意味が専門化させていくと、そもそも「似た処理」の本当の共通の性質部分そのものの意味になりやすい。「似た処理」が必要と思ったとき、その部分について本当に的確な意味として既に切り出されて専門化されている。
汎用な物が抽出され、それを利用する事で、コードがさらに簡潔になる
前項とほとんど同じだが、部分部分の小さな単位に的確な名前を付けて独立させると、それは小さく、そして元の場所のしがらみから離れて汎用的な物になる。そうやって汎用的な物がどんどん独立していき、そしてそれらの塊にも適切な名前を与えるべく専門の塊にしていくことで汎用性を持つライブラリが出来ていく。
そのようなライブラリを他からも使用する事で、コードは全体としてさらに簡潔に書きやすくなっていく。
一番大事なのは、常に正しい名前を求める意志であり、場合によっては必ずしもその通りの名前を付けなくてもいい
もちろん普通はその名前を付けた方がいい。いいけど、上記のメリットは正しい名前を求める意志・思考自体によるところが大きい。だから、言語でのお約束として、たとえば自明なレベルではループカウンタはi,j,k,...を使うなら実際のコードではそれを使ってもいいし、とにかく簡潔な名前を使う慣習の言語では、名前に若干の説明不足が生じても仕方ない*3。*4
名前重要、超重要。そしてその先
ちゃんと名前を付けるってのは、基本と言えば余りに基本的な事。でもそれは、その一言ではとても尽くせない、広大な汎用性を持つ超重要事項。それに本気になればなるほど、それに見合ったご利益が得られる、と思う。
このエントリでは、一般的な「きれいな」ソースコードのための超絶最重要事項として正しい名前を挙げた。これは自分なんかが言わなくても、実践できてる人も多いかも知れない。そういう人は、もう一段上の「美しい」コードについて、ハッカー達の濃い思いをじっくり味わうのもよいかも知れない。「きれいな」と「美しい」の違いとかは、danさんが書いてくれる事を期待(→ なんとホントにdanさんがエントリ書いてくれました! 404 Blog Not Found:いい仕事をするためのたった一つの心得 - 「美しい」から「かっこいい」へ)。
追記
コメントやブクマを見て、説明不足だったかもと思ったところを補足。
きれいな名前じゃなくて、「正しい」名前
重要なのは、しゃれた名前とか、縁起のいい名前とか、知的な名前とか、詩的な名前とか、かわいい名前とか、そういうのじゃない。ただ単に「それは何?」という事を考え抜き、コードに対して「そのまんま」な名前を考える。そして、名前そのまんまのようにコードを書き、そのまんまになるように直す。それだけ。センスがある人は(邪魔にならない限り)そう言う要素を混ぜてもいいかも知れないけど、必要なのはもっと愚直に考える事。
「それはどんなかんじ?」じゃなくて、「それは何?」
たとえば、手に負えないようなぐちゃぐちゃなソースに対して、"spagetti"なんて名前を付けても、まあ確かにある意味そのまんまかも知れないけど正しい名前じゃない。「中身がどう」じゃなくて「中身が何か」を表さなくては意味がない。
「こんなのに正しい名前なんて付けられない」じゃなくて、正しい名前を付けられるように整理して分割する
俗に「ひとつの関数・メソッドは、25行(1画面)以内」というプログラミングの格言がある。自分がプログラミングをまともに始めた頃は、そんな無茶なという認識だった。その頃は、それに一言で正しい名前なんて付けられないようなのを書いてた。でも、正しい名前を付ける事を強く意識して内容を一言で言える単位毎にまとめて整理・分割していくと、いつのまにか25行以上なんて関数・メソッドはごく例外的なものだけという状況になっていた。曖昧にダラダラ書いた関数・メソッドでは、正しい名前は付けられない。そこをうんうんムリに考えるんじゃなくて、関数・メソッドの方を整理して分ける。これは、クラスやモジュールでも同じ。いつの間にデカくなってきたクラスやモジュールは、「正しい名前であること」を維持できているか見直して、整理して分割して、正しい名前を付け直す。
名前そのものじゃなくて、正しい名前を付けられるソースにする事
大事なのはソースをよくする事であって、名前はそのきっかけや手掛かりに過ぎないとも言える。目的はソースを見やすくするだけじゃなくて、構造や設計を含めてきれいにする事だし。
役割がシンプルで単一なものになるように整理分割されてないと、一言でそのまんま言い表せる名前は付けられない。中身を一言でうまく言えないソースは、部分部分にそれぞれ正しい名前を付けられるように整理して正しい名前で分割する。これをしなければ、名前だけそこそこの物を考えても、「名前でソースをきれいに」としては全くの片手落ちで、前のエントリで書いた効果の多くが得られない。
「正しい名前」の実現は、ソースを書き換えてきれいにしていくための方法であり、考え方。漠然とソースを書き換えてもきれいになるとは限らない。だから羅針盤が必要。その羅針盤が、「正しい名前」と「名前通りのソース」を追い求める事。
「きれいなソース」は、リターンの大きい、時間の投資
正しい名前を考え、ソースもきちんと直すには、確かに時間はかかる。目の前の10行だか100行だかを、ただとりあえず動くだけのレベルで書くのに比べたら、余計に時間はかかる。その程度の規模で、動いたらもうメンテせずに棄ててしまうプログラムならば、こだわってもご利益に見合わないかもしれない。
しかし、たいていのプログラミングは、目の前の10行だか100行だかを書いて終わりじゃない。実際は、全体としてもっともっと沢山のコードを書き、そしてその後それをメンテナンスしなくてはいけない。そこで、「きれいなソース」のために投じた時間に、莫大な利子が付いて返ってくる。整理されてなくて、何やってるかも不明確で、ちょっと条件が変わると動かなくなるようなソースが一部分でもあると、それに関係するプログラムを書くときに何かにつけてあれやこれやの無駄な時間を食われる。それが一部分じゃなくて、大部分のソースがそんな状態だと、もはや手枷足枷目隠しをされてプログラミングするのと同じだ*5。
そしてもうひとつ。きちんとそれが何かを徹底的に考え、徹底的にプログラムを整理する事は、こだわった分だけ、ほかならぬそのプログラマ自身のスキルを着実に向上させる。プログラムをただ書くのではなく、ちゃんと考えて整理するために仮にいまは10の時間が掛かっても、それをやった分、次は9の時間でそれができ、その次は8の時間で、というふうに、投資に必要な時間がどんどん短くなっていく。投資額は小さくなっていくが、リターンは減るどころかどんどん大きくなる。こんなにおいしい投資をしないのは、あなたの時間とあなたの誇りを自ら捨ててる事と同義ではないか。
-疑問・質問歓迎-
疑問・質問・その他、歓迎。米欄のほか、続きのエントリ「そのソースコードが汚い理由:共通した根源的な間違い - よくわかりません」の末尾で(求められてなくても勝手に)回答。
*1:ただし、引きこもりがきれいなコードをかけないということは全くないと私は思います
*2:なので、後で断り無く記事をブラッシュアップするかも。→実際ちょっと加筆修正してます(おもに、すこし「部分」を強調。あと設計にも触れた)。
*3:そういう場合は、長い「正しい名前」はコメントとして付しておくといいかも
*4:あと関数型言語とかだと、抽出したものが余りに抽象的でまんまの意味となる的確な名前を簡潔になんて付けようもなかったりするけど、そういう言語ではそう言う物は既に標準ライブラリに大抵入ってるし、大事なのはそう言う思考って事で
*5:恐ろしいのは、大なり小なり、プログラミングとはそう言うものだという認識がまかり通っている組織が往々にして存在する事。