後端面試問題
來源: arialdomartini/Back-End-Developer-Interview-Questions 中文: monklof/Back-End-Developer-Interview-Questions 參考: https://xargin.com/backend-engineer-interview/#general
1. 通用問題:
1.1. 語言設計中空引用 (null reference )的存在有什麼問題?假設你想要將空引用的概念從你的首選語言中移除,可能導致什麼結果?
從go或者java來講,只要有空引用,那麼就會有解引用的時候的nullpointer Exception 或者 panic 的問題。但是貿然移除不是一個好選擇,會導致在其它語言通信的時候的語義缺失問題。例如在 web 服務的參數綁定時,沒法通過 null 來判斷是否是因為調用方沒有傳遞導致的問題。特別是 go,這個問題很麻煩。
1.2. 為什麼函數式編程重要?什麼時候適用函數式語言?
1.3. 設計(design)、架構(architecture)、功能(functionality)和美學(aesthetic)之間有什麼區別?討論一下。
1.4. 微軟、谷歌、歐朋(opera)和火狐這類公司是如何從他們的瀏覽器中獲利的?
通過市場佔有,成為搜索引擎的導流通道; 捆綁一些自家的服務,也是導流入口;
1.5. 為什麼打開TCP套接字有很大的開銷?
需要握手三次
1.6. 封裝的重要性體現在哪兒?
屏蔽掉evil的細節部分,讓使用者聚焦在業務開發上。這樣也就避免了一些通用功能的反復開發。
1.7. ? 什麼是實時系統(real-time system)?它與普通系統有什麼區別?
1.8. 實時語言(real-time language)和堆內存分配(heap memory allocation)之間的關係是什麼?
堆內存(heap)使用一般比較慢,而且可能不連續,帶有gc的語言其gc主要也是工作在堆區(heap)。堆內存分配可能和實時是相互矛盾的吧。
- stack 編譯時已知較快,
- heap編譯時未知(malloc new), gc對heap此回收
1.9. 不變性(Immutability)是指: (變量的)值只能在創建的時候被設置一次,之後就不能被改變。為什麼不變性對寫更加安全的代碼有幫助?
- 因為不變, 所以沒有並發時的讀寫衝突問題
- 不變性指創建的時候只能被設置一次, 之後不能被改變
- immutablility具有併發安全, 每次併發進程可能對相同的數據進行操作, 但不能對其進行修改.
Mutable(變性) Go objects: (可以被make 都是)
- arrays and slices
- maps
- channels
- closures which are capturing at least 1 variable from the outer scope
- 不具有併發安全
Immutable(不變性) Go objects:
- interfaces
- booleans, numeric values (including values of type int)
- strings
- pointers
- function pointers, and closures which can be reduced to function pointers
- structs having a single field
- 具有併發安全
1.10. 可變值(mutable values)和不可變值(immutable values)有哪些優缺點?
1.11. ? 什麼是O/R阻抗失衡(Object-Relational impedence mismatch)?
1.12. 如果你需要使用緩存,你使用哪些原則來確定緩存的大小?
- 命中率: query cache的命中率在98%以上,而另外的能在85%就算是很高了
- 緩存更新策略: FIFO, LFU, LRU
- 缓存最大數據量: 指在緩存中能處理元素的最大個數或者說所能使用的最大空間。通常來講,各種緩存機制都會對緩存的最大數據量進行限制,可以是固定大小的存儲空間、集合個數,或者是由操作系統所能處理和分配的存儲空間決定
- 影響緩存的三個因素(命中率、緩存更新策略、緩存最大數據量)
1.13. TCP和HTTP有什麼區別?
- tcp工作在傳輸層,http工作在應用層
- http是建立在tcp可靠連接之上的應用層協議
1.14. 在客戶端渲染(client-side rendering)和服務端渲染(server-side rendering)之間,你是如何權衡的?
客戶端如果性能好,可以做一些複雜的計算和渲染, 服務端渲染可以提高客戶端的展示速度,但要權衡資源。 有一些圖形計算任務都堆到服務器上的話,可能使後端資源吃緊。
1.15. 如何在一個不可靠的協議之上構建一個可靠的通信協議?
- 參考谷歌的quic協議
- 其實也可以自己建立ack機制,
- 滑動窗口之類的
2. 開放式問題:
2.1. 為什麼人們會抵制變化?
變化需要學習。學習會帶給很多人恐懼。
2.2. 如何向你的祖母解釋什麼是線程? Process,thread, goroutine
Process
- An instance of a running program is called a process.
- Process provides environment for program to execute.
- OS allocates memory.
- Code - machine instructions
- Data - Global data
- Heap - Dynamic memory allocation
- Stack - Local variables of function
Thread
- 每個os 執行緒都有固定大小stack memory(2 mb),
- Thread 轉移到另一個 thread需要 context switch
Goroutine
- stack 的大小限制可以1GB
- 協程更加輕量,一個程序可以隨意啟動成千上萬個goroutine
- goroutine 被 Goruntime 所調度,這一點和線程不一樣。也就是說,Go 語言的並發是由Go 自己所調度的,自己決定同時執行多少個goroutine,什麼時候執行哪幾個。這些對於我們開發者來說完全透明,只需要在編碼的時候告訴Go 語言要啟動幾個goroutine,至於如何調度執行,我們不用關心。
- 不需要context switch, 成本較低
Parallelism(並行)
- 同時做很多事情
- 讓不同的代碼片段同時在不同的物理處理器上執行
Concurrency 並發
- 同時管理很多事情, 這些事情可能只做一半就被暫停去做別的事情
- concurrency(併發)效果比parallelism(並行)好
2.3. 作為一個軟體工程師,你想要既要有創新力,又要產出具有可預測性。採用什麼策略才能使這兩個目標可以共存呢?
1.思考方面:多學習先人的既有做法,思考為什麼要這麼做。再思考這麼做會不會有什麼問題,如果想不明白,去社群提問。
2.形成自己的程式庫,縮短自己的單位"任務"的工作時間。並經常對自己完成一項特定任務的時間進行統計。這樣在排期上給出的時間會較合理。
2.4. 什麼是好的代碼?
- 首先要易讀,命名規範。遵循任意一種代碼風格約束,使用靜態代碼检查工具幫助自己強制實現這些規範。
- 其次函数不能太長,要能一屏幕看到底。
2.5. ? 解釋什麼是流(Streaming)和如何實現一個流?
2.6. ? 假設你的公司給你一周的時間,用來改善你和同事的生活: 你將如何使用這一周?
2.7. ? 本週你學了什麼?
2.8. ? 所有的設計中都會有美學元素(aesthetic element)的存在。問題是,你認為美學元素是你的朋友還是敵人?
2.9. ? 列出最近你讀過的5本書
2.10. 假設目前有個大型公司(非常有錢),他們的開發流程是瀑布式流程(Waterfall),如果需要你在他們公司引入持續交付(Continue Devivery),你會怎麼做?
尋找合適的CI/CD 工具, 教導如何使用, 慢慢導入
2.11. 我們來談談"重複造輪子","非我發明症", "吃自己做出來的狗糧"的這些做法吧。
(注: 重複造輪子: Reinventing the wheel; 非我發明症:Not Invented Here Syndrome; 吃自己做出來的狗糧: Eating Your Own Dog Food)
- 如果現存的輪子有缺陷,且作者不願意改,那重複發明沒什麼問題。
- 非我發明症的人追求的大概是項目的最大程度的可控,但容易走向極端。
- 自己造的輪子自己一定要測, 要使用。
2.12. 在你當前的工作流中,什麼事情是你計劃下一步需要自動化的?
重複的非腦力勞動,都準備自動化。
2.13. 為什麼寫軟體是困難的?是什麼使軟體的維護變得困難?
是不斷的疊代、變更和系統初始設計的矛盾,使代碼變得越來越醜陋,難讀。
2.14. 你喜歡在全新項目(Green Field Project)上工作, 還是在已有項目(Brown Field Project)基礎上工作?為什麼?
各有好處
2.15. 當你在瀏覽器地址欄輸入google.com回車之後都發生了什麼?
TODO: 有空補個圖 https://github.com/alex/what-happens-when
- dns(udp)
- tcp bind/connect
- http GET/POST keep-alive
2.16. 從輸入網址到渲染畫面, 整個流程
- 把url拿去問 DNS server 取得 ip
- 有了ip後 就可建立 TCP 連線,
- 期間有幾次握手, 確保封包抵達
- server 收到可能會先通過 firewall, nginx
- 在跑到程式碼, 經過 route, middleware cache 等. 然後在到業務邏輯
- 傳回client
2.17. ? 當操作系統CPU處於空閒的時候,它可能在處理哪些事情?
2.18. 如何向一個5歲的孩子解釋什麼是Unicode/數據庫事務?
事務:能一串搞定的事情,不能停!要麼就全給我撤了!
2.19. ? 如何維護單體架構(monolithic architecture)?
- 找一個好的靜態代碼分析工具,例如 C/C++,用understand。
- 現代編程語言,可以用vscode和GoLand之類的ide。
- 同時可以通過腦圖,把複雜的業務邏輯繪製出來,方便理解。
2.20. 一個"專業的開發者"意味著什麼?
效率高,代碼好,TDD
2.21. 軟體開發是藝術、是技藝還是工程?你的觀點是什麼?
工程,有矩可循。但也有他的藝術在。
2.22. "喜歡這個的人也喜歡...",如何在一個電子商務商店裡實現這種功能?
找出所有該商品出現過的訂單, 統計其它商品 sku_id 和 同時出現的訂單數,記錄百分比。 (實時推薦?)
2.23. 為什麼在創新上,企業會比創業公司慢些?
- 因為層層向上匯報,導致決策速度會慢。
- 技術系統有歷史包袱,疊代緩慢。
- 技術人員的惰性。
2.24. 為什麼說,對於涉及密碼學的問題,你不應該嘗試應用自己的發明或者設計?
因為沒有什麼意義, 術業有專攻
3. 設計模式相關問題
3.1. ? 請用一個例子表明,全局對像是邪惡的存在
3.2. ? 假設你工作的系統不支持事務性,你會如何從頭開始實現它?
3.3. ? 什麼是好萊塢原則(Hollywood Principles)?
3.4. ? 關於迪米特法則(最少知識原則): 寫一段代碼違反它, 然後修復它。 (注: 迪米特法則:the Law of Demeter, 最少知識原則: the Principle of Least Knowledge)
3.5. ? Active-Record模式有什麼限制和缺陷?
3.6. ? Data-Mapper模式和Active-Record模式有什麼區別?
3.7. ? 空對像模式(Null Object Pattern)的目的是什麼?
Null Object Pattern 會有至少一個 real object,一個 null object,以及他們共通的繼承對象 abstract object,Client 會透過 abstract object 的介面來使用 real object 或是 null object。這種使用方式就是大家所熟悉的多型 (Polymorphism)
Null Object 使用時機
- Null Object Pattern 通常會跟其他 Pattern 一起出現,像上面的例子就是跟 Strategy Pattern 的結合,使用 Strategy Pattern 的目標之一就是要減少散落在各地的條件判斷,當遇到什麼事都不需要做的情況時,Null Object Pattern 就會派上用場了!
- Null Object Pattern 也通常會是 Flyweight Pattern,因為行為基本上會是一樣的,所以可以重用同一個實例,來達到減少記憶體的使用量。
- 雖然他的名字是 Null Object,但其實不一定碰到 Null reference 才能使用 Null Object, 像上面的例子就是這樣故意安排的,不做任何事情或是擁有預設行為也是一個使用 Null Object 的好時機!
source from : [Design Pattern] Null Object 空物件模式
3.8. 為什麼組合(Composition)比繼承(Inheritance)更好?
組合靈活,可以在子類裡無痛隨意添加特性。 (詳情參見head first 設計模式)
3.9. ? 什麼是反腐敗層(Anti-corruption Layer)?
3.10. 你可以寫一個線程安全的單例(Singleton)類嗎?
type singleton struct{}
var instance *singleton
// 方法一
var mu sync.Nutex
func GetInstance() singleton {
if instance == nil {
mu.Lock()
defer mu.Unlock()
if instance == nil {
instance = &singleton{}
}
}
return instance
}
方法二
var once sync.Once
func GetInstanceOnce() *singleton{
once.Do(func(){
instance = &singleton{}
})
return instance
}
3.11. ? 數據抽象(Data Abstraction)能力是指能改變實現而不影響客戶端的這種能力。請構造一個一個例子,違反這個特性,並且嘗試修復它
3.12. 你是如何處理依賴關係地獄(Dependency Hell)的?
合理分層,集成/重寫一部分依賴到項目中
3.13. 為什麼說goto語句是惡魔般的存在?
讓程式的邏輯不是單向流動,難以理解和 debug
3.14. ? 健壯性是進行軟體設計時的一個通用原則,它建議 "發送時要保守,接收時要開放"。這也經常被寫成,"做一個有耐心的讀者,做一個謹慎的作者"。你能解釋一些這背後的邏輯嗎? 譯者註: "發送時要保守,接收時要開發"的原文是: "Be conservative in what you send, be liberal in what you accept",有點類似於“嚴於律己,寬於待人”的意味。
4. 代碼設計相關問題:
4.1. 你在進行軟體設計時會考慮軟體測試嗎?軟體測試是如何影響軟體設計的?
參考 the art of readable code, 為了測試更方便,會迫使代碼的邏輯片段較短, 入參出參依賴較少,從而一定程度上提高代碼的可讀性。
4.2. ? 內聚和耦合的區別是什麼?
4.3. 重構在哪些場景下有用?
代碼維護成本節節高漲的時候,小重構應該隨時進行。
4.4. 代碼中的註釋有用嗎?
必須有 參見 the art of readable code
4.5. ? 設計和架構有什麼區別?
4.6. ? 為什麼在測試驅動開發(TDD)中是先寫測試,再寫代碼?
4.7. ? C++支持多繼承,Java 允許類實現多個接口。這些特性對正交性有什麼影響?使用多繼承和使用多接口有區別嗎? [這個問題來自Andrew .Hunt 和 David Thomas寫的《程序員修煉之道》]
4.1. ? 在存儲過程 or 預存程序(Stored Procedures)中寫業務邏輯有什麼優缺點?
優點
預存程序具有下列的好處:
- 預存程序可封裝,並隱藏複雜的商業邏輯。
- 預存程序可以回傳值,並可以接受參數。
- 預存程序無法使用 SELECT 指令執行,因為它是子程式,與檢視表、資料表或使用者定義函式不同。
- 預存程序可以用在資料檢驗,強制實行商業邏輯等。
- 代碼變得簡潔
缺點
- 預存程序,往往客製化於特定的資料庫上,因為支援的程式語言不同。當切換到其他廠商的資料庫系統時,需要重寫原有的預存程序。
- 預存程序的效能調校與撰寫,受限於各種資料庫系統。
- 難以擴容, 遷移
5. 語言相關問題:
5.1. 告訴我你的首選語言的三個最壞的缺陷
- 沒有簡易的三元表達式
- 錯誤沒有錯誤碼,沒有錯誤堆棧
- 包管理一坨屎
5.2. ? 為什麼現在函數式編程這麼越來越受關注?
5.3. ? 閉包是什麼?它有什麼用途?閉包和類有什麼共同點?
5.4. 泛型有什麼用途?
- 泛型代碼可以讓你寫出根據自我需求定義、適用於任何類型的,靈活且可重用的函數和類型。
- 它的可以讓你避免重複的代碼,用一種清晰和抽像的方式來表達代碼的意圖。
- 你可能在C++中,聽到的術語是Template, 而Swift,Java和C#則採用Generic(泛型),但概念是相近的。
5.5. ? 什麼是高階函數?有什麼用途?用你的首選語言寫個例子出來。
5.6. ? 討論一下,如何寫一個循環,然後把它轉換成遞歸函數,要避免易變性。
5.7. ? 有些語言將函數視為第一公民,這是什麼意思?
5.8. 用一個例子說明匿名函數是有用的。
因為非匿名函數在定義時,就已經創建函數對象和作用域對象;所以,即使未調用,也佔用內存空間;
匿名函數,僅在調用時,才臨時創建函數對象和作用域鏈對象;調用完,立即釋放,所以匿名函數比非匿名函數更節省內存空間
5.9. ? 什麼是動態方法調度(Dynamic Method Dispatch)?
5.10. ? 名字空間(Namespace)有什麼用?有什麼可以替代它的嗎?
5.11. ? 談談Java和C#之間的互操作性(Interoperability) (任選其他兩門語言都行)
5.12. ? 為什麼很多軟件工程師不喜歡Java?
5.13. ? 你認為好的語言好在哪裡?差的語言差在哪裡?
5.14. ? 寫兩個函數,一個是"引用透明的(Referentially Transparent)",另一個是"引用不透明的(Referentially Opaque)"。討論之。
5.15. 什麼是棧(stack)?什麼是堆(heap)?
- stack: 編譯時已知, 較快, 連續
- heap: 編譯時不知道大小, 可能不連續
5.16. ? 為什麼一個語言中,"函數是第一公民"是很重要的?
5.17. ? 模式匹配(Pattern Matching)和Switch語句(Switch clauses)的區別在哪兒?
5.18. 為什麼有些語言設計上沒有異常機制?這有什麼優缺點?
要麼因為老(C),要麼因為不喜歡(GO) 難以做 checked exception, 會導致寫出較醜的代碼,或圈複雜度較高的代碼=> if err ?= nil
5.19. ? 如果Cat是一個Animal, 那麼TaskCare是一個TakeCare嗎?
6. Web相關問題:
6.1. ? 為什麼"第一方cookie(first-party cookie)"和"第三方cookie(third-party cookie)"被如此不同的對待?
7. 數據庫相關問題:
7.1. 如果要你將一個項目從MySQL遷移至PostgreSQL中,你會如何遷移?
可参考ptsc之類的工具的做法
7.2. 為什麼SELECT * FROM table WHERE field = null不能匹配空的字段?
is null or <=> null
7.3. 什麼是ACID(原子性,一致性,隔離性,持久性)原則?
7.4. 你是如何進行數據庫模式(Database schema)遷移的?
現代項目一般都有專門的schema在線修改工具,比如ptsc/ghost
7.5. 延遲加載(lazy loading)是如何實現的?什麼場景下有用?他有什麼缺陷?
當不是所有資料都需要把關聯資料取出來時, 考量到不必要的效能花費,可以使用 load() 方法, 真正需要時才去 DB 拿關聯資料。
使用 ORM (Object Relational Mapper),你能夠獲得可靠的效能增漲。 一個ORM可以做的所有事情,也能被手動的編寫出來。但是,這需要一個高階專家。 ORM 的最重要的是“Lazy Loading”,也就是說,只有在需要的去取值的時候才會去真正的去做。 但你也需要小心這種機制的副作用,因為這很有可能會因為要去建立很多很多小的查詢反而會降低效能。 ORM 還可以把你的SQL語句打包成一個事務,這會比單獨執行他們快得多得多。
- 當網路有問題,可選擇積極式載入(急切加載, Eager Loading),可減少網路往返
- 但如果載入太多實體,回傳的封包就會越大,這時可切換到消極式載入
- 不確定現階段會使用哪些關聯,選擇消極式載入,可避免一次載入太多實體,程式碼也會比較容易閱讀;若確定則使用積極式載入,但別一次載入太多資料
Mysql資料庫調優和效能優化 https://blog.johnsonlu.org/eloquent-eager-loading/
Eager Loading (急切加載):
- 用於你的關聯很少時或是主機與Sql間的Ping 值很高。
- 減少數據訪問的延遲,在一次數據庫的訪問中返回所有的數據。 減少與數據庫的交互次數
- 當數據量很多,或者實體級聯關係複雜時要特別注意性能了
Lazy Loading (延遲加載):
- 你的資源關聯非常多,且你不需要即時獲得關聯的資源。
- 可能會因為數據訪問的延遲而降低性能,考慮到每訪問父實體的子實體時,就需要訪問數據庫。
7.6. ? 什麼是N+1問題?
7.7. 如何找出應用中開銷最大的查詢?
- php -> xhprof
- go -> pprof https://blog.wu-boy.com/2020/06/golang-benchmark-pprof
8. 非關聯型數據庫相關問題:
8.1. 什麼是最終一致性 Eventual Consistency?
在分散式系統中, 資料的讀取, 最終都能取得已更新的資料,但不完全保證能立即取得已更新的資料 AP-可用性與部分容錯性
8.2. 關於CAP理論,舉一些CP、AP、CA系統的例子。
CAP 理論 : https://medium.com/@thegiive/cap-%E7%90%86%E8%AB%96-19cd81c82658

