优大网

分类存档: 开发 ( 8 / 8)

如何打磨设计能力? 9位创始人为初创企业支招

在越来越拥挤的初创企业世界里,视觉设计的重要性往往可以与杀手级用户体验比肩。在许多情况下,尤其对于 Web 初创企业而言,这两者都是不可或缺的。前不久我们在《右脑革命:别学编程了,学艺术吧》中也曾发出过重视设计的呼吁。如何才能提高初创企业的设计能力呢?以下是 9 位创始人的体会。

1.找到自己的方式

如果你是做设计的,要想提高技能可以去设计博客和展示好设计的网站如D-lists看看。如果不是,首先就得学习一些基本的设计原则(平衡、比例等),然后严格运用到项目当中。

2.画草图

画草图是我日常工作之外的一个爱好。我喜欢用草图把界面绘制出来,展示给某些客户看看他们是否认同。这是强化自己设计能力的一种简单易行的方式。

3.读《Design for Hackers》

David Kadavy 的《Design for
Hackers
》是一本极佳的设计入门读物,该书正是为左脑思考者(如程序员)而写。Kadavy 在书中重温了设计基础,并帮助你形成自己的判断思路。这不是一本可以一口气读完的书,但是他可以让你成为更好的设计师。

4.委派

我不是设计师。我可以就我的需求跟设计师扯上几个小时,但是我没办法从技术的角度或理论角度把想法复制出来。所以要么你得雇一位有经验的设计师,要么就得花大价钱找一位既能设计项目,又能照顾好用户体验的人。

5.用Adobe Illustrator和Lynda.com

装个 Adobe
Illustrator,然后到Lynda.com培训自己一个月。实践是让自己变得更好的最好方式。通过基本的是错法,我已经可以设计我们的销售材料、网站、Facebook 页面等。

6.学习行话

如果既不能把握又懂欣赏设计语言的话,是很难改进设计技能的。花点时间去了解一下线框和网站地图。了解“全局”和“第三级(tertiary)”导航栏的区别。知道什么时候使用矢量图形。对设计术语的了解有助于你改进技能以及更好地与设计师沟通。

7.逛逛设计博客

光是看看业界领袖的设计,读读他们写的文章就能够学会很多了。知道设计不好是一回事。但是能够弄明白为什么这种字体不行、为什么蓝色要更深一点则需要更多的技能。如果你希望与好的设计师一起工作,你就得学会沟通你的设计目标与反对理由。

8.学习,然后幕后指导

所有初创企业都需要好的设计师,创始人不要指望自己能担当这一角色。不过,你应该学会成为一名好的艺术总监。像其他学科一样学习设计—成为细节的奴隶,仔细研究其他成功网站,学会解释你的创意,然后很好地与自己的设计师沟通。

9.重视设计与创意

归根到底,你得重视设计和创意。如果你不重视这两样东西,自然也看不到发展这些技能的需要。仅仅因为你看不到它的价值,并不意味着你不需要做这些事情!

http://www.36kr.com/p/202269.html

研发周报:神奇!1KB JavaScript代码编写的3D蜜蜂

http://www.csdn.net/article/2013-03-29/2814706-3D-BEE-1KB-JavaScript

摘要:忙碌的一周总算过去,闲暇时不妨来细细品味我们精心为你呈现的这份技术大餐。本期热点:神奇!1KB JavaScript代码编写的3D蜜蜂;魔兽之父专访:今年游戏产业会出现一场革命;回顾:那些被平台方封杀的第三方应用。

我们挑选了本周研发频道的精华文章,推荐给您的绝对“有料”,闲暇时不妨来细细品味我们精心为你呈现的这份技术大餐,或许有您意想不到的收获。本期热点:神奇!1KB JavaScript代码编写的3D蜜蜂;魔兽之父专访:今年游戏产业会出现一场革命;回顾:那些被平台方封杀的第三方应用。

本期热点回放:

1.神奇!1KB JavaScript代码编写的3D蜜蜂

相信很多网友都还记得去年CSDN报道过同样是1KB JavaScript代码所编写的 3D玫瑰,或者2010年的 3D圣诞树。细心的网友可能注意到了,这两个神奇的作品都是以为西班牙开发者 Román Cortés在JS1K大赛上的投稿作品,今年他又带来了同样神奇的3D蜜蜂——长着触角、系着领带。

 

左、右分别是做过动作模糊化处理前、后的样子

由于 JS1K规则限制,该作品仅支持Firefox 18、Chrome 24以及Opera 12、13。如果你已安装以上浏览器,可以选择观看 线上演示

该作品的实现使用到了很多算法和渲染技术,作者在自己的 博客上详细地 解释了其技术原理。源代码如下(经过压缩):

 

1
_="G=[V,j=WV,-j]];jX=83,(+3+KD37Uji&32?70:,-80Y2]b=a[j]=c.cloneNode(LEb.getContext('2d'bAb;x=8# 8-x,6+x(j<17X7;<xeH@x*@x*y,.07$OsetIntervalcLE5;a e#73D-=7$7<ZaH0#-i$Q,-8Oj++;ZE8Q/((i+)%3095W0-e*5,6-e#*5#OG.sortreturn -eV]}OG.map=L^)/-[,=+]V];y=^X^)(X/6)+;x=0Y8Q;~~F2]W-@x-~F4]/1*^5+/F3]-@yL;!F2]&&Z_beginPath(_moJa(_bezierCurJ))})},j=4OjLj?cA1Q:9Q;z=x=*yZj|(z+1)%.5<.1&&(z*(191+(E527368,xB9+Ui3*e,(59525-y*(5188+(+(280*,yB8-(j&2]j||abs(E8-i%)<54-D&&YD2e4#+e*/|[1$4$,,$,8$][~~(D48+e/3)W90+D8,i])cos(>>j*4&15)sin(i(function(b#){F0])*--;)F1] StylE'hsla('+[for(`,j+y,Math.iG.push([),_drawImage(a[270+'%',.fill(j&15K(y=))/8`/2,y501660j/.height=1]+')',i(.5+.5#,e$,1@30+A.width=B*(10*Di/Ee=Fb[HRect(JveTo(Yi*Li=O);Qe3UPI*V[0W],X+Y-KZi^_a.`+x+";for(Y=0;$="`_^ZYXWVUQOLKJHFEDBA@$#    "[Y++];)with(_.split($))_=join(pop());eval(_)

相关作品: 

 

 

更新:

  1. 之前的版本在最新版Chrome和Firefox Nightly中存在无法显示翅膀的bug,已修复,线上演示地址并没有改变。
  2. 有网友提到另一个JS1K作品——Strange crystals(无尽的隧道)也非常神奇,3D蜜蜂的作者Román Cortés已将两者合而为一,并且仍然小于1KB。
  3. 应网友要求,笔者已将“3D蜜蜂”源代码以及“隧道里的3D蜜蜂”源代码打包上传,可以点击这里下载。

 

2.魔兽之父专访:今年游戏产业会出现一场革命

