くたばれカーゴ・カルト・プログラミング
何?
Wikipedia によると。
実際の目的には役に立たないコードやプログラム構造を儀式的に含めておくプログラミングのスタイルである。カーゴ・カルト・プログラミングは主に、プログラマが解決しようとしているバグか解決策のいずれかかまたは両方を理解していない場合に見受けられる。 他にも、スキルの低い、ないし新人プログラマ(または当面の問題を経験したことのないプログラマ)が、そのコードが何をしているか理解が足りないまま、またはもしかしたら新しい場所にも必要なのではないかと、別の場所から関係ない部分も含めてコードをコピーしてしまうことで発生する可能性がある。
ひいてはコピペ駆動開発
どうして
不要なコードは、ロジック的に余計な処理であるがゆえにバグを生み出す温床(しかも実装者がわかってない事が多く、余計にバグりやすい)であったり、本来行うべき処理を読み取る中でノイズ(雑音)となって、レビュー時間、もしくは保守時の工数を浪費してしまう。
性能(無駄な処理)、安全性(余計な処理によるバグ発生)、保守性(可読性の低下)全てに悪影響をもたらす忌むべきもの。
どうする?
大抵は書いてあることの意味を理解していない為に起こる。
これには大きく分けて二つのパターン、もしくは両方の場合がある。
- コピペ元がそもそも煩雑で、追いかけ切れない
- ボーイスカウトの原則を守られず、コードが秘伝のタレになっている
- 色々検証してて、動いたからいいやとリファクタをサボった
- 可読性に無頓着でともかく手続きを並べまくってそのまま
- 実装者が元のコードを読む気がない
- やる気が無い
- 読んでる時間的余裕がない
時間的余裕がない場合を除き、コミット前に書いた部分を最初から見直すのが一番。
また、修正の場合でも関連箇所はもっと共通化分けできないかとか考えるのが良い(DRY原則、ボーイスカウトの原則)。
「1行1行注視して読める」では足りない。
メソッド化と分離を繰り返すと、次第にメソッドの流れは単純な複数のメソッドコールになっていく。
結果として、「流し読みできる」レベルで抽象化しているのが望ましい。
蛇足
因みにレビュアーの観点でこれをやるプログラマのもっともあるある Top3 は
- 可読性に無頓着でともかく手続きを並べまくってそのまま
可読性の重要性を理解していない。
知らないだけのパターンが多く、丁寧に説明すれば大体わかってくれる。
それでダメなら、スパゲティなコードの保守プロジェクトに突っ込んで、次に原理主義的に綺麗なコード(オープンソース)のカスタムを経験させるに限る。 - コードが秘伝のタレ
特にプロマネがせっつくだけで品質に興味がない場合で発生しやすい。
この場合、現場が整理したいって言っても大抵工数を理由に拒否されるか、「動いてるのに手を加えるなんてありえない」と拒否される。
この場合は、バグ数を理由に、レビューの実施(にかこつけてリファクタしてしまう)。 - やる気が無い
このタイプは大体2タイプ。どっちも「こんなに戻って仕事進まないなら覚えた方がマシ」と思わせたら勝ち。- もらう金一緒なのに苦労とかしたくない派
→ 超絶レビューバックで矯正。 - そんなのしなくてもやってこれた派
→ 汚いコードに劣等感持つまで、リファクタのやり方を指摘。
- もらう金一緒なのに苦労とかしたくない派
1 クラス 1 機能、1 メソッド 1 処理の徹底
どういう事?
名前の通り、クラス設計において、1つの機能は1クラスとして用意します。
また、1機能の実現もまた手続きがありますので、それら手続きも基本的にクラス、メソッドとします。
さらに、手続きの中でもループして書き換えるとか、なんらかの値に変更するなどの「その操作そのものは機能と言えないが、しかし一つの手順ではある」というコードをメソッド化します。
要するにあらゆる手続きは全てメソッド化し、分離可能なら処理毎にクラス化し、クラス単位の処理内容を徹底して単一化します。
場合によってはクラス内にメソッドは 1 つ、行数が数行という場合も発生しますが、それでもやります。
なぜ?
病的なまでに処理を分離するということは、クラス/メソッドの役割がそれだけシンプルになる。
これにクラス名、メソッド名を適切に設定すれば、修正が必要な際にも対象のクラスを即座に発見することができるようになります。
また、処理が十分に分離されているということは、処理は十分に単純であるということ。
単純な処理ほど 再利用が行いやすくなります 。
KISS原則 も必然的に守れますね。
どうする?
これはコーディング時にメソッド化すべき物は何かを常に考えます。
例えば、ログイン処理なんかはこんな感じでしょう。
コード自体はサンプルコードです。コピペしても多分コンパイルできないかと
最初に、メソッドと処理内容をざっくり考えます。
public HTTPResponse getProfile(final HTTPRequest request) { // リクエストからログインアカウントID、ひいては権限を取得します。 // リクエストから取得するプロフィールの ID を取得します。 // 取得した ID をキーに、対象のアカウントを DB から取得します。 // アカウントが今のユーザ/権限でアクセスできるかどうかを判定します。 // アクセスできる場合は表示用の画面を返します。 // アクセスができなければエラー画面を返します。 }
では実装を開始してみましょう。
@Autowierd private AuthorityRepository authorityRepository; public HTTPResponse getProfile(final HTTPRequest request, final HTTPSession session) { // リクエストからログインアカウントID、ひいては権限を取得します。 Optional<Long> loginAccountId = session.get(Constants.SESSION_ACCOUNT_ID).map(Long::parseLong); Optional<Authority> authority = loginAccountId.flatMap(id -> authorityRepository.getOneByAccountId(id)); // リクエストから取得するプロフィールの ID を取得します。 // 取得した ID をキーに、対象のアカウントを DB から取得します。 // アカウントが今のユーザ/権限でアクセスできるかどうかを判定します。 // アクセスできる場合は表示用の画面を返します。 // アクセスができなければエラー画面を返します。 }
でもこれは要するに手続きで、目的はセッションをキーに権限情報を取得する事にありますから、メソッド化した方が余計な変数が増えずに済みます。
public HTTPResponse getProfile(final HTTPRequest request, final HTTPSession session) { // リクエストからログインアカウントID、ひいては権限を取得します。 Optional<Authority> authority = this.getLoginAuthority(session); // リクエストから取得するプロフィールの ID を取得します。 // 取得した ID をキーに、対象のアカウントを DB から取得します。 // アカウントが今のユーザ/権限でアクセスできるかどうかを判定します。 // アクセスできる場合は表示用の画面を返します。 // アクセスができなければエラー画面を返します。 } private Optional<Authority> getLoginAuthority(final HTTPSession session) { Optional<Long> loginAccountId = session.get(Constants.SESSION_ACCOUNT_ID).map(Long::parseLong); return loginAccountId.flatMap(id -> authorityRepository.getOneByAccountId(id)); }
セッションからアカウントIDを取得する事は、およそ共通の処理でしょう。
ならば utils サブパッケージを作って分離しておくべきでしょう。
package controllers.utils; public class SessionManagement { public static Optional<Long> readLoginAccountId(final HTTPSession s) { return s.get(Constants.SESSION_ACCOUNT_ID).map(Long::parseLong) }
追加で
@Autowierd private AuthorityRepository authorityRepository; public Optional<Authority> getLoginAuthority(final HTTPSession session) { return readLoginAccountId(session) .flatMap(authorityRepository::findOneById); }
こうした分離を繰り返すと、いつしか処理が本来の「getProfile(プロフィール取得)」から外れて、個々の小さな処理となります。
これを適切なクラスに分離していくことで、再利用性の高いクラスが出来ていき、同じような事を繰り返す事がなくなっていきます。
蛇足ですが、見ての通り、汎用的な処理とは即ち極小の、業務ロジックに直接関係しない個々の処理である事がわかります。
これは全体を眺めただけで見通すのは難しい。
設計はトップダウンとボトムアップ - 謎言語使いの徒然 にも繋がる話となります。
小さく区切って整理する。これが保守性を上げる一助となります。
私なら、getProfile は私ならこんな書き方をさせますね。
public Response getProfile(final HTTPRequest request, final HTTPSession session) { // 権限を取得し、取れないようなら、未ログインとして蹴ります Optional<Authority> authority = sessionUtils.getLoginAuthority(session); if (authority.isEmpty()) return redirect(request, Constants.LOGIN_URL); // リクエストから取得するプロフィールの ID を取得します。 return this.readProfileId(request) // アカウントが今のユーザ/権限でアクセスできるかどうかを判定します。 .filter(targetId -> this.profiles.isAccessibleProfile(targetId, authority.get())) // 取得した ID をキーに、対象のアカウントを DB から取得します。 .flatMap(targetId -> this.profiles.getByAccountId(targetId)) // アクセス取得できる場合は、表示用の画面を返します。 .map(prof -> renderProfilePage(prof)) // アクセスができなければエラー画面を返します。 .orElse(redirect(request, Constants.HOME).with( Flash.error(Messages.get(Constants.ERROR_FORBIDDEN)) )); }
オブジェクトの状態は作らない
ここで言う状態とは、クラス変数と振る舞いの状態を意味します。
例えば、クラス内の特定のフラグが true となったらメソッドの挙動が変わるなどです。
状態とは?
Java で例えれば、Closable インターフェース実装クラスなんかがそれに当たります。
リソースの解放と言う意味では仕方ないのでしょうが、close() を実行したあとでメソッドを呼び出すと例外を吐き出すようになります。
リソースを解放した後にアクセスできないのは当然の話ですし、close 後は使用不可(例外)を吐き出すだけまだマシと言うものでしょう。
一番怖いのは「動作が変わる」と言うパターンです。
例えばクラス自体が状態遷移図のようなものを持っている場合です。
例
具体的にはこんな感じのクラス設計だ。
- クラス動作に必要な値を、コンストラクタで初期化せず、setter 経由でしか設定できない。
- メソッド呼び出しに順序が指定されている。
- クラス変数の状態によって、getter 以外の public メソッドの挙動が変わってしまう。
何故状態が悪?
こうしたクラス設計とは、つまりクラスの内部的な動作を知らないと、意図した結果が得られない事を意味する。
オブジェクト指向の成り立ちが、もともと シミュレーション用言語 Simula なので、状態の変化と言うのはあって当然だったと思われる。
また、現実的にも自販機などのようにお金を投入された状態(ドリンク選択待機状態)など、状態を持つものは多い。
さらに、オブジェクト指向の本質、抽象化による現実の模倣(車とかがよく例に出されるが、それも状態だらけだ)としても、状態を是としている。
だが、いくつかの理由で、これはバグを産みやすくなる。
- (先に挙げたように)クラスの内部実装を理解していないと、正しく使えない。
つまり、「この動作はこの機能の実装にも必要なんだけど…」と思った時に、そのクラスのコードを全て理解しないと使えないという事だ。
状態がなく、クラス/メソッド名が確かなら、そもそもインターフェースだけで使い方がなんとなくわかる筈なのにも関わらずだ。 - 状態を持ったオブジェクトはテストしづらい。
例えば、クラス内の状態を2変数で持ったとしましょう。すると、「テストすべき状態数=1変数で操作される状態数 x 1変数で操作される状態数」と倍数的に増えてしまう。
まさかテストせずにリリースする訳にも行かないでしょう?
テストコードを書かないとしても、結合テストでは、ここの分岐を動かすための操作、事前条件を整えるのが難しくなる。
そうしたものは、結合レベルでは見逃す事も多い。 - 安心して他のメソッドに渡せない。
オブジェクトに状態があり、それが書き換え可能だと、メソッド引数に入れて渡した時、その先で書き換えられてしまう恐れがつきまとう。
それを避けるために、全ての手続きを追いかける必要性をプログラマに課してしまい、その行き着く先は1メソッドに全て記述する巨大な手続きだ。
多くのプログラマが全力で嫌がるCOBOLバッチの世界へようこそ…。 - 常にコード内でオブジェクトの状態を確認しなければならない。
メソッドによってオブジェクトの状態が変わるという事は、メソッドの呼び出し順序にすら制限しかねない不安定なものになる。
次に待っているのは、その状態をフルコントロールするための状態確認処理の増加だ。
それはビジネス的に本質的なものではなく、可読性を損う。
つまり、状態を持てば持つほど、それを使用するプログラムはそのコントロールに手間を裂く必要に迫られ、複雑化を招いてしまう。
これは保守性とは相容れない。
どう避ける?
私はこの問題に対し、 「クラス変数を使わない」 という方針をとる。
代わりに使用するのが 「クラス定数」 つまり、 コンストラクタによる初期化以外の値の変更を拒絶する 方針だ。
こうすれば、コンストラクタによってクラスの振る舞いは確定されるので、下記のようなメリットがある。
- テストが書きやすい
- 気軽にメソッド引数に渡しやすい
- メソッドの呼び出し順序生が発生しにくい(DB など、外部入出力があるとまた少し違いますが)
- 自分のメソッド内(もしくは自分のクラス内)で初期化したオブジェクトの振る舞いは固定なので、振る舞いを当てにしたコードを安全に書ける
欠点も挙げないと不公平でしょうから、欠点も挙げておきます。
- 別の状態のオブジェクトを作るために、new が必要となってしまう。
最近の Java/C# のランタイムは優秀で、変数を使わない場合の new コストはかなり低くなってきてはいる。
しかし、ハードウェアの制約、ゲーム業界ほどの性能厨だと流石に考慮せざるを得ない。 - 状態で物事を考えるという考え方を捨てなければならない。
この考え方はどちらかと言えば「関数型言語」の思考に近い。
慣れるには時間がかり、作るにも頭を働かせる必要がある。
しかし私はこの学習コストを必要悪だと考える。
理由は現在発生中のパラダイムシフトだ。
C++/Java は後発の方だが、F#, Scala, Rust, Kotlin などのように「オブジェクト指向 & 関数型」のハイブリッド化が時流だ。
いずれやらざるを得ないのなら、諦めてやるべきだ。
仕事が先細っていいなら構わないが…
設計はトップダウンとボトムアップ
設計のトップダウン
コードが設計だとしても、全ての設計が不要になるわけではない。
もちろん、Excel 方眼紙に書いた誰も読まないコード書いた方が早い設計書ではない。
まずは機能設計までと名前空間(パッケージやディレクトリ構成)だ。
何の事はない、機能ごとにパッケージを作りなさいという話だ。
私は MVC 形状の Web アプリを作る機会が多いので、例えばこんな感じになる。
- root
- (処理の種類、controllers, models, entities, utils 等)
- (機能カテゴリ名、account, administrators)
- (機能名、login, logout, signup 等)
- (機能カテゴリ名、account, administrators)
- (処理の種類、controllers, models, entities, utils 等)
はっきり言えば、この時点では機能一覧みたいな感じだ。
文章で言えば、「このアプリにある機能一覧」とか、本で言えば「目次」みたいなものだ。
もう一つあまりにも複雑になりそうな場合は、メモ書き程度のクラス図、メッセージ図、シーケンス図や、ものによっては状態遷移図も書く。*1
何故これしか書かないのかと言えば、それ以上の粒度で書いたとしても、(保守性も考慮して)なるだけシンプルに実装しようとすればどうしてもメソッドもクラスも増える。
つまり、これ以上細かい粒度で書いた所で、ドキュメント保守という名の、生産性とは程遠いメンテナンス地獄が発生してしまう。
設計のボトムアップ
上記で既に半分述べてしまったが、機能を実現するためのコーディングを開始する。
しかし、可読性を考えたり、コードをシンプルにしようとすると、メソッドが増えてくる。
例えば、ログインであれば下記のような手続きがあるだろう。
- 画面からのリクエストをバリデーションする
- 該当するアカウントをテーブルから取得する
- 取り出したハッシュ済みパスワードと、画面から入力されたパスワードを検証する
- ログイン済みのセッションを作成する
- 画面応答を作成して返す
といった手順だろう。
当たり前だが、これらは全部メソッド化すべきだ。
何故なら、一つのメソッドで記述すれば、処理の区切りを発見しづらくなる。
つまり追いづらくなり、変数を使っていればその寿命がわからなくなる。
バリデーションにしても、各フィールドの定義、長さ、使用可能な文字などの設定もある。
当然この定義とバリデーションの実行(リクエストからのデータの取得等)も分離するだろう。
そういった事を読みやすくしようと考えれば、当然ステップごとにメソッド化するだろう。
一つのクラスにメソッドが増え過ぎれば、クラス分離を考える。
例えばバリデーションに関する定義クラスを分離するなどだ。
適切な名前さえつけておけば、コードの意味が読み取りやすく、何かあっても十分に分離しているので、修正箇所は一箇所。
それも他に影響を考えなくていい。
クラスが増えれば、種類ごとにサブパッケージを考える。
すると、当初からメソッドが増え、クラスが増え、パッケージが増える。
それは可読性を上げるための手段であり、日本語で無理に書かれた仕様書より保守性を上げる手段だ。
しかもこれは、設計からは見えてこない可読性に根ざした修正だ。つまりボトムアップでしか発生しない。
WF の限界
WF ではトップダウンで全て流れるから、このボトムアップな設計は許容されないだろう。
WF あるあるの 1 メソッド 100 行超えの超大作なんかは、この仕様書メンテナンスコストを嫌うために発生しているものが多い。
WF の後戻り工数はそれくらい重い。
(もしくは設計通りに作れという政治的な圧力があるか、開発者から考える頭が欠如してしまったかだ)
この辺りが WF という手法の限界だ。
ここまで予測できるのなら WF もありだろう。
だが、C言語がメモリリークやポインタ操作ミスでクラッシュを招くように、C言語はプログラマに全能性を求めてしまっている。
同様に、WF の流れは設計者に全能を求めてしまう。
その他考えてる事
Martin Fowler 氏に言わせれば、 コードはドキュメントである ということだ。
この論文では、作成したものを表現するという点に関して、ソースコードは、その実現方法を含む完全な形で全てが記載されているという点があげられる。
また、この文章の中に、「コードは設計である」 という話もある。
ここで WF の設計を考えると、その設計はそのままコードに置き換えられるか?不可能だ。
なぜなら、そこまでできる位なら、設計書をコンパイルした方が圧倒的に効率的だ。
そう考えて、かつて富士通は限定的とは言え 設計書をコンパイルして実行できるようにする 試みもしている。
現実的に広まったかは別として、面白い観点だ。
だがそれを WF の定義で見て設計と呼ぶのか?コンパイルして実行できるものを書く行為が?
結局の所、 コードを書くことと設計する事に差はほぼないという事だ 。
むしろ、エネルギーも「熱→水蒸気→(タービンにかけて)電気」と変換する上でエネルギーをロスするように、工程を増やして他人を介在すれば、それだけ原型の仕様が失われる。
そこを考えれば、設計/製造と分けることが現実と乖離していることがわかる。*2
車輪の再発明は避ける
プログラマなら何度も聞いた筈。
でも、実際に車輪の再発明って結構やってるのでは?
車輪の再発明とは?
何のことはない、同じようなものを何回も書く事をいう。
例えば、AJAX API を作るという場合なら、XML/JSON でシリアライズ/デシリアライズの環境を整えて、レスポンスヘッダを弄って、オブジェクトのシリアライズ結果をレスポンスのストリームに書き込む…
何言ってんだレベルだよね。
例えば Spring なら、コントローラに「RestController」をアノテートして、オブジェクトをそのまま返せば済む話だったりする。
Play なら、Json オブジェクトにクラスを食わせて結果を返す だね。
CSRF 対策だって、自前でセキュアなワンタイムトークンを作るとか阿呆な所業だ。
こういう技術の実現をより低レベルから構築することが車輪の再発明という。
何故避ける?
大体の事は はてなキーワード に書いてある。
標準で提供されているものは注意深く検討した上で作成されており、バグは少なく、処理も速いため、余程のことがない限り作成のために使った時間と金をロスするだけである。
商用システム上で車輪の再発明を行ってしまった場合は作成した後も、試験・運用などの開発フェーズでも面倒を見る局面がありコストは更に嵩む。
また担当SEやプログラマが入れ変わった場合に、再発明された機能のコードまで含めて勉強させなければならないため、無駄になるコストは意外とバカにならない。
ただ、あえて再発明をする必要がある時もある。例えば現状のプログラムがなんとか動いているのだがコードがスパゲッティになってしまい、それ以上の拡張性を期待できなくなった時などである。
しかし、多くの場合は設計がまずいことが多いため、リファクタリングを行ったほうが適切である。
誰が好き好んで スパゲティコード なんて弄るか!
ライブラリを使えば、増量ソースが少なくて済む。
たまに「その学習コストが…」なんていう阿呆が居るが、何も極めろなんて言ってないし、求められる事も普通ない。
「お前の学習コストは、JSON 一つ覚えるのに月単位もかかるのか?」と問いたい(むしろかかるならその学習能力のなさを驚きたい)。
後、ライブラリを使うことには意味がある。
「Excelで帳票出して」と言われて、Excel のバイナリファイルの仕様を1から勉強して自力で実装するのと、 Apache POI 覚えるのどっちがいい?
つまりそういう事だ。
車輪の再発明ってどうやって避けるのか?
これには、まず自分がやろうとしている事が、本当に自分でやるべき事なのかを考えることから始まる。
だが、ここが一番の問題なんだ。
「CSVパースだって機能を実現するためのやるべきことだ」なんて思ってると、車輪の再発明はなくならない。
まずこれの切り分けが無いと、何度でも車輪の再発明を繰り返してしまう。
この認識だと、「沢山のライブラリを知ってれば、簡単に解決できるものを選べるよね?」という解決法になってしまう
これでは 「ライブラリや OSS を探そう」という発想が生まれない
そのヒントは実は昔の論文にある。
以下はその抜粋だ
魔法のように、すぐに役に立ちプログラマの生産性を倍増させるような技術や実践 (特効薬) は、今後10年間(論文が著された1986年の時点から10年の間)は現れないだろう、と主張した。
ログラマの生産性の限界は「本質的な複雑性」(essential complexity)についてのみ当てはまると述べているのであり、「偶有的な複雑性」(accidental complexity)に対する挑戦については支持している。
* 偶有的な複雑性は、ソフトウェア開発者自身が発生させている解決可能な問題に関連する。例えば、アセンブリ言語のプログラムコードの記述、最適化、バッチ処理などによってもたらされる遅延は、偶有的な複雑性に由来する。
* 本質的な複雑性は、解決すべき問題によってもたらされるものであり、これを取り除くことはできない。もし利用者が30の異なることを行う1つのプログラムを望む場合、この30のことは本質的であり、開発するべきプログラムはこの30の異なることを実行しなければならない。銀の弾丸などない –Frederick Phillips Brooks, Jr. 氏の論文
超平たく言うと、「本質的な複雑性(≒要件)は時代とビジネスで変わるんだから、特効薬なんてあるわけないよ。でも偶有的な複雑性(≒要件を実現するための手段)はどんどんやれ」って事だ。
つまり、作る前に考えればいい。
「これから書こうとしているコードは要件そのものなのか?それとも、要件実現のための技術の一つなのか?」 そこが切り分けられれば、ライブラリを探すことができる。
この切り分けがしにくいなら、そもそもメソッド化やクラス化の単位が分かってないか、何が一般的技術(XML,JSON,Ajax等)なのかを知らない。
そういう人はまだ設計を行うべきではない。
大丈夫だ、技術なんてものは、ライブラリなんてものは使う為に作られてる。
つまり勉強することはできるんだ。
勉強し続ける
前のエントリ でも書いたが、勉強はスキルを上げる唯一の手段だ。
加えて言えば、勉強する気もなく古い技術ですべて解決しようとする人、とはコードを改善したいとも思わない人とは、僕は仲良くできないだろう(きっと一緒に仕事をしたら喧嘩になるか、そいつにコードを書かせない)。
勉強は大事だ
これは古い技術を馬鹿にしている訳ではない。
パラダイム・シフトは多くの場合、古い技術をより洗練させた形で実現することでおこるからだ。
正に温故知新。
オブジェクト指向だって、C++/Java で一気に有名になったが、元を正せば Simula 言語のクラスを C 言語の struct の発展形として実装したのが広まっただけだ。
もっと言えばオブジェクト指向という言葉も smalltalk 由来だ(C++ファミリとは違ったオブジェクト指向の流派だ)。
http://d.hatena.ne.jp/sumim/20080415/p1
C# には 2008 年ごろから、Java は 8 で対応したラムダ式なんて、自分の知る限り LISP/ML/haskell にまで遡る。
マルチスレッドのより良い制御だって Java/Scala に Akka があるが、この Akka のアクターモデルなんて一体いつの発明だよって話だ。
だがこれは、新しいものを学びもせず、学習コストは嫌いだと拒絶するのは意味が違う。
何故新しいライブラリ、フレームワーク、言語が出るのかを考えた事はあるだろうか?
それは主に2つの理由があると考えている。
- ビジネスが時代と共に変化し、そのビジネス要件を容易に実現するため
要するにやりたいことをより単純な手段で解決するためだ。
クラウドが出て、分散処理が流行ると、Hadoop あたりからどんどん分散処理が増えた。
現在 DeepLerning が大流行だが、早速ライブラリまで出ている。
ビジネスの進化に追従した結果だ。
今は落ち着いたが HTML5 当初は AltJS 言語がやたら出たのも記憶に新しい。 - No silver bullet - essence and accidents of software engineering:銀の弾などない— ソフトウェアエンジニアリングの本質と偶有的事項
フレデリック・ブルックス.1986年著
「本質的(ビジネス要件)な複雑性と、偶有的(要件のために行う手続き)な複雑性で考えると、本質的な複雑性に対する特効薬はない」という論文。
逆を言えば、偶有的複雑性は簡略化し得る事を意味してる。
今更フレームワークなしで Web システムを作るか?正気の沙汰ではない。
全く勉強しなければ取り残されるのは上記が理由だ。
学習は価値観を変える
この業界の個人生産性はアホのような差がある。
それをもたらすのが学習だ。
そして、学習は価値観も変える。
final String displayName = user.getDisplayName() == null ? user.getAccountId() : user.getDisplayName();
言っておいて何だが、多くの職場がそうであるように、三項演算子は禁止としているところが多い。
それのどこが悪いのかなんてそうそう気にすることではないだろう。
だが、 scala とかの関数型言語にどっぷり浸かると意見が変わる。
val displayName = if (user.displayName.isEmpty) user.accountId else user.displatName.get
scala は関数型言語で、関数型言語は定例的に「if は文ではなくて式」なのだ。だから、if や下手すれば for にも返値がある。
言語仕様上それは「処理ブロックの最後の一行」なわけだが、この処理どこかで見なかったか?
そう、さっき見た 三項演算子 だ。
「条件分岐」なのではなく、「条件で式の結果が変わる」という考え方に変わる。
(余談だが、こうした言語に慣れると、「if で値が返せないとかダサい」と考えるようになる)
当たり前だが、こんなコードの中で複雑な条件を載せるとか、返値が複雑化するとかなら scala ユーザでも if で長々とは書かない。
簡潔に書ける事こそ是としているからだ。
だが、逆に簡潔に書くことができるなら、Java でも三項演算子の使用に何のためらいがある?
それはきっとこんなださいコードを書くより良いはずだ。
String tmpName = null; if (user.getDisplayName() == null) { tmpName = user.getAccountId(); } else { tmpName = user.getDisplayName(); } final String displayName = tmpName;
こうしたコードの価値観だって、更に勉強すればより良い書き方が出てくるかも知れないのだ。
その時、僕の価値観はまた少し変わるかも知れない。
例えば JavaScript ならこんな書き方もある。
const displayName = user.displayName || user.accountId;
null = false の扱いだから、null なら右の値が返る。
boolean 同士だったらまた意味論が変わってしまうこともあるし、名前が重要になってくる書き方だ。
まぁ書き方の是非は置いといても、学べばシンプルに物事が書ける事が多い。
だからこそ学ぶのだ。
プログラムを読みやすくするための個人的Tips
個人的に綺麗なコードを書くために心がけているもの。 数が多いので、効果があると個人的に思っているものだけ集めてる。
なぜ読みやすく書くのか
読みやすいことに興味がない人のコードで、本当に汚ければ遠慮なく指摘するし、そのコードを保守したいとも触りたいとも、仕事でなければ関わりたくすら無い。
一度だけ書いて、二度と保守もメンテも必要無い書捨てのスクリプトなら、とやかくは言わない。
しかし、多くのプロダクトがそうであるように、アプリケーションはメンテナンスする。
それはバグ改修だったり、性能向上だったり、機能追加だったりする。
その時、作りを理解するのは何か?
Excel 方眼紙に書いた、行単位では意味が読み取れないような日本語の文章か?
違うだろ?
結局のところ読んで、理解して、手を入れるのは コード なんだ。
加えて実際の修正量とコードを読む量どっち多い?
聞くまでもない。
間違いなく読む量だ
なら、読みやすいコードこそ把握しやすく、バグも埋め込みにくく、容易に修正できるものだと言えるのでは?
だから 綺麗に書かれたコードが正義なんだ と考える。
他にも、コードは仕様上でも重要なドキュメントだ。
- コードは設計だ
Jack W. Reeves 氏の論文。 - なぜコードが重要なドキュメントなのかというと、 詳細かつ正確なドキュメントはコードしかないからだ
Martin Fowler 氏の論文。
読みづらいドキュメントを誰が訂正するんだ?誰も触りたくなんて無い。
だから学ぶんだ。
美しく書くには
コードの美醜と言うものを知らなければできない…と経験で上がってきた人は居ると思う。
それも方法の一部であることは事実である。
美しいとは経験だ。当然ながら無垢な3歳時が名画を見て「すごい」とは思っても「美しい」とは思わない。
それは経験のなせることだからだ。
そうした意味では OSS のソース、それも多くのマージがあったものが良いかもしれない。
(複数人がメンテする必要があるということは、それ相応に読めるように出来ているだろうと予測はできる)
だが、もう一つ学ぶ方法がある。
それは「 原則を学ぶ 」だ。
原則自体は、書籍「プリンシプル オブ プログラミング」を見ていただくとして、特に効果のあった思考を順におっていこうと思う。
本にもなっている。気になる人はこのエントリの下の方参照。
普段気にしてるもの一覧
- 基本方針
- 設計レベルで気をつけろ!
プロジェクトが燃える原因は大体上流だ。プログラムがこんがらがるのは大体設計だ。- 車輪の再発明は避ける
- YAGNI原則(Wikipedia)
You aren't gonna need it. そんなのまだ必要ないよ(だからその分シンプルにしろ)。 - 設計はトップダウンとボトムアップ
- GRASPパターン(Wikipedia)
General Responsibility Assignment Software Pattern.
オブジェクト指向設計の基本とは、適切なクラスに適切な責任を割り当てることである - http://d.hatena.ne.jp/asakichy/20090331/1238472501
設計の簡素化のため、オブジェクトの利用、オブジェクトの生成・管理を混ぜて考えてはならない。 - オブジェクトの状態は作らない
- 1 クラス 1 機能、1 メソッド 1 処理の徹底
- コーディングで気をつけろ!
- くたばれカーゴ・カルト・プログラミング
- ユーティリティクラスを書きまくる
- 全メソッドをテストする前提でユニットテスト出来ない設計はしない
- 1 クラス 1 機能、1 メソッド 1 処理の徹底
- ボーイスカウトの原則
- 名前の付け方に気をつけろ
- 段落とフォーマットを揃える
- 適切なコメント
- ユーティリティクラスを除いて、依存するクラス数は 10 個以下に抑えよう
- 分岐一つはバグ一つ埋め込むものだ
- 条件式の作法
- 変数は極力使うな
- null は滅びればいい
- ネストは二段、最悪でも三段
- 一つの処理の流れが 5 行を超えたら、メソッド化を考える
- メソッド行が 20 超えたら作りを疑え
- メソッド数が 10 を超えたら、クラス分離を考える
- 仕様を悪用するんじゃない
項目が多いので、別のエントリで色々書こうと思う。
参考
- ケント・ベックのSmalltalkベストプラクティス・パターン―シンプル・デザインへの宝石集
smalltalk は自然言語に近い公文がウリ。読ませるコードについての示唆は、他言語でも有効です。 - リーダブル・コード
往年の名著。初心者向けに色々書いてくれている。ただし、一部は陳腐化しているかもしれない。 - プリンシプル オブ プログラミング3年目までに身につけたい一生役立つ101の原理原則
なぜ僕が3年目の時までにこの本が出なかったのか…。実感してそうしようと思うには、実務経験1-2年くらい欲しい。 - 新装版 リファクタリング 既存のコードを安全に改善する
ボーイスカウト(ボーイスカウトたるもの「帰るときには、自分が来たときより綺麗にしなければならない」)の原則を守る方々に…。 - オブジェクト指向の法則集(オブラブ様)
オブジェクトラブとはいったものです。