天天寫業務代碼的程序員,怎麼成為技術大牛,開始寫技術代碼?

天天寫業務代碼,感覺沒長進,也沒實際需求可以讓自己寫深入的技術代碼,好焦躁

相關問題

程序員如何避免寫過多的業務邏輯代碼? - 職業發展

去年有人問過了,似乎是同一個問題

怎麼才能做軟體架構師? - 計算機軟體開發


一個產品業務的開發過程中必然存在很多需要解決的問題,比如 崩潰,死鎖,性能低下,延遲高,伺服器不穩定,數據丟失,某些功能不知道怎麼實現。

產品業務如果要成功,這些問題必須要解決,至少解決其中絕大部分。

誰解決這些問題誰就是大牛,你想去寫業務邏輯公司也捨不得。
遇到這種問題直接退縮或者推給別人,就寫一輩子業務邏輯吧。

問題就是機會,你主動去解決問題,你沒搞定別人也沒搞定啊,萬一搞定了就是你牛逼,多划算的買賣啊。

以我多年解決問題的經驗來看,其實大多問題並不難,只需要認真去google下跟蹤調試進源代碼深處就能解決,這種問題其實就是誰敢上誰就行。很多人不去解決,就是因為懶和慫。問題解決多了,就會越來越有感覺,別人也就更傾向把疑難雜症交給你。所以一個組裡只有一兩個人能成長起來,因為只要有一個人成長了其它人就失去了機會,並不是這一兩個人比其他人優秀很多,只是他們是第一個敢於主動迎難而上的人。

要不怎麼說,性格決定命運呢。


2016.10.08更新:更加完整的回答請看我的專欄文章,補充了一個點:Do exercise
大牛養成指南(3)- 天天寫業務代碼,如何成為技術大牛? - 李運華的文章 - 知乎專欄
===================以下是原答案=================================
粗略的掃了一下前面的答案,有幾個典型的答案我覺得有必要反駁一下:
1)拜大牛為師 -- 你想得美
看起來很美好,實際上想拜大牛為師的多了去了,大牛憑什麼看中你呀;而且一個公司或者部門的大牛本來就不多,你正好和大牛在一個組的幾率是很小的;如果都不在同一個組,你根本都沒有機會接觸大牛,更別說深入請教和學習了;
如果是組內的小牛,一般都是骨幹核心,本來工作也很忙,你天天問一些在他看來比較低級的問題,別人也煩呀。像我這種樂於助人好為人師的,可遇不可求 :)
2016.09.26補充:不是說不要向比你厲害的人學習和請教,而是說不要問書本或者google能夠查到的東西;
2)業務代碼一樣很牛逼 -- 很傻
這樣的答主我估計沒有怎麼真正參與業務開發吧? 實際上在公司裡面,業務代碼真的沒太多技術含量,就是實現產品功能即可,而且翻來覆去就那麼一些,寫多了真的會很煩躁的。
3)業務代碼寫多了能力就上去了 -- 很天真
這也是誤人子弟的,寫一萬行hello world,水平不可能提升的;redis也就3萬行代碼規模,幾個人能寫出redis ?你寫10萬行業務代碼都寫不出redis的。
4)上班太忙沒時間自己學習 -- 你想多了
嗯,這是中國國情,難道你還指望每天上班給2小時給你自我提升 ?

我講講我是怎麼混出來的。
最重要的是明確一個道理:靠自己!在你水平不高的時候,不要想著人脈、關係這些,不要被那些亂七八糟的勵志書忽悠了。否則你會發現熱臉貼冷屁股,要不就是浪費錢請人吃了飯而已。
要靠自己去學習提升,沒有時間就擠出時間,將打遊戲、睡懶覺、看電影的時間擠出來;
要靠自己去爭取機會,不要等著機會砸到你頭上。你說自己天天完成業務代碼就OK了,從來不分享、從來不提優化建議、別人討論nginx你連nginx是啥都不知道、有任務能推就推。。。。。。然後等著哪天主管突然給你安排一個有技術含量的事情?這是不可能的。
要考自己主動去探索,不管是熟悉更多業務,還是熟悉更多代碼,還是了解整體架構,都需要自己主動去做。不要指望誰告訴你這裡是重點,那裡是難點,還有關鍵點在哪裡。

總結一句話:只有你自己牛逼了,機會才會到你頭上

前面講了大道理,接下來講我的實際方法,不多,就兩條。
【Do more】
做的更多,做的比你主管安排給你的任務更多。

我在華為的時候,負責一個版本的開發,這個版本的工作量大約是2000行左右,但是我除了做完這個功能,還將關聯的功能全部掌握清楚了,代碼(大約10000行)也全部看了一遍,做完這個版本後,我對這個版本相關的整套業務全部很熟悉了。經過一兩次會議後,大家發現我對這塊掌握最熟了,接下來就有趣了:產品討論需求找我、測試有問題也找我、老大對外支撐也找我;後來,不是我負責的功能他們也找我,即使我當時不知道,我也會看代碼或者找文檔幫他們回答。。。。。。最後我就成了我這個系統的「專家」了。雖然這個時候我還是做業務的,還是寫業務代碼,但是我已經對整個業務都很熟悉了。

以上只是一個簡單的例子,其實就是想說:要想有機會,首先你得從人群中冒出來,要想冒出來,你就必須做到與眾不同,要做到與眾不同,你就要做得更多

怎麼做得更多呢?可以從以下幾個方面著手:
1)熟悉更多業務,不管是不是你負責的
2)熟悉更多代碼,不管是不是你寫的
3)熟悉端到端
比如說你負責web後台開發,但實際上用戶發起一個http請求,要經過很多中間步驟才到你的伺服器(例如瀏覽器緩存、DNS、nginx等),伺服器一般又會有很多處理才到你寫的那部分代碼(路由、許可權等)這整個流程中的很多系統絕大部分人是不可能去參與寫代碼的,但掌握了這些知識對你的綜合水平有很大作用,例如方案設計、線上故障處理這些更加有含金量的技術工作都需要綜合技術水平。
4)自學
寫業務代碼所用到的技術比較少,我們可以自己學習更多,說不定哪天就用上了。
以java為例,大部分業務代碼就是if-else加個資料庫操作,但我們完全可以自己學些更多java的知識,例如垃圾回收,調優,網路編程等,這些可能暫時沒用,但真要用的時候,不是google一下就可以了,這個時候誰已經掌握了相關知識和技能,機會就是誰的。
以垃圾回收為例,我自己平時就抽時間學習了這些知識,學了1年都沒用上,但後來用上了幾次,每次都解決了卡死的大問題。

【Do better】
要知道這個世界上沒有完美的東西,你負責的系統和業務,總有不合理和可以改進的地方,識別出來,並且給出解決方案,然後向主管提出,一次不行兩次,多提幾次,只要有一次機會,你就有機會了。
例如:
重複代碼太多,是否可以引入設計模式?
系統性能一般,可否進行優化?
目前是單機,如果做成雙機是否更好?
版本開發質量不高,是否引入高效的單元測試和集成測試方案?
。。。。。。。。。。。。。。。。。。。
只要你去想,其實總能發現可以改進的地方的;如果你覺得系統哪裡都沒有改進的地方,那就說明你的水平還不夠,可以多學習相關技術,多看看業界其它公司怎麼做,BAT都怎麼做。

我2013年調配到九游,剛開始接手了一個簡單的後台系統,每天就是配合前台做數據增刪改查,看起來完全沒意思,是吧?如果只做這些確實沒意思,但我們接手後做了很多事情:
解耦,將一個後台拆分為2個後台,提升可擴展性和穩定性;
雙機,將單機改為雙機系統,提高可靠性;
優化,將原來一個耗時5小時的介面優化為耗時5分鐘。。。。。。
後來我們這個組承擔了更多的系統,現在這個小組5個人,負責了6個系統。

歸納總結一句話:靠自己、做更多、做更好
記住:不是因為給你安排高水平的工作你才有機會提升水平,而是因為你水平高了才有機會給你安排高水平工作!

==================2016.01.17補充======================
特別申明一下:我說業務代碼沒有「太多」技術含量,並不是說「完全」沒有技術含量,理解問題千萬不要走極端。有個網友說寫了半年業務代碼就覺得沒意思了,我覺得這就有點急躁了。即使是業務代碼,也還是有一定技術含量的,以java編程為例,static、final、synchronize、HashMap、try-catch-finally等語言基本特性,這些在業務代碼開發中還是比較常見的,很多人也經常用,但大部分人都沒有深入去研究這些語言特性的原理和作用,只是一知半解的使用,所以經常會看到代碼中亂用、濫用、隨便用的現象。
===================2016.09.28補充======================
成為技術大牛就像遊戲中打怪升級一樣,要不斷的打更高級別的怪物才能不斷升級。這個過程中,業務代碼只是比較初級的怪物,業務代碼都寫不好,肯定不能成為技術大牛;但只是把業務代碼寫好了,一樣成為不了大牛!就像遊戲時小怪都打不死,直接去打BOSS是不可能的;但一直只是打小怪,即使能夠秒殺小怪,離打大BOSS還差得遠