通过一次偶然的机会,krisviceral.com博主对魔兽之父Patrick Wyatt(Pat)进行了专访。在采访中Pat表示,相比与Warcraft和StarCraft,他更为Guild War感到骄傲,因为这款游戏的开发并没有像前两者那样拼命加班,而且也成功地锻炼了很多新人开发者。

Patrick Wyatt(Pat)曾任Blizzard开发&搜索副总裁,参与过Warcraft、StarCraft、Diablo、battle.net、Guild Wars等产品的开发,CSDN之前也对他所写的Warcraft、StarCraft开发历程相关博文进行了 编译、报道。本文译自krisviceral.com博主Kviceral对Pat的专访,Pat对游戏、AI、软件开发等方面都发表了自己的观点。

 

魔兽之父Patrick Wyatt

你曾参与过很多广受好评的游戏开发工作,其中最令你骄傲的是哪款?

Pat:这是一个很难回答的问题,因为每个游戏都倾注了我的心血。真的要算起来应该是Guild War,这是我们以小团队努力的成功。

我们从Blizzard离职后创立了一个新公司(ArenaNet),从零开始打造出了这款极具创新的游戏,甚至没有拼命地去加班工作,但是最终我们成功了。

在ArenaNet,我们招聘了很多刚毕业的学生,所以他们中很多人并不知道Guild War的开发流程与“传统”游戏开发有多少不同,但我很高兴他们没有和行业中大多数人一样常年crunching。

 

Guild War刚开发出来时,我们是唯一一个放弃订阅模式(通过支付每月的订阅费无限玩游戏)的商业MMORPG(大型多人在线角色扮演游戏),你在购买了这款游戏之后就可以无限制地玩。

 

在过去2-3年里,你最喜欢的游戏是什么?

Pat:我很享受DayZ这款僵尸题材的生存游戏,我也很惊讶自己竟会喜欢这款游戏,在地面上匍匐前进10分钟听起来可没什么意思,但是这款游戏的背景会让你为此着迷!我偶尔也玩League of Legends,同样很喜欢Kindom Rush(iPhone/iPad)这款深受Warcraft和StarCraft影响的塔防游戏,我和我的孩子都很爱玩。最近我还发现了AirMech,都是不错的游戏。

你认为过去5年里游戏界最大的创新是什么?

毫无疑问是“免费游戏”这种商业模式在西方市场的巨大成功——它改变了游戏的开发方式。设计师需要更致力于创造迷人的体验,以保持对用户的长期粘性;但负面的影响在于,有的设计师把他们的创造力用在了让用户沉迷于游戏或者是其它“黑暗技巧”,而非增加游戏的趣味性。 我希望使用“心理操纵”等“黑暗技巧”的游戏会越来越少。

另外一处在于移动游戏。创造有趣但是低成本的游戏非常值得赞赏!最近,我发现自己玩移动游戏的时间要远高于PC/主机游戏。相信随着游戏设计师更精于利用移动设备小屏幕的优势,能为我们带来更多优秀的作品。 >>查看原文

3.回顾:那些被平台方封杀的第三方应用

一款应用从萌芽状态到最后真正的上线,期间要经历种种磨难,好不容易推出了,然而还要遭到平台方的百般刁难。倘若你的应用不符合平台方的开发者协议,那么将会遭到封杀。那么之前所做的努力岂不功亏一篑?本文例举了部分被平台方封杀应用的典型案例,仅供参考。

NO.1 Adblock浏览器插件遭谷歌封杀

事件回放:2013年3月,谷歌把一款非常流行的应用Adblock Plus从它的应用商店Google Play里删除了,遭此毒手的还有其它几个跟屏蔽广告相关的应用。

官方理由:Adblock应用违反了“开发者部署协议”里 4.4 节的内容:“使用未经批准的方式妨碍用户对其它服务或产品的访问”。

开发者申诉:Adblock Plus的创始人Till Faida称:“我们知道广告收入对谷歌来说十分重要,但Adblock Plus并不是自动屏蔽所有的广告;而是让用户自由选择屏蔽还是不屏蔽”。

评论:谷歌是通告广告挣钱的,而Adblock Plus插件可屏蔽广告。坦率地说,谷歌拖到现在才对这样的应用下手才是让人意外的地方。

 

  • NO.2 Vintage Camera遭到Facebook扼杀
  • NO.3  第三方图片托管服务Twitpic、yfrog遭Twitter删除
  • NO.4  第三方支付遭Google Play封杀
  • NO.5  Dropbox SDK应用被苹果下架
  • NO.6  Fawave、微博通等新浪微博第三方应用遭封杀
  • NO.7  Emoti for Facebook被Facebook莫名删除

 

如同一位网友所说:没有真正的开放,也没有真正的公平竞争。平台方永远拥有一票否决权。那么,开发者如何才能在举步维艰中继续前行,绝处逢生呢?怎样才能避免遭到平台方的“不待见”呢?

笔者认为,平台方与开发者发生“摩擦”无非是侵权或是分红不均导致的。究竟该如何画这条“红线”,淘宝VP王文彬的回答很有代表性,此前他在接受 CSDN记者专访谈到如何看待平台方跟开发者逐利的问题表示:定义哪些是官方基础工具,哪些是第三方工具,很难画出一条明显的界限,会有一些模糊地带。有一点需要强调,淘宝不会为了跟开发者争夺利润去铲除第三方应用。淘宝更关心的是平台上用户数据的安全性和隐私能否得到保障,用户体验能否提高,淘宝希望看到平台上的第三方厂商做大做强。

4.飞信正面迎战微信,用户能否买账?

 

一份意外泄露的中移动内部PPT不仅搅皱了通信和互联网行业的一池春水,也搅乱了相关公司的心。中国移动欲重构飞信正面迎接微信,发改委称要对微信进行收费,那么用户能买账吗?

飞信是中国移动于2007年5月推出的一款综合通信服务,即融合语音(IVR)、GPRS、短信等多种通信方式,覆盖三种不同形态(完全实时的语音服务、准实时的文字和小数据量通信服务、非实时的通信服务)的客户通信需求,实现互联网和移动网间的无缝通信服务。

微信是腾讯公司于2011年1月21日推出的一款通过网络快速发送语音短信、视频、图片和文字,支持多人群聊的手机聊天软件。用户可以通过微信与好友进行形式上更加丰富的类似于短信、彩信等方式的联系。微信软件本身完全免费,使用任何功能都不会收取费用,微信时产生的上网流量费由网络运营商收取。微信是一种更快速的即时通讯工具,与传统的短信沟通方式相比,更灵活、智能,且节省资费。

2007年中国移动推出的聊天工具,至今已有6年光景的飞信软件,因与短信业务连通而广受关注,但目前处境并不乐观。据统计,如今飞信的用户量还不到腾讯QQ的五分之一,月均使用时间不足后者的四十五分之一。更让飞信感到恐惧的是,在2012中国互联网大会上,腾讯CEO马化腾在微博中宣布,微信用户人数已高达2亿,截至2013年01月注册用户量已经突破3亿。这个诞生仅2年多的手机聊天软件,发展如此迅猛,使业内人士颇为震惊。

