Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
コードリファクタリングの手引き
Search
Tetsushi Hasesaku
February 25, 2019
Programming
690
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
コードリファクタリングの手引き
Tetsushi Hasesaku
February 25, 2019
More Decks by Tetsushi Hasesaku
See All by Tetsushi Hasesaku
Dockerで作るRails実行環境
hare8563
0
340
Vagrantで作るRails開発環境構築
hare8563
0
320
VagrantとDockerで作る、ポータブル開発環境
hare8563
2
820
Other Decks in Programming
See All in Programming
「エンジニアインターン、どうやって取った?」準備のリアルを語るLT会 Progate BAR
akiomatic
0
130
Dataformのリポジトリを立ち上げるときにまずやること / dataform-day0-2026
snhryt
0
160
RTSPクライアントを自作してみた話
simotin13
0
600
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
350
Even G2とAWSで推しのエージェントを召喚しよう!
har1101
1
110
タクシーアプリ『GO』の バックエンド開発のおける AI利活用と若者のすべて
pyama86
3
2k
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
260
ユニットテストの先へ:テスト技法で要求・仕様を整理するJava開発実践 / Beyond_Unit_Testing_Practical_Java_Development_Techniques_for_Organizing_Requirements_and_Specifications
shimashima35
0
400
Composerを使ったサプライチェーン攻撃の様子を眺めてみる #phpstudy
o0h
PRO
2
250
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
8
4.5k
Developing with AI Agents — Codex, Claude Code & Cowork Practical Guide
x5gtrn
PRO
0
1.3k
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
2.1k
Featured
See All Featured
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
390
Color Theory Basics | Prateek | Gurzu
gurzu
0
360
Agile that works and the tools we love
rasmusluckow
331
21k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
870
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
200
Prompt Engineering for Job Search
mfonobong
0
340
WCS-LA-2024
lcolladotor
0
630
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
1.7k
Optimizing for Happiness
mojombo
378
71k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.5k
Transcript
コードリファクタリングの手引き オブジェクト指向の原則とテストをかけるようにするメンテナンス術 pixiv Inc. @Hare8563 2019.2.25
2 • 晴佐久 哲士 • ピクシブ株式会社 • フルスタックエンジニア ◦ サーバサイド
(Rails) ◦ フロントエンド (React/Redux, Typescript) ◦ 環境構築 (AWS, Vagrant, Docker) ◦ Unity (C#, C++) • 趣味 ◦ プログラミング ◦ お絵かき ◦ CG制作 @Hare8563 フルスタック エンジニア
まずはこのコードを見てください 3
4 public void LoadAsync (DownloadLicense newLicense , Action <GameObject >
onLoadComplete , Action <float> onDownloadProgress = null, Action <Exception > onError = null) { CachedLicense newCachedLicense = new CachedLicense (newLicense ); DownloadLicense ? cachedDownloadLicense = LoadExistLicense (newLicense .licenseid ); if (cachedDownloadLicense != null && newCachedLicense .IsSameModel (cachedDownloadLicense .GetValueOrDefault ())) { SaveCachedLicense (newCachedLicense ); StartCoroutine (LoadAsyncCachedBinary (newCachedLicense , (characterBinary ) => { if (onDownloadProgress != null) onDownloadProgress (1.0f); LoadAsyncFromBinary (characterBinary , (GameObject character ) => { onLoadComplete (character ); }, onError ); })); } else { LoginedRequest (requestPath : "/api/request_path" , methods : HTTPMethods .Get, onSuccess : (downloadHandler ) => { byte[] downloadBinary = downloadHandler .data; LoadAsyncFromBinary (downloadBinary , (GameObject character ) => { Queue.Enqueue (() => { SaveEncryptedModelFile (newCachedLicense , downloadBinary ); SaveCachedLicense (newCachedLicense ); }); onLoadComplete (character ); }, onError ); }, onProgress : onDownloadProgress , onError : (downloadHandler ) => { Exception error = new Exception (downloadHandler .text); if (onError != null) onError (error); } ); } }
これを読んで内容を理解できました? 5
じゃあ、これならどうでしょうか 6
7 public void LoadAsync(DownloadLicense newLicense, Action<GameObject> onLoadComplete, Action<float> onDownloadProgress =
null, Action<Exception> onError = null) { CachedLicense newCachedLicense = new CachedLicense(newLicense); var loader = Factory.Create(newCachedLicense, this); loader.OnLoadComplete = onLoadComplete; loader.OnProgress = onDownloadProgress; loader.OnError = onError; loader.Load(); }
なにをやりたいのかなんとなくわかる! 8
じつはこれ、リファクタリング前と後のコードな のです。 9
今日の内容 1. なぜ、コードは読みにくくなるのか 2. どういう風にリファクタリングをしていくのか・何を意識する のか 3. 実際にどうやってリファクタリングに立ち向かったか 10
(注意) 本内容は、完全に私の経験に基づいた独断と偏見混じりの内 容になっております。 あまり鵜呑みにしないよう、ご了承ください 11
なぜ、コードは読みにくくなるのか 12
なぜコードは読みにくくなるのか • 複数人でコードに触れるため • 政治的な都合 • すでに手がつけられない無法地帯 13
コードに複数人が手をつけるのは当たり前 14
コードに複数人が手をつけるのは当たり前 15 とりあえず動 けばいいか
コードに複数人が手をつけるのは当たり前 16 こう書けばもっ とかっこよく書 ける!
コードに複数人が手をつけるのは当たり前 17 読みやすく書 こう
コードを書く人によって考え方や力量は バラバラなのだ 18
結果、読みにくいコードができあがる 19
政治的な都合で、適当に書かないと間に合わない 20
こういったのが積もり積もって 21
誰も手をつけられない状態になる 22
こうならない対処法自体は簡単なのだ 23
コーディング規約やレビューをきちんとする 24 こういう風に書いて ね。 レビューも僕を必ず 通してね
書くときは、間に合わせで書いていいけど 25
納期に間に合ったらきちんと整理しましょう 26
指示する側もきちんとその時間作ってあげてね 27
じゃあ、実際にどう整理していく(リファクタリン グ)していくか考えていきましょう 28
どういう風にリファクタリングをしていくのか・何 を意識するのか 29
何を意識すればいい 30
『テストが適切に書けること』 31
32 public void LoadAsync (DownloadLicense newLicense , Action <GameObject >
onLoadComplete , Action <float> onDownloadProgress = null, Action <Exception > onError = null) { CachedLicense newCachedLicense = new CachedLicense (newLicense ); DownloadLicense ? cachedDownloadLicense = LoadExistLicense (newLicense .licenseid ); if (cachedDownloadLicense != null && newCachedLicense .IsSameModel (cachedDownloadLicense .GetValueOrDefault ())) { SaveCachedLicense (newCachedLicense ); StartCoroutine (LoadAsyncCachedBinary (newCachedLicense , (characterBinary ) => { if (onDownloadProgress != null) onDownloadProgress (1.0f); LoadAsyncFromBinary (characterBinary , (GameObject character ) => { onLoadComplete (character ); }, onError ); })); } else { LoginedRequest (requestPath : "/api/request_path" , methods : HTTPMethods .Get, onSuccess : (downloadHandler ) => { byte[] downloadBinary = downloadHandler .data; LoadAsyncFromBinary (downloadBinary , (GameObject character ) => { Queue.Enqueue (() => { SaveEncryptedModelFile (newCachedLicense , downloadBinary ); SaveCachedLicense (newCachedLicense ); }); onLoadComplete (character ); }, onError ); }, onProgress : onDownloadProgress , onError : (downloadHandler ) => { Exception error = new Exception (downloadHandler .text); if (onError != null) onError (error); } ); } }
イメージにするとこんな感じになってる 33
LoadAsync 34 キャッシュの有無 をチェックする キャッシュから読 み込む HTTPリクエストをし てダウンロードする ダウンロードした ファイルから読み
込む ダウンロードしたの を暗号化して保存 する
これがいわゆる密結合の状態 35
これだと個々の機能をテストできない 36
テストをできるように分解するにはどうしたらい い? 37
正しいオブジェクト指向を学ぼう 38
SOLIDの5原則 39
SOLIDの5原則 • 単一責任の原則 (Single Responsibility) • オープン・クローズドの原則 (Open/closed) • リスコフの置換原則
(Liskov substitution) • インターフェース分離の原則 (Interface segregation) • 依存関係逆転の原則 (Dependency inversion) 40
単一責任の原則 • ひとつのクラスが受け持つ機能はひとつに限定 ◦ 言い換えると、変更理由はひとつにする • 例としては、ファイルの読み込み処理 ◦ ファイルの読み込みと、読み込んだものを解釈するは分ける ◦
読み込みのフォーマットが変わった時、解釈機能だけ変更すればよくな る 41
オープン・クローズドの原則 • 拡張は容易にできて、修正は他のクラスに影響を及ぼさ ないようにする原則 • クラスは、他のオブジェクトに依存するのではなく、そのイ ンターフェースに依存する(あるいは、newを隠蔽する)よう にすると実現可能 42
43 // このメソッドは内部で DerivedObjectに依存してしまっている // DerivedObject を別のものに変更したい時や、状況に応じて切り替えたいときに それができない public void
BadMethod() { DerivedObject obj = new DerivedObject(); obj.someMethod(); // これ以降、objを使った処理が続く }
44 // Factory経由でIObjectを実装した何かが受け取れる // このメソッドはInterfaceに依存しているため、比較的拡張がやりやすい public void GoodMethod() { IObject
obj = Factory.Create(); obj.someMethod(); // これ以降、IObjectに依存した処理が入る }
リスコフの置換原則 • 基底クラスから派生したクラスは、常に基底クラスと置き 換えられる挙動にしなければならない • サブクラスへ要求すること(パラメータ等)はむやみに増や さず、できること(実装するメソッド等)は減らすべきでない 45
46 class BaseClass { public abstract void DrawShape() { //
このメソッドは何か形をキャンバスに書き込む } } class DerivedClass : BaseClass { public override void DrawShape() { // このメソッドはファイルに形に関するメタな情報を書き込む // このような実装をすると、すでに`BaseClass#DrawShape`を使ってキャンバスに書き込んでいるところで置き換えた時 // なんらかのシェイプが書き込まれないため、期待した挙動にならなくなる } }
47 class BaseClass { public virtual void PrintWord() { Console.WriteLine("Print
any message" ); } } class DerivedClass : BaseClass { public string Message; public DerivedClass() { } public override void PrintWord() { // 要求する情報が増えているため、そのまま使うと nullを参照してしまう Console.WriteLine("Print " + Message + " message"); } }
インターフェース分離の原則 • インターフェースはシンプルにするという原則 • 一つのインターフェースに余計な機能をつぎ込んではいけ ません ◦ 例として、「ファイルをロードする」という機能を提供するインターフェース に「ファイルをローカルに保存する」という関係のない機能は必要ない ◦
IFileLoader と IFileSave で分けるべき 48
依存関係逆転の原則 • 上位のモジュールの実装は、下位のモジュールの実装に 依存してはならず、双方とも「抽象」に依存するようにしな いといけない ◦ 悪い例として、CarクラスがEngineクラスに依存した実装 49
50 // エンジンオブジェクトに依存した実装 class Car { public void Run() {
Engine engine = new Engine(); // エンジンの状態を変更する engine.State = EngineState.RUN; // エンジンのレバーをあげる engine.PullLever(); // ピストンを動かす engine.MovePiston(); } }
51
52 // インターフェースに依存した状態 class Car { private IEngine engine; public
Car(IEngine engine) { this.engine = engine; } public void Run() { engine.ChangeRunMode (); } } interface IEngine { // エンジンを走る状態に変更する void ChangeRunMode (); } class Engine : IEngine { private EngineState state; // 自分の状態は自分でコントロールする public void ChangeRunMode () { this.state = EngineState .RUN; lever.Pull(); piston.Move(); } }
53
依存性の注入 (Dependency Injection) • CarクラスのコンストラクタでCarが依存する、IEngineを引 数として渡している • 外部からそのクラスが依存する機能を渡すことを依存性 の注入と呼ぶ ◦
これができると、テストで外部からモックを差し込むといったことができる のでとても重要 54
個人的に重要な原則は3つ 55
SOLIDの5原則 • 単一責任の原則 (Single Responsibility) • オープン・クローズドの原則 (Open/closed) • リスコフの置換原則
(Liskov substitution) • インターフェース分離の原則 (Interface segregation) • 依存関係逆転の原則 (Dependency inversion) 56
実際にどうやってリファクタリングに立ち向 かったか 57
58 public void LoadAsync (DownloadLicense newLicense , Action <GameObject >
onLoadComplete , Action <float> onDownloadProgress = null, Action <Exception > onError = null) { CachedLicense newCachedLicense = new CachedLicense (newLicense ); DownloadLicense ? cachedDownloadLicense = LoadExistLicense (newLicense .licenseid ); if (cachedDownloadLicense != null && newCachedLicense .IsSameModel (cachedDownloadLicense .GetValueOrDefault ())) { SaveCachedLicense (newCachedLicense ); StartCoroutine (LoadAsyncCachedBinary (newCachedLicense , (characterBinary ) => { if (onDownloadProgress != null) onDownloadProgress (1.0f); LoadAsyncFromBinary (characterBinary , (GameObject character ) => { onLoadComplete (character ); }, onError ); })); } else { LoginedRequest (requestPath : "/api/request_path" , methods : HTTPMethods .Get, onSuccess : (downloadHandler ) => { byte[] downloadBinary = downloadHandler .data; LoadAsyncFromBinary (downloadBinary , (GameObject character ) => { Queue.Enqueue (() => { SaveEncryptedModelFile (newCachedLicense , downloadBinary ); SaveCachedLicense (newCachedLicense ); }); onLoadComplete (character ); }, onError ); }, onProgress : onDownloadProgress , onError : (downloadHandler ) => { Exception error = new Exception (downloadHandler .text); if (onError != null) onError (error); } ); } }
まずは、このコードを見て、密結合になってし まっているのでテストできるように分離したい 59
LoadAsync 60 キャッシュの有無 をチェックする キャッシュから読 み込む HTTPリクエストをし てダウンロードする ダウンロードした ファイルから読み
込む ダウンロードしたの を暗号化して保存 する
こう分離できないかな 61
LoadAsyn c 62 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン
ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
LoadAsyn c 63 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン
ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
もともとのコード 64 CachedLicense newCachedLicense = new CachedLicense(newLicense); DownloadLicense ? cachedDownloadLicense
= LoadExistLicense (newLicense.licenseid); if (cachedDownloadLicense != null && newCachedLicense .IsSameModel(cachedDownloadLicense .GetValueOrDefault ())) { } else { }
これを状況ごとに機能を振り分けるFactoryに 切り出す (newの隠蔽) 65
Factory 66 public static void Create(string Id, ICoroutineHandlable coroutineHandler, Action<ILoader>
onSuccess) { CachedLicense? cachedLicense = LicenseManager.LoadExistLicense(Id); if(cachedLicense != null) { onSuccess(new CachedLoader(cachedLicense.GetValueOrDefault(), coroutineHandler, EncriptionFile.ReadBytes)); } else { API.PostDownloadLicense(Id: Id, onSuccess:(DownloadLicense license) => { var newCachedLicense = LicenseManager.LicenseCache(license); var saveModule = new EncryptSave(EncriptionFile.WriteBytes); onSuccess(new DownloadLoader(newCachedLicense, saveModule)); }); } }
Factory 67 public static void Create(string Id, ICoroutineHandlable coroutineHandler, Action<ILoader>
onSuccess) { CachedLicense? cachedLicense = LicenseManager.LoadExistLicense(Id); if(cachedLicense != null) { onSuccess(new CachedLoader(cachedLicense.GetValueOrDefault(), coroutineHandler, EncriptionFile.ReadBytes)); } else { API.PostDownloadLicense(Id: Id, onSuccess:(DownloadLicense license) => { var newCachedLicense = LicenseManager.LicenseCache(license); var saveModule = new EncryptSave(EncriptionFile.WriteBytes); onSuccess(new DownloadLoader(newCachedLicense, saveModule)); }); } } 状況に応じて、ロード機能を分けたモジュールを提供する Factoryを作 成
Factory 68 public static void Create(string Id, ICoroutineHandlable coroutineHandler, Action<ILoader>
onSuccess) { CachedLicense? cachedLicense = LicenseManager.LoadExistLicense(Id); if(cachedLicense != null) { onSuccess(new CachedLoader(cachedLicense.GetValueOrDefault(), coroutineHandler, EncriptionFile.ReadBytes)); } else { API.PostDownloadLicense(Id: Id, onSuccess:(DownloadLicense license) => { var newCachedLicense = LicenseManager.LicenseCache(license); var saveModule = new EncryptSave(EncriptionFile.WriteBytes); onSuccess(new DownloadLoader(newCachedLicense, saveModule)); }); } } キャッシュがある場合は、キャッシュから読み込むローダをコールバッ クに渡す
Factory 69 public static void Create(string Id, ICoroutineHandlable coroutineHandler, Action<ILoader>
onSuccess) { CachedLicense? cachedLicense = LicenseManager.LoadExistLicense(Id); if(cachedLicense != null) { onSuccess(new CachedLoader(cachedLicense.GetValueOrDefault(), coroutineHandler, EncriptionFile.ReadBytes)); } else { API.PostDownloadLicense(Id: Id, onSuccess:(DownloadLicense license) => { var newCachedLicense = LicenseManager.LicenseCache(license); var saveModule = new EncryptSave(EncriptionFile.WriteBytes); onSuccess(new DownloadLoader(newCachedLicense, saveModule)); }); } } キャッシュがない場合は、ライセンスをダウンロードしてきて、そこから ダウンロードする機能を渡す
Factory 70 public static void Create(string Id, ICoroutineHandlable coroutineHandler, Action<ILoader>
onSuccess) { CachedLicense? cachedLicense = LicenseManager.LoadExistLicense(Id); if(cachedLicense != null) { onSuccess(new CachedLoader(cachedLicense.GetValueOrDefault(), coroutineHandler, EncriptionFile.ReadBytes)); } else { API.PostDownloadLicense(Id: Id, onSuccess:(DownloadLicense license) => { var newCachedLicense = LicenseManager.LicenseCache(license); var saveModule = new EncryptSave(EncriptionFile.WriteBytes); onSuccess(new DownloadLoader(newCachedLicense, saveModule)); }); } } 保存機能を提供するモジュール、ダウンロードしたライセンス情報など はDIで入れている
Factory 71 public static void Create(string Id, ICoroutineHandlable coroutineHandler, Action<ILoader>
onSuccess) { CachedLicense? cachedLicense = LicenseManager.LoadExistLicense(Id); if(cachedLicense != null) { onSuccess(new CachedLoader(cachedLicense.GetValueOrDefault(), coroutineHandler, EncriptionFile.ReadBytes)); } else { API.PostDownloadLicense(Id: Id, onSuccess:(DownloadLicense license) => { var newCachedLicense = LicenseManager.LicenseCache(license); var saveModule = new EncryptSave(EncriptionFile.WriteBytes); onSuccess(new DownloadLoader(newCachedLicense, saveModule)); }); } } onSuccessで戻り値になるオブジェクトは実態ではなく、 Interfaceに依存している
LoadAsyn c 72 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン
ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
Factory 73 public static void Create(string Id, ICoroutineHandlable coroutineHandler, Action<ILoader>
onSuccess) { CachedLicense? cachedLicense = LicenseManager.LoadExistLicense(Id); if(cachedLicense != null) { onSuccess(new CachedLoader(cachedLicense.GetValueOrDefault(), coroutineHandler, EncriptionFile.ReadBytes)); } else { API.PostDownloadLicense(Id: Id, onSuccess:(DownloadLicense license) => { var newCachedLicense = LicenseManager.LicenseCache(license); var saveModule = new EncryptSave(EncriptionFile.WriteBytes); onSuccess(new DownloadLoader(newCachedLicense, saveModule)); }); } } 暗号化、復号処理は関数として、引数に渡す . これも処理をDIし ている
LoadAsyn c 74 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン
ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
じつはこの2つはどこから読み込むかだけで、 読み込むフォーマットは同じ 75
読み込む機能は継承で両方に提供し、どう取 り出すかだけ分ける 76
77
78 public abstract class BaseLoader : ILoader { public Action<float>
OnProgress { get; set; } public Action<Exception> OnError { get; set; } public Action<GameObject> OnLoaded { get; set; } // inteface が提供する部分は継承先で実装する public abstract void Load(); protected void FileLoadImpl (byte[] binary) { // バイナリデータを読み込む実際の処理 } }
LoadAsyn c 79 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン
ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
80 public class DownloadLoader : BaseLoader { private CachedLicense cachedLicense
; private ISavable fileSavable ; public DownloadLoader (CachedLicense license, ISavable save) { cachedLicense = license; fileSavable = save; } public override void Load() { LoginedRequest (requestPath : "/api/request_path" , methods: HTTPMethods .Get, onSuccess: (downloadHandler ) => { byte[] downloadBinary = downloadHandler .data; fileSavable .Save(downloadBinary ); FileLoadImpl (downloadBinary ); }, onProgress : onDownloadProgress , onError: (downloadHandler ) => { Exception error = new Exception(downloadHandler .text); if (onError != null) onError(error); } ); } }
81 public class DownloadLoader : BaseLoader { private CachedLicense cachedLicense
; private ISavable fileSavable ; public DownloadLoader (CachedLicense license, ISavable save) { cachedLicense = license; fileSavable = save; } public override void Load() { LoginedRequest (requestPath : "/api/request_path" , methods: HTTPMethods .Get, onSuccess: (downloadHandler ) => { byte[] downloadBinary = downloadHandler .data; fileSavable .Save(downloadBinary ); FileLoadImpl (downloadBinary ); }, onProgress : onDownloadProgress , onError: (downloadHandler ) => { Exception error = new Exception(downloadHandler .text); if (onError != null) onError(error); } ); } } ファイルのダウンロード機能をここでは いれて、ロード機能の実態は基底クラ スに入れている
LoadAsyn c 82 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン
ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
83 public class CachedLoader : BaseLoader { private ICoroutineHandlable CoroutineHandler
; private CachedLicense cachedLicense ; private Func<string , byte[]> fileDecryptFunc ; // コールチンを実行する機能、ファイルを復号する機能を注入 public CachedLoader (CachedLicense license , ICoroutineHandlable coroutine , Func<string , byte[]> decryptFunc ) { cachedLicense = license ; CoroutineHandler = coroutine ; fileDecryptFunc = decryptFunc ; } public override void Load() { cachedLicense .Save(); CoroutineHandler .RunMonoCoroutine (LoadAsyncCachedBinary (cachedLicense )); } private IEnumerator LoadAsyncCachedBinary (CachedLicense cachedLicense ) { byte[] binary = new byte[] { }; UnityThreadQueue .Instance .Enqueue (() => { binary = LoadCachedBinary (cachedLicense ); }); yield return WaitFor (); if (OnProgress != null) { OnProgress (1.0f); } FileLoadImpl (binary ); } }
84 public class CachedLoader : BaseLoader { private ICoroutineHandlable CoroutineHandler
; private CachedLicense cachedLicense ; private Func<string , byte[]> fileDecryptFunc ; // コールチンを実行する機能、ファイルを復号する機能を注入 public CachedLoader (CachedLicense license , ICoroutineHandlable coroutine , Func<string , byte[]> decryptFunc ) { cachedLicense = license ; CoroutineHandler = coroutine ; fileDecryptFunc = decryptFunc ; } public override void Load() { cachedLicense .Save(); CoroutineHandler .RunMonoCoroutine (LoadAsyncCachedBinary (cachedLicense )); } private IEnumerator LoadAsyncCachedBinary (CachedLicense cachedLicense ) { byte[] binary = new byte[] { }; UnityThreadQueue .Instance .Enqueue (() => { binary = LoadCachedBinary (cachedLicense ); }); yield return WaitFor (); if (OnProgress != null) { OnProgress (1.0f); } FileLoadImpl (binary ); } } 毎フレーム、キャッシュファイルからバ イナリを読むことだけやっている
最終的な構成 85
86
こうすることでモックを使い適切にテストを実装 することができるようになった 87
まとめ • プロダクトコードは様々な要因によって読みにくくなる • リファクタリングをするには「テストを適切に書ける」ような コードを目指そう • そのためには、正しいオブジェクト指向の原則の理解が必 須 88
最後に • SOLID 5原則はちょっと曖昧な表現で、もう少し具体的にどうしたらいいかわからないとい う人は、これがオススメです 89 • ThoughtWorksアンソロジー • 第5章
「オブジェクト指向エクササイズ」 • 以下のように具体的な指針が書いてあ る ◦ 一つのメソッドにインデントは一段 まで ◦ else区は使わない ◦ getter/setterを使わない など