在一個分散式系統中,只能滿足以下三個項目的任兩項:
- 一致性(Consistency) - 每次讀取都可以得到最新的資料,但偶爾會拿到錯誤
- 可用性(Availability) - 每次讀取都可以得到非錯誤的回應,但不能保證可以得到最新的資料, 不能超時不能吐 error.
- 部分容錯性(Partition tolerance)) - 在任意分區的網路故障情況下,系統仍然能夠持續運行
P 表示分區容錯性(Partition tolerance)
MySQL如何達到P:
- 主從複製(Master-Slave Replication):通過設置主從關係,將主數據庫的變更同步到一個或多個從數據庫上。當主數據庫發生故障或分區時,從數據庫可以繼續提供讀取服務,保持系統的可用性。
- 主從多從(Master-Multi-Slave Replication):類似於主從複製,但主數據庫可以同步到多個從數據庫上,提供更高的可用性和容錯性。
- 故障切換(Failover):在主從複製中,當主數據庫發生故障時,可以自動將從數據庫切換為新的主數據庫,以保持系統的連續運作。
- 分區(Sharding):將數據庫分為多個區域或分片,每個分片可以在獨立的伺服器上運行。這樣可以將數據分散到不同的節點上,提高系統的擴展性和容錯性。
- 高可用性集群(High Availability Cluster):通過設置數據庫集群,將數據庫部署在多個節點上,並使用心跳機制和自動故障檢測來實現高可用性和分區容錯性。
網路是不可靠的,你的設計必須要確保部分容錯性,所以你只能夠在一致性與可用性中做出取捨。
CP - 一致性與部分容錯性 (保證原子讀寫時)
還是一個分區關掉服務,只提供一個分區的數據來確保一致性 等待分區的節點回覆可能會導致超時錯誤,如果你的系統的需求是需要 保證原子讀寫時, CP 是一個不錯的選擇。 
以下是一些使用CP的例子:
- 關係型數據庫(如MySQL、PostgreSQL):這些數據庫通常遵循ACID(原子性、一致性、隔離性和持久性)原則,強調數據的一致性。在面臨網絡分區時,這些數據庫會阻塞或拒絕寫操作,以確保數據的一致性。
- 一致性分佈式存儲系統(如Apache Cassandra):這些系統使用分佈式一致性協議(如Paxos或Raft)來實現數據的一致性。當面臨網絡分區時,系統可能選擇阻塞或拒絕寫操作,以確保數據的一致性。
- 一致性的消息佇列(如Apache Kafka):消息佇列通常提供強一致性保證,確保消息的順序和傳遞的一致性。當面臨網絡分區時,消息佇列可能選擇暫停或拒絕消息的傳遞,以確保一致性。
- zookeeper, etcd
需要注意的是,CP的選擇意味著在面臨網絡分區時,系統會犧牲可用性。這意味著在分區期間,系統可能無法提供完全的讀寫服務,並且可能會阻塞或拒絕一些操作
AP - 可用性與部分容錯性, 最終一致性
兩個分區都可以服務,但是回傳合理但是不正確的數據 每個進行回覆的節點中的最新版本可能不是最新的,當分區節點解析完畢後,寫入的操作可能需要一些時間來傳播資料。 當你的系統需求需要保證 最終一致性,或當外部系統故障時,系統要能夠繼續運作時,AP 是一個不錯的選擇
在寫入後的讀取操作最終可以看到被寫入的資料(通常在數毫秒內)。資料透過非同步的方式被複製。DNS 或是電子郵件系統使用的就是這種方式,最終一致性在高可用的系統中效果很好。
以下是一些使用AP的例子:
- NoSQL數據庫(如MongoDB、Couchbase):這些數據庫通常提供高可用性和分佈式架構,允許數據在不同節點之間進行異步同步。在面臨網絡分區時,這些數據庫會繼續接受讀寫請求,但可能出現數據的部分不一致性。
- 分佈式文件系統(如Hadoop HDFS、GlusterFS):這些系統允許數據在多個節點之間進行複製和分佈存儲,以實現高可用性和容錯性。在面臨網絡分區時,系統會繼續提供讀寫服務,但可能出現數據的部分不一致性。
- 分佈式網絡服務(如微服務架構):這些系統將業務邏輯拆分為多個微服務,每個服務運行在不同節點上,並使用異步通信機制進行交互。在面臨網絡分區時,每個服務繼續運行並提供服務,但可能出現部分服務不可用或無法進行準確的一致性保證。
- Redis屬於CAP中的AP(可用性和分區容錯性)。在Redis的設計中,它強調了高可用性和分佈式架構,使得數據可以在不同節點之間進行複製和分佈存儲。當面臨網絡分區時,Redis會繼續提供可用的讀寫服務,但可能出現數據的部分不一致性。 Redis通過使用主從複製(Master-Slave Replication)和分片(sharding)等機制來實現高可用性和容錯性。 它允許在分區期間繼續進行讀寫操作,但在不同節點之間的數據同步可能存在一定的延遲,因此可能會出現數據的部分不一致性。 這種設計選擇使得Redis能夠提供高吞吐量和低延遲的性能,同時保持系統的可用性。 需要注意的是,由於Redis追求高可用性和分區容錯性,它對於一致性的要求相對較低。因此,在某些情況下,當系統面臨網絡分區或節點失敗時,Redis可能會出現數據的不一致性
CA
所以如果你的分布式系統做到 CA,犧牲 Partition Tolerance,那就是代表你的機房永遠不會出現網路分區,永遠不會掉包 ,那麼完美的機房給我來一打吧... 除非有可能有完美的網路環境,否則 CA 根本就是傳統定義的單機系統,而非分布式系統。所以不要再被騙「我這個分布式儲存系統只能做到 CA」
來源及延伸閱讀
主從複製 主從切換
主從複製是一種數據複製機制,用於實現數據的冗余和高可用性 主從複製(Master-Slave Replication)是一種常見的數據複製機制,用於實現數據的高可用性和容錯性。 在主從複製中,存在一個主節點(Master)和多個從節點(Slaves)。 主節點負責接收並處理用戶的寫操作,而從節點則負責接收主節點的數據變更並將其應用到自身的數據庫。 當主節點接收到寫操作後,它會將這些操作的數據變更記錄成日誌(log)並持久化。同時,它也會將這些變更推送給所有的從節點。從節點接收到變更後,它會將這些變更應用到自身的數據庫中,以保持與主節點的數據一致性。
主從切換是一種故障轉移機制,用於在主節點故障時維持系統的可用性 主從切換(Master-Slave Failover)或稱為從節點晉升(Slave Promotion)。 主節點負責處理寫操作,而從節點則用於數據的複製和讀取。 當主節點發生故障或不可用時,需要選擇一個從節點來替代主節點的功能,使系統能夠繼續運作。 主從切換是一種自動或手動的過程,它將一個從節點提升為新的主節點,使其接管主節點的責任。在主從切換過程中,需要保證數據的一致性,通常使用選舉機制或其他協調機制來確定哪個從節點成為新的主節點。
8.3 ? NoSQL是如何解決可伸縮性的挑戰的?
8.4 什麼情況下你會使用類似於MongoDB的文檔數據庫而不是關係型數據庫(如Mysql或者PostgreSQL)?
- use Redis as a cache as it has in-memory storage.
- Use MongoDB for content management and document type of storage needs. 讀快
- Use Cassandra for highly available, wide column storage cases. 寫快
9. 代碼版本管理相關問題:
9.1. ? 為什麼在Mercurial或者git中(管理)分支比SVN容易?
9.2. ? 分散式版本控制系統(比如git),相比集中式版本控制系統(如svn)有哪些優勢和劣勢?
(注:集中式版本控制系統: Centralized Version Control Systems; 分散式版本控制系統: Distributed Version Control Systems)
9.3. 能描述一下什麼是GitHubFlow和GitFlow工作流嗎?
GitHub Flow
GitHub Flow 只需要記住主分支 master 其他分支都是從主分支在開出來, 接下來 Deploy 到 Production 則是透過 Tag 方式來解決。由開發團隊主管來下 Tag,
Gitflow