微信因其操作简便,能广交好友,成为年轻人的交友工具。最初微信以文字通讯、手机图片分享为卖点,在后期开发过程中以发现“查看附近的人”和“摇一摇”功能更受用户欢迎。微信日新增用户以数十万数量级增长,确立了它在移动APP市场的绝对优势地位。

5.Web框架排行榜 Netty、Servlet和Vert.x位列前三一个框架对网站性能会产生多大影响呢?答案或许会让你大吃一惊。本文数据由自称CTO外包公司的TechEmpower测试所得:

框架排行榜:

 

令人惊讶地是,排在前三的是Netty、Vert.x和Java Servelet。那么它们比Ruby、Django到底快多少呢?一开始我们猜测可能会4倍差异,但在Vert.x和Ruby on Rails之间竟相差40倍,真让人瞠目结舌。

在此次测试中,我们根据精确的文档和社区意见进行配置和部署,尽可能按照真实的生产运行环境来配置和部署。关于每个框架的测试环境是如何搭建和测试的,我们都进行了详细的描述,大家可以 点击查看每个框架的测试详情

开源系列:

 

6.腾讯开源基于HTML5技术的专业级图像处理引擎 AlloyImage

腾讯Web前端 AlloyTeam又推出了最新的开源项目,一个基于HTML5技术的专业级图像处理引擎——AlloyImage(简称AI)以及一个在线Web图像处理平台—— AlloyPhoto(简称AP)。这预示着腾讯的Web前端团队将在底层基础技术层面做深入研究,并将更多的为业界提供基础技术服务,同时也将与HTML5梦工场一起推动HTML5技术在业界的广泛深入的应用。

AlloyImage是一个使用Javascript语言开发的,基于Web的在线图像处理引擎,除了核心底层图像处理引擎,还同时集成了一些方便快捷的图像处理API,您可以将它简单快捷的引用到您的Web网页中,做出与PhotoShop一样的优美效果。甚至,你可以用AlloyImage来开发一个Web在线图像处理软件,如:Web版的PhotoShop—— AlloyPhoto

 

AlloyPhoto简约版主界面

AlloyImage主要使用HTML5的canvas技术,并在多图层(layer)处理方面做了创新性的尝试,不仅如此,在技术实现层面,其架构方便扩展,使用者可以很容易写出现一个AlloyImage的滤镜插件,很多API支持重载,参数传递灵活。

目前,AlloyPhoto有 简约版和 专业版两个版本,其 源码托管在GitHub上。后续AlloyImage将会持续更新,将更多的处理效果与工具加入到其中,并且会在性能方面做出一些优化与尝试,AlloyImage将力求做一个开放、开源的强大的JS图像库。

7.Polycode:免费、开源的跨平台(游戏和APP)开发工具

Polycode是一款免费、开源的跨平台游戏和APP开发工具,遵循MIT协议,其核心采用C++编写,支持Mac OS、Windows、Linux,即将支持iOS和Android。

开发者可以把Ploycode当做C++库,也可以在其IDE里编写Lua脚本。Ploycode API的设计理念是“用更少的代码做更多的工作”。它使用scenegraph管理系统执行渲染和更新操作,也可以自定义渲染和更新。

在IDE里编写的代码可以直接发布到所支持的平台上,如果只是编写Lua脚本,那么IDE会自动创建“ployapp”应用程序格式,其中包含了所有的代码和资源,并且把它们封装到一个特定平台层上。

在GitHub上托管地址: https://github.com/ivansafrin/Polycode

此外,类似的C++跨平台开源游戏引擎和开发工具有: Moai:其主要面向PC、手机、及基于云端的游戏开发; Cocos2d-x:跨平台的开源移动2D游戏框架,易学易用; Cinder、 Unity3D以及GarageGames开源的Torque 3D游戏引擎

8.Linux容器运行时Docker开源

Docker是一个云计算平台,它利用Linux的LXC、AUFU、Go语言、cgroup实现了资源的独立,可以很轻松的实现文件、资源、网络等隔离,其最终的目标是实现类似PaaS平台的应用隔离。

值得关注的特性:

 

  • 文件系统隔离:每个进程容器运行在一个完全独立的根文件系统里。
  • 资源隔离:系统资源,像CPU和内存等可以分配到不同的容器中,使用cgroup。
  • 网络隔离:每个进程容器运行在自己的网络空间,虚拟接口和IP地址。
  • 日志:收集和记录标准流(stdout/stderr/stdin)里的每个进程容器,用于实时和批量检索。
  • 变更管理:被修改的容器文件系统会被提交到一个新的image,留着重用来创造更多的容器,无需模板或手动配置。
  • 交互式shell

前往官网查看更多详细介绍:http://docker.io/
 

源码托管地址:https://github.com/dotcloud/docker/

往期回顾:

研发周报:王淮给技术创业团队的十点建议

10步让你成为更优秀的程序员

作者: Paul Firth  来源: 外刊IT评论  发布时间: 2013-01-01 20:29  阅读: 11031 次  推荐: 89   原文链接   [收藏]  

  英文原文:10 steps to becoming a better programmer

这篇文章要介绍的,是我作为专业程序员这些年来学到的能真正提高我的代码质量和整体工作效率的 10 件事情。

1. 永远不要复制代码

不惜任何代价避免重复的代码。如果一个常用的代码片段出现在了程序中的几个不同地方,重构它,把它放到一个自己的函数里。重复的代码会导致你的同事在读你的代码时产生困惑。而重复的代码如果在一个地方修改,在另外一个地方忘记修改,就会产生到处是 bug,它还会使你的代码体积变得臃肿。现代的编程语言提供了很好的方法来解决这些问题,例如,下面这个问题在以前很难解决,而如今使用 lambda 却很好实现:

/// <summary>
/// 一些函数含有部分重复代码
/// </summary>
void OriginalA()
{
    DoThingsA();
    // unique code
    DoThingsB();
}
/// <summary>
/// 另外一个含有部分重复代码的函数
/// </summary>
void OriginalB()
{
    DoThingsA();
    // 没有重复的代码
    DoThingsB();
}

现在我们重构含有部分相同代码的函数,用 delegate 模式重写它们:

/// <summary>
/// Encapsulate shared functionality
/// </summary>
/// <param name="action">User defined action</param>
void UniqueWrapper(Action action)
{
    DoThingsA();
    action();
    DoThingsB();
}
/// <summary>
/// New implmentation of A
/// </summary>
void NewA()
{
    UniqueWrapper(() =>
    {
        // unique code
    });
}
/// <summary>
/// New implementation of B
/// </summary>
void NewB()
{
    UniqueWrapper(() =>
    {
        // unique code
    });
}

2. 留意你开始分心的时候

当你发现自己在浏览 facebook 或微博,而不是在解决问题,这通常是一种你需要短暂休息的信号。离开办公桌,去喝一杯咖啡,或去跟同事聊 5 分钟。尽管这样做看起来有点反直觉,但长久去看,它会提高你的工作效率。

3. 不要匆忙赶任务而放弃原则

当带着压力去解决一个问题或修改一个 bug,你很容易失去自制,发现自己匆匆忙忙,甚至完全忘了一直坚持的重要的测试过程。这通常会导致更多的问题,会让你在老板或同事眼里显得很不专业。

