Be Like Water--程序设计的平衡和取舍

来源:互联网 发布:远洋数据推销什么 编辑:程序博客网 时间:2024/06/02 20:30

这里写图片描述

起初编程,有的时候想得多有的时候想得少,一路过来,回头看看,大致这几个方面在实际项目中,关系格外重大:

  1. 运行效率,直接影响产品的质量,算法设计到底层代码和硬件级别去理解程序的运行
  2. 规模复杂度,在产品上规模之后的第一要素,需要理清关系,使用经典设计来控制复杂度
  3. 开发效率,总时间总是不够的,局部单个任务做到“完美”在整体上很可能就是不完美了

三个方面关系总体比较暧昧,比如追求运行效率的时候,往往会给复杂度和开发效率带来压力,等等实际项目中三个因素我们要做的就是因地制宜,根据项目的实际情况来选择在总体上的最佳方案。

正如李小龙所言,如水一般,在瓶子一样的项目中,就形成瓶子一样,在茶壶一样的项目中,就茶壶一样。
阅读项目,学习知识和算法,做恰当的实现。

以streaming状态的生命周期管理为例:

我们streaming本身状态是由10多个参数来控制,并且在不同地图不同时段是由EnvironmentManager(EnvMgr)来更新管理的。
但是玩家如果处在一个特别高的视角的时候,就需要扩大streaming的距离,获得一个更广的视角,这个就要override其中3个参数。
那么这个override的策略就可以从运行效率,复杂度和开发效率三个方面来梳理下。

=EnvMgr侧=
有时候“小人物上篮”就是最优解

简单直接版:

struct StreamingOverrideConfig{    float mRange;    float mSize;    float mFreq;};void EnvMgr::OverrideStreaming(bool enable, StreamingOverrideConfig* v);//then in tickvoid EnvMgr::Update(float delta){//updates...//override    if(mFreeCamOverride)    {        mCofnig.mRange = mFreeCamOverride.mRange;        //...    }}

这种写法可以说是刚开始编程的人最容易出现的情况,它的优缺点是:

  • 非常简单易懂,实现快
  • 量少的时候ok,量多的时候,代码就没法看了,而且各种情况之间override的优先级不清,会出很多问题

但是正如它的优点,在量少,早期的时候,它就是一个最优解。

扩展性强的版本

struct StreamingConfigOverrideCmd{public:    //这个函数中去override config    virtual void Override(StreamingConfig& config)=0;    int mPriority;};void EnvMgr::Update(float delta){    //updates    ...    //override部分    //整理优先级    sort(mOverrideCmds);    //处理cmd的override    for_each(mOverrideCmds)    {        cmd->Override(mConfig);    }}

这个版本会是相对成熟一些,扩展性强,需要override的直接扩展cmd就好。
优缺点:

  • 实现相对慢,没那么直接
  • 扩展性好

一般来说,如果override的config数量在三个的时候,代码就比较难看了,就需要考虑这种了。
==各种情况下的选择原则==
所以如果现在系统中未来override config需求不明,甚至基本看起来很少,那么就简单直接秒杀就好,这种情况下上各种设计,就没那么简洁,在项目开发中属于一个偏拖沓的选择了。
反之,如果看到需求有变多的可能,或者实现者看到自己做的这个override已经是第三个了,那么换成cmd模式就是比较好了。

==知易行难的平衡取舍==
前面聊的是,总体原则就是各个方法有自己的特点,需要因地制宜就好,是一个了解起来很直接的东东。
但是实践起来,有两个大的难点:
1,就是要知道各个状态管理的时候,需要考虑那些点,有哪些选择,把问题拆解的mece(mutually exclusive, collectively exhaustive,完全穷尽,相互独立),这个就是要对技术和实际情况有充分的了解。
2,以平和客观的心态来做选择,我们可能因为怕麻烦或者是炫技而做出非最优(而技术上其实我们可以做到)

需要考虑颇多的player状态管理

看起来就是override一个streaming状态,只要player做一个free camera的时候设置就好了。
这里就是我们非常有可能低估的地方,player状态部分往往是游戏的核心模块,状态复杂多变,而且经手的人非常多,这样就对于复杂度的控制要求很高。
我们也看两种选择:
=状态切换时候设置=
也就是我们最常能够想到的

//player 来管理状态void Player::EnableFreeCam(bool v){    if(v!=mIsFreeCam)    {        mIsFreeCam = v;        EnvMgr::GetInstance()->OverrideConfig(v, mFreeCamStrConfig);    }}

既简单又直接,写起来也快,效率也是最优。
但是这也是风险颇高的一个,在于:

  • 如果状态出错,代价高:streaming状态如果管理错误,我们难以发现,到时候正常游戏的时候,多加载了很多东西,性能下降,规模大了之后,我们不太容易发现,发现了也很难查过来
  • player各种复杂,经手人多,要保证player各种退出机制下把free camera状态设对,需要不少功夫(比如突然换关,切人,player直接删除等等非常规退出都要考虑到这一点)

=每帧重置型=

//Playervoid Player::Update(float delta){//udpates...//override every frameEnvMgr::GetInstance()->OverrideConfig(mIsFreeCam, mFreeCamStrConfig);//env mgrvoid EnvMgr::Update(float t){    //execute and clear    if(mFreeCamOverride)    {        //override streaming        mFreeCamOverride = false;    }}

这种方法优缺点:

  • +:player无论以各种状态退出,都可以保证streaming状态的正确管理,非常的简单直接,想出错都难
  • -:如果量比较大,或者是一个大型的执行,那么会有效率隐患

后面这个写法一般在程序员看来评价会不高,但是在效率ok,player的状态又非常复杂的情况下是一个最优解,关键就是要了解到player状态会在规模复杂度和人多手杂的情况下会出现什么。
所谓因地制宜,这个“地”的了解可以说就是“行难”中的“难”了。

sum
所以最后看来,并无最优只有最适合,正确的阅读项目,广泛深入的学习,最后方能如水一样,最好的方式贴合项目,最因地制宜的构建开发过程。