經過3個月的開發,有很多感觸。
以前一直以為技術是開發成敗的第一因素,現在發現,等到你代碼寫的時間夠長,經驗夠豐富,什么功能都能隨手完成,對於業務的分析能力變成了第一位。
爐石山寨版的BS版本用到的HTML5的SVG,我看了一個下午的教程,借鑒以前GUI+和HTML的經驗,很快就能寫點東西出來了。
WebSocket,Github上找了一個開源的C#項目,通訊這塊也是幾個小時就搞定了。Javascript不是很熟悉,當時閉包這樣的一些概念也算聽說過,Js也是無障礙就寫成了。
整個項目的技術壁壘其實不是很高,難的是對於爐石的業務的理解。
設計一個項目,就是將項目分割成若干個子系統,然后用適當的設計模式,讓代碼出現在它該出現的地方。
一個項目的代碼量,肯定是不斷增加,然后通過重構減少,然后又加入新的子系統,導致代碼量的增加,再通過重構減少,這樣的螺旋形的折騰和反復(迭代)使得代碼越來越完善。
同時,隨着對於業務的理解,很有可能會對原來的設計產生顛覆性的修改。爐石的開發中,我3次做了顛覆性的設計的修改。
血的教訓告訴我們,開發之前,一定要做好業務的研究。
如果你覺得你的代碼這樣寫也不好,那樣寫也不對,不用糾結了,好好睡一覺,然后重新整理一下業務,修改一下你的業務模型,然后所有問題就迎刃而解了。
中國的外包公司,包括一些有名的大公司,都不喜歡寫文檔,或者文檔落后與代碼。
代碼可以上線,可以取得業績,文檔似乎完全只是為了應付CMM的規定,所以,別說考慮文檔的美觀了,就是一個和代碼同步的文檔,對於大多數公司來說,也很奢侈。
很多人有一個觀點,好的代碼是不需要注釋和文檔的,代碼就是最好的注釋和文檔。
其實,我覺得,文檔不是偽代碼,應該是對於業務的一種解釋,以及編碼的一個依據。
我覺得需要這些枚舉,是通過怎樣的調查分析得到的結論。
我覺得業務流程是怎樣的,我通過分析業務,畫出流程圖得到的一個結果。
很多人說,爐石的C#代碼通過反編譯可以看得到,為什么要重復去山寨呢?
這段時間工作很空閑,寫代碼寫得舊了,所以想通過山寨爐石來提高自己的分析和設計能力。
爐石其實你真正的考慮怎么設計的好,怎么使得你的設計可以同時滿足BS,CS,網絡版,單機版,也是非常不容易的。然后爐石的業務,如果很多地方不考慮擴展性,也不是很復雜,一個月足夠完成所有的編碼工作了。
但是,如果要考慮到擴展性,考慮到重復使用,考慮到IOC這樣的東西,則需要好好考慮的。
例如,施放法術,
我寫到最后就是一個施放法術的接口(抽象),然后各種法術的具體實現,
施法流程是調用了施放法術的接口,通過法術數據去調用具體的實現。
法術接口:
(博客園的插入代碼推薦有了,旁邊那個插入代碼沒有存在的意義了,個人覺得)
using Engine.Action; using Engine.Card; using Engine.Client; using System; using System.Collections.Generic; namespace Engine.Effect { public interface IAtomicEffect { /// <summary> /// 對方復原操作 /// </summary> /// <param name="game"></param> /// <param name="actField"></param> void ReRunEffect(ActionStatus game, String[] actField); /// <summary> /// 對英雄動作 /// </summary> /// <param name="game"></param> /// <param name="PlayInfo"></param> /// <returns></returns> String DealHero(ActionStatus game, PublicInfo PlayInfo); /// <summary> /// 對隨從動作 /// </summary> /// <param name="game"></param> /// <param name="Minion"></param> /// <returns></returns> String DealMinion(ActionStatus game, MinionCard Minion); /// <summary> /// 獲得效果信息 /// </summary> /// <param name="InfoArray"></param> void GetField(List<String> InfoArray); } }
一個傷害效果
using Engine.Action; using Engine.Client; using Engine.Control; using Engine.Utility; using System; using System.Collections.Generic; namespace Engine.Effect { /// <summary> /// 攻擊效果 /// </summary> public class AttackEffect : IAtomicEffect { /// <summary> /// 效果表達式 /// </summary> public String 傷害效果表達式 = String.Empty; /// <summary> /// 傷害加成 /// </summary> public Boolean 傷害加成 = false; /// <summary> /// 獲得效果信息 /// </summary> /// <param name="InfoArray"></param> void IAtomicEffect.GetField(List<string> InfoArray) { 傷害效果表達式 = InfoArray[0]; 傷害加成 = ExpressHandler.GetBooleanExpress(InfoArray[1]); } /// <summary> /// 對英雄動作 /// </summary> /// <param name="game"></param> /// <param name="PlayInfo"></param> /// <returns></returns> String IAtomicEffect.DealHero(ActionStatus game, Client.PublicInfo PlayInfo) { int AttackPoint = ExpressHandler.GetEffectPoint(game, 傷害效果表達式); //調整傷害值 if (傷害加成) AttackPoint += game.AllRole.MyPublicInfo.BattleField.AbilityDamagePlus; if (PlayInfo.Hero.AfterBeAttack(AttackPoint)) { game.battleEvenetHandler.事件池.Add(new Engine.Utility.CardUtility.全局事件() { 觸發事件類型 = CardUtility.事件類型枚舉.受傷, 觸發位置 = PlayInfo.Hero.戰場位置 }); } return Server.ActionCode.strAttack + CardUtility.strSplitMark + PlayInfo.Hero.戰場位置.ToString() + CardUtility.strSplitMark + AttackPoint.ToString(); } /// <summary> /// 對隨從動作 /// </summary> /// <param name="game"></param> /// <param name="Minion"></param> /// <returns></returns> String IAtomicEffect.DealMinion(ActionStatus game, Card.MinionCard Minion) { int AttackPoint = ExpressHandler.GetEffectPoint(game, 傷害效果表達式); //調整傷害值 if (傷害加成) AttackPoint += game.AllRole.MyPublicInfo.BattleField.AbilityDamagePlus; if (Minion.設置被攻擊后狀態(AttackPoint)) { game.battleEvenetHandler.事件池.Add(new Engine.Utility.CardUtility.全局事件() { 觸發事件類型 = CardUtility.事件類型枚舉.受傷, 觸發位置 = Minion.戰場位置 }); } return Server.ActionCode.strAttack + CardUtility.strSplitMark + Minion.戰場位置.ToString() + CardUtility.strSplitMark + AttackPoint.ToString(); } /// <summary> /// 對方復原操作 /// </summary> /// <param name="game"></param> /// <param name="actField"></param> void IAtomicEffect.ReRunEffect(ActionStatus game, string[] actField) { int AttackPoint = int.Parse(actField[3]); if (actField[1] == CardUtility.strYou) { //MyInfo if (actField[2] == Client.BattleFieldInfo.HeroPos.ToString("D1")) { game.AllRole.MyPublicInfo.Hero.AfterBeAttack(AttackPoint); } else { game.AllRole.MyPublicInfo.BattleField.BattleMinions[int.Parse(actField[2]) - 1].設置被攻擊后狀態(AttackPoint); } } else { //YourInfo if (actField[2] == Client.BattleFieldInfo.HeroPos.ToString("D1")) { game.AllRole.MyPublicInfo.Hero.AfterBeAttack(AttackPoint); } else { game.AllRole.MyPublicInfo.BattleField.BattleMinions[int.Parse(actField[2]) - 1].設置被攻擊后狀態(AttackPoint); } } } } }
/// <summary> /// 實施效果 /// </summary> /// <param name="singleEffect"></param> /// <param name="game"></param> /// <param name="RandomSeed"></param> /// <returns></returns> public static List<string> RunSingleEffect(EffectDefine singleEffect, ActionStatus game, int RandomSeed) { List<string> Result = new List<string>(); List<string> PosList = SelectUtility.GetTargetList(singleEffect.AbliltyPosPicker, game, RandomSeed); foreach (string PosInfo in PosList) { var PosField = PosInfo.Split(CardUtility.strSplitMark.ToCharArray()); var strResult = string.Empty; if (PosField[0] == CardUtility.strMe) { switch (int.Parse(PosField[1])) { case BattleFieldInfo.HeroPos: Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strMe + CardUtility.strSplitMark + BattleFieldInfo.HeroPos.ToString("D1")).DealHero(game, game.AllRole.MyPublicInfo)); break; case BattleFieldInfo.AllMinionPos: for (int i = 0; i < game.AllRole.MyPublicInfo.BattleField.MinionCount; i++) { Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strMe + CardUtility.strSplitMark + (i + 1).ToString("D1")).DealMinion(game, game.AllRole.MyPublicInfo.BattleField.BattleMinions[i])); } break; case BattleFieldInfo.AllRolePos: Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strMe + CardUtility.strSplitMark + BattleFieldInfo.HeroPos.ToString("D1")).DealHero(game, game.AllRole.MyPublicInfo)); for (int i = 0; i < game.AllRole.MyPublicInfo.BattleField.MinionCount; i++) { Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strMe + CardUtility.strSplitMark + (i + 1).ToString("D1")).DealMinion(game, game.AllRole.MyPublicInfo.BattleField.BattleMinions[i])); } break; default: Result.Add(GetEffectHandler(singleEffect, game, PosInfo).DealMinion(game, game.AllRole.MyPublicInfo.BattleField.BattleMinions[int.Parse(PosField[1]) - 1])); break; } } else { switch (int.Parse(PosField[1])) { case BattleFieldInfo.HeroPos: Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strYou + CardUtility.strSplitMark + BattleFieldInfo.HeroPos.ToString("D1")).DealHero(game, game.AllRole.YourPublicInfo)); break; case BattleFieldInfo.AllMinionPos: for (int i = 0; i < game.AllRole.YourPublicInfo.BattleField.MinionCount; i++) { Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strYou + CardUtility.strSplitMark + (i + 1).ToString("D1")).DealMinion(game, game.AllRole.YourPublicInfo.BattleField.BattleMinions[i])); } break; case BattleFieldInfo.AllRolePos: Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strYou + CardUtility.strSplitMark + BattleFieldInfo.HeroPos.ToString("D1")).DealHero(game, game.AllRole.YourPublicInfo)); for (int i = 0; i < game.AllRole.YourPublicInfo.BattleField.MinionCount; i++) { Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strYou + CardUtility.strSplitMark + (i + 1).ToString("D1")).DealMinion(game, game.AllRole.YourPublicInfo.BattleField.BattleMinions[i])); } break; default: Result.Add(GetEffectHandler(singleEffect, game, PosInfo).DealMinion(game, game.AllRole.YourPublicInfo.BattleField.BattleMinions[int.Parse(PosField[1]) - 1])); break; } } } return Result; }
最后,通過對於業務的不斷理解,有一些看上去不一樣的東西變得一樣了。
法術,光環,戰吼,亡語,其實都是一樣的
一些看上去一樣的東西,變得不一樣了
奧秘有的是修改觸發行為,有的是追加效果;光環有的是影響戰場的其他隨從,有的是被戰場影響
等到你將業務真正分析清楚了,寫代碼就是一個體力活了。
而且,作為程序員,修改自己的代碼,大家都不情願,辛辛苦苦寫的代碼不願意修改。
如果文檔先行,修改一下文檔,大家還是很樂意的,不用測試,不用返工。而且修改文檔是時間成本最小的。如果上線后再修改BUG,那個時間成本。。。。。
題外話:
VS14CTP已經開始使用了,ASPNET的vNext版本也開始嘗試了,然后KRE的Self-Host功能也做個試驗了,部署在遠程服務器上,本地完全可以訪問。
但是,性能不能和原生的IIS相比,園子里面有一篇文章介紹過的,地址忘記了,大概相差兩三倍吧。
第一次玩MVC(技術面試的時候,這個好像一定要問的,沒有玩過MVC好像就不懂ASP一樣,一直純手工寫WebForm的人,情何以堪,IOC好像也是必問的,現在的技術面試太看重理論了)
,蠻有趣的,但是我有個疑問,MVC加上EF對於關系型數據庫支持的很少,如何讓MVC和NOSQL一起工作呢?
NOSQL的元數據對於MVC來說,總覺得會出現不兼容的情況。。。。