我做業務時候,常常反思:

  1. 運營們有一個緊急需求,能不能立即響應?馬上修改代碼發布上線,不需要測試工程師介入
    • 代碼粒度是否夠細?是否足夠解耦?修改起來是否方便?是否能夠通過線上配置文件避免發布?
    • 修改代碼後,自測是否能保證質量?是否需要單元測試/集成測試?
    • 運營手裡有大把的妹子資源,如何利用資源?如何造福其他的單身技術同學?
    • 程序員撩妹從快速響應需求開始。
  2. 產品有了新需求,這個需求的原因是什麼?目的是什麼?設計是否合理?
    • 產品狗們總是一次提很多需求。每次有新需求都抓住他們問清原因和目的,是否讓他感受到了程序爸爸的厲害?
    • 更深入的了解也業務之後,自己是否對於所在行業有了更深的理解?
    • 了解業務後,對於項目開發周期,控制迭代的安排是否更有條理。
    • 砍需求是不是更容易了?
    • 這個複雜的需求,現有的技術體系是否能夠快速實現?
  3. 設計師想要一個很炫酷的效果,對於體驗能帶來多少價值?這些價值應該怎麼在數據上體現出來?
    • 如何實現特效?
    • 在低端安卓機器下是否有性能問題?
    • 這個效果挺吊,能不能做成通用的組件?
    • 這個蠻屌的效果提高頁面點擊率了么?相關數據如何採集?
    • 如何在數據平台挖掘這些數據?
    • 最後拿到結果,告訴設計師:「其實這個特效沒啥屌用,就我們自己YY的,爽了自己而已」

簡單的看,做業務好像並不能學到多牛逼的技術


與運營合作:

    1. 代碼更加模塊化,粒度合適。
    2. 項目變得可配置,省心。

與產品 合作:

    1. 互相學習,自己也更懂行業
    2. 能夠管理項目,項目需求評審=&>視覺評審=&>開發排期=&>測試調優=&>產品驗收

與設計師合作:

    1. 屌屌的視覺特效,技術不小的長進
    2. 低端機型的性能調優,需要debug、Profiles

不過最近半年在做業務的同時,順便也

    • 掌握了團隊內所有的基礎技術原理與實現
    • 公用代碼用的不爽了,就改造基礎代碼庫
    • 玩了玩數據採集、數據挖掘
    • 玩了玩ABTest
    • 寫了一點公用組件代碼

後來團隊內的那幫技術大牛好像願意帶著我玩了,有時討論也會拉上我一起了。


出自 是不是服務端編程剛開始都得從寫業務開始?

業務邏輯複雜起來是很考驗你的coding skill的。如果忽略技巧,用蠻力寫業務邏輯,不追求代碼之神,敲完就算,當然無聊啦。

---

你可以

switch (gender):
case "male":
if (level === "gold") {

} else if (level === "silver") {

} else {

}
break;
case "female":
if (level === "gold") {

} else if (level === "silver") {

} else {

}
break;
case "unknown":
if (level === "gold") {

} else if (level === "silver") {

} else {

}
break;

這是一般業務邏輯程序員認為的「業務邏輯」,你每天寫這種代碼,當然覺得無聊。

你也可以

GENDER = {
MALE: "male",
FEMALE: "female』,
UNKNOWN: "unknown"
}
LEVEL = {
GOLD: "gold",
SILVER: "silver",
BRONZE: "bronze"
}

avatars = {
GENDER.MALE =&> {
LEVEL.GOLD =&>
LEVEL.SILVER =&>
LEVEL.BRONZE =&>
},
GENDER.FEMALE =&> {
LEVEL.GOLD =&>
LEVEL.SILVER =&>
LEVEL.BRONZE =&>
},
GENDER.UNKNOWN =&> {
LEVEL.GOLD =&>
LEVEL.SILVER =&>
LEVEL.BRONZE =&>
}
}

user.gender = (user.gender in GENDER) ? user.gender : GENDER.UNKNOWN
user.level = (user.level in LEVEL) ? user.level : LEVEL.BRONZE

user.avatar = avatars[user.gender][user.level];

這樣寫業務邏輯就有趣多了。

後一種是Data Driven的思考方式,用數據,或者設計數據來驅動邏輯,讓邏輯的外觀從代碼里消失,從而讓代碼更緊湊,節奏感更強,讀起來更舒服,改起來更方便。這是在web開發里常用的技巧,遊戲邏輯里應該也有不少數據驅動的場景,如果你能為特定邏輯設計出一目了然的數據,就能大大降低邏輯的複雜度,寫出的東西更魯棒,效率通常也更高,就不會覺得寫這種業務無聊了,因為有技巧在裡面。

---

你可以

for (items) {
item[..] =
// or
if () {
return item
}
}

也可以

items.each(item =&> process(item))
items.filter(item =&> verify(item))
items.map(item =&> update(item))

用好函數式,能幫你化解各種無聊的循環,外觀上看,代碼的抽象層次更高了,語句更清晰了,讀起來更舒服了。

---

你可以

class {
showLogo () {
showAd()
...
}
showBanner () {
showAd()
...
}
showSidebar () {
showAd()
...
}
}

也可以

class {
@decorated by AdDecorator
showLogo,
showBanner,
showSidebar
...
}

簡單的Decorator,是不是讓代碼緊湊精簡,有意思多了?

---

你可以

s : string = "thanks "
if () {
s = s + name
s = s + " for purchasing
if () {
s = s + quantity
s = s + product
}
}
s = s + ..

也可以

t = "thanks {name} for purchasing {quantity} {product} .."
s = t.compile({name, quantity, product})

把比較髒的局部拼接邏輯變成一目了然的模版,品味高出截。

---

你可以

function attach() {
app.A =
db.update(app.states)
log(..)
}
function move() {
app.B =
db.update(app.stats)
log(..)
}

也可以

app.on("change", "stats", stats =&> {
db.update(stats)
log(..)
})
function attack() {
app.update(A ..)
}
function move() {
app.update(B ..)
}

...

同樣繁雜的業務邏輯可以寫得拖沓重複,無聊透頂,也可以寫得精粹漂亮,有樂趣和美感啊。只需要在敲鍵盤前多花點時間來構思,並不難。

你確定底層編程需要更高階的思想嗎,其實只是數據和操作的形態不同罷了,思維和技巧還是適用的。再說要多底層才能滿足你的虛榮心呢?

* 以上代碼為偽碼,概念實現請參照編程語言。
* 支持你自己
https://www.linkev.com/?a_aid=itlr


做任何技術,無論「底層」基礎設施,還是上層「業務」系統,都需要積累兩部分知識:一是基礎知識,二是領域知識。

拿我自己為例。總的來說是資料庫研發,在多數派「業務」研發眼裡屬於底層技術沒錯吧。之前我做SQL,功能龐大,需要編譯器,關係模型,優化,數據類型,分散式計算等知識,還需要理解DBA和上層開發的需求和用法。可是呢那些做事務的同學眼裡,SQL不就是做個parser嘛!對於SQL的同學屬於基礎知識的在事務組同學看來是領域知識。

然後我現在回頭開始做底層事務和存儲。需要分散式,文件系統IO,事務,並發編程,負載均衡等知識。可是呢那些做SQL的同學眼裡,不就是實現幾個現成的介面嘛!

那,做system的基礎知識是什麼呢?學校本科教的那些東西。

所以呢,認清你自己技術的基礎知識和領域知識,拓寬基礎,打深領域。不要糾結這些,腳踏實地莫焦躁。修鍊提高自己,不用care別人怎麼說。


其實通常抱怨『每天都在寫業務代碼』 的同學,通常業務代碼寫的也不咋地。 有些人眼裡看到團隊一成不變的開發節奏,是『機會』,有些人就只會埋怨 。

以前端為例,我遇到的幾乎每個會有大量運營頁的團隊(夠低端的需求吧),基本都會有個內部的運營頁生成工具或系統的配套。 你覺得機會是留給能改善現狀的人(比如產出工具的人),還是抱怨的人?。

純粹只是吐個槽


就說一句:沒有一個煤老闆是因為挖煤既快又多而當上老闆的。


本人工作兩年,略有一些體會
首先第一步,什麼是業務代碼?什麼是技術代碼。其實我理解題主的想法,害怕天天寫業務代碼,覺得自己沒有任何技術成長,自己對公司或者團隊也不是太滿意。

我也有過類似的情況,當初(也就是2年前),為了保持自己快速的學習效率,我在選擇技術時會選擇自己喜歡的技術,上的都是各種最新的技術,什麼MongoDB, Node.js, Redis之類,偶爾也寫寫C/C++代碼,這樣感覺每天都能學會很多新技術,不過這樣的人一抓一大把,最後的結果是上線的服務還是不穩定,填不完的坑,到最後,搞得自己只能每天改Bug,得不償失,也是對公司、團隊不負責任。

一年前,也就是從第一家公司辭職後,我開始反思自己的問題,得出的結論是:業務與技術就像二進位中的0和1,總是相反的,追求業務,那必然需要選用一些成熟的技術,那技術成熟,對你的技術成長自然就不太有效果了。

不過在我做了差不多8-9個月的自由職業,再重新加入startup半年以來,現在的體會又發生了一些微妙的變化。

為了保持自己的技術成長,其實並不一定是保持著每天學習新技術,相反地,學習新技術的自己永遠是在學習別人的東西,然而,通過對你特定領域內有相當的深入理解,從而抽象總結出自己的一套理論後,這反而是從0到1的創造,相比於學習「新」技術,發明「新技術」豈不是更有挑戰性么?

