スパゲティから学んだこと

f:id:bokuo-okubo:20150525094147j:plain

昔作ったiOSのコードをリファクタリングしたおはなし

昨年、開発メンバー二人で、iOSのアプリケーションをフルスクラッチで組みました。自分が大枠の設計、その後1.5ヶ月ぐらいをかけて二人で実装したものです。

書いた時の自分は、プログラマになって半年ぐらい、ろくなGUIプログラミング経験なし、といった有様で、設計手法、Objective-Cの言語仕様、Cocoaの使いかた,,,,etcを学びつつ生産するという割と無理ゲーなことをやっていました。また納期も短かったこともあり、とにかく動くもの作る、ということに注力していて、それはまあ美味しそうなスパゲティを生産しました。

そして、ここ最近、そのコードのリファクタリングをやっています。書いた当時の自分に比べ、半年くらいの時間しか経っていないのではありますが、その間に学んだことの量がハンパないので、リファクタリングを通して色々と気づいたところが多く、自分で思ったところをメモしておきます。

お品書き

  • したごしらえ
  • まずはレシピを整えよう
  • いざ調理
  • まとめと、参考になった本と読みたい本

□したごしらえ

へっぽこコーダーの僕たちは、マッチョなリファクタリング哲学を持ち合わせる脳みそもなく、 まずは現状に対してどうアプローチをとるべきなのか考えなければなりませんでした。

錯綜した仕様

プログラマ歴としてまだ日の浅かった当時の自分にとり、ゼロからアプリケーションの設計をすることは、今から思えばかなりハードルが高かったな、と思います。それでもそれなりにパッチワーク的に知識を集約し、なんとか完成までこぎつけ、現在も稼働するプロジェクトのはじめの道筋をつけるところまではできたわけです。

しかし、どんなに"良さ気"な設計ができていても、根本的にコードレベルでの実現力がヌルかったので、その設計方針をきちんと落としこむことができず、技術的負債を積み上げることとなりました。

当時の自分はUMLなんてわからないので仕様書も書けず、またテスト駆動の重要性/存在の理由について考えがまわっていなかったのでテストコードもありません。リファクタリングしていく前の状態は、テストコードも明文化された仕様もない、ただバージョン管理されたコードベースと生成物がある状態でした。

セオリー通りのリファクタリングができない

本来であれば現状の機能に対してユニットテストを書き、仕様を一旦フリーズさせてからリファクタリングに入るべきだと思います。

しかしながら、我々のコードでセオリーを実行していくには、問題点が多すぎました。

もともとのコードがクソ過ぎてユニットテスト書くのがダルすぎる!!!!!!!!!!!!!

  • 杜撰な命名のクラス/メソッドたち
  • 肥大した神ViewControllerたち
  • 果てしなく続く長大なメソッド
  • 杜撰なCRUD処理/サーバレスポンスのハンドリング
  • etc..

このような要因から、現状のグジャグジャの仕様に対してテストを書くのはだるすぎる、という話になり、-- (テストコードなしで)現状の仕様を崩さずリファクタリングしていく -- にはどうすればいいか、ということを検討しました。


□ まずはレシピを整えよう

相手にするコードがぐちょぐちょなだけに、リファクタリングしていく上でもうかつに手を出せませぬ。。 どうすれば効率的、可能な限り安全にリファクタリングできるか、というレシピを考えました。

リファクタリング方針

現状の仕様に対して、

  • 可能な限りドキュメントを作成する

要求仕様がそもそも明文化されていないので、コードから要求仕様をビルドアップしていく。

ドキュメント生成ツールを利用し、詳細仕様htmlを別リポジトリ管理とし、その更新作業をスクリプトで自動化しました。

仕様について、コーディング規約について、検討、検証しながら同時的に決めていく。

ある程度リファクタリングに関しての意識が足並みが揃った時点から、gitホスティングサービスを利用して、issue/プルリクエストを多用するチケット駆動体制にしていく。

というところを意識していこう、という方針でリファクタリングをはじめていきました。

仕様書生成/テストがらみ

