开端的夸姣

没有前史包袱,就没有压力,便是夸姣的。

假定项目发动了这样一个事务——造车:出产一辆小轿车(Car),分别在不同的零件车间(车架(Sheel)、发动机(Engine)、车轮(Wheel))装置相应的零件,一切零件装置完结后回到提车车间就能够提车。

拒绝代码PUA,优雅地迭代业务代码

这样的需求开发起来很简略:

  • 数据实体
data class Car(
    var shell: Shell? = null,
    var engine: Engine? = null,
    var wheel: Wheel? = null,
) : Serializable {
    override fun toString(): String {
        return "Car: Shell(${shell}), Engine(${engine}), Wheel(${wheel})"
    }
}
data class Shell(
    ...
) : Serializable
data class Engine(
    ...
) : Serializable
data class Wheel(
    ...
) : Serializable
  • 零件车间(以车架为例)
class ShellFactoryActivity : AppCompatActivity() {
    private lateinit var btn: Button
    private lateinit var back: Button
    private lateinit var status: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_shell_factory)
        val car = intent.getSerializableExtra("car") as Car
        status = findViewById(R.id.status)
        btn = findViewById(R.id.btn)
        btn.setOnClickListener {
            car.shell = Shell(
                id = 1,
                name = "比亚迪车架",
                type = 1
            )
            status.text = car.toString()
        }
        back = findViewById(R.id.back)
        back.setOnClickListener {
            setResult(RESULT_OK, intent.apply {
                putExtra("car", car)
            })
            finish()
        }
    }
}
class EngineFactoryActivity : AppCompatActivity() {
    // 和装置车架流程相同
}
class WheelFactoryActivity : AppCompatActivity() {
    // 和装置车架流程相同
}
  • 提车车间
class MainActivity : AppCompatActivity() {
    private var car: Car? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        car = Car()
        refreshStatus()
        findViewById<Button>(R.id.shell).setOnClickListener {
            val it = Intent(this, ShellFactoryActivity::class.java)
            it.putExtra("car", car)
            startActivityForResult(it, REQUEST_SHELL)
        }
        findViewById<Button>(R.id.engine).setOnClickListener {
            val it = Intent(this, EngineFactoryActivity::class.java)
            it.putExtra("car", car)
            startActivityForResult(it, REQUEST_ENGINE)
        }
        findViewById<Button>(R.id.wheel).setOnClickListener {
            val it = Intent(this, WheelFactoryActivity::class.java)
            it.putExtra("car", car)
            startActivityForResult(it, REQUEST_WHEEL)
        }
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode != RESULT_OK) return
        when (requestCode) {
            REQUEST_SHELL -> {
                Log.i(TAG, "装置车架完结")
                car = data?.getSerializableExtra("car") as Car
            }
            REQUEST_ENGINE -> {
                Log.i(TAG, "装置发动机完结")
                car = data?.getSerializableExtra("car") as Car
            }
            REQUEST_WHEEL -> {
                Log.i(TAG, "装置车轮完结")
                car = data?.getSerializableExtra("car") as Car
            }
        }
        refreshStatus()
    }
    private fun refreshStatus() {
        findViewById<TextView>(R.id.status).text = car?.toString()
        findViewById<Button>(R.id.save).run {
            isEnabled = car?.shell != null && car?.engine != null && car?.wheel != null
            setOnClickListener {
                Toast.makeText(this@MainActivity, "提车咯!", Toast.LENGTH_SHORT).show()
            }
        }
    }
    companion object {
        private const val TAG = "MainActivity"
        const val REQUEST_SHELL = 1
        const val REQUEST_ENGINE = 2
        const val REQUEST_WHEEL = 3
    }
}

即便是初学者也能看出来,事务完结起来很简略,通过ActivitystartActivityForResult就能跳转到相应的零件车间,装置好零件回到提车车间就完事了。

开端迭代

往往事务的第一个版别便是这么简略,感觉也没什么好重构的。