舉個例子,現在的團隊為了能夠將敏捷性提升到極致,正在採用一套叫DSL-based的編程方法,就是說儘可能地講所有的業務邏輯都抽象成類似配置文件或某種特殊文件格式的文件來對特別服務進行配置,從而實現零代碼的業務抽象,在此過程中,我們通過借用GraphQL的語法特徵來改進SQL以及MongoDB原有的視圖實現。

雖然很多時候我們會聽到別人說「過度抽象」一詞,個人也十分認同這一觀點,不過我覺得如果在你越來越了解你這個行業的一些信息後,可以嘗試為你的每一類型的服務都採用DSL/config的方式,說直白一點,就是讓你的每一個服務都像配置一台nginx那麼簡單,極致地優化每一個細節,包括部署、修改、敏捷性以及適應性等,我相信這些是業務與技術的統一。


有點不敢相信,即便是業務代碼,難道題主在工作中沒遇到過麻煩?

至少最常見的「代碼拷貝」就是一種讓人無聊的麻煩吧。那你有嘗試過去解決代碼拷貝的問題嗎?
呃……沒有代碼拷貝的問題,那代碼提交,測試,發布的過程總遇到過一些問題吧,有沒有很繁瑣很容易操作失誤?有嘗試過把這些問題自動化處理嗎?
呃……系統各種處理一鍵完成,那好歹系統有一份bug list吧?沒事去把一些懸而未決的bug給fix了,並測試好確保不會引入新的bug,這種事情有嘗試過嗎(別跟我說bug沒指派給你)?
業務代碼技術含量不高是沒錯,但是其中的問題肯定還是很多的,否則也不會出現系統重構了。能不能從中成長,不在於天天寫這些代碼,而在於天天寫這樣的代碼卻滿足於此。


高票答案說了好多方向性的指導意見,那我就結合我自己的工作場景,具體聊聊我眼中業務代碼應該怎樣提高吧~

---------------------分割線-----------------------

本文主要作為筆者閱讀Eric Evans的《Domain-Driven Design領域驅動設計》一書,同時拜讀了我司大神針對業務代碼封裝的一套業務框架後,對於如何編寫複雜業務代碼的一點粗淺理解和思考。

ps,如有錯誤及疏漏,歡迎探討,知道自己錯了才好成長么,我是這麼認為的,哈哈~

背景介紹

忘記在哪裡看到的句子了,有 「看花是花,看花不是花,看花還是花」 三種境界。這三個句子恰好代表了我從初入公司到現在,對於公司代碼的看法的三重心路歷程。

學習動機

「看花是花」

得益於我司十幾年前一幫大神資料庫表模型設計的優異,一開始剛進公司的時候,很是驚嘆。通過客戶端配配屬性,一個查詢頁面和一個資源實體的屬性控制項頁面就生成好了。

框架本身負責管理頁面屬性和查詢頁面的顯示內容,以及右鍵菜單與 js 函數的綁定關係,同時當其他頁面需要調用查詢及屬性頁面時,將頁面的實現和調用者頁面做了隔離,僅通過預留的簡單的調用模式及參數進行調用。這樣複雜功能的實現則不受框架限制與影響,留給業務開發人員自己去實現,客觀上滿足了日常的開發需求。

我司將繁雜而同質化的查詢及屬性頁面開發簡化,確實客觀上減輕了業務開發人員的工作壓力,使其留出了更多精力進行業務代碼的研究及開發工作。

這套開發機制的發現,對我來說收穫是巨大的,具體的實現思路,與本文無關,這裡就不作過多贅述了。這種新奇感和驚嘆感,就是剛開始說的 「看花是花」 的境界吧。

「看花不是花」

那麼 「看花不是花」 又該從何說起呢?前面說了,框架完美的簡化了大量重複基礎頁面的開發工作,同時,框架本身又十分的剋制,並不干涉業務代碼的開發工作。

但是從客觀上而言,業務代碼本身由於包含了業務領域的知識,複雜可以說是先天的屬性。隨著自己工作所負責業務的深入,接觸更多的業務必然也不再是框架所能涵蓋的資源查詢與屬性編輯頁面。

同時考慮到業務編寫人員本身相對於框架人員技術上的弱勢,以及業務領域本身具有的複雜性的提升,我一開始所面對的,就是各種的長達幾百行的函數,隨處可見的判斷語句,參差不齊的錯誤提示流程,混亂的資料庫訪問語句。在這個階段,對業務代碼開始感到失望,這也就是之後的 「看花不是花」 的境界吧

「看花還是花」

有一天,我突然發現面對紛繁而又雜亂的業務代碼,總還有一個模塊 「濯清漣而不妖」,其中規範了常用的常量對象,封裝了前後端交互機制,約定了異常處理流程,統一了資料庫訪問方式,更重要的是,思考並實現了一套代碼層面的業務模型,並最終實現了業務代碼基本上都是幾十行內解決戰鬥,常年沒有 bug,即便有,也是改動三兩行內就基本解決的神一般效果(有所誇張,酌情理解:P)

這是一個寶庫。原來業務代碼也可以這麼簡潔而優雅。

唯一麻煩的就是該模塊的業務複雜,相應的代碼層面的業務模型的類層次結構也複雜,一開始看不太懂,直到我看了 Eric Evans的《Domain-Driven Design領域驅動設計》才逐漸有所理解。

因為此內部業務框架做的事情很多,篇幅有限,這裡僅對最具借鑒意義的領域建模思考作介紹。

業務場景

我主要負責傳輸網資源管理中的傳輸模塊管理。這個部分涉及相對來說比較複雜的關聯關係,所以如果代碼組織不夠嚴謹的話,極易繞暈和出錯,下面以一張簡單的概念圖來描述一下部分實體對象間的關聯關係。

如圖,簡單來說時隙和通道是一對多的父子關係,同時業務電路和多段通道間的下級時隙,存在更複雜的一對多承載關係。

所以這個關係中複雜的地方在哪裡呢?理解上經常會繞混,電路創建要選多段通道時隙,通道本身要管理多個時隙。這樣時隙和通道以及電路同時存在了一對多的聯繫,如何去準確的理解和區分這種聯繫,並將之有效的梳理在代碼層面就很需要一定技巧。

稍微拓展一下,改改資源類型,把業務電路換為傳輸通道,把傳輸通道換為傳輸段,這套關係同樣成立。

另外,真實的業務場景中,業務電路的下層路由,不僅支持高階通道,還支持段時隙,埠等資源。

整體設計

從業務場景中我們可以看到,資源實體間的模型關係其實有很多相類似的地方。比如大體上總是分為路由關係,和層級關係這麼兩種,那麼如何才能高效的對這兩種關係進行代碼層面的建模以高效的進行復用,同時又保留有每個資源足夠的拓展空間呢?

傳統思路

我們先來考慮一下,按照傳統的貧血模型去處理傳輸通道這個資源,針對傳輸通道的需求,它是如何處理的呢?

最粗陋的常規模型,其實就是依據不同類型的資源對需求進行簡單分流,然後按照管理劃分 Controller 層,Service 層,Dao 層。各層之間的交互,搞得好一點的會通過抽象出的一個薄薄的 domain 域對象,搞的不好的直接就是 List,Map,Object 對象的粗陋組合。

代碼示例

/**
* 刪除通道 不調用ejb了
* 業務邏輯:
* 判斷是否被用,被用不能刪除
* 判斷是否是高階通道且已被拆分,被拆不能刪除
* 邏輯刪除通道路由表
* 清空關聯時隙、波道的channel_id欄位
* 將埠的狀態置為空閑
* 邏輯刪除通道表
* @param paramLists 被刪除通道,&必填屬性:channelID&
* @return 是否刪除成功
* @throws BOException 業務邏輯判斷不滿足刪除條件引發的刪除失敗&
* &通道非空閑狀態&&
* &高階通道已被拆分&&
* &刪除通道資料庫操作失敗&&
* &刪除HDSL系統失敗&&
* @return 成功與否
*/
public String deleteChannel(String channelId){
String returnResult = "true:刪除成功";
Map& condition = new HashMap&();
condition.put("channelID",channelId);
condition.put("min_index","0");
condition.put("max_index","1");
boolean flag=true;
List&&> channel = this.channelJdbcDao.queryChannel(condition);
if(channel==null||channel.size()==0){
return "false:未查詢到通道信息";
}
//判斷是否被用,被用不能刪除
String oprStateId = channel.get(0).get("OPR_STATE_ID").toString();
if(!"170001".equals(oprStateId)){
return "false:通道狀態非空閑,不能刪除";
}
//判斷是否是高階通道且已被拆分,被拆不能刪除
flag=this.channelJdbcDao.isSplited(channelId);
if(!flag){
return "false:高階通道已被拆分,不能刪除";
}
//邏輯刪除通道路由表 並且清空關聯時隙、波道的channel_id欄位
this.channelJdbcDao.deleteChannelRoute(channelId,oprStateId);
//將通道埠的埠狀態置為空閑
this.channelJdbcDao.occupyPort(String.valueOf(channel.get(0).get("A_PORT_ID")),"170001");
this.channelJdbcDao.occupyPort(String.valueOf(channel.get(0).get("Z_PORT_ID")),"170001");
//邏輯刪除通道表
this.channelJdbcDao.delete(channelId);
//如果通道走了HDSL時隙則刪除HDSL系統及下屬資源 ,這裡重新調用了傳輸系統的刪除的ejb。
List&&> syss=this.channelJdbcDao.findSysByChannel(channelId);
for(int i=0;i&&> paramLists = new ArrayList&&>();
List paramList = new ArrayList();
Map map = new HashMap();
map.put("res_type_id", "1001");
map.put("type", "MAIN");
paramList.add(map);
map = new HashMap();
map.put("sys_id", syss.get(i).get("SYS_ID"));
paramList.add(map);
//EJB裡面從第二個數據開始讀取要刪除的系統id,所以下面又加了一層 。
map = new HashMap();
map.put("res_type_id", "1001");
map.put("type", "SUB");
paramList.add(map);
map = new HashMap();
map.put("sys_id", syss.get(i).get("SYS_ID"));
paramLists.add(map);
String inputXml = this.createInputXML("1001", "deleteTrsSys",
"TrsSysService", paramLists);
String result = this.getEJBResult(inputXml);
if(result==null||"".equals(result)){//如果ejb處理失敗是以拋異常形式,被底層捕獲而未拋出,導致返回結果為空
return "false:刪除HDSL系統失敗";
}
Document document = XMLUtils.createDocumentFromXmlString(result);
Element rootElement = XMLUtils.getRootElement(document);
if (!TransferUtil.getResultSign(rootElement).equals("success")){
result =rootElement.attribute("description").getValue();
return "false:刪除HDSL系統失敗"+resu< } } } return returnResu< }

