4 ms·
With the default toolchain, Go has essentially two levels of visibility scoping: package-private (lower case) vs. exported (upper case) items, and module-intern
by janosdebugs 3y ago
With the default toolchain, Go has essentially two levels of visibility scoping: package-private (lower case) vs. exported (upper case) items, and module-internal vs. public packages (packages in the "internal" folder are not accessible from other modules). To make matters worse, all files in the same folder belong to the same package. (There are other toolchains out there that do things differently.)
In larger projects, especially with lots of contributors, I found that you'll want to practice some level of defensive programming because code review can only catch so many errors. In other words, you'll want to make sure that parts of your program only talk to each other via "sanctioned" APIs. Go makes this difficult because anything in the same folder can access data in structures defined in the same folder.
For example, the Kubernets backend for ContainerSSH (https://github.com/ContainerSSH/libcontainerssh/tree/main/internal/kubernetes https://github.com/ContainerSSH/libcontainerssh/tree/main/in...) has two parts, one dealing with the integration with the rest of ContainerSSH, while the other deals with Kubernetes. In order to provide some level of separation, we added an interface in the middle to document the package-internal API somewhat: https://github.com/ContainerSSH/libcontainerssh/blob/main/internal/kubernetes/kubernetesClient.go https://github.com/ContainerSSH/libcontainerssh/blob/main/in... However, this is suboptimal and leads to unnecessary boilerplate code.
You could, of course, forego the interface, but that would mean that code reviewers would have to make sure nobody is taking the easy route and messing with the internal state of a struct that they have no business messing with. In other words, you'd want some level of encapsulation which is not easily doable in Go.
In other languages you have much more granular visibility options, either in an OOP way (private, protected, public, package-private, friend classes, etc), or by providing things like header files that describe an interface between parts of the application.
- maleldil 3y agoThank you for the in-depth response! Just to clarify my understanding, do you mean you want to encapsulate data structures within the same package (i.e. files in the same directory)? If so, I'm not clear why you can't break those up into a different package.
- janosdebugs 3y agoYou absolutely can, but then you a) have a TON of packages, which makes it really hard to structure in a codebase this size and b) you still can't ensure that the lower level package gets called only from the higher level package and not from somewhere else in the codebase. You could use modules, but modules have very strict versioning requirements and make large refactoring difficult. Not unsolvable problems, but difficult in a large codebase. (Go workspaces make it a little better, but not by much.)