可是事务难免会进行迭代。比方事务迭代到1.1版别:客户想要给轿车装上行车电脑,而装置行车电脑不需求跳转到另一个车间,而是在提车车间操作,可是需求很长的时刻。

看起来也简略,新增一个Computer实体类和ComputerFactoryHelper

object ComputerFactoryHelper {
    fun provideComputer(block: Computer.() -> Unit) {
        Thread.sleep(5_000)
        block(Computer())
    }
}
data class Computer(
    val id: Int = 1,
    val name: String = "行车电脑",
    val cpu: String = "麒麟90000"
) : Serializable {
    override fun toString(): String {
        return "$name-$cpu"
    }
}

再在提车车间新增按钮和逻辑代码:

findViewById<Button>(R.id.computer).setOnClickListener {
    object : Thread() {
        override fun run() {
            ComputerFactoryHelper.provideComputer {
                car?.computer = this
                runOnUiThread { refreshStatus() }
            }
        }
    }.start()
}

现在看起来也没啥难的,那是因为咱们模拟的事务场景足够简略,可是相信许多实际项目的屎山代码,便是通过这样的事务迭代,一点一点地堆积而成的。

从迭代到溃散

咱们来采访一下最近被一个小小的事务迭代需求搞溃散的Android开发——小王。

记者:小王你好,听说最近你Emo了,甚至多次萌生了就地辞去职务的想法?

小王:最近AI不是很火吗,产品给我提了一个需求,在上传音乐时能够选择在后端生成一个AI视频,然后一起上传。

记者:哦?这不是一个小需求吗?

小王:可是我翻开现在上传事务的代码就傻了啊!就说Activity吧,有:BasePublishActivity,BasePublishFinallyActivity,SinglePublishMusicActivity,MultiPublishMusicActivity,PublishFinallyActivity,PublishCutMusicFinallyActivity, Publish(好多好多)FinallyActivity… 当然,这只是冰山一角。再说上传流程。假如只上传一首音乐,需求先调一个接口/sts拿到一个Oss Token,再调用第三方的Oss库上传文件,拿到一个url,然后再把这个url和其他的信息(标题、标签等)组成一个HashMap,再调用一个接口/save提交到后端,相当于调3个接口… 假如要批量上传N个音乐,就要调3 * N个接口,假如还要给每个音乐配M个图片,就要调3 * N+3 * N * M个接口… 假如上传一个音乐配一个本地视频,就要调3 * 2 * N个接口,而且,上传视频流程还不相同的是,需求在调用/save接口之后再调用第三方Oss上传视频文件…再说数据类。上面说到上传过程中需求增加图片、视频、活动类型啥的,代码里封装了一个EditInfo类足足有30个特点!,由于是Java代码而且完结了Parcelable接口,光一个Data类就有400多行!你认为这就完了?EditInfo需求在上传时转成PublishInfo类,PublishInfo还能够转成PublishDraft,PublishDraft能够保存到数据库中,从数据库中能够读取PublishDraft然后转成EditInfo再从头编辑…

记者:(感觉小王精神状态有点问题,于是掐掉了直播画面)

相信小王的这种情况,许多事务开发同学都经历过吧。回头再看一下前面的造车事务,其实和小王的上传事务相同,便是一开端很简略,迭代个7、8个版别就开端陷入一种窘境:即便迭代需求再小,开发起来都很困难。

高雅地迭代事务代码?

假如咱们想要高雅地迭代事务代码,应该怎样做呢?

小王遇到的这座屎山,咱们现在就不要去碰了,先就从前面说到的造车事务开端吧。

许多同学会想到重构,俺也相同。接下来,我就要讨论一下如何高雅安全地重构既有事务代码。

先抛出一个观点:对于程序员来说,想要保持“高雅”,最重要的质量便是笼统。

❓ 这时可能有同学就要反驳我了:过早的笼统没有必要。

❗ 别急,我现在要说的笼统,并不是代码层面的笼统,而是对事务的笼统,甚至对技能思想的笼统