上面這些代碼,是我司n年前的一段已廢棄代碼。其實也是很典型的一種業務代碼編寫方式。

可以看到,比較關鍵的幾個流程是 :

空閑不能刪除(狀態驗證)—&>路由刪除-&>埠置為空閑(路由資源置為空閑)-&>資源實體刪除

其中各個步驟的具體實現,基本上都是通過調用 dao 層的方法,同時配合若干行service層代碼來實現的。這就帶來了第一個弊端,方法實現和 dao層實現過於緊密,而dao層的實現又是和各個資源所屬的表緊密耦合的。因此即便電路的刪除邏輯和通道的刪除邏輯有很相似的邏輯,也必然不可能進行代碼復用了。

如果非要將不同資源刪除方法統一起來,那也必然是充斥著各種的 if/else 語句的硬性判斷,總代碼量卻甚至沒有減少反而增加了,得不償失。

拓展思考

筆者曾經看過前人寫的一段傳輸資源的保存方法的代碼。

方法目的是支持傳輸通道/段/電路三個資源的保存,方法參數是一些複雜的 List,Map 結構組合。由於一次支持了三種資源,每種資源又有自己獨特的業務判斷規則,多情況組合以後複雜度直接爆炸,再外原本方法的編寫人員沒有定期重構的習慣,所以到了筆者接手的時候,是一個長達500多行的方法,其間充斥著各式各樣的 if 跳轉,循環處理,以及業務邏輯驗證。

解決辦法

面對如此棘手的情況,筆者先是參考《重構·改善既有代碼設計》一書中的一些簡單套路,拆解重構了部分代碼。將原本的 500 行變成了十來個幾十行左右的小方法,重新組合。

方案局限

  • 重構難度及時間成本巨大。
  • 有大量的 if/else 跳轉根本沒法縮減,因為代碼直接調用 dao 層方法,必然要有一些 if/else 方法用來驗證資源類型然後調用不同的 dao 方法
  • 也因為上一點,重構僅是小修小補,化簡了一些輔助性代碼的調用(參數提取,錯誤處理等),對於業務邏輯調用的核心代碼卻無法進行簡化。service層代碼量還是爆炸

小結

站在分層的角度思考下,上述流程按照技術特點將需求處理邏輯分為了三個層次,可是為什麼只有 Service 層會出現上述複雜度爆炸的情況呢?

看到這樣的代碼,不由讓我想到了小學時候老師教寫文章,講文章要鳳頭,豬肚,豹尾。還真是貼切呢 :-)

換做學生時代的我,可能也就接受了,但是見識過高手的代碼後,才發現寫代碼並不應該是簡單的行數堆砌。

業務情景再分析

對於一個具體的傳輸通道A的對象而言,其內部都要管理哪些數據呢?

  • 資源對象層面
    • 自身屬性信息
  • 路由層面
    • 下級路由對象列表
  • 層次關係層面
    • 上級資源對象
    • 下級資源對象列表

可以看到,所有這些數據其實分為了三個層面:

  1. 作為普通資源,傳輸通道需要管理自身的屬性信息,比如速率,兩端網元,兩端埠,通道類型等。
  2. 作為帶有路由的資源,傳輸通道需要管理關聯的路由信息,比如承載自己的下層傳輸段,下層傳輸通道等。
  3. 作為帶有層次關係的資源,傳輸通道需要管理關聯的上下級資源信息,比如自己拆分出來的時隙列表。

更進一步,將傳輸通道的這幾種職責的適用範圍關係進行全業務對象級別匯總整理,如下所示:

各種職責對應的業務對象範圍如下:

  • 同時具有路由和層次關係的實體:
    • 傳輸時隙、傳輸通道、傳輸段、傳輸電路
  • 具有路由關係的實體:
    • 文本路由
  • 具有層次結構關係的對象:
    • 設備、機房、埠
  • 僅作為資源的實體:
    • 傳輸網管、傳輸子網、傳輸系統

拓展思考

微觀層面

以傳輸通道這樣一個具體的業務對象來看,傳統的貧血模型基本不會考慮到傳輸通道本身的這三個層次的職責。但是對象的職責並不設計者沒意識到而變得不存在。如前所述的保存方法,因為要兼顧對象屬性的保存,對象路由數據的保存,對象層次結構數據的保存,再乘上通道,段,電路三種資源,很容易導致複雜度的飆升和耦合的嚴重。

因此,500行的函數出現某種程度上也是一種必然。因為原本業務的領域知識就是如此複雜,將這種複雜性簡單映射在 Service 層中必然導致邏輯的複雜和代碼維護成本的上升。

宏觀層面

以各個資源的職責分類來看,具備路由或層次關係的資源並不在少數。也就是說,貧血模型中,承擔類似路由管理職責的代碼總是平均的分散在通道,段,電路的相關 Service 層中。

每種資源都不同程度的實現了一遍,而並沒有有效的進行抽象。這是在業務對象的代碼模型角度來說,是個敗筆。

在這種情況下就算使用重構的小技巧,所能做的也只是對於各資源的部分重複代碼進行抽取,很難自然而然的在路由的業務層面進行概念抽象。

既然傳統的貧血模型沒法應對複雜的業務邏輯,那麼我們又該怎麼辦呢?

新的架構

代碼示例

@Transactional
public int deleteResRoute(ResIdentify operationRes) {
int result = ResCommConst.ZERO;

//1:獲得需要保存對象的Entity
OperationRouteResEntity resEntity = context.getResEntity(operationRes,OperationRouteResEntity.class);

//2:獲得路由對象
List& entityRoutes = resEntity.loadRouteData();

//3:刪除路由
result = resEntity.delRoute();

//4:釋放刪除的路由資源狀態為空閑
this.updateEntitysOprState(entityRoutes, ResDictValueConst.OPR_STATE_FREE);

//日誌記錄
resEntity.loadPropertys();
String resName = resEntity.getResName();
String resNo = resEntity.getResCode();
String eport = "刪除[" + ResSpecConst.getResSpecName(operationRes.getResSpcId()) + ": " + resNo + "]路由成功!";
ResEntityUtil.recordOprateLog(operationRes, resName, resNo, ResEntityUtil.LOGTYPE_DELETE, eport);

return resu< }

上述代碼是我們傳輸業務模塊的刪除功能的service層代碼片段,可以看到相較先前介紹的代碼示例而言,最大的不同,就是多出來了個 entity 對象,路由資源的獲取是通過這個對象,路由資源的刪除也是通過這個對象。所有操作都只需要一行代碼即可完成。對電路如此,對通道也是如此。

當然,別的service層代碼也可以很方便的獲取這個entity對象,調用相關的方法組合實現自己的業務邏輯以實現復用。

那麼這種效果又是如何實現的呢?

概念揭示

首先我們得思考一下,作為一個類而言,最重要的本質是什麼?

答案是數據和行為。

照這個思路,對於一個業務對象,比如傳輸通道而言,進行分析:

  • 在數據層面,每個通道記錄了自身屬性信息及其關聯的傳輸時隙、傳輸段、傳輸電路等信息數據。
  • 在行為層面,每個通道都應該有增刪改查自身屬性、路由、下級資源、綁定/解綁上級資源等行為。

那麼在具體的業務建模時又該如何理解這兩點呢?

答案就是這張圖:

可以看到大體分為了三種類型的元素,

  • Context(上下文容器):
  1. 程序啟動時,開始持有各個 DataOperation 對象
  2. 程序運行時,負責創建 Entity 對象,並將相應的 DataOperation 對象裝配進 Entity 對象實例中
  • Entity(實體對象):每個用到的資源對象都生成一個 Entity 實例,以存放這個對象特有的實例數據。
  • DataOperation(數據操作對象):不同於 Entity,每類用到的資源對象對應一個相應的 DataOperation 子類型,用以封裝該類對象特有的數據操作行為

ps,雖然我這裡畫的 EntityDataOperation 對象只是一個方框,但實際上 EntityDataOperation 都有屬於他們自己的 N 多個適用與不同場景的介面和模板類

數據管理

筆者是個宅男,因為並木有女朋友,又不喜歡逛街,所以買東西都是網購。這就產生了一個很有意思的影響——隔三差五就要取快遞。