4. 测试你完成的代码

你知道你的代码能做什么,而且试了一下,它确实好用,但你实际上需要充分的验证它。分析所有可能的边界情况,测试在所有可能的条件下它都能如期的工作。如果有参数,传递一些预期范围外的值。传递一个 null 值。如果可能,让同事看看你的代码,问他们能否弄坏它。单元测试是到达这种目的的常规方法。

5. 代码审查

提交你的代码之前,找个同事一起坐下来,向他解释你做了哪些修改。通常,这样做的过程中你就能发现代码中的错误,而不需要同事说一句话。这比自己审查自己的代码要有效的多得多。

6. 让代码更少

如果你发现写了大量的代码来解决一个简单的问题,你很可能做错了。下面的 boolean 用法是一个很好的例子:

if (numMines > 0)
{
   enabled=true;
}
else
{
   enabled=false;
}

这时你应该写成这样:

enabled = numMines > 0;

代码越少越好。这会使 bug 更少,重构可能性更小,出错的几率更小。要适度。可读性同等重要,你可不能这样做而使代码丧失可读性。

7. 为优雅的代码而努力

优雅的代码非常的易读,只用手边很少的代码、让机器做很少的运算就能解决问题。在各种环境中都做到代码优雅是很难的,但经过一段时间的编程,你会对优雅的代码是个什么样子有个初步的感觉。优雅的代码不会通过重构来获得。当你看到优雅的代码是会很高兴。你会为它自豪。例如,下面就是一个我认为是优雅的方式来计算多边形面积的方法:

static public double GetConvexPolygonArea (Vector2[] vertices)
{
    double area = 0;
    for (int i = 0; i < vertices.Length; i++)
    {
        Vector2 P0 = vertices[i];
        Vector2 P1 = vertices[(i + 1) % vertices.Length];
        area += P0.Wedge (P1);
    }
    return area / 2;
}

8. 编写不言自明的代码

勿庸置疑,注释是编程中很重要的一部分,但能够不言自明的代码更胜一筹,因为它能让你在看代码时就能理解它。函数名变量名要慎重选择,好的变量/方法名字放到语言语义环境中时,不懂编程的人都能看懂。例如:

void DamagePlayer (Player player, int damageAmount)
{
    if (!player.m_IsInvincible && !player.m_IsDead)
    {
        player.InflictDamage ( damageAmount );
    }
}

能自我说明的代码不能代替注释。注释是用来解释“为什么”的,而自我说明的代码是来描述“是什么”的。

9. 不要使用纯数字

直接把数字嵌入代码中是一种恶习,因为无法说明它们是代表什么的。当有重复时更糟糕——相同的数字在代码的多个地方出现。如果只修改了一个,而忘记了其它的。这就导致 bug。一定要用一个命名常量来代表你要表达的数字,即使它在代码里只出现一次。

10. 不要做手工劳动

当做一系列动作时,人类总是喜欢犯错误。如果你在做部署工作,并且不是一步能完成的,那你就是在做错事。尽量的让工作能自动化的完成,减少人为错误。当做工作量很大的任务时,这尤其重要。

11. 避免过早优化

当你要去优化一个已经好用的功能代码时,你很有可能会改坏它。优化只能发生在有性能分析报告指示需要优化的时候,通常是在一个项目开发的最后阶段。性能分析之前的优化活动纯属浪费时间,并且会导致 bug 出现。

好吧,我说是 10 个,但你却得到了额外赠送的一个!

这些就是我要说的,我希望它们能帮助你改进编程开发过程。

下次再见!祝快乐!

Cheers, Paul.

摘自:http://kb.cnblogs.com/page/168183/

如何编写出拥抱变化的代码?

http://www.csdn.net/article/2013-02-25/2814251-coding-change

发表于2013-02-26 09:06| 13321次阅读| 来源net.tutsplus| 70 条评论| 作者Patkos Csaba

摘要:编写高效优质的代码一直是程序员所追求的目标之一,那么什么样的代码才叫优质呢?其中最重要的莫过于易维护、易修改。本文作者从面向对象和SOLID两大方面,非常详细地总结了如何编写出易修改的代码,绝对让你受益匪浅。

在实际的开发中,编写出易维护和易接受变化的代码并非易事,想要实现可能更加困难重重:源码难于理解、依赖关系指向不明、耦合也很令人头疼。难道就真的就没有办法了吗?本文中我们一起探讨几个技术原则和一些编码理念,让你的代码跟着需求走,而且易维护易拓展。

介绍些面向对象方法

面向对象编程(OOP)是一种很受欢迎的编程思想,它保证了代码的组织性和重用性。软件公司采用OOP思想编程已经好多年了,如今仍然在项目开发中使用这一思想。OOP拥有一系列非常好的编程原则,如果使用恰当,它会让你的代码更好、更整洁和更易维护。

1.内聚力

这里的内聚力是指拥有一些共同的特征的东西而逐渐凝聚到一起,而不能在一起的东西则会被移除出去。可以用一个类来说明内聚力:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ANOTCohesiveClass {
   private $firstNumber;
   private $secondNumber;
   private $length;
   private $width;
   function __construct($firstNumber$secondNumber) {
      $this->firstNumber = $firstNumber;
      $this->secondNumber = $secondNumber;
   }
   function setLength($length) {
      $this->length = $length;
   }
   function setHeight($height) {
      $this->width = $height;
   }
   function add() {
      return $this->firstNumber + $this->secondNumber;
   }
   function subtract() {
      return $this->firstNumber - $this->secondNumber;
   }
   function area() {
      return $this->length * $this->width;
   }
}

 

该例定义了一个类以及一些表示数字和大小的字段。而这些属性通过他们的名称来判断是否应该在一起。add()和substract()方法来对两个number进行操作,此外还定义了area()来操作length和width这两个字段。

这个类只负责各个独立的群体信息,显然,内聚力很低。重构上面的例子:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ACohesiveClass {
   private $firstNumber;
   private $secondNumber;
   function __construct($firstNumber$secondNumber) {
      $this->firstNumber = $firstNumber;
      $this->secondNumber = $secondNumber;
   }
   function add() {
      return $this->firstNumber + $this->secondNumber;
   }
   function subtract() {
      return $this->firstNumber - $this->secondNumber;
   }
}

重构以后,该类明显变成了高内聚特征的类。为什么?因为这个类里的每个部分都与另外一部分彼此联系。虽然在实际开发中编写出高内聚的类比较困难,但开发人员应该坚持这样做,坚持就是胜利。
2.正交性

就简单而言,正交是指隔离或排除副作用。一个方法、类或者模块改变了其他无关的方法、类或模块就不是正交。例如,飞机的黑匣子就具有正交性,它自身就具备电源、麦克风和传感器等这些功能。而它对外在的其他东西没有任何影响,它只提供一种机制,用来保存和检索飞行数据。

