iT邦幫忙

2022 iThome 鐵人賽

DAY 5
0
Software Development

Kotlin on the way系列 第 5

Day 5 會失控的變數範圍 Limited the scope of variable

  • 分享至 

  • xImage
  •  

everything the light touch is our kingdom
Lion king

english version is down below

變數的範圍決定了誰可以存取他,為什麼這需要在意呢?為什麼我們不要把每個變數都變成全域的讓大家都可以存取呢?

事實是那會非常危險,考量到潛在的可變性,無上限的記憶體用量,race condition等,把全部變數都變成全域可不是個好主意

那我們有規則可循嗎?要如何寫出更好的設計呢?我可可以遵循幾個規則

第一,讓變數的範圍盡可能的小,如果一個變數只在迴圈裡使用,就只在迴圈裡聲明他

從這

val userState
if(isAdmin) {
    userState = UserState.Valid
} else {
    userState = UserState.InValid
}

val notes:List<Note> = ...
for(i in notes.indice){
    notes[i]
}

locations.forEach{
    it.lat
    it.lng
}

變成這樣

val userState = if(isAdmin) UserState.Valid else UserState.InValid

val notes:List<Note> = ...
for (note in notes){
    
}

locations.forEach{ (lat,lng) ->
    
}

第二,了解你的語言語法
不同的語言語法會有些微差異,拿 Kotlin 為例,要了解val/ var 的getter/ setter,val 並不總是不變的

第三,全域變數不應該是可變的,出非你有好的理由如此設計,因為全局可變變數,會有從不同組件無法預期的變化

那單例模式呢?他算是違反了規則嗎?
在安卓裡面,有幾個常見使用單例情境,像是與網路連線,讀取靜態資料等等,單例的設計核心在於某個東西,因有唯一的一個,且可讓全局讀取,如果在單例之中,有著可變的變數,那仍然違反的好的設計原則

變數本身應盡可能地靠近使用它的地方,而使用變數的範圍則應盡可能的小

用 visibility modifier 控制可見度

有別於區域變數和全局變數,在Kotlin 裡另外還有其他的範圍,如果你熟悉語法,你應該已經知道了

private
protected
internal

在物件導向裡有 Visibility modifiers , 在函式裡的變數是區域變數,在頂層聲明的變數的全域變數,現在來了解例一個成員變數 member variable

open class Adapter(){
    var count = 0
        private set
    protected var clickEvent: (Int) -> Unit = {}
    private var regexRule = ""
}

class NestAdapter:Adapter(){
    init {
        //access getter to count        
        count
        //access getter and setter to clickEvent
        clickEvent = {
            println(it)
        }
        // ERROR to access regexRule
        regexRule
    }
}

countAdapter 類別的成員變數, 預設的可見設置是public, 但我把他的 setter 設置成 private, 從結論來看,他的 getter 是 public, 但 setter 是 private, 所以他只能從類別內部修改,但外部可以讀取

clickEvent 可以從聲明他的類別中存取,還有其子類

除了成員變數 visibility modifier 還能在檔案類型用

//DataStoreFactory.kt
private val Context.dataStore by preferencesDataStore(
    name = USER_PREFERENCES_NAME
)

他改變了 extension variable 的 visibility 限制於該檔案內

最後要討論的 visibility modifier 是 internal, 他是要控制模組化之間的可見度

modularization archticture

每個模組是為一個功能所建造,而每個模組都能有其子模組,要讓範例簡單一點,我們會在網路模組寫出下面代碼

//:core:network Constant.kt
internal const val BASE_URL = BuildType.BASE_URL

現在 BASE_URL:core:network 模組是全域的, 但對引入 :core:network 模組的其他模組是不可見的, 所以 :data:books:data:reviews 的模組不會看到 BASE_URL

注意潛在可能的改變

範圍和可見性特別重要,他們應該越受限制越好,範圍限制越多,工程師就能越專注在眼前的業務邏輯和程式,我們人類有著受限的智能管理,如果變數能越廣泛的存取寫入,就越可能遇到不可預期且難以判斷的錯誤

在安卓里,常見的記憶體洩漏

第一個是聲明了變數,但沒有使用,他就佔用了記憶體直到系統將其回收掉
另一個是,生命週期短的持有了生命週期長的引用


如果你的 fragment 持有 application 的引用,那 fragment 就無法被被系統回收掉,因為他會認為 fragment 對 application 還有未完成的任務,所以才會保有引用

