- Hands-On Dependency Injection in Go
- Corey Scott
- 468字
- 2021-06-10 19:17:44
Code bloat
Code bloat smells are cases where unwieldy slabs of code have been added to structs or functions so that they have become hard to understand, maintain, and test. Frequently found in older code, they are often the result of a gradual degradation and lack of maintenance rather than intentional choices.
They can be found with a visual scan of the source code or by employing a cyclomatic complexity checker (a software metric that indicates the complexity of a piece of code) such as gocyclo (https://github.com/fzipp/gocyclo).
These smells include the following:
- Long methods: While the code is run on computers, it is written for humans. Any method of more than about 30 lines should be split into smaller chunks. While it makes no difference to the computer, it makes it easier for us humans to understand.
- Long structs: Similar to long methods, the longer a struct, the harder it is to understand and therefore maintain. Long structs typically also indicate the struct is doing too much. Splitting one struct into several smaller ones is also a great way to increase the reusability potential of the code.
- Long parameter lists: Long parameter lists also indicate that the method is likely doing more than it should. When adding new features, it is tempting to add a new parameter to an existing function to account for the new use case. This is a slippery slope. This new parameter is either optional/unnecessary for the existing use cases or is an indication of a significant increase in complexity in the method.
- Long conditional blocks: Switch statements are amazing. The problem is they are very easy to abuse and tend to multiply like proverbial rabbits. Perhaps the most significant problem, however, is their effect on the readability of the code. Long conditional blocks take up a lot of space and interrupt the readability of the function. Consider the following code:
func AppendValue(buffer []byte, in interface{}) []byte{
var value []byte
// convert input to []byte
switch concrete := in.(type) {
case []byte:
value = concrete
case string:
value = []byte(concrete)
case int64:
value = []byte(strconv.FormatInt(concrete, 10))
case bool:
value = []byte(strconv.FormatBool(concrete))
case float64:
value = []byte(strconv.FormatFloat(concrete, 'e', 3, 64))
}
buffer = append(buffer, value...)
return buffer
}
By taking interface{} as input, anywhere we wish to use it, we are almost forced to have a switch like this one. We would be better off changing from interface{} to an interface and then adding the necessary operations to the interface. This approach is better illustrated by the json.Marshaller and driver.Valuer interfaces in the standard library.
Applying DI to these smells will typically reduce the complexity of individual pieces of code by breaking them into smaller, separate pieces, which in turn makes them easier to understand, maintain, and test.
- Learn ECMAScript(Second Edition)
- 編程卓越之道(卷3):軟件工程化
- Web交互界面設計與制作(微課版)
- 算法大爆炸:面試通關步步為營
- Learning Bayesian Models with R
- Java EE 7 Development with NetBeans 8
- 軟件架構:Python語言實現
- Flux Architecture
- Linux操作系統基礎案例教程
- AppInventor實踐教程:Android智能應用開發前傳
- LabVIEW虛擬儀器入門與測控應用100例
- Python 3.7從入門到精通(視頻教學版)
- Getting Started with Python and Raspberry Pi
- Apache Camel Developer's Cookbook
- Python一行流:像專家一樣寫代碼