一个典型的非正交系统例子就是汽车电子设备。提高汽车的速度也存在些负面影响,比如会增加无线电音量,然而对汽车来说,速度并不是正交。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Calculator {
   private $firstNumber;
   private $secondNumber;
   function __construct($firstNumber$secondNumber) {
      $this->firstNumber = $firstNumber;
      $this->secondNumber = $secondNumber;
   }
   function add() {
      $sum $this->firstNumber + $this->secondNumber;
      if ($sum > 100) {
         (new AlertMechanism())->tooBigNumber($sum);
      }
      return $sum;
   }
   function subtract() {
      return $this->firstNumber - $this->secondNumber;
   }
}
class AlertMechanism {
   function tooBigNumber($number) {
      echo $number 'is too big!';
   }
}

在这个例子中,Calculator类里的add()方法里列了几个意想不到的行为:它生成AlertMechanism对象并调用其中的一个方法。实际上,该库的使用者并不希望消息被打印到屏幕上,相反,他们则是要计算数字之和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Calculator {
   private $firstNumber;
   private $secondNumber;
   function __construct($firstNumber$secondNumber) {
      $this->firstNumber = $firstNumber;
      $this->secondNumber = $secondNumber;
   }
   function add() {
      return $this->firstNumber + $this->secondNumber;
   }
   function subtract() {
      return $this->firstNumber - $this->secondNumber;
   }
}
class AlertMechanism {
   function checkLimits($firstNumber$secondNumber) {
      $sum = (new Calculator($firstNumber$secondNumber))->add();
      if ($sum > 100) {
         $this->tooBigNumber($sum);
      }
   }
   function tooBigNumber($number) {
      echo $number 'is too big!';
   }
}

这样明显好多了,AlertMechanish在Calculator中没有任何负面影响,相反,在任何需要弹出警告的地方都可以使用AlertMechanish。

3.依赖和耦合

大多数情况下,这两个单词是可以互换的,但是在某些情况下,又存在优先级关系。

那么,什么是依赖呢?当对象A需要使用对象B时,为了执行其规定的行为,我们说A依赖B。在OOP中,依赖是极其常见的。对象之间经常互相依赖才发挥功效。因此消除依赖是一项崇高的追求,这样做几乎是不可能的。控制依赖和减少依赖则是非常完美的。

就紧耦合(heavy-coupling)和松耦合(loose-coupling)而言,通常是指一个对象依赖于其他对象的程度。

在一个松耦合系统中,一个对象的变化会减少对其依赖对象的影响。在这样的系统中,类取决于接口而不是具体的实现(将会在下面提到)。这就是为什么松耦合系统对修改更加开放的原因。

Coupling in a Field

让我们看下面这个例子:

 

1
2
3
4
5
6
class Display {
   private $calculator;
   function __construct() {
      $this->calculator = new Calculator(1,2);
   }
}

 

这段代码很常见,在该例中,Display类依赖Calculator类并直接引用该类。Display类里的 $calculator字段属于Calculator类型。该对象和字段直接调用Calculator的构造函数。

 通过访问其他类方法进行耦合

大家可以先看下面的代码:

 

1
2
3
4
5
6
7
8
9
class Display {
   private $calculator;
   function __construct() {
      $this->calculator = new Calculator(1, 2);
   }
   function printSum() {
      echo $this->calculator->add();
   }
}

Display类调用Calculator对象的add()方法。这是另外一种耦合方式,一个类访问另外一个类的方法。

通过方法引用进行耦合

你也可以通过方法引用进行耦合:

 

1
2
3
4
5
6
7
8
9
10
11
12
class Display {
   private $calculator;
   function __construct() {
      $this->calculator = $this->makeCalculator();
   }
   function printSum() {
      echo $this->calculator->add();
   }
   function makeCalculator() {
      return new Calculator(1, 2);
   }
}

需引起注意的是,makeCalculator()方法返回一个Calculator对象,这也是一种依赖。

利用多态进行耦合

遗传可能是依赖里的最强表现形式。

 

1
2
3
4
5
class AdvancedCalculator extends Calculator {
   function sinus($value) {
      return sin($value);
   }
}

通过依赖注入降低耦合

开发人员可以通过依赖注入来降低耦合度,例如:

1
2
3
4
5
6
7
class Display {
   private $calculator;
   function __construct(Calculator $calculator = null) {
      $this->calculator = $calculator ? : $this->makeCalculator();
   }
// ... //
}

利用Display的构造函数对Calculator对象进行注入,从而减少了Display对Calculator类产生的依赖。

利用接口降低耦合

例如:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
interface CanCompute {
   function add();
   function subtract();
}
class Calculator implements CanCompute {
   private $firstNumber;
   private $secondNumber;
   function __construct($firstNumber$secondNumber) {
      $this->firstNumber = $firstNumber;
      $this->secondNumber = $secondNumber;
   }
   function add() {
      return $this->firstNumber + $this->secondNumber;
   }
   function subtract() {
      return $this->firstNumber - $this->secondNumber;
   }
}
class Display {
   private $calculator;
   function __construct(CanCompute $calculator = null) {
      $this->calculator = $calculator ? : $this->makeCalculator();
   }
   function printSum() {
      echo $this->calculator->add();
   }
   function makeCalculator() {
      return new Calculator(1, 2);
   }
}

 

该代码定义了一个CanCompute接口,在OOP中,接口可以看作一个抽象类型,它所定义的成员必须由类或结构来实现。在上述代码中,Calculator类来实现CanCompute接口。

Display构造函数期望有个对象来实现Cancompute接口,这时,Display的依赖对象Calculator被打破。然而,我们可以创建另一个类对象来实现Cancompute,并且传递一个对象到Display的构造函数中。Display现在只依赖于Cancompute接口,但即使这样依赖关系仍然是可选的。如果我们不传递任何参数给Display的构造函数,那么它将通过调用makeCalculator()方法来创建一个Calculator对象。这种技术经常被开发者们使用,尤其对驱动测试开发(TDD)极其有帮助。

SOLID原则

SOLID是一套代码编写守则,也就是大家常常说的敏捷开发原则,最初由Robert C. Martin所提出。使用它编写出来的代码不仅干净整洁,而且易维护、易修改和易扩展。实践表明,其在可维护性上有着非常积极的影响,更多资料大家可以阅读: Agile Software Development, Principles, Patterns, and Practices

SOLID所涵盖的话题非常广,下面我将会针对本文的主旨介绍一些简单易学的方法。

1.单一责任原则(SRP)

一个类只干一件事。听起来简单,但在实践中却可能相当难。

 

1
2
3
4
5
6
class Reporter {
   function generateIncomeReports();
   function generatePaymentsReports();
   function computeBalance();
   function printReport();
}

 

查看上面的代码,你认为该类的受益者会是哪个部门?会计部是用于收支平衡、财政部可能用来编写收入/支出报告,甚至归档部来打印和存档报告。然而每个部门都希望有属于自己的方法,并且根据自身需求来做些自定义的方法。

这样的类往往都是高内聚低耦合的。

2.Open-Closed原则(OCP)

类(和模块)应具备很好的功能扩展性,以及对现有功能具有一定的保护能力。让我们一起来看下典型的电风扇例子,你有一个开关来控制风扇:

 