什么是代码层面的笼统?比方刚刚的Shell/Engine/WheelFactoryActivity,其实是能够笼统为BaseFactoryActivity,然后通过完结其中的零件类型就行了。但我不会主张你这么做,为啥?看看小王方才的疯言疯语就明白了。各个XxxFactoryActivity看着差不多,但在实际项目中很可能会开枝散叶,各自迭代出不同的事务细节。到那时,项目里便是各种BaseXxxActivityXxxV1ActivityXxxV2Activity

那什么又是事务的笼统?直接上代码:

interface CarFactory {
    val factory: suspend Car.() -> Car
}

造车事务,无论在哪个环节,都是在Car上装置零件(或者任何出其不意的操作),然后产生一个新的Car;别的,这个环节无论是跳转到零件车间装置零件,还是在提车车间里装置行车电脑,都是耗时操作,所以需求加suspend关键词。

❓ 这时可能有同学说:害!你这和BaseFactoryActivity有啥差异,不便是把笼统类换成接口了吗?

❗ 别急,我并没有要让XxxFactoryActivity去继承CarFactory啊,想想小王吧,这个XxxFactoryActivity就相当于他的同事在两年前写的代码,小王肯定打死都不会想去修正这儿面的代码的。

Computer是新事务,咱们只改它。首要咱们根据这个接口把ComputerFactoryHelper改一下:

object ComputerFactoryHelper : CarFactory {
    private suspend fun provideComputer(block: Computer.() -> Unit) {
        delay(5_000)
        block(Computer())
    }
    override val factory: suspend Car.() -> Car = {
        provideComputer {
            computer = this
        }
        this
    }
}

那么,在提车车间就能够这样改:

private var computerFactory: CarFactory = ComputerFactoryHelper
findViewById<Button>(R.id.computer).setOnClickListener {
    lifecycleScope.launchWhenResumed {
        computerFactory.factory.invoke(car)
        refreshStatus()
    }
}

❓ 那么XxxFactoryActivity相关的流程又应该怎样重构呢?

Emo时刻

我先反诘一下咱们,为啥咱们Android程序员总是盯着Activity不放呢?

我再反诘一下咱们,为啥咱们Android程序员总是盯着Activity不放呢?

我再再反诘一下咱们,为啥咱们Android程序员总是盯着Activity不放呢?

甚至,许多人即便学了ComposeFlutter,依然对Activity心心念念。

当你在一千个日日夜夜里,重复地写着XxxActivity,写onCreate/onResume/onDestroy,写startActivityForResult/onActivityResult时,当你每次想要换工作,翻开面经背诵Activity的发动形式,生命周期,AMS原理时,可曾对Activity有过厌恶,可曾思考过编程的含义?

你也曾努力查阅Activity的源码,学习MVP/MVVM/MVI架构,企图让你的Activity保持清洁,但无论你怎样努力,却一向活在Activity的暗影之下。

你有没有想过,咱们正在被Activity PUA

说实话,作为一名INFP,本人不是很适合做程序员。比较技能栈的丰厚和技能原理的深化,我更看重的是写代码的感触。假如写代码都能被PUA,那我还怎样愉快的写代码?

当我Emo了很久之后,我意识到了,我一向在被代码PUA,不光是同事的代码,也有自己的代码,甚至有Android结构,以及外国大佬不断推出的各种新技能新结构。

对对对!你们都没有问题,是我太菜了555555555

高雅回身

Emo过后,还是得回到残酷的职场啊!可是咱们要高雅地回身回来!

❓ 方才不是说要处理XxxFactoryActivity相关事务吗?

❗ 这时我就要说到别的一种笼统:技能思想的笼统

Activity?F*ck off!

Activity的跳转返回啥的,也无非便是一次耗时操作嘛,咱们也应该将它笼统为CarFactory,便是这个东东:

interface CarFactory {
    val factory: suspend Car.() -> Car
}

根据这个信仰,我从回忆中想到这么一个东东:ActivityResultLauncher

说实话,我以前都没用过这玩意儿,可是我这时好像抓到了救命稻草。

随意搜了个教程并谢谢他,参阅这篇博客,咱们能够把startActivityForResultonActivityResult这套流程,封装成一次异步调用。

