Upgrade to Pro — share decks privately, control downloads, hide ads and more …

コードリファクタリングの手引き

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

 コードリファクタリングの手引き

Avatar for Tetsushi Hasesaku

Tetsushi Hasesaku

February 25, 2019
Tweet

More Decks by Tetsushi Hasesaku

Other Decks in Programming

Transcript

  1. 2 • 晴佐久 哲士 • ピクシブ株式会社 • フルスタックエンジニア ◦ サーバサイド

    (Rails) ◦ フロントエンド (React/Redux, Typescript) ◦ 環境構築 (AWS, Vagrant, Docker) ◦ Unity (C#, C++) • 趣味 ◦ プログラミング ◦ お絵かき ◦ CG制作 @Hare8563 フルスタック エンジニア
  2. 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); } ); } }
  3. 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(); }
  4. 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); } ); } }
  5. SOLIDの5原則 • 単一責任の原則 (Single Responsibility) • オープン・クローズドの原則 (Open/closed) • リスコフの置換原則

    (Liskov substitution) • インターフェース分離の原則 (Interface segregation) • 依存関係逆転の原則 (Dependency inversion) 40
  6. 46 class BaseClass { public abstract void DrawShape() { //

    このメソッドは何か形をキャンバスに書き込む } } class DerivedClass : BaseClass { public override void DrawShape() { // このメソッドはファイルに形に関するメタな情報を書き込む // このような実装をすると、すでに`BaseClass#DrawShape`を使ってキャンバスに書き込んでいるところで置き換えた時 // なんらかのシェイプが書き込まれないため、期待した挙動にならなくなる } }
  7. 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"); } }
  8. 50 // エンジンオブジェクトに依存した実装 class Car { public void Run() {

    Engine engine = new Engine(); // エンジンの状態を変更する engine.State = EngineState.RUN; // エンジンのレバーをあげる engine.PullLever(); // ピストンを動かす engine.MovePiston(); } }
  9. 51

  10. 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(); } }
  11. 53

  12. SOLIDの5原則 • 単一責任の原則 (Single Responsibility) • オープン・クローズドの原則 (Open/closed) • リスコフの置換原則

    (Liskov substitution) • インターフェース分離の原則 (Interface segregation) • 依存関係逆転の原則 (Dependency inversion) 56
  13. 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); } ); } }
  14. LoadAsyn c 62 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン

    ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
  15. LoadAsyn c 63 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン

    ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
  16. もともとのコード 64 CachedLicense newCachedLicense = new CachedLicense(newLicense); DownloadLicense ? cachedDownloadLicense

    = LoadExistLicense (newLicense.licenseid); if (cachedDownloadLicense != null && newCachedLicense .IsSameModel(cachedDownloadLicense .GetValueOrDefault ())) { } else { }
  17. 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)); }); } }
  18. 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を作 成
  19. 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)); }); } } キャッシュがある場合は、キャッシュから読み込むローダをコールバッ クに渡す
  20. 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)); }); } } キャッシュがない場合は、ライセンスをダウンロードしてきて、そこから ダウンロードする機能を渡す
  21. 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で入れている
  22. 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に依存している
  23. LoadAsyn c 72 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン

    ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
  24. 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し ている
  25. LoadAsyn c 74 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン

    ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
  26. 77

  27. 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) { // バイナリデータを読み込む実際の処理 } }
  28. LoadAsyn c 79 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン

    ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
  29. 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); } ); } }
  30. 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); } ); } } ファイルのダウンロード機能をここでは いれて、ロード機能の実態は基底クラ スに入れている
  31. LoadAsyn c 82 キャッシュから読 み込む ダウンロードした ファイルから読み 込む HTTPリクエスト をしてダウン

    ロードする ダウンロードしたの を暗号化して保存 する キャッシュの有 無をチェックす る
  32. 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 ); } }
  33. 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 ); } } 毎フレーム、キャッシュファイルからバ イナリを読むことだけやっている
  34. 86

  35. 最後に • SOLID 5原則はちょっと曖昧な表現で、もう少し具体的にどうしたらいいかわからないとい う人は、これがオススメです 89 • ThoughtWorksアンソロジー • 第5章

    「オブジェクト指向エクササイズ」 • 以下のように具体的な指針が書いてあ る ◦ 一つのメソッドにインデントは一段 まで ◦ else区は使わない ◦ getter/setterを使わない など