可是快遞點大媽不認識我,我也不是每天出門帶身份證。這就很尷尬,因為我每次總是需要和大媽圍繞 「Thehope 就是我」 解釋半天。

所以每次解釋的時候,我都在想,如果我帶了身份證或者其他類似的證件,該有多方便。

什麼是 Entity

我們一般認為,一個人有一個標識,這個標識會陪伴他走完一生(甚至死後)。這個人的物理屬性會發生變化,最後消失。他的名字可能改變,財務關係也會發生變化,沒有哪個屬性是一生不變的。然而,標識卻是永久的。我跟我5歲時是同一個人嗎?這種聽上去像是純哲學的問題在探索有效的領域模型時非常重要。
稍微變換一下問題的角度:應用程序的用戶是否關心現在的我和5歲的我是不是同一個人?
—— Eric Evans《領域驅動設計》

簡單的取快遞或許使你覺得帶有標識的對象概念並沒有什麼了不起。但是我們把場景拓展下,你不光要完成取快遞的場景,如果你需要買火車票呢?如果還要去考試呢?伴隨著業務場景的複雜化,你會越來越發現,有個統一而清晰的標識概念的對象是多麼的方便。

再來看看 Eric Evans 在《領域驅動設計》如何介紹 Entity 這個概念的:

一些對象主要不是由它們的屬性定義的。它們實際上表示了一條「標識線」(A Thread of Identity),這條線經過了一個時間跨度,而且對象在這條線上通常經歷了多種不同的表示。
這種主要由標識定義的對象被稱作 Entity。它們的類定義、職責、屬性和關聯必須圍繞標識來變化,而不會隨著特殊屬性來變化。即使對於哪些不發生根本變化或者生命周期不太複雜的 Entity ,也應該在語義上把它們作為 Entity 來對待,這樣可以得到更清晰的模型和更健壯的實現。

確定標識

得益於我司資料庫模型管理的細緻,對於每條資源數據都可以通過他的規格類型id,以及資料庫主鍵id,獲得一個唯一確定標識特徵。

如圖:

這裡舉出的 Entity 的屬性及方法僅僅是最簡單的一個示例,實際業務代碼中的 Entity,還包括許多具備各種能力的子介面。

引入Entity

如圖:

可以看到 entity 對象實際上分為了兩個主要的介面,RouteEntity 和 HierarchyEntity。
其中 RouteEntity 主要規定要實現的方法是 addRoute(), 即添加路由方法
其中 HierarchyEntity 主要規定要實現的方法是 addLowerRes() 與 setUpperRes() ,即添加子資源對象和設置父資源兩種方法。

那麼這兩個介面是如何抽象建模得到的呢?

確定功能的邊界

從微觀的實例對象層面來看,因為每個實例都可能擁有完全不一樣的路由和層級關係,所以我們建模時候,用抽象出的 Entity 概念,表示哪些每個需要維護自己的屬性/路由/層次關聯數據的對象實例。

從高一層的類的層次去分析,我們可以發現,對路由的管理,對層次結構的管理,貫穿了傳輸電路,傳輸通道,傳輸段,傳輸時隙等很多業務類型。所以這個時候就需要我們在介面層面,根據業務特徵,抽象出管理不同類型數據的 Entity 類型,以實現內在關聯關係的復用。

因此我們對 Entity 介面進行細化而建立了的 RouteEntity 和 HierarchyEntity 兩個子介面,比如

  • Entity 需要維護自己的 id 標識,屬性信息。
  • RouteEntity 就需要內部維護一個路由數據列表。
  • HierarchyEntity 就需要維護一個父對象和子資源列表。

這樣通過對不同的 Entity 管理的數據的職責與類型的進一步明確,保證在不同場景下,做到使用不同的 Entity 就可以滿足相應需求。。。。的數據前提 :P

拓展思考

既然 Entity 概念的引入是為了解決各資源對象具體實例的實例數據的存儲問題。那麼各個資源對象特有的行為操作怎麼滿足呢?比如傳輸通道和傳輸電路都有自己的表,起碼在dao層的操作就肯定不一樣,再加上各個資源自己獨特的增刪改查驗證邏輯,如果這些行為都放在 Entity 中。。。妥妥的類型爆炸啊~

另外,將數據與行為的職責耦合在一起,從領域建模的思想上就是一個容易混淆而不明智的決定。
站在微觀角度來說,每個 Entity 實例所管理的實例數據是不同的,而同一類資源的行為操作(它的方法)卻是無狀態的。
站在宏觀角度來說,具有路由特徵的或者具有層次特徵一簇實體,有抽象共性的價值(比如都需要管理路由列表或者父子對象信息),而涉及具體的行為實現,每種具體的資源又天然不完全相同。

小結

這裡我們可以再思考下前文貼的兩段代碼,當我們沒有 Entity 對象時,許多應該由 Entity 進行存儲和管理的數據,就不得不通過 map/list 去實現,比如上文的第一段代碼。這就帶來第一個弊端,不到運行時,你根本不知道這個容器內存放的是哪種業務規格的資源。

第二個弊端就是,當你使用 map/list 來代替本應存在的 Entity 對象時,你也拒絕了將對象的行為和數據整合在一起的可能(即不可能寫出resEntity.loadRouteData() 這樣清晰的代碼,實現類似的邏輯只能是放在 Service 層中去實現,不過放在 Service 又增加了與具體資源邏輯的耦合)

所以,以數據和行為分離的視角,將業務對象以策略模式進行解耦,抽離成專職數據管理的 Entity 對象,以及專職行為實現的 DataOperation 簇對象,就顯得非常有價值了。

行為管理

引入 DataOperation

接下來有請出我們的 DataOperation 元素登場~

以傳輸通道為例,對於傳輸通道的所屬路由而言,常用的功能無非就是的增刪改查這幾個動作。

確定變化的邊界

還是從微觀的實例對象層面先進行分析
業務行為邏輯會因為操作的實體數據是傳輸通道A,或者傳輸通道B 而變得不同嗎?答案是不會。
正如資料庫行記錄的變化不引起表結構的變化一樣,本質上一類資源所擁有的行為和對象實例的關係,應該是一對多的。
所以只要都是傳輸通道,那麼其路由增刪改查的行為邏輯總是一致的。

結合某一著名設計原則:

找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起

所以我們應該將資源不變的行為邏輯抽離出來,以保證 Entity 可以專註於自己對數據的管理義務,達到更高級的一種復用。

這也就是為什麼需要抽象 DataOperation 概念的原因之一。

進一步從類的層次去分析
不同種類的資源,其具體的數據操作行為必然是存在差別的(比如與資料庫交互時,不同資源對應的表就不同)。
所以不同種類的業務對象都必然會有自己的 DataOperation 子類,比如 TrsChannelDataOperation、TrsSegDataOperation 等,以確保每類業務對象獨特的數據操作邏輯的靈活性。

再進一步去分析
在更高的層級上去分析,靈活性我們因為實現類的細化已經具備了,那麼復用的需求又該怎麼去滿足呢?
與 Entity 對象一樣,我們可以在具體的 TrsChannelDataOperation、TrsSegDataOperation 等實體類之上,抽象出 RouteResDataOperation、HierarchyResDataOperation 等介面,規定好應該具備的方法。

Entity 對象面對需要調用 DataOperation 的場景,就以這些介面作為引用,從而使路由或者層次處理的業務代碼從細節的實現中解放出來。

拓展思考

這裡可以仔細思考一下,Entity 和 DataOperation 應該在什麼時候建立好二者之間的聯繫呢?

小結

我們已經分析好了對象的數據和行為該如何建模,那麼,我們又該如何將這二者統一起來呢?

有請我們的第三類元素,Context 登場~

組裝

先來看看這樣一個例子:

汽車發動機是一種複雜的機械裝置,它由數十個零件共同協作來侶行發動機的職責 — 使軸轉動。我們可以試著設計一種發動機組,讓它自己抓取一組活塞並塞到氣缸中,火花塞也可以自己找到插孔並把自己擰進去。但這樣組裝的複雜機器可能沒有我們常見的發動機那樣可靠或高效。相反,我們用其他東西來裝配發動機。或許是一個機械師,或者是一個工業機器人。無論是機器還是人,實際上都比二者要裝配的發動機複雜。裝配零件的工作與使軸旋轉的工作完全無關。裝配者的功能只是在生產汽車時才需要,我們駕駛時並不需要機器人或機械師。由於汽車的裝配和駕駛永遠不會同事發生。因此將這兩種功能合併到同一個機制中是毫無意義的。同理,裝配複雜的複合對象的工作也最好與對象要執行的工作分開。
——Eric Evans《領域驅動設計》

與發動機小栗子相類似,代碼中我們當然可以通過構造器的方式用到哪個對象再組裝哪個對象。不過比較一下這樣兩段代碼:

沒有 Context 元素的代碼:

@Transactional
public int deleteResRoute(ResIdentify operationRes, boolean protectFlag) {
...
//1.獲取需要保存對象的Entity
OperationRouteResEntity resEntity = new TrsChannelResEntity();
if(ResSpecConst.isChannelEntity(operationRes.getResSpcId())){
ComponentsDefined component = new TrsChannelDataOperation();
resEntity.initResEntityComponent(conponent);
}
...
}

有了 Context 元素以後

