Hi,
I am trying to come up with a good cyclomatic complexity number for typical Go codes. Go is a unique language where its explicit error handling results in a higher cyclomatic number and yet its simple syntax and orthogonal design allows Go to still be readable even at a higher cyclomatic number. I am interested in knowing whether McCabe's number of 10 is still a good general rule for typical Go projects.
In my own codes, I find McCabe's number of 10 is a bit limiting for complex functions. While I can refactor the functions to get to 10 or less, it generally results in a less readable code. There is usually too much fragmentation with important parts of the codes being described elsewhere in various sub-functions. Although Go codes are still readable at 15, I think 12 is a good ideal number for typical Go functions. I am trying to test whether 12 is indeed the ideal cyclomatic number for Go or whether McCabe's 10 is still the ideal.
This is incorrect unless only the last statement in a try block can throw an exception.
The reality is that adding a try-catch block adds an unknown (hidden) cost to the cyclomatic complexity unless all the throw sites can be enumerated to arbitrary depth (due to unchecked exceptions).
Explicit error handling is more upfront (honest) and less combinatorial.
Theoretically speaking, you shouldn't discount the error handling because it represents an alternate execution path. However, the difference between other languages and Go is that in other languages you can typically put one big try/catch over a block of code and it adds only +1 to the number, while in Go each function that returns an error must be checked and that adds many more +1s to the number. You can extract out a block of codes and put it into a separate function to mimic the try/catch block. However, in my own experimentation, it isn't quite an elegant solution and the codes generally become harder to read. I am thinking that the only viable alternative is to determine a new guideline number for Go since Go isn't quite like any other languages.The question is what is a good number that is applicable to typical Go projects? The whole objective of cyclomatic complexity is to ensure readable and maintainable codes.
However, my problem is that McCabe's upper limit of 10 doesn't seem to be applicable to Go codes.
We took a number of complex functions from our codebase, and refactor them until everyone agrees that they are readable and cannot be further reduced without hurting readability. We managed to get some functions to below 10, and some remain in the 10 -12 range. Hence, we established the upper limit of 12.
Btw, have you tried to refactor the ones with 25 and see how far you can go?
I don't know enough about the theory of cyclomatic complexity, is the idea for it to be applied rigidly? We have treated it like a useful "double check that function" marker and not much more.
Btw, have you tried to refactor the ones with 25 and see how far you can go?I was able to get them both under 10 (9 and 5). One of them will actually get mainlined, it is a fairly straightforward improvement I just hadn't revisited in some time. The other one is far more debatable, I don't like it -- co-worker thinks it is an improvement. After passing it around and discussing it -- with our little group, there is a very high correlation between "function size" < "lines visible on screen" and happiness... Those using setups that allow less lines visible on screen (via font size, editor tooling, screen resolution, whatever) favor the smaller functions (which are a side-effect of lower cyclo count), those of us who can display a lot more lines of code easily tend to be more comfortable / prefer the bigger but singular functions.
Thanks for the feedback. Usually switch statement can be substituted with lookup tables. However, it depends on the situation. Sometimes lookup tables leads to more clutters. Earlier I wondered how optimized the switch implementation in Go was, so I wrote a little test earlier comparing a switch statement against lookup table. It appears that using lookup table is slightly faster.
(The code is http://play.golang.org/p/In2GVeNsC0 but you cannot run it in Go playground. You may need to copy it and compile it in your machine.)Btw, do you have problems in getting to 10 or less, or is 12 a good number in your case?
--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/HNNUjE5VWos/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
if THIS then
do some logic in order to return THAT
if THAT then
do some logic in order to return THIS
if SOMETHINGELSE then
do some logic in order to return THAT and THIS
else
do some logic in order to return THIS and THATTHIS, ERR := fnGetThis()
if ERR then
return error
THAT, ERR := fnGetThat()
if ERR then
return error
SOMETHINGELSE, ERR := fnGetSomethingElse()
if ERR then
return error
WHATEVER, ERR := fnGetWhatever()
if ERR then
return error
do some logic in order to produce the expected result using THIS, THAT, SOMETHINGELSE and WHATEVERRefactoring these to fit any cyclomatic limit also makes code worse and
not worth it. But increasing global limit because of these exceptional
cases doesn't makes any sense - instead, just add pragma to such func:
func validateSomething() { // nolint:gocyclo
if { // gocyclo:skip