网页私服论坛

 找回密码
 立即注册
搜索
查看: 163934|回复: 9

浅谈程序的核心

[复制链接]

1

主题

6

帖子

23

积分

新手上路

Rank: 1

积分
23
QQ
发表于 2015-10-17 20:06:58 | 显示全部楼层 |阅读模式
   5 天前 上传 下载附件 (45.65 KB)  


  GameRes游资网授权发布 文 / 安柏霖

  在《the art of unix programming》中,复杂度的控制被看的非常的重,里面一句话提到编程项目的核心就是对于复杂度的控制,以及simple原则其实也在讲这个事情。
我自己在08年也写了关于这个的话题:http://blog.csdn.net/toughbro/article/details/2679254

  7年过去了,也经历了《天涯明月刀》这样的重型项目的磨练,也有了更多的认识。

  复杂度的要点

  复杂度的要点所在就是程序给大脑带来的负担,它等同于程序员提升和开发程序的难易程度,这个负担随着模块的复杂度大约是平方级数增长。

  如果负担很低,那么一段程序的就容易控制,程序员就容易提升程序的质量(包括开发效率,运行稳定性和运行效率)。

  所以我们也不需要在任何时候任何情况去做复杂度的最小化,如果一个模块本身规模很小,那么就不需要花很多精力去做进一步简化(当然处于自我提升和精益求精的本能,在时间允许的情况下,做这个当然好的了)

  同时低复杂度度也不等同于最少行数的代码,而是给大脑带来最少负担的代码,比如后文举得代码例子,虽然另外一种写法代码行数更多,但是由于它符合一个更稳定的模式,所以在大脑负担和心理负担都更轻,它可以认为是更低复杂度的代码。

  复杂度控制的实际意义

  实际价值

  先从实用的角度来看:关乎运行效率和开发效率(当然其他的扩展性等等也会包括,但是实际在项目里的感受是这两个尤其的明显)。

  其实7年前我也是毫无疑问的这么认为的,但是实践起来并不是一码事情,大约几年前,才真正的形成开发的原则。

  开发效率

   5 天前 上传 下载附件 (106.14 KB)  


  这个最深刻的认识原则当初开发地形系统,包括从编辑器的底层部分(UI部分是另外一个同事做的)以及runtime部分,从材质到高度图,系统庞大而且复杂。

  开发过程中,也不可避免的遭遇到需求变动(包括材质系统的能力,地图大小这种非常颠覆性的)。

  时间紧任务重,一直想尽量快点把东西做好,开发过程中,代码整理和系统整体控制没有做太多,然后其他组可以同步进行,然后再进行代码整理。

  但是对于一个庞大的系统,这种策略就不好。

  写程序的时候,质量和效率最好的情况就是始终对于整个系统,在代码级别保持一个非常清晰的状态,你心里知道要写成什么样,写的过程,整体的代码也清晰合理,与你心里的样子相印证,然后可以心如止水的一直非常快的写,整个过程非常的享受。

  而如果实现过程中,缺乏对于系统良好的认识和整理,希望“随便搞搞,搞出来再整理“,这种在小型情况下是ok的,但是大型系统下,即便思维保持清晰,但是庞大的系统缺乏整理,而造成非常的复杂,很多东西由于前后设计的不一致,导致是处于一个不合理的复杂情况–需要你去死记。

  这样造成的结果就是,即便你对于整体系统的设计非常的清晰,但是在编程过程中,由于系统的一定的混乱,让你没法整个过程非常清晰的,心如止水的进行,整个的过程,磕磕绊绊,让人疲惫不堪。

  所以在后半段,就停下来改变了策略,先做充分的整理,把不需要的部分去除,然后把代码整理到完全准备好来做新代码的实现,才去做新的实现,这样反而是最快的,写起来也愉快迅捷。

  运行效率

   5 天前 上传 下载附件 (168.65 KB)  


  处理效率,常规的基本做法是profile热点,以及根据游戏的情况进行feature的关闭。
但是这个能做的事情是非常有限的,如果想做进一步提升性能,接近性能的极限,必须要做的就是:

  - 对于每一个模块有充分的理解

  - 可以做到快速的反复尝试迭代

  处理性能热点,在优化早期是一个非常高效的做法,准确来讲,热点处理是”在有水分的情况下,高效提升性能“的方法。

  但是在追求极限性能方面,热点优化还是不够,某一个模块的性能消耗是不是超过了它应该有的,以及一个排名10名开外的模块其实是不需要高频运行的等等,这些都是热点处理不能解决的。

  在对于程序有充分了解,就可以进行更彻底的调整,把大量的运行做并行,低频执行或者直接优化掉。

  实践中看下来,这样的处理会把程序的性能带到一个新的台阶。

  这个道理可以说是知易行难,难就难在,对一个超大系统(比如对于《天涯明月刀》来说,就是整个客户端,覆盖几十万行的代码),如何做到充分理解,如何做到容易的彻底的修改优化。

  所以关键点又回到复杂度,只有程序的复杂度得到最好的控制,才能较好的做这个工作。