Doxygenを使い、仕様書の生成を行いました。 また、別リポジトリで生成されたhtmlの管理を行い、 ビルドツール(rake)でそのフローを自動化しました。

リファクタリング済/新規実装のコードで、ビジネス層にあたる部分に関しては、なるべくテストコードを書くようにして...います。(あんまり実践できてない) SwiftのQuickをつかってみている。

ワークフローについて

お互い仕事の片手間にやっていることもあり、

  • 問題点
  • 書くべき/リファクタすべき機能
  • 書くべきコード

の直交性を意識してissueを発行し、プルリクエストベースで開発していくスタイルにしました。 週末や空き時間に、やんなきゃいけないことをちょこっとだけやる、って感じのスタイルにできて、よかったです。


□ いざ調理

プロジェクト全体として改善(広義のリファクタリング)すべき点は3点ありました。

  1. 設計レベルでの再設計
  2. コードレベルでの再設計(狭義のリファクタリング)
  3. 開発環境の移植性、テスト環境の構築

特に1.,2.に関しては、どちらの方向からも参照関係を持つので、問題解決の順番についてきちんと考慮しなければな、と思いました。

上記三点を同時並行的に進めていきましたが、はじめの部分に関して順序関係を入れて書いてみようと思います。

でかすぎるメソッドの分割、変数/メソッドのリネーム

プロジェクトを離れていたこともあり、僕自身はそもそも実装したコードが完全に脳みそのストレージから落ちていたので、まずはコードリーディングから入りました。

が、まぁ杜撰な命名、杜撰な設計なのでコードが読みにくい読みにくい。。

そこで、機能として代表的なクラスをいくつかにしぼり、ライブコーディングしながら仕様の確認をしていく、というペアプログラミングもどきのようなことを実践しました。具体的には、ほぼほぼ手続きの羅列として記述されていたメソッドの中身を、きちんと変数のスコープを確認しながら、機能性を意識した名前をつけたメソッドに分割していく作業を共同で行っていく感じです。

メソッドの分割で見えてくるもの

"語感のスコープ"があまりにも漠然としたプロシージャを、どのような操作の集まりなのかを意識した命名を行ったプロシージャとして定義しなおすことで、類似の操作を行うプロシージャのパターンをコードベース全体の中から沢山掬い出すことができました。

元々のコードは、似たようなViewの生成、通信機能などが画面や機能ごとに冗長に記述されていて、ものすごく凝集度が低かったです。。。また、本来別メソッドとして切り分け、条件分岐はメソッドレベルで切り替えるべきようなところも、長大なメソッドの中にそのままロジックが記述されていて、本当に恐ろしい状態でした。。。よく動いてたな、っていうレベル。

設計レベルでのリファクタリング

類似機能がある程度溜まってきたところで、新規クラスとして再設計するとか、アーキテクチャレベルでの機能の位置を変えて、委譲させるような形にできないかを考えました。

というか、今このフェーズでコード書いてる、ってかんじですね。




リファクタリングする際に役立った知識

このへんがものすごく役立ちました。

テストコードを書くようになった。

普段は僕はサーバ側の仕事をしていますが、このコードを書いた当時と違い、(アプリに対してぬるいわけではないんだけど)絶対に落とせない、という制約が強い中で仕事をして、きちんとテストコードを書く文化に触れました。

ユニットテストに関してのコードの保証云々、には色々な議論があると思うので、テストコード書かなきゃいけない主義、っていうのは行き過ぎかな、と思っているのですが、個人的にとても良いな、と思う点は、コードを見る視点が切り替えられる、ということです。 プロダクトコードを書いているときはどうしても機能の内側に自分の意識が入って、コードのなかをウニョウニョ漂っているので、実現すべき機能の最短ラインに落ちてしまいがちです。一方でテストコードを書いてるときは、そのプロダクトコードを外部から利用する形で書くので、別の方向からもういちどコードを眺め直すことができる。セルフチェックとしての点が結構強いのだな、と考えています。

デザインパターンを学習した