9.4. 什麼是rebase?
使用 Rebase 來合併分支的好處, 就是它不像一般合併可能會產生額外的合併專用的 Commit, 而且歷史順序可以依照誰 Rebase 誰而決定使用
通常在還沒有推(Push)出去但感覺得有點亂(或太瑣碎)的 Commit,我會先使用 Rebase 分支來整理完再推出去
使用 ORIG_HEAD 還原rebase
git reset ORIG_HEAD --hard
在 Git 有另一個特別的紀錄點叫做 ORIG_HEAD,這個 ORIG_HEAD 會記錄「危險操作」之前 HEAD 的位置。
例如分支合併或是 Reset 之類的都算是所謂的「危險操作」
透過這個紀錄點來取消這次 Rebase 相對的更簡單
9.5. ? 為什麼合併操作(merge)在Mercurial和git中比在SVN和CVS中容易?
10. 併發問題:
10.1. 為什麼我們需要並發呢?解釋一下。
parallelism
- 讓不同的代碼片段同時在不同的物理處理器上執行
- 同時做很多事情
concurrency
- 同時管理很多事情, 這些事情可能只做一半就被暫停去做別的事情
- 併發效果比並行好
10.2. ? 為什麼測試多線程/並發代碼這麼困難?
10.3. 什麼是競爭條件(Race Condition)?用任何一個語言寫一個例子。
func main() {
a := 0
times := 3000
var wg sync.WaitGroup wg.Add(times)
for i := 0; i<times;i++{
a++
}
}
11. 分散式系統相關問題:
11.1. 怎麼測試一個分佈式系統?
Logging: ELK, 用於記錄離散的日誌事件, 包含程序執行到某一點或某一個階段的詳細信息
Metrics: Prometheus, 可聚合的數據, 通常是固定類型的時序數據, 包掛 Counter, Gauge(測量), Histogram(直方圖)等
Tracing: Jaeger, 紀錄單個請求的處理流程, 其中八卦服務調用和處理時長等信息
- (a) 單元測試:開發人員完成,覆蓋基本邏輯
- (b) 白盒異常測試:有針對的對各個系統異常進行代碼級模擬,驗證系統是否有能力處理並保持可用
- (c) 接口測試:保證服務的各個對外接口符合預期,基本功能驗證
- (d) 集成測試:高壓力、高並發、多種系統協作的基本功能和異常場景測試(軟件、硬件異常)
- (e) 穩定性測試:高壓力模擬常見應用和故障的混合場景,多種方式並行進行。
- (f) 仿真測試:建立客戶應用回歸環境,仿真客戶使用場景
11.2. ! 什麼場景下你會在兩個系統中採用異步通信機制?
11.3. ! 遠程過程調用(PRC)的通用缺點是什麼?
無法跨語言, 可讀性差
- 傳輸協議
- RPC,可以基於TCP協議,也可以基於HTTP協議
- HTTP,基於HTTP協議
- 傳輸效率
- RPC,使用自定義的TCP協議,可以讓請求報文體積更小,或者使用HTTP2協議,也可以很好的減少報文的體積,提高傳輸效率
- HTTP,如果是基於HTTP1.1的協議,請求中會包含很多無用的內容,如果是基於HTTP2.0,那麼簡單的封裝以下是可以作為一個RPC來使用的,這時標準RPC框架更多的是服務治理
- 效能消耗,主要在於序列化和反序列化的耗時
- RPC,可以基於thrift實現高效的二進位制傳輸
- HTTP,大部分是通過json來實現的,位元組大小和序列化耗時都比thrift要更消耗效能
- 負載均衡
- RPC,基本都自帶了負載均衡策略
- HTTP,需要配置Nginx,HAProxy來實現
- 服務治理(下游服務新增,重啟,下線時如何不影響上游呼叫者)
- RPC,能做到自動通知,不影響上游
- HTTP,需要事先通知,修改Nginx/HAProxy配置
總結:
- RPC主要用於公司內部的服務呼叫,效能消耗低,傳輸效率高,服務治理方便。
- HTTP主要用於對外的異構環境,瀏覽器介面呼叫,APP介面呼叫,第三方介面呼叫等。
11.4. 如果你為了可擴展性和魯棒性(Robustness 強健性)而構建一個分佈式的系統,分別在封閉安全的網絡環境情況下,和地理上的位置不同但是網絡環境不是封閉和安全的情況下,你會考慮什麼不同的事情?
強健性: 電腦軟體在輸入錯誤、磁碟故障、網路過載或有意攻擊情況下,能否不死機
11.5. ? 在Web應用中如何管理容錯性?在桌面端呢?
11.6. ? 在分佈式系統中,如何處理故障?
11.7. ? 讓我們來談談在網絡分區(network partitions)情況下的幾種(一致性)解決方案吧。
11.8. ? 你認為分佈式計算中有哪些謬論?
11.9. ? 你在什麼時候會使用Request/Response模式,什麼時候使用Publish/Subscribe模式?
12. 軟體生命週期和團隊管理相關問題:
12.1. ? 什麼是敏捷(Agility)?
12.2. ? 你是如何處理遺留代碼(Legacy Code)的?
12.3. ? 假設我是你們公司的CEO,請向我解釋什麼是看板,並且說服我在它上面投資。
12.4. ? 敏捷(Agility)和瀑布(Waterfall)之間的最大區別是什麼?
12.5. ?作為團隊管理者,你對會議太多這個問題是如何處理的?
12.6. ? 你會如何處理延期很長時間了的項目?
12.7. ? "個體與交互重於過程和工具"和"客戶協作重於合同談判"佔了敏捷宣言(AgileManifesto)的一半,談論一下這兩個觀念。
12.8 ? 如果你是你們公司的CTO,你會採取什麼樣的決策?
12.9 ? 你覺得項目經理有用嗎?
12.10 ? 如果要你組織一個彈性工作制的開發團隊(即沒有強制工作時間的要求),並且假期制度是"按需休假"你會如何做?
12.11 ? 你會如何管理一個人員流動非常高的團隊?如何在不加薪的條件下說服團隊成員不要離開?
12.12 ? 除了代碼之外,你最關注你的同事的哪3項素質?
12.13 ? 關於代碼,你最希望非技術人員能知道的的三件事是什麼?
13. 邏輯和算法相關問題:
13.1. 只用LIFO棧如何構造一個FIFO隊列?只用FIFO隊列如何構造一個LIFO棧?
先將原先的 stack pop出來 並push 到另一個stack , 在將另一個stack pop出來
使用queue但是pop時從頭開始
- 寫一段有棧溢出的代碼。
- 寫一個尾遞歸版本的階乘函數。
- 使用任何一個語言,寫一個REPL,功能是echo你輸入的字符串。然後將它演化成一個逆波蘭表達式的計算器。
- 如果需要你設計一個文件系統磁盤碎片整理程序,你會如何設計?
- 寫一個生成隨機迷宮的程序。
13.2 寫一段有內存洩漏(Memory Leak)的範例
https://www.mdeditor.tw/pl/pCUM/zh-tw
Go 程式可能會在一些情況下造成記憶體洩漏。go101 網站總結了各種記憶體洩漏的情況
- 獲取長字串中的一段導致長字串未釋放
- 同樣,獲取長 slice 中的一段導致長 slice 未釋放
- 在長 slice 新建 slice 導致洩漏
- goroutine 洩漏
- time.Ticker 未關閉導致洩漏
- Finalizer 導致洩漏
- Deferring Function Call 導致洩漏
範例ㄧ
var a []int
func test(b []int) { // 導致b部會被回收
a = b[:1]
return
}
主要就是理解上面的 a = b[:1] 是一個引用,其實新、舊 slice 指向的都是同一片記憶體地址,那麼只要全域性量 a 在, b 就不會被回收。
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
b := a[1:3]
b[0] = 11 // b[0]的改寫,即對a[1]的改寫
fmt.Println(a[1]) // a[1]被寫成了11
fmt.Println(len(a), cap(a)) // 10 10
fmt.Println(len(b), cap(b)) // 2 9
}
如果想避免這個問題 append 方法的實現,如果append的目標slice空間不夠,會重新申請一個 array 來放需要 append 的內容, 所以 &b[0] 和 &a[0] 的值是不一樣的,而 &a[0] 和 &c[0] 地址是一致的:
var b []int
var c []int
// 現在,如果再沒有其它值引用著承載著a元素的記憶體塊,
// 則此記憶體塊可以被回收了。
func test(a []int) {
c = a[:1]
b = append(a[:0:0], a[:1]...)
fmt.Println(&a[0], &c[0], &b[0]) //0xc0000aa030 0xc0000aa030 0xc0000b2038
}
方法一 : 將 a 切片複製為 b 切片的一個子集:
func test(b []int) {
a = make([]int, 1)
copy(a, b[:1])
return
}
這樣,a 切片將擁有與 b[:1] 相同的元素,但是它們將是兩個獨立的切片,不會共享底層數組。當 test 函數返回後,a 切片將繼續存在,而 b 切片將在函數調用結束時被垃圾回收。
方法二: 使用一個新的切片來存儲 b 切片的子集:
func test(b []int) {
a = append([]int(nil), b[:1]...)
return
}
這種方法通過使用 append 函數和一個空切片來創建一個新的切片 a,並將 b[:1] 的元素追加到其中。同樣,當 test 函數返回後,a 切片將繼續存在,而 b 切片將被垃圾回收。
範例二
func FindPhoneNumber(filename string) []byte{
b, _ := ioutil.ReadFile(filename)
return regexp.MustCosmpile("[0-9]+").Find(b)
}
// 返回的 []byte 指性保存整個文件的數組, 由與切片引用了整個原始數組,
// 導致gc不能即時釋放底層數組的空間
// 解法
func FindPhoneNumber(filename string) []byte{
b, _ := ioutil.ReadFile(filename)
b = regexp.MustCosmpile("[0-9]+").Find(b)
return append([]byte{},b...)
}
範例三
var a []*int{...}
a = a[:len(a)-1] // 刪除最後一個元素, 可能導致無法gc
// 解法
var a []*int{...}
a[len(a)-1] = nil // gc 回收最後一個元素內存
a = a[:len(a)-1]
範例四
如果想避免這個問題,文章頂部的連結裡給出了方法, 它之所以能夠重新分配的原因在於 append 方法的實現,如果append的目標slice空間不夠,會重新申請一個 array 來放需要 append 的內容,所以 &b[0] 和 &a[0] 的值是不一樣的,而 &a[0] 和 &c[0] 地址是一致的:
var b []int
var c []int
// 現在,如果再沒有其它值引用著承載著a元素的記憶體塊,
// 則此記憶體塊可以被回收了。
func test(a []int) {
c = a[:1] // 有引用到a
b = append(a[:0:0], a[:1]...) // b就不會引用到a了
fmt.Println(&a[0], &c[0], &b[0]) //0xc0000aa030 0xc0000aa030 0xc0000b2038
}
範例五 C++
// Program with memory leak
#include <bits/stdc++.h>
using namespace std;
// function with memory leak
void func_to_show_mem_leak()
{
int* ptr = new int(5);
// body
// return without deallocating ptr
return;
}
// driver code
int main()
{
// Call the function
// to get the memory leak
func_to_show_mem_leak();
return 0;
}
範例六 C++
#include <iostream>
using namespace std;
int main()
{
int *a = new int(123);
cout << *a << endl;
// We should write "delete a;" here
a = new int(456);
cout << *a << endl;
delete a;
return 0;
}
13.3. 隨機生成一個的數字序列,裡面每個數字都不同。
https://go.dev/play/p/1oq5UKRZLa5
func main() {
var num = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
rand.Seed(time.Now().UnixNano())
var count = 4
for i := 0; i < count; i++ {
randIndex := rand.Intn(len(num))
tmp := num[randIndex]
num[randIndex] = num[i]
num[i] = tmp
}
fmt.Println(num)
fmt.Println(num[:count])
}
隨機產生亂數
https://kimi0230.github.io/MyGoNote/Utils/randomStringUtils/Generate-a-random-string.html 會重複 https://go.dev/play/p/bLA-l_uYEXy
const letterBytes = "0123456789"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
13.4. ? 寫一個簡單的垃圾回收系統。
13.5. ? 使用任何一門語言,寫一個基本的消息代理。
13.6. ? 寫一個基礎的web服務器,然後畫一張線路圖,展示你將來還想要實現的功能。
13.7. 如何對一個10GB的文件進行排序?如果是10TB的數據,你會採用什麼方法?
外部排序
- 思路: 將超大文件分成若干部分,每一部分是可以讀入內存的,例如,將10GB的文件分為40份,則每一份只有256M,將每一份讀入內存,用快排(quick sort),堆排等方式進行排序,在寫到一個文件中。這樣,我們得到了40個已經有序的小文件。 再採用歸併的方式,將40個文件合併為一個大文件,則這個大文件就是我們要的結果
- 難點
- 歸併的方式可以考慮二路或者四路歸併
- 失敗樹
- 失敗樹的性質 失敗樹里的每個節點中所存的值是失敗者的下標
13.8. ? 請實現rnd()函數
14. 軟體架構相關問題:
14.1. 什麼情況下緩存是沒用的,甚至是危險的?
沒有規律 沒有重複性 不遵循80/20法則
14.2. ? 為什麼事件驅動的架構能提高可擴展性(scalability)?
https://www.infoq.cn/article/event-sourcing
事件驅動架構在性能、可擴展性和容錯性方面獲得了五顆星評級, 這是這種架構風格的主要優勢。 高性能是通過異步通信與高度並行處理相結合來實現的。 高可擴展性是通過事件處理器(也稱為競爭消費者)的編程負載均衡實現的
作者:城里的月光_欧阳 链接:https://www.jianshu.com/p/7b47a64c1fc8 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
14.3. ? 什麼樣的代碼是可讀性強的代碼?
14.4. ? 緊急設計(Emergent Design)和演化架構(Evolutionary Architecture)之間的區別是什麼?
14.5. 橫向擴展(scale out) vs 縱向擴展(scale up): 有什麼區別?分別在什麼場景下使用?
- 橫向擴展 scale out => 加機器
- 縱向擴展 scale up => 提升機器配置, memory cpu...
14.6. ! 分佈式系統中如何處理"故障切換(failover)"和"用戶會話(user session)"?
14.5. ? 什麼是CQRS(Command Query Responsibility Segregation)?他和最早的Command-Query Separation原則有什麼區別?
- 命令與查詢責任拆分簡稱CQRS 模式 其中負責為CQRS實現方案提供永久信息源的寫入存儲通常基於事件驅動模式實現。 此鏈接介紹瞭如何利用獨立接口實現數據更新, 從而將應用程序內的數據讀取同更新數據操作區分開來。
14.6. 什麼是三層架構?
三層架構是一種常見的B/S軟體架構設計。 分成
- 表現層(Presentation Layer)
- 業務邏輯層(Business Layer)
- 資料訪問層(Data Access Layer)
一個軟體或網頁的開發大概幾個大步驟,
- 首先讓使用者看到畫面,並進行操作。表現層
- 接下來拿到使用者輸入的資料,進行業務邏輯的處理。業務邏輯層
- 與資料庫互動,做增刪改查。資料訪問層 4. 最後讓使用者看到相對應的成功畫面