@Transactional
public int deleteResRoute(ResIdentify operationRes, boolean protectFlag) {
...
//1.獲取需要保存對象的Entity
OperationRouteResEntity resEntity = context.getResEntity(operationRes,OperationRouteResEntity.class);
...
}

是不是立竿見影的效果!

為什麼需要 Context

事實上前文對 Entity 和 DataOperation 只是領域建模的第一步,只是個雛形。而這裡的 context 對象,才是畫龍點睛的那一筆。

為什麼這麼說呢?也有無數其他的模塊對業務邏輯做過類似的複雜抽象,但都因為調用的時候總要調用者親自調用構造器生成實例而顯得異常繁瑣,並導致業務開發人員很難持續的堅持使用業務模型對象,最終導致代碼模型的形同虛設。

對象的功能主要體現在其複雜的內部配置以及關聯方面。我們應該一直對一個對象進行提煉,直到所有與其意義或在交互中的角色無關的內容已經完全被剔除為止,一個對象在它的生命周期中要承擔大量的職責。如果再讓複雜對象負責其自身的創建,那麼職責的過載將會導致問題產生。——Eric Evans《領域驅動設計》

為了避免這樣的問題,我們有必要對 Entity 等實體對象的裝配與運行進行解耦實現,這也即是我們的 Context 元素的主要職責之一。比如上述兩段代碼,在其他元素不做改變的情況下,僅僅出於對職責的明確而引入的 Context 元素,對業務代碼編寫卻有了質的提升。

但實際上,正如一開始的小栗子說的,「無論是裝配機還是裝配師,都比要裝配的發動機要複雜」,我們的 context 所執行的邏輯其實也是相當複雜的,但只要對客戶(這裡的客戶指的是使用 context 的業務代碼)幫助更大,即便再複雜也構不成不去做的理由,下面我們就來聊聊這個實質上的複雜工廠是如何運作的。

引入 Context

下面的代碼即 context.getEntity(...) 的代碼邏輯

public OperationResEntity getResEntity(String resSpcId) {
ResEntity entity = factory.getResEntity(resSpcId);
if(entity instanceof OperationResEntity){
ResComponentHolder holder = componentSource.getComponent(resSpcId);
if(entity instanceof ContextComponentInit){
((ContextComponentInit)entity).initResEntityComponent(holder);
}
}else{
throw new ResEntityContextException("資源規格:"+resSpcId+",實體類:"+entity.getClass().getName()+"未實現OperationResEntity介面");
}
return (OperationResEntity)entity;
}

虛擬機在載入類的時候,整個生命周期包括:載入、驗證、準備、解析、初始化、使用和卸載 7 個階段。我們的 Entity 實體並沒有這麼複雜,使用之前,僅需載入、驗證、初始化三個階段即可。

下面就來詳細聊聊這三個部分:

載入 Entity

ResEntity entity = factory.getResEntity(resSpcId);

在 Context 中,載入操作僅有一行...

待續(這部分比較複雜,涉及了抽象工廠模式,spring 事件機制,觀察者模式等等,有些複雜,暫時還沒想好如何去表述它的邏輯,以後想好了再補充吧~)

小結

待續。。。

參考資料

Eric Evans的《Domain-Driven Design領域驅動設計》

聯繫作者

zhihu.com segmentfault.com oschina.net


我自己的切身經驗。寫業務代碼-修改業務框架-修改技術框架-做業務架構-涉及硬體中間件-全面的架構師-開始尋找頸椎病治療專家…


業務代碼與技術代碼

無論業務代碼、技術代碼,本身都是技術活。題主沒說清楚他的業務代碼和技術代碼具體指什麼,是什麼行業領域的,定義有點模糊,只好根據一般理解籠統地回答。

通常所說的「業務代碼」負責實現用戶的業務功能,主要與用戶、系統的功能需求有關,對應於軟體架構業務邏輯(Business Logic)或領域(Domain)邏輯層的代碼,類似於 MVC 模式里的
Model。而「技術代碼」大概是指那些與業務功能無直接關係而常與系統的非功能需求有關的架構級代碼,例如各種開發平台、框架、中間件中處理網路通信、數據存儲、多線程管理、語言處理等等技術架構、基礎設施方面的低層或底層代碼。

可是,上層或應用層的業務代碼就一定沒技術含量嗎?不一定。產品組、業務組的代碼叫業務代碼,平台組、架構組的代碼叫技術代碼,所以業務代碼就一定沒技術、沒難度、沒意思?這認識上恐怕有點誤區,業務代碼難不難、值不值得你投入取決於你的行業、業務領域本身的問題複雜度,其實許多行業領域的業務代碼也是很有難度、值得鑽研的。

我理解題主大概更想說的是:老是重複乾沒啥意思的工作,編寫自己早已掌握了的、低難度、低技術含量的代碼(這些不一定都是業務代碼)是浪費時間,耽誤了個人成長的機會。這點我基本同意。

如果你的業務代碼大多是簡單的 CRUD,確實可以考慮挪位子了。

拜師學藝

要想成為技術大牛,絕非一件易事,所以完全不必也不應焦躁。

具體辦法很多,說一個在公司里最簡單直接、快速有效的辦法(上策、捷徑):

拜你團隊里的技術大牛(比如架構師,或其它技術明顯超過你的同事、Coach、Mentor 等等)為師,讓他們平時能經常指導你(開小灶),給你分配一些有技術難度的任務做,有計劃、有步驟地提高。(能否做到,看你的人緣了)

在實際工作中有高人指點、帶領必然可以少走許多彎路,這比你平時利用業餘時間通過自學、啃書、報培訓班等等來提升,要高效得多。問題是,碼農大多加班還不來及、忙不過來,能有多少業餘時間呢?所以,最好是就從日常工作中獲得提高,而不是另找其他時間。

轉崗?

天天寫業務代碼的程序員,怎麼成為技術大牛,開始寫技術代碼?

這裡有一個前提條件:首先,你要從天天寫沒什麼技術含量或早已熟悉、厭倦的所謂「業務代碼」中解脫出來,開始有機會寫一些難度更大的「技術代碼」,不然你還是沒有足夠的時間和精力來學習提高,會成長得很慢。

如果架構師、團隊領導安排你寫「技術代碼」、做一些更有難度的工作,自然你就可以名正言順地通過日常工作來提升自己的技術了,這是一舉兩得。

敏捷開發如何處理這類問題?

在一個真正的敏捷開發團隊里,還有一些很好的機制和辦法來幫助全體碼農提高技術:

1、開發任務自選制

在 Scrum 團隊里,所有的開發任務由誰來做幾乎都是員工自選的,沒有經理的分配和指派,如何實現合理的任務劃分靠大家的協作與商量。

任何一個產品的代碼都會有高難度、低難度的部分。如果一些低難度的代碼任務你寫久、寫厭了,可以提出來,跟別人作個交換,嘗試做一些難度更大的任務。

2、輪轉工作制(Rotation)

工作輪轉也是軟體工程界一個由來已久的最佳實踐。

極限編程(XP)通過結對編程把輪轉做到了極致。

。。。


阿里云云棲社區,彙集阿里技術精粹,點此關注

著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

小編特地從阿里技術協會(ATA)分享一篇內部文章:

不管是開發、測試、運維,每個技術人員心理多多少少都有一個成為技術大牛的夢,畢竟「夢想總是要有的,萬一實現了呢」!正是對技術夢的追求,促使我們不斷地努力和提升自己。

然而「夢想是美好的,現實卻是殘酷的」,很多同學在實際工作後就會發現,夢想是成為大牛,但做的事情看起來跟大牛都不沾邊,例如,程序員說「天天寫業務代碼還加班,如何才能成為技術大牛」,測試說「每天都有執行不完的測試用例」,運維說「扛機器接網線敲shell命令,這不是我想要的運維人生」。。。。。。知乎上類似的問題「天天寫業務代碼的程序員,怎麼成為技術大牛,開始寫技術代碼?」關注人數有6K+,答案有120+,當時我也回答了並且點贊數最多,後來做職業等級晉陞面評和溝通的時候,又有了新的發現和想法,於是有了系統的整理一篇文章的想法,希望讓更多同學在技術大牛的路上能夠少走一些彎路。

由於我是程序員,所以以下的一些例子都是基於程序開發的,但大道理是相通的,測試、運維都可以借鑒。

幾個典型的誤區

拜大牛為師

知乎上有人認為想成為技術大牛最簡單直接、快速有效的方式是「拜團隊技術大牛為師」,讓他們平時給你開小灶,給你分配一些有難度的任務。

我個人是反對這種方法的,主要的原因有幾個:

  • 大牛很忙,不太可能單獨給你開小灶,更不可能每天都給你開1個小時的小灶;而且一個團隊裡面,如果大牛平時經常給你開小灶,難免會引起其他團隊成員的疑惑,我個人認為如果團隊里的大牛如果真正有心的話,多給團隊培訓是最好的。然而做過培訓的都知道,準備一場培訓是很耗費時間的,課件和材料至少2個小時(還不能是碎片時間),講解1個小時,大牛們一個月做一次培訓已經是很高頻了。
  • 因為第一個原因,所以一般要找大牛,都是帶著問題去請教或者探討。因為回答或者探討問題無需太多的時間,更多的是靠經驗和積累,這種情況下大牛們都是很樂意的,畢竟影響力是大牛的一個重要指標嘛。然而也要特別注意:如果經常問那些書本或者google能夠很容易查到的知識,大牛們也會很不耐煩的,畢竟時間寶貴。經常有網友問我諸如「jvm的-Xmn參數如何配置」這類問題,我都是直接回答「請直接去google」,因為這樣的問題實在是太多了,如果自己不去系統學習,每個都要問是非常浪費自己和別人的時間的。
  • 大牛不多,不太可能每個團隊都有技術大牛,只能說團隊裡面會有比你水平高的人,即使他每天給你開小灶,最終你也只能提升到他的水平;而如果是跨團隊的技術大牛,由於工作安排和分配的原因,直接請教和輔導的機會是比較少的,單憑參加幾次大牛的培訓,是不太可能就成為技術大牛的。