1
2
3
4
5
6
7
8
9
10
11
12
class Switch_ {
   private $fan;
   function __construct() {
      $this->fan = new Fan();
   }
   function turnOn() {
      $this->fan->on();
   }
   function turnOff() {
      $this->fan->off();
   }
}

 

这段代码创建了Switch_类,用来创建和控制Fan对象。注意这里的下划线,在PHP中是不允许把类名定义为Switch的。

这时,你的老板希望能利用该开关控制电风扇上的电灯,那么你就不得不修改Switch_这个类。

对现有代码进行修改存在一部分风险,很有可能对系统其他部分产生影响。所以在添加新功能时的最好的方法是避开现有功能。

在OOP中,你可以发现Switch_对Fan类有很强的依赖性。这正是我们的问题所在,基于此,做出如下修改:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Switchable {
   function on();
   function off();
}
class Fan implements Switchable {
   public function on() {
      // code to start the fan
   }
   public function off() {
      // code to stop the fan
   }
}
class Switch_ {
   private $switchable;
   function __construct(Switchable $switchable) {
      $this->switchable = $switchable;
   }
   function turnOn() {
      $this->switchable->on();
   }
   function turnOff() {
      $this->switchable->off();
   }
}

 

该代码定义了一个Switchable接口,它里面所定义的方法需要开关启用选项来实现。Fan对象实现Switchable和Switch_并且接受一个参数到Switchable对象的构造函数里。

这样做有哪些好处?

首先,该解决方案打破了Switch_和Fan之间的依赖关系。Switch_不知道它要开启风扇,并且也不关心。其次引进的Light类不会影响Switch_或Switchable。难道你想用Switch_类来控制Light对象吗?代码如下:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Light implements Switchable {
   public function on() {
      // code to turn ligh on
   }
   public function off() {
      // code to turn light off
   }
}
class SomeWhereInYourCode {
   function controlLight() {
      $light new Light();
      $switch new Switch_($light);
      $switch->turnOn();
      $switch->turnOff();
   }
}

 

3.Liskov替换原则(LSP)

LSP是指子类永不打破父类的功能,这点是非常重要的。用户定义一个子类只是希望能实现其自有功能,而不是去影响原来的功能。

乍看有点困惑,还是让我们一起来看看代码吧:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
class Rectangle {
   private $width;
   private $height;
   function setWidth($width) {
      $this->width = $width;
   }
   function setHeigth($heigth) {
      $this->height = $heigth;
   }
   function area() {
      return $this->width * $this->height;
   }
}

 

定义一个简单的Rectangle类,我们可以设置它的高度和宽度,并且area()方法可以计算出该矩形的面积。再看下面例子:

 

1
2
3
4
5
6
7
class Geometry {
   function rectArea(Rectangle $rectangle) {
      $rectangle->setWidth(10);
      $rectangle->setHeigth(5);
      return $rectangle->area();
   }
}

 

rectArea()方法接受一个Rectangle对象作为一个参数,设置其高度和宽度并且返回该图形的面积。

正方形乃是矩形中的一个特殊图形,我们定义Square类来继承Rectangle:

 

1
2
3
class Square extends Rectangle {
   // What code to write here?
}

 

我们有好几种方法来重写area()方法并且返回该正方形的宽度:

 

1
2
3
4
5
6
7
8
9
10
class Rectangle {
   protected $width;
   protected $height;
   // ... //
}
class Square extends Rectangle {
   function area() {
      return $this->width ^ 2;
   }
}

 

把Rectangle的字段改为protected,好让Square有访问的权限。从几何的角度来看是非常合理的,因为正方形的边长是相等的,所以返回正方形的宽度是非常合理的。

然而从编程的角度来看又存在一个问题;如果Square是一个Rectangle,把它馈入到Geometry类是没有任何问题的,但这样做以后,Geometry的代码就显的多余,毫无意义可言。它设置了高度和宽度两个值,这也就是为什么square不是rectangle编程。LSP正很好是说明了这一点。

4.接口隔离原则(ISP)

该原则主要集中用在把大接口分成多个小接口和特殊的接口。基本思路是在同一个类中,不同的用户不应该知道不同的接口——除非该用户需要用到那个接口。即使一个用户不需要使用该类的所有方法,但它仍然依赖于这些方法。所以为什么不根据用户需要定义相应的接口呢?

想象下,如果我们要实现一个股票市场应用,我们要有一个经纪人(Broker)来购买和出售股票,并且报告每天的收益和损失。一个简单的实现方法是定义一个Broker接口,一个NYSEBroker类用来实现Broker和一些用户的接口类:创建交易(TransactionUI)和写报告(DailyReporter)。代码可以类似下面这样:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
interface Broker {
   function buy($symbol$volume);
   function sell($symbol$volume);
   function dailyLoss($date);
   function dailyEarnings($date);
}
class NYSEBroker implements Broker {
   public function buy($symbol$volume) {
      // implementsation goes here
   }
   public function currentBalance() {
      // implementsation goes here
   }
   public function dailyEarnings($date) {
      // implementsation goes here
   }
   public function dailyLoss($date) {
      // implementsation goes here
   }
   public function sell($symbol$volume) {
      // implementsation goes here
   }
}
class TransactionsUI {
   private $broker;
   function __construct(Broker $broker) {
      $this->broker = $broker;
   }
   function buyStocks() {
      // UI logic here to obtain information from a form into $data
      $this->broker->buy($data['sybmol'], $data['volume']);
   }
   function sellStocks() {
      // UI logic here to obtain information from a form into $data
      $this->broker->sell($data['sybmol'], $data['volume']);
   }
}
class DailyReporter {
   private $broker;
   function __construct(Broker $broker) {
      $this->broker = $broker;
   }
   function currentBalance() {
      echo 'Current balace for today ' date(time()) . "\n";
      echo 'Earnings: ' $this->broker->dailyEarnings(time()) . "\n";
      echo 'Losses: ' $this->broker->dailyLoss(time()) . "\n";
   }
}

 

虽然这段代码可以正常工作,但它违反了ISP。DailyReporter和TransactionUI都依赖Broker接口。然而,它们只使用接口的一部分。TransactionUI使用buy()和sell()方法,而DailyReporter只用到dailyEarnings()和dailyLoss()方法。

你怀疑Broker没有内聚力,因为它的一些方法没有任何相关性。也许你说的对,但是具体答案还得由Broker说了算;销售和购买可能与当前的盈余有相当大的关系。例如当亏本的时候有可能就不会执行购买操作。

此时,你可能会说Broker违反了SRP,因为有两个类以不同的方式在使用它,可能有两个不同的执行者。好吧,其实它并没有违反SRP。唯一的执行者就是Broker。他会根据当前的形式做出购买/出售操作,其最终的依赖对象是整个系统和业务。

