App development pragmatic best practices

7ace70cd355db1983dea895fbe01a4ef?s=47 rallat
November 02, 2017

App development pragmatic best practices

Video - https://www.youtube.com/watch?v=8ni8RY__WeU
A talk by Yigit Boyar (@yigitboyar) and Israel Ferrer Camacho (@rallat). In this talk, we want to share best practices to overcome those challenges every app has. We shared a survey asking over a 100 Android devs about their biggest challenges in Android app development (https://goo.gl/forms/QazA4OJUGzs7kCKj2). From analyzing the data from the survey, common challenges surfaced. First, we'll talk about best practices in app organization, architecture, and testing. After that we will be tackling some of Android's specific challenges like supporting OS versions, configuration changes, updating UI with live data (network, database), Fragments, concurrency and testing.

7ace70cd355db1983dea895fbe01a4ef?s=128

rallat

November 02, 2017
Tweet

Transcript

  1. App development pragmatic practices best

  2. App development pragmatic practices

  3. Israel Ferrer Camacho @rallat Yiğit boyar @yigitboyar

  4. Survey Numbers People’s voice

  5. None
  6. Most difficult part of building an app?

  7. None
  8. 1st - Architecture

  9. 1st - Architecture 2nd - Android Specifics

  10. What is a recurring problem in your codebase?

  11. None
  12. 1st - Testing

  13. 1st - Testing Code Bloat

  14. 1st - Testing Code Bloat 2nd - Architecture

  15. Design patterns that you want to replace in your codebase?

  16. 39% 19% 7% 25% 10%

  17. 39% 61%

  18. 61%

  19. God classes 61%

  20. God classes God activities 61%

  21. God classes God activities mvc 61%

  22. God classes God activities mvp 61%

  23. mvvm God classes God activities 61%

  24. God classes God activities mwhatver 61%

  25. God classes God activities lack of architecture mwhatver 61%

  26. God classes God activities lack of architecture singletons mwhatver 61%

  27. Architecture Let’s build a foundation

  28. Why Now?

  29. Why Now? • It was always there, just popular now

  30. Why Now? • It was always there, just popular now

    • Mature ecosystem
  31. Why Now? • It was always there, just popular now

    • Mature ecosystem • Bigger apps, bigger expectations
  32. Why Now? • It was always there, just popular now

    • Mature ecosystem • Bigger apps, bigger expectations • ART >>>> Dalvik
  33. Just Popular Now

  34. Just Popular Now

  35. Just Popular Now • Surely, writing Gmail was not easy

  36. Just Popular Now • Surely, writing Gmail was not easy

    • Works offline
  37. Just Popular Now • Surely, writing Gmail was not easy

    • Works offline • With 32 mb memory
  38. Just Popular Now • Surely, writing Gmail was not easy

    • Works offline • With 32 mb memory • a screen of Pixel XL = 14 mb
  39. User Expectations

  40. User Expectations works great on my EVO 4G

  41. User Expectations works great on my EVO 4G is there

    any plans of adding the zoom functionality in the gmail app?
  42. User Expectations works great on my EVO 4G is there

    any plans of adding the zoom functionality in the gmail app? more and more emails are becoming HTML version now a days
  43. User Expectations works great on my EVO 4G is there

    any plans of adding the zoom functionality in the gmail app? more and more emails are becoming HTML version now a days and it would be really nice to have the ability to zoom in and out of them inside gmail
  44. User Expectations works great on my EVO 4G is there

    any plans of adding the zoom functionality in the gmail app? more and more emails are becoming HTML version now a days and it would be really nice to have the ability to zoom in and out of them inside gmail September 21, 2010 at 1:53 PM
  45. Mature Ecosystem

  46. Mature Ecosystem • Need to write

  47. Mature Ecosystem • Need to write • networking code

  48. Mature Ecosystem • Need to write • networking code •

    image loading
  49. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments
  50. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small
  51. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small
  52. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write
  53. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write • retrofit
  54. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write • retrofit • glide, picasso
  55. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write • retrofit • glide, picasso • fragments, conductor
  56. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write • retrofit • glide, picasso • fragments, conductor • split APKs etc
  57. A Shift in Focus Primitive functionality 2010

  58. A Shift in Focus Primitive functionality Complex features 2010 2017

  59. Bigger Apps Bigger Expectations

  60. Bigger Apps Bigger Expectations • 60 fps

  61. Bigger Apps Bigger Expectations • 60 fps • lots of

    functionality
  62. Bigger Apps Bigger Expectations • 60 fps • lots of

    functionality • lots of integrations
  63. Bigger Apps Bigger Expectations • 60 fps • lots of

    functionality • lots of integrations • html everywhere
  64. Bigger Apps Bigger Expectations • 60 fps • lots of

    functionality • lots of integrations • html everywhere • works fully offline
  65. Bigger Apps Bigger Expectations • 60 fps • lots of

    functionality • lots of integrations • html everywhere • works fully offline • rich text editor
  66. Yiğit’s architecture recommendations

  67. Today

  68. Today RxJava Dagger MVP Reactive Cross Platform MVVM Clean Architecture

    Unit Testing Robolectric Data Binding Material Design Architecture Components Flutter React Native
  69. What To Do? One Rule

  70. Separation of Concerns

  71. Separation of Concerns

  72. Separation of Concerns • Have it in day one, stick

    to it
  73. Separation of Concerns • Have it in day one, stick

    to it • Build architecture over time, as needed
  74. Think Like Recursion

  75. Think Like Recursion • A simple base case (or cases)—a

    terminating scenario that does not use recursion to produce an answer
  76. Think Like Recursion • A simple base case (or cases)—a

    terminating scenario that does not use recursion to produce an answer • A set of rules that reduce all other cases toward the base case
  77. Do Not Over-Engineer

  78. Do Not Over-Engineer • Libraries, guides are tools not goals

  79. Do Not Over-Engineer • Libraries, guides are tools not goals

    • The goal is to ship and maintain
  80. Do Not Over-Engineer • Libraries, guides are tools not goals

    • The goal is to ship and maintain • Don’t use something just because X does
  81. Do Not Over-Engineer • Libraries, guides are tools not goals

    • The goal is to ship and maintain • Don’t use something just because X does • Their problem is not yours, neither their solution.
  82. Israel’s architecture recommendation

  83. MVWW

  84. WW is for “whatever works”

  85. Architecture is not a dogma

  86. None
  87. Apply Principles

  88. with Consistency Apply Principles

  89. Principles can evolve

  90. Dependency Injection

  91. Dependency Injection

  92. Dependency Injection • Allows to declare dependencies of an object

    upfront.
  93. Dependency Injection • Allows to declare dependencies of an object

    upfront. • This helps to easily mock dependencies and unit test the object
  94. Single Responsibility

  95. None
  96. View layer

  97. View manipulation

  98. View manipulation

  99. View manipulation Logic layer

  100. View manipulation Screen behavior

  101. View manipulation Screen behavior

  102. Data layer View manipulation Screen behavior

  103. Repository Interface View manipulation Screen behavior Network DB Memory cache

  104. Complex screens

  105. Complex screens • Multiple logic layer components each with a

    single responsibility.
  106. Complex screens • Multiple logic layer components each with a

    single responsibility. • Logic layer components can subscribe to events tied to their logic (E.g. Rx, EventBus)
  107. Shared logic

  108. Shared logic • E.g: Like a tweet happens in the

    timeline and the profile, tweet detail, DM, moments, pretty much everywhere.
  109. Shared logic • E.g: Like a tweet happens in the

    timeline and the profile, tweet detail, DM, moments, pretty much everywhere. • If the view is shared you can create a 3 layered complete feature.
  110. Shared logic • E.g: Like a tweet happens in the

    timeline and the profile, tweet detail, DM, moments, pretty much everywhere. • If the view is shared you can create a 3 layered complete feature. • If the view is not share then add another layer…
  111. Data layer View layer Logic layer

  112. Use cases (Like logic) Screen behavior View layer Data layer

  113. Goal

  114. Goal • Each layer has a reason to exist.

  115. Goal • Each layer has a reason to exist. •

    Consistency in codebase
  116. Goal • Each layer has a reason to exist. •

    Consistency in codebase • Testable logic layer
  117. Goal • Each layer has a reason to exist. •

    Consistency in codebase • Testable logic layer • Reusable of logic layer
  118. Android Specifics Mon amie

  119. Lifecycles

  120. Lifecycles • Embrace lifecycles, cannot ignore

  121. Lifecycles • Embrace lifecycles, cannot ignore • It is a

    solved problem, if you follow guides
  122. ViewModel Repository Activity HowTo: Separation update UI hold data data

    logic fetch data
  123. ViewModel Repository Activity HowTo: Separation update UI hold data data

    logic fetch data
  124. ViewModel Repository Activity HowTo: Separation update UI hold data data

    logic Lifecycle aware fetch data
  125. 4 Options • Manual • LiveData • AutoDisposable • Data

    Binding
  126. Manual Lifecycle Handling

  127. class MyViewModel { val users : Observable<List<User>> = // get

    it from Repository }
  128. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() }
  129. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onStart() { subscription = model.users.observe(adapter::setList) } }
  130. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onStart() { subscription = model.users.observe(adapter::setList) } fun onStop() { subscription?.cancel() } }
  131. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } }
  132. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(adapter::setList) } }
  133. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(adapter::setList) } fun onStop() { subscription?.cancel() } }
  134. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(adapter::setList) } fun onStop() { subscription?.cancel() } }
  135. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(adapter::setList) } fun onStop() { subscription?.cancel() } } might happen after onStop
  136. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { if (getLifecycle().isAtLeast(STARTED)) { subscription = subscription ?: model.users.observe(adapter::setList) } } fun onStop() { subscription?.cancel() } }
  137. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { if (getLifecycle().isAtLeast(STARTED)) { subscription = subscription ?: model.users.observe(adapter::setList) } } fun onStop() { subscription?.cancel() } } need to check current state
  138. Data Binding

  139. class MyViewModel { val users : Observable<List<User>> = // get

    it from Repository }
  140. class MyViewModel { val users : ObservableList<User> = // get

    it from Repository }
  141. class MyFragment : Fragment { val model by viewModel<MyViewModel>() val

    binding = // get binding instance fun onCreate() { binding.users = model.users } }
  142. class MyFragment : Fragment { val model by viewModel<MyViewModel>() val

    binding = // get binding instance fun onCreate() { button.onClick(this::loadUsers) } }
  143. class MyFragment : Fragment { val model by viewModel<MyViewModel>() val

    binding = // get binding instance fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { binding.users = model.users } }
  144. LiveData

  145. class MyViewModel { val users : LiveData<List<User>> = //get it

    from Repository }
  146. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users.observe(this, adapter::setList) } }
  147. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users.observe(this, adapter::setList) } }
  148. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users.observe(this, adapter::setList) } }
  149. class MyFragment : Fragment { var subscription : Unit? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } }
  150. class MyFragment : Fragment { var subscription : Unit? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } }
  151. class MyFragment : Fragment { var subscription : Unit? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(this, adapter::setList) } }
  152. AutoDisposable

  153. class MyViewModel { val users : ObservableList<User> = // get

    it from Repository }
  154. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users .autoDisposeWith(this) .subscribe(adapter::setList) } }
  155. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users .autoDisposeWith(this) .subscribe(adapter::setList) } }
  156. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users .autoDisposeWith(this) .subscribe(adapter::setList) } }
  157. class MyFragment : Fragment { val model by viewModel<MyViewModel>() var

    disposable : Disposable? = null fun onCreate() { button.onClick(this::loadUsers) } }
  158. class MyFragment : Fragment { val model by viewModel<MyViewModel>() var

    disposable : Disposable? = null fun onCreate() { button.onClick(this::loadUsers) } }
  159. class MyFragment : Fragment { val model by viewModel<MyViewModel>() var

    disposable : Disposable? = null fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { disposable = disposable ?: model .autoDisposeWith(this) .subscribe(adapter::setList) } }
  160. Configuration changes

  161. Config Change != App Restart

  162. Config Change App Restart

  163. Config Change App Restart

  164. Config Change • Decouple UI and data App Restart •

    Drive UI from disk
  165. Config Change • Decouple UI and data • Use ViewModel

    App Restart • Drive UI from disk • Use savedInstanceState
  166. Configuration Changes Activity hold data fetch data update UI data

    logic
  167. Configuration Changes Activity hold data fetch data update UI data

    logic
  168. ViewModel Repository Activity Configuration Changes update UI hold data data

    logic fetch data
  169. ViewModel Repository Activity Configuration Changes update UI hold data data

    logic fetch data
  170. Configuration Changes

  171. Configuration Changes • Views restore their states automatically

  172. Configuration Changes • Views restore their states automatically • If

    UI is data drive, no extra work necessary
  173. App Restart

  174. App Restart • Nothing in memory survives :(

  175. App Restart • Nothing in memory survives :( • NBU

    was one of the top problems in the survey
  176. App Restart ViewModel Repository Activity

  177. App Restart ViewModel Repository Activity Saved State Disk

  178. App Restart Saved State Disk • View’s information (e.g. RecyclerView

    position) • UI’s arguments • Application data
  179. A Practical Example

  180. UserProfileActivity

  181. ViewModel Activity App Restart Saved State • userId userId

  182. ViewModel Activity App Restart Saved State • userId userId

  183. ViewModel Activity App Restart Saved State • userId userId

  184. ViewModel App Restart userId Repository Disk fetch data

  185. Repository ViewModel App Restart Disk fetch data userId userId

  186. Repository ViewModel App Restart Disk fetch data userId userId

  187. Repository ViewModel App Restart Disk fetch data userId userId

  188. Repository ViewModel App Restart Disk fetch data LiveData<User> User

  189. Repository ViewModel App Restart Disk fetch data LiveData<User> User

  190. Repository ViewModel Activity App Restart LiveData<User> User

  191. Repository ViewModel Activity App Restart LiveData<User> User

  192. Repository ViewModel Activity Configuration Changes LiveData<User> User

  193. Repository ViewModel Activity Configuration Changes LiveData<User>

  194. ViewModel Activity Configuration Changes Saved State • userId LiveData<User> User

    userId
  195. ViewModel Activity Configuration Changes Saved State • userId LiveData<User> User

    userId
  196. ViewModel Activity Configuration Changes Saved State • userId LiveData<User> User

    User userId
  197. ViewModel Activity Configuration Changes Saved State • userId LiveData<User> User

    User
  198. ViewModel Activity Configuration Changes Saved State • userId LiveData<User> User

    User User
  199. ViewModel Activity Configuration Changes Saved State • userId LiveData<User> User

    User
  200. Fragmentation how to deal with it

  201. Types • Hardware • Screen Sizes and Densities • SDK

    version • Open GL
  202. Screen fragmentation • Flexible UIs with ConstraintLayout (9+) • Create

    custom layouts for specific size/densities • Use wrap_content, match_parent • Density independent units (dp, sp) • VectorDrawables (limitations)
  203. Hardware Fragmentation • Generate a class that defines hardware profile

    - low, medium, high depending on: RAM and CPU cores and frequency. • Yearclass lib foundation to classify hardware profiles https://github.com/facebook/device-year-class
  204. when(applicationContext.hardwareProfiler) { HardwareProfile.HIGH -> animator.runAll() HardwareProfile.MEDIUM -> animator.runMin() HardwareProfile.LOW ->

    finish() }
  205. interface Animator { fun run() } class AnimatorFactory { fun

    animatorForProfile(profile: HardwareProfile): Animator = when (profile) { HardwareProfile.HIGH -> AnimatorHighPerf() HardwareProfile.MEDIUM -> AnimatorMediumPerf() else -> object : Animator { override fun run() { } } } } // and then val animator = AnimatorFactory().animatorForProfile(applicationContext.hardwareProfiler) animator.run()
  206. SDK Fragmentation

  207. Dependencies your app needs them

  208. RxJava

  209. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError);
  210. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError);
  211. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi
  212. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi
  213. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi Reactive push
  214. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi Reactive push
  215. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi Reactive push Useful built-in operators
  216. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi Reactive push Useful built-in operators
  217. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi Reactive push Easy and Declarative Threading Useful built-in operators
  218. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi Reactive push Easy and Declarative Threading Useful built-in operators
  219. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi Reactive push Easy case handling Easy and Declarative Threading Useful built-in operators
  220. None
  221. None
  222. “With great power comes great responsibility”

  223. Dagger 2

  224. Build dependency graph

  225. None
  226. @Inject

  227. @Inject @Module & @Provide

  228. @Inject @Module & @Provide @Component

  229. @Inject @Module & @Provide @Component @Scope

  230. None
  231. RepoComponent

  232. RepoComponent NetworkModule

  233. RepoComponent DbModule NetworkModule

  234. RepoComponent TwitterActionsModule DbModule NetworkModule

  235. RepoComponent TwitterActionsModule DbModule NetworkModule LikeAction

  236. RepoComponent TwitterActionsModule DbModule NetworkModule LikeAction RetweetAction

  237. RepoComponent TwitterActionsModule DbModule NetworkModule LikeAction RetweetAction ReplyAction

  238. RepoComponent TwitterActionsModule DbModule NetworkModule LikeAction RetweetAction ReplyAction TimeLine

  239. RepoComponent ApplicationComponent TwitterActionsModule DbModule NetworkModule LikeAction RetweetAction ReplyAction TimeLine

  240. Warnings • Rx and Dagger help to scale your app.

    • It comes at a cost. Learning their DSL, idioms. The team needs to learn it and grow criteria for code reviews. Exception: your name is Jake or Ron and you work at Google. • Moreover, many new hires will need to learn Android and these new idioms all at once.
  241. Custom frameworks Warnings from custom land

  242. • Does Android provide it? • Broadly abroad open-source option?

    • Can you find a simpler approach? • Ask your coworkers or industry peers to know if makes sense. Sometimes we have great ideas in our caves, but they aren’t so much.
  243. Warning • When you commit to build your own framework,

    you are the one responsible for maintain it. • New comers to the code base will have to learn it.
  244. Refactor Winds of change

  245. Refactor Options

  246. Refactor Options • Every week, month, quarter

  247. Refactor Options • Every week, month, quarter • When inevitable

  248. Refactor Options • Every week, month, quarter • When inevitable

    • When you feel like ?
  249. Refactor Options • Every week, month, quarter • When inevitable

    • When you feel like ?
  250. Rule of Tree

  251. Rule of Tree • By Don Roberts

  252. Rule of Tree • By Don Roberts • 1st time

    • just do it!
  253. Rule of Tree • By Don Roberts • 1st time

    • just do it! • 2nd time • ok to copy
  254. Rule of Tree • By Don Roberts • 1st time

    • just do it! • 2nd time • ok to copy • 3rd time • Now you refactor
  255. Refactor Tips

  256. Refactor Tips • Small bites

  257. Refactor Tips • Small bites • Add it into the

    task estimation
  258. Refactor Tips

  259. Refactor Tips • You touch it, you fix it!

  260. Refactor Tips • You touch it, you fix it! •

    Refactor a file when it is touched
  261. Refactor Tips • You touch it, you fix it! •

    Refactor a file when it is touched • Separate the refactor CL from the original
  262. Refactor Tips

  263. Refactor Tips • Testing is a must!

  264. Refactor Tips • Testing is a must! • If there

    are tests, make sure they pass
  265. Refactor Tips • Testing is a must! • If there

    are tests, make sure they pass • If there are no tests, first write them
  266. Testing

  267. Unit test logic layer

  268. Impossible to test legacy code

  269. Espresso test recorder

  270. Separation of concerns Dependency Injection

  271. UI test with Espresso

  272. Best answers

  273. What is a recurring problem in your codebase?

  274. Keyboard

  275. Patterns you dislike and will like to replace in your

    code base?
  276. Hungarian notation

  277. What is the best part of your codebase?

  278. None

  279. None.

  280. Thank you! Israel Ferrer Camacho @rallat Yiğit boyar @yigitboyar