綜合上述的幾個原因,我認為對於大部分人來說,要想成為技術大牛,首先還是要明白「主要靠自己」這個道理,不要期望有個像武功師傅一樣的大牛手把手一步一步的教你。適當的時候可以通過請教大牛或者和大牛探討來提升自己,但大部分時間還是自己系統性、有針對性的提升。

業務代碼一樣很牛逼

知乎上有的回答認為寫業務代碼一樣可以很牛逼,理由是業務代碼一樣可以有各種技巧,例如可以使用封裝和抽象使得業務代碼更具可擴展性,可以通過和產品多交流以便更好的理解和實現業務,日誌記錄好了問題定位效率可以提升10倍。。。。。。等等。

業務代碼一樣有技術含量,這點是肯定的,業務代碼中的技術是每個程序員的基礎,但只是掌握了這些技巧,並不能成為技術大牛,就像遊戲中升級打怪一樣,開始打小怪,經驗值很高,越到後面經驗值越少,打小怪已經不能提升經驗值了,這個時候就需要打一些更高級的怪,刷一些有挑戰的副本了,沒看到哪個遊戲只要一直打小怪就能升到頂級的。成為技術大牛的路也是類似的,你要不斷的提升自己的水平,然後面臨更大的挑戰,通過應對這些挑戰從而使自己水平更上一級,然後如此往複,最終達到技術大牛甚至業界大牛的境界,寫業務代碼只是這個打怪升級路上的一個挑戰而已,而且我認為是比較初級的一個挑戰。

所以我認為:業務代碼都寫不好的程序員肯定無法成為技術大牛,但只把業務代碼寫好的程序員也還不能成為技術大牛。

上班太忙沒時間自己學習

很多人認為自己沒有成為技術大牛並不是自己不聰明,也不是自己不努力,而是中國的這個環境下,技術人員加班都太多了,導致自己沒有額外的時間進行學習。

這個理由有一定的客觀性,畢竟和歐美相比,我們的加班確實要多一些,但這個因素只是一個需要克服的問題,並不是不可逾越的鴻溝,畢竟我們身邊還是有那麼多的大牛也是在中國這個環境成長起來的。

我認為有幾個誤區導致了這種看法的形成:

1)上班做的都是重複工作,要想提升必須自己額外去學習

形成這個誤區的主要原因還是在於認為「寫業務代碼是沒有技術含量的」,而我現在上班就是寫業務代碼,所以我在工作中不能提升。

2)學習需要大段的連續時間
很多人以為要學習就要像學校上課一樣,給你一整天時間來上課才算學習,而我們平時加班又比較多,周末累的只想睡懶覺,或者只想去看看電影打打遊戲來放鬆,所以就沒有時間學習了。

正確的做法

Do more

做的更多,做的比你主管安排給你的任務更多

我在HW的時候,負責一個版本的開發,這個版本的工作量大約是2000行左右,但是我除了做完這個功能,還將關聯的功能全部掌握清楚了,代碼(大約10000行)也全部看了一遍,做完這個版本後,我對這個版本相關的整套業務全部很熟悉了。經過一兩次會議後,大家發現我對這塊掌握最熟了,接下來就有趣了:產品討論需求找我、測試有問題也找我、老大對外支撐也找我;後來,不是我負責的功能他們也找我,即使我當時不知道,我也會看代碼或者找文檔幫他們回答。。。。。。最後我就成了我這個系統的「專家」了。雖然這個時候我還是做業務的,還是寫業務代碼,但是我已經對整個業務都很熟悉了。

以上只是一個簡單的例子,其實就是想說:要想有機會,首先你得從人群中冒出來,要想冒出來,你就必須做到與眾不同,要做到與眾不同,你就要做得更多

怎麼做得更多呢?可以從以下幾個方面著手:
1)熟悉更多業務,不管是不是你負責的;熟悉更多代碼,不管是不是你寫的

這樣做有很多好處,舉幾個簡單的例子:

  • 需求分析的時候更加準確,能夠在需求階段就識別風險、影響、難點
  • 問題處理的時候更加快速,因為相關的業務和代碼都熟悉,能夠快速的判斷問題可能的原因並進行排查處理
  • 方案設計的時候考慮更加周全,由於有對全局業務的理解,能夠設計出更好的方案

2)熟悉端到端

比如說你負責web後台開發,但實際上用戶發起一個http請求,要經過很多中間步驟才到你的伺服器(例如瀏覽器緩存、DNS、nginx等),伺服器一般又會經過很多處理才到你寫的那部分代碼(路由、許可權等)這整個流程中的很多系統或者步驟,絕大部分人是不可能去參與寫代碼的,但掌握了這些知識對你的綜合水平有很大作用,例如方案設計、線上故障處理這些更加有含金量的技術工作都需要綜合技術水平。

「系統性」、「全局性」、「綜合性」這些字眼看起來比較虛,但其實都是技術大牛的必備的素質,要達到這樣的境界,必須去熟悉更多系統、業務、代碼。

3)自學

一般在比較成熟的團隊,由於框架或者組件已經進行了大量的封裝,寫業務代碼所用到的技術確實也比較少,但我們要明白「唯一不變的只有變化」,框架有可能要改進,組件可能要替換,或者你換了一家公司,新公司既沒有組件也沒有框架,要你從頭開始來做。這些都是機會,也是挑戰,而機會和挑戰只會分配給有準備的人,所以這種情況下我們更加需要自學更多東西,因為真正等到要用的時候再來學已經沒有時間了。

以java為例,大部分業務代碼就是if-else加個資料庫操作,但我們完全可以自己學些更多java的知識,例如垃圾回收,調優,網路編程等,這些可能暫時沒用,但真要用的時候,不是google一下就可以了,這個時候誰已經掌握了相關知識和技能,機會就是誰的。

以垃圾回收為例,我自己平時就抽時間學習了這些知識,學了1年都沒用上,但後來用上了幾次,每次都解決了卡死的大問題,而有的同學,寫了幾年的java代碼,對於stop-the-world是什麼概念都不知道,更不用說去優化了。

Do better

要知道這個世界上沒有完美的東西,你負責的系統和業務,總有不合理和可以改進的地方,這些「不合理」和「可改進」的地方,都是更高級別的怪物,打完後能夠增加更多的經驗值。識別出這些地方,並且給出解決方案,然後向主管提出,一次不行兩次,多提幾次,只要有一次落地了,這就是你的機會。

例如:

重複代碼太多,是否可以引入設計模式?

系統性能一般,可否進行優化?

目前是單機,如果做成雙機是否更好?

版本開發質量不高,是否引入高效的單元測試和集成測試方案?

目前的系統太龐大,是否可以通過重構和解耦改為3個系統?

阿里中間件有一些系統感覺我們也可以用,是否可以引入 ?

。。。。。。。。。。。。。。。。。。。

只要你去想,其實總能發現可以改進的地方的;如果你覺得系統哪裡都沒有改進的地方,那就說明你的水平還不夠,可以多學習相關技術,多看看業界其它公司怎麼做,BAT都怎麼做。

我2013年調配到九游,剛開始接手了一個簡單的後台系統,每天就是配合前台做數據增刪改查,看起來完全沒意思,是吧?如果只做這些確實沒意思,但我們接手後做了很多事情:

  • 解耦,將一個後台拆分為2個後台,提升可擴展性和穩定性;
  • 雙機,將單機改為雙機系統,提高可靠性;
  • 優化,將原來一個耗時5小時的介面優化為耗時5分鐘

還有其它很多優化,後來我們這個組承擔了更多的系統,後來這個小組5個人,負責了6個系統。

Do exercise

在做職業等級溝通的時候,發現有很多同學確實也在嘗試Do more、Do better,但在執行的過程中,幾乎每個人都遇到同一個問題:光看不用效果很差,怎麼辦?

例如:

  • 學習了jvm的垃圾回收,但是線上比較少出現FGC導致的卡頓問題,就算出現了,恢復業務也是第一位的,不太可能線上出現問題然後讓每個同學都去練一下手,那怎麼去實踐這些jvm的知識和技能呢?
  • Netty我也看了,也了解了Reactor的原理,但是我不可能參與Netty開發,怎麼去讓自己真正掌握Reactor非同步模式呢?
  • 看了《高性能MySQL》,但是線上的資料庫都是DBA管理的,測試環境的資料庫感覺又是隨便配置的,我怎麼去驗證這些技術呢?
  • 框架封裝了DAL層,資料庫的訪問我們都不需要操心,我們怎麼去了解分庫分表實現?

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

諸如此類問題還有很多,我這裡分享一下個人的經驗,其實就是3個詞:learning、trying、teaching!

1)Learning