14.7. ? 如何設計一個可擴展性高的系統?
14.8. 處理C10k問題的策略有哪些?
epoll/kqueue
14.9. ? 如果讓你來設計一個去中心化的P2P系統,你會如何設計?
14.10. ? 為什麼CGI的擴展性不好?
https://goog-book.blogspot.com/2018/11/cgiaspphpjsp-aspnet.html
14.11. 在設計系統時,你如何防止供應商依賴 Vendor Lock-in?
取各vendor的最大集合 (公約數),抽象接口,隨時替換。
14.12. 在可擴展性上,發布/訂閱(Publish-Subscribe)模式有什麼缺點?
擴展時對雙方都不透明,需要重新做 load balance
14.13. ? 80年代以後,CPU有哪些變化?這些變化,對編程產生了什麼影響?
14.14. ? 性能生命週期(performace lifecycle)中,你認為哪個部分是需要考慮進去的?如何管理?
14.15. ? 除了惡意攻擊造成的拒絕服務現像以外,哪些設計或者架構上的問題會導致拒絕服務?
time wait ?
14.16. ! 性能和可擴展性之間有什麼關係?
14.17. 什麼時候緊耦合是OK的?
不會被任何第三方使用的工具類、函數嵌入在自己的系統中
14.18. ? 一個系統要有什麼特徵才能適配雲計算環境 ( Cloud Ready )?
14.19. ? Does unity of design imply an aristocracy of architects?
15. 面向服務架構( SOA )和微服務 ( Microservice )相關問題:
15.1. ? 在SOA中,為什麼長期存活的事務(Long-lived transation)不被看好,而Saga卻被看好?
傳統三層式架構、SOA 在實現交易機制時,多半是依賴「資料庫系統內建」的交易功能(Commit/Rollback)。主要是對資料的變更行為進行管控,確保在不同欄位的數字,能在不被打斷的前提下,同時進行資料修改。若其中有任何異常,則將資料恢復成變更行為被觸發之前的狀態。
SAGA 模式本質上是一種補償式(Compensation)的交易機制,其又分做 Choreography 和 Orchestration 兩種作法,一個是去中心化的作法,另一種是中心化的設計。無論是哪一種作法,都是基於事件驅動的模式下進行。
面對分散式交易的需求,大體上有兩種主要思路:
- 補償式交易
- 多階段提交機制
在最原始的設計中,SAGA 就是類似一種「射後不理」的機制,也就是丟出事件要求做了再說,其中若是有一個環節失敗,就再拋出事件告訴大家 Rollback(回滾)吧。這是很理想的作法,適合分散式架構下的實現,但在這樣原始的設計設計上,如果碰到業務邏輯複雜的系統,其實很有可能會造成「無法回滾」的情況。
但能否解決這個問題呢?其實可以,只要在設計上,加上兩階段提交(2PC)的機制,就能解決。只是這樣做以後,事件會越來越複雜,數量也會越來越多。 而且這樣做以後,會將整件事提升到另一種等級的困難度。
15.2. ! SOA和MicroService之間有什麼區別?
後者是前者的演化,會影響到一部分公司的組織架構,技術本質上沒區別
15.3. 我們來談談Web服務的版本管理、版本兼容性、重大變更管理這些事情吧.
參考semver,其實就是api版本控制和管理
15.4. 在saga中事務和補償操作(compensation operation)之間的區別是什麼?在SOA中呢?
就是邏輯回滾操作
15.5. 微服務不能做得太"微",你認為什麼時候微服務太"微"了?
一個函數一個微服務
15.6. MicroService架構的優劣是什麼?
- 優:
- 維護容易
- 劣:
- 調試困難
- 邏輯跟踪困難
- 配套基礎設施開發成本高
16. 安全相關問題:
16.1 什麼是雙因素認證(Two Factor Authentication)?在一個已有的Web應用中,你如何實現這種機制?
MFA
17. 比爾蓋茨式問題:
17.1. ? 如果你把一面鏡子放在掃描儀上,會發生什麼?
17.2. ? 假設有一個和你完全一樣的克隆人,而他是你的上司,你願意和他工作嗎?
17.3. ? 現在請你面試一下我。
17.4. ? 為什麼Quora上的回答會比Yahoo Answer上的回答好?
17.5. ? 對手是現代語言,你的任務是要為Cobol辯護,你會如何進行?
17.6. ? 10年後的你是什麼樣子?
17.7. ? 假設你是我老闆,我被解雇了。你會如何通知我?
17.8. ? 我想要重構一個系統,而你想要從頭重寫。我們來爭論一下該怎麼弄吧。然後我們反轉角色,再爭論一下。
17.9. ? 老闆要你對公司撒謊,你的反應是什麼?
17.10. ? 如果你可以穿越到以前,你會給年輕時候的你什麼建議?
18. 代碼示例問題:
18.1 這段Javascript函數的輸出是什麼?
function hookupevents ( ) {
for ( var i = 0 ; i < 3 ; i ++ ) {
document . getElementById ( "button" + i )
. addEventListener ( "click" , function ( ) {
alert ( i ) ;
} ) ;
}
}
閉包問題,在js裡會輸出三個3 其它語言也有類似的問題,但是其它語言裡的解決方法和這裡可能不一樣。 。比如go
18.2 關於類型擦除(Type Erasure),這段Java代碼的輸出是什麼?為什麼?
ArrayList< Integer > li = new ArrayList< Integer > ();
ArrayList< Float > lf = new ArrayList< Float > ();
if (li.getClass() == lf.getClass()) // evaluates to true
System.out.println( "Equal" );
java的類型擦除會導致直接輸出equal,調研一下 http://blog.csdn.net/lonelyroamer/article/details/7868820
18.3 你能指出哪兒有內存洩漏嗎?
public class Stack {
private Object [] elements;
private int size = 0 ;
private static final int DEFAULT_INITIAL_CAPACITY = 16 ;
public Stack () {
elements = new Object [ DEFAULT_INITIAL_CAPACITY ];
}
public void push ( Object e ) {
ensureCapacity();
elements[size ++ ] = e;
}
public Object pop () {
if (size == 0 )
throw new EmptyStackException ();
return elements[ -- size];
}
/**
* Ensure space for at least one more element, roughly
* doubling the capacity each time the array needs to grow.
*/ private void ensureCapacity () {
if (elements . length == size)
elements = Arrays . copyOf(elements, 2 * size + 1 );
}
}
pop 那邊沒釋放 elements
18.4. ! if語句,或者更加通用點,條件表達式通常是過程式編程/命令式編程的形式。你能去掉這段代碼中的switch語句,用面向對象的方式來修改這段代碼嗎?
public class Formatter {
private Service service;
public Formatter ( Service service ) {
this . service = service;
}
public String doTheJob ( String theInput ) {
String response = service . askForPermission();
switch (response) {
case " FAIL " :
return " error " ;
case " OK " :
return String . format( " %s%s " , theInput , theInput);
default :
return null ;
}
}
}
18.5. ? 你能去掉這裡的if語句,將它改成更加面向對象嗎?
public class TheService {
private final FileHandler fileHandler;
private final FooRepository fooRepository;
public TheService ( FileHandler fileHandler , FooRepository fooRepository ) {
this . fileHandler = fileHandler;
this . fooRepository = fooRepository;
}
public String Execute ( final String file ) {
final String rewrittenUrl = fileHandler . getXmlFileFromFileName(file);
final String executionId = fileHandler . getExecutionIdFromFileName(file);
if ((executionId == " " ) || (rewrittenUrl == " " )) {
return " " ;
}
Foo knownFoo = fooRepository . getFooByXmlFileName(rewrittenUrl);
if (knownFoo == null ) {
return " " ;
}
return knownFoo . DoThat (file);
}
}
18.6. 如何重構這段代碼?
function ( )
{
HRESULT error = S_OK ;
if ( SUCCEEDED ( Operation1 ( ) ) )
{
if ( SUCCEEDED ( Operation2 ( ) ) )
{
if ( SUCCEEDED ( Operation3 ( ) ) )
{
if ( SUCCEEDED ( Operation4 ( ) ) )
{
}
else
{
error = OPERATION4FAILED ;
}
}
else
{
error = OPERATION3FAILED ;
}
}
else
{
error = OPERATION2FAILED ;
}
}
else
{
error = OPERATION1FAILED ;
}
return error ;
}
function()
{
HRESULT error = S_OK;
if(!SUCCEEDED(Operation1()))
{
return OPERATION1FAILED;
}
if(!SUCCEEDED(Operation2()))
{
return OPERATION2FAILED;
}
if(!SUCCEEDED(Operation3()))
{
return OPERATION3FAILED;
}
if(!SUCCEEDED(Operation4()))
{
return OPERATION4FAILED;
}
return error;
}