这个后来在实践中,优化过程中,大约一半时间是在做代码的调整和重构,代码合理就会让优化更加的可行和高效。

  复杂度控制的方法与实践

  实践下来,复杂度控制的能力在我看来可以从三个方面来拆解:渴望,目标与时间积累。

  渴望:

  首先最有效的方式就是去承担实际的,要覆盖非常大范畴的开发任务,这种情况下,你就会对于复杂度有切肤之痛,你就会非常真切的了解到复杂度是什么,什么是重要的,让你抓狂的,什么只是虚张声势,无足轻重的,有了非常充分的渴望,那么后面的积累和实践就容易多了。

  目标:

  方法和实践会是非常的多,但是目标却简单很多,就是能够始终保持对于整个系统,在代码级别非常的清晰。在开发设计和做决定的时候,能有心如止水般的顺畅即可。所以一定程度上,可以说复杂度控制还是比较主观的,也很看火候的。比如有时候项目本来就比较小,即便复杂度控制不是很好,但是也非常的清晰,hold住,那就可以把更多的精力放在其他方面。

  方法:

  个人实践中,这几个方面可以注意下:

  - 任务切分+代码整理:在较小型的任务结束的时候,就开始做小规模的代码整理,始终保持代码是干净的

  - 模式+自然:积累更多的模式,比如一大片的代码,其实就是做了pool的事情,那么这一大片的复杂度就是一个词:pool。让所有的东西都更加自然,符合编程的优秀实践,这样需要你记和注意的东西就很少,那么它就是一个很低的复杂度。

  比如下面这个代码:

   5 天前 上传 下载附件 (15.54 KB)  


  这个在实际程序中就不是一个好的实践,在看到这片代码的时候,应该本能的注意到a[5]如果它的大小变化了怎么办,就会出现for的访问越界的可能。

   5 天前 上传 下载附件 (26.1 KB)  


  那么再次看到这样的代码的时候,就会比较放心,一路就过去了,那么这个就可以认为是复杂度比较低的(需要注意的或者刻意要记的东西少)。

  所以保持一个总结积累就变得非常重要,对于编程模式或者算法越来越多的积累,那么在开发和思考的时候,就可以以更高的维度去做,那么对于压缩复杂度,提升思维速度和质量就非常的重要了。

  并且,在这个层面上看,尽量返璞归真的编程风格是一个更加有力的编程风格。

  复杂度控制的“敌人”

  没有意识到“复杂度”的重要性

  遇到不少程序员(甚至是大部分)对于复杂度无感,把一些算法和效率因素重要性远远放在复杂度之上,甚至是以写出很复杂的程序为荣。这一块不是很容易沟通,只有实际去承担大量的程序实现,对复杂度有切肤之痛的情况,才能有一个真实的认识。

  还有就是没有及时和项目组沟通,争取足够的时间来处理复杂度问题以及清理代码,相当多的程序员都不会对复杂度有充分的认识,那么要求项目经理有足够的认识在我看来不太合理。基本上较有可行性的方法是程序员给予足够的沟通,以及在实现估时上留有充分的余量,而如果出现没有意识到,没有沟通充分,甚至是为了取悦manager而无视复杂度,疯狂追求实现时间的情况,这都太糟糕了。

  进度问题

  时间紧任务重的情况,这个前面已经提过了,但是实际项目中还是会反复出现,这块其实是可以是一个大的话题。

  首先每个程序员需要建立一个代码实现的profile机制–我个人一直使用worklog,然后对于自己的开发效率有一个跟踪,这样才能知道哪种方法是正确的更快的。磨刀什么情况下才不误砍材工,profile了才知道。

  根据具体情况采取具体的策略,个人经验下,相当的情况都是一边实现一边整理是更快的。
编程基本功,就是快速稳定的实现了,这个需要长期的有意识的积累。

  good for the programmer’s soul

  “Low-level programming is good for the programmer’s soul.” - John Carmack

  对于卡神的这句话,无比的赞同,做底层代码实现,对硬件和系统有透彻的理解,对于程序员去清晰的理解整个程序如何运行的至关重要,你就会更好的以底层的思维去思考。

  同样的道理,也可以用于高层的复杂度控制上面,更多的优秀的编程实践,更好的理解要做的事情,理解系统本身,最后达到一个最简洁的实现,整个设计和实现的过程,可以让人进入心如止水的状态,同样的”good for the programmer’s soul“

网页游戏私服论坛 http://www.c14.com

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

0

主题

6

帖子

29

积分

新手上路

Rank: 1

积分
29
QQ
发表于 2015-10-17 21:24:58 | 显示全部楼层
snghun 发表于 2015-10-13 11:32
我还是觉得这个代码才有问题。