open class BaseActivity : AppCompatActivity() {
    private lateinit var startActivityForResultLauncher: StartActivityForResultLauncher
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        startActivityForResultLauncher = StartActivityForResultLauncher(this)
    }
    fun startActivityForResult(
        intent: Intent,
        callback: (resultCode: Int, data: Intent?) -> Unit
    ) {
        startActivityForResultLauncher.launch(intent) {
            callback.invoke(it.resultCode, it.data)
        }
    }
}

MainActivity继承BaseActivity,就能够绕过Activity了,后边的事情就简略了。只需咱们了解过协程,就能轻易想到异步转同步这一一般操作:suspendCoroutine

于是,咱们就能够在不修正XxxFactoryActivity的情况下,写出根据CarFactory的代码了。还是以车架车间为例:

class ShellFactoryHelper(private val activity: BaseActivity) : CarFactory {
    override val factory: suspend Car.() -> Car = {
        suspendCoroutine { continuation ->
            val it = Intent(activity, ShellFactoryActivity::class.java)
            it.putExtra("car", this)
            activity.startActivityForResult(it) { resultCode, data ->
                (data?.getSerializableExtra("car") as? Car)?.let {
                    Log.i(TAG, "装置车壳完结")
                    shell = it.shell
                    continuation.resumeWith(Result.success(this))
                }
            }
        }
    }
}

然后在提车车间,和Computer事务同样的运用方法:

private var shellFactory: CarFactory = ShellFactoryHelper(this)
findViewById<Button>(R.id.shell).setOnClickListener {
    lifecycleScope.launchWhenResumed {
        shellFactory.factory.invoke(car)
        refreshStatus()
    }
}

最终,在咱们的提车车间,依靠的便是一些CarFactory,一切的事务操作都是笼统的。抵达这个阶段,相信咱们都有了自己的一些想法了(比方保护一个carFactoryList,用Hilt管理CarFactory依靠,泛型封装等),想要继续怎样重构/保护,就全看自己的实际情况了。

class MainActivity : BaseActivity() {
    private var car: Car = Car()
    private var computerFactory: CarFactory = ComputerFactoryHelper
    private var engineFactory: CarFactory = EngineFactoryHelper(this)
    private var shellFactory: CarFactory = ShellFactoryHelper(this)
    private var wheelFactory: CarFactory = WheelFactoryHelper(this)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        refreshStatus()
        findViewById<Button>(R.id.shell).setOnClickListener {
            lifecycleScope.launchWhenResumed {
                shellFactory.factory.invoke(car)
                refreshStatus()
            }
        }
        findViewById<Button>(R.id.engine).setOnClickListener {
            lifecycleScope.launchWhenResumed {
                engineFactory.factory.invoke(car)
                refreshStatus()
            }
        }
        findViewById<Button>(R.id.wheel).setOnClickListener {
            lifecycleScope.launchWhenResumed {
                wheelFactory.factory.invoke(car)
                refreshStatus()
            }
        }
        findViewById<Button>(R.id.computer).setOnClickListener {
            lifecycleScope.launchWhenResumed {
                Toast.makeText(this@MainActivity, "稍等一会儿", Toast.LENGTH_LONG).show()
                computerFactory.factory.invoke(car)
                Toast.makeText(this@MainActivity, "装好了!", Toast.LENGTH_LONG).show()
                refreshStatus()
            }
        }
    }
    private fun refreshStatus() {
        findViewById<TextView>(R.id.status).text = car.toString()
        findViewById<Button>(R.id.save).run {
            isEnabled = car.shell != null && car.engine != null && car.wheel != null && car.computer != null
            setOnClickListener {
                Toast.makeText(this@MainActivity, "提车咯!", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

总结

  • 笼统是程序员保持高雅的最重要才能。
  • 笼统不该限制在代码层面,而是要上升到事务,甚至技能思想上。
  • 有意识地对代码PUA说:No!
  • 学习新技能时,不该只学会调用,也不该迷失在技能原理上,更重要的是花哨的技能和朴实的编程思想之间的化学反应。