毫无疑问,上述代码肯定是违反了ISP,两个UI类都依赖于整个Broker。这是很常见的问题,改变下观点,代码可以这样修改:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
interface BrokerTransactions {
   function buy($symbol$volume);
   function sell($symbol$volume);
}
interface BrokerStatistics {
   function dailyLoss($date);
   function dailyEarnings($date);
}
class NYSEBroker implements BrokerTransactions, BrokerStatistics {
   public function buy($symbol$volume) {
      // implementsation goes here
   }
   public function currentBalance() {
      // implementsation goes here
   }
   public function dailyEarnings($date) {
      // implementsation goes here
   }
   public function dailyLoss($date) {
      // implementsation goes here
   }
   public function sell($symbol$volume) {
      // implementsation goes here
   }
}
class TransactionsUI {
   private $broker;
   function __construct(BrokerTransactions $broker) {
      $this->broker = $broker;
   }
   function buyStocks() {
      // UI logic here to obtain information from a form into $data
      $this->broker->buy($data['sybmol'], $data['volume']);
   }
   function sellStocks() {
      // UI logic here to obtain information from a form into $data
      $this->broker->sell($data['sybmol'], $data['volume']);
   }
}
class DailyReporter {
   private $broker;
   function __construct(BrokerStatistics $broker) {
      $this->broker = $broker;
   }
   function currentBalance() {
      echo 'Current balace for today ' date(time()) . "\n";
      echo 'Earnings: ' $this->broker->dailyEarnings(time()) . "\n";
      echo 'Losses: ' $this->broker->dailyLoss(time()) . "\n";
   }
}

 

修改后的代码明显变的有意义而且尊重了ISP。DailyReporter只依赖BrokerStatistics,它无需关心和知道出售和购买这两个操作。另一方面,TransactionUI只关心购买和出售。NYSEBroker和先前的定义是一样的,实现BrokerTransactions和BrokerStatistics接口。

更复杂的例子你可以前往Rober C.Martin博客上查看 The Interface Segregation Principle里的首篇论文。

5.依赖倒置原则(DIP)

这条原则指出高层模块不应该依赖低层模块,两者都应该依赖于抽象。抽象不应该依赖细节,细节反过来应依赖于抽象。简单地说,你应该尽可能的依赖于抽象而不是实现。

DIP的诀窍是你想反转依赖,但是又想一直保持着整个控制流。回顾下OCP(Switch和Light类),在原始实现中是直接利用开关来控制灯的。

 

你会看到整个依赖和控制流都是由Switch流向Light。当不想直接控制Light时,你可以引进接口这一概念。

 

非常神奇!引进接口后,代码同时满足了DIP和OCP两大原则。正如你上图所看到的,倒置了依赖,但整个控制流是不变的。

高级设计

关于代码的另一重要方面是高级设计和通用体系结构。一个混乱的架构所产生的代码往往是很难修改的,所以保持一个干净整洁的架构是必不可少的,第一步就是理解如何根据不同的内容分离代码。

 

在这张图中,最主要的部分是业务逻辑,它能够如预期那样正常有效的工作并且与其他部分不存在任何瓜葛。站在高级设计角度可以看作为正交性。

从右边的“main”开始看,箭头进入应用程序——创建对象工厂。一个理想的解决方案是从各个特定的工厂中得到相应的对象,但这有点不切实际。不过当有机会这样做的时候还是要使用,并且让它们保持在业务逻辑之外。

再看底部,定义持久层(数据库、文件访问、网络通信)用来保证信息的持久性。业务逻辑层是没有对象知道持久层是如何工作的。

左边则是交互机制。MVC比如Laravel、CakePHP,只能是交付机制而已。

当你看到应用程序架构或目录时,你应该注意其架构是说明程序将要做什么,而不是使用什么技术或数据库。

最后,为了确保所有的依赖项都指向业务逻辑层。用户接口、工厂、数据库则是具体的实现,而你永远不要只依赖于它们。依赖倒置指向业务逻辑模块,无需修改业务逻辑的依赖关系即可允许我们改变依赖。

关于设计模型

在使代码变得易于修改和理解的过程中,设计模型扮演着非常重要的角色。从结构的角度来看,设计模式显然是很有好处的,它们是行之有效并且深思熟虑的解决方案。更多关于设计模式内容,可以前往 Tuts+ Premium course 

测试的力量

测试驱动开发(TDD)所编写出来的代码是很容易测试的。TDD迫使你尊重以上原则来编写代码,从而使你的程序更易被测试。单元测试运行速度很快,应该非常快,当你在一个类里使用10个对象来测试一个单独方法时,你的代码很有可能是有问题的。

总结

俗话说,实践乃是检验真理的唯一标准,所以开发者只有在平时的工作中坚持使用这些原则才能编写出理想的代码。与此同时,不要轻易满足于自己所编写出的代码,要努力让你的代码易于维护、干净并且拥抱变化。(编译/张红月 责编/王然)

来自: net.tutsplus

自动生成代码工具

背景

很多业务很多功能对应的操作都很接近,很多代码都是共通的,只需改动一部分。如果新建一张表,共通的代码能自动生成就好,不需要拷贝原来的代码还要修改,能节省很多时间。

准备

1、MyBatis使用Generator自动生成代码,工具配置、数据库连接读取等通过它来完成。需要下载mybatis-generator-core-1.3.2

MyBatis生成的是固定格式的的几个模板,而且只能是java相关的代码,并且很多东西还不太灵活,不能满足很多业务。

2、通过velocity生成模板文件。需要下载velocity1.7

设计

1.一个表对应的数据通过MyBatis读取。

2.按一定规则自定义一套模板文件,通过velocity生成每个表对应的模板文件。自定义的模板文件可以满足PHP/JAVA等各种语言的代码生成。

开发语言:java

详细说明及源码

正准备开发中,待续!

 

 

StarCraft开发:用肮脏的技巧解决难题

发表于2013-02-28 16:15| 7145次阅读| 来源Code of Honor| 53 条评论| 作者Patrick Wyatt

摘要:在之前的文章中,Warcraft之父讲述了自己是如何以及为何重启StarCraft的开发,在“离终止日期仅剩下两月”的压迫下,开发团队不得不做出了很多错误的决定,以至于带来了众多遗留问题。在本文中,Patrick讲述了其中一个难题——路径寻找,以及他最终是如何通过肮脏的技巧来解决的。

游戏单位的路径寻找功能是很多玩家从未关注的东西,除非它存在巨大的问题,然后这个并不重要的问题会引发玩家更大的愤怒,最终导致“毁灭世界”的大灾难。在StarCraft的开发过程中,很多时候其路径寻找功能完全不起作用。

随着StarCraft开发的延长,它看起来像是个不会完工的项目:永远离发布还有两个月,但没有认识征兆显示正在接近传说中的发布日期。“幸运”——我有意地使用这个词——的是,Blizzard之前就有拖延游戏发布的经验。

我们一直都有“目标”(尽管用“期望”也许更好) 发布日期,但除非游戏已经准备完毕,否则我们并不打算发布它。Blizzard“静待完善”的政策意味着承诺只发布高质量产品,也意味着无法保证游戏的发布日期。

无论如何,若想发布最终 产品,我们还需要解决一系列难题。和其他游戏游戏一样,在开发的收官阶段总需要寻找并修补各式各样的缺陷,其数量可以千记。

有的只是小问题,只需稍作修补;不幸的是,并非所有缺陷都是如此。

其它的,比如之前提到的多人同步bug,会需要编程团队多个成员持续工作数周来解决。其他程序员,比如 Ages of EmpiresSupreme Commander的开发者,也报告过类似的同步bug经历。