sizeof(a)/sizeof(a[0] 的意义在哪里?
培训班出来的吗
回复 支持 反对

使用道具 举报

2

主题

9

帖子

27

积分

新手上路

Rank: 1

积分
27
QQ
发表于 2015-10-17 22:32:32 | 显示全部楼层
snghun 发表于 2015-10-13 11:32
我还是觉得这个代码才有问题。

sizeof(a)/sizeof(a[0] 的意义在哪里?
意义在于

未来如果程序需要修改 a[10],你不用在代码里四处找可能跟这个数组下标有关系的代码片段,万一漏修改了一个,那这个bug就GG了,楼主的意思是尽量降低耦合度,避免一些参数修改带来的很多记忆隐患。

楼主代码只是个小片段,我们看起来通常不会那么傻,不去修改for循环里的5,但如果代码成千上万行,那就有风险了。
回复 支持 反对

使用道具 举报

2

主题

10

帖子

38

积分

新手上路

Rank: 1

积分
38
QQ
发表于 2015-10-17 22:40:07 | 显示全部楼层
snghun 发表于 2015-10-13 11:32
我还是觉得这个代码才有问题。

sizeof(a)/sizeof(a[0] 的意义在哪里?
....................
回复 支持 反对

使用道具 举报

0

主题

3

帖子

17

积分

新手上路

Rank: 1

积分
17
QQ
发表于 2015-10-17 23:02:17 | 显示全部楼层

我觉得这个代码更好啊。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 反对

使用道具 举报

1

主题

9

帖子

31

积分

新手上路

Rank: 1

积分
31
QQ
发表于 2015-10-17 23:35:49 | 显示全部楼层
我觉得好的代码,是足够应付未来大多数的需求变化的。秘诀就在于把逻辑尽量独立,拆分的越细越好。
举几个例子:
同步数据,可以拆分为两个接口:修改数据和发送数据。
写数据库,可以拆分为两个独立步骤:提交数据库请求和具体执行SQL语句。
再举个复杂的抽取卡片的例子:
抽取卡片可以分成几个接口:判断抽取条件, 抽取算法,同步抽取结果。

看一个人程序写的好不好,就看他能否把一个看起来是一体的事情抽象成N个小步骤。只要抽象的足够细致,那么哪怕策划需求修改,你也一定能用组合接口的方式拼凑出一个新功能。这其中最关键的就是数据和数据之间的关系,没有数据是不能切分的,一定是都能切分的。

至于优化,我的意见是,用业务需求来推进优化。随着业务需求的逐步明确,最后代码会自己优化完毕。过早的优化=大坑。
回复 支持 反对

使用道具 举报

0

主题

8

帖子

29

积分

新手上路

Rank: 1

积分
29
QQ
发表于 2015-10-17 23:50:34 | 显示全部楼层
本帖最后由 snghun 于 2015-10-13 11:36 编辑


我还是觉得这个代码才有问题。

sizeof(a)/sizeof(a[0] 的意义在哪里?
难不成 a[5]的大小还能是10怎么着?可能吗?

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 反对

使用道具 举报

0

主题

8

帖子

29

积分

新手上路

Rank: 1

积分
29
QQ
发表于 2015-10-18 00:42:31 | 显示全部楼层
同意楼上的看法,逻辑独立是很有帮助的,否则就是一个大坑,要改点东西就变成噩梦,无休止的加班重复劳动
回复 支持 反对

使用道具 举报

1

主题

6

帖子

25

积分

新手上路

Rank: 1

积分
25
QQ
发表于 2015-10-18 00:47:31 | 显示全部楼层
sea_bug 发表于 2015-10-16 11:18
意义在于

未来如果程序需要修改 a[10],你不用在代码里四处找可能跟这个数组下标有关系的代码片段,万一 ...
#define A_MAX 5 //有事,修改此处

int a[A_MAX];

for( ...; i < A_MAX; ...)
回复 支持 反对

使用道具 举报

1

主题

9

帖子

28

积分

新手上路

Rank: 1

积分
28
QQ
发表于 2015-10-18 01:03:49 | 显示全部楼层
snghun 发表于 2015-10-13 08:31
我觉得这个代码更好啊。
抓到游戏圈坑子程序员一名。
楼主的写法,用我的话来讲,是“自适应式代码”。
自适应代码的意思就是,当需求有改动的时候,程序代码不用修改。
你引用的写法,如果需求更迭导致数组长度有变动,那你还要改程序,烦不烦?更可怕的是,如果忘记要改代码,那么很可能数组溢出,程序最后宕机。

游戏圈为什么加班,就是这些细节一点一滴加出来的班。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|网页私服论坛  

GMT+8, 2017-10-20 00:26 , Processed in 0.060798 second(s), 30 queries .

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表