這個是第一階段,看書、google、看視頻、看別人的博客都可以,但要注意一點是「系統化」,特別是一些基礎性的東西,例如JVM原理、Java編程、網路編程,HTTP協議。。。。。。等等,這些基礎技術不能只通過google或者博客學習,我的做法一般是先完整的看完一本書全面的了解,然後再通過google、視頻、博客去有針對性的查找一些有疑問的地方,或者一些技巧。

2)Trying

這個步驟就是解答前面提到的很多同學的疑惑的關鍵點,形象來說就是「自己動手豐衣足食」,也就是自己去嘗試搭建一些模擬環境,自己寫一些測試程序。例如:

  • Jvm垃圾回收:可以自己寫一個簡單的測試程序,分配內存不釋放,然後調整各種jvm啟動參數,再運行的過程中使用jstack、jstat等命令查看jvm的堆內存分布和垃圾回收情況。這樣的程序寫起來很簡單,簡單一點的就幾行,複雜一點的也就幾十行。
  • Reactor原理:自己真正去嘗試寫一個Reactor模式的Demo,不要以為這個很難,最簡單的Reactor模式代碼量(包括注釋)不超過200行(可以參考Doug Lee的PPT)。自己寫完後,再去看看netty怎麼做,一對比理解就更加深刻了。
  • MySQL:既然有線上的配置可以參考,那可以直接讓DBA將線上配置發給我們(注意去掉敏感信息),直接學習;然後自己搭建一個MySQL環境,用線上的配置啟動;要知道很多同學用了很多年MySQL,但是連個簡單的MySQL環境都搭不起來。
  • 框架封裝了DAL層:可以自己用JDBC嘗試去寫一個分庫分表的簡單實現,然後與框架的實現進行對比,看看差異在哪裡。
  • 用瀏覽器的工具查看HTTP緩存實現,看看不同種類的網站,不同類型的資源,具體是如何控制緩存的;也可以自己用Python寫一個簡單的HTTP伺服器,模擬返回各種HTTP Headers來觀察瀏覽器的反應。

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

還有很多方法,這裡就不一一列舉,簡單來說,就是要將學到的東西真正試試,才能理解更加深刻,印第安人有一句諺語:I hear and I forget. I see and I remember. I do and I understand ,而且「試試」其實可以比較簡單,很多時候我們都可以自己動手做。

當然,如果能夠在實際工作中使用,效果會更好,畢竟實際的線上環境和業務複雜度不是我們寫個模擬程序就能夠模擬的,但這樣的機會可遇不可求,大部分情況我們還真的只能靠自己模擬,然後等到真正業務要用的時候,能夠信手拈來。

3)Teaching

一般來說,經過Learning和Trying,能掌握70%左右,但要真正掌握,我覺得一定要做到能夠跟別人講清楚。因為在講的時候,我們既需要將一個知識點系統化,也需要考慮各種細節,這會促使我們進一步思考和學習。同時,講出來後看或者聽的人可以有不同的理解,或者有新的補充,這相當於繼續完善了整個知識技能體系。

這樣的例子很多,包括我自己寫博客的時候經常遇到,本來我覺得自己已經掌握很全面了,但一寫就發現很多點沒考慮到;組內培訓的時候也經常看到,有的同學寫了PPT,但是講的時候,大家一問,或者一討論,就會發現很多點還沒有講清楚,或者有的點其實是理解錯了。寫PPT、講PPT、討論PPT,這個流程全部走一遍,基本上對一個知識點掌握就比較全面了。

後記

成為技術大牛夢想雖然很美好,但是要付出很多,不管是Do more還是Do better還是Do exercise,都需要花費時間和精力,這個過程中可能很苦逼,也可能很枯燥,這裡我想特彆強調一下:前面我講的都是一些方法論的東西,但真正起決定作用的,其實還是我們對技術的熱情和興趣!

作者:阿里云云棲社區 - 知乎

著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

親們阿里云云棲社區已開通專欄,歡迎關注閱讀:我是程序員 - 知乎專欄


你說的有道理!
你說的有道理!
你說的有道理!

But,也只是有道理。

其實吧,我剛開始也是這種想法。天天碼業務,有個幾把技術含量啊,勞資要寫那些牛逼的動畫,勞資要實現那些牛逼的框架,勞資要用自己封裝的網路請求,勞資要用自己封裝的緩存機制,勞資要。。。
就這麼滴,時間又特么過了半年,眼瞅著快特么過年了,盆友圈裡的弟弟妹妹們開始發年終總結了,各種書單,各種期望。。。我特么此刻迷茫了,在心裡指著自己鼻子說,你看看你特么天天都幹啥了??
然後我特么想通了。
來分析下樓主情況,其實樓主這麼想是期待進步的,這一點是好事,但是有可能更多是停留在『想』這個層面,你之所以覺得天天都在碼業務代碼,那是因為你效率太低了,天天碼業務已經夠你折騰了,根本騰不出時間學更多技術含量高,逼格也不低的東西。
而且即使你已經意識到了自己效率低,但是你已經懶惰到即使知道了也不去主動改變的地步。這樣的你,你告訴我,誰來拯救你?
如果這種情況一直持續下去,你可能會越來越不屑去深入了解業務,以至於造成惡性循環,加班完成業務,空餘時間變得更少,更沒動力去自主學習新技術、新語言,甚至連複雜的業務都能把你拖垮了。這種情況持續下去我想你應該想得到結果。
So,不要抱怨整天折騰業務(即使有些業務本身很傻逼),像大禹治水一樣,堵是行不通的,疏才是王道。怎麼疏?深入了解你所不恥的業務需求,高效率完成任務,騰出更多時間做自己的事,學習新技術、新語言,就算用來玩遊戲你特么也賺了啊。這樣才能建立正向循環,你那破公司待不爽了,那當然跳啊,不特么留下一隻腳印,也特么不帶走一片雲彩,天生驕傲,就是這麼吊!

另外,並不是每個公司的業務都傻逼的無可救藥的,大多數領域,深入了解業務也是你攻擊力加成的一部分,面試的時候經驗也會在這一塊體現出來。
有點說教,但是話糙理不糙。
飯來了,不說了。


你的想法是不對的。

你的問題背後的觀點是,你想用公司的代碼來練手,把沒學過的知識或者是剛學到的知識投入公司的項目,由此不斷得到技術提升。
這叫自私,或者叫幼稚

事實上,程序員做事應該是私下利用自己大或小的實驗項目,把你需要用到的新知識學明白了,然後純熟地應用到公司的項目中。
這叫負責


其實題主遇到的問題很普遍,早些年咱們叫它CRUD程序員,整天做資料庫的增刪改查。

一方面是想法有問題,除非做基礎研究,一般技術都是要服務於業務,公司花錢雇你是要解決各種問題的。

另一方面,業務代碼本身並不是空中樓閣,它是搭建在各種基礎框架代碼之上的,從你們的業務框架到操作系統層面,可研究的東西很多。而且我不太相信你們的業務框架足夠完美到純粹只做CRUD之類就能工作了,因此可發揮的空間肯定還有很多,只需要不斷詢問自己:代碼真的沒有可改進空間了嗎?如果覺得是,那就再多學習新知識,多學習高手代碼,再回來看,沒準又會覺得代碼稀爛了。


技術的東西都要靠自己下班時間多寫多練多琢磨的,上班就是要踏實幹公司的活,努力讓老闆給你機會承擔更大更複雜的項目,甚至帶項目帶人,這種綜合性的成長絲毫不亞於你寫了很多純技術代碼,如果你能做到上班踏實幹活,下班多花時間擴展技術視野提升水平,大兄弟,你就是不可多得的人才。

這個道理看似簡單也很容易踐行,但是真正能做到的寥寥無幾,說到底,踏實兩個字,說的容易做的難。上班就抱怨天天寫ifelse沒水平,下班又不好好看書,不能怪別人,只能怪自己不夠毅力不夠踏實。


一個完整應用中,由軟體團隊完成的部分可能僅占 5%。如果你有興趣的話,可以去看應用引用的庫(日誌,json,字元串,http,並發,容器等)、框架(spring,ibatis等),組件(模板引擎velocity,緩存,消息中間件),容器(tomcat,jetty),規範(servlet,restful),前端(瀏覽器,jquery,..),運行環境(Linux,docker,DNS,代理伺服器)等等的實現,夠你學的了。你要能掌握一個應用所用到的這些,將來組裡的系統架構師就你了。


技術代碼都是偷摸乾的。不然碼農天天加班幹嘛,真以為寫業務代碼呢,還不是天天折騰技術。說是加班寫代碼,其實是在加班玩」遊戲「。 什麼MVC, MVVM, Design Pattern, 面向對象,函數式編程, 還不都是打著省時間提高效率的名義編出來的。 明明寫個頁面就好的東西,非要分層啊分模型啊。


上面的話都是在黑,小朋友不要認真往心裡去。


這個行業不是典型的「你行你上」嗎?
所以當持續學習,而不必抱怨。


推薦閱讀:

單身程序狗解決了一個技術難題後沒有妹子可以炫耀或誇一下自己怎麼辦?
你見過的最想笑的,最奇葩的,最逗逼的代碼是什麼?
Microsoft BASIC 源代碼公布,如何評價比爾·蓋茨寫代碼水平?
晚上腦子裡也想著bug,睡不好怎麼辦?
作為程序員,你有哪些正在做的個人項目?

TAG:程序員 | 編程 | 敏捷開發 | 軟體架構師 |