有的bug与开发过程本身相关。Protoss Carrier(神族的航空母舰)相比于其他单位总是有延迟,因为它处理任何事情的方式与众不同。在某个时间点,Carrier的控制从游戏代码主干中分支出来,然后在重新集成时发生了预料之外的问题,因此任何其它单位新增的功能都需要为Carrier重新实现;每次其它单位中修复的bug,也会在Carrier发现类似bug,不过修复起来更加麻烦。

但拖延StarCraft发布的最大问题还要数路径寻找问题。

路径寻找功能并非完全坏掉的,恰恰相反,大多数情况下表现得非常好,但是很多边界情况下的问题导致游戏迟迟无法发布。

游戏单位常常会卡住,在战场上停下来。大多数情况下,它们最终能够找到正确的路径,缓慢地向前进或者在周围旋转;但有时候会越来越挤,最终无法再前进一步,整个任务会陷入上下班一样的堵塞状况。

这个问题让玩家感到沮丧,也让AI变得虚弱,经常无法平衡任务并且浪费设计时间。

虽然我并非顶级RTS玩家,但是在游戏发布前我还是算擅长的,因为我发现Goliaths(人族巨人)因为差劲的路径寻找问题被过度加强了,因此通过小心指引Goliaths绕过障碍,我就能在关键时刻顶住火力,战胜“宏”玩家。可惜的是,Goliaths被平衡后我的技巧就不再起作用了。sigh

早期的路径寻找功能非常粗糙——虽然有精选的算法指引单位运动,但是项目过程中一些糟糕的决定导致其天生残疾。

我们怎么会在这里?


之前的一篇文章里,我提到了路径寻找难题。StarCraft使用了Warcraft的游戏引擎,使用tile引擎来渲染地形,而这个tile引擎为处理16个8×8组成的32×32方形单元做了专门的优化。我在Warcraft中设计这样的架构是因为:之前为超级任天堂和Genesis开发游戏时都运作良好。这些游戏机有专门绘制8×8tile的硬件支持,而这些功能在PC上也容易模拟。

因为Warcraft I和II和的视角近乎是自上而下,游戏地图上所有物体(森林、地形、建筑)的边缘都是是水平或者垂直,因此渲染引擎将世界当作正方形tile地图来渲染有益于路径寻找,在这两个游戏中,每个32×32的像素块要么可通过要么不可通过。如下图,我将可通过的tile注以绿色边缘。图中有的tile显示为可通过,实际上不可以;在下方你能看到兵营建筑的图形就不能 填满它所坐落的48×48区域,剩下两个看起来可通过,实际上不可通过的tile。

tile为32×32的Warcarft 2地图

但是,为了将StarCraft变得更有视觉吸引力(详情请见《StarCraft: Orcs in Space 在欺骗中浴火重生》),开发团队将其切换为对角形式。但是底层的地形引擎并没有为对角tile重新设计,仅仅重新绘制了tile。

新视角看起来非常不错,但为了保证路径寻找的正常工作,必须增加路径寻找地图的分辨率:现在精细为16个8×8的tile。得益于更高的分辨率,地图上也可以呈现出更多的单位,这也意味着为了搜寻更大的路径空间需要花费更多的计算能力。

现在路径寻找更具挑战性,因为tile的对角线边缘将很多正方形tile不均等地分割了开来,这样确定一个一个tile可通过或者不可通过变得更加困难。为了保证所有的tile标记的正确,需要更多的计算工作。

StarCraft地图编辑器非常难写,因为很多为了将对角图形放置在方形地图上带来了很多边界情况。编写特制的“tile修复”代码至少需要数月时间。

和使用了对角渲染引擎的Diablo不同,StarCraft开发团队不得不继续使用旧引擎,尽管每周都会发现更多的新问题。

这张图显示了一座桥是如何以8×8的拼;tile组成的,有几个标注为了绿色。透视角度的贴图不均等地覆盖在tile上,这导致大桥的两边都出现了楼梯一样的边缘,正如你在图中所见,红线将每个tile都切割为不规则的形状。

使用8×8tile的StarCraft

因为项目一直受到离发布仅剩两个月的压力,所以不可能为了让路径寻找变得更容易而重新设计地形引擎,也因此路径寻找功能一直难以完善。为了解决棘手的边缘问题,路径控制代码成为了一个巨大的状态机,用来处理各种专门的“让我离开这里”的命令。

高峰时间


如果说路径寻找问题有一个最严重的情况,那一定是采伐单位(人族SCV,虫族drone,神族probe),它们在采集水晶或者vespene gas(之后的矿物“minerals”)会产生严重的堵塞,最后慢慢停止。当玩家在忙于管理进攻或者第二基地时,基地的采伐单位就会堵塞,因此金库不再有收入,然后玩家会发现因为没有足够的金钱而无法继续建造。

资源采集的基本问题在于,玩家希望最大化每个矿源采伐者的数目以获得最多的金钱。采伐者会经常来往于矿产与基地之间,所以经常会和其他采伐者相向而行,并且“撞”在一起。一个狭小的空间涌入了过多的采伐者,往往会导致其中大部分会阻塞在一起直到矿藏被采伐完。

我们如何从这里出去?


我记不清那时是自愿还是被要求来解决这个问题了,在阅读了大部分的路径寻找代码后,我明白了自己并没有那么聪明以至“解决这个难题”,因此我想出了一个肮脏的小把戏。

程序员经常会沉迷于寻找最纯粹、简洁甚至崇高、神圣的解决方案,但在项目中必须有所舍弃。如果解决得很好,没有人会注意到实现背后邪恶的妥协,正如Brandon Sheffield在《Dirty Coding Tricks》一文中所写的那样。

我的想法很简单,只要采伐者往采矿的方向前进,或者是往回运送矿物时,它们会自动忽略与其它单位的碰撞。通过消除采伐者单位间碰撞的代码,运输过程再也不会遇到阻塞的状况,因此采伐者能够高效地运作。

如果你选中大群正在采集水晶的采伐者并命令其停下来,你会发现他们 会立刻展开、寻找没有被其它单位占领的tile。

如果你仔细观察的话会发现这很明显,但它就是在你的眼前隐藏了——并没有触发你视觉意识层面,当然专业玩家和地图开发者/MOD开发者除外。

简而言之,它就是管用,这就是最好的技巧。

虽然,想要完成游戏还有很多工作要做,但是这次技巧让我们免于重新开发游戏引擎,节约了很多时间。

其它单位的路径寻找问题开发团队已经能够胜任了,因此没什么可说的,尽管Protoss Dragoon(神族龙骑士)是个例外,因为它作为最大的地面单位,还是经常会出现路径寻找问题。

最终结果是,路径寻找已经足够胜任,我们也从中学到了沉重的教训。

原文链接: Code of Honor

相关文章:

StarCraft开发的荆棘之路

StarCraft开发:如何避免链表引起的游戏崩溃

StarCraft: Orcs in Space 在欺骗中浴火重生

较新的文章

Copyright © 2024 优大网 浙ICP备13002865号

回到顶部 ↑