iT邦幫忙

2021 iThome 鐵人賽

DAY 21
2
Mobile Development

Jetpack Compose X Android Architecture X Functional Reactive Programming系列 第 21

新需求與架構設計的演進

在前面的二十天中我們完成了基本需求,但是這樣的進度在真實的專案中只是剛開始而已,有可能到目前為止做的只是 prototype,大家玩了一下覺得很喜歡,想要將他變成更完整的 App 再上架,又或是這是一個 MVP(minimum viable product),而且已經上架經過市場驗證,想要再加更多功能以符合使用者的期待。不管如何,在功能越來越多的情況下,核心邏輯會越來越複雜,原本的程式架構已經支援不了新的需求,架構設計的改變是勢在必行的。

當然這時候一定會有不同聲音出現,對於產品這邊的角色當然希望功能會快出來越好,對於工程這邊的角度則是希望新功能盡量不要犧牲開發的品質,這兩邊的角色通常是站在對立面,犧牲掉任何一邊都不是好事。對於開發來說,最怕產品端朝令夕改,今天晚上提的需求明天就要,一切彷彿沒有原則可言,一個奇怪的需求就要使得原本規劃好的架構大改。那產品端怕什麼呢?怕開發跟他們說這個做不到,架構要大改,至少要花兩個月的時間重構才能開發這個新功能。

其實這裡有個很有趣的一點,就是“架構”。工程師很常犯一個毛病,喜歡設計一個厲害、爲未來一切都規劃好、自己很滿意的架構,抽出未來需要重用的函式庫或元件,做著 infrastructure 層級的工作。然而過了一年後一切不如所願,自己的規劃的共用元件無法在新功能派上用場,很多寫好的,放在一旁的函式庫從未被使用,自己非常滿意的 infrastrcture 元件限制了新公能的開發。時間久了,當功能越來越難開發時,通常到了這時候,那個大家都不想面對的事情就要發生了,那就是 - 整個專案需要“打掉重練”。

在這鐵人賽的最後一部分,我將新增一些新需求,而這些新需求會打破原有的設計,原來的設計的限制無法達這些新需求。因此我們需要一個新的架構,但是這個架構不應該隨時需要大幅度的修改,他會跟著產品的演進而一起成長,進而同時滿足產品端與工程端人員的期待。

便利貼第三部

先來複習一下前面兩部各完成了什麼

第一部:顯示便利貼、使用手勢移動便利貼、同步雲端資料

第二部:完成新增、刪除、修改顏色、編輯文字這四個功能

第三部分我們將要新增功能是:放大、縮小以及平移整張白板,這是一個非常巨大的改變,因為放大縮小就代表了顯示的便利貼數量再也不是固定的,從十幾個到上百個便利貼都有可能,再加上每個人都有可能擁有屬於他們的編輯區域,因此隨時取得全部便利貼的更新是一件非常浪費效能的事。

手勢操作也是另外一個問題,目前拖曳便利貼可以直接在未選取狀態的便利貼上進行,平移白板的這個功能就因此變得不太可行,因為使用者很有可能是想平移整張白板,但是卻不小心移動到一張便利貼,使用者體驗變得不是很好,所以這部分需要跟產品端做溝通,有時候他們不會第一時間就意識到這個問題的存在(雖然這個問題很明顯,但現實上就是會有做了才會發現的問題),主動提出問題並一起想出一個好的解法是工程端這邊的責任。最糟糕的是自己默默想出解法,做了兩個禮拜之後 Demo 給大家看,才發現跟產品端的人的想像差了十萬八千里,到那時候就已經浪費掉太多時間了。

再來是顯示順序的問題,也就是哪一張便利貼該在上面,哪一張便利貼該在下面,這個通常來說也不會是一個產品端容易發現的問題,他們只要看到畫面沒問題就好了,但是對於開發這邊,要是沒有一定的上下順序的話,就有可能在每次移動便利貼時都要重新調整順序,而這個順序也沒有特別的演算法或依據,就只是依照 Firebase 給我們便利貼的順序來顯示,至於 Local 更改過的資料怎麼辦呢?為了不破壞掉原有的順序,只好選擇採用“取代”的方式來保持原有的順序,在之前的實作裡我就是這樣做的:

override fun getAllNotes(): Observable<List<Note>> {
    return Observables.combineLatest(updatingNoteSubject, allNotesSubject)
        .map { (optNote, allNotes) ->
            optNote.map { note ->
                val noteIndex = allNotes.indexOfFirst { it.id == note.id }
                allNotes.subList(0, noteIndex) + note + allNotes.subList(noteIndex + 1, allNotes.size)
            }.orElseGet { allNotes }
        }
}

這段程式碼是在 FirebaseNoteRepository 裡的,在這段程式碼的第六行中,為了確保顯示的順序是對的,我拆掉原有的 List 將它分成兩段,然後再把相對應的 note 安插在中間,就可以不用擔心每一次新的資料的順序有改變而影響顯示的順序。

像這樣的實作就是一種 "Workaround" ,雖然暫時解決了問題,但是沒有意識到更根本的原因是什麼。為了解決這個問題,我們可能會在 Note 裡新增一個 z 的欄位,然而這個 z 的新欄位是工程端做的決定,但是產品端從頭到尾都沒有被通知到這個決定,也無法參與討論,當之後產品端發現並提出不同想法時,但是這個想法跟目前的機制差太多,這時候要改就是難上加難了。

小結

相信在一間正常的公司裡,我們都希望產品能夠成功,在現實生活中,重寫架構應該都是一件大家都不願意看到的選項,而且一但這麼做,很容易過個幾年,就會覺得上一次設計的架構有夠難用,又整個重寫一次。因此進行架構的設計應該要與產品的“形狀”一致,而這樣設計出來的架構,會很大部分的符合產品未來的走向,所以未來加新功能會很快,產品端很滿意,工程端也因為設計出可高度重用,好維護的架構而獲得成就感,大家都開心!

注:以上說的這些當然很不容易達到,最重要的還是需要“人”的配合,有好同事都是上輩子燒好香換來的,要是再考慮這因素就不是本系列文的重點了,在這系列文章我將忽略“人”這個因素,假設大家都是很好溝通的好同事為前提。


上一篇
專案檔案結構
下一篇
Clean architecture in Android
系列文
Jetpack Compose X Android Architecture X Functional Reactive Programming30

尚未有邦友留言

立即登入留言