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
Android開発における、レガシーコードと戦うためのリファクタリング
Search
Jumpei Yamamoto
August 20, 2015
Programming
3
1.8k
Android開発における、レガシーコードと戦うためのリファクタリング
2015/8/20 マネーフォワード×Eight -モバイルアプリ勉強会
Android開発におけるリファクタリングの実際を紹介します。
Jumpei Yamamoto
August 20, 2015
Tweet
Share
More Decks by Jumpei Yamamoto
See All by Jumpei Yamamoto
KotlinでDSLを作る #Kotlin_Sansan
boohbah
2
1.6k
みんな大好き拡張関数 #kotlin_sansan
boohbah
1
9.1k
sealed class in Kotlin1.1
boohbah
1
1.7k
Sansanではたらくアプリエンジニアの20%ルール
boohbah
0
740
KotlinでDSL
boohbah
0
9.1k
ObservableArrayとPikkel
boohbah
2
1.3k
KotlinでPhantom Type #kotlin_sansan
boohbah
2
3.9k
#jkug Kotlinのclass delegation
boohbah
1
350
#DroidKaigi 既存のAndroidプロジェクトに Kotlinを導入した話
boohbah
5
2.6k
Other Decks in Programming
See All in Programming
CQRS/ESのクラスとシステムフロー ~ RailsでフルスクラッチでCQRSESを組んで みたことから得た学び~
suzukimar
0
150
Ruby で作る RISC-V CPU エミュレーター / RISC-V CPU emulator made with Ruby
hayaokimura
5
1.2k
一緒に働きたくなるプログラマの思想 #QiitaConference
mu_zaru
84
21k
データと事例で振り返るDevin導入の"リアル" / The Realities of Devin Reflected in Data and Case Studies
rkaga
3
2.9k
OpenTelemetryで始めるベンダーフリーなobservability / Vendor-free observability starting with OpenTelemetry
seike460
0
140
TVer iOSチームの共通認識の作り方 - Findy Job LT iOSアプリ開発の裏側 開発組織が向き合う課題とこれから
techtver
PRO
0
110
Digging into the Matrix: Practicing Code Archaeology
arthurdoler
PRO
0
120
ソフトウェア品質特性、意識してますか?AIの真の力を引き出す活用事例 / ai-and-software-quality
minodriven
16
4.9k
マテリアルって何者?RealityKitで扱うマテリアル入門
nao_randd
0
120
Cloudflare Workersで進めるリモートMCP活用
syumai
12
1.7k
UMAPをざっくりと理解 / Overview of UMAP
kaityo256
PRO
3
1.6k
Design Pressure
hynek
0
180
Featured
See All Featured
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
Reflections from 52 weeks, 52 projects
jeffersonlam
349
20k
Product Roadmaps are Hard
iamctodd
PRO
53
11k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
34
2.2k
Adopting Sorbet at Scale
ufuk
76
9.4k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
32
5.8k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
24
2.8k
Documentation Writing (for coders)
carmenintech
71
4.8k
Testing 201, or: Great Expectations
jmmastey
42
7.5k
The Cult of Friendly URLs
andyhume
78
6.4k
How GitHub (no longer) Works
holman
314
140k
The Cost Of JavaScript in 2023
addyosmani
49
7.9k
Transcript
レガシーコードと 戦うためのリファクタリング 2015/8/20 マネーフォワード×Eight -‐‑‒モバイルアプリ勉強会-‐‑‒ Sansan株式会社 Eight事業部
⼭山本純平
⾃自⼰己紹介 ⼭山本純平 Sansan株式会社 Eight事業部 Androidアプリやってます 2015年年6⽉月⼊入社
趣味はギター ソウルミュージック好き @boohbah
前職では ガラケーのネイティブアプリ (メーラーとか) スマートフォンの プラットフォーム開発
ウェブポ Flashの年年賀状サービス Windowsの WPFを使った 写真アプリ
scene という写真アプリの iOS版を作ったりしていました
None
Eightは この7⽉月に全⾯面リニューアル 名刺刺管理理アプリからビジネスネットワーキングサービスへ進化
6⽉月から本格的な開発が始まり かなりタイトなスケジュール そんな中にEightの開発にJoinしたのですが…
前任者が退職 引き継ぎ期間は2⽇日 資料料はほとんどなし
引き継ぎ1⽇日⽬目 • まずは残タスクの洗い出し • スケジュールの確認
残りはペアプロ 仕掛中のタスクを丸⼀一⽇日⼀一緒に実装 開発環境の整備 設計・実装内容の確認 気になる実装に関する経緯 これが最も効率率率的に情報を引き出す⼿手段
※引き継ぎ資料料がない場合
さて本題
レガシーコードと 戦うためのリファクタリング
タイトなスケジュールの中 なぜリファクタリングか?
–Martin Fowler ʮϦϑΝΫλϦϯάʯ “リファクタリングはソフトウェアを 理理解しやすくする”
複雑なコードを理理解する 理理解した結果を後に残すことができる 既存のコードを理理解しながら開発を進めるにあたって リファクタリングがもっとも効率率率よく進められる⽅方法
今⽇日紹介する リファクタリングの実際 • 名前をつける • FatなActivityと戦う
名前をつける
public void onLoadFinished(Integer result) { switch (result) { case 10:
showErrorAlertDialog(); break; case 11: showErrorAlertDialog(); break; case 12: showErrorAlertDialog(); break; case 20: case 30: case 31: showErrorAlertDialog(); break; case 32: showErrorAlertDialog(); break; case 1: break; } } 何をあらわすのか わかりません!! 問題点
public enum FirstMyCardEntryStatus { /// ໊ը૾ߘཁٻʢະߘঢ়ଶʣ BEFORE_UPLOAD(10), /// ໊ը૾࠶ߘཁٻʢະߘঢ়ଶʣ REQUEST_REUPLOAD(11),
/// αϜωΠϧԽલঢ়ଶ BEFORE_THUMBNAIL(20), /// ໊ը૾ΤϯτϦૹ৴લ(໊ը૾ΤϯτϦొલ) BEFORE_SEND_ENTRY(30), /// ໊ը૾ΤϯτϦૹ৴ࡁΈ(໊ը૾ΤϯτϦొྃ) SENT_ENTRY(31), /// ໊ը૾ΤϯτϦड৴ࡁΈ(໊ը૾ೖྗྃ) FINISHED_ENTRY(32), /// ໊ը૾ΤϯτϦ࠷ॳͷϓϩϑΟʔϧͱͯ͠ηοτࡁΈ SET_AS_FIRST_PROFILE(40); private final int mStatusCode; FirstMyCardEntryStatus(int statusCode) { mStatusCode = statusCode; } public static FirstMyCardEntryStatus of(int code) { for (FirstMyCardEntryStatus e : FirstMyCardEntryStatus.values()) { if (code == e.mStatusCode) { return e; } } throw new IllegalArgumentException(); } public int getStatusCode() { return mStatusCode; } public boolean isAuthorized() { return this == SET_AS_FIRST_PROFILE; } } enumを使う 条件判定にも名前を つけられる
統⼀一した名前をつける
運営からのお知らせ ServiceMessage ConductNews FeedAnnouncement 問題点 同じ機能でも実装箇所によって 違う名前がついているため
コードがわかりにくい
統⼀一した名前 ユビキタス⾔言語 単語帳
実はこれまでも プロジェクト内単語帳は存在 ⽇日本語としては共通の単語の コンセンサスはある程度度のとれていた プログラム上の機能名(英語)は 開発者のセンスによる
⽇日本語を英語に訳す段階で ⽤用語がバラバラになってしまっていた
ユビキタス⾔言語には英訳も必要!! iOS -‐‑‒ Androidで命名を合わせるようにコミュニ ケーションをとる ソースコード上もわかりやすい名前に統⼀一
Fatな Activity/Fragmentと戦う
問題点 • 2000⾏行行超えるActivity / Fragment • メンテナンス、変更更が⾮非常に困難
Activityでは何をするべきか?
ドメイン駆動設計のレイヤアーキテクチャ Presentation Layer 7JFX 1SFTFOUFS Domain Layer 6TFDBTF Infrastructure Layer
Entity %# /FUXPSL Application Layer Activity アプリケーションレイヤ アプリケーションの活動を 調整する薄いレイヤ。 ビジネスロジックを含まな い。また、ビジネスオブジェ クトの状態を保持しない。 しかし、アプリケーション の処理理の進み具合を保持す ることがある。
Activityは ライフサイクルの管理理と 画⾯面遷移のハンドリング に集中するべき
実際は… Presentation Layer 7JFX Presenter Domain Layer Use-‐‑‒case Infrastructure Layer
Entity %# /FUXPSL Application Layer Activity Presenter, Use-‐‑‒case モジュールが存在せず その実装がすべて Activity/Fragmentに 含まれていた Activity
Activityを軽量量化する Presentation Layer 7JFX Presenter Domain Layer Use-‐‑‒case Infrastructure Layer
Entity %# /FUXPSL Application Layer Activity どうやってUse-‐‑‒case部 分を切切り出すか? Activity どうやってPresenter 部分を切切り出すか?
Activityから Use-‐‑‒caseを切切り出す
例例) メッセージをやりとりする画⾯面 メッセージを投稿する 定期的にサーバーに更更新をチェック ※ちなみにこのActivityは900⾏行行
ボタンをクリックしたらMessageをポストして結果を保存 @Override public void onClick(View v) { switch (v.getId()) {
case R.id.message_send_button: Message message = new Message(postMessageText); sendMessage(message); break; default: break; } } private void sendMessage(final Message message) { PostMessageRequest request = new PostMessageRequest(message, new Listener<JsonNode>() { @Override public void onResponse(JsonNode response) { JsonNode messageNode = response.get("message"); EightMessage message = new EightMessage(messageNode); mMessageDB.insert(message); mMessageList.add(message); // දࣔΛupdate } }); request.post(); } MessageActivity MessageDB mMessageDB; ArrayList<Message> mMessageList; -‐ void sendMessage(Message); ボタンをクリックしたら 通信して あれDBに保存しつつ 画⾯面に反映するActivity
MessageActivity MessageUsecase mMessageUsecase; MessageRepository mMessageRepository MessageRepository MessageDB mMessageDB;
ArrayList<Message> mMessageList; + void add(Message); + void setUpdateListener(Listener) MessageUsecase MessageRepository mMessageRepository; -‐ void sendMessage(Message); sendMessage() add() Listen 通信して結果を保存する 処理理をUsecaseとして分離離 DBに保存しつつ Listを管理理するクラスを Repositoryとして分離離 Repositoryからの updateの通知を受けてViewをupdate
Presentation Layer 7JFX Presenter Domain Layer 6TFDBTF Infrastructure Layer Entity
%# /FUXPSL Application Layer Activity Use-‐‑‒case部分を 切切り出せた! Activity
Activityから Presenterを切切り出す
7JFX Activity 6TFDBTF onClick等のイベント 表⽰示データのセット public class FeedFragment extends AbstractPagerFragment
{ private RecyclerView mFeedListView; private SwipeRefreshLayout mSwipeRefreshLayout; private TextView mTalkIconBadge; private TextView mNewsIconBadge; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_feed, container, false); mFeedListView = (RecyclerView) view.findViewById(R.id.fragment_feed_recycler_view); mTalkIconBadge = (TextView) view.findViewById(R.id.action_bar_badge_icon_count); mNewsIconBadge = (TextView) view.findViewById(R.id.action_bar_badge_icon_count); mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.fragment_feed_list_refresh); return view; } これまでの実装: Viewを制御するコードがすべてActivityに
MVVMパターン Data Binding from Android Studio 1.3
7JFX 7JFX.PEFM Activity 表⽰示データの取得 6TFDBTF onClick等のイベント public class CreatePostActivity extends
Activity { CreatePostViewModel mViewModel; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mViewModel = new CreatePostViewModel(); ActivityCreatePostBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_create_post); binding.setViewModel(mViewModel); }
Presentation Layer 7JFX ViewModel Domain Layer 6TFDBTF Infrastructure Layer Entity
%# /FUXPSL Application Layer Activity Presenter部分を ViewModelとして 切切り出す Use-‐‑‒case部分を 切切り出せた!
Presentation Layer 7JFX ViewModel Domain Layer 6TFDBTF Infrastructure Layer Entity
%# /FUXPSL Application Layer Activity Activityは ライフサイクル の管理理 画⾯面遷移の管理理 に集中すること ができる
そして、理理解しやすい メンテしやすいアプリケーションに
最後に
$PQZSJHIU4BOTBO *OD"MMSJHIUTSFTFSWFE 4BOTBOҰॹʹ৽͍͠ՁΛ࡞͍ͬͯ͘ ؒΛ͕͍ͯ͞͠·͢ɻ 3VCZ 3VCZPO3BJMT ʢ8FCΞϓϦέʔγϣϯʣ $ɼ"41/&5.7$ ʢ8FCΞϓϦέʔγϣϯʣ J04"OESPJEΞϓϦ
ݸਓ໊͚ཧΞϓϦʮ&JHIUʯ ໊σʔλԽࢄॲཧγεςϜ ๏ਓ໊͚ཧαʔϏεʮ4BOTBOʯ ๏ਓ໊͚ཧαʔϏε ʮ4BOTBOʯ ݸਓ໊͚ཧΞϓϦʮ&JHIUʯ ΤϯδχΞืूத 4BOTBO࠾༻ ݕࡧ SFDSVJU!TBOTBODPN·Ͱ ͓ؾܰʹ͝࿈བྷ͍ͩ͘͞ɻ ڵຯͷ͋Δํ
ご清聴 ありがとうございました。