class Fragment {
    private val app = this.requireActivity.application
}

這樣的問題並不是只有安卓才有,你應該熟悉你使用的平台上的生命週期,並注意錯誤的變數使用

總結

  1. 使用 val 多過 var
  2. 注意多個改變點
  3. 注意 getter
  4. 範圍越小越好
  5. 注意記憶體使用

English

The scope of a variable decides how to access a variable, why should we care, why don't we make everything global so everyone can access them?

The truth is that is very dangerous, consider the mutability from multiple place, unlimit memory usage, race condition ...etc, make everything as global is not a great idea

So what is the rule? What should we do for better design?
There are following rule
First, make scope as small as possible
if one variable only used in for loop, then declare it inside for loop
from this

val userState
if(isAdmin) {
    userState = UserState.Valid
} else {
    userState = UserState.InValid
}

val notes:List<Note> = ...
for(i in notes.indice){
    notes[i]
}

locations.forEach{
    it.lat
    it.lng
}

to this

val userState = if(isAdmin) UserState.Valid else UserState.InValid

val notes:List<Note> = ...
for (note in notes){
    
}

locations.forEach{ (lat,lng) ->
    
}

Second, know about your language syntax
Different language work differently, take Kotlin as example, the different between var and val is getter and setter, val is not always immutable

Third, your global shouldn't be mutable, unless you have good reason, because it might be changed by other module unexpectly

What about singleton pattern, does it break the rule, in Android, there are common usage of singelotion, for instance, used for network connection, and access to read const data, the core to singleton is there should be onw and the only one instance

variable should be declare as closer as possible to where it used, and the range should be as small as possible

control visibility with prefix modifier

Apart from local variable and global variable, there are other scope in Kotlin, if you are familiar with Kotlin syntax, you should already know about

private
protected
internal

there are Visibility modifiers in oop, a variable inside function is a local variable, and a variable declare in top level is global variable, now let me introduce a new variable, member variable

open class Adapter(){
    var count = 0
        private set
    protected var clickEvent: (Int) -> Unit = {}
    private var regexRule = ""
}

class NestAdapter:Adapter(){
    init {
        //access getter to count        
        count
        //access getter and setter to clickEvent
        clickEvent = {
            println(it)
        }
        // ERROR to access regexRule
        regexRule
    }
}

the count is a member variable of Adapter class, the default visibility is public, but I set its setter to private, in collusion, its getter is public, but setter is private, so it can only change the value inside the class

clickEvent could be access from the class declare it, and all its subclass

Beside the member variable, visibility modifier could be used for file

//DataStoreFactory.kt
private val Context.dataStore by preferencesDataStore(
    name = USER_PREFERENCES_NAME
)

which change the visibility of extension variable to only inside this file

the last visibility modifier we gonna discuss is internal, this modifier is used for control visibility in modularization

modularization archticture

each is build for one feature, and it could also have submodule, to make demo simple, I will declare a variable like this

//:core:network Constant.kt
internal const val BASE_URL = BuildType.BASE_URL

now the BASE_URL is global inside :core:network module, but private to other module import :core:network module, so :data:books and :data:reviews can't access to BASE_URL

mind its potential change

Scope and visibility is extra important, it should be as restrict as possible, the smaller the scope is, the less thing engineer need to concern, we human have limited intellectual manageability, if there is a variable is widely access, and can be written by everywhere, it is possible cause unexpected issue and hard to be caught.

common memory leak in Android

There are two common mistake kane cause memory leak in Android, the first one is declare a variable but you never use it, so will take space in memory until garbage collect recycle it, the other is more difficult to find out
a class has shorter lifecycle holding a reference to a longer lifecycle


if your fragment holding a reference to application, the fragment can't be recycled due to to garbage collect algorithm assume the fragment class have something to do with application

class Fragment {
    private val app = this.requireActivity.application
}

A similar issue is not Android only, you should be familiar with the lifetime of all your using platforms, and be aware of wrong usage of variables.

summary

  1. prefer val over var
  2. mind the multiple point
  3. mind the getter
  4. make scope as small as possible
  5. aware memory usage

上一篇
Day 4 別當連 if 都寫爛的工程師 Make your if statement better
下一篇
Day 6 函式職人,一生懸命 Make your function simple
系列文
Kotlin on the way31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言