Kotlin編譯調校之WarningsAsErrors
- 2020 年 1 月 21 日
- 筆記
這之前的文章中,我們介紹過如果通過Kotlin編譯器參數實現將所有的warnings按照errors對待,主要的實現方法是這樣
//Code to be added kotlinOptions { allWarningsAsErrors = true }
那麼問題可能就會被提出來,開啟這一選項有什麼好處呢,畢竟我需要修改很多文件。
通常情況下,開啟後的作用,我們可以歸納為如下
- 發現更多的潛在問題和崩潰
- 減少不必要的代碼(變量,參數)
- 發現不好的編碼實踐
- 發現更多的API棄用問題
- 最終增加代碼的健壯性和優雅程度
如下,我們會通過一些實踐來說明一些問題
Nothing to Inline(作用不大的內聯)
@Suppress("NOTHING_TO_INLINE") inline fun String?.isNotNullNorEmpty(): Boolean { //Expected performance impact of inlining // 'public inline fun String?.isNotNullNorEmpty(): Boolean // defined in com.example.warningsaserrorscases in file NothingToInlineWarnings.kt' // is insignificant. // Inlining works best for functions with parameters of functional types return this != null && this.isNotEmpty() }
- Kotlin的inline關鍵字會將對應的方法內聯到調用者的方法體,減少進棧出棧操作
- inline最好的場景是處理函數類型參數,比如lambda
- 刻意的inline可能導致方法體膨脹,增大class文件大小。
- 處理這種警告,建議是去除inline關鍵字
- 如果執意inline時,使用
@Suppress("NOTHING_TO_INLINE")
壓制編譯器警告
INACCESSIBLE_TYPE(不可訪問的類型)
public class RequestManager { public static RequestManager sInstance = new RequestManager(); private static class TimelineRequest { public String from; } public TimelineRequest getTimelineRequest() { return new TimelineRequest(); } }
fun testInaccessibleType() { //Type RequestManager.TimelineRequest! is inaccessible in this context // due to: private open class TimelineRequest defined // in com.example.warningsaserrorscases.RequestManager @Suppress("INACCESSIBLE_TYPE") RequestManager.sInstance.timelineRequest }
- 上述的
testInaccessibleType
無法訪問TimelineRequest
的屬性和方法 - 具體的解決辦法,可以是設置
TimelineRequest
為public,而非private - 必要時可以使用
@Suppress("INACCESSIBLE_TYPE")
壓制警告
UNCHECKED_CAST(未檢查的類型轉換)
fun <T> Any.toType(): T? { //Unchecked cast: Any to T @Suppress("UNCHECKED_CAST") return this as? T }
- 上面
this as? T
屬於未檢查的類型轉換,可能在運行時拋出轉換異常 - 不推薦使用
@Suppress("UNCHECKED_CAST")
壓制警告 - 推薦使用reified方式處理
//a better way inline fun <reified T> Any.toType(): T? { return if (this is T) { this } else { null } }
WHEN_ENUM_CAN_BE_NULL_IN_JAVA(Enum 可能為null)
fun testEnum1() { //Enum argument can be null in Java, but exhaustive when contains no null branch when(SeasonUtil.getCurrentSeason()) { Season.SPRING -> println("Spring") Season.SUMMER -> println("Summer") Season.FALL -> println("Fall") Season.WINTER -> println("Winter") //else -> println("unknown") } } fun testEnum2() { //Enum argument can be null in Java, but exhaustive when contains no null branch @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") when(SeasonUtil.getCurrentSeason()) { Season.SPRING -> println("Spring") Season.SUMMER -> println("Summer") Season.FALL -> println("Fall") Season.WINTER -> println("Winter") } }
- 上述的
SeasonUtil.getCurrentSeason()
可能為null - 建議增加
else -> println("unknown")
處理when的缺省情況 - 不建議使用
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
壓制警告
PARAMETER_NAME_CHANGED_ON_OVERRIDE(方法重寫修改參數名)
interface OnViewClickedListener { fun onViewClicked(viewId: Int) } fun testParameterNameChangedOnOverride() { // The corresponding parameter in the supertype 'OnViewClickedListener' // is named 'viewId'. // This may cause problems when calling this function with named arguments. object : OnViewClickedListener { override fun onViewClicked(@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") id: Int) { println("onViewClicked id=$id") } } }
- 出問題的情況是當我們使用具名變量指定參數值時,可能出問題。
- 建議方法參數與源方法保持一致。
- 不建議壓制警告
Name shadowing(命名遮擋)
fun testNameShadowing(message: String) { run { //Name shadowed: message @Suppress("NAME_SHADOWING") val message = "Hello World" println(message) } }
- 當run方法後面的lambda中的message與
testNameShadowing
的message命名一致時,就發生了所謂的Name shadowing(命名遮擋) - Name shadowing很容易導致問題,且排查起來不易察覺。
- 建議主動通過命名差異來解決這個問題
- 不建議壓制警告
Uncessary cases (不必要的編碼場景)
UNNECESSARY_SAFE_CALL(不必要的安全調用)
fun testUnnecessarySafeCall(message: String) { @Suppress("UNNECESSARY_SAFE_CALL") println(message?.toIntOrNull()) }
- 上述的安全調用其實是顯得多餘,因為Kotlin內部會有
Intrinsics
做參數非空的與判斷 - 另外安全調用會增加if條件檢查
- 建議主動移不必要的安全調用
- 不建議壓制警告
SENSELESS_COMPARISON(無意義的比較)
fun testSenselessComparison(message: String) { //Condition 'message != null' is always 'true' @Suppress("SENSELESS_COMPARISON") if (message != null) { } }
- 和前面的例子一樣,這種檢查是多餘的,因為Kotlin內部會有
Intrinsics
做參數非空的與判斷 - 建議主動移除無意義的比較
- 不建議壓制警告
UNNECESSARY_NOT_NULL_ASSERTION(不需要的非空斷言)
fun testUncessaryNotNullAssertion(message: String) { //Unnecessary non-null assertion (!!) on a non-null receiver // of type String @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") println(message!!.toIntOrNull()) }
- 這種斷言是多餘的,因為Kotlin內部會有
Intrinsics
做參數非空的與判斷 - 建議主動移除不需要的非空斷言
- 不建議壓制警告
USELESS_IS_CHECK(沒有用的實例類型檢查)
fun testUselessIsCheck(message: String) { //Check for instance is always 'true' @Suppress("USELESS_IS_CHECK") if (message is String) { } }
- 沒有意義的類型檢查,因為Kotlin內部會有
Intrinsics
做參數非空的與判斷 - 建議主動移除不必要的檢查
- 不建議壓制警告
VARIABLE_WITH_REDUNDANT_INITIALIZER(變量初始化多餘)
fun testVariableWithRedundantInitializer() { //Variable 'message' initializer is redundant @Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER") var message: String? = null; message = System.currentTimeMillis().toString() println(message) }
- 建議手動移除多餘的初始化
- 不建議壓制警告
Deprecation (方法棄用)
fun testGetDrawable(context: Context) { @Suppress("DEPRECATION") context.resources.getDrawable(R.mipmap.ic_launcher) }
建議的方法是尋找替代棄用方法的其他方法,以getDrawable為例,我們可以使用
ContextCompat.getDrawable(getActivity(), R.drawable.name);
ResourcesCompat.getDrawable(getResources(), R.drawable.name, null);
ResourcesCompat.getDrawable(getResources(), R.drawable.name, anotherTheme);
- 必要時可以選擇壓制警告
unsued cases(開發者編碼沒有用到的情況)
Parameter 『extra』 is never used(參數沒有使用)
fun showMessage(message: String, extra: String?) { println(message) }
解決方法
- 移除extra參數
- 方法中使用extra參數
- 使用
@Suppress("UNUSED_PARAMETER")
壓制警告
Parameter 『index』 is never used, could be renamed to _(匿名參數沒有使用,可以使用佔位符)
fun forEachList() { listOf<String>("Hello", "World").forEachIndexed { index, s -> println(s) } }
- 將
index
改成佔位符_
- 使用
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
壓制警告
Variable 『currentTimeStamp』 is never used(變量未使用)
fun unusedVariable() { @Suppress("UNUSED_VARIABLE") val currentTimeStamp = System.currentTimeMillis() println("unusedVariable") }
- 移除變量
- 使用@Suppress壓制警告
The expression is unused(表達式未使用)
fun test(status: Int) { when(status) { 1 -> "First" 2 -> "Second" else -> "Else" } }
- 移除不用的表達式
- 使用
Suppress
壓制警告
UNUSED_VALUE && ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE (未使用的值,賦值後未使用的變量)
fun testUnusedValue() { // The value '"Hello"' assigned to 'var message: String? // defined in com.example.warningsaserrorscases.test' is never used @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") var message: String? = null @Suppress("UNUSED_VALUE") message = "Hello" }
- 移除不用變量
- 使用
Suppress
壓制警告
關於@Suppress
- 不建議濫用,因優先考慮其他的更好的解決問題的方式
- 及時使用一定要限定最小作用範圍,通常的選擇範圍盡量限制在變量(variable),參數(parameter)或者語句(statement)上。
- 上面代碼中出現了很多
@Suppress
主要目的是顯示警告的名稱,而不是提倡大家使用壓制的方式處理警告。
以上。