変更に強いコードとうまい設計を目指す覚え書き

えーと要するに「どうにもならないプログラム」を作らないための覚え書きですが、そのための方法として

  • 変更に強いコード
  • うまい設計(変更に強いだけでなく、全体的にニュアンスとして)

を目指したらいいんじゃないかと思い、そのためのネタをとりあえず吐き出してみました。基本、雰囲気です。

勘違いや、嘘ではないが本当でもない話、逆に有害な話をまき散らしている可能性もあります。そういう場合はできれば分かりやすくツッコんでください。「これはひどい」タグだけつけられても何が悪いのか分からない可能性大です。

逆にこの記事は毒にも薬にもならない可能性も高いです。

途中で文体が大きく変わりますが気にしないでください。

前提

  • 言語そのもの、アルゴリズム、構造化、OO の基礎知識

は、当然持ってるものとします。反対に

  • 「匠」系のスキル

は要求しません。というか組み込みとか知らないし。

アプリケーションの分野による違いはあまり関係ないような気がしていますし、DBMS のチューニングとかそんな話はここではまったく無視してます。ところどころ Java っぽい OO 用語が出てきますが、他の言語の場合は似た概念を適当に補完して読んでください。

目指す姿

そもそも変更に強いコードやうまい設計がどういうものか、慣れないうちは分からないかもしれない。短くしろとは言われるが、そんな短くできっかよと思ってる人はまず以下を読め。1

短く読みやすく変更に強いコードとは

  • 短いブロック
    • 短いブロックは複雑な機能を持てないので名前付けも容易
    • 機能が単純であればテストも書きやすい
    • 全体の長さが同じでも中のブロックが短ければ当然メソッドの数は多くなる。これはコード全体の「関節の多さ」に繋がる、つまり柔軟2
  • 読みやすさはあいまいさの少なさ、登場する語彙の少なさ
  • クラス名とメソッド名だけで何が起きるか容易に想像できる

などを概ね満たしていると思われる。

補足

まずほとんどの場合、長いブロックは単に物理的に長いだけでなく冗長である。ほとんどのプログラマの書くコードは実は誰でも書けるコードであり、それがものすごく長いブロックを持つというのは、たいてい間違っており、無駄に長い。短さはほとんどの場合正しい。自分のコードだけ特殊だなどと思っちゃダメ。

また長いブロックを書くプログラマは多くの場合、メソッドに適切な名前をつけられない。なぜならブロックが長いということはその中に詰め込まれている機能が多くなっているわけだから、仕事が複雑になっており、メソッド名という一言で表現できなくなってしまっているから。3

メソッドが短くなると数が増え、一見読みやすさと矛盾するように見えるが、逆にメソッドになっていない if 文などは意図を解釈するのに時間が掛かる。ほとんどのコードは代入か分岐かメソッド呼び出しかループであり、名前がついていないとどういう動作なのか一瞬で把握することができない。だからメソッドにして明確な名前をつけることにはもぅのすごく意味がある。数が増えることを恐れてはならない。

だいたい、メソッドになってないとその if 文が果たして本当に正しく動作しているのか、ユニットテストで検証しにくいじゃないか。テストのないコードは脆弱なのだ。

数が増えると当然内部からしか呼び出す必要のないメソッドが増えてくる。こいつらをみんな private にすればよい。public か private か悩んじゃうとかみんな public になっちゃう場合はたいてい何か間違っている。

うまい設計

コードとの対比を強調するために、中身を追いかけなくても分かる部分に限定して抜き出してみる。というわけで「クラスが閉じているのは絶対条件」とします。

  • インターフェイスが明確であり、そこ以外気にする必要がない
  • コードと設定が分離していて、ある程度の変更はコードを直さなくても可能
  • 設定の反映が楽
  • 設定をしなくてもある程度思った通りに動く
  • クラスを組み合わせて利用しやすい
    • 依存性はできるだけ外から入れろ
  • DIコンテナを利用して組み合わせていることすら意識せずに使える

プラクティス

では具体的にどうやって目指すのかと言うと、

図を描く

  • 図ではある程度までしか通用しないが、全体を俯瞰するために有効
  • クラスの独立性、DIの様子などは図でチェックしやすい
  • UML とか意識しなくていい
    • UML を必要とするのは UML を理解しているスーツや SI と話さなきゃいけないときだけ。自分で書くなら要らない。

変更のテクニックを学ぶ

  • リファクタリング
  • wrapper

インターフェイスを意識する。

JavaScript や Ruby のように動的に中身を入れ替えちゃう方法は素人にはオススメできない。

コードの読み書き

  • 理屈も大事だが読み書きの絶対量が不足していてはうまくならない
  • いいコードを読み込むか、あるいは反面教師となるまずいコードの修正を試みることでよりよい形を思い描ける
    • 直接自分が使わなくても、伝統的に有名な、入門者にオススメの C のコードは何本かあるので、雰囲気だけでも読んでみるとよい。Cに不慣れでも意外と「意味」は分かる。というか分かると信じて読め。

Unix系CLIを使ってみる

余力があればでいいが、Unix系CLI によるテキスト処理は勉強になる。ただしテキスト処理に興味がないならオススメしない。

Unixの伝統的なツールは基本的に単純なことしかできないので、例えばいきなり Perl や Ruby などの「大きなツール」で書かずに、大昔からあるツールを組み合わせて仕事をするようにしてみると、小さい道具を組み合わせて大きな仕事をするトレーニングになる。

逆に言うと大きな仕事を小さな仕事に分解するトレーニングにもなる。

よくある間違い

  • なんかメソッドが set_* とか get_* だらけになる
    • メンバ変数を直接取得しなきゃいけないようなケースなんてそんなにない、っていうかあんまりなんでもかんでもメンバ変数にしなくていい。むしろメンバ変数は最小限でいいじゃん。
  • テストがうまく書けない
    • そのメソッドの機能が複雑です。単純な値を受け取って単純な値を返すメソッドにガシガシ分割しましょう。というか、「関数」ってなんだか知ってる? 単にコードブロックに名前がついたものじゃないんだよ。写像なんだよ。
  • テストを書くのが邪魔くさい
    • テストに使う値をテストコードの中にそのまま書いちゃダメです。テストコードと言えどコードはコード。マジックナンバー入れちゃダメってお母さん教えたでしょ。入力と期待される assert の結果を全部 YAML で書いてループでぐるぐる回してエラーが出たらそこの値をどべっと吐き出すコードを書いておきましょう。あとはテストの追加の大半は YAML への追記でまかなえます。ほとんどの assert は equal かどうかのテストになるはずです。
  • どうやってもコードが短くならない
    • データ構造が間違ってます。
  1. 実際適切な短さは言語によって変わってくると思うので、具体的な数字は現場によりけり。 

  2. 可動域の話は置いておく 

  3. Rails のようにあえて長いメソッド名を採用する方法もあると思うけど、あれは中のコードが長過ぎてしゃあなしで長くなってるんじゃないですぞ。 

More