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

Refactoring with Branch by Abstraction and a Kotlin twist

Refactoring with Branch by Abstraction and a Kotlin twist

An approach for making large-scale changes in a system with the Branch by Abstraction technique, and how Kotlin can help to make the change in a timely manner.

https://www.droidship.com/posts/branch-by-abstraction/

The presentation took place at GDG Android Athens Meetup.

https://www.meetup.com/GDG-Android-Athens/events/247078126/

Kostas Tsalikis

January 31, 2018
Tweet

Other Decks in Technology

Transcript

  1. Roadmap • A case of a large-scale refactoring • Enter

    Branch by Abstraction • Pros and Cons • Show me the code! • Excuse me, where is my Kotlin twist? • Back to Refactoring • Final thoughts • References • Q&A
  2. A case of a large-scale refactoring. - “We need to

    migrate to library X that solves problem Y.” Usual steps: 1. Create a new branch in git. 2. Begin the new implementation. 3. Once the huge API is ready - after a couple of weeks - submit a pull request. 4. Merge.
  3. A case of a large-scale refactoring. Some constraints: 1. Make

    the transition as painless as possible. 2. Provide feedback and review code as soon as possible. 3. Everything must work all the time. 4. Even if the new feature is not complete, push the delivery button at will.
  4. Enter Branch by Abstraction Martin Fowler: “Branch by Abstraction is

    a technique for making a large-scale change to a software system in gradual way that allows you to release the system regularly while the change is still in-progress.”
  5. Enter Branch by Abstraction How it works: 1. Add an

    abstraction over the part of the system you want to replace. 2. Refactor so all client code uses the abstraction layer. 3. Add new implementation, and make the abstraction delegate appropriately. 4. Remove the old implementation when no longer used. 5. Once the refactoring is complete delete the abstraction layer.
  6. Enter Branch by Abstraction - Steps 1 & 2 -

    Add an Abstraction and Refactor Client Code Client Code Old implementation Abstraction Layer
  7. Enter Branch by Abstraction - Step 3 - Add new

    implementation Client Code Client Code Old implementation Abstraction Layer New implementation
  8. Enter Branch by Abstraction - Step 4 - Remove the

    old implementation Client Code Client Code Old implementation Abstraction Layer New implementation
  9. Enter Branch by Abstraction - Step 5 - Remove the

    Abstraction layer Client Code Client Code Abstraction Layer New implementation
  10. Enter Branch by Abstraction - Variations How does the client

    code gets its dependencies? • Can we change the way the implementation is provided in one place with Dependency Injection? • Do we need to make a local change in the way the dependencies are provided?
  11. Enter Branch by Abstraction - Variations The structure of the

    system: • might make it easy to make submodules and apply the technique on them • might expose an interface or may not • might be very poor
  12. Enter Branch by Abstraction - Variations The current interface may

    be subject to change: • Due to the way layers communicate with each other(i.e. Change from callbacks to RxJava) • Due to the fact that the wrong messages(objects) are communicated between the layers
  13. Enter Branch by Abstraction - Variations The development process of

    the team: • May leverage the power of feature flags • May make use of code reviews
  14. Cons - Development process may slow down. - Difficult to

    apply if the code needs external audit. - The code must have a reasonable structure already.
  15. A simple but not trivial example. Create an app that

    stores famous quotes locally in your phone. In the role of the library to be replaced: SqlBrite2 Gradually migrate to Room without losing your already awesome saved quotes.
  16. Show me the code! - Initial Structure public interface QuotesDataSource

    { Observable<List<Quote>> getSavedQuotes(); Observable<Boolean> add(Quote quote); } public class SqlBriteDataSource implements QuotesDataSource { @Override public Observable<List<Quote>> getSavedQuotes() { // select all with sqlBrite } @Override public Observable<Boolean> add(Quote quote) { // insert with sqlBrite } }
  17. Show me the code! - Steps 1 & 2 public

    class MixedDataSource implements QuotesDataSource { } private final QuotesDataSource oldDataSource; public MixedDataSource(QuotesDataSource oldDataSource) { this.oldSDataSource = oldDataSource; } @Override public Observable<Boolean> add(Quote quote) { return oldDataSource.add(quote); } @Override public Observable<List<Quote>> getSavedQuotes() { return oldDataSource.getSavedQuotes(); }
  18. Excuse me, where is my Kotlin twist? public class MixedDataSource

    implements QuotesDataSource { private final QuotesDataSource oldDataSource; public MixedDataSource(QuotesDataSource oldDataSource) { this.oldSDataSource = oldDataSource; } @Override public Observable<List<Quote>> getSavedQuotes() { return oldDataSource.getSavedQuotes(); } @Override public Observable<Boolean> add(Quote quote) { return oldDataSource.add(quote); } } class MixedDataSource(private val oldDataSource: QuotesDataSource) : QuotesDataSource { override fun getSavedQuotes(): Observable<MutableList<Quote>> { return oldDataSource.savedQuotes } override fun add(quote: Quote?): Observable<Boolean> { return oldDataSource.add(quote) } }
  19. Excuse me, where is my Kotlin twist? class MixedDataSource(private val

    oldDataSource: QuotesDataSource) : QuotesDataSource by oldDataSource
  20. Back to Refactoring - Step 3 class MixedDataSource(private val oldDataSource:

    QuotesDataSource, private val newDataSource: QuotesDataSource) : QuotesDataSource by oldDataSource { } override fun getSavedQuotes(): Observable<MutableList<Quote>> { return newDataSource.savedQuotes }
  21. Back to Refactoring - Step 3, Continuation class MixedDataSource(private val

    oldDataSource: QuotesDataSource, private val newDataSource: QuotesDataSource) : QuotesDataSource by oldDataSource { override fun getSavedQuotes(): Observable<MutableList<Quote>> { return newDataSource.savedQuotes } } override fun add(quote: Quote): Observable<Boolean> { return newDataSource.add(quote) }
  22. Back to Refactoring - Step 4 class MixedDataSource(private val oldDataSource:

    QuotesDataSource, private val newDataSource: QuotesDataSource) : QuotesDataSource by newDataSource
  23. Final thoughts • Better suites large refactoring. • Coexisting implementations

    not always easy to achieve. • Nothing too exotic. • Works better with well-structured code.