23種類全部やれたのか、というと半分ぐらいしかきちんと機能説明できないような気がしますが、 ともかくも、他のクラス/インスタンスから当該クラス/インスタンスを利用する、ということのバリエーションについて、いくつかきちんと紙に図示して学習しておいたことが役立ちました。

実際にパターンに落とす/落とせるかどうかはさておき、クラスレベルでの設計に関して見通しが良くなったということと、「インタフェイスに対してロジックを考える」ような、コードの凝集度を上げていくためのセンスが身についたと感じました。

関数型言語(もといLisp)を書くようになった

関数型言語、学び始めたばかりではありますが、LispHaskellを触ってから得られた感覚として、副作用があるのかないのか、ということをきちんと自覚しなければならない、ということを思い知りました。

オブジェクト指向関数型言語もきちんと理解できていないのに生半可なことは言えないです。という前置きつきですが、 オブジェクト指向に関しての色々な議論がある昨今ですが、GUIを扱うようなプログラミングではオブジェクトに状態を持てるということ、そしてなにより、Smalltalk流のメッセージパッシングは、抽象概念を具体性を持って考え、コードにしていく上でやっぱりわかりやすいと思っていて、オブジェクト指向はこれからも大切なんだろうな、って思い至っています。

ただ一方で、僕は当該のアプリケーションを書いたときに、副作用を持つ/持たないメソッドについてあまりにも無自覚でした。結構平気で返り値の無いメソッドの中に分岐入れてたりとかしていて、今考えると頭悪かったな、って思います。

余談ですがLisp書いてからObjective-Cのメッセージング式を見たら、ああかわいいなぁ、と思うようになりました。 とまれやっぱSwiftのほうが生産性高いと思います。Xcodeの機能がSwiftに対してはまだもっさりしてるのは気に食わないけど。

DRYを体現したい

(誤解を恐れず言うならば)所謂GoFデザインパターンは、クラスベースかつ静的型付け(寄り)の言語で、クラス/インスタンス レイヤでの抽象的な操作を実際のコードに落とすための方法論 だと思いました。ポリモフィズムの体現のための、エッセンス。

一方で関数型言語での関数設計は、 コードレイヤでの設計のお作法のエッセンス が詰まっているように感じます。

関数型なのか、オブジェクト指向なのか、というような選択的なことでなく、凝集度が高くて保守しやすいコードを書く、っていう共通の高みを目指してコードを書けるように意識したいな、って思いました。


□ まとめと、参考になった本と読みたい本

まとめ

実際にはまだコード書いている途中で、まだもう少し続きそうではあるのですが、 全体を通して感じていることとして、糞コード直すの楽しいな、って思ってます。笑

自分たちで自分たちに採点しているような感じで、昔の自分のセンスのなさに身悶えながらではありますが、

別のところで得てきた知識がどんなものだったか、というのが再確認されるような気がして、個人的には楽しんでいます。

参考になった本と読みたい本

-- 参考にしたやつ --

□ 詳解 Objective-C 2.0 第3版

使いこなしきれて無かったプロトコルの使い方とか、クロージャの使い方とか、結構見ました。 なんだかんだでランタイムと密に結合してる言語でかつ暗黙知が多い言語だとおもうので、黒本は持ってないとだめですね。

□ 独習デザインパターン

この手の本で手元にあったのがこれだけだったので。。 あとはwebで大体事足りますね。 パターンを実際にどう使うか、っていうところは感覚的に慣れるしかないんだろうな、と思いました。

□ リーダブルコード

名前重要だよ。っていう前半部分だけでも十分だと思います。 実は英語版しか読んでない。

-- これから読みたいやつ --

□ 新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)

おまえまず真っ先にこれ読めよ、って話なんでしょうけど読んでないというオチ。

□ エリック・エヴァンスドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

いろんなレイヤーのデザインがある中で、もっとアーキテクチャ設計に関しては広い知識を入れたいな、って思います。サーバ単体、アプリ単体のアプリケーション設計しかまだ到達してないんだもんなぁ.. エンタープライズ アプリケーションアーキテクチャパターン も読まなきゃね。