Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Go

Why Do I Have Questions to GO

4.11/5 (2 votes)
9 Feb 2023CPOL3 min read 5.8K  
Lot of fancy languages were born and have died since I started working in IT, but a few of them continue their pitiful existance.
Usually, I use C++ for all my needs, but sometimes my manager tells me that C++ is an obsolete language with a long and expensive development cycle. And it also does not fit the fancy [insert your favorite technology list] architecture...

Introduction

I don't have any bad feeling or prejudice to new technologies until I get such a controversial experience as I did with Go.

Background

I had to add some new features into a Go project which is a bit outdated to the current development status. It was a rather good development, but not well documented. It also had references to a bit outdated code.

Using the Code

Do not use code from this article somewhere else. It is an example of how you can do nasty things without any compiler's warning. Please don't try it at home, and at work!

Format String Issues

Let's begin with a old and famous printf format string:

C++
log.Printf("Unable to store %v. Error %v", err)

Why the row above will be compiled without a warning? We're in the 21st century and either C/C++ will warn you about argument count mistake! Another example below will also compile without a warning:

C++
log.Printf("Error %v", err, var)

People say that format string is not validated not only in Go, but this could take a time for debugging when you're a beginner.

Language Structure

Interfaces

There's a great solution to avoid multiple inheritance which is widely used in Java and .NET - interfaces. Let's skip the discussion about a proper object oriented language design and proceed with Go implementation. I don't want to express all my feelings about the way in which Go inheritance is implemented. But let's take a look at the Go interfaces. You see a rather good definition like:

C++
type (
   Summary interface {
      ...
      Put(key, value string) error
      ...
   }
   ...
)

You've got some types that implement the interface above. And somewhere faraway from the implementation, you've got a code below:

C++
s := Summaries.Get("Summary of my type")
...
s.Put("XXX","YYY")

There is no integrated development environment (IDE) which could help you determine which types can be cloaked behind the interface. You must only know it!

Another way to use interfaces which is disgusting for me is creating functions which receive or return parameters with interface type like shown below:

C++
func DoWell(query string, args ...interface{}) interface{} {
 return nil
}

I think I understand the intentions that make the example above to be compiled, but I don't like them.

References/Values

When you have a map in C++, you can store an instance of some type in it and you've got an access to the instance via reference, like shown below:

C++
...
std::map<int, std::string> idToString;
...
idToString[0] = "NULL";
...
// you can freely change a value under the reference
std::transform(
    idToString[0].begin(), 
    idToString[0].end(), 
    idToString[0].begin(), 
    ::tolower);

We get more or less the same behavior in Java/.NET collections. But Go chooses its own way:

C++
idToString := make(map[int]string)
...
idToString[0] = "NULL"
...
val,ok := idToString[0] // that's right val is a value, not reference
...
idToString[0] = val // the only way to change it in the map 

It's ok when you store a value-based types in the map. But when you have to store any resource handles in the map, you have to store pointers/references there:

C++
type Resource struct {
   res *<package>.DB
   refCount int
}
type ResourceMap map[string]*Resource
var resMap = make(ResourceMap)

func MakeConnection(resName string) {
   resMap[resName] = &Resource{<package>.DB.Open(resName),1}
}

func GetResource(resName string) *Resource {
   var (
      res *Resource = nil
      ok  = false
   )
   if res,ok = resMap[resName]; ok {
      return res
   } else {
      // I know that the code below is nasty and it will cause Panic()
      // but it will compile without a warning
      res.res = <package>.DB.Open(resName)
      res.refCount = 1
      resMap[resName] = res
   }
   return res
}

Testing Issues

The project was almost fully covered with tests. And it is great, until you break almost all of them with a new feature implementation. In one part, I tried to replace map with more comprehensive disk based cache. Instead of map type:

C++
type RecordCacheMap map[string]string

I designed a type with handles to a disk storage:

C++
type RecordCache struct { ... db *<package>.DB ... }

Everything was fine until I tried to fix the tests. Because these tests contain a lot of assertions like shown below:

C++
test.Assert().EqualValues(instance1, instance2)

These rows caused tests to fail with Panic() until all the instances were passivated with releasing local storage handles. Since I'm not a Go fan, I don't have any idea why any handle with value different from NIL creates so many problems.

Points of Interest

I am really impressed with the intentions which make Google create and support Go. I don't have any hate to the language itself, but I do have some questions to the compiler.

History

  • 9th February, 2023: First edition of the text. I will update it when I get more knowledge. And I will definitely write a new tip when I see significant changes in Go.
  • 10th February, 